Skip to main content

Securing Nginx

danger

Starting v0.21.0, Pomerium will no longer support Forward Auth. Supporting Forward Auth requires Pomerium to route requests from third-party proxies to make access control decisions. This goes against zero-trust principles as specified in the BeyondCorp model, which states that all traffic should flow through a single proxy.

Since this guide was written, Pomerium has developed into a first-class proxy service, and provides an integrated Ingress Controller for Kubernetes.

For previous versions of Pomerium up to v0.20.0, this guide will remain available for those already using Nginx with Pomerium, but we strongly encourage new users to configure Pomerium as the single method of authentication and proxying.

By the end of this guide, you will have a locally running installation of verify 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

config.yaml
# Main configuration flags : https://www.pomerium.com/docs/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://verify.localhost.pomerium.io
to: https://httpbin
allowed_domains:
- pomerium.com
- gmail.com
pass_identity_headers: true

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;
include /etc/nginx/proxy.conf;
}
}

# 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 - verify

Nginx configuration for the protected endpoint

# Protected application
server {
listen 80;
listen 443 ssl http2;

server_name verify.localhost.pomerium.io;
ssl_certificate /etc/nginx/nginx.pem;
ssl_certificate_key /etc/nginx/nginx-key.pem;


location = /ext_authz {
internal;

proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Forwarded-Proto "";

proxy_set_header Host fwdauth.localhost.pomerium.io;
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
proxy_set_header X-Original-Method $request_method;
proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Forwarded-For $remote_addr;

proxy_set_header X-Auth-Request-Redirect $request_uri;

proxy_buffering off;

proxy_buffer_size 4k;
proxy_buffers 4 4k;
proxy_request_buffering on;
proxy_http_version 1.1;

proxy_ssl_server_name on;
proxy_pass_request_headers on;

client_max_body_size 1m;

# Pass the extracted client certificate to the auth provider

set $target http://pomerium/verify?uri=$scheme://$http_host$request_uri;

# uncomment to emulate nginx-ingress behavior
# set $target http://pomerium/verify?uri=$scheme://$http_host$request_uri&rd=$pass_access_scheme://$http_host$escaped_request_uri;
proxy_pass $target;
}

location @authredirect {
internal;
add_header Set-Cookie $auth_cookie;

# uncomment to emulate nginx-ingress behavior
# return 302 https://fwdauth.localhost.pomerium.io/?uri=$scheme://$host$request_uri&rd=$pass_access_scheme://$http_host$escaped_request_uri;

return 302
https://fwdauth.localhost.pomerium.io/?uri=$scheme://$host$request_uri;
}

location / {
proxy_pass http://verify;

include /etc/nginx/proxy.conf;
# If we get a 401, respond with a named location
error_page 401 = @authredirect;
# this location requires authentication
auth_request /ext_authz;
auth_request_set $auth_cookie $upstream_http_set_cookie;
add_header Set-Cookie $auth_cookie;
}
}

Docker Compose

docker-compose.yaml
version: "3"
services:
nginx:
# to emulate nginx-ingress behavior, use openresty which comes with 'escaped_request_uri'
# pre-compiled. Also uncomment lines marked `uncomment to emulate nginx-ingress behavior`
# in the nginx `.conf` configuration files.
# image: openresty/openresty
image: nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./verify.conf:/etc/nginx/conf.d/verify.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
- ./proxy.conf:/etc/nginx/proxy.conf

verify:
image: pomerium/verify
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 verify.localhost.pomerium.io.

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

That's it!

Your verify install is protected by Pomerium.

Adapting

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

  • Update verify.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