[AWS CDK] Amazon Aurora DBクラスターのCAを指定してみた (新API対応版)
AWS CDKでAmazon Aurora DBクラスターのCAを指定したいな
こんにちは、のんピ(@non____97)です。
皆さんはAWS CDKでAmazon Aurora DBクラスターの認証局(CA)を指定したいなと思ったことはありますか? 私はあります。
Aurora DBクラスターのデフォルトのCAはrds-ca-2019
です。
以下記事で紹介している通り、rds-ca-2019
のCAの失効日は2024/8/23です。
そのため、どうせならrds-ca-rsa2048-g1
など失効期限までの猶予がより長いCAを使いたいところです。
ただし、AWS CDKのL2 ConstructではCAを指定することができません。その対応としてみんな大好きEscape hatchを使うことになります。DevelopesIOでもそのような記事が紹介されています。
しかし、以下記事で紹介しているようなAurora Serverless v2をL2 Constructで定義した場合、上述の記事で紹介されている方法では対応できませんでした。
以下、その理由と対応方法を紹介します。
2023/9/25 : AWS CDK v2.97.0からL2 ConstructでDBクラスターのインスタンスに対してCAを設定できるようになりました。
new DatabaseCluster(this, 'Database', { engine: rds.DatabaseClusterEngine.auroraMysql({ version: rds.AuroraMysqlEngineVersion.VER_3_01_0 }), writer: rds.ClusterInstance.provisioned('writer', { caCertificate: rds.CaCertificate.RDS_CA_RDS2048_G1, }), readers: [ rds.ClusterInstance.serverlessV2('reader', { caCertificate: rds.CaCertificate.of('custom-ca'), }), ], vpc, });
いきなりまとめ
- DBクラスターの新APIと旧APIとでIDやプロパティが指す値が変わった
instanceIdentifiers
やinstanceEndpoints
はReaderの情報しか返さなくなった- DBクラスター内のDBインスタンスのIDが
Instance${instanceIndex}
などの連番ではなく、ClusterInstance.provisioned
やClusterInstance.serverlessV2
のIDが使われるようになった
- DBクラスターの子ConstructのdefaultChildの型を確認して、DBインスタンスであればCAを指定することで対応
Aurora Serverless v2をL2 Constructで定義した場合に上手くCAが設定できない理由
Aurora Serverless v2をL2 Constructで定義した場合に上手くCAが設定できない理由は、新APIと旧APIとでIDやプロパティが指す値が変わったためです。
上述で紹介した記事ではCAを指定する際、instanceIdentifiers
からDBクラスター内のインスタンス数分ループしてInstance${instanceIndex}
であるConstructを引っ張ってきています。
const aurora = new rds.DatabaseCluster(this, "aurora", { engine: rds.DatabaseClusterEngine.auroraMysql({ version: rds.AuroraMysqlEngineVersion.VER_3_03_0 }), instanceProps: { instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.MEDIUM), vpc, vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, }, removalPolicy: cdk.RemovalPolicy.DESTROY, // for experimental }); for (let i = 1; i <= aurora.instanceIdentifiers.length; i++) { const instance = aurora.node.findChild(`Instance${i}`) as rds.CfnDBInstance; instance.addPropertyOverride("CACertificateIdentifier", "rds-ca-rsa2048-g1"); }
新APIでは旧APIからDBクラスター内のDBインスタンスの作成方法が変わりました。
その影響か新APIではinstanceIdentifiers
やinstanceEndpoints
はReaderの情報しか返さなくなりました。
新APIのDBクラスター内のDBインスタンスを作成方法はcluster.ts の _createInstancesを確認しましょう。
/** * Create cluster instances * * @internal */ protected _createInstances(props: DatabaseClusterProps): InstanceConfig { const instanceEndpoints: Endpoint[] = []; const instanceIdentifiers: string[] = []; const readers: IAuroraClusterInstance[] = []; // need to create the writer first since writer is determined by what instance is first const writer = props.writer!.bind(this, this, { monitoringInterval: props.monitoringInterval, monitoringRole: props.monitoringRole, removalPolicy: props.removalPolicy ?? RemovalPolicy.SNAPSHOT, subnetGroup: this.subnetGroup, promotionTier: 0, // override the promotion tier so that writers are always 0 }); (props.readers ?? []).forEach(instance => { const clusterInstance = instance.bind(this, this, { monitoringInterval: props.monitoringInterval, monitoringRole: props.monitoringRole, removalPolicy: props.removalPolicy ?? RemovalPolicy.SNAPSHOT, subnetGroup: this.subnetGroup, }); readers.push(clusterInstance); if (clusterInstance.tier < 2) { this.validateReaderInstance(writer, clusterInstance); } instanceEndpoints.push(new Endpoint(clusterInstance.dbInstanceEndpointAddress, this.clusterEndpoint.port)); instanceIdentifiers.push(clusterInstance.instanceIdentifier); }); this.validateClusterInstances(writer, readers); return { instanceEndpoints, instanceIdentifiers, }; }
props.readers
がある場合のみinstanceEndpoints
やinstanceIdentifiers
にpushしているのが分かりますね。
そのため、新APIではループのさせ方を変える必要があります。
続いてIDも変わりました。
aws-cdk/cluster.ts の ClusterInstanceとAuroraClusterInstance確認しましょう。
/** * Create an RDS Aurora Cluster Instance. You can create either provisioned or * serverless v2 instances. * * @example * * declare const vpc: ec2.Vpc; * const cluster = new rds.DatabaseCluster(this, 'Database', { * engine: rds.DatabaseClusterEngine.auroraMysql({ version: rds.AuroraMysqlEngineVersion.VER_2_08_1 }), * writer: rds.ClusterInstance.provisioned('writer', { * instanceType: ec2.InstanceType.of(ec2.InstanceClass.R6G, ec2.InstanceSize.XLARGE4), * }), * serverlessV2MinCapacity: 6.5, * serverlessV2MaxCapacity: 64, * readers: [ * // will be put in promotion tier 1 and will scale with the writer * rds.ClusterInstance.serverlessV2('reader1', { scaleWithWriter: true }), * // will be put in promotion tier 2 and will not scale with the writer * rds.ClusterInstance.serverlessV2('reader2'), * ] * vpc, * }); */ export class ClusterInstance implements IClusterInstance { /** * Add a provisioned instance to the cluster * * @example * ClusterInstance.provisioned('ClusterInstance', { * instanceType: ec2.InstanceType.of(ec2.InstanceClass.R6G, ec2.InstanceSize.XLARGE4), * }); */ public static provisioned(id: string, props: ProvisionedClusterInstanceProps = {}): IClusterInstance { return new ClusterInstance(id, { ...props, instanceType: ClusterInstanceType.provisioned(props.instanceType), }); } /** * Add a serverless v2 instance to the cluster * * @example * ClusterInstance.serverlessV2('ClusterInstance', { * scaleWithWriter: true, * }); */ public static serverlessV2(id: string, props: ServerlessV2ClusterInstanceProps = {}): IClusterInstance { return new ClusterInstance(id, { ...props, promotionTier: props.scaleWithWriter ? 1 : 2, instanceType: ClusterInstanceType.serverlessV2(), }); } private constructor(private id: string, private readonly props: ClusterInstanceProps) { } /** * Add the ClusterInstance to the cluster */ public bind(scope: Construct, cluster: IDatabaseCluster, props: ClusterInstanceBindOptions): IAuroraClusterInstance { return new AuroraClusterInstance(scope, this.id, { cluster, ...this.props, ...props, }); } }
class AuroraClusterInstance extends Resource implements IAuroraClusterInstance { public readonly dbInstanceArn: string; public readonly dbiResourceId: string; public readonly dbInstanceEndpointAddress: string; public readonly instanceIdentifier: string; public readonly type: InstanceType; public readonly tier: number; public readonly instanceSize?: string; constructor(scope: Construct, id: string, props: AuroraClusterInstanceProps) { super( scope, props.isFromLegacyInstanceProps ? `${id}Wrapper` : id, { physicalName: props.instanceIdentifier, }); this.tier = props.promotionTier ?? 2; if (this.tier > 15) { throw new Error('promotionTier must be between 0-15'); } const isOwnedResource = Resource.isOwnedResource(props.cluster); let internetConnected; let publiclyAccessible = props.publiclyAccessible; if (isOwnedResource) { const ownedCluster = props.cluster as DatabaseCluster; internetConnected = ownedCluster.vpc.selectSubnets(ownedCluster.vpcSubnets).internetConnectivityEstablished; publiclyAccessible = ownedCluster.vpcSubnets && ownedCluster.vpcSubnets.subnetType === ec2.SubnetType.PUBLIC; } // Get the actual subnet objects so we can depend on internet connectivity. const instanceType = (props.instanceType ?? ClusterInstanceType.serverlessV2()); this.type = instanceType.type; this.instanceSize = this.type === InstanceType.PROVISIONED ? props.instanceType?.toString() : undefined; // engine is never undefined on a managed resource, i.e. DatabaseCluster const engine = props.cluster.engine!; const enablePerformanceInsights = props.enablePerformanceInsights || props.performanceInsightRetention !== undefined || props.performanceInsightEncryptionKey !== undefined; if (enablePerformanceInsights && props.enablePerformanceInsights === false) { throw new Error('`enablePerformanceInsights` disabled, but `performanceInsightRetention` or `performanceInsightEncryptionKey` was set'); } const instanceParameterGroup = props.parameterGroup ?? ( props.parameters ? new ParameterGroup(props.cluster, 'InstanceParameterGroup', { engine: engine, parameters: props.parameters, }) : undefined ); const instanceParameterGroupConfig = instanceParameterGroup?.bindToInstance({}); const instance = new CfnDBInstance( props.isFromLegacyInstanceProps ? scope : this, props.isFromLegacyInstanceProps ? id : 'Resource', { // Link to cluster engine: engine.engineType, dbClusterIdentifier: props.cluster.clusterIdentifier, promotionTier: props.isFromLegacyInstanceProps ? undefined : this.tier, dbInstanceIdentifier: this.physicalName, // Instance properties dbInstanceClass: props.instanceType ? databaseInstanceType(instanceType) : undefined, publiclyAccessible, enablePerformanceInsights: enablePerformanceInsights || props.enablePerformanceInsights, // fall back to undefined if not set performanceInsightsKmsKeyId: props.performanceInsightEncryptionKey?.keyArn, performanceInsightsRetentionPeriod: enablePerformanceInsights ? (props.performanceInsightRetention || PerformanceInsightRetention.DEFAULT) : undefined, // only need to supply this when migrating from legacy method. // this is not applicable for aurora instances, but if you do provide it and then // change it it will cause an instance replacement dbSubnetGroupName: props.isFromLegacyInstanceProps ? props.subnetGroup?.subnetGroupName : undefined, dbParameterGroupName: instanceParameterGroupConfig?.parameterGroupName, monitoringInterval: props.monitoringInterval && props.monitoringInterval.toSeconds(), monitoringRoleArn: props.monitoringRole && props.monitoringRole.roleArn, autoMinorVersionUpgrade: props.autoMinorVersionUpgrade, allowMajorVersionUpgrade: props.allowMajorVersionUpgrade, }); // For instances that are part of a cluster: // // Cluster DESTROY or SNAPSHOT -> DESTROY (snapshot is good enough to recreate) // Cluster RETAIN -> RETAIN (otherwise cluster state will disappear) instance.applyRemovalPolicy(helperRemovalPolicy(props.removalPolicy)); // We must have a dependency on the NAT gateway provider here to create // things in the right order. if (internetConnected) { instance.node.addDependency(internetConnected); } this.dbInstanceArn = this.getResourceArnAttribute(instance.attrDbInstanceArn, { resource: 'db', service: 'rds', arnFormat: ArnFormat.COLON_RESOURCE_NAME, resourceName: this.physicalName, }); this.instanceIdentifier = this.getResourceNameAttribute(instance.ref); this.dbiResourceId = instance.attrDbiResourceId; this.dbInstanceEndpointAddress = instance.attrEndpointAddress; } }
Instance${instanceIndex}
などの連番ではなく、ClusterInstance.provisioned
やClusterInstance.serverlessV2
のIDがそのまま使われていることが分かります。
対応してみる
対応箇所が分かったので、新APIの対応版を作成してみます。
まず、適当にAurora DBクラスターを作成します。
import * as cdk from "aws-cdk-lib"; import { Construct } from "constructs"; export interface AuroraProps { vpc: cdk.aws_ec2.IVpc; securityGroup: cdk.aws_ec2.ISecurityGroup; } export class Aurora extends Construct { constructor(scope: Construct, id: string, props: AuroraProps) { super(scope, id); // DB Cluster Parameter Group const dbClusterParameterGroup = new cdk.aws_rds.ParameterGroup( this, "DbClusterParameterGroup", { engine: cdk.aws_rds.DatabaseClusterEngine.auroraPostgres({ version: cdk.aws_rds.AuroraPostgresEngineVersion.VER_15_2, }), description: "aurora-postgresql15", parameters: { log_statement: "none", "pgaudit.log": "all", "pgaudit.role": "rds_pgaudit", shared_preload_libraries: "pgaudit", }, } ); // DB Parameter Group const dbParameterGroup = new cdk.aws_rds.ParameterGroup( this, "DbParameterGroup", { engine: cdk.aws_rds.DatabaseClusterEngine.auroraPostgres({ version: cdk.aws_rds.AuroraPostgresEngineVersion.VER_15_2, }), description: "aurora-postgresql15", } ); // Subnet Group const subnetGroup = new cdk.aws_rds.SubnetGroup(this, "SubnetGroup", { description: "description", vpc: props.vpc, subnetGroupName: "SubnetGroup", vpcSubnets: props.vpc.selectSubnets({ onePerAz: true, subnetType: cdk.aws_ec2.SubnetType.PRIVATE_ISOLATED, }), }); // Monitoring Role const monitoringRole = new cdk.aws_iam.Role(this, "MonitoringRole", { assumedBy: new cdk.aws_iam.ServicePrincipal( "monitoring.rds.amazonaws.com" ), managedPolicies: [ cdk.aws_iam.ManagedPolicy.fromAwsManagedPolicyName( "service-role/AmazonRDSEnhancedMonitoringRole" ), ], }); // DB Cluster const dbCluster = new cdk.aws_rds.DatabaseCluster(this, "Default", { engine: cdk.aws_rds.DatabaseClusterEngine.auroraPostgres({ version: cdk.aws_rds.AuroraPostgresEngineVersion.VER_15_2, }), writer: cdk.aws_rds.ClusterInstance.provisioned("InstanceA", { instanceType: cdk.aws_ec2.InstanceType.of( cdk.aws_ec2.InstanceClass.T3, cdk.aws_ec2.InstanceSize.MEDIUM ), allowMajorVersionUpgrade: false, autoMinorVersionUpgrade: true, enablePerformanceInsights: true, parameterGroup: dbParameterGroup, performanceInsightRetention: cdk.aws_rds.PerformanceInsightRetention.DEFAULT, publiclyAccessible: false, instanceIdentifier: "db-instance-a", }), readers: [ cdk.aws_rds.ClusterInstance.serverlessV2("InstanceB", { autoMinorVersionUpgrade: false, allowMajorVersionUpgrade: false, enablePerformanceInsights: false, parameterGroup: dbParameterGroup, publiclyAccessible: false, scaleWithWriter: true, instanceIdentifier: "db-instance-b", }), ], backup: { retention: cdk.Duration.days(7), preferredWindow: "16:00-16:30", }, cloudwatchLogsExports: ["postgresql"], cloudwatchLogsRetention: cdk.aws_logs.RetentionDays.ONE_YEAR, clusterIdentifier: "db-cluster", copyTagsToSnapshot: true, defaultDatabaseName: "testDB", deletionProtection: false, iamAuthentication: false, monitoringInterval: cdk.Duration.minutes(1), monitoringRole, parameterGroup: dbClusterParameterGroup, preferredMaintenanceWindow: "Sat:17:00-Sat:17:30", storageEncrypted: true, vpc: props.vpc, securityGroups: [props.securityGroup], subnetGroup, }); // Manage master user password const cfnDbCluster = dbCluster.node .defaultChild as cdk.aws_rds.CfnDBCluster; cfnDbCluster.manageMasterUserPassword = true; cfnDbCluster.masterUsername = "postgresAdmin"; cfnDbCluster.addPropertyDeletionOverride("MasterUserPassword"); dbCluster.node.tryRemoveChild("Secret"); dbCluster.node.children.forEach((children) => { console.log(`DB Cluster children id : ${children.node.id}`); }); } }
デプロイする際、DBクラスターの子ConstructのIDを出力するようにしてみました。
$ npx cdk deploy DB Cluster children id : Resource DB Cluster children id : LogRetentionpostgresql DB Cluster children id : InstanceA DB Cluster children id : InstanceB ✨ Synthesis time: 7.41s . . (以下略) . .
ClusterInstance.provisioned (InstanceA)
やClusterInstance.serverlessV2 (InstanceB)
のIDとなっているのがDBクラスター内のDBインスタンスのConstructですね。
デプロイ後、DBインスタンスのCAを確認します。
rds-ca-2019
となっていますね。(なぜかWriter / Readerが定義したものと逆になっていますが、ここでは突っ込まないでおきます)
AWS CLIでも確認しておきます。
$ aws rds describe-db-instances \ --filters Name=db-cluster-id,Values=db-cluster \ --query 'DBInstances[*].CACertificateIdentifier' [ "rds-ca-2019", "rds-ca-2019" ]
それでは、DBインスタンスにCArds-ca-rsa4096-g1
を指定するように変更してみます。
変更箇所は以下のとおりです。
// CA dbCluster.node.children.forEach((children) => { if (children.node.defaultChild instanceof cdk.aws_rds.CfnDBInstance) { ( children.node.defaultChild as cdk.aws_rds.CfnDBInstance ).addPropertyOverride("CACertificateIdentifier", "rds-ca-rsa4096-g1"); } });
DBクラスターの子ConstructのdefaultChildがCfnDBInstance
かチェックし、CfnDBInstance
であればCAをEscape hatchで指定します。
編集後、diffを確認します。
$ npx cdk diff Stack AuroraStack Resources [~] AWS::RDS::DBInstance Aurora/Default/InstanceA AuroraInstanceA387C8329 └─ [+] CACertificateIdentifier └─ rds-ca-rsa4096-g1 [~] AWS::RDS::DBInstance Aurora/Default/InstanceB AuroraInstanceBE4D8957F └─ [+] CACertificateIdentifier └─ rds-ca-rsa4096-g1
CArds-ca-rsa4096-g1
が設定されそうですね。
デプロイ後、本当にCAがrds-ca-rsa4096-g1
となっているか確認します。
CAがrds-ca-rsa4096-g1
になっていますね。
AWS CLIでも確認しておきます。
$ aws rds describe-db-instances \ --filters Name=db-cluster-id,Values=db-cluster \ --query 'DBInstances[*].CACertificateIdentifier' [ "rds-ca-rsa4096-g1", "rds-ca-rsa4096-g1" ]
SSL/TLSを使った接続確認
Amazon Linux 2023からDBクラスターにSSL/TLSで接続できるか確認します。
# PostgreSQL 15のクライアントのインストール $ sudo dnf install postgresql15 -y Last metadata expiration check: 0:41:27 ago on Tue Jun 6 21:13:01 2023. Dependencies resolved. ========================================================================================================================== Package Architecture Version Repository Size ========================================================================================================================== Installing: postgresql15 x86_64 15.0-1.amzn2023.0.2 amazonlinux 1.6 M Installing dependencies: postgresql15-private-libs x86_64 15.0-1.amzn2023.0.2 amazonlinux 143 k Transaction Summary ========================================================================================================================== Install 2 Packages Total download size: 1.8 M Installed size: 6.5 M Downloading Packages: (1/2): postgresql15-private-libs-15.0-1.amzn2023.0.2.x86_64.rpm 1.3 MB/s | 143 kB 00:00 (2/2): postgresql15-15.0-1.amzn2023.0.2.x86_64.rpm 9.0 MB/s | 1.6 MB 00:00 -------------------------------------------------------------------------------------------------------------------------- Total 7.3 MB/s | 1.8 MB 00:00 Running transaction check Transaction check succeeded. Running transaction test Transaction test succeeded. Running transaction Preparing : 1/1 Installing : postgresql15-private-libs-15.0-1.amzn2023.0.2.x86_64 1/2 Installing : postgresql15-15.0-1.amzn2023.0.2.x86_64 2/2 Running scriptlet: postgresql15-15.0-1.amzn2023.0.2.x86_64 2/2 Verifying : postgresql15-15.0-1.amzn2023.0.2.x86_64 1/2 Verifying : postgresql15-private-libs-15.0-1.amzn2023.0.2.x86_64 2/2 Installed: postgresql15-15.0-1.amzn2023.0.2.x86_64 postgresql15-private-libs-15.0-1.amzn2023.0.2.x86_64 Complete! # シークレットから認証情報を取得 $ get_secrets_value=$(aws secretsmanager get-secret-value \ --secret-id 'rds!cluster-8a84d67a-61f3-421d-8cd4-aaa0c1877f56' \ --region us-east-1 \ | jq -r .SecretString) # 環境変数に埋め込み $ export PGUSER=$(echo "${get_secrets_value}" | jq -r .username) $ export PGPASSWORD=$(echo "${get_secrets_value}" | jq -r .password) $ export PGHOST=db-cluster.cluster-cicjym7lykmq.us-east-1.rds.amazonaws.com $ export PGPORT=5432 $ export PGDATABASE=testDB $ psql psql (15.0, server 15.2) SSL connection (protocol: TLSv1.2, cipher: AES128-SHA256, compression: off) Type "help" for help. # sslinfo のエクステンションを作成 testDB=> create extension sslinfo; CREATE EXTENSION # SSL/TLSを使用しているか確認 testDB=> select ssl_is_used(); ssl_is_used ------------- t (1 row) # 使用しているSSL/TLSの暗号スイートの確認 testDB=> select ssl_cipher(); ssl_cipher --------------- AES128-SHA256 (1 row)
バナーなどからTLS 1.2 (AES128-SHA256) で接続していることが分かります。
SSL/TLS周りの情報を確認します。
testDB=> SELECT name as "Parameter name", setting as value, short_desc FROM pg_settings WHERE name LIKE '%ssl%'; Parameter name | value | short_desc ----------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------- ssl | on | Enables SSL connections. ssl_ca_file | /rdsdbdata/rds-metadata/ca-cert.pem | Location of the SSL certificate authority file. ssl_cert_file | /rdsdbdata/rds-metadata/server-cert.pem | Location of the SSL server certificate file. ssl_ciphers | TLS_RSA_WITH_AES_128_CBC_SHA256:DHE-RSA-AES128-SHA:TLS_RSA_WITH_AES_128_GCM_SHA256:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-GCM-SHA384:TLS_RSA_WITH_AES_256_GCM_SHA384:DHE-RSA-AES128-GCM-SHA256:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:DHE-RSA-AES256-SHA256:ECDHE-RSA-AES256-SHA384:TLS_RSA_WITH_AES_256_CBC_SHA:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:ECDHE-RSA-AES256-GCM-SHA384:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA2 56:DHE-RSA-AES128-SHA256:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:ECDHE-RSA-AES256-SHA:TLS_RSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 | Sets the list of allowed SSL ciphers. ssl_crl_dir | /rdsdbdata/rds-metadata/ssl_crl_dir/ | Location of the SSL certificate revocation list directory. ssl_crl_file | | Location of the SSL certificate revocation list file. ssl_dh_params_file | | Location of the SSL DH parameters file. ssl_ecdh_curve | prime256v1 | Sets the curve to use for ECDH. ssl_key_file | /rdsdbdata/rds-metadata/server-key.pem | Location of the SSL server private key file. ssl_library | OpenSSL | Shows the name of the SSL library. ssl_max_protocol_version | TLSv1.2 | Sets the maximum SSL/TLS protocol version to use. ssl_min_protocol_version | TLSv1.2 | Sets the minimum SSL/TLS protocol version to use. ssl_passphrase_command | | Command to obtain passphrases for SSL. ssl_passphrase_command_supports_reload | off | Controls whether ssl_passphrase_command is called during server reload. ssl_prefer_server_ciphers | on | Give priority to server ciphersuite order. (15 rows)
デフォルトではTLS 1.2しか使用しないようですね。
Aurora PostgreSQLのSSL/TLS周りの情報は以下AWS公式ドキュメントにまとまっています。
せっかくなので、より強い256bit暗号を使うように設定変更してみましょう。
Amazon Linux 2023にデフォルトでインストールされているOpenSSLがサポートしている暗号化スイートを確認します。
$ openssl ciphers -v 'HIGH:!ADH:!MD5:!RC4:!SRP:!PSK:!DSS:!ECDHE:!ECDSA:!EDH:!DH:!ECDH:!CAMELLIA256' TLS_AES_256_GCM_SHA384 TLSv1.3 Kx=any Au=any Enc=AESGCM(256) Mac=AEAD TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any Au=any Enc=CHACHA20/POLY1305(256) Mac=AEAD TLS_AES_128_GCM_SHA256 TLSv1.3 Kx=any Au=any Enc=AESGCM(128) Mac=AEAD TLS_AES_128_CCM_SHA256 TLSv1.3 Kx=any Au=any Enc=AESCCM(128) Mac=AEAD AES256-GCM-SHA384 TLSv1.2 Kx=RSA Au=RSA Enc=AESGCM(256) Mac=AEAD AES256-CCM8 TLSv1.2 Kx=RSA Au=RSA Enc=AESCCM8(256) Mac=AEAD AES256-CCM TLSv1.2 Kx=RSA Au=RSA Enc=AESCCM(256) Mac=AEAD ARIA256-GCM-SHA384 TLSv1.2 Kx=RSA Au=RSA Enc=ARIAGCM(256) Mac=AEAD AES128-GCM-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=AESGCM(128) Mac=AEAD AES128-CCM8 TLSv1.2 Kx=RSA Au=RSA Enc=AESCCM8(128) Mac=AEAD AES128-CCM TLSv1.2 Kx=RSA Au=RSA Enc=AESCCM(128) Mac=AEAD ARIA128-GCM-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=ARIAGCM(128) Mac=AEAD AES256-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=AES(256) Mac=SHA256 AES128-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=AES(128) Mac=SHA256 CAMELLIA128-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=Camellia(128) Mac=SHA256 AES256-SHA SSLv3 Kx=RSA Au=RSA Enc=AES(256) Mac=SHA1 AES128-SHA SSLv3 Kx=RSA Au=RSA Enc=AES(128) Mac=SHA1 CAMELLIA128-SHA SSLv3 Kx=RSA Au=RSA Enc=Camellia(128) Mac=SHA1
今回はAES256-GCM-SHA384
を使ってみましょう。
DBクラスターのパラメーターグループを変更します。
// DB Cluster Parameter Group const dbClusterParameterGroup = new cdk.aws_rds.ParameterGroup( this, "DbClusterParameterGroup", { engine: cdk.aws_rds.DatabaseClusterEngine.auroraPostgres({ version: cdk.aws_rds.AuroraPostgresEngineVersion.VER_15_2, }), description: "aurora-postgresql15", parameters: { log_statement: "none", "pgaudit.log": "all", "pgaudit.role": "rds_pgaudit", shared_preload_libraries: "pgaudit", ssl_ciphers: "TLS_RSA_WITH_AES_256_GCM_SHA384", }, } );
修正後、diffを確認します。
$ npx cdk diff Stack AuroraStack Resources [~] AWS::RDS::DBClusterParameterGroup Aurora/DbClusterParameterGroup AuroraDbClusterParameterGroupC83D24F1 └─ [~] Parameters └─ [+] Added: .ssl_ciphers
デプロイ後、再度DBクラスターに接続します。
$ psql psql (15.0, server 15.2) SSL connection (protocol: TLSv1.2, cipher: AES256-GCM-SHA384, compression: off) Type "help" for help. testDB=> select ssl_cipher(); ssl_cipher ------------------- AES256-GCM-SHA384 (1 row) testDB=> SELECT name as "Parameter name", setting as value, short_desc FROM pg_settings WHERE name LIKE 'ssl_ciphers'; Parameter name | value | short_desc ----------------+---------------------------------+--------------------------------------- ssl_ciphers | TLS_RSA_WITH_AES_256_GCM_SHA384 | Sets the list of allowed SSL ciphers. (1 row)
設定したとおりTLS_RSA_WITH_AES_256_GCM_SHA384
で接続していることが確認できました。
証明書の検証ができるかも確認しておきます。
# すべての AWS リージョン の中間証明書とルート証明書の両方を含む証明書バンドルのダウンロード $ wget https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem --2023-06-06 22:14:36-- https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem Resolving truststore.pki.rds.amazonaws.com (truststore.pki.rds.amazonaws.com)... 18.165.98.93, 18.165.98.60, 18.165.98.84, ... Connecting to truststore.pki.rds.amazonaws.com (truststore.pki.rds.amazonaws.com)|18.165.98.93|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 174184 (170K) [application/octet-stream] Saving to: ‘global-bundle.pem’ global-bundle.pem 100%[==============================================>] 170.10K --.-KB/s in 0.007s 2023-06-06 22:14:37 (23.0 MB/s) - ‘global-bundle.pem’ saved [174184/174184] # 証明書を検証して接続 $ psql "sslrootcert=global-bundle.pem sslmode=verify-full" psql (15.0, server 15.2) SSL connection (protocol: TLSv1.2, cipher: AES256-GCM-SHA384, compression: off) Type "help" for help. testDB=>
問題なく接続できましたね。
Construct treeを制するものがAWS CDKを制する
AWS CDKで新API版のAmazon Aurora DBクラスターのCAを指定してみました
「Construct treeを制するものがAWS CDKを制する」ような気がしてきました。APIに変更があったときは必ずdiffをして影響範囲を調べることが重要ですね。
使用したコードは以下リポジトリに保存しています。
この記事が誰かの助けになれば幸いです。
以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!