Python
AWS
環境変数
boto3
SecretsManager
0

環境変数地獄にはAWS Secrets Managerで立ち向かえ!

はじめに

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マネジメントコンソール

46020218-d4e5b580-c118-11e8-9aa7-69edbecb8de2.png

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_NAMEPASSWORDtutorials/MyFristTutorialSecret というシークレットから取得するときのシンプルな例です。

おわりに

AWS Secrets Managerを利用して環境変数地獄に対処してみました。
スプレッドシートやS3で環境変数の管理を経て、現在は一旦Secrets Managerを利用した環境変数管理に落ち着いています。

やはりマネージドなサービスに乗っかるのが色々と楽をできます。
しかし、なんだかなぁと思う点も多いのでよりスマートな方法はないかと模索し続けたいですね。

AWSはPython以外の言語もSDKを公開しており、同じようにSecrets Managerを利用することができると思うのでぜひ活用してみてください。


  1. DjangoCongress JP 2018レガシーDjangoアプリケーションの現代化」 で取り上げられていた問題です。