AWS

IAM認証を使用して、EC2(Linux)/LambdaからRDSに接続する

IAM認証を使用して、EC2(Linux)/LambdaからRDSに接続する

RDSが提供する機能の1つに、IAM認証があります。

AWS Identity and Access Management (IAM) データベース認証を使用して、DB インスタンスを認証できます。IAM データベース認証には、MariaDB、MySQL、および PostgreSQL を使用します。この認証方法では、DB インスタンスに接続するときにパスワードを使用する必要はありません。代わりに、認証トークンを使用します。

MariaDB、MySQL、および PostgreSQL の IAM データベース認証

本ページでは、EC2インスタンスおよびLambda関数から、IAM認証でRDSにアクセスする方法を確認します。

構築する環境

Diagram of connecting to RDS from EC2(Linux) / Lambda using IAM authentication.

DBインスタンスを作成します。
IAM認証を有効化します。
エンジンはMySQLです。

プライベートサブネットにEC2インスタンスおよびLambda関数を作成します。
EC2インスタンスは最新版のAmazon Linux2、Lambda関数はPython3.8とします。
両リソースはDBインスタンスにIAM認証で接続するクライアントとして使用します。

CloudFormationカスタムリソースに関連づけるLambda関数を2種類作成します。
1種類目はLambda関数用のLambdaレイヤーを作成します。
2種類目はDBインスタンスの初期化処理を行います。
カスタムリソースに関連づけることによって、CloudFormationスタック作成時に、自動的に関数を実行するように設定します。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

awstut-fa/128 at main · awstut-an-r/awstut-fa
Contribute to awstut-an-r/awstut-fa development by creating an account on GitHub.

テンプレートファイルのポイント解説

RDS

DBインスタンス

Resources:
  DBInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      AllocatedStorage: !Ref DBAllocatedStorage
      AvailabilityZone: !Sub "${AWS::Region}${AvailabilityZone}"
      EnableIAMDatabaseAuthentication: true
      DBInstanceClass: !Ref DBInstanceClass
      DBInstanceIdentifier: !Sub "${Prefix}-dbinstance"
      DBName: !Ref DBName
      DBSubnetGroupName: !Ref DBSubnetGroup
      Engine: !Ref DBEngine
      EngineVersion: !Ref DBEngineVersion
      MasterUsername: !Ref DBMasterUsername
      MasterUserPassword: !Ref DBMasterUserPassword
      VPCSecurityGroups:
        - !Ref DBSecurityGroup
Code language: YAML (yaml)

ポイントはEnableIAMDatabaseAuthenticationプロパティです。
本プロパティに「true」を設定することで、DBインスタンスでIAM認証が有効化されます。

Lambda関数を使用した初期化処理

Resources:
  SQLParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Name: !Sub "${Prefix}-customresource-03"
      Type: String
      Value: !Sub |
        CREATE USER ${DBIamUsername} IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS'; 
        GRANT SELECT ON *.* TO 'iamuser'@'%';
        USE ${DBName};
        CREATE TABLE ${DBTableName} (id INT UNSIGNED AUTO_INCREMENT, name VARCHAR(30), PRIMARY KEY(id));
        INSERT INTO planet (name) VALUES ("Mercury");
        INSERT INTO planet (name) VALUES ("Venus");
        INSERT INTO planet (name) VALUES ("Earth");
        INSERT INTO planet (name) VALUES ("Mars");
        INSERT INTO planet (name) VALUES ("Jupiter");
        INSERT INTO planet (name) VALUES ("Saturn");
        INSERT INTO planet (name) VALUES ("Uranus");
        INSERT INTO planet (name) VALUES ("Neptune");

  Function:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Environment:
        Variables:
          DB_ENDPOINT_ADDRESS: !Ref DBInstanceEndpointAddress
          DB_ENDPOINT_PORT: !Ref MySQLPort
          DB_PASSWORD: !Ref DBMasterUserPassword
          DB_USER: !Ref DBMasterUsername
          REGION: !Ref AWS::Region
          SQL_PARAMETER: !Ref SQLParameter
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import mysql.connector
          import os
          
          db_endpoint_port = int(os.environ['DB_ENDPOINT_PORT'])
          db_endpoint_address = os.environ['DB_ENDPOINT_ADDRESS']
          db_password = os.environ['DB_PASSWORD']
          db_user = os.environ['DB_USER']
          region = os.environ['REGION']
          sql_parameter = os.environ['SQL_PARAMETER']
          
          CREATE = 'Create'
          response_data = {}
          
          def lambda_handler(event, context):
            try:
              if event['RequestType'] == CREATE:
                client = boto3.client('ssm', region_name=region)
                response = client.get_parameter(Name=sql_parameter)
                sql_statements = response['Parameter']['Value']
                
                conn = mysql.connector.connect(
                  host=db_endpoint_address,
                  port=db_endpoint_port,
                  user=db_user,
                  password=db_password
                  )
                cur = conn.cursor()
                
                for sql in sql_statements.splitlines():
                  print(sql)
                  cur.execute(sql)
                  
                cur.close()
                conn.commit()
                
              cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
                
            except Exception as e:
              print(e)
              cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
      FunctionName: !Sub "${Prefix}-customresource-03"
      Handler: !Ref Handler
      Layers:
        - !Ref LambdaLayer
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
      Timeout: !Ref Timeout
      VpcConfig:
        SecurityGroupIds:
          - !Ref FunctionSecurityGroup
        SubnetIds:
          - !Ref FunctionSubnet
Code language: YAML (yaml)

Lambda関数で実行するコードをインライン表記で定義します。
詳細につきましては、以下のページをご確認ください。

VPC内に本関数を配置し、DBインスタンスにアクセスして、DBの初期化処理を行います。
詳細につきましては、以下のページをご確認ください。

実行するSQL文はSSMパラメータストアに保存し、これを関数が参照します。
SQL文の中で、特に1〜2行目がポイントです。

1行目はIAM認証によってDBインスタンスにアクセスするユーザを作成するコマンドです。
2行目は作成したユーザにデータベースを操作する権限を付与するコマンドです。

残りのコマンドはテストレコードを書き込む処理を行います。
以下のページの内容を参考にしました。

ステップ 3: MySQL データベースにデータを入力する - アマゾン ウェブ サービス
テーブルが作成されたので、事前に設定された SQL スクリプトを使用して、最初の MySQL テーブルを接続、入力、および実行する方法を学びます。

EC2インスタンス

Resources:
  Instance:
    Type: AWS::EC2::Instance
    Properties:
      IamInstanceProfile: !Ref InstanceProfile
      ImageId: !Ref ImageId
      InstanceType: !Ref InstanceType
      NetworkInterfaces:
        - DeviceIndex: 0
          SubnetId: !Ref InstanceSubnet
          GroupSet:
            - !Ref InstanceSecurityGroup
      UserData: !Base64 |
        #!/bin/bash -xe
        yum update -y
        yum install -y mariadb
        curl -OL https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem
Code language: YAML (yaml)

ユーザデータでインスタンスの初期化処理を定義します。
初期化処理に関する詳細は以下のページをご確認ください。

ポイントは3行目と4行目です。

前者はMySQLクライアントをインストールするコマンドです。
Amazon Linux 2において、各種RDSに接続する方法に関しては、以下のページをご確認ください。

後者はIAM認証に必要な証明書をダウンロードするコマンドです。
前提として、IAM認証を行う場合、SSL通信が必須となります。

データベースとの間で送受信されるネットワークトラフィックは、Secure Socket Layer (SSL) または Transport Layer Security (TLS) を使用して暗号化されます。

MariaDB、MySQL、および PostgreSQL の IAM データベース認証

そしてSSL通信を行うためには、証明書を用意する必要があります。

SSL/TLS 接続は、クライアントと DB インスタンスの間を移動するデータを暗号化することによって、1 つのセキュリティ層を提供します。サーバー証明書を使用すると、Amazon RDS DB インスタンスへの接続が確立されていることを検証することによって、追加のセキュリティレイヤーが提供されます。これを行うには、プロビジョニングしたすべての DB インスタンスに自動インストールされたサーバー証明書をチェックします。

SSL/TLS を使用した DB インスタンスへの接続の暗号化

使用可能な証明書は以下のページにまとめられています。

SSL/TLS を使用した DB インスタンスへの接続の暗号化 - Amazon Relational Database Service
SSL/TLS を使用して Amazon RDS DB インスタンスへの暗号化された接続を作成します。

今回は全リージョンの証明書バンドルであるglobal-bundle.pemを使用します。

以下が本インスタンス用のIAMロールです。

Resources:
  InstanceRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - ec2.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
      Policies:
        - PolicyName: RDSIamAthenticationPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - rds-db:connect
                Resource:
                  - !Sub "arn:aws:rds-db:${AWS::Region}:${AWS::AccountId}:dbuser:${DBInstanceResourceId}/${DBIamUsername}"
Code language: YAML (yaml)

後述する動作確認時に、SSM Session Managerで本インスタンスに接続するため、AWS管理ポリシーAmazonSSMManagedInstanceCoreをアタッチします。

以下のページを参考にして、インラインポリシーにIAM認証用の設定を行います。

IAM データベースアクセス用の IAM ポリシーの作成と使用 - Amazon Relational Database Service
ユーザーまたはロールに DB インスタンス への接続を許可するには、IAM ポリシーを作成する必要があります。その後、ポリシーをアクセス許可セットまたはロールにアタッチします。

アクションにrds-db:connectを、リソースにDBインスタンス内のユーザ名を指定します。

Lambda関数

Resources:
  Function:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Environment:
        Variables:
          DB_ENDPOINT_ADDRESS: !Ref DBInstanceEndpointAddress
          DB_ENDPOINT_PORT: !Ref MySQLPort
          DB_NAME: !Ref DBName
          DB_TABLENAME: !Ref DBTableName
          DB_USER: !Ref DBIamUsername
          REGION: !Ref AWS::Region
          SSL_CERTIFICATE: /opt/python/global-bundle.pem
      Code:
        ZipFile: |
          import boto3
          import datetime
          import json
          import mysql.connector
          import os
          
          db_endpoint_address = os.environ['DB_ENDPOINT_ADDRESS']
          db_endpoint_port = int(os.environ['DB_ENDPOINT_PORT'])
          db_name = os.environ['DB_NAME']
          db_tablename = os.environ['DB_TABLENAME']
          db_user = os.environ['DB_USER']
          region = os.environ['REGION']
          ssl_certificate = os.environ['SSL_CERTIFICATE']
          
          os.environ['LIBMYSQL_ENABLE_CLEARTEXT_PLUGIN'] = '1'
          
          client = boto3.client('rds', region_name=region)
          
          def lambda_handler(event, context):
              token = client.generate_db_auth_token(
                  DBHostname=db_endpoint_address,
                  Port=db_endpoint_port,
                  DBUsername=db_user,
                  Region=region)

              conn = mysql.connector.connect(
                  host=db_endpoint_address,
                  user=db_user,
                  password=token,
                  port=db_endpoint_port,
                  database=db_name,
                  ssl_ca=ssl_certificate)

              cur = conn.cursor()
              read_sql = 'select * from {tbl};'.format(tbl=db_tablename)
              cur.execute(read_sql)
              content = [record for record in cur]
              
              cur.close()
              conn.close()
              
              return {
                  'statusCode': 200,
                  'body': json.dumps(content, indent=2)
              }
      FunctionName: !Sub "${Prefix}-function"
      Handler: !Ref Handler
      Layers:
        - !Ref LambdaLayer1
        - !Ref LambdaLayer2
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
      Timeout: !Ref Timeout
      VpcConfig:
        SecurityGroupIds:
          - !Ref FunctionSecurityGroup
        SubnetIds:
          - !Ref FunctionSubnet
Code language: YAML (yaml)

Lambda関数で実行するコードをインライン表記で定義します。
詳細につきましては、以下のページをご確認ください。

今回の構成では、Lambda関数をVPC内に配置します。
VpcConfigプロパティで、関数を配置するサブネットや、適用するセキュリティグループを指定します。

実行するコードの内容ですが、以下の通りです。

  1. CloudFormationテンプレートで定義した環境変数を、os.environにアクセスして取得する。
  2. Boto3でRDS用クライアントオブジェクトを作成する。
  3. generate_db_auth_tokenメソッドを実行して、IAM認証用のトークンを取得する。
  4. mysql.connector.connectメソッドを実行して、IAM認証でDBインスタンスに接続する。
  5. SELECT文を実行して、テーブル内の全データを取得する。

ポイントはLambdaレイヤーです。
後述するCloudFormationカスタムリソースに関連づいたLambda関数で、自動的に作成します。
今回は2つのレイヤーを作成して、本関数に関連づけます。
一方はMySQL接続用パッケージ用で、もう一方はSSL通信用の証明書用のレイヤーです。

SSL通信用の証明書の設置場所もポイントです。

Lambda 関数にレイヤーを含めると、Lambda は関数実行環境で/optディレクトリにレイヤーコンテンツを抽出します。

関数からレイヤーコンテンツにアクセスする

そして本関数のランタイム環境はPython3.8ですから、/opt/pythonディレクトリに証明書が設置されることになります。

以下が本関数用のIAMロールです。

Resources:
  FunctionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
      Policies:
        - PolicyName: RDSIamAthenticationPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - rds-db:connect
                Resource:
                  - !Sub "arn:aws:rds-db:${AWS::Region}:${AWS::AccountId}:dbuser:${DBInstanceResourceId}/${DBIamUsername}"
Code language: YAML (yaml)

VPC内で本関数を実行するために、AWS管理ポリシーAWSLambdaVPCAccessExecutionRoleをアタッチします。

加えてEC2インスタンスと同様に、IAM認証用の設定を行います。

本関数のFunction URLを有効化します。

Resources:
  FunctionUrl:
    Type: AWS::Lambda::Url
    Properties:
      AuthType: NONE
      TargetFunctionArn: !GetAtt Function.Arn
      
  FunctionUrlPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunctionUrl
      FunctionName: !GetAtt Function.Arn
      FunctionUrlAuthType: NONE
      Principal: "*"
Code language: YAML (yaml)

詳細は以下のページをご確認します。

Lambdaレイヤー

Pythonパッケージ用レイヤー
Resources:
  RequirementsParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Name: !Sub "${Prefix}-customresource-01"
      Type: String
      Value: |
        mysql-connector-python
        
  LambdaLayer:
    Type: AWS::Lambda::LayerVersion
    DependsOn:
      - CustomResource
    Properties:
      CompatibleArchitectures:
        - !Ref Architecture
      CompatibleRuntimes:
        - !Ref Runtime
      Content:
        S3Bucket: !Ref CodeS3Bucket
        S3Key: !Ref LayerS3Key
      Description: !Ref Prefix
      LayerName: !Sub "${Prefix}-customresource-01"
      
  CustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt Function.Arn

  Function:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Environment:
        Variables:
          LAYER_PACKAGE: !Ref LayerPackage
          REGION: !Ref AWS::Region
          REQUIREMENTS_PARAMETER: !Ref RequirementsParameter
          S3_BUCKET: !Ref CodeS3Bucket
          S3_BUCKET_FOLDER: !Ref Prefix
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import os
          import pip
          import shutil
          import subprocess
          
          layer_package = os.environ['LAYER_PACKAGE']
          region = os.environ['REGION']
          requirements_parameter = os.environ['REQUIREMENTS_PARAMETER']
          s3_bucket = os.environ['S3_BUCKET']
          s3_bucket_folder = os.environ['S3_BUCKET_FOLDER']
          
          CREATE = 'Create'
          response_data = {}
          
          work_dir = '/tmp'
          requirements_file = 'requirements.txt'
          package_dir = 'python'
          
          requirements_path = os.path.join(work_dir, requirements_file)
          package_dir_path = os.path.join(work_dir, package_dir)
          layer_package_path = os.path.join(
            work_dir,
            layer_package
            )
          
          def lambda_handler(event, context):
            try:
              if event['RequestType'] == CREATE:
                ssm_client = boto3.client('ssm', region_name=region)
                ssm_response = ssm_client.get_parameter(Name=requirements_parameter)
                requirements = ssm_response['Parameter']['Value']
                #print(requirements)
                
                with open(requirements_path, 'w') as file_data:
                  print(requirements, file=file_data)
                
                pip.main(['install', '-t', package_dir_path, '-r', requirements_path])
                shutil.make_archive(
                  os.path.splitext(layer_package_path)[0],
                  format='zip',
                  root_dir=work_dir,
                  base_dir=package_dir
                  )
                
                s3_resource = boto3.resource('s3')
                bucket = s3_resource.Bucket(s3_bucket)
                
                bucket.upload_file(
                  layer_package_path,
                  '/'.join([s3_bucket_folder, layer_package])
                  )
                
              cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
                
            except Exception as e:
              print(e)
              cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
      EphemeralStorage:
        Size: !Ref EphemeralStorageSize
      FunctionName: !Sub "${Prefix}-customresource-01"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
      Timeout: !Ref Timeout
Code language: YAML (yaml)

先述の関数では、MySQLに接続するために、mysql(mysql-connector-python)パッケージを使用しましたが、これをLambdaレイヤーとして用意します。

今回はCloudFormationカスタムリソースに関連づくLambda関数を使用して、自動的にLambdaレイヤーを作成します。

SSM Parameter Storeにダウンロードするパッケージのリストを登録します。
先述のパッケージを登録します。

詳細は以下のページをご確認ください。

証明書用レイヤー
Resources:
  UrlsParameter:
    Type: AWS::SSM::Parameter
    Properties:
      Name: !Sub "${Prefix}-customresource-02"
      Type: String
      Value: |
        https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
      
  LambdaLayer:
    Type: AWS::Lambda::LayerVersion
    DependsOn:
      - CustomResource
    Properties:
      CompatibleArchitectures:
        - !Ref Architecture
      CompatibleRuntimes:
        - !Ref Runtime
      Content:
        S3Bucket: !Ref CodeS3Bucket
        S3Key: !Ref LayerS3Key
      Description: !Ref Prefix
      LayerName: !Sub "${Prefix}-customresource-02"
      
  CustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt Function.Arn

  Function:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Environment:
        Variables:
          LAYER_PACKAGE: !Ref LayerPackage
          REGION: !Ref AWS::Region
          URLS_PARAMETER: !Ref UrlsParameter
          S3_BUCKET: !Ref CodeS3Bucket
          S3_BUCKET_FOLDER: !Ref Prefix
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import os
          import shutil
          import subprocess
          import urllib
          
          layer_package = os.environ['LAYER_PACKAGE']
          region = os.environ['REGION']
          urls_parameter = os.environ['URLS_PARAMETER']
          s3_bucket = os.environ['S3_BUCKET']
          s3_bucket_folder = os.environ['S3_BUCKET_FOLDER']
          
          CREATE = 'Create'
          response_data = {}
          
          work_dir = '/tmp'
          package_dir = 'python'
          
          package_dir_path = os.path.join(work_dir, package_dir)
          layer_package_path = os.path.join(
            work_dir,
            layer_package
            )
            
          ssm_client = boto3.client('ssm', region_name=region)
          s3_client = boto3.client('s3', region_name=region)
          
          def lambda_handler(event, context):
            try:
              if event['RequestType'] == CREATE:
                ssm_response = ssm_client.get_parameter(Name=urls_parameter)
                urls = ssm_response['Parameter']['Value']
                
                result = subprocess.run(
                  ['mkdir', package_dir_path],
                  stdout=subprocess.PIPE,
                  stderr=subprocess.PIPE
                )
                
                for url in urls.splitlines():
                  print(url)
                  file_name = os.path.basename(url)
                  download_path = os.path.join(package_dir_path, file_name)
                  
                  data = urllib.request.urlopen(url).read()
                  
                  with open(download_path, mode='wb') as f:
                    f.write(data)

                shutil.make_archive(
                  os.path.splitext(layer_package_path)[0],
                  format='zip',
                  root_dir=work_dir,
                  base_dir=package_dir
                )

                s3_client.upload_file(
                  layer_package_path,
                  s3_bucket,
                  os.path.join(s3_bucket_folder, layer_package)
                )
                
              cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
              
            except Exception as e:
              print(e)
              cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
      EphemeralStorage:
        Size: !Ref EphemeralStorageSize
      FunctionName: !Sub "${Prefix}-customresource-02"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
      Timeout: !Ref Timeout
Code language: YAML (yaml)

先述の関数では、IAM認証を行うために証明書を使用しました。
これをLambdaレイヤーとして用意します。

こちらもCloudFormationカスタムリソースに関連づくLambda関数を使用して、自動的にLambdaレイヤーを作成します。

SSM Parameter StoreにダウンロードするファイルのURLリストを登録します。
先述の証明書のURLを登録します。

詳細は以下のページをご確認ください。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • EC2インスタンス:i-0a625917a26ccc6ae
  • Lambda関数:fa-128-function
  • Lambda関数のFunction URL:https://wndut2cyprxccm4cxjpm5or7bm0takbo.lambda-url.ap-northeast-1.on.aws/
  • Lambdaレイヤー1:fa-128-customresource-01
  • Lambdaレイヤー2:fa-128-customresource-02
  • DB初期化処理用Lambda関数:fa-128-customresource-03
  • DBインスタンス:fa-128-dbinstance
  • DBインスタンスのエンドポイント:fa-128-dbinstance.cl50iikpthxs.ap-northeast-1.rds.amazonaws.com

AWS Management Consoleから各リソースを確認します。

まずDBインスタンスを確認します。

Detail of RDS 1.

正常にDBインスタンスが作成されています。
そしてIAM認証が有効化されていることも確認できます。

Lambdaレイヤーを確認します。

Detail of Lambda 1.
Detail of Lambda 2.

確かに2つのレイヤーが作成されていることがわかります。

DBインスタンスの初期化処理用のLambda関数の実行結果を確認します。

Detail of Lambda 3.

確かにCloudFormationカスタムリソースによって、本関数が実行されました。
そして本関数内でDBの初期化処理用のSQL文が実行されたことがわかります。

動作確認

準備が整いましたので、実際の動作を確認します。

EC2インスタンスからDBインスタンスにIAM認証でアクセス

まずEC2インタンスにアクセスします。
インスタンスへのアクセスはSSM Session Managerを使用します。

% aws ssm start-session --target i-0a625917a26ccc6ae
...
sh-4.2$Code language: Bash (bash)

SSM Session Managerの詳細につきましては、以下のページをご確認ください。

ユーザーデータによるインスタンスの初期化処理の実行状況を確認します。

sh-4.2$ sudo yum list installed | grep mariadb
mariadb.aarch64                       1:5.5.68-1.amzn2               @amzn2-core
mariadb-libs.aarch64                  1:5.5.68-1.amzn2               installed

sh-4.2$  mysql -V
mysql  Ver 15.1 Distrib 5.5.68-MariaDB, for Linux (aarch64) using readline 5.1

sh-4.2$ ls -l /*.pem
-rw-r--r-- 1 root root 174184 May  3 00:27 /global-bundle.pemCode language: Bash (bash)

正常にMySQLクライアントパッケージがインストールされていることがわかります。
またSSL通信用の証明書についてもダウンロードされています。

確認が済みましたので、IAM認証でDBインスタンスにアクセスします。

sh-4.2$ mysql \
--host=fa-128-dbinstance.cl50iikpthxs.ap-northeast-1.rds.amazonaws.com \
--port=3306 \
--ssl-ca=/global-bundle.pem \
--default-auth=mysql_clear_password \
--user=iamuser \
--password=`aws rds generate-db-auth-token \
  --hostname fa-128-dbinstance.cl50iikpthxs.ap-northeast-1.rds.amazonaws.com \
  --port 3306 \
  --username iamuser \
  --region ap-northeast-1`
...
MySQL [(none)]>Code language: Bash (bash)

正常にアクセスすることができました。

アクセス用のコマンドは以下のページを参考にしました。

コマンドラインから IAM 認証を使用して、DB インスタンスに接続する: AWS CLI および mysql クライアント - Amazon Relational Database Service
以下に示すように、AWS CLI および mysql コマンドラインツールを使用して、コマンドラインから Amazon RDS DB インスタンス に接続できます。

なお今回はMariaDB用のクライアントパッケージを使用しているため、上記のページで言及されているenable-cleartext-pluginオプションが存在しません。
ですから代わりに「–default-auth=mysql_clear_password」オプションを使用します。

Pluggable Authentication Overview
The authentication of users is delegated to plugins.

試しにテストテーブルに対して、SELECT文を実行します。

MySQL [(none)]> use tutorial;
...
Database changed

MySQL [tutorial]> select * from planet;
+----+---------+
| id | name    |
+----+---------+
|  1 | Mercury |
|  2 | Venus   |
|  3 | Earth   |
|  4 | Mars    |
|  5 | Jupiter |
|  6 | Saturn  |
|  7 | Uranus  |
|  8 | Neptune |
+----+---------+
8 rows in set (0.01 sec)Code language: SQL (Structured Query Language) (sql)

確かにSELECT文を実行することができました。

このようにIAM認証でEC2インスタンスからDBインスタンスにアクセスし、操作することができます。

Lambda関数からDBインスタンスにIAM認証でアクセス

Detail of Lambda 4.

上記の関数を実行して動作を確認します。
関数の実行は、Function URLにアクセスして行います。

$ curl https://wndut2cyprxccm4cxjpm5or7bm0takbo.lambda-url.ap-northeast-1.on.aws/
[
  [
    1,
    "Mercury"
  ],
  [
    2,
    "Venus"
  ],
  [
    3,
    "Earth"
  ],
  [
    4,
    "Mars"
  ],
  [
    5,
    "Jupiter"
  ],
  [
    6,
    "Saturn"
  ],
  [
    7,
    "Uranus"
  ],
  [
    8,
    "Neptune"
  ]
]Code language: Bash (bash)

正常に応答がありました。
DBインスタンスのテストテーブルの内容が返ってきました。

このようにIAM認証でLambda関数からDBインスタンスにアクセスし、操作することができます。

まとめ

EC2インスタンスおよびLambda関数から、IAM認証でRDSにアクセスする方法を確認しました。

タイトルとURLをコピーしました