programing

app.yaml로 환경변수를 GAE에 안전하게 저장

testmans 2023. 11. 6. 21:42
반응형

app.yaml로 환경변수를 GAE에 안전하게 저장

API 키와 다른 민감한 정보를 저장해야 합니다.app.yamlGAE에 배치하기 위한 환경 변수로서.이 문제는 제가 이 문제를 해결하기 위해app.yaml이 정보는 GitHub에 공개됩니다(좋지 않음).프로젝트에 맞지 않기 때문에 데이터스토어에 정보를 저장하고 싶지 않습니다.대신 에 나열된 파일에서 값을 바꾸고 싶습니다..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}

무슨 생각 있어요?

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

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

env_variables:
  SECRET: 'my_secret'

그럼 이것도 포함해서.env_variables.yaml에서app.yaml

includes:
  - env_variables.yaml

마지막으로 추가합니다.env_variables.yaml로..gitignore, 비밀 변수가 저장소에 존재하지 않도록 하기 위해서입니다.

이 경우에는.env_variables.yaml배포 관리자 간에 공유가 필요합니다.

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

프로젝트에서 다음 클래스를 사용하여 구성 데이터를 데이터스토어에 넣었습니다.

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

데이터스토어에 해당 키에 대한 값이 있으면 해당 키를 얻을 수 있습니다.없으면 자리 표시자 레코드가 생성되고 예외가 표시됩니다.예외가 발생하면 개발자 콘솔로 이동하여 자리 표시자 레코드를 업데이트해야 합니다.

이것이 설정된 구성 값에서 추측을 제거한다는 것을 발견했습니다.설정할 구성 값이 확실하지 않으면 코드를 실행하면 알려줍니다!

위의 코드는 memcache를 사용하는 ndb 라이브러리와 후드 아래의 데이터스토어를 사용하기 때문에 빠릅니다.


업데이트:

jelder가 App Engine 콘솔에서 데이터스토어 값을 찾아 설정하는 방법을 물어봤습니다.방법은 다음과 같습니다.

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

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

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

  4. 위의 코드를 실행하면 키가 나타납니다.모두 NOT SET 값을 갖게 됩니다.각각을 클릭하고 값을 설정합니다.

도움이 되길 바랍니다!

Your settings, created by the Settings class

Click to edit

Enter the real value and save

여러분이 글을 올릴 때는 존재하지 않았지만, 여기에 끼어드는 다른 사람들을 위해 구글은 이제 시크릿 매니저라는 서비스를 제공합니다.

구글 클라우드 플랫폼의 안전한 위치에 비밀을 저장하는 간단한 REST 서비스입니다.이것은 Data Store보다 더 나은 접근 방식으로, 저장된 비밀을 확인하기 위해 추가적인 단계가 필요하고, 필요한 경우 프로젝트의 여러 측면에서 개별 비밀을 서로 다르게 보호할 수 있습니다.

버전 관리 기능을 제공하므로 암호 변경을 비교적 쉽게 처리할 수 있을 뿐만 아니라 필요한 경우 런타임에 암호를 검색하고 생성할 수 있는 강력한 쿼리 및 관리 계층을 제공합니다.

파이썬 SDK

사용 예시:

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_version_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

제 접근 방식은 앱 엔진 앱 자체 내에서만 클라이언트 비밀을 저장하는 것입니다.클라이언트 암호는 소스 제어에도 없고 로컬 컴퓨터에도 없습니다.이를 통해 앱 엔진 공동 작업자는 클라이언트 비밀에 대한 걱정 없이 코드 변경을 배포할 수 있습니다.

클라이언트 암호를 데이터스토어에 직접 저장하고, 암호에 액세스하는 대기 시간을 개선하기 위해 Memcache를 사용합니다.데이터스토어 엔티티는 한 번만 생성하면 되고 이후 배포 시에도 지속됩니다.물론 앱 엔진 콘솔을 사용하여 언제든지 이러한 엔티티를 업데이트할 수 있습니다.

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

  • App Engine Remote API 대화형 셸을 사용하여 엔티티를 만듭니다.
  • 더미 값으로 엔티티를 초기화할 Admin only 핸들러를 만듭니다.이 관리 처리기를 수동으로 호출한 다음 App Engine 콘솔을 사용하여 운영 클라이언트 암호로 엔티티를 업데이트합니다.

가장 좋은 방법은 client_secrets.json 파일에 키를 저장하고 .gitignore 파일에 나열하여 git에 업로드하는 것을 제외하는 것입니다.환경에 따라 키가 다를 경우 app_identity api를 사용하여 appid가 무엇인지 확인하고 적절하게 로드할 수 있습니다.

여기에 꽤 포괄적인 예가 있습니다 -> 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()

이 솔루션은 더 이상 사용되지 않는 appcfg.py 에 의존합니다.

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

대부분의 대답은 구식입니다.지금 구글 클라우드 데이터스토어를 사용하는 것은 사실 조금 다릅니다.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')

엔티티 이름은 'TWITER_APP_KEY', 종류는 '설정', 'value'는 TWITER_APP_KEY 엔티티의 속성이라고 가정합니다.

Google 클라우드 트리거 대신 github 액션으로 (Google 클라우드 트리거는 자체 app.yaml을 찾지 못하고 자체적으로 빌어먹을 환경 변수를 관리할 수 없습니다.)

다음은 방법입니다.

환경 : App 엔진, 표준(flex 아님), Nodejs Express 어플리케이션, PostgreSQL CloudSql

먼저 설정:

1. Create a new Google Cloud Project (or select an existing project).

2. Initialize your App Engine app with your project.

[Create a Google Cloud service account][sa] or select an existing one.

3. Add the the following Cloud IAM roles to your service account:

    App Engine Admin - allows for the creation of new App Engine apps

    Service Account User - required to deploy to App Engine as service account

    Storage Admin - allows upload of source code

    Cloud Build Editor - allows building of source code

[Download a JSON service account key][create-key] for the service account.

4. Add the following [secrets to your repository's secrets][gh-secret]:

    GCP_PROJECT: Google Cloud project ID

    GCP_SA_KEY: the downloaded service account key

app.yaml

runtime: nodejs14
env: standard
env_variables:
  SESSION_SECRET: $SESSION_SECRET
beta_settings:
  cloud_sql_instances: SQL_INSTANCE

그러면 깃허브 작용은

name: Build and Deploy to GKE

on: push

env:
  PROJECT_ID: ${{ secrets.GKE_PROJECT }}
  DATABASE_URL: ${{ secrets.DATABASE_URL}}
jobs:
  setup-build-publish-deploy:
    name: Setup, Build, Publish, and Deploy
    runs-on: ubuntu-latest

steps:
 - uses: actions/checkout@v2
 - uses: actions/setup-node@v2
   with:
    node-version: '12'
 - run: npm install
 - uses: actions/checkout@v1
 - uses: ikuanyshbekov/app-yaml-env-compiler@v1.0
   env:
    SESSION_SECRET: ${{ secrets.SESSION_SECRET }}  
 - shell: bash
   run: |
        sed -i 's/SQL_INSTANCE/'${{secrets.DATABASE_URL}}'/g' app.yaml
 - uses: actions-hub/gcloud@master
   env:
    PROJECT_ID: ${{ secrets.GKE_PROJECT }}
    APPLICATION_CREDENTIALS: ${{ secrets.GCLOUD_AUTH }}
    CLOUDSDK_CORE_DISABLE_PROMPTS: 1
   with:
    args: app deploy app.yaml

github 동작에 비밀을 추가하려면 다음으로 이동해야 합니다: 설정/비밀

내가 bash 스크립트로 모든 대체를 처리할 수 있었다는 것을 주목하세요.그래서 저는 github 프로젝트 "ikuanyshbekov/app-yaml-env-compiler@v1"에 의존하지 않을 것입니다.0"

GAE가 app.yaml의 환경 변수를 처리하는 가장 쉬운 방법을 제시하지 못한 것은 유감입니다.베타 설정/클라우드 sql 인스턴스를 업데이트해야 하므로 KMS를 사용하고 싶지 않습니다.app.yaml에 모든 걸 대입해야 했어요.

이렇게 하면 저는 환경에 맞는 구체적인 행동을 하고 비밀을 관리할 수 있습니다.

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

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

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

요약하자면, 사용자만 액세스할 수 있는 환경에서 빌드 프로세스 중에 값을 밀어 넣기만 하면 됩니다.아직 빌드를 자동화하지 않았다면, 그렇게 해야 합니다.

또 다른 옵션은 당신이 말한 것입니다. 데이터베이스에 입력하세요.만약 당신이 그렇게 하지 않는 이유가 일이 너무 느리기 때문이라면, 두 번째 계층 캐시로서 값을 메모리 캐시에 넣고, 첫 번째 계층 캐시로서 값을 인스턴스에 고정하기만 하면 됩니다.값이 변경될 수 있고 재부팅하지 않고 인스턴스를 업데이트해야 하는 경우에는 언제 변경되는지 확인할 수 있는 해시를 보관하거나 값이 변경될 때 트리거합니다.여기까지입니다.

javascript/nodejs에서 이 문제를 어떻게 해결했는지를 알려주고 싶었습니다.로컬 개발을 위해 환경 변수를 .env 파일에서 process.env로 로드하는 'dotenv' npm 패키지를 사용했습니다.GAE를 사용하기 시작하면서 환경 변수는 'app.yaml' 파일로 설정해야 한다는 것을 알게 되었습니다.음, 저는 지역 개발을 위해 'dotenv'를 사용하고 GAE를 위해 'app.yaml'을 사용하고 싶지 않아서, 지역 개발을 위해 process.env에 app.yaml 환경 변수를 로드하는 스크립트를 조금 작성했습니다.누군가에게 도움이 되기를 바랍니다.

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

당신은 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

스크램블된(encrypted 및 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

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

미디엄 포스트에서 부분적으로 영감을 받았습니다.

마틴의 답변 연장

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

클라우드 데이터스토어에 앱 엔진 환경 변수를 저장할 수 있는 gae_env라는 pypi 패키지가 있습니다.후드 밑에 멤캐시도 사용해서 빠릅니다.

용도:

import gae_env

API_KEY = gae_env.get('API_KEY')

데이터스토어에 해당 키의 값이 있으면 해당 키가 반환됩니다.없을 경우 자리 표시자 레코드가 생성되고ValueNotSetError던져질 것입니다.예외가 발생하면 개발자 콘솔로 이동하여 자리 표시자 레코드를 업데이트해야 합니다.


Martin의 답변과 마찬가지로 데이터스토어의 키 값을 업데이트하는 방법은 다음과 같습니다.

  1. 개발자 콘솔에서 데이터스토어 섹션으로 이동

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

  3. Kind 드롭다운 상자에서GaeEnvSettings.

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

Your settings, created by the Settings class

Click to edit

Enter the real value and save


사용/구성에 대한 자세한 내용은 패키지의 GitHub 페이지로 이동합니다.

나의 해결책은 github 동작과 github 비밀을 통해 app.yaml 파일의 비밀을 대체하는 것입니다.

app.yaml (앱 엔진)

env_variables:
  SECRET_ONE: $SECRET_ONE
  ANOTHER_SECRET: $ANOTHER_SECRET

workflow.yaml(기투브)

steps:
  - uses: actions/checkout@v2
  - uses: 73h/gae-app-yaml-replace-env-variables@v0.1
    env:
      SECRET_ONE: ${{ secrets.SECRET_ONE }}
      ANOTHER_SECRET: ${{ secrets.ANOTHER_SECRET }}

여기서 Github 액션을 찾을 수 있습니다.
https://github.com/73h/gae-app-yaml-replace-env-variables

로컬 개발 시에는 .env 파일에 비밀을 작성합니다.

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

반응형