app.yamlを使用して環境変数をGAEに安全に保存する


97

app.yamlGAEにデプロイするための環境変数として、APIキーやその他の機密情報を保存する必要があります。これの問題は、app.yamlGitHub にプッシュすると、この情報が公開される(良くない)ことです。プロジェクトに適さないため、データストアに情報を保存したくありません。むしろ、.gitignoreアプリの各デプロイメントにリストされているファイルから値を交換したいと思います。

これが私のapp.yamlファイルです:

application: myapp
version: 3 
runtime: python27
api_version: 1
threadsafe: true

libraries:
- name: webapp2
  version: latest
- name: jinja2
  version: latest

handlers:
- url: /static
  static_dir: static

- url: /.*
  script: main.application  
  login: required
  secure: always
# auth_fail_action: unauthorized

env_variables:
  CLIENT_ID: ${CLIENT_ID}
  CLIENT_SECRET: ${CLIENT_SECRET}
  ORG: ${ORG}
  ACCESS_TOKEN: ${ACCESS_TOKEN}
  SESSION_SECRET: ${SESSION_SECRET}

何か案は?


72
GAEに、開発者コンソールを介してインスタンス環境変数を設定するオプションを追加してほしい(私がよく知っている他のすべてのPaaSと同様)。
スペイン列車

4
データストアを使用できます。この回答を参照してください:stackoverflow.com/a/35254560/1027846
MustafaİlhanFeb

データストアの使用に関する上記のmustilicaのコメントを拡張します。これを行うためにプロジェクトで使用するコードについては、以下の私の回答を参照してください:stackoverflow.com/a/35261091#35261091。実際には、開発者コンソールから環境変数を編集でき、プレースホルダー値が自動的に作成されます。
Martin Omander 2017年

MustilicaとMartinに感謝します。私たちは実際にしばらくデータストアアプローチを使用してきましたが、これがこの問題の最良の解決策であることに同意します。jsonファイルのアプローチであるIMOよりもCI / CDのセットアップが簡単です。
スペイン列車

1
2019とGAEはまだこの問題を修正していません:/
Josh Noe

回答:


53

機密データの場合は、ソース管理にチェックインされるため、ソースコードに保存しないでください。間違った人(組織の内部または外部)がそこにいる可能性があります。また、開発環境はおそらく本番環境とは異なる構成値を使用します。これらの値がコードに格納されている場合、開発と本番で別のコードを実行する必要がありますが、これは面倒で悪い習慣です。

私のプロジェクトでは、このクラスを使用してデータストアに構成データを配置します。

from google.appengine.ext import ndb

class Settings(ndb.Model):
  name = ndb.StringProperty()
  value = ndb.StringProperty()

  @staticmethod
  def get(name):
    NOT_SET_VALUE = "NOT SET"
    retval = Settings.query(Settings.name == name).get()
    if not retval:
      retval = Settings()
      retval.name = name
      retval.value = NOT_SET_VALUE
      retval.put()
    if retval.value == NOT_SET_VALUE:
      raise Exception(('Setting %s not found in the database. A placeholder ' +
        'record has been created. Go to the Developers Console for your app ' +
        'in App Engine, look up the Settings record with name=%s and enter ' +
        'its value in that record\'s value field.') % (name, name))
    return retval.value

アプリケーションはこれを実行して値を取得します。

API_KEY = Settings.get('API_KEY')

データストアにそのキーの値がある場合、それを取得します。存在しない場合は、プレースホルダーレコードが作成され、例外がスローされます。例外により、デベロッパーコンソールに移動してプレースホルダーレコードを更新するように通知されます。

これにより、構成値の設定から推測を省くことができます。設定する構成値がわからない場合は、コードを実行するだけでわかります。

上記のコードは、内部でmemcacheとデータストアを使用するndbライブラリを使用しているため、高速です。


更新:

jelderは、App Engineコンソールでデータストアの値を見つけて設定する方法を尋ねました。方法は次のとおりです。

  1. https://console.cloud.google.com/datastore/にアクセスます

  2. プロジェクトがまだ選択されていない場合は、ページの上部でプロジェクトを選択します。

  3. では種類ドロップダウンボックス、選択設定

  4. 上記のコードを実行すると、キーが表示されます。それらの値はすべてNOT SETです。それぞれをクリックし、その値を設定します。

お役に立てれば!

設定クラスによって作成された設定

クリックして編集する

実際の値を入力して保存


2
提供されたすべての回答の中で、これはHerokuの処理方法に最も近いようです。GAEは初めてなので、Developers Consoleでプレースホルダーレコードを見つける場所がよくわかりません。スクリーンショットを投稿したり、ボーナスポイントについて説明したりできますか?
エルダー

2
dam〜…すべてのgcloudに敬意を表して、この特定のニーズのために別のサービスを使用しなければならないのはかなり悪いようです。その上、googleは、firebase関数内のenv変数に「100%-herokuish」のアプローチを提供しますが、gcloud関数には提供しません(少なくともドキュメント化されていません…私が間違っていなければ)
Ben

1
ここに、独自性と環境変数のフォールバックを追加するアプローチに基づく要点があります-gist.github.com/SpainTrain/6bf5896e6046a5d9e7e765d0defc8aa8
スペイントレイン

3
Firebase以外の@Ben関数は、env変数をサポートします(少なくとも)。
NReilingh

3
@obl-App Engineアプリは独自のデータストアに対して自動的に認証され、認証の詳細は必要ありません。それはかなりきちんとしている:-)
マーティンオマンダー

46

このソリューションはシンプルですが、すべての異なるチームに適しているとは限りません。

まず、環境変数をenv_variables.yamlに入れます。たとえば、

env_variables:
  SECRET: 'my_secret'

次に、これenv_variables.yamlapp.yaml

includes:
  - env_variables.yaml

最後に、env_variables.yamltoを追加して.gitignore、秘密変数がリポジトリに存在しないようにします。

この場合、env_variables.yamlデプロイメントマネージャ間でニーズを共有する必要があります。


1
明らかではないものを追加するだけで、環境変数がに見つかり、process.env.MY_SECRET_KEYローカルの開発環境でこれらの環境変数が必要な場合は、ノードdotenvパッケージを使用できます
Dave Kiss

2
env_variables.yamlすべてのインスタンスに到達する方法は、パズルの欠けている部分です。
Christopher Oezbek

また、これをローカルで使用する方法は?
Christopher Oezbek

@ChristopherOezbek 1.デプロイ方法は?使うだけgcloud app deploy通常どおりGoogle Cloudにデプロイするのとします。2.シークレット環境変数をローカルに設定するにはどうすればよいですか?多くの方法があります。あなただけ使用することができますexport@DaveKissが提案のようなプロンプトコマンドまたは任意のツールを使用しています。
Shih-Wen Su

これが最も簡単な解決策です。シークレットは、アプリケーションからを介してアクセスできますos.environ.get('SECRET')
Quinn Comendant

19

私のアプローチは、App Engineアプリ自体にのみクライアントシークレットを格納することです。クライアントシークレットは、ソース管理にもローカルコンピュータにもありません。これは、という利点を有する任意の App Engineコラボレーターがクライアントシークレットを気にすることなくコード変更をデプロイできるあります。

クライアントシークレットを直接Datastoreに保存し、Memcacheを使用してシークレットへのアクセスのレイテンシを改善しています。データストアエンティティは一度だけ作成する必要があり、今後のデプロイでも保持されます。もちろん、App Engineコンソールを使用して、いつでもこれらのエンティティを更新できます。

1回限りのエンティティ作成を実行するには、2つのオプションがあります。

  • App Engine Remote APIインタラクティブシェルを使用してエンティティを作成します。
  • ダミー値でエンティティを初期化する管理専用ハンドラを作成します。この管理ハンドラーを手動で呼び出してから、App Engineコンソールを使用して、実稼働クライアントシークレットでエンティティを更新します。

7
まったく複雑ではありません。アプリエンジンに感謝します。
裁判所が

16

それを行う最善の方法は、client_secrets.jsonファイルにキーを保存し、それを.gitignoreファイルにリストすることでgitにアップロードされないようにすることです。環境ごとに異なるキーがある場合は、app_identity apiを使用してアプリIDを特定し、適切にロードできます。

ここにかなり包括的な例があります-> https://developers.google.com/api-client-library/python/guide/aaa_client_secrets

ここにいくつかのサンプルコードがあります:

# declare your app ids as globals ...
APPID_LIVE = 'awesomeapp'
APPID_DEV = 'awesomeapp-dev'
APPID_PILOT = 'awesomeapp-pilot'

# create a dictionary mapping the app_ids to the filepaths ...
client_secrets_map = {APPID_LIVE:'client_secrets_live.json',
                      APPID_DEV:'client_secrets_dev.json',
                      APPID_PILOT:'client_secrets_pilot.json'}

# get the filename based on the current app_id ...
client_secrets_filename = client_secrets_map.get(
    app_identity.get_application_id(),
    APPID_DEV # fall back to dev
    )

# use the filename to construct the flow ...
flow = flow_from_clientsecrets(filename=client_secrets_filename,
                               scope=scope,
                               redirect_uri=redirect_uri)

# or, you could load up the json file manually if you need more control ...
f = open(client_secrets_filename, 'r')
client_secrets = json.loads(f.read())
f.close()

2
間違いなく正しい方向ですが、これはapp.yamlアプリのデプロイ時に値を交換する問題には対処しません。何かアイデアはありますか?
ベン

1
したがって、環境ごとに異なるclient_secretsファイルを用意してください。たとえば、client_secrets_live.json、client_secrets_dev.json、client_secrets_pilot.jsonなどの場合、Pythonロジックを使用して、現在使用しているサーバーを特定し、適切なjsonファイルをロードします。app_identity.get_application_id()メソッドは、現在使用しているサーバーを自動検出するのに役立ちます。これはあなたが言っているようなことですか?
グウィンハウエル

@BenGrunfeld私の答えを見てください。私の解決策はまさにこれを行います。この答えが問題をどのように解決するかはわかりません。私は、秘密の設定をgitに含ませず、デプロイメントの一部としてgitを使用することを目標としています。ここでも、このファイルはどこかにあり、展開プロセスにプッシュされる必要があります。これはアプリで行うこともできますが、私が強調した手法を使用するだけでよく、これをapp.yamlと比較して使用する場合は、別のファイルに保存します。質問を理解すれば、オープンソースアプリをライブラリメーカーの実際のクライアントシークレットまたは製品とともに出荷することに似ています。キー。
therewillbesnacks 2014年

1
それを理解するのに少し時間がかかりましたが、これは正しいアプローチだと思います。アプリケーション設定(app.yaml)と秘密鍵および機密情報を混在させていません。私が本当に気に入っているのは、Googleワークフローを使用してタスクを実行していることです。@GwynHowellに感謝します。=)
Ben

1
同様のアプローチは、そのJSONファイルをアプリのデフォルトのGCSバケット(cloud.google.com/appengine/docs/standard/python/…)の既知の場所に配置することです。
スペイン列車

16

これは投稿時には存在しませんでしたが、ここで偶然見つけた人のために、GoogleはSecret Managerというサービスを提供しています。

これは、Googleクラウドプラットフォームの安全な場所にシーク​​レットを保存するためのシンプルなRESTサービスです(もちろんSDKでラップされています)。これはデータストアよりも優れたアプローチであり、保存されたシークレットを確認するための追加の手順が必要であり、よりきめ細かな権限モデルがあります。必要に応じて、プロジェクトのさまざまな側面に対して個別のシークレットを異なる方法で保護できます。

バージョン管理を提供するため、比較的簡単にパスワードの変更を処理できるだけでなく、必要に応じて、実行時にシークレットを検出および作成できる堅牢なクエリおよび管理レイヤーを使用できます。

Python SDK

使用例:

from google.cloud import secretmanager_v1beta1 as secretmanager

secret_id = 'my_secret_key'
project_id = 'my_project'
version = 1    # use the management tools to determine version at runtime

client = secretmanager.SecretManagerServiceClient()

secret_path = client.secret_verion_path(project_id, secret_id, version)
response = client.access_secret_version(secret_path)
password_string = response.payload.data.decode('UTF-8')

# use password_string -- set up database connection, call third party service, whatever

3
これが新しい正解です。シークレットマネージャーはまだベータ版ですが、これは環境変数を操作するときの前進です。
レオン王

@KingLeon、これを使用すると、大量のos.getenv('ENV_VAR')s をリファクタリングする必要がありますか?
アレハンドロ

上記のようなコードを関数に入れて、のようなものを使用しますSECRET_KEY = env('SECRET_KEY', default=access_secret_version(GOOGLE_CLOUD_PROJECT_ID, 'SECRET_KEY', 1))。デフォルトを設定してaccess_secret_version
キングレオン

また、私はdjango-environを使用しています。github.com/joke2k/django-environ
キングレオン

15

このソリューションは非推奨のappcfg.pyに依存しています

アプリをGAEにデプロイするときにappcfg.pyの-Eコマンドラインオプションを使用して環境変数を設定できます(appcfg.py update)

$ appcfg.py
...
-E NAME:VALUE, --env_variable=NAME:VALUE
                    Set an environment variable, potentially overriding an
                    env_variable value from app.yaml file (flag may be
                    repeated to set multiple variables).
...

展開後のどこかにこれらの環境変数を照会できますか?(私はそうは望みません。)
Ztyx 2018年

gcloudユーティリティを使用して、このように環境変数を渡す方法はありますか?
Trevor

6

ほとんどの回答は古くなっています。Google Cloud Datastoreの使用は、実際には少し異なります。https://cloud.google.com/python/getting-started/using-cloud-datastore

次に例を示します。

from google.cloud import datastore
client = datastore.Client()
datastore_entity = client.get(client.key('settings', 'TWITTER_APP_KEY'))
connection_string_prod = datastore_entity.get('value')

これは、エンティティ名が「TWITTER_APP_KEY」、種類が「設定」、「値」がTWITTER_APP_KEYエンティティのプロパティであると想定しています。


3

いくつかのアプローチを実行できるようです。私たちは同様の問題を抱えており、次のことを行います(ユースケースに合わせて):

  • 動的なapp.yaml値を格納するファイルを作成し、ビルド環境の安全なサーバーに配置します。本当に偏執的である場合は、値を非対称的に暗号化できます。バージョン管理/動的プルが必要な場合は、これをプライベートリポジトリに保持することも、シェルスクリプトを使用して適切な場所からコピー/プルすることもできます。
  • デプロイスクリプト中にgitからプルする
  • git pull後、yamlライブラリを使用して純粋なpythonで読み書きしてapp.yamlを変更します。

これを行う最も簡単な方法は、HudsonBambooJenkinsなどの継続的インテグレーションサーバーを使用することです。上記のすべての項目を実行するプラグイン、スクリプトステップ、またはワークフローを追加するだけです。たとえば、Bamboo自体で構成されている環境変数を渡すことができます。

要約すると、アクセス権しかない環境でビルドプロセス中に値をプッシュするだけです。ビルドをまだ自動化していない場合は、自動化する必要があります。

別のオプションオプションはあなたが言ったことです、それをデータベースに入れてください。これを行わない理由が遅すぎる場合は、2番目のレイヤーのキャッシュとしてmemcacheに値をプッシュし、1番目のレイヤーのキャッシュとしてインスタンスに値を固定します。値が変わる可能性があり、インスタンスを再起動せずにインスタンスを更新する必要がある場合は、ハッシュを保持して、いつ変更されたかを確認したり、何かを行って値を変更したときにトリガーしたりできます。それはそれであるはずです。


1
FWIW、このアプローチは、12 Factor Appガイドライン(12factor.net)の構成ファクターに最も厳密に従っています
スペイン列車、

3

変数をgoogle kmsで暗号化し、ソースコードに埋め込む必要があります。(https://cloud.google.com/kms/

echo -n the-twitter-app-key | gcloud kms encrypt \
> --project my-project \
> --location us-central1 \
> --keyring THEKEYRING \
> --key THECRYPTOKEY \
> --plaintext-file - \
> --ciphertext-file - \
> | base64

スクランブルされた(暗号化され、base64でエンコードされた)値を環境変数(yamlファイル内)に入れます。

復号化を始めるためのいくつかのpythonishコード。

kms_client = kms_v1.KeyManagementServiceClient()
name = kms_client.crypto_key_path_path("project", "global", "THEKEYRING", "THECRYPTOKEY")

twitter_app_key = kms_client.decrypt(name, base64.b64decode(os.environ.get("TWITTER_APP_KEY"))).plaintext

3

Google Datastoreの使用に基づく@Jason Fの答えは近いですが、コードは、ライブラリドキュメントのサンプルの使用に基づいて少し古くなっています。これは私のために働いたスニペットです:

from google.cloud import datastore

client = datastore.Client('<your project id>')
key = client.key('<kind e.g settings>', '<entity name>') # note: entity name not property
# get by key for this entity
result = client.get(key)
print(result) # prints all the properties ( a dict). index a specific value like result['MY_SECRET_KEY'])

このミディアムポストから部分的に影響を受けた


2

この問題をjavascript / nodejsでどのように解決したかに注意したいだけです。ローカル開発では、環境変数を.envファイルからprocess.envにロードする 'dotenv' npmパッケージを使用しました。GAEを使い始めたとき、環境変数を「app.yaml」ファイルに設定する必要があることを学びました。まあ、ローカル開発に 'dotenv'を、GAEに 'app.yaml'を使用したくなかったので(2つのファイル間で環境変数を複製しました)、app.yaml環境変数をプロセスに読み込む小さなスクリプトを書きました。 .env、ローカル開発用。これが誰かを助けることを願っています:

yaml_env.js:

(function () {
    const yaml = require('js-yaml');
    const fs = require('fs');
    const isObject = require('lodash.isobject')

    var doc = yaml.safeLoad(
        fs.readFileSync('app.yaml', 'utf8'), 
        { json: true }
    );

    // The .env file will take precedence over the settings the app.yaml file
    // which allows me to override stuff in app.yaml (the database connection string (DATABASE_URL), for example)
    // This is optional of course. If you don't use dotenv then remove this line:
    require('dotenv/config');

    if(isObject(doc) && isObject(doc.env_variables)) {
        Object.keys(doc.env_variables).forEach(function (key) {
            // Dont set environment with the yaml file value if it's already set
            process.env[key] = process.env[key] || doc.env_variables[key]
        })
    }
})()

コードにできるだけ早くこのファイルを含めれば完了です。

require('../yaml_env')

これはまだ事実ですか?.envシークレット変数を含むファイルを使用しているためです。私はapp.yamlファイルでそれらを複製していませんし、私のデプロイしたコードはまだ機能します。.envクラウド内のファイルがどうなるか心配です。暗号化されますか?.envデプロイされたgcloud ファイル変数に誰もアクセスしないようにするにはどうすればよいですか?
Gus

GAEはapp.yamlファイルで定義されたすべての変数をノード環境に自動的に追加するため、これはまったく必要ありません。これは基本的に、.envパッケージで定義された変数を使用してdotenvが行うのと同じです。しかし、env変数を使用してapp.yamlをVCSまたはパイプラインにプッシュできないため、CDをどのようにセットアップする必要があるのか​​と思います...
Jornve

1

マーティンの答えを拡張する

from google.appengine.ext import ndb

class Settings(ndb.Model):
    """
    Get sensitive data setting from DataStore.

    key:String -> value:String
    key:String -> Exception

    Thanks to: Martin Omander @ Stackoverflow
    https://stackoverflow.com/a/35261091/1463812
    """
    name = ndb.StringProperty()
    value = ndb.StringProperty()

    @staticmethod
    def get(name):
        retval = Settings.query(Settings.name == name).get()
        if not retval:
            raise Exception(('Setting %s not found in the database. A placeholder ' +
                             'record has been created. Go to the Developers Console for your app ' +
                             'in App Engine, look up the Settings record with name=%s and enter ' +
                             'its value in that record\'s value field.') % (name, name))
        return retval.value

    @staticmethod
    def set(name, value):
        exists = Settings.query(Settings.name == name).get()
        if not exists:
            s = Settings(name=name, value=value)
            s.put()
        else:
            exists.value = value
            exists.put()

        return True

1

Cloud Datastoreにappengine環境変数を保存できるようにするgae_envと呼ばれるpypiパッケージがあります。内部的にはMemcacheも使用しているため、高速です

使用法:

import gae_env

API_KEY = gae_env.get('API_KEY')

データストアにそのキーの値がある場合、それが返されます。ない場合は、プレースホルダーレコード__NOT_SET__が作成され、ValueNotSetErrorがスローされます。例外により、デベロッパーコンソールに移動してプレースホルダーレコードを更新するように通知されます。


マーティンの回答と同様に、データストアのキーの値を更新する方法を次に示します。

  1. 開発者コンソールのデータストアセクションに移動

  2. プロジェクトがまだ選択されていない場合は、ページの上部でプロジェクトを選択します。

  3. では種類ドロップダウンボックスを選択GaeEnvSettings

  4. 例外が発生したキーには値があります__NOT_SET__

設定クラスによって作成された設定

クリックして編集する

実際の値を入力して保存


使用方法/構成の詳細については、パッケージのGitHubページに移動してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.