【初心者】AWS Secrets Manager と AWS Systems Manager Parameter Store を使ってみる


# 1. 目的
- AWSのセキュリティ関連サービスの復習をしている。パスワードなどの保護すべき値を安全に保存するための仕組みであるAWS Secrets Manager と AWS Systems Manager Parameter Store について、それぞれを試して使い勝手などを確認する。
- 機能の差異や使い分けについては、Qiita記事「[AWSのParameter StoreとSecrets Manager、結局どちらを使えばいいのか？比較](https://qiita.com/tomoya_oka/items/a3dd44879eea0d1e3ef5)」に詳しく整理、網羅されており、それを読むだけで一通り理解できるが、一応自分でどんなものか試してみる。

# 2. やったこと

- RDSと、RDSへ接続し操作する用のEC2インスタンスを作成する。
- RDSに接続する際のパスワードをそれぞれAWS Secrets Manager と AWS Systems Manager Parameter Storeに保存し、EC2インスタンスからの接続時に、値を呼び出して利用する。

# 3. 構成図

![構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189302/57bfbd10-a955-1fe5-f096-d640ee87d65a.png)


# 4. 設定手順

## 4.1 環境準備
- VPC、EC2インスタンス(Amazon Linux 2)、RDS DB(MySQL 8.0.20)を作成する。
- RDSに接続するための認証情報を「user: admin, password: PASSWORD」に設定しておく。
- EC2インスタンス(Amazon Linux 2)で、Python3, MySQLクライアントが利用できるように各種パッケージのインストールを行う。
    - 参考手順(Python mysqlclientのインストール): 「[mysqlclient Python3でMySQLに接続する](https://www.python.ambitious-engineer.com/archives/818)」
    - 参考手順(mysql クライアントコマンドのインストール): 「[【AWS EC2】Amazon Linux2にMySQLのclientだけをインストールしてRDSに接続する方法](https://qiita.com/tamorieeeen/items/d9b2af588f1dfd43120d)」

```
[ec2-user@ip-10-0-0-196 ~]$ sudo yum install python3
[ec2-user@ip-10-0-0-196 ~]$ python3 -V
Python 3.7.9
[ec2-user@ip-10-0-0-196 ~]$ sudo pip3 install boto3
[ec2-user@ip-10-0-0-196 ~]$ sudo yum install python-devel mysql-devel
[ec2-user@ip-10-0-0-196 ~]$ sudo yum install python3-devel
[ec2-user@ip-10-0-0-128 ~]$ sudo yum install gcc
[ec2-user@ip-10-0-0-128 ~]$ sudo pip3 install mysqlclient
[ec2-user@ip-10-0-0-128 ~]$ sudo yum localinstall -y https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm
[ec2-user@ip-10-0-0-128 ~]$ sudo yum install -y mysql-community-client
```

- RDS DBに接続し、テスト用のテーブルを作っておく。

```
[ec2-user@ip-10-0-0-128 ~]$ mysql -h mksamba-testdb-202103.XXXXXXXXXXXX.ap-northeast-1.rds.amazonaws.com -P 3306 -u admin -p
mysql> create database mksambadb;
mysql> use mksambadb;
mysql> create table mksambadb.mytable(id int, stamp varchar(30));
mysql> insert into mytable (id, stamp)
    -> values('1', '1000');
```

## 4.2 EC2インスタンス(Python3)からの接続確認
- RDSに接続するためのID/Passwordをコードにそのまま埋め込んで、RDSに接続できることを確認する。

``` dbconnect01.py
import MySQLdb

connection = MySQLdb.connect(
    host='mksamba-testdb-202103.XXXXXXXXXXXX.ap-northeast-1.rds.amazonaws.com',
    user='admin',
    passwd='PASSWORD',
    db='mksambadb')
cursor = connection.cursor()

cursor.execute("SELECT * FROM mksambadb.mytable")
for row in cursor:
    print(row)
connection.commit()
connection.close()
```

```
[ec2-user@ip-10-0-0-128 ~]$ python3 dbconnect01.py
(1, '1000')
```

## 4.3 Secret Manager の設定
- Secret Manager にパスワードを保存する設定を行う。単純にパスワードのみを保存することもできるが、今回は「RDSデータベース認証情報」形式での保存を行う。
- Secret Managerの画面にて、「新しいシークレットを保存する」を選択し、必要な項目を入力する。
    - シークレットの種類として、「RDSデータベースの認証情報」を選択する。
    - ユーザ名は「admin」、パスワードは設定したパスワード「PASSWORD」を入力する。
    - 暗号化キーとして、あらかじめ作成済のKMSカスタマーマスターキーを指定する。
    - このID/Passwordを使用して接続するRDS DBを選択する。

![secret01a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189302/3575c4ad-2b9f-d03d-8358-12f3c3c4dc98.png)

- このsecretの名前を「mksamba-secret-for-rds」とする。

 ![secret02a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189302/d4e6e3a5-0e39-4fce-23c5-fc9c6254f1b8.png)

- 今回はローテーションは無効のままにしておく。

![secret03a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189302/c74e193c-9843-2711-7bf2-da4140446c1f.png)

- 最後に値を取得するためのサンプルコードが表示される。これをコピペして利用する。


![secret04a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189302/76a3dfc3-eb3b-d198-7bd2-6e60136e2c89.png)

![secret05a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189302/37534700-57bd-77b3-63c7-372aaeb96c83.png)


## 4.4 Secret Manager からの値の取得

- EC2インスタンスからRDS DBに接続する際に、パスワードをコードに埋め込むのではなく、Secret Managerに保存した値を取得して使用するように設定する。
- EC2インスタンスに、Secret Manager及びKMSの権限があるIAM Roleを付与する。(今回は検証のため、Poweruser相当の権限を付与)
- コードは以下のように修正する。注意点として、Secret(ID/Passwordの値を保存)が文字列型のため、辞書型に変更した上で値を取得する。DevelopersIOの記事「[PythonでAWS Secrets ManagerからAPIキーを取得するときのちょっとしたポイント](https://dev.classmethod.jp/articles/secrets_manager_tips_get_api_key/)」のやり方を利用させて頂いた。

``` dbconnect02.py
import MySQLdb
import boto3
import base64
from botocore.exceptions import ClientError
import ast

secret_name = "mksamba-secret-for-rds"
region_name = "ap-northeast-1"

session = boto3.session.Session()
client = session.client(
    service_name='secretsmanager',
    region_name=region_name
)

try:
     get_secret_value_response = client.get_secret_value(
        SecretId=secret_name
    )
except ClientError as e:
    if e.response['Error']['Code'] == 'DecryptionFailureException':
        raise e
    elif e.response['Error']['Code'] == 'InternalServiceErrorException':
        raise e
    elif e.response['Error']['Code'] == 'InvalidParameterException':
        raise e
    elif e.response['Error']['Code'] == 'InvalidRequestException':
        raise e
    elif e.response['Error']['Code'] == 'ResourceNotFoundException':
        raise e
else:
    if 'SecretString' in get_secret_value_response:
        secret = get_secret_value_response['SecretString']
    else:
        decoded_binary_secret = base64.b64decode(get_secret_value_response['SecretBinary'])

secret = ast.literal_eval(secret)

connection = MySQLdb.connect(
    host='mksamba-testdb-202103.XXXXXXXXXXXX.ap-northeast-1.rds.amazonaws.com',
    user=secret['username'],
    passwd=secret['password'],
    db='mksambadb')
cursor = connection.cursor()

cursor.execute("SELECT * FROM mksambadb.mytable")
for row in cursor:
    print(row)
connection.commit()
connection.close()
```

## 4.5 Parameter Store の設定
- 今度はパスワードの値をParameter Storeに保存してみる。
- AWS Systems Managerの画面にて、「パラメータストア」- 「パラメータを作成」を選択する。
    - タイプは「安全な文字列」を選択し、暗号化を行う。
    - あらかじめ作成済のKMS カスタマーマスターキーを指定する。
    - 値として、パスワードの文字列「PASSWORD」を入力する。

![secret06a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189302/05f5a971-78da-bc1a-c785-28dd310ef647.png)

## 4.6 Parameter Store からの値の取得

- EC2インスタンスからRDS DBに接続する際に、パスワードをコードに埋め込むのではなく、Parameter Storeに保存した値を取得して使用するように設定する。
- EC2インスタンスに、Systems Manager及びKMSの権限があるIAM Roleを付与する。(今回は検証のため、Poweruser相当の権限を付与)
- コードは以下のように修正する。手順はDevelopersIO記事「[AWS SDK for Python (Boto3) で AWS Systems Manager パラメータストアから情報を取得する](https://dev.classmethod.jp/articles/get-data-from-system-manager-parameter-store-using-boto3-ja/)」を参照。

``` dbconnect03.py
import MySQLdb
import boto3
import json

ssm = boto3.client('ssm', 'ap-northeast-1')
response = ssm.get_parameters(
    Names=[
        'mksamba-password-rds',
    ],
    WithDecryption=True
)

connection = MySQLdb.connect(
    host='mksamba-testdb-202103.XXXXXXXXXXXX.ap-northeast-1.rds.amazonaws.com',
    user='admin',
    passwd=response['Parameters'][0]['Value'],
    db='mksambadb')
cursor = connection.cursor()

cursor.execute("SELECT * FROM mksambadb.mytable")
for row in cursor:
    print(row)
connection.commit()
connection.close()
```

# 5. 所感
- どちらも隠したい値を暗号化して保存しておき、必要な時に取り出して使用する、という点ではそんなに変わりなく使えるかなと思った。費用や、細かい機能の差異(Secret Managerの値の自動更新機能など)の必要性に応じて判断できるようにしたい。