Archiviazione sicura delle variabili di ambiente in GAE con app.yaml


97

Ho bisogno di archiviare chiavi API e altre informazioni sensibili app.yamlcome variabili di ambiente per la distribuzione su GAE. Il problema con questo è che se app.yamleseguo il push su GitHub, questa informazione diventa pubblica (non va bene). Non voglio memorizzare le informazioni in un datastore in quanto non si adatta al progetto. Piuttosto, mi piacerebbe scambiare i valori da un file che è elencato in .gitignoreogni distribuzione dell'app.

Ecco il mio file 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}

Qualche idea?


72
Vorrei che GAE aggiungesse l'opzione per impostare le variabili env dell'istanza tramite la console degli sviluppatori (come ogni altro PaaS con cui ho familiarità).
Treno Spagna

4
Puoi usare datastore. Fare riferimento a questa risposta: stackoverflow.com/a/35254560/1027846
Mustafa İlhan

Espandendo il commento di mustilica sopra sull'utilizzo del datastore. Vedi la mia risposta di seguito per il codice che utilizzo nei miei progetti per farlo: stackoverflow.com/a/35261091#35261091 . In effetti, ti consente di modificare le variabili di ambiente dalla console degli sviluppatori e i valori dei segnaposto vengono creati automaticamente.
Martin Omander

Grazie Mustilica e Martin. Abbiamo effettivamente utilizzato l'approccio del datastore da un po 'e sono d'accordo che sia la migliore soluzione a questo problema. Più facile da fare con una configurazione CI / CD rispetto all'approccio ai file json, IMO.
Treno Spagna

1
2019 e GAE ancora non ha risolto il problema: /
Josh Noe

Risposte:


53

Se si tratta di dati sensibili, non è necessario archiviarli nel codice sorgente poiché verranno archiviati nel controllo del codice sorgente. Le persone sbagliate (all'interno o all'esterno della tua organizzazione) potrebbero trovarlo lì. Inoltre, l'ambiente di sviluppo probabilmente utilizza valori di configurazione diversi dall'ambiente di produzione. Se questi valori sono archiviati nel codice, sarà necessario eseguire codice diverso in fase di sviluppo e produzione, il che è complicato e non corretto.

Nei miei progetti, inserisco i dati di configurazione nel datastore usando questa 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

La tua applicazione lo farebbe per ottenere un valore:

API_KEY = Settings.get('API_KEY')

Se c'è un valore per quella chiave nel datastore, lo otterrai. In caso contrario, verrà creato un record segnaposto e verrà generata un'eccezione. L'eccezione ti ricorderà di andare alla Developers Console e aggiornare il record segnaposto.

Trovo che questo eviti di indovinare l'impostazione dei valori di configurazione. Se non sei sicuro di quali valori di configurazione impostare, esegui il codice e te lo dirà!

Il codice sopra usa la libreria ndb che usa memcache e il datastore sotto il cofano, quindi è veloce.


Aggiornare:

jelder ha chiesto come trovare i valori di Datastore nella console di App Engine e impostarli. Ecco come:

  1. Vai su https://console.cloud.google.com/datastore/

  2. Seleziona il tuo progetto nella parte superiore della pagina se non è già selezionato.

  3. Nella casella a discesa Tipo , seleziona Impostazioni .

  4. Se hai eseguito il codice sopra, verranno visualizzate le tue chiavi. Avranno tutti il ​​valore NON IMPOSTATO . Fare clic su ciascuno e impostarne il valore.

Spero che questo ti aiuti!

Le tue impostazioni, create dalla classe Impostazioni

Clicca per modificare

Inserisci il valore reale e salva


2
Di tutte le risposte fornite, questa sembra la più vicina a come Heroku gestisce le cose. Essendo piuttosto nuovo a GAE, non capisco bene dove trovare il record segnaposto nella Developers Console. Puoi spiegare, o per punti bonus, pubblicare screenshot?
jelder

2
dam ~ ... con tutto il rispetto per gcloud, sembra piuttosto brutto dover utilizzare un altro servizio per questa specifica esigenza. Oltre a ciò, Google fornisce un approccio "100% -herokuish" per le variabili env all'interno delle funzioni Firebase, ma non per le funzioni gcloud (almeno non documentato ... se non sbaglio)
Ben

1
Ecco una sintesi basata sul tuo approccio che aggiunge l'unicità e il fallback delle variabili ambientali: gist.github.com/SpainTrain/6bf5896e6046a5d9e7e765d0defc8aa8
Spain Train

3
Le funzioni @Ben Non-Firebase supportano le variabili env (ora, almeno).
NReilingh

3
@obl: un'app App Engine viene automaticamente autenticata nel proprio datastore, non sono necessari dettagli di autenticazione. È abbastanza pulito :-)
Martin Omander

46

Questa soluzione è semplice ma potrebbe non essere adatta a tutte le diverse squadre.

Per prima cosa, inserisci le variabili di ambiente in env_variables.yaml , ad es.

env_variables:
  SECRET: 'my_secret'

Quindi, includilo env_variables.yamlnel fileapp.yaml

includes:
  - env_variables.yaml

Infine, aggiungi env_variables.yamla .gitignore, in modo che le variabili segrete non esistano nel repository.

In questo caso, le env_variables.yamlesigenze devono essere condivise tra i gestori di distribuzione.


1
Solo per aggiungere ciò che potrebbe non essere ovvio per alcuni, le tue variabili d'ambiente verrebbero quindi trovate process.env.MY_SECRET_KEYe se hai bisogno di queste variabili d'ambiente nel tuo ambiente di sviluppo locale puoi usare il dotenvpacchetto del nodo
Dave Kiss

2
Come sarebbe env_variables.yamlarrivare a tutte le istanze è un pezzo mancante del puzzle.
Christopher Oezbek

Inoltre: come usarlo localmente?
Christopher Oezbek

@ChristopherOezbek 1. Come distribuire? Basta usarlo gcloud app deploycome faresti normalmente per eseguire il deployment su Google Cloud. 2. Come impostare localmente le variabili d'ambiente segrete? Ci sono molti modi. Puoi semplicemente usarlo exportnel prompt dei comandi o utilizzare qualsiasi strumento come suggerito da @DaveKiss.
Shih-Wen Su

Questa è la soluzione più semplice. È possibile accedere ai segreti nell'applicazione tramite os.environ.get('SECRET').
Quinn Comendant

19

Il mio approccio consiste nell'archiviare i segreti del client solo all'interno dell'app App Engine stessa. I segreti del client non si trovano né nel controllo del codice sorgente né in alcun computer locale. Questo ha il vantaggio che qualsiasi collaboratore di App Engine può implementare modifiche al codice senza doversi preoccupare dei segreti del client.

Memorizzo i segreti dei client direttamente in Datastore e utilizzo Memcache per una migliore latenza nell'accesso ai segreti. Le entità Datastore devono essere create solo una volta e persisteranno nelle distribuzioni future. ovviamente la console di App Engine può essere utilizzata per aggiornare queste entità in qualsiasi momento.

Sono disponibili due opzioni per eseguire la creazione di entità una tantum:

  • Utilizza la shell interattiva dell'API remota di App Engine per creare le entità.
  • Crea un gestore solo amministratore che inizializzerà le entità con valori fittizi. Richiama manualmente questo gestore amministratore, quindi utilizza la console di App Engine per aggiornare le entità con i segreti del client di produzione.

7
Per niente complicato. Grazie app engine.
courtimas

16

Il modo migliore per farlo è memorizzare le chiavi in ​​un file client_secrets.json ed escluderlo dal caricamento su git elencandolo nel file .gitignore. Se disponi di chiavi diverse per ambienti diversi, puoi utilizzare app_identity api per determinare qual è l'ID app e caricarlo in modo appropriato.

C'è un esempio abbastanza completo qui -> https://developers.google.com/api-client-library/python/guide/aaa_client_secrets .

Ecco un po 'di codice di esempio:

# 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
Sicuramente nella giusta direzione, ma questo non risolve il problema di scambiare i valori in app.yamlfase di distribuzione dell'app. Qualche idea lì?
Ben

1
Quindi avere un file client_secrets diverso per ogni ambiente. Ad esempio client_secrets_live.json, client_secrets_dev.json, client_secrets_pilot.json ecc., Quindi utilizzare la logica Python per determinare su quale server ci si trova e caricare il file json appropriato. Il metodo app_identity.get_application_id () può essere utile per rilevare automaticamente il server su cui ti trovi. È questo il genere di cose che intendi?
Gwyn Howell

@ BenGrunfeld vedi la mia risposta. La mia soluzione fa esattamente questo. Non vedo come questa risposta risolva la domanda. Presumo che l'obiettivo sia mantenere la configurazione segreta fuori da git e utilizzare git come parte della distribuzione. Qui, questo file deve ancora essere da qualche parte e inserito nel processo di distribuzione. Questo può essere qualcosa che fai nella tua app, ma dovresti semplicemente usare le tecniche che ho evidenziato, magari archiviarle in un altro file se vuoi usare questo contro app.yaml. Se capisco la domanda, è qualcosa di simile alla spedizione di un'app open source con l'effettivo segreto o prodotto del client del produttore della libreria. chiave.
therewillbesnacks

1
Mi ci è voluto un po 'per capirlo, ma penso che questo sia l'approccio corretto. Non stai mescolando le impostazioni dell'applicazione ( app.yaml) con chiavi segrete e informazioni riservate, e quello che mi piace davvero è che stai utilizzando il flusso di lavoro di Google per eseguire l'attività. Grazie @GwynHowell. =)
Ben

1
Un approccio simile consiste nel posizionare il file JSON in una posizione nota nel bucket GCS predefinito dell'app ( cloud.google.com/appengine/docs/standard/python/… ).
Treno Spagna

16

Questo non esisteva quando hai postato, ma per chiunque altro si imbatti qui, Google offre ora un servizio chiamato Secret Manager .

È un semplice servizio REST (con SDK che lo avvolgono, ovviamente) per archiviare i tuoi segreti in un luogo sicuro sulla piattaforma cloud di Google. Questo è un approccio migliore rispetto a Data Store, che richiede passaggi aggiuntivi per vedere i segreti archiviati e dispone di un modello di autorizzazione più granulare: puoi proteggere i singoli segreti in modo diverso per diversi aspetti del tuo progetto, se necessario.

Offre il controllo delle versioni, in modo da poter gestire le modifiche alle password con relativa facilità, nonché un robusto livello di gestione e query che consente di scoprire e creare segreti in fase di esecuzione, se necessario.

Python SDK

Utilizzo di esempio:

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
Questa dovrebbe essere la nuova risposta corretta. Secret Manager è ancora in Beta, ma questa è la via da seguire quando si lavora con le variabili d'ambiente.
King Leon

@ KingLeon, userebbe questo significherebbe dover rifattorizzare un gruppo di messaggi os.getenv('ENV_VAR')?
Alejandro

Metto un codice simile a quello sopra in una funzione, quindi uso qualcosa di simile SECRET_KEY = env('SECRET_KEY', default=access_secret_version(GOOGLE_CLOUD_PROJECT_ID, 'SECRET_KEY', 1)). Impostazione predefinita per utilizzare ilaccess_secret_version
King Leon

Inoltre, sto usando django -viron. github.com/joke2k/django-environ
King Leon

15

Questa soluzione si basa sul deprecato appcfg.py

Puoi utilizzare l'opzione della riga di comando -E di appcfg.py per configurare le variabili di ambiente quando distribuisci la tua app a GAE (aggiornamento 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).
...

Puoi interrogare quelle variabili d'ambiente da qualche parte dopo la distribuzione? (Spero di no.)
Ztyx

C'è un modo per passare le variabili d'ambiente in questo modo usando l' gcloudutilità?
Trevor

6

La maggior parte delle risposte sono obsolete. L'uso del datastore cloud di Google è in realtà leggermente diverso in questo momento. https://cloud.google.com/python/getting-started/using-cloud-datastore

Ecco un esempio:

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')

Ciò presuppone che il nome dell'entità sia "TWITTER_APP_KEY", il tipo è "impostazioni" e "valore" è una proprietà dell'entità TWITTER_APP_KEY.


3

Sembra che tu possa fare alcuni approcci. Abbiamo un problema simile e facciamo quanto segue (adattato al tuo caso d'uso):

  • Crea un file che memorizzi tutti i valori dinamici di app.yaml e posizionalo su un server sicuro nel tuo ambiente di compilazione. Se sei veramente paranoico, puoi crittografare i valori in modo asimmetrico. Puoi anche tenerlo in un repository privato se hai bisogno del controllo della versione / estrazione dinamica, o semplicemente usare uno script della shell per copiarlo / estrarlo dalla posizione appropriata.
  • Esegui il pull da git durante lo script di distribuzione
  • Dopo il pull git, modifica il file app.yaml leggendolo e scrivendolo in puro python usando una libreria yaml

Il modo più semplice per farlo è utilizzare un server di integrazione continua come Hudson , Bamboo o Jenkins . Aggiungi semplicemente un plug-in, un'istruzione di script o un flusso di lavoro che esegue tutti gli elementi sopra menzionati. Ad esempio, è possibile passare le variabili di ambiente configurate in Bamboo stesso.

In sintesi, è sufficiente inserire i valori durante il processo di creazione in un ambiente a cui solo si ha accesso. Se non stai già automatizzando le tue build, dovresti esserlo.

Un'altra opzione è ciò che hai detto, inseriscilo nel database. Se il motivo per cui non lo fai è che le cose sono troppo lente, inserisci semplicemente i valori in memcache come cache di secondo livello e aggiungi i valori alle istanze come cache di primo livello. Se i valori possono cambiare e devi aggiornare le istanze senza riavviarle, tieni solo un hash che puoi controllare per sapere quando cambiano o lo attivano in qualche modo quando qualcosa che fai cambia i valori. Dovrebbe essere così.


1
FWIW, questo approccio segue più da vicino il fattore di configurazione nelle linee guida 12 Factor App ( 12factor.net )
Spain Train

3

Dovresti crittografare le variabili con google kms e incorporarle nel tuo codice sorgente. ( 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

inserire il valore codificato (crittografato e codificato in base64) nella variabile d'ambiente (nel file yaml).

Un po 'di codice python per iniziare a decifrare.

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

La risposta di @Jason F basata sull'utilizzo di Google Datastore è vicina, ma il codice è un po 'obsoleto in base all'utilizzo di esempio sui documenti della libreria . Ecco lo snippet che ha funzionato per me:

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'])

In parte ispirato da questo post di Medium


2

Volevo solo notare come ho risolto questo problema in javascript / nodejs. Per lo sviluppo locale ho utilizzato il pacchetto npm "dotenv" che carica le variabili di ambiente da un file .env in process.env. Quando ho iniziato a utilizzare GAE, ho imparato che le variabili di ambiente devono essere impostate in un file "app.yaml". Bene, non volevo usare 'dotenv' per lo sviluppo locale e 'app.yaml' per GAE (e duplicare le mie variabili d'ambiente tra i due file), quindi ho scritto un piccolo script che carica le variabili d'ambiente app.yaml nel processo .env, per lo sviluppo locale. Spero che questo aiuti qualcuno:

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]
        })
    }
})()

Ora includi questo file il prima possibile nel tuo codice e il gioco è fatto:

require('../yaml_env')

È ancora così? Perché sto usando un .envfile con le variabili segrete. Non li sto duplicando nel mio app.yamlfile e il mio codice distribuito funziona ancora. .envTuttavia, sono preoccupato per ciò che accade al file nel cloud. Viene crittografato o qualcosa del genere? Come posso assicurarmi che nessuno acceda alle .envvariabili del file gcloud una volta che è stato distribuito?
Gus

Questo non è affatto necessario perché GAE aggiunge automaticamente tutte le variabili definite nel file app.yaml all'ambiente del nodo. Fondamentalmente questo è lo stesso di dotenv con le variabili definite nel pacchetto .env. Ma mi chiedo come devi configurare il CD poiché non puoi inviare app.yaml con env vars a un VCS o una pipeline ...
Jornve

1

Estendendo la risposta di 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

1

È disponibile un pacchetto pypi chiamato gae_env che consente di salvare le variabili di ambiente di appengine in Cloud Datastore. Sotto il cofano, utilizza anche Memcache, quindi è veloce

Utilizzo:

import gae_env

API_KEY = gae_env.get('API_KEY')

Se è presente un valore per quella chiave nel datastore, verrà restituito. In caso contrario, __NOT_SET__verrà creato un record segnaposto e verrà generato un ValueNotSetError. L'eccezione ti ricorderà di andare alla Developers Console e aggiornare il record segnaposto.


Simile alla risposta di Martin, ecco come aggiornare il valore per la chiave in Datastore:

  1. Vai alla sezione Datastore nella console degli sviluppatori

  2. Seleziona il tuo progetto nella parte superiore della pagina se non è già selezionato.

  3. Nella casella a discesa Tipo , seleziona GaeEnvSettings.

  4. Le chiavi per le quali è stata sollevata un'eccezione avranno valore __NOT_SET__.

Le tue impostazioni, create dalla classe Impostazioni

Clicca per modificare

Inserisci il valore reale e salva


Vai alla pagina GitHub del pacchetto per maggiori informazioni sull'utilizzo / configurazione

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.