Automatically generated strings for secrets using yaml config - kubernetes

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

Related

Role created by environment variable set by kubernetes secret in side official postgres image ends with a `+`

I am seeing a very strange issue trying to start the official postgres:14.6-alpine image on Kubernetes.
For reference the official postgres image allows for configuring the initialization script using the POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB environment variables.
I have the following secret and configmap defined:
---
apiVersion: v1
kind: Secret
metadata:
namespace: default
name: postgres-credentials
data:
DATABASE_URL: cG9zdGdyZXM6Ly9sZXRzY2h1cmNoOnBhc3N3b3JkQHBvc3RncmVzOjU0MzIvbGV0c2NodXJjaA==
POSTGRES_USER: bGV0c2NodXJjaA==
POSTGRES_PASSWORD: cGFzc3dvcmQ=
---
apiVersion: v1
kind: ConfigMap
metadata:
namespace: default
name: postgres-config
data:
POSTGRES_DB: letschurch
The value POSTGRES_USER value of bGV0c2NodXJjaA== decodes to letschurch and the POSTGRES_PASSWORD value of cGFzc3dvcmQ= decodes to password.
I also have the following deployment:
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
labels:
app: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
restartPolicy: Always
containers:
- image: postgres:14.6-alpine
name: postgres
ports:
- containerPort: 5432
envFrom:
- configMapRef:
name: postgres-config
- secretRef:
name: postgres-credentials
When I shell into the running container, I can echo out the environment variables, and they appear to be intact:
postgres-74f67b778-lsv4c:/# echo $POSTGRES_USER
letschurch
postgres-74f67b778-lsv4c:/# echo $POSTGRES_PASSWORD
password
postgres-74f67b778-lsv4c:/# echo $POSTGRES_DB
letschurch
postgres-74f67b778-lsv4c:/# echo -n $POSTGRES_USER | wc -c
10
postgres-74f67b778-lsv4c:/# echo -n $POSTGRES_PASSWORD | wc -c
8
postgres-74f67b778-lsv4c:/# echo -n $POSTGRES_DB | wc -c
10
postgres-74f67b778-lsv4c:/# [ "$POSTGRES_USER" = "$POSTGRES_DB" ] && echo 'good!'
good!
However, I am not able to connect with the role letschurch. I can connect as temporal (another role I have set up with an init script), and when I run \l and \du I see that the role (but not the database name) have a + appended:
temporal=> \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
---------------------+------------+----------+------------+------------+--------------------------
letschurch | letschurch+| UTF8 | en_US.utf8 | en_US.utf8 |
temporal=> \du
List of roles
Role name | Attributes | Member of
------------+------------------------------------------------------------+-----------
letschurch+| Superuser, Create role, Create DB, Replication, Bypass RLS | {}
| |
temporal | | {}
At first I thought that the base64-encoded POSTGRES_USER environment variable might have some whitespace or something encoded in it, so I double checked that I was encoding the value properly with echo -n letschurch | base64, and as you can see in the shell output above the resulting value is exactly 10 characters long, no extra whitespace. Also, the POSTGRES_USER and POSTGRES_DB environment variables are equal, but they appear to result in different outcomes in postgres.
Also, this does not happen with docker-compose. Given the following configuration, everything works as expected:
postgres:
image: postgres:14.6-alpine
environment:
POSTGRES_USER: letschurch
POSTGRES_PASSWORD: password
POSTGRES_DB: letschurch
ports:
- '5432:5432'
What am I missing here? Why does the letschurch role get a + appended to it? Is there something to do with secrets that doesn't apply to configMaps that I'm missing?
I think this had to do with a specific setting I had enabled for Docker Desktop for Mac:
I had changed from the default value (gRPC FUSE) to VirtioFS. When I changed it back to gRPC FUSE a number of issues resolved themselves, this one included.

Helm - go templates : bad character U+002F '/'

I have this job yaml which work well:
apiVersion: batch/v1
kind: Job
metadata:
name: cli-commands
spec:
template:
spec:
containers:
- name: cli-commands
image: ubuntu:22.04
command: [ 'bash', '-c']
args:
- DEBIAN_FRONTEND=noninteractive apt update && apt install -y curl && curl -sL https://aka.ms/InstallAzureCLIDeb | bash &&
echo installation successful &&
az storage directory create --account-name {{ .Values.env.secret.azurestorageaccountname | b64dec}} --name {{ .Release.Namespace }}
--share-name {{ .Values.systemFilesPath | default (.Values.serviceName) }}
--account-key *****
restartPolicy: Never
The issue is:
I need to change this part of code (first version):
--share-name {{ .Values.systemFilesPath | default (.Values.serviceName) }}
into this (second version):
--share-name {{ .Values.systemFilesPath | default coreregciqa/(.Values.serviceName) }}
but the second version doesn't work and throws this error:
bad character U+002F '/'
How can I resolve that?
All what I want to do is to create a default value which looks like that (for example):
coreregciqa/mono ,but I dont know how to deal with the '/' in this case.
My first version works well because I don't have the '/' there, but my second version doesn't work.
I need your advice please.
Thanks a lot.
Default needs a value after it, and it can't automatically complete the splicing, you need to explicitly call the function to connect the string.
Like this:
--share-name {{ .Values.systemFilesPath | default (printf "coreregciqa/%s" .Values.serviceName) }}

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.

Variable substitution in a configuration file

Variable substitution in Gitlab only seem to work inside the gitlab-ci.yml file.
However I have a configuration file (k8s secret) where I would like to set different values depending if I'm in a staging or a production environment.
apiVersion: v1
kind: Secret
metadata:
name: tls-config
namespace: myNamespace
type: kubernetes.io/tls
data:
tls.crt: |
${TLS_CRT}
tls.key: |
${TLS_KEY}
Where TLS_CRT & TLS_KEY would be variables defined in /settings/ci_cd/variables.
How should one handle variable substitution in this case?
You could use an editor like sed
For example, if you had the file like:
data:
tls.crt: |
#TLS_CRT#
tls.key: |
#TLS_KEY#
You could use sed like this as part of your GitLab job to replace its contents:
myjob:
script:
- sed -i "s|#TLS_CRT#|${TLS_CRT}|" ./path/to/file
- sed -i "s|#TLS_KEY#|${TLS_KEY}|" ./path/to/file
Then the file contents will be something like this:
data:
tls.crt: |
MIIC2DCCAcCgAwIBAgIBATANBgkqh ...
tls.key: |
MIIEpgIBAAKCAQEA7yn3bRHQ5FHMQ ...
Using the pattern #word# is not totally necessary. However, it does make using the sed easier, as it avoids needing to escape the literal ${} characters in the shell command.
As a short explanation of the sed command used here:
# v shorthand for --in-place; edits the file directly
sed -i "s|FIRST_ARGUMENT|SECOND_ARGUMENT|" ./path/to/file
# ^ replace this ^ with this ^ in this file
Fore more info, see here: https://unix.stackexchange.com/a/112024/453397

Can Kubernetes secrets store newlines?

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