Securing Kubernetes secret files for source control? - security

According to the Kubernetes secrets docs, creating a secret is as easy as base64-encoding the data and placing it in a file.
How then, if base64 can be decoded as easily as it's encoded, can we secure/encrypt the secret values in the file? It would be nice to be able to commit the secret files into source control, however simply committing the file with base64-encoded data is in no way secure.
For example, here is the example given in the docs:
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
password: dmFsdWUtMg0K
username: dmFsdWUtMQ0K
If you went to base64decode.org, you would see that those password/username values simply are "value-2". This file is unfit for source control. How can we secure the data in the file so that it is safe for source control? Or is this considered bad practice, and we should just add the file to .gitignore?

It isn't base64 encoded for security, it is to allow binary content to be stored in secrets. You likely should not commit secret definitions to source control.

For confidential secret keys, can you store them in etcd and retrieve them with confd ?
otherwise, if you really want them in scm, then can you use git-crypt?
https://github.com/AGWA/git-crypt

I'd deploy them with ansible, and encrypt the secrets using ansible-vault, so they could be inside the repository. In addition, they could be stored as text, applying the base64 filter over a template.
Anyway, as it was said before, secrets are not secure. They are just encoded in base64 and could be decoded with:
kubectl get secret mysecret -o jsonpath="{.data.username}" | base64 -d
kubectl get secret mysecret -o jsonpath="{.data.password}" | base64 -d
(what is very useful, by the way)

Related

Create a simple text secret in Google Cloud Secret Manager using CLI

When using the GUI on Google Cloud Console to create a secret all I needed to provide was the secret name and it's value and I'm done.
However, I would like to use the gcloud cli to create simple string secrets.
So far, all the documentations in the docs keep mentioning --data-file like below:
gcloud secrets create sample-secret --data-file="/path/to/file.txt"
How can I use simple strings as the secret value similar to the GUI flow such that I can have a command like
gcloud secrets create apiKey "adadadad181718783"
Must it always be a file?
you could try with this sample command
printf "s3cr3t" | gcloud secrets create my-secret --data-file=-
setting the --data-file=- flag to "-" will read the secret data from stdin.
You can check this documentation for reference

where are tokens created by kubectl create token stored (from v1.24 on)?

This question concerns kubernetes v1.24 and up
So I can create tokens for service accounts with
kubectl create token myserviceaccount
The created token works and serves the purpose, but what I find confusing is that when I kubectl get sa SECRETS field of myserviceaccount is still 0. The token doesn't appear in kubectl get secrets either.
I've also seen that I can pass --bound-object-kind and --bound-object-name to kubectl create token but this doesn't seem to do anything (visible) either...
Is there a way to see created token? And what is the purpose of --bound.. flags?
Thanks to the docs link I've stumbled upon today (I don't know how I've missed it when asking the question because I've spent quite some time browsing through the docs...) I found the information I was looking for. I feel like providing this answer because I find v1d3rm3's answer incomplete and not fully accurate.
The kubernetes docs confirm v1d3rm3's claim (which is btw the key to answering my question):
The created token is a signed JSON Web Token (JWT).
Since the token is JWT token the server can verify if it has signed it, hence no need to store it. JWTs expiry time is set not because the token is not associated with an object (it actually is, as we'll see below) but because the server has no way of invalidating a token (it would actually need to keep track of invalidated tokens because tokens aren't stored anywhere and any token with good signature is valid). To reduce the damage if a token gets stolen there is an expiry time.
Signed JWT token contains all the necessary information inside of it.
The decoded token (created with kubectl create token test-sa where test-sa is service account name) looks like this:
{
"aud": [
"https://kubernetes.default.svc.cluster.local"
],
"exp": 1666712616,
"iat": 1666709016,
"iss": "https://kubernetes.default.svc.cluster.local",
"kubernetes.io": {
"namespace": "default",
"serviceaccount": {
"name": "test-sa",
"uid": "dccf5808-b29b-49da-84bd-9b57f4efdc0b"
}
},
"nbf": 1666709016,
"sub": "system:serviceaccount:default:test-sa"
}
Contrary to v1d3rm3 answer, This token IS associated with a service account automatically, as the kubernets docs link confirm and as we can also see from the token content above.
Suppose I have a secret I want to bind my token to (for example kubectl create token test-sa --bound-kind Secret --bound-name my-secret where test-sa is service account name and my-secret is the secret I'm binding token to), the decoded token will look like this:
{
"aud": [
"https://kubernetes.default.svc.cluster.local"
],
"exp": 1666712848,
"iat": 1666709248,
"iss": "https://kubernetes.default.svc.cluster.local",
"kubernetes.io": {
"namespace": "default",
"secret": {
"name": "my-secret",
"uid": "2a44872f-1c1c-4f18-8214-884db5f351f2"
},
"serviceaccount": {
"name": "test-sa",
"uid": "dccf5808-b29b-49da-84bd-9b57f4efdc0b"
}
},
"nbf": 1666709248,
"sub": "system:serviceaccount:default:test-sa"
}
Notice that binding happens inside the token, under kubernetes.io key and if you describe my-secret you will still not see the token. So the --bound-... flags weren't visibly (from secret object) doing anything because binding happens inside the token itself...
Instead of decoding JWT tokens, we can also see details in TokenRequest object with
kubectl create token test-sa -o yaml
Regarding your question it seems that the command “kubectl create token myserviceaccount” is not as you expect because according to the official documentation, when you are configuring another SA, the token is automatically created and it is referenced by the service account.
Consider that Any tokens for non-existent service accounts will be cleaned up by the token controller.
Regarding to the configuration of the SA check the link
If you want to know more about how the authentication works in the service account token works you can consult context you can consult the link
Incase that you have Sa set it yo can check how to manage it in the link
For establishing bidirectional trust between a node joining the cluster and a control-plane node, consult the link
If you desire to check the token assigned, you can follow:
Get information about your Kubernetes secret object. Secrets are used to store access credentials:
kubectl get secret --namespace={namespace}
output:
NAME TYPE DATA AGE
admin.registrykey kubernetes.io/dockercfg 1 1h
default-token-2mfqv kubernetes.io/service-account-token 3 1h
Get details of the service account token.
kubectl get secret default-token-2mfqv --namespace={namespace} -o yaml
Following is a sample output:
apiVersion: v1
data:
ca.crt: S0tLS1CR...=
namespace: ZGVmYXVsdA==
token: ZXlKaGJHY...=
kind: Secret
metadata:
annotations:
kubernetes.io/service-account.name: default
kubernetes.io/service-account.uid: df441c69-f4ba-11e6-8157-525400225b53
creationTimestamp: 2017-02-17T02:43:33Z
name: default-token-2mfqv
namespace: default
resourceVersion: "37"
selfLink: /api/v1/namespaces/default/secrets/default-token-2mfqv
uid: df5f1109-f4ba-11e6-8157-525400225b53
type: kubernetes.io/service-account-token
Note: The token in the sample output is encoded in base64. You must decode the token and then set this token by using kubectl.
Decode and set the base64-encoded token.
kubectl config set-credentials sa-user --token=$(kubectl get secret <secret_name> -o jsonpath={.data.token} | base64 -d)
kubectl config set-context sa-context --user=sa-user
In the command, <secret_name> type the name of your service account secret.
Connect to the API server.
curl -k -H "Authorization:Bearer {token}"
You can now use kubectl to access your cluster without a time limit for token expiry.
And finally, here you can read about the –bound flag.
****Considering at 1.24 version this have change ****
Once the Pod is running using a SA you could check:
Generate ServiceAccount token manually
Simply generate tokens manually to use in pipelines or whenever we need to contact the K8s Apiserver:
kubectl create token cicd
kubectl create token cicd --duration=999999h
Tip: You can inspect the tokens using for example https://jwt.io, just don’t do this with production tokens!
Create a Secret for the ServiceAccount
We can create Secrets manually and assign these to a ServiceAccount:
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
name: cicd
annotations:
kubernetes.io/service-account.name: "cicd"
if you describe the Secret, we’ll also see that a token was generated for it:
kubectl describe secret cicd
One big difference is though, that the ServiceAccount doesn’t have a Secret section any longer like before:
kubectl get sa cicd -oyaml
To find a Secret belonging to a ServiceAccount we need to search for all Secrets having the proper annotation.
Delete a ServiceAccount
If we delete the ServiceAccount then the Secret will also be deleted automatically, just like in previous versions:
kubectl delete sa cicd
kubectl get sa,secret # all gone
You could check the following video for reference
Look at golder3 answer.
Is there a way to see created token?
No, there isn't. Tokens created with Token Request API are one time creation. Kubernetes doesn't manage tokens, the only way to manage tokens is associate it with a Secret or Pod object. Tokens are JWT objects, so, for security reasons, expiration time of a created token not associated with an object is, by default, of one hour. It can be configured with --duration property.
And what is the purpose of --bound.. flags?
The purpose of --bound flags is to associate a token with a specific object.
From v1.24, you've to manually create tokens.
Using TokenRequest API
It depends of the use case, but it's not so easy to manage these tokens. They're created with the command kubectl create, some examples are:
kubectl create token SERVICE_ACCOUNT_NAME
if the Service Account is in a specific namespace, then you need to define on command:
kubectl create token SERVICE_ACCOUNT_NAME -n NAMESPACE
You can defined expiration time too:
kubectl create token SERVICE_ACCOUNT_NAME --duration 5h
Reference: https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#-em-token-em-
Token associated with a Secret
To create a token associated with a secret object, you can use kubectl apply with a file:
apiVersion: v1
kind: Secret
metadata:
name: demo-token # the name of secret
annotations:
kubernetes.io/service-account.name: "name_of_sa" # the name of the ServiceAccount
type: kubernetes.io/service-account-token
Then, just execute:
kubectl apply -f file.yml
Or, if the ServiceAccount is in a specific namespace
kubectl apply -f file.yml -n NAMESPACE

Azure Data Factory - Storing Inline Passwords

I have a pipeline in Azure Data Factory that starts by going to a REST API to obtain an authorization token. In order to obtain this token, the initial POST request needs to contain a username, password, and private key in the request body. It looks like this:
{
"Username": "<myusername>",
"Password": "<mypassword>",
"PrivateKey":"<privatekey>"
}
Currently I just have this stored as plain text in the Web activity in ADF
To me this doesn't seem very secure and I'm wondering if there is a better way to store this JSON string. I've looked into Azure Key Vault, but that seems to be for storing "data store" credentials.... What is the best practice for storing credentials like this to be used by ADF?
You can save the individual values as Secrets in Key vault and fetch them individually via Web activity from KeyVault with masked output thereby making your ADF secure.
Below GITHUb location contains the Pipeline JSON :
https://github.com/NandanHegde15/Azure-DataFactory-Generic-Pipelines/blob/main/Get%20Secret%20From%20KeyVault/Pipeline/GetSecretFromKeyVault.json
Other way would be to use SecureString Parameter
But would say to avoid using the parameter and leverage the Key Vault
the credentials can be saved in the key vault secret
The secret can be called for authentication in the linked service that connects to the required base url
Refer https://learn.microsoft.com/en-us/azure/data-factory/connector-http?tabs=data-factory#create-a-linked-service-to-an-http-source-using-ui

What kind of security does `identity` provider in Kubernetes EncryptionConfiguration provide?

Ref: https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/#providers
According to the docs
Resources written as-is without encryption. When set as the first provider, the resource will be decrypted as new values are written.
When set as the first provider, the resource will be decrypted as new values are written. sounds confusing. If resources are written as is with no encryption into etcd, why does decrypted as new values are written mean ?
And following that
By default, the identity provider is used to protect secrets in etcd, which provides no encryption.
What kind of security does identity provider give if no encryption happens and if encryption happens, what kind of encryption is it?
As stated in etcd about security
Does etcd encrypt data stored on disk drives?
No. etcd doesn't encrypt key/value data stored on disk drives. If a user need to encrypt data stored on etcd, there are some options:
Let client applications encrypt and decrypt the data
Use a feature of underlying storage systems for encrypting stored data like dm-crypt
First part of the question:
By default, the identity provider is used to protect secrets in etcd, which provides no encryption.
It means that by default k8s api is using identity provider while storing secrets in etcd and it doesn't provide any encryption.
Using EncryptionConfiguration with the only one provider: identity gives you the same result as not using EncryptionConfiguration at all (assuming you didn't have any encrypted secrets before at all).
All secret data will be stored in plain text in etcd.
Example:
providers:
- identity: {}
Second part of your question:
Resources written as-is without encryption.
This is described and explained in the first part of the question
When set as the first provider, the resource will be decrypted as new values are written.
Take a look at this example:
providers:
- aescbc:
keys:
- name: key1
secret: <BASE 64 ENCODED SECRET>
- identity: {}
What this configuration means for you:
The new provider introduced into your EncryptionConfiguration does not affect existing data.
All existing secrets in etcd (before this configuration has been applied) are still in plain text.
Starting with this configuration all new secrets will be saved using aescbc encryption. All new secrets in etcd will have prefix k8s:enc:aescbc:v1:key1.
In this scenario you will have in etcd a mixture of encrypted and not encrypted data.
So the question is why we are using those two providers?
provider: aescbc is used to write new secrets as encrypted data during write operation and to decrypt existing secrets during read operation.
provider: identity is still necessary to read all not encrypted secrets.
Now we are switching our providers in EncryptionConfiguration:
providers:
- identity: {}
- aescbc:
keys:
- name: key1
secret: <BASE 64 ENCODED SECRET>
In this scenario you will have in etcd a mixture of encrypted and not encrypted data.
Starting with this configuration all new secrets will be saved in plain text
For all existing secrets in etcd with prefix k8s:enc:aescbc:v1:key1 provider: aescbc configuration will be used to decrypt existing secrets stored in etcd.
When set as the first provider, the resource will be decrypted as new values are written
In order to switch from mixture of encrypted and not encrypted data into scenario that we have only "not encrypted" data, you should perform read/write operation for all secrets:
$ kubectl get secrets --all-namespaces -o json | kubectl replace -f -
why's it there if it offers no encryption but the docs seem to talk about decryption and how it protects.
It's necessary to have the provider type of identity if you have a mixture of encrypted and not encrypted data
or if you want to decrypt all existing secrets (stored in etcd) encrypted by another provider.
The following command reads all secrets and then updates them to apply server side encryption. More details can be found in this paragraph
$ kubectl get secrets --all-namespaces -o json | kubectl replace -f -
Depending on your EncryptionConfiguration, all secrets will be saved as not encrypted -if the first provider is: identity or encrypted if the first provider is different type.
In addtion
EncryptionConfig is disabled as default setting. To use it, you have to add --encryption-provider-config in your kube-apiserver configuration. Identity is not encrypting any data, as per Encrypted Providers documentation it has 3x N/A.

How does a container ~read~ the secrets stored in a (mounted) secret volume?

This article talks about how to MOUNT the Secret Volume.
https://learn.microsoft.com/en-us/azure/container-instances/container-instances-volume-secret
Use a secret volume to supply sensitive information to the containers
in a container group. The secret volume stores your secrets in files
within the volume, accessible by the containers in the container
group. By storing secrets in a secret volume, you can avoid adding
sensitive data like SSH keys or database credentials to your
application code.
But it does not discuss how to read-out the secrets after it (the secret volume) is created.
The germane code from the article listed below.
"volumes": [
{
"name": "secretvolume1",
"secret": {
"mysecret1": "TXkgZmlyc3Qgc2VjcmV0IEZPTwo=",
"mysecret2": "TXkgc2Vjb25kIHNlY3JldCBCQVIK"
}
}
It looks like any of the containers can mount this special secret volume.
"volumeMounts": [
{
"name": "secretvolume1",
"mountPath": "/mnt/secrets"
}
How does the (parent) container (of the secret volume via the volume-mount) read out the secrets? The secrets are not supposed to be file-persisted.
Better stated, how does my application code retrieve the secret values?
This guy almost got me there, but didn't. :( He reads out the secret using the command line.
https://www.c-sharpcorner.com/article/secret-volumes-with-demo-and-empty-volumes-in-azure-container-instances/
Good call granadaCoder. Yes, the key of the secret, i.e. "mysecret1", "mysecret2", becomes the filename in the volumeMount path, i.e. "/mnt/secrets".
Notice that the values of the secret should be base64-encoded. Their decoded values become the content of the files.
In your case, you will find /mnt/secrets/mysecret1 with content "My first secret FOO", and /mnt/secrets/mysecret2 with content "My second secret BAR" in your container.

Resources