Ir al contenido
  1. Posts/

Protegiendo Secretos con Mozilla Sops y Age

·1009 palabras·5 mins· loading · loading ·
DevOps Seguridad Kubernetes mozilla sops devops kubernetes secrets seguridad
Autor
Enmanuel Moreira
Ingeniero DevOps de día y aprendiz de Barman en mis tiempos libres, con experiencia en Kubernetes, Cloud, y DevOps. También disfruta de hacer stream de juegos, hablar de CI/CD, desplegar en producción un viernes con Terraform y automatizar tareas aburridas con Ansible.

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:

DigitalOcean Referral Badge

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
Recuerda que la llave privada no debemos guardarla en ningún repositorio Git.

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!