Program Tip

app.yaml을 사용하여 GAE에 환경 변수를 안전하게 저장

programtip 2020. 10. 21. 21:23
반응형

app.yaml을 사용하여 GAE에 환경 변수를 안전하게 저장


app.yamlGAE에 배포 하기 위해 API 키 및 기타 민감한 정보 를 환경 변수 로 저장해야합니다 . 문제는 내가 app.yamlGitHub에 푸시 하면이 정보가 공개된다는 것입니다 (좋지 않음). 프로젝트에 적합하지 않기 때문에 데이터 저장소에 정보를 저장하고 싶지 않습니다. 오히려 .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}

어떤 아이디어?


민감한 데이터 인 경우 소스 제어에 체크인되므로 소스 코드에 저장해서는 안됩니다. 조직 내부 또는 외부에서 잘못된 사람들이 찾을 수 있습니다. 또한 개발 환경은 프로덕션 환경과 다른 구성 값을 사용합니다. 이러한 값이 코드에 저장되면 개발 및 프로덕션에서 다른 코드를 실행해야하는데 이는 지저분하고 나쁜 습관입니다.

내 프로젝트에서 다음 클래스를 사용하여 데이터 저장소에 구성 데이터를 넣습니다.

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

데이터 저장소에 해당 키에 대한 값이 있으면 가져옵니다. 없는 경우 자리 표시 자 레코드가 생성되고 예외가 발생합니다. 예외는 Developers Console로 이동하여 자리 표시 자 레코드를 업데이트하도록 알려줍니다.

구성 값 설정에서 추측이 필요하다는 것을 알았습니다. 설정할 구성 값이 확실하지 않은 경우 코드를 실행하면 알려줍니다!

위 코드는 내부적으로 Memcache와 데이터 저장소를 사용하는 ndb 라이브러리를 사용하므로 빠릅니다.


최신 정보:

jelder 는 App Engine 콘솔에서 Datastore 값을 찾고 설정하는 방법을 물었습니다. 방법은 다음과 같습니다.

  1. https://console.cloud.google.com/datastore/로 이동합니다.

  2. 아직 선택하지 않은 경우 페이지 상단에서 프로젝트를 선택합니다.

  3. 에서 종류 드롭 다운 상자, 선택 설정 .

  4. 위의 코드를 실행하면 키가 표시됩니다. 모두 NOT SET 값을 갖습니다 . 각각을 클릭하고 값을 설정하십시오.

도움이 되었기를 바랍니다!

Settings 클래스에서 생성 한 설정

편집하려면 클릭

실제 값을 입력하고 저장


이 솔루션은 간단하지만 모든 팀에 적합하지 않을 수 있습니다.

먼저 환경 변수를 env_variables.yaml에 넣습니다 . 예 :

env_variables:
  SECRET: 'my_secret'

그런 다음이 포함 env_variables.yaml의를app.yaml

includes:
  - env_variables.yaml

마지막으로, 추가 env_variables.yaml.gitignore비밀 변수가 리포지터리 (repository) 내에 존재하지 않습니다 그래서.

이 경우 env_variables.yaml배치 관리자간에 공유해야합니다.


내 접근 방식은 App Engine 앱 자체 내 에서만 클라이언트 비밀을 저장하는 것입니다. 클라이언트 암호는 소스 제어 나 로컬 컴퓨터에 없습니다. 이는 모든 App Engine 공동 작업자가 클라이언트 보안 비밀에 대해 걱정할 필요없이 코드 변경 사항을 배포 할 수 있다는 이점이 있습니다.

클라이언트 암호를 Datastore에 직접 저장하고 Memcache를 사용하여 암호 액세스 지연 시간을 개선합니다. Datastore 항목은 한 번만 생성하면되며 향후 배포시 유지됩니다. 물론 App Engine 콘솔을 사용하여 언제든지 이러한 항목을 업데이트 할 수 있습니다.

일회성 엔티티 생성을 수행하는 두 가지 옵션이 있습니다.

  • App Engine Remote API 대화 형 셸을 사용하여 항목을 만듭니다.
  • 더미 값으로 엔티티를 초기화하는 관리자 전용 핸들러를 만듭니다. 이 관리 핸들러를 수동으로 호출 한 다음 App Engine 콘솔을 사용하여 프로덕션 클라이언트 비밀번호로 항목을 업데이트합니다.

이를 수행하는 가장 좋은 방법은 client_secrets.json 파일에 키를 저장하고 .gitignore 파일에 나열하여 git에 업로드되지 않도록 제외하는 것입니다. 환경마다 다른 키가있는 경우 app_identity api를 사용하여 앱 ID가 무엇인지 확인하고 적절하게로드 할 수 있습니다.

여기에 상당히 포괄적 인 예제가 있습니다-> https://developers.google.com/api-client-library/python/guide/aaa_client_secrets .

다음은 몇 가지 예제 코드입니다.

# 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()

GAE에 앱을 배포 할 때 appcfg.py의 -E 명령 줄 옵션을 사용하여 환경 변수를 설정할 수 있습니다 (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).
...

몇 가지 접근 방식을 할 수있는 것 같습니다. 유사한 문제가 있으며 다음을 수행합니다 (사용 사례에 맞게 조정 됨).

  • 동적 app.yaml 값을 저장하는 파일을 만들고 빌드 환경의 보안 서버에 배치합니다. 정말 편집증이라면 값을 비대칭 적으로 암호화 할 수 있습니다. 버전 제어 / 동적 가져 오기가 필요한 경우 개인 저장소에 보관하거나 셸 스크립트를 사용하여 적절한 위치에서 복사 / 가져올 수도 있습니다.
  • 배포 스크립트 중에 git에서 가져 오기
  • git pull 후 yaml 라이브러리를 사용하여 순수 Python으로 읽고 쓰는 방식으로 app.yaml을 수정합니다.

이를 수행하는 가장 쉬운 방법은 Hudson , Bamboo 또는 Jenkins 와 같은 지속적 통합 서버를 사용하는 것 입니다. 위에서 언급 한 모든 항목을 수행하는 플러그인, 스크립트 단계 또는 워크 플로를 추가하기 만하면됩니다. 예를 들어 Bamboo 자체에 구성된 환경 변수를 전달할 수 있습니다.

요약하면 액세스 권한 만있는 환경에서 빌드 프로세스 중에 값을 푸시하면됩니다. 빌드를 아직 자동화하지 않았다면 그렇게해야합니다.

또 다른 옵션 옵션은 당신이 말한 것입니다. 데이터베이스에 넣으십시오. 그렇게하지 않는 이유가 너무 느리기 때문이라면 값을 두 번째 레이어 캐시로 memcache에 푸시하고 값을 첫 번째 레이어 캐시로 인스턴스에 고정하면됩니다. 값이 변경 될 수 있고 인스턴스를 재부팅하지 않고 업데이트해야하는 경우 해시를 유지하여 변경시기를 확인하거나 수행 한 작업이 값을 변경할 때 어떻게 든 트리거 할 수 있습니다. 그게 다야.


대부분의 답변은 구식입니다. Google 클라우드 데이터 저장소를 사용하는 것은 실제로 현재 약간 다릅니다. https://cloud.google.com/python/getting-started/using-cloud-datastore

예를 들면 다음과 같습니다.

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

여기서는 항목 이름이 'TWITTER_APP_KEY'이고 종류가 'settings'이며 'value'가 TWITTER_APP_KEY 항목의 속성이라고 가정합니다.


google kms로 변수를 암호화하고 소스 코드에 포함해야합니다. ( 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

스크램블 된 (암호화되고 base64로 인코딩 된) 값을 환경 변수 (yaml 파일)에 넣습니다.

암호 해독을 시작하는 데 도움이되는 비단뱀 코드입니다.

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

javascript / nodejs에서이 문제를 어떻게 해결했는지 기록하고 싶었습니다. 로컬 개발을 위해 .env 파일에서 process.env로 환경 변수를로드하는 'dotenv'npm 패키지를 사용했습니다. GAE를 사용하기 시작했을 때 환경 변수를 'app.yaml'파일에 설정해야한다는 것을 알게되었습니다. 글쎄, 나는 로컬 개발에 'dotenv'를 사용하고 GAE에 'app.yaml'을 사용하고 싶지 않았기 때문에 (그리고 두 파일 사이에 내 환경 변수를 복제), app.yaml 환경 변수를 프로세스에로드하는 작은 스크립트를 작성했습니다. .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

Cloud Datastore에 appengine 환경 변수를 저장할 수있는 gae_env 라는 pypi 패키지 가 있습니다. 내부적으로는 Memcache도 사용하므로

용법:

import gae_env

API_KEY = gae_env.get('API_KEY')

데이터 저장소에 해당 키에 대한 값이 있으면 반환됩니다. 없는 경우 자리 표시 자 레코드 __NOT_SET__가 생성되고 a ValueNotSetError가 발생합니다. 예외는 Developers Console 로 이동 하여 자리 표시 자 레코드를 업데이트하도록 알려줍니다 .


Martin의 답변과 유사하게 Datastore에서 키 값을 업데이트하는 방법은 다음과 같습니다.

  1. 개발자 콘솔에서 데이터 저장소 섹션 으로 이동

  2. 아직 선택하지 않은 경우 페이지 상단에서 프로젝트를 선택합니다.

  3. 에서 종류 드롭 다운 상자를 선택합니다 GaeEnvSettings.

  4. 예외가 발생한 키는 값을 갖습니다 __NOT_SET__.

Settings 클래스에서 생성 한 설정

편집하려면 클릭

실제 값을 입력하고 저장


사용 / 구성에 대한 자세한 정보 패키지의 GitHub 페이지이동 하십시오.


Google Datastore 사용을 기반으로 한 @Jason F의 답변 은 비슷하지만 코드는 라이브러리 문서 의 샘플 사용을 기반으로 약간 구식 입니다. 나를 위해 일한 스 니펫은 다음과 같습니다.

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

Medium 게시물에서 부분적으로 영감을 얻었 습니다.

참고 URL : https://stackoverflow.com/questions/22669528/securely-storing-environment-variables-in-gae-with-app-yaml

반응형