haproxy-auth-gateway is an authentication and authorization gateway for cloud native apps.
haproxy-auth-gateway features include:
- parsing JWT token from the HTTP Authorization header
- Keycloak realm roles support
- RS256, HS256, HS512 signature verification
- expiration time verification
- issuer verification
- audience verification
haproxy-auth-gateway can be configured with the following env variables:
OAUTH_PUBKEY_PATH
- contains location to issuer public key (mandatory)OAUTH_ISSUER
- contains name of the issuer (optional)OAUTH_AUDIENCE
- contains name of the audience (optional)
haproxy-auth-gateway is available on docker hub:
docker pull lukasz/haproxy-auth-gateway
or on ghcr.io:
docker pull ghcr.io/lukaszbudnik/haproxy-auth-gateway
The below example shows how to deploy & configure lukasz/haproxy-auth-gateway
in Kubernetes. It also shows how to invoke the Lua verify script and write ACLs.
If you are interested in running a complete distributed demo app on Kubernetes check out: lukaszbudnik/keycloak-kubernetes. This demo app uses Keycloak as Identity and Access Management solution and haproxy-auth-gateway for transparent authentication and authorization for backend services.
haproxy-auth-gateway requires:
- your haproxy config (file)
- public key of the JWT issuer (file)
OAUTH_PUBKEY_PATH
set to the path of the public key of the JWT issuer (env variable)OAUTH_ISSUER
andOAUTH_AUDIENCE
are optional should you want a more fine-grained JWT verification (env variable)
You can create haproxy config and public key files as config maps:
kubectl create configmap haproxy-auth-gateway-iss-cert --from-file=config/hotel.pem
kubectl create configmap haproxy-auth-gateway-haproxy-cfg --from-file=config/haproxy.cfg
Then you can map them to volumes and then mount them into haproxy-auth-gateway container. In the container spec you also set the env variables:
apiVersion: apps/v1
kind: Deployment
metadata:
name: gateway
labels:
app.kubernetes.io/name: gateway
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: gateway
template:
metadata:
labels:
app.kubernetes.io/name: gateway
spec:
containers:
- name: gateway
image: lukasz/haproxy-auth-gateway
env:
- name: OAUTH_PUBKEY_PATH
value: /etc/certs/hotel.pem
- name: OAUTH_ISSUER
value: issuer_is_optional
- name: OAUTH_AUDIENCE
value: audience_is_optional
ports:
- containerPort: 80
volumeMounts:
- name: iss-cert
mountPath: /etc/certs
- name: haproxy-cfg
mountPath: /usr/local/etc/haproxy
volumes:
- name: haproxy-cfg
configMap:
name: haproxy-auth-gateway-haproxy-cfg
- name: iss-cert
configMap:
name: haproxy-auth-gateway-iss-cert
---
apiVersion: v1
kind: Service
metadata:
name: gateway
labels:
app.kubernetes.io/name: gateway
spec:
type: ClusterIP
clusterIP: None
selector:
app.kubernetes.io/name: gateway
ports:
- protocol: TCP
port: 80
Then we are ready to deploy haproxy-auth-gateway:
kubectl apply -f gateway.yaml
haproxy-auth-gateway will verify passed JWT and will (if all good):
- set
txn.authorized
variable totrue
- set
txn.roles
variable to a comma separated list ofrealm_access.roles
Above variables can be used in haproxy ACLs.
For example:
# deny if no Authorization header sent
http-request deny unless { req.hdr(authorization) -m found }
# invoke the jwtverify Lua script
http-request lua.jwtverify
# check if authorized successfully
http-request deny unless { var(txn.authorized) -m bool }
# check roles
http-request deny if PATH_camarero ! { var(txn.roles) -m sub camarero }
The script outputs many useful debug messages. To enable debug add the following configuration to you haproxy.cfg
:
global
log stdout local0 debug
defaults
log global
A sample JWT token generated by Keycloak looks like this:
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJXejFuaDNCWDI4UHMxVEMzSDRoOW52Q1VWRXpjVVBzQms4Z1NmeEp4ZS1JIn0.eyJleHAiOjE2MTM4NTQ3OTgsImlhdCI6MTYxMzg1Mzg5OCwiYXV0aF90aW1lIjoxNjEzODUzNjk2LCJqdGkiOiIxMmI1YTMxYS1hYjM1LTQxMDMtYTkxNC0wZjRlODUzMzg4ZjUiLCJpc3MiOiJodHRwczovL2F1dGgubG9jYWx0ZXN0Lm1lL2F1dGgvcmVhbG1zL2hvdGVsIiwic3ViIjoiMWE1NWUxMjktZjliYi00ZDYwLWJlZDEtMGJhYmIwOWJlZTNlIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoicmVhY3QiLCJub25jZSI6Ijg4NGNiMzY1LTRjMTQtNDZhYS04ZTBjLWViM2Q3ZjBjYTRmMCIsInNlc3Npb25fc3RhdGUiOiI3NDZhNDZhZC1hY2Y3LTRhMTYtYWI2Yy1iMWZhNWE1YTgxZDMiLCJhY3IiOiIwIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiY29jaW5lcmEiLCJkb25jZWxsYSIsImNhbWFyZXJvIl19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiQW5nZWxhIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYW5nZWxhIiwiZ2l2ZW5fbmFtZSI6IkFuZ2VsYSJ9.kkv2K-XYpHexnKmCoyNED_pO7G8hNI8hi2WCUzhpErkvrazNNZmUYZ8ZAjiybpi1u6ouc2EsHGykTNhUBD2jRhb2dWHYqcEEDaIn9MUq62B-nbTIcB-6vf1SrKnY_Vdnq_olmV_MhIJSQjPbDfcCVKKiUxHYmSBc9Vuno-enPehfUb_EpoRaM24SfJ0WDU281rTPxsgAJBdB4Yg0E9KMfCgaXkwRaHXMEGVpzHHqdi8S1lWwxs12Par-Qz4HqP-Tsw6KqNPU11dG3v6H_Q2fWmDsX5vvMqnmWkMQOFzco2fffsx7lcClPxNw3VghSVT-qB_7dMKUoT-DfyIo1Rcbqw
When parsed and decoded becomes the following.
Header:
{
"alg": "RS256",
"typ": "JWT",
"kid": "Wz1nh3BX28Ps1TC3H4h9nvCUVEzcUPsBk8gSfxJxe-I"
}
Payload:
{
"exp": 1613854798,
"iat": 1613853898,
"auth_time": 1613853696,
"jti": "12b5a31a-ab35-4103-a914-0f4e853388f5",
"iss": "https://auth.localtest.me/auth/realms/hotel",
"sub": "1a55e129-f9bb-4d60-bed1-0babb09bee3e",
"typ": "Bearer",
"azp": "react",
"nonce": "884cb365-4c14-46aa-8e0c-eb3d7f0ca4f0",
"session_state": "746a46ad-acf7-4a16-ab6c-b1fa5a5a81d3",
"acr": "0",
"allowed-origins": ["http://localhost:3000"],
"realm_access": {
"roles": ["cocinera", "doncella", "camarero"]
},
"scope": "openid email profile",
"email_verified": false,
"name": "Angela",
"preferred_username": "angela",
"given_name": "Angela"
}
Public key:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyn1SYrKiXgJembEG1emG
lUw/NliK0tOTeKr8eBp7TZxI8D9k9FUkfzEeQyWekShPt3yTG9boZ9Sq/K7FAfs7
vXFG+kTKYYXysvfdkHHKJnPWEAJgqj3vDEpHB/Xqw5OtqOkSNPNYOxJ65ZmmZVNB
77NpGK5xW5s7xc7XXvLuILhfbOQXlObPbMnjVcnQSGHjmfbtTKsQ/im6ayxtShsL
FQgEJycplJU21WRy3T9cDHpGOMF3LehFIOmsxspcuC/idS0Nber3Fuw9QndSHZQL
KPTkDlyacPu9SyOJiMmD9S4QOZo9UVQWA8JlKa+KuL6TXyZ1OZdSkPSX1o1xeH7L
ewIDAQAB
-----END PUBLIC KEY-----
haproxy-auth-gateway is based on great project from haproxytech folks: https://github.com/haproxytech/haproxy-lua-oauth.
haproxy-auth-gateway contains changes to support Keycloak realm roles out of the box.