Access key 주기적 변경

ZER0·2022년 9월 15일
1

AWS 보안 설정 가이드

목록 보기
10/47
post-custom-banner

1. 관련 법령


2. 개요

  • AWS 리소스 핸들링을 위해 IAM 계정에 Access key(Access key ID/Secret access key)를 발급한 경우 주기적으로 변경 필요

3. 취약점 판단 기준

  • Access key 변경 주기를 지정하지 않고, 주기적으로 Access key를 변경하지 않은 경우 취약
  • Access key 변경 주기를 지정하고, 주기적으로 Access key를 변경한 경우 취약하지 않음

4. 취약점 확인 방법 - (1) 관리 콘솔에서 확인

  • 관리 콘솔에서 [IAM] 검색 → [사용자] 메뉴 클릭
  • [활성 키 수명(Active key age)]이 내부 정책에서 정의한 Access key 변경 주기를 초과하는지 여부 확인

4. 취약점 확인 방법 - (2) AWS CLI에서 확인

  • IAM 권한을 보유한 계정의 Access key를 활용해 AWS CLI에서 Command(1) 실행
    • Command(1)
      aws iam get-credential-report --query 'Content' --output text | base64 -d | cut -d, -f1,9,10,11,14,15,16
  • access_key_active, access_key_last_lotated 컬럼 값을 확인하여 주기적 변경 여부 확인

5. 취약점 조치 방법

  • [알림] 다양한 방법이 있을 수 있으나 본 항목에서는 Secret Manager와 Lambda를 활용한 방법을 안내

  • 관리 콘솔에서 [Secret Manager] 검색 → [새 보안 암호 저장] 클릭

  • Access key를 저장할 Secret Manager 생성(키/값에는 임시 값 입력)

  • [자동 교체 구성] 비활성화 설정 후 [다음] 클릭

  • 관리 콘솔에서 [IAM] 검색 → [정책] 메뉴 → [정책 생성] 클릭

  • JSON(1)을 활용하여 JSON 정책 생성

    • JSON(1)
      {
          "Version": "2012-10-17",
          "Statement": [
              {
                  "Sid": "SecretsManagerReadWritePermission",
                  "Effect": "Allow",
                  "Action": [
                      "secretsmanager:GetSecretValue",
                      "secretsmanager:DescribeSecret",
                      "secretsmanager:PutSecretValue",
                      "secretsmanager:UpdateSecretVersionStage",
                      "secretsmanager:ListSecretVersionIds"
                  ],
                  "Resource": [
                      "SECRETS MANAGER의 ARN"
                  ]
              },
              {
                  "Sid": "KMSWritePermission",
                  "Effect": "Allow",
                  "Action": [
                      "kms:Decrypt",
                      "kms:Encrypt"
                  ],
                  "Resource": [
                      "aws/secretsmanager의 ARN"
                  ]
              },
              {
                  "Sid": "SecretsManagerKMSListingPermission",
                  "Effect": "Allow",
                  "Action": [
                      "kms:ListKeys",
                      "secretsmanager:ListSecrets"
                  ],
                  "Resource": "*"
              }
          ]
      }
  • IAM 서비스에서 [역할] 메뉴 → [역할 만들기] 클릭

  • 사용 사례 설정

  • JSON(1)을 활용해 만든 정책과 [AWSLambdaBasicExecutionRole] 정책 할당

  • 관리 콘솔에서 [Lambda] 검색 → [함수 생성] 클릭

  • 함수 생성 설정

  • Lambda(1)을 활용해 함수 작성 → [Deploy] 클릭

    • Lambda(1)
       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'] 
       		master_dict = get_secret_dict(secretsmanager_client, current_dict['masterarn'], "AWSCURRENT") 
       		master_iam_client = boto3.client('iam', aws_access_key_id=master_dict['accesskey'], aws_secret_access_key=master_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(master_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']) 
       			master_iam_client.delete_access_key(UserName=username, AccessKeyId=existing_access_keys[0]['AccessKeyId']) 
       		# request new access key and gather the response 
       		new_access_key = master_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

  • JSON(2)를 활용해 IAM 계정이 자신의 패스워드와 Access Key를 변경하도록 하는 정책 생성

    • JSON(2)
      {
          "Version": "2012-10-17",
          "Statement": [
              {
                  "Effect": "Allow",
                  "Action": [
                      "iam:ListUsers",
                      "iam:GetAccountPasswordPolicy"
                  ],
                  "Resource": "*"
              },
              {
                  "Effect": "Allow",
                  "Action": [
                      "iam:*AccessKey*",
                      "iam:ChangePassword",
                      "iam:GetUser",
                      "iam:*ServiceSpecificCredential*",
                      "iam:*SigningCertificate*"
                  ],
                  "Resource": [
                      "arn:aws:iam::*:user/${aws:username}"
                  ]
              }
          ]
      }
  • Access Key를 자동으로 교체할 IAM 계정에 JSON(2)를 활용해 만든 정책 할당

    • 이후의 작업을 위해 임시로 [AWSLambda_FullAccess] 정책 추가 할당 필요
  • [AWSLambda_FullAccess] 정책을 할당한 IAM 계정의 AWS CLI 접속

  • Command(2) 명령어 실행

    • Command(2)
       aws lambda add-permission --function-name [lambda의 ARN] --principal secretsmanager.amazonaws.com --action lambda:InvokeFunction --statement-id SecretsManagerAccess

  • 관리 콘솔에서 [Secret Manager] 검색 → 생성한 보안 암호 클릭

  • [보안 암호값 검색] 클릭 → [편집] 클릭

  • 보안 암호 값 편집

    • accesskey : Access key를 변경하고자 하는 IAM 계정의 Access Key 값
    • secretkey : Access key를 변경하고자 하는 IAM 계정의 Secret Access Key 값
    • username : Access key를 변경하고자 하는 IAM 계정명
    • masterarn : 해당 Secret Manager의 ARN
  • [교체 편집] 클릭 → 교체 구성 설정

  • [저장] 시 Access Key는 자동으로 교체되며, 변경 주기 이전에 교체를 원할 경우 [보안 암호 즉시 교체] 클릭

  • Secret Manager에서 Access Key 변경 확인

  • CloudWatch Log groups에서 Access Key 변경 확인


6. 참고

profile
Security Compliance Engineer
post-custom-banner

0개의 댓글