AWS 노트

Parameter Store의 Parameter를 참조한 CDK stack을 deploy할 때 주의할 점

Jonchann 2020. 5. 25. 20:17

하드코딩을 막기 위해 AWS의 ParameterStore(SSM)을 사용해야 한다는 이야기를 적었었다.
AWS SDK로 TypeScript에서 Parameter Store의 파라미터를 참조하기 (비동기처리와 async, await, promise)

Prameter Store를 이용해 CDK stack에서 하드코드 없애기

 

이번에는 ParameterStore를 이용해 기밀정보를 마스크한 CDK stack이나 여타 다른 TypeScript 스크립트 파일을 AWS에 deploy할 때 어떤 점을 주의해야 하는가에 대해 적을 것이다.

 

이 글의 전제는 기본(default 혹은 production) 계정과 개발용(development) 계정이 다르다는 것이다.

일반적으로

보통 CDK stack을 AWS CloudFormation에 deploy할 때의 command는 다음과 같다.

$ cdk deploy {StackName} {options if exist}

StackName은 저번에도 말 했지만 bin/{stackFileName.ts}에 있는 id값을 적어야 한다.

만약 개발용 계정이 있어 로컬에서 개발용 계정으로 deploy를 해야 하는데 로컬에 설정되어 있는 credential의 defalut profile이 개발용 계정이 아니라면 다음과 같은 --profile옵션을 붙여야 한다.

$ cdk deploy {StackName} --profile {profile_name_of_development_account} {other options if exist}

 

@aws-cdk/aws-ssm 을 이용해 파라미터를 ParameterStore에서 참조하는 경우


aws의 다른 서비스를 사용할 때와 마찬가지로 --profile 옵션으로 계정 선택이 가능하다.

aws-sdk 를 이용해 파라미터를 ParameterStore에서 참조하는 경우


CDK와 SDK를 함께 사용하는 경우

굳이 @aws-cdk/aws-ssm 있는데 aws-sdk를 사용해야 하나? 싶을 수 있지만 @aws-cdk/aws-ssm의 경우, CDK stack 정보를 scope의 인수로 받기 때문에 cdk.Stack을 계승하는 class가 아닌 이상 사용할 수 없다.

 

특히 SecureString 타입의 파라미터는 @aws-cdk/aws-ssm으로 직접 참조는 불가능하다:

RFC: Have CDK put SecureString type parameter values into SSM securely

The only method to put a secure param in ssm is in the aws sdk.
The only way to access aws sdk in cdk is some sort of custom construct.
Anything you inject into the aws custom component shows up in the template. If you use a custom component with a seperate lambda in a zipped asset, you still have to pass the value. That also shows up in the template.

그래서 aws-sdk를 사용하는 것인데 command 가장 앞 부분에 다른 옵션을 붙여야 한다.
(CDK stack에서는 SecureString을 참조하지 않는다고 @aws-cdk/aws-ssmaws-sdk를 병용하면 괜히 같은 ParameterStore에 격납되어 있는 값인데 두 번이나 불러와야하기 때문에 @aws-cdk/aws-ssm은 아예 사용하지 않는 쪽으로 가닥을 잡았다. String 타입 파라미터는 aws-sdk로도 참조해야하기 때문)

AWS_PROFILE={profile_name_of_development_account} cdk deploy {StackName} {options if exist}

Loading Credentials in Node.js from the Shared Credentials File에 보면 아래와 같은 내용이 적혀 있다.

By default, the SDK checks the AWS_PROFILE environment variable to determine which profile to use. If the AWS_PROFILE variable is not set in your environment, the SDK uses the credentials for the [default] profile. To use one of the alternate profiles, set or change the value of the AWS_PROFILE environment variable. For example, given the configuration file shown above, to use the credentials from the work account, set the AWS_PROFILE environment variable to work-account (as appropriate for your operating system).

기본적으로 SDK는 AWS_PROFILE이라는 환경 변수를 체크해 어떤 profile을 사용할지 정하는데 만약 이러한 환경변수가 지정되어있지 않을 경우 --profile 옵션과는 상관 없이 default profile 값을 사용해 정보를 취득한다고 한다.

따라서, ParameterStore에서 파라미터를 참조한 CDK stack을 참조해 deploy를 할 때 다음과 같은 command를 입력하면

$ cdk deploy {StackName} --profile {profile_name_of_development_account} {options if exist}

CDK stack은 개발용 계정으로 deploy하면서 그 속에서 참조하는 파라미터는 default계정의 ParameterStore에서 참조하게 되는 것이다.


CDK도 SDK도 개발용 계정에서 정보를 취득하려면 아래와 같은 command를 입력해야 한다:

$ AWS_PROFILE={profile_name_of_development_account} cdk deploy {StackName} --profile {profile_name_of_development_account}

 

SDK만 사용하는 경우

CDK로 관리하는 코드가 아니라면 --profile 옵션에서 값을 받을 수 없다(굳이 붙일 수는 있지만 아무런 영향력이 없는 것 같다).
때문에 다음과 같이 command 앞에 옵션을 붙여줘야 한다.

AWS_SDK_LOAD_CONFIG=1 AWS_PROFILE=development npx ts-node {path/file_name}.ts

AWS_SDK_LOAD_CONFIG가 true 혹은 1이라면 ~/.aws/credentials 속 내용을 SDK가 열람할 수 있게 된다고 한다.
(하지만 false나 0을 넘겨도 개발용 계정 credential을 참조했으므로 어떤 차이인지는 확인하지 못했다. 실험하다가 기본 계정에 deploy해버리면 안되므로)


--profile 옵션을 붙이지 않으면 Missing credentials in config, if using AWS_CONFIG_FILE, set AWS_SDK_LOAD_CONFIG=1 에러가 발생한다.


그렇다고 위의 에러가 발생하지 않게 --profile 옵션을 붙인다고 제대로 개발용 계정의 정보가 사용되지는 않으므로 위와 같은 command를 사용해야 한다.

 

command에 같은 profile 이름을 여러번 적지 않을 방법이 있을까? (아직 고민 중 결론 없음 주의)

new AWS.SharedInFileCredentials('')


boto3에서 Session을 지정하는 것과 같은 방식이다.

const credentials = new AWS.SharedInFileCredentials('{profile_name_of_development_account}')

하지만 이 방식은 해당 repository에 contribute 하고 있는 모든 팀원들이 같은 이름으로 profile을 설정해야 하니 별로 건전한 방법인 것 같지는 않다.

 

process.env.CDK_DEFAULT_ACCOUNT, process.env.CDK_DEFAULT_REGION


npm install @types/node를 install하고 --profile옵션의 값에서부터 account_idregion 정보를 취득해 aws-sdk에게 잘 전해줄 수 있을지도 모른다.

Environments에 따르면 process.env.CDK_DEFAULT_ACCOUNTprocess.env.CDK_DEFAULT_REGION으로 참조하면 된다고도 하니.

If you don't specify an environment when you define a stack, the stack is said to be environment-agnostic. AWS CloudFormation templates synthesized from such a stack will try to use deploy-time resolution on environment-related attributes such as stack.account, stack.region, and stack.availablityZones (Python: availability_zones).
(중략)
When you pass in your environment using CDK_DEFAULT_ACCOUNT and CDK_DEFAULT_REGION, the stack will be deployed in the account and Region determined by the AWS CDK CLI at the time of synthesis.

 

예를 들어, 아래와 같은 식으로 credential을 전달하고 싶었으나 사실상 role_arn을 credential을 판단하는 조건으로 넘기는 방법을 찾지 못했으므로 일단 보류.
(참고로 credentials에 넘겨도 없는 속성이라며 거부당했다)

const ssm = new AWS.SSM({
    region: process.env.CDK_DEFAULT_REGION,
      role_arn: arn:aws:iam::{account_id}:role/admin-role
})

 

게다가 이 방법은 cdk로 deploy할 때가 아니면 참조할 수 없는 정보이기 때문에 같은 repository를 함께 파라미터 참조하도록 했다고 하더라도 deploy command가 달라지므로 헷갈릴 수 있다.

 

region만이라면 AWS.config.region


account_id는 참조하지 않고 region만 참조하는 경우(있을까 싶다) AWS.config.region으로 언제나 어느 옵션이 붙든 참조할 수 있는 것을 확인했다.


물론 계정마다 region이 다르다면 이야기는 다르겠지만.

 

참고로 AWS.config는 다음과 같은 내용이 격납되어있다.

Config {
  credentials: null,
  credentialProvider: CredentialProviderChain {
    providers: [
      [Function],
      [Function],
      [Function],
      [Function],
      [Function],
      [Function],
      [Function]
    ],
    resolveCallbacks: []
  },
  region: '{region}',
  logger: null,
  apiVersions: {},
  apiVersion: null,
  endpoint: undefined,
  httpOptions: { timeout: 120000 },
  maxRetries: undefined,
  maxRedirects: 10,
  paramValidation: true,
  sslEnabled: true,
  s3ForcePathStyle: false,
  s3BucketEndpoint: false,
  s3DisableBodySigning: true,
  s3UsEast1RegionalEndpoint: 'legacy',
  s3UseArnRegion: undefined,
  computeChecksums: true,
  convertResponseTypes: true,
  correctClockSkew: false,
  customUserAgent: null,
  dynamoDbCrc32: true,
  systemClockOffset: 0,
  signatureVersion: null,
  signatureCache: true,
  retryDelayOptions: {},
  useAccelerateEndpoint: false,
  clientSideMonitoring: false,
  endpointDiscoveryEnabled: false,
  endpointCacheSize: 1000,
  hostPrefixEnabled: true,
  stsRegionalEndpoints: 'legacy'
}

활용할 수 있는 길은 없어 보인다.