Подтвердить что ты не робот

Безопасное хранение переменных среды в 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}

Любые идеи?

4b9b3361

Ответ 1

Если это конфиденциальные данные, вы не должны хранить его в исходном коде, поскольку он будет проверен в исходном элементе управления. Неправильные люди (внутри или вне вашей организации) могут найти его там. Кроме того, среда разработки, вероятно, использует различные значения конфигурации из вашей рабочей среды. Если эти значения хранятся в коде, вам придется запускать разный код в процессе разработки и производства, что является грязной и плохой практикой.

В моих проектах я помещал данные конфигурации в хранилище данных, используя этот класс:

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\ value field.') % (name, name))
    return retval.value

Ваше приложение будет делать это, чтобы получить значение:

API_KEY = Settings.get('API_KEY')

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

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

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


Update:

jelder попросил найти значения Datastore на консоли App Engine и установить их. Вот как:

  • Перейдите к https://console.cloud.google.com/datastore/

  • Выберите проект в верхней части страницы, если он еще не выбран.

  • В раскрывающемся списке Вид выберите Настройки.

  • Если вы запустили код выше, ваши ключи появятся. Все они будут иметь значение NOT SET. Нажмите каждый из них и установите его значение.

Надеюсь, это поможет!

Ваши настройки, созданные классом Settings

Нажмите, чтобы изменить

Введите реальное значение и сохраните

Ответ 2

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

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

env_variables:
  SECRET: 'my_secret'

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

includes:
  - env_variables.yaml

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

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

Ответ 3

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

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

Существует два варианта однократного создания сущностей:

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

Ответ 4

Лучший способ сделать это - сохранить ключи в файле 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()

Ответ 5

Вы можете использовать опцию командной строки -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).
...

Ответ 6

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

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

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

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

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

Ответ 7

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

Ответ 8

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

Ответ 9

Просто хотел заметить, как я решил эту проблему в javascript/nodejs. Для локального развития я использовал пакет "dotenv" npm, который загружает переменные среды из файла .env в process.env. Когда я начал использовать GAE, я узнал, что переменные окружения должны быть установлены в файле "app.yaml". Ну, я не хотел использовать "dotenv" для локальной разработки и "app.yaml" для GAE (и дублировать переменные среды между двумя файлами), поэтому я написал немного script, который загружает переменные среды app.yaml в process.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 already set
            process.env[key] = process.env[key] || doc.env_variables[key]
        })
    }
})()

Теперь включите этот файл как можно раньше в свой код, и все будет готово:

require('../yaml_env')

Ответ 10

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

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\ 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

Ответ 11

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

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

import gae_env

API_KEY = gae_env.get('API_KEY')

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


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

  1. Перейдите в раздел Datastore в консоли разработчиков.

  2. Выберите ваш проект в верхней части страницы, если он еще не выбран.

  3. В раскрывающемся GaeEnvSettings Kind выберите GaeEnvSettings.

  4. Ключи, для которых было __NOT_SET__ исключение, будут иметь значение __NOT_SET__.

Your settings, created by the Settings class

Click to edit

Enter the real value and save


Перейдите на страницу пакета GitHub для получения дополнительной информации об использовании/конфигурации.

Ответ 12

@Jason F ответ, основанный на использовании Google Datastore, близок, но код немного устарел из-за примера использования в библиотеке документов. Вот фрагмент, который работал для меня:

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

Отчасти вдохновлено этим средним постом