End User Auth and Authz with Openshift Service Mesh and Keycloak

In this article, I will share the setup for enabling Authentication and Authorization in Openshift Service Mesh with Keycloak

Installing Openshift Service Mesh

Follow the Installing Red Hat OpenShift Service Mesh guide for setup

Enable following configuration in your ServiceMeshControlPlane resource

  • Strict mTLS across the mesh
  • Automatic istio route creation
apiVersion: maistra.io/v2
kind: ServiceMeshControlPlane
spec:
  version: v1.1
  security:
    controlPlane:
      mtls: true
  gateways:
    openshiftRoute:
      enabled: true

Keycloak

Keycloak is an open-source identity and access management application that uses open protocols and is easily integrated with other providers. It is the open-source project base of Red Hat Single Sign-on

Deploying Red Hat Single Sign-on

The easiest way to deploy SSO is from the operator hub

sso-operator

Follow the Keycloak identity provider article for adding new secuity realm, client, role, user

Deploying Bookinfo example application

Create a new namespace

oc new-project bookinfo

Edit the default Service Mesh Member Roll YAML and add bookinfo to the members list

apiVersion: maistra.io/v1
kind: ServiceMeshMemberRoll
metadata:
  name: default
spec:
  members:
  - bookinfo

From the CLI, deploy the Bookinfo application in the bookinfo project by applying the bookinfo.yaml file

$ oc apply -n bookinfo -f https://raw.githubusercontent.com/Maistra/istio/maistra-2.0/samples/bookinfo/platform/kube/bookinfo.yaml

Create the ingress gateway by applying the bookinfo-gateway.yaml file

$ oc apply -n bookinfo -f https://raw.githubusercontent.com/Maistra/istio/maistra-2.0/samples/bookinfo/networking/bookinfo-gateway.yaml

Set the value for the GATEWAY_URL parameter

$ oc get routes -n istio-system | grep bookinfo
$ export GATEWAY_URL=$(oc -n istio-system get route bookinfo-gateway-pl2rw -o jsonpath='{.spec.host}')

Adding default destination rules

I have enabled global mutual TLS in the control plane, so I’ll deploy the destination rule with all mtls

$ oc apply -n bookinfo -f https://raw.githubusercontent.com/Maistra/istio/maistra-2.0/samples/bookinfo/networking/destination-rule-all-mtls.yaml

Verifying the Bookinfo installation

Run this command to confirm that Bookinfo is deployed

$ curl -o /dev/null -s -w "%{http_code}\n" http://$GATEWAY_URL/productpage

Authentication with JWT and RBAC

Enabling User-End Authentication

Now it is time to enable end-user authentication

The first thing you need to do is validate that it is possible to communicate between all services without authentication

$ curl -k -o /dev/null -w "%{http_code}" http://$GATEWAY_URL/productpage
200

You can create the end-user authentication policy

$ cat <<EOF | oc apply -n bookinfo -f -
apiVersion: authentication.istio.io/v1alpha1
kind: Policy
metadata:
  name: "productpage-jwt"
  namespace: "bookinfo"
spec:
  targets:
    - name: productpage
  peers:
    - mtls: {}
  origins:
    - jwt:
        issuer: "https://keycloak-sso.apps.amp01.lab.amp.aapaws/auth/realms/istio"
        jwksUri: "https://keycloak-sso.apps.amp01.lab.amp.aapaws/auth/realms/istio/protocol/openid-connect/certs"
        audiences:
          - customer
        triggerRules:
          - excludedPaths:
              - prefix: /healthz
  principalBinding: USE_ORIGIN
EOF

NOTE: If you see Origin authentication failed. after passing right access token and url. Verify your istio-pilot pods for any x509: certificate signed by unknown authority if thats the case follow this workaround

Then let’s run the curl again

curl -k http://$GATEWAY_URL/productpage

And you will see something like

Origin authentication failed.

Set the value for the TOKEN parameter from keyclock

export TOKEN=$(curl -sk --data "username=demo&password=demo&grant_type=password&client_id=istio" https://keycloak-sso.apps.amp01.lab.amp.aapaws/auth/realms/istio/protocol/openid-connect/token | jq ".access_token")

Then let’s run the curl again, this time with the token

$ curl -k -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $TOKEN" http://$GATEWAY_URL/productpage
200

Authorization

Create a deny-all policy in the namespace. The policy doesn’t have a selector field, which applies the policy to every workload in the namespace. The spec: field of the policy has the empty value {}. The empty value means that no traffic is permitted, effectively denying all requests

$ cat <<EOF | oc apply -n bookinfo -f -
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-all
spec:
  {}
EOF

Once the policy takes effect, verify that mesh rejected the curl connection to the workload

$ curl -k -H "Authorization: Bearer $TOKEN" http://$GATEWAY_URL/productpage
RBAC: access denied

To give read access to the productpage workload, create the policy that applies to workload with label app: productpage and allows users with roles customer to access it with all method

$ cat <<EOF | oc apply -n bookinfo -f -
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: "productpage-authz"
  namespace: "bookinfo"
spec:
  selector:
    matchLabels:
      app: productpage
  rules:
    - to:
        - operation:
            methods: ["*"]
      when:
        - key: request.auth.claims[roles]
          values: ["customer"]
EOF

Wait for the newly defined policy to take effect

After the policy takes effect, verify the connection to the httpbin workload succeeds

curl -k  -o /dev/null -w "%{http_code}" -H "Authorization: Bearer $TOKEN" http://$GATEWAY_URL/productpage
200

However, you can see the following errors on the page

  • Error fetching product details
  • Error fetching product reviews on the page

Run the following command to create the details-viewer policy to allow the productpage workload, which issues requests using the cluster.local/ns/bookinfo/sa/bookinfo-productpage service account, to access the details workload through GET methods

$ oc apply -f - <<EOF
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "details-viewer"
  namespace: bookinfo
spec:
  selector:
    matchLabels:
      app: details
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/bookinfo/sa/bookinfo-productpage"]
    to:
    - operation:
        methods: ["GET"]
EOF

Run the following command to create a policy reviews-viewer to allow the productpage workload, which issues requests using the cluster.local/ns/bookinfo/sa/bookinfo-productpage service account, to access the reviews workload through GET methods

$ oc apply -f - <<EOF
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "reviews-viewer"
  namespace: bookinfo
spec:
  selector:
    matchLabels:
      app: reviews
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/bookinfo/sa/bookinfo-productpage"]
    to:
    - operation:
        methods: ["GET"]
EOF

Point your browser at the Bookinfo productpage (http://$GATEWAY_URL/productpage). Now, you should see the “Bookinfo Sample” page with “Book Details” on the lower left part, and “Book Reviews” on the lower right part. However, in the “Book Reviews” section, there is an error Ratings service currently unavailable

This is because the reviews workload doesn’t have permission to access the ratings workload. To fix this issue, you need to grant the reviews workload access to the ratings workload. Next, we configure a policy to grant the reviews workload that access

Run the following command to create the ratings-viewer policy to allow the reviews workload, which issues requests using the cluster.local/ns/bookinfo/sa/bookinfo-reviews service account, to access the ratings workload through GET methods

$ oc apply -f - <<EOF
apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "ratings-viewer"
  namespace: bookinfo
spec:
  selector:
    matchLabels:
      app: ratings
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/bookinfo/sa/bookinfo-reviews"]
    to:
    - operation:
        methods: ["GET"]
EOF

Point your browser at the Bookinfo productpage (http://$GATEWAY_URL/productpage). You should see the “black” and “red” ratings in the “Book Reviews” section