本記事の内容は執筆時点(2022/6/21)での内容です。今後のアップデートにより結果が変わる可能性があります。
Terraformのコード(HCL)をスキャンすることができるSnyk IaCをGitHub Actionsから利用する方法を紹介します。またSnyk IaCのほかにもTerraform用のOSSスキャンツールであるtfsec、terrascanも試してみて、それぞれのスキャン結果についてみていこうと思います。
スキャン対象のコード
本記事においては、TerraformでAWSのサービスをプロビジョニングするシナリオで進めていきます。
具体的には以下のような構成でAWSを構築します。
- 1つのVPCと1つのパブリックサブネット
- パブリックサブネットにEC2を配置
- EC2にセキュリティグループを割り当て、インターネットからのssh(22ポート)を許可
スキャン対象のコードは以下の通りです。少し長いので内容の気になる方は展開して確認してください。
スキャン対象のTerraformのコード
resource "aws_vpc" "example" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
}
resource "aws_subnet" "example" {
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1a"
vpc_id = aws_vpc.example.id
map_public_ip_on_launch = true
}
resource "aws_internet_gateway" "example" {
vpc_id = aws_vpc.example.id
}
resource "aws_route_table" "example" {
vpc_id = aws_vpc.example.id
}
resource "aws_route" "example" {
gateway_id = aws_internet_gateway.example.id
route_table_id = aws_route_table.example.id
destination_cidr_block = "0.0.0.0/0"
}
resource "aws_route_table_association" "example" {
subnet_id = aws_subnet.example.id
route_table_id = aws_route_table.example.id
}
resource "aws_security_group" "example" {
vpc_id = aws_vpc.example.id
name = "example"
}
resource "aws_security_group_rule" "in_ssh" {
security_group_id = aws_security_group.example.id
type = "ingress"
cidr_blocks = ["0.0.0.0/0"]
from_port = 22
to_port = 22
protocol = "tcp"
}
resource "aws_security_group_rule" "out_all" {
security_group_id = aws_security_group.example.id
type = "egress"
cidr_blocks = ["0.0.0.0/0"]
from_port = 0
to_port = 0
protocol = "-1"
}
data "aws_ssm_parameter" "amzn2_ami" {
name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
}
resource "aws_instance" "example" {
ami = data.aws_ssm_parameter.amzn2_ami.value
vpc_security_group_ids = [aws_security_group.example.id]
subnet_id = aws_subnet.example.id
instance_type = "t2.micro"
}
GitHub Actionsのワークフロー
事前準備と注意
Snyk Iacを利用するためにはSnykにサインアップして、Auth Tokenを取得する必要があります。
こちらの手順に従ってサインアップし、Auth Tokenを取得してください。
取得したAuth TokenはGitHub Secretに保存します。今回のシナリオではSNYK_TOKEN
という名前で保存しています。
また、Snyk IaCの無償プランではスキャン回数に上限があります。(月に300回まで)
ワークフローの作成
次にようなワークフロー定義を用意しました。
それぞれについて簡単に解説します。
name: terraform-sast
on:
pull_request:
jobs:
terraform-sast:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v2
- name: Run Snyk to check configuration files for security issues
uses: snyk/actions/iac@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
file: <terraformのコードが存在するディレクトリ>
- name: tfsec
uses: aquasecurity/tfsec-action@v1.0.2
with:
soft_fail: true
working_directory: <terraformのコードが存在するディレクトリ>
- name: Run Terrascan
uses: tenable/terrascan-action@main
with:
iac_type: 'terraform'
iac_version: 'v14'
policy_type: 'aws'
only_warn: true
iac_dir: <terraformのコードが存在するディレクトリ>
Snykによるコードスキャン
こちらのactionsを利用します。先ほど取得したAuth Tokenをパラメータとしてわたします。
なお、Snykではmonitorコマンドを利用することで、GUI上で脆弱性を確認出来たり、継続した監視ができる機能を提供します。本記事では詳細に触れませんが、気になる方は試してみてください。
tfsecによるコードスキャン
tfsecを利用したactionsはいくつかありますが、tfsec開発元のAqua Securityが公開しているものを利用します。
このactionsはスキャンのみのシンプルなものですが、Pull Requestにスキャン結果をコメントしてくれるactionsも存在します。
terrascanによるコードスキャン
tenableが開発するterrascanですが、こちらも公式のactionsを利用します。terrascanはTerraformに加えて、kubernetesのマニフェストやkustomize、helmなどもスキャンできます。パラメータからTerraform、AWSを利用することを指定します。
GitHub Actionsの実行結果
GitHub Actionsの実行結果を見ていきます。検出された脆弱性は以下の通りです。
ツール | Critical | High | Medium | Low |
---|---|---|---|---|
Snyk IaC | 0 | 0 | 3 | 4 |
tfsec | 2 | 2 | 0 | 3 |
terrascan | 0 | 2 | 1 | 1 |
個別の中身について確認していきます。
Snyk IaCの検出結果
検出された脆弱性の重大度はいずれもMedium以下でした。セキュリティグループでパブリックアクセスが有効になっている、EC2の利用するストレージが暗号化されていない、といった脆弱性を確認できます。設定を変更することでクラウドの堅牢性が向上する(制約が増える)ものが多い印象です。
検出された脆弱性はいずれもSnykの管理するルールと紐づいています。
Terraformのコード上のどのリソースにどのような脆弱性が含まれているか、およびどのパラメータによりそれを制御できるかを確認できるようになっていました。
Snyk IaCの検出結果出力
✗ AWS Security Group Rule allows public access [Medium Severity] [SNYK-CC-TF-37] in VPC
introduced by resource > aws_security_group_rule[in_ssh] > cidr_blocks
✗ Non-Encrypted root block device [Medium Severity] [SNYK-CC-TF-53] in EC2
introduced by resource > aws_instance[example] > root_block_device > encrypted
✗ Rule allows open egress [Low Severity] [SNYK-CC-TF-72] in VPC
introduced by resource > aws_security_group_rule[out_all]
✗ EC2 instance accepts IMDSv1 [Low Severity] [SNYK-CC-TF-130] in EC2
introduced by resource > aws_instance[example] > metadata_options
✗ Missing description [Low Severity] [SNYK-CC-TF-56] in VPC
introduced by resource > aws_security_group[example] > description
✗ EC2 API termination protection is not enabled [Low Severity] [SNYK-CC-AWS-426] in EC2
introduced by resource > aws_instance[example] > disable_api_termination
✗ Public IPs are automatically mapped to instances [Low Severity] [SNYK-CC-AWS-427] in VPC
introduced by resource > aws_subnet[example] > map_public_ip_on_launch
tfsecの検出結果
重大度がCriticalの2件の脆弱性が確認できました。セキュリティグループのイングレス、エグレスのパブリックアクセスが有効になっていることがCriticalとして検出されています。Snyk IaCにおいてはそれぞれMedium、Lowの重大度でした。リソースのDescriptionが書かれていない、のようなリソースを管理するうえで必要な設定が不足しているという検出結果が多い印象です。
出された脆弱性はいずれもAqua Securityの管理するルールと紐づいています。
Terraformのコード上のどのリソースにどのような脆弱性が含まれているか、およびどのパラメータによりそれを制御できるか、参考URLを確認できるようになっていました。
tfsecの検出結果出力
Result #1CRITICALSecurity group rule allows ingress from public internet.
────────────────────────────────────────────────────────────────────────────────
main.tf:43
────────────────────────────────────────────────────────────────────────────────
40 resource"aws_security_group_rule""in_ssh" {
41 security_group_id = aws_security_group.example.id
42 type = "ingress"
43 [cidr_blocks = ["0.0.0.0/0"]
44 from_port = 22
45 to_port = 22
46 protocol = "tcp"
47 }
────────────────────────────────────────────────────────────────────────────────
ID aws-vpc-no-public-ingress-sgr
Impact Your port exposed to the internet
Resolution Set a more restrictive cidr range
More Information
-https://aquasecurity.github.io/tfsec/v1.26.0/checks/aws/vpc/no-public-ingress-sgr/
-https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule#cidr_blocks
────────────────────────────────────────────────────────────────────────────────
Result #2CRITICALSecurity group rule allows egress to multiple public internet addresses.
────────────────────────────────────────────────────────────────────────────────
main.tf:52
────────────────────────────────────────────────────────────────────────────────
49 resource"aws_security_group_rule""out_all" {
50 security_group_id = aws_security_group.example.id
51 type = "egress"
52 [cidr_blocks = ["0.0.0.0/0"]
53 from_port = 0
54 to_port = 0
55 protocol = "-1"
56 }
────────────────────────────────────────────────────────────────────────────────
ID aws-vpc-no-public-egress-sgr
Impact Your port is egressing data to the internet
Resolution Set a more restrictive cidr range
More Information
-https://aquasecurity.github.io/tfsec/v1.26.0/checks/aws/vpc/no-public-egress-sgr/
-https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group
────────────────────────────────────────────────────────────────────────────────
Result #3HIGHInstance does not require IMDS access to require a token
────────────────────────────────────────────────────────────────────────────────
main.tf:62-71
────────────────────────────────────────────────────────────────────────────────
62 resource"aws_instance""example" {
63 ami = data.aws_ssm_parameter.amzn2_ami.value
64 vpc_security_group_ids = [aws_security_group.example.id]
65 subnet_id = aws_subnet.example.id
66 instance_type = "t2.micro"
67
68 tags = {
69 Name = "example"
70 }
71 }
────────────────────────────────────────────────────────────────────────────────
ID aws-ec2-enforce-http-token-imds
Impact Instance metadata service can be interacted with freely
Resolution Enable HTTP token requirement for IMDS
More Information
-https://aquasecurity.github.io/tfsec/v1.26.0/checks/aws/ec2/enforce-http-token-imds/
-https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#metadata-options
────────────────────────────────────────────────────────────────────────────────
Result #4HIGHRoot block device is not encrypted.
────────────────────────────────────────────────────────────────────────────────
main.tf:62-71
────────────────────────────────────────────────────────────────────────────────
62 resource"aws_instance""example" {
63 ami = data.aws_ssm_parameter.amzn2_ami.value
64 vpc_security_group_ids = [aws_security_group.example.id]
65 subnet_id = aws_subnet.example.id
66 instance_type = "t2.micro"
67
68 tags = {
69 Name = "example"
70 }
71 }
────────────────────────────────────────────────────────────────────────────────
ID aws-ec2-enable-at-rest-encryption
Impact The block device could be compromised and read from
Resolution Turn on encryption for all block devices
More Information
-https://aquasecurity.github.io/tfsec/v1.26.0/checks/aws/ec2/enable-at-rest-encryption/
-https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#ebs-ephemeral-and-root-block-devices
────────────────────────────────────────────────────────────────────────────────
Result #5LOWSecurity group explicitly uses the default description.
────────────────────────────────────────────────────────────────────────────────
main.tf:35-38
────────────────────────────────────────────────────────────────────────────────
35 resource"aws_security_group""example" {
36 vpc_id = aws_vpc.example.id
37 name = "example"
38 }
────────────────────────────────────────────────────────────────────────────────
ID aws-vpc-add-description-to-security-group
Impact Descriptions provide context for the firewall rule reasons
Resolution Add descriptions for all security groups
More Information
-https://aquasecurity.github.io/tfsec/v1.26.0/checks/aws/vpc/add-description-to-security-group/
-https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group
-https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule
────────────────────────────────────────────────────────────────────────────────
Result #6LOWSecurity group rule does not have a description.
────────────────────────────────────────────────────────────────────────────────
main.tf:40-47
────────────────────────────────────────────────────────────────────────────────
40 resource"aws_security_group_rule""in_ssh" {
41 security_group_id = aws_security_group.example.id
42 type = "ingress"
43 cidr_blocks = ["0.0.0.0/0"]
44 from_port = 22
45 to_port = 22
46 protocol = "tcp"
47 }
────────────────────────────────────────────────────────────────────────────────
ID aws-vpc-add-description-to-security-group-rule
Impact Descriptions provide context for the firewall rule reasons
Resolution Add descriptions for all security groups rules
More Information
-https://aquasecurity.github.io/tfsec/v1.26.0/checks/aws/vpc/add-description-to-security-group-rule/
-https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group
-https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule
────────────────────────────────────────────────────────────────────────────────
Result #7LOWSecurity group rule does not have a description.
────────────────────────────────────────────────────────────────────────────────
main.tf:49-56
────────────────────────────────────────────────────────────────────────────────
49 resource"aws_security_group_rule""out_all" {
50 security_group_id = aws_security_group.example.id
51 type = "egress"
52 cidr_blocks = ["0.0.0.0/0"]
53 from_port = 0
54 to_port = 0
55 protocol = "-1"
56 }
────────────────────────────────────────────────────────────────────────────────
ID aws-vpc-add-description-to-security-group-rule
Impact Descriptions provide context for the firewall rule reasons
Resolution Add descriptions for all security groups rules
More Information
-https://aquasecurity.github.io/tfsec/v1.26.0/checks/aws/vpc/add-description-to-security-group-rule/
-https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group
-https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule
────────────────────────────────────────────────────────────────────────────────
timings
──────────────────────────────────────────
disk i/o 25.6µs
parsing 1.462609ms
adaptation 138.001µs
checks 41.260244ms
total 42.886454ms
counts
──────────────────────────────────────────
modules downloaded 0
modules processed 1
blocks processed 11
files read 1
results
──────────────────────────────────────────
passed 1
ignored 0
critical 2
high 2
medium 0
low 3
1 passed, 7 potential problem(s) detected.
terrascanの検出結果
検出された脆弱性がほかの2ツールよりも少ない4つでした。ほかの2ツールでは検出されていたEC2のストレージの暗号化の脆弱性が検出されていません。
ルールセットはtenableが管理するもので、この3つの中では一番ルールの数が少ないです。
Terrascan IaCの検出結果出力
Description : EC2 instances should disable IMDS or require IMDSv2 as this can be related to the weaponization phase of kill chain
File : main.tf
Module Name : root
Plan Root : ./
Line : 62
Severity : MEDIUM
-----------------------------------------------------------------------
Description : Security Groups - Unrestricted Specific Ports - (SSH,22)
File : main.tf
Module Name : root
Plan Root : ./
Line : 40
Severity : HIGH
-----------------------------------------------------------------------
Description : Ensure that detailed monitoring is enabled for EC2 instances.
File : main.tf
Module Name : root
Plan Root : ./
Line : 62
Severity : HIGH
-----------------------------------------------------------------------
Description : Ensure VPC flow logging is enabled in all VPCs
File : main.tf
Module Name : root
Plan Root : ./
Line : 1
Severity : LOW
-----------------------------------------------------------------------
考察とまとめ
いずれのツールを用いた場合においても、非常に簡単にGitHub Actionsに組み込めることを確認できました。
「GUIによるリッチな表示の恩恵を受けたい」、「IaC以外のアプリケーションの脆弱性チェックなども含めて統合的に管理したい」といったユースケースにはSnyk、「OSSでTerraformのスキャンを簡易に実施したい」といったユースケースにはtfsecがいいのではと考えています。
最後までお読みいただきありがとうございました。