This is a Proof of Concept of protecting Open Data Hub with Authorino external authorization on an OpenShift cluster.
- OpenShift cluster with the following operators installed:
- Kiali
- Jaeger
- OpenShift Service Mesh (OSSM)
- Open Data Hub
- Authorino
- CLI tools
kubectl
oc
jq
① Clone the repo
git clone [email protected]:guicassolato/odh-authorino.git && cd odh-authorino
② Store the OpenShift cluster domain in the shell
⚠️ This step is important as well for other parts of the tutorial further below. Do not skip it.
export CLUSTER_DOMAIN=gui-rhods.2hfs.s1.devshift.org
③ Login to the cluster
oc login --token=... --server=https://api.$CLUSTER_DOMAIN:6443
④ Configure the service mesh control plane
make smcp | kubectl apply -f -
sleep 4 # to prevent kubectl wait from failing
kubectl wait --for condition=Ready smcp/basic --timeout 300s -n istio-system
⑤ Deploy Authorino
export AUTH_NS=authorino
make authorino | kubectl apply -f -
Patch the service mesh configuration to register the new external authorization provider:
kubectl patch smcp/basic -n istio-system --type merge -p "{\"spec\":{\"techPreview\":{\"meshConfig\":{\"extensionProviders\":[{\"name\":\"auth-provider\",\"envoyExtAuthzGrpc\":{\"service\":\"authorino-authorino-authorization.$AUTH_NS.svc.cluster.local\",\"port\":50051}}]}}}}"
(Optional) Avoid injecting the sidecar proxy in the Authorino container:
kubectl wait --for condition=Available deployment/authorino --timeout 300s -n authorino
kubectl patch deployment/authorino -n authorino --type merge -p "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"sidecar.istio.io/inject\":\"false\"}}}}}"
⑥ Store useful OAuth endpoints in the shell
⚠️ This step is important for other parts of the tutorial further below. Do not skip it.
endpoint=$(kubectl -n default run oidc-config --attach --rm --restart=Never -q --image=curlimages/curl -- https://kubernetes.default.svc/.well-known/oauth-authorization-server -sS -k)
export AUTH_ENDPOINT=$(echo $endpoint | jq -r .authorization_endpoint)
export TOKEN_ENDPOINT=$(echo $endpoint | jq -r .token_endpoint)
① Deploy the sample application (Talker API)
make talker-api | kubectl apply -f -
② Test endpoints of the Talker API protected behind Authorino
Try the API without an access token:
curl http://talker-api.apps.$CLUSTER_DOMAIN -I
# HTTP/1.1 302 Found
# location: https://oauth-openshift.apps....
Try the API as the same user logged in to OpenShift cluster in the terminal:
The expected result is
403 Forbidden
because the token does not have the required scope, nor the user is bound to a role that grants permission.
curl -H "Authorization: Bearer $(oc whoami -t)" http://talker-api.apps.$CLUSTER_DOMAIN -I
# HTTP/1.1 403 Forbidden
(Optional) Inspect the token:
kubectl create -o json -f -<<EOF
apiVersion: authentication.k8s.io/v1
kind: TokenReview
spec:
token: $(oc whoami -t)
EOF
Check that the callback endpoint skips the authorization:
This will be useful in another step further below, when simulating a webapp (frontend + backend for frontend) that consumes the API.
curl http://talker-api.apps.$CLUSTER_DOMAIN/oauth/callback -I
# HTTP/1.1 200 OK
③ Try the API as a webapp that also runs inside the cluster
The Talker API itself will be used as the backend for frontend of the webapp, and the Internet browser and terminal as the frontend. The codes 🅱 and 🅵 will be used to identify in the commands below which of these components respectively the command simulates.
Request a protected endpoint of the API in the browser:
open http://talker-api.apps.$CLUSTER_DOMAIN
Login as a user of the OpenShift cluster and delegate powers to the service account.
🅱 Finish the OAuth flow in the terminal:
export OAUTH_CLIENT_SECRET=$(kubectl get $(kubectl get secrets -n talker-api -o name | grep talker-api-bff-token) -n talker-api -o jsonpath='{.data.token}' | base64 -d)
export ACCESS_TOKEN=$(curl -S -d client_id=system:serviceaccount:talker-api:talker-api-bff \
-d client_secret=$OAUTH_CLIENT_SECRET \
-d redirect_uri=http://talker-api.apps.${CLUSTER_DOMAIN}/oauth/callback \
-d grant_type=authorization_code \
-d code=… \
-d state=… \
$TOKEN_ENDPOINT | jq -r .access_token)
🅵 Send a request to the API as the webapp:
curl -H "Authorization: Bearer $ACCESS_TOKEN" http://talker-api.apps.$CLUSTER_DOMAIN -I
# HTTP/1.1 200 OK
(Optional) Inspect the token:
kubectl create -o json -f -<<EOF
apiVersion: authentication.k8s.io/v1
kind: TokenReview
spec:
token: $ACCESS_TOKEN
EOF
④ Try the API as the talker-api-bff
SA itself (without delegation)
Request a short-lived token for the SA:
This step could be replaced by other methods for the application to obtain the token, such as volume projection.
export SA_TOKEN=$(kubectl create --raw /api/v1/namespaces/talker-api/serviceaccounts/talker-api-bff/token -f -<<EOF | jq -r .status.token
{ "apiVersion": "authentication.k8s.io/v1", "kind": "TokenRequest", "spec": { "expirationSeconds": 600 } }
EOF
)
Send a GET request to the API:
curl -H "Authorization: Bearer $SA_TOKEN" http://talker-api.apps.$CLUSTER_DOMAIN -I
# HTTP/1.1 200 OK
Send a POST request to the API:
curl -H "Authorization: Bearer $SA_TOKEN" http://talker-api.apps.$CLUSTER_DOMAIN -I -X POST
# HTTP/1.1 403 Forbidden
(Optional) Inspect the token:
kubectl create -o json -f -<<EOF
apiVersion: authentication.k8s.io/v1
kind: TokenReview
spec:
token: $SA_TOKEN
EOF
jq -R 'split(".") | .[0],.[1] | @base64d | fromjson' <<< $(echo "$SA_TOKEN")
TODO(@guicassolato)
make authorino | kubectl delete -f -
make talker-api | kubectl delete -f -
make smcp | kubectl delete -f -