0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TerraformドリフトをFalcoプラグインでリアルタイム検知する仕組み

0
Posted at

はじめに — Terraformドリフトはなぜ怖いのか

Terraformでインフラをコード管理していても、誰かがAWSコンソールから直接変更してしまうことは日常的に起こります。これがいわゆる「ドリフト(drift)」です。

たとえば、障害対応中にセキュリティグループのインバウンドルールを手動で開放した。あるいは、検証のためにEC2のインスタンスタイプをコンソールから変更した。こうした変更はTerraformのstateには反映されず、コードと実態が乖離したまま放置されます。

従来、ドリフト検知の定番は terraform plan の定期実行です。しかし、このアプローチには明確な限界があります。

  • 検知の遅延: cronで回すにしても数時間〜1日単位のバッチ処理。変更の瞬間から検知までにタイムラグが生まれる
  • コストの問題: 大規模環境では plan 1回に数分〜数十分かかる。実行頻度を上げれば API Rate Limit にも抵触しうる
  • コンテキストの欠落: plan は「何が変わったか」はわかるが、「誰が・いつ・なぜ変えたか」は教えてくれない

本記事では、Falcoのプラグインシステムを活用してTerraformドリフトをリアルタイムに検知するOSSツール「TFDrift-Falco」のアーキテクチャと実装を解説します。

Falcoとは — ランタイムセキュリティからインフラ監視へ

FalcoはCNCF Graduatedプロジェクトのランタイムセキュリティツールです。もともとはeBPFを使ってLinuxカーネルレベルのシステムコールを監視し、コンテナの不審な挙動(予期しないシェル起動、機密ファイルへのアクセスなど)をリアルタイムに検知するためのツールとして広く使われてきました。

Falcoの強力なポイントはプラグインシステムにあります。v0.35以降、Falcoはカーネルイベントだけでなく、プラグイン経由でさまざまなイベントソースを取り込めるようになりました。特に重要なのが以下のプラグインです。

  • aws_cloudtrail: AWS CloudTrailのイベントをリアルタイムに取得
  • gcpaudit: GCP Cloud Audit Logsを取得
  • azure_activity (カスタム): Azure Activity Logsを取得

つまり、「カーネルイベント監視」から「クラウドイベント監視」への拡張がFalcoの設計思想の中で自然に実現されています。TFDrift-Falcoはこの仕組みに乗る形で、クラウドの変更イベントをリアルタイムに受け取り、Terraformのstateと比較するというアプローチを取ります。

TFDrift-Falcoのアーキテクチャ全体像

TFDrift-Falcoのデータフローを図にすると以下のようになります。

各クラウドプロバイダのイベントログ(CloudTrail / Audit Logs / Activity Logs)がFalcoのプラグインを通じてgRPCストリームとして流れ込み、TFDrift-Falcoがリアルタイムに処理するという構成です。

対応するイベント数は以下のとおりです。

プロバイダ イベントソース イベント数 サービス数
AWS CloudTrail 500+ 40+
GCP Cloud Audit Logs 170+ 27+
Azure Activity Logs 119 20+

実装の核心: クラウドイベントをドリフトアラートに変換する

1. Falco Subscriber — gRPCストリームの購読

TFDrift-Falcoはまず、FalcoのgRPC APIに接続してイベントストリームを購読します。pkg/falco/subscriber.goがその実装です。

// Subscriber subscribes to Falco outputs via gRPC
type Subscriber struct {
    cfg         config.FalcoConfig
    client      *client.Client
    grpcConn    *grpc.ClientConn
    gcpParser   *gcp.AuditParser     // GCP Audit Log parser
    azureParser *azure.ActivityParser // Azure Activity Log parser
}

func (s *Subscriber) Start(ctx context.Context, eventCh chan<- types.Event) error {
    // Falco gRPC APIに接続し、イベントストリームを購読
    // TLS / Insecure の両方に対応
    // ...
}

Falcoから受信した各イベントは parseFalcoOutput メソッドでソース別に振り分けられます。

func (s *Subscriber) parseFalcoOutput(res *outputs.Response) *types.Event {
    switch res.Source {
    case "aws_cloudtrail":
        return s.parseAWSEvent(res)
    case "gcpaudit":
        return s.gcpParser.Parse(res)
    case "azure_activity":
        return s.azureParser.Parse(res)
    default:
        return nil
    }
}

2. Event Parser — CloudTrailイベントの正規化

AWSのCloudTrailイベントを例にとると、pkg/falco/event_parser.goでFalcoの出力フィールドから構造化イベントへの変換が行われます。

func (s *Subscriber) parseAWSEvent(res *outputs.Response) *types.Event {
    fields := res.OutputFields

    eventName := getStringField(fields, "ct.name")       // 例: "AuthorizeSecurityGroupIngress"
    eventSource := getStringField(fields, "ct.src")       // 例: "ec2.amazonaws.com"
    resourceType := s.mapEventToResourceType(eventName, eventSource)

    userIdentity := types.UserIdentity{
        Type:      getStringField(fields, "ct.user.type"),
        ARN:       getStringField(fields, "ct.user.arn"),
        UserName:  getStringField(fields, "ct.user"),
    }

    changes := s.extractChanges(eventName, fields)

    return &types.Event{
        Provider:     "aws",
        EventName:    eventName,
        ResourceType: resourceType,       // "aws_security_group"
        ResourceID:   resourceID,
        UserIdentity: userIdentity,       // 誰が変更したか
        Changes:      changes,            // 何が変わったか
    }
}

ここで重要なのは、CloudTrailのイベント名(AuthorizeSecurityGroupIngress)からTerraformのリソースタイプaws_security_group)へのマッピングです。

3. イベント→リソースタイプのマッピング

pkg/falco/mappings/配下に、サービスカテゴリごとのマッピング定義があります。

// pkg/falco/mappings/networking.go
var NetworkingMappings = map[string]string{
    "AuthorizeSecurityGroupIngress": "aws_security_group",
    "AuthorizeSecurityGroupEgress":  "aws_security_group",
    "RevokeSecurityGroupIngress":    "aws_security_group",
    "CreateVpc":                     "aws_vpc",
    "DeleteVpc":                     "aws_vpc",
    "CreateSubnet":                  "aws_subnet",
    "CreateNatGateway":              "aws_nat_gateway",
    // ... 他多数
}

// pkg/falco/mappings/security.go
var SecurityMappings = map[string]string{
    "PutRolePolicy":          "aws_iam_role_policy",
    "UpdateAssumeRolePolicy": "aws_iam_role",
    "AttachRolePolicy":       "aws_iam_role_policy_attachment",
    "CreatePolicy":           "aws_iam_policy",
    "CreateKey":              "aws_kms_key",
    // ... 他多数
}

マッピングファイルは compute.gonetworking.gosecurity.gostorage_and_database.goother_services.go の5カテゴリに分かれており、合計500以上のCloudTrailイベントをカバーしています。

4. 競合解決 — 同名イベントの曖昧さを解消する

AWSでは異なるサービスが同じイベント名を使うケースがあります。たとえば CreateCluster はEKS、ECS、Redshift、ElastiCacheのいずれでも使われます。pkg/falco/mappings/conflicts.goがこの曖昧さを eventSource フィールドで解決します。

func ResolveEventSourceConflict(eventName string, eventSource string) string {
    switch eventName {
    case "CreateCluster", "DeleteCluster", "ModifyCluster":
        if eventSource == "eks.amazonaws.com" {
            return "aws_eks_cluster"
        }
        if eventSource == "ecs.amazonaws.com" {
            return "aws_ecs_cluster"
        }
        if eventSource == "redshift.amazonaws.com" {
            return "aws_redshift_cluster"
        }
        return "aws_eks_cluster" // デフォルト

    case "CreateAlias", "DeleteAlias":
        if eventSource == "lambda.amazonaws.com" {
            return "aws_lambda_alias"
        }
        return "aws_kms_alias" // デフォルト
    // ... 15以上の競合パターンに対応
    }
}

5. Change Extractor — 変更属性の抽出

pkg/falco/change_extractor.goは、イベントの種類に応じて「何が変わったか」をTerraformの属性名レベルで抽出します。

func (s *Subscriber) extractChanges(eventName string, fields map[string]string) map[string]interface{} {
    changes := make(map[string]interface{})

    switch eventName {
    case "ModifyInstanceAttribute":
        if val, ok := fields["ct.request.instancetype"]; ok && val != "" {
            changes["instance_type"] = val
        }
    case "PutBucketEncryption":
        if config, ok := fields["ct.request.serversideencryptionconfiguration"]; ok {
            changes["server_side_encryption_configuration"] = config
        }
    case "AuthorizeSecurityGroupIngress":
        if val, ok := fields["ct.request.ippermissions"]; ok {
            changes["ingress"] = val
        }
    // ... イベントごとの抽出ロジック
    }
    return changes
}

6. Drift Detector — Terraform Stateとの比較

最終段階はpkg/detector/event_handler.goです。抽出されたイベントをTerraformのstateと突き合わせ、ドリフトを判定します。

func (d *Detector) handleEvent(event types.Event) {
    // 1. Terraform stateでリソースを検索
    resource, exists := d.stateManager.GetResource(event.ResourceID)
    if !exists {
        // state に存在しない → 非管理リソース (unmanaged)
        d.sendUnmanagedResourceAlert(&event)
        return
    }

    // 2. 属性の差分を検出
    drifts := d.detectDrifts(resource, event.Changes)
    if len(drifts) == 0 {
        return // 差分なし
    }

    // 3. ルール評価 → 重大度決定 → アラート送信
    for _, drift := range drifts {
        matchedRules := d.evaluateRules(resource.Type, drift.Attribute)
        severity := d.getSeverity(matchedRules)
        d.sendDriftAlert(event, drift, severity)
    }
}

この検知はThree-way分析と呼ばれるアプローチを取っています。

  • Unmanaged: クラウドに存在するがTerraform stateにない(手動作成)
  • Missing: Terraform stateにあるがクラウドから削除された
  • Modified: 両方に存在するが属性値が異なる(コンソールからの変更)

デモモードで試す

TFDrift-Falcoは --demo フラグでクラウドの認証情報なしに動作を体験できます。以下はデモモードで表示されるダッシュボードUIの画面です。AWS/GCPのドリフトイベントがリアルタイムに一覧表示され、重大度・プロバイダ・リソースタイプ・変更者・リージョンが一目で把握できます。

TFDrift-Falco ダッシュボード — Drift Events画面
TFDrift-Falcoのイベント監視画面。AWS/GCPのドリフトをリアルタイムに検知・表示している

# リポジトリをクローン
git clone https://github.com/higakikeita/tfdrift-falco.git
cd tfdrift-falco

# デモモードで起動(クラウド認証不要)
go run ./cmd/tfdrift --demo

Docker Composeを使ったフルスタック環境の構築も簡単です。

# 設定ファイルを用意
cp config.yaml.example config.yaml

# 全サービスを起動(Falco + Backend + React UI)
docker compose up -d

# 各サービスへのアクセス
# Frontend:  http://localhost:3000
# Backend:   http://localhost:8080/api/v1
# WebSocket: ws://localhost:8080/ws

docker-compose.yml では、Falco(gRPCサーバ)、TFDriftバックエンド(APIサーバ)、React フロントエンドの3つのサービスが定義されています。

アラート出力のJSON例を示します。

{
  "event_type": "terraform_drift_detected",
  "provider": "aws",
  "resource_type": "aws_security_group",
  "resource_id": "sg-0123456789abcdef0",
  "change_type": "modified",
  "detected_at": "2026-03-29T10:15:30Z",
  "severity": "high",
  "region": "us-east-1",
  "user": "john.doe",
  "source": "tfdrift-falco",
  "expected": { "ingress": [{"from_port": 443, "to_port": 443}] },
  "actual": { "ingress": [{"from_port": 443, "to_port": 443}, {"from_port": 22, "to_port": 22}] },
  "cloudtrail_event": "AuthorizeSecurityGroupIngress",
  "falco_rule": "AWS Security Group Modification",
  "version": "1.0.0"
}

Falcoルールの設定例

TFDrift-Falcoが使うFalcoルールの定義は rules/terraform_drift.yaml に格納されています。ルールの記述は宣言的で、どのCloudTrailイベントを監視するかを条件式で定義します。

- rule: AWS Security Group Modification
  desc: Detect security group rule changes
  condition: >
    ct.name in ("AuthorizeSecurityGroupIngress", "RevokeSecurityGroupIngress",
    "AuthorizeSecurityGroupEgress", "RevokeSecurityGroupEgress",
    "CreateSecurityGroup", "DeleteSecurityGroup")
  output: >
    AWS Security Group modified (event=%ct.name user=%ct.user region=%ct.region)
  priority: WARNING
  source: aws_cloudtrail
  tags: [terraform, drift, security, network]

- rule: AWS IAM Modification
  desc: Detect IAM policy and role changes
  condition: >
    ct.name in ("PutRolePolicy", "DeleteRolePolicy", "UpdateAssumeRolePolicy",
    "PutUserPolicy", "DeleteUserPolicy", "CreateRole", "DeleteRole")
  output: >
    AWS IAM modification detected (event=%ct.name user=%ct.user)
  priority: CRITICAL
  source: aws_cloudtrail
  tags: [terraform, drift, iam, security]

まとめ・今後の展望

TFDrift-Falcoは、Falcoのプラグインシステムを活用することで terraform plan の定期実行では得られないリアルタイム性とコンテキスト(誰が・いつ・何を変更したか)を実現しています。

現在のv0.9.0では3大クラウド(AWS / GCP / Azure)に対応し、合計789以上のイベントを検知可能です。今後の開発ロードマップとしては以下を予定しています。

  • OPA/Rego統合(Policy-as-Code): ドリフトの重大度や対応方針をポリシーとしてコード化(次回記事で詳しく解説予定
  • Grafanaダッシュボード連携: Loki + Promtailによるログ集約とアラート可視化
  • v1.0.0 GA: 10,000+ノード対応のスケーラビリティ検証

リポジトリは以下からアクセスできます。スターやIssueでのフィードバックをお待ちしています。

👉 https://github.com/higakikeita/tfdrift-falco


この記事はTFDrift-Falcoシリーズの第1回です。第2回「OPA/Regoでドリフトポリシーをコード化する」、第3回「AWS/GCP/Azure 3クラウドのドリフト相関分析ダッシュボードを作った話」も予定しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?