- Overview
- Disable Anonymous Authentication for kube-apiserver
- Data Encryption in Kubernetes
- Kubelet Server Certificate Approval
- Disabling Auto-Mounting of Tokens for Service Accounts
- Use Strong Cryptographic Ciphers for API Server
- Implementing OAuth2 Authorization in Kubernetes
The current document describes the manual steps or procedures that are not covered by the KubeMarine
code itself, but should be implemented to get a production-ready Kubernetes cluster.
kube-bench
is a well-known open-source tool to check the Kubernetes cluster against the CIS Kubernetes Benchmark
. The report is divided on several parts. Each check has its own unique number. The items could be identified by that number.
Useful links: kube-bench
Kube-bench Identifier:
- 1.2.1
The --anonymous-auth
option manages anonymous requests to the kube-apiserver
. By default, it enables anonymous requests.
Note: If you disable anonymous authentication for kube-apiserver
,
some Kubemarine maintenance procedures will not work automatically,
and will require manual actions before and after the maintenance.
For more information, refer to Limitations.
- A working Kubernetes cluster.
- The following RBAC resources:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: healthz
rules:
- nonResourceURLs: ["/readyz", "/livez"]
verbs: ["get"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: healthz
namespace: kube-system
---
apiVersion: v1
kind: Secret
metadata:
annotations:
kubernetes.io/service-account.name: healthz
name: token-healthz
namespace: kube-system
type: kubernetes.io/service-account-token
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: healthz
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: healthz
subjects:
- kind: ServiceAccount
name: healthz
namespace: kube-system
Note: ClusterRole and ClusterRoleBinding are not required
if you have system:discovery
or system:public-info-viewer
ClusterRoleBindings installed on the cluster (default).
Though, such role bindings provide wider permissions than those that are necessary for the probes.
-
Add
anonymous-auth: "false"
into thekubeadm-config
configmap. For example:apiVersion: v1 data: ClusterConfiguration: | apiServer: certSANs: - 192.168.56.106 - ubuntu extraArgs: anonymous-auth: "false" ...
-
Change the
kube-apiserver
manifest on each control plane nodes one by one according to the following example:apiVersion: v1 kind: Pod metadata: ... name: kube-apiserver namespace: kube-system spec: containers: - command: - kube-apiserver - --anonymous-auth=false ... livenessProbe: failureThreshold: 8 httpGet: host: 192.168.56.106 path: /livez port: 6443 scheme: HTTPS httpHeaders: - name: Authorization value: Bearer <TOKEN> ... readinessProbe: failureThreshold: 3 httpGet: host: 192.168.56.106 path: /readyz port: 6443 scheme: HTTPS httpHeaders: - name: Authorization value: Bearer <TOKEN> ... startupProbe: failureThreshold: 24 httpGet: host: 192.168.56.106 path: /livez port: 6443 scheme: HTTPS httpHeaders: - name: Authorization value: Bearer <TOKEN> ...
Where, TOKEN is the result of the following command:
kubectl -n kube-system get secret token-healthz -o jsonpath='{.data.token}' | base64 --decode
If the --anonymous-auth
is set to "false", the upgrade and node addition procedures need some changes in the workflow.
Both procedures needs enabling anonymous-auth
on all existing control plane nodes before the kubeadm
run.
After the procedure is performed, the Disabling Procedure should be performed on all control plane nodes.
Besides, disabled anonymous requests to kube-apiserver
need changes in the monitoring system, if the resources like healthz
, readyz
, and livez
are used in the system.
Kube-bench Identifier:
- 1.2.29
- 1.2.30
The following section describes the Kubernetes cluster capabilities to store and manipulate the encrypted data.
ETCD as a Kubernetes cluster storage can interact with the encrypted data. The encryption/decryption procedures are the part of the kube-apiserver
functionality.
An example of the EncryptionConfiguration
file is as follows:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
- configmaps
providers:
- aesgcm:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- aescbc:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- secretbox:
keys:
- name: key1
secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
- identity: {}
It should be created preliminarily and placed in the /etc/kubernetes/enc/
directory.
The next step is to enable the encryption settings in kubeadm-config
:
data:
ClusterConfiguration: |
apiServer:
...
extraArgs:
...
encryption-provider-config: /etc/kubernetes/enc/enc.yaml
extraVolumes:
...
- hostPath: /etc/kubernetes/enc
mountPath: /etc/kubernetes/enc
name: enc
pathType: DirectoryOrCreate
There is an --encryption-provider-config
option that points to the EncryptionConfiguration
file location. The kube-apiserver
should have the following parts in the manifest yaml:
...
spec:
containers:
- command:
- kube-apiserver
...
- --encryption-provider-config=/etc/kubernetes/enc/enc.yaml
...
volumeMounts:
- name: enc
mountPath: /etc/kubernetes/enc
readonly: true
...
volumes:
- name: enc
hostPath:
path: /etc/kubernetes/enc
type: DirectoryOrCreate
In the above case, secrets
and configmaps
are encrypted on the first key of the aesgcm
provider, but the previously encrypted secrets
and configmaps
are decrypted on any keys of any providers that are matched. This approach allows to change both encryption providers and keys during the operation. The keys should be random strings in base64 encoding. identity
is the default provider that does not provide any encryption at all.
For more information, refer to https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/.
As per the CIS benchmark (kube-bench checks), the aesgcm
provider for encryption is not recognized as an appropriate provider. To fulfil this requirement, we have to configure aescbc
, secretxbox
, or kms
as an encryption provider.
There is an encryption provider kms
that allows using an external Key Management Service
for the key storage, therefore the keys are not stored in the EncryptionConfiguration
file, which is more secure. The kms
provider needs to deploy a KMS plugin for further use.
The Trousseau
KMS plugin is an example. It works through a unix socket, therefore Trousseau
pods must be run on the same nodes as kube-apiserver
. In case of using a KMS provider, the EncryptionConfiguration
is as follows (Vault
is a KMS):
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
- configmaps
providers:
- kms:
name: vaultprovider
endpoint: unix:///opt/vault-kms/vaultkms.socket
cachesize: 100
timeout: 3s
- identity: {}
Also, the unix socket must be available for kube-apiserver
:
apiVersion: v1
kind: Pod
metadata:
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
volumeMounts:
- mountPath: /opt/vault-kms/vaultkms.socket
name: vault-kms
...
volumes:
- hostPath:
path: /opt/vault-kms/vaultkms.socket
type: Socket
name: vault-kms
The environment variable VAULT_ADDR
matches the address of the Vault
service and --listen-addr
argument points to the KMS plugin unix socket in the following example:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: vault-kms-provider
namespace: kube-system
...
spec:
template:
spec:
initContainers:
- name: vault-agent
image: vault
securityContext:
privileged: true
args:
- agent
- -config=/etc/vault/vault-agent-config.hcl
- -log-level=debug
env:
- name: VAULT_ADDR
value: http://vault-adress:8200
...
containers:
- name: vault-kms-provider
image: ghcr.io/ondat/trousseau:v1.1.3
imagePullPolicy: Always
args:
- -v=5
- --config-file-path=/opt/trousseau/config.yaml
- --listen-addr=unix:///opt/vault-kms/vaultkms.socket
- --zap-encoder=json
- --v=3
For more information, refer to:
- https://kubernetes.io/docs/tasks/administer-cluster/kms-provider/
- https://github.com/ondat/trousseau/wiki/Trousseau-Deployment
The first step for disabling encryption is to make the identity
provider default for encryption. The enabling of EncryptionConfiguration
should be similar to the following example:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
- configmaps
providers:
- identity: {}
- aesgcm:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- aescbc:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- secretbox:
keys:
- name: key1
secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
The next step is to replace all resources that were previously encrypted (for example, secrets
):
# kubectl get secrets --all-namespaces -o json | kubectl replace -f -
It is then possible to remove the encryption settings from the kubeadm-config
configmap and kube-apiserver
manifest.
- Since the
/etc/kubernetes/enc/enc.yaml
file has keys, access to the file must be restricted. For instance:
# chmod 0700 /etc/kubernetes/enc/
- The proper way for using encryption is to rotate the keys. The rotation procedure of the keys should take into consideration the fact that the
EncryptionConfiguration
file must be equal on eachcontrol-plane
node. During the keys' rotation procedure, some operation of getting the encrypted resources may be unsuccessful. - The
kube-apiserver
has an--encryption-provider-config-automatic-reload
option that allows to apply a newEncryptionConfiguration
withoutkube-apiserver
reload. - ETCD restore procedures should take into consideration the keys' rotation, otherwise some data may be unavailable due to keys that were used for the encryption and is not available after restoration. The backup procedure may include an additional step that renews all encrypted data before the ETCD backup. This approach decreases the security level for the data in ETCD backup, but it prevents any inconvenience in the future. Another option is not to delete the keys from
env.yml
even if they are not used for encryption/decryption anymore. - External services that interact with ETCD may stop working due to encryption enabling.
Kube-bench Identifier:
- 1.2.5
The kubelet
server certificate is self-signed by default, and is usually stored in the /var/lib/kubelet/pki/kubelet.crt
file. To avoid using the self-signed kubelet
server certificate, alter the cluster.yaml
file in the following way:
...
services:
kubeadm_kubelet:
serverTLSBootstrap: true
rotateCertificates: true
kubeadm:
apiServer:
extraArgs:
kubelet-certificate-authority: /etc/kubernetes/pki/ca.crt
...
These settings enforce kubelet
on each node of the cluster to request certificate approval (for kubelet
server part) from the default Kubernetes CA and rotate certificate in the future. The kube-apiserver
machinery does not approve certificate requests for kubelet
automatically. They can be approved manually by the following commands. Use the following command to get the list of certificate requests:
# kubectl get csr
NAME AGE SIGNERNAME REQUESTOR REQUESTEDDURATION CONDITION
csr-2z6rv 12m kubernetes.io/kubelet-serving system:node:nodename-1 <none> Pending
csr-424qg 89m kubernetes.io/kubelet-serving system:node:nodename-2 <none> Pending
Use the following command to approve a particular request:
kubectl certificate approve csr-424qg
These commands might be automated in several ways.
Generally, CronJob
runs the approval command above for every CSR according to some schedule.
It is possible to install the kubelet-csr-approver service. For more information, refer to kubelet-csr-approver. This service approves the CSR automatically when a CSR is created according to several settings. It is better to restrict nodes' IP addresses (providerIpPrefixes
option) and FQDN templates (providerRegex). For more information, refer to the official documentation.
Kube-bench Identifier:
- 5.1.5
Create explicit service accounts wherever a Kubernetes workload requires specific access to the Kubernetes API server.
Modify the configuration of each default service account to include this value automountServiceAccountToken: false
To disable the auto-mounting of service account tokens, create secrets associated with a particular service account and mount that secret as a volume to the pod's specification wherever necessary.
To achieve this, implement the following procedure.
To disable auto-mounting of a token, add automountServiceAccountToken: false
flag to the service account properties as shown below.
apiVersion: v1
kind: ServiceAccount
metadata:
name: ingress-nginx
namespace: ingress-nginx
automountServiceAccountToken: false
...
Create a new Kubernetes secret of type kubernetes.io/service-account-token
as follows.
apiVersion: v1
kind: Secret
metadata:
name: ingress-nginx-token
namespace: ingress-nginx
annotations:
kubernetes.io/service-account.name: ingress-nginx
type: kubernetes.io/service-account-token
Edit the POD specification and mount the secret as a volume to the pod as follows.
...
volumeMounts:
- name: ingress-nginx-token
mountPath: /var/run/secrets/kubernetes.io/serviceaccount
...
volumes:
- name: ingress-nginx-token
secret:
secretName: ingress-nginx-token
...
After this, restart the pod to reflect the changes and verify that the secret is mounted to the pod at the specified mount point.
Kube-bench Identifier:
- 1.2.31
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
Edit the API server pod specification file /etc/kubernetes/manifests/kube-apiserver.yaml
on the control all plane nodes and add below parameter to the API server arguments
spec:
containers:
- command:
- kube-apiserver
...
- --tls-cipher-suites=TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA
...
Save the file with above mentioned changes and the kube-apiserver pods will be restarted. Restart the pods manually in case automatic restart doesn't happen in order to apply the changes in the cluster.
Also make sure to update kubeadm-config
configmap in kube-system namespace to store these changes. Elseon running any of the mantenance procedue, these changes would be lost.
kubectl edit cm kubeadm-config -n kube-system
Automated Application for Strong Cryptographic Ciphers for API Server During New Cluster Installation
For applying Strong Cryptographic Ciphers for API server at the time of installation of cluster itself, then it can be done through Kubemarine. To do so, follow the below procedure:
- Add cryptographic ciphers suites to the kubeadm config as extra arguments for API server in
cluster.yaml
file
services:
kubeadm:
kubernetesVersion: v1.28.3
...
apiServer:
extraArgs:
tls-cipher-suites: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
...
- Run Kubemarine install procedure using above config added to
cluster.yaml
file
Kube-bench Identifier:
- 3.1.2
Service account token authentication should not be used for users.
Alternative mechanisms provided by Kubernetes such as the use of OIDC should be implemented in place of service account tokens.
Here is a list of available tools that can be used for Identity and Access Management in Kubernetes cluster.
- Dex - A lightweight OIDC provider server that can be configured to work with various identity providers. More information about this tool can be found on https://github.com/dexidp/dex. The documentation for configuring Dex with your k8s cluster can be found at https://dexidp.io/docs/kubernetes/.
- OpenUnison - An open-source OIDC provider, focusing on security and ease of use. More information about this tool can be found at https://github.com/OpenUnison/openunison-k8s-idm-oidc. The documentation for configuring OpenUnison with your k8s cluster can be found at https://openunison.github.io/.
- Keycloak - An open source identity and access management solution. More information about this tool can be found at https://www.keycloak.org. The documentation for configuring Keycloak with your k8s cluster can be found at https://medium.com/elmo-software/kubernetes-authenticating-to-your-cluster-using-keycloak-eba81710f49b.
- JWT Authenticator - Kubernetes itself offers a built-in "JWT Authenticator". This authenticator validates tokens issued by an OIDC provider based on the configured issuer and retrieves the public key for verification through OIDC discovery. More information about this tool can be found at https://kubernetes.io/docs/reference/access-authn-authz/authentication/.