1年以上前のリリースになりますが、AWSではInSpecをSSMドキュメントから使用することが可能になっています。
AWS Systems Manager に InSpec by Chef のサポートを追加
おそらく、当時にInspec2.0がリリースされ、クラウドリソースに対するテスト機能がサポートされ始めたことが背景にありそうです。
Announcing InSpec 2.0
InSpecで何ができるのか、とりあえず動かしたい派の人にも合ってそう、ということでAmazon SSMベースのInspecをご紹介です。
+InSpec概要
+まずは動作確認
+SSMによるInSpec実行の仕組み
+AWSリソースに対するInspecテスト
InSpec概要
Chef社がOSSとして開発しているテスト向けフレームワークです。 オーケストレーションツールのChefや、テスト自動化ツールのServerspecとは、互換性があります。
InSpec is a run-time framework and rule language used to specify compliance, security, and policy requirements. It includes a collection of resources that help you write auditing controls quickly and easily. The syntax used by both open source and |chef compliance| auditing is the same. The open source |InSpec resource| framework is compatible with |chef compliance|.
後発のInSpecのメリットは、AWSリソースに対するメソッドがサポートされてる点や文法が短縮されている点など、テストコードの簡略化ができることです。
サポート対象のクラウドとして、AWS、Azure、GCP、がInSpecリファレンスに載っています。
InSpec Resources Reference
まずは動作確認
・テスト対象:SSMエージェントをインストール済みのEC2(CentOS 7)
・SSM RunCommandから「AWS-RunInspecChecks」ドキュメントを実行する
・テストコード:DevSecコミュニティがGitHubで公開しているテストコードを使用。
今回はそのファイル一式をS3に保存してRunCommand実行時に参照する。
SSM RunCommandからInspecを動かしてみます。
S3バケット内は、以下の構成で始めます。
aws s3 ls s3://$My_Bucket/DevSec_practice_linux/ PRE controls/ ---テストコード.今回はos_spec.rb, package_spec.rb ,sysctl_spec.rbの3つ。 PRE libraries/ ---今回は未使用テストコードで使用するユーザ定義のメソッドを定義可能. 303 inspec.yml ---テスト概要.テスト環境の定義が重要。あとはcopyright等.
SSM RunCommand実行時、InSpecファイルがS3にある場合は、次のようにソース指定します。
{ "path": "https://s3.amazonaws.com/test-tsakurai/DevSec_practice_linux" }
そのSSM RunCommandをAWS CLIで書くと、次の通りです。
aws ssm send-command --document-name "AWS-RunInspecChecks" \ --targets "Key=instanceids,Values=i-xxxxxxxxxxxxxxxxx" \ --parameters '{"sourceType":["S3"],"sourceInfo":["{\"path\":\"https://s3.amazonaws.com/$My_Bucket/DevSec_practice_linux\"}\n"]}' \ --output-s3-bucket-name "$My_Bucket" --output-s3-key-prefix "ssm" --region ap-northeast-1
SSM RunCommandによって正常にInSpec実行が完了すると、AWSコンソールでは次のような画面になります。
AWSコンソールでも分かる通り、SSM RunCommand実行時のログは3種類、出力されます。 詳細は後続でふれていきます。
・ステップ1(downloadContent)
Content downloaded to /var/lib/amazon/ssm/i-xxxxxxxxxxxxxxxxx/document/orchestration/(SSM実行時に生成されるコマンドID)/downloads/
・ステップ2(runInSpecLinux)
[stdout]
Using existing Chef Development Kit
Executing InSpec tests
Completed InSpec checks and put 81 compliant (81 critical, 0 high, 0 low) and 44 non-compliant (44 critical, 0 high, 0 low) items
[stderr] ※SSM RunCommand実行時の標準エラー出力.
・ステップ3(runInSpecWindows)
Step execution skipped due to incompatible platform. Step name: runInSpecWindows
※Linuxサーバに対して実行しているため、Windows用の処理は自動的にスキップ.
「AWS-RunInspecChecks」を使用するメリットは、実行結果がAWSコンソールから確認可能な状態で保存される点でもあります。
SSMによるInSpec実行の仕組み
検証して分かりましたが、現時点では「AWS-RunInspecChecks」ドキュメントは、テスト対象のサーバOSにおけるテストしかサポートしていません。残念ながら。。 そのため、冒頭でふれたような"AWSリソースに対するInSpecテスト"をSSM経由で実行したい場合、任意のOSスクリプトを準備する必要があります。
"AWSリソースに対するInSpecテスト"に必要なものを確認するため、「AWS-RunInspecChecks」の処理内容をサーバ側から見ていきます。 先述のログにもありますが、「AWS-RunInspecChecks」実行によって、対象サーバには色々とファイルがダウンロードされています。
ll /var/lib/amazon/ssm/i-xxxxxxxxxxxxxxxxx/document/orchestration/{コマンドID}/downloads/ total 20 -rw-r--r-- 1 root root 34 xxx.etag drw------- 2 root root 228 controls -rw-r--r-- 1 root root 36 inspec.lock -rwx------ 1 root root 303 inspec.yml drw------- 2 root root 158 libraries -rw-r--r-- 1 root root 3953 report_compliance -rw-r--r-- 1 root root 2346 run_inspec.sh
RunCommand実行時に参照したS3には無かったファイルがダウンロードされています。
・run_inspec.sh
bashスクリプト。RunCommand実行ログと照合すると、「AWS-RunInspecChecks」は本スクリプトを実行しているだけのようです。「CefDKインストール」、「InSpecテスト実行」、「report_complianceスクリプト起動」、「CefDKアンインストール」、が主な処理内容です。
・report_compliance
rubyスクリプト。run_inspec.shから起動され、InSpecテスト結果をSSMに保存する処理(put_compliance_itemsのAPI)を担当しています。本スクリプト内では、テストコード内のimpact値に基づき、テスト結果を下記3つの重大度に分類しています。
low = 0.0...0.4 high = 0.4...0.7 critical = 0.7..1.0 (その他範囲の値もcritical)
run_inspec.shがあることで、テスト対象サーバのChefDK有無を意識せずにInSpecテストを実行できます。なお、ChefDKがインストール済みであればそちらを使用するためインストールはスキップされます。
AWSリソースに対するInspecテスト
今回の目的としては、AWSリソースに対するテストファイルを準備するほか、run_inspec.shをSSMドキュメント「AWS-RunShellScript」から使用することで、期待する動作に近づきました。 まず、run_inspec.shを2行だけ修正します。
# vi run_inspec.sh ##修正部分のみ記載 < export AWS_REGION=ap-northeast-1 > #inspec exec . --reporter json | ruby ./report_compliance < inspec exec . -t aws:// --reporter json | ruby ./report_compliance
上記のように、AWSリソースのテストでは、InSpec実行時に「-t」オプションを明示する必要があります。「AWS_REGION」環境変数は、テスト結果をSSMに保存するAWS API(report_compliance)を実行するため必要です。
InSpecテストファイルは任意のディレクトリに保存すればOKです。 次に、テストデータの作成です。
##InSpec初期ファイル作成 # inspec init profile subnet-test qqqqqqqqqqqqqqqqqqqqqqqqqqq InSpec Code Generator qqqqqqqqqqqqqqqqqqqqqqqqqqq Creating new profile at /tools/subnet-test ~ Creating file README.md ~ Creating directory controls ~ Creating file controls/example.rb ~ Creating file inspec.yml ~ Creating directory libraries # cd subnet-test/ # ll total 8 drwxr-xr-x. 2 root root 24 May 27 08:50 controls -rw-r--r--. 1 root root 217 May 27 08:50 inspec.yml drwxr-xr-x. 2 root root 6 May 27 08:50 libraries -rw-r--r--. 1 root root 86 May 27 08:50 README.md # vi inspec.yml ##修正部分のみ記載 > platform: os < platform: aws
「report_compliance」と修正済みの「run_inspec.sh」は、このinspec.ymlと同じフォルダに置きます。ちなみにこれら2つのスクリプトのコメントには、Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. との記載はありますが、コピーや変更、配布など、無償で許可する点も併記されていました。 ありがたく流用します。
今回は、VPCサブネットに簡単なテストを実行してみます。
・VPC内に指定したCIDRのサブネットが存在するか
・全てのサブネットは/24で作成されているか
・サブネットのAZはaゾーンまたはcゾーンを使用しているか
下記内容を.rbファイルとして、controlsディレクトリ内に保存します。
control 'subnet-01' do impact 1.0 title 'check the subnets' desc 'check the subnets' LIST=["172.16.0.0/24", "172.16.1.0/24", "172.16.2.0/24"] describe aws_subnets.where( vpc_id: 'vpc-xxxxxxxx' ) do its('cidr_blocks') { should include '172.16.0.0/24' } its('cidr_blocks') { should include '172.16.1.0/24' } its('cidr_blocks') { should include '172.16.2.0/24' } end end control 'subnet-02' do impact 1.0 title 'check subnets cidr' desc 'all the cidr is /24 size' aws_subnets.where( vpc_id: 'vpc-xxxxxxxx' ).subnet_ids.each do |subnet_id| describe aws_subnet(subnet_id) do its('cidr_block') { should match(%r{.*\/24}) } end end end control 'subnet-03' do impact 1.0 title 'check subnets AZ' desc 'only AZ-a or AZ-c is permitted' aws_subnets.where( vpc_id: 'vpc-xxxxxxxx' ).subnet_ids.each do |subnet_id| describe aws_subnet(subnet_id) do its('availability_zone') { should match(%r{ap-northeast-1a|ap-northeast-1c}) } end end end
では、SSM RunCommandから「AWS-RunShellScript」ドキュメントを実行します。
作業ディレクトリにinspec init
でテストファイル一式を作成したディレクトリパス、実行コマンドに./run_inspec.sh
、をRunCommandのパラメータとして指定します。
そのSSM RunCommandをAWS CLIで書くと、次の通りです。
aws ssm send-command --document-name "AWS-RunShellScript" \ --targets "Key=instanceids,Values=i-xxxxxxxxxxxxxxxxx" \ --parameters '{"workingDirectory":["/tools/subnet-test"],"executionTimeout":["3600"],"commands":["./run_inspec.sh"]}' \ --output-s3-bucket-name "$My_Bucket" --output-s3-key-prefix "ssm" --region ap-northeast-1
実行結果をAWSコンソールから確認すると、以下のようになります。 1つだけdゾーンを使用しているサブネットがあったため、Non-Compliantになっていることが分かります。
最後に
テスト自動化まで記載する予定でしたが、思った以上に準備が長くなったため、持ち越します。
基本的には、CloudWatch EventsトリガーのLambda関数で「send-command」APIからInSpecテストを実行し、テスト結果は「list-compliance-items」で取得する、という流れでAWS環境のテストが定期的に自動実行できます。
InSpecはOSSながらAWSをサポートしていますが、インフラテストをコーディングする際、タグ設定を参照できないのはつらい部分です。
※inSpecにおけるAWSリソースに対するクラス実装はこちら
タグ設定をテスト対象にするというより、タグを条件分岐やループの処理で使えると、コーディングも楽になりつつテスト範囲が拡がりそうです。
DIYの精神で自分がやれ、という話はさておき、読者の皆々様がInSpecコミッタ―になる一助になれば幸いです。