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

Python 使用app.yaml在GAE中安全存储环境变量,python,google-app-engine,python-2.7,environment-variables,Python,Google App Engine,Python 2.7,Environment Variables,我需要将API密钥和其他敏感信息存储在app.yaml中,作为在GAE上部署的环境变量。问题是,如果我将app.yaml推送到GitHub,这些信息就会公开(不好)。我不想将信息存储在数据存储中,因为它不适合该项目。相反,我希望在应用程序的每个部署中交换.gitignore中列出的文件中的值 这是我的app.yaml文件: application: myapp version: 3 runtime: python27 api_version: 1 threadsafe: true libra

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

有什么想法吗?

最好的方法是将密钥存储在client_secrets.json文件中,并通过在.gitignore文件中列出它来排除上传到git的密钥。如果您有不同环境的不同密钥,则可以使用app_identity api确定应用程序id,并适当加载

这里有一个相当全面的例子->

下面是一些示例代码:

# 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()

听起来你可以做一些方法。我们有一个类似的问题,并执行以下操作(适用于您的用例):

  • 创建一个存储任何动态app.yaml值的文件,并将其放置在生成环境中的安全服务器上。如果你真的是偏执狂,你可以不对称地加密这些值。如果需要版本控制/动态拉取,甚至可以将其保存在私有回购中,或者仅使用shells脚本将其复制/从适当的位置拉取
  • 在部署脚本期间从git中提取
  • 在git拉取之后,通过使用yaml库在纯python中读写app.yaml来修改它
最简单的方法是使用连续集成服务器,例如,或。只需添加一些插件、脚本步骤或工作流即可完成我提到的所有上述项目。例如,您可以传入在竹子本身中配置的环境变量

总之,在构建过程中,只需在您只能访问的环境中推入值。如果您还没有实现构建的自动化,那么您应该这样做


另一个选择是你说的,把它放到数据库里。如果不这样做的原因是速度太慢,只需将值作为第二层缓存推送到memcache中,并将值作为第一层缓存固定到实例中。如果值可以更改,并且您需要在不重新启动实例的情况下更新实例,那么只需保留一个哈希值,您可以检查它以了解它们何时更改,或者在您执行的操作更改了值时以某种方式触发它。应该是这样。

我的方法是只在应用程序引擎应用程序本身中存储客户端机密。客户端机密既不在源代码管理中,也不在任何本地计算机上。这样做的好处是,任何应用引擎合作者都可以部署代码更改,而不必担心客户端机密

我将客户机机密直接存储在数据存储中,并使用Memcache提高访问机密的延迟。数据存储实体只需创建一次,并将在未来的部署中持久化。当然,应用程序引擎控制台可用于随时更新这些实体

执行一次性实体创建有两个选项:

  • 使用App Engine交互式shell创建实体
  • 创建一个仅管理的处理程序,该处理程序将使用伪值初始化实体。手动调用此管理处理程序,然后使用应用程序引擎控制台使用生产客户端机密更新实体

如果是敏感数据,则不应将其存储在源代码中,因为它将被签入源代码管理。错误的人(组织内部或外部)可能会在那里找到它。此外,开发环境可能使用与生产环境不同的配置值。如果这些值存储在代码中,您将不得不在开发和生产中运行不同的代码,这是一种混乱和糟糕的做法

在我的项目中,我使用以下类将配置数据放入数据存储:

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和引擎盖下的数据存储,因此速度很快


更新:

1. Create a new Google Cloud Project (or select an existing project).

2. Initialize your App Engine app with your project.

[Create a Google Cloud service account][sa] or select an existing one.

3. Add the the following Cloud IAM roles to your service account:

    App Engine Admin - allows for the creation of new App Engine apps

    Service Account User - required to deploy to App Engine as service account

    Storage Admin - allows upload of source code

    Cloud Build Editor - allows building of source code

[Download a JSON service account key][create-key] for the service account.

4. Add the following [secrets to your repository's secrets][gh-secret]:

    GCP_PROJECT: Google Cloud project ID

    GCP_SA_KEY: the downloaded service account key
jelder询问如何在App Engine控制台中找到数据存储值并进行设置。以下是如何:

  • 如果尚未选择项目,请在页面顶部选择该项目

  • 在种类下拉框中,选择设置

  • 如果您运行上面的代码,将显示您的密钥。它们都将具有未设置的值。单击每一个并设置其值

  • 希望这有帮助


    此解决方案依赖于不推荐使用的appcfg.py

    将应用程序部署到GAE时,可以使用appcfg.py的-E命令行选项设置环境变量(appcfg.py更新)


    我只是想说明我是如何用javascript/nodejs解决这个问题的。对于本地开发,我使用了“dotenv”npm包,它将环境变量从.env文件加载到process.env中。当我开始使用GAE时,我了解到需要在“app.yaml”文件中设置环境变量。我不想在本地开发中使用'dotenv',在GAE中使用'app.yaml'(并在两个文件之间复制我的环境变量),所以我编写了一个小脚本,将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's already set
                process.env[key] = process.env[key] || doc.env_variables[key]
            })
        }
    })()
    
    现在,请在代码中尽早包含此文件,您就完成了:

    require('../yaml_env')
    

    扩展马丁的答案

    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
    
    import gae_env
    
    API_KEY = gae_env.get('API_KEY')
    
    env_variables:
      SECRET: 'my_secret'
    
    includes:
      - env_variables.yaml
    
    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')
    
    echo -n the-twitter-app-key | gcloud kms encrypt \
    > --project my-project \
    > --location us-central1 \
    > --keyring THEKEYRING \
    > --key THECRYPTOKEY \
    > --plaintext-file - \
    > --ciphertext-file - \
    > | base64
    
    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
    
    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'])
    
    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_version_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
    
    1. Create a new Google Cloud Project (or select an existing project).
    
    2. Initialize your App Engine app with your project.
    
    [Create a Google Cloud service account][sa] or select an existing one.
    
    3. Add the the following Cloud IAM roles to your service account:
    
        App Engine Admin - allows for the creation of new App Engine apps
    
        Service Account User - required to deploy to App Engine as service account
    
        Storage Admin - allows upload of source code
    
        Cloud Build Editor - allows building of source code
    
    [Download a JSON service account key][create-key] for the service account.
    
    4. Add the following [secrets to your repository's secrets][gh-secret]:
    
        GCP_PROJECT: Google Cloud project ID
    
        GCP_SA_KEY: the downloaded service account key
    
    runtime: nodejs14
    env: standard
    env_variables:
      SESSION_SECRET: $SESSION_SECRET
    beta_settings:
      cloud_sql_instances: SQL_INSTANCE
    
    name: Build and Deploy to GKE
    
    on: push
    
    env:
      PROJECT_ID: ${{ secrets.GKE_PROJECT }}
      DATABASE_URL: ${{ secrets.DATABASE_URL}}
    jobs:
      setup-build-publish-deploy:
        name: Setup, Build, Publish, and Deploy
        runs-on: ubuntu-latest
    
    steps:
     - uses: actions/checkout@v2
     - uses: actions/setup-node@v2
       with:
        node-version: '12'
     - run: npm install
     - uses: actions/checkout@v1
     - uses: ikuanyshbekov/app-yaml-env-compiler@v1.0
       env:
        SESSION_SECRET: ${{ secrets.SESSION_SECRET }}  
     - shell: bash
       run: |
            sed -i 's/SQL_INSTANCE/'${{secrets.DATABASE_URL}}'/g' app.yaml
     - uses: actions-hub/gcloud@master
       env:
        PROJECT_ID: ${{ secrets.GKE_PROJECT }}
        APPLICATION_CREDENTIALS: ${{ secrets.GCLOUD_AUTH }}
        CLOUDSDK_CORE_DISABLE_PROMPTS: 1
       with:
        args: app deploy app.yaml