• 作成:

AWS CDKを使ってAWS Private Certificate Authority(AWSプライベートCA)の認証機関を作成して有効化する

問題

AWS Private Certificate Authority(AWSプライベートCA)の認証機関を作成することになりました。

当然こういったものはAWS CDKを使って管理したいものです。短命な鍵オンリーなモードでもあるだけで月額50ドル飛んで行くそうなので、なおさらですね。

ただ、 aws-cdk-lib.aws_acmpcaでは、高水準な抽象化がされたクラスでの生成管理が出来なくて、 Cfnレベルのリソースを使う必要があってかなり調べるのに苦労したので、メモしておきます。

認証機関を作るだけでは使えない

認証機関を作るだけならば、 CfnCertificateAuthorityを、適当にnewしてやれば終了です。

ただそれだと保留中の認証機関が出来るだけで、ルートCAも作成されず、様々な操作が出来ません。

手動でwebコンソールをcdk deployの間に叩きに行くとかは絶対に避けたい話ですね。

なので以下の3つのクラスを連携させる必要があります。

ルートCA証明書を作成するためにはテンプレート引数が必要

コンソールでは「CA証明書をインストール」となっているのでそれをすれば良いのだろうと思って、 new CfnCertificateしたのですが、

The certificate authority is not in a valid state for issuing certificates (Service: AWSACMPCA; Status Code: 400; Error Code: InvalidStateException;)

というエラーに悩まされ続けてきました。

例外処理 - AWS Certificate Manager には確かにこのエラーが書かれているのですが、待機中の認証機関にが証明書を発行出来ないというわけで、でも待機を解くには証明書をインストールしないといけないというジレンマに陥りました。

もがき苦しんだ結果、 Creating and installing the CA certificate - AWS Private Certificate Authority で、 --template-arn arn:aws:acm-pca:::template/RootCACertificate/V1 が指定されているのを見て、 CAデフォルトの証明書を作るにはtemplateArnプロパティに特定のテンプレートの指定が必要だとやっと分かりました。

確かに書いてはあったんですが、誰もこれをやっていなかったのか、サンプルコードが全然転がってなくて該当の引数を見つけるのに苦労しました。

認証機関生成サンプルコード

引数の受け渡しとかもちょっと微妙にわかりにくいので、スタックの例を書いておきます。

import { Stack, StackProps } from "aws-cdk-lib";
import {
  CfnCertificate,
  CfnCertificateAuthority,
  CfnCertificateAuthorityActivation,
} from "aws-cdk-lib/aws-acmpca";
import { HostedZone } from "aws-cdk-lib/aws-route53";
import { Construct } from "constructs";

interface Props extends StackProps {
  rootZone: HostedZone;
}

/**
 * 短命限定で証明書を発行するAWS Private Certificate Authorityを立ち上げる。
 * 短命限定モードだと比較的安いとは言え、存在しているだけで月50ドルのそれなりのコストがかかる。
 */
export default class ShortLivedPrivateCaStack extends Stack {
  cfnCertificateAuthority: CfnCertificateAuthority;

  cfnCertificate: CfnCertificate;

  cfnCertificateAuthorityActivation: CfnCertificateAuthorityActivation;

  constructor(scope: Construct, id: string, props: Props) {
    super(scope, id, props);

    const { rootZone } = props;

    // 高水準なclassがfromで管理外から引っ張ってくる方法以外存在しないようなので、
    // 仕方なくCfnの低水準なAPIを利用。

    // 認証機関を生成。
    this.cfnCertificateAuthority = new CfnCertificateAuthority(
      this,
      "CertificateAuthority",
      {
        type: "ROOT", // サブドメインを利用しているわけではない。
        keyAlgorithm: "好きなアルゴリズムを指定してください",
        signingAlgorithm: "好きなアルゴリズムを指定してください",
        subject: {
          commonName: rootZone.zoneName,
          // 適当に証明書の情報を書いておいてください。
        },
        usageMode: "SHORT_LIVED_CERTIFICATE", // 短命専用モードを利用。
      },
    );

    // 認証機関向けにルート証明書を発行。
    this.cfnCertificate = new CfnCertificate(this, "Certificate", {
      certificateAuthorityArn: this.cfnCertificateAuthority.attrArn,
      certificateSigningRequest:
        this.cfnCertificateAuthority.attrCertificateSigningRequest,
      signingAlgorithm: this.cfnCertificateAuthority.signingAlgorithm,
      validity: {
        type: "YEARS",
        value: 証明書の長さを指定,
      },
      templateArn: "arn:aws:acm-pca:::template/RootCACertificate/V1",
    });

    // 認証機関を有効化。
    this.cfnCertificateAuthorityActivation =
      new CfnCertificateAuthorityActivation(
        this,
        "CertificateAuthorityActivation",
        {
          certificate: this.cfnCertificate.attrCertificate,
          certificateAuthorityArn: this.cfnCertificateAuthority.attrArn,
        },
      );
  }
}