
CloudFormation 템플릿용 린팅 도구
cfn-nag 도구는 CloudFormation 템플릿에서 안전하지 않은 인프라를 나타낼 수 있는 패턴을 찾습니다. 대략적으로 다음 항목을 찾습니다:
도구에 대한 자세한 배경 정보는 Stelligent 블로그의 다음 게시물을 참조하십시오:
CloudFormation 템플릿 개발 과정 초기에 보안 문제 찾기 - "cfn-nag"
Ruby >= 2.5.x가 설치되어 있다고 가정하면 설치 방법은 간단합니다:
gem install cfn-nag
MacOS 또는 Linux에서는 brew를 사용하여 설치할 수도 있습니다:
brew install ruby brew-gem
brew gem install cfn-nag
cfn_nag를 CodePipeline에서 액션으로 실행하려면 AWS Serverless Application Repository를 통해 배포할 수 있습니다.
실행하려면:
cfn_nag_scan --input-path <path to cloudformation json>
경로는 디렉터리 또는 특정 템플릿일 수 있습니다. 디렉터리인 경우 모든 .json, .template, .yml 및 .yaml 파일이 하위 디렉터리로 재귀적으로 처리됩니다.
기본 출력 형식은 자유 형식 텍스트이지만, --output-format json 플래그로 JSON 출력을 선택할 수 있습니다.
선택적으로 --debug 플래그는 규칙 로딩 내부 정보를 출력합니다.
지원되는 스위치의 전체 목록은 --help로 실행하십시오.
cfn-nag가 현재 지원하는 모든 규칙 목록을 보려면 표준 출력으로 덤프하는 명령줄 유틸리티가 있습니다:
cfn_nag_rules
편의를 위해 Dockerfile이 제공됩니다. DockerHub에 stelligent/cfn_nag로 게시되어 있습니다.
https://hub.docker.com/r/stelligent/cfn_nag
로컬에서도 빌드할 수 있습니다.
docker build -t stelligent/cfn_nag .
템플릿이 포함된 로컬 디렉터리를 Docker 컨테이너에 마운트한 다음 컨테이너에서 cfn_nag를 호출할 수 있습니다. 이 예제는 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은 지속적 통합 파이프라인에서 코드를 평가하기 위해 GitHub Workflow의 일부로 실행할 수 있습니다.
GitHub Workflow 파일에서 cfn_nag Action을 사용하는 단계를 만드십시오:
- name: Simple test
uses: stelligent/cfn_nag@master
with:
input_path: tests
GitHub Action에 대한 자세한 정보는 여기에서 확인할 수 있습니다.
cfn-nag는 적용할 규칙의 허용 목록인 "프로필" 개념을 지원합니다. 프로필은 줄마다 규칙 식별자가 포함된 텍스트 파일입니다. --profile-path 명령줄 인수로 지정하면 cfn-nag는 해당 특정 규칙의 위반만 반환합니다.
"프로필"을 만든 동기는 개발자마다 중요하게 생각하는 규칙이 다를 수 있기 때문입니다. 예를 들어, "infrastructure_developer"는 IAM 규칙에 관심이 있을 수 있지만, "app_developer"는 IAM 리소스를 생성할 수조차 없어 그런 규칙에 관심이 없을 수 있습니다.
다음은 프로필 예시입니다:
F1
F2
F27
W3
W5
거부 목록은 기본적으로 프로필의 반대입니다: 절대 적용하지 않을 규칙 목록입니다. --deny-list-path 명령줄 인수로 지정하면 cfn-nag는 파일에 지정된 해당 규칙의 위반을 절대 반환하지 않습니다.
규칙이 둘 다에 지정된 경우 거부 목록이 프로필보다 우선하며 규칙은 적용되지 않습니다.
형식은 다음과 같습니다. 중요한 필드는 RulesToSuppress와 항목별 id 두 가지뿐입니다. reason은 cfn-nag가 해석하지 않지만, 규칙을 절대 적용해서는 안 되는 이유를 정당화하고 문서화하는 것이 좋습니다.
RulesToSuppress:
- id: W3
reason: W3 is something we never care about at enterprise X
억제하려는 규칙이 있는 경우, 영향을 받는 리소스에 cfn_nag Metadata 키를 추가하여 cfn-nag가 해당 규칙에 대해 실패나 경고를 발생시키지 않도록 할 수 있습니다.
예를 들어, 인터넷에서 인바운드 연결에 열려 있는 공용 ELB를 다음과 같은 리소스로 설정하는 경우:
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는 다음과 같은 경고를 발생시킵니다:
$ 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
메타데이터를 추가하면 이러한 경고를 억제할 수 있습니다:
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
CloudFormation 템플릿 파라미터는 값이 배포 시점에 지정되므로 정적 분석에 문제가 될 수 있습니다. 즉, 정적 분석이 수행될 때 값은 사용할 수 없으며, 정적 분석은 앞에 있는 "코드"만 볼 수 있습니다. 따라서 cidr이 파라미터화되고 0.0.0.0/0이 배포 시 전달되는 경우 보안 그룹 인그레스 규칙 0.0.0.0/0은 플래그로 표시되지 않습니다.
파라미터 값을 확인할 수 있도록 사용자는 --parameter-values-path=<filename/uri> 플래그를 사용하여 명령줄에서 cfn_nag와 cfn_nag_scan 모두에 전달되는 JSON 파일에 파라미터 값을 지정할 수 있습니다.
JSON 형식은 단일 키 "Parameters"이며, 그 값은 각 키/값 쌍이 Parameters에 매핑되는 사전입니다:
{
"Parameters": {
"Cidr": "0.0.0.0/0"
}
}
이렇게 하면 다음 파라미터에 "0.0.0.0/0"이 제공됩니다:
Parameters:
Cidr:
Type: String
주의: JSON에 추가 파라미터가 있으면 자동으로 무시됩니다(cfn_nag_scan이 모든 템플릿에 동일한 JSON을 적용할 수 있도록 하기 위함).
JSON이 잘못되었거나 위 사양을 충족하지 않으면 FATAL 위반으로 구문 분석이 실패합니다.
0.5.55 이전에는 Fn::FindInMap 호출이 사실상 무시되었습니다. 기본 모델이 그대로 두었기 때문에 규칙에 Hash 값으로 나타났습니다. 예: { "Fn::FindInMap" => [map1, key1, key2]}
0.5.55부터 모델은 FindInMap 호출에 대한 값을 계산하여 규칙에 제시하려고 시도합니다. 이 평가는 다음 키를 지원합니다:
평가 로직이 키 값을 알아낼 수 없으면 전체 표현식에 대해 Hash를 반환하는 이전 동작으로 기본 설정됩니다.
0.5.55 이전에는 AWS 의사 함수 호출도 사실상 무시되었습니다. 기본 모델이 그대로 두었기 때문에 규칙에 Hash 값으로 나타났습니다. 예: {"Ref"=>"AWS::Region"}. 일반적인 사용 사례는 지역별로 매핑을 구성하는 것이므로 의사 함수 평가는 맵 평가를 더 잘 지원하는 데 중요합니다.
0.5.55부터 모델은 기본값과 함께 다음 AWS 의사 함수를 규칙에 제시합니다:
'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'
추가로, 최종 사용자는 기존 파라미터 치환 메커니즘을 통해 제공되는 값을 재정의할 수 있습니다. 예:
{
"Parameters": {
"AWS::Region": "eu-west-1"
}
}