LoginSignup
5
0

EC2+RDS Proxy+SecretsManager+RDS

Last updated at Posted at 2023-12-21

はじめに

今年のカレンダーはとある会社のとあるチームで活動した1年間を振り返っていきます

今回は業務で初めてAWSを触ったときの自己学習を備忘録として残していたので供養します
少し情報が古い可能性もありますので、ご注意ください。

やりたいこと

  • EC2を踏み台にしてRDSに接続
  • RDS接続は、SecretsManagerを使ってRDS Proxy経由で行う

インバウンドルールの設定

  • 各リソースやネットワーク系の構築に加えてネットワーク間の通信設定が必要
  • ec2からproxyを経由してrdsへアクセスしたいので、インバウンドルールの設定をする
    • ec2からproxyにアクセスする許可を設定したいので、proxyのインバウンドルールにec2のIPアドレスを指定
    • proxyからrdsにアクセスする許可を設定したいので、rdsのインバウンドルールにproxyのIPアドレスを指定

構成図

qita_aws.drawio.png

cloudformationのテンプレート

ファイルに記載する基本的な構成は以下の通り

AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  UserName:
    Description: Type of this UserName.
    Type: String

Resources:
  QiitaUser:
    Type: AWS::IAM::User
    Properties:
      UserName: !Sub "${UserName}"
プロパティ 概要
AWSTemplateFormatVersion テンプレート形式を指定
Parameters 一部の項目を変数にできる。同じ構成で複数の環境を立てる際に使える
Resource 使用するAWSリソースを指定
Logical ID テンプレート内でユニークなID。他リソースから参照するときに使える
Type AWSリソースプロパティタイプのリファレンスに一覧がある
Properties リソース作成時に指定する。タイプによって利用できるプロパティが異なるので、公式ドキュメントなどを参考に指定する
tags リソースに命名できる
Outputs 作成したスタックの出力タブに指定した値を表示させることができる
  • !Sub "${hoge}" :リージョン名やID、スタック名などをパラメータ参照できる
  • !Ref hoge :指定したリソースの値を返す事ができる
  • !GetAtt: リソースの属性を返せる(リソースによって返せる値は異なる)

export/import

stackA.yml
Outputs:
  OutputQiitaName:
    Value: !Ref QiitaName
    Export:
      Name: ExportQiitaName
  OutputQiitaArn:
    Value: !GetAtt Qiita.Arn
    Export:
      Name: ExportQiitaArn
stackB.yml
Resources:
 Qiita:
    Type:
    Properties:
      hogeName: !ImportValue ExportQiitaName
      hogeArn: !ImportValue ExportQiitaArn
  • スタックAでexportしたNameやArnは、スタックBでimportすると参照できる
  • !ImportValue :別のスタックでエクスポートされた値を参照できる
  • exportはOutputsで出力したい値を指定
  • exportされている値の一覧は aws cloudformation list-exports で確認できる

IAMスタックを作成

以下のqita_iam.ymlを使用してcloudformationでスタックを作成

qiita_iam.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Create IAM

Parameters:
  UserName:
    Description: Type of this UserName.
    Type: String

Resources:
  # ------------------------------------------------------------#
  #  qiita IAM User
  # ------------------------------------------------------------#
  QiitaIAMUser:
    Type: AWS::IAM::User
    Properties:
      UserName: !Sub "${UserName}"
      Tags:
        - Key: "name"
          Value: "qiita"
  QiitaIAMUserAccessKey:
    Type: AWS::IAM::AccessKey
    Properties:
      UserName: !Ref QiitaIAMUser

  QiitaIAMUserAccessKeySecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name:
        Fn::Sub: "${QiitaIAMUser}-credentials"
      SecretString:
        Fn::Sub: '{"accessKeyId":"${QiitaIAMUserAccessKey}","secretAccessKey":"${QiitaIAMUserAccessKey.SecretAccessKey}"}'

Outputs:
  AccessKey:
    Value: !Ref QiitaIAMUserAccessKey
  SecretAccessKey:
    Value: !GetAtt QiitaIAMUserAccessKey.SecretAccessKey

  • 作成したファイルをアップロードしてスタックを作成
    image.png

  • スタックの作成に成功すると、CREATE_COMPLETEと表示される
    image.png

  • qiita_iam.ymlのOutputsで記載したAccessKeyとSecretAccessKeyが「出力」タブに表示される
    image.png

ネットワーク系のスタックを作成(VPC、Subnet、SecurityGroup、RouteTable、InternetGateway)

  • 以下のqiita_network.ymlをcloudformationにアップロードしてスタックを作成
qiita_network.yml
Parameters:
  ProjectName:
    Description: Type of this ProjectName.
    Type: String

Resources:
  # ------------------------------------------------------------#
  #  vpc
  # ------------------------------------------------------------#
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/22
      Tags:
        - Key: "Name"
          Value: "qiita"

  # ------------------------------------------------------------#
  #  InternetGateway
  # ------------------------------------------------------------#
  InternetGateway:
    Type: "AWS::EC2::InternetGateway"
    Properties:
      Tags:
        - Key: Name
          Value: !Sub "${ProjectName}-igw"

  InternetGatewayAttachment:
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  # ------------------------------------------------------------#
  #  subnet
  # ------------------------------------------------------------#
  PublicSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.0.0/24
      MapPublicIpOnLaunch: true
      AvailabilityZone: ap-northeast-1a
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectName}-public-subnet"
  PublicSubnetC:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
      AvailabilityZone: ap-northeast-1c
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectName}-public-subnetC"
  PrivateSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.2.0/24
      AvailabilityZone: ap-northeast-1a
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectName}-private-subnetA"
  PrivateSubnetC:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.3.0/24

      AvailabilityZone: ap-northeast-1c
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectName}-private-subnetC"

  # ------------------------------------------------------------#
  #  RouteTable
  # ------------------------------------------------------------#
  PublicRouteTableA:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectName}-public-routetableA"

  PublicRouteA:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref PublicRouteTableA
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway

  PublicRouteAssociationA:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTableA
      SubnetId: !Ref PublicSubnetA

  PublicRouteTableC:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectName}-public-routetableC"

  PublicRouteC:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref PublicRouteTableC
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway

  PublicRouteAssociationC:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTableC
      SubnetId: !Ref PublicSubnetC

VPC

プロパティ 概要
CidrBlock IPv4 CIDRブロック。作成するVPCにおけるIPアドレスの範囲
EnableDnsHostnames DNSホスト名取得の有効/無効
EnableDnsSupport VPCに対してDNS解決がサポートされているかどうか
InstanceTenancy VPC内に起動されるインスタンスの許可されているテナンシー(設定するとEC2の料金が割増になる)
  • EC2 等のインスタンスはサブネット内で起動する。サブネットはVPC内に配置する必要があるため、VPCから作成
    • VPCの中にサブネットを作成(private/public)
    • VPCの作成には CIDR を1つだけ設定
    • 各サブネットにアベイラビリティゾーンを用意

Subnet

プロパティ 概要
VpcId サブネットを作成するVPCのID
AvailabilityZone サブネットを作成するアベイラビリティゾーン
CidrBlock 作成するサブネットにおけるIPアドレスの範囲
MapPublicIpOnLaunch サブネットで起動されたインスタンスがパブリック IPv4 アドレスを受け取るかどうか。パブリックサブネットでは true、プライベートサブネットでは false を指定
  • 今回はEC2を配置するパブリックサブネットと、RDB サーバーを配置するプライベートサブネットの計3つを作成。
  • プライベートサブネットに対してはAZを2つ用意し、冗長化
  • 今回は 10.0.1.0/24 , 10.0.2.0/24 , 10.0.3.0/24 の3つのサブネットを作成

InternetGateway

プロパティ 概要
InternetGatewayId インターネットゲートウェイのID
VpcId VPC ID
VpnGatewayId 仮想プライベートゲートウェイのID
  • インターネットとVPCをつなぐ役割
    インターネットゲートウェイはVPCに関連付ける

RouteTable

プロパティ 概要
DestinationCidrBlock ルーティング先の照合に使用するIPv4CIDRブロック
DestinationIpv6CidrBlock ルーティング先の照合に使用するIPv6CIDRブロック
EgressOnlyInternetGatewayId EgressOnlyインターネットゲートウェイID
GatewayId ゲートウェイID
InstanceId NATインスタンスID
NatGatewayId NATゲートウェイID
NetworkInterfaceId ネットワークインターフェイスID
RouteTableId ルートテーブルID
TransitGatewayId トランジットゲートウェイID
VpcPeeringConnectionId VPC ピア接続ID
  • ネットワーク経路を示す役割
  • 1つのサブネットに1つのルートテーブルを用意
  • ルートテーブルへのアタッチはRouteTableId:で指定
  • Type: AWS::EC2::SubnetRouteTableAssociationで、インターネットへ抜けられるサブネットを指定

RDS MySQL Aurora

secret&rds
https://techblog.nhn-techorus.com/archives/17674

セキュリティグループも一緒につくる
依存関係はよしなにしてくれる

qita_rds.yml
Parameters:
  ProjectName:
    Description: Type of this ProjectName.
    Type: String
  VPC:
    Type: "AWS::EC2::VPC::Id"
  SubnetIds:
    Type: List<AWS::EC2::Subnet::Id>
  DBName:
    Description: Type of this DatabaseName example sample_db.
    Type: String
  DBPassword:
    Description: Type of this DatabasePassword.
    Type: String
  # ProxyTargetDBClusterIdentifiers:
  #     Type: CommaDelimitedList

Resources:
  DBCluster:
    Type: AWS::RDS::DBCluster
    Properties:
      DBClusterIdentifier: !Sub "${ProjectName}-db-cluster"
      DBSubnetGroupName: !Ref "DBSubnetGroup"
      DatabaseName: !Sub "${DBName}"
      Engine: aurora-mysql
      EngineMode: "serverless"
      EngineVersion: "5.7.mysql_aurora.2.10.2"
      DBClusterParameterGroupName: !Ref DBParameterGroup
      # MasterUserPassword: !Sub "${DBPassword}"
      # MasterUsername: admin
      MasterUsername: !Join ['', ['{{resolve:secretsmanager:', !Ref SecretManager, ':SecretString:username}}' ]]
      MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref SecretManager, ':SecretString:password}}' ]]
      StorageEncrypted: true
      ScalingConfiguration:
        MinCapacity: 1
        AutoPause: false
        MaxCapacity: 2
      VpcSecurityGroupIds:
        - !Ref AuroraSecurityGroup
      DBSubnetGroupName: !Ref DBSubnetGroup
    DeletionPolicy: Delete

  # ------------------------------------------------------------#
  #  parameter group
  # ------------------------------------------------------------#
  DBParameterGroup:
    Type: AWS::RDS::DBClusterParameterGroup
    Properties:
      Family: aurora-mysql5.7
      Description: Database Parameter Group
      Parameters:
        character_set_database: utf8mb4
        character_set_client: utf8mb4
        character_set_connection: utf8mb4
        character_set_results: utf8mb4
        character_set_server: utf8mb4
        time_zone: Asia/Tokyo

  # ------------------------------------------------------------#
  #  subnet group
  # ------------------------------------------------------------#
  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupName: !Sub "${ProjectName}-db-subnet-group"
      DBSubnetGroupDescription: for db
      SubnetIds: !Split [",", !Join [",", !Ref SubnetIds]]

  # ------------------------------------------------------------#
  #  security group
  # ------------------------------------------------------------#
  AuroraSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: SecurityGroup for Aurora
      VpcId: !Sub "${VPC}"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          CidrIp: 10.0.1.0/24
      Tags:
        - Key: "Name"
          Value: !Sub "${ProjectName}-db-sg"
    # DependsOn: VPC

  # ------------------------------------------------------------#
  #  secret manager
  # ------------------------------------------------------------#
  SecretManager:
    Type: AWS::SecretsManager::Secret
    Properties: 
      Description: "Secrets Manager for RDS"
      SecretString:
        !Sub '{"username": "admin","password": "${DBPassword}"}'
      Name: !Sub "${ProjectName}-Secrets"
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-Secrets

  SecretManagerAttachment:
    Type: AWS::SecretsManager::SecretTargetAttachment
    Properties: 
      SecretId: !Ref SecretManager
      TargetId: !Ref DBCluster
      TargetType: AWS::RDS::DBCluster



  QitaRDSInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      AllocatedStorage: 5
      DBInstanceClass: db.t3.small
      Engine: MySQL
      MasterUsername: !Join ['', ['{{resolve:secretsmanager:', !Ref SecretManager, ':SecretString:username}}' ]]
      MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref SecretManager, ':SecretString:password}}' ]]
      BackupRetentionPeriod: 0
      DBInstanceIdentifier: 'qiita-proxy-instance'
 
  QitaRDSProxy:
    Type: AWS::RDS::DBProxy
    Properties:
      DBProxyName: !Sub "QitaRDBProxyName"
      EngineFamily: MYSQL
      RoleArn: !GetAtt IAMRole.Arn
      Auth:
        - AuthScheme: SECRETS
        # プロキシが使用するシークレットを選択
          SecretArn: !Ref "SecretManager"
      # RDS Proxyにアタッチするセキュリティグループ
      VpcSecurityGroupIds:
        - !ImportValue EC2-SG
        # RDS Proxyを構築するVPC Subnet
        # 選択したvpcでデータベースが使用可能なIP範囲を指定
      VpcSubnetIds:
        - !ImportValue private-subnetA
        - !ImportValue private-subnetC

  QitaRDSProxyTargetGroup:
    Type: AWS::RDS::DBProxyTargetGroup
    DependsOn:
      - QitaRDSInstance
    Properties:
      DBProxyName: !Ref QitaRDSProxy
      TargetGroupName: default
      # Proxyが接続するRDS Auroraクラスターの識別子
      # DBClusterIdentifier: !Ref DBCluster
      DBInstanceIdentifiers: 
        - !Ref QitaRDSInstance
      # DBClusterIdentifiers: !Ref ProxyTargetDBClusterIdentifiers

  RDSProxySecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: "RDS Proxy security group"
      VpcId: !Sub "${VPC}"
      SecurityGroupIngress: 
        # ec2のIPアドレスをインバウンドルールに設定
        - SourceSecurityGroupId: !ImportValue EC2-SG
          IpProtocol: "tcp"
          FromPort: 3306
          ToPort: 3306

  IAMRole:
      Type: "AWS::IAM::Role"
      Properties:
          AssumeRolePolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Principal:
                  Service:
                    - rds.amazonaws.com
                Action:
                  - 'sts:AssumeRole'
          ManagedPolicyArns: 
            - !Ref IAMPolicy


  IAMPolicy:
      Type: "AWS::IAM::ManagedPolicy"
      Properties:
          PolicyDocument: |
              {
                  "Version": "2012-10-17",
                  "Statement": [
                      {
                          "Sid": "GetSecretValue",
                          "Action": [
                              "secretsmanager:GetSecretValue",
                              "secretsmanager:GetResourcePolicy",
                              "secretsmanager:DescribeSecret",
                              "secretsmanager:ListSecretVersionIds"
                          ],
                          "Effect": "Allow",
                          "Resource": [
                              "*"
                          ]
                      },
                      {
                          "Sid": "DecryptSecretValue",
                          "Action": [
                              "kms:Decrypt"
                          ],
                          "Effect": "Allow",
                          "Resource": [
                              "*"
                          ],
                          "Condition": {
                              "StringEquals": {
                                  "kms:ViaService": "secretsmanager.ap-northeast-1.amazonaws.com"
                              }
                          }
                      }
                  ]
              }

Outputs:
  Endpoint:
    Value: !GetAtt DBCluster.Endpoint.Address
  ResourceArn:
    Value: !Sub "arn:aws:rds:ap-northeast-1:${AWS::AccountId}:cluster:${ProjectName}-db-cluster"
  SecretArn:
    Value: !Ref SecretManagerAttachment

security group

プロパティ 概要
GroupDescription セキュリティグループの説明
GroupName セキュリティグループの名前
SecurityGroupIngress インバウンドルールを宣言
IpProtocol IPプロトコル
FromPort ポート範囲の開始番号
FromPort ポート範囲の終了番号
CidrIp IPv4の範囲
VpcId VPCのID

Type: AWS::RDS::DBProxy

プロパティ 概要
DBProxyName 作成するRDS Proxy名
EngineFamily
RequireTLS TLSが必須かどうか
RoleArn RDS Proxyに設定するIAM Role
Auth Secrets Managerを利用してユーザ名とパスワードで認証
VpcSecurityGroupIds RDS Proxyにアタッチするセキュリティグループ
VpcSubnetIds RDS Proxyを構築するVPC Subnet

Type: AWS::RDS::DBProxyTargetGroup

プロパティ 概要
DBProxyName AWS::RDS::DBProxyで作成したRDS Proxy名
TargetGroupName ターゲットグループ名はdefaultが必須

Type: "AWS::EC2::SecurityGroup"

プロパティ 概要
SecurityGroupIngress ec2のIPアドレスをインバウンドルールに設定

ec2

qita_ec2.yml
Parameters:
  ProjectName:
    Description: Type of this ProjectName.
    Type: String
  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: "AWS::EC2::KeyPair::KeyName"
  PubSub:
    Type: "AWS::EC2::Subnet::Id"
  VPC:
    Type: "AWS::EC2::VPC::Id"

Resources:
  # ------------------------------------------------------------#
  #  ec2
  # ------------------------------------------------------------#
  EC2:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-078296f82eb463377
      KeyName: !Ref KeyName
      InstanceType: t2.micro
      NetworkInterfaces:
        - AssociatePublicIpAddress: "true"
          DeviceIndex: "0"
          SubnetId: !Ref PubSub
          GroupSet:
            - !Ref EC2SG
      UserData: !Base64 |
        #!/bin/bash
        sudo yum install -y mysql
      Tags:
        - Key: Name
          Value: !Sub "${ProjectName}-ec2"

  # ------------------------------------------------------------#
  #  ec2 security group
  # ------------------------------------------------------------#
  EC2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${ProjectName}-ec2-sg"
      GroupDescription: Allow SSH access only MyIP
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
# グローバルIP
          CidrIp: 0.0.0.0
Outputs:
  EC2PublicIP:
    Value: !GetAtt EC2.PublicIp
    Description: Public IP of EC2 instance

プロパティ 概要
ImageId マシンイメージのIDを使用。インスタンスを起動するのに必要なOSやボリューム情報などのテンプレートのこと
keyname キーペア名

  • キーペアの作成
    image.png

  • EC2へセキュリティを考慮したSSH接続をするための認証方式にキーペアを使用。
    1つは自身で管理するプライベートキー。もう1つはEC2のインスタンスに保管されるパブリックキー。

ssh pemについて
https://qiita.com/miriwo/items/3da2391f37dab6b5452c
https://blog.recruit.co.jp/rmp/infrastructure/retry-aws-bastion-host-vpc/

ec2へのssh接続を確認するコマンドは以下

ssh -i ~/.ssh/hoge-ec2.pem ec2-user@EC2のpublicIP

このような表示がされればOK
image.png

mysqlへ接続

mysql -u username -p -h RDSのエンドポイント

secret managerで作成したパスワードを入力すると接続が確認できる
image.png

参考

CloudFormationテンプレート
https://dev.classmethod.jp/articles/cloudformation-beginner01/

組み込み関数
https://dev.classmethod.jp/articles/cloudformation-tips-focused-on-refs/

cloudformationメモ
https://www.magata.net/memo/index.php?AWS%20CloudFormation%A5%E1%A5%E2

ネットワークの構築
https://qiita.com/kono-hiroki/items/6863aa06754fb25f28fc

VPC とサブネット
https://zenn.dev/tmasuyama1114/articles/aws-cloudformation-basics

ネットワーク系
https://zenn.dev/anaka/articles/627d0a112d57ed

インバウンドルール
https://qiita.com/yz2cm/items/2448f1545fef406ce8e3

lambda/secrets
https://www.seeds-std.co.jp/blog/creators/2021-08-13-002824/

cloudformationに秘密情報を渡す
https://techblog.zozo.com/entry/pass_secrets_to_cloudformation

ダイナミックリファレンスでシークレットを取得
https://docs.aws.amazon.com/ja_jp/secretsmanager/latest/userguide/cfn-example_reference-secret.html

proxyはsecretをどのように利用しているのか
https://dev.classmethod.jp/articles/how-rdsproxy-uses-secrets-manager/

proxyの構築
https://qiita.com/homoluctus/items/be6c9565ddd615d1d9ac

IAMroleにsecretへの権限許可
https://dev.classmethod.jp/articles/rds-proxy-useradd/

5
0
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
5
0