본문으로 건너뛰기

SecretsManager RDS Rotation

배경

RDS User들의 계정 정보를 보관하기 위해 SecretsManager를 활용할 수 있다.

특히, DB 보안상 매번 Password를 교체해야하는데 이를 AWS에서 제공하는 Lambda를 이용하여 SecretsManager에 등록된 User Password를 수정할 수 있다.

여태까지 이슈가 발생한 내용들 위주로 정리하고 어떤 식으로 동작하는지에 대해 정리하려고 한다.

Rotation은 SecretsManager의 값만 변경하는 것이 아니다. Database의 Password를 변경하고 새로운 Password로 접속이 되는지 확인한 뒤 Current Version으로 변경한다.

아키텍처

Secrets Manager가 Rotation Lambda를 호출하고, Lambda가 Secrets Manager API와 RDS에 접근하는 Rotation 구성

AWS에서 제공하는 Rotation Lambda의 로직은 아래의 4단계로 동작한다.

  1. createSecret: 새 비밀번호를 만들고 새 version에 AWSPENDING을 붙인다.
  2. setSecret: AWSPENDING 자격 증명을 RDS 사용자에 반영한다.
  3. testSecret: 새 자격 증명으로 데이터베이스 접속을 검증한다.
  4. finishSecret: 새 version으로 AWSCURRENT를 이동하고 이전 version에 AWSPREVIOUS를 붙인다.

필수 요소로는 다음과 같다.

  • Lambda
  • RDS 또는 Aurora
  • Secrets Manager
  • Lambda가 RDS와 Secrets Manager API에 접근할 수 있는 네트워크 경로
  • Lambda 실행 role의 Secrets Manager·KMS·로그 권한과 DB 비밀번호 변경 권한

Lambda

AWS에서 제공하는 RDS Rotation Template을 사용하거나 이를 기반으로 Custom Rotation Lambda를 만들 수 있다. Lambda에서는 Secret ARN과 Version Token을 확인하고 AWSCURRENT와 AWSPENDING이 동일한 DB와 User를 가리키는지 확인한다.

VPC 내부에 Lambda가 있다면 RDS Security Group에서 Lambda Security Group을 Source로 허용해야한다. 또한 SecretsManager API에 접근하기 위해 NAT 또는 Interface VPC Endpoint도 필요하다.

SecretsManager

해당 Key, Value에는 다음과 같은 값들이 필요하다. Key는 대소문자를 구분하기 때문에 주의해야한다.

{
  "engine": "postgres",
  "host": "db.example.internal",
  "username": "app_user",
  "password": "<PASSWORD>",
  "dbname": "app",
  "port": 5432
}
  • engine: 어떤 RDS Engine인지
  • host: RDS Endpoint 또는 접근 가능한 Domain
  • username, password: RDS User와 Password
  • dbname, port: Database 이름과 Port
  • masterarn: Alternating Users 방식을 사용할 때 필요한 관리자 Secret ARN

위의 값들로 RDS에 접근하기 때문에 정보가 다르다면 Rotation이 정상적으로 동작하지 않는다. DNS, Network, TLS 설정이 맞지 않아도 setSecret 또는 testSecret 단계에서 실패한다.

정상 유무를 파악하기 위해서는 SecretsManager의 Version을 확인해야한다.

Version과 staging label

SecretsManager에는 Version이 있다. 문제가 발생했을 때 이전 값으로 확인하거나 돌아갈 수 있도록 Version에 Staging Label을 붙여서 상태를 관리한다.

Rotation을 돌렸을 때 SecretsManager는 아래와 같이 3가지 상태가 존재하게 된다.

AWSPREVIOUS

직전 AWSCURRENT version을 가리킨다. 마지막 정상 자격 증명을 확인하거나 복구할 때 참고한다.

AWSCURRENT

애플리케이션이 기본적으로 조회하는 현재 version이다.

AWSPENDING

rotation 중 새로 생성·검증 중인 version이다. 성공하면 같은 version이 AWSCURRENT가 되고, AWSPENDING은 같은 version에 남아 있거나 제거될 수 있다.

상태

Rotation 성공 후의 대표 상태는 다음과 같다.

[VERSION_OLD] - AWSPREVIOUS
[VERSION_NEW] - AWSCURRENT (경우에 따라 AWSPENDING도 같은 version)

Rotation이 실패하면 다음처럼 AWSPENDING이 별도 version에 남을 수 있다.

[VERSION_OLD] - AWSPREVIOUS
[VERSION_CURRENT] - AWSCURRENT
[VERSION_FAILED] - AWSPENDING

이 상태에서는 다음 rotation이 이전 작업이 진행 중이라고 판단해 실패할 수 있다.

해결 방법

Rotation이 실패하더라도 AWSPENDING을 무조건 제거하거나 성공할 때까지 기다리는 방식은 피해야한다.

먼저 Lambda Log와 CloudTrail Event를 확인하고 Version별 Password와 Network를 점검한다.

aws secretsmanager list-secret-version-ids \
  --secret-id [SECRET_NAME] \
  --include-deprecated

Troubleshooting 문서나 Error Message에서 오래 남은 Pending Version을 제거하라고 안내하고, 해당 Version이 실제 DB에서 사용되지 않는다는 것을 확인한 경우에만 Label을 제거한다.

aws secretsmanager update-secret-version-stage \
  --secret-id [SECRET_NAME] \
  --version-stage AWSPENDING \
  --remove-from-version-id [PENDING_VERSION_ID]

정상적으로 Rotation이 진행 중인데 AWSPENDING을 임의로 제거한다면 SecretsManager에서는 Rotation이 완료되지 않은 것으로 판단할 수 있다.

Error 확인

CloudWatch의 해당 생성한 Lambda이름으로 된 로그 그룹을 확인해보면 아래와 같은 로그를 확인할 수 있다.

[ERROR] valueError: Unable to log into database with previous, current or pending secret of secret arn [SECRETSMANAGER_ARN]

Error 원인에는 여러가지가 있다. 현재까지 내가 확인한 에러는 아래와 같은 사유들이 있다.

Network Error

Multi Account 환경에서는 네트워크, 보안 등 다양한 관점에서 트러블 슈팅이 이루어진다. 가능하면 RDS 보안 그룹에는 Lambda subnet CIDR 전체보다 Lambda 보안 그룹을 source로 허용한다.

이 때는 CloudShell을 이용해서 어떤 부분에서 문제가 발생했는지 확인할 수 있다. 이 내용은 아래의 포스트에 저장해두었다.

CloudShell로 특정 환경에서 네트워크 테스트하기

Domain

Domain을 이용하여 SecretsManager를 이용할 수 있다. RDS의 Domain이 아니라 Route53에 등록된 Domain 말이다.

Secret의 host에는 RDS Endpoint뿐만 아니라 Lambda에서 확인할 수 있는 Route53 Private DNS도 사용할 수 있다. 다만 Rotation 도중 AWSCURRENT와 AWSPENDING의 Host를 다르게 변경하면 안된다.

해당 오류를 찾는데 상당한 시간을 들였다. 당시에는 rds.force_ssl을 1에서 0으로 변경했을 때 접속이 가능했다.

참고 자료