Structured Authentication Config in Kubernetes v1.29: What does it bring?

KEP 3331 (Structured Authentication Config) is the most significant Kubernetes authentication system update for the last 6 years, and it’s ambitious enough to meet all user demands. Its alpha version is supposed to be included in the Kubernetes v1.28 release (scheduled for August 15th)*. The KEP itself has already been merged, but as we all know, plans are prone to change. In this introduction, I’ll explain why this enhancement emerged and what it will change for K8s users.

* UPDATE (July 25th, 2023): While the KEP itself was merged for the upcoming v1.28 release, the Structured Authentication Config source code implemented in this issue was not in the merge-ready state by code-freeze. Thus it will appear in the next Kubernetes release (v1.29) only.

Disclaimer: As a Kubernetes SIG Auth member and a Dex core maintainer, I was directly involved in developing the feature with engineers from Google and Microsoft.

Background

To see what advantages Structured Authentication Config offers, let’s first review the existing user authentication methods:

  • Static token file. This method means the tokens are stored in a static file in unencrypted form. As such, it does not conform to the CIS Benchmark recommendations.
  • X509 client certs. It’s a suitable and secure method. However, certificates cannot be revoked as of now. So we’ll either have to wait for the issued certificate to expire or run a complex certificate replacement mechanism across the Kubernetes cluster. This option is not viable for large installations with many users and K8s clusters.
  • OpenID Connect tokens, authenticating proxy, and webhook token authentication are excellent alternatives, with each having its own pros. However, with the introduction of a Structured Authentication Configuration, you might not need any of them.

Well, it’s time to reveal the advantages of this new feature in Kubernetes. With Structured Authentication Configuration, you can…:

  1. Use not only OIDC tokens but also any JWT-compliant ones;
  2. Change the configuration dynamically. The path to the file where the Structured Authentication Config is stored is passed as a flag, and the Kubernetes API automatically updates the settings if changes are made to this file;
  3. Use multiple authentication providers (e.g., Okta, Keycloak, GitLab) simultaneously — no need to rely on tools like Dex;
  4. Use CEL (Common Expression Language) to find out whether the token’s claims match the user’s attributes in Kubernetes (username, group).

Sounds convincing enough? Let’s take a closer look at how you can use it.

Using Structured Authentication Config

Here is an example of a Structured Authentication Config:

apiVersion: apiserver.config.k8s.io/v1alpha1
kind: AuthenticationConfiguration
jwt:
- issuer:
    url: https://example.com
    clientIDs:
    - my-app
  claimMappings: {...}
  claimValidationRules: [...]
  userInfoValidationRules: [...]

Such config file might contain various authentication providers, passed as an array to the JWT field. You can implement rules to extract information about users via claimMappings:

claimMappings:
  username:
    expression: 'claims.username + ":external-user"'
  groups:
    expression: 'claims.roles.split(",")'
  uid:
    claim: 'sub'
  extra:
  - key: '"client_name"'
    value: 'claims.aud'

In the example above, we used CEL to define the following two rules:

  1. User roles that are passed as a single string are automatically split into array elements by a comma delimiter (claims.roles.split(",")).
  2. The :external-user postfix is appended to the username of each external user. If you use your own authorization and role assignment system instead of RBAC, you can pass such attributes to authorization webhooks, which is handy.

Now let’s take a look at the validation rules used for authentication. The Kubernetes documentation states that the system: prefix is reserved for internal use in Kubernetes, and the administrator has to make sure that the cluster doesn’t have any users or groups with names starting with system:.

Suppose there is LDAP and a system:masters group, which we set for a new user during registration:

  • cn: system:masters
  • gidNumber: 500
  • memberUid: John Doe

If John logs in via Dex, the system:masters group will be added to his group list, making them a cluster superuser who can do whatever he wants. Hardly anyone is going to do something like that intentionally, but it’s still a pretty realistic scenario.

{"level":"info","msg":"login successful: connector \"ldap-local\", username=\"jdoe\", preferred_username=\"\", email=\"john.doe@example.com\", groups=[\"system:masters\" \"developers:maintainers\"]","time":"2023-07-14T09:00:00Z"}

ATTRIBUTE   VALUE
Username    john.doe@example.com
Groups      [system:masters developers:maintainers system:authenticated]

Prior to the emergence of the Structured Authentication Config, the following validation methods could be used for authentication:

  • You could set the prefix via the --oidc-groups-prefix parameter.
  • You could use filters — for example, connectors have a group filter in Dex.
connectors:
  - type: gitlab
    id: gitlab
    name: GitLab
    config:
      # If `groups` is provided, this acts as a whitelist - only the user's GitLab groups
      # that are in the configured `groups` below will go into the groups claim.
      # Conversely, if the user is not in any of the configured `groups`, the user
      # will not be authenticated.
      
      groups:
      - my-group

Now, let’s see how this works in a Structured Authentication Config. There are claimValidationRules. You can check the inbound claims and specify in the rules that there are mandatory fields and fields whose values must comply with certain restrictions. This way, you can identify and weed out invalid tokens while retrieving claims from tokens:

claimValidationRules:
- claim: hd
  requiredValue: example.com
- expression: 'claims.hd == "example.com"'
  message: the hd claim must be set to example.com
- expression: 'claims.exp - claims.nbf <= 86400'
  message: total token lifetime must not exceed 24 hours

As for the system group issue, there are userInfoValidationRules in the Structured Authentication Config — you can use them to ensure that no user name or group name starts with the system.

userInfoValidationRules:
- rule: "!userInfo.username.startsWith('system:')"
  message: username cannot used reserved system: prefix
- rule: "userInfo.groups.all(group, !group.startsWith('system:'))"
  message: groups cannot used reserved system: prefix

Summary

Let’s summarize the capabilities the Structured Authentication Config has:

  • A new kube-apiserver flag that passes the link to the authentication configuration file;
  • Dynamic application of a configuration file after it has been modified;
  • Simultaneous use of multiple OIDC providers;
  • CEL support;
  • Rules for token validation;
  • Rules for retrieving information from a token and applying it to user attributes;
  • Structured configuration, i.e. you can use regular versioning instead of messing around with many flags.

You can find more information about Kubernetes Structured Authentication Config in KEP-3331. Give it a try by enabling the StructuredAuthenticationConfiguration feature gate when Kubernetes v1.28 lands, and let us know about your experience!

P.S.

Previously, I also authored the “Auth API to get self user attributes” feature (KEP-3325). Introduced as alpha in Kubernetes 1.26, it is expected to become stable in the upcoming v1.28.

Comments

Your email address will not be published. Required fields are marked *