AWS Private CA のマルチアカウント CA 階層設計について考える

AWS Private CA のマルチアカウント CA 階層設計について考える

2025.11.10

はじめに

前回の記事では、AWS Private CA の証明書発行方法 (API 経由と ACM 連携) を比較しました。

https://dev.classmethod.jp/articles/aws-private-ca-certificate-issuance-comparison/

今回は実運用を見据え、「CA をどう構成すべきか」という設計面に焦点を当てます。

ルート CA が侵害されると全証明書の信頼性が失われるため、AWS ではマルチアカウント構成による CA 階層設計がベストプラクティスとして推奨されています。
本記事では、なぜこの構成が推奨されるのか、メリット・デメリットを整理し、実際に構築して気づいた点を共有します。

CA 階層とは

CA 階層は、PKI において信頼の連鎖を構築する仕組みです。
ルート CA → 中間 CA → エンドエンティティ証明書という階層構造を持ち、証明書の信頼性は証明書チェーンを遡って検証されます。

AWS のベストプラクティスでは、ルート CA は中間 CA 証明書の発行のみに使用し、エンドエンティティ証明書は中間 CA から発行することが推奨されています。

https://docs.aws.amazon.com/privateca/latest/userguide/ca-best-practices.html

CA 階層の構造をイメージ図にすると以下のようになります。

ca.png

これにより、ルート CA を厳重に保護しつつ、日常的な証明書発行は中間 CA で行う役割分担が実現できます。

マルチアカウント構成の設計

アーキテクチャ概要

AWS Well-Architected Framework では、CA 階層の各レベルを別アカウントに配置することを推奨しています。

https://docs.aws.amazon.com/wellarchitected/latest/framework/sec_protect_data_transit_key_cert_mgmt.html

今回は以下のような構成で実装してみました。

cross-account-1.png

AWS RAM によるクロスアカウント共有

中間 CA 証明書をルート CA に発行してもらうには、AWS Resource Access Manager (RAM) などを使用してルート CA を共有する必要があります。
RAM でマネージド型アクセス許可を関連付けることができますが、ポリシーの選択には注意が必要です。
今回の場合、中間 CA からルート CA に acm-pca:IssueCertificate で証明書を発行してもらう必要があります。
そのため、下位 CA に署名するときのテンプレート SubordinateCACertificate_PathLen0/V1 に許可しているポリシーを選びました。

resource-access-manager-1.png

Private CA におけるリソースベースポリシーについては以下の記事を参照してください。

https://dev.classmethod.jp/articles/managed-aws-private-ca-access-on-aws-ram/

パスレングス制約について

なお、中間 CA 証明書発行時に SubordinateCACertificate_PathLen0/V1 テンプレートを使用すると、パスレングスが 0 に設定されます。
これにより、中間 CA からは更なる下位 CA を作成できず、エンドエンティティ証明書のみ発行可能になります。
パスレングス制約は証明書作成後に変更できないため、設計段階で考慮すべきポイントになりますね。

ルート CA と中間 CA の準備

実際にマルチアカウント構成で CA 階層を構築しました。

ルート CA は前回の記事で作成したものをそのまま使います。
RAM などによるリソースの共有設定が済んでいれば、以下のように中間 CA を作成したアカウントでもルート CA の情報を閲覧できるようになります。

private-ca-1.png

中間 CA は以下のようにしておきました。CN は intermediate.example.com としています。

intermediate-ca-1.png

検証してみた

やることは前回の記事とほぼ同じで、

  • 秘密鍵と CSR の作成
  • 中間 CA からエンドエンティティ証明書を発行
  • エンドエンティティ証明書と証明書チェーンを取得

という流れです。

CSR と秘密鍵は以下のような内容で作成しました。

$ openssl req -out intermediate-csr.pem -new -newkey rsa:2048 -nodes -keyout intermediate-private-key.pem

...

Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:WA
Locality Name (eg, city) []:Seattle
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Example Corp
Organizational Unit Name (eg, section) []:Sales
Common Name (e.g. server FQDN or YOUR name) []:app.example.com

作成した CSR を中間 CA に渡して、エンドエンティティ証明書を発行します。有効期限は 1 年です。

$ aws acm-pca issue-certificate \
      --certificate-authority-arn arn:aws:acm-pca:ap-northeast-1:222222222222:certificate-authority/foo \
      --csr fileb://intermediate-csr.pem \
      --signing-algorithm "SHA256WITHRSA" \
      --validity Value=1,Type="YEARS"

{
    "CertificateArn": "arn:aws:acm-pca:ap-northeast-1:222222222222:certificate-authority/foo/certificate/bar"
}

上記で発行された ARN を指定して、エンドエンティティ証明書と証明書チェーンをそれぞれ取得します。

$ aws acm-pca get-certificate \
      --certificate-arn arn:aws:acm-pca:ap-northeast-1:222222222222:certificate-authority/foo/certificate/bar \
      --certificate-authority-arn arn:aws:acm-pca:ap-northeast-1:222222222222:certificate-authority/foo \
      --output text --query 'Certificate' > intermediate-server.crt

$ aws acm-pca get-certificate \
      --certificate-arn arn:aws:acm-pca:ap-northeast-1:222222222222:certificate-authority/foo/certificate/bar \
      --certificate-authority-arn arn:aws:acm-pca:ap-northeast-1:222222222222:certificate-authority/foo \
      --output text --query 'CertificateChain' > intermediate-ca-cert.crt

取得したエンドエンティティ証明書をサーバに設置して、クライアントから接続してみましょう。
サーバ用スクリプトは前回記事と同様ですが、証明書と秘密鍵の箇所は今回取得・作成したものに置き換えています。

import ssl
import http.server

context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain('intermediate-server.crt', 'intermediate-private-key.pem')

server = http.server.HTTPServer(('localhost', 8443), http.server.SimpleHTTPRequestHandler)
server.socket = context.wrap_socket(server.socket, server_side=True)

print("TLS server running on https://localhost:8443")
server.serve_forever()

サーバを起動して、リクエストを送ってみます。

$ python3 server.py
TLS server running on https://localhost:8443

===

$ curl -Iv --cacert ca.crt --resolve app.example.com:8443:127.0.0.1 https://app.example.com:8443

...

curl: (60) SSL certificate problem: unable to get local issuer certificate

ダメでした。エラーメッセージを見ると「unable to get local issuer certificate」と出ています。

これは証明書チェーンの検証に失敗したことを意味します。今回、クライアント側では --cacert ca.crt でルート CA 証明書を指定していますが、中間 CA 証明書については指定していません。
TLS/SSL の仕組みでは、クライアントが持っているのはルート CA 証明書のみで、中間 CA 証明書はサーバ側から送信される必要があります。

つまり、サーバ側はエンドエンティティ証明書だけでなく、中間 CA 証明書も含めた証明書チェーンを返す必要があるわけです。
というわけで、エンドエンティティ証明書と中間 CA 証明書を組み合わせた証明書を作成します。

cat intermediate-server.crt intermediate-ca-cert.crt > intermediate-fullchain.crt

これをサーバ側に設置することで、正しく通信できるようになります。

おわりに

マルチアカウント CA 階層構成は、セキュリティと運用性を両立させるベストプラクティスです。
ルート CA を専用アカウントで管理することで、侵害リスクを最小化できます。

一方で、コスト面には注意が必要です。プライベート CA あたり月額 400 USD が発生するので、ルート CA と中間 CA を構築すると合計で月額 800 USD となります。
決して無視できない金額ですので、求められているセキュリティ要件や、コストとリスクを天秤に掛けて、慎重に検討する必要があるでしょう。

この記事をシェアする

FacebookHatena blogX

関連記事