インポートしたACM証明書の更新手順について

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

この記事では、Security Hubで検出された「[ACM.1] インポートされ ACM によって発行された証明書は、一定期間後に更新する必要があります」というセキュリティ課題の修正方法について解説します。

ポリシーの説明

ACM の Security Hub コントロール – AWS Security Hub

[ACM.1] インポートされ ACM によって発行された証明書は、一定期間後に更新する必要があります

このコントロールは、 AWS Certificate Manager (ACM) 証明書が指定された期間内に更新されているかどうかを確認します。インポートした証明書と ACM によって提供された証明書の両方をチェックします。指定された期間内に証明書が更新されない場合、コントロールは失敗します。更新期間に対してカスタムパラメータ値を指定しない限り、Security Hub はデフォルト値の 30 日を使用します。

AWS Certificate Manager (ACM) は、AWS環境で利用するSSL/TLS証明書の管理を容易にするサービスです。ACMが発行する証明書は、有効期限が近づくと自動的に更新されますが、ユーザーが外部の認証局から取得した証明書をACMにインポートした場合、その更新プロセスは自動化されません。これらのインポートされた証明書は、有効期限が切れる前に手動で新しい証明書と置き換える必要があります。更新を怠ると、ウェブサイトやアプリケーションへのアクセスに問題が生じ、セキュリティ上の懸念も引き起こします。

修復方法

有効期限が近づいている、または期限切れのインポートされたACM証明書を更新します。これには、新しい証明書をインポートし、関連するAWSリソース(ロードバランサー、CloudFrontディストリビューションなど)でその証明書を使用するように更新する手順が含まれます。

AWSコンソールでの修正手順

  1. AWS Certificate Manager > 証明書に移動します
  2. 「証明書をインポート」で各種証明書を記載し、証明書インポートを選択
  3. 「証明書一覧」でステータスが発行済みであることを確認する

Terraformでの修復手順

ACM証明書の自動更新と有効期限管理のためのTerraformコードと、重要な修正ポイントを説明します。

# ★重要: ACM Certificate
resource "aws_acm_certificate" "main" {
  domain_name               = var.domain_name
  subject_alternative_names = var.alternative_names

  # ★重要: 検証方法はDNSを推奨(メール検証より信頼性が高い)
  validation_method = "DNS"

  # ★重要: 証明書の自動更新を確実にするための設定
  lifecycle {
    create_before_destroy = true
  }

  tags = merge(var.tags, {
    Name = "cert-${var.environment}"
  })
}

# ★重要: DNS検証レコードの作成
resource "aws_route53_record" "cert_validation" {
  for_each = {
    for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = var.route53_zone_id
}

# ★重要: 証明書の検証完了を待機
resource "aws_acm_certificate_validation" "main" {
  certificate_arn         = aws_acm_certificate.main.arn
  validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}

# CloudWatch Metric Alarm for certificate expiry
resource "aws_cloudwatch_metric_alarm" "cert_expiry" {
  alarm_name          = "acm-cert-expiry-${var.environment}"
  comparison_operator = "LessThanThreshold"
  evaluation_periods  = "1"
  metric_name        = "DaysToExpiry"
  namespace          = "AWS/ACM"
  period             = "86400"  # 1 day
  statistic          = "Minimum"
  threshold          = var.expiry_threshold
  alarm_description  = "Certificate will expire in less than ${var.expiry_threshold} days"
  alarm_actions      = var.alarm_sns_topic_arns

  dimensions = {
    CertificateArn = aws_acm_certificate.main.arn
  }

  tags = var.tags
}

# SNS Topic for certificate alerts
resource "aws_sns_topic" "cert_alerts" {
  count = var.create_sns_topic ? 1 : 0
  name  = "acm-cert-alerts-${var.environment}"

  tags = var.tags
}

# SNS Topic Policy
resource "aws_sns_topic_policy" "cert_alerts" {
  count = var.create_sns_topic ? 1 : 0
  arn   = aws_sns_topic.cert_alerts[0].arn

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "AllowCloudWatchAlarms"
        Effect = "Allow"
        Principal = {
          Service = "cloudwatch.amazonaws.com"
        }
        Action   = "SNS:Publish"
        Resource = aws_sns_topic.cert_alerts[0].arn
      }
    ]
  })
}

# EventBridge Rule for monitoring certificate events
resource "aws_cloudwatch_event_rule" "cert_events" {
  name        = "acm-cert-events-${var.environment}"
  description = "Capture ACM certificate events"

  event_pattern = jsonencode({
    source      = ["aws.acm"]
    detail-type = [
      "ACM Certificate Approaching Expiration",
      "ACM Certificate Expired",
      "ACM Certificate Validation State Change"
    ]
  })

  tags = var.tags
}

# EventBridge Target
resource "aws_cloudwatch_event_target" "cert_events" {
  rule      = aws_cloudwatch_event_rule.cert_events.name
  target_id = "SendToSNS"
  arn       = var.create_sns_topic ? aws_sns_topic.cert_alerts[0].arn : var.existing_sns_topic_arn

  input_transformer {
    input_paths = {
      certificateArn = "$.detail.certificateArn"
      status        = "$.detail.status"
      domainName    = "$.detail.domainName"
    }
    input_template = "\\\\"ACM Certificate Event: Domain <domainName> (ARN: <certificateArn>) status changed to <status>\\\\""
  }
}

# Variables
variable "domain_name" {
  description = "Primary domain name for the certificate"
  type        = string
}

variable "alternative_names" {
  description = "List of alternative domain names"
  type        = list(string)
  default     = []
}

variable "route53_zone_id" {
  description = "Route53 hosted zone ID for DNS validation"
  type        = string
}

variable "environment" {
  description = "Environment name"
  type        = string
}

variable "expiry_threshold" {
  description = "Days before expiry to trigger alarm"
  type        = number
  default     = 30
}

variable "create_sns_topic" {
  description = "Whether to create a new SNS topic"
  type        = bool
  default     = true
}

variable "existing_sns_topic_arn" {
  description = "Existing SNS topic ARN if not creating new"
  type        = string
  default     = ""
}

variable "alarm_sns_topic_arns" {
  description = "List of SNS topic ARNs for CloudWatch alarms"
  type        = list(string)
  default     = []
}

variable "tags" {
  description = "Tags for resources"
  type        = map(string)
  default     = {}
}

# Outputs
output "certificate_arn" {
  description = "ARN of the created certificate"
  value       = aws_acm_certificate.main.arn
}

output "certificate_status" {
  description = "Status of the certificate"
  value       = aws_acm_certificate.main.status
}

output "domain_validation_options" {
  description = "Domain validation options"
  value       = aws_acm_certificate.main.domain_validation_options
}

主要な修正ポイントは以下の通りです:

  1. 証明書の自動更新設定(最重要):
resource "aws_acm_certificate" "main" {
  validation_method = "DNS"  # DNS検証を使用
  lifecycle {
    create_before_destroy = true  # 自動更新のために必要
  }
}

  1. DNS検証の自動化:
# DNS検証レコードの自動作成
resource "aws_route53_record" "cert_validation" {
  for_each = {
    for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => dvo
  }
}

  1. モニタリングとアラート:
# 有効期限の監視
resource "aws_cloudwatch_metric_alarm" "cert_expiry" {
  metric_name = "DaysToExpiry"
  threshold   = var.expiry_threshold
}

使用方法:

  1. 変数の設定:
# terraform.tfvars
domain_name      = "example.com"
alternative_names = ["*.example.com"]
route53_zone_id  = "Z1234567890"
environment      = "production"
expiry_threshold = 30

  1. 適用:
terraform init
terraform plan
terraform apply

重要な注意点:

  1. 検証方法の選択:
# DNS検証を推奨
validation_method = "DNS"

  1. 自動更新の確保:
lifecycle {
  create_before_destroy = true
}

  1. 監視と通知:
# EventBridge Rule
resource "aws_cloudwatch_event_rule" "cert_events" {
  event_pattern = jsonencode({
    source = ["aws.acm"]
    detail-type = [
      "ACM Certificate Approaching Expiration"
    ]
  })
}

追加のベストプラクティス:

  1. 複数ドメインの管理:
subject_alternative_names = var.alternative_names

  1. タグ付け:
tags = merge(var.tags, {
  Environment = var.environment
  AutoRenew = "true"
})

  1. 検証の確認:
resource "aws_acm_certificate_validation" "main" {
  certificate_arn = aws_acm_certificate.main.arn
}

この設定により:

  • 証明書の自動更新の確保
  • DNS検証の自動化
  • 有効期限の監視
  • アラートの自動通知

が実現されます。

注意:

  • DNS検証を使用するためにはRoute53のアクセス権限が必要
  • 証明書の更新には検証が必要
  • インポートされた証明書は自動更新されない

最後に

今回は、インポートされたACM証明書を更新する方法についてご紹介しました。インポートされた証明書の更新は手動で行う必要があるため、有効期限を定期的に確認し、計画的に更新作業を実施することが重要です。更新を怠るとサービス停止やセキュリティリスクにつながるため、注意が必要です。

こちらの内容の検出は弊社が提供するSecurifyのCSPM機能で簡単に検出及び管理する事が可能です。運用が非常にラクに出来る製品になっていますのでぜひ興味がある方はお問い合わせお待ちしております。

最後までお読みいただきありがとうございました。この記事が皆さんの役に立てば幸いです。

この記事をシェアする

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

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

料金プランを詳しく見る