Le Blog Amazon Web Services

AWS Secrets Controller : comment intégrer AWS Secrets Manager avec Kubernetes

Kubernetes vous permet de stocker et de gérer des informations sensibles en dehors du podSpec en utilisant un objet secret, par exemple, une clé d’API ou bien un certificat. Cela permet de traiter les objets sensibles différemment des autre objets Kubernetes. Cependant de nombreux clients évitent d’utiliser Kubernetes Secrets car cela ne permettait pas dans sa version initiale de protéger les secrets à l’aide d’un mécanisme de chiffrement fort basé sur des clés gérées par le client.
D’où l’idée de créer cette preuve de concept (PoC, de l’anglais « Proof of Concept »), qui montre comment utiliser des secrets contenus dans un service externe (AWS Secrets Manager) grâce aux contrôleurs d’’admission dynamiques de Kubernetes.

Quelques fonctionnalités clés d’Amazon Elastic Kubernetes Service (EKS)

Amazon Elastic Kubernetes Service (Amazon EKS) supporte le chiffrement d’enveloppe des secrets Kubernetes par AWS Key Management Service (AWS KMS). Avec le chiffrement d’enveloppe, vous pouvez utiliser une clé principale KMS que vous gérez pour chiffrer la clé de données utilisée à son tour par Kubernetes pour chiffrer les secrets. Cela permet de renforcer votre posture de sécurité car elle dépend d’une clé stockée en dehors de Kubernetes, et viens s’ajouter au chiffrement du volume contenant les données de l’etcd utilisé par AWS. Vous pouvez également générer cette clé au sein de votre propre infrastructure de gestion de clé et l’importer dans le magasin de clé KMS pour davantage de contrôle. Pour approfondir vos connaissances sur les mécanismes natifs de Kubernetes sur le chiffrement des secrets, referez-vous à la documentation Kubernetes sur le chiffrement des données au repos.

Bien que le chiffrement d’enveloppe permette de sécuriser Kubernetes Secrets pour stocker des secrets, cela ne permet pas de répondre à certains inconvénients. En effet, la granularité des secrets Kubernetes se situe au niveau du namespace et ils sont donc accessibles indifféremment à tous les pods de ce namespace. Par ailleurs, le renouvellement des secrets Kubernetes n’est pas automatique, vous devez le faire manuellement lorsque c’est nécessaire.

Solutions alternatives à Kubernetes Secrets

Nos clients ont contourné les désavantages de Kubernetes Secrets en utilisant des gestionnaires de secrets externes, tels que Hashicorp’s Vault, qui supporte la granularité de permission requise et le renouvellement des secrets de manière automatique. Il s’intègre avec Kubernetes à l’aide des mécanismes natifs de Comptes de Service pour les pods et les mutating webhooks. Un Compte de Service alloue une identité à un Pod, qui lui permet d’accéder aux secrets dans Vault. Le mutating webhook permet d’injecter un container d’initialisation dans un Pod afin de pré-charger les secrets depuis Vault vers un Volume temporaire. Ensemble, ils permettent d’utiliser les secrets contenus dans Vault depuis Kubernetes.

Le PoC décrit ici utilise une approche similaire pour intégrer Kubernetes avec AWS Secrets Manager. Secrets Manager présente de nombreux avantages comparé à Kubernetes Secrets. Tout d’abord, il vous permet d’aisément gérer, récupérer et d’assurer le renouvellement des informations d’identification de base de données, de clés d’API ainsi que d’autres secrets tout au long de leur cycle de vie. Ensuite il permet un renouvellement secret intégré à plusieurs services AWS tels qu’Amazon RDS, Amazon Redshift et Amazon DocumentDB. Il est également extensible en ce sens que vous pouvez l’utiliser pour renouveler d’autres types de secrets. Enfin, il vous donne la possibilité de contrôler l’accès aux secrets avec granularité d’autorisation très fine et d’auditer le renouvellement des secrets de manière centralisée pour les ressources dans le cloud AWS, ainsi que pour les services et ressources tiers qui s’exécutent sur site.

Solution proposée

Ce PoC utilise ces mécanismes natifs de Kubernetes :

  • Les Annotations sont des tableaux de paires « clé-valeur » (« key-value », en anglais) non-identifiants que l’on peut attacher à des objets. Dans ce PoC nous les utilisons pour activer et désactiver l’injecteur du conteneur d’initialisation, ainsi que pour spécifier l’Amazon Resource Name (ARN) du secret.
  • L’ API Downward permet d’obtenir les metadatas d’un pod. Elle est utilisée ici pour récupérer les valeurs des paires « clé-valeur » dans les annotations du Pod.
  • Le mutating webhook est invoqué quand un pod est créé. Il est implémenté comme un pod exécuté dans le cluster. Si la paire secret.k8s.aws/sidecarInjectorWebhook:enabled est présente dans les annotations du pod, le webhook va injecter le conteneur d’initialisation dans le pod.
  • Les compte de Service pour les pods (IRSA, IAM roles for Service Account) permettent d’allouer un rôle IAM à un pod Kubernetes. Dans ce PoC, ils permettent au pod d’accéder à Secrets Manager, de récupérer le secret chiffré, ainsi que de le déchiffrer à l’aide d’AWS KMS. Les comptes de services vous permettent d’autoriser l’accès aux secrets protégés par Secrets Manager.
  • Un conteneur d’initialisation est un conteneur spécialisé qui s’exécute avant les conteneurs d’application dans un Pod. Dans notre PoC, il sert à récupérer le secret depuis Secrets Manager pour l’écrire dans un volume disque en RAM qui est par la suite monté par le conteneur d’application.

Ces mécanismes sont mis en œuvre de la manière suivante : à chaque fois qu’un Pod ayant les annotations requises est déployé dans le cluster, le mutating webhook injecte le conteneur d’initialisation dans le Pod. Si le Compte de Service spécifié dans le podSpec autorise l’accès au secret référencé comme secrets.k8s.aws/secret-arn: <secret arn>, celui-ci sera déchiffré par le conteneur d’initialisation et placé dans un disque RAM. Cela permet de s’assurer que le secret reste en mémoire volatile et ne persiste pas après l’arrêt du Pod. On garantit cela en spécifiant Memory comme support pour un emptyDir volume. Quand le conteneur d’initialisation est terminé, le conteneur d’application démarre et monte le disque en RAM comme un volume. Le diagramme suivant décrit le mécanisme de webhook utilisé dans le PoC :

Déploiement du webhook pour le contrôleur d’accès à Secrets Manager

Le contrôleur d’accès à Secrets Manager est déployé à l’aide d’un chart Helm. Celui-ci va créer :

  • Un Déploiement Kubernetes pour exécuter le contrôleur d’admission;
  • Un Service Kubernetes qui publie ce déploiement;
  • Un Secret Kubernetes contenant les certificats TLS pour le contrôleur d’admission;
  • Un objet de type MutatingWekhookConfiguration.

Pour plus de détails sur l’installation de Helm, referez-vous à la documentation Helm.

Détail des commandes à exécuter :

1. Tout d’abord, ajoutez le dépot de charts Helm contenant les charts du contrôleur d’admission à l’injecteur de secret;

$ helm repo add secret-inject https://aws-samples.github.io/aws-secret-sidecar-injector/

2. Les dépôts Chart sont fréquemment mis à jour. Pour s’assurer que la version locale est à jour également, vous devez exécuter régulièrement la commande de mise à jour de dépôt repo update;

$ helm repo update

3. Installez le contrôleur d’accès à Secrets Manager en utilisant son chart Helm;

$ helm install secret-inject secret-inject/secret-inject

4. Vérifiez que les objets Kubernetes correspondants sont créés.

$ kubectl get mutatingwebhookconfiguration
NAME                            WEBHOOKS   AGE
aws-secret-inject               1          21s

Le webhook d’injection des secrets étant créé, vous devez ensuite créer les secrets.

Création d’un secret

Vous pouvez directement créer des secrets dans Secrets Manager en utilisant les APIs AWS. Vous pouvez également gérer Secrets Manager directement depuis Kubernetes. Le projet Native Secrets (NASE) implémente un mutating webhook en mode serverless. Il est implémenté dans une fonction Lambda qui expose une terminaison API de type HTTP, qui est déclaré dans Kubernetes dans l’objet mutating webhook. Les appels pour créer et mettre à jour les secrets Kubernetes natifs sont “redirigés” vers le webhook qui écrit le secret contenu dans le manifeste de secret dans Secrets Manager, et retourne son nom ARN à Kubernetes qui le stocke à son tour comme un secret Kubernetes.

Procédure détaillée pour un exemple d’usage: gestion des identifiants d ‘accès d’une base de données avec AWS Secrets Manager

Pour ce PoC, nous nous proposons de déployer un serveur web fictif qui requière un mot de passe pour accéder à un serveur de base de données. Ce mot de passe est stocké dans Secrets Manager.
Comme expliqué précédemment le mot de passe est récupéré par le biais d‘un conteneur d’initialisation qui est injecté dans le Pod par notre mutating webhook.

Dans notre PoC, nous disposons déjà d’un secret créé dans Secrets Manager. Pour créer votre secret dans Secrets Manager, referez-vous à la documentation AWS.

Dans les commandes suivantes, le secret est nommé PoC-database-password dans AWS Secret Manager. La sortie de la commande affichera le nom que vous aurez donné a votre secret, ainsi que votre numéro de compte AWS ainsi le nom de la région AWS dans laquelle vous déployez ce PoC, par example eu-west-3 pour Paris.

$ aws secretsmanager list-secrets 
    {
    "SecretList": [
        {
            "ARN": "arn:aws:secretsmanager:<<YOUR AWS REGION>>:<<YOUR ACCOUNT ID>>::secret:PoC-database-password-pmX1kl",
            "Name": "PoC-database-password",
            "LastChangedDate": "2021-11-23T07:48:16.129000+00:00",
            "Tags": [],
            "SecretVersionsToStages": {
                "420449fc-d3fd-4f1f-b6cc-ec28d5265a7a": [
                    "AWSCURRENT"
                ]
            },
            "CreatedDate": "2021-11-23T07:48:15.947000+00:00"
        }
    ]
}}

Notez le nom ARN complet du secret, qui est utilisé dans les étapes suivantes.

Création d’un rôle IAM permettant l’accès à AWS Secrets Manager

Nous devons ensuite créer un rôle permettant à notre serveur web d’accéder au secret stocké dans Secrets Manager. Vous devez tout d’abord créer une stratégie IAM (IAM policy) permettant de lire des secrets dans Secrets Manager :

$ aws iam create-policy --policy-name webserver-secrets-policy --policy-document file://policy.json

Voici un exemple de stratégie IAM policy.json qui accorde la permission de lire l’identifiant de la base de donnée dans Secrets Manager.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "webserversecret",
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetResourcePolicy",
                "secretsmanager:GetSecretValue",
                "secretsmanager:DescribeSecret",
                "secretsmanager:ListSecretVersionIds"
            ],
            "Resource": "arn:aws:secretsmanager:<<YOUR AWS REGION>>:<<YOUR ACCOUNT ID>>:secret:PoC-database-password-hlRvvF"
        },
        {
            "Sid": "secretslists",
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetRandomPassword",
                "secretsmanager:ListSecrets"
            ],
            "Resource": "*"
        }
    ]
}

Dans un environnement de production vous souhaiterez probablement restreindre cette stratégie IAM à un secret spécifique.

Note: Utilisez ici le nom ARN du secret listé dans l’étape précédente.

Vous devez ensuite créer un rôle IAM, basé sur cette stratégie, que vous attacherez à votre pod. Pour ce PoC, nous utilisons les comptes de Service pour les pods afin d’avoir un contrôle d’accès fin à la lecture des secrets contenus dans Secrets Manager. Les comptes de services nécessitent que vous créiez un fournisseur IAM OIDC (Open ID Connect) pour votre cluster.

Voici les étapes pour créer un rôle IAM associé avec un Compte de Service :

1. Enregistrez l’identité de compte AWS dans une variable d’environnement avec la commande suivante ;

$ AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)

2. Enregistrez ensuite l’identité du fournisseur OIDC dans une variable d’environnement ;

$ OIDC_PROVIDER=$(aws eks describe-cluster --name <cluster-name> --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///")

3. Vous devez créer ensuite une stratégie d’approbation (Trust Policy) associée à ce rôle pour permettre aux utilisateurs fédérés par l’OIDC d’assumer ce rôle. Pour ce faire, exécutez les commandes ci-dessous;

$ read -r -d '' TRUST_RELATIONSHIP <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_PROVIDER}"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "${OIDC_PROVIDER}:sub": "system:serviceaccount:default:webserver-service-account",
          "${OIDC_PROVIDER}:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}
EOF
$ echo "${TRUST_RELATIONSHIP}" > trust.json

Cette commande crée un fichier nommé trust.json.

4. Créez ensuite un rôle IAM;

$ aws iam create-role --role-name webserver-secrets-role --assume-role-policy-document file://trust.json --description "IAM Role to access webserver secret"

5. Attachez la stratégie webserver-secrets-policy créée dans l’étape 1 à ce rôle.

$ aws iam attach-role-policy --role-name webserver-secrets-role --policy-arn=arn:aws:iam::${AWS_ACCOUNT_ID}:policy/webserver-secret-policy

Création d’un Compte de Service Kubernetes

Maintenant que le rôle prévu pour le Compte de Service est créé, vous devez créer le Compte de Service en question et lui associer ce rôle en menant à bien les actions suivantes :

1. Créez un Compte de Service Kubernetes;

$ kubectl create sa webserver-service-account

2. Ajoutez une annotation au Compte de Service qui référence le rôle IAM créé précédemment. Nous utilisons ici le nom ARN de ce rôle.

$ kubectl edit sa webserver-service-account
apiVersion: v1
kind: ServiceAccount
metadata:annotations:eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/webserver-secrets-role
  creationTimestamp: "2021-11-22T21:50:21Z"
  name: webserver-service-account
  namespace: default
  resourceVersion: "25669"
  selfLink: /api/v1/namespaces/default/serviceaccounts/webserver-service-account
  uid: 60073ca0-c979-4d2d-b46d-60cbe1c3faae
secrets:
- name: webserver-service-account-token-wlfg8

Mise à l’essai de la solution

Il faut maintenant déployer un serveur web au sein d’un pod qui se connecte à la base de donnée dont les identifiants de connexion sont contenus dans Secrets Manager.

Créez un objet de déploiement Kubernetes pour le serveur web.

$ cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    run: webserver
  name: webserver
spec:
  replicas: 1
  selector:
    matchLabels:
      run: webserver
  template:
    metadata:
      annotations:
        secrets.k8s.aws/sidecarInjectorWebhook: enabled
        secrets.k8s.aws/secret-arn: arn:aws:secretsmanager:us-east-1:123456789012:secret:database-password-hlRvvF
      labels:
        run: webserver
    spec:
      serviceAccountName: webserver-service-account
      containers:
      - image: busybox:1.28
        name: webserver
        command: ['sh', '-c', 'echo $(cat /tmp/secret) && sleep 3600']
EOF

Notez que les annotations et Comptes de Service (serviceAccountName) créés ci-dessus sont définis dans les spécifications du pod.

Le mutating webhook crée un emptyDir volume secret-vol et le monte sous /tmp/ pour tous les conteneurs contenus dans le pod. Le secret déchiffré et écrit dans /tmp/secret.
A des fins de démonstration, le pod affiche également le secret dans STDOUT. Ceci n’est pas recommandé dans un environnement de production.

$  kubectl logs webserver-66f6bb988f-x774k{"username":"admin","password":"P@$$word1024","engine":"mysql","host":"database-1.cluster.us-east-1.rds.amazonaws.com","port":3306,"dbClusterIdentifier":"database-1"}

Mises en gardes

Ce PoC vous donne un moyen natif de consommer les secrets de Secrets Manager à travers Kubernetes. Néanmoins, nous vous faisons part de quelques mises en garde dont vous devez être conscient. Tout d’abord, le stockage des secrets a un coût. Ensuite, Secrets Manager a une limite sur la taille (64KB) des secrets et sur le nombre d’accès par seconde aux secrets (par exemple, en lecture, GetSecretValue est limité à 2,000 lectures par seconde). Prenez soin de prendre en compte ces coûts et ces limites avant de mettre en œuvre cette solution.

Ce PoC est aussi plus complexe que le mécanisme de secrets natif de Kubernetes. Par exemple, vous devez installer et enregistrer le mutating webhook. Vous devez aussi correctement annoter les pods qui vont utiliser les secrets depuis Secrets Manager et ils doivent référencer un Compte de Service qui a les permissions requises afin de récupérer les secrets référencés dans les annotations. Cela dit, si vous avez besoin d’un mécanisme pour fournir un accès granulaire aux secrets ou si vous devez renouveler vos secrets, la charge supplémentaire de configuration peut en valoir la peine.
Enfin, le but de ce PoC est de démontrer le type d’intégration qui peut être réalisé entre AWS Secrets Manager et Kubernetes. Il n’est pas destiné à être utilisé en production.

Futures directions

Le PoC AWS Secrets Controller vous permet d’accéder aux secrets contenus dans AWS Secrets Manager à l’aide d’un conteneur d’Initialisation pendant le démarrage du pod. Les améliorations futures de ce PoC incluent la possibilité d‘exécuter un conteneur sidecar dans le pod pour garder le secret à jour lorsque celui-ci est renouvelé par Secrets Manager.

Le Secret Store Container Storage Interface (CSI) driver permet de monter des volumes depuis des magasins de secrets externes contenant des secrets, mots de passes, et des certificats. Ce projet inclue une interface pour fournisseur enfichable (pluggable provider interface) qui peut être utilisée pour intégrer AWS Secrets Manager comme magasin de secret externe pour les secrets Kubernetes.

Conclusion

Nous espérons que vous avez apprécié cet apprentissage de la sécurisation des secrets pour vos applications Kubernetes. Vous pourrez trouver le code source de cette solution ici. N’hésitez pas à nous contacter via GitHub issues pour toutes questions et commentaires. Les push requests sont bienvenues.

Article original rédigé en anglais par Jeremy Cowan (Architecte de Solutions spécialisé dans les conteneurs chez AWS), Amit Borulkar (Architecte de Solutions chez AWS), Mahendra Revanasiddappa (Consultant DevOps chez AWS), et traduit par Serge Moro, Architecte de Solutions Partenaires Senior chez AWS France.