Skip to content

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.