Si bien muchos piensan que es una buena idea de guardar información como usuarios de base de datos, contraseñas, claves API u cualquier otro tipo de información sensible en texto plano y enviarlos a repositorios Git públicos como GitHub, GitLab o BitBucket o inclusive privados, te tengo una mala noticia: NO, lo es. Si aun asi quieres hacerlo, debes encriptarlo primero usando Mozilla SOPS (Secret Operations) y AGE.
PROMO DigitalOcean#
Antes de comenzar, quería contarles que hay una promoción en DigitalOcean donde te dan un crédito de USD 200.00 durante 60 días para que puedas probar los servicios que este Proveedor Cloud ofrece. Lo único que tienes que hacer es suscribirte a DigitalOcean con el siguiente botón:
O a través del siguiente enlace: https://bit.ly/digitalocean-itsm
Instalando SOPS#
SOPS es un editor de secretos que soporta formatos de archivo como: YAML, JSON, ENV, INI y BINARY y podemos encriptarlos con AWS KMS, GCP KMS, Azure Key Vault, age, y PGP (este ultimo descontinuado).
SOPS tiene varias opciones para instalarse, con paquetes construidos para distribuciones basadas en Debian/RedHat.
Desde el repositorio oficial de GitHub de SOPS, vamos a la parte de Releases
Si lo queremos instalar en Debian:
wget https://github.com/mozilla/sops/releases/download/v3.7.3/sops_3.7.3_amd64.deb
sudo dpkg -i ./sops_3.7.3_amd64.deb
o Redhat/AlmaLinux/Rocky/Fedora:
wget https://github.com/mozilla/sops/releases/download/v3.7.3/sops-3.7.3-1.x86_64.rpm
sudo dnf localinstall sops-3.7.3-1.x86_64.rpm
Otra opción podría ser descargarnos el binario de SOPS, instalarlo en /usr/local/bin y darle permisos de ejecución:
wget https://github.com/mozilla/sops/releases/download/v3.7.3/sops-v3.7.3.linux.amd64
sudo mv sops-v3.7.3.linux.amd64 /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops
Comprobemos la version instalada: queremos
sops -v
Y deberíamos ver la siguiente salida:
sops 3.7.3 (latest)
Instalando age#
age es una herramienta escrita en Go, moderna, simple y segura de encriptación. Podemos encriptar y desencriptar nuestros archivos haciendo posible su almacenamiento en repositorios Git.
También cuenta con multiples opciones para instalarlo:
Debian:
sudo apt-get install age
RedHat:
sudo dnf install age
O manual desde la sección Releases del proyecto age:
wget -O age.tar.gz https://github.com/FiloSottile/age/releases/download/v1.0.0/age-v1.0.0-linux-amd64.tar.gz
Luego descomprimimos y movemos los ejecutables a /usr/local/bin:
tar xf age.tar.gz
sudo mv age/age /usr/local/bin
sudo mv age/age-keygen /usr/local/bin
Y les damos permisos de ejecución:
sudo chmod +x /usr/local/bin/age /usr/local/bin/age-keygen
Comprobamos las versiones:
De age:
age -v
Y de age-keygen:
age-keygen -version
En ambos caso deberíamos ver la versión:
v1.0.0
Configurando Llaves de Encriptación#
Ya que tenemos age instalado en nuestro equipo, vamos a generar nuestras llaves publica y privada:
age-keygen -o key.txt
Y nos mostraria la siguiente salida:
age-keygen: warning: writing secret key to a world-readable file
Public key: age16emn5m6lkccns748kz0cyms2jwdykhz4e4pquu50eqt9qlnz8dvquwqcs5
Y si vemos el contenido del archivo key.txt:
# created: 2022-10-03T13:37:37-03:00
# public key: age16emn5m6lkccns748kz0cyms2jwdykhz4e4pquu50eqt9qlnz8dvquwqcs5
AGE-SECRET-KEY-1MZMVXNDA0V4JMYPDAD9V49VQA0KH456AWKSK6S5D22SR0FGYYVGS0E860T
Para facilitar un poco las cosas, vamos a crear una carpeta en nuestro directorio HOME donde guardaremos nuestra llave privada:
mkdir ~/.sops
mv ./key.txt ~/.sops
Y exportaremos una variable de entorno hacia nuestro .zshrc o .bashrc
Zsh:
echo "export SOPS_AGE_KEY_FILE=$HOME/.sops/key.txt" >> ~/.zshrc
Bash:
echo "export SOPS_AGE_KEY_FILE=$HOME/.sops/key.txt" >> ~/.bashrc
Vamos a Encriptar#
Una vez configurado nuestro sistema, procederemos a encriptar nuestros secretos. Hay muchas formas de abordar esto dependiendo del tipo de archivo.
YAML#
Pueden ser secretos de Kubernetes, valores de Helm o solo yaml plano.
Creamos un archivo con el siguiente contenido:
secretos.yml
---
apiVersion: v1
kind: Secret
metadata:
name: mysql-secreto
namespace: default
stringData:
MYSQL_USER: root
MYSQL_PASSWORD: Password_superSecret@
Para encriptar el archivo:
sops --encrypt --age $(cat $SOPS_AGE_KEY_FILE |grep -oP "public key: \K(.*)") --encrypted-regex '^(data|stringData)$' --in-place ./secretos.yaml
Si prestamos atención a la parte del comando que dice --encrypted-regex '^(data|stringData)$'
le estamos indicando a sops que busque dentro de ese archivo la clave stringData
y que encripte su contenido.
Ahora, para desencriptarlo:
sops --decrypt --age $(cat $SOPS_AGE_KEY_FILE |grep -oP "public key: \K(.*)") --encrypted-regex '^(data|stringData)$' --in-place ./secretos.yaml
Kubernetes#
Es bastante util que apliquemos los cambios a Kubernetes sin tener que desencriptar el archivo sino que lo hacemos al vuelo:
Primero lo encriptamos:
sops --encrypt --age $(cat $SOPS_AGE_KEY_FILE |grep -oP "public key: \K(.*)") --encrypted-regex '^(data|stringData)$' --in-place ./secretos.yaml
Y luego lo desencriptamos y aplicamos los cambios al cluster al vuelo, haciéndole pipe a kubectl:
sops --decrypt --age $(cat $SOPS_AGE_KEY_FILE |grep -oP "public key: \K(.*)") --encrypted-regex '^(data|stringData)$' ./secretos.yaml | kubectl apply -f -
Comprobemos los cambios:
kubectl describe secrets mysql-secret
kubectl get secret mysql-secret -o jsonpath='{.data}'
kubectl get secret mysql-secret -o jsonpath='{.data.MYSQL_PASSWORD}' | base64 --decode
.ENV#
Creamos un archivo con el siguiente contenido:
secretos.env
MYSQL_USER=superroot
MYSQL_PASSWORD="Password_superSecret@!!!!############"
Encriptamos:
sops --encrypt --age $(cat $SOPS_AGE_KEY_FILE |grep -oP "public key: \K(.*)") -i .env
Desencriptamos:
sops --decrypt --age $(cat $SOPS_AGE_KEY_FILE |grep -oP "public key: \K(.*)") -i .env
No olvidemos agregar el archivo .decrypted~secretos.env al .gitignore para que no lo suba a Git.
JSON#
Creamos un archivo con el siguiente contenido:
secretos.json
{
"mySqlUser": "superroot",
"password": "Password_superSecret@!!!!#######"
}
Encriptamos:
sops --encrypt --age $(cat $SOPS_AGE_KEY_FILE |grep -oP "public key: \K(.*)") -i secretos.json
Desencriptamos:
sops --decrypt --age $(cat $SOPS_AGE_KEY_FILE |grep -oP "public key: \K(.*)") -i secret.json
No olvidemos agregar el archivo .decrypted~secretos.json al .gitignore para que no lo suba a Git.
.INI#
secretos.ini
[database]
user = superroot
password = Password_superSecret@!!!!1223
Encriptamos:
sops --encrypt --age $(cat $SOPS_AGE_KEY_FILE |grep -oP "public key: \K(.*)") -i secretos.json
Desencriptamos:
sops --decrypt --age $(cat $SOPS_AGE_KEY_FILE |grep -oP "public key: \K(.*)") -i secret.json
No olvidemos agregar el archivo .decrypted~secretos.ini al .gitignore para que no lo suba a Git.
Archivos#
secretos.sql
--- https://xkcd.com/327/
--- DO NOT USE
INSERT INTO Students VALUES ( 'Robert' ); DROP TABLE STUDENTS; --' )
Encriptamos:
sops --encrypt --age $(cat $SOPS_AGE_KEY_FILE |grep -oP "public key: \K(.*)") --in-place ./secretos.sql
Desencriptamos:
sops --decrypt --age $(cat $SOPS_AGE_KEY_FILE |grep -oP "public key: \K(.*)") --in-place ./secret.sql
Flux#
Si tienes un flujo GitOps con Flux, para desencriptar secretos en el cluster te recomiendo leer la documentación de Flux: https://fluxcd.io/flux/guides/mozilla-sops/#configure-in-cluster-secrets-decryption
Conclusión#
Encriptar y desencriptar secretos nunca había sido tal fácil. Con SOPS y age puedes realizar tal tarea sin necesidad de complicarnos mucho y ahorrarnos grandes dolores de cabeza con malas practicas. El siguiente objetivo seria el de juntar todas las piezas y automatizar el flujo de CI/CD para que reconozcan esos valores, cosa que muy seguramente sera en un proximo articulo.
Espero les haya gustado y nos vemos en la próxima!