it-swarm.com.ru

Безопасное хранение переменных среды в GAE с помощью app.yaml

Мне нужно хранить ключи API и другую конфиденциальную информацию в app.yaml как переменные среды для развертывания в GAE. Проблема в том, что если я отправлю 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}

Есть идеи?

53
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')

Если в хранилище данных есть значение для этого ключа, вы получите его. Если нет, будет создана запись-заполнитель и будет сгенерировано исключение. Исключение будет напоминать вам о необходимости перейти в Консоль разработчика и обновить запись заполнителя.

Я считаю, что это отнимает догадки из установки значений конфигурации. Если вы не уверены, какие значения конфигурации установить, просто запустите код, и он скажет вам!

Приведенный выше код использует библиотеку ndb, которая использует memcache и хранилище данных под капотом, так что это быстро.


Обновление:

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

32
Martin Omander

Мой подход заключается в хранении клиентских секретов only в самом приложении App Engine. Секреты клиента не находятся ни в системе контроля версий, ни на локальных компьютерах. Это дает то преимущество, что any соавтор App Engine может развертывать изменения кода, не беспокоясь о клиентских секретах.

Я храню клиентские секреты непосредственно в Datastore и использую Memcache для улучшенной задержки доступа к секретам. Объекты хранилища данных нужно создавать только один раз, и они будут сохраняться в течение последующих развертываний. Конечно, консоль App Engine может использоваться для обновления этих объектов в любое время.

Существует два варианта выполнения одноразового создания объекта:

  • Используйте App Engine Удаленный API Интерактивная оболочка для создания сущностей.
  • Создайте обработчик только для администратора, который будет инициализировать объекты с фиктивными значениями. Вручную вызовите этот обработчик администратора, а затем с помощью консоли App Engine обновите сущности секретами производственного клиента.
18
Bernd Verst

Лучший способ сделать это - сохранить ключи в файле client_secrets.json и исключить их из загрузки в git, перечислив их в файл .gitignore. Если у вас разные ключи для разных сред, вы можете использовать app_identity api, чтобы определить, что это за идентификатор приложения, и загрузить соответствующим образом.

Здесь есть довольно полный пример -> 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

Вы можете использовать параметр командной строки -E appcfg.py для настройки переменных среды при развертывании приложения в 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

Это простое решение, но может не подходить для всех разных команд. 

Сначала поместите переменные окружения в env_variables.yaml , например,

env_variables:
  SECRET: 'my_secret'

Затем включите этот env_variables.yaml в app.yaml

includes:
  - env_variables.yaml

Наконец, добавьте env_variables.yaml к .gitignore, чтобы секретные переменные не существовали в хранилище.

В этом случае env_variables.yaml должен быть общим для администраторов развертывания.

5
Shih-Wen Su

Похоже, вы можете сделать несколько подходов. У нас похожая проблема, и мы делаем следующее (адаптировано к вашему варианту использования):

  • Создайте файл, в котором будут храниться любые динамические значения app.yaml, и поместите его на защищенный сервер в вашей среде сборки. Если вы действительно параноик, вы можете асимметрично зашифровать значения. Вы даже можете сохранить это в частном репо, если вам нужен контроль версий/динамическое извлечение, или просто использовать скрипт оболочки, чтобы скопировать/вытащить его из подходящего места.
  • Вытащить из git во время сценария развертывания
  • После git pull измените app.yaml, прочитав и написав его на чистом python с использованием библиотеки yaml

Самый простой способ сделать это - использовать сервер непрерывной интеграции, такой как Hudson , Bamboo или Jenkins . Просто добавьте некоторый плагин, шаг сценария или рабочий процесс, который выполняет все перечисленные выше элементы. Например, вы можете передать переменные окружения, которые сконфигурированы в самом Bamboo.

Таким образом, просто введите значения во время процесса сборки в среде, к которой у вас есть доступ. Если вы еще не автоматизировали свои сборки, вы должны это сделать.

Другой вариант - то, что вы сказали, поместите его в базу данных. Если причина, по которой вы этого не делаете, заключается в том, что все происходит слишком медленно, просто вставьте значения в memcache как кэш 2-го уровня и прикрепите значения к экземплярам как кэш первого уровня. Если значения могут измениться, и вам нужно обновить экземпляры, не перезагружая их, просто сохраните хэш, который вы можете проверить, чтобы узнать, когда они меняются, или как-то активируйте его, когда что-то, что вы делаете, изменяет значения. Это должно быть так.

2
therewillbesnacks

Существует пакет pypi gae_env , который позволяет сохранять переменные среды appengine в Cloud Datastore. Под капотом он также использует Memcache, поэтому его быстро

Использование:

import gae_env

API_KEY = gae_env.get('API_KEY')

Если в хранилище данных есть значение для этого ключа, оно будет возвращено . Если его нет, будет создана запись-заполнитель __NOT_SET__ и будет выброшена переменная ValueNotSetError. Исключение будет напоминать вам о необходимости перейти в Консоль разработчика и обновить запись заполнителя.


Как и в ответе Мартина, здесь показано, как обновить значение ключа в хранилище данных:

  1. Зайдите в Раздел хранилища данных в консоли разработчиков

  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

Расширяя ответ Мартина

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

Большинство ответов устарели. Использование хранилища данных Google Cloud на самом деле сейчас немного по-другому. 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.

0
Jason F

Вы должны зашифровать переменные с помощью 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
0
Anders Elton

Просто хотел отметить, как я решил эту проблему в javascript/nodejs. Для локальной разработки я использовал пакет 'dotenv' npm, который загружает переменные окружения из файла .env в process.env. Когда я начал использовать GAE, я узнал, что переменные окружения должны быть установлены в файле app.yaml. Ну, я не хотел использовать «dotenv» для локальной разработки и «app.yaml» для GAE (и дублировать мои переменные среды между двумя файлами), поэтому я написал небольшой скрипт, который загружает переменные среды 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')
0
gbruins