Skip to content

haproxy-auth-gateway is an authentication and authorization gateway for cloud native apps.

License

Notifications You must be signed in to change notification settings

lukaszbudnik/haproxy-auth-gateway

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

haproxy-auth-gateway Docker

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)

Docker image

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

Example

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.

Kubernetes deployment

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 and OAUTH_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 ACL

haproxy-auth-gateway will verify passed JWT and will (if all good):

  • set txn.authorized variable to true
  • set txn.roles variable to a comma separated list of realm_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 }

Troubleshooting

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

Sample JWT

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

Original project

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.