Amazon Redshiftクラスターの保存時暗号化設定について

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

この記事では、Amazon Redshiftクラスターの保存時暗号化の有効化について、リスクと対策を解説します。

ポリシーの説明

Amazon Redshiftは、ペタバイト規模のデータウェアハウスサービスとして、企業の重要なビジネスデータを格納・分析するために使用されます。金融データ、医療記録、個人情報などの機密性の高いデータを扱うサービスにおいて、保存時暗号化は必須のセキュリティ対策です。

Redshiftの保存時暗号化は、AES-256暗号化アルゴリズムを使用して、クラスター内のすべてのデータを暗号化します。

  • FIPS 140-2 Level 2認定のHSMによるキー保護

暗号化はAES-256ハードウェアアクセラレーションを使用して実行されるため、パフォーマンスへの影響は最小限(1-3%程度)です。

修復方法

コンソールでの修復手順

重要:既存の非暗号化Redshiftクラスターでは暗号化を直接有効化できません。暗号化された新しいクラスターへの移行が必須です。

方法1: スナップショットを使用した移行

  1. Redshiftコンソールhttps://console.aws.amazon.com/redshiftv2/)にアクセスし、対象のクラスターを選択します。
  2. 移行前の準備
    • 現在のクラスター設定を記録(ノードタイプ、ノード数、パラメータグループ等)
    • メンテナンスウィンドウを設定し、アプリケーションへ通知
    • 移行中のダウンタイムを最小化するための計画を立案
  3. スナップショットの作成
    • 「アクション」→「スナップショットの作成」を選択
    • スナップショット識別子を入力(例:encryption-migration-$(date +%Y%m%d-%H%M%S)
    • 「スナップショットの作成」をクリック
  4. スナップショットの完了を待機
    • スナップショットのステータスが「available」になるまで待ちます
    • 大規模クラスターの場合、数十分から数時間かかる可能性があります
  5. 暗号化されたクラスターの作成
    • スナップショット一覧から作成したスナップショットを選択
    • 「アクション」→「スナップショットから復元」を選択
    • 新しいクラスター識別子を入力(例:original-cluster-name-encrypted
    • 「データベース構成」セクション
      • データベース名、ポート、パラメータグループを確認
    • 「暗号化」セクションで以下を設定:
      • 「データベースの暗号化」: チェックを入れる
      • 「AWS KMSキー」: 以下のいずれかを選択
        • AWS管理キーaws/redshift(簡単だが管理権限が限定的)
        • カスタマー管理キー:事前に作成したKMSキー(推奨)
    • 「クラスターの詳細」セクション
      • ノードタイプ、ノード数は元のクラスターと同じ設定を使用
    • 「ネットワークとセキュリティ」セクション
      • VPC、サブネットグループ、セキュリティグループを確認
      • 「パブリックアクセス可能」: 無効(セキュリティ上推奨)
    • 「クラスターを復元」をクリック
  6. 移行の検証と切り替え
    • 新しいクラスターのエンドポイントを取得
    • テスト環境で接続テストを実施
    • データ整合性を確認(レコード数、サンプルデータの照合)
    • アプリケーションの接続文字列を更新
    • パフォーマンスを監視し、問題がないことを確認
  7. 古いクラスターの削除
    • 完全な移行確認後、24-48時間待機
    • 最終スナップショットを作成(ロールバック用)
    • 古い非暗号化クラスターを削除

Terraformでの修復手順

暗号化を有効にしたRedshiftクラスターを作成するTerraformコードと、ベストプラクティスを説明します。

# データソース
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

# KMSキーの作成(カスタムキーを使用する場合)
resource "aws_kms_key" "redshift" {
  description             = "KMS key for Redshift cluster encryption"
  deletion_window_in_days = 30  # 本番環境では30日を推奨(最小7日、最大30日)
  enable_key_rotation     = true  # 年次で自動ローテーション
  multi_region           = false  # コスト削減のためシングルリージョン

  tags = merge(
    var.common_tags,
    {
      Name        = "redshift-encryption-key"
      Purpose     = "Redshift cluster encryption"
      Environment = var.environment
    }
  )
}

resource "aws_kms_alias" "redshift" {
  name          = "alias/redshift-encryption"
  target_key_id = aws_kms_key.redshift.key_id
}

# KMSキーポリシー
resource "aws_kms_key_policy" "redshift" {
  key_id = aws_kms_key.redshift.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "Enable IAM User Permissions"
        Effect = "Allow"
        Principal = {
          AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
        }
        Action   = "kms:*"
        Resource = "*"
      },
      {
        Sid    = "Allow Redshift to use the key"
        Effect = "Allow"
        Principal = {
          Service = "redshift.amazonaws.com"
        }
        Action = [
          "kms:Decrypt",
          "kms:Encrypt",
          "kms:ReEncrypt*",
          "kms:GenerateDataKey*",
          "kms:CreateGrant",
          "kms:DescribeKey"
        ]
        Resource = "*"
        Condition = {
          StringEquals = {
            "kms:ViaService" = "redshift.${data.aws_region.current.name}.amazonaws.com"
          }
        }
      }
    ]
  })
}

# Redshiftサブネットグループ
resource "aws_redshift_subnet_group" "main" {
  name       = "redshift-subnet-group"
  subnet_ids = var.subnet_ids

  tags = {
    Name = "Redshift subnet group"
  }
}

# Redshiftパラメータグループ(セキュリティ強化)
resource "aws_redshift_parameter_group" "secure" {
  name   = "${var.cluster_identifier}-secure-params"
  family = "redshift-1.0"

  parameter {
    name  = "require_ssl"
    value = "true"  # SSL/TLS接続を強制
  }

  parameter {
    name  = "enable_user_activity_logging"
    value = "true"  # ユーザーアクティビティログを有効化
  }

  parameter {
    name  = "statement_timeout"
    value = "43200000"  # 12時間(ミリ秒)
  }

  parameter {
    name  = "max_concurrency_scaling_clusters"
    value = var.enable_concurrency_scaling ? "1" : "0"
  }

  parameter {
    name  = "use_fips_ssl"
    value = "true"  # FIPS 140-2準拠のSSLを使用
  }

  tags = var.common_tags
}

# セキュリティグループ
resource "aws_security_group" "redshift" {
  name        = "redshift-cluster-sg"
  description = "Security group for Redshift cluster"
  vpc_id      = var.vpc_id

  ingress {
    from_port   = 5439
    to_port     = 5439
    protocol    = "tcp"
    cidr_blocks = var.allowed_cidr_blocks
    description = "Redshift port"
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "Redshift Security Group"
  }
}

# 暗号化されたRedshiftクラスター
resource "aws_redshift_cluster" "encrypted" {
  cluster_identifier = var.cluster_identifier
  database_name      = var.database_name
  master_username    = var.master_username
  master_password    = var.master_password  # AWS Secrets Managerの使用を推奨
  node_type          = var.node_type
  cluster_type       = var.number_of_nodes > 1 ? "multi-node" : "single-node"
  number_of_nodes    = var.number_of_nodes

  # 暗号化設定(必須)
  encrypted                  = true  # 必ず true に設定
  kms_key_id                = aws_kms_key.redshift.arn  # カスタムKMSキーを使用

  # ネットワーク設定
  cluster_subnet_group_name  = aws_redshift_subnet_group.main.name
  vpc_security_group_ids     = [aws_security_group.redshift.id]
  publicly_accessible        = false

  # パラメータグループ
  cluster_parameter_group_name = aws_redshift_parameter_group.secure.name

  # バックアップ設定
  automated_snapshot_retention_period = var.snapshot_retention_days
  preferred_maintenance_window        = var.maintenance_window
  snapshot_cluster_identifier         = var.source_cluster_id  # スナップショットからの復元時に使用

  # 監査ログ(コンプライアンスのため必須)
  logging {
    enable        = true
    bucket_name   = var.audit_log_bucket
    s3_key_prefix = "redshift-logs/${var.environment}/${var.cluster_identifier}/"
  }

  # 拡張VPC ルーティング(セキュリティ強化)
  enhanced_vpc_routing = true

  # スナップショットコピー(別リージョンへの暗号化コピー)
  dynamic "snapshot_copy" {
    for_each = var.enable_snapshot_copy ? [1] : []
    content {
      destination_region = var.snapshot_copy_region
      retention_period   = var.snapshot_copy_retention
      grant_name        = aws_redshift_snapshot_copy_grant.main[0].snapshot_copy_grant_name
    }
  }

  tags = {
    Name        = "Encrypted Redshift Cluster"
    Environment = var.environment
    Encrypted   = "true"
  }

  # 削除保護
  skip_final_snapshot       = false
  final_snapshot_identifier = "${var.cluster_identifier}-final-${formatdate("YYYY-MM-DD-hhmm", timestamp())}"
  allow_version_upgrade     = true
  apply_immediately        = false  # メンテナンスウィンドウで適用

  lifecycle {
    prevent_destroy = true
  }
}

# スナップショットコピーグラント(クロスリージョン暗号化コピー用)
resource "aws_redshift_snapshot_copy_grant" "main" {
  count = var.enable_snapshot_copy ? 1 : 0

  snapshot_copy_grant_name = "${var.cluster_identifier}-copy-grant"
  kms_key_id              = var.snapshot_copy_kms_key_id
}

# 既存クラスターからの移行用モジュール
module "migration" {
  source = "./modules/redshift-migration"
  count  = var.migrate_from_existing ? 1 : 0

  source_cluster_id      = var.source_cluster_id
  target_cluster_id      = aws_redshift_cluster.encrypted.id
  snapshot_retention     = 7
  migration_window_start = "02:00"
  migration_window_end   = "06:00"

  tags = var.common_tags
}

# 移行ステータスのチェック
data "aws_redshift_cluster" "source" {
  count              = var.migrate_from_existing ? 1 : 0
  cluster_identifier = var.source_cluster_id
}

resource "aws_cloudwatch_metric_alarm" "migration_status" {
  count               = var.migrate_from_existing ? 1 : 0
  alarm_name          = "${var.cluster_identifier}-migration-status"
  comparison_operator = "LessThanThreshold"
  evaluation_periods  = "1"
  metric_name        = "HealthStatus"
  namespace          = "AWS/Redshift"
  period             = "300"
  statistic          = "Average"
  threshold          = "1"
  alarm_description  = "Alert when migration cluster is unhealthy"

  dimensions = {
    ClusterIdentifier = aws_redshift_cluster.encrypted.id
  }

  alarm_actions = [var.sns_topic_arn]
}

# CloudWatchアラーム(暗号化ステータス監視)
resource "aws_cloudwatch_metric_alarm" "encryption_status" {
  alarm_name          = "redshift-encryption-check"
  comparison_operator = "LessThanThreshold"
  evaluation_periods  = "1"
  metric_name         = "ClusterEncrypted"
  namespace           = "AWS/Redshift"
  period              = "300"
  statistic           = "Average"
  threshold           = "1"
  alarm_description   = "Alert when Redshift cluster is not encrypted"

  dimensions = {
    ClusterIdentifier = aws_redshift_cluster.encrypted.id
  }

  alarm_actions = [var.sns_topic_arn]
}

# Secrets Manager でパスワードを管理(推奨)
resource "aws_secretsmanager_secret" "redshift_master" {
  name = "${var.cluster_identifier}-master-password"

  rotation_rules {
    automatically_after_days = 30
  }
}

resource "aws_secretsmanager_secret_version" "redshift_master" {
  secret_id     = aws_secretsmanager_secret.redshift_master.id
  secret_string = jsonencode({
    username = var.master_username
    password = random_password.master.result
    engine   = "redshift"
    host     = aws_redshift_cluster.encrypted.endpoint
    port     = 5439
    dbname   = var.database_name
  })
}

resource "random_password" "master" {
  length  = 32
  special = true
}

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

variable "database_name" {
  description = "Database name"
  type        = string
  default     = "mydb"
}

variable "master_username" {
  description = "Master username"
  type        = string
  default     = "admin"
  sensitive   = true
}

variable "master_password" {
  description = "Master password"
  type        = string
  sensitive   = true
}

variable "node_type" {
  description = "Node type"
  type        = string
  default     = "ra3.xlplus"  # RA3インスタンスを推奨(ストレージとコンピュートの分離)
  validation {
    condition     = contains(["dc2.large", "dc2.8xlarge", "ra3.xlplus", "ra3.4xlarge", "ra3.16xlarge"], var.node_type)
    error_message = "Invalid node type. Choose from dc2 or ra3 instance families."
  }
}

variable "number_of_nodes" {
  description = "Number of nodes"
  type        = number
  default     = 2
}

variable "snapshot_retention_days" {
  description = "Automated snapshot retention period in days"
  type        = number
  default     = 7
  validation {
    condition     = var.snapshot_retention_days >= 1 && var.snapshot_retention_days <= 35
    error_message = "Snapshot retention must be between 1 and 35 days."
  }
}

variable "maintenance_window" {
  description = "Preferred maintenance window"
  type        = string
  default     = "sun:02:00-sun:06:00"
}

variable "enable_concurrency_scaling" {
  description = "Enable concurrency scaling"
  type        = bool
  default     = false
}

variable "source_cluster_id" {
  description = "Source cluster ID for migration"
  type        = string
  default     = ""
}

variable "audit_log_bucket" {
  description = "S3 bucket for audit logs"
  type        = string
}

variable "environment" {
  description = "Environment name"
  type        = string
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "vpc_id" {
  description = "VPC ID"
  type        = string
}

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

variable "allowed_cidr_blocks" {
  description = "CIDR blocks allowed to connect to Redshift"
  type        = list(string)
  default     = []
}

variable "common_tags" {
  description = "Common tags for all resources"
  type        = map(string)
  default     = {}
}

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

variable "enable_snapshot_copy" {
  description = "Enable cross-region snapshot copy"
  type        = bool
  default     = false
}

variable "snapshot_copy_region" {
  description = "Destination region for snapshot copy"
  type        = string
  default     = ""
}

variable "snapshot_copy_retention" {
  description = "Retention period for copied snapshots"
  type        = number
  default     = 7
}

variable "snapshot_copy_kms_key_id" {
  description = "KMS key ID in destination region"
  type        = string
  default     = ""
}

variable "migrate_from_existing" {
  description = "Flag to migrate from existing cluster"
  type        = bool
  default     = false
}

# 出力
output "cluster_endpoint" {
  value       = aws_redshift_cluster.encrypted.endpoint
  description = "Redshift cluster endpoint"
  sensitive   = true
}

output "cluster_encrypted" {
  value       = aws_redshift_cluster.encrypted.encrypted
  description = "Encryption status of the cluster"
}

output "kms_key_id" {
  value       = aws_kms_key.redshift.id
  description = "KMS key ID used for encryption"
}

 

主要な修正ポイント

  1. encrypted = true:
    • Redshiftクラスターリソースで暗号化を明示的に有効化
    • この設定はクラスター作成時のみ指定可能(後から変更不可)
  2. kms_key_id:
    • カスタムKMSキーを使用することでキー管理の柔軟性を確保
    • キーポリシーでRedshiftサービスに必要な権限を付与
    • CloudTrailでキー使用を監査可能
  3. enhanced_vpc_routing:
    • すべてのCOPY/UNLOADトラフィックがVPCを経由
    • S3へのアクセスはVPCエンドポイント経由
    • ネットワークトラフィックの完全な制御が可能
  4. logging:
    • 監査ログをS3バケットに保存
    • ユーザーアクティビティ、接続ログ、ユーザーログを記録
    • AWS Athenaでログ分析が可能
  5. lifecycle prevent_destroy:
    • Terraformでの誤削除を防止
    • 本番環境では必須の設定
    • 削除前に明示的な確認が必要

最後に

この記事では、Amazon Redshiftクラスターの保存時暗号化の有効化について、詳細なリスク分析と実践的な対策を解説しました。Redshiftの暗号化は、データウェアハウスに格納された機密データを保護するための基本的かつ重要なセキュリティ対策です。

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

参考資料

 

この記事をシェアする

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

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

料金プランを詳しく見る