it-swarm.com.de

Sichere Speicherung von Umgebungsvariablen in GAE mit app.yaml

Ich muss API-Schlüssel und andere vertrauliche Informationen in app.yaml als Umgebungsvariablen für die Bereitstellung auf GAE speichern. Das Problem dabei ist, dass wenn ich app.yaml zu GitHub drücke, diese Informationen öffentlich (nicht gut) werden. Ich möchte die Informationen nicht in einem Datastore speichern, da sie nicht zum Projekt passen. Vielmehr möchte ich die Werte aus einer Datei austauschen, die bei jeder Bereitstellung der App in .gitignore aufgeführt ist.

Hier ist meine app.yaml-Datei:

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}

Irgendwelche Ideen?

53
Ben

Wenn es sich um vertrauliche Daten handelt, sollten Sie sie nicht im Quellcode speichern, da sie in der Quellcodeverwaltung überprüft werden. Die falschen Personen (innerhalb oder außerhalb Ihrer Organisation) können es dort finden. Außerdem verwendet Ihre Entwicklungsumgebung wahrscheinlich andere Konfigurationswerte als Ihre Produktionsumgebung. Wenn diese Werte im Code gespeichert werden, müssen Sie in Entwicklung und Produktion einen anderen Code ausführen, was unordentlich und schlechte Praxis ist.

In meinen Projekten füge ich mit dieser Klasse Konfigurationsdaten in den Datastore ein:

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

Ihre Anwendung würde dies tun, um einen Wert zu erhalten:

API_KEY = Settings.get('API_KEY')

Wenn für diesen Schlüssel ein Wert im Datastore vorhanden ist, erhalten Sie ihn. Ist dies nicht der Fall, wird ein Platzhalterdatensatz erstellt und eine Ausnahme ausgelöst. Die Ausnahme erinnert Sie daran, zur Developers Console zu gehen und den Platzhalterdatensatz zu aktualisieren.

Ich finde, das nimmt das Schätzen von Config-Werten ein. Wenn Sie sich nicht sicher sind, welche Konfigurationswerte eingestellt werden sollen, führen Sie einfach den Code aus, und es wird Ihnen sagen!

Der obige Code verwendet die ndb-Bibliothek, die Memcache und den Datastore unter der Haube verwendet, damit es schnell geht.


Update:

jelder fragte, wie die Datastore-Werte in der App Engine-Konsole ermittelt und festgelegt werden können. Hier ist, wie:

  1. Gehen Sie zu https://console.cloud.google.com/datastore/

  2. Wählen Sie Ihr Projekt oben auf der Seite aus, falls es noch nicht ausgewählt ist.

  3. Wählen Sie in der Dropdown-Box KindEinstellungen aus.

  4. Wenn Sie den Code oben ausgeführt haben, werden Ihre Schlüssel angezeigt. Sie haben alle den Wert NOT SET. Klicken Sie auf jeden und legen Sie seinen Wert fest.

Hoffe das hilft!

 Your settings, created by the Settings class

 Click to edit

 Enter the real value and save

32
Martin Omander

Mein Ansatz ist, Client-Geheimnisse only in der App Engine-App selbst zu speichern. Die Client-Geheimnisse befinden sich weder in der Quellcodeverwaltung noch auf lokalen Computern. Dies hat den Vorteil, dass any App Engine Collaborator Codeänderungen bereitstellen kann, ohne sich um die Clientgeheimnisse kümmern zu müssen.

Ich speichere Client-Geheimnisse direkt im Datastore und verwende Memcache, um die Latenz beim Zugriff auf die Geheimnisse zu verbessern. Die Datastore-Entitäten müssen nur einmal erstellt werden und bleiben auch in zukünftigen Bereitstellungen erhalten. Natürlich kann die App Engine-Konsole jederzeit dazu verwendet werden, diese Entitäten zu aktualisieren.

Es gibt zwei Optionen, um die einmalige Entitätserstellung durchzuführen:

  • Verwenden Sie die App Engine Remote API Interactive Shell, um die Entitäten zu erstellen.
  • Erstellen Sie einen Admin-Handler, der die Entitäten mit Dummy-Werten initialisiert. Rufen Sie diesen Administrator-Handler manuell auf, und aktualisieren Sie dann die Entitäten mit den App-Client-Geheimnissen über die App Engine-Konsole.
18
Bernd Verst

Am besten speichern Sie die Schlüssel in einer client_secrets.json-Datei und schließen diese aus, indem Sie sie in Ihrer .gitignore-Datei auf git hochladen. Wenn Sie unterschiedliche Schlüssel für verschiedene Umgebungen haben, können Sie mit app_identity api die App-ID ermitteln und entsprechend laden.

Es gibt hier ein recht umfassendes Beispiel -> https://developers.google.com/api-client-library/python/guide/aaa_client_secrets .

Hier ist ein Beispielcode:

# 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()
16
Gwyn Howell

Sie können die Befehlszeilenoption -E von appcfg.py verwenden, um die Umgebungsvariablen festzulegen, wenn Sie Ihre App für GAE bereitstellen (Update 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).
...
15
jla

Diese Lösung ist einfach, kann jedoch nicht für alle Teams geeignet sein. 

Setzen Sie zuerst die Umgebungsvariablen in eine env_variables.yaml , z.

env_variables:
  SECRET: 'my_secret'

Fügen Sie dann diesen env_variables.yaml in den app.yaml ein.

includes:
  - env_variables.yaml

Fügen Sie schließlich env_variables.yaml zu .gitignore hinzu, damit die geheimen Variablen nicht im Repository vorhanden sind.

In diesem Fall muss der env_variables.yaml von den Deployment Managern gemeinsam genutzt werden.

5
Shih-Wen Su

Es klingt, als könnten Sie einige Ansätze machen. Wir haben ein ähnliches Problem und tun Folgendes (angepasst an Ihren Anwendungsfall):

  • Erstellen Sie eine Datei, die alle dynamischen Werte von app.yaml speichert, und platzieren Sie sie auf einem sicheren Server in Ihrer Build-Umgebung. Wenn Sie wirklich paranoid sind, können Sie die Werte asymmetrisch verschlüsseln. Sie können dies sogar in einem privaten Repo behalten, wenn Sie Versionskontrolle/dynamisches Ziehen benötigen, oder verwenden Sie einfach ein Shells-Skript, um es zu kopieren/von der entsprechenden Stelle zu ziehen.
  • Ziehen Sie während des Implementierungsskripts aus git
  • Ändern Sie nach dem Git-Pull die app.yaml, indem Sie sie mit einer yaml-Bibliothek in reinem Python lesen und schreiben

Der einfachste Weg, dies zu tun, ist die Verwendung eines kontinuierlichen Integrationsservers wie Hudson , Bamboo oder Jenkins . Fügen Sie einfach ein Plug-In, einen Skriptschritt oder einen Workflow hinzu, der alle oben genannten Elemente ausführt. Sie können beispielsweise Umgebungsvariablen übergeben, die in Bamboo selbst konfiguriert sind.

Zusammenfassend können Sie die Werte während des Erstellungsprozesses in einer Umgebung eingeben, auf die Sie nur Zugriff haben. Wenn Sie Ihre Builds noch nicht automatisieren, sollten Sie dies tun.

Eine weitere Option ist das, was Sie gesagt haben, legen Sie es in die Datenbank. Wenn Sie dies nicht tun, weil die Dinge zu langsam sind, legen Sie die Werte einfach als 2. Layer-Cache in den Memcache und verbinden Sie die Werte mit den Instanzen als First-Layer-Cache. Wenn sich die Werte ändern können und Sie die Instanzen aktualisieren müssen, ohne sie neu zu starten, behalten Sie einfach einen Hash, den Sie überprüfen können, wenn sie geändert werden, oder lösen Sie ihn aus, wenn etwas die Werte ändert. Das sollte es sein.

2

Es gibt ein pypi-Paket mit dem Namen gae_env , mit dem Sie Umgebungsvariablen im Cloud-Datastore speichern können. Unter der Haube wird Memcache verwendet, damit es schnell geht

Verwendungszweck:

import gae_env

API_KEY = gae_env.get('API_KEY')

Wenn für diesen Schlüssel ein Wert im Datastore vorhanden ist, wird er zurückgegeben. Wenn dies nicht der Fall ist, wird ein Platzhalterdatensatz __NOT_SET__ erstellt und eine ValueNotSetError ausgelöst. Die Ausnahme erinnert Sie daran, dass Sie zur Developers Console gehen und den Platzhalterdatensatz aktualisieren.


Ähnlich wie bei Martins Antwort wird hier der Wert für den Schlüssel in Datastore aktualisiert:

  1. Gehen Sie in der Entwicklerkonsole zu Datastore Section

  2. Wählen Sie Ihr Projekt oben auf der Seite aus, falls es noch nicht ausgewählt ist.

  3. Wählen Sie in der Dropdown-Box KindGaeEnvSettings aus.

  4. Schlüssel, für die eine Ausnahme ausgelöst wurde, haben den Wert __NOT_SET__.

 Your settings, created by the Settings class

 Click to edit

 Enter the real value and save


Gehen Sie zur GitHub-Seite des Pakets , um weitere Informationen zur Verwendung/Konfiguration zu erhalten

1
Prince Odame

Martin's Antwort erweitern

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
JSBach

Die meisten Antworten sind veraltet. Die Verwendung des Google Cloud-Datenspeichers ist derzeit ein bisschen anders. https://cloud.google.com/python/getting-started/using-cloud-datastore

Hier ist ein Beispiel:

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

Dies setzt voraus, dass der Entitätsname "Twitter_APP_KEY" ist, die Art "Einstellungen" ist und "Wert" eine Eigenschaft der Entität "Twitter_APP_KEY" ist.

0
Jason F

Sie sollten die Variablen mit Google kms verschlüsseln und in Ihren Quellcode einbetten. ( 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

fügen Sie den verschlüsselten Wert (verschlüsselt und base64-verschlüsselt) in Ihre Umgebungsvariable ein (in der yaml-Datei).

Python-Code, mit dem Sie mit dem Entschlüsseln beginnen können.

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
0
Anders Elton

Ich wollte nur wissen, wie ich dieses Problem in Javascript/nodejs gelöst habe. Für die lokale Entwicklung habe ich das npm-Paket 'dotenv' verwendet, das Umgebungsvariablen aus einer .env-Datei in process.env lädt. Als ich mit GAE angefangen habe, habe ich gelernt, dass Umgebungsvariablen in einer 'app.yaml'-Datei gesetzt werden müssen. Nun, ich wollte nicht 'dotenv' für lokale Entwicklung und 'app.yaml' für GAE verwenden (und meine Umgebungsvariablen zwischen den beiden Dateien kopieren), also habe ich ein kleines Skript geschrieben, das app.yaml-Umgebungsvariablen in den Prozess lädt .env, für lokale Entwicklung. Hoffe das hilft jemandem:

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

Fügen Sie diese Datei jetzt so früh wie möglich in Ihren Code ein, und Sie sind fertig:

require('../yaml_env')
0
gbruins