Rancher, qu’est ce que c’est ?
Rancher est un excellent outil de gestion d’environnements Kubernetes. Il permet d’aggréger vos clusters on premise aussi bien que chez les cloud provider dans une unique interface.
Il offre aussi la possibilité de créer des cluster Kubernetes (avec RKE ou k3s notamment) mais également d’en importer tout en ajoutant une couche d’authentification et de gestion des RBAC (Role Based Access Control).
Cela permet notamment d’ouvrir des accès limités à des namespaces aux équipes de développement pour qu’elles puissent vérifier le bon fonctionnement de leurs applications.
Bref, un outil que nous utilisons depuis des années (la v1 pour être précis), avant que l’intégration de Kubernetes ne remplace ce bon vieux Cattle (dont on trouve encore quelques reliques au niveau des namespaces et des agents).
Authentification des registres
Il y a en revanche un point qui pêche en terme d’intégration : celle des registres tiers à authentification par jeton.
En matière de sécurité, les bonnes pratiques consistent à avoir des jetons d’authentification aux durées de vie contrôlées et courtes.
C’est ce qu’impose notamment Amazon avec Elastic Container Registry, son service managé de stockage d’images docker.
Évidemment, pour déployer des applications, nos clusters auront besoin d’y accéder au travers de Rancher qui nous propose les solutions suivantes :
Comme on peut le voir on ne dispose que de solutions à base de login / mot de passe. Or, ECR impose l’usage de jetons dont la durée de validité est de 12h maximum.
Fonctionnement de l’authentification ECR
Voyons comment accéder à notre registre ECR depuis un shell, nous aurons pour cela besoin du cli AWS :
aws ecr get-login-password
Cela produira une chaîne de ce type, faisant office de mot de passe temporaire :
eyJwYXlsb2FkIjoiQkhNVmppMGpCSEtWQTUxREVvUkN6cEl6QWJ4NXBHNHFPQWJKbXgvenJtNzYwMTF6
[TRUNCATED]
OiIyIiwidHlwZSI6IkRBVEFfS0VZIiwiZXhwaXJhdGlvbiI6MTY4MjAzNzkyMH0
La chaine en question pourra ensuite être utilisée pour s’authentifier sur le registre :
docker login --username AWS --password-stdin ACCOUNT_ID.dkr.ecr.eu-west-1.amazonaws.com
Ou en une seule commande :
aws ecr get-login-password | docker login --username AWS --password-stdin ACCOUNT_ID.dkr.ecr.eu-west-1.amazonaws.com
Dès lors nous pouvons accéder à notre registre de façon transparente, mais pour une durée limitée.
Intégrer un renouvellement de nos accès dans Rancher
Dans Rancher, nous voulons garantir que l’accès au registre fonctionnera de manière permanente.
Pour se faire, nous devons donc automatiser le renouvellement du jeton. Pour nos besoins, j’ai packagé une image disponible sur le hub docker incluant à la fois kubectl
et le client aws
(fuse/aws-cli-kubectl:0.1.0).
Avant de nous lancer dans la création de notre cron job nous allons renseigner en secret les valeurs nécessaires à l’authentification (aws_account_id
, aws_access_key_id
, aws_secret_access_key
, aws_region
).
Puis nous allons dérouler nos commandes dans un pod, qui utilise notre image et injecte notre configuration. Plutôt que de procéder par essai / erreur, on lance une image qui ne fait rien (sleep 3600) à laquelle on s’attache pour exécuter nos commandes.
apiVersion: v1
kind: Pod
metadata:
name: ecr-token-renew
spec:
containers:
- name: ecr-token-renew
image: fuse/aws-cli-kubectl:0.1.0
command: ["sleep", "3600"]
env:
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: aws-ecr
key: aws_access_key_id
- name: AWS_REGION
valueFrom:
secretKeyRef:
name: aws-ecr
key: aws_region
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: aws-ecr
key: aws_secret_access_key
restartPolicy: OnFailure
Notre but est de supprimer et de recréer à intervalle régulier le secret contenant les informations de connexion au registre docker.
Notre pod (et notre cron job) étant lancé avec un compte de service (serviceaccount
), nous allons vérifier que nous avons les bons droits.
On commence par rentrer dans notre conteneur :
kubectl exec -it ecr-token-renew -- /bin/sh
puis on teste les accès :
$ kubectl auth can-i create secrets
no
$ kubectl auth can-i delete secrets
no
De base notre serviceaccount
ne dispose pas des droits suffisants. Nous allons donc créer un rôle permettant de réaliser les opérations de type CRUD sur les secrets et lui attacher.
On crée donc le fichier role.yml
comme suit :
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: app-namespace
name: crud-secrets
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
Et on l’applique :
kubectl apply -f role.yml
De même on crée l’objet RoleBinding
:
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: crud-secrets-binding
namespace: app-namespace
subjects:
- kind: ServiceAccount
name: default
namespace: app-namespace
roleRef:
kind: Role
name: crud-secrets
apiGroup: rbac.authorization.k8s.io
Et on l’applique :
kubectl apply -f role.yml
On peut de nouveau vérifier que tout fonctionne avec kubectl auth can-i
.
Suppression et ajout du secret à intervalle régulier
Maintenant que nous avons les bons droits nous allons dérouler les commandes suivantes :
# le nom de notre secret, à votre bon vouloir
SECRET_NAME=ecr-registry-token
# on récupère le token en s'authentifiant (le cli utilise automatiquement AWS_ACCESS_KEY_ID et AWS_SECRET_ACCESS_KEY)
TOKEN=$(aws ecr get-login-password --region $AWS_REGION)
# on supprime le secret sans râler s'il n'existe pas encore (premier run)
kubectl delete secret --ignore-not-found $SECRET_NAME
# on recrée le secret en injectant la configuration qui va bien
kubectl create secret docker-registry $SECRET_NAME \
--docker-server=https://${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com \
--docker-username=AWS \
--docker-password="${TOKEN}"
Une fois qu’on a vérifié que tout fonctionnait correctement de manière interactive il nous reste à packager le tout dans un CronJob
qui tournera toutes les 6h (on évite de le faire tourner toutes les 12h pour ne pas être trop juste et se garder de la latitude si une exécution échoue).
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: ecr-token-renew
spec:
schedule: "0 */6 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: ecr-token-renew
image: fuse/aws-cli-kubectl:0.1.0
command:
- /bin/sh
- -c
- |-
SECRET_NAME=ecr-registry-token
TOKEN=$(aws ecr get-login-password --region $AWS_REGION)
kubectl delete secret --ignore-not-found $SECRET_NAME
kubectl create secret docker-registry $SECRET_NAME \
--docker-server=https://${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com \
--docker-username=AWS \
--docker-password="${TOKEN}"
env:
- name: AWS_ACCOUNT_ID
valueFrom:
secretKeyRef:
name: aws-ecr
key: aws_account_id
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: aws-ecr
key: aws_access_key_id
- name: AWS_REGION
valueFrom:
secretKeyRef:
name: aws-ecr
key: aws_region
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: aws-ecr
key: aws_secret_access_key
restartPolicy: OnFailure
Conclusion
Dans cet article, nous avons vu comment palier au manque d’intégration dynamique des registres dans Rancher en générant nous même des tokens à intervalles réguliers.
J’espère qu’il vous sera utile en attendant une solution built in.
L’équipe Ouidou.