【実務・中級編】 IaC(Terraform/CloudFormation)の静的解析による設定ミス検知 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

お疲れ。最近、クラウドの初期設定ミスを踏み台にされて、インフラをごっそり持っていかれるインシデントの調査依頼が後を絶たない。

「うちのインフラはすべてTerraformでコード化されているから安全です」――そう胸を張る開発チームに限って、いざコードを覗いてみると、世界に向けて全開放されたS3バケットや、パスワードが平文でベタ書きされたRDSインスタンスが平然とデプロイされていたりする。

Infrastructure as Code(IaC)は、インフラの構築を迅速かつ再現性高く行うための強力な武器だ。しかし同時に、「セキュリティ上の致命的な設定ミスを、一瞬で全リージョンにばら撒くための自動核兵器」にもなり得る。今回は、デプロイボタンを押す前にその爆弾を強制解除するための「IaC静的解析」の極意を、現場のリアルな知見を交えて伝授しよう。

—

なぜ「デプロイ後」のセキュリティ診断では遅すぎるのか

ペネトレーションテストの現場でクラウド環境をアセスメントすると、大抵の原因は「人間のうっかりミス」だ。
「テスト環境だから一時的に公開設定にした」「サンプルコードの 0.0.0.0/0 を消し忘れた」――こうしたミスが本番環境のTerraformコードに混入し、そのままapplyされてしまう。

動的ペネトレーションテストや、クラウド上のCSPM(Cloud Security Posture Management)ツールによる検知は、いわば「泥棒が入った後に警報器が鳴る状態」だ。もちろん重要だが、プロの攻撃者は侵入した瞬間にログを無効化し、バックドアを仕掛ける。

我々が目指すべきは、コードがGitにコミットされた瞬間、あるいはCI/CDパイプラインのプルリクエストの段階で、脆弱なコードを物理的に弾き返す「シフトレフト(左方移行)」のセキュリティだ。

—

IaC静的解析ツールの選定と現場への導入

Terraformの静的解析ツールとして現在デファクトスタンダードとなっているのは、tfsec や checkov だ。これらはコードを実行することなく構文木(AST)を解析し、CISベンチマークなどのセキュリティ基準に違反していないかを高速にチェックしてくれる。

特に tfsec は、AWS、Azure、GCPの主要な脆弱性パターンの検知精度が高く、開発者の手元(ローカル)からCI/CDまでシームレスに組み込めるため、我的には強く推奨している。

まずは、ローカル環境のMac(Homebrew)でサクッとインストールして試してみよう。

# tfsecのインストール
brew install tfsec

# プロジェクトのルートディレクトリでスキャンを実行
tfsec .

もしコード内に危険な設定があれば、以下のように赤裸々に指摘してくれる。

Result #1 [HIGH Critical] [AVD-AWS-0086] - Public S3 bucket access is allowed
   ṅ∷ and/or resource "aws_s3_bucket_public_access_block" is missing
   - main.tf:14

この「怒られが発生する状態」を、いかに開発の邪魔をせず、しかし強制力を持ってCI/CDパイプラインに組み込むかが腕の見せ所だ。

—

現場で即死する「典型的なバッドパターン」と対策コード

では、実際のTerraformコードを例に、攻撃者が好む「おいしい設定ミス」と、それを完璧に塞ぐ「セキュアな実装」を見ていこう。

1. S3バケットのパブリック公開(データ流出の温床)

開発者がやりがちなのが、静的ウェブサイトホスティングやアセット配置のために、S3バケットを丸裸にしてしまう設定だ。

【危険なバッドパターン (main.tf)】

# 【NG】バケットポリシーやブロックパブリックアクセスの設定が抜けている
resource "aws_s3_bucket" "dangerous_bucket" {
  bucket = "my-company-leaky-bucket"
  acl    = "public-read" # 誰でも読み取り可能になっている
}

これがあるだけで、世界中のボットが数分以内にこのバケットを発見し、機密データをスクレイピングしていく。

【コピペで使えるセキュアな実装 (main.tf)】

# 【OK】パブリックアクセスを完全にブロックし、暗号化とバージョニングを強制する
resource "aws_s3_bucket" "secure_bucket" {
  bucket = "my-company-secure-bucket"
}

# パブリックアクセスブロックを必ずセットで定義する
resource "aws_s3_bucket_public_access_block" "secure_bucket_block" {
  bucket                  = aws_s3_bucket.secure_bucket.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# 保存データの暗号化(AES-256)を強制
resource "aws_s3_bucket_server_side_encryption_by_default" "secure_bucket_crypto" {
  bucket = aws_s3_bucket.secure_bucket.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

ここまで書いて初めて、tfsec のチェックをパスできる。

—

2. セキュリティグループの 0.0.0.0/0 フルオープン

テスト用に作成したSecurity Group(SG)のインバウンドルールに 0.0.0.0/0 を指定し、SSH(ポート22)や管理画面(ポート8080等)を全世界に曝け出すミスも後を絶たない。

【危険なバッドパターン (security_group.tf)】

# 【NG】全世界からのSSH接続を許可している
resource "aws_security_group" "bad_sg" {
  name        = "allow_all_ssh"
  description = "Allow SSH from anywhere"

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] # 攻撃者の総当たり攻撃の的になる
  }
}

【コピペで使えるセキュアな実装 (security_group.tf)】

# 【OK】社内ネットワークや踏み台(VPN)のIPレンジのみに限定する
variable "allowed_cidr" {
  type        = string
  description = "社内VPNまたは許可された管理用IPレンジ"
  default     = "203.0.113.50/32" # ※実際の運用値に変更すること
}

resource "aws_security_group" "secure_sg" {
  name        = "secure_ssh"
  description = "Allow SSH only from trusted IP"

  ingress {
    description = "SSH from trusted IP only"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.allowed_cidr] # 特定のIP以外からのアクセスを物理的に遮断
  }

  egress {
    description = "Allow all outbound traffic for patching and updates"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

—

GitHub ActionsによるCI/CDパイプラインへの組み込み

ローカルでいくら「気をつけよう」と言っても、人間はミスをする生き物だ。だからこそ、GitHub Actionsを使って、プルリクエストの作成時に自動で tfsec を走らせ、脆弱性がある場合はマージボタンをロックする仕組みを強制する。

プロジェクトのルートに .github/workflows/tfsec.yml を配置しよう。

【コピペで動く GitHub Actions 設定ファイル (.github/workflows/tfsec.yml)】

name: "Terraform Security Scan (tfsec)"

# プルリクエスト作成時およびmainブランチへのプッシュ時に発動
on:
  pull_request:
    branches:
      - main
  push:
    branches:
      - main

jobs:
  tfsec:
    name: tfsec-scanner
    runs-on: ubuntu-latest

    permissions:
      contents: read
      pull-requests: write # PRにコメントを投稿するために必要

    steps:
      # リポジトリのコードをチェックアウト
      - name: Checkout Code
        uses: actions/checkout@v4

      # tfsecを実行し、セキュリティ上の重大なミスがないかスキャン
      - name: Run tfsec
        uses: tfsec/tfsec-action@v1.0.3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          # 重大な脆弱性(HIGH/CRITICAL)が見つかった場合はビルドを失敗させる
          min_severity: HIGH

このワークフローを導入すれば、仮に開発者がうっかりパブリックなS3バケットの設定をコミットしてプルリクエストを出したとしても、GitHub上で自動的にチェックが走り、エラーとして弾かれる。セキュリティチーフとして夜中に起こされる回数が劇的に減る瞬間だ。

—

チームへの定着化と現場のリアルな運用Tips

最後に、こうした静的解析ツールを現場に導入する際の「生々しい教訓」をいくつか伝えておく。

1. 最初から厳しくしすぎない
既存の巨大なコードベースに対して、いきなりすべての tfsec の警告(LOWやMEDIUM含む)をエラーにすると、開発チームから大ブーイングが起き、セキュリティツール自体がバイパスされるようになる。最初は min_severity: HIGH または CRITICAL のみに絞り、徐々に基準を引き上げていくのが政治的にも正しいアプローチだ。
2. どうしても例外が必要な場合の逃げ道を用意する
ビジネス上の理由や一時的な検証で、どうしても静検知のルールにひっかかる設定を書かざるを得ない場合がある。その際は、ツール側の機能(tfsec:ignore:AVD-AWS-0086 などのコメント注釈)を使い、「なぜその例外が必要なのかの正当な理由(チケット番号など)」をコード上にコメントとして残すルールを徹底させよう。理由のない無視は絶対に認めない。

セキュリティは、気合いや個人のスキルに依存した瞬間から崩壊する。仕組みで縛り、開発者が「自然にセキュアなコードしか書けない環境」を構築することこそが、我々エンジニアの仕事だ。今日から君のプロジェクトにも tfsec を導入し、安全なクラウドライフを死守してほしい。健闘を祈る。

コメント

タイトルとURLをコピーしました