
Strumento di linting per template CloudFormation
Lo strumento cfn-nag cerca nei modelli CloudFormation pattern che potrebbero indicare infrastrutture non sicure. In parole povere, cerca:
Per ulteriori informazioni sullo strumento, consulta questo articolo sul blog di Stelligent:
Supponendo che Ruby >= 2.5.x sia installato, l'installazione è solo una questione di:
gem install cfn-nag
Su MacOS o Linux puoi anche installare con brew:
brew install ruby brew-gem
brew gem install cfn-nag
Per eseguire cfn_nag come azione in CodePipeline, puoi eseguire il deploy tramite l'AWS Serverless Application Repository.
Per eseguire:
cfn_nag_scan --input-path <path to cloudformation json>
Il percorso può essere una directory o un modello specifico. Se è una directory, tutti i file .json, .template, .yml e .yaml verranno elaborati, inclusa la ricorsione nelle sottodirectory.
Il formato di output predefinito è testo libero, ma l'output JSON può essere selezionato con il flag --output-format json.
Facoltativamente, un flag --debug scaricherà informazioni sugli interni del caricamento delle regole.
Esegui con --help per un elenco completo delle opzioni supportate.
Per vedere un elenco di tutte le regole attualmente supportate da cfn-nag, esiste un'utilità a riga di comando che le invia a stdout:
cfn_nag_rules
È fornito un Dockerfile per comodità. È pubblicato su DockerHub come stelligent/cfn_nag.
https://hub.docker.com/r/stelligent/cfn_nag
Puoi anche crearlo localmente.
docker build -t stelligent/cfn_nag .
Puoi montare una directory locale contenente i modelli nel container Docker e quindi chiamare cfn_nag all'interno del container. Questo esempio utilizza i modelli di test usati negli unit test di 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 può essere eseguito come parte di un GitHub Workflow per valutare il codice durante le pipeline di integrazione continua.
Nel tuo file GitHub Workflow, crea uno step che utilizza l'Action cfn_nag:
- name: Simple test
uses: stelligent/cfn_nag@master
with:
input_path: tests
Maggiori informazioni sulla GitHub Action sono disponibili qui.
cfn-nag supporta il concetto di "profilo" che è di fatto una lista di consentiti (allow list) delle regole da applicare. Il profilo è un file di testo
che deve contenere un identificatore di regola per riga. Quando viene specificato tramite l'argomento da riga di comando --profile-path,
cfn-nag restituirà SOLO le violazioni di quelle specifiche regole.
La motivazione alla base della creazione di un "profilo" è che diversi sviluppatori potrebbero interessarsi a regole diverse. Ad esempio, uno "sviluppatore di infrastrutture" potrebbe preoccuparsi delle regole IAM, mentre uno "sviluppatore di applicazioni" potrebbe non essere nemmeno in grado di creare risorse IAM e quindi non interessarsi a quelle regole.
Ecco un esempio di profilo:
F1
F2
F27
W3
W5
La deny list è sostanzialmente l'opposto del profilo: è un elenco di regole da NON applicare MAI. Quando viene specificata tramite
l'argomento da riga di comando --deny-list-path, cfn-nag non restituirà MAI violazioni dalle regole specificate
nel file.
Nel caso in cui una regola sia specificata in entrambi, la deny list avrà priorità sul profilo e la regola non verrà applicata.
Il formato è il seguente. Gli unici due campi salienti sono RulesToSuppress e l'id per ogni elemento. La reason non
verrà interpretata da cfn-nag, ma è consigliato giustificare e documentare il motivo per cui la regola non dovrebbe mai essere applicata.
RulesToSuppress:
- id: W3
reason: W3 is something we never care about at enterprise X
Nel caso in cui esista una regola che desideri sopprimere, è possibile aggiungere una chiave Metadata di cfn_nag alla risorsa interessata per dire a cfn_nag di non generare un errore o un avviso per quella regola.
Ad esempio, se stai configurando un ELB pubblico aperto alle connessioni in ingresso da internet con risorse come le seguenti:
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 genererà avvisi come i seguenti:
$ 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
Aggiungendo i metadati, questi avvisi possono essere soppressi:
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
I parametri dei modelli CloudFormation possono rappresentare un problema per l'analisi statica poiché i valori vengono specificati al momento del deployment. In altre parole, i valori non sono disponibili quando viene eseguita l'analisi statica - l'analisi statica può solo guardare al "codice" che ha di fronte. Pertanto, una regola di ingresso di un security group di 0.0.0.0/0 non verrà segnalata se il cidr è parametrizzato e lo 0.0.0.0/0 viene passato al momento del deployment.
Per consentire il controllo dei valori dei parametri, un utente può specificare i valori dei parametri in un file JSON passato da riga di comando
sia a cfn_nag che a cfn_nag_scan con il flag --parameter-values-path=<filename/uri>.
Il formato del JSON è una singola chiave, "Parameters", il cui valore è un dizionario con ogni coppia chiave/valore mappata ai parametri:
{
"Parameters": {
"Cidr": "0.0.0.0/0"
}
}
Questo fornirà "0.0.0.0/0" al seguente parametro:
Parameters:
Cidr:
Type: String
ATTENZIONE: se ci sono parametri extra nel JSON, vengono ignorati silenziosamente (per consentire a cfn_nag_scan di applicare
lo stesso JSON a tutti i modelli).
Se il JSON è malformato o non soddisfa la specifica precedente, l'analisi fallirà con una violazione FATAL.
Prima della versione 0.5.55, le chiamate a Fn::FindInMap venivano di fatto ignorate. Il modello sottostante le
lasciava così com'erano, quindi apparivano come valori Hash alle regole. Ad esempio: { "Fn::FindInMap" => [map1, key1, key2]}
A partire dalla versione 0.5.55, il modello tenterà di calcolare il valore per una chiamata a FindInMap e di presentare tale valore alle regole. Questa valutazione supporta chiavi che sono:
Se la logica di valutazione non riesce a determinare il valore per una chiave, ripiegherà sul vecchio comportamento di restituire l' Hash per l'intera espressione.
Anche prima della versione 0.5.55, le chiamate alle pseudofunzioni AWS venivano di fatto ignorate. Il modello sottostante le
lasciava così com'erano, quindi apparivano come valori Hash alle regole. Ad esempio: {"Ref"=>"AWS::Region"}.
Un caso d'uso comune è organizzare le mappature per regione, quindi la valutazione delle pseudofunzioni è importante per supportare meglio
la valutazione delle mappe.
A partire dalla versione 0.5.55, il modello presenterà alle regole le seguenti pseudofunzioni AWS con i valori predefiniti:
'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'
Inoltre, l'utente finale può sovrascrivere il valore fornito tramite il tradizionale meccanismo di sostituzione dei parametri. Ad esempio:
{
"Parameters": {
"AWS::Region": "eu-west-1"
}
}
Fino alla versione 0.4.66 di cfn_nag, il modello sottostante non eseguiva alcuna elaborazione di Fn::If all'interno di un modello. Ciò significava che se una proprietà aveva un valore condizionale, spettava alla regola interpretare il Fn::If. Dato che un Fn::If poteva apparire praticamente ovunque, creava una situazione di "gioco a colpire la talpa" per gli sviluppatori di regole. Nella migliore delle ipotesi, la logica della regola poteva ignorare i valori che erano Hash, presumendo che il valore non fosse un Hash in primo luogo.
Per affrontare questo problema, il comportamento predefinito di cfn_nag ora è quello di sostituire Fn::If con il risultato del ramo vero. Ciò significa che, per impostazione predefinita, le regole non ispezioneranno i risultati del ramo falso alla ricerca di violazioni di sicurezza.
Oltre a sostituire Fn::If a livello di valore della proprietà, lo stesso comportamento viene applicato a Fn::If al livello superiore di Properties. Ad esempio:
Resource1:
Type: Foo
Properties: !If
- IsNone
- Description: Up
- Description: DOwn
Apparirà uguale a:
Resource1:
Type: Foo
Properties:
Description: Up
Per fornire un certo controllo su questo comportamento, un utente può specificare i valori delle condizioni in un file JSON passato da riga di comando
sia a cfn_nag che a cfn_nag_scan con il flag --condition-values-path=<filename/uri>.
Il formato del JSON è un dizionario con ogni coppia chiave/valore mappata alle Conditions:
{
"Condition1": true,
"Condition2": false
}
La base di SPCM è descritta nel post del blog Thought Experiment Proposed Complexity Metric for IAM Policy Documents.
A partire dalla versione 0.6.0 di cfn_nag:
spcm_scan può scansionare una directory di modelli CloudFormation (come cfn_nag_scan) e generare un report con le metriche SPCM
in formato JSON o HTMLcfn_nag_scan --rule-arguments spcm_threshold:100--rule-arguments.
L'oggetto Rule deve solo dichiarare un attr_accessor, ad esempio attr_accessor :spcm_threshold e cfn_nag
si occuperà dei dettagli per iniettare i valori da --rule-argumentsLa release 0.5.x include alcune modifiche importanti su come le regole personalizzate (possono) essere distribuite e caricate. Prima di questa release,
c'erano due posti da cui venivano caricate le regole: la directory lib/cfn-nag/custom_rules all'interno della gem principale cfn_nag,
e la directory delle regole personalizzate specificata da riga di comando.
Ci sono due casi d'uso che hanno imposto una riprogettazione di come/dove vengono caricate le regole personalizzate. Il meccanismo di caricamento delle regole è stato generalizzato in modo che i repository di regole personalizzate possano essere utilizzati per scoprire le regole.
Un mucchio di "file di regole" sparsi su un filesystem non è ideale dal punto di vista tradizionale dello sviluppo software.
Non c'è versione o tracciabilità su questi file, quindi 0.5.x introduce il concetto di "cfn_nag rule gem". Uno sviluppatore
può sviluppare regole personalizzate come parte di una gem separata, versionarla e installarla... e queste regole vengono referenziate da cfn_nag
finché i metadati della gem includono cfn_nag_rules => true. Per una gem chiamata ad esempio "cfn-nag-hipaa-rules", verrà caricato qualsiasi *.rb presente in
lib/cfn-nag-hipaa-rules. Qualsiasi regola personalizzata dovrebbe derivare da CfnNag::BaseRule in cfn-nag/base_rule (non cfn-nag/custom-rules/base). Se la regola deve derivare da qualcos'altro, definire un metodo cfn_nag_rule? che restituisce true farà sì che venga caricata anch'essa come regola.
Quando cfn_nag è in esecuzione in un AWS Lambda - non esiste realmente un filesystem (oltre a /tmp) nel senso tradizionale. Pertanto, solo le regole principali sono utilizzabili dalla Lambda. Per supportare le regole personalizzate, cfn_nag supporta lo scoprire le regole da un bucket S3 invece che dal filesystem.
Tutto ciò che probabilmente hai visto su come sviluppare regole personalizzate in Ruby rimane valido.
Per scoprire regole da un bucket S3, crea un file s3.yml con questo contenuto:
---
repo_class_name: S3BucketBasedRuleRepo
repo_arguments:
s3_bucket_name: cfn-nag-rules-my-enterprise
prefix: /rules
Per applicare i file *Rule.rb nel bucket cfn-nag-rules-my-enterprise con il prefisso /rules (ad esempio /rules/MyNewRule.rb), specifica questo file da riga di comando a cfn_nag come segue:
cat my_cfn_template.yml | cfn_nag --rule-repository s3.yml
Se le regole si trovano in più di un bucket, crea più file s3*.yml e specificali nell'argomento --rule-repository.
Se le credenziali AWS dell'ambiente hanno il permesso di accedere al bucket cfn-nag-rules-enterprise, troverà tutte le regole
del tipo /rules/*Rule.rb. Se si desidera utilizzare un particolare aws_profile, aggiungilo come chiave sotto repo_arguments, ad esempio
aws_profile: my_aws_profile
Oltre a filesystem, installazioni gem e S3 - la nuova architettura supporta teoricamente lo sviluppo di altri "rule repository" per caricare regole da DynamoDb, database relazionali o altri servizi web.
Per creare nuove regole per uso personale e/o contributo alla community, consulta Sviluppo di regole personalizzate per i dettagli.
Uno screencast che mostra lo sviluppo TDD di regole personalizzate dall'inizio alla fine è disponibile qui:
https://www.youtube.com/watch?v=JRZct0naFd4&t=1601s
Per eseguire le spec, devi assicurarti di avere Docker installato e le dipendenze di cfn_nag installate tramite
gem install bundle
bundle install
Quindi, per eseguire tutte le spec, basta eseguire rake test:all.
Per eseguire i test end-to-end, esegui rake test:e2e. Lo script raggrupperà tutte le gem nel Gemfile, creerà e installerà localmente la gem cfn_nag, installerà le dipendenze delle spec e poi eseguirà i test taggati come 'end_to_end'. Scaricherà anche modelli di esempio forniti da Amazon ed eseguirà cfn_nag_scan su di essi, per vedere se alcuni modelli noti come validi causano eccezioni all'interno di cfn-nag.
Per installare localmente il ramo git corrente:
bundle install
scripts/deploy_local.sh
È disponibile un ambiente di sviluppo remoto completo, creato e configurato con tutti gli strumenti e le impostazioni pre-configurati per facilitare lo sviluppo e la creazione di regole. Puoi abilitarlo utilizzando la funzionalità di sviluppo remoto di VS Code.
Folder contains a dev container configuration file. Reopen folder to develop in a container, fai clic sul pulsante Reopen in Container[Dev Container] cfn_nag DevelopmentMaggiori informazioni sulla configurazione dello sviluppo remoto in VS Code sono disponibili qui: Sviluppo remoto con VS Code.
Per segnalare un bug o richiedere una funzionalità, invia una issue tramite il repository GitHub: https://github.com/stelligent/cfn_nag/issues/new