Naturally, when using an SSO mechanism other than HTTP Basic Authentication, we need to display a “Sign Out” button to users. But when combined with static site generation (SSG), we do not know the sign out URI yet, since it may depend on the particular configuration of the deployment.

In order to actually render the button, we were faced with two choices:

  1. Run the SSG tool not in CI, but when the Docker container is started.
  2. Render the link without an URI and let the web server inject it at runtime.

In our case, the SSG tool is Jekyll, so adding its entire Ruby toolchain would defeat the purpose of using NGINX: being as lightweight as possible. Additionally, this huge image would create more attack surface.[1] So, we quickly eliminated this option.

But how could the second choice be implemented? Fortunately, Server Side Includes can help here. In a nutshell, the generated pages contain almost all content, plus some small instructions to the web server to additionally insert more content. SSI can be used to fetch content from other servers, conditionally enable or disable areas in a page, or merely to print the contents of a variable somewhere (like the current date and time).

NGINX supports SSI after enabling the configuration option:

location / {
  root /var/www;
  ssi on;
}

In the Jekyll template file, we can now add the following snippet:

<a href="<!--#echo var="ssisignouturl"-->">Sign out</a>

When browsing the page locally with jekyll serve, this will produce invalid HTML markup (although it does not break the page because browsers are lenient), so I recommend only rendering it in production mode (i.e. in CI):

{% if jekyll.environment == "production" %}
  <a href="<!--#echo var="ssisignouturl"-->">Sign out</a>
{% endif %}

Other SSG tools have similar means to distinguish between different environments.

Now the only open issue is that NGINX does not pass through environment variables that are set for the process to the SSI engine. To make matters worse, NGINX decidedly does not even support reading environment variables in the configuration file.

According to the official NGINX Docker image, the way to pass such variables into the running container is by using templates. The image contains a startup script that runs envsubst on the configuration files that are marked as “templates”. When starting the container, the template is processed and the environment variables are copied into the actual NGINX configuration.

In order to leverage this mechanism, move the relevant part of your NGINX configuration into a template file called default.conf.template (or any other name ending in .conf.template). The Dockerfile is adapted accordingly:

FROM nginx:1.19.9

COPY default.conf.template /etc/nginx/templates/

ENV SIGN_OUT_URL="#"

This is necessary so that the startup scripts pick up the file and run it through envsubst.

Now, in the NGINX configuration, you can access the variables:

location / {
  root /var/www;
  ssi on;
  set $ssisignouturl "${SIGN_OUT_URL}";
}

When the container is started without further ado, the script generates the following configuration:

location / {
  root /var/www;
  ssi on;
  set $ssisignouturl "#";
}

This is because our Dockerfile specifies a default value for SIGN_OUT_URL.

It is very important that in the NGINX template, the SSI variable name (ssisignouturl) differs from the environment variable name (SIGN_OUT_URL), because otherwise envsubst would substitute both occurences. The scripts leave undefined variable occurences unchanged.

Finally, after changing the configuration as above, the SSI engine can substitute the URL. Build and start the Docker image as follows:

docker build -t docs .
docker run --rm -it -p 80:80 -e SIGN_OUT_URL="https://sso.bigcorp.com/signout" docs

This will render the following HTML:

<a href="https://sso.bigcorp.com/signout">Sign out</a>

What we found surprising here is that NGINX has no built-in mechanism to pass through environment variables and that we had to instead rely on the startup scripts provided by the Docker image. In situations where the official Docker image cannot be used, I would recommend generating the sign out link as a text file and including the file via include, instead of printing a variable with echo. Further documentation can be found on the NGINX site.

  1. Kubernetes Init Containers could help here, but we wanted to avoid moving work from CI to runtime.  ↩

TAGS