はじめに
Twelve Fact App に準拠するアプリケーション開発・運用の悩みの種の一つに「環境変数地獄」1があります。
環境ごと、サービスごとに倍々に膨れ上がる環境変数をどう管理するべきかという問題です。
AWS Secrets Managerを使うとAWSでアプリケーションと環境変数を安全に分離して管理することができます。
AWS Secrets Managerを活用することで環境変数の管理をなるべく簡潔にしよういうのがこの記事の主旨です。
AWS Secrets Manager とは
データベースの認証情報やAPIKeyなどのアプリケーションの環境変数を安全に格納・配布・交換・使用できるシークレット管理サービス。
Amazon RDS for MySQL、PostgreSQL、Amazon Aurora への統合を組み込むことでDBの認証情報のローテーションが提供されるのが パラメータストア との違いの一つです。
Secrets Managerへの登録方法
1. AWSマネジメントコンソール
GUIで環境変数の追加が行えますが、複数の環境変数を追加する際はとても手間がかかります。
また、Secrets Managerの仕様なのか、A-Zで環境変数をソートしてくれず、追加順でしか表示できません。
環境変数の微修正などの目的以外は、CLIからの登録をおすすめです。
2. AWS CLI
https://docs.aws.amazon.com/cli/latest/reference/secretsmanager/index.html
AWS CLIが利用できるように準備をしてください。利用方法は適宜調べてください。
IAMやリージョンを適切に設定してから以下のコマンドでSecrets Managerに環境変数の登録ができます。
aws secretsmanager create-secret --name xxx --description xxxx --secret-string file://example.json
create-secret
オプションは新規作成時のみ有効なので、環境変数を更新する場合は put-secret-value
オプションを利用する必要があります。
example.json
は シークレットのプレーンテキストで表示されるフォーマットです。(ドキュメントで提示されているフォーマットだとうまくいかなかったので注意が必要です。)
{
"Key" : "Value",
"USER_NAME" : "user_name",
"PASSWORD": "password"
...
}
このフォーマットのjson形式で管理しておくと環境変数の作成や更新がCLIから行えるので便利です。
例えば新たに環境を増やす場合、ベースとなる環境のjsonファイルをコピーして値を適切な値に書き換えるだけで、そのjsonファイルを元に新しい環境の環境変数をSecrets Managerに登録することができます。
CLIでの登録は環境変数の値のバイト数が長すぎると登録できない場合があるので注意が必要です。
Secrets Managerからの呼び出し方法
登録した環境変数を呼び出してみます。Pythonで boto3 を利用してクライアント側の実装をしてみます。
import json
import boto3
from botocore.exceptions import ClientError
class SecretsManager:
def __init__(self, aws_region, aws_access_key_id=None, aws_secret_access_key=None):
self.aws_region = aws_region
self.aws_access_key_id = aws_access_key_id
self.aws_secret_access_key = aws_secret_access_key
def get_secret_values(self, secret_name):
client = boto3.client(
'secretsmanager',
aws_access_key_id=self.aws_access_key_id,
aws_secret_access_key=self.aws_secret_access_key,
region_name=self.aws_region
)
try:
resp = client.get_secret_value(SecretId=secret_name)
secret_values = json.loads(resp['SecretString'])
except ClientError:
raise
except KeyError:
raise
except json.JSONDecodeError:
raise
else:
return secret_values
def get_secret_value(self, secret_key, secret_values):
try:
secret_value = secret_values[secret_key]
except KeyError:
raise
else:
return secret_value
※ AWS_ACCESS_KEY_ID
, AWS_SECRET_ACCESS_KEY
に関しては適切なIAM Roleをアタッチすることで設定不要です。デフォルトのポリシーで SecretsManagerReadWrite
があります。
サードパーティライブラリ aws-sm を公開しました
https://github.com/jumpyoshim/aws-sm
上記の SecretsManager
クラスを pip install
で使えるようにしました。
インストール
pip install aws-sm
使い方
from aws_sm import SecretsManager
AWS_ACCESS_KEY_ID = ***************
AWS_SECRET_ACCESS_KEY = ***************
secretsmanager = SecretsManager('ap-northeast-1', AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
secrets = secretsmanager.get_secret_values('tutorials/MyFristTutorialSecret')
USER_NAME = secretsmanager.get_secret_value('USER_NAME', secrets)
PASSWORD = secretsmanager.get_secret_value('PASSWORD', secrets)
これは USER_NAME
と PASSWORD
を tutorials/MyFristTutorialSecret
というシークレットから取得するときのシンプルな例です。
おわりに
AWS Secrets Managerを利用して環境変数地獄に対処してみました。
スプレッドシートやS3で環境変数の管理を経て、現在は一旦Secrets Managerを利用した環境変数管理に落ち着いています。
やはりマネージドなサービスに乗っかるのが色々と楽をできます。
しかし、なんだかなぁと思う点も多いのでよりスマートな方法はないかと模索し続けたいですね。
AWSはPython以外の言語もSDKを公開しており、同じようにSecrets Managerを利用することができると思うのでぜひ活用してみてください。
-
DjangoCongress JP 2018 「レガシーDjangoアプリケーションの現代化」 で取り上げられていた問題です。 ↩