Reverse proxy (Apache HTTPD)¶
EntryStore is usually not exposed directly but runs behind a reverse proxy that terminates TLS and serves the EntryScape frontend from the same host. This way you avoid having to expose port 8080 and risk not being reachable in restrictive networks. The configuration of SSL is also easier this way.
The following Apache HTTPD configuration pipes all EntryStore traffic under /store/ to EntryStore 6.x listening on port 8080 of the same host, and serves EntryScape for all other paths. It requires the modules proxy, proxy_http, headers, rewrite, ssl, expires and deflate.
<VirtualHost *:80>
ServerAdmin admin@domain.tld
ServerName domain.tld
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
</VirtualHost>
<VirtualHost *:443>
ServerAdmin admin@domain.tld
ServerName domain.tld
DocumentRoot /srv/domain.tld/entryscape/
RedirectMatch 404 /\.git
AliasMatch ^/((?!(store|rowstore|config|libs|nls|style.css)).*)$ /srv/domain.tld/entryscape/index.html
HostnameLookups off
ErrorLog /var/log/apache2/error-domain.tld.log
CustomLog /var/log/apache2/access-domain.tld.log combined
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/domain.tld/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/domain.tld/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/domain.tld/fullchain.pem
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
SSLHonorCipherOrder on
SSLCompression off
SSLSessionTickets off
Header always set Strict-Transport-Security "max-age=31536000"
# Forwarded headers: never pass on what the client sent, see below
RequestHeader unset Forwarded
RequestHeader unset X-Forwarded-For
RequestHeader unset X-Forwarded-Host
RequestHeader unset X-Forwarded-Port
RequestHeader unset X-Forwarded-Prefix
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"
ProxyPass /store/ http://127.0.0.1:8080/store/
ProxyPassReverse /store/ http://127.0.0.1:8080/store/
ExpiresActive On
<Directory "/srv/domain.tld/entryscape*">
Options FollowSymLinks MultiViews
AllowOverride All
Require all granted
Header always set X-Frame-Options SAMEORIGIN
Header always set X-Content-Type-Options nosniff
ExpiresDefault A1800
<FilesMatch "\.(gif|jpg|png|svg)$">
Header set Cache-Control "max-age=10800"
</FilesMatch>
</Directory>
<Location /store/>
SetOutputFilter DEFLATE
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
# mod_deflate appends "-gzip" to the ETag of compressed responses; strip it again
# from If-None-Match so that conditional requests keep working
RequestHeader edit "If-None-Match" '^"(.*)-gzip"$' '"$1"'
</Location>
</VirtualHost>
EntryStore 5.x
EntryStore 5.x deployed in a servlet container is usually reachable under the context name of the web application, e.g. http://domain.tld:8080/entrystore/. Adapt the ProxyPass lines accordingly:
ProxyPass /store/ http://domain.tld:8080/entrystore/
ProxyPassReverse /store/ http://domain.tld:8080/entrystore/
5.x does not send X-Frame-Options and X-Content-Type-Options itself, so for 5.x keep these two Header always set lines on the level of the virtual host instead of inside <Directory>. The forwarded headers described below are specific to 6.x.
Forwarded headers and client IP¶
EntryStore 6.x builds absolute URLs (e.g. redirect targets of SAML and OIDC logins) from the forwarded headers X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port and X-Forwarded-Prefix, and takes the client IP address from Forwarded (for=) or else from the leftmost entry of X-Forwarded-For. The client IP is used for rate limiting, reCAPTCHA verification, the lastUsedIpAddress of sessions and the request log.
EntryStore cannot tell whether these headers were set by the proxy or by the client. The reverse proxy must therefore remove client-supplied Forwarded and X-Forwarded-* headers and set X-Forwarded-For to the address of the connecting client instead of appending to it. In the example above, RequestHeader unset removes the incoming headers and mod_proxy then adds X-Forwarded-For and X-Forwarded-Host itself. With nginx the equivalent is:
proxy_set_header Forwarded "";
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
EntryStore itself should only be reachable through the proxy, e.g. by binding it to the loopback interface with server.address=127.0.0.1 in entrystore.properties or by a firewall rule. Without a reverse proxy, set server.forward-headers-strategy=none.
Caching¶
EntryStore 6.x sends ETag, Last-Modified and Cache-Control headers and answers conditional requests with 304 (see HTTP caching and formats). A proxy configuration that overrides Cache-Control or Expires (e.g. ExpiresDefault for /store/) or removes or rewrites ETag disables this. The expiration settings in the example above therefore only apply to the static EntryScape files.
Security headers¶
EntryStore 6.x sends X-Content-Type-Options: nosniff and X-Frame-Options: DENY itself. The example above sets these headers only for the EntryScape files to avoid sending them twice with conflicting values.
If the proxy sets a Content-Security-Policy and reCAPTCHA is enabled, the policy must allow the reCAPTCHA sources on /store/auth/signup and /store/auth/pwreset: https://www.google.com/recaptcha/ and https://www.gstatic.com/recaptcha/ as script source and https://www.google.com/recaptcha/ as frame source. The HTML pages of EntryStore use no inline styles, so style-src 'self' is sufficient.