redshift-data cli를 이용해서 sql을 실행할 때 인증방식으로 AWS Secrets Manager를 사용하려고 합니다. Secrets Manager콘솔에서 새 시크릿을 만들고 Redshift의 패스워드를 자동으로 교체하는 설정까지 해보았습니다.

구성

Redshift클러스터는 배스천 서버를 이용해서 외부에서도 접속할 수 있도록 했고, 패스워드를 자동교체하는 람다는 NAT게이트웨이가 아닌 VPC엔드포인트를 통하도록 설정했습니다.

aws-architecture

시크릿 생성과 자동교체 설정

  • 새 시크릿을 생성합니다. Redshift 클러스터의 유저명과 패스워드를 입력하고 클러스터를 선택 해 줍니다. KMS키는 디폴트 키를 선택했습니다.
    create-secret1
  • 자동교체를 활성화 하고 교체 간격을 선택 해 줍니다. 새 람다함수를 생성합니다.
    create-secret2

자동으로 생성되는 Cloudformation 스택생성에 실패했습니다. 서브넷 갯수 초과로 인한 실패입니다.

Properties validation failed for resource SecretsManagerRedshiftRotationSingleUser with message: #/VpcConfig/SubnetIds: expected maximum item count: 16, found: 20

수동으로 자동교체 재설정

실패 한 Cloudformation 스택을 삭제하고 동일한 템플릿으로 스택을 생성합니다. 파라미터는 실패한 스택과 동일하게 입력하고 서브넷은 람다를 작성 할 서브넷을 입력 해 줍니다.

KeyValue
kmsKeyArn-
excludeCharacters:/@"'\
functionNameSecretsManagerRedshiftRotationLambda
endpointhttps://secretsmanager.ap-northeast-1.amazonaws.com
invokingServicePrincipalsecretsmanager.amazonaws.com
vpcSecurityGroupIdssg-0b3xxxcfa
vpcSubnetIdssubnet-0e171673999462f90,subnet-0c21ae038cbe71f48

Cloudformation 스택생성이 완료되면 시크릿 자동 교체 설정화면을 열고 작성된 람다함수를 선택 해 줍니다.

create-secret3

람다 실행 실패

람다에 설정한 Security Group은 Redshift에 연결된 Security Group과 동일한데 (자동으로 람다 생성시에도 선택한 Redshift 클러스터와 동일한 Security Group이 할당됩니다.) Redshift에 연결된 Security Group은 AWS Secrets Manager가 생성하는 Lambda 함수의 인바운드 액세스를 허용해야 합니다.

[ERROR]	d352ae22-3b4b-4203-88cf-224160303d56	setSecret: Unable to log into database with previous, current, or pending secret of secret arn arn:aws:secretsmanager:ap-northeast-1:xxxx:secret:dev/Demo/Redshift-8c3rDL
[ERROR] ValueError: Unable to log into database with previous, current, or pending secret of secret arn arn:aws:secretsmanager:ap-northeast-1:xxxx:secret:dev/Demo/Redshift-8c3rDL

Security Group에 인바운드 액세스 추가

Security Group에 자기자신으로부터 Redshift에 access 가능하도록 인바운드 access 설정을 추가

redshift-sg

시크릿의 보안 암호 즉시 교체 실행

정상적으로 패스워드가 교체되었습니다.

[INFO]	2021-07-26T00:49:43.267Z	71e0d30d-8017-402f-b4a0-2305320e8d86	createSecret: Successfully put secret for ARN arn:aws:secretsmanager:ap-northeast-1:xxxx:secret:dev/Demo/Redshift-8c3rDL and version 1048bb1f-c616-4105-ac27-1508b53f8196.
[INFO]	2021-07-26T00:49:44.137Z	e4b636ee-6db7-4657-829f-c6585c1a67c1	setSecret: Successfully set password for user awsuser in Redshift DB for secret arn arn:aws:secretsmanager:ap-northeast-1:xxxx:secret:dev/Demo/Redshift-8c3rDL.
[INFO]	2021-07-26T00:49:44.476Z	44366043-316b-4494-8a30-67d3bad38883	testSecret: Successfully signed into Redshift DB with AWSPENDING secret in arn:aws:secretsmanager:ap-northeast-1:xxxx:secret:dev/Demo/Redshift-8c3rDL.
[INFO]	2021-07-26T00:49:44.798Z	8253ea08-1bb1-4474-a669-699a51dfc795	finishSecret: Successfully set AWSCURRENT stage to version 1048bb1f-c616-4105-ac27-1508b53f8196 for secret arn:aws:secretsmanager:ap-northeast-1:xxxx:secret:dev/Demo/Redshift-8c3rDL.

테스트 쿼리 실행

작성한 시크릿을 이용해서 sql을 실행 해 줍니다. 테스트용 유저에는 AmazonRedshiftDataFullAccess권한을 추가했습니다.

aws redshift-data execute-statement \
    --database dev \
    --cluster-identifier test-redshift-cluster \
    --secret-arn arn:aws:secretsmanager:ap-northeast-1:xxxx:secret:dev/Demo/Redshift-8c3rDL  \
    --region ap-northeast-1 \
    --sql "select * from sales limit 5;" \
    --profile redshift

* 결과
{
    "ClusterIdentifier": "test-redshift-cluster",
    "CreatedAt": "2021-07-26T09:56:18.888000+09:00",
    "Database": "dev",
    "Id": "61c1630b-9676-4ed4-bb25-d73f92efee76",
    "SecretArn": "arn:aws:secretsmanager:ap-northeast-1:xxxx:secret:dev/Demo/Redshift-8c3rDL"
}

sql결과 확인

aws redshift-data get-statement-result \
    --id 61c1630b-9676-4ed4-bb25-d73f92efee76 \
    --region ap-northeast-1 \
    --profile redshift

* 결과
{
    "Records": [
        [
            {
                "longValue": 33095
            },
            {
                "longValue": 36572
            },
            {
                "longValue": 30047
            },
            {
                "longValue": 660
            },
            {
                "longValue": 2903
            },
            {
                "longValue": 1827
            },
            {
                "longValue": 2
            },
            {
                "stringValue": "234.00"
            },
            {
                "stringValue": "35.10"
            },
            {
                "stringValue": "2008-01-01 09:41:06"
            }
        ]
        ... 생략
    ],
    "ColumnMetadata": [
        {
            "isCaseSensitive": false,
            "isCurrency": false,
            "isSigned": true,
            "label": "salesid",
            "length": 0,
            "name": "salesid",
            "nullable": 0,
            "precision": 10,
            "scale": 0,
            "schemaName": "public",
            "tableName": "sales",
            "typeName": "int4"
        }
        ... 생략
    ],
    "TotalNumRows": 5
}