How to Add a User to Kubernetes?

Let's look at the authentication options in k8s and try to understand their pros and cons

All Kubernetes clusters have two categories of users: Kubernetes-managed service accounts and regular users. In other words, there's no way to add a user to Kubernetes via kubectl or an API call. Below we'll work out how to solve this problem.

Unlike users, a service account can be added to k8s as a separate resource, and its access credentials are stored in Secrets

You can enable several authentication methods at once. It's usually recommended to use at least two methods:

  • service account tokens for service accounts;
  • at least one other method for authenticating users.

When several authentication modules are enabled, the first module that successfully authenticates the request halts further execution. The API server does not guarantee the order in which the authenticators run.

x.509

Even though a regular user cannot be added via an API call, any user presenting a valid certificate signed by the cluster's certificate authority (CA) is considered authenticated. In this configuration, Kubernetes determines the username from the Common name field in the certificate's "subject" (for example, "/CN=bob"). The role-based access control (RBAC) subsystem then determines whether the user has the rights to perform a specific operation on a resource.

To begin with, you need to create a private key and a certificate signing request (CSR):

1openssl genrsa -out devopstrain.pem
2openssl req -new -key devopstrain.pem -out devopstrain.csr -subj "/CN=devopstrain"

You can also specify the user's groups:

1openssl req -new -key devopstrain.pem -out devopstrain.csr -subj "/CN=devopstrain/O=devgroup"

In this example the username will be devopstrain. The RBAC rules will be tied to it.

Let's output the contents of the CSR in base64 format:

1cat devopstrain.csr | base64 | tr -d '\n'

Let's create a yaml with a CertificateSigningRequest. Note the request field — you need to fill it with the content obtained in the previous command.

 1cat <<'EOF'> devopstrain-csr.yaml
 2apiVersion: certificates.k8s.io/v1
 3kind: CertificateSigningRequest
 4metadata:
 5  name: user-request-devopstrain
 6spec:
 7  groups:
 8  - system:authenticated
 9  request: LS0tLS1CRUdJTi...
10  signerName: kubernetes.io/kube-apiserver-client
11  expirationSeconds: 315569260
12  usages:
13  - digital signature
14  - key encipherment
15  - client auth
16EOF

The expirationSeconds field sets the certificate's validity period. In this case, 10 years.

Let's create the resource and check its status:

1kubectl create -f devopstrain-csr.yaml
2kubectl certificate approve user-request-devopstrain

The certificate should be signed. Now let's download it

1kubectl get csr user-request-devopstrain -o jsonpath='{.status.certificate}' | base64 -d > devopstrain-user.crt

From the already active (current working kubeconfig) you need to extract the certificate authority:

1kubectl config view --raw -o go-template='{{index ((index (index .clusters 0) "cluster")) "certificate-authority-data"|base64decode}}' > ca.crt

And let's create a kubeconfig for the new user:

1kubectl --kubeconfig ~/.kube/config-devopstrain config set-cluster preprod --server=https://KUBERNETES-API-ADDRESS
2kubectl --kubeconfig ~/.kube/config-devopstrain config set-cluster preprod --certificate-authority=ca.crt
3kubectl --kubeconfig ~/.kube/config-devopstrain config set-credentials devopstrain --client-certificate=devopstrain-user.crt --client-key=devopstrain.pem --embed-certs=true
4kubectl --kubeconfig ~/.kube/config-devopstrain config set-context default --cluster=preprod --user=devopstrain
5kubectl --kubeconfig ~/.kube/config-devopstrain config use-context default

Replace KUBERNETES-API-ADDRESS with the host or IP address of the k8s API server

Add RBAC rules for the new user:

 1cat <<'EOF'> devopstrain-rbac.yaml
 2apiVersion: v1
 3kind: Namespace
 4metadata:
 5  name: devopstrain-ns
 6spec: {}
 7status: {}
 8---
 9kind: Role
10apiVersion: rbac.authorization.k8s.io/v1
11metadata:
12  name: devopstrain
13  namespace: devopstrain-ns
14rules:
15- apiGroups: ["", "extensions", "apps"]
16  resources: ["*"]
17  verbs: ["*"]
18- apiGroups: ["batch"]
19  resources:
20  - jobs
21  - cronjobs
22  verbs: ["*"]
23---
24kind: RoleBinding
25apiVersion: rbac.authorization.k8s.io/v1
26metadata:
27  name: devopstrain
28  namespace: devopstrain-ns
29subjects:
30- kind: User
31  name: devopstrain
32  apiGroup: rbac.authorization.k8s.io
33roleRef:
34  apiGroup: rbac.authorization.k8s.io
35  kind: Role
36  name: devopstrain
37EOF
38
39kubectl apply -f devopstrain-rbac.yaml

Let's test access through the kubeconfig we created:

1kubectl --kubeconfig ~/.kube/config-devopstrain get pods -n devopstrain-ns

Downsides of this approach:

  • external certificate management is required (there's no built-in user database)
  • a user's certificate cannot be revoked (that is, access can only be restricted at the IP or RBAC level)

Static token

The API server reads tokens from a file when the --token-auth-file=SOMEFILE parameter is specified on the command line. Currently the tokens are valid indefinitely, and the list of tokens cannot be changed without restarting the API server.

The token file is a csv file with at least 3 columns: token, username, user uid, followed by optional group names.

Note:

If you have more than one group, the column must be in double quotes, for example:

1token,user,uid,"group1,group2,group3"

Adding a bearer token to a request

When using bearer token authentication from an http client, the API server expects an Authorization header with the value Bearer .

1Authorization: Bearer 31ada4fd-adec-460c-809a-9e56ceb75269

Bootstrap token

To enable simplified initialization of new clusters, Kubernetes includes a dynamically managed token type called a Bootstrap Token. These tokens are stored as Secrets in the kube-system namespace, where they can be dynamically managed and created. The Controller Manager contains a TokenCleaner controller that removes Bootstrap tokens as they expire.

Service account token

This is the authentication method enabled by default, which uses signed tokens to validate requests. Let's create the necessary resources:

 1apiVersion: v1
 2kind: ServiceAccount
 3metadata:
 4  name: devopstrain
 5  namespace: default
 6secrets:
 7- name: devopstrain-token
 8---
 9apiVersion: v1
10kind: Secret
11metadata:
12  name: devopstrain-token
13  annotations:
14    kubernetes.io/service-account.name: devopstrain
15type: kubernetes.io/service-account-token

After this, you can create a kubeconfig file in a similar way to the x.509 certificate case. The difference is that you need to pass in the token:

1kubectl config set-credentials USER --token=TOKEN-FROM-SECRET

The downside of this approach is that the access credentials live in secrets, and anyone who can read them can obtain the same level of access as this service account. The upside is that access can be revoked.

OpenID Connect token

OIDC stands for "OpenID Connect" and is an authentication protocol built on top of the OAuth 2.0 protocol. OIDC provides a standardized way to authenticate users and pass claims about a user between web applications and identity providers.

OIDC was designed to simplify the development of applications that use multiple identity providers. It allows an application to authorize a user via any provider that supports the OIDC protocol and to obtain information about the user, such as their name, email, and other information that the identity provider supplies.

To configure OIDC support in the Kubernetes API server, you need to pass the following parameters:

--oidc-issuer-url - the URL of the OpenID provider you want to use for authentication.

--oidc-client-id - the client identifier that was registered on the OpenID provider's side, identifying your Kubernetes cluster.

--oidc-username-claim - the name of the field that contains the username in the OpenID token. This may differ depending on the OpenID provider.

--oidc-groups-claim - the name of the field that contains the user's list of groups in the OpenID token. This may also differ depending on the OpenID provider.

--oidc-ca-file - the path to the root certificate authority's certificate file

There are other parameters besides these. Refer to the documentation.

Webhooks

In this scenario, each user is issued a token by a third-party identity provider (often abbreviated to IdP). As with other authentication flows, the user includes this when sending an API request to the cluster. However, instead of trying to parse this token itself, the cluster instead triggers a webhook upon receiving the request and passes the token to the third-party provider for authentication. This is essentially similar to the static token flow, except that instead of comparing the token against a file on the API server, the cluster reaches out to the identity provider for a "yes" or "no" answer.

Authenticating proxies

The last method of authenticating third-party users that we'll cover in this article is the use of an Authenticating Proxy (often abbreviated to AP). This approach differs from all the others in this article in that it doesn't require the use of access tokens. You can require users to authenticate for each request to your cluster separately and avoid the problems of token revocation altogether.

This method works such that requests are not routed directly to your cluster's API server, but are instead sent to a third-party service: the Authenticating Proxy. It functions as the Identity Provider for this authentication method, which means it's the place where your users must prove their identity. This can be done in any way you like — a username and password, a hardware token such as a Yubikey, or SSH keys configured in your terminal profile.

Once the user has authenticated with the Authenticating Proxy, the proxy forwards the request to the cluster along with all the necessary information about the user. Communication between the AP and your cluster happens over HTTP secured by client certificates. The AP must present a valid client certificate signed by a certificate authority recognized by the cluster before any other information in its HTTP requests to the cluster will be read.

This authentication method is very flexible, allowing you to use any user authentication method you like, and it fully decouples the authentication layer from your cluster. If you need to revoke access for a particular user, you can do so at the AP level without changing your cluster's settings and risking downtime.