Skip to content

Instantly share code, notes, and snippets.

@atomatt
Last active September 29, 2022 16:38
Show Gist options
  • Select an option

  • Save atomatt/2308a608b2019167feb77b8aff54e31b to your computer and use it in GitHub Desktop.

Select an option

Save atomatt/2308a608b2019167feb77b8aff54e31b to your computer and use it in GitHub Desktop.
Using nginx as an authenticating proxy

Quick and dirty example of protecting a service with an authenticating nginx proxy and HTTP basic auth. The proxy passes the identity of the caller to the upstream. Key rotation is (sort-of) supported.

The easiest way to play is in a local minikube with ingress, minikube start --addons ingress,ingress-dns, and Tilt to inject the development domain (via nip.io).

Overview:

  • Ingress is via the auth proxy. The auth proxy forwards to the real service.
  • Users are in a htpasswd file in a ConfigMap.
  • Adding new credentials is easy, e.g. htpasswd -nbB alice p. bcrypt should mean they're safe enough but it could be stored in a vault easily enough.
  • Adding a "$identity/$n" prefix means credentials can be rotated without changing the caller's identity.
  • The ConfigMap is a volume mount. The pod gets periodically updated. nginx reloads at runtime. No downtime or restarts.
$ curl -Ss http-apikey-demo.192.168.49.2.nip.io                                               
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.23.1</center>
</body>
</html>

$ curl -Ss http-apikey-demo.192.168.49.2.nip.io -u alice:p | jq '.request.headers | { "x-auth-identity" }'
{
  "x-auth-identity": "alice"
}

$ curl -Ss http-apikey-demo.192.168.49.2.nip.io -u 'alice/2:pp' | jq '.request.headers | { "x-auth-identity" }'
{
  "x-auth-identity": "alice"
}

$ curl -Ss http-apikey-demo.192.168.49.2.nip.io -u 'bob:p' | jq '.request.headers | { "x-auth-identity" }'
{
  "x-auth-identity": "bob"
}
apiVersion: apps/v1
kind: Deployment
metadata:
name: http-apikey-demo
spec:
selector:
matchLabels:
app: http-apikey-demo
template:
metadata:
labels:
app: http-apikey-demo
spec:
containers:
- name: http-apikey-demo
image: ealen/echo-server
resources:
limits:
memory: "32Mi"
cpu: "100m"
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: http-apikey-demo
spec:
selector:
app: http-apikey-demo
ports:
- port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: http-apikey-demo-auth-proxy
spec:
selector:
matchLabels:
app: http-apikey-demo-auth-proxy
template:
metadata:
labels:
app: http-apikey-demo-auth-proxy
spec:
containers:
- name: http-apikey-demo-auth-proxy
image: nginx:1.23.1
resources:
limits:
memory: "32Mi"
cpu: "100m"
ports:
- containerPort: 80
volumeMounts:
- name: http-apikey-demo-auth-proxy-config
mountPath: /etc/nginx
volumes:
- name: http-apikey-demo-auth-proxy-config
configMap:
name: http-apikey-demo-auth-proxy-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: http-apikey-demo-auth-proxy-config
data:
nginx.conf: |
events {
worker_connections 1024;
}
http {
map $remote_user $auth_identity {
default $remote_user;
"~(.+)\/[0-9]+$" $1;
}
server {
listen 80;
location / {
auth_basic "http-apikey-demo";
auth_basic_user_file /etc/nginx/users.htpasswd;
proxy_set_header X-Auth-Identity $auth_identity;
proxy_set_header Authorization "";
proxy_pass http://http-apikey-demo;
}
}
}
users.htpasswd: |
alice:$2y$05$q.bXZrMAEuH9iZLbB6RvIOLfp8ijRsdWKanSoO9Hk6uNULBu5lQtu
alice/2:$2y$05$9YdF70yw6cueEajpTKVoAO7EoQzqgmTkd3pbqdJEQ.EPyvpCz/Mc6
bob:$2y$05$llcYVF1naYsPjW0fWKScHeNX5E7l2fXYpmQl.mzxo/Lgfer.rsfnW
---
apiVersion: v1
kind: Service
metadata:
name: http-apikey-demo-auth-proxy
spec:
selector:
app: http-apikey-demo-auth-proxy
ports:
- port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: http-apikey-demo
labels:
name: http-apikey-demo
spec:
rules:
- host: http-apikey-demo.localhost
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: http-apikey-demo-auth-proxy
port:
number: 80
ip=str(local('minikube ip')).strip()
tld='{}.nip.io'.format(ip)
# Crudely patch Ingress host names to use TLD.
objects = read_yaml_stream('http-apikey.yaml')
for o in objects:
if (o['apiVersion'], o['kind']) == ('networking.k8s.io/v1', 'Ingress'):
o['spec']['rules'][0]['host'] = o['spec']['rules'][0]['host'].replace('.localhost', '.'+tld)
k8s_yaml(encode_yaml_stream(objects))
@atomatt
Copy link
Author

atomatt commented Sep 29, 2022

It's good to be secure by default, but some endpoints are inherently public, e.g. health and status probes, public keys, etc. That would be service specific so needs to be configurable somehow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment