goauthentik / authentik

The authentication glue you need.
https://goauthentik.io
Other
13.73k stars 920 forks source link

How to use Kubernetes Service Accounts for Machine-to-Machine Authentication? #5393

Open dekarl opened 1 year ago

dekarl commented 1 year ago

Describe your question/ I'm trying to use Kubernetes Service Accounts to access an external minio service by presenting a service account token as inbound JWT to authentik, adding some claims for IAM policy mapping to an outbound JWT to use for access of minio via SDK.

Is this a supported use case? It appears that purely programmatic OIDC IDP are not supported yet.

I'd love to setup a webhook similar to https://github.com/aws/amazon-eks-pod-identity-webhook to automagically provide workloads with credentials to access minio object storage (or other services)

Relevant infos Kubernetes 1.25

https://github.com/gardener/service-account-issuer-discovery to host the OIDC discovery and JWKS

Screenshots If applicable, add screenshots to help explain your problem.

Logs Output of docker-compose logs or kubectl logs respectively

Version and Deployment (please complete the following information):

Additional context https://github.com/goauthentik/authentik/blob/main/website/docs/providers/oauth2/client_credentials.md

https://min.io/docs/minio/linux/administration/identity-access-management/oidc-access-management.html#authentication-and-authorization-flow

dekarl commented 1 year ago

I have played around a bit, creating a "Generic OpenID Connect" federation source with the WKS, filling all other values with "no".

Then I added an "OAuth2/OpenID Provider" to get a Client ID, trusting the federation provider for machine-to-machine auth.

Once I added an application to go with the provider I could authenticate with an inbound JWT and get an outbound JWT as documented in https://github.com/goauthentik/authentik/blob/main/website/docs/providers/oauth2/client_credentials.md A matching user is automatically generated identified as "-"

Missing part is verifying the issuer and audience of the inbound JWT. The former could be automatically configured from the OIDC discovery endpoint. But using that complains about missing URLs.

And adding a "policy" claim to the outbound JWT.

Is the following a valid plan?

If so, then the two requests to trade the tokens into different tokens can be encoded into a kubernetes mutating webhook to reads from service accounts and mutates pods so a workload can "just use the tokens" to go with the service account in a similar way to the AWS IRSA setup. The mapping between kubernetes service accounts and minio IAM policies would then happen in the authentik user pool "somehow".

dekarl commented 1 year ago

Just saw the example at https://goauthentik.io/blog/2023-03-30-JWT-a-token-that-changed-how-we-see-identity#why-use-jwts-in-authentik

Application A (running in Kubernetes) wants to access application B (secured behind authentik). App A takes the JWT that it gets from Kubernetes (which acts as an auth server), sends that JWT to authentik, and authentik verifies it against the signing key from Kubernetes. Then, based on which namespace or other criteria App A is running in, authentik can give or deny access to App B or any other applications that are using authentik - all without any passwords being entered.

let's document how all the pieces fit together

BeryJu commented 1 year ago

I meant to reply to this earlier, but yeah the steps you put in the second comment are basically exactly the steps to configure this.

The autogenerated user should have a correct username with a format of <provider_name>-<sub of the JWT>, for example tf-mimir-system:serviceaccount:monitoring-system:prometheus-prometheus

To do access control you can create a policy like this:

jwt = request.context.get("oauth_jwt", None)
if not jwt:
  return False

return jwt["sub"] == "system:serviceaccount:monitoring-system:prometheus-prometheus"

Which just verifies the sub, the issuer isn't verified in this config

dekarl commented 1 year ago

Thanks for getting back. I'm just putting the puzzle together one piece at a time. And I'm impressed with Authentik with every success I have.

E.g. now have added the addition of a dynamic policy claim from user/group attributes. It was easier then expected once I found out how the pieces go together. It's a very simple and elegant "policy mapping" of type "scope mapping". The claim looks good in the debugger, properly merging policies from user and a group together. "it just works"

return {
    "policy": user.group_attributes().get("minio:policy"),
}