J'ai besoin de stocker des clés d'API et d'autres informations sensibles dans app.yaml
en tant que variables d'environnement pour le déploiement sur GAE. Le problème avec ceci est que si je pousse app.yaml
vers GitHub, cette information devient publique (pas bonne). Je ne veux pas stocker les informations dans un magasin de données car cela ne convient pas au projet. J'aimerais plutôt échanger les valeurs d'un fichier répertorié dans .gitignore
à chaque déploiement de l'application.
Voici mon fichier 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}
Des idées?
S'il s'agit de données sensibles, vous ne devez pas les stocker dans le code source car elles seront vérifiées dans le contrôle de source. Les mauvaises personnes (à l'intérieur ou à l'extérieur de votre organisation) peuvent le trouver là-bas. En outre, votre environnement de développement utilise probablement différentes valeurs de configuration de votre environnement de production. Si ces valeurs sont stockées dans du code, vous devrez exécuter un code différent en développement et en production, ce qui est compliqué et peu pratique.
Dans mes projets, j'ai placé les données de configuration dans le magasin de données en utilisant cette classe:
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
Votre application ferait cela pour obtenir une valeur:
API_KEY = Settings.get('API_KEY')
S'il existe une valeur pour cette clé dans le magasin de données, vous l'obtiendrez. S'il n'y en a pas, un enregistrement fictif sera créé et une exception sera levée. L'exception vous rappellera d'aller à la console des développeurs et de mettre à jour l'enregistrement d'espace réservé.
Je trouve que cela élimine le choix de valeurs de configuration. Si vous ne savez pas quelles valeurs de configuration définir, lancez simplement le code et il vous le dira!
Le code ci-dessus utilise la bibliothèque ndb qui utilise memcache et le datastore sous le capot, donc c'est rapide.
Mettre à jour:
jelder a demandé comment trouver et définir les valeurs du magasin de données dans la console App Engine. Voici comment:
Sélectionnez votre projet en haut de la page s'il ne l'est pas déjà.
Dans la liste déroulante Kind, sélectionnez Paramètres.
Si vous avez exécuté le code ci-dessus, vos clés apparaîtront. Ils auront tous la valeur NOT SET. Cliquez sur chacun et définissez sa valeur.
J'espère que cela t'aides!
Mon approche consiste à stocker les secrets du client uniquement dans l'application App Engine elle-même. Les secrets du client ne sont ni dans le contrôle de source ni sur des ordinateurs locaux. Cela présente l'avantage que tout collaborateur App Engine peut déployer des modifications de code sans avoir à se soucier des secrets du client.
Je stocke les secrets des clients directement dans le magasin de données et utilise Memcache pour améliorer la latence d'accès aux secrets. Les entités du magasin de données ne doivent être créées qu'une seule fois et elles persisteront lors de futurs déploiements. Bien entendu, la console App Engine peut être utilisée pour mettre à jour ces entités à tout moment.
Il existe deux options pour effectuer la création d’entités uniques:
La meilleure façon de le faire est de stocker les clés dans un fichier client_secrets.json et d’empêcher que cela soit transféré à git en le répertoriant dans votre fichier .gitignore. Si vous avez différentes clés pour différents environnements, vous pouvez utiliser app_identity api pour déterminer l'identifiant de l'application et le charger correctement.
Il existe un exemple assez complet ici -> https://developers.google.com/api-client-library/python/guide/aaa_client_secrets .
Voici un exemple de code:
# 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()
Vous pouvez utiliser l'option de ligne de commande -E appcfg.py pour configurer les variables d'environnement lorsque vous déployez votre application sur GAE (mise à jour 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).
...
Cette solution est simple mais peut ne pas convenir à toutes les équipes.
Tout d’abord, placez les variables d’environnement dans env_variables.yaml , par exemple,
env_variables:
SECRET: 'my_secret'
Ensuite, incluez ce env_variables.yaml
dans le app.yaml
includes:
- env_variables.yaml
Enfin, ajoutez le env_variables.yaml
à .gitignore
, afin que les variables secrètes n'existent pas dans le référentiel.
Dans ce cas, le env_variables.yaml
doit être partagé entre les gestionnaires de déploiement.
On dirait que vous pouvez faire quelques approches. Nous avons un problème similaire et procédons comme suit (adapté à votre cas d'utilisation):
La méthode la plus simple consiste à utiliser un serveur d'intégration continue tel que Hudson , Bamboo ou Jenkins . Ajoutez simplement un plug-in, une action de script ou un workflow prenant en charge tous les éléments susmentionnés. Vous pouvez transmettre des variables d'environnement configurées dans Bamboo lui-même, par exemple.
En résumé, il suffit d'insérer les valeurs lors de votre processus de construction dans un environnement auquel vous n'avez accès que. Si vous n'automatisez pas déjà vos versions, vous devriez l'être.
Une autre option est ce que vous avez dit, mettez-le dans la base de données. Si vous ne le faites pas, c'est que les choses sont trop lentes, insérez simplement les valeurs dans memcache en tant que cache de la deuxième couche, puis épinglez les valeurs aux instances en tant que cache de la première couche. Si les valeurs peuvent changer et que vous devez mettre à jour les instances sans les redémarrer, conservez simplement un hachage que vous pouvez vérifier pour savoir quand elles changent ou le déclencher d'une manière ou d'une autre lorsque vous modifiez les valeurs. Cela devrait être ça.
Un package pypi appelé gae_env vous permet de sauvegarder des variables d'environnement de moteur d'application dans Cloud Datastore. Sous le capot, il utilise également Memcache pour sa rapidité
Usage:
import gae_env
API_KEY = gae_env.get('API_KEY')
S'il existe une valeur pour cette clé dans le magasin de données, elle sera renvoyée . S'il n'y en a pas, un espace réservé __NOT_SET__
sera créé et un ValueNotSetError
sera généré. L'exception vous rappellera d'aller à la Console de développeur et de mettre à jour l'enregistrement de substitution.
Semblable à la réponse de Martin, voici comment mettre à jour la valeur de la clé dans le magasin de données:
Allez à Section Datastore dans la console des développeurs.
Sélectionnez votre projet en haut de la page s'il ne l'est pas déjà.
Dans la liste déroulante Kind, sélectionnez GaeEnvSettings
.
Les clés pour lesquelles une exception a été déclenchée auront la valeur __NOT_SET__
.
Allez sur la page GitHub du paquet pour plus d'informations sur l'utilisation/la configuration
Prolonger la réponse 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
La plupart des réponses sont obsolètes. Utiliser Google Cloud Datastore est en fait un peu différent en ce moment. https://cloud.google.com/python/getting-started/using-cloud-datastore
Voici un exemple:
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')
Cela suppose que le nom de l'entité est "Twitter_APP_KEY", le type est "paramètres" et "valeur" est une propriété de l'entité Twitter_APP_KEY.
Vous devriez chiffrer les variables avec Google kms et les intégrer dans votre code source. ( 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
placez la valeur brouillée (chiffrée et codée en base64) dans votre variable d’environnement (dans le fichier yaml).
Du code pythonish pour vous aider à déchiffrer.
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
Je voulais juste noter comment j'ai résolu ce problème dans javascript/nodejs. Pour le développement local, j'ai utilisé le package 'dotenv' npm qui charge les variables d'environnement à partir d'un fichier .env dans process.env. Quand j'ai commencé à utiliser GAE, j'ai appris que les variables d'environnement devaient être définies dans un fichier 'app.yaml'. Eh bien, je ne voulais pas utiliser 'dotenv' pour le développement local et 'app.yaml' pour GAE (et dupliquer mes variables d'environnement entre les deux fichiers); .env, pour le développement local. J'espère que cela aide quelqu'un:
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]
})
}
})()
Maintenant, incluez ce fichier le plus tôt possible dans votre code, et vous avez terminé:
require('../yaml_env')