使用app.yaml将环境变量安全地存储在GAE中


97

我需要将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}

有任何想法吗?


72
我希望GAE可以通过开发者控制台添加选项来设置实例环境变量(就像我熟悉的所有其他PaaS一样)。
西班牙火车

4
您可以使用数据存储。请参考以下答案:stackoverflow.com/a/35254560/1027846
Mustafaİlhan16年

扩展了mustilica关于使用数据存储区的评论。请参阅下面的答案,了解我在项目中使用的代码:stackoverflow.com/a/35261091#35261091。实际上,它使您可以从开发人员控制台编辑环境变量,并自动创建占位符值。
Martin Omander

谢谢mustilica和马丁。实际上,我们一段时间以来一直在使用数据存储方法,我同意这是解决此问题的最佳方法。使用CI / CD设置比使用json文件方法IMO更容易。
西班牙火车

1
2019年,GAE 仍未解决此问题:/
Josh Noe

Answers:


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询问如何在App Engine控制台中找到数据存储区值并进行设置。方法如下:

  1. 前往https://console.cloud.google.com/datastore/

  2. 如果尚未选择项目,请在页面顶部选择它。

  3. 种类下拉框中,选择设置

  4. 如果您运行上面的代码,您的密钥将会显示。它们都将具有值NOT SET。单击每个并设置其值。

希望这可以帮助!

您的设置,由“设置”类创建

点击编辑

输入实际值并保存


2
在提供的所有答案中,这似乎与Heroku处理事情的方式最接近。对于GAE来说还很陌生,我不太了解在Developers Console中的哪里可以找到占位符记录。您能解释一下,或为了获得奖励积分而发布屏幕截图吗?
jelder

2
dam〜…出于对gcloud的所有应有的尊重,为满足此特定需求而不得不使用另一项服务似乎很糟糕。除此之外,谷歌确实提供了“100%-herokuish”的方式为ENV火力职能范围内瓦尔,但gcloud功能没有(至少无证......如果我没看错)

1
这是基于您的方法的要点,该要点增加了唯一性和环境变量回退-gist.github.com/SpainTrain/6bf5896e6046a5d9e7e765d0defc8aa8
西班牙火车

3
@Ben Non-Firebase函数确实支持env vars(至少现在)。
NReilingh

3
@obl-App Engine应用会自动验证到其自己的数据存储区,无需验证详细信息。它非常整洁:-)
Martin Omander '19年

46

此解决方案很简单,但可能不适合所有不同的团队。

首先,将环境变量放入env_variables.yaml中,例如,

env_variables:
  SECRET: 'my_secret'

然后,将其包含env_variables.yamlapp.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获得所有实例是这个难题的缺失。
克里斯托弗·奥兹别克

另外:如何在本地使用它?
Christopher Oezbek

@ChristopherOezbek 1.如何部署?只需像平常一样使用gcloud app deploy即可部署到Google Cloud。2.如何在本地设置秘密环境变量?有很多方法。您可以只export在命令提示符下使用,也可以使用建议的任何工具,例如@DaveKiss。
苏士文

这是最简单的解决方案。可以在您的应用程序中通过访问秘密os.environ.get('SECRET')
Quinn Comendant

19

我的方法是将客户端机密存储在App Engine应用本身中。客户端机密既不在源代码控制中,也不在任何本地计算机上。这样的好处是,任何 App Engine合作者都可以部署代码更改,而不必担心客户端机密。

我将客户端机密直接存储在数据存储区中,并使用Memcache改善了访问机密的延迟。数据存储区实体仅需要创建一次,并将在以后的部署中保持不变。当然,可以随时使用App Engine控制台更新这些实体。

有两种方法可以执行一次性实体创建:

  • 使用App Engine 远程API交互式外壳程序创建实体。
  • 创建一个仅管理员处理程序,该处理程序将使用伪值初始化实体。手动调用此管理处理程序,然后使用App Engine控制台使用生产客户端密码更新实体。

7
一点也不复杂。谢谢应用引擎。
courtsimas

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在部署应用程序时换出值的问题。有什么想法吗?
2014年

1
因此,每个环境都有一个不同的client_secrets文件。例如client_secrets_live.json,client_secrets_dev.json,client_secrets_pilot.json等,然后使用python逻辑来确定您所在的服务器并加载相应的json文件。app_identity.get_application_id()方法可能有助于自动检测您所在的服务器。这是您的意思吗?
Gwyn Howell 2014年

@BenGrunfeld看到我的答案。我的解决方案正是这样做的。我不知道这个答案如何解决这个问题。我假设目标是使秘密配置不进入git,并将git用作部署的一部分。在这里,该文件仍然需要放在某个地方并被推送到部署过程中。这可以是您在应用程序中执行的操作,但是您只需要使用我强调的技术即可,如果要使用vs.app.yaml,则可以将其存储在另一个文件中。如果我理解这个问题,那类似于将开源程序与图书馆制造商的实际客户机密或产品一起出售。键。
therewillbesnacks

1
我花了一些时间来解决这个问题,但是我认为这是正确的方法。您不会将应用程序设置(app.yaml)与密钥和机密信息混合使用,而我真正喜欢的是您正在使用Google工作流程来完成任务。谢谢@GwynHowell。=)

1
一种类似的方法是将该JSON文件放置在应用程序默认GCS存储桶(cloud.google.com/appengine/docs/standard/python/…)中的已知位置。
西班牙火车

16

发布时不存在此功能,但对于在这里偶然发现的其他人,Google现在提供一项称为Secret Manager的服务

这是一个简单的REST服务(当然,其中包含SDK)将您的机密存储在Google云平台上的安全位置。与Data Store相比,这是一种更好的方法,需要额外的步骤来查看存储的机密并具有更细粒度的权限模型-如果需要,您可以针对项目的不同方面以不同的方式保护单个机密。

它提供版本控制,因此您可以相对轻松地处理密码更改,以及强大的查询和管理层,使您能够在必要时在运行时发现和创建机密信息。

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仍处于Beta中,但这是使用环境变量时的前进之路。
国王莱昂

@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
King Leon

另外,我正在使用django-environ。github.com/joke2k/django-environ
国王莱昂

15

此解决方案依赖于已弃用的appcfg.py

将应用程序部署到GAE时,可以使用appcfg.py的-E命令行选项设置环境变量(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实用程序传递环境变量?
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值的文件,并将其放置在构建环境中的安全服务器上。如果您确实偏执,则可以非对称地加密值。如果您需要版本控制/动态拉取,甚至可以将其保存在专用回购中,或者仅使用shell脚本将其复制/从适当的地方拉出。
  • 在部署脚本期间从git中提取
  • 在git pull之后,通过使用yaml库在纯python中读写来修改app.yaml

最简单的方法是使用持续集成服务器,例如HudsonBambooJenkins。只需添加一些插件,脚本步骤或工作流程即可完成我提到的所有上述项目。例如,您可以传入在Bamboo本身中配置的环境变量。

总之,在您只能访问的环境中,只需在构建过程中输入值即可。如果您尚未使构建自动化,则应该这样做。

另一个选项就是您所说的内容,将其放入数据库中。如果您不这样做的原因是操作太慢,则只需将值作为第二层缓存推送到内存缓存中,然后将值作为第一层缓存固定到实例即可。如果值可以更改并且您需要在不重新启动实例的情况下更新实例,则只需保留一个散列即可检查它们何时更改,或者在您进行某些更改后以某种方式触发它。应该的。


1
FWIW,此方法最紧密地遵循12因子应用指南(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文件中)。

一些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

3

@Jason F 基于使用Google数据存储的答案很接近,但是基于库docs上的示例用法,代码有些过时了。这是对我有用的代码片段:

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中解决此问题的。对于本地开发,我使用了“ 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 文件变量?
Gus

根本不需要这样做,因为GAE会自动将app.yaml文件中定义的所有变量添加到节点环境中。基本上,这与dotenv使用.env程序包中定义的变量相同。但是我想知道如何设置CD,因为您无法将带有env vars的app.yaml推送到VC​​S或管道中……
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

有一个名为gae_env的pypi软件包,可让您将Appengine环境变量保存在Cloud Datastore中。在后台,它还使用Memcache,因此其速度很快

用法:

import gae_env

API_KEY = gae_env.get('API_KEY')

如果数据存储中有该键的值,则将其返回。如果没有,__NOT_SET__将创建一个占位符记录并ValueNotSetError抛出一个。该异常将提醒您转到开发人员控制台并更新占位符记录。


与Martin的答案类似,这是如何更新数据存储区中键的值:

  1. 转到开发人员控制台中的“ 数据存储”部分

  2. 如果尚未选择项目,请在页面顶部选择它。

  3. 在“ 种类”下拉框中,选择GaeEnvSettings

  4. 引发异常的键将具有价值__NOT_SET__

您的设置,由“设置”类创建

点击编辑

输入实际值并保存


转到软件包的GitHub页面以获取有关用法/配置的更多信息

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.