Skip to content

User guide: Using Authorino as ValidatingWebhook service

Authorino provides an interface for raw HTTP external authorization requests. This interface can be used for integrations other than the typical Envoy gRPC protocol, such as (though not limited to) using Authorino as a generic Kubernetes ValidatingWebhook service.

The rules to validate a request to the Kubernetes API – typically a POST, PUT or DELETE request targeting a particular Kubernetes resource or collection –, according to which either the change will be deemed accepted or not, are written in an Authorino AuthConfig custom resource. Authentication and authorization are performed by the Kubernetes API server as usual, with auth features of Authorino implementing the additional validation within the scope of an AdmissionReview request.

This user guide provides an example of using Authorino as a Kubernetes ValidatingWebhook service that validates requests to CREATE and UPDATE Authorino AuthConfig resources. In other words, we will use Authorino as a validator inside the cluster that decides what is a valid AuthConfig for any application which wants to rely on Authorino to protect itself.

The AuthConfig to validate other AuthConfigs will enforce the following rules: - Authorino features that cannot be used by any application in their security schemes: - Anonymous Access - Plain identity object extracted from context - Kubernetes authentication (TokenReview) - Kubernetes authorization (SubjectAccessReview) - Festival Wristband tokens - Authorino features that require a RoleBinding to a specific ClusterRole in the 'authorino' namespace, to be used in a AuthConfig: - Authorino API key authentication - All metadata pulled from external sources must be cached for precisely 5 minutes (300 seconds)

For convenience, the same instance of Authorino used to enforce the AuthConfig associated with the validating webhook will also be targeted for the sample AuthConfigs created to test the validation. For using different instances of Authorino for the validating webhook and for protecting applications behind a proxy, check out the section about sharding in the docs. There is also a user guide on the topic, with concrete examples.

Authorino features in this guide: For further details about Authorino features in general, check the [docs](./../features.md).


Requirements

  • Kubernetes server
  • Auth server / Identity Provider (IdP) that implements OpenID Connect authentication and OpenID Connect Discovery (e.g. Keycloak)

Create a containerized Kubernetes server locally using Kind:

kind create cluster --name authorino-tutorial

Deploy a Keycloak server preloaded with all the realm settings required for this guide:

kubectl create namespace keycloak
kubectl -n keycloak apply -f https://raw.githubusercontent.com/kuadrant/authorino-examples/main/keycloak/keycloak-deploy.yaml

1. Install the Authorino Operator

curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s

This step will also install cert-manager in the cluster (required).

2. Deploy Authorino

Create the namespace:

kubectl create namespace authorino

Create the TLS certificates:

curl -sSL https://raw.githubusercontent.com/Kuadrant/authorino/main/deploy/certs.yaml | sed "s/\$(AUTHORINO_INSTANCE)/authorino/g;s/\$(NAMESPACE)/authorino/g" | kubectl -n authorino apply -f -

Create the Authorino instance:

kubectl -n authorino apply -f -<<EOF
apiVersion: operator.authorino.kuadrant.io/v1beta1
kind: Authorino
metadata:
  name: authorino
spec:
  clusterWide: true
  listener:
    ports:
      grpc: 50051
      http: 5001 # for admissionreview requests sent by the kubernetes api server
    tls:
      certSecretRef:
        name: authorino-server-cert
  oidcServer:
    tls:
      certSecretRef:
        name: authorino-oidc-server-cert
EOF

The command above will deploy Authorino as a separate service (as opposed to a sidecar of the protected API and other architectures), in cluster-wide reconciliation mode, and with TLS termination enabled. For other variants and deployment options, check out the Getting Started section of the docs, the Architecture page, and the spec for the Authorino CRD in the Authorino Operator repo.

Create the AuthConfig:

kubectl -n authorino apply -f -<<EOF
apiVersion: authorino.kuadrant.io/v1beta2
kind: AuthConfig
metadata:
  name: authconfig-validator
spec:
  # admissionreview requests will be sent to this host name
  hosts:
  - authorino-authorino-authorization.authorino.svc

  # because we're using a single authorino instance for the validating webhook and to protect the user applications,
  # skip operations related to this one authconfig in the 'authorino' namespace
  when:
  - selector: context.request.http.body.@fromstr|request.object.metadata.namespace
    operator: neq
    value: authorino

  # kubernetes admissionreviews carry info about the authenticated user
  authentication:
    "k8s-userinfo":
      plain:
        selector: context.request.http.body.@fromstr|request.userInfo

  authorization:
    "features":
      opa:
        rego: |
          authconfig = json.unmarshal(input.context.request.http.body).request.object

          forbidden { count(object.get(authconfig.spec, "authentication", [])) == 0 }
          forbidden { authconfig.spec.authentication[_].anonymous }
          forbidden { authconfig.spec.authentication[_].kubernetesTokenReview }
          forbidden { authconfig.spec.authentication[_].plain }
          forbidden { authconfig.spec.authorization[_].kubernetesSubjectAccessReview }
          forbidden { authconfig.spec.response.success.headers[_].wristband }

          apiKey { authconfig.spec.authentication[_].apiKey }

          allow { count(authconfig.spec.authentication) > 0; not forbidden }
        allValues: true

    "apikey-authn-requires-k8s-role-binding":
      priority: 1
      when:
      - selector: auth.authorization.features.apiKey
        operator: eq
        value: "true"
      kubernetesSubjectAccessReview:
        user:
          selector: auth.identity.username
        resourceAttributes:
          namespace: { value: authorino }
          group: { value: authorino.kuadrant.io }
          resource: { value: authconfigs-with-apikeys }
          verb: { value: create }

    "metadata-cache-ttl":
      priority: 1
      opa:
        rego: |
          invalid_ttl = input.auth.authorization.features.authconfig.spec.metadata[_].cache.ttl != 300
          allow { not invalid_ttl }
EOF

Define a ClusterRole to control the usage of protected features of Authorino:

kubectl apply -f -<<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: authorino-apikey
rules:
- apiGroups: ["authorino.kuadrant.io"]
  resources: ["authconfigs-with-apikeys"] # not a real k8s resource
  verbs: ["create"]
EOF

4. Create the ValidatingWebhookConfiguration

kubectl -n authorino apply -f -<<EOF
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: authconfig-authz
  annotations:
    cert-manager.io/inject-ca-from: authorino/authorino-ca-cert
webhooks:
- name: check-authconfig.authorino.kuadrant.io
  clientConfig:
    service:
      namespace: authorino
      name: authorino-authorino-authorization
      port: 5001
      path: /check
  rules:
  - apiGroups: ["authorino.kuadrant.io"]
    apiVersions: ["v1beta2"]
    resources: ["authconfigs"]
    operations: ["CREATE", "UPDATE"]
    scope: Namespaced
  sideEffects: None
  admissionReviewVersions: ["v1"]
EOF

5. Try it out

Create a namespace:

kubectl create namespace myapp

With a valid AuthConfig

kubectl -n myapp apply -f -<<EOF
apiVersion: authorino.kuadrant.io/v1beta2
kind: AuthConfig
metadata:
  name: myapp-protection
spec:
  hosts:
  - myapp.io
  authentication:
    "keycloak":
      jwt:
        issuerUrl: http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant
EOF
# authconfig.authorino.kuadrant.io/myapp-protection created

With forbidden features

Anonymous access:

kubectl -n myapp apply -f -<<EOF
apiVersion: authorino.kuadrant.io/v1beta2
kind: AuthConfig
metadata:
  name: myapp-protection
spec:
  hosts:
  - myapp.io
EOF
# Error from server: error when applying patch:
# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta2\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"hosts\":[\"myapp.io\"]}}\n"}},"spec":{"authentication":null}}
# to:
# Resource: "authorino.kuadrant.io/v1beta2, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta2, Kind=AuthConfig"
# Name: "myapp-protection", Namespace: "myapp"
# for: "STDIN": error when patching "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized
kubectl -n myapp apply -f -<<EOF
apiVersion: authorino.kuadrant.io/v1beta2
kind: AuthConfig
metadata:
  name: myapp-protection
spec:
  hosts:
  - myapp.io
  authentication:
    "anonymous-access":
      anonymous: {}
EOF
# Error from server: error when applying patch:
# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta2\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"authentication\":{\"anonymous-access\":{\"anonymous\":{}}},\"hosts\":[\"myapp.io\"]}}\n"}},"spec":{"authentication":{"anonymous-access":{"anonymous":{}},"keycloak":null}}}
# to:
# Resource: "authorino.kuadrant.io/v1beta2, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta2, Kind=AuthConfig"
# Name: "myapp-protection", Namespace: "myapp"
# for: "STDIN": error when patching "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized

Kubernetes TokenReview:

kubectl -n myapp apply -f -<<EOF
apiVersion: authorino.kuadrant.io/v1beta2
kind: AuthConfig
metadata:
  name: myapp-protection
spec:
  hosts:
  - myapp.io
  authentication:
    "k8s-tokenreview":
      kubernetesTokenReview:
        audiences: ["myapp"]
EOF
# Error from server: error when applying patch:
# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta2\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"authentication\":{\"k8s-tokenreview\":{\"kubernetesTokenReview\":{\"audiences\":[\"myapp\"]}}},\"hosts\":[\"myapp.io\"]}}\n"}},"spec":{"authentication":{"k8s-tokenreview":{"kubernetesTokenReview":{"audiences":["myapp"]}},"keycloak":null}}}
# to:
# Resource: "authorino.kuadrant.io/v1beta2, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta2, Kind=AuthConfig"
# Name: "myapp-protection", Namespace: "myapp"
# for: "STDIN": error when patching "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized

Plain identity extracted from context:

kubectl -n myapp apply -f -<<EOF
apiVersion: authorino.kuadrant.io/v1beta2
kind: AuthConfig
metadata:
  name: myapp-protection
spec:
  hosts:
  - myapp.io
  authentication:
    "envoy-jwt-authn":
      plain:
        selector: context.metadata_context.filter_metadata.envoy\.filters\.http\.jwt_authn|verified_jwt
EOF
# Error from server: error when applying patch:
# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta2\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"authentication\":{\"envoy-jwt-authn\":{\"plain\":{\"selector\":\"context.metadata_context.filter_metadata.envoy\\\\.filters\\\\.http\\\\.jwt_authn|verified_jwt\"}}},\"hosts\":[\"myapp.io\"]}}\n"}},"spec":{"authentication":{"envoy-jwt-authn":{"plain":{"selector":"context.metadata_context.filter_metadata.envoy\\.filters\\.http\\.jwt_authn|verified_jwt"}},"keycloak":null}}}
# to:
# Resource: "authorino.kuadrant.io/v1beta2, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta2, Kind=AuthConfig"
# Name: "myapp-protection", Namespace: "myapp"
# for: "STDIN": error when patching "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized

Kubernetes SubjectAccessReview:

kubectl -n myapp apply -f -<<EOF
apiVersion: authorino.kuadrant.io/v1beta2
kind: AuthConfig
metadata:
  name: myapp-protection
spec:
  hosts:
  - myapp.io
  authentication:
    "keycloak":
      jwt:
        issuerUrl: http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant
  authorization:
    "k8s-subjectaccessreview":
      kubernetesSubjectAccessReview:
        user:
          selector: auth.identity.sub
EOF
# Error from server: error when applying patch:
# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta2\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"authentication\":{\"keycloak\":{\"jwt\":{\"issuerUrl\":\"http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant\"}}},\"authorization\":{\"k8s-subjectaccessreview\":{\"kubernetesSubjectAccessReview\":{\"user\":{\"selector\":\"auth.identity.sub\"}}}},\"hosts\":[\"myapp.io\"]}}\n"}},"spec":{"authorization":{"k8s-subjectaccessreview":{"kubernetesSubjectAccessReview":{"user":{"selector":"auth.identity.sub"}}}}}}
# to:
# Resource: "authorino.kuadrant.io/v1beta2, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta2, Kind=AuthConfig"
# Name: "myapp-protection", Namespace: "myapp"
# for: "STDIN": error when patching "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized

Festival Wristband tokens:

kubectl -n myapp apply -f -<<EOF
apiVersion: v1
kind: Secret
metadata:
  name: wristband-signing-key
stringData:
  key.pem: |
    -----BEGIN EC PRIVATE KEY-----
    MHcCAQEEIDHvuf81gVlWGo0hmXGTAnA/HVxGuH8vOc7/8jewcVvqoAoGCCqGSM49
    AwEHoUQDQgAETJf5NLVKplSYp95TOfhVPqvxvEibRyjrUZwwtpDuQZxJKDysoGwn
    cnUvHIu23SgW+Ee9lxSmZGhO4eTdQeKxMA==
    -----END EC PRIVATE KEY-----
type: Opaque
---
apiVersion: authorino.kuadrant.io/v1beta2
kind: AuthConfig
metadata:
  name: myapp-protection
spec:
  hosts:
  - myapp.io
  authentication:
    "keycloak":
      jwt:
        issuerUrl: http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant
  response:
    success:
      headers:
        "wristband":
          wristband:
            issuer: http://authorino-authorino-oidc.authorino.svc.cluster.local:8083/myapp/myapp-protection/wristband
            signingKeyRefs:
            - algorithm: ES256
              name: wristband-signing-key
EOF
# secret/wristband-signing-key created
# Error from server: error when applying patch:
# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta2\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"authentication\":{\"keycloak\":{\"jwt\":{\"issuerUrl\":\"http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant\"}}},\"hosts\":[\"myapp.io\"],\"response\":{\"success\":{\"headers\":{\"wristband\":{\"wristband\":{\"issuer\":\"http://authorino-authorino-oidc.authorino.svc.cluster.local:8083/myapp/myapp-protection/wristband\",\"signingKeyRefs\":[{\"algorithm\":\"ES256\",\"name\":\"wristband-signing-key\"}]}}}}}}}\n"}},"spec":{"response":{"success":{"headers":{"wristband":{"wristband":{"issuer":"http://authorino-authorino-oidc.authorino.svc.cluster.local:8083/myapp/myapp-protection/wristband","signingKeyRefs":[{"algorithm":"ES256","name":"wristband-signing-key"}]}}}}}}}
# to:
# Resource: "authorino.kuadrant.io/v1beta2, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta2, Kind=AuthConfig"
# Name: "myapp-protection", Namespace: "myapp"
# for: "STDIN": error when patching "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized

With features that require additional permissions

Before adding the required permissions:

kubectl -n myapp apply -f -<<EOF
apiVersion: authorino.kuadrant.io/v1beta2
kind: AuthConfig
metadata:
  name: myapp-protection
spec:
  hosts:
  - myapp.io
  authentication:
    "api-key":
      apiKey:
        selector:
          matchLabels: { app: myapp }
EOF
# Error from server: error when applying patch:
# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta2\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"authentication\":{\"api-key\":{\"apiKey\":{\"selector\":{\"matchLabels\":{\"app\":\"myapp\"}}}}},\"hosts\":[\"myapp.io\"]}}\n"}},"spec":{"authentication":{"api-key":{"apiKey":{"selector":{"matchLabels":{"app":"myapp"}}}},"keycloak":null}}}
# to:
# Resource: "authorino.kuadrant.io/v1beta2, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta2, Kind=AuthConfig"
# Name: "myapp-protection", Namespace: "myapp"
# for: "STDIN": error when patching "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Not authorized: unknown reason

Add the required permissions:

kubectl -n authorino apply -f -<<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: authorino-apikey
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: authorino-apikey
subjects:
- kind: User
  name: kubernetes-admin
EOF
# rolebinding.rbac.authorization.k8s.io/authorino-apikey created

After adding the required permissions:

kubectl -n myapp apply -f -<<EOF
apiVersion: authorino.kuadrant.io/v1beta2
kind: AuthConfig
metadata:
  name: myapp-protection
spec:
  hosts:
  - myapp.io
  authentication:
    "api-key":
      apiKey:
        selector:
          matchLabels: { app: myapp }
EOF
# authconfig.authorino.kuadrant.io/myapp-protection configured

With features that require specific property validation

Invalid:

kubectl -n myapp apply -f -<<EOF
apiVersion: authorino.kuadrant.io/v1beta2
kind: AuthConfig
metadata:
  name: myapp-protection
spec:
  hosts:
  - myapp.io
  authentication:
    "keycloak":
      jwt:
        issuerUrl: http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant
  metadata:
    "external-source":
      http:
        url: http://metadata.io
      cache:
        key: { value: global }
        ttl: 60
EOF
# Error from server: error when applying patch:
# {"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta2\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"myapp\"},\"spec\":{\"authentication\":{\"keycloak\":{\"jwt\":{\"issuerUrl\":\"http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant\"}}},\"hosts\":[\"myapp.io\"],\"metadata\":{\"external-source\":{\"cache\":{\"key\":{\"value\":\"global\"},\"ttl\":60},\"http\":{\"url\":\"http://metadata.io\"}}}}}\n"}},"spec":{"authentication":{"api-key":null,"keycloak":{"jwt":{"issuerUrl":"http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant"}}},"metadata":{"external-source":{"cache":{"key":{"value":"global"},"ttl":60},"http":{"url":"http://metadata.io"}}}}}
# to:
# Resource: "authorino.kuadrant.io/v1beta2, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta2, Kind=AuthConfig"
# Name: "myapp-protection", Namespace: "myapp"
# for: "STDIN": error when patching "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized

Valid:

kubectl -n myapp apply -f -<<EOF
apiVersion: authorino.kuadrant.io/v1beta2
kind: AuthConfig
metadata:
  name: myapp-protection
spec:
  hosts:
  - myapp.io
  authentication:
    "keycloak":
      jwt:
        issuerUrl: http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant
  metadata:
    "external-source":
      http:
        url: http://metadata.io
      cache:
        key: { value: global }
        ttl: 300
EOF
# authconfig.authorino.kuadrant.io/myapp-protection configured

Cleanup

If you have started a Kubernetes cluster locally with Kind to try this user guide, delete it by running:

kind delete cluster --name authorino-tutorial

Otherwise, delete the resources created in each step:

kubectl delete namespace myapp
kubectl delete namespace authorino
kubectl delete clusterrole authorino-apikey
kubectl delete namespace keycloak

To uninstall the Authorino Operator and manifests (CRDs, RBAC, etc), run:

kubectl delete -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml