Terraformのテンプレート開発環境を構築する
Terraform(以下TF)のツールとして以下のツールをインストールした。
tfenv: Terraformバージョン管理ツール
tflint: 静的解析ツール
terraform-docs: モジュールドキュメント生成ツール
tfsec: 静的セキュリティ解析ツール
checkov: 静的コード分析ツール
他のツールは shuaibiyy/awesome-terraform を参照
使用したDockerfileは以下の通り
FROM debian:bullseye-20230411-slim
ARG DEFAULT_TERRAFROM_VERSION=1.4.4
ARG TFENV_VERSION=v3.0.0
ARG TFLINT_VERSION=v0.45.0
ARG TFDOCS_VERSION=v0.16.0
ARG TFSEC_VERSION=v1.28.1
ARG CHECKOV_VERSION=2.3.160
RUN apt-get update -y && \
# 日本語化
apt-get install -y locales && \
sed -i -E 's/# (ja_JP.UTF-8)/\1/' /etc/locale.gen && \
locale-gen && \
apt-get install -y curl git unzip
# tfenvインストール
ENV TFENV_HOME=/usr/local/tfenv
ENV TFENV_TAR_URL=https://github.com/tfutils/tfenv/archive/refs/tags/${TFENV_VERSION}.tar.gz
ENV PATH=$PATH:${TFENV_HOME}/bin
RUN mkdir -p "${TFENV_HOME}" && \
curl -fSL "${TFENV_TAR_URL}" | tar zxvf - --strip-components=1 -C "${TFENV_HOME}"
# デフォルトのterraformインストール
RUN tfenv install "${DEFAULT_TERRAFROM_VERSION}"
# tflintインストール
ENV TFLINT_VERSION="${TFLINT_VERSION}"
RUN curl -fSL https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash
# tfdocインストール
ENV TFDOCS_HOME=/usr/local/tfdocs
ENV PATH=$PATH:${TFDOCS_HOME}/bin
ENV TFDOCS_TAR_URL=https://github.com/terraform-docs/terraform-docs/releases/download/${TFDOCS_VERSION}/terraform-docs-${TFDOCS_VERSION}-Linux-amd64.tar.gz
RUN mkdir -p "${TFDOCS_HOME}/bin" && \
curl -fSL "${TFDOCS_TAR_URL}" | tar zxvf - -C "${TFDOCS_HOME}" && \
chmod +x "${TFDOCS_HOME}/terraform-docs" && \
mv "${TFDOCS_HOME}/terraform-docs" "${TFDOCS_HOME}/bin/"
# tfsecインストール
ENV TFSEC_VERSION="${TFSEC_VERSION}"
RUN curl -fSL https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash
# checkovインストール
RUN apt-get install -y python3 && \
apt-get install -y python3-pip && \
python3 -m pip install -U checkov==2.2.5
公式のVSCode用の拡張 Marketplace/HashiCorp Terraform も用意されている。評判は悪そう。。。
UI: HashiCorp.terraform
tfenv
TFのバージョン管理ツール
tfenv install バージョン番号
で指定バージョンのTFをインストールできる他、.terraform-versionファイルを作成し tfenv install
を実行することで記述されたバージョンのTFがインストールされ使用できるようになる。
1.4.4
tfenv install
terraform --version
で確認Terraform v1.4.4
on linux_amd64
Your version of Terraform is out of date! The latest version
is 1.4.5. You can update by downloading from https://www.terraform.io/downloads.html
Terraform
terraformを実行してみる
【AWS編】Terraform公式チュートリアル【翻訳】 を参考にmain.tfを作成し実行してみる。
main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16"
}
}
required_version = ">= 1.2.0"
}
provider "aws" {
region = "us-west-2"
}
resource "aws_instance" "app_server" {
ami = "ami-830c94e3"
instance_type = "t2.micro"
tags = {
Name = "ExampleAppServerInstance"
}
}
準備
terraform init
を実行し初期化してプロバイダーをインストールする。
terraform init
の実行Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 4.16"...
- Installing hashicorp/aws v4.62.0...
- Installed hashicorp/aws v4.62.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
実行すると以下のファイルとディレクトリが作成された
.terraform.lock.hcl
.terraform
.terraform/providers/registry.terraform.io/hashicorp/aws/4.62.0/linux_amd64/terraform-provider-aws_v4.62.0_x5
確認
terraform validate
で有効なテンプレートであるか確認
terraform validate
を実行Success! The configuration is valid.
計画
terraform plan
を実行する
terraform plan
を実行Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_instance.app_server will be created
+ resource "aws_instance" "app_server" {
+ ami = "ami-830c94e3"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ disable_api_stop = (known after apply)
+ disable_api_termination = (known after apply)
+ ebs_optimized = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ host_resource_group_arn = (known after apply)
+ iam_instance_profile = (known after apply)
+ id = (known after apply)
+ instance_initiated_shutdown_behavior = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t1.micro"
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = (known after apply)
+ monitoring = (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ placement_partition_number = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ secondary_private_ips = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ subnet_id = (known after apply)
+ tags = {
+ "Name" = "ExampleAppServerInstance"
}
+ tags_all = {
+ "Name" = "ExampleAppServerInstance"
}
+ tenancy = (known after apply)
+ user_data = (known after apply)
+ user_data_base64 = (known after apply)
+ user_data_replace_on_change = false
+ vpc_security_group_ids = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
適用
terraform apply
を実行する
terraform apply -auto-approve
を実行Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_instance.app_server will be created
+ resource "aws_instance" "app_server" {
+ ami = "ami-830c94e3"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ disable_api_stop = (known after apply)
+ disable_api_termination = (known after apply)
+ ebs_optimized = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ host_resource_group_arn = (known after apply)
+ iam_instance_profile = (known after apply)
+ id = (known after apply)
+ instance_initiated_shutdown_behavior = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t1.micro"
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = (known after apply)
+ monitoring = (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ placement_partition_number = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ secondary_private_ips = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ subnet_id = (known after apply)
+ tags = {
+ "Name" = "ExampleAppServerInstance"
}
+ tags_all = {
+ "Name" = "ExampleAppServerInstance"
}
+ tenancy = (known after apply)
+ user_data = (known after apply)
+ user_data_base64 = (known after apply)
+ user_data_replace_on_change = false
+ vpc_security_group_ids = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
aws_instance.app_server: Creating...
aws_instance.app_server: Still creating... [10s elapsed]
aws_instance.app_server: Still creating... [20s elapsed]
aws_instance.app_server: Still creating... [30s elapsed]
aws_instance.app_server: Creation complete after 34s [id=i-08f43a9387eabd711]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
AWSコンソールをのぞくとリソースが作成されていた。
-auto-approve オプションは強制的に適用させるためのオプション。terraform apply
だとInteractiveに動くため-auto-approveで強制実行させてみた。
ただし、CIでやるにはよろしくなさそう。
別の方法として`terraform plan` 時に -out オプションを使用するとplan fileが出力される。
それを terraform apply ${PLAN_FILE}
と指定して実行できるそうな。
また、planの中身をMDに変換するツールもあるらしい。
削除
terraform destroy
を実行するとInteractiveに動くterraform destroy
は terraform apply -destroy
のaliasらしい。
terraform apply -auto-approve -destroy
を実行aws_instance.app_server: Refreshing state... [id=i-08f43a9387eabd711]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# aws_instance.app_server will be destroyed
- resource "aws_instance" "app_server" {
- ami = "ami-830c94e3" -> null
- arn = "arn:aws:ec2:us-west-2:123456789101:instance/i-08f43a9387eabd711" -> null
- associate_public_ip_address = true -> null
- availability_zone = "us-west-2c" -> null
- cpu_core_count = 1 -> null
- cpu_threads_per_core = 1 -> null
- disable_api_stop = false -> null
- disable_api_termination = false -> null
- ebs_optimized = false -> null
- get_password_data = false -> null
- hibernation = false -> null
- id = "i-08f43a9387eabd711" -> null
- instance_initiated_shutdown_behavior = "stop" -> null
- instance_state = "running" -> null
- instance_type = "t1.micro" -> null
- ipv6_address_count = 0 -> null
- ipv6_addresses = [] -> null
- monitoring = false -> null
- placement_partition_number = 0 -> null
- primary_network_interface_id = "eni-09de77ee395cbbd15" -> null
- private_dns = "ip-172-31-11-125.us-west-2.compute.internal" -> null
- private_ip = "172.31.11.125" -> null
- public_dns = "ec2-35-89-169-164.us-west-2.compute.amazonaws.com" -> null
- public_ip = "35.89.169.164" -> null
- secondary_private_ips = [] -> null
- security_groups = [
- "default",
] -> null
- source_dest_check = true -> null
- subnet_id = "subnet-092f134f" -> null
- tags = {
- "Name" = "ExampleAppServerInstance"
} -> null
- tags_all = {
- "Name" = "ExampleAppServerInstance"
} -> null
- tenancy = "default" -> null
- user_data_replace_on_change = false -> null
- vpc_security_group_ids = [
- "sg-2da27048",
] -> null
- capacity_reservation_specification {
- capacity_reservation_preference = "open" -> null
}
- enclave_options {
- enabled = false -> null
}
- maintenance_options {
- auto_recovery = "default" -> null
}
- metadata_options {
- http_endpoint = "enabled" -> null
- http_put_response_hop_limit = 1 -> null
- http_tokens = "optional" -> null
- instance_metadata_tags = "disabled" -> null
}
- private_dns_name_options {
- enable_resource_name_dns_a_record = false -> null
- enable_resource_name_dns_aaaa_record = false -> null
- hostname_type = "ip-name" -> null
}
- root_block_device {
- delete_on_termination = true -> null
- device_name = "/dev/sda1" -> null
- encrypted = false -> null
- iops = 0 -> null
- tags = {} -> null
- throughput = 0 -> null
- volume_id = "vol-0d0c8c535b33f7906" -> null
- volume_size = 8 -> null
- volume_type = "standard" -> null
}
}
Plan: 0 to add, 0 to change, 1 to destroy.
aws_instance.app_server: Destroying... [id=i-08f43a9387eabd711]
aws_instance.app_server: Still destroying... [id=i-08f43a9387eabd711, 10s elapsed]
aws_instance.app_server: Still destroying... [id=i-08f43a9387eabd711, 20s elapsed]
aws_instance.app_server: Still destroying... [id=i-08f43a9387eabd711, 30s elapsed]
aws_instance.app_server: Destruction complete after 31s
Apply complete! Resources: 0 added, 0 changed, 1 destroyed.
試しに terraform plan -destroy
を実行してみた。
terraform plan -destroy
を実行aws_instance.app_server: Refreshing state... [id=i-08f43a9387eabd711]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# aws_instance.app_server will be destroyed
- resource "aws_instance" "app_server" {
- ami = "ami-830c94e3" -> null
- arn = "arn:aws:ec2:us-west-2:123456789101:instance/i-08f43a9387eabd711" -> null
- associate_public_ip_address = true -> null
- availability_zone = "us-west-2c" -> null
- cpu_core_count = 1 -> null
- cpu_threads_per_core = 1 -> null
- disable_api_stop = false -> null
- disable_api_termination = false -> null
- ebs_optimized = false -> null
- get_password_data = false -> null
- hibernation = false -> null
- id = "i-08f43a9387eabd711" -> null
- instance_initiated_shutdown_behavior = "stop" -> null
- instance_state = "running" -> null
- instance_type = "t1.micro" -> null
- ipv6_address_count = 0 -> null
- ipv6_addresses = [] -> null
- monitoring = false -> null
- placement_partition_number = 0 -> null
- primary_network_interface_id = "eni-09de77ee395cbbd15" -> null
- private_dns = "ip-172-31-11-125.us-west-2.compute.internal" -> null
- private_ip = "172.31.11.125" -> null
- public_dns = "ec2-35-89-169-164.us-west-2.compute.amazonaws.com" -> null
- public_ip = "35.89.169.164" -> null
- secondary_private_ips = [] -> null
- security_groups = [
- "default",
] -> null
- source_dest_check = true -> null
- subnet_id = "subnet-092f134f" -> null
- tags = {
- "Name" = "ExampleAppServerInstance"
} -> null
- tags_all = {
- "Name" = "ExampleAppServerInstance"
} -> null
- tenancy = "default" -> null
- user_data_replace_on_change = false -> null
- vpc_security_group_ids = [
- "sg-2da27048",
] -> null
- capacity_reservation_specification {
- capacity_reservation_preference = "open" -> null
}
- enclave_options {
- enabled = false -> null
}
- maintenance_options {
- auto_recovery = "default" -> null
}
- metadata_options {
- http_endpoint = "enabled" -> null
- http_put_response_hop_limit = 1 -> null
- http_tokens = "optional" -> null
- instance_metadata_tags = "disabled" -> null
}
- private_dns_name_options {
- enable_resource_name_dns_a_record = false -> null
- enable_resource_name_dns_aaaa_record = false -> null
- hostname_type = "ip-name" -> null
}
- root_block_device {
- delete_on_termination = true -> null
- device_name = "/dev/sda1" -> null
- encrypted = false -> null
- iops = 0 -> null
- tags = {} -> null
- throughput = 0 -> null
- volume_id = "vol-0d0c8c535b33f7906" -> null
- volume_size = 8 -> null
- volume_type = "standard" -> null
}
}
Plan: 0 to add, 0 to change, 1 to destroy.
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
tflint
静的解析ツール
.tflint.hcl を準備し tflint --init
で初期化することで対象ルールセットをインストールして使用する。
以前はtflint腹持ちのTFと使用するTFのバージョンの同期が必要だったようだが、今はv1.x系ならほぼ問題ないらしい。(Compatibility with Terraform)
.tflint.hcl の中身は terraform-linters/tflint-ruleset-aws のものを使用
plugin "aws" {
enabled = true
version = "0.22.1"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
tflint --init
root@1fbd264426b9:/workspace# tflint --version
TFLint version 0.45.0
+ ruleset.aws (0.22.1)
+ ruleset.terraform (0.2.2-bundled)
実行
そのままmain.tfを使用する。
tflint
を実行(何も表示されない)
試しに存在しないインスタンスタイプに変更してやってみる
instance_type = "x.micro"
tflint
を実行1 issue(s) found:
Error: "x.micro" is an invalid value as instance_type (aws_instance_invalid_type)
on main.tf line 18:
18: instance_type = "x.micro"
tfsec
tfsecを実行する(main.tfは元に戻した)
tfsec
を実行Result #1 HIGH Instance does not require IMDS access to require a token
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
main.tf:16-23
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
16 resource "aws_instance" "app_server" {
17 ami = "ami-830c94e3"
18 instance_type = "t2.micro"
19
20 tags = {
21 Name = "ExampleAppServerInstance"
22 }
23 }
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
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.28.1/checks/aws/ec2/enforce-http-token-imds/
- https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#metadata-options
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Result #2 HIGH Root block device is not encrypted.
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
main.tf:16-23
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
16 resource "aws_instance" "app_server" {
17 ami = "ami-830c94e3"
18 instance_type = "t2.micro"
19
20 tags = {
21 Name = "ExampleAppServerInstance"
22 }
23 }
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
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.28.1/checks/aws/ec2/enable-at-rest-encryption/
- https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#ebs-ephemeral-and-root-block-devices
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
timings
──────────────────────────────────────────
disk i/o 2.880857ms
parsing 284.605µs
adaptation 95.502µs
checks 16.294024ms
total 19.554988ms
counts
──────────────────────────────────────────
modules downloaded 0
modules processed 1
blocks processed 3
files read 1
results
──────────────────────────────────────────
passed 3
ignored 0
critical 0
high 2
medium 0
low 0
3 passed, 2 potential problem(s) detected.
テンプレートを以下の用に直し再度実行
resource "aws_instance" "app_server" {
ami = "ami-830c94e3"
instance_type = "t2.micro"
metadata_options {
http_tokens = "required"
}
root_block_device {
encrypted = true
}
tags = {
Name = "ExampleAppServerInstance"
}
}
tfsec
を実行 timings
──────────────────────────────────────────
disk i/o 3.699285ms
parsing 278.107µs
adaptation 109.703µs
checks 7.237266ms
total 11.324361ms
counts
──────────────────────────────────────────
modules downloaded 0
modules processed 1
blocks processed 3
files read 1
results
──────────────────────────────────────────
passed 5
ignored 0
critical 0
high 0
medium 0
low 0
No problems detected!