Dans le cadre de mon nouveau poste en tant que Head of Security, je suis responsable des identifiants de connexion et de la sécurité AWS.

Aujourd'hui, je vais vous parler d'une bonne pratique en matière de sécurité AWS qui s'appelle la rotation des clés. L'idée est de modifier ses clés au moins tous les 90 jours, et plus souvent si possible, pour qu'en cas de fuite de données, les informations d'identification soient déjà expirées.

Dans la mesure du possible, nous sommes supposés utiliser des rôles pour nos instances AWS et réduire l'utilisation des clés d'API. Cependant, chez Nuxeo, certains serveurs Jenkins sont en dehors d'AWS et il est toujours pratique de pouvoir exécuter des API AWS à partir de votre ordinateur portable.

Avant de mettre en place une politique de sécurité chez Nuxeo, j'aime bien me mettre à la place des différents utilisateurs pour faciliter leur travail. Pour le scénario ci-dessus, j'ai donc décidé de trouver une solution pour la rotation des clés Shell et Jenkins.

La rotation des clés est un processus très simple. Les étapes sont les suivantes :

  1. Création d'une nouvelle clé AWS
  2. Stockage de la nouvelle clé AWS
  3. Suppression de l'ancienne clé AWS

CLI AWS

C'est toujours pratique de pouvoir vérifier la liste de contrôles d'accès (ACL) d'un objet sur votre ordinateur lorsque quelqu'un vous demande de l'aider à comprendre le problème qu'il/elle rencontre avec son objet S3.

aws --profile=nuxeo s3api get-object-acl --bucket MyBucket --key MyObject

L'idéal serait de pouvoir changer la clé d'accès en utilisant une seule commande comme celle-ci :

aws --profile=nuxeo configure rotate-keys

Mais malheureusement, cette commande n'existe pas encore. La Pull request a été lancée ici et elle attend d'être mergée (peut-être que si tout le monde ajoute un +1, le processus ira plus vite :) )

Mais en attendant, j'ai créé un autre utilitaire AWS : yaws

Il est disponible sur pip

pip install yaws

Et vous permet de modifier la clé en entrant :

yaws --profile=nuxeo rotate-keys

Les clés sont mises à jour ! Vous pouvez continuer à utiliser votre AWS et il vous suffit d'ajouter une table de planification.

0 0 * * * yaws --profile=nuxeo rotate-keys

Et voilà. Vos clés sont modifiées toutes les heures et vous n'avez plus rien à faire.

Jenkins

Jenkins doit généralement réaliser certaines opérations dans le Cloud, par ex. envoyer un artéfact vers un bucket S3 ou exécuter un script Ansible.

Il existe un plug-in aws-credentials-plugin qui gère le stockage des clés à votre place et qui prend en charge l'opération assumeRole si vous spécifiez un paramètre roleArn.

De cette façon, vous pouvez disposer d'une seule clé d'accès associée à plusieurs accès Jenkins AWS avec plusieurs rôles.

Mais nous devons encore ajouter la rotation de ces clés d'accès et mettre à jour toutes les clés correspondantes.

Pour y parvenir, on peut par exemple utiliser un script Groovy comme celui-ci :

import com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsImpl;
import com.cloudbees.plugins.credentials.domains.*;

node {
    // Charger toutes les informations de connexion AWS depuis Jenkins
    def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
        com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsImpl.class,
        jenkins.model.Jenkins.instance
    )
    // Création d'un mappage des clés d'accès et des accès associés
    def rotate = [:]
    // Définition du mappage
    creds.each {
        if (!rotate[it.accessKey]) {
            rotate[it.accessKey] = []
        }
        rotate[it.accessKey].add(it)
    }
    // Pour chaque clé d'accès
    rotate.each {
       // Modification de la clé d'accès
        accessKey = it.value[0].accessKey
        secret = it.value[0].secretKey.getPlainText()
        withEnv(["AWS_ACCESS_KEY_ID=${accessKey}","AWS_SECRET_ACCESS_KEY=${secret}"]) {
            newKeyRaw = sh(returnStdout: true, script: "${aws_bin} iam create-access-key").trim()
            newKey = readJSON(text: newKeyRaw)
            sh(script: "${aws_bin} iam delete-access-key --access-key-id=${accessKey}")
            println("Rotate key ${accessKey} to ${newKey.AccessKey.AccessKeyId}")
        }
        // La clé d'accès a été modifiée sur AWS. Il faut encore mettre à jour les accès qui l'utilisent
        def credentials_store = jenkins.model.Jenkins.instance.getExtensionList(
            'com.cloudbees.plugins.credentials.SystemCredentialsProvider'
            )[0].getStore()
        it.value.each {
            cred = it
            domain = null
            // Recherche du domaine pour trouver les accès : non optimal
            credentials_store.getDomains().each {
                curDomain = it
                credentials_store.getCredentials(it).each {
                    if (it == cred) {
                        domain = curDomain
                    }
                }
            }
            if (curDomain) {
                newIt = new AWSCredentialsImpl(it.scope, it.id, newKey.AccessKey.AccessKeyId, newKey.AccessKey.SecretAccessKey, it.description, it.iamRoleArn, it.iamMfaSerialNumber)
                credentials_store.updateCredentials(curDomain, it, newIt)
            } else {
                println("WARNING: key ${it.id} (${it.AccessKey} failed to be update with ${newKey.AccessKey.AccessKeyId}")
            }
        }
    }
}

Pour exécuter ce script, vous devez être administrateur Jenkins et ne pas l'exécuter dans le bac à sable Groovy.

Il ne vous reste plus qu'à créer une tâche planifiée sur Jenkins !

Vault HashiCorp

Il existe d'autres approches que celles que je viens de vous présenter, par ex. un Vault HashiCorp, qui peut générer des accès jetables pour vous. Ça sera probablement le sujet d'un prochain blog.