
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"
}
}
cfn_nag のバージョン 0.4.66 までは、基盤となるモデルはテンプレート内の Fn::If の処理を一切行いませんでした。これは、プロパティが条件付きの値を持つ場合、Fn::If を解析するのはルール次第であることを意味しました。Fn::If はほぼどこにでも現れる可能性があるため、ルール開発者にとってはモグラ叩きの状況が生まれました。せいぜい、ルールロジックは、値がそもそも Hash でない場合に、Hash である値を無視できる程度でした。
この問題に対処するため、cfn_nag のデフォルトの動作は、Fn::If を true の結果で置換するようになりました。これは、デフォルトではルールが false の結果をセキュリティ違反について検査しないことを意味します。
Fn::If の置換はプロパティ値レベルに加えて、Properties のトップレベルにある Fn::If にも同じ動作が適用されます。たとえば:
Resource1:
Type: Foo
Properties: !If
- IsNone
- Description: Up
- Description: DOwn
は次と同じように見えます:
Resource1:
Type: Foo
Properties:
Description: Up
この動作をある程度制御するために、ユーザーは --condition-values-path=<filename/uri> フラグで cfn_nag と cfn_nag_scan の両方にコマンドラインで渡す JSON ファイルに条件値を指定できます。
JSON の形式は、各キー/値ペアが Conditions にマッピングされる辞書です:
{
"Condition1": true,
"Condition2": false
}
SPCM の基礎は、ブログ記事 IAM ポリシードキュメントの複雑度メトリクスに関する思考実験 で説明されています。
cfn_nag のバージョン 0.6.0 以降:
spcm_scan は、(cfn_nag_scan と同様に)CloudFormation テンプレートのディレクトリをスキャンし、SPCM メトリクスを JSON または HTML 形式でレポートとして生成できますcfn_nag_scan --rule-arguments spcm_threshold:100--rule-arguments メカニズムを介して設定用のエンドユーザー値を受け入れるルールを開発できるようになりました。Rule オブジェクトは attr_accessor を宣言するだけでよく、例: attr_accessor :spcm_threshold。cfn_nag が --rule-arguments から値を注入する詳細を処理します。0.5.x のリリースには、カスタムルールの配布と読み込み方法に関するいくつかの大きな変更が含まれています。このリリース以前は、ルールが読み込まれる場所が2つありました: コアの cfn_nag gem 内の lib/cfn-nag/custom_rules ディレクトリと、コマンドラインで指定されたカスタムルールディレクトリです。
カスタムルールの読み込み方法/場所の再設計を余儀なくさせた2つのユースケースがあります。ルールの読み込みメカニズムは、カスタムルールリポジトリを使用してルールを発見できるように一般化されています。
ファイルシステムに散在する多数の「ルールファイル」は、従来のソフトウェア開発の観点からは優れていません。これらのファイルにはバージョンやトレーサビリティがないため、0.5.x では「cfn_nag ルール gem」という概念が導入されました。開発者はカスタムルールを別の gem の一部として開発し、バージョン管理してインストールできます...そして、gem のメタデータに cfn_nag_rules => true が含まれている限り、それらのルールは cfn_nag から参照されます。「cfn-nag-hipaa-rules」のような名前の gem の場合、lib/cfn-nag-hipaa-rules 配下の任意の *.rb が読み込まれます。カスタムルールは、cfn-nag/base_rule にある CfnNag::BaseRule から派生させる必要があります(not cfn-nag/custom-rules/base)。ルールが他の何かから派生する必要がある場合は、true を返すメソッド cfn_nag_rule? を定義することでも、ルールとして読み込ませることができます。
cfn_nag が AWS Lambda で実行されている場合、従来の意味でのファイルシステム(/tmp 以外)は実際にはありません。したがって、Lambda ではコアルールのみが使用できます。カスタムルールをサポートするために、cfn_nag はファイルシステムの代わりに S3 バケットからルールを発見することをサポートしています。
Ruby でのカスタムルールの開発方法についてこれまでに見てきたことはすべて、今でも有効です。
S3 バケットからルールを発見するには、次の内容で s3.yml ファイルを作成します:
---
repo_class_name: S3BucketBasedRuleRepo
repo_arguments:
s3_bucket_name: cfn-nag-rules-my-enterprise
prefix: /rules
バケット cfn-nag-rules-my-enterprise 内のプレフィックス /rules(例: /rules/MyNewRule.rb)を持つ *Rule.rb ファイルを適用するには、このファイルを cfn_nag にコマンドラインで次のように指定します:
cat my_cfn_template.yml | cfn_nag --rule-repository s3.yml
ルールが複数のバケットにある場合は、複数の s3*.yml ファイルを作成し、--rule-repository 引数で指定します。
環境の AWS 認証情報にバケット cfn-nag-rules-enterprise へのアクセス権限がある場合、/rules/*Rule.rb のようなすべてのルールが見つかります。特定の aws_profile を使用する必要がある場合は、repo_arguments の下にキーとして追加します。例: aws_profile: my_aws_profile
ファイルシステム、gem インストール、S3 以外にも、新しいアーキテクチャは理論上、DynamoDb、リレーショナルデータベース、その他の Web サービスからルールを読み込むための他の「ルールリポジトリ」の開発をサポートします。
独自の使用やコミュニティへの貢献のために新しいルールを作成するには、詳細について カスタムルール開発 を参照してください。
TDD によるカスタムルール開発を最初から最後まで示すスクリーンキャストは、こちらでご覧いただけます:
https://www.youtube.com/watch?v=JRZct0naFd4&t=1601s
スペックを実行するには、Docker がインストールされ、cfn_nag の依存関係が次のコマンドでインストールされていることを確認する必要があります:
gem install bundle
bundle install
その後、すべてのスペックを実行するには、rake test:all を実行するだけです。
エンドツーエンドテストを実行するには、rake test:e2e を実行します。このスクリプトは、Gemfile 内のすべての gem をバンドルし、cfn_nag gem をローカルにビルドしてインストールし、spec の依存関係をインストールしてから、'end_to_end' タグが付いたテストを実行します。また、Amazon が提供するサンプルテンプレートを取得し、それらに対して cfn_nag_scan を実行して、既知の正常なテンプレートが cfn-nag 内で例外を引き起こさないかどうかを確認します。
現在の git ブランチをローカルにインストールするには:
bundle install
scripts/deploy_local.sh
ルールの開発と作成を容易にするために、すべてのツールと設定が事前構成された完全なリモート開発環境が作成され、セットアップされています。VS Code のリモート開発機能を使用してこれを有効にできます。
Folder contains a dev container configuration file. Reopen folder to develop in a container が表示されたら、Reopen in Container ボタンをクリックします[Dev Container] cfn_nag Development オプションを使用しますVS Code リモート開発セットアップの詳細は、VS Code リモート開発 をご覧ください。
バグを報告したり機能をリクエストしたりするには、次の GitHub リポジトリ経由で issue を送信してください: https://github.com/stelligent/cfn_nag/issues/new