La plupart des fournisseurs de plateformes Cloud Public nous précisent une matrice de responsabilité partagée des risques (un overview des responsabilités dans un précédent article dans AWS). Dans les faits, l’utilisateur est responsable de la sécurité dans le Cloud tandis que le fournisseur est responsable de la sécurité de ses infrastructures. Partant de ce postulat, tout n’est pas à faire mais une bonne partie des risques de la Cloud Control Matrix sont à couvrir de la même manière que dans un Cloud Privé. L’objectif de ce billet est de vous présenter un outil open-source, Cloud Custodian, qui permet de répondre à tous les besoins de sécurité qui nous incombent en tant qu'utilisateurs d’un Cloud Public.
Certains fournisseurs de Cloud Public mettent à disposition des outils de compliance adaptés à leurs problématiques. Citons Security Hub et Config sur AWS, Chronicle et Cloud Asset Inventory sur GCP ou encore Security Center et Sentinel sur Azure. Néanmoins la question du contrôle des plateformes sur des critères liés au métier même des applications sur un moteur unifié entre les providers reste en suspens.
Développé par Capital One, Cloud Custodian est une librairie open-source qui permet de mettre en place des règles de conformité, des contrôles de sécurité sur la configuration des ressources grâce à un simple langage déclaratif en YAML, le tout compatible sur tous les gros fournisseurs de Cloud Public.
Voici une policy permettant de trouver et supprimer tous les disques qui ne sont pas attachés à une machine dans une souscription Azure :
# myPolicy.yml
policies:
- name: find-non-attached-disks
resource: azure.disk
filters:
- type: value
key: managedBy
value: null
actions:
- type: delete
Ensuite, une policy notifiant la présence d’un snapshot Pub/Sub relatif à un topic déjà supprimé dans GCP (notez que pour ce cas, vous avez besoin d’un autre topic Pub/Sub qui portera l’envoi de notification) :
# myPolicy.yml
policies:
- name: notify-on-delete-pub-sub-snapshot
resource: gcp.pubsub-snapshot
filters:
- type: value
key: topic
value: _deleted-topic_
actions:
- type: notify
to:
- notification@adress.com
format: txt
transport:
type: pubsub
topic: _my-notification-pubsub-topic_
NB: Dans le cas de GCP, la librairie Cloud Custodian est encore en beta et continue d’évoluer pour couvrir plus de services et cas d’usage.
Pour simplifier le contenu de ce billet, nous allons essentiellement expliciter le fonctionnement de Cloud Custodian via des exemples sur AWS. Commençons avec l’exemple de cette policy :
# myPolicy.yml
policies:
- name: set-default-ebs-encryption
resource: aws.account
filters:
- type: default-ebs-encryption
state: false
actions:
- type: set-ebs-encryption
state: true
key: alias/aws/ebs
Pour utiliser cette règle après avoir installé Cloud Custodian, rien de plus simple, il vous suffira de lancer custodian run myPolicy.yml, ainsi le moteur va hériter de vos variables d’environnement pour exécuter cette policy.
Exécuter cette policy forcera tous les nouveaux volumes EBS à être chiffrés par défaut utilisant la clé AWS KMS spécifiée (alias/aws/ebs) pour les ressources EBS.
D’un point de vue technique, le moteur de Cloud Custodian va exécuter un processus Python (3.x) pour ensuite appeler les API d’AWS conformément à ce qui est décrit dans la policy.
Compte tenu des évolutions des standards de sécurité ainsi que de l’hétérogénéité des axes d’attaque, le plus souvent internes mais aussi externes, le modèle de centralisation et de gouvernance des règles de sécurité s’est adapté. En adoptant l’approche policy-based, à l’instar de nombre de domaines informatiques (Réseau, Automatisation, Orchestration, …), on enrichit le framework général de sécurité au sein des SI. Ce modèle s’articule autour de différents paradigmes pour fonctionner d’une manière optimale :
Dans le cas de Cloud Custodian, la librairie open-source couvre les parties PMC & PDP, il est d’usage d’y associer un VCS (Version Control System i.e. : Git) comme PR pour profiter des ses propriétés collaboratives et d'historisation. Le socle d’exécution d’une règle Cloud Custodian est dépendant du type de policy ainsi que du fournisseur Cloud Public que l’on cherche à lancer, il existe plusieurs modes de fonctionnement que nous expliciterons plus bas.
En tant que SecOps, nous cherchons un moyen simple et clair pour gérer la sécurité sur les différents environnements sous notre casquette. Se baser sur des politiques déclaratives apporte autant d’avantages que l’utilisation d’Infrastructure as Code pour centraliser d’une manière intelligible et pérenne dans le temps les caractéristiques techniques de nos plateformes. De plus, cela permet d’apporter tous les avantages liés au code en termes de versionnement, de normalisation mais aussi de profiter des pratiques DevOps déjà en place.
Une policy Cloud Custodian s’appréhende selon une structure bien précise, chaque fichier comprend une ou plusieurs policies n’ayant pas forcément de lien entre elles, néanmoins une bonne pratique est de les regrouper par couverture de risque. Par exemple, si l’on cherche à couvrir le risque de chiffrement at rest des volumes EBS dans AWS ou peut facilement définir la structure suivante :
# ebs-sse-enforcement.yml
policies:
- name: set-default-ebs-encryption #Policy 1 will enforce the default setting
....
- name: update-default-ebs-encryption #Policy 2 will catch a change in the default setting and set it back
....
- name: notify-ebs-unencrypted-creation #Policy 3 will verify ebs encryption at each new resource creation
....
- name: notify-ebs-unencrypted-attached #Policy 4 will verify ebs encryption at each volume attachment
....
- name: report-unecnrypted-ebs #Policy 5 will scan all ebs volumes and report each unencrypted
....
Dans l’exemple ci-dessus nous adressons les problématiques de sécurité suivantes :
Coupler cette approche à un fonctionnement GitOps et une CI/CD permet de rendre vos politiques de sécurités transparentes et source de vérité des règles de sécurité pour tous les utilisateurs des plateformes que vous cherchez à sécuriser.
Nous allons voir dans cette section les différentes solutions qui sont mises à notre disposition avec cet outil pour couvrir les risques inhérents à l’utilisation des plateformes du Cloud Public, toujours en prenant l’exemple d’AWS.
Pour plus d’information concernant les possibilités qu’offre cette librairie open-source, une CLI est mise à disposition une fois le package installé.
Run : custodian schema [args] pour avoir une vue détaillée de ce qui est disponible avec la version courante de la librairie ainsi que le schéma de policy attendu pour chaque option.
NB : Cloud Custodian utilisera par la suite votre environnement pour trouver les identifiants nécessaires à exécuter les actions que vous avez définies. La commande custodian run path/to/file.yml permet l’utilisation de credentials directs mais aussi, en fonction du provider défini dans la policy, divers mécanismes d’impersonation. Par exemple, pour une policy orientée pour AWS, nous pouvons spécifier les arguments --assume-role ou --profile.
Avant de commencer à écrire des policies Cloud Custodian pour un cas d’usage bien précis, il faut comprendre comment les écrire et à quoi sert chaque champ. De manière générale, il faudra écrire des policies comme suit :
# myPolicy.yml
policies:
- name: set-default-ebs-encryption #Identification unique de la policy
resource: aws.account #Type de ressource concernée
filters: #Liste de filtres qui seront appliqués lors de l'exécution
- type: default-ebs-encryption
state: false
actions: #Liste d’actions à effectuer sur les ressources filtrées
- type: set-ebs-encryption
state: true
key: alias/aws/ebs
On peut donc traduire la séquence précédente de la manière qui suit :
« Nous cherchons dans la ressource account (qui contient les paramètres généraux d’une région ou d’un compte) le paramètre default-ebs-encryption comme étant désactivé, si un tel paramètre est trouvé dans cet état, l’action de la policy est de l’activer.»
Il existe plusieurs autres configurations de policies utilisables en fonction du risque que l’on cherche à couvrir, en voici quelques exemples.
Reprenons ici l’exemple présenté ci-dessus ; cette policy est vouée à une exécution locale, c’est à dire qu’elle ne sera exécutée que sur commande (manuelle, d’une CI/CD ou d’un automate). Le but de cette policy est d’être exécutée de manière unitaire.
Tous les fournisseurs de plateforme Cloud Public offrent une solution d’analyse d’évènements (communément appelée auditlog) à laquelle nous pouvons associer une exécution de code pour couvrir un risque en temps réel.
Dans le cas d’AWS, CloudTrail et CloudWatch peuvent servir de déclencheurs à Cloud Custodian pour effectuer une action en fonction d’un évènement spécifique. Cette fonctionnalité s’implémente de la façon suivante :
# myPolicy.yml
policies:
- name: notify-ebs-unencrypted-creation #Identification unique de la policy
resource: aws.ebs #Type de ressource concernée
filters: #Liste de filtres qui seront appliqués lors de l'exécution
- type: value
key: Encrypted
value: False
description: "This function will notify when an unencrypted EBS volume is created"
mode:
function-prefix: "c7n-"
type: cloudtrail #Type d'exécution, ici en événementiel au travers d'un évènement lancé par AWS Cloudtrail
events: #Définition des événements de déclenchement
- source: ec2.amazonaws.com
event: CreateVolume
ids: responseElements.volumeId #Id de la ressource à identifier dans la requête entrante
role: arn:aws:iam::{account_id}:role/Custodian-role-EBS # IAM rôle qui sera utilisé par la fonction AWS Lambda
actions: #Actions à effectuer sur les ressources filtrées
- type: notify
transport:
- properties:
topic: arn:aws:sns:us-east-1:{account_id}:topics/my-notification-topic #Topic SNS de notification
type: sns
Concrètement, l’exécution de cette policy va déployer plusieurs éléments sur le compte AWS cible.
NB : Avant d’appliquer cette policy, il faut s’assurer que le compte AWS inclut un Trail CloudTrail multi région, un rôle IAM nommé Custodian-role-EBS utilisable par le service lambda.amazonaws.com ainsi qu’un topic SNS nommé my-notification-topic sur la région us-east-1.
On note donc que l'ajout d’un bloc mode permet de déporter le socle d’exécution de la policy d’une exécution locale à une exécution se basant sur le service FaaS du fournisseur Cloud Public. Ici dans AWS mais on retrouve le même fonctionnement avec les Cloud Functions de GCP ou Azure Functions dans le Cloud de Microsoft pour ne citer qu’eux.
Au même titre que dans l’exécution sur évènements, on peut moduler le bloc mode pour avoir une exécution périodique selon une expression cron() ou une expression rate().
# myPolicy.yml
policies:
- name: notify-ebs-unencrypted-creation #Identification unique de la policy
resource: aws.ebs #Type de ressource concernée
filters: #Liste de filtres qui seront appliqués lors de l'exécution
- type: value
key: Encrypted
value: False
description: "This function will notify when an unencrypted EBS volume is created"
mode:
function-prefix: "c7n-"
type: periodic #Type d'exécution, ici en événementiel au travers d'un évènement lancé par AWS CloudTrail
schedule: rate(1 day) #Déclenchement tous les jours à l'heure de l'exécution de la policy, peut être remplacé par une expression cron(0 1 * * *) pour être déclenché tous les jours à 1h du matin
role: arn:aws:iam::{account_id}:role/Custodian-role-EBS # IAM rôle qui sera utilisé par la fonction AWS Lambda
actions: #Actions à effectuer sur les ressources filtrées
- type: notify
transport:
- properties:
topic: arn:aws:sns:us-east-1:{account_id}:topics/my-notification-topic #Topic SNS de notification
type: sns
Si l’on considère qu’un scan est nécessaire sur une base journalière pour avoir à l’instant T l’état de conformité de votre plateforme en fonction des critères de sécurité, vous pouvez rechercher par exemple toutes instances-profiles pour EC2 qui ont des droits administrateurs et les tagger pour effectuer une vérification manuelle ultérieure.
Nous savons à présent comment se définit une policy ainsi que les différentes applications possibles et modes de fonctionnement. Il existe néanmoins quelques utilisations spécifiques à certains services dues à leur temps de création par exemple, qui ne permettent pas d’effectuer directement les actions de remise en conformité.
Comme vous pouvez le deviner de par son nom, cette action permet de marquer une ressource pour y effectuer une action ou une vérification ultérieure. Dans le cas d’une table AWS DynamoDB par exemple, si l’on souhaite forcer une certaine configuration il faut tout d’abord appliquer un tag sur la ressource puis attendre que la table soit finalisée avant de faire les modifications de mise en conformité.
Pour mettre en place ce type d’action, nous pouvons dans le même fichier YAML déclarer plusieurs policies pour effectuer cette action en deux temps :
# myPolicy.yml
policies:
- name: tag-tables-without-pitr #Identification unique de la policy
resource: aws.dynamodb-table #Type de ressource concernée
filters: #Liste de filtres qui seront appliqués lors de l'éxécution
- type: continuous-backup
key: ContinuousBakcupStatus
value: DISABLED
description: "This function will tag a DynamoDB Table for action at creation if continous backups are disabled"
mode:
function-prefix: "c7n-"
type: cloudtrail #Type d'exécution, ici en événementiel au travers d'un évènement lancé par AWS CloudTrail
events: #Définition des événements de déclenchement
- source: dynamodb.amazonaws.com
event: CreateTable
ids: responseElements.TableName #Id de la ressource à identifier dans la requête entrante
role: arn:aws:iam::{account_id}:role/Custodian-role-DYNAMODB # IAM rôle qui sera utilisé par la fonction AWS Lambda
actions: #Actions à effectuer sur les ressources filtrées
- type: mark-for-op
tag: custodian-action
op: set-continous-backup
- name: enforce-tables-pitr #Identification unique de la policy
resource: aws.dynamodb-table #Type de ressource concernée
filters: #Liste de filtres qui seront appliqués lors de l'éxécution
- type: continuous-backup
key: ContinuousBakcupStatus
value: DISABLED
- type: marked-for-op
tag: custodian-action
op: set-continous-backup
description: "This function will enforce continuous backups on a tagged DynamoDB Table"
mode:
function-prefix: "c7n-"
type: periodic
schedule: rate(2 minutes)
role: arn:aws:iam::{account_id}:role/Custodian-role-DYNAMODB # IAM rôle qui sera utilisé par la fonction AWS Lambda
actions: #Actions à effectuer sur les ressources filtrées
- type: set-continous-backup
- type: remove-tag # Pour finir enlevons le tag placé précédemment
tags: ['custodian-action']
Avec cette action en deux temps, nous avons donc la possibilité de forcer des paramétrages de ressources avec un temps d’action réduit même si le paramètre n’est pas modifiable à la création de la ressource.
Dans sa démarche Cloud Native, l’outil Cloud Custodian open-source est en perpétuelle évolution (environ une release tous les deux à trois mois). Mais toute évolution si grande soit-elle ne pourra que se rapprocher et non pas égaler les évolution des fournisseurs cloud. Dans une bonne partie des cas, déclencher un traitement spécifique est nécessaire pour couvrir vos besoins. Je parle ici de la possibilité d’exécuter d’autre fonctions serverless après filtrage par Cloud Custodian, cela vous permet d’effectuer des workflows de remédiation complexes et plus adaptés à votre contexte de sécurité par l’utilisation de l’action générique (disponible sur toutes les ressources) invoke-lambda dans AWS. Dans les faits, cette fonctionnalité va renvoyer à un autre processus serverless (AWS Lambda, Cloud Fonction, Azure Fonction), le résultat du filtrage demandé au travers de la policy pour vous permettre d’exécuter du code que vous avez produit sur les ressources identifiées.
Pour illustrer cette action, nous allons tirer profit de l’RBAC d’AWS. Admettons que dans notre environnement, tous les rôles IAM créés doivent êtres soumis à une Permission Boundary. Pour savoir quelle boundary doit s’appliquer sur un rôle fraîchement créé, nous nous basons sur un référentiel dans DynamoDB. La règle est la suivante : Selon le path du rôle, une permission boundary spéciale y est appliquée.
Admettons qu'une base DynamoDB est déjà disponible sur la plateforme avec les paramètres suivants :
Role-path (PK) |
Boundary-identifier |
/Administrators/ |
arn:aws:iam::XXXXXXXXX:policy/AdminBoundary/generic-policy |
/PowerUsers/ |
arn:aws:iam::XXXXXXXXX:policy/PUBoundary/generic-policy |
/Application/ |
arn:aws:iam::XXXXXXXXX:policy/AppBoundary/generic-policy |
Ainsi qu’une fonction AWS Lambda nommée custom-boundary-attacher emportant le pseudo-code suivant dans le langage de votre choix :
Fonction exec(event) {
path = event.requestParameters.get(‘path’) //Permet de récupérer le path du rôle
boundary_arn = AWSDynamoDBTable.Role-path(path).get(’Boundary-identifier’)
AWSIAMRole(‘role’).SetBoundary(boundary_arn)
}
Une fois ces composants déployés, il n’y a plus qu'à écrire la policy Cloud Custodian qui réagira à la création d’un rôle pour exécuter la fonction ci-dessus :
```
- name: iam-attach-boundary-at-creation
resource: iam-role
filters:
- type: event
key: detail.requestParameters.permissionBoundaryArn
value: false
description: "This function will trigger a custom process to attach a permission boundary on a newly created IAM Role"
mode:
function-prefix: "c7n-"
type: cloudtrail #Type d'exécution, ici en événementiel au travers d'un évènement lancé par AWS CloudTrail
events: #Définition des événements de déclenchement
- source: iam.amazonaws.com
event: CreateRole
ids: responseElements.RoleName #Id de la ressource à identifier dans la requête entrante
role: arn:aws:iam::{account_id}:role/Custodian-role-IAM
actions:
- type: invoke-lambda
function: custom-boundary-attacher
On note ici l’utilisation d’un filtre générique event, ce filtre très pratique vous permet d’effectuer le filtrage avec Cloud Custodian directement sur l’évènement déclenché via CloudTrail, dans notre cas on vient filtrer les rôles à la création qui n’ont pas de permission boundary.
On voit dans ce cas d’usage, qu'à partir de cette fonctionnalité, on peut couvrir quasiment tous les risques liés aux ressources des fournisseurs cloud, en profitant des différentes possibilités de filtrage et de déclenchement de Cloud Custodian.
Cloud Custodian permet aussi de réagir à la suppression d’une ressource pour par exemple notifier qu’une action potentiellement malveillante a eu lieu.
Quand l’on cherche à déterminer la suppression ou l’absence d’une ressource, le workflow est toujours le même, nous devons tout d’abord utiliser la resource: account puis dans les filtres décrire une nouvelle policy pour cibler l’élément qui doit être manquant.
Par exemple, voici comment réagir à la suppression d’un Rôle IAM en notifiant un topic SNS :
- name: iam-notify-on-role-deletion
resource: account
filters:
- type: missing
policy:
resource: iam-role
filters:
- type: value
key: roleName
op: eq
value: detail.requestParameters.roleName
description: "This function will notify an SNS topic when an IAM role is deleted"
mode:
function-prefix: "c7n-"
type: cloudtrail #Type d'exécution, ici en événementiel au travers d'un évènement lancé par AWS CloudTrail
events: #Définition des événements de déclenchement
- source: iam.amazonaws.com
event: DeleteRole
ids: requestParameters.RoleName #Id de la ressource à identifier dans la requête entrante
role: arn:aws:iam::{account_id}:role/Custodian-role-IAM
actions:
- type: notify
transport:
- properties:
topic: arn:aws:sns:us-east-1:{account_id}:topics/my-notification-topic #Topic SNS de notification
type: sns
On note ici l’utilisation d’un autre filtre générique value_qui se base sur le retour d’un appel API Describe${resource}. Dans notre cas, Cloud Custodian va vérifier qu'aucun rôle IAM, ayant le nom trouvé en paramètre de la requête de déclenchement DeleteRole (detail.requestParameters.roleName), n'est présent sur la plateforme avant d'effectuer l'action.
Dans ce billet, vous avez acquis le vernis nécessaire pour utiliser cet outil et commencer à développer vos propres règles de sécurité. L’utilisation “standalone” de Cloud Custodian est déjà efficace en soi, on voit d’ailleurs la volonté des fournisseurs Cloud d’ouvrir la sécurisation de leurs plateformes à ce type d’outils avec l’intégration de Cloud Custodian dans la liste des produits disponibles au travers d’AWS Security Hub. Il existe plusieurs dérivations de projets intégrés à la logique de Cloud Custodian, comme c7n_org, c7n_mailer, c7n_guardian, c7n_policystream, etc… De plus, si l’on souhaite déployer une implémentation de Cloud Custodian à l’échelle, dans un environnement distribué, emportant plusieurs organisations, comptes, projets ou tenants, vous aurez besoin de pousser plus loin la logique de développement autour de Cloud Custodian.