AWS PrivateLinkで異なるアカウントのRDS(Aurora Serverless)にdata-apiで接続する


この記事でやること

AWS PrivateLinkを用いて、異なるアカウントにあるAurora Serverlessに対してdata-api接続で接続します
リージョンは同じである必要があります
ちょっとハマったのでメモとして記事にしました

※data-api接続は2020年2月にPrivateLink経由で接続できるようになりました
https://aws.amazon.com/jp/about-aws/whats-new/2020/02/amazon-rds-data-api-now-supports-aws-privatelink/

※Aurora Serverless v2っていうのがα版で来てるみたいです。要チェックですね!
https://dev.classmethod.jp/articles/aurora-serverless-v2/

まずPrivateLinkとはなにか

AWSアカウント間接続に使う仕組みです

AWSのドキュメントを見ると、VPCのエンドポイントのページに書いてあります
https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/endpoint-service.html

最初かなり混乱したのですが、こういうこと↓です
https://qiita.com/mksamba/items/20903940b8b256ef2487

VPC PrivateLinkというサービスメニューがあるわけではなく、「エンドポイントサービスとインターフェースエンドポイントをつなげること」=「PrivateLink」という感じなので、ちょっと分かりにくいなと感じた。

PrivateLinkとVPC Peeringの違い

AWSアカウント間接続には、どちらかを使うことになります

PrivateLink

比較的新しい方のサービスです。エンドポイントへのアクセスを別アカウントに飛ばします

接続元アカウントでエンドポイントを作成し、

  • 許可する接続先のAWSサービス(例、com.amazonaws.ap-northeast-1.rds-data)
  • 接続元のサブネット
  • 許可する通信のSG(security group)

を設定してアクセス権限を管理します

エンドポイントごとにSGが紐づくため、「どこに対して」「どの通信を」許可しているのかが明示しやすいです。
そのため「想定外の通信を許可してしまっていた」ことが起こりにくいです

接続先の指定はIPレンジではなく接続先エンドポイントの種類(例えばcom.amazonaws.ap-northeast-1.rds-data)
と対象リソースのarn(アカウントID情報を含む)で指定することになります
そのため複数の接続先とピアリングするケースで、接続先のIPレンジが被っていても問題ありません
接続元エンドポイントがリバースプロキシのように働き、接続を仲介していそうです

インターフェースエンドポイントは同一アカウントである必要があります
https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpce-interface.html#vpce-interface-limitations:~:text=%E5%90%8C%E3%81%98%E3%83%AA%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E5%86%85%E3%81%AE%E3%82%A8%E3%83%B3%E3%83%89%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%E3%81%AE%E3%81%BF%E3%81%8C%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99

料金

東京リージョンでは、インターフェースエンドポイントの場合
基本料金:0.014 * 24 * 30→10$/月
に加えて、下記がかかります
ネットワーク料金:10$/1TB
https://aws.amazon.com/jp/privatelink/pricing/

VPC Peering

比較的古い方のサービスです。VPC同士をくっつけます

接続元アカウントのVPCのルートテーブルに対して、
特定のIPレンジ(例えば、22.192.0.0/16)に対する通信をピアリングに対して接続するようにさせます。
ピアリングはそのアクセスを別VPC(別アカウントや別リージョンでも良い)の同じIPアドレスに対して飛ばします。

従って、接続元・接続先の間(または接続先と別の接続先)の間でIPレンジがバラけるようにする必要があります。
許可したいIPレンジに対するSGでのアクセス権限管理は、EC2インスタンス等に個別に付与することになります。
そのため、中央集権的なアカウント(弊社でいうとメインのサービスが乗っているアカウント)から見たときに、権限の統制がしづらいため、セキュリティが厳しい領域においてはあまり好まれません

料金

Peering自体に料金は掛かりません
EC2と同じようにネットワーク間通信量のみがかかります
https://aws.amazon.com/jp/vpc/faqs/#Peering_Connections:~:text=Q%3AVPC%20%E3%83%94%E3%82%A2%E3%83%AA%E3%83%B3%E3%82%B0%E6%8E%A5%E7%B6%9A%E3%81%AE%E6%96%99%E9%87%91%E3%81%AF%E3%81%84%E3%81%8F%E3%82%89%E3%81%A7%E3%81%99%E3%81%8B%3F

Assume Role

異なるアカウントのリソースに対するアクセスには、
先に接続先アカウントでIAM roleを作成しておき、Assume Roleでそのroleを引き受けることになります
(直接元アカウントのIAMにアクセス許可させることはできません)

接続元IAM

下記のようなcfn(cloud formation)テンプレートになります

  ServiceUser:
    Type: AWS::IAM::User
    Properties:
      Path: "/"
      Policies:
        -
          PolicyName: "assume-for-rds-data"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Sid: AssumeRdsData
                Effect: Allow
                Action:
                  - sts:AssumeRole
                Resource: arn:aws:iam::YOUR_AWS_ACCOUNT_ID:role/DataApiAccessRole

接続先IAM

  DataApiAccessRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: DataApiAccessRole
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action: "sts:AssumeRole"
            Principal:
              AWS:
                - "arn:aws:iam::接続元アカウントID:root"
      Policies:
        -
          PolicyName: "access-rds-data"
          PolicyDocument:
            # https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/data-api.html
            # https://aws.amazon.com/jp/premiumsupport/knowledge-center/secrets-manager-share-between-accounts/
            Version: '2012-10-17'
            Statement:
              - Sid: SecretsManagerDbCredentialsAccess
                Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                Resource: arn:aws:secretsmanager:ap-northeast-1:接続先(KMS所有者)のアカウントID:secret:secretの名前
              - Sid: CMKDecrypt
                Effect: Allow
                Action:
                  - kms:Decrypt
                  - kms:DescribeKey
                Resource: arn:aws:kms:ap-northeast-1:接続先(KMS所有者)のアカウントID:key/鍵のID
              - Sid: RDSDataServiceAccess
                Effect: Allow
                Action:
                  - secretsmanager:ListSecrets,
                  - secretsmanager:GetRandomPassword,
                  - tag:GetResources,
                  - rds-data:BatchExecuteStatement
                  - rds-data:BeginTransaction
                  - rds-data:CommitTransaction
                  - rds-data:ExecuteStatement
                  - rds-data:RollbackTransaction
                Resource: "*"

data-apiの仕組み

data-apiとはsql接続をhttpsを通して行うAWSの仕組みのことです
data-apiをauroa serverlessで利用するには、コンソールからdata-apiを有効化する必要があります(cfnではできない)

利用側はsqlドライバが不要で、認証もsecret manager経由のIAMで行うため鍵管理が不要です

コネクション管理も自動でやってくれて、同時接続への耐性が高いようです
(ただそれでもバッチを並列実行させて大量に叩くと重くなりましたが)
https://speakerdeck.com/iwatatomoya/aurora-serverlessfalsedata-apinituite?slide=16
https://dev.classmethod.jp/articles/how-data-api-manage-connection-pooling/

secret manager

secret managerとはDBの鍵やIAMユーザのsecretなど、
隠さないとまずいものを保管してIAMで取得できるようにしてくれるサービスです

CMK鍵とsecretがあり、CMK鍵を使ってsecretを施錠するイメージです
default鍵もありますが、他アカウントからアクセスさせる場合は、権限管理上新規に作ったほうがいいです

CMK鍵1つにつき1$/月と、量に応じた料金がかかります
https://aws.amazon.com/jp/kms/pricing/#Key_Storage:~:text=%E3%82%AD%E3%83%BC%E3%82%B9%E3%83%88%E3%83%AC%E3%83%BC%E3%82%B8%E3%81%AE%E6%9C%88%E9%A1%8D%201%20USD%20%E3%81%8C

data-api周りの設定

クロスアカウントだといろいろ複雑です
下記手順が必要です

1.接続先のsecret managerで新規のCMK鍵を作る

2.CMK鍵の設定にて接続元アカウントからのアクセスを許可します

{
    "Version": "2012-10-17",
    "Id": "key-consolepolicy",
    "Statement": [
        {
            "Sid": "Enable IAM User Permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::接続先(KMS所有者)のアカウントID:root"
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Sid": "Allow use of the key",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::接続元アカウントID:root",
                ]
            },
            "Action": [
                "kms:Decrypt",
                "kms:DescribeKey"
            ],
            "Resource": "arn:aws:kms:ap-northeast-1:接続先(KMS所有者)のアカウントID:key/鍵のID"
        }
    ]
}

3.CMK鍵を使って、secretを作る
secretにはDBのユーザとパスワードを格納します

4.secretの設定にてASSUMEされる接続先IAMからのアクセスを許可

{
  "Version" : "2012-10-17",
  "Statement" : [ {
    "Effect" : "Allow",
    "Principal" : {
      "AWS" : [
        "arn:aws:iam::接続先(KMS所有者)のアカウントID:user/ASSUMEされるIAM"
      ]
    },
    "Action" : "secretsmanager:GetSecretValue",
    "Resource" : "*",
    "Condition" : {
      "ForAnyValue:StringEquals" : {
        "secretsmanager:VersionStage" : "AWSCURRENT"
      }
    }
  } ]
}

5.接続先アカウントにDBに接続できる権限のIAMを作る

6.接続元アカウントに上記にassumeできるIAMを作る

7.接続元アカウントでエンドポイントを作成する
https://dev.classmethod.jp/articles/rds-data-api-now-supports-aws-privatelink/
その際、セキュリティグループをattachする。権限はhttpsのin-outを最低限許可する

8.接続元・接続先双方のサブネットで該当のhttps通信がNetworkACLで遮断されないことを確認する

まとめ

Aurora Serverlessに対してdata-api接続をするだけならマニュアル通りにできるのですが、
異なるアカウントとなると直接書いてあるドキュメントが見当たらなかったのでまとめてみました
IAM周りがややこしいですね

ちなみにSQLAlchemyのdata-api接続用のプラグインもあります
https://pypi.org/project/sqlalchemy-aurora-data-api/