# Securing Nginx

This recipe's sources can be found on github

At the end, you will have a locally running install of httpbin behind nginx with policy enforced by Pomerium.

# Background

Nginx can be configured to authorize requests by calling an external authorization service. Pomerium is compatible with this external authentication protocol and can thus be used to protect services behind nginx. In this configuration, Pomerium does not proxy traffic, but authorizes it on behalf of nginx. This is useful for integrating into existing load balancer infrastructure.

For more information on using Pomerium as an external authorization endpoint, see forward auth in the Pomerium docs.

# How It Works

  • Create a standard pomerium configuration to authenticate against your identity provider (IdP)
  • Configure nginx to authorize incoming requests via pomerium
  • Pomerium authenticates users via IdP
  • Nginx queries Pomerium on each request to verify the traffic is authorized
  • Pomerium verifies the traffic against policy, responding to nginx
  • Nginx proxies the traffic or responds with an error

# Pre-requisites

This recipe is designed to run on a local docker-compose instance. The included configuration can be adopted for any nginx deployment.

  • docker
  • docker-compose
  • A copy of the example repo checked out
  • Valid credentials for your OIDC provider
  • (Optional) mkcert to generate locally trusted certificates

# Certificates (optional)

This demo comes with its own certificates, but they will generate warnings in your browser. You may instead provide your own or use mkcert to generate locally trusted certificates.

After installing mkcert, run the following inside the example repo:

mkcert -install
   mkcert '*.localhost.pomerium.io'

This will install a trusted CA and generate a new wildcard certificate:

  • _wildcard.localhost.pomerium.io.pem
  • _wildcard.localhost.pomerium.io-key.pem

To provide your own certificates through another mechanism, please overwrite these files or update docker-compose.yaml accordingly.

# Configure

# Pomerium

Update config.yaml with your IdP settings and desired policy

# Main configuration flags : https://www.pomerium.io/docs/reference/reference/

pomerium_debug: true
address: :80
cookie_secret: YVFTMIfW8yBJw+a6sYwdW8rHbU+IAAV/SUkCTg9Jtpo=
shared_secret: 80ldlrU2d7w+wVpKNfevk6fmb8otEx6CqOfshj2LwhQ=

idp_provider: "google"
idp_client_id: REPLACEME
idp_client_secret: REPLACEME

insecure_server: true
forward_auth_url: http://fwdauth.localhost.pomerium.io
authenticate_service_url: https://authenticate.localhost.pomerium.io

policy:
  - from: https://httpbin.localhost.pomerium.io
    to: https://httpbin
    allowed_domains:
      - pomerium.com
      - gmail.com

# Nginx - pomerium

Nginx configuration for Pomerium endpoints

# Pomerium endpoint
server {
    listen 443 ssl;
    server_name  authenticate.localhost.pomerium.io fwdauth.localhost.pomerium.io;
    ssl_certificate /etc/nginx/nginx.pem;
    ssl_certificate_key /etc/nginx/nginx-key.pem;

    location / {
      proxy_pass http://pomerium;
      proxy_set_header Host $http_host;
      proxy_http_version 1.1;
    }
}

# Define an upstream so that we don't need resolvers when we use variables in proxy_pass directives
# https://stackoverflow.com/questions/17685674/nginx-proxy-pass-with-remote-addr
upstream pomerium {
    server pomerium;
}

# Nginx - httpbin

Nginx configuration for the protected endpoint

# Protected application
server {
    listen 443 ssl;
    listen 80;
    server_name  httpbin.localhost.pomerium.io;
    ssl_certificate /etc/nginx/nginx.pem;
    ssl_certificate_key /etc/nginx/nginx-key.pem;

    location / {
      proxy_pass http://httpbin;
    }

    ### External Authorization

    # Send auth check to /authorize location.
    auth_request /authorize;

    # Set cookies we get back from the auth check
    auth_request_set $saved_set_cookie $upstream_http_set_cookie;
    add_header Set-Cookie $saved_set_cookie;

    # If we get a 401, respond with a named location
    error_page 401 = @error401;
    # On 401, redirect the user to forward auth to start authentication flow
    location @error401 {
        return 302 https://fwdauth.localhost.pomerium.io/?uri=$scheme://$http_host$request_uri;
    }

    # The auth request must be a subpath of the server
    location /authorize {
      proxy_pass http://pomerium/verify?uri=$scheme://$http_host$request_uri;
      proxy_set_header Host fwdauth.localhost.pomerium.io;
      proxy_http_version 1.1;
    }

    ### End External Authorization
}

# Docker Compose

version: "3"
services:
  nginx:
    image: nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./httpbin.conf:/etc/nginx/conf.d/httpbin.conf
      - ./pomerium.conf:/etc/nginx/conf.d/pomerium.conf
      - ./_wildcard.localhost.pomerium.io.pem:/etc/nginx/nginx.pem
      - ./_wildcard.localhost.pomerium.io-key.pem:/etc/nginx/nginx-key.pem

  httpbin:
    image: kennethreitz/httpbin:latest
    expose:
      - 80
  pomerium:
    image: pomerium/pomerium:latest
    volumes:
      - ./config.yaml:/pomerium/config.yaml:ro
    expose:
      - 80

Run docker-compose up. After a few seconds, browse to httpbin.localhost.pomerium.io.

You should be prompted to log in through your IdP and then granted access to the deployed httpbin instance.

# That's it!

Your httpbin install is protected by Pomerium.

# Adapting

To re-use the configuration in this demo in other contexts:

  • Update httpbin.conf to reflect the correct forward auth URL in location @error401
  • Update pomerium.conf to reflect the pomerium hostname(s) or IP(s) in upstream pomerium
  • Update pomerium.conf to reflect your pomerium authenticate and forward auth hostnames in server_name