{"id":23591464,"url":"https://github.com/dvob/k8s-s2s-auth","last_synced_at":"2025-05-07T17:13:39.311Z","repository":{"id":64302711,"uuid":"308339531","full_name":"dvob/k8s-s2s-auth","owner":"dvob","description":"Kubernetes Service-to-Service Authentication using Service Accounts","archived":false,"fork":false,"pushed_at":"2021-03-30T09:46:51.000Z","size":71,"stargazers_count":17,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-05-07T17:13:32.547Z","etag":null,"topics":["authentication","golang","jwt","keycloak","kubernetes","oidc","pod","service-to-service","serviceaccount","tokenreview","vault"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dvob.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-29T13:37:44.000Z","updated_at":"2025-04-16T20:57:59.000Z","dependencies_parsed_at":"2023-01-15T09:45:33.743Z","dependency_job_id":null,"html_url":"https://github.com/dvob/k8s-s2s-auth","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dvob%2Fk8s-s2s-auth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dvob%2Fk8s-s2s-auth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dvob%2Fk8s-s2s-auth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dvob%2Fk8s-s2s-auth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dvob","download_url":"https://codeload.github.com/dvob/k8s-s2s-auth/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252922335,"owners_count":21825639,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","golang","jwt","keycloak","kubernetes","oidc","pod","service-to-service","serviceaccount","tokenreview","vault"],"created_at":"2024-12-27T07:39:09.144Z","updated_at":"2025-05-07T17:13:39.294Z","avatar_url":"https://github.com/dvob.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Kubernetes Service Accounts\nService accounts are well known in Kubernetes to access the Kubernets API from within the cluster. This is often used for infrastructure components like operators and controllers. But we can also use service accounts to implement authentication in our own applications.\n\nThis README tries to give an overview on how service accounts work and and shows a couple of variants how you can use them for authentication. Further this repository contains an example Go service which shows how to implement the authentication in an application.\n\nIf you have questions, feedback or if you want to share your expirience using these features feel free to start a [discussion](https://github.com/dvob/k8s-s2s-auth/discussions).\n\n* [Tutorial](#tutorial)\n  * [Scenario](#scenario)\n  * [Setup Cluster](#setup-cluster)\n    * [minikube](#minikube)\n  * [Service 1 (Client)](#service-1-client)\n  * [Service 2 (Server)](#service-2-server)\n    * [TokenReview](#tokenreview)\n    * [JWT](#jwt)\n    * [Disadvantages](#disadvantages)\n    * [TokenRequestProjection](#tokenrequestprojection)\n    * [ServiceAccountIssuerDiscovery](#serviceaccountissuerdiscovery)\n* [Appendix](#appendix)\n  * [Links](#links)\n  * [Mentioned Kubernetes Features](#mentioned-kubernetes-features)\n\n# Tutorial\n## Scenario\nIn our tutorial we look at a simple scenario with to services:\n* Service 1 (Client)\n* Service 2 (Server)\n\nService1 wants to call Service2. Service2 shall only respond to authenticated requests.\n\n## Setup Cluster\nSetup a test cluster with kind, minikube or another tool of your choice. At a later point we want to explore the TokenRequestProjection feature, thats why we have set the following options on the API server:\n* `--service-account-issuer=yourIssuer`\n* `--service-account-signing-key-file=pathToServiceAccountKey`\n\nFor this tutorial I used Kubernetes 1.20.2 and an earlier version also ran on 1.19.4 but for this you have to set additional options (see in Git history).\n\n### minikube\n```\nminikube start --kubernetes-version v1.20.2 \\\n\t--extra-config=apiserver.service-account-issuer=https://kubernetes.default.svc \\\n\t--extra-config apiserver.service-account-signing-key-file=/var/lib/minikube/certs/sa.key\n```\n\n## Service 1 (Client)\nFirst we want to deploy Service 1 and see how Service 1 can get a service account token which it later needs to authenticate itself to Service 2:\n```\nkubectl create ns mytest\n\n# set context to the new namespace\nkubectl config set-context $( kubectl config current-context ) --namespace mytest\n```\n\nIf we create a new namespace the service account controller creates the default service account for us. Further the token controller creates a secret which contains the service account token.\nWe verify that the default service account and its token got created and extract the token from the secret:\n```\nkubectl get serviceaccount default\n\ntoken_name=$( kubectl get serviceaccounts default --template '{{ ( index .secrets 0 ).name }}' )\n\nkubectl get secret $token_name\n\ntoken=$( kubectl get secret $token_name --template '{{ .data.token }}' | base64 -d )\necho $token\n```\n\nThe service account token is a JWT so the claims in the payload can be inspected as follows:\n```\necho $token | cut -d . -f 2 | base64 -d | jq\n```\n```\n{\n  \"iss\": \"kubernetes/serviceaccount\",\n  \"kubernetes.io/serviceaccount/namespace\": \"mytest\n  \"kubernetes.io/serviceaccount/secret.name\": \"default-token-bwsb6\",\n  \"kubernetes.io/serviceaccount/service-account.name\": \"default\",\n  \"kubernetes.io/serviceaccount/service-account.uid\": \"9c2dc042-3543-4cc6-a458-542c0f56a324\",\n  \"sub\": \"system:serviceaccount:mytest:default\"\n}\n```\n\nIf we create a pod without specifiyng anything concering servcie accounts the service account admission controller sets certain defaults for us:\n* sets the service account itself\n* adds a volume with the token to to the pod\n* add volume mount for each container which makes the token available under `/var/run/secrets/kubernetes.io/serviceaccount/token`\n\nLets verify this by creating a pod which prints out the token:\n```\nkubectl create -f - \u003c\u003cEOF\napiVersion: v1\nkind: Pod\nmetadata:\n  name: read-token\nspec:\n  restartPolicy: Never\n  containers:\n  - name: read-token\n    image: busybox\n    command: ['cat', '/var/run/secrets/kubernetes.io/serviceaccount/token']\nEOF\n```\n\nAfter the creation of the pod we can see that the service account, the volume and the volume mount got set:\n```\n# look for .spec.serviceAccountName, .spec.volumes and .spec.containers[0].volumeMounts\nkubectl get pod read-token -o yaml\n```\n\nWait until the pod has completed and then verify that the pod printed out the service account token:\n```\n# wait for completed state\nkubectl get pod -w\n\n# verifiy that the pod could read the token\nkubectl logs read-token\n```\n\nWe could see how we get obtain a token in a pod and now we start Service 1 which will read the token and send it to Service 2.\n```\nkubectl apply -f - \u003c\u003cEOF\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: service1\n  name: service1\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: service1\n  template:\n    metadata:\n      labels:\n        app: service1\n    spec:\n      containers:\n      - name: service1\n        image: dvob/k8s-s2s-auth\n        args:\n        - client\n        - http://service2.mytest.svc\nEOF\n```\nSince we haven't started Service 2 yet the requests will fail, but we'll change this in a moment.\n\n## Service 2 (Server)\nService 1 sends its token to Service 2 in the HTTP Authorization header:\n```\nGET / HTTP/1.1\nHost: service2.mytest.svc:8080\nAuthorization: Bearer eyJhbGciOiJSUzI1...\u003cshortened\u003e...NiIsImtpZKRavXR4H3UQ\nUser-Agent: Go-http-client/1.1\n```\n\nService 2 now shall authenticate the requests so it somehow has to validate the token from the HTTP header. There are multiple ways to do this:\n* Token Review API\n* Verify Signature of the token (JWT)\n\n### TokenReview\nThe API server has the public key to verify the tokens. External parties can send tokens to the token review API to verify if they are valid. That's what we try out next. For this we send a `TokenReview` with a token (we use `$token` which we've extracted before) to the API server. Since this action is not available as part of `kubectl` we use curl.\n\nFor that we first have to extract the pathes to the credentials from the kube config:\n```\nURL=$( kubectl config view --raw -o json | jq -r '.clusters[] | select( .name == \"minikube\") | .cluster.server' )\nCA=$( kubectl config view --raw -o json | jq -r '.clusters[] | select( .name == \"minikube\") | .cluster[\"certificate-authority\"]' )\nCERT=$( kubectl config view --raw -o json | jq -r '.users[] | select( .name == \"minikube\") | .user[\"client-certificate\"]' )\nKEY=$( kubectl config view --raw -o json | jq -r '.users[] | select( .name == \"minikube\") | .user[\"client-key\"]' )\n```\n\nThen we can send the token (`$token`) as part of the `TokenReview` to the API server:\n```\ncurl --cacert \"$CA\" --cert \"$CERT\" --key \"$KEY\" --request POST --header \"Content-Type: application/json\" \"$URL/apis/authentication.k8s.io/v1/tokenreviews\" --data @- \u003c\u003cEOF\n{\n  \"kind\": \"TokenReview\",\n  \"apiVersion\": \"authentication.k8s.io/v1\",\n  \"spec\": {                                \n    \"token\": \"$token\"\n  }\n}\nEOF\n```\n\nAs a response we get a TokenReview again but with the result of the review in the status. We see that the token is authenticated together with information about the token owner like its username and groups:\n```json\n{\n  \"kind\": \"TokenReview\",\n  \"apiVersion\": \"authentication.k8s.io/v1\",\n  \"spec\": {\n    \"token\": \"eyJhbGciOiJSUzI1NiIsImtpZCI6Imp4cWhWd2xGal82eTNWQ1loZ25TSG5SVFZxa1llZEZySjNRVW5WV1F2XzgifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJzMSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkZWZhdWx0LXRva2VuLWJ3c2I2Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImRlZmF1bHQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI5YzJkYzA0Mi0zNTQzLTRjYzYtYTQ1OC01NDJjMGY1NmEzMjQiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6czE6ZGVmYXVsdCJ9.CggLOjeMQjh_GoLkUHSyLJqC2jYlKyZHRzT86GrfmgYP3uMgNGfkUk7j2llOQEGLko-TRmTCby3PrioZd-3MdM2vYa9ZQBsP8GcZZNEHvQ8zY5qEiBpsh_KFuMoXb--nNVlz6uyZbZEo5bewWCzdFRKTU8OuZZGAnkDQXNB3E_WzB2KunIjmIZ_iUdWYwaTUmslvu7TBNV27PhQf1krj6Go0TwlhFoWd_wXEYiSl6qOkkCqzaACnKcJ2aKzaPPdOWj0GOR6PsdkN-LQcpr5-M3Pw3rihjQgB34HUNCHc_du3xeQRSsRM4Q1iU-mS-vLZ5VXs2KHnedOcngVCOp_NAQ\"\n  },\n  \"status\": {\n    \"authenticated\": true,\n    \"user\": {\n      \"username\": \"system:serviceaccount:mytest:default\",\n      \"uid\": \"9c2dc042-3543-4cc6-a458-542c0f56a324\",\n      \"groups\": [\n        \"system:serviceaccounts\",\n        \"system:serviceaccounts:mytest\",\n        \"system:authenticated\"                                                       \n      ]\n    }        \n  }          \n}\n```\n\nIf the `--service-account-lookup` option is enabled on the API server (which is the default), the token review API does not only verify the signature of the token but also checks if the service account still exists.\nIf we remove the default service account and call the token review API again we get back an error in the status of the token review:\n```\nkubectl delete sa default\n```\n```\n  \"kind\": \"TokenReview\",\n  \"apiVersion\": \"authentication.k8s.io/v1\",\n  ...\n  \"status\": {\n    \"user\": {},\n    \"error\": \"[invalid bearer token, Token has been invalidated]\"\n  }\n```\n\nNow start Service 2 to see the token review in action. For this we create a deployment, a service and the appropriate clusterrolebinding which lets us use the token review API:\n```\nkubectl apply -f - \u003c\u003cEOF\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: service2\n  labels:\n    app: service2\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: service2\n  template:\n    metadata:\n      labels:\n        app: service2\n    spec:\n      containers:\n      - name: service2\n        image: dvob/k8s-s2s-auth\n        args:\n        - server\n        - --mode\n        - tokenreview\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: service2\n  labels:\n    app: service2\nspec:\n  ports:\n  - port: 80\n    protocol: TCP\n    targetPort: 8080\n  selector:\n    app: service2\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  creationTimestamp: null\n  name: auth-delegator\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: system:auth-delegator\nsubjects:\n- kind: ServiceAccount\n  name: default\n  namespace: mytest\nEOF\n```\n\nIf we now take a look at the logs of Service 1 we no longer see `no such host` errors but errors that our Token has been invalidated:\n```\nkubectl logs -f -l app=service1\n```\n```\n...\n2021/03/30 08:34:49 Get \"http://service2.mytest.svc\": dial tcp: lookup service2.mytest.svc on 10.96.0.10:53: no such host\n2021/03/30 08:34:54 Get \"http://service2.mytest.svc\": dial tcp: lookup service2.mytest.svc on 10.96.0.10:53: no such host\n2021/03/30 08:34:59 target=http://service2.mytest.svc, status=401, response: 'Unauthorized: [invalid bearer token, Token has been invalidated]'\n2021/03/30 08:35:04 target=http://service2.mytest.svc, status=401, response: 'Unauthorized: [invalid bearer token, Token has been invalidated]'\n...\n```\nThat is because we deleted the service account before which invalidated the token. To get rid of the error we have to restart Service 1.\n```\nkubectl delete pod -l app=service1\n```\nIf we now look into the logs of Service 1 again we see that our requests get authenticated correctly and we get back a HTTP 200 from Service 2.\n```\nkubectl logs -f -l app=service1\n```\n```\n2021/03/30 08:38:39 start client: http://service2.mytest.svc\n2021/03/30 08:38:39 target=http://service2.mytest.svc, status=200, response: 'hello system:serviceaccount:mytest:default'\n2021/03/30 08:38:44 target=http://service2.mytest.svc, status=200, response: 'hello system:serviceaccount:mytest:default'\n...\n```\n\n### JWT\nThe token review API is not really a standard. However, JWTs is a standard and many frameworks and libraries support authentication using JWTs.\nTo validate a token we need the public key of the private key which was used to sign the service account token.\nWe find the public key on the Kube API server, since it is used there to authenticate requests from service accounts and to answer token review API requests.\nThe location of the public key is configured with the option `--service-account-key-file`. In a minikube setup we can find the location as follows:\n```\nkubectl -n kube-system get pod kube-apiserver-minikube -o yaml | grep service-account-key-file\n```\n```\n    - --service-account-key-file=/var/lib/minikube/certs/sa.pub\n```\n\nCopy the public key to our host:\n```\nminikube ssh sudo cat /var/lib/minikube/certs/sa.pub \u003e sa.pub\n```\n\nVerify the signature of the service account token we have extracted before:\n```\nopenssl dgst -sha256 -verify sa.pub -signature \u003c( echo -ne $token | awk -F. '{ printf(\"%s\", $3) }' | tr '\\-_' '+/' | base64 -d ) \u003c( echo -ne $token | awk -F. '{ printf(\"%s.%s\", $1, $2) }' )\n```\nThe decoding of the signature shows an error `base64: invalid input` because in the JWT signature the padding (`=`) is removed and `base64` does not like that. Nevertheless the verification should still work and show `Verfied OK`. We also have to convert the signature which is base64url encoded signature to a base64 so that base64 can decode it.\n\nNow we can start Service 2 with the `--mode` option set to `jwt-pubkey` to see the authentication with the public key in action. We do this just locally that we don't have to create a configmap for `sa.pub`.\n```\ndocker run -it -p 8080:8080 --rm -v $(pwd)/sa.pub:/sa.pub dvob/k8s-s2s-auth server --mode jwt-pubkey --pub-key /sa.pub\n```\n\nTest it with curl:\n```\ncurl -H \"Authorization: Bearer $token\" http://localhost:8080\n```\n\n### Disadvantages\nWe have now seen how we can use service accounts to implement authentication. But the shown methods have some drawbacks:\n\nTokenReview:\n* Needs additional request to the API server\n* The application has to talk to the Kubernetes API\n* Token never expires unless you delete/recreate the service account\n\nJWT:\n* Tokens never expire (no `exp` field in JWT)\n* Tokens have no audience (no `aud` field in JWT)\n* Copying the service account public key (`sa.pub`) from the API server to all services which have to do authentication is not optimal\n\n### TokenRequestProjection\nThe TokenRequestProjection feature enables the injection of service account tokens into a Pod through a projected volume.\n\nIn contrast to the service account tokens we've used before, these tokens have the following benefits:\n* Tokens expire (`exp` claim in JWT is set)\n* Tokens are bound to an audience (`aud` claim in JWT)\n* Tokens are never stored as secret but directly injeted into the pod from `kubelet`\n* Tokens are bound to a pod. When the pod gets deleted the token review API no longer treats the token as valid\n\nCreate a pod with a projected service account volume:\n```\nkubectl create -f - \u003c\u003cEOF\napiVersion: v1\nkind: Pod\nmetadata:\n  name: read-token2\nspec:\n  restartPolicy: Never\n  containers:\n  - name: read-token\n    image: busybox\n    command: ['cat', '/var/run/secrets/tokens/service2']\n    volumeMounts:\n    - mountPath: /var/run/secrets/tokens\n      name: service2-token\n  serviceAccountName: default\n  volumes:\n  - name: service2-token\n    projected:\n      sources:\n      - serviceAccountToken:\n          path: service2\n          expirationSeconds: 600\n          audience: service2\nEOF\n```\n\nWait until the Pod has completed and take a look at the claims of our new token:\n```\nkubectl logs read-token2 | cut -d. -f2 | base64 -d | jq\n```\n\nSince we're now able to create tokens for specific audiences we update service2 to only accept tokens for the audience 'service2':\n```\nkubectl apply -f - \u003c\u003cEOF\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: service2\n  labels:\n    app: service2\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: service2\n  template:\n    metadata:\n      labels:\n        app: service2\n    spec:\n      containers:\n      - name: service2\n        image: dvob/k8s-s2s-auth\n        args:\n        - server\n        - --mode\n        - tokenreview\n        - --audience\n        - service2\nEOF\n```\n\nNow Service 1 is no longer able to send requests to Service 2 because Service 2 requires that the audience is `service2`. You can verify this in the logs of Service 1.\nWe now update Service 1 so that it uses a projected service account volume with the appropriate audience:\n```\nkubectl apply -f - \u003c\u003cEOF\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: service1\n  name: service1\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: service1\n  template:\n    metadata:\n      labels:\n        app: service1\n    spec:\n      containers:\n      - name: service1\n        image: dvob/k8s-s2s-auth\n        args:\n        - client\n        - http://service2.mytest.svc\n        - --token-file\n        - /var/run/secrets/tokens/service2\n        volumeMounts:\n          - mountPath: /var/run/secrets/tokens\n            name: service2-token\n      volumes:\n      - name: service2-token\n        projected:\n          sources:\n          - serviceAccountToken:\n              path: service2\n              expirationSeconds: 600\n              audience: service2\nEOF\n```\nNow you can verify in the logs of Service 1 that it again is able to authenticate itself to Servcice 2.\n\n### ServiceAccountIssuerDiscovery\nThe ServiceAccountIssuerDiscovery is available as a beta feature since Kubernetes 1.20. It allows to fetch the public key from the API server to verfiy the JWT signatures. For this it uses the OpenID Connect Discovery mechanism. Since this is a standard it is supported by many libraries and frameworks.\n\nAllow access to the discovery endpoint:\n```\nkubectl apply -f - \u003c\u003cEOF\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: system:service-account-issuer-discovery\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: system:service-account-issuer-discovery\nsubjects:\n- apiGroup: rbac.authorization.k8s.io\n  kind: Group\n  name: system:unauthenticated\nEOF\n```\n\nFetch the openid-configuration URL (see above on how to set the environment variables):\n```\ncurl --cacert \"$CA\" --cert \"$CERT\" --key \"$KEY\" \"$URL/.well-known/openid-configuration\"\n```\n```json\n{\n  \"issuer\": \"https://kubernetes.default.svc\",\n  \"jwks_uri\": \"https://192.168.49.2:8443/openid/v1/jwks\",\n  \"response_types_supported\": [\n    \"id_token\"\n  ],\n  \"subject_types_supported\": [\n    \"public\"\n  ],\n  \"id_token_signing_alg_values_supported\": [\n    \"RS256\"\n  ]\n}\n```\n```\njwks_uri=$(curl --cacert \"$CA\" --cert \"$CERT\" --key \"$KEY\" \"$URL/.well-known/openid-configuration\" | jq -r .jwks_uri )\n```\n\nFrom here we fetch the JWKS (JSON Web Key Set) URL `jwks_uri` from which we can download the actual public key:\n```\ncurl --cacert \"$CA\" $jwks_uri\n```\n```\n{\n  \"keys\": [\n    {\n      \"use\": \"sig\",\n      \"kty\": \"RSA\",\n      \"kid\": \"iUPSHSAprvOukTss4IlKZ8VVrMOy4G4NqXxBT-3ae-o\",\n      \"alg\": \"RS256\",\n      \"n\": \"pshH9GeIJcuUDbJwP9oummjXcJcMh8bIXTM9GT2sMx8P7CgyKrp0XXLghpYJB_Kqar8jHo1U-B2QWKI-rIyS7Nx9CfpENhnLWDcj2XZmC3gw2X93e9ZYM74xyvvCFGnu34RMS0TjaQtQRaFVnwmxmjK0sHbwwMq8rfqRRyr8Rg-9yz03TQdUMeSfUTE-I-bykX7F_XezFRAFwgOR-ZCMAcq4BrB4j0l2OH5v3QmNXAVr_ytSxEF-yrrP3oUwauLfIBo-xxHcWJfnOdaZ25DiH5zY3TilA3F4FP3vbiNApMZaJBwvrypUxHBnVf-cmBNbfuRl6o6ZgLYZHAMVm1mAXw\",\n      \"e\": \"AQAB\"\n    }\n  ]\n}\n```\n\nTo see this in action we update the Service 2 deployment and change the `--mode` option to `oidc-discovery`.\n```\nkubectl apply -f - \u003c\u003cEOF\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: service2\n  labels:\n    app: service2\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: service2\n  template:\n    metadata:\n      labels:\n        app: service2\n    spec:\n      containers:\n      - name: service2\n        image: dvob/k8s-s2s-auth\n        args:\n        - server\n        - --mode\n        - oidc-discovery\n        - --issuer-url\n        - https://kubernetes.default.svc\n        - --ca\n        - /var/run/secrets/kubernetes.io/serviceaccount/ca.crt\n        - --audience\n        - service2\nEOF\n```\nWe also have to set the `--ca` variable so that Service 2 trusts the certificate under https://kubernetes.default.svc. Service 2 now during the startup connects to the OIDC disovery endpoints and gets the jwks_uri. Then it downloads the public key from the JWKS URI endpoint which it then uses to validate the tokens.\n\nThis way your application can rely entirely on OIDC standards and no longer has to talk to Kubernetes APIs.\n\n# Appendix\n## Links\n* Service Accounts\n  * https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/\n* TokenRequestProjection\n  * https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#service-account-token-volume-projection\n  * https://github.com/kubernetes/community/blob/master/contributors/design-proposals/storage/svcacct-token-volume-source.md\n  * https://jpweber.io/blog/a-look-at-tokenrequest-api/\n* ServiceAccountIssuerDiscovery\n  * https://github.com/kubernetes/enhancements/blob/master/keps/sig-auth/20190730-oidc-discovery.md\n\n## Mentioned Kubernetes Features\n\nFeature | Stage | Version | Description\n--- | --- | --- | ---\nTokenRequest | GA | 1.20 | Enable the TokenRequest endpoint on service account resources.\nTokenRequestProjection | GA | 1.20 | Enable the injection of service account tokens into a Pod through the projected volume.\nServiceAccountIssuerDiscovery | beta | 1.20 | Enable OIDC discovery endpoints (issuer and JWKS URLs) for the service account issuer in the API server.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdvob%2Fk8s-s2s-auth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdvob%2Fk8s-s2s-auth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdvob%2Fk8s-s2s-auth/lists"}