diff --git a/docs/features.md b/docs/features.md index 4f3318ca..2bae5354 100644 --- a/docs/features.md +++ b/docs/features.md @@ -355,30 +355,69 @@ Authorino's built-in OPA module precompiles the policies in reconciliation-time ### Kubernetes SubjectAccessReview ([`authorization.kubernetes`](https://pkg.go.dev/github.com/kuadrant/authorino/api/v1beta1?utm_source=gopls#Authorization_KubernetesAuthz)) -Access control enforcement based on rules defined in the Kubernetes authorization system (e.g. as `ClusterRole` and `ClusterRoleBinding` resources of Kubernetes RBAC authorization). +Access control enforcement based on rules defined in the Kubernetes authorization system, i.e. `Role`, `ClusterRole`, `RoleBinding` and `ClusterRoleBinding` resources of Kubernetes RBAC. -Authorino issues a [SubjectAccessReview](https://kubernetes.io/docs/reference/kubernetes-api/authorization-resources/subject-access-review-v1) inquiry checking with the underlying Kubernetes cluster whether the user can access the requested API resouce. It can be used with `resourceAttributes` or `nonResourceAttributes` (the latter inferring HTTP verb and method from the original request). +Authorino issues a [SubjectAccessReview](https://kubernetes.io/docs/reference/kubernetes-api/authorization-resources/subject-access-review-v1) (SAR) inquiry that checks with the underlying Kubernetes server whether the user can access a particular resource, resurce kind or generic URL. -A Kubernetes authorization policy config looks like the following in an Authorino `AuthConfig`: +It supports **resource attributes authorization check** (parameters defined in the `AuthConfig`) and **non-resource attributes authorization check** (HTTP endpoint inferred from the original request). +- Resource attributes: adequate for permissions set at namespace level, defined in terms of common attributes of operations on Kubernetes resources (namespace, API group, kind, name, subresource, verb) +- Non-resource attributes: adequate for permissions set at cluster scope, defined for protected endpoints of a generic HTTP API (URL path + verb) + +Example of Kubernetes role for resource attributes authorization: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: pet-reader +rules: +- apiGroups: ["pets.io"] + resources: ["pets"] + verbs: ["get"] +``` + +Example of Kubernetes cluster role for non-resource attributes authorization: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: pet-editor +rules: +- nonResourceURLs: ["/pets/*"] + verbs: ["put", "delete"] +``` + +Kubernetes authorization policy configs look like the following in an Authorino `AuthConfig`: ```yaml authorization: - name: kubernetes-rbac kubernetes: user: - valueFrom: # It can be a fixed value as well, by using `value` instead + valueFrom: # values of the parameter can be fixed (`value`) or fetched from the Auhtorization JSON (`valueFrom.authJSON`) authJSON: auth.identity.metadata.annotations.userid - groups: [] # User groups to test for. + groups: [] # user groups to test for. - resourceAttributes: # Omit it to perform a non-resource `SubjectAccessReview` based on the request's path and method (verb) instead - namespace: # other supported resource attributes are: group, resource, name, subresource and verb + # for resource attributes permission checks; omit it to perform a non-resource attributes SubjectAccessReview with path and method/verb assumed from the original request + # if included, use the resource attributes, where the values for each parameter can be fixed (`value`) or fetched from the Auhtorization JSON (`valueFrom.authJSON`) + resourceAttributes: + namespace: value: default + group: + value: pets.io # the api group of the protected resource to be checked for permissions for the user + resource: + value: pets # the resource kind + name: + valueFrom: { authJSON: context.request.http.path.@extract:{"sep":"/","pos":2} } # resource name – e.g., the {id} in `/pets/{id}` + verb: + valueFrom: { authJSON: context.request.http.method.@case:lower } # api operation – e.g., copying from the context to use the same http method of the request ``` -`user` and `resourceAttributes` can be specified as a fixed value or patterns to fetch from the Authorization JSON. +`user` and properties of `resourceAttributes` can be defined from fixed values or patterns of the Authorization JSON. -An array of required `groups` can as well be specified and it will be used in the `SubjectAccessReview`. +An array of `groups` (optional) can as well be set. When defined, it will be used in the `SubjectAccessReview` request. ### Keycloak Authorization Services (UMA-compliant Authorization API) diff --git a/docs/user-guides/kubernetes-subjectaccessreview.md b/docs/user-guides/kubernetes-subjectaccessreview.md index 6a7295e5..a9bd750b 100644 --- a/docs/user-guides/kubernetes-subjectaccessreview.md +++ b/docs/user-guides/kubernetes-subjectaccessreview.md @@ -8,14 +8,12 @@ Manage permissions in the Kubernetes RBAC and let Authorino to check them in req - Authorino can delegate authorization decision to the Kubernetes authorization system, allowing permissions to be stored and managed using the Kubernetes Role-Based Access Control (RBAC) for example. The feature is based on the `SubjectAccessReview` API and can be used for `resourceAttributes` or `nonResourceAttributes` (the latter inferring HTTP verb and method from the original request). + Authorino can delegate authorization decision to the Kubernetes authorization system, allowing permissions to be stored and managed using the Kubernetes Role-Based Access Control (RBAC) for example. The feature is based on the `SubjectAccessReview` API and can be used for `resourceAttributes` (parameters defined in the `AuthConfig`) or `nonResourceAttributes` (inferring HTTP path and verb from the original request). - Check out as well the user guides about [Authentication with Kubernetes tokens (TokenReview API)](./kubernetes-tokenreview.md), [Authentication with API keys](./api-key-authentication.md) and [Token normalization](./token-normalization.md). + Check out as well the user guide about [Authentication with Kubernetes tokens (TokenReview API)](./kubernetes-tokenreview.md). For further details about Authorino features in general, check the [docs](./../features.md). @@ -93,13 +91,7 @@ kubectl -n authorino port-forward deployment/envoy 8000:8000 & ## 6. Create the `AuthConfig` -The `AuthConfig` below defines: -- 2 sets of identities trusted to access the API: - - users that authenticate with API keys (**`api-key-users`**), and - - service accounts that authenticate with Kubernetes service account tokens (**`service-accounts`**); -- 2 authorization policies based on Kubernetes SubjectAccessReview: - - resource access reviews when the requested endpoint matches `/resources(/\w+)?` (**`resource-endpoints`**), and - - non-resource access reviews when the requested endpoint does not match `/resources(/\w+)?` (**`non-resource-endpoints`**) +The `AuthConfig` below sets all Kubernetes service accounts as trusted users of the API, and relies on the Kubernetes RBAC to enforce authorization using Kubernetes SubjectAccessReview API for non-resource endpoints: ```sh kubectl -n authorino apply -f -< /tmp/kind-cluster-user-cert.pem -yq r ~/.kube/config "users(name==$CURRENT_K8S_USER).user.client-key-data" | base64 -d > /tmp/kind-cluster-user-cert.key -``` - -Use the Kubernetes user's client TLS certificate to obtain a short-lived access token for the `api-consumer-1` `ServiceAccount`: - -```sh -export ACCESS_TOKEN=$(curl -k -X "POST" "$KUBERNETES_API/api/v1/namespaces/authorino/serviceaccounts/api-consumer-1/token" \ - --cert /tmp/kind-cluster-user-cert.pem --key /tmp/kind-cluster-user-cert.key \ - -H 'Content-Type: application/json; charset=utf-8' \ - -d $'{ "apiVersion": "authentication.k8s.io/v1", "kind": "TokenRequest", "spec": { "audiences": ["talker-api"], "expirationSeconds": 600 } }' | jq -r '.status.token') +Run a pod that consumes one of the greeting endpoints of the API from inside the cluster, as service account `api-consumer-1`, bound to the `talker-api-greeter` and `talker-api-speaker` cluster roles in the Kubernetes RBAC: + +```sh +kubectl -n authorino run greeter --attach --rm --restart=Never -q --image=quay.io/3scale/authorino-examples:api-consumer --overrides='{ + "apiVersion": "v1", + "spec": { + "containers": [{ + "name": "api-consumer", "image": "quay.io/3scale/authorino-examples:api-consumer", "command": ["./run"], + "args":["--endpoint=http://envoy.authorino.svc.cluster.local:8000/hi","--method=POST","--interval=0","--token-path=/var/run/secrets/tokens/api-token"], + "volumeMounts": [{"mountPath": "/var/run/secrets/tokens","name": "access-token"}] + }], + "serviceAccountName": "api-consumer-1", + "volumes": [{"name": "access-token","projected": {"sources": [{"serviceAccountToken": {"path": "api-token","expirationSeconds": 7200}}]}}] + } +}' -- sh +# Sending... +# 200 +``` + +Run a pod that sends a `POST` request to `/say/blah` from within the cluster, as service account `api-consumer-1`: + +```sh +kubectl -n authorino run speaker --attach --rm --restart=Never -q --image=quay.io/3scale/authorino-examples:api-consumer --overrides='{ + "apiVersion": "v1", + "spec": { + "containers": [{ + "name": "api-consumer", "image": "quay.io/3scale/authorino-examples:api-consumer", "command": ["./run"], + "args":["--endpoint=http://envoy.authorino.svc.cluster.local:8000/say/blah","--method=POST","--interval=0","--token-path=/var/run/secrets/tokens/api-token"], + "volumeMounts": [{"mountPath": "/var/run/secrets/tokens","name": "access-token"}] + }], + "serviceAccountName": "api-consumer-1", + "volumes": [{"name": "access-token","projected": {"sources": [{"serviceAccountToken": {"path": "api-token","expirationSeconds": 7200}}]}}] + } +}' -- sh +# Sending... +# 200 +``` + +Run a pod that sends a `POST` request to `/say/blah` from within the cluster, as service account `api-consumer-2`, bound only to the `talker-api-greeter` cluster role in the Kubernetes RBAC: + +```sh +kubectl -n authorino run speaker --attach --rm --restart=Never -q --image=quay.io/3scale/authorino-examples:api-consumer --overrides='{ + "apiVersion": "v1", + "spec": { + "containers": [{ + "name": "api-consumer", "image": "quay.io/3scale/authorino-examples:api-consumer", "command": ["./run"], + "args":["--endpoint=http://envoy.authorino.svc.cluster.local:8000/say/blah","--method=POST","--interval=0","--token-path=/var/run/secrets/tokens/api-token"], + "volumeMounts": [{"mountPath": "/var/run/secrets/tokens","name": "access-token"}] + }], + "serviceAccountName": "api-consumer-2", + "volumes": [{"name": "access-token","projected": {"sources": [{"serviceAccountToken": {"path": "api-token","expirationSeconds": 7200}}]}}] + } +}' -- sh +# Sending... +# 403 ``` -Consume the API as `api-consumer-1`, which is bound to the `talker-api-greeter`, `talker-api-speaker` and `talker-api-resource-reader` roles in the Kubernetes RBAC: - -```sh -curl -H "Authorization: Bearer $ACCESS_TOKEN" -X POST http://talker-api-authorino.127.0.0.1.nip.io:8000/hello -# HTTP/1.1 200 OK -``` +
+ Extra: consume the API as service account api-consumer-2 from outside the cluster -```sh -curl -H "Authorization: Bearer $ACCESS_TOKEN" -X POST http://talker-api-authorino.127.0.0.1.nip.io:8000/say/happy-to-be-here -# HTTP/1.1 200 OK -``` +
-```sh -curl -H "Authorization: Bearer $ACCESS_TOKEN" http://talker-api-authorino.127.0.0.1.nip.io:8000/resources/123 -# HTTP/1.1 200 OK -``` + To obtain access tokens to consume the API as service accounts from outside the cluster, start by proxying requests to the Kubernetes API: -Use the Kubernetes user's client TLS certificate to obtain a short-lived access token for the `api-consumer-2` `ServiceAccount`: + ```sh + kubectl proxy --port=8181 # holds the shell + ``` -```sh -export ACCESS_TOKEN=$(curl -k -X "POST" "$KUBERNETES_API/api/v1/namespaces/authorino/serviceaccounts/api-consumer-2/token" \ - --cert /tmp/kind-cluster-user-cert.pem --key /tmp/kind-cluster-user-cert.key \ - -H 'Content-Type: application/json; charset=utf-8' \ - -d $'{ "apiVersion": "authentication.k8s.io/v1", "kind": "TokenRequest", "spec": { "audiences": ["talker-api"], "expirationSeconds": 600 } }' | jq -r '.status.token') -``` + Then, obtain a short-lived access token for service account `api-consumer-2`, bound to the `talker-api-greeter` cluster role in the Kubernetes RBAC, using the Kubernetes TokenRequest API: -Consume the API as `api-consumer-2`, which is bound to the `talker-api-greeter` role in the Kubernetes RBAC: + ```sh + ACCESS_TOKEN=$(curl -k -X "POST" "http://localhost:8181/api/v1/namespaces/authorino/serviceaccounts/api-consumer-2/token" \ + -H 'Content-Type: application/json; charset=utf-8' \ + -d $'{ "apiVersion": "authentication.k8s.io/v1", "kind": "TokenRequest", "spec": { "expirationSeconds": 600 } }' | jq -r '.status.token') + ``` -```sh -curl -H "Authorization: Bearer $ACCESS_TOKEN" -X POST http://talker-api-authorino.127.0.0.1.nip.io:8000/hey -# HTTP/1.1 200 OK -``` + Consume the API as `api-consumer-2` from outside the cluster: -```sh -curl -H "Authorization: Bearer $ACCESS_TOKEN" -X POST http://talker-api-authorino.127.0.0.1.nip.io:8000/say/something -i -# HTTP/1.1 403 Forbidden -# x-ext-auth-reason: Not authorized: unknown reason -``` + ```sh + curl -H "Authorization: Bearer $ACCESS_TOKEN" -X POST http://talker-api-authorino.127.0.0.1.nip.io:8000/hello -i + # HTTP/1.1 200 OK + ``` -```sh -curl -H "Authorization: Bearer $ACCESS_TOKEN" http://talker-api-authorino.127.0.0.1.nip.io:8000/resources/123 -i -# HTTP/1.1 403 Forbidden -# x-ext-auth-reason: Not authorized: unknown reason -``` + ```sh + curl -H "Authorization: Bearer $ACCESS_TOKEN" -X POST http://talker-api-authorino.127.0.0.1.nip.io:8000/say/something -i + # HTTP/1.1 403 Forbidden + ``` +
## Cleanup diff --git a/docs/user-guides/kubernetes-tokenreview.md b/docs/user-guides/kubernetes-tokenreview.md index c2ecf323..b0a1f44b 100644 --- a/docs/user-guides/kubernetes-tokenreview.md +++ b/docs/user-guides/kubernetes-tokenreview.md @@ -123,23 +123,16 @@ EOF ## 8. Consume the API from outside the cluster -Get the Kubernetes API base endpoint and current Kubernetes user, and save the user's TLS certificate and TLS key to file: +Proxy requests to the Kubernetes API (holds the shell): ```sh -CURRENT_K8S_CONTEXT=$(kubectl config view -o json | jq -r '."current-context"') -CURRENT_K8S_USER=$(kubectl config view -o json | jq -r --arg K8S_CONTEXT "${CURRENT_K8S_CONTEXT}" '.contexts[] | select(.name == $K8S_CONTEXT) | .context.user') -CURRENT_K8S_CLUSTER=$(kubectl config view -o json | jq -r --arg K8S_CONTEXT "${CURRENT_K8S_CONTEXT}" '.contexts[] | select(.name == $K8S_CONTEXT) | .context.cluster') -KUBERNETES_API=$(kubectl config view -o json | jq -r --arg K8S_CLUSTER "${CURRENT_K8S_CLUSTER}" '.clusters[] | select(.name == $K8S_CLUSTER) | .cluster.server') - -yq r ~/.kube/config "users(name==$CURRENT_K8S_USER).user.client-certificate-data" | base64 -d > /tmp/kind-cluster-user-cert.pem -yq r ~/.kube/config "users(name==$CURRENT_K8S_USER).user.client-key-data" | base64 -d > /tmp/kind-cluster-user-cert.key +kubectl proxy --port=8181 ``` -Use the Kubernetes user's client TLS certificate to obtain a short-lived access token for the `api-consumer-1` `ServiceAccount`: +Obtain a short-lived access token for the `api-consumer-1` `ServiceAccount`: ```sh -export ACCESS_TOKEN=$(curl -k -X "POST" "$KUBERNETES_API/api/v1/namespaces/authorino/serviceaccounts/api-consumer-1/token" \ - --cert /tmp/kind-cluster-user-cert.pem --key /tmp/kind-cluster-user-cert.key \ +export ACCESS_TOKEN=$(curl -k -X "POST" "http://localhost:8181/api/v1/namespaces/authorino/serviceaccounts/api-consumer-1/token" \ -H 'Content-Type: application/json; charset=utf-8' \ -d $'{ "apiVersion": "authentication.k8s.io/v1", "kind": "TokenRequest", "spec": { "audiences": ["talker-api"], "expirationSeconds": 600 } }' | jq -r '.status.token') ``` @@ -173,7 +166,7 @@ metadata: spec: containers: - name: api-consumer - image: quay.io/3scale/authorino:api-consumer + image: quay.io/3scale/authorino-examples:api-consumer command: ["./run"] args: - --endpoint=http://envoy.authorino.svc.cluster.local:8000/hello