ストーリー
佐藤CTOがTerraformのコードを指差しました。
IaCセキュリティの重要性
IaCに潜む典型的なセキュリティ問題
| 問題 | 影響 | 検出ツール |
|---|---|---|
| S3バケットのパブリックアクセス | データ漏洩 | tfsec, Checkov |
| セキュリティグループの全開放 | 不正アクセス | tfsec, Checkov |
| 暗号化未設定のRDS | データ漏洩 | tfsec, Checkov |
| IAMポリシーのワイルドカード | 過剰な権限 | IAM Access Analyzer |
| CloudTrail無効化 | 監査不能 | AWS Config Rules |
| VPCフローログ未設定 | ネットワーク監視不能 | Checkov |
tfsec / trivy config
tfsecはTerraformのセキュリティスキャンツールです(現在はTrivyに統合)。
# BAD: セキュリティ問題のあるTerraformコード
resource "aws_s3_bucket" "data" {
bucket = "my-data-bucket"
# tfsec:ignore[aws-s3-bucket-logging] ← 個別に無視する場合
# 問題1: バージョニング未設定
# 問題2: 暗号化未設定
# 問題3: パブリックアクセス制御未設定
# 問題4: ログ未設定
}
resource "aws_security_group" "web" {
name = "web-sg"
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"] # 問題: 全ポート全開放
}
}
# GOOD: セキュリティベストプラクティスに準拠
resource "aws_s3_bucket" "data" {
bucket = "my-data-bucket"
}
resource "aws_s3_bucket_versioning" "data" {
bucket = aws_s3_bucket.data.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.s3.arn
}
bucket_key_enabled = true
}
}
resource "aws_s3_bucket_public_access_block" "data" {
bucket = aws_s3_bucket.data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_logging" "data" {
bucket = aws_s3_bucket.data.id
target_bucket = aws_s3_bucket.access_logs.id
target_prefix = "s3-access-logs/"
}
resource "aws_security_group" "web" {
name = "web-sg"
description = "Web server security group"
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"] # VPC内のみ
description = "HTTPS from VPC"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
description = "Allow all outbound"
}
tags = {
Name = "web-sg"
}
}
Checkov
Checkovは、Terraform、CloudFormation、Kubernetes、Dockerfileなど多数のIaCフレームワークに対応したスキャンツールです。
# GitHub Actions での Checkov 実行
- name: Checkov IaC Scan
uses: bridgecrewio/checkov-action@master
with:
directory: ./infrastructure
framework: terraform
output_format: sarif
output_file_path: checkov-results.sarif
soft_fail: false
skip_check: CKV_AWS_18 # 特定チェックのスキップ(理由をコメントで記載)
Checkov カスタムポリシー
# custom_policies/s3_naming_convention.py
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckResult, CheckCategories
class S3NamingConvention(BaseResourceCheck):
def __init__(self):
name = "Ensure S3 bucket follows naming convention"
id = "CUSTOM_S3_001"
supported_resources = ['aws_s3_bucket']
categories = [CheckCategories.CONVENTION]
super().__init__(name=name, id=id, categories=categories,
supported_resources=supported_resources)
def scan_resource_conf(self, conf):
bucket_name = conf.get("bucket", [""])[0]
# 命名規則: {env}-{project}-{purpose}
import re
pattern = r'^(dev|stg|prod)-[a-z]+-[a-z]+$'
if re.match(pattern, bucket_name):
return CheckResult.PASSED
return CheckResult.FAILED
check = S3NamingConvention()
Policy as Code(OPA / Rego)
OPA(Open Policy Agent)
OPAは汎用的なポリシーエンジンで、Rego言語でポリシーを記述します。
# policy/terraform.rego
package terraform.security
# S3バケットは暗号化必須
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
not has_encryption(resource)
msg := sprintf("S3 bucket '%s' must have server-side encryption enabled", [resource.name])
}
has_encryption(resource) {
resource.change.after.server_side_encryption_configuration[_].rule[_].apply_server_side_encryption_by_default[_].sse_algorithm
}
# セキュリティグループは0.0.0.0/0の全ポート開放を禁止
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_security_group"
ingress := resource.change.after.ingress[_]
ingress.cidr_blocks[_] == "0.0.0.0/0"
ingress.from_port == 0
ingress.to_port == 0
msg := sprintf("Security group '%s' must not allow unrestricted access on all ports", [resource.name])
}
# RDSは暗号化と自動バックアップ必須
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_db_instance"
not resource.change.after.storage_encrypted
msg := sprintf("RDS instance '%s' must have storage encryption enabled", [resource.name])
}
# タグの必須チェック
required_tags := {"Environment", "Team", "CostCenter"}
deny[msg] {
resource := input.resource_changes[_]
tags := object.get(resource.change.after, "tags", {})
missing := required_tags - {key | tags[key]}
count(missing) > 0
msg := sprintf("Resource '%s' is missing required tags: %v", [resource.name, missing])
}
OPAをCI/CDに統合
# Terraform plan → OPA 検証 → Apply の流れ
- name: Terraform Plan
run: |
cd infrastructure
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
- name: OPA Policy Check
run: |
opa eval \
--input infrastructure/tfplan.json \
--data policy/ \
--format pretty \
'data.terraform.security.deny'
# deny が1つでもあればビルドを失敗
VIOLATIONS=$(opa eval \
--input infrastructure/tfplan.json \
--data policy/ \
--format json \
'count(data.terraform.security.deny)')
if [ "$VIOLATIONS" != "0" ]; then
echo "Policy violations found!"
exit 1
fi
Sentinel(HashiCorp)
HashiCorp Sentinel は Terraform Enterprise/Cloud 向けの Policy as Code フレームワークです。
# sentinel/require-encryption.sentinel
import "tfplan/v2" as tfplan
# 全S3バケットにサーバーサイド暗号化を要求
s3_buckets = filter tfplan.resource_changes as _, rc {
rc.type is "aws_s3_bucket" and
rc.mode is "managed" and
(rc.change.actions contains "create" or rc.change.actions contains "update")
}
encryption_check = rule {
all s3_buckets as _, bucket {
bucket.change.after.server_side_encryption_configuration is not null
}
}
main = rule {
encryption_check
}
AWS Config Rules によるドリフト検知
// CDK で AWS Config Rules を定義
import * as config from 'aws-cdk-lib/aws-config';
// S3バケットのパブリックアクセスチェック
new config.ManagedRule(this, 'S3PublicReadRule', {
identifier: 'S3_BUCKET_PUBLIC_READ_PROHIBITED',
configRuleName: 's3-bucket-public-read-prohibited',
});
// セキュリティグループの開放チェック
new config.ManagedRule(this, 'SgOpenRule', {
identifier: 'RESTRICTED_INCOMING_TRAFFIC',
configRuleName: 'restricted-incoming-traffic',
inputParameters: {
blockedPort1: '22',
blockedPort2: '3389',
},
});
// RDS暗号化チェック
new config.ManagedRule(this, 'RdsEncryptionRule', {
identifier: 'RDS_STORAGE_ENCRYPTED',
configRuleName: 'rds-storage-encrypted',
});
// CloudTrail有効チェック
new config.ManagedRule(this, 'CloudTrailRule', {
identifier: 'CLOUD_TRAIL_ENABLED',
configRuleName: 'cloudtrail-enabled',
});
// 自動修復アクション
new config.CfnRemediationConfiguration(this, 'S3Remediation', {
configRuleName: 's3-bucket-public-read-prohibited',
targetId: 'AWS-DisableS3BucketPublicReadWrite',
targetType: 'SSM_DOCUMENT',
automatic: true,
maximumAutomaticAttempts: 3,
retryAttemptSeconds: 60,
parameters: {
S3BucketName: {
ResourceValue: { Value: 'RESOURCE_ID' },
},
},
});
まとめ
| ポイント | 内容 |
|---|---|
| IaCセキュリティスキャン | tfsec/Trivy、Checkovでインフラコードの脆弱性を検出 |
| Policy as Code | OPA/Regoで組織のセキュリティポリシーをコードとして強制 |
| Sentinel | Terraform Enterprise/Cloud向けのポリシーフレームワーク |
| AWS Config Rules | デプロイ後のコンプライアンスドリフトを継続的に検知 |
| 自動修復 | ポリシー違反の自動修復で人的ミスを排除 |
チェックリスト
- IaCに潜むセキュリティ問題の主なパターンを理解した
- tfsec/Checkovでterraformコードをスキャンできる
- OPA/Regoでカスタムセキュリティポリシーを記述できる
- Policy as CodeをCI/CDパイプラインに統合できる
- AWS Config Rulesでドリフト検知を設定できる
次のステップへ
次は「演習:セキュリティパイプラインを構築しよう」です。Step 3で学んだSAST/SCA/DAST/コンテナスキャン/IaCスキャンを統合した、包括的なセキュリティパイプラインを構築しましょう。
推定読了時間: 40分