Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add structured authentication configuration #11841

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions inventory/sample/group_vars/k8s_cluster/k8s-cluster.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ credentials_dir: "{{ inventory_dir }}/credentials"
# kube_oidc_auth: false
# kube_token_auth: false


## Variables for OpenID Connect Configuration https://kubernetes.io/docs/admin/authentication/
## To use OpenID you have to deploy additional an OpenID Provider (e.g Dex, Keycloak, ...)

Expand Down Expand Up @@ -118,7 +117,8 @@ kube_network_node_prefix_ipv6: 120

# The port the API Server will be listening on.
kube_apiserver_ip: "{{ kube_service_addresses | ansible.utils.ipaddr('net') | ansible.utils.ipaddr(1) | ansible.utils.ipaddr('address') }}"
kube_apiserver_port: 6443 # (https)
# https port
kube_apiserver_port: 6443

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure where those come from ? This can probably be dropped

# Kube-proxy proxyMode configuration.
# Can be ipvs, iptables
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
kube_apiserver_structured_auth_jwt_issuers: []
# Example JWT issuer configuration:
# kube_apiserver_structured_auth_jwt_issuers:
# - issuer:
# url: "https://issuer1.example.com"
# audiences:
# - audience1
# - audience2
# audienceMatchPolicy: MatchAny
# claimMappings:
# username:
# expression: "claims.username"
# groups:
# expression: "claims.groups"
# uid:
# expression: "claims.uid"
# userValidationRules:
# - expression: "!user.username.startsWith('system:')"
# message: "username cannot use reserved system: prefix"
# - issuer:
# url: "https://issuer2.example.com"
# discoveryURL: "https://discovery.example.com/.well-known/openid-configuration"
# audiences:
# - audience3
# - audience4
# audienceMatchPolicy: MatchAny
# claimMappings:
# username:
# expression: "claims.username"
# groups:
# expression: "claims.groups"
# uid:
# expression: "claims.uid"
# userValidationRules:
# - expression: "!user.username.startsWith('kubespray:')"
# message: "username cannot use reserved kubespray: prefix"
5 changes: 3 additions & 2 deletions roles/kubernetes/control-plane/defaults/main/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,10 @@ kube_apiserver_admission_plugins_podnodeselector_default_node_selector: ""
# kube_oidc_client_id: kubernetes
## Optional settings for OIDC
# kube_oidc_username_claim: sub
# kube_oidc_username_prefix: 'oidc:'
# kube_oidc_username_prefix: "oidc:"
# kube_oidc_groups_claim: groups
# kube_oidc_groups_prefix: 'oidc:'
# kube_oidc_groups_prefix: "oidc:"
# kube_oidc_uid_claim: "sub"
# Copy oidc CA file to the following path if needed
# kube_oidc_ca_file: {{ kube_cert_dir }}/ca.pem
# Optionally include a base64-encoded oidc CA cert
Expand Down
33 changes: 33 additions & 0 deletions roles/kubernetes/control-plane/tasks/kubeadm-setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,39 @@
mode: "0640"
when: kube_apiserver_tracing

- name: Kubeadm | Configure Structured Authentication
vars:
all_kube_apiserver_feature_gates: "{{ kube_apiserver_feature_gates + kube_feature_gates }}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$ grep -R _feature_gates roles/
roles/kubespray-defaults/defaults/main/main.yml:kube_feature_gates: []
roles/kubespray-defaults/defaults/main/main.yml:kube_apiserver_feature_gates: []
roles/kubespray-defaults/defaults/main/main.yml:kube_controller_feature_gates: []
roles/kubespray-defaults/defaults/main/main.yml:kube_scheduler_feature_gates: []
roles/kubespray-defaults/defaults/main/main.yml:kube_proxy_feature_gates: []
roles/kubespray-defaults/defaults/main/main.yml:kubelet_feature_gates: []
roles/kubespray-defaults/defaults/main/main.yml:kubeadm_feature_gates: []

The feature gates which are active by default are not defined in Kubespray, so I don't this this will work.
I don't think we need to try to validate this anyway: for intelligent defaults, that's good, but this is an opt-in configuration from the user. Let them ; worst scenario, the apiserver will fail to start when validating its configuration.

when:
- not kube_oidc_auth
- ("StructuredAuthenticationConfiguration=true" in all_kube_apiserver_feature_gates and kube_version | regex_replace("^v", "") is version("1.30.0", "<", version_type="semver"))
or
("StructuredAuthenticationConfiguration=false" not in all_kube_apiserver_feature_gates and kube_version | regex_replace("^v", "") is version("1.30.0", ">=", version_type="semver"))
block:
- name: Kubeadm | Create apiserver authentication config directory
file:
path: "{{ kube_config_dir }}/authentication"
state: directory
mode: "0640"

- name: Merge additional userValidationRules
set_fact:
kube_apiserver_structured_auth_jwt_issuers: >-
{{
kube_apiserver_structured_auth_jwt_issuers | map('combine', {
'userValidationRules': item.userValidationRules + additional_user_validation_rules
}) | list
}}
loop: "{{ kube_apiserver_structured_auth_jwt_issuers }}"
loop_control:
loop_var: item

- name: Kubeadm | Write apiserver authentication config yaml
template:
src: apiserver-authentication-config.yaml.j2
dest: "{{ kube_config_dir }}/authentication/apiserver-authentication-config.yaml"
mode: "0640"

# Nginx LB(default), If kubeadm_config_api_fqdn is defined, use other LB by kubeadm controlPlaneEndpoint.
- name: Set kubeadm_config_api_fqdn define
set_fact:
Expand Down
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should just use the upstream structure in our variable and render it with to_nice_yaml, like in #11852

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
apiVersion: apiserver.config.k8s.io/v1beta1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might depend on the K8s version, isn't this alpha before 1.30 ?

kind: AuthenticationConfiguration
{% if kube_apiserver_structured_auth_jwt_issuers | length > 0 %}
jwt:
{% for issuer in kube_apiserver_structured_auth_jwt_issuers %}
- issuer:
url: "{{ issuer.issuer.url }}"
{% if issuer.issuer.discoveryURL is defined %}
discoveryURL: "{{ issuer.issuer.discoveryURL }}"
{% endif %}
audiences:
{% for audience in issuer.issuer.audiences %}
- {{ audience }}
{% endfor %}
audienceMatchPolicy: {{ issuer.issuer.audienceMatchPolicy }}
{% if issuer.claimValidationRules is defined %}
claimValidationRules:
{% for validationRule in issuer.claimValidationRules %}
expression: "{{ validationRule.expression }}"
message: "{{ validationRule.message }}"
{% endfor %}
{% endif %}
claimMappings:
username:
expression: "{{ issuer.claimMappings.username.expression }}"
groups:
expression: "{{ issuer.claimMappings.groups.expression }}"
uid:
expression: "{{ issuer.claimMappings.uid.expression }}"
{% if issuer.claimMappings.extra is defined %}
extra:
{% for extra in issuer.claimMappings.extra %}
- key: "{{ extra.key }}"
expression: "{{ extra.expression }}"
{% endfor %}
{% endif %}
{% if issuer.userValidationRules | length > 0 %}
userValidationRules:
{% for rule in issuer.userValidationRules %}
- expression: "{{ rule.expression }}"
message: "{{ rule.message }}"
{% endfor %}
{% endif %}
{% endfor %}
{% else %}
jwt:
{% endif %}
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ apiServer:
{% if kube_apiserver_service_account_lookup %}
service-account-lookup: "{{ kube_apiserver_service_account_lookup }}"
{% endif %}
{% if kube_oidc_auth | default(false) 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 %}
oidc-issuer-url: "{{ kube_oidc_url }}"
oidc-client-id: "{{ kube_oidc_client_id }}"
{% if kube_oidc_ca_file is defined %}
Expand All @@ -173,6 +173,9 @@ apiServer:
oidc-groups-prefix: "{{ kube_oidc_groups_prefix }}"
{% endif %}
{% endif %}
{% if not kube_oidc_auth %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the feature are incompatible, not using the OIDC authenticator does not imply the structured authentication configuration is used, so this should not use kube_oidc_auth to decide this.
Either we can have a dedicated boolean, or check if the struct of the variable is equivalent to not being configured (jwt == [] ? )

authentication-config: {{ kube_config_dir }}/authentication/apiserver-authentication-config.yaml
{% endif %}
{% if kube_webhook_token_auth | default(false) %}
authentication-token-webhook-config-file: {{ kube_config_dir }}/webhook-token-auth-config.yaml
{% endif %}
Expand Down Expand Up @@ -261,6 +264,13 @@ apiServer:
readOnly: false
pathType: DirectoryOrCreate
{% endif %}
{% if not kube_oidc_auth %}
- name: structauth
hostPath: {{ kube_config_dir }}/authentication
mountPath: {{ kube_config_dir }}/authentication
readOnly: true
pathType: DirectoryOrCreate
{% endif %}
{% if kube_apiserver_tracing %}
- name: tracing
hostPath: {{ kube_config_dir }}/tracing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ apiServer:
- name: service-account-lookup
value: "{{ kube_apiserver_service_account_lookup }}"
{% endif %}
{% if kube_oidc_auth | default(false) 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 %}
- name: oidc-issuer-url
value: "{{ kube_oidc_url }}"
- name: oidc-client-id
Expand All @@ -208,6 +208,10 @@ apiServer:
value: "{{ kube_oidc_groups_prefix }}"
{% endif %}
{% endif %}
{% if not kube_oidc_auth %}
- name: authentication-config
value: "{{ kube_config_dir }}/authentication/apiserver-authentication-config.yaml"
{% endif %}
{% if kube_webhook_token_auth | default(false) %}
- name: authentication-token-webhook-config-file
value: "{{ kube_config_dir }}/webhook-token-auth-config.yaml"
Expand Down Expand Up @@ -317,6 +321,13 @@ apiServer:
readOnly: false
pathType: DirectoryOrCreate
{% endif %}
{% if not kube_oidc_auth %}
- name: structauth
hostPath: {{ kube_config_dir }}/authentication
mountPath: {{ kube_config_dir }}/authentication
readOnly: true
pathType: DirectoryOrCreate
{% endif %}
{% if kube_apiserver_tracing %}
- name: tracing
hostPath: {{ kube_config_dir }}/tracing
Expand Down
5 changes: 5 additions & 0 deletions roles/kubernetes/control-plane/vars/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ kube_apiserver_admission_plugins_needs_configuration:
- PodSecurity
- PodNodeSelector
- ResourceQuota
additional_user_validation_rules:
- expression: "!user.username.startsWith('system:')"
message: "username cannot used reserved system: prefix"
- expression: "user.groups.all(group, !group.startsWith('system:'))"
message: "groups cannot used reserved system: prefix"
Comment on lines +10 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explicit why we're adding those ?