
Herramienta de linting para plantillas de CloudFormation
La herramienta cfn-nag busca patrones en las plantillas de CloudFormation que puedan indicar infraestructura insegura. En términos generales, buscará:
Para obtener más información sobre la herramienta, consulte esta publicación en el blog de Stelligent:
Suponiendo que Ruby >= 2.5.x esté instalado, la instalación es solo cuestión de:
gem install cfn-nag
En MacOS o Linux, también puede instalarlo alternativamente con brew:
brew install ruby brew-gem
brew gem install cfn-nag
Para ejecutar cfn_nag como una acción en CodePipeline, puede implementarlo a través del AWS Serverless Application Repository.
Para ejecutar:
cfn_nag_scan --input-path <ruta al json de cloudformation>
La ruta puede ser un directorio o una plantilla en particular. Si es un directorio, se procesarán todos los archivos .json, .template, .yml y .yaml, incluida la recursión en subdirectorios.
El formato de salida predeterminado es texto de forma libre, pero la salida json se puede seleccionar con la bandera --output-format json.
Opcionalmente, una bandera --debug volcará información sobre los entresijos de la carga de reglas.
Ejecute con --help para obtener una lista completa de los modificadores admitidos.
Para ver una lista de todas las reglas que cfn-nag admite actualmente, hay una utilidad de línea de comandos que las volcará a stdout:
cfn_nag_rules
Se proporciona un Dockerfile por conveniencia. Está publicado en DockerHub como stelligent/cfn_nag.
https://hub.docker.com/r/stelligent/cfn_nag
También puede construirlo localmente.
docker build -t stelligent/cfn_nag .
Puede montar un directorio local que contenga plantillas en el contenedor Docker y luego llamar a cfn_nag en el contenedor. Este ejemplo utiliza las plantillas de prueba utilizadas en las pruebas unitarias 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 se puede ejecutar como parte de un GitHub Workflow para evaluar código durante los pipelines de integración continua.
En su archivo de GitHub Workflow, cree un paso que utilice la cfn_nag Action:
- name: Simple test
uses: stelligent/cfn_nag@master
with:
input_path: tests
Puede encontrar más información sobre la GitHub Action aquí.
cfn-nag admite la noción de un "perfil" que es efectivamente una lista de permitidos de reglas a aplicar. El perfil es un archivo de texto
que debe contener un identificador de regla por línea. Cuando se especifica mediante el argumento de línea de comandos --profile-path,
cfn-nag SOLO devolverá violaciones de esas reglas particulares.
La motivación detrás de la creación de un "perfil" es que diferentes desarrolladores podrían preocuparse por diferentes reglas. Por ejemplo, un "desarrollador_de_infraestructura" podría preocuparse por las reglas de IAM, mientras que un "desarrollador_de_aplicaciones" podría ni siquiera poder crear recursos de IAM y, por lo tanto, no le importarían esas reglas.
Aquí hay un ejemplo de perfil:
F1
F2
F27
W3
W5
La lista de denegación es básicamente lo opuesto al perfil: es una lista de reglas que NUNCA se aplican. Cuando se especifica mediante el
argumento de línea de comandos --deny-list-path, cfn-nag NUNCA devolverá violaciones de esas reglas particulares especificadas
en el archivo.
En caso de que una regla se especifique en ambos, la lista de denegación tendrá prioridad sobre el perfil y la regla no se aplicará.
El formato es el siguiente. Los únicos dos campos importantes son RulesToSuppress y el id de cada elemento. El reason no será
interpretado por cfn-nag, pero se recomienda para justificar y documentar por qué la regla nunca debería aplicarse.
RulesToSuppress:
- id: W3
reason: W3 is something we never care about at enterprise X
En caso de que haya una regla que desee suprimir, se puede agregar una clave cfn_nag Metadata al recurso afectado para indicarle a cfn_nag que no genere un fallo o advertencia para esa regla.
Por ejemplo, si está configurando un ELB orientado al público que está abierto a conexiones entrantes desde internet con recursos como los siguientes:
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 generará advertencias como las siguientes:
$ 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
Al agregar los metadatos, estas advertencias se pueden suprimir:
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
Los parámetros de plantilla de CloudFormation pueden presentar un problema para el análisis estático, ya que los valores se especifican en el punto de implementación. En otras palabras, los valores no están disponibles cuando se realiza el análisis estático: el análisis estático solo puede mirar el "código" que tiene delante. Por lo tanto, una regla de entrada de un grupo de seguridad de 0.0.0.0/0 no se marcará si el cidr está parametrizado y el 0.0.0.0/0 se pasa en el momento de la implementación.
Para permitir la verificación de los valores de los parámetros, un usuario puede especificar los valores de los parámetros en un archivo JSON pasado en la línea de comandos
tanto a cfn_nag como a cfn_nag_scan con la bandera --parameter-values-path=<nombre_del_archivo/uri>.
El formato del JSON es una sola clave, "Parameters", cuyo valor es un diccionario con cada par clave/valor que se asigna a los Parameters:
{
"Parameters": {
"Cidr": "0.0.0.0/0"
}
}
Esto proporcionará "0.0.0.0/0" al siguiente Parameter:
Parameters:
Cidr:
Type: String
TENGA CUIDADO: si hay parámetros adicionales en el JSON, se ignoran silenciosamente (para permitir que cfn_nag_scan aplique
el mismo JSON a todas las plantillas).
Si el JSON está malformado o no cumple con la especificación anterior, el análisis fallará con una violación FATAL.
Antes de la versión 0.5.55, las llamadas a Fn::FindInMap se ignoraban efectivamente. El modelo subyacente las
dejaba en paz y, por lo tanto, aparecían como valores Hash para las reglas. Por ejemplo: { "Fn::FindInMap" => [map1, key1, key2]}
A partir de la versión 0.5.55, el modelo intentará calcular el valor de una llamada a FindInMap y presentará ese valor a las reglas. Esta evaluación admite claves que son:
Si la lógica de evaluación no puede averiguar el valor de una clave, se retomará el comportamiento anterior de devolver el Hash de toda la expresión.
También antes de la versión 0.5.55, las llamadas a las pseudofunciones de AWS se ignoraban efectivamente. El modelo subyacente las
dejaba en paz y, por lo tanto, aparecían como valores Hash para las reglas. Por ejemplo: {"Ref"=>"AWS::Region"}.
Un caso de uso común es organizar los mappings por región, por lo que la evaluación de pseudofunciones es importante para admitir mejor la
evaluación de mapas.
A partir de la versión 0.5.55, el modelo presentará las siguientes pseudofunciones de AWS a las reglas con los valores predeterminados:
'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'
Además, el usuario final puede anular el valor proporcionado mediante el mecanismo tradicional de sustitución de parámetros. Por ejemplo:
{
"Parameters": {
"AWS::Region": "eu-west-1"
}
}
Hasta la versión 0.4.66 de cfn_nag, el modelo subyacente no realizaba ningún procesamiento de Fn::If dentro de una plantilla. Esto significaba que si una propiedad tenía un valor condicional, dependía de la regla analizar el Fn::If. Dado que un Fn::If podía aparecer en casi cualquier lugar, creaba una situación de "juego del topo" para los desarrolladores de reglas. En el mejor de los casos, la lógica de la regla podía ignorar los valores que eran Hash, asumiendo que el valor no era un Hash en primer lugar.
Para abordar este problema, el comportamiento predeterminado de cfn_nag ahora es sustituir Fn::If con el resultado verdadero. Esto significa que, de forma predeterminada, las reglas no inspeccionarán los resultados falsos en busca de violaciones de seguridad.
Además de sustituir Fn::If a nivel del valor de la propiedad, el mismo comportamiento se aplica a Fn::If en el nivel superior de Properties. Por ejemplo:
Resource1:
Type: Foo
Properties: !If
- IsNone
- Description: Up
- Description: DOwn
Se verá igual que:
Resource1:
Type: Foo
Properties:
Description: Up
Para proporcionar cierto control sobre este comportamiento, un usuario puede especificar los valores de las condiciones en un archivo JSON pasado en la línea de comandos
tanto a cfn_nag como a cfn_nag_scan con la bandera --condition-values-path=<nombre_del_archivo/uri>.
El formato del JSON es un diccionario con cada par clave/valor que se asigna a las Conditions:
{
"Condition1": true,
"Condition2": false
}
La base de SPCM se describe en la publicación del blog Thought Experiment Proposed Complexity Metric for IAM Policy Documents.
A partir de la versión 0.6.0 de cfn_nag:
spcm_scan puede escanear un directorio de plantillas de CloudFormation (como cfn_nag_scan) y generar un informe con las métricas SPCM
en formato JSON o HTMLcfn_nag_scan --rule-arguments spcm_threshold:100--rule-arguments.
El objeto Rule solo necesita declarar un attr_accessor, por ejemplo, attr_accessor :spcm_threshold y cfn_nag
se encargará de los detalles para inyectar valores desde --rule-argumentsEl lanzamiento de 0.5.x incluye algunos cambios importantes en cómo las reglas personalizadas (pueden) distribuirse y cargarse. Antes de este lanzamiento,
había dos lugares desde donde se cargaban las reglas: el directorio lib/cfn-nag/custom_rules dentro de la gema principal cfn_nag,
y el directorio de reglas personalizadas especificado en la línea de comandos.
Hay dos casos de uso que forzaron un rediseño de cómo/dónde se cargan las reglas personalizadas. El mecanismo de carga de reglas se ha generalizado de modo que los repositorios de reglas personalizadas se pueden usar para descubrir reglas.
Un montón de "archivos de reglas" dispersos en un sistema de archivos no es excelente desde una perspectiva tradicional de desarrollo de software.
No hay versión ni trazabilidad en estos archivos, por lo que 0.5.x introduce la noción de una "gema de reglas cfn_nag". Un desarrollador
puede desarrollar reglas personalizadas como parte de una gema separada, versionarla e instalarla... y esas reglas se referencian desde cfn_nag
siempre que los metadatos de la gema incluyan cfn_nag_rules => true. Para una gema con un nombre como "cfn-nag-hipaa-rules", cualquier *.rb en
lib/cfn-nag-hipaa-rules se cargará. Cualquier regla personalizada debe derivarse de CfnNag::BaseRule en cfn-nag/base_rule (no cfn-nag/custom-rules/base). Si la regla debe derivarse de otra cosa, definir un método cfn_nag_rule? que devuelva true también hará que se cargue como regla.
Cuando cfn_nag se ejecuta en un AWS Lambda - realmente no hay un sistema de archivos (además de /tmp) en el sentido tradicional. Por lo tanto, solo las reglas principales se pueden usar desde la Lambda. Para admitir reglas personalizadas, cfn_nag admite el descubrimiento de reglas desde un bucket de S3 en lugar del sistema de archivos.
Todo lo que probablemente haya visto sobre cómo desarrollar reglas personalizadas en Ruby sigue siendo válido.
Para descubrir reglas desde un bucket de S3, cree un archivo s3.yml con este contenido:
---
repo_class_name: S3BucketBasedRuleRepo
repo_arguments:
s3_bucket_name: cfn-nag-rules-my-enterprise
prefix: /rules
Para aplicar archivos *Rule.rb en el bucket cfn-nag-rules-my-enterprise con el prefijo /rules (por ejemplo, /rules/MyNewRule.rb), especifique este archivo en la línea de comandos a cfn_nag de la siguiente manera:
cat my_cfn_template.yml | cfn_nag --rule-repository s3.yml
Si las reglas están en más de un bucket, cree múltiples archivos s3*.yml y especifíquelos en el argumento --rule-repository.
Si las credenciales de AWS ambientales tienen permiso para acceder al bucket cfn-nag-rules-enterprise, entonces encontrará todas las reglas
como /rules/*Rule.rb. Si se debe usar un aws_profile particular, agréguelo como una clave en repo_arguments, por ejemplo,
aws_profile: my_aws_profile
Más allá del sistema de archivos, instalaciones de gemas y S3: la nueva arquitectura teóricamente admite el desarrollo de otros "repositorios de reglas" para cargar reglas desde DynamoDb, bases de datos relacionales u otros servicios web.
Para crear nuevas reglas para su propio uso y/o contribución comunitaria, consulte Custom Rule Development para obtener más detalles.
Hay una screencast que demuestra el desarrollo de reglas personalizadas TDD de principio a fin disponible aquí:
https://www.youtube.com/watch?v=JRZct0naFd4&t=1601s
Para ejecutar las specs, debe asegurarse de tener Docker instalado y las dependencias de cfn_nag instaladas mediante
gem install bundle
bundle install
Luego, para ejecutar todas las specs, simplemente ejecute rake test:all.
Para ejecutar las pruebas de extremo a extremo, ejecute rake test:e2e. El script agrupará todas las gemas en el Gemfile, construirá e instalará la gema cfn_nag localmente, instalará las dependencias de las specs y luego ejecutará las pruebas etiquetadas con 'end_to_end'. También descargará plantillas de muestra proporcionadas por Amazon y ejecutará cfn_nag_scan contra ellas, para ver si alguna plantilla conocida como buena causa excepciones dentro de cfn-nag.
Para instalar la rama git actual localmente:
bundle install
scripts/deploy_local.sh
Hay un entorno de desarrollo remoto completo creado y configurado con todas las herramientas y ajustes preconfigurados para facilitar el desarrollo y la creación de reglas. Puede habilitar esto utilizando la funcionalidad de desarrollo remoto de VS Code.
Folder contains a dev container configuration file. Reopen folder to develop in a container, haga clic en el botón Reopen in Container[Dev Container] cfn_nag DevelopmentPuede encontrar más información sobre la configuración de desarrollo remoto de VS Code aquí: VS Code Remote Development.
Para informar un error o solicitar una función, envíe un problema a través del repositorio de GitHub en: https://github.com/stelligent/cfn_nag/issues/new