gae - google cloud python web app
Almacenamiento seguro de variables de entorno en GAE con app.yaml (7)
Extendiendo la respuesta de Martin
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
Necesito almacenar claves API y otra información sensible en app.yaml
como variables de entorno para la implementación en GAE. El problema con esto es que si app.yaml
a GitHub, esta información se vuelve pública (no es buena). No quiero almacenar la información en un almacén de datos, ya que no se ajusta al proyecto. Más bien, me gustaría cambiar los valores de un archivo que figura en .gitignore
en cada implementación de la aplicación.
Aquí está mi archivo 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}
¿Algunas ideas?
La mejor forma de hacerlo es almacenar las claves en un archivo client_secrets.json y excluir que se carguen en git al listarlo en su archivo .gitignore. Si tiene claves diferentes para diferentes entornos, puede usar app_identity api para determinar cuál es la id de la aplicación y cargarla adecuadamente.
Hay un ejemplo bastante completo aquí -> https://developers.google.com/api-client-library/python/guide/aaa_client_secrets .
Aquí hay un código de ejemplo:
# 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()
Mi enfoque es almacenar secretos de clientes solo dentro de la aplicación de App Engine. Los secretos del cliente no están en control de fuente ni en ninguna computadora local. Esto tiene la ventaja de que cualquier colaborador de App Engine puede implementar cambios de código sin tener que preocuparse por los secretos del cliente.
Almazo secretos de clientes directamente en Datastore y uso Memcache para mejorar la latencia y acceder a los secretos. Las entidades de Datastore solo necesitan crearse una vez y persistirán en futuras implementaciones. por supuesto, la consola de App Engine se puede usar para actualizar estas entidades en cualquier momento.
Hay dos opciones para realizar la creación de la entidad única:
- Use el shell interactivo App Engine Remote API para crear las entidades.
- Cree un controlador exclusivo de administrador que inicialice las entidades con valores ficticios. Invoque manualmente este controlador de administrador y luego use la consola de App Engine para actualizar las entidades con los secretos del cliente de producción.
Parece que puedes hacer algunos acercamientos. Tenemos un problema similar y hacemos lo siguiente (adaptado a su caso de uso):
- Cree un archivo que almacene cualquier valor dinámico de app.yaml y colóquelo en un servidor seguro en su entorno de compilación. Si eres realmente paranoico, puedes encriptar asimétricamente los valores. Incluso puede mantener esto en un repositorio privado si necesita control de versiones / extracción dinámica, o simplemente usar un guión de conchas para copiarlo / extraerlo del lugar apropiado.
- Extraiga de git durante la secuencia de comandos de implementación
- Después de la extracción de git, modifique la aplicación.yaml leyéndola y escribiéndola en python puro utilizando una biblioteca yaml
La forma más sencilla de hacerlo es utilizar un servidor de integración continua como Hudson , Bamboo o Jenkins . Simplemente agregue algún complemento, paso de guión o flujo de trabajo que haga todos los elementos anteriores que mencioné. Puede pasar variables de entorno configuradas en Bamboo, por ejemplo.
En resumen, solo inserte los valores durante su proceso de compilación en un entorno al que solo tiene acceso. Si todavía no estás automatizando tus compilaciones, deberías.
Otra opción es lo que dijiste, póngalo en la base de datos. Si la razón para no hacer eso es que las cosas son demasiado lentas, simplemente inserte los valores en Memcache como un caché de segunda capa, y fije los valores en las instancias como un caché de primera capa. Si los valores pueden cambiar y necesita actualizar las instancias sin reiniciarlos, solo mantenga un hash que puede verificar para saber cuándo lo cambian o lo activan de alguna manera cuando algo que usted hace cambia los valores. Eso debería ser.
Puede usar la opción de línea de comando -E de appcfg.py para configurar las variables de entorno cuando implementa su aplicación en GAE (appcfg.py update)
$ 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).
...
Si se trata de datos confidenciales, no debe almacenarlos en el código fuente, ya que se verificará en el control de la fuente. Las personas equivocadas (dentro o fuera de su organización) pueden encontrarlo allí. Además, su entorno de desarrollo probablemente utiliza diferentes valores de configuración de su entorno de producción. Si estos valores se almacenan en código, tendrá que ejecutar un código diferente en el desarrollo y la producción, lo cual es una práctica desordenada y mala.
En mis proyectos, puse datos de configuración en el almacén de datos utilizando esta clase:
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
Su aplicación haría esto para obtener un valor:
API_KEY = Settings.get(''API_KEY'')
Si hay un valor para esa clave en el almacén de datos, la obtendrá. Si no lo hay, se creará un registro de marcador de posición y se lanzará una excepción. La excepción le recordará que vaya a Developers Console y actualice el registro de marcador de posición.
Encuentro que esto quita la conjetura de establecer valores de configuración. Si no está seguro de los valores de configuración que debe establecer, ejecute el código y se lo indicará.
El código anterior utiliza la biblioteca ndb que usa Memcache y el almacén de datos bajo el capó, por lo que es rápido.
Actualizar:
jelder preguntó cómo encontrar los valores de Datastore en la consola de App Engine y configurarlos. Aquí es cómo:
Seleccione su proyecto en la parte superior de la página si aún no está seleccionado.
En el cuadro desplegable Tipo , seleccione Configuración .
Si ejecutó el código anterior, sus claves aparecerán. Todos ellos tendrán el valor NOT SET . Haga clic en cada uno y establezca su valor.
¡Espero que esto ayude!
Solo quería observar cómo resolví este problema en javascript / nodejs. Para el desarrollo local utilicé el paquete ''dotenv'' npm, que carga variables de entorno de un archivo .env a process.env. Cuando comencé a usar GAE, aprendí que las variables de entorno deben establecerse en un archivo ''app.yaml''. Bueno, no quería usar ''dotenv'' para el desarrollo local y ''app.yaml'' para GAE (y duplicar las variables de mi entorno entre los dos archivos), así que escribí un pequeño script que carga las variables de entorno de app.yaml en proceso .env, para el desarrollo local. Espero que esto ayude a alguien:
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]
})
}
})()
Ahora incluye este archivo lo antes posible en tu código, y listo:
require(''../yaml_env'')