GAT技術ブログ

株式会社ギブ・アンド・テイクの技術ブログ

GAT技術ブログ

【AWS】ALB↔ECS↔Auroraの全区間をTLS暗号化!公開鍵暗号の基礎から構築・検証まで解説

1. はじめに

本記事では、ALB(Application Load Balancer)→ECS(Fargate)→Aurora PostgreSQL 構成を題材に、通信区間ごとにTLSで暗号化する方法を解説します。

この記事でわかること

  • 公開鍵暗号方式・共通鍵暗号方式・デジタル証明書といった、TLSを支える暗号技術の基礎
  • クライアント↔ALB、ALB↔ECS、ECS↔Auroraの3区間をそれぞれ暗号化する方法
  • ALBのHTTPSターゲットグループ(再暗号化)に関する技術的な仕様

この記事で扱わないこと

  • ALB / ECS / Aurora の単一リージョン基本構築手順(前提として作成済みのものとします)
  • マルチリージョン構成やDR(災害対策)

なお、今回題材にするアプリケーション層の構成(ECSでNode.js + Express、Auroraへの接続にRDS CAバンドルを使う点など)は、以前の記事で扱ったウォームスタンバイDR構成の単一リージョン部分をベースにしています。DRの仕組み自体に興味がある方は、あわせてご覧ください。


2. なぜ「内部通信」まで暗号化するのか

Webアプリのセキュリティというと、まず「クライアント↔サーバー間のHTTPS化」が思い浮かびます。実際、ALBにACM証明書を設定してHTTPSリスナーを立てるところまでは、多くの構成で当たり前に行われています。

一方で、ALBから先(ALB↔ECS、ECS↔Aurora)の通信は、設定次第で平文のままVPC内を流れます。

ALBのターゲットグループはデフォルトでHTTPであり、Auroraへの接続もクライアント側でSSLを明示しなければ平文にフォールバックし得ます。

VPC内部の通信だから安全、という考え方もありますが、次のような観点から「内部通信も暗号化する」構成を選ぶケースが増えています。実際、案件対応をしている中でも、以前より内部通信の暗号化を希望されるお客様が増えてきたように感じます。

  • ゼロトラスト的な考え方
  • 金融・医療など、規制上の要件で通信経路全体の暗号化が求められる業種
  • 監査・セキュリティ診断で「内部通信の暗号化状況」を問われるケース

そこで本記事では、ALB↔ECS、ECS↔Auroraを含めた3区間すべてをTLSで暗号化する構成を実際に組んでみます。


3. 暗号技術の基礎:公開鍵暗号とTLSの仕組み

手を動かす前に、TLSがどのような暗号技術の組み合わせで成り立っているかを整理します。

3.1 共通鍵暗号と公開鍵暗号

暗号方式は大きく2種類に分けられます。

共通鍵暗号方式は、暗号化と復号に同じ鍵を使う方式です。処理が高速な一方、通信相手ごとに「鍵をどう安全に渡すか」という鍵配送問題が残ります。

公開鍵暗号方式は、暗号化用の「公開鍵」と復号用の「秘密鍵」を別々に用意する方式です。公開鍵は誰に渡しても構いませんが、秘密鍵は本人だけが保持します。公開鍵で暗号化したデータは、対応する秘密鍵でしか復号できません。この性質により、事前に鍵を安全に共有する必要がなくなります。

ただし公開鍵暗号方式は、共通鍵暗号方式に比べて計算コストが高いという弱点があります。

公開鍵暗号方式の具体例:RSA暗号

公開鍵暗号方式の代表的な実装がRSA暗号です。

以下の図は、p = 3、q = 11 という具体的な数値を使って、RSA暗号の鍵ペアが作られる流れ(上段)と、実際に平文 M = 5 を暗号化・復号する流れ(下段)を示したものです。

※ mod とは「割り算の余りを求める計算」のことです(例:17 mod 5 = 2)。

鍵ペアの生成(図の上段)

N = p × q は、公開鍵・秘密鍵の両方に共通して含まれる「土台」の数です。一方、L = (p-1) × (q-1) は、鍵を作るためだけに使う一時的な数で、公開鍵・秘密鍵そのものには含まれません。

公開鍵の E は、L と互いに素(1以外の共通の約数を持たない)な数として選びます。秘密鍵の D は、E と掛け合わせて L で割ると余りが 1 になる数、いわば E とペアになる数です。こうして得られた(E, N)が公開鍵、(D, N)が秘密鍵になります。

暗号化・復号の流れ(図の下段)

暗号化は平文を E 乗して N で割った余りを求め(C = ME mod N)、復号は暗号文を D 乗して N で割った余りを求めます(M = CD mod N)。

E と D は L を基準にした特別な関係になるように選ばれているため、この2つの計算はちょうど元の M に戻る仕組みになっています。

公開鍵(E, N)から秘密鍵Dを逆算するには、N を p × q に分解して L を求める必要があります。ただし今回のように p、q が小さいと、N = 33 は誰でも一瞬で 3 × 11 に分解できてしまい、安全性はありません。

実際のRSA暗号ではp、qに100桁を超える巨大な素数を使うため、Nの素因数分解はスーパーコンピューターでも現実的な時間では終わりません。この「大きな数の素因数分解には膨大な時間がかかる」という性質こそが、RSA暗号の安全性の根拠です。

3.2 ハイブリッド暗号方式

TLSでは、両方式の長所を組み合わせた「ハイブリッド暗号方式」が使われています。

  1. 通信の最初に公開鍵暗号方式(RSA暗号など)を使い、共通鍵暗号で使う「セッション鍵」を安全に交換する
  2. 以降の実際のデータ通信は、処理が高速な共通鍵暗号方式(AESなど)でセッション鍵を使って暗号化する

つまり、公開鍵暗号方式は「鍵交換の安全性」を担保し、共通鍵暗号方式は「通信の速度」を担保する、という役割分担になっています。

RSA暗号は3.1で見たとおり mod によるべき乗演算を伴うため、通信のたびに大量のデータをRSA暗号だけで暗号化すると処理コストが大きくなります。そのため、TLSでは「セッション鍵という小さなデータだけ」を公開鍵暗号方式でやり取りし、本体のデータは共通鍵暗号方式に任せる、という設計になっています。

上図の①〜④が鍵交換、⑤がデータ通信のフェーズに対応します。公開鍵暗号方式が使われるのは③・④の鍵交換部分だけで、実際のデータ本体(⑤以降)は処理の軽い共通鍵暗号方式でやり取りされます。

なお、上図はTLSの基本原理をわかりやすくするための簡略化例です。

実際のTLS通信では、クライアントがセッション鍵を生成してサーバーの公開鍵で暗号化して送る、という方式ではなく、ECDHEという鍵交換方式が使われるのが一般的です。ECDHEは、Diffie-Hellman鍵交換という考え方をベースに、楕円曲線(Elliptic Curve)という数学的な仕組みを使い、さらに鍵をセッションごとに使い捨てにする(Ephemeral)という特徴を組み合わせた方式です。

ECDHEでは、クライアント・サーバーの双方が自分だけの秘密の値を用意し、そこから計算した公開の値だけを交換します。 お互いが「自分の秘密の値」と「相手から受け取った公開の値」を組み合わせて計算すると、双方とも同じ値(セッション鍵のもと)にたどり着きます。通信を盗聴していても、やり取りされる公開の値だけからこの共有の値を逆算するのは、値が十分大きければ現実的な時間ではできません。

また、この秘密の値は鍵交換のたびに使い捨てにされるため、サーバーの長期的な秘密鍵が将来漏えいしても、過去の通信で使われた使い捨ての秘密の値までは復元できず、過去の通信内容が芋づる式に復号されることはありません。これを前方秘匿性(Forward Secrecy)と呼び、現在のTLSでECDHEが使われる主な理由です。

なお、ECDHE自体はTLS 1.3で新しく登場した技術ではなく、TLS 1.2以前からcipher suiteの一つとして選択可能でした。ただしTLS 1.2までは、前方秘匿性のない静的なRSA鍵交換を使うサーバーも数多く残っていました。TLS 1.3ではこの静的なRSA鍵交換が仕様から削除され、公開鍵を使う鍵交換はすべて前方秘匿性を持つようになりました。

つまり「TLS 1.3からECDHEが使われ始めた」というより、「TLS 1.3で、前方秘匿性のない古い方式が使えなくなった」というわけです。

※TLS 1.3は2018年にRFC 8446として公開され、現在はRFC 9846が最新の仕様です。なお、事前共有鍵を使う特殊な接続方法は例外で、前方秘匿性を持ちません。

3.3 デジタル証明書と認証局(CA)の役割

公開鍵暗号方式には、もう一つ解決すべき問題があります。「その公開鍵が、本当に通信したい相手のものか」をどう確認するか、という問題です。

この問題を解決するのがデジタル証明書です。証明書には公開鍵と、その所有者の情報(ドメイン名など)が含まれており、認証局(CA:Certificate Authority)と呼ばれる第三者機関が、その内容が正しいことをデジタル署名によって保証します。

デジタル署名の仕組み

デジタル署名は、3.1で見た公開鍵暗号方式を「暗号化」ではなく「本人証明」の目的で応用したものです。

上図のとおり、証明書は「CAの秘密鍵で署名(発行フロー)→CAの公開鍵で検証(検証フロー)」という、3.1のRSA暗号とは逆向きの使い方で正当性が保証されます。秘密鍵を持つのはCA本人だけなので、CAの公開鍵で正しく検証できたことが、そのままCA本人が署名した証明になります。

クライアントは、あらかじめ信頼できるCAの情報(ルート証明書)をOSやブラウザに保持しており、通信相手の証明書がそのCAによって署名されているかを検証することで、なりすましを検知します。

AWSでは、この証明書の発行・管理をAWS Certificate Manager(ACM)が担います。今回の構成でも、ALBのHTTPSリスナー用証明書はACMで発行した証明書を利用します。

3.4 TLSハンドシェイクの流れ(概要)

上図は、これまで見てきた「証明書の検証」と「鍵交換」が、実際の通信開始時にどのような順序で行われるかをまとめたものです(TLSのバージョンにより細部は異なります)。④のセッション鍵交換は、3.2で触れたとおり実際にはECDHEで行われますが、ここでは流れを掴みやすいよう簡略化しています。

ここで重要なのは、③の「証明書の検証」を行うのは証明書を受け取る側(多くの場合クライアント)であるという点です。この点は、7章で補足する「ALBの再暗号化」の話につながります。


4. 今回構築するアーキテクチャ

今回構築する構成は、次のとおりです。

通信区間ごとの暗号化方式をまとめると、次のようになります。

区間 プロトコル 証明書 検証の有無
クライアント ↔ ALB HTTPS(TLS) ACMパブリック証明書(独自ドメイン、DNS検証) クライアント側で検証あり
ALB ↔ ECS HTTPS(TLS、再暗号化) ECSコンテナに配置した自己署名証明書 ALBは検証しない(7章で補足)
ECS ↔ Aurora SSL/TLS RDSグローバルCAバンドル ssl: { rejectUnauthorized: true }でアプリ側が検証

この表のとおり、3区間はどれも「暗号化はされる」ものの、証明書の検証有無や検証主体が異なります。


5. 構築してみた

5.1 前提

ここからは、実際の設定例を通じて各区間のTLS化方法を解説します。

なお、解説にあたっては、以下がすでに構築されていることを前提とします。

  • VPC:各サブネット(パブリック/プライベート/DB)とルーティングや各種SG設定済み
  • ALB:作成済み(ターゲットグループ・リスナーは5.3以降で作成します)
  • Aurora PostgreSQL:クラスター作成済みで、accountsテーブル(id, account_number, holder_name, balance, updated_at列)も作成済み
  • Route 53:独自ドメインのパブリックホストゾーン作成済み
  • ECR:リポジトリ作成済み

5.2 Route 53 + ACMでパブリック証明書を発行する

まず、クライアント↔ALB間のHTTPS化に使うACMパブリック証明書を発行します。

ACMコンソールで [証明書をリクエスト][パブリック証明書をリクエスト] を選択し、対象のドメイン名を入力します。検証方法は [DNS検証] を選択します。

証明書のリクエスト後、ACMが提示するCNAMEレコードの内容を、対象ドメインのRoute 53ホストゾーンに追加します。Route 53が同一アカウント内のホストゾーンであれば、ACMコンソールから [Route 53でレコードを作成] を選ぶだけで自動追加できます。

数分〜数十分待つと、証明書のステータスが「発行済み」に変わります。

5.3 ターゲットグループの作成

EC2コンソールの [ターゲットグループ] から新規作成します。

  • ターゲットの種類:IPアドレス(Fargate用)
  • プロトコル:HTTPS、ポート:443
  • VPC:対象のVPCを選択
  • ヘルスチェックプロトコル:HTTPS
  • ターゲットのIPアドレスは何も指定しない

5.4 ALBにHTTPSリスナーを追加する

ALBコンソールで対象のロードバランサーを選択し、[リスナーとルール] タブから [リスナーを追加] をクリックします。

  • プロトコル:HTTPS
  • ポート:443
  • デフォルトSSL/TLS証明書:5.2で発行したACM証明書
  • ターゲットグループ:5.3で作成したターゲットグループ
  • ルーティングアクション:ターゲットグループへ転送

5.5 アプリケーションイメージの作成とECRへのPush

ALB↔ECS間を再暗号化するには、ECSコンテナ側もHTTPSで待ち受ける必要があります。今回は自己署名証明書をコンテナイメージに組み込みます。

FROM node:24-alpine

WORKDIR /usr/src/app

# ECS→Aurora間のSSL検証に使うRDSグローバルCAバンドルを取得
RUN apk add --no-cache ca-certificates curl openssl \
  && update-ca-certificates \
  && curl -fsSL https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem \
     -o /usr/src/app/rds-ca-bundle.pem

# ALB↔ECS間のTLSで使う自己署名証明書を生成(有効期限365日)
RUN openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /usr/src/app/server.key \
  -out /usr/src/app/server.crt \
  -subj "/CN=internal-ecs-app"

COPY package*.json ./
RUN npm install --only=production

COPY . .

EXPOSE 443

CMD ["node", "server.js"]

server.jsは、Node.jsのhttpsモジュールで待ち受けるように変更します。

const express = require('express');
const https = require('https');
const fs = require('fs');
const { Pool } = require('pg');

const app = express();

// ALB↔ECS間TLS用の自己署名証明書・秘密鍵
const tlsOptions = {
  key: fs.readFileSync('/usr/src/app/server.key'),
  cert: fs.readFileSync('/usr/src/app/server.crt'),
};

// ECS→Aurora向けSSL設定(RDS CAバンドルで検証)
const caPath = process.env.RDS_CA_BUNDLE_PATH || '/usr/src/app/rds-ca-bundle.pem';
const pool = new Pool({
  host: process.env.PGHOST,
  port: Number(process.env.PGPORT) || 5432,
  user: process.env.PGUSER,
  password: process.env.PGPASSWORD,
  database: process.env.PGDATABASE,
  ssl: {
    ca: fs.readFileSync(caPath, 'utf8'),
    rejectUnauthorized: true,
  },
});

app.get('/', (_req, res) => {
  res.send('<h1>Core Banking Sample (TLS end-to-end)</h1>');
});

app.get('/accounts', async (_req, res) => {
  try {
    const result = await pool.query('SELECT account_number, holder_name, balance, updated_at FROM accounts ORDER BY id;');
    res.json(result.rows);
  } catch (err) {
    console.error('DB error', err);
    res.status(500).send('DB error');
  }
});

// ECS→Aurora間のSSL接続状態を確認するための診断用エンドポイント(6.3で使用)
app.get('/debug/ssl', async (_req, res) => {
  try {
    const result = await pool.query(
      'SELECT pid, ssl, version, cipher FROM pg_stat_ssl WHERE pid = pg_backend_pid();'
    );
    res.json(result.rows[0]);
  } catch (err) {
    console.error('DB error', err);
    res.status(500).send('DB error');
  }
});

const port = process.env.PORT || 443;
https.createServer(tlsOptions, app).listen(port, () => {
  console.log(`HTTPS listening on ${port}`);
});

上記のDockerfile・server.jsに加え、以下のpackage.jsonを作成します。

{
  "name": "alb-ecs-tls-app",
  "version": "1.0.0",
  "private": true,
  "main": "server.js",
  "dependencies": {
    "express": "^5.2.1",
    "pg": "^8.22.0"
  }
}

/debug/sslは、アプリが実際に使っているpool(ECS→Aurora間のSSL設定そのもの)でpg_stat_sslを直接確認するための診断用エンドポイントです。後述の動作確認で使用します。

Dockerfileとserver.jsの更新ができたら、CloudShellでイメージをビルドし、ECRにPushします。

今回はCloudShell上からイメージをPushします。作業用ディレクトリを作成して移動し、上記の資材をそこへ配置します。

mkdir ~/alb-ecs-tls-app && cd ~/alb-ecs-tls-app

イメージをビルドします。

docker build -t alb-ecs-tls-app .

ECRにログインします。

aws ecr get-login-password --region <リージョン> | docker login --username AWS --password-stdin <AWSアカウントID>.dkr.ecr.<リージョン>.amazonaws.com

イメージにタグを付けてPushします。

docker tag alb-ecs-tls-app:latest <AWSアカウントID>.dkr.ecr.<リージョン>.amazonaws.com/<ECRリポジトリ名>:latest

docker push <AWSアカウントID>.dkr.ecr.<リージョン>.amazonaws.com/<ECRリポジトリ名>:latest

Pushしたイメージのリポジトリ URIは、5.6でタスク定義を作成する際に指定します。

5.6 ECSクラスター・タスク定義・サービスの作成

ECSクラスターを作成し、5.5で作成したイメージを指定してタスク定義、サービスを作成します。

  • クラスター:起動タイプ Fargate で作成
  • タスク定義:
    • コンテナイメージ:5.5でPushしたECRのイメージURI
    • コンテナポート:443
    • 環境変数:
      • PGHOST:Auroraクラスターのエンドポイント
      • PGPORT:5432
      • PGUSER:DBユーザー名
      • PGPASSWORD:DBパスワード
      • PGDATABASE:DB名
  • サービス:
    • 起動タイプ:Fargate
    • ネットワーキング:プライベートサブネット、ECSタスク用セキュリティグループを選択
    • ロードバランシング:Application Load Balancer → 5.3で作成したターゲットグループへ登録、コンテナポート:443

今回はSSL/TLSによる通信経路の暗号化が主眼のため、PGPASSWORDなどの接続情報はタスク定義のenvironmentに平文で設定しています。本来は、AWS Secrets Managerを参照する構成がベストプラクティスです。

5.7 Auroraのパラメータグループでrds.force_sslを有効化する

最後に、ECS↔Aurora間の接続を強制的にSSL/TLSにします。

Aurora PostgreSQLでは、DBクラスターパラメータグループ(インスタンス単位ではなくクラスター単位の設定)に含まれるrds.force_sslパラメータで、SSL接続を必須にできます。

Aurora PostgreSQL 17以降は、デフォルトの default.aurora-postgresql<バージョン> パラメータグループ自体でrds.force_sslが1(有効)になっており、この時点で追加の作業は不要です

一方、Aurora PostgreSQL 16以前はデフォルトが0(無効)です。16以前から17以降へメジャーバージョンアップすると、この値が0から1に変わるため、SSL接続に対応していないアプリケーションはアップグレードを境に接続できなくなります。 16以前のまま有効化する場合は、カスタムのDBクラスターパラメータグループを作成し、Auroraクラスターに関連付ける必要があります。なお、デフォルトのパラメータグループ自体は編集できません。

  1. RDSコンソールで [パラメータグループ][パラメータグループの作成]
  2. パラメータグループファミリー:使用しているAurora PostgreSQLのバージョンに対応するもの
  3. タイプ:DBクラスターパラメータグループ
  4. 作成後、rds.force_ssl1に変更して保存

作成したパラメータグループをAuroraクラスターに関連付け([変更][DBクラスターパラメータグループ])ます。rds.force_sslをデフォルトの0から1に変更する場合は再起動が必要になることがあるため、メンテナンスウィンドウなどを考慮して適用してください。

rds.force_sslを有効化すると、pg_hba.confが自動的に更新され、非SSL接続はhostsslルールにより拒否されるようになります。

実際に、クエリエディタなどからpg_hba_file_rulesビューを確認すると、以下のようなルールが登録されています。

SELECT * FROM pg_hba_file_rules;

このhostsslのルールは、ネットワーク経由で接続する場合(VPC内部からのECS→Aurora接続も含みます)、SSL暗号化接続(hostssl)のみを許可し、パスワード認証(md5)を行うことを意味します。rds.force_sslを有効化する前は、この行がhost(SSLの有無を問わない)になっており、有効化後にhostsslへ変わっていることで、設定が効いていることを確認できます。

このあたりは、以下のre:Postが参考になります。


6. 動作確認

6.1 クライアント → ALB

ブラウザまたはcurlで、独自ドメインのHTTPSエンドポイントにアクセスします。

curl -v https://your-domain.example.com/

証明書がACM発行のパブリック証明書であり、CAチェーンが正しく検証されることを確認します。実行結果の例は以下のとおりです(IPアドレス・ドメイン名はマスキングしています)。

$ curl -v https://your-domain.example.com/
*   Trying xxx.xxx.xxx.xxx:443...
* Connected to your-domain.example.com (xxx.xxx.xxx.xxx) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=*.your-domain.example.com
*  start date: Aug 11 00:00:00 2026 GMT
*  expire date: Feb 24 23:59:59 2027 GMT
*  subjectAltName: host "your-domain.example.com" matched cert's "*.your-domain.example.com"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M01
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
> GET / HTTP/2
> Host: your-domain.example.com
> user-agent: curl/7.81.0
> accept: */*
>
< HTTP/2 200
< date: Tue, 11 Aug 2026 13:32:03 GMT
< content-type: text/html; charset=utf-8
< content-length: 45
< x-powered-by: Express
<
* Connection #0 to host your-domain.example.com left intact
<h1>Core Banking Sample (TLS end-to-end)</h1>

SSL certificate verify ok.となっており、証明書がホスト名と一致したうえで検証に成功し、ECS上のExpressアプリからのレスポンス(x-powered-by: Express)まで正常に返っていることが確認できます。

6.2 ALB → ECS

ターゲットグループの [ターゲット] タブで、ステータスが「healthy」になっていることを確認します。ヘルスチェックがHTTPSで正常に通っていれば、ALB↔ECS間のTLS再暗号化が機能しています。

ALBのターゲットグループで、ヘルスチェックプロトコルが HTTPS になっていることを確認します。

ターゲットのステータスが Healthy になっていれば、ALB↔ECS間のTLS通信が正常に行われています。

6.3 ECS → Aurora

5.5で追加した/debug/sslエンドポイントを使い、ECSアプリが実際に使っている接続(pool)自体のSSL状態を確認します。

実行結果の例は以下のとおりです(IPアドレス・ドメイン名はマスキングしています)。

$ curl -v https://your-domain.example.com/debug/ssl
*   Trying xxx.xxx.xxx.xxx:443...
* Connected to your-domain.example.com (xxx.xxx.xxx.xxx) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=*.your-domain.example.com
*  start date: Aug 11 00:00:00 2026 GMT
*  expire date: Feb 24 23:59:59 2027 GMT
*  subjectAltName: host "your-domain.example.com" matched cert's "*.your-domain.example.com"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M01
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
> GET /debug/ssl HTTP/2
> Host: your-domain.example.com
> user-agent: curl/7.81.0
> accept: */*
>
< HTTP/2 200
< date: Sat, 15 Aug 2026 04:36:17 GMT
< content-type: application/json; charset=utf-8
< content-length: 78
< x-powered-by: Express
< etag: W/"4e-znMlVoU9E3Sbf5W4J1f9+xO9vc0"
<
* Connection #0 to host your-domain.example.com left intact
{"pid":55291,"ssl":true,"version":"TLSv1.3","cipher":"TLS_AES_256_GCM_SHA384"}

なお、上記は実際のcurl実行結果からログの一部を省略して掲載しています。

レスポンスボディのssl:true、version:TLSv1.3が、まさにECS↔Aurora間のSSL接続状態を示しています。このエンドポイントの内部では、SELECT pid, ssl, version, cipher FROM pg_stat_ssl WHERE pid = pg_backend_pid();というクエリを実行しています。

pg_stat_sslは、Auroraに接続中の各セッションについてSSL/TLSの利用状況を保持するシステムビューです。pg_backend_pid()は「今このクエリを実行している自分自身の接続」のプロセスIDを返す関数で、WHERE pid = pg_backend_pid()と組み合わせることで、数ある接続の中から自分自身の接続の行だけに絞り込んでいます。

ssl列がtrueであれば、SSL/TLS経由で接続できていることが確認できます。

6.4 まとめて確認する

6.1〜6.3では区間ごとに切り分けて確認しましたが、5.5の/accountsエンドポイントはAuroraへの問い合わせ結果をJSONで返す処理のため、この1回のリクエストの結果からも3区間それぞれのTLSが機能していることを読み取れます。

$ curl -v https://your-domain.example.com/accounts
*   Trying xxx.xxx.xxx.xxx:443...
* Connected to your-domain.example.com (xxx.xxx.xxx.xxx) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=*.your-domain.example.com
*  start date: Aug 11 00:00:00 2026 GMT
*  expire date: Feb 24 23:59:59 2027 GMT
*  subjectAltName: host "your-domain.example.com" matched cert's "*.your-domain.example.com"
*  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M01
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
> GET /accounts HTTP/2
> Host: your-domain.example.com
> user-agent: curl/7.81.0
> accept: */*
>
< HTTP/2 200
< date: Wed, 12 Aug 2026 14:42:31 GMT
< content-type: application/json; charset=utf-8
< content-length: 284
< x-powered-by: Express
< etag: W/"11c-8jIBp5gb6xO+g6EOs53n1pCQeeY"
<
* Connection #0 to host your-domain.example.com left intact
[{"account_number":1000000001,"holder_name":"山田太郎","balance":500000,"updated_at":null},{"account_number":1000000002,"holder_name":"佐藤花子","balance":1200000,"updated_at":null},{"account_number":1000000003,"holder_name":"鈴木一郎","balance":850000,"updated_at":null}]

※ログの一部を省略して掲載しています。

この1回のリクエストの結果から、3区間それぞれのTLSが機能していることを次のように読み取れます。

  • クライアント↔ALB:SSL certificate verify ok.HTTP/2 200から、TLSで正しく通信できていることが分かります。
  • ALB↔ECS:5.5のコンテナはHTTPSでしか待ち受けておらず、5.3のターゲットグループもHTTPS/443のみを登録しています。そのため、レスポンスが正常に返っていること(x-powered-by: Express)自体がHTTPS通信の証拠です。
  • ECS↔Aurora:5.7でrds.force_sslを有効化しているため、非SSL接続はAuroraに拒否されます。accountsテーブルの実データが200 OKで返ってきていること自体が、SSL接続の証拠になります。

7. 補足:ALBの再暗号化とバックエンド証明書の扱い

ALBのHTTPSターゲットグループは、ECS側が提示する証明書の正当性までは検証しません。自己署名証明書でも、期限切れの証明書でも、ALBはエラーにせずそのまま通信を確立します。この点は、以下の公式ドキュメントにも明記されています。

※引用:AWS公式ドキュメント『Target groups for your Application Load Balancers』(2026年8月13日アクセス)URL: Target groups for your Application Load Balancers - Elastic Load Balancing

ロードバランサーは、ターゲットにインストールする証明書を使用して、ターゲットとの TLS 接続を確立します。ロードバランサーはこれらの証明書を検証しません。したがって、自己署名証明書または期限切れの証明書を使用できます。ロードバランサーとそのターゲットは仮想プライベートクラウド (VPC) 内にあり、ロードバランサーとターゲット間のトラフィックはパケットレベルで認証されるため、ターゲットの証明書が有効でない場合でも、中間者攻撃やスプーフィングのリスクはありません。(以下略)

これはAWSの仕様上の設計です。ALBとターゲットは同一VPC内にあり、通信自体がネットワークレベルで保護されているため、証明書の正当性を検証しなくても、盗聴を防ぐという「暗号化」の目的自体は十分に果たされます。

今回のように内部通信の暗号化そのものを主目的とする構成であれば、この仕様を踏まえたうえで問題なく利用できます。


8. まとめ

本記事では、ALB→ECS→Auroraの単一リージョン構成を題材に、公開鍵暗号・共通鍵暗号・デジタル証明書というTLSの基礎技術を整理したうえで、3つの通信区間それぞれをTLSで暗号化する手順を確認しました。

  • クライアント↔ALB:ACMパブリック証明書によるHTTPS化
  • ALB↔ECS:自己署名証明書によるHTTPS再暗号化
  • ECS↔Aurora:rds.force_sslによるSSL接続の強制と、RDS CAバンドルによるサーバー証明書の検証

あわせて、ALBのHTTPSターゲットグループはバックエンド証明書を検証しないという仕様にも触れました。今回のように内部通信の暗号化そのものが目的の構成であれば、この仕様を理解したうえで問題なく利用できます。