[ACM] AWSの無料サーバ証明書の発行と組込をCloudFormationで試してみた

2016.08.11

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

AWSチームのすずきです。

先日、2016年8月9日のアップデートにより、CloudFormation が、AWSのサーバ証明書発行サービスACM(AWS Certificate Manager)に対応し、 無料のサーバ証明書の作成と、ELB、CloudFrontへのサーバ証明書の組み込みを、CloudFormationで実施する事が可能になりました。

今回、その内容について紹介させていただきます。

ACM サーバ証明書の作成

以下のCloudFormationテンプレートを利用して、サーバ証明書を作成します。

JSONテンプレート

  • acm-test.json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "ACM test 2016-08-09",
  "Parameters": {
    "DomainName": {
      "Description": "FQDN of the certificate being requested.",
      "Type": "String",
      "Default": ""
    },
    "ValidationDomain": {
      "Description": "The domain to which validation email is sent.",
      "Type": "String",
      "Default": ""
    }
  },
  "Resources": {
    "ACMCertificate": {
      "Type": "AWS::CertificateManager::Certificate",
      "Properties": {
        "DomainName": { "Fn::Join" : [ "", [ "*.", { "Ref" : "DomainName"} ]]},
        "SubjectAlternativeNames": [{ "Ref": "DomainName" }],
        "DomainValidationOptions": [{
          "DomainName": { "Fn::Join" : [ "", [ "*.", { "Ref" : "DomainName"} ]]},
          "ValidationDomain": { "Ref": "ValidationDomain" }
        }]
      }
    }
  }
}

CloudFormation操作

テンプレート指定

  • ACM設定用のJSONテンプレートを指定します

cfn-support-acm-01

パラメータ指定

スタック名
  • 任意の名称とします
DomainName
  • 発行するサーバ証明書のドメイン(FQDN)を指定します。
  • コモンネームは「*.DomainName」、ワイルドカード証明書とします。
  • ZoneApex、ホスト名省略したアドレスにも対応する証明書とするため、SAN(SubjectAlternativeNames)指定を行います。
ValidationDomain
  • メールによるドメイン認証対象となるFQDNを指定します
  • 通常「DomainName」と同一の値を指定します
  • 証明書の発行対象がサブドメインの場合、親ドメインの指定も可能です

cfn-support-acm-02

オプション

  • 今回は利用しません

cfn-support-acm-03

確認

cfn-support-acm-04

ACM ステータス確認

  • CloudFromationで作成したサーバ証明書、Statusは「Pending validation」承認待ちである事を確認します
  • 承認メールの通知先は、Detailed statusで確認する事が可能です

cfn-support-acm-05

メール承認

  • ドメイン管理者宛に届くメールのリンクより、承認画面を開きます

cfn-support-acm-06

  • ブラウザを開き、承認操作実施します

cfn-support-acm-07

  • ACMのStatus 「Issued」となった事を確認します。

cfn-support-acm-08

CloudFormation 確認

  • CloudFromation 、タイムアウトする前にドメイン承認が実施されると、「CreateComplete」となります。

cfn-support-acm-09

ELB利用

  • ACMで作成した証明書のARNは、「"Ref":リソース名」で取得可能です
  • ELBのリスナー設定、SSLCertificateIdに、ACMで作成した証明書のARNを指定して利用します

テンプレート抜粋

"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
"Properties": {
  "Listeners": [
    {
      "InstancePort": "80",
      "LoadBalancerPort": "443",
      "Protocol": "HTTPS",
      "InstanceProtocol": "HTTP",
      "SSLCertificateId": { "Ref" : "ACMCertificate" }
    }
  ]
}

CloudFront利用

  • CloudFrontはバージニア(us-east-1)で作成したACM証明書を利用します。
  • ACMの証明書作成するCloudFormationはバージニアでの実行が必要です。
  • AcmCertificateArn にACMで作成した証明書のARNを指定して利用します。
  • CloudFrontのSSLは、追加費用不要なSNI(sni-only)を指定します。

テンプレート抜粋

"Type": "AWS::CloudFront::Distribution",
"Properties": {
  "DistributionConfig": {
    "ViewerCertificate": {
      "SslSupportMethod": "sni-only",
      "AcmCertificateArn": { "Ref": "ACMCertificate"}
    }
  }
}

まとめ

今回のCloudFromationのアップデート以前、ELB、CloudFrontにACMで発行した サーバ証明書を利用するためには、AWSコンソール、CLIによる手動操作が必須、 作業ミスや、CloudFomationをUpdateStackで更新する際の干渉などにも注意が必要でした。

ACMを利用して、サーバ証明書の有効期限や鍵の管理をAWSに任せる事で、 従来の証明書と比較し、管理、運用に費やす工数も大幅に抑制する事が可能です。 今回のCloudFormationのアップデートにより、更に使いやすくなったACM、ぜひご活用ください。

また、現在ACMのドメイン認証はメールのみ、DNSのTXTレコードなどによる認証が待たれますが、 現在の方式でも、SESのメール受信とLambdaの組み合わせで自動化ができそう。 こちら成功したら、改めて紹介させて頂きたいと思います。

テンプレート例

  • Route53、Alias、CNAMEなどのDNSレコード設定は別途必要です

ELB用

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "ACM ELB test 2016-08-09",
  "Metadata" : {
    "AWS::CloudFormation::Interface" : {
      "ParameterGroups" : [
        {
          "Label" : { "default": "ACM Configuration" },
          "Parameters" : [ "DomainName", "ValidationDomain" ]
        },
        {
          "Label" : { "default" : "ELB Configuration" },
          "Parameters" : [ "DummyElbSubnet" ]
        }
      ],
      "ParameterLabels" : {
        "DomainName" : { "default" : "Domain Name" },
        "ValidationDomain" : { "default" : "Validation Domain Name" }
      }
    }
  },
  "Parameters": {
    "DomainName": {
      "Description": "FQDN of the certificate being requested.",
      "Type": "String",
      "Default": ""
    },
    "ValidationDomain": {
      "Description": "The domain to which validation email is sent.",
      "Type": "String",
      "Default": ""
    },
    "DummyElbSubnets" : {
      "Description" : "VPC subnets List (ELB)",
      "Type" : "List<AWS::EC2::Subnet::Id>"
    }
  },
  "Resources": {
    "ACMCertificate": {
      "Type": "AWS::CertificateManager::Certificate",
      "Properties": {
        "DomainName": { "Fn::Join" : [ "", [ "*.", { "Ref" : "DomainName"} ]]},
        "SubjectAlternativeNames": [{ "Ref": "DomainName" }],
        "DomainValidationOptions": [{
          "DomainName": { "Fn::Join" : [ "", [ "*.", { "Ref" : "DomainName"} ]]},
          "ValidationDomain": { "Ref": "ValidationDomain" }
        }]
      }
    },
    "DummyElb": {
      "DependsOn" : "ACMCertificate",
      "Type": "AWS::ElasticLoadBalancing::LoadBalancer",
      "Properties": {
        "Subnets": { "Ref" : "DummyElbSubnets" },
        "Listeners": [
          {
            "InstancePort": "80",
            "LoadBalancerPort": "443",
            "Protocol": "HTTPS",
            "InstanceProtocol": "HTTP",
            "SSLCertificateId": { "Ref" : "ACMCertificate" }
          }
        ],
        "Instances": []
      }
    }
  },
  "Outputs": {
    "ACMCertificate": {
      "Description": "ACMCertificate ARN",
      "Value": { "Ref": "ACMCertificate" }
    }
  }
}

CloudFront

  • バージニア(us-east-1)で実行
  • OriginDomainNameには、CloudFrontのカスタムオリジンとするFQDNを指定します。
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "ACM CloudFront test 2016-08-09",
  "Metadata": {
    "AWS::CloudFormation::Interface": {
      "ParameterGroups": [
        {
          "Label": { "default": "ACM Configuration" },
          "Parameters": [ "DomainName", "ValidationDomain" ]
        },
        {
          "Label": { "default": "CloudFront Configuration" },
          "Parameters": [ "OriginDomainName" ]
        }
      ],
      "ParameterLabels": {
        "DomainName": { "default": "Domain Name" },
        "ValidationDomain": { "default": "Validation Domain Name" },
        "OriginDomainName": { "default": "Origin Domain Name" }
      }
    }
  },
  "Parameters": {
    "DomainName": {
      "Description": "FQDN of the certificate being requested.",
      "Type": "String",
      "Default": ""
    },
    "ValidationDomain": {
      "Description": "The domain to which validation email is sent.",
      "Type": "String",
      "Default": ""
    },
    "OriginDomainName": {
      "Description": "Origin information to specify a custom origin.",
      "Type": "String",
      "Default": ""
    }
  },
  "Resources": {
    "ACMCertificate": {
      "Type": "AWS::CertificateManager::Certificate",
      "Properties": {
        "DomainName": {
          "Ref": "DomainName"
        },
        "DomainValidationOptions": [
          {
            "DomainName": { "Ref": "DomainName" },
            "ValidationDomain": { "Ref": "ValidationDomain" }
          }
        ]
      }
    },
    "DummyCloudFrontDistribution": {
      "DependsOn" : "ACMCertificate",
      "Type": "AWS::CloudFront::Distribution",
      "Properties": {
        "DistributionConfig": {
          "Origins": [
            {
              "DomainName": { "Ref": "OriginDomainName" },
              "Id": "DeliveryOrigin",
              "CustomOriginConfig": {
                "HTTPPort": "80",
                "HTTPSPort": "443",
                "OriginProtocolPolicy": "http-only"
              }
            }
          ],
          "Enabled": "true",
          "Comment": { "Ref": "AWS::StackId" },
          "Aliases": [{ "Ref": "DomainName"}],
          "DefaultCacheBehavior": {
            "TargetOriginId": "DeliveryOrigin",
            "SmoothStreaming": "false",
            "ForwardedValues": {
              "QueryString": "false",
              "Cookies": { "Forward": "all" }
            },
            "ViewerProtocolPolicy": "allow-all"
          },
          "ViewerCertificate": {
            "SslSupportMethod": "sni-only",
            "AcmCertificateArn": { "Ref": "ACMCertificate"}
          }
        }
      }
    }
  },
  "Outputs": {
    "ACMCertificate": {
      "Description": "ACMCertificate ARN",
      "Value": { "Ref": "ACMCertificate" }
    }
  }
}