Python 使用app.yaml在GAE中安全存储环境变量
我需要将API密钥和其他敏感信息存储在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
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