
Outil de linting pour les modèles CloudFormation
L'outil cfn-nag recherche des motifs dans les modèles CloudFormation qui peuvent indiquer une infrastructure non sécurisée. En gros, il recherche :
Pour plus d'informations sur l'outil, veuillez consulter cet article sur le blog de Stelligent :
En supposant que Ruby >= 2.5.x est installé, l'installation est simplement une question de :
gem install cfn-nag
Sur MacOS ou Linux, vous pouvez également installer via brew :
brew install ruby brew-gem
brew gem install cfn-nag
Pour exécuter cfn_nag en tant qu'action dans CodePipeline, vous pouvez le déployer via le AWS Serverless Application Repository.
Pour exécuter :
cfn_nag_scan --input-path <path to cloudformation json>
Le chemin peut être un répertoire ou un modèle particulier. S'il s'agit d'un répertoire, tous les fichiers .json, .template, .yml et .yaml seront traités, y compris en parcourant récursivement les sous-répertoires.
Le format de sortie par défaut est un texte libre, mais la sortie json peut être sélectionnée avec l'option --output-format json.
En option, --debug affichera des informations sur les mécanismes internes de chargement des règles.
Exécutez avec --help pour obtenir la liste complète des options prises en charge.
Pour voir la liste de toutes les règles actuellement prises en charge par cfn-nag, il existe un utilitaire en ligne de commande qui les affiche sur stdout :
cfn_nag_rules
Un Dockerfile est fourni pour plus de commodité. Il est publié sur DockerHub sous le nom stelligent/cfn_nag.
https://hub.docker.com/r/stelligent/cfn_nag
Vous pouvez également le construire localement.
docker build -t stelligent/cfn_nag .
Vous pouvez monter un répertoire local contenant des modèles dans le conteneur Docker, puis appeler cfn_nag dans le conteneur. Cet exemple utilise les modèles de test utilisés dans les tests unitaires de cfn_nag :
$ docker run -v `pwd`/spec/test_templates:/templates -t stelligent/cfn_nag /templates/json/efs/filesystem_with_encryption.json
{
"failure_count": 0,
"violations": [
]
}
$ docker run -v `pwd`/spec/test_templates:/templates -t stelligent/cfn_nag /templates/json/efs/filesystem_with_no_encryption.json
{
"failure_count": 1,
"violations": [
{
"id": "F27",
"type": "FAIL",
"message": "EFS FileSystem should have encryption enabled",
"logical_resource_ids": [
"filesystem"
]
}
]
}
cfn_nag_scan peut être exécuté dans le cadre d'un workflow GitHub pour évaluer le code pendant les pipelines d'intégration continue.
Dans votre fichier de workflow GitHub, créez une étape qui utilise l'action cfn_nag :
- name: Simple test
uses: stelligent/cfn_nag@master
with:
input_path: tests
Plus d'informations sur l'action GitHub sont disponibles ici.
cfn-nag prend en charge la notion de « profil », qui est en réalité une liste d'autorisation de règles à appliquer. Le profil est un fichier texte qui doit contenir un identifiant de règle par ligne. Lorsqu'il est spécifié via l'argument de ligne de commande --profile-path, cfn-nag ne renverra QUE les violations de ces règles particulières.
La motivation derrière la création d'un « profil » est que différents développeurs peuvent être concernés par différentes règles. Par exemple, un « infrastructure_developer » peut être concerné par les règles IAM, tandis qu'un « app_developer » peut ne même pas être en mesure de créer des ressources IAM et donc ne pas se soucier de ces règles.
Voici un exemple de profil :
F1
F2
F27
W3
W5
La liste de refus est en quelque sorte l'opposé du profil : c'est une liste de règles à NE JAMAIS appliquer. Lorsqu'elle est spécifiée via l'argument de ligne de commande --deny-list-path, cfn-nag ne renverra JAMAIS de violations pour les règles particulières spécifiées dans le fichier.
Si une règle est spécifiée dans les deux, la liste de refus aura priorité sur le profil et la règle ne sera pas appliquée.
Le format est le suivant. Les deux seuls champs importants sont RulesToSuppress et le id de chaque élément. La reason ne sera pas interprétée par cfn-nag, mais il est recommandé de justifier et de documenter pourquoi la règle ne devrait jamais être appliquée.
RulesToSuppress:
- id: W3
reason: W3 is something we never care about at enterprise X
Dans le cas où vous souhaitez supprimer une règle, une clé Metadata de cfn_nag peut être ajoutée à la ressource concernée pour indiquer à cfn_nag de ne pas signaler d'échec ou d'avertissement pour cette règle.
Par exemple, si vous configurez un ELB public ouvert aux connexions entrantes depuis Internet avec des ressources comme celles-ci :
public_alb.yaml
# Partial template
PublicAlbSecurityGroup:
Properties:
GroupDescription: 'Security group for a public Application Load Balancer'
VpcId:
Ref: vpc
Type: AWS::EC2::SecurityGroup
PublicAlbSecurityGroupHttpIngress:
Properties:
CidrIp: 0.0.0.0/0
FromPort: 80
GroupId:
Ref: PublicAlbSecurityGroup
IpProtocol: tcp
ToPort: 80
Type: AWS::EC2::SecurityGroupIngress
cfn_nag lèvera des avertissements comme ceux-ci :
$ cfn_nag_scan -i public_alb.yaml
------------------------------------------------------------
public_alb.yaml
------------------------------------------------------------------------------------------------------------------------
| WARN W9
|
| Resources: ["PublicAlbSecurityGroup"]
|
| Security Groups found with ingress cidr that is not /32
------------------------------------------------------------
| WARN W2
|
| Resources: ["PublicAlbSecurityGroup"]
|
| Security Groups found with cidr open to world on ingress. This should never be true on instance. Permissible on ELB
Failures count: 0
Warnings count: 2
En ajoutant les métadonnées, ces avertissements peuvent être supprimés :
public_alb_with_suppression.yaml
# Partial template
PublicAlbSecurityGroup:
Properties:
GroupDescription: 'Security group for a public Application Load Balancer'
VpcId:
Ref: vpc
Type: AWS::EC2::SecurityGroup
Metadata:
cfn_nag:
rules_to_suppress:
- id: W9
reason: "This is a public facing ELB and ingress from the internet should be permitted."
- id: W2
reason: "This is a public facing ELB and ingress from the internet should be permitted."
PublicAlbSecurityGroupHttpIngress:
Properties:
CidrIp: 0.0.0.0/0
FromPort: 80
GroupId:
Ref: PublicAlbSecurityGroup
IpProtocol: tcp
ToPort: 80
Type: AWS::EC2::SecurityGroupIngress
$ cfn_nag_scan -i public_alb_with_suppression.yaml
------------------------------------------------------------
public_alb_with_supression.yaml
------------------------------------------------------------
Failures count: 0
Warnings count: 0
Les paramètres de modèle CloudFormation peuvent poser un problème pour l'analyse statique car les valeurs sont spécifiées au moment du déploiement. En d'autres termes, les valeurs ne sont pas disponibles lorsque l'analyse statique est effectuée - l'analyse statique ne peut examiner que le « code » qui se trouve devant elle. Par conséquent, une règle d'entrée de groupe de sécurité de 0.0.0.0/0 ne sera pas signalée si le cidr est paramétré et que le 0.0.0.0/0 est transmis au moment du déploiement.
Pour permettre la vérification des valeurs des paramètres, un utilisateur peut spécifier les valeurs des paramètres dans un fichier JSON transmis en ligne de commande à cfn_nag et cfn_nag_scan avec l'option --parameter-values-path=<filename/uri>.
Le format du JSON est une clé unique, « Parameters », dont la valeur est un dictionnaire dont chaque paire clé/valeur correspond aux paramètres :
{
"Parameters": {
"Cidr": "0.0.0.0/0"
}
}
Cela fournira « 0.0.0.0/0 » au paramètre suivant :
Parameters:
Cidr:
Type: String
ATTENTION : s'il y a des paramètres supplémentaires dans le JSON, ils sont silencieusement ignorés (pour permettre à cfn_nag_scan d'appliquer le même JSON à tous les modèles).
Si le JSON est malformé ou ne respecte pas la spécification ci-dessus, l'analyse échouera avec une violation FATALE.
Avant la version 0.5.55, les appels à Fn::FindInMap étaient effectivement ignorés. Le modèle sous-jacent les laissait tels quels, et ils apparaissaient donc comme des valeurs de type Hash pour les règles. Par exemple : { "Fn::FindInMap" => [map1, key1, key2]}
À partir de la version 0.5.55, le modèle tentera de calculer la valeur d'un appel à FindInMap et de présenter cette valeur aux règles. Cette évaluation prend en charge les clés qui sont :
Si la logique d'évaluation ne peut pas déterminer la valeur d'une clé, elle reviendra au comportement précédent, à savoir renvoyer le Hash pour l'expression entière.
Avant 0.5.55 également, les appels aux pseudo-fonctions AWS étaient effectivement ignorés. Le modèle sous-jacent les laissait tels quels, et ils apparaissaient donc comme des valeurs de type Hash pour les règles. Par exemple : {"Ref"=>"AWS::Region"}. Un cas d'utilisation courant consiste à organiser les mappages par région, de sorte que l'évaluation des pseudo-fonctions est importante pour mieux prendre en charge l'évaluation des mappages.
À partir de la version 0.5.55, le modèle présentera les pseudo-fonctions AWS suivantes aux règles avec les valeurs par défaut :
'AWS::URLSuffix' => 'amazonaws.com',
'AWS::Partition' => 'aws',
'AWS::NotificationARNs' => '',
'AWS::AccountId' => '111111111111',
'AWS::Region' => 'us-east-1',
'AWS::StackId' => 'arn:aws:cloudformation:us-east-1:111111111111:stack/stackname/51af3dc0-da77-11e4-872e-1234567db123',
'AWS::StackName' => 'stackname'
De plus, l'utilisateur final peut remplacer la valeur fournie via le mécanisme traditionnel de substitution de paramètres. Par exemple :
{
"Parameters": {
"AWS::Region": "eu-west-1"
}
}
Jusqu'à la version 0.4.66 de cfn_nag, le modèle sous-jacent ne traitait pas les Fn::If dans un modèle. Cela signifiait que si une propriété avait une valeur conditionnelle, c'était à la règle de l'analyser. Étant donné qu'un Fn::If pouvait apparaître presque n'importe où, cela créait un jeu de taupe pour les développeurs de règles. Au mieux, la logique de la règle pouvait ignorer les valeurs qui étaient des Hash, en supposant que la valeur n'était pas un Hash en premier lieu.
Afin de résoudre ce problème, le comportement par défaut de cfn_nag consiste désormais à substituer Fn::If par le résultat de la branche vraie. Cela signifie que, par défaut, les règles n'inspecteront pas les résultats de la branche fausse pour détecter des violations de sécurité.
En plus de substituer Fn::If au niveau des valeurs de propriété, le même comportement est appliqué aux Fn::If au niveau supérieur des Properties. Par exemple :
Resource1:
Type: Foo
Properties: !If
- IsNone
- Description: Up
- Description: DOwn
Sera identique à :
Resource1:
Type: Foo
Properties:
Description: Up
Pour offrir un certain contrôle sur ce comportement, un utilisateur peut spécifier les valeurs des conditions dans un fichier JSON transmis en ligne de commande à cfn_nag et cfn_nag_scan avec l'option --condition-values-path=<filename/uri>.
Le format du JSON est un dictionnaire dont chaque paire clé/valeur correspond aux conditions :
{
"Condition1": true,
"Condition2": false
}
La base du SPCM est décrite dans l'article de blog Thought Experiment Proposed Complexity Metric for IAM Policy Documents.
À partir de la version 0.6.0 de cfn_nag :
spcm_scan peut analyser un répertoire de modèles CloudFormation (comme cfn_nag_scan) et générer un rapport avec les métriques SPCM au format JSON ou HTMLcfn_nag_scan --rule-arguments spcm_threshold:100--rule-arguments. L'objet Rule doit seulement déclarer un attr_accessor, par ex. attr_accessor :spcm_threshold et cfn_nag se chargera des détails pour injecter les valeurs depuis --rule-argumentsLa version 0.5.x inclut des changements majeurs dans la manière dont les règles personnalisées (peuvent) être distribuées et chargées. Avant cette version, il y avait deux endroits d'où les règles étaient chargées : le répertoire lib/cfn-nag/custom_rules au sein du gem cfn_nag principal, et le répertoire de règles personnalisées spécifié en ligne de commande.
Deux cas d'utilisation ont forcé une refonte de la façon dont les règles personnalisées sont chargées et d'où elles sont chargées. Le mécanisme de chargement des règles a été généralisé afin que des dépôts de règles personnalisées puissent être utilisés pour découvrir des règles.
Un ensemble de « fichiers de règles » dispersés sur un système de fichiers n'est pas idéal d'un point de vue traditionnel du développement logiciel. Il n'y a ni version ni traçabilité sur ces fichiers, c'est pourquoi 0.5.x introduit la notion de « gem de règles cfn_nag ». Un développeur peut développer des règles personnalisées dans un gem séparé, le versionner et l'installer... et ces règles sont référencées depuis cfn_nag tant que les métadonnées du gem incluent cfn_nag_rules => true. Pour un gem nommé comme « cfn-nag-hipaa-rules », tous les *.rb sous lib/cfn-nag-hipaa-rules seront chargés. Toutes les règles personnalisées doivent dériver de CfnNag::BaseRule dans cfn-nag/base_rule (pas cfn-nag/custom-rules/base). Si la règle doit dériver d'autre chose, la définition d'une méthode cfn_nag_rule? qui renvoie true la fera également charger comme une règle.
Lorsque cfn_nag s'exécute dans un AWS Lambda - il n'y a pas vraiment de système de fichiers (en dehors de /tmp) au sens traditionnel. Par conséquent, seules les règles principales sont utilisables depuis la Lambda. Pour prendre en charge les règles personnalisées, cfn_nag prend en charge la découverte de règles à partir d'un bucket S3 au lieu du système de fichiers.
Tout ce que vous avez probablement vu sur la façon de développer des règles personnalisées en Ruby reste vrai.
Pour découvrir des règles à partir d'un bucket S3, créez un fichier s3.yml avec ce contenu :
---
repo_class_name: S3BucketBasedRuleRepo
repo_arguments:
s3_bucket_name: cfn-nag-rules-my-enterprise
prefix: /rules
Pour appliquer les fichiers *Rule.rb dans le bucket cfn-nag-rules-my-enterprise avec le préfixe /rules (par ex. /rules/MyNewRule.rb), spécifiez ce fichier en ligne de commande à cfn_nag comme suit :
cat my_cfn_template.yml | cfn_nag --rule-repository s3.yml
Si les règles se trouvent dans plusieurs buckets, créez plusieurs fichiers s3*.yml et spécifiez-les dans l'argument --rule-repository.
Si les informations d'identification AWS de l'environnement disposent de l'autorisation d'accéder au bucket cfn-nag-rules-enterprise, il trouvera toutes les règles comme /rules/*Rule.rb. Si un aws_profile particulier doit être utilisé, ajoutez-le comme clé sous repo_arguments, par ex. aws_profile: my_aws_profile
Au-delà du système de fichiers, des installations de gems et de S3 - la nouvelle architecture prend théoriquement en charge le développement d'autres « dépôts de règles » pour charger des règles depuis DynamoDb, des bases de données relationnelles ou d'autres services web.
Pour créer de nouvelles règles pour votre propre usage et/ou une contribution communautaire, consultez Développement de règles personnalisées pour plus de détails.
Une capture vidéo démontrant le développement de règles personnalisées TDD de A à Z est disponible ici :
https://www.youtube.com/watch?v=JRZct0naFd4&t=1601s
Pour exécuter les specs, vous devez vous assurer que Docker est installé et que les dépendances de cfn_nag sont installées via
gem install bundle
bundle install
Ensuite, pour exécuter toutes les specs, il suffit de lancer rake test:all.
Pour exécuter les tests de bout en bout, lancez rake test:e2e. Le script regroupera tous les gems du Gemfile, construira et installera le gem cfn_nag localement, installera les dépendances des specs, puis exécutera les tests étiquetés « end_to_end ». Il téléchargera également des modèles d'exemple fournis par Amazon et exécutera cfn_nag_scan sur ceux-ci, afin de voir si des modèles connus pour être valides provoquent des exceptions dans cfn-nag.
Pour installer la branche git actuelle localement :
bundle install
scripts/deploy_local.sh
Il existe un environnement de développement à distance complet, créé et configuré avec tous les outils et paramètres préconfigurés pour faciliter le développement et la création de règles. Vous pouvez l'activer en utilisant la fonctionnalité de développement à distance de VS Code.
Folder contains a dev container configuration file. Reopen folder to develop in a container, cliquez sur le bouton Reopen in Container[Dev Container] cfn_nag DevelopmentPlus d'informations sur la configuration du développement à distance de VS Code sont disponibles ici : VS Code Remote Development.
Pour signaler un bug ou demander une fonctionnalité, soumettez un problème via le dépôt GitHub : https://github.com/stelligent/cfn_nag/issues/new