2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Aurora(Postgres)にRDS Proxy経由(IAM認証)でLambdaから接続する設定をcdk v2で行ったメモ

Last updated at Posted at 2022-01-09

概要

LambdaからRDS Proxy経由でDB設定を行う手順のメモ。
RDSは前々回利用したものを使う。
また、RDSへの接続は前回の手順でポートフォワーディングをして行う。
コード本文にパスワードを含めないほうがかっこいいと思ってRDSProxyへの接続はIAM認証を使用している。
また、Adminユーザ以外でのユーザの接続もここで試す。
cdk,lambdaともにTypescriptを使用。

ソースコード

アーキテクチャ

image.png

環境

  • Windows 10
  • aws-cli/2.4.7 Python/3.8.8 Windows/10 exe/AMD64 prompt/off
  • aws-cdk: 2.4.0

CDK

環境変数

今回利用するDB設定用の環境変数(最終系)。
ARNやエンドポイントは、RDSProxy作成後にコンソールから確認して設定しているので(Lambda用のStackを実行する直前に設定)。
Stackをまとめたり、一つのAppで複数のStackを管理したらこのあたりは不要になるかも。

プロキシARNはarn:aws:rds-dbで指定すること。コンソールからコピーするとarn:aws:rdsになるので注意。
arn:aws:rds-db:ap-northeast-1:000000:db-proxy:prx-xxx
×arn:aws:rds:ap-northeast-1:000000:db-proxy:prx-xxx

proxy.grantConnect(role, props.dbAdminName);では以下のようなポリシーを持ったロールが作成される。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": "rds-db:connect",
            "Resource": "arn:aws:rds-db:ap-northeast-1:000000:dbuser:prx-xxx/rdsadmin",
            "Effect": "Allow"
        },
        {
            "Action": "rds-db:connect",
            "Resource": "arn:aws:rds-db:ap-northeast-1:000000:dbuser:prx-xxx/rdsuser",
            "Effect": "Allow"
        }
    ]
}
cdk/.env
VPC_ID=vpc-hoge
PRIVATE_SG_ID=sg-fuga
PUBLIC_SG_ID=sg-hoge
SUBNET_GROUP_NAME=SubnetGroupForAurora
PUBLIC_SUBNET_ID=subnet-hoge
DB_SECRET_NAME=db-secrets
DB_ADMIN_NAME=rdsadmin
+ DB_USER_SECRET_NAME=db-rdsuser-secrets
+ DB_USER_NAME=rdsuser
+ DB_USER_RESOURCE_ARN=プロキシARN(arn:aws:rds-db:ap-northeast-1:000000:db-proxy:prx-xxx)
+ DB_PROXY_ENDPOINT=proxy.proxy-hoge.ap-northeast-1.rds.amazonaws.com

VPC設定

Lambda用のセキュリティグループを追加

cdk/lib/vpc-stack.ts
import { Aspects, Stack, StackProps, Tag, Tags } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Peer, Port, PrivateSubnet, PrivateSubnetProps, SecurityGroup, SubnetType, Vpc } from 'aws-cdk-lib/aws-ec2';
import { SubnetGroup } from 'aws-cdk-lib/aws-rds';

const DB_PORT = 5432;

interface VpcStackProps extends StackProps {
  subnetGroupName: string
}
export class VpcStack extends Stack {
  constructor(scope: Construct, id: string, props: VpcStackProps) {
    super(scope, id, { ...props, subnetGroupName: undefined } as StackProps);
    const cidr = '10.0.0.0/16';
    const vpc = new Vpc(this, 'VPC', {
      cidr,
      natGateways: 0, // デフォルトは1。
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: "public-subnet",
          subnetType: SubnetType.PUBLIC
        },
      ], // デフォルトはAZごとに1つのパブリックサブネットと1つのプライベートサブネット
      maxAzs: 3 // 3を指定すると、 public: 10.0.0.0 - 10.0.0.2 の3つ分のcidrBlockが使われる。
    })
    // Tags.of(vpc).add('Stack', id);
    Tags.of(vpc).add('Name', 'vpc');

    // プライベートSubnet(RDS用)
    const privateSubnetProps: PrivateSubnetProps[] = [
      { availabilityZone: 'ap-northeast-1a', vpcId: vpc.vpcId, cidrBlock: '10.0.3.0/24' },
      { availabilityZone: 'ap-northeast-1c', vpcId: vpc.vpcId, cidrBlock: '10.0.4.0/24' },
      { availabilityZone: 'ap-northeast-1d', vpcId: vpc.vpcId, cidrBlock: '10.0.5.0/24' },
    ]

    const subnets = privateSubnetProps.map((prop, i) => {
      const subnet = new PrivateSubnet(this, `MyPrivateSubnet${i}`, prop);
      Tags.of(subnet).add('Name', `private-subnet-${i}`);
      Tags.of(subnet).add('aws-cdk:subnet-type', SubnetType.PRIVATE_ISOLATED);
      return subnet
    });

    //------------------ Aurora用の設定 ----------------------------------
    const dbConnectionGroup = new SecurityGroup(this, 'SecurityGroupForPrivateSubnets', {
      vpc,
      description: 'seburity group for Aurora'
    })
    Tags.of(dbConnectionGroup).add('Name', 'SecurityGroupForPrivateSubnets');

    const subnetGroupForAurora = new SubnetGroup(this, 'SubnetGroupForAurora', {
      vpc,
      vpcSubnets: { subnets },
      description: 'subnet group for Aurora db',
      subnetGroupName: props.subnetGroupName
    });
    Tags.of(subnetGroupForAurora).add('Name', 'SubnetGroupForAurora');

    //------------------ 踏み台用の設定 ----------------------------------
    const securityGroupPublic = new SecurityGroup(this, 'SecurityGroupForPublicSubnets', {
      vpc,
      description: 'seburity group for bastion'
    })
    Tags.of(securityGroupPublic).add('Name', 'SecurityGroupForPublicSubnets');
    dbConnectionGroup.addIngressRule(securityGroupPublic, Port.tcp(DB_PORT));


+    //------------------ Lambda用の設定 ----------------------------------
+    const lambdaToRDSProxyGroup = new SecurityGroup(this, 'Lambda to RDS Proxy Connection', vpc });
+    dbConnectionGroup.addIngressRule(lambdaToRDSProxyGroup, Port.tcp(DB_PORT), 'allow lambda connection');
    //------------------ 共通設定 ----------------------------------
    // 作成したリソース全てにタグをつける
    Aspects.of(this).add(new Tag('Stack', id));
  }
}

RDS Proxy設定

IAM認証とTLS必須の設定を行ってProxyを作成。
ProxyのセキュリティグループはRDSと同様とする。
ProxyにSecretManager経由でのRDS接続用の管理者・ユーザの接続権限を与える。

cdk/lib/aurora-stack.ts
import { Aspects, RemovalPolicy, Stack, StackProps, Tag, Tags } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { InstanceClass, InstanceSize, InstanceType, SecurityGroup, Vpc } from 'aws-cdk-lib/aws-ec2';
import { AuroraPostgresEngineVersion, Credentials, DatabaseCluster, DatabaseClusterEngine, DatabaseProxy, ProxyTarget, SubnetGroup } from 'aws-cdk-lib/aws-rds';
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
import { AccountPrincipal, Effect, PolicyStatement, Role } from 'aws-cdk-lib/aws-iam';

interface AuroraStackProps extends StackProps {
  vpcId: string
  sgId: string
  subnetGroupName: string
  dbAdminName: string
  dbAdminSecretName: string
  dbReadOnlyUserName: string
  dbReadOnlyUserSecretName: string
}

export class AuroraStack extends Stack {
  constructor(scope: Construct, id: string, props: AuroraStackProps) {
    const superProps = {
      ...props, vpcId: undefined, sgId: undefined, subnetName: undefined
      , dbSecretName: undefined, dbAdminName: undefined, dbUserPassword: undefined

    } as StackProps
    super(scope, id, superProps);

    const vpc = Vpc.fromLookup(this, 'Vpc', { vpcId: props.vpcId })
    const securityGroup = SecurityGroup.fromLookupById(this, 'SecurityGroup', props.sgId);
    const subnetGroup = SubnetGroup.fromSubnetGroupName(this, 'SubnetGroup', props.subnetGroupName.toLowerCase());

    const secret = this.createSecret({ secretName: props.dbAdminSecretName, rdsName: props.dbAdminName });

    const cluster = new DatabaseCluster(this, 'clusterForAurora', {
      // LTSのバージョンを選択.RDSProxyは10と11のみのサポート 2021.12.10
      engine: DatabaseClusterEngine.auroraPostgres({ version: AuroraPostgresEngineVersion.VER_11_9 }),
      removalPolicy: RemovalPolicy.DESTROY, // 本番運用だと消しちゃだめだと思う
      defaultDatabaseName: 'postgres',
      instanceProps: {
        vpc,
        securityGroups: [securityGroup],
        instanceType: InstanceType.of(InstanceClass.T4G, InstanceSize.MEDIUM),
      },
      instances: 1,
      subnetGroup,
      credentials: Credentials.fromSecret(secret)
    });


+    // RDSで読み取り用に作成する予定のユーザをシークレットに登録
+    const secretForDBUser = this.createSecret({ secretName: props.dbReadOnlyUserSecretName, rdsName: props.dbReadOnlyUserName });


+    const proxy = new DatabaseProxy(this, 'Proxy', {
+      proxyTarget: ProxyTarget.fromCluster(cluster),
+      secrets: [secret, secretForDBUser],
+      vpc,
+      securityGroups: [securityGroup],
+      requireTLS: true,
+      iamAuth: true
+    });
+    Tags.of(proxy).add('Name', 'AuroraProxy');


+    const role = new Role(this, 'DBProxyRole', { assumedBy: new AccountPrincipal(this.account) });
+    Tags.of(role).add('Name', 'AuroraProxyRole');


+    proxy.grantConnect(role, props.dbAdminName);
+    proxy.grantConnect(role, props.dbReadOnlyUserName);

    // 作成したリソース全てにタグをつける
    Aspects.of(this).add(new Tag('Stack', id));

  }
  private createSecret(props: { secretName: string, rdsName: string }) {
    const secret = new Secret(this, props.secretName, {
      secretName: props.secretName,
      generateSecretString: {
        secretStringTemplate: JSON.stringify({ username: props.rdsName }),
        excludePunctuation: true, // '、/、"、@、スペースはpostgresのパスワードに利用できないので除外
        includeSpace: false,
        generateStringKey: 'password'
      }
    })
    Tags.of(secret).add('Name', props.secretName);
    return secret;
  }
}

Lambda設定

lambdaのcdkを動かす前に、RDSを作成し、読取専用のユーザを作成しておく。(後述)
lambdaはapi gatewayを通してアクセスするものとする。読取・書込をそれぞれのユーザで検証できるように設定。
lambda layerにnode_modulesを配置し、同階層にProxyのTLS用のpemファイルをコピーすることとする。
(src/common/data/AmazonRootCA1.pemルート証明書が配置されているとする。 *)
lambda layerは /opt/nodejs/のパスでアクセスできるので、ソース上からのアクセスは/opt/nodejs/data/AmazonRootCA1.pemとなる。

cdk/package.json
{
  "scripts": {
    "createLayer": "ts-node lib/process/pre.ts",
    "copyData": "cd .. && npm run bundleData",
    "prelambda-deploy": "npm run createLayer && npm run copyData",
    "lambda-deploy": "cdk -a \"npx ts-node --prefer-ts-exts bin/lambda.ts\" destroy PrivateLambdaStack -c @aws-cdk/core:newStyleStackSynthesis=true --profile produser"
  }
}
package.json
{
  "scripts": {
    "bundleData": "cp -r src/common/data cdk/bundle-node_modules/nodejs/data"
  }
}
cdk/bin/lambda.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { PrivateLambdaStack } from '../lib/private-lambda-stack';
import { config } from 'dotenv'
config();

const app = new cdk.App();
const env = {
  account: process.env.CDK_DEFAULT_ACCOUNT,
  region: process.env.CDK_DEFAULT_REGION
}

const envNames = ['VPC_ID', 'PRIVATE_LAMBDA_SG_ID', 'DB_USER_RESOURCE_ARN', 'DB_ADMIN_NAME', 'DB_PROXY_ENDPOINT', 'DB_USER_NAME'] as const
const checkEnvs = (e: any): e is Record<(typeof envNames)[number], string> => {
  for (const a of envNames) {
    if (!e[a]) throw new Error(`please set environment variable ${a}`)
  }
  return true
}
if (!checkEnvs(process.env)) throw new Error('到達しない')

new PrivateLambdaStack(app, 'PrivateLambdaStack', {
  env,
  vpcId: process.env.VPC_ID,
  sgId: process.env.PRIVATE_LAMBDA_SG_ID,
  rdsProxyArn: process.env.DB_USER_RESOURCE_ARN,
  dbAdminName: process.env.DB_ADMIN_NAME,
  dbProxyEndpont: process.env.DB_PROXY_ENDPOINT,
  dbReadOnlyUserName: process.env.DB_USER_NAME
});
cdk/lib/private-lambda-stack.ts
import { Aspects, Duration, Stack, StackProps, Tag, Tags } from 'aws-cdk-lib';
import { NodejsFunction, BundlingOptions } from 'aws-cdk-lib/aws-lambda-nodejs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';
import { NODE_LAMBDA_LAYER_DIR } from './process/setup';
import { ISecurityGroup, IVpc, SecurityGroup, SelectedSubnets, SubnetType, Vpc } from 'aws-cdk-lib/aws-ec2';
import { LambdaIntegration, RestApi } from 'aws-cdk-lib/aws-apigateway';
import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam';

interface PrivateLambdaStackProps extends StackProps {
  vpcId: string
  sgId: string
  rdsProxyArn: string
  dbAdminName: string
  dbProxyEndpont: string
  dbReadOnlyUserName: string
}

export class PrivateLambdaStack extends Stack {
  constructor(scope: Construct, id: string, props: PrivateLambdaStackProps) {
    super(scope, id, props);
    const vpc = Vpc.fromLookup(this, 'Vpc', { vpcId: props.vpcId })
    const securityGroup = SecurityGroup.fromLookupById(this, 'SecurityGroup', props.sgId);
    const vpcSubnets = vpc.selectSubnets({ subnetType: SubnetType.PRIVATE_ISOLATED })

    const nodeModulesLayer = new lambda.LayerVersion(this, 'NodeModulesLayer',
      {
        code: lambda.AssetCode.fromAsset(NODE_LAMBDA_LAYER_DIR),
        compatibleRuntimes: [lambda.Runtime.NODEJS_14_X]
      }
    );
    const bundling = {
      externalModules: [
        'aws-sdk', // Use the 'aws-sdk' available in the Lambda runtime
        'date-fns', // Layrerに入れておきたいモジュール
        'pg'
      ],
    }
    const lambdaParamsDefault = {
      layers: [nodeModulesLayer],
      vpc,
      vpcSubnets,
      securityGroups: [securityGroup],
      bundling
    }

    const helloLambda = this.createLambda({
      ...lambdaParamsDefault,
      entry: `../src/handler/api/hello.ts`,
      name: 'hellorLambda',
      descritption: 'サンプル用メッセージ表示'
    })

    const rdsEnv = {
      DB_PORT: '5432',
      DB_HOST: props.dbProxyEndpont,
      DB_USER: props.dbAdminName,
      DB_DBNAME: 'postgres',
    }
    const rdsEnvReadOnly = {
      ...rdsEnv,
      DB_USER: props.dbReadOnlyUserName,
    }
    const rdsAdminResource = `${props.rdsProxyArn}/${props.dbAdminName}`;
    const rdsReadOnlyUserResource = `${props.rdsProxyArn}/${props.dbReadOnlyUserName}`;
    const adminPolicy = new PolicyStatement({
      effect: Effect.ALLOW,
      actions: ['rds-db:connect'],
      resources: [rdsAdminResource],
    })
    const readOnlyUserPolicy = new PolicyStatement({
      effect: Effect.ALLOW,
      actions: ['rds-db:connect'],
      resources: [rdsReadOnlyUserResource],
    })


    const electricLambda = this.createLambda({
      ...lambdaParamsDefault,
      entry: `../src/handler/api/getElectric.ts`,
      name: 'electricLambda',
      descritption: 'RDSAdminでテーブル参照',
      environment: { ...rdsEnv },
      initialPolicy: [adminPolicy],
      timeoutSec: 10, // DBへの再接続を1秒置き、3回まで行うため、デフォルトの3秒より延ばしておく
    })

    const api = new RestApi(this, 'ServerlessRestApi', { cloudWatchRole: false });
    api.root.addResource('hello').addMethod('GET', new LambdaIntegration(helloLambda));
    const electricResource = api.root.addResource('electric')
    electricResource.addMethod('GET', new LambdaIntegration(electricLambda));

    const electricLambda2 = this.createLambda({
      ...lambdaParamsDefault,
      entry: `../src/handler/api/getElectric.ts`,
      name: 'electricLambda2',
      descritption: 'RDS 読取専用ユーザでテーブル参照',
      environment: { ...rdsEnvReadOnly },
      initialPolicy: [readOnlyUserPolicy],
      timeoutSec: 10,
    })

    const electricReadonlyResource = api.root.addResource('electric-readonly');
    electricReadonlyResource.addMethod('GET', new LambdaIntegration(electricLambda2));

    const electricLambda3 = this.createLambda({
      ...lambdaParamsDefault,
      entry: `../src/handler/api/postElectric.ts`,
      name: 'electricLambda3',
      descritption: 'RDS 読取専用ユーザでテーブル挿入',
      environment: { ...rdsEnvReadOnly },
      initialPolicy: [readOnlyUserPolicy],
      timeoutSec: 10,
    })

    electricReadonlyResource.addMethod('POST', new LambdaIntegration(electricLambda3));

    const electricLambda4 = this.createLambda({
      ...lambdaParamsDefault,
      entry: `../src/handler/api/postElectric.ts`,
      name: 'electricLambda4',
      descritption: 'RDSAdminでテーブル挿入',
      environment: { ...rdsEnv },
      initialPolicy: [adminPolicy],
      timeoutSec: 10,
    })
    electricResource.addMethod('POST', new LambdaIntegration(electricLambda4));

    Aspects.of(this).add(new Tag('Stack', id));
  }

  private createLambda(props: {
    layers: lambda.LayerVersion[]
    vpc: IVpc
    vpcSubnets: SelectedSubnets
    securityGroups: ISecurityGroup[]
    bundling: BundlingOptions
    name: string
    descritption: string
    entry: string
    environment?: Record<string, string>
    initialPolicy?: PolicyStatement[]
    timeoutSec?: number
  }) {
    const func = new NodejsFunction(this, props.name, {
      runtime: lambda.Runtime.NODEJS_14_X,
      entry: props.entry,
      functionName: props.name,
      description: props.descritption,
      layers: props.layers,
      vpc: props.vpc,
      vpcSubnets: props.vpcSubnets,
      securityGroups: props.securityGroups,
      bundling: props.bundling,
      environment: props.environment,
      initialPolicy: props.initialPolicy,
      timeout: props.timeoutSec ? Duration.seconds(props.timeoutSec) : undefined,
    });
    Tags.of(func).add('Name', props.name);
    return func;
  }
}

Postgresへ接続するLambdaのソース

IAM認証で接続する。
接続できなかった場合、最大5回リトライ。
ローカルで実行した場合には、dockerで起動されたDBに接続するようにしている。
(ローカル実行時には環境変数process.env.AWS_SAM_LOCALtrueに設定される。)
また、lambdaのライフサイクル的に、lambda内でのプールの使いまわしは難しい。
確実にコネクションをクローズできるようにラッパーメソッドを用意しておく。

src/common/persistants/postgres.ts
import { Pool } from 'pg';
import { RDS, SecretsManager } from 'aws-sdk'
import * as fs from 'fs'
import type { PoolClient } from 'pg';

const RETRY_COUNT = 5;
const RETRY_INTERVAL_MILLI_SECOND = 100;
const signer = new RDS.Signer()
let pool: Pool | null = null;

class Postgres {
  #client: PoolClient

  async init() {
    if (!pool) throw new Error('pool is undefined')

    for (let i = 0; i < RETRY_COUNT; i++) {
      try {
        this.#client = await pool.connect();
        return;
      } catch (e) {
        console.warn(`error try ${i}`, e);
        await new Promise(resolve => setTimeout(resolve, RETRY_INTERVAL_MILLI_SECOND));
      }
    }
    throw new Error('connect failed')
  }

  async execute(query: string, params = []) {
    return (await this.#client.query(query, params)).rows;
  }

  async release() {
    await this.#client.release(true);
  }

  async begin() {
    await this.#client.query('BEGIN');
  }

  async commit() {
    await this.#client.query('COMMIT');
  }

  async rollback() {
    await this.#client.query('ROLLBACK');
  }
}

const getPool = () => {
  if (process.env.AWS_SAM_LOCAL === 'true') {
    // ローカル実行用。admin。 docker/.envで設定したPostgresへの接続内容。host.docker.internalはdockerコンテナ内からホスト上のサービスに対して接続するときのDNS名。
    const connectionString = 'postgresql://admin:secret@host.docker.internal:5432/postgres';
    return new Pool({ connectionString });
  }

  const { DB_PORT, DB_HOST, DB_USER, DB_DBNAME, AWS_REGION } = process.env;
  if (!DB_PORT) throw new Error(`DB_PORT is undefined`)
  if (!DB_HOST) throw new Error(`DB_HOST is undefined`)
  if (!DB_USER) throw new Error(`DB_USER is undefined`)
  if (!DB_DBNAME) throw new Error(`DB_DBNAME is undefined`)
  if (!AWS_REGION) throw new Error('AWS_REGION is undefined') // RDSとLambdaが同一のリージョンに存在する想定

  const signerOptions = {
    region: AWS_REGION,
    hostname: DB_HOST, 
    port: Number(DB_PORT),
    username: DB_USER,
  }

  return new Pool({
    host: signerOptions.hostname,
    port: signerOptions.port,
    user: signerOptions.username,
    database: DB_DBNAME,
    // IAM認証のため、パスワードの代わりにトークンを使用する。
    password: () => signer.getAuthToken(signerOptions),
    ssl: {
      ca: fs.readFileSync('/opt/nodejs/data/AmazonRootCA1.pem')
    }
  });
}

const getClient = async () => {
  if (!pool) pool = getPool();
  const postgres = new Postgres();
  await postgres.init();
  return postgres;
};

export const executeSingleQuery = async (query: string, params: any[] = []) => {
  if (!pool) pool = getPool();
  for (let i = 0; i < RETRY_COUNT; i++) {
    try {
      const ret = await pool.query(query, params);
      return ret.rows;
    } catch (e) {
      console.warn(`error try ${i}`, e);
      await new Promise(resolve => setTimeout(resolve, RETRY_INTERVAL_MILLI_SECOND));
    }
  }
  throw new Error('connect failed')
}

export const executeTransaction = async (executeQuery: (client: Postgres) => Promise<any>) => {
  const client = await getClient();

  try {
    await client.begin();
    const ret = await executeQuery(client);
    await client.commit();
    return ret
  } catch (e: any) {
    console.warn('transaction rollback. error ->', e);
    await client.rollback();
    throw e;
  } finally {
    await client.release();
  }
}
src/handler/api/getElectric.ts
import { APIGatewayProxyHandler } from 'aws-lambda';
import { executeSingleQuery } from '@/common/persistants/postgres';

export const handler: APIGatewayProxyHandler = async (event) => {
  const result = await executeSingleQuery('select * from electric');
  return {
    statusCode: 200,
    body: JSON.stringify(result)
  };
};
src/handler/api/postElectric.ts
import { APIGatewayProxyHandler } from 'aws-lambda';
import { getPostgresClient } from '@/common/persistants/postgres';

export const handler: APIGatewayProxyHandler = async (event) => {
  const client = await getPostgresClient();
  const result = await client.execute(`insert into electric(id,name,measuredtime,value) values(99,'test',now(),1111);`);
  return {
    statusCode: 200,
    body: JSON.stringify(result)
  };
};

動作確認

まずは、RDSのデプロイを行う。

cd cdk
npm run vpc-deploy
npm run aurora-deploy
npm run bastion-deploy

次に、ポートフォワーディングを有効にする。

./bin/create-document.sh
./bin/start-port-forwarding.sh

ここで、シークレットマネージャで作成した読取用ユーザのパスワードを確認しておく。

SECRET_NAME=db-rdsuser-secrets
SECRET=$(aws secretsmanager get-secret-value --region ap-northeast-1 --secret-id $SECRET_NAME --profile produser | jq .SecretString | jq fromjson)
echo $SECRET | jq -r .password

データベースで読取用のユーザを作成する。
テスト用のテーブルとデータも作成しておく。

CREATE USER rdsuserWITH PASSWORD '確認したパスワード';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO rdsuser;

create table electric (
  id integer,
  name varchar,
  measuredtime timestamp,
  value numeric(19,1)
);
insert into electric(id,name,measuredtime,value) values(1,'test1',now(),100);
insert into electric(id,name,measuredtime,value) values(2,'test2',now(),10.11);
select * from electric;

コンソールからデプロイされたRDSProxyを確認し、cdk/.envDB_USER_RESOURCE_ARNDB_PROXY_ENDPOINTの値を記述する。
lambdaをデプロイする。

cd cdk
npm run lambda-deploy

REST で結果を確認する。

###
get https://hoge.execute-api.ap-northeast-1.amazonaws.com/prod/hello

###
get  https://hoge.execute-api.ap-northeast-1.amazonaws.com/prod/electric

###
get  https://hoge.execute-api.ap-northeast-1.amazonaws.com/prod/electric-readonly

###
post  https://hoge.execute-api.ap-northeast-1.amazonaws.com/prod/electric

###
post  https://hoge.execute-api.ap-northeast-1.amazonaws.com/prod/electric-readonly
# エラー発生。permission denied for table electric

参考

公式

RDS Proxy の開始方法
IAMデータベースアクセス用のIAMポリシーの作成と使用
Troubleshooting for RDS Proxy

Amazon RDS Proxy のご紹介 ... ピン留め問題など
ピン留めを回避する
AWSのポリシーとアクセス許可

CDK v2 - RDS

野良

CDK Patternsを参考にしてRDS Proxyを利用したAPIをデプロイしてみた
SAMでローカル実行したLambdaからMongodbへのアクセスを試してみたメモ
aws-cdk v2でlambda layers を扱ったメモ
docker - コンテナからホスト上のサービスに対して接続したい
IAM ロールの PassRole と AssumeRole をもう二度と忘れないために絵を描いてみた
[AWS SAM] Amazon RDS Proxy をつかって Amazon Aurora MySQL に接続するサーバーレスアプリケーションを構築する
LambdaでRDS Proxy経由でAurora(MySQL)に接続する(IAM認証)
TypeORMからIAMデータベース認証でRDSへ接続する

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?