Simple API Python couplé à un front qui permet de jouer au pile ou face. Au bout d'un certain score... un secret s'affiche.
- Le secret peut-être définit par l'utilisateur via la variable
SECRET_VALUE
. - Cette application a besoin d'une base de donnée relationelle pour fonctionner correctement. Configurez la connexion avec les variables
DB_HOSTNAME
,DB_USERNAME
,DB_PASSWORD
etDB_DATABASE
. - Enfin, cette application est prévue pour devenir Vault aware. Vous pouvez donc lui passer les variables
VAULT_ADDR
etVAULT_TOKEN
. Mais il faudra modifier le code pour que cela puisse avoir un effet quelconque.
Adaptez ma CI à votre cas, pushez l'image dans votre registry (en publique).
Installer Vault (pour la CLI).
Configurer la CLI :
vault -autocomplete-install
Avec l'outil de votre choix : Kind, Minikube, k3s, ...
Obtenir des informations sur le cluster Kind :
kubectl cluster-info --context kind-discovering-vault
Helm, un chart Helm, c'est quoi ?
helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
helm upgrade --install csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver \
--namespace kube-system
helm repo add hashicorp https://helm.releases.hashicorp.com
helm upgrade --install vault hashicorp/vault \
--namespace=vault \
--create-namespace \
-f manifests/vault/values.yaml
Créer un enregistrement DNS qui pointe sur les worker nodes (+ voir slides).
k get nodes -o wide
# /etc/hosts
# 172.18.0.2 vault.kubernetes.fr
D'ailleurs, comment on crée de la haute disponibilité ?
Le cluster est en place !
WTF ?! Le pod vault est en 0/1...
kubectl -n vault exec -it vault-0 -- vault operator init
# Unseal Key 1: HZUKc4vVbrpWpBge7ga2hekA83wEo073cssW30iaMuJp
# Unseal Key 2: iQy19WSM3A+Tkm27Vvv0PanHlnFNOidpPwiEteo3dZl5
# Unseal Key 3: dfg4+1WV4uLWfkW/4sZ2lUiJIXiwB4Qim2eeYRVvBVNV
# Unseal Key 4: lVo5pu0IR2Qt6MkvU8duS6QyXiWg2hdrLyfZUJNDdVa4
# Unseal Key 5: 72j53CfHturomZlr2KURBHSNw2baupTfPpaicovJOtdU
#
# Initial Root Token: hvs.nVce65NceyR6OBn2H25pG9sn
#
# Vault initialized with 5 key shares and a key threshold of 3. Please securely
# distribute the key shares printed above. When the Vault is re-sealed,
# restarted, or stopped, you must supply at least 3 of these keys to unseal it
# before it can start servicing requests.
#
# Vault does not store the generated root key. Without at least 3 keys to
# reconstruct the root key, Vault will remain permanently sealed!
#
# It is possible to generate new unseal keys, provided you have a quorum of
# existing unseal keys shares. See "vault operator rekey" for more information.
kubectl -n vault exec -it vault-0 -- vault operator unseal
export VAULT_ADDR=http://vault.kubernetes.fr:30000
vault login
- Création d'une Auth Method, on prendra la UserPass
- Création d'un utilisateur
- Création d'une policy « admins »
- Affectation de la policy à notre utilisateur
helm upgrade --install postgresql oci://registry-1.docker.io/bitnamicharts/postgresql \
--namespace=demo \
--create-namespace \
-f manifests/postgresql/values.yaml
Se connecter à la base de donnée pour voir si tout est ok :
sudo apt update && sudo apt install postgresql-client
kubectl port-forward --namespace demo svc/postgresql 5432:5432 &
PGPASSWORD="postgres" psql --host 127.0.0.1 -U postgres -d flip_a_coin -p 5432
Si vous voulez cancel le port-forward :
jobs
fg %job_id
Ctrl + C
helm upgrade --install flip-a-coin manifests/flip-a-coin \
--namespace=demo \
--create-namespace \
-f manifests/flip-a-coin/values.yaml
Si vous le souhaitez, vous pouvez créer un enregistrement DNS qui pointe sur les worker nodes, mais pour cette application cette fois (avec un nom de domaine différent).
k get nodes -o wide
# /etc/hosts
# 172.18.0.2 vault.kubernetes.fr
# 172.18.0.2 app.kubernetes.fr
Faites-le avec Terraform (pensez à adapter les variables !) :
cd infrastructure/vault
terraform init
terraform apply
cd -
Peu de configuration car Vault est déjà dans le cluster Kubernetes, et Helm nous a vachement simplifié la vie...
Avec les valeurs par défaut
vault secrets enable database
Puis, on configure ce secret engine :
vault write database/config/demo \
plugin_name=postgresql-database-plugin \
verify_connection=false \
allowed_roles="*" \
connection_url="postgresql://{{username}}:{{password}}@postgresql.demo.svc.cluster.local:5432/flip_a_coin?sslmode=disable" \
username=postgres \
password=postgres
vault write --force /database/rotate-root/demo
T'as plus le mot de passe :-)
NOTE: Si on veut on peut rotate à la main le mot de passe root depuis l'UI et la CLI
On crée un role readonly et un role admin :
vault write database/roles/readonly \
db_name=demo \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
revocation_statements="ALTER ROLE \"{{name}}\" NOLOGIN;" \
default_ttl="1h" \
max_ttl="24h"
vault write database/roles/admin \
db_name=demo \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT ALL ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
revocation_statements="ALTER ROLE \"{{name}}\" NOLOGIN;" \
default_ttl="1h" \
max_ttl="24h"
Bon, maintenant si tu veux un accès à la base :
vault read database/creds/readonly
# OU
vault read database/creds/admin
On se crée un petit role juste pour notre application :
vault write database/roles/flip_a_coin \
db_name=demo \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT ALL ON flip_a_coin IN SCHEMA public TO \"{{name}}\";" \
revocation_statements="ALTER ROLE \"{{name}}\" NOLOGIN;" \
default_ttl="1h" \
max_ttl="24h"
On peut observer les utilisateurs créés sur Postgres :
\du
😎 DB SECURED!
Il y a toujours un secret dans les values, mais c'est un secret temporaire.
Si vraiment ça pose problème, il y a des solutions...
Côté DB on est bon.
Mais... notre application ne fonctionne plus ! Car elle n'a plus le bon secret.
Il faudrait qu'elle puisse communiquer avec Vault pour en générer un.
Ça tombe bien, nous avions activé l'Auth Method Kubernetes.
Donc n'importe quel pod de notre cluster possède une identité valide vis à vis de notre cluster Vault.
Car ce dernier communique avec l'API Kubernetes, afin d'obtenir des informations sur les Service Account, que possèdent chaque pod de notre cluster.
voir schema slide
Donc c'est cool, Vault sait qui est qui. On peut accorder des droits à notre application pour qu'elle puisse faire comme la commande vault read database/creds/flip_a_coin
.
Et si possible, on voudrait accorder des droit limités... Pour cela :
path "database/creds/flip_a_coin" {
capabilities = ["read"]
}
Appellons cette policy « flip_a_coin_get_db_creds ».
Même si un Service Account a été automatiquement créé par Kubernetes pour notre application, il est bon d'en créer un explicitement, ne serais-ce que pour le nommage de ce dernier.
Ajoutez-le dans les templates Helm :
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Release.Name }} # on utilise le nom de la release pour la dynamicité
automountServiceAccountToken: true
Ne pas oublier de le donner à notre pod : spec.template.spec.serviceAccountName: {{ .Release.Name }}
On upgrade :
helm upgrade --install flip-a-coin manifests/flip-a-coin \
--namespace=demo \
--create-namespace \
-f manifests/flip-a-coin/values.yaml
Il sert à faire le binding entre notre Service Account Kubernetes et notre policy.
vault write auth/kubernetes/role/flip_a_coin \
bound_service_account_names=flip-a-coin \
bound_service_account_namespaces=demo \
policies=flip_a_coin_get_db_creds \
ttl=1h
config.py
from hvac import Client
from hvac.api.auth_methods import Kubernetes
client = Client(
url="http://vault-internal.vault.svc.cluster.local:8200/"
)
f = open('/var/run/secrets/kubernetes.io/serviceaccount/token')
jwt = f.read()
Kubernetes(client.adapter).login(
role="flip_a_coin",
jwt=jwt
)
res = client.is_authenticated()
print("res:", res)
Mais pas de HTTPS ici donc tant pis :'(
helm upgrade --install secrets manifests/secrets \
--namespace=demo \
--create-namespace \
-f manifests/secrets/values.yaml
Ajouter les variables d'environment au template :
apiVersion: apps/v1
kind: Deployment
...
spec:
template:
spec:
serviceAccountName: ...
containers:
- name: ...
image: ...
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: vault-db-creds-secret
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: vault-db-creds-secret
key: password
# OU
volumeMounts:
- name: 'vault-db-creds'
mountPath: '/mnt/secrets-store'
readOnly: true
volumes:
- name: vault-db-creds
csi:
driver: 'secrets-store.csi.k8s.io'
readOnly: true
volumeAttributes:
secretProviderClass: 'vault-db-creds'
... RIP
Créer un secret kv-v2. Utiliser CSI pour le passer en variable d'enrionnement à l'application via SECRET_VALUE
.