Безпечне зберігання змінних середовища в GAE за допомогою app.yaml


98

Мені потрібно зберігати ключі 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}

Будь-які ідеї?


73
Я хотів би, щоб GAE додав опцію встановлення env vars через консоль розробника (як і будь-який інший PaaS, з яким я знайомий).
Поїзд Іспанії

4
Ви можете використовувати сховище даних. Будь ласка, зверніться до цієї відповіді: stackoverflow.com/a/35254560/1027846
Мустафа Ілхан

Розширюючи коментар mustilica вище щодо використання сховища даних. Дивіться мою відповідь нижче щодо коду, який я використовую у своїх проектах для цього: stackoverflow.com/a/35261091#35261091 . По суті, це дозволяє редагувати змінні середовища з консолі розробника, а значення заповнювачів створюються автоматично.
Мартін Омандер

Дякую mustilica та Мартін. Ми фактично використовували підхід до сховища даних деякий час, і я погоджуюсь, що це найкраще рішення цієї проблеми. Це простіше зробити з налаштуванням CI / CD, ніж підхід до файлів json, IMO.
Поїзд Іспанії

1
2019 і GAE досі не вирішили цю проблему: /
Джош Ное,

Відповіді:


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

Якщо у сховищі даних є значення для цього ключа, ви отримаєте його. Якщо цього не сталося, буде створено запис заповнювача та видано виняток. Виняток нагадає вам перейти на Консоль розробників та оновити запис заповнювача.

Я вважаю, що це виключає здогадки щодо встановлення значень конфігурації. Якщо ви не впевнені, які значення конфігурації встановити, просто запустіть код, і він вам це скаже!

У наведеному вище коді використовується бібліотека ndb, яка використовує memcache та сховище даних під капотом, тому це швидко.


Оновлення:

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

  1. Перейдіть на сторінку https://console.cloud.google.com/datastore/

  2. Виберіть свій проект у верхній частині сторінки, якщо він ще не вибраний.

  3. У спадному вікні Kind виберіть Налаштування .

  4. Якщо ви запустили код вище, ваші ключі з’являться. Усі вони матимуть значення НЕ ВСТАНОВЛЕНО . Клацніть на кожному та встановіть його значення.

Сподіваюся, це допомагає!

Ваші налаштування, створені класом Налаштування

Клацніть, щоб редагувати

Введіть дійсне значення та збережіть


2
З усіх наданих відповідей це здається найближчим до того, як Героку поводиться з речами. Будучи досить новим для GAE, я не зовсім розумію, де в консолі розробників знайти запис заповнювача. Ви можете пояснити, або за бонусні бали, опублікувати скріншоти?
jelder

2
греблі ~… при всій повазі до gcloud, виглядає зовсім погано, коли доводиться користуватися іншою службою для цієї конкретної потреби. Окрім цього, Google надає підхід "100% -herokuish" для env vars у функціях firebase, але не для функцій gcloud (принаймні недокументованих ... якщо я не помиляюся)
Бен,

1
Ось суть, заснована на вашому підході, яка додає унікальність та резервну змінну середовища - gist.github.com/SpainTrain/6bf5896e6046a5d9e7e765d0defc8aa8
Іспанія Поїзд

3
Функції @Ben Non-Firebase підтримують env vars (принаймні зараз).
NReilingh,

3
@obl - додаток App Engine автоматично аутентифікується у власному сховищі даних, деталі автентифікації не потрібні. Це досить акуратно :-)
Мартін Омандер

49

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

Спочатку помістіть змінні середовища у env_variables.yaml , наприклад,

env_variables:
  SECRET: 'my_secret'

Потім включіть це env_variables.yamlвapp.yaml

includes:
  - env_variables.yaml

Нарешті, додайте env_variables.yamlдо .gitignore, щоб секретні змінні не існували у сховищі.

У цьому випадку env_variables.yamlпотреби мають бути розподілені між менеджерами з розгортання.


1
Просто для того, щоб додати те, що для деяких може бути не очевидним, ваші змінні середовища тоді будуть знайдені, process.env.MY_SECRET_KEYі якщо вам потрібні ці змінні середовища у вашому локальному середовищі розробника, ви можете використовувати dotenvпакет вузлів
Dave Kiss

2
Як би env_variables.yamlдістатися до всіх інстанцій - це відсутній фрагмент головоломки.
Крістофер Озбек,

1
Також: Як використовувати це локально?
Крістофер Озбек,

@ChristopherOezbek 1. Як розгорнути? Просто використовуйте, gcloud app deployяк зазвичай, для розгортання в Google Cloud. 2. Як локально встановити секретні змінні середовища? Є багато способів. Ви можете просто використовувати exportв командному рядку або використовувати будь-які інструменти, такі як запропонований @DaveKiss.
Ши-Вень Су

Це найпростіше рішення. До секретів можна отримати доступ у вашій заявці через os.environ.get('SECRET').
Quinn Comendant

19

Мій підхід полягає в тому, щоб зберігати секрети клієнта лише в самій програмі App Engine. Секрети клієнта не знаходяться ні в контролі джерел, ні на локальних комп’ютерах. Це має ту перевагу, що будь-який співавтор App Engine може розгортати зміни коду, не турбуючись про секрети клієнта.

Я зберігаю клієнтські секрети безпосередньо в сховищі даних і використовую Memcache для поліпшення затримки доступу до секретів. Сутності Datastore потрібно створити лише один раз, і вони будуть зберігатися в наступних розгортаннях. звичайно, консоль App Engine можна використовувати для оновлення цих об’єктів у будь-який час.

Є два варіанти одноразового створення сутності:

  • Для створення об’єктів використовуйте інтерактивну оболонку App Engine Remote API .
  • Створіть обробник лише для адміністратора, який ініціалізує сутності з фіктивними значеннями. Виконайте вручну цей обробник адміністратора, а потім використовуйте консоль App Engine, щоб оновити сутності із секретами робочого клієнта.

7
Зовсім не складно. Дякую двигун програми.
courtimas

17

Цього не існувало, коли ви розміщували публікації, але для всіх, хто сюди потрапляє, Google тепер пропонує послугу під назвою Secret Manager .

Це простий сервіс REST (звичайно, з обгортанням SDK) для зберігання ваших секретів у безпечному місці на хмарній платформі Google. Це кращий підхід, ніж Магазин даних, вимагає додаткових кроків для перегляду збережених секретів і має більш чітку модель дозволу - ви можете захистити окремі секрети по-різному для різних аспектів вашого проекту, якщо вам це потрібно.

Він пропонує версійні версії, завдяки чому ви можете відносно легко обробляти зміни паролів, а також надійний рівень запитів та управління, що дозволяє виявляти та створювати секрети під час виконання, якщо це необхідно.

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
Це повинна бути нова правильна відповідь. Secret Manager все ще знаходиться в бета-версії, але це шлях уперед при роботі зі змінними середовища.
Король Леон

@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-environment. github.com/joke2k/django-environ
Король Леон

16

Найкращий спосіб зробити це - зберегти ключі у файлі 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()

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

1
Мені знадобився час, щоб обійти голову, але я думаю, що це правильний підхід. Ви не змішуєте налаштування програми ( app.yaml) із секретними ключами та конфіденційною інформацією, і мені дуже подобається те, що ви використовуєте робочий процес Google для виконання завдання. Дякую @GwynHowell. =)
Бен

1
Подібним підходом було б розміщення цього файлу JSON у відомому місці в стандартному сегменті GCS програми ( cloud.google.com/appengine/docs/standard/python/… ).
Поїзд Іспанії

15

Це рішення спирається на застарілий appcfg.py

Ви можете використовувати параметр командного рядка -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).
...

Чи можете ви запитати ці змінні середовища десь після розгортання? (Сподіваюсь, що ні.)
Ztyx,

Чи є спосіб передавати змінні середовища в такий спосіб за допомогою gcloudутиліти?
Тревор

6

Більшість відповідей застарілі. Зараз використання хмарного магазину даних Google насправді дещо інше. 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 змініть app.yaml, прочитавши та записавши його чистим python, використовуючи бібліотеку yaml

Найпростіший спосіб зробити це - використовувати сервер безперервної інтеграції, такий як Хадсон , Бамбук або Дженкінс . Просто додайте якийсь плагін, крок сценарію або робочий процес, який виконує всі вищезазначені пункти, про які я згадав. Ви можете передати змінні середовища, які налаштовані в самому Bamboo, наприклад.

Підсумовуючи, просто вставте значення під час процесу збірки в середовищі, до якого ви маєте доступ. Якщо ви ще не автоматизуєте свої збірки, вам слід це зробити.

Інший варіант - це те, що ви сказали, помістіть це в базу даних. Якщо ваша причина того, що ви цього не робите, полягає в тому, що справи занадто повільні, просто натисніть значення у memcache як кеш 2-го шару та закріпіть значення в екземплярах як кеш першого шару. Якщо значення можуть змінюватися, і вам потрібно оновити екземпляри, не перезавантажуючи їх, просто тримайте хеш, який ви можете перевірити, щоб знати, коли вони змінюються, або якось активувати це, коли щось, що ви робите, змінює значення. Це має бути все.


1
FWIW, цей підхід найбільш точно відповідає фактору конфігурації, наведеному в керівництві додатків 12 Factor ( 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).

Якийсь пітонічний код для початку розшифрування.

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

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

Частково натхненний цим дописом Medium


2

Просто хотів зазначити, як я вирішив цю проблему в 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')

Це все ще так? Тому що я використовую .envфайл із секретними змінними. Я не дублюю їх у своєму app.yamlфайлі, і мій розгорнутий код все ще працює. Мене турбує, що відбувається з .envфайлом у хмарі. Це зашифровується чи щось інше? Як я можу переконатись, що ніхто не отримує доступ до змінних .envфайлу gcloud після його розгортання?
Гас,

Це взагалі не потрібно, оскільки GAE автоматично додає всі змінні, визначені у файлі app.yaml, до середовища вузла. В основному це те саме, що робить dotenv зі змінними, визначеними в .env-пакеті. Але мені цікаво, як вам потрібно встановити компакт-диск, оскільки ви не можете підштовхувати app.yaml з env vars до VCS або конвеєра ...
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

Існує пакет pypi під назвою gae_env, який дозволяє зберігати змінні середовища додатків у Cloud Datastore. Під капотом він також використовує Memcache так швидко

Використання:

import gae_env

API_KEY = gae_env.get('API_KEY')

Якщо у сховищі даних є значення для цього ключа, він буде повернутий. Якщо цього не сталося, __NOT_SET__буде створено запис-заповнювач , і ValueNotSetErrorбуде викинуто заповіт. Виняток нагадає вам перейти на Консоль розробника та оновити запис заповнювача.


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

  1. Перейдіть до розділу сховища даних на консолі розробників

  2. Виберіть свій проект у верхній частині сторінки, якщо він ще не вибраний.

  3. У спадному вікні Kind виберіть GaeEnvSettings.

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

Ваші налаштування, створені класом Налаштування

Клацніть, щоб редагувати

Введіть дійсне значення та збережіть


Перейдіть на сторінку пакета GitHub для отримання додаткової інформації про використання / конфігурацію

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.