Amazon DocumentDB クラスターの削除保護の有効化について

このブログシリーズ 「クラウドセキュリティ 実践集」 では、一般的なセキュリティ課題を取り上げ、「なぜ危険なのか?」 というリスクの解説から、 「どうやって直すのか?」 という具体的な修復手順(コンソール、AWS CLI、Terraformなど)まで、分かりやすく解説します。

この記事では、Amazon DocumentDB クラスターの削除保護が有効化されていない場合のリスクと、その対策について詳しく解説します。

ポリシーの説明

すべてのAmazon DocumentDBクラスターで削除保護を有効にすることを強く推奨します。これにより、偶発的なデータベース削除や不正なユーザーによる削除を防止できます。特に本番環境やミッションクリティカルなデータを扱うクラスターでは必須の設定です。削除保護の設定をAWS Configやカスタムスクリプトで定期的に監査し、重要なクラスターが確実に保護されていることを確認してください。また、クラスター削除のプロセスを文書化し、複数人の承認を必要とする手順を確立してください。

修復方法

AWSマネジメントコンソールでの修復手順

以下の手順で、DocumentDBクラスターの削除保護を有効にできます:

  1. AWSマネジメントコンソールにログインします。
  2. サービス一覧から「Amazon DocumentDB」を選択します。
  3. 左側のナビゲーションペインから「Clusters」を選択します。
  4. 削除保護を有効にしたいクラスターの名前をクリックします。
  5. 「Configuration」タブで現在の削除保護の状態を確認します。
  6. 「Actions」ドロップダウンメニューから「Modify」を選択します。
  7. 「Additional configuration」セクションまでスクロールします。
  8. 「Deletion protection」のチェックボックスをオンにします。
  9. ページ下部の「Continue」ボタンをクリックします。
  10. 変更内容を確認し、「Apply immediately」を選択して即座に変更を適用します。
  11. 「Modify cluster」ボタンをクリックして変更を確定します。

注意: 削除保護の有効化は即座に反映され、クラスターの再起動は不要です。

Terraformでの修復手順

DocumentDBクラスターの削除保護を有効にする包括的なTerraformコードです:

# DocumentDBクラスターパラメータグループ
resource "aws_docdb_cluster_parameter_group" "main" {
  family      = var.docdb_family  # DocumentDBバージョンに応じて動的に設定
  name        = "${var.cluster_identifier}-params"
  description = "DocumentDB cluster parameter group with comprehensive security settings"

  parameter {
    name  = "tls"
    value = "enabled"  # TLS暗号化を必須に
  }

  parameter {
    name  = "audit_logs"
    value = "enabled"  # 監査ログを有効化
  }

  parameter {
    name  = "profiler"
    value = "enabled"  # パフォーマンスプロファイラーを有効化
  }

  parameter {
    name  = "profiler_threshold_ms"
    value = var.profiler_threshold_ms  # スロークエリのログ閾値
  }

  # TTL監視の有効化(DocumentDB 4.0以降)
  dynamic "parameter" {
    for_each = var.docdb_family == "docdb4.0" || var.docdb_family == "docdb5.0" ? [1] : []
    content {
      name  = "ttl_monitor"
      value = "enabled"
    }
  }

  tags = var.tags

  lifecycle {
    create_before_destroy = true
  }
}

# サブネットグループ
resource "aws_docdb_subnet_group" "main" {
  name       = "${var.cluster_identifier}-subnet-group"
  subnet_ids = var.subnet_ids

  tags = merge(
    var.tags,
    {
      Name = "${var.cluster_identifier}-subnet-group"
    }
  )
}

# セキュリティグループ
resource "aws_security_group" "docdb" {
  name_prefix = "${var.cluster_identifier}-docdb-"
  description = "Security group for DocumentDB cluster"
  vpc_id      = var.vpc_id

  ingress {
    from_port       = 27017
    to_port         = 27017
    protocol        = "tcp"
    security_groups = var.allowed_security_groups
    description     = "Allow DocumentDB access from application security groups"
  }

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

  tags = merge(
    var.tags,
    {
      Name = "${var.cluster_identifier}-docdb-sg"
    }
  )

  lifecycle {
    create_before_destroy = true
  }
}

# DocumentDBクラスター
resource "aws_docdb_cluster" "main" {
  cluster_identifier              = var.cluster_identifier
  engine                         = "docdb"
  engine_version                 = var.engine_version
  master_username                = var.master_username
  master_password                = random_password.docdb_password.result
  db_cluster_parameter_group_name = aws_docdb_cluster_parameter_group.main.name

  # 削除保護を必ず有効化(最重要)
  deletion_protection = var.environment == "production" ? true : var.deletion_protection

  # バックアップ設定
  backup_retention_period = var.backup_retention_period  # 推奨: 7日以上
  preferred_backup_window = var.preferred_backup_window
  preferred_maintenance_window = var.preferred_maintenance_window

  # スナップショット設定
  skip_final_snapshot       = false  # 必ず最終スナップショットを作成
  final_snapshot_identifier = "${var.cluster_identifier}-final-${formatdate("YYYYMMDD-hhmmss", timestamp())}"
  copy_tags_to_snapshot     = true   # タグをスナップショットにコピー

  # 暗号化設定
  storage_encrypted = true
  kms_key_id       = var.kms_key_id != "" ? var.kms_key_id : aws_kms_key.docdb[0].arn

  # 監査ログの有効化
  enabled_cloudwatch_logs_exports = ["audit", "profiler"]

  # ネットワーク設定
  db_subnet_group_name   = aws_docdb_subnet_group.main.name
  vpc_security_group_ids = [aws_security_group.docdb.id]

  tags = merge(
    var.tags,
    {
      Name                = var.cluster_identifier
      DeletionProtection  = "Enabled"
      Environment        = var.environment
      Backup             = "Enabled"
    }
  )

  lifecycle {
    prevent_destroy = true  # Terraformレベルでも削除を防止
  }
}

# KMSキー(オプション)
resource "aws_kms_key" "docdb" {
  count                   = var.kms_key_id == "" ? 1 : 0
  description             = "KMS key for DocumentDB cluster ${var.cluster_identifier}"
  deletion_window_in_days = 30
  enable_key_rotation     = true

  tags = merge(
    var.tags,
    {
      Name = "${var.cluster_identifier}-docdb-kms"
    }
  )
}

resource "aws_kms_alias" "docdb" {
  count         = var.kms_key_id == "" ? 1 : 0
  name          = "alias/${var.cluster_identifier}-docdb"
  target_key_id = aws_kms_key.docdb[0].key_id
}

# パスワードの安全な生成
resource "random_password" "docdb_password" {
  length  = 32
  special = true

  # DocumentDBで使用できない特殊文字を除外
  override_special = "!#$%&*()-_=+[]{}<>:?"
}

# AWS Secrets Managerでパスワードを管理
resource "aws_secretsmanager_secret" "docdb_credentials" {
  name_prefix             = "${var.cluster_identifier}-docdb-"
  description            = "DocumentDB cluster master credentials"
  recovery_window_in_days = var.secret_recovery_window_days

  tags = var.tags
}

resource "aws_secretsmanager_secret_version" "docdb_credentials" {
  secret_id = aws_secretsmanager_secret.docdb_credentials.id
  secret_string = jsonencode({
    username = var.master_username
    password = random_password.docdb_password.result
    engine   = "docdb"
    host     = aws_docdb_cluster.main.endpoint
    port     = aws_docdb_cluster.main.port
    dbClusterIdentifier = aws_docdb_cluster.main.cluster_identifier
  })
}

# DocumentDBインスタンス
resource "aws_docdb_cluster_instance" "main" {
  count              = var.instance_count
  identifier         = "${var.cluster_identifier}-${count.index + 1}"
  cluster_identifier = aws_docdb_cluster.main.id
  instance_class     = var.instance_class

  performance_insights_enabled = var.performance_insights_enabled
  performance_insights_kms_key_id = var.performance_insights_enabled ? var.kms_key_id : null

  tags = merge(
    var.tags,
    {
      Name = "${var.cluster_identifier}-${count.index + 1}"
    }
  )
}

# CloudWatchアラーム(削除保護監視)
# 注意: DocumentDBには標準のDeletionProtectionメトリクスがないため、
# AWS ConfigまたはLambda関数で定期的にチェックする必要があります
resource "aws_lambda_function" "check_deletion_protection" {
  filename         = "check_deletion_protection.zip"
  function_name    = "${var.cluster_identifier}-check-deletion-protection"
  role            = aws_iam_role.lambda_execution.arn
  handler         = "index.handler"
  runtime         = "python3.9"
  timeout         = 60

  environment {
    variables = {
      CLUSTER_IDENTIFIER = var.cluster_identifier
      SNS_TOPIC_ARN     = var.sns_topic_arn
    }
  }

  tags = var.tags
}

# EventBridgeルールで定期実行(1時間ごと)
resource "aws_cloudwatch_event_rule" "check_deletion_protection" {
  name                = "${var.cluster_identifier}-check-deletion-protection"
  description         = "Check DocumentDB deletion protection status"
  schedule_expression = "rate(1 hour)"
  tags               = var.tags
}

resource "aws_cloudwatch_event_target" "lambda" {
  rule      = aws_cloudwatch_event_rule.check_deletion_protection.name
  target_id = "CheckDeletionProtectionLambda"
  arn       = aws_lambda_function.check_deletion_protection.arn
}

resource "aws_lambda_permission" "allow_eventbridge" {
  statement_id  = "AllowExecutionFromEventBridge"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.check_deletion_protection.function_name
  principal     = "events.amazonaws.com"
  source_arn    = aws_cloudwatch_event_rule.check_deletion_protection.arn
}

# 変数定義
variable "cluster_identifier" {
  type        = string
  description = "The cluster identifier"

  validation {
    condition     = can(regex("^[a-z][a-z0-9-]*$", var.cluster_identifier)) && length(var.cluster_identifier) <= 63
    error_message = "Cluster identifier must start with a letter, contain only lowercase letters, numbers, and hyphens, and be 63 characters or less."
  }
}

variable "docdb_family" {
  type        = string
  description = "DocumentDB parameter group family"
  default     = "docdb5.0"  # 最新バージョンをデフォルトに

  validation {
    condition     = contains(["docdb3.6", "docdb4.0", "docdb5.0"], var.docdb_family)
    error_message = "DocumentDB family must be one of: docdb3.6, docdb4.0, docdb5.0."
  }
}

variable "profiler_threshold_ms" {
  type        = number
  description = "Profiler threshold in milliseconds for slow query logging"
  default     = 100

  validation {
    condition     = var.profiler_threshold_ms >= 0 && var.profiler_threshold_ms <= 2147483647
    error_message = "Profiler threshold must be between 0 and 2147483647 milliseconds."
  }
}

variable "deletion_protection" {
  type        = bool
  description = "Enable deletion protection (automatically true for production)"
  default     = true
}

variable "environment" {
  type        = string
  description = "Environment (e.g., production, staging, development)"

  validation {
    condition     = contains(["production", "staging", "development"], var.environment)
    error_message = "Environment must be production, staging, or development."
  }
}

variable "backup_retention_period" {
  type        = number
  description = "The backup retention period in days"
  default     = 7

  validation {
    condition     = var.backup_retention_period >= 1 && var.backup_retention_period <= 35
    error_message = "Backup retention period must be between 1 and 35 days."
  }
}

variable "instance_count" {
  type        = number
  description = "Number of instances in the cluster"
  default     = 2

  validation {
    condition     = var.instance_count >= 1
    error_message = "Instance count must be at least 1."
  }
}

variable "tags" {
  type        = map(string)
  description = "A map of tags to assign to the resource"
  default     = {}
}

variable "sns_topic_arn" {
  type        = string
  description = "SNS topic ARN for alarm notifications"
}

variable "engine_version" {
  type        = string
  description = "DocumentDB engine version"
  default     = "5.0.0"
}

variable "master_username" {
  type        = string
  description = "Master username for DocumentDB cluster"
  default     = "docdbadmin"
  sensitive   = true
}

variable "kms_key_id" {
  type        = string
  description = "KMS key ID for encryption (if empty, a new key will be created)"
  default     = ""
}

variable "subnet_ids" {
  type        = list(string)
  description = "List of subnet IDs for the DB subnet group"
}

variable "vpc_id" {
  type        = string
  description = "VPC ID for the security group"
}

variable "allowed_security_groups" {
  type        = list(string)
  description = "List of security group IDs allowed to access DocumentDB"
  default     = []
}

variable "instance_class" {
  type        = string
  description = "Instance class for DocumentDB instances"
  default     = "db.r5.large"
}

variable "preferred_backup_window" {
  type        = string
  description = "Preferred backup window"
  default     = "03:00-04:00"
}

variable "preferred_maintenance_window" {
  type        = string
  description = "Preferred maintenance window"
  default     = "sun:04:00-sun:05:00"
}

variable "secret_recovery_window_days" {
  type        = number
  description = "Recovery window for secret deletion"
  default     = 7
}

variable "performance_insights_enabled" {
  type        = bool
  description = "Enable Performance Insights"
  default     = true
}

# 出力
output "cluster_endpoint" {
  value       = aws_docdb_cluster.main.endpoint
  description = "The cluster endpoint"
}

output "cluster_reader_endpoint" {
  value       = aws_docdb_cluster.main.reader_endpoint
  description = "The cluster reader endpoint"
}

output "deletion_protection_status" {
  value       = aws_docdb_cluster.main.deletion_protection
  description = "The deletion protection status"
}

output "secret_arn" {
  value       = aws_secretsmanager_secret.docdb_credentials.arn
  description = "The ARN of the secret containing the database credentials"
  sensitive   = true
}

 

重要なポイント

  1. 多層防御: deletion_protection = truelifecycle { prevent_destroy = true }の両方を設定
  2. バックアップ戦略: 自動バックアップと最終スナップショットの両方を設定
  3. セキュリティ強化: KMS暗号化、TLS必須、監査ログ有効化
  4. 監視とアラート: 削除保護が無効化された場合のCloudWatchアラーム
  5. 適切なバリデーション: 入力変数の検証で誤設定を防止

最後に

この記事では、Amazon DocumentDB クラスターの削除保護が有効化されていない場合のリスクと、具体的な修復方法について解説しました。削除保護は、重要なデータベースを偶発的または悪意のある削除から守る重要なセキュリティ機能です。

この問題の検出は弊社が提供するSecurifyのCSPM機能で簡単に検出及び管理することが可能です。 運用が非常に楽にできる製品になっていますので、ぜひ興味がある方はお問い合わせお待ちしております。 最後までお読みいただきありがとうございました。この記事が皆さんの役に立てば幸いです。

参考情報

この記事をシェアする

クラウドセキュリティ対策実践集一覧へ戻る

貴社の利用状況に合わせた見積もりを作成します。

料金プランを詳しく見る