From f81842d0ed6a62e519c823673a9ef7f78465e70f Mon Sep 17 00:00:00 2001 From: Chad Swenson Date: Mon, 20 Apr 2026 21:55:52 -0500 Subject: [PATCH] Implement structured authentication configuration for API server (#13035) --- .../group_vars/k8s_cluster/k8s-cluster.yml | 15 +++++++ roles/kubernetes/control-plane/tasks/main.yml | 20 +++++++++ .../templates/kubeadm-config.v1beta4.yaml.j2 | 16 +++++-- .../kubespray_defaults/defaults/main/main.yml | 42 ++++++++++++------- 4 files changed, 74 insertions(+), 19 deletions(-) diff --git a/inventory/sample/group_vars/k8s_cluster/k8s-cluster.yml b/inventory/sample/group_vars/k8s_cluster/k8s-cluster.yml index 1a18db527..881e244d0 100644 --- a/inventory/sample/group_vars/k8s_cluster/k8s-cluster.yml +++ b/inventory/sample/group_vars/k8s_cluster/k8s-cluster.yml @@ -53,6 +53,21 @@ credentials_dir: "{{ inventory_dir }}/credentials" # kube_oidc_groups_claim: groups # kube_oidc_groups_prefix: 'oidc:' +## Structured AuthenticationConfiguration https://kubernetes.io/docs/reference/access-authn-authz/authentication/#using-authentication-configuration +## Note: --authentication-config and --oidc-* flags are mutually exclusive +# kube_apiserver_use_authentication_config_file: false +# kube_apiserver_authentication_config_jwt: +# - issuer: +# url: https://issuer.example.com +# audiences: +# - my-audience +# claimMappings: +# username: +# expression: 'claims.sub' +# kube_apiserver_authentication_config_anonymous: +# enabled: "{{ kube_api_anonymous_auth }}" +# conditions: [] + ## Variables to control webhook authn/authz # kube_webhook_token_auth: false # kube_webhook_token_auth_url: https://... diff --git a/roles/kubernetes/control-plane/tasks/main.yml b/roles/kubernetes/control-plane/tasks/main.yml index 0cb7292cd..f8ebd9dd2 100644 --- a/roles/kubernetes/control-plane/tasks/main.yml +++ b/roles/kubernetes/control-plane/tasks/main.yml @@ -18,6 +18,19 @@ mode: "0640" when: kube_webhook_authorization | default(false) +- name: Create structured AuthenticationConfiguration file + copy: + content: "{{ authn_config | to_nice_yaml(indent=2, sort_keys=false) }}" + dest: "{{ kube_config_dir }}/apiserver-authentication-config-{{ kube_apiserver_authentication_config_api_version }}.yaml" + mode: "0640" + vars: + authn_config: + apiVersion: apiserver.config.k8s.io/{{ kube_apiserver_authentication_config_api_version }} + kind: AuthenticationConfiguration + jwt: "{{ kube_apiserver_authentication_config_jwt }}" + anonymous: "{{ kube_apiserver_authentication_config_anonymous }}" + when: kube_apiserver_use_authentication_config_file + - name: Create structured AuthorizationConfiguration file copy: content: "{{ authz_config | to_nice_yaml(indent=2, sort_keys=false) }}" @@ -99,6 +112,13 @@ include_tasks: kubeadm-etcd.yml when: etcd_deployment_type == "kubeadm" +- name: Cleanup unused AuthenticationConfiguration file versions + file: + path: "{{ kube_config_dir }}/apiserver-authentication-config-{{ item }}.yaml" + state: absent + loop: "{{ ['v1alpha1', 'v1beta1', 'v1'] | reject('equalto', kube_apiserver_authentication_config_api_version) | list }}" + when: kube_apiserver_use_authentication_config_file + - name: Cleanup unused AuthorizationConfiguration file versions file: path: "{{ kube_config_dir }}/apiserver-authorization-config-{{ item }}.yaml" diff --git a/roles/kubernetes/control-plane/templates/kubeadm-config.v1beta4.yaml.j2 b/roles/kubernetes/control-plane/templates/kubeadm-config.v1beta4.yaml.j2 index 8d6af8f13..47e6011ee 100644 --- a/roles/kubernetes/control-plane/templates/kubeadm-config.v1beta4.yaml.j2 +++ b/roles/kubernetes/control-plane/templates/kubeadm-config.v1beta4.yaml.j2 @@ -131,8 +131,7 @@ apiServer: value: "{{ kube_apiserver_pod_eviction_not_ready_timeout_seconds }}" - name: default-unreachable-toleration-seconds value: "{{ kube_apiserver_pod_eviction_unreachable_timeout_seconds }}" -{% if kube_api_anonymous_auth is defined %} -{# TODO: rework once suppport for structured auth lands #} +{% if kube_api_anonymous_auth is defined and not kube_apiserver_use_authentication_config_file %} - name: anonymous-auth value: "{{ kube_api_anonymous_auth }}" {% endif %} @@ -181,7 +180,7 @@ apiServer: - name: service-account-lookup value: "{{ kube_apiserver_service_account_lookup }}" {% endif %} -{% if kube_oidc_auth and kube_oidc_url is defined and kube_oidc_client_id is defined %} +{% if kube_oidc_auth and kube_oidc_url is defined and kube_oidc_client_id is defined and not kube_apiserver_use_authentication_config_file %} - name: oidc-issuer-url value: "{{ kube_oidc_url }}" - name: oidc-client-id @@ -207,6 +206,10 @@ apiServer: value: "{{ kube_oidc_groups_prefix }}" {% endif %} {% endif %} +{% if kube_apiserver_use_authentication_config_file %} + - name: authentication-config + value: "{{ kube_config_dir }}/apiserver-authentication-config-{{ kube_apiserver_authentication_config_api_version }}.yaml" +{% endif %} {% if kube_webhook_token_auth %} - name: authentication-token-webhook-config-file value: "{{ kube_config_dir }}/webhook-token-auth-config.yaml" @@ -279,7 +282,7 @@ apiServer: - name: tracing-config-file value: "{{ kube_config_dir }}/tracing/apiserver-tracing.yaml" {% endif %} -{% if kubernetes_audit or kube_token_auth or kube_webhook_token_auth or apiserver_extra_volumes or ssl_ca_dirs | length %} +{% if kubernetes_audit or kube_token_auth or kube_webhook_token_auth or kube_apiserver_use_authorization_config_file or kube_apiserver_use_authentication_config_file or apiserver_extra_volumes or ssl_ca_dirs | length %} extraVolumes: {% if kube_token_auth %} - name: token-auth-config @@ -301,6 +304,11 @@ apiServer: hostPath: {{ kube_config_dir }}/apiserver-authorization-config-{{ kube_apiserver_authorization_config_api_version }}.yaml mountPath: {{ kube_config_dir }}/apiserver-authorization-config-{{ kube_apiserver_authorization_config_api_version }}.yaml {% endif %} +{% if kube_apiserver_use_authentication_config_file %} + - name: authentication-config + hostPath: {{ kube_config_dir }}/apiserver-authentication-config-{{ kube_apiserver_authentication_config_api_version }}.yaml + mountPath: {{ kube_config_dir }}/apiserver-authentication-config-{{ kube_apiserver_authentication_config_api_version }}.yaml +{% endif %} {% if kubernetes_audit or kubernetes_audit_webhook %} - name: {{ audit_policy_name }} hostPath: {{ audit_policy_hostpath }} diff --git a/roles/kubespray_defaults/defaults/main/main.yml b/roles/kubespray_defaults/defaults/main/main.yml index d7280a69f..9fe5c0171 100644 --- a/roles/kubespray_defaults/defaults/main/main.yml +++ b/roles/kubespray_defaults/defaults/main/main.yml @@ -521,6 +521,33 @@ external_hcloud_cloud: ## arg2: "value2" controller_extra_args: {} +## Structured authentication config +## Structured AuthenticationConfiguration (GA in k8s v1.34) configures the API server's authentication with a structured configuration file. +## Note: The `--authentication-config` and `--oidc-*` flags are mutually exclusive. The two features cannot be used at the same time. +## Docs: https://kubernetes.io/docs/reference/access-authn-authz/authentication/#using-authentication-configuration +## KEP: https://github.com/kubernetes/enhancements/tree/master/keps/sig-auth/3331-structured-authentication-configuration +## Config API Reference: https://kubernetes.io/docs/reference/config-api/apiserver-config.v1/#apiserver-config-k8s-io-v1-AuthenticationConfiguration +kube_apiserver_use_authentication_config_file: false +kube_apiserver_authentication_config_api_version: "{{ 'v1beta1' if kube_version is version('1.34.0', '<') else 'v1' }}" +kube_apiserver_authentication_config_anonymous: + enabled: "{{ kube_api_anonymous_auth }}" + conditions: [] +kube_apiserver_authentication_config_jwt: [] +## Example structured authentication issuer config that replicates --oidc-* flag based config by reusing the kube_oidc_* variables +# kube_apiserver_authentication_config_jwt: +# - issuer: +# url: "{{ kube_oidc_url }}" +# certificateAuthority: "{{ kube_oidc_ca_file }}" +# audiences: +# - "{{ kube_oidc_client_id }}" +# claimMappings: +# username: +# claim: "{{ kube_oidc_username_claim }}" +# prefix: "{{ kube_oidc_username_prefix }}" +# groups: +# claim: "{{ kube_oidc_groups_claim }}" +# prefix: "{{ kube_oidc_groups_prefix }}" + ## List of authorization modes that must be configured for ## the k8s cluster. Only 'AlwaysAllow', 'AlwaysDeny', 'Node' and ## 'RBAC' modes are tested. Order is important. @@ -566,21 +593,6 @@ kube_apiserver_authorization_config_authorizers: # - expression: "!('kubeadm:cluster-admins' in request.groups)" # - expression: "!('system:masters' in request.groups)" -## Two workarounds are required to use AuthorizationConfiguration with kubeadm v1.29.x: -## 1. Enable the StructuredAuthorizationConfiguration feature gate: -# kube_apiserver_feature_gates: -# - StructuredAuthorizationConfiguration=true -## 2. Use the following kubeadm_patches to remove defaulted authorization-mode flags (Workaround for a kubeadm defaulting bug on v1.29.x. fixed in 1.30+ via: https://github.com/kubernetes/kubernetes/pull/123654) -# kubeadm_patches: -# - target: kube-apiserver -# type: strategic -# patch: -# spec: -# containers: -# - name: kube-apiserver -# $deleteFromPrimitiveList/command: -# - --authorization-mode=Node,RBAC - rbac_enabled: "{{ ('RBAC' in authorization_modes and not kube_apiserver_use_authorization_config_file) or (kube_apiserver_use_authorization_config_file and kube_apiserver_authorization_config_authorizers | selectattr('type', 'equalto', 'RBAC') | list | length > 0) }}" # When enabled, API bearer tokens (including service account tokens) can be used to authenticate to the kubelet's HTTPS endpoint