Demo: Using Authorino as ValidatingWebhook service to validate other AuthConfigs applied to the cluster
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 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 aditional 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:
- Only 'oidc' identity method can be used
- Using Dex SSO server requires additional permission for the user defined in the Kubernetes RBAC
For convinience, 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.
Here's how the architecture and data plane (dotted lines) of using Authorino as a Validating Webhook service will look like:
kind create cluster --name authorino-demo
(Optional) Start watching the workloads in a separate terminal: (▶︎)
watch -n 3 "kubectl get pods --all-namespaces --user=kind-authorino-demo | grep -viE 'Completed|OOMKilled'"
Deploy Keycloak: (▶︎)
kubectl create namespace keycloak
kubectl -n keycloak apply -f https://raw.githubusercontent.com/kuadrant/authorino-examples/main/keycloak/keycloak-deploy.yaml
Deploy Dex: (▶︎)
kubectl create namespace dex
kubectl -n dex apply -f https://raw.githubusercontent.com/kuadrant/authorino-examples/main/dex/dex-deploy.yaml
kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.4.0/cert-manager.yaml
kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml
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:
http: 5001 # for admissionreview requests sent by the kube-api-server
tls:
certSecretRef:
name: authorino-server-cert
oidcServer:
tls:
certSecretRef:
name: authorino-oidc-server-cert
EOF
Create the AuthConfig: (▶︎)
kubectl -n authorino apply -f -<<EOF
apiVersion: authorino.kuadrant.io/v1beta1
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
identity:
- name: k8s-userinfo
plain:
authJSON: context.request.http.body.@fromstr|request.userInfo
authorization:
- name: check-identity
opa:
allValues: true
inlineRego: |
authconfig = json.unmarshal(input.context.request.http.body).request.object
forbidden { authconfig.spec.identity[_].apiKey }
forbidden { authconfig.spec.identity[_].oauth2 }
forbidden { authconfig.spec.identity[_].kubernetes }
forbidden { authconfig.spec.identity[_].plain }
forbidden { authconfig.spec.identity[_].anonymous }
using_dex { startswith(authconfig.spec.identity[_].oidc.endpoint, "http://dex") }
allow { count(authconfig.spec.identity) > 0; not forbidden }
- name: k8s-rbac
priority: 1 # so it waits for the opa policy first
when:
- selector: auth.authorization.check-identity.using_dex
operator: eq
value: "true"
kubernetes:
user:
valueFrom: { authJSON: auth.identity.username }
resourceAttributes:
namespace: { value: authorino }
group: { value: sso.company.com }
resource: { value: dex }
verb: { value: use }
EOF
Define a Role
to control the usage of Dex: (▶︎)
kubectl -n authorino apply -f -<<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: dex-user
rules:
- apiGroups: ["sso.company.com"]
resources: ["dex"] # not a real k8s resource
verbs: ["use"] # not a real k8s verb
EOF
kubectl 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: ["v1beta1"]
resources: ["authconfigs"]
operations: ["CREATE", "UPDATE"]
scope: Namespaced
sideEffects: None
admissionReviewVersions: ["v1"]
EOF
Set the user credentials: (▶︎)
openssl genrsa -out /tmp/john.key
openssl req -new -key /tmp/john.key -out /tmp/john.csr -subj "/CN=john"
kubectl apply -f -<<EOF
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: john
spec:
request: $(cat /tmp/john.csr | base64 | tr -d '\n')
signerName: kubernetes.io/kube-apiserver-client
usages:
- client auth
- digital signature
- key encipherment
groups:
- system:authenticated
EOF
kubectl certificate approve john
kubectl get csr/john -o jsonpath='{.status.certificate}' | base64 -d > /tmp/john.crt
kubectl config set-credentials john --client-certificate=/tmp/john.crt --client-key=/tmp/john.key --embed-certs=true
Create a namespace and grant permissions for the user: (▶︎)
kubectl create namespace apps
kubectl create role namespace-owner --verb="*" --resource="*.*" -n apps
kubectl create rolebinding namespace-owners --role=namespace-owner --user=john -n apps
Set the Kubernetes context: (▶︎)
kubectl config set-context --current --user=john --namespace=apps
kubectl apply -f -<<EOF
apiVersion: authorino.kuadrant.io/v1beta1
kind: AuthConfig
metadata:
name: myapp-protection
spec:
hosts:
- myapp.io
identity:
- name: api-key
apiKey:
labelSelectors: { app: myapp }
EOF
Output:
Error from server: error when creating "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Unauthorized
kubectl apply -f -<<EOF
apiVersion: authorino.kuadrant.io/v1beta1
kind: AuthConfig
metadata:
name: myapp-protection
spec:
hosts:
- myapp.io
identity:
- name: keycloak
oidc:
endpoint: http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant
EOF
Output:
authconfig.authorino.kuadrant.io/myapp-protection created
Without permission: (▶︎)
kubectl apply -f -<<EOF
apiVersion: authorino.kuadrant.io/v1beta1
kind: AuthConfig
metadata:
name: myapp-protection
spec:
hosts:
- myapp.io
identity:
- name: keycloak
oidc:
endpoint: http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant
- name: dex
oidc:
endpoint: http://dex.dex.svc.cluster.local:5556
EOF
Output:
Error from server: error when applying patch:
{"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"authorino.kuadrant.io/v1beta1\",\"kind\":\"AuthConfig\",\"metadata\":{\"annotations\":{},\"name\":\"myapp-protection\",\"namespace\":\"apps\"},\"spec\":{\"hosts\":[\"myapp.io\"],\"identity\":[{\"name\":\"keycloak\",\"oidc\":{\"endpoint\":\"http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant\"}},{\"name\":\"dex\",\"oidc\":{\"endpoint\":\"http://dex.dex.svc.cluster.local:5556\"}}]}}\n"}},"spec":{"identity":[{"name":"keycloak","oidc":{"endpoint":"http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant"}},{"name":"dex","oidc":{"endpoint":"http://dex.dex.svc.cluster.local:5556"}}]}}
to:
Resource: "authorino.kuadrant.io/v1beta1, Resource=authconfigs", GroupVersionKind: "authorino.kuadrant.io/v1beta1, Kind=AuthConfig"
Name: "myapp-protection", Namespace: "apps"
for: "STDIN": admission webhook "check-authconfig.authorino.kuadrant.io" denied the request: Not authorized: unknown reason
As admin, grant permission to the user: (▶︎)
kubectl --user=kind-authorino-demo -n authorino apply -f -<<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dex-users
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: dex-user
subjects:
- kind: User
name: john
EOF
Try again with permission: (▶︎)
kubectl apply -f -<<EOF
apiVersion: authorino.kuadrant.io/v1beta1
kind: AuthConfig
metadata:
name: myapp-protection
spec:
hosts:
- myapp.io
identity:
- name: keycloak
oidc:
endpoint: http://keycloak.keycloak.svc.cluster.local:8080/auth/realms/kuadrant
- name: dex
oidc:
endpoint: http://dex.dex.svc.cluster.local:5556
EOF
Output:
authconfig.authorino.kuadrant.io/myapp-protection configured
kind delete cluster --name authorino-demo
kubectl config unset users.john