web-dev-qa-db-ja.com

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

GAEにデプロイするための環境変数として、app.yamlにAPIキーとその他の機密情報を保存する必要があります。これに関する問題は、app.yamlをGitHubにプッシュすると、この情報が公開される(良くない)ことです。プロジェクトに適さないため、データストアに情報を保存したくありません。むしろ、アプリの各デプロイメントで.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}

何か案は?

69
Ben

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

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

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. Kindドロップダウンボックスで、Settingsを選択します。

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

お役に立てれば!

Your settings, created by the Settings class

Click to edit

Enter the real value and save

45
Martin Omander

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

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

env_variables:
  SECRET: 'my_secret'

次に、これを含めますenv_variables.yaml の中に app.yaml

includes:
  - env_variables.yaml

最後に、env_variables.yamlから.gitignore。これにより、シークレット変数がリポジトリに存在しなくなります。

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

23
Shih-Wen Su

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

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

ワンタイムエンティティ作成を実行するには、2つのオプションがあります。

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

最善の方法は、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()
16
Gwyn Howell

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

$ 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).
...
15
jla

いくつかのアプローチを実行できるようです。同様の問題があり、以下を実行します(ユースケースに合わせて):

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

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

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

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

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ファイル内の)環境変数に入れます。

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

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
2
Anders Elton

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

使用法:

import gae_env

API_KEY = gae_env.get('API_KEY')

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


Martinの答えと同様に、Datastoreのキーの値を更新する方法は次のとおりです。

  1. 開発者コンソールの Datastore Section に移動します

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

  3. Kindドロップダウンボックスで、GaeEnvSettingsを選択します。

  4. 例外が発生したキーの値は__NOT_SET__

Your settings, created by the Settings class

Click to edit

Enter the real value and save


パッケージのGitHubページ にアクセスして、使用法/構成の詳細を確認してください。

1
Prince Odame

ほとんどの答えは時代遅れです。 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エンティティのプロパティであることを前提としています。

1
Jason F

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')
1
gbruins

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

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
JSBach