Skip to main content

Secrets

"Embora seja possível armazenar informações usando ConfigMaps, não é a abordagem ideal para dados sensíveis, como senhas.

Para esses casos, é recomendável utilizar Secrets, projetadas especificamente para armazenar informações críticas, como senhas ou chaves, assim como o ConfigMap, porém com camadas adicionais de segurança."

Secrets funcionam exatamente como ConfigMaps. Primeiro devemos criá-las para serem usadas de forma que sejam injetadas dentro de outros objetos.

Temos dois métodos de criação de Secrets.

  • Imperativo: Quando utilizamos diretamente a linha de comando.
  • Declarativo: Quando utilizamos arquivos.

Um objeto Secret é exatamente igual ao objeto ConfigMap em sua forma, mas os dados precisam estar encodados em base64.

apiVersion: v1
kind: Secret
metadata:
data:

Para criar uma secret utilizando o modo imperativo podemos fazer:

#Generic é o tipo da secret
kubectl create secret generic mysecret --from-literal=key1=david,key2=david2

# Observe o que o valor não é mostrado
kubectl describe secrets mysecret
Name: mysecret
Namespace: default
Labels: <none>
Annotations: <none>

Type: Opaque

Data
====
key1: 17 bytes

# Para deletar

kubectl delete secrets mysecret
secret "mysecret" deleted

Durante o comando automaticamente a o valor das chaves já ficam encodados.

Se quisermos criar isso com arquivos precisamos passar as chaves com valor encodado em base64.

echo -n "david" | base64
ZGF2aWQ=

# só para mostrar como fazer o decode
echo "ZGF2aWQ=" | base64 --decode
david

echo -n "david2" | base64
ZGF2aWQy

echo "ZGF2aWQy" | base64 --decode
david2
apiVersion: v1
kind: Secret
metadata:
name: mysecret
data:
key1: ZGF2aWQ=
key2: ZGF2aWQy
kubectl apply -f mysecret.yaml
secret/mysecret created

kubectl get secrets
NAME TYPE DATA AGE
mysecret Opaque 2 22s
sh.helm.release.v1.istio-base.v1 helm.sh/release.v1 1 12d
sh.helm.release.v1.metrics-server.v1 helm.sh/release.v1 1 12h

Se quisermos ver os valores opacos precisamos pegar a saída completa do yaml.

kubectl get secrets mysecret -o yaml
apiVersion: v1
data:
key1: ZGF2aWQ= #<<<<
key2: ZGF2aWQy #<<<<
kind: Secret
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"key1":"ZGF2aWQ=","key2":"ZGF2aWQy"},"kind":"Secret","metadata":{"annotations":{},"name":"mysecret","namespace":"default"}}
creationTimestamp: "2024-03-18T14:16:06Z"
name: mysecret
namespace: default
resourceVersion: "2824576"
uid: 20429ec4-c03c-4184-ae0f-

Com esses valores precisamo fazer o decode como foi mostrado anteriormente.

Agora vamos utilizar essa secret em um pod. Seria exatamente como faríamos com o configmap, veja a diferença.

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
labels:
run: echo
name: echo
spec:
containers:
- image: alpine
name: echo
command: ["sleep"]
args:
- "1000"
# envFrom:
# - configMapRef:
# name: myconfigmap
envFrom:
- secretRef:
name: mysecret
dnsPolicy: ClusterFirst
restartPolicy: Always
EOF

kubectl exec echo -- env | grep key
key1=david
key2=david2

Neste caso estamos injetando o valor de todas as chaves key1=david e key2=david2. Para adicionar somente uma dessas chaves podemos fazer.

Para pegar somente um chave da secret que contém várias podemos fazer.

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
labels:
run: echo2
name: echo2
spec:
containers:
- image: alpine
name: echo
command: ["sleep"]
args:
- "1000"
env:
- name: mykey
valueFrom:
secretKeyRef:
name: mysecret
key: key2
dnsPolicy: ClusterFirst
restartPolicy: Always
EOF

kubectl exec echo2 -- env | grep key
mykey=david2

Também podemos injetar a secret como arquivos.

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
labels:
run: echo3
name: echo3
spec:
containers:
- image: alpine
name: echo
command: ["sleep"]
args:
- "1000"
volumeMounts:
- name: mysecret-volume
mountPath: /mysecrets
volumes:
- name: mysecret-volume
secret:
secretName: mysecret
EOF

kubectl exec echo3 -- ls /mysecrets
key1
key2
kubectl exec echo3 -- cat /mysecrets/key1
david%
kubectl exec echo3 -- cat /mysecrets/key2
david2%

No exemplo acima estamos criando um volume com os valores das chaves e estes serão usados no volume mount. Teremos /mysecrets/key1 e /mysecrets/key2 e o conteúdo será o valores reais das chaves, ou seja, não encodados.

Como esse pod esta no worker3, vamos vamos conferir o que acontece lá dentro.

k get pods echo3 -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
echo3 1/1 Running 0 5m26s 10.44.0.6 cka-cluster-worker3 <none> <none>

kubectl get pods echo3 -o yaml | grep uid
uid: 08198260-0b94-4934-93a3-b63d252163a7

# OS COMANDO ABAIXO SERÃO DENTRO DO WORKER3 PARA ONDE O POD FOI
root@cka-cluster-worker3:/opt# cd /var/lib/kubelet/pods/
root@cka-cluster-worker3:/var/lib/kubelet/pods# ls
08198260-0b94-4934-93a3-b63d252163a7 576a9764-b33e-48e4-82d3-daa925c59c0b b055b81d-96d4-4fe7-8666-635a9d03f4bf e9870bb1-96dd-4ca2-b2e0-885cac2d26f8 fe715166-b49a-4e50-8e5b-1b6e3b65d5b0
50b04dc3-709a-40fa-a916-4f78d3f3ce1b 67a3c831-9b96-4987-8b98-4695e90b0322 c9626cf9-9095-48e0-b313-e1b8f616a0b8 eb4b3290-e36a-4a8e-9d28-39b0cfbe4ac1

# Mesmo uid
root@cka-cluster-worker3:/var/lib/kubelet/pods# cd 08198260-0b94-4934-93a3-b63d252163a7/
root@cka-cluster-worker3:/var/lib/kubelet/pods/08198260-0b94-4934-93a3-b63d252163a7# ls
containers etc-hosts plugins volumes
root@cka-cluster-worker3:/var/lib/kubelet/pods/08198260-0b94-4934-93a3-b63d252163a7# cd volumes/
root@cka-cluster-worker3:/var/lib/kubelet/pods/08198260-0b94-4934-93a3-b63d252163a7/volumes# ls
kubernetes.io~projected kubernetes.io~secret
root@cka-cluster-worker3:/var/lib/kubelet/pods/08198260-0b94-4934-93a3-b63d252163a7/volumes# cd kubernetes.io~secret/
root@cka-cluster-worker3:/var/lib/kubelet/pods/08198260-0b94-4934-93a3-b63d252163a7/volumes/kubernetes.io~secret# ls
mysecret-volume
root@cka-cluster-worker3:/var/lib/kubelet/pods/08198260-0b94-4934-93a3-b63d252163a7/volumes/kubernetes.io~secret# cd mysecret-volume/
root@cka-cluster-worker3:/var/lib/kubelet/pods/08198260-0b94-4934-93a3-b63d252163a7/volumes/kubernetes.io~secret/mysecret-volume# ls
key1 key2
root@cka-cluster-worker3:/var/lib/kubelet/pods/08198260-0b94-4934-93a3-b63d252163a7/volumes/kubernetes.io~secret/mysecret-volume# cat key1
david

Alguns detalhes sobre secrets:

  • Secrets não são encriptadas, mas encodadas. Qualquer um pode simplesmente durante o código fazer o decode de uma secret em base64 assim como fizemos manualmente.
  • As secrets não são armazenas encriptadas no etcd, mas é possível fazer a encriptação via rest.
  • Qualquer um com permissão de criar pods/deployments no mesmo namespace que a secrets podem acessar as secrets. É necessário garantir os privilégios para acessar as secrets usando RBAC.
  • É bom considerar outras alternativas para armazenar como AWS, Azure, GCP, Vault, etc

Encryption at Rest

É possível encriptar os dados quando são gravados no ETCD. Isso não quer dizer que os dados depois de lidos do ETCD estejam encriptados.

Para que isso seja possível o kube-apiserver precisa saber o que deve ser encriptado ou não.

No kube-apiserver temos as configurações passadas.

kubectl get pods -n kube-system kube-apiserver-cka-cluster-control-plane -o=jsonpath='{.spec.containers[*].command}' | jq
[
"kube-apiserver",
"--advertise-address=172.18.0.5",
"--allow-privileged=true",
"--authorization-mode=Node,RBAC",
"--client-ca-file=/etc/kubernetes/pki/ca.crt",
"--enable-admission-plugins=NodeRestriction",
"--enable-bootstrap-token-auth=true",
"--etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt",
"--etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt",
"--etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key",
"--etcd-servers=https://127.0.0.1:2379",
"--kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt",
"--kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key",
"--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname",
"--proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt",
"--proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key",
"--requestheader-allowed-names=front-proxy-client",
"--requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt",
"--requestheader-extra-headers-prefix=X-Remote-Extra-",
"--requestheader-group-headers=X-Remote-Group",
"--requestheader-username-headers=X-Remote-User",
"--runtime-config=",
"--secure-port=6443",
"--service-account-issuer=https://kubernetes.default.svc.cluster.local",
"--service-account-key-file=/etc/kubernetes/pki/sa.pub",
"--service-account-signing-key-file=/etc/kubernetes/pki/sa.key",
"--service-cluster-ip-range=10.96.0.0/12",
"--tls-cert-file=/etc/kubernetes/pki/apiserver.crt",
"--tls-private-key-file=/etc/kubernetes/pki/apiserver.key"
]

--encription-provider-config não foi passado.

Vamos primeiro criar esse arquivo de configuração para saber o que deve ou não ser encriptado.

Não é necessário encriptar todos os recursos somente aquele que tem dados sensíveis por exemplo secrets e configmaps.

# Precisamos gerar uma chave de encriptação com o comando abaixo. Essa chave será usadas para encriptar os dados que vão para o etcd.
ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)

echo $ENCRYPTION_KEY
5T5/bTFcDZYzYisA4DC1mw1Qpl7NIP4BOhMkfM7zgz8=

cat > encryption-config.yaml <<EOF
apiVersion: v1
kind: EncryptionConfig
resources:
- resources: # Quais os recursos que queremos encriptar??
- secrets
- configmaps
providers:
- aescbc:
keys:
- name: key1
secret: ${ENCRYPTION_KEY}
- identity: {}

EOF

Os providers são os usados para encriptar os recursos. A ordem que são passados importa. Se identity que não possui nada for o primeiro nada será criptografado.

Coloque o arquivo entro de /etc/kubernetes por exemplo que já está montado e referencie o path para esse arquivo.

Daqui pra frente todos os recursos configmaps e secrets estão encriptados no ETCD.

Se você já tiver secrets e precisa encriptá-las precisamos recriá-las.

kubectl get secrets --all-namespaces -o yaml > allsecrets.yaml
kubectl replace --force -f allsecrets.yaml