Help us improve your experience.

Let us know what you think.

Do you have time for a two-minute survey?

Announcement: Try the Ask AI chatbot for answers to your technical questions about Juniper products and solutions.

close
header-navigation
keyboard_arrow_up
{ "lCode": "en_US", "lName": "English", "folder": "en_US" }
English
 

Secrets

date_range 01-Apr-20

All modern network systems need to deal with sensitive information, such as username, passwords, SSH keys, etc. in the platform. The same applies to the pods in a Kubernetes environment. However, exposing this information in your pod specs as cleartext may introduce security concerns and you need a tool or method to resolve the issue – at least to avoid the cleartext credentials as much as possible.

The Kubernetes secrets object is designed specifically for this purpose – it encodes all sensitive data and exposes it to pods in a controlled way.

The official definition of Kubernetes secrets is:

"A Secret is an object that contains a small amount of sensitive data such as a password, a token, or a key. Such information might otherwise be put in a Pod specification or in an image; putting it in a secret object allows for more control over how it is used and reduces the risk of accidental exposure."

Users can create secrets, and the system also creates secrets. To use a secret, a pod needs to reference the secret.

There are many different types of secrets, each serving a specific use case, and there are also many methods to create a secret and a lot of different ways to refer to it in a pod. A complete discussion of secrets is beyond the scope of this book, so please refer to the official documentation to get all of the details and track all up-to-date changes.

Here, we’ll look at some commonly used secret types. You will also learn several methods to create a secret and how to refer to it in your pods. And once you get to the end of the section, you should understand the main benefits of a Kubernetes secrets object and how it can help improve your system security.

Let’s begin with a few secret terms:

  • Opaque: This type of secret can contain arbitrary key-value pairs, so it is treated as unstructured data from Kubernetes’ perspective. All other types of secret have constant content.

  • Kubernetes.io/Dockerconfigjson: This type of secret is used to authenticate with a private container registry (for example, a Juniper server) to pull your own private image.

  • TLS: A TLS secret contains a TLS private key and certificate. It is used to secure an ingress. You will see an example of an ingress with a TLS secret in Chapter 4.

  • Kubernetes.io/service-account-token: When processes running in containers of a pod access the API server, they have to be authenticated as a particular account (for example, account default by default). An account associated with a pod is called a service-account. Kubernetes.io/service-account-token type of secret contains information about Kubernetes service-account. We won’t elaborate on this type of secret and service-account in this book.

  • Opaque secret: The secret of type opaque represents arbitrary user-owned data – usually you want to put some kind of sensitive data in secret, for example, username, password, security pin, etc., just about anything you believe is sensitive and you want to carry into your pod.

Define Opaque Secret

First, to make our sensitive data looks less sensitive, let’s encode it with the base64 tool:

content_copy zoom_out_map
$ echo -n 'username1' | base64 
dXNlcm5hbWUx
$ echo -n 'password1' | base64 
cGFzc3dvcmQx

Then put the encoded version of the data in a secret definition YAML file:

content_copy zoom_out_map
apiVersion: v1 
kind: Secret 
metadata:
name: secret-opaque 
type: Opaque
data:
username: dXNlcm5hbWUx 
password: cGFzc3dvcmQx

Alternatively, you can define the same secret from kubectl CLI directly, with the --from-literal option:

content_copy zoom_out_map
kubectl create secret generic secret-opaque    \
--from-literal=username='username1'	\
--from-literal=password='password1'

Either way, a secret will be generated:

content_copy zoom_out_map
$ kubectl get secrets
NAME	TYPE	DATA AGE
secret-opaque	Opaque	2	8s

$ kubectl get secrets secret-opaque -o yaml 
apiVersion: v1
data:
password: cGFzc3dvcmQx 
username: dXNlcm5hbWUx 
kind: Secret
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"password":"cGFzc3dvcmQx","username":"dXNlcm5hbWUx"},"kind":"Secret","metadata":{"annotations":{},"name"
:"secret-opaque","namespace":"ns-user-1"},"type":"Opaque"} 
creationTimestamp: 2019-08-22T22:51:18Z
name: secret-opaque 
namespace: ns-user-1 
resourceVersion: "885702"
selfLink: /api/v1/namespaces/ns-user-1/secrets/secret-opaque
 

uid: 5a78d9d4-c52f-11e9-90a3-0050569e6cfc 
type: Opaque

Refer Opaque Secret

Next you will need to use the secret in a pod, and the user information contained in the secret will be carried into the pod. As mentioned, there are different ways to refer the opaque secret in a pod, and correspondingly, the result will be different.

Typically, user information carried from a secret can appear in a container in one of these forms:

  • Files

  • Environmental variables

Now let’s demonstrate using secret to generate environmental variables in a container:

content_copy zoom_out_map
#pod-webserver-do-secret.yaml 
apiVersion: v1
kind: Pod 
metadata:
name: contrail-webserver-secret 
labels:
app: webserver 
spec:
containers:
- name: contrail-webserver-secret
image: contrailk8sdayone/contrail-webserver 
#envFrom:
#- secretRef:
# name: test-secret 
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: secret-opaque 
key: username
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: secret-opaque 
key: password

Spawn the pod and container from this YAML file:

content_copy zoom_out_map
$ kubectl apply -f pod/pod-webserver-do-secret.yaml
pod/contrail-webserver-secret created

Log in the container and verify the generated environmental variables:

content_copy zoom_out_map
$ kubectl exec -it contrail-webserver-secret -- printenv | grep 
SECRET SECRET_USERNAME=username1 
SECRET_PASSWORD=password1

The original sensitive data encoded with base64 is now present in the container!

Dockerconfigjson Secret

The dockerconfigjson secret, as the name indicates, carries the Docker account credential information that is typically stored in a .docker/config.json file. The image in a Kubernetes pod may point to a private container registry. In that case, Kubernetes needs to authenticate it with that registry in order to pull the image. The dockerconfigjson type of secret is designed for this very purpose.

Docker Credential Data

The most straightforward method to create a kubernetes.io/dockerconfigjson type of secret is to provide login information directly with the kubectl command and let it generate the secret:

content_copy zoom_out_map
$ kubectl create secret docker-registry secret-jnpr1 \
--docker-server=hub.juniper.net	\
--docker-username=JNPR-FieldUser213	\
--docker-password=CLJd2kpMsVc9zrAuTFPn 
secret/secret-jnpr created

Verify the secret creation:

content_copy zoom_out_map
$ kubectl get secrets
NAME	TYPE	DATA AGE
secret-jnpr	kubernetes.io/dockerconfigjson	1		6s #<--- 
default-token-hkkzr   kubernetes.io/service-account-token   3		62d
Note

Only the first line in the output is the secret you have just created. The second line is a kubernetes.io/service-account-token type of secret that the Kubernetes system creates automatically when the contrail setup is up and running.

Now inspect the details of the secret:

content_copy zoom_out_map
$ kubectl get secrets secret-jnpr -o yaml 
apiVersion: v1
data:
.dockerconfigjson: eyJhdXRocyI6eyJodWIuanVuaXBlci5uZXQvc2...<snipped>... 
kind: Secret
metadata:
creationTimestamp: 2019-08-14T05:58:48Z 
name: secret-jnpr
namespace: ns-user-1 
resourceVersion: "870370"
selfLink: /api/v1/namespaces/ns-user-1/secrets/secret-jnpr 
uid: 9561cdc3-be58-11e9-9367-0050569e6cfc
type: kubernetes.io/dockerconfigjson

Not surprisingly, you don’t see any sensitive information in the form of cleartext. There is a data portion of the output where you can see a very long string as the value of key: dockerconfigjson. Its appearance seems to have transformed from the original data, but at least it does not contain sensitive information anymore – after all one purpose of using a secret is to improve the system security.

However, the transformation is done by encoding, not encryption, so there is still a way to manually retrieve the original sensitive information: just pipe the value of key .dockerconfigjson into the base64 tool, and the original username and password information is viewable again:

content_copy zoom_out_map
$ echo "eyJhdXRocyI6eyJodWIuanVua..." | base64 -d | python -mjson.tool
{
		"auths": { 
			"hub.juniper.net": {
						"auth": "Sj5QUi1GaWVsZFVzZXIyMTM6Q0xKZDJqcE1zVmM5enJBdVRGUG4=",
						"password": "CLJd2kpMsVc9zrAuTFPn", "username": "JNPR-FieldUser213"
			}
		}
}

Some highlights in this output are:

  • The python -mjson.tool is used to format the decoded json data before displaying to the terminal.

  • There is an auth key-value pair. It is the token generated based on the authentication information you gave (username and password).

  • Later on, when equipped with this secret, a pod will use this token, instead of the username and password to authenticate itself towards the private Docker registry hub.juniper.net in order to pull a Docker image.

Tip

Here’s another way to decode the data directly from the secret object:

content_copy zoom_out_map
$ kubectl get secret secret-jnpr1 \
--output="jsonpath={.data.\.dockerconfigjson}" \
| base64 --decode | python -mjson.tool
{
			"auths": { 
					"hub.juniper.net/security": {
							"auth": "Sj5QUi1GaWVsZFVzZXIyMTM6Q0xKZDJqcE1zVmM5enJBdVRGUG4=",
							"password": "CLJd2kpMsVc9zrAuTFPn", "username": "JNPR-FieldUser213"
					}
			}
}

The --output=xxxx option filters the kubectl get output so only the value of .dockerconfigjson under data is displayed. The value is then piped into base64 with option --decode (alias of -d) to get it decoded.

A docker-registry secret created manually like this will only work with a single private registry. To support multiple private container registries you can create a secret from the Docker credential file.

Docker Credential File (~/.Docker/config.json)

As the name of the key .dockerconfigjson in the secret we created indicates, it serves a similar role as the Docker config file: .docker/config.json. Actually, you can generate the secret directly from the Docker configuration file.

To generate the Docker credential information, first check the Docker config file:

content_copy zoom_out_map
$ cat .docker/config.json
{
......
"auths": {},
......
}

There’s nothing really here. Depending on the usage of the set up you may see different output, but the point is that this Docker config file will be updated automatically every time you docker login a new registry:

content_copy zoom_out_map
$ cat mydockerpass.txt | \ 
	docker login hub.juniper.net \
		--username JNPR-FieldUser213 \
		--password-stdin 
Login Succeeded

The file mydockerpass.txt is the login password for username JNPR-FieldUser213. Saving the password in a file and then piping it to the docker login command with --password-stdin option has an advantage of not exposing the password cleartext in the shell history.

Tip

If you want you can write the password directly, and you will get a friendly warning that this is insecure.

content_copy zoom_out_map
$ docker login hub.juniper.net --username XXXXXXXXXXXXXXX --password XXXXXXXXXXXXXX 
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Login Succeeded

Now the Docker credential information is generated in the updated config.json file:

content_copy zoom_out_map
$ cat .docker/config.json
{
......
		"auths": { #<---
				"hub.juniper.net": {
				"auth": "Sj5QUi1GaWVsZFVzZXIyMTM6Q0xKZDJqcE1zVmM5enJBdVRGUG4="
				}
		},
......
}

The login process creates or updates a config.json file that holds the authorization token. Let’s create a secret from the .docker/config.json file:

content_copy zoom_out_map
$ kubectl create secret generic secret-jnpr2 \
--from-file=.dockerconfigjson=/root/.docker/config.json \
--type=kubernetes.io/dockerconfigjson 
secret/secret-jnpr2 created

$ kubectl get secrets
 

NAME	TYPE	DATA AGE
secret-jnpr2 kubernetes.io/dockerconfigjson 1 8s #<--- default-token-
hkkzr kubernetes.io/service-account-token 3 63d secret-jnpr
	kubernetes.io/dockerconfigjson 1 26m
$ kubectl get secrets secret-jnpr2 -o yaml 
apiVersion: v1
data:
.dockerconfigjson: ewoJImF1dGhzIjoIlNrNVFVaTFHYVdWc1pGVnpaWEl5TVRNNlEweEtaREpxY0UxelZtTTVlbkpCZ 
FZSR1VHND0iCgkJfQoJfSwKCSJIdHRwSGVhZGVycyI6IHsKCQkiVXNlci1BZ2VudCI6ICJEb2NrZXItQ2xpZW50LzE4LjAzLjE 
tY2UgKGxpbnV4KSIKCX0sCgkiZGV0YWNoS2V5cyI6ICJjdHJsLUAiCn0=
kind: Secret 
metadata:
creationTimestamp: 2019-08-15T07:35:25Z 
name: csrx-secret-dr2
namespace: ns-user-1 
resourceVersion: "878490"
selfLink: /api/v1/namespaces/ns-user-1/secrets/secret-jnpr2 
uid: 3efc3bd8-bf2f-11e9-bb2a-0050569e6cfc
type: kubernetes.io/dockerconfigjson

$ kubectl get secret secret-jnpr2 --output="jsonpath={.data.\.dockerconfigjson}" | base64 --decode
{
......
			"auths": { "hub.juniper.net": {
					"auth": "Sj5QUi1GaWVsZFVzZXIyMTM6Q0xKZDJqcE1zVmM5enJBdVRGUG4="
			}
					},
......
}

YAML File

You can also create a secret directly from a YAML file the same way you create other objects like service or ingress.

To manually encode the content of the .docker/config.json file:

content_copy zoom_out_map
$ cat .docker/config.json | base64 
ewoJImF1dGhzIjogewoJCSJodWIuanVuaXBlci5uZXQiOiB7CgkJCSJhdXRoIjogIlNrNVFVaTFH 
YVdWc1pGVnpaWEl5TVRNNlEweEtaREpxY0UxelZtTTVlbkpCZFZSR1VHND0iCgkJfQoJfSwKCSJI 
dHRwSGVhZGVycyI6IHsKCQkiVXNlci1BZ2VudCI6ICJEb2NrZXItQ2xpZW50LzE4LjAzLjEtY2Ug 
KGxpbnV4KSIKCX0sCgkiZGV0YWNoS2V5cyI6ICJjdUAiCn0=

Then put the base64 encoded value of the .docker/config.json file as data in below the YAML file:

content_copy zoom_out_map
#secret-jnpr.yaml 
apiVersion: v1 
kind: Secret
type: kubernetes.io/dockerconfigjson 
metadata:
	name: secret-jnpr3 
	namespace: ns-user-1 
data:
.dockerconfigjson: ewoJImF1dGhzIjogewoJCSJodW......
$ kubectl apply -f secret-jnpr.yaml 
secret/secret-jnpr3 created
 

$ kubectl get secrets
NAME	TYPE	DATA AGE
default-token-hkkzr   kubernetes.io/service-account-token   3		64d
secret-jnpr1	kubernetes.io/dockerconfigjson	1		9s
secret-jnpr2	kubernetes.io/dockerconfigjson	1		6m12s
secret-jnpr3	kubernetes.io/dockerconfigjson	1		78s

Keep in mind that base64 is all about encoding instead of encryption – it is considered the same as plain text. So sharing this file compromises the secret.

Refer Secret in Pod

After a secret is created, it can be referred to by a pod/rc or deployment in order to pull an image from the private registry. There are many ways to refer to secrets. This section will examine using imagePullSecrets under pod spec to refer to the secret.

An imagePullSecret is a way to pass a secret that contains a Docker (or other) image registry password to the kubelet so it can pull a private image on behalf of your pod.

Create a pod pulling the Juniper cSRX container from the private repository:

content_copy zoom_out_map
apiVersion: v1 
kind: Pod 
metadata:
	name: csrx-jnpr 
labels:
app: csrx 
annotations:
k8s.v1.cni.cncf.io/networks: '[
{ "name": "vn-left-1" },
{ "name": "vn-right-1" }
]'
spec:
containers:
#- name: csrx 
# image: csrx
-	name: csrx
image: hub.juniper.net/security/csrx:18.1R1.9 
ports:
-	containerPort: 22 
#imagePullPolicy: Never 
imagePullPolicy: IfNotPresent 
stdin: true
tty: true 
securityContext:
privileged: true 
imagePullSecrets:
-	name: secret-jnpr

Now, generate the pod:

content_copy zoom_out_map
$ kubectl apply -f csrx/csrx-with-secret.yaml 
pod/csrx-jnpr created

The cSRX is up and running:

content_copy zoom_out_map
$ kubectl get pod
NAME	READY STATUS RESTARTS AGE
csrx-jnpr		1/1     Running   0		20h

And behind the scenes, the pod authenticates itself towards the private registry, pulls the image, and launches the cSRX container:

content_copy zoom_out_map
$ kubectl describe pod csrx
......
Events:
19h Normal Scheduled Pod Successfully assigned ns-user-1/csrx to cent333
19h Normal Pulling Pod pulling image "hub.juniper.net/security/csrx:18.1R1.9"
19h Normal Pulled Pod  Successfully pulled image "hub.juniper.net/security/csrx:18.1R1.9" 
19h Normal Created Pod Created container
19h Normal Started Pod Started container

As you saw from our test, the secret objects are created independently of the pods, and inspecting the object spec does not provide the sensitive information directly on the screen.

Secrets are not written to the disk, but are instead stored in a tmpfs file system, only on nodes that need them. Also, secrets are deleted when the pod that is dependent on them is deleted.

On most native Kubernetes distributions, communication between users and the API server is protected by SSL/TLS. Therefore, secrets transmitted over these channels are properly protected.

Any given pod does not have access to the secrets used by another pod, which facilitates encapsulation of sensitive data across different pods. Each container in a pod has to request a secret volume in its volumeMounts for it to be visible inside the container. This feature can be used to construct security partitions at the pod level.

footer-navigation