
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 が現在サポートしているすべてのルールの一覧を表示するには、それらを stdout にダンプするコマンドラインユーティリティがあります:
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
cfn-nag は「プロファイル」の概念をサポートします。これは、適用するルールの許可リストです。プロファイルはテキストファイルで、1行に1つのルール識別子を含む必要があります。--profile-path コマンドライン引数で指定すると、cfn-nag はそれらの特定のルールからの違反のみを返します。
「プロファイル」を作成する動機は、開発者によって注目するルールが異なる可能性があることです。たとえば、「infrastructure_developer」は IAM ルールを気にするかもしれませんが、「app_developer」は IAM リソースを作成できない可能性があり、そのためこれらのルールを気にしないかもしれません。
プロファイルの例:
F1
F2
F27
W3
W5
拒否リストは基本的にプロファイルの反対です。決して適用しないルールのリストです。--deny-list-path コマンドライン引数で指定すると、cfn-nag はファイル内で指定されたそれらの特定のルールからの違反を決して返しません。
ルールが両方に指定された場合、拒否リストがプロファイルより優先され、そのルールは適用されません。
形式は次のとおりです。重要な2つのフィールドは 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 はフラグ付けされません。
パラメータ値をチェックできるようにするには、コマンドラインで cfn_nag と cfn_nag_scan の両方に --parameter-values-path=<filename/uri> フラグで渡す 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"
}
}