← back

OAuth2-Proxy in Istio

Written by Matthias Osswald - October 2021
 
 
Since it took me some time to combine the correct resources to protect a microservice in an Istio mesh using an OAuth2-Proxy I would like to share the summarized parts of the setup here.

Simplified Authentication Flow

If a user wants to access the microservice (e.g. nginx) he/she/* will hit the OAuth2-Proxy first. For logged in users the proxy will display the service. For not yet logged in users the proxy will initiate the login flow (e.g. OIDC flow using Keycloak as an IdP). This mean the user will be redirected to the IdP, logs in, is redirected back, the retrieved data is verified (has not been tampered with and comes from the correct IdP) and then the user gets a session and is therefore able to see the service now.

 App-User
¯\_(ツ)_/¯ -----> OAuth2-Proxy ---valid jwt--> Microservice (e.g. Nginx)
                        |
                    login user
                        |
                IdP (e.g. Keycloak)

Setting your Cluster Up

From the Istio docs I was able to get the mesh config adaption. You can edit the cm using:

kubectl edit configmap istio -n istio-system

The config look like below, where "oauth2-proxy" is the name of the Istio extension provider, "oauth2-proxy.demo.svc.cluster.local" is your OAuth2-Proxy service running in the "demo" namespace.

    extensionProviders:
    - name: "oauth2-proxy"
      envoyExtAuthzHttp:
        service: "oauth2-proxy.demo.svc.cluster.local"
        port: "4180"
        includeHeadersInCheck:
        - "authorization"
        - "cookie"
        headersToUpstreamOnAllow:
        - "authorization"
        - "path"
        - "x-auth-request-user"
        - "x-auth-request-email"
        - "x-auth-request-access-token"
        headersToDownstreamOnDeny:
        - "content-type"
        - "set-cookie"

Secondly a AuthorizationPolicy is required to protect your endpoint which is to be protected ("nginx.mydomain.com" in this case). The AuthorizationPolicy rules also support path matching (paths: ["/data"]) and method matching (methods: ["POST"]) or combinations of these. In this case the app to be protected is a nginx instance named "nginx" and running in the "demo" namespace. The Istio VirtualService exposing it exposes the route "nginx.mydomain.com".

When the rules match it will use a "CUSTOM"-action which is the "oauth2-proxy" extension provider which was defined in the mesh.

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: ext-authz
  namespace: demo
spec:
  selector:
    matchLabels:
      app: nginx
  action: CUSTOM
  provider:
    name: oauth2-proxy
  rules:
  - to:
    - operation:
        hosts: ["nginx.mydomain.com"]

The last missing part is an OAuth2-Proxy instance serving as the extension provider. In this example the OAuth2-Proxy will connect to an OIDC identity provider.

In the example the IdP is a Keycloak instance also running in the mesh exposed on the route "sso.mydomain.com" with a "test-realm" and a "test-client" configured.

apiVersion: v1
kind: Secret
metadata:
  name: oauth2-proxy
  namespace: demo
stringData:
  OAUTH2_PROXY_CLIENT_ID: test-client
  OAUTH2_PROXY_CLIENT_SECRET: 42a70abe-e004-5555-8221-3d6f37c89cb2
  OAUTH2_PROXY_COOKIE_SECRET: b81b2d5f6bffe355a17bf1605f7bd088
---
apiVersion: v1
kind: Service
metadata:
  name: oauth2-proxy
  namespace: demo
spec:
  selector:
    app: oauth2-proxy
  ports:
  - name: http
    port: 4180
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: oauth2-proxy
  namespace: demo
spec:
  selector:
    matchLabels:
      app: oauth2-proxy
  template:
    metadata:
      labels:
        app: oauth2-proxy
    spec:
      containers:
      - name: oauth2-proxy
        image: quay.io/oauth2-proxy/oauth2-proxy:latest
        args:
        - --provider=oidc
        - --cookie-secure=true
        - --cookie-samesite=lax
        - --cookie-refresh=1h
        - --cookie-expire=4h
        - --cookie-name=_oauth2_proxy
        - --set-authorization-header=true
        - --email-domain=*
        - --http-address=0.0.0.0:4180
        - --upstream=static://200
        - --skip-provider-button=true
        - --whitelist-domain=.mydomain.com
        - --oidc-issuer-url=https://sso.mydomain.com/auth/realms/test-realm
        env:
        - name: OAUTH2_PROXY_CLIENT_ID
          valueFrom:
            secretKeyRef:
              name: oauth2-proxy
              key: OAUTH2_PROXY_CLIENT_ID
        - name: OAUTH2_PROXY_CLIENT_SECRET
          valueFrom:
            secretKeyRef:
              name: oauth2-proxy
              key: OAUTH2_PROXY_CLIENT_SECRET
        - name: OAUTH2_PROXY_COOKIE_SECRET
          valueFrom:
            secretKeyRef:
              name: oauth2-proxy
              key: OAUTH2_PROXY_COOKIE_SECRET
        resources:
          requests:
            cpu: 10m
            memory: 100Mi
        ports:
        - containerPort: 4180
          protocol: TCP
        readinessProbe:
          periodSeconds: 3
          httpGet:
            path: /ping
            port: 4180

The OAuth2-Proxy docs shows all the configuration options for the "args" section. There are many options to be considered (e.g. Redis cache is recommended for production instead).