[AWS CDK] Amazon Aurora DBクラスターのCAを指定してみた (新API対応版)

Construct treeを制するものがAWS CDKを制する
2023.06.07

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やプロパティが指す値が変わった
    • instanceIdentifiersinstanceEndpointsはReaderの情報しか返さなくなった
    • DBクラスター内のDBインスタンスのIDがInstance${instanceIndex}などの連番ではなく、ClusterInstance.provisionedClusterInstance.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ではinstanceIdentifiersinstanceEndpointsは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がある場合のみinstanceEndpointsinstanceIdentifiersにpushしているのが分かりますね。

そのため、新APIではループのさせ方を変える必要があります。

続いてIDも変わりました。

aws-cdk/cluster.ts の ClusterInstanceAuroraClusterInstance確認しましょう。

aws-cdk/packages/aws-cdk-lib/aws-rds/lib/aurora-cluster-instance.ts

/**
 * 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,
    });
  }
}

aws-cdk/packages/aws-cdk-lib/aws-rds/lib/aurora-cluster-instance.ts

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.provisionedClusterInstance.serverlessV2のIDがそのまま使われていることが分かります。

対応してみる

対応箇所が分かったので、新APIの対応版を作成してみます。

まず、適当にAurora DBクラスターを作成します。

./lib/constructs/aurora.ts

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を確認します。

デフォルトの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になっていることを確認

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)でした!