セキュリティリスク回避のためのIAM認証導入

はじめに

Amazon RDSではユーザー名とパスワード以外にIAM認証という機能があります。RDSなどの接続用パスワードをソースコードに書き込むのはよくあるセキュリティリスクの一つではあるんですが、環境変数やParameter Store、Secrets Managerに格納するのはそれはそれで面倒臭いです。そこで、IAM認証という機能を使ってパスワードを使わないようにします。

今回は、MySQLでEC2からCLI接続するときの認証をIAM認証を使ったケースを試してみます。

準備

EC2からMySQLに接続するためのCLIをインストールします。以下を参照しますが、MariaDBクライアントを利用します。

docs.aws.amazon.com

今後の作業用のために環境変数としてDB_ENDPOINTIAM_USERNAMEを指定します。

export DB_ENDPOINT=RDSのエンドポイント
export IAM_USERNAME=IAM認証するユーザ名

手順

具体的な手順は以下のre:Postにて公開されていますので、この手順に従って作業を進めます。

repost.aws

IAM認証の有効化

まず、RDSのインスタンス作成時や変更でIAM認証を有効化しておきます。

IAM認証を有効化してない場合、以下のSQL文の実行時にエラーメッセージERROR 1524 (HY000): Plugin 'AWSAuthenticationPlugin' is not loadedが出力されます。

CREATE USER IAM認証するユーザ名 IDENTIFIED WITH AWSAuthenticationPlugin as 'RDS';

SSLを使用して接続するように設定します。

ALTER USER IAM認証するユーザ名 REQUIRE SSL;

IAMポリシーの作成

接続の許可を設定するためにIAMポリシーを設定します。詳細は以下を確認します。

docs.aws.amazon.com

作成したIAMポリシーをもとにIAMロールを作成し、EC2にアタッチすると準備はOKです。

証明書を取得する

接続時に指定する証明書をダウンロードします。上記のre:Postの記事にある証明書を使って接続しようとするとself signed certificate in certificate chainというエラーメッセージがでて接続できませんでした。そのため、以下のリンク先からリンクが貼られている証明書から使用しているAWSリージョンにあった証明書をダウンロードします。

docs.aws.amazon.com

今回の場合、東京リージョンで実施したので、wget https://truststore.pki.rds.amazonaws.com/ap-northeast-1/ap-northeast-1-bundle.pemを実行しました。

認証トークンを生成する

AWS CLIを使って認証トークンを取得して環境変数TOKENに保存します。以下のコマンドを実行します。

TOKEN=`aws rds generate-db-auth-token --hostname $DB_ENDPOINT --port 3306 --username $IAM_USERNAME`

接続する

以下のコマンドを実行します。

mariadb -h $DB_ENDPOINT  -u $IAM_USERNAME --ssl-ca=ap-northeast-1-bundle.pem --password=$TOKEN

無事、接続できました。

[ec2-user@ip-xxx-xxx-xxx-xxx ~]$ mariadb -h $DB_ENDPOINT  -u $IAM_USERNAME --ssl-ca=ap-northeast-1-bundle.pem --password=$TOKEN
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 39
Server version: 8.0.35 Source distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)]> 

考察

初期セットアップは思いのほか大変ですが、パスワードを取り回さなくてもよいのは非常に楽です。

今回はMySQLのクライアントで試してみましたが、元々はプログラム内でRDSのパスワードを保持したくないという思いがあります。これを実装してみたいです。

また、PostgreSQLもIAM認証に対応しているのでPostgreSQL版も試してみます。

無償版LocalStackでのRuby SDKを用いたS3操作検証方法

はじめに

AWS上でのシステムを構築する際に簡単な挙動をローカル環境で確認したいという要望はちょくちょく聞きます。そのときによく使われるのがLocalStackです。

www.localstack.cloud

有償版・無償版それぞれあるのですが、無償版でも多くの機能が使えます。詳細は以下を参照。

docs.localstack.cloud

今回はこのLocalStackの無償版を使ってRuby SDKでS3の操作を試してみます。

前提

以下のバージョンで試しました。

  • LocalStack 3.3.0
  • Ruby 3.3.0
  • AWS SDK S3 gem 1.146.1

LocalStackの準備

LocalStackのセットアップはDockerを使うと簡単です。以下のようなcompose.ymlを作成しました。

version: "3.8"

services:
  localstack:
    container_name: "${LOCALSTACK_DOCKER_NAME:-localstack-main}"
    image: localstack/localstack:3.3.0
    ports:
      - "127.0.0.1:4566:4566"
    environment:
      - DEBUG=${DEBUG:-0}
    volumes:
      - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"
  app:
    build: .
    tty: true
    volumes:
      - .:/myapp/

アプリの準備

アプリ検証用としてDockerfileも用意しておきます。

FROM ruby:3.3.0-slim

WORKDIR /myapp

COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock

RUN bundle install

COPY lib /myapp/lib

gemの管理用としてGemfileを以下の内容で作成します。

source 'https://rubygems.org'

gem 'aws-sdk-s3', '~> 1.146.1'

Gemfile.lockは空ファイルとして作成します。

アプリはlib/app.rbとして準備しておきます。今回はこんな感じで。

require 'aws-sdk-s3'

region = "us-east-1"
bucket_name = "bucket01"
key_name = "key01"

Aws.config.update(
  endpoint: "http://localstack:4566",
  access_key_id: "test",
  secret_access_key: "test",
  region: region,
  force_path_style: true
)

client = Aws::S3::Client.new

# バケットを作成
client.create_bucket(bucket: bucket_name)
p client.list_buckets

# オブジェクトを作成
client.put_object(bucket: bucket_name, key: key_name, body: "Hello World")

# オブジェクトを読み込み
p client.get_object(bucket: bucket_name, key: key_name).body.read

# オブジェクト情報一覧
p client.list_objects_v2(bucket: bucket_name)

実行

準備が整えば、docker compose upを実行するとアプリが起動します。起動しているコンテナのうち、appコンテナに接続してruby lib/app.rbを実行するとAWS SDK S3 gemを利用したS3の動作が確認できます。

## ↓ client.list_bucketsの実行結果
#<struct Aws::S3::Types::ListBucketsOutput buckets=[#<struct Aws::S3::Types::Bucket name="bucket01", creation_date=2024-04-07 04:47:48 UTC>], owner=#<struct Aws::S3::Types::Owner display_name="webfile", id="xxxxx">>
## ↓ client.get_objectの実行結果
"Hello World"
## ↓ client.list_objects_v2の実行結果
#<struct Aws::S3::Types::ListObjectsV2Output is_truncated=false, contents=[#<struct Aws::S3::Types::Object key="key01", last_modified=2024-04-07 05:18:59 UTC, etag="\"yyyyy\"", checksum_algorithm=[], size=11, storage_class="STANDARD", owner=nil, restore_status=nil>], name="bucket01", prefix="", delimiter=nil, max_keys=1000, common_prefixes=[], encoding_type=nil, key_count=1, continuation_token=nil, next_continuation_token=nil, start_after=nil, request_charged=nil>

考察・注意点

LocalStackはあくまで他ベンダーによるエミュレータですので、挙動がAWSのものと一致しているとは限らないのは注意が必要です。

また、開発用としてAws.config.updateendpointaccess_key_idなどは個別に設定する必要があり、環境変数化などのなんらかの対処は必要になるかなと思います。

最後に、Aws.config.updateで指定しているforce_path_style: trueについてです。S3はPath styleとVirtual hosted styleの2種類のアドレスモデルがあり、Path styleは廃止予定となっています。

aws.amazon.com

force_path_style: trueの記述は、Path styleでアクセスすることを指定するものです。この記述がない場合、以下のエラーメッセージが出力されます。

/usr/local/lib/ruby/3.3.0/net/http.rb:1603:in `initialize': Failed to open TCP connection to bucket01.localstack:4566 (getaddrinfo: Name or service not known) (Seahorse::Client::NetworkingError)

なんらかの回避方法を探しているのですが、この記事を書いているときには見つけることができませんでした。

AWS SAMを使ったAWS Lambda関数のVPC設定

はじめに

ちょっと前までAWS Lambda上で動くアプリをJavaで実装することをやっていました。

このアプリの特性として、S3やRDSと接続することになるのですが、これらの環境をマネジメントコンソールで操作するのは面倒くさいので、AWS SAMを使って実装してみることにします。

VPCの設定

マネジメントコンソール上の設定で面倒なのは、VPCの設定です。AWS SAMのドキュメントを見るとプロパティの最後にVpcConfigがあるのでこれを設定することになります。

docs.aws.amazon.com

ここでは、必要なVPCAWS SAMのテンプレートファイル(template.yml)にて作成するようにします。

  vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      InstanceTenancy: default
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: AWS-Flayway-Lambda-VPC
  private1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.0.0/24
      VpcId: !Ref vpc
      AvailabilityZone: !Select [0, !GetAZs '']
      Tags:
        - Key: Name
          Value: AWS-Flayway-Lambda-private1
  private2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.1.0/24
      VpcId: !Ref vpc
      AvailabilityZone: !Select [1, !GetAZs '']
      Tags:
        - Key: Name
          Value: AWS-Flayway-Lambda-private2
  # route table
  routeTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref vpc
      Tags:
        - Key: Name
          Value: AWS-Flayway-Lambda-route-table
  # route tableのアサイン
  subnet1Association:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref private1
      RouteTableId: !Ref routeTable
  subnet2Association:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref private2
      RouteTableId: !Ref routeTable
  # VPC LambdaからS3にアクセスするためのVPCエンドポイント
  vpcEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref vpc
      VpcEndpointType: Gateway
      ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
      RouteTableIds:
        - !Ref routeTable
  # Lambdaが使用するセキュリティグループ
  securityGroupForLambda:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: security-group-for-lambda
      GroupDescription: For Lambda
      VpcId: !Ref vpc
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: -1

ここで指定したサブネットやセキュリティグループを使ってVpcConfigを以下のように指定します。

  function:
    Type: AWS::Serverless::Function
    # 関数の定義
    Properties:
    # 中略
      VpcConfig:
        SecurityGroupIds:
          - !GetAtt securityGroupForLambda.GroupId
        SubnetIds:
          - !Ref private1
          - !Ref private2

マネジメントコンソールを使った設定で、毎回つまづくのがAWSLambdaVPCAccessExecutionRoleの不足によりVPC設定ができないということです。AWS SAMのテンプレートのPoliciesを使って必要なロールを設定しておきます。

  function:
    Type: AWS::Serverless::Function
    # 関数の定義
    Properties:
      # 省略
      Policies:
        - AWSLambdaBasicExecutionRole
        - AWSLambda_ReadOnlyAccess
        - AWSXrayWriteOnlyAccess
        - AWSLambdaVPCAccessExecutionRole
        - AmazonS3ReadOnlyAccess
     # 以下略

以上の設定からスムーズにLambda関数のVPC設定を行うことができ、またS3との接続も容易にできるようになりました。

考察

今回はAWS SAMを使ったAWS Lambda関数のVPC設定を実装しました。大量にあるプロパティの中からVPC設定の部分をマニュアルから見つけるのは難しいのですが、最近(2023年11月に)入った以下の更新で、AWS SAMの設定をSAMテンプレートファイルとしてダウンロードできるようになりました。

aws.amazon.com

今回はこの機能を使って、まずはマネジメントコンソールで設定した後、VpcConfigの設定を実装してみました。このような機能を使うといわゆるIaCも簡単に実装できるのかなと考えています。

GemをLayerに登録する際のAWS Lambda制限への対応

はじめに

AWS LambdaにてAWS SDK for Rubyを使うことになり、単純にgemをLayerとして登録しようとしたら少しトラブったのでその対処方法を以下に記します。

何が起きたか?

前回までのブログ記事のようにgem 'aws-sdk'と書いたGemfileを用いてLayer用のzipファイルを作り、Layerを作ろうとしたらUnzipped size must be smaller than 262144000 bytesというエラーメッセージが出ました。

原因

AWS Lambdaにはさまざまな制限(クォータ)が設定されているのですが、今回は「デプロイパッケージ (.zip ファイルアーカイブ) のサイズ」に引っ掛かっているのが原因です。詳細は以下を参照。

docs.aws.amazon.com

今回、Layerとして登録しようとしていたzipファイルのサイズは62.4MBという大きさ。これぐらいのサイズでクォータに引っかかるんだとちょっとびっくりしました。

対処方法

対処方法の一つとして、gem 'aws-sdk'と書くのではなく、サービスごとに用意されているgem(例えばS3であればgem 'aws-sdk-s3')にすればサイズの縮小が望めます。

ただ、そもそもAWS Lambdaの実行環境にはデフォルトでAWS SDK for Rubyがインストールされています。実際、Gem::Specification.sort_by(&:name).map{ |g| "#{g.name} #{g.version}" }を実行すると以下の結果が得られました(Ruby 3.2のAWS Lambdaランタイム環境)。

["abbrev 0.1.1", "aws-eventstream 1.3.0", "aws-partitions 1.892.0", "aws-sdk 3.2.0", "aws-sdk-accessanalyzer 1.45.0", "aws-sdk-account 1.21.0", "aws-sdk-acm 1.65.0", "aws-sdk-acmpca 1.65.0", (中略), "aws-sdk-xray 1.63.0", "aws-sigv2 1.2.0", "aws-sigv4 1.8.0", "base64 0.2.0", "base64 0.1.1", "benchmark 0.2.1", "bigdecimal 3.1.6", "bigdecimal 3.1.3", "bundler 2.5.6", "bundler 2.3.26", "cgi 0.3.6", "csv 3.2.6", "date 3.3.3", "delegate 0.3.0", "did_you_mean 1.6.3", "digest 3.1.1", "drb 2.1.1", "english 0.7.2", "erb 4.0.2", "error_highlight 0.5.1", "etc 1.4.2", "fcntl 1.0.2", "fiddle 1.1.1", "fileutils 1.7.0", "find 0.1.1", "forwardable 1.3.3", "getoptlong 0.2.0", "io-console 0.6.0", "io-nonblock 0.2.0", "io-wait 0.3.0", "ipaddr 1.2.5", "irb 1.6.2", "jmespath 1.6.2", "json 2.6.3", "logger 1.5.3", "mutex_m 0.1.2", "net-http 0.4.1", "net-protocol 0.2.1", "nkf 0.1.2", "observer 0.1.1", "open-uri 0.3.0", "open3 0.1.2", "openssl 3.1.0", "optparse 0.3.1", "ostruct 0.5.5", "pathname 0.2.1", "pp 0.4.0", "prettyprint 0.1.1", "pstore 0.1.2", "psych 5.0.1", "racc 1.6.2", "rdoc 6.5.0", "readline 0.0.3", "readline-ext 0.1.5", "reline 0.3.2", "resolv 0.2.2", "resolv-replace 0.1.1", "rexml 3.2.6", "rinda 0.1.1", "ruby2_keywords 0.0.5", "rubygems-update 3.5.6", "securerandom 0.2.2", "set 1.0.3", "shellwords 0.1.0", "singleton 0.1.1", "stringio 3.0.4", "strscan 3.0.5", "syntax_suggest 1.1.0", "syslog 0.1.1", "tempfile 0.1.3", "time 0.2.2", "timeout 0.3.1", "tmpdir 0.1.3", "tsort 0.1.1", "un 0.2.1", "uri 0.12.2", "weakref 0.1.2", "yaml 0.2.1", "zlib 3.0.0"]

そもそもLayerとして登録する必要はなく、ソースコードにてrequire 'aws-sdk-サービス名'とすれば使えます。

考察

思いのほかクォータの上限が低く、簡単にクォータに引っかかったのでびっくりしました。zipファイルアーカイブは実装と実行のサイクルが短いのがメリットなのですが、クォータの上限を踏まえると気軽にGemを追加するのはこれまでのブログ記事での検証を踏まえると、厳しいかもしれません。

コンテナイメージを使う場合は10GBがクォータなので、コンテナイメージで動かす方が何かと楽なのかなと考えます。

LayerにGemを格納してAWS Lambda関数を成功させる方法(2)

はじめに

先日、Layerにgemを格納してAWS Lambda関数を動かす記事を書きました。

miyohide.hatenablog.com

この記事では、Rubyで実装されたgemを使ってみたのですが、この記事ではネイティブ拡張を使ったgemを使う方法を記します。具体的には、PostgreSQLと接続するためのGemであるpg gemを使う方法を記します。

gemをzipで固めるだけではダメ?

任意の環境でbundle config set --local path 'vendor/bundle' && bundle installを実行し、zip -r my_ruby_package.zip vendorにてzipファイルを作成してLayerとして登録、関数にも紐付けして実行するとlibpq.so.5: cannot open shared object file: No such file or directoryのようなエラーメッセージが出力されます。

このエラーメッセージは、今回試したpg gemではOSが持っている共通ライブラリlibpq.so.5に依存しており、Lambda関数が動作する環境にはこれらの共通ライブラリが入っていないために発生しているのが原因です。

このため、動作環境に対してlibpq.so.5などをLD_LIBRARY_PATHに格納されるようにファイルをコピーします。具体的には、libディレクトリを作成し、libディレクトリ以下にlibpq.so.5などを格納、zip -r my_ruby_package.zip vendor libを実行してlibディレクトリもzipファイル化しておきます。

このzipファイルがLayerとして登録された場合、Lambdaの環境変数LD_LIBRARY_PATHに含まれるパス/opt/liblibpq.so.5などのファイルが格納されることになります。

docs.aws.amazon.com

ただ、この手法を続けても/lib64/libm.so.6: version 'GLIB_2.29' not foundというエラーメッセージが出て結局動きませんでした。

Layerを作るためのコンテナイメージを作る

Layerを作るには環境依存が大きいため、Layerを作るためのコンテナイメージを作ることにします。以下のようなDockerfileを用意します。

FROM public.ecr.aws/sam/build-ruby3.2:latest-x86_64

WORKDIR /var/task

RUN amazon-linux-extras install -y postgresql14
RUN yum -y install postgresql-devel postgresql
RUN gem update bundler
CMD ["bash", "make_layer.sh"]

pg gemを動かすために必要なライブラリ等をコピーするためのshell scriptmake_layer.shを以下のような中身で用意します。

bundle config --local silence_root_warning true
bundle config set clean true
bundle config set path '/var/task'

bundle install

mkdir -p /var/task/lib

cp -a /usr/lib64/libpq.so.5.14 /var/task/lib/libpq.so.5
cp -a /usr/lib64/liblber-2.4.so.2.10.7 /var/task/lib/liblber-2.4.so.2
cp -a /usr/lib64/libldap_r-2.4.so.2.10.7 /var/task/lib/libldap_r-2.4.so.2
cp -a /usr/lib64/libsasl2.so.3.0.0 /var/task/lib/libsasl2.so.3
cp -a /usr/lib64/libssl3.so /var/task/lib/libssl3.so
cp -a /usr/lib64/libsmime3.so /var/task/lib/libsmime3.so
cp -a /usr/lib64/libnss3.so /var/task/lib/libnss3.so
cp -a /usr/lib64/libnssutil3.so /var/task/lib/libnssutil3.so

cd /var/task
zip -r layer.zip .

この作り方は以下の記事を参考にしました。

www.farend.co.jp

neenet-pro.com

あとは、docker build -t ruby-lambda-layer .を実行してコンテナイメージを作成し、docker run --rm -it -v $PWD:/var/task ruby-lambda-layerを実行するとzipファイルが作成されます。

環境変数LD_PRELOADの設定

これでLayerに登録するzipファイルが作成できましたので、登録して実行してみます。すると、/usr/lib64/libnssutil3.so: version 'NSSUTIL_3.82' not foundというエラーメッセージが出ました。

このエラーメッセージの解決にかなり悩みましたが、以下のブログ記事の記載で解決しました。

carrfane.medium.com

具体的には、環境変数LD_PRELOAD/opt/lib/libnssutil3.soを設定します。

結果、単純にrequire 'pg'としただけの以下のプログラムですが無事動作しました。

動作結果。

考察

ここまできてようやくpg gemを動作することができました。動作には、Lambdaの動作環境に強く依存したライブラリのコピーが必要となり、動作させるだけでも一苦労です。また、将来的な動作環境の変更の際にまた試行錯誤するのは大変かと思います。

このため、以下の記事で書いたようにLambdaで動くコンテナイメージを作っておいた方が考えることが少なくて良さそうです。

miyohide.hatenablog.com

LayerにGemを格納してAWS Lambda関数を成功させる方法

はじめに

AWS LambdaでRubyを使って実装しようとしたとき、Gemを使いたいことがよくあります。Gemを使う際にちょっとハマったので、解決方法を記します。

とりあえずGemの使用例としてActiveSupportを使った以下のプログラムを動かしてみたいと思います。

require 'active_support/all'

def lambda_handler(event:, context:)
    p 4.month.from_now
    "Hello Ruby Lambda"
end

これを動かそうとすると、ActiveSupportを読み込めずにLambda関数は失敗します。

Layerの作成

本件を解決するために、Layerを作成します。公式ドキュメントに以下の記述がありますのでそれを参考にします。

docs.aws.amazon.com

Gemfileを以下のように実装します。

# frozen_string_literal: true

source "https://rubygems.org"

gem 'activesupport'

任意の環境(Cloud9とか)で以下のコマンドを実行します。

bundle config set --local path 'vendor/bundle' && bundle install

これでvendor/bundle以下にGemファイルがインストールされるので、これをzipファイルとしてまとめます。

zip -r my_ruby_package.zip vendor

このzipファイルをLayerとして登録しておきます。関数にも紐付けしておきます。

環境変数の設定

上記のドキュメントに記載がありますが、Lambdaはレイヤーのコンテンツをその実行環境の/optディレクトリに読み込むようです。実際に中身を覗いてみると、/opt/vendor/bundle/ruby/3.2.0以下にGemが入っています。

このため、環境変数GEM_PATH/opt/vendor/bundle/ruby/3.2.0を指定しておきます。

実行

これで準備が整いました。関数を実行してみると成功します。

考察

以上の作業を行うことでGemをLayerに格納しつつ、Gemを使ったLambda関数を実行することができました。ちょっと前準備が多いのが面倒ですね。

今回はActiveSupportを試しましたが、RubyのGemにはC言語で実装したものなどが存在します。例えばMySQLPostgreSQLと接続するために使うものが該当します。これらのGemを使うときにはさらに作業が必要となりそうです。この解決方法については後日記します。

RDS(PostgreSQL)におけるデータベース所有者設定でのエラー回避方法

はじめに

RDSにてPostgreSQLを使っているとき、マスターユーザーとは別のユーザーを作ってそのユーザがオーナーのデータベースを作ろうとするとERROR: must be member of role "xxxxx"というメッセージが出ることがあります。例を以下に記します。

postgres=> CREATE ROLE testuser001 WITH PASSWORD 'hogehogehoge' LOGIN;
CREATE ROLE
postgres=> CREATE DATABASE mytestdb001 WITH OWNER testuser001;
ERROR:  must be member of role "testuser001"
postgres=> 

この状態の原因と対策を以下に記します。

原因

RDSのマスターユーザー(ここではpostgres)にSuperuserが付与されていないのが原因です。\duにて権限を見てみます。

postgres=> \du
                                                                                        List of roles
    Role name    |                         Attributes                         |                                                  Member of

-----------------+------------------------------------------------------------+--------------------------------------------------------------------------------------------------------
------
 postgres        | Create role, Create DB                                    +| {rds_superuser}
                 | Password valid until infinity                              |
 rds_ad          | Cannot login                                               | {}
 rds_iam         | Cannot login                                               | {}
 rds_password    | Cannot login                                               | {}
 rds_replication | Cannot login                                               | {}
 rds_superuser   | Cannot login                                               | {pg_read_all_data,pg_write_all_data,pg_monitor,pg_signal_backend,pg_checkpoint,rds_replication,rds_pass
word}
 rdsadmin        | Superuser, Create role, Create DB, Replication, Bypass RLS+| {}
                 | Password valid until infinity                              |
 rdsrepladmin    | No inheritance, Cannot login, Replication                  | {}
 rdstopmgr       |                                                            | {pg_monitor,pg_checkpoint}

postgres=>

このようにマスターユーザー(postgres)にはSuperuserが付与されておらず、RDS固有のロールであるrdsadminにSuperuserが付与されています。

対策

マスターユーザー(postgres)が新たに作成したユーザー(testuser001)のメンバにしてあげればOKです。

postgres=> GRANT testuser001 TO postgres;
GRANT ROLE
postgres=>

これで、当初の目的であるマスターユーザーとは別のユーザーを作ってそのユーザがオーナーのデータベースを作ることができます。

postgres=> CREATE DATABASE mytestdb001 WITH OWNER testuser001;
CREATE DATABASE
postgres=>

\lにて確認してみます。

postgres=> \l
                                                    List of databases
    Name     |    Owner    | Encoding |   Collate   |    Ctype    | ICU Locale | Locale Provider |   Access privileges
-------------+-------------+----------+-------------+-------------+------------+-----------------+-----------------------
 mytestdb001 | testuser001 | UTF8     | en_US.UTF-8 | en_US.UTF-8 |            | libc            |
 postgres    | postgres    | UTF8     | en_US.UTF-8 | en_US.UTF-8 |            | libc            |
 rdsadmin    | rdsadmin    | UTF8     | en_US.UTF-8 | en_US.UTF-8 |            | libc            | rdsadmin=CTc/rdsadmin+
             |             |          |             |             |            |                 | rdstopmgr=Tc/rdsadmin
 template0   | rdsadmin    | UTF8     | en_US.UTF-8 | en_US.UTF-8 |            | libc            | =c/rdsadmin          +
             |             |          |             |             |            |                 | rdsadmin=CTc/rdsadmin
 template1   | postgres    | UTF8     | en_US.UTF-8 | en_US.UTF-8 |            | libc            | =c/postgres          +
             |             |          |             |             |            |                 | postgres=CTc/postgres
(6 rows)

postgres=>

オンプレミスのPostgreSQLですとこのようなことはなかった気がしますので、念の為記録しておきます。

考察

上記ではrdsadminの詳細についてスルーしましたが、詳細はAWSの以下のドキュメントに記されています。

docs.aws.amazon.com

オンプレミスのPostgreSQLと同じノリで操作するとこういうところでつまづくので、上記のようなドキュメントには目を通しておいた方が良いかなと考えています。

参考

この記事を書くためにAWS re:Postを検索すると以下の記事がヒットしました。

repost.aws

むやみに権限を付与しなくてもよいかなと思いますが、対策の一つとしてよいかなと考えています。