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.
Authorino capabilities featured in this guide:
- Identity verification & authentication → Plain
- Identity verification & authentication → Kubernetes TokenReview
- Identity verification & authentication → API key
- External auth metadata → HTTP GET/GET-by-POST
- Authorization → Kubernetes SubjectAccessReview
- Authorization → Open Policy Agent (OPA) Rego policies
- Dynamic response → Festival Wristband tokens
- Common feature → Conditions
- Common feature → Priorities
For further details about Authorino features in general, check the docs.
Requirements¶
- Kubernetes server with permissions to install cluster-scoped resources (operator, CRDs and RBAC)
- Identity Provider (IdP) that implements OpenID Connect authentication and OpenID Connect Discovery (e.g. Keycloak)
If you do not own a Kubernetes server already and just want to try out the steps in this guide, you can create a local containerized cluster by executing the command below. In this case, the main requirement is having Kind installed, with either Docker or Podman.
Deploy the identity provider and authentication server. For the examples in this guide, we are going to use a Keycloak server preloaded with all required realm settings.
The Keycloak server is only needed for trying out validating AuthConfig resources that use the authentication server.
kubectl create namespace keycloak
kubectl -n keycloak apply -f https://raw.githubusercontent.com/kuadrant/authorino-examples/main/keycloak/keycloak-deploy.yaml
The next steps walk you through installing Authorino, deploying and configuring a sample service called Talker API to be protected by the authorization service.
Using Kuadrant |
---|
If you are a user of Kuadrant you may already have Authorino installed and running. In this case, skip straight to step ❸. At step ❺, alternatively to creating an For more about using Kuadrant to enforce authorization, check out Kuadrant auth. |
❶ Install the Authorino Operator (cluster admin required)¶
The following command will install the Authorino Operator in the Kubernetes cluster. The operator manages instances of the Authorino authorization service.
curl -sL https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/utils/install.sh | bash -s
❷ Deploy Authorino¶
Create the namespace:
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:
The following command will request an instance of Authorino as a separate service1 that watches for AuthConfig
resources cluster-wide2, with TLS enabled3.
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
For convenience, the same instance of Authorino pointed as 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.
❸ Create the AuthConfig
and related ClusterRole
¶
Create the AuthConfig
with the auth rules to validate other AuthConfig resources applied to the cluster.
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)
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
❹ 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
❺ Try it out¶
Create a namespace:
With a valid AuthConfig
¶
Kuadrant users –
For this and other example AuthConfigs below, if you create a Kuadrant AuthPolicy instead, the output of the commands shall differ. The requested AuthPolicy may be initially accepted, but its state will turn ready or not ready depending on whether the corresponding AuthConfig requested by Kuadrant is accepted or rejected, according to the validating webhook rules. Check the state of the resources to confirm.
For more, see Kuadrant auth.
|
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/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/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/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/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/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/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/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/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/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:
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
-
In contrast to a dedicated sidecar of the protected service and other architectures. Check out Architecture > Topologies for all options. ↩
-
cluster-wide
reconciliation mode. See Cluster-wide vs. Namespaced instances. ↩ -
For other variants and deployment options, check out Getting Started, as well as the
Authorino
CRD specification. ↩