Can Kubernetes secrets store newlines? - kubernetes

I've created a secret from a file using a command like:
kubectl create secret generic laravel-oauth \
--from-file=./.work-in-progress/oauth_private.key \
--from-file=./.work-in-progress/oauth_public.key
However it seems new lines are stripped from the files (when using the secrets as ENV variables).
There is a 'encoding' note in the docs that state:
The serialized JSON and YAML values of secret data are encoded as
base64 strings. Newlines are not valid within these strings and must
be omitted. When using the base64 utility on Darwin/macOS users should
avoid using the -b option to split long lines. Conversely Linux users
should add the option -w 0 to base64 commands or the pipeline base64 |
tr -d '\n' if -w option is not available.
However I assumed this only applies for 'manually' created secrets via YAML files.

The new lines are not stripped the files are just base64 encoded as mentioned in the other answers too. For example:
# mycert.pem
-----BEGIN CERTIFICATE-----
xxxxxx
xxxxxx
...
-----END CERTIFICATE-----
Then:
$ kubectl create secret generic mysecret --from-file=./cert.pem
Then:
$ kubectl get secret mysecret -o=yaml
apiVersion: v1
data:
cert.pem: <base64 encoded string>
kind: Secret
metadata:
creationTimestamp: 2018-11-14T18:11:46Z
name: mysecret
namespace: default
resourceVersion: "20180431"
selfLink: /api/v1/namespaces/default/secrets/mysecret
uid: xxxxxx
type: Opaque
Then if you decode it, you will get the original secret.
$ echo '<base64 encoded string>' | base64 -D
-----BEGIN CERTIFICATE-----
xxxxxx
xxxxxx
...
-----END CERTIFICATE-----
Also, this is not necessarily secure at rest. If you are looking for more security you can use something like Hashicorp Vault or as alluded by #Alex Bitnami's sealed secrets.

The note you refer to is for the base64 encoded string itself (not the content that was encoded).
Using secrets as env var will potentially expose them via the dashboard "preview eye" (if you use the Kube Dashboard), you should mount them into a directory and make the app load them from there instead; I fell for that too and was surprised I was able to view the secret.
I've not come across the stripping of new line characters, as the above command would simply do a base64 of the content (including new line chars). That said, storing the secrets b64 encoded is not exactly safe either, you should consider using sealed-secrets (bitnami) instead, it works just like normal secrets, but is actually encrypted at rest.
HTH,
Alex

It seems newlines work fine (maybe I ran into another issue earlier).
Here is a full example:
#!/usr/bin/env bash
set -euo pipefail
printf "123\n456\n789" > ./.work-in-progress/example.txt
kubectl create secret generic example-test \
--from-file=./.work-in-progress/example.txt \
--dry-run -o yaml | kubectl apply -f -
cat <<EOF | kubectl apply -f -
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: example
labels:
app: example
spec:
replicas: 1
selector:
matchLabels:
app: example
template:
metadata:
labels:
app: example
spec:
volumes:
- name: example-test-volume
secret:
secretName: example-test
containers:
- name: app
command: ["sleep", "99999999"]
image: busybox:latest
imagePullPolicy: IfNotPresent
volumeMounts:
- name: example-test-volume
mountPath: /tmp/example
env:
- name: exampleenv
valueFrom:
secretKeyRef:
name: example-test
key: example.txt
EOF

Related

Kubernetes - Create custom secret holding SSL certificates

I have a problem. In my kubernetes cluster I am running a GitLab image for my own project. This image requires a .crt and .key as certificates for HTTPS usage. I have setup an Ingress resource with a letsencrypt-issuer, which successfully obtains the certificates. But to use those they need to be named as my.dns.com.crt and my.dns.com.key. So I manually ran the following 3 commands:
kubectl get secret project-gitlab-tls -n project-utility \
-o jsonpath='{.data.tls\.crt}' | base64 --decode > /mnt/data/project/gitlab/certs/tls.crt
kubectl get secret project-gitlab-tls -n project-utility \
-o jsonpath='{.data.tls\.key}' | base64 --decode > /mnt/data/project/gitlab/certs/tls.key
kubectl create secret generic gitlab-registry-certs \
--from-file=gitlab.project.com.crt=/mnt/data/project/gitlab/certs/tls.crt \
--from-file=gitlab.project.com.key=/mnt/data/project/gitlab/certs/tls.key \
--namespace project-utility
The first 2 commands print the decoded crt/key content in a file, so that the third command can use those files to create a custom mapping to the specific DNS names. Then in the GitLab deployment I mount this gitlab-registry-certs like this:
volumeMounts:
- mountPath: /etc/gitlab/ssl
name: registry-certs
volumes:
- name: registry-certs
secret:
secretName: gitlab-registry-certs
This all works, but I want this process to be automated, because I am using ArgoCD as deployment tool. I thought about a job, but a job runs a ubuntu version which is not allowed to make changes to the cluster, so I need to call a bash script on the external host. How can I achieve this, because I can only find things about jobs which run an image and not how to execute host commands. If there is a way easier method to use the certificates that I am not seeing please let me know, because I kinda feel weird about this way of using the certificates, but GitLab requires the naming convention of <DNS>.crt and <DNS>.key, so thats why I am doing the remapping.
So the question is how to automate this remapping process so that on cluster generation a job will be executed after obtaining the certificates but before the deployment gets created?
Why are you bothering with this complicated process of creating a new secret? Just rename them in your volumeMounts section by using a subPath:
containers:
- ...
volumeMounts:
- name: registry-certs
mountPath: /etc/gitlab/ssl/my.dns.com.crt
subPath: tls.crt
- name: registry-certs
mountPath: /etc/gitlab/ssl/my.dns.com.key
subPath: tls.key
volumes:
- name: registry-certs
secret:
secretName: project-gitlab-tls
More info in the documentation.

How to create a Kubernetes configMap from part of a yaml file?

As I know the way to create a configMap in Kubernetes from a file is to use:
--from-file option for kubectl
What I am looking for is a way to only load part of the yaml file into the configMap.
Example:
Let's say I have this yml file:
family:
Boys:
- name: Joe
- name: Bob
- name: dan
Girls:
- name: Alice
- name: Jane
Now I want to create a configMap called 'boys' which will include only the 'Boys' section.
Possible?
Another thing that could help if the above is not possible is when I am exporting the configMap as environment variables to a pod (using envFrom) to be able to only export part of the configMap.
Both options will work for me.
Any idea?
The ConfigMap uses a key and value for its configuration. Based on your example, you get multiple arrays of data where there are multiple values with their own keys. But you can create multiple ConfigMap from different file for these issues.
First you need to create .yaml files to create a ConfigMap guided by the documentation.
First file call Boys.yaml
# Env-files contain a list of environment variables.
# These syntax rules apply:
# Each line in an env file has to be in VAR=VAL format.
# Lines beginning with # (i.e. comments) are ignored.
# Blank lines are ignored.
# There is no special handling of quotation marks (i.e. they will be part of the ConfigMap value)).
name=Joe
name=Bob
name=Dan
Second file call Girls.yaml
name=Alice
name=Jane
Create your ConfigMap
kubectl create configmap NmaeOfYourConfigmap --from-env-file=PathToYourFile/Boys.yaml
where the output is similar to this:
apiVersion: v1
kind: ConfigMap
metadata:
creationTimestamp:
name: NmaeOfYourConfigmap
namespace: default
resourceVersion:
uid:
data:
name: Joe
name: Bob
name: Dan
Finally, you can pass these ConfigMap to pod or deployment using configMapRef entries:
envFrom:
- configMapRef:
name: NmaeOfYourConfigmap-Boys
- configMapRef:
name: NmaeOfYourConfigmap-Girls
Configmaps cannot contain rich yaml data. Only key value pairs. So if you want to have a list of things, you need to express this as a multiline string.
With that in mind you could use certain tools, such a yq to query your input file and select the part you want.
For example:
podman run -rm --interactive bluebrown/tpl '{{ .family.Boys | toYaml }}' < fam.yaml \
| kubectl create configmap boys --from-file=Boys=/dev/stdin
The result looks like this
apiVersion: v1
kind: ConfigMap
metadata:
name: boys
namespace: sandbox
data:
Boys: |+
- name: Joe
- name: Bob
- name: dan
You could also encode the file or part of the file with base64 and use that as an environment variable, since you get a single string, which is easily processable, out of it. For example:
$ podman run --rm --interactive bluebrown/tpl \
'{{ .family.Boys | toYaml | b64enc }}' < fam.yaml
# use this string as env variable and decode it in your app
LSBuYW1lOiBKb2UKLSBuYW1lOiBCb2IKLSBuYW1lOiBkYW4K
Or with set env which you could further combine with dry run if required.
podman run --rm --interactive bluebrown/tpl \
'YAML_BOYS={{ .family.Boys | toYaml | b64enc }}' < fam.yaml \
| kubectl set env -e - deploy/myapp
Another thing is, that YAML is a superset of JSON, in many cases you are able to convert YAML to JSON or at least use JSON like syntax.
This can be useful in such a scenario in order to express this as a single line string rather than having to use multiline syntax. It's less fragile.
Every YAML parser will be able to parse JSON just fine. So if you are parsing the string in your app, you won't have problems.
$ podman run --rm --interactive bluebrown/tpl '{{ .family.Boys | toJson }}' < fam.yaml
[{"name":"Joe"},{"name":"Bob"},{"name":"dan"}]
Disclaimer, I created the above used tool tpl. As mentioned, you might as well use alternative tools such as yq.

Kubernetes Secret is not stored in encoded format in environment variables

I am a beginner to Kubernetes. I have created a secret file and referred it in deployment yaml file.
app-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: app-secret
data:
username: YWRtaW4=
password: YWRtaW4=
deploy.yaml
env:
- name: DEPLOY_ENV
value: ${env}
- name: NAMESPACE_NAME
valueFrom:
fieldRef:
fieldPath : metadata.namespace
- name: APP_USERNAME
valueFrom:
secretKeyRef:
name: app-secret
key: username
- name: APP_PASSWORD
valueFrom:
secretKeyRef:
name: app-secret
key: password
While using the command kubectl get secret pod-54rfxd -n dev-ns -o json, it is printing the username and password in encoded format only. When i query for the environment variables list using the command kubectl exec pod-54rfxd -n dev-ns -- printenv, it was giving below result.
APP_USERNAME=admin
APP_PASSWORD=admin
Why it was not in encoded format in environment variables. Could you please let me know the reason and is it possible to have it in encoded format?
You could use the stringData format:
apiVersion: v1
kind: Secret
metadata:
name: app-secret
stringData:
username: "YWRtaW4="
password: "YWRtaW4="
From K8s doc:
K8s doc
Secret get stored with the base64 encoded format when you create the secret. hile adding or injecting the secret into the pod or deployment Kubernetes by default decode the secret with base64 so due to that you are getting the plain text from OS as environment variables.
there are some other option to encrypt at rest not encode.
https://cloud.google.com/kubernetes-engine/docs/how-to/encrypting-secrets
https://docs.oracle.com/en-us/iaas/Content/ContEng/Tasks/contengencryptingdata.htm
is it possible to have it in encoded format?
what you can do is to save the encoded .env file into secret and mount that file into the deployment path that .env file will be accessible to the application while content inside it will be encoded.
kubectl exec pod-54rfxd -n dev-ns -- printenv
this command only you can run maybe when you have admin permission of cluster otherwise not other can access inside the pod.

How to set kubernetes secret key name when using --from-file other than filename?

Is there a way to set a kubernetes secret key name when using --from-file other than the filename?
I have a bunch of different configuration files that I use as secrets.json within my containers. However, to organize my files, none of them are named secrets.json on my host. For example secrets.dev.json or secrets.test.json. My apps only know to read in secrets.json.
When I create a secret with kubectl create secret generic my-app-secrets --from-file=secrets.dev.json, this results in the key name being secrets.dev.json and not secrets.json.
I'm mounting in my secret contents as a file (this is a carry-over from migrating from Docker swarm).
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-deployment
spec:
template:
spec:
volumes:
- name: my-secret
secret:
secretName: my-app-secrets
containers:
- name: my-app
volumeMounts:
- name: my-secret
mountPath: "/run/secrets/secrets.json"
subPath: "secrets.json"
Because secrets.json doesn't exist as a key because it used the filename (secrets.dev.json), it ends up getting turned into a directory instead. I end up getting this mount path: /run/secrets/secrets.json/secrets.dev.json.
I'd like to be able to set the key name to secrets.json instead of using the filename of secrets.dev.json.
You can specify key name [--from-file=[key=]source]
kubectl create secret generic my-app-secrets --from-file=secrets.json=secrets.dev.json
Here, secrets.json is key name and secrets.dev.json is source

Automatically generated strings for secrets using yaml config

I have a deployment config for an app, that (among other things) creates a secret for a mysql database:
---
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
type: Opaque
data:
MYSQL_USER: my_user
MYSQL_PASSWORD: my_random_secret
MYSQL_DATABASE: my_db
MYSQL_ROOT_PASSWORD: my_random_secret
---
etc...
The deployment file is under source control, so I don't want to place the secrets there.
Does anyone know how I can tell Kubernetes to generate random strings for each variable which has my_random_secret as a value in my example? Preferably something that can be configured using the yaml file, without needing to invoke any extra commands.
As far I have understood that you do not want to keep your secret information locally. So that you need to generate them when you are creating that secret.
I think there is a way to create Kubernetes resource using go-template. Didn't find enough information for that. I can't help you in this way.
But you can also create secret using script. And your secret will not be exposed.
Following script can help you in that case. This will generate random password for you and will create secret with that.
cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
type: Opaque
data:
MYSQL_PASSWORD: $(head -c 24 /dev/random | base64)
MYSQL_ROOT_PASSWORD: $(head -c 24 /dev/random | base64)
stringData:
MYSQL_USER: my_user
MYSQL_DATABASE: my_db
EOF
Run this script.
Hope it will work for you
If you are using Helm chart, you can do this:
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
type: Opaque
data:
MYSQL_USER: bXlfdXNlcgo=
MYSQL_PASSWORD: {{ randAlphaNum 16 | b64enc }}
MYSQL_DATABASE: bXlfZGIK
MYSQL_ROOT_PASSWORD: {{ randAlphaNum 16 | b64enc }}
Here,
echo "my_user" | base64 => bXlfdXNlcgo= &
echo "my_db" | base64 => bXlfZGIK
Otherwise, we can have a similar kind of feature. Or, if you want to generate it from the bash/shell script we can have $(head /dev/urandom | LC_ALL=C tr -dc A-Za-z0-9 | head -c16 | base64) as a unique password generator on the shell.
You can also use open ssl
openssl rand -base64 32
Or if you need plaintext/numbers:
openssl rand -base64 32 | tr -cd '[:alpha:]\n'
Or if you don't want the trailing CR:
openssl rand -base64 32 | tr -cd '[:alpha:]'
Note that anything longer than -base64 48 might add CRs to the output. Adjust your tr to taste, e.g.
openssl rand -base64 128 | tr -cd '[:alpha:]'
will concatenate the multiple lines from openssl, but omit a trailing \n as well