TerraformのコードをスキャンするSnyk IaC（＋OSSのツール）をGitHub Actionsで試す

本記事の内容は執筆時点（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ポート)を許可

スキャン対象のコードは以下の通りです。少し長いので内容の気になる方は展開して確認してください。

<details><summary>スキャン対象のTerraformのコード</summary><div>

```hcl
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"
}

```

</div></details>

## GitHub Actionsのワークフロー

### 事前準備と注意

Snyk Iacを利用するためにはSnykにサインアップして、Auth Tokenを取得する必要があります。
こちらの手順に従ってサインアップし、Auth Tokenを取得してください。
取得したAuth TokenはGitHub Secretに保存します。今回のシナリオでは`SNYK_TOKEN`という名前で保存しています。

https://docs.snyk.io/tutorials/amazon-web-services/aws-code-suite/snyk-security/create-account-and-obtain-a-token

また、Snyk IaCの無償プランではスキャン回数に上限があります。（月に300回まで）

https://snyk.io/plans/


### ワークフローの作成

次にようなワークフロー定義を用意しました。
それぞれについて簡単に解説します。

```yaml
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によるコードスキャン

https://github.com/snyk/actions/tree/master/iac

こちらのactionsを利用します。先ほど取得したAuth Tokenをパラメータとしてわたします。
なお、Snykでは[monitorコマンド](https://docs.snyk.io/snyk-cli/commands/monitor)を利用することで、GUI上で脆弱性を確認出来たり、継続した監視ができる機能を提供します。本記事では詳細に触れませんが、気になる方は試してみてください。

#### tfsecによるコードスキャン

https://github.com/aquasecurity/tfsec-action

tfsecを利用したactionsはいくつかありますが、tfsec開発元のAqua Securityが公開しているものを利用します。
このactionsはスキャンのみのシンプルなものですが、[Pull Requestにスキャン結果をコメントしてくれるactions](https://github.com/aquasecurity/tfsec-pr-commenter-action)も存在します。

#### terrascanによるコードスキャン

https://github.com/tenable/terrascan-action

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の管理するルールと紐づいています。

https://snyk.io/security-rules/aws/

Terraformのコード上のどのリソースにどのような脆弱性が含まれているか、およびどのパラメータによりそれを制御できるかを確認できるようになっていました。


<details><summary>Snyk IaCの検出結果出力</summary><div>


```
  ✗ 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
```

</div></details>


### tfsecの検出結果

重大度がCriticalの2件の脆弱性が確認できました。セキュリティグループのイングレス、エグレスのパブリックアクセスが有効になっていることがCriticalとして検出されています。Snyk IaCにおいてはそれぞれMedium、Lowの重大度でした。リソースのDescriptionが書かれていない、のようなリソースを管理するうえで必要な設定が不足しているという検出結果が多い印象です。

出された脆弱性はいずれもAqua Securityの管理するルールと紐づいています。

https://aquasecurity.github.io/tfsec/v1.26.0/checks/aws/

Terraformのコード上のどのリソースにどのような脆弱性が含まれているか、およびどのパラメータによりそれを制御できるか、参考URLを確認できるようになっていました。

<details><summary>tfsecの検出結果出力</summary><div>


```
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.
```


</div></details>



### terrascanの検出結果

検出された脆弱性がほかの2ツールよりも少ない4つでした。ほかの2ツールでは検出されていたEC2のストレージの暗号化の脆弱性が検出されていません。
ルールセットはtenableが管理するもので、この3つの中では一番ルールの数が少ないです。

https://runterrascan.io/docs/_print/#pg-07a53d0ea8f57b62a06a9235598d5c55

<details><summary>Terrascan IaCの検出結果出力</summary><div>


```
	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
	-----------------------------------------------------------------------

```

</div></details>

## 考察とまとめ

いずれのツールを用いた場合においても、非常に簡単にGitHub Actionsに組み込めることを確認できました。

「GUIによるリッチな表示の恩恵を受けたい」、「IaC以外のアプリケーションの脆弱性チェックなども含めて統合的に管理したい」といったユースケースにはSnyk、「OSSでTerraformのスキャンを簡易に実施したい」といったユースケースにはtfsecがいいのではと考えています。

最後までお読みいただきありがとうございました。