最近、アーキテクチャにRDSのリードレプリカを組み込む作業を行ったのですが、Lambda(NestJS)からRDS(リードレプリカ)へのSSL/TLS接続を設定する際、以下のエラーメッセージが表示されました。
Error: self-signed certificate in certificate chain
この問題の原因と解決方法を備忘録として残します。
原因
単純に、使用する証明書が不適切だったのが原因でした。
RDS Proxyを介する場合の証明書
元々の構成では、Lambda(NestJS)からRDS Proxyを経由してRDS(プライマリ)に接続しており、この場合にはAmazonRootCA1.pem
証明書を使用していました。
RDS Proxyを使用する際には、AWSが提供するAmazonRootCA1.pem
というルート証明書が重要です。この証明書は、RDS Proxyの身元確認と通信の暗号化を保証するために用いられます。
Lambda(NestJS) → RDS Proxy → RDS(プライマリ)
RDS(リードレプリカ)への直接接続
今回新たに追加したRDS(リードレプリカ)への接続では、RDS Proxyを介さず直接Lambdaから接続する必要がありました(RDS Proxyは主に書き込み操作の負荷を軽減し、接続管理を効率化するために使用されますが、読み取り専用のリードレプリカではこれが必要ないため、直接接続を選択しました)。
この直接接続では、リージョンに特化したap-northeast-1-bundle.pem(~-bundle.pem)
証明書が必要でした。
Lambda(NestJS) → RDS(リードレプリカ)
解決方法
この問題を解決するために、RDS(リードレプリカ)への接続で適切なSSL/TLS証明書を使用しました。
AWSの公式ページに証明書のバンドルap-northeast-1-bundle.pem
があったので、それを参照し直すことでエラーが解消されました。
補足:証明書の読み込み方法(NestJS)
アプリケーション(NestJS)でのSSL/TLS証明書の読み込みは、AWS S3から証明書を取得する形で実装しました。
S3からの証明書取得処理
S3CertificatesService
クラスを定義し、AWS SDKを使用してS3にアクセスします。このクラスでは、指定されたバケットとキーから証明書を取得するgetCertificateFromS3
メソッドを提供します。
import { Injectable } from '@nestjs/common'
import * as AWS from 'aws-sdk'
@Injectable()
export class S3CertificatesService {
private s3: AWS.S3
constructor() {
this.s3 = new AWS.S3()
}
async getCertificateFromS3(bucket: string, key: string): Promise<string> {
const params = {
Bucket: bucket,
Key: key
}
const data = await this.s3.getObject(params).promise()
if (data.Body) {
return data.Body.toString()
}
throw new Error('Failed to retrieve certificate from S3. Body is undefined.')
}
}
DB接続設定
DBへの接続設定では、上記サービスを利用してS3から証明書を取得し、DB接続の設定に適用します。ここではap-northeast-1-bundle.pem
証明書を取得し、DB(MySQL)へのSSL接続に使用しています。
const certificate = await s3CertificatesService.getCertificateFromS3('rds-certificates', 'ap-northeast-1-bundle.pem')
const dbConfig = {
type: 'mysql',
host: '',
port: 3306,
username: '',
database: '',
password: '',
ssl: {
ca: certificate,
rejectUnauthorized: true
}
}