해당 기사에서는 AWS Secrets Manager 보안 암호 교체를 사용하여 IAM 액세스 키를 주기적으로 교체하는 방안에 대해서 설명합니다.
액세스 키와 같은 장기 자격 증명이 있는 IAM 사용자를 사용하는 대신 임시 자격 증명을 사용하는 것을 권장합니다. 하지만, IAM 사용자의 장기 자격 증명이 필요한 특정 사용 사례가 있는 경우 액세스 키를 주기적으로 교체하는 것이 좋습니다. AWS Secrets Manager와 AWS Lambda를 사용하여 주기적으로 IAM 액세스 키 교체를 달성할 수 있습니다. 더 나아가 Secrets Manager를 사용하면 액세스 키와 같은 자격증명을 애플리케이션 소스 코드에 하드 코딩 하지 않고 자격 증명 필요 시 보안 암호를 검색하여 사용하므로 보안 환경 개선과 자격 증명을 교체할 때 애플리케이션에 변경 사항을 배포하지 않아도 되므로 가용성 향상 측면에서 유용합니다. [1]
해당 기사의 최종 목적은 IAM 장기 자격증명인 액세스 키를 AWS Secrets Manager에 저장 후 자격증명 필요 시 보안 암호를 검색하여 자격증명을 사용합니다. 이 후, Secrets Manager 보안 암호 교체 옵션을 사용하여 주기적으로 자격증명을 교체합니다. 정상적으로 프로세스 완료 시, 보안 암호는 새로운 액세스 키로 교체되고 가장 오래 된 액세스 키는 삭제됩니다.
사전 요구 사항
- 액세스 키를 보유한 IAM 사용자
- 액세스 키를 저장하고 있는 보안 암호
- 액세스 키 교체를 위한 lambda 함수
단계 1. 액세스 키 확인
$ aws iam list-access-keys --user-name "Your User Name"
- 자신의 IAM 사용자에 대한 액세스 키를 교체하려면 다음 정책에 따른 권한이 있어야 합니다.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "ManageOwnAccessKeys",
"Effect": "Allow",
"Action": [
"iam:CreateAccessKey",
"iam:DeleteAccessKey",
"iam:GetAccessKeyLastUsed",
"iam:GetUser",
"iam:ListAccessKeys",
"iam:UpdateAccessKey",
"iam:TagUser"
],
"Resource": "arn:aws:iam::*:user/${aws:username}"
}]
}
단계 2. 다른 유형의 보안 암호 생성 [2]
다른 유형의 보안 암호 옵션을 사용하면 모든 유형의 서비스에 대한 자격 증명 또는 기타 정보를 저장할 수 있는 보안 암호를 생성 할 수 있습니다. 아래와 같은 Key / Value를 포함하는 보안 암호를 생성합니다.
Secret key | Secret value |
---|
accesskey | Your Access Key |
secretkey | Your Secret Key |
username | Your User Name |
secretarn | Your Secret ARN |
- secretarn은 보안 암호 생성 후 확인되는 ARN으로 편집합니다.
$ aws secretsmanager get-secret-value --secret-id "Your Secret Name"
단계 3. 액세스 키 교체를 위한 Lambda 함수 생성
Secrets Manager는 모든 유형의 암호에 대한 교체 기능을 생성할 수 있는 시작점으로 템플릿을 제공합니다. [3] 다음 예제 코드를 참고하실 수 있습니다.
import boto3
import json
import logging
import os
import time
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
arn = event['SecretId']
token = event['ClientRequestToken']
step = event['Step']
# Setup the client
secretsmanager_client = boto3.client('secretsmanager')
# Make sure the version is staged correctly
metadata = secretsmanager_client.describe_secret(SecretId=arn)
logging.info(repr(metadata))
versions = metadata['VersionIdsToStages']
if token not in versions:
logger.error("Secret version %s has no stage for rotation of secret %s." % (token, arn))
raise ValueError("Secret version %s has no stage for rotation of secret %s." % (token, arn))
if "AWSCURRENT" in versions[token]:
logger.info("Secret version %s already set as AWSCURRENT for secret %s." % (token, arn))
return
elif "AWSPENDING" not in versions[token]:
logger.error("Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn))
raise ValueError("Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn))
if step == "createSecret":
logging.debug("createSecret %s" % arn)
logging.info("for IAM user access keys secret creation is handled by IAM ")
elif step == "setSecret":
logging.debug("setSecret %s" % arn)
current_dict = get_secret_dict(secretsmanager_client, arn, "AWSCURRENT", required_fields=['username'])
username = current_dict['username']
origin_dict = get_secret_dict(secretsmanager_client, current_dict['secretarn'], "AWSCURRENT")
origin_iam_client = boto3.client('iam', aws_access_key_id=origin_dict['accesskey'], aws_secret_access_key=origin_dict['secretkey'])
# load any pre-existing access keys. sorted by created descending. if the count is 2+ remove the oldest key
existing_access_keys = sorted(origin_iam_client.list_access_keys(UserName=username)['AccessKeyMetadata'], key=lambda x: x['CreateDate'])
if len(existing_access_keys) >= 2:
logger.info("at least 2 access keys already exist. deleting the oldest version: %s" % existing_access_keys[0]['AccessKeyId'])
origin_iam_client.delete_access_key(UserName=username, AccessKeyId=existing_access_keys[0]['AccessKeyId'])
# request new access key and gather the response
new_access_key = origin_iam_client.create_access_key(UserName=username)
current_dict['accesskey'] = new_access_key['AccessKey']['AccessKeyId']
current_dict['secretkey'] = new_access_key['AccessKey']['SecretAccessKey']
logging.info('applying new secret value to AWSPENDING')
# save the new access key to the pending secret
secretsmanager_client.put_secret_value(SecretId=arn, ClientRequestToken=token, SecretString=json.dumps(current_dict), VersionStages=['AWSPENDING'])
elif step == "testSecret":
logging.debug("testSecret %s" % arn)
# load the pending secret for testing
pending_dict = get_secret_dict(secretsmanager_client, arn, "AWSPENDING", required_fields=['username'], token = token)
# attempt to call an iam service using the credentials
test_client = boto3.client('iam', aws_access_key_id=pending_dict['accesskey'], aws_secret_access_key=pending_dict['secretkey'])
try:
test_client.get_account_authorization_details()
except test_client.exceptions.ClientError as e:
# the test fails if and only if Authentication fails. Authorization failures are acceptable.
if e.response['Error']['Code'] == 'AuthFailure':
logging.error("Pending IAM secret %s in rotation %s failed the test to authenticate. exception: %s" % (arn, pending_dict['username'], repr(e)))
raise ValueError("Pending IAM secret %s in rotation %s failed the test to authenticate. exception: %s" % (arn, pending_dict['username'], repr(e)))
elif step == "finishSecret":
logging.debug("finishSecret %s" % arn)
# finalize the rotation process by marking the secret version passed in as the AWSCURRENT secret.
metadata = secretsmanager_client.describe_secret(SecretId=arn)
current_version = None
for version in metadata["VersionIdsToStages"]:
if "AWSCURRENT" in metadata["VersionIdsToStages"][version]:
if version == token:
# The correct version is already marked as current, return
logger.info("finishSecret: Version %s already marked as AWSCURRENT for %s" % (version, arn))
return
current_version = version
break
# finalize by staging the secret version current
secretsmanager_client.update_secret_version_stage(SecretId=arn, VersionStage="AWSCURRENT", MoveToVersionId=token, RemoveFromVersionId=current_version)
logger.info("finishSecret: Successfully set AWSCURRENT stage to version %s for secret %s." % (token, arn))
else:
raise ValueError("Invalid step parameter")
def get_secret_dict(secretsmanager_client, arn, stage, required_fields=[], token=None):
# Only do VersionId validation against the stage if a token is passed in
if token:
secret = secretsmanager_client.get_secret_value(SecretId=arn, VersionId=token, VersionStage=stage)
else:
secret = secretsmanager_client.get_secret_value(SecretId=arn, VersionStage=stage)
plaintext = secret['SecretString']
secret_dict = json.loads(plaintext)
# Run validations against the secret
for field in required_fields:
if field not in secret_dict:
raise KeyError("%s key is missing from secret JSON" % field)
# Parse and return the secret JSON string
return secret_dict
- Lambda 함수가 보안 암호를 교체하기 위해 실행 역할에 아래와 같은 권한 정책이 필요합니다. [4]
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:DescribeSecret",
"secretsmanager:GetSecretValue",
"secretsmanager:PutSecretValue",
"secretsmanager:UpdateSecretVersionStage"
],
"Resource": "Your Secret ARN"
},
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetRandomPassword"
],
"Resource": "*"
}
]
}
단계 4. Secrets Manager 교체 구성
자동 교체를 켜면 cron() 또는 rate() 표현식을 사용하여 보안 암호 교체 일정을 설정할 수 있습니다. rate 표현식을 사용하면 시간 또는 일 간격으로 반복되는 교체 일정을 생성할 수 있으며, cron 표현식을 사용하면 보다 세부적인 교체 일정을 생성할 수 있습니다. 또한, Secrets Manager 교체 일정은 UTC 표준 시간대를 사용하며, 보안 암호를 4시간 마다 교체할 수 있습니다. [6]
단계 5. 보안 암호 즉시 교체 및 확인
교체 구성 완료 시, 보안 암호를 즉시 교체 할 수 있습니다.
정상적으로 교체가 성공되면, 보안 암호가 교체되고 오래 된 액세스 키는 삭제됩니다. 운영방식에 따라 이전 사용하던 액세스 키를 비 활성화 또는 삭제하는 방안을 추가 구현할 수 있습니다.
$ aws secretsmanager get-secret-value --secret-id "Your Secret Name"
$ aws iam list-access-keys --user-name "Your User Name"
관련 정보
[1] AWS Secrets Manager란 무엇인가요? - https://docs.aws.amazon.com/ko_kr/secretsmanager/latest/userguide/intro.html
[2] AWS Secrets Manager 보안 암호 생성 - https://docs.aws.amazon.com/ko_kr/secretsmanager/latest/userguide/create_secret.html
[3] AWS Secrets Manager 교체 함수 템플릿 - https://docs.aws.amazon.com/ko_kr/secretsmanager/latest/userguide/reference_available-rotation-templates.html#OTHER_rotation_templates
[4] Lambda 교체 함수 실행 역할에 대한 정책 - https://docs.aws.amazon.com/ko_kr/secretsmanager/latest/userguide/rotating-secrets-required-permissions-function.html#rotating-secrets-required-permissions-function-example
[5] 고객 관리형 키에 대한 정책 설명 - https://docs.aws.amazon.com/ko_kr/secretsmanager/latest/userguide/rotating-secrets-required-permissions-function.html#rotating-secrets-required-permissions-function-cust-key-example
[6] Secrets Manager 교체의 예약 표현식 - https://docs.aws.amazon.com/ko_kr/secretsmanager/latest/userguide/rotate-secrets_schedule.html