新サービス「AWS Secrets Manager」をチュートリアル2種(基本設定、RDSローテーション)で基礎から学ぶ

「なんか出た! 便利そう・・・だけど、ほんま使えんの?」

先日のAWS Summits 2018 | San Franciscoで発表された、AWS Secrets Manager、もう皆さんお使いでしょうか?

速報はこちら。

【完全新機能】DB認証情報やOAuthキーを一元管理可能なAWS Secrets Managerが発表されました!

直後に、AWSコンソールとドキュメントが公開され、チュートリアルで実際に手を動かして試してみたので其の様子をお届けします。シークレット情報の更新と共に、RDSのパスワードも同期的に更新され、アプリケーション影響無しに認証情報を定期的に更新できるのは、なかなかおもしろいです。

やってみて感じた運用上の注意点も最後に記載しているので、AWS Secrets Manager、「実際どんなもんやねん」と気になる方は是非目を通していただければと思います。

 __
(祭) ∧ ∧
 Y  ( ゚Д゚)
 Φ[_ソ__y_l〉     Secrets Managerダワッショイ
    |_|_|
    し'´J

「AWS Secrets Manager」って、結局なに?

公式ページ(AWS Secrets Manager | シークレットを更新、管理、取得)に概要は記載されていますが、主な特徴はこんな感じです。

AWS Secrets Managerのざっくりした特徴5点

  • 各種アプリケーションやITリソースのアクセスに必要なシークレット情報を一元管理
  • ユーザーはAWS Secrets Manager API経由でアクセスするため、各種シークレット情報をプレーンテキストで保持する必要が無い
  • RDS(MySQL、PostgreSQL)、Amazon Auroraに統合されており、シークレット情報の更新とデータベースパスワードの更新を自動化可能
  • APIキーや、OAuthトークンなどのシークレットにも拡張可能
  • IAMポリシーを利用して、シークレット情報へのアクセス制御が可能

よくあるユースケースは、データベース接続時のパスワード管理ではないでしょうか。AWS Secrets Managerは、同時に生成されるLambdaによってシークレット情報のローテーションと同期して、RDSのパスワードも更新してくれます。

これを自前実装するの、むっちゃ面倒くさいですよね。今回紹介する2つのチュートリアルでは、その点、実際の手順とあわせて説明していきます。

今回実施したチュートリアルの内容

  • チュートリアル1:シークレット情報の作成と取得
  • チュートリアル2:RDS接続シークレット情報の作成とローテーションの挙動確認

ほな、気軽に行ってみましょ。

チュートリアル1:シークレット情報の作成と取得

以下のチュートリアルを実施していきます。

Tutorial: Storing and Retrieving a Secret - AWS Secrets Manager

ステップ1:AWS Secretes Managerを利用したシークレットの作成と格納

Secrets Managerにログインします。

「新しいシークレットの保存」をクリック。

「新しいシークレットの保存」ページが表示されます。

シークレットタイプで「他の種類のシークレット」を選択。暗号化キーの選択では、「DefaultEncryptionKey」を選択します。これはAWSシークレットマネージャーが作成するデフォルト暗号化キーのため、費用はかかりません。もし、自分で作ったカスタムマスターキー(CMK)を利用するのであれば、別途KMSの料金が必要となります。

シークレットキー/値に、以下の情報を入力します。

シークレットキー
username myserviceusername
password MyVerySecureP@ssw0rd!

「プレーンテキスト」をクリックすると、入力内容がJSONで確認できます。もちろん編集も可能。

「次へ」をクリックすると「シークレットの名前と説明」の入力を求められます。

シークレット名にtutorials/MyFirstTutorialSecret、説明にhe secret I created for the first tutorialを入力し、「次へ」をクリックします。ちなみに、シークレット名は後からの変更はできないようです。

「自動ローテーションの設定」が表示されます。

このチュートリアルでは、ローテーションは無効化しておくので、このまま「次へ」をクリックします。

最後の確認画面が表示されます。今までの入力内容と同時に、アプリケーション側でシークレットを取得するサンプルコードも表示されています。現状、Java、JavaScript、Pythonの3種類です。親切設計やで。

Pythonのコードサンプルはこんな感じです。

# Use this code snippet in your app.
import boto3
from botocore.exceptions import ClientError


def get_secret():
    secret_name = "tutorials/MyFirstTutorialSecret"
    endpoint_url = "https://secretsmanager.ap-northeast-1.amazonaws.com"
    region_name = "ap-northeast-1"

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

    try:
        get_secret_value_response = client.get_secret_value(
            SecretId=secret_name
        )
    except ClientError as e:
        if e.response['Error']['Code'] == 'ResourceNotFoundException':
            print("The requested secret " + secret_name + " was not found")
        elif e.response['Error']['Code'] == 'InvalidRequestException':
            print("The request was invalid due to:", e)
        elif e.response['Error']['Code'] == 'InvalidParameterException':
            print("The request had invalid params:", e)
    else:
        # Decrypted secret using the associated KMS CMK
        # Depending on whether the secret was a string or binary, one of these fields will be populated
        if 'SecretString' in get_secret_value_response:
            secret = get_secret_value_response['SecretString']
        else:
            binary_secret_data = get_secret_value_response['SecretBinary']
            
        # Your code goes here.

「保存」ボタンを押して、シークレットを格納します。シークレット一覧に先程保存した「tutorials/MyFirstTutorialSecret」が表示されればOKです。

ステップ2:AWS Secretes Managerからのシークレット情報を取得

手順1で格納したシークレットを、AWSコンソールとAWS CLIで取得する方法を見ていきましょう。

AWSコンソールでの取得手順

むっちゃ簡単。AWS Secretes Managerを開いて、格納されている内容を参照するだけです。

AWS Secretes Manager CLIを利用した取得方法

事前にこちら(AWS Command Line Interface とは)を参考に、AWS CLIを設定しておきます。既にインストール済みの方も、最新版にバージョンアップしておきましょう。

シークレットへアクセスするための認証情報が既にセットされていれば、以下のコマンドで、先程格納したシークレット情報を取得できます。

$ aws secretsmanager describe-secret --secret-id tutorials/MyFirstTutorialSecret
{
    "VersionIdsToStages": {
        "c70f7c99-1f89-4c35-b656-6fa076c982e1": [
            "AWSCURRENT"
        ]
    },
    "LastChangedDate": 1522905807.758,
    "Description": "he secret I created for the first tutorial",
    "ARN": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:tutorials/MyFirstTutorialSecret-aiNCjl",
    "Name": "tutorials/MyFirstTutorialSecret"
}

ここでは、VersionIdsToStagesに注目。これは、アクティブなシークレットのリストとそれぞれのバージョンにアタッチされたラベルを表しています。このチュートリアルでは、シングルステージングラベルのAWSCURRENTに関連付けられたバージョンID(UUID形式)を利用します。

暗号化されたシークレットテキスト内容を参照するには、以下の通りです。

$ aws secretsmanager get-secret-value --secret-id tutorials/MyFirstTutorialSecret --version-stage AWSCURRENT
{
    "Name": "tutorials/MyFirstTutorialSecret",
    "VersionId": "c70f7c99-1f89-4c35-b656-6fa076c982e1",
    "SecretString": "{\"username\":\"myserviceusername\",\"password\":\"MyVerySecureP@ssw0rd!\"}",
    "VersionStages": [
        "AWSCURRENT"
    ],
    "CreatedDate": 1522905807.735,
    "ARN": "arn:aws:secretsmanager:ap-northeast-1:629895769338:secret:tutorials/MyFirstTutorialSecret-aiNCjl"
}

このコマンドですが、--version-stageは必須ではありません。指定しない場合、デフォルトでAWSCURRENTが利用されます。

どうでしょう。シークレット情報の作成も取得も非常に簡単なのがおわかりいただけたかと思います。チュートリアル1はこれにて終了。

チュートリアル2では、RDS(MySQL)のパスワードをローテーションする設定を実施していきます。

チュートリアル2:RDS接続シークレット情報の作成とローテーションの挙動確認

以下のチュートリアルを実施していきます。

Tutorial: Rotating a Secret for an AWS Database - AWS Secrets Manager

このチュートリアルでは、AWS Database用のシークレットを定期的にローテーションするスケジュールを設定します。手動でシークレット情報を更新しても、新しいパスワードで正常にAWS Databaseにアクセスできることを確認します。

ステップ1:テスト用データベースの作成

ここでは、テスト用にMySQL RDSを作成します。テスト用なので最小構成で組んでいきます。DBエンジンのバージョンもDBインスタンスのクラスも任意で構いません。

インスタンスの仕様はデフォルトにし、設定を以下で入力します。

設定内容
DBインスタンス識別子 MyTestDatabaseInstance
マスターユーザーの名前 adminuser
マスターパスワード myPassword

詳細設定画面では、「パブリックアクセシビリティ」を「はい」に設定します。これにより、自動的にデータベースにElastic IPが付与され、LambdaローテーションファンクションにVPCをアタッチする必要が無くなります。その他のネットワーク設定は初期値で設定します。

これは、チュートリアル用の最低限の設定です。プライベートVPCを利用する場合(普通はこっちすね)は、データベースが格納されているサブネットからルーティングテーブルにNAT gatewayへのルーティング設定を行います。また、適切なセキュリティグループの設定も必要です。LambdaをVPC内の設置する場合の構成については、弊社菊池のこちらの記事(機密管理サービス AWS Secrets Manager で RDS のパスワードローテーションを試す | Developers.IO)を参照ください。

その他の設定はデフォルト(ポートもデフォルトの3306のまま)で、RDSを作成します。

また、RDSに適用されているセキュリティグループにおいて、任意の場所からのポート3306インバウンドの許可設定も忘れずに設定してください。

ステップ2:シークレットの作成

Secrets Managerのコンソールにアクセスし、シークレットを作成します。

シークレットタイプの選択で「RDSデータベース認証情報」を選択。ステップ1で設定したマスターユーザーとマスターパスワードを入力。暗号化キーはデフォルトのまま。下に、先程作成したRDSのインスタンスが表示されているので選択します。

「シークレットの名前と説明」が表示されるので、シークレット名に「MyTestDatabaseMasterSecret」を入力。

自動ローテーションの設定が表示されますが、この時点では一旦「自動ローテーションを無効化」して、次へボタンを押し、内容を確認した後、シークレットを作成します。

ステップ3:作成したシークレットの確認

このステップでは、先細作成したシークレットを利用して、MySQLへのアクセスを検証します。RDSにアクセス可能なVPC内にEC2を作成し、以下のコマンドを実行していきます。

最初にMySQLクライアントとjqをインストール。AWS CLIを最新化します。

$ sudo yum install mysql jq -y
$ sudo pip install awscli --upgrade --user

インストールが完了したら、AWS CLIを利用して以下の情報を確認します。無事、前のステップでシークレットに設定した情報が取得できればOK。

$ aws secretsmanager get-secret-value --secret-id MyTestDatabaseMasterSecret | jq .SecretString | jq fromjson
{
  "username": "adminuser",
  "password": "myPassword",
  "engine": "mysql",
  "host": "mytestdatabaseinstance.XXXXXXXXXX.ap-northeast-1.rds.amazonaws.com",
  "port": 3306,
  "dbInstanceIdentifier": "mytestdatabaseinstance"
}

上記確認後、各パラメータを変数に格納します。

$ secret=$(aws secretsmanager get-secret-value --secret-id MyTestDatabaseMasterSecret | jq .SecretString | jq fromjson)
$ user=$(echo $secret | jq -r .username)
$ password=$(echo $secret | jq -r .password)
$ endpoint=$(echo $secret | jq -r .host)
$ port=$(echo $secret | jq -r .port)

パラメータが格納されたら、MySQLへの接続を試してみましょう。無事接続できましたかね。ダメそうなら、セキュリティグループあたりを見直しましょう。やってることは非常に単純です。

$ mysql -h $endpoint -u $user -P $port -p$password
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 26
Server version: 5.6.39-log MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

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

ステップ4:シークレットのローテーション設定

いよいよ、シークレットのローテーション設定します。先ほど作成した、シークレットを選択。

「ローテーションの編集」をクリック。

「Edit rotation configuration」画面が表示されるので、以下を入力。

  • 自動ローテーションを有効化
  • ローテーションの間隔:30日
  • ステップ1で指定したシークレットを使用

「保存」ボタンをクリックすると、しばらくすると、ローテーション設定が更新され、シークレットを更新するAWS Lambdaが自動的に作成され、ローテーションが実行されます。以下の画面で、ローテーションステータスが有効になっていることがわかります。

では、再度、シークレット情報を取得してみましょう。ローテーションにより認証情報のパスワードが更新されているはずです。

$ aws secretsmanager get-secret-value --secret-id MyTestDatabaseMasterSecret | jq .SecretString | jq fromjson
{
  "username": "adminuser",
  "engine": "mysql",
  "host": "mytestdatabaseinstance.XXXXXXXXXX.ap-northeast-1.rds.amazonaws.com",
  "password": "P]Nth,z58_-mw;RBOkK$u:]bwT+lLQ8u",
  "port": 3306,
  "dbInstanceIdentifier": "mytestdatabaseinstance"
}

これだけだと、シークレット情報が更新されていることを確認しただけです。実際にこの新しいシークレット情報でRDSにアクセスできるか試してみます。

secret=$(aws secretsmanager get-secret-value --secret-id MyTestDatabaseMasterSecret | jq .SecretString | jq fromjson)
user=$(echo $secret | jq -r .username)
password=$(echo $secret | jq -r .password)
endpoint=$(echo $secret | jq -r .host)
port=$(echo $secret | jq -r .port)

RDSアクセスします。無事、新しいパスワードでログインできる=RDSのパスワードが更新されていることが、確認できました!

$ mysql -h $endpoint -u $user -P $port -p$password
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 333
Server version: 5.6.39-log MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

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

mysql>

以上、お疲れ様でした。

運用上を気をつけておくべきポイント3点

一通り動作を確認しましたが、実際に運用する上でのポイントを記載します。

ポイント1:DBにパブリック・アクセスさせないために、ローテーション用のLambdaはVPC内に置く

チュートリアルでも記載しましたが、RDSは通常、パブリック・アクセスはさせません。その場合、シークレット情報をローテーションするためのLambdaは、VPC内に設置する必要があります。

  • LambdaがNatGateway経由でAWS Secrets Managerへアクセスできるようにする
  • LambdaからRDSにアクセスできるようにする

これらの設定の概要図については、「機密管理サービス AWS Secrets Manager で RDS のパスワードローテーションを試す | Developers.IO」を参照ください。

ポイント2:DB接続ユーザーはadminユーザー以外を利用する

チュートリアルでは、DB接続ユーザーとしてadminユーザーを利用していますが、通常アプリケーションから接続する際にadminユーザーを利用するのはリスクが大きいです。Secrets Managerは、RDS接続情報として任意のユーザーが設定可能なので、アプリケーションで利用する適切なユーザーを利用してください。

ポイント3:シークレット情報取得のAPI課金に注意

AWS Secrets Managerの料金は以下の通り。

Pricing | AWS Secrets Manager | Amazon Web Services (AWS)

  • PER SECRET PER MONTH
    • $0.40 per secret per month
  • PER 10,000 API CALLS
    • $0.05 per 10,000 API calls

アプリケーションからDB接続する際全ての接続でAPIコールすると、運用環境であれば1万コールはあっというまに使われてしまう可能性があります。料金を節約するのであれば、DB接続情報はアプリケーション側でキャッシュする仕組みがあると良いでしょう。

ただ、アプリケーション側でキャッシュすると、シークレット情報がローテーションされDBのパスワードが変更されたときに、キャッシュが更新されないとアプリケーションからDBアクセスできなくなります。ローテーションがかかったときは、Lambdaの中でDB接続キャッシュをクリアするカスタマイズを入れるのが良いかと思います。

まとめ「むっちゃ簡単に導入できるので、まずは試して感じてみましょ」

以上、AWS Secrets Managerの基本的な使い方と、RDSシークレット情報のローテーションをチュートリアル形式で説明しました。

RDSの接続情報をセキュアに管理しつつ、ローテーションを自動化できるため、使い方によっては機密情報の管理を大幅に簡略化できる可能性があります。運用上の注意点もありますがメリットも大きいのではないでしょうか。

非常に手軽に導入できるので、まずはこのチュートリアルで実際に手を動かしながら、開発環境等での導入を検討いただければと思います。

それでは、今日はこのへんで。濱田(@hamako9999)でした。