Control Tower만으로는 조직별 요구사항을 일관되게 반영하거나 여러 계정에 공통된 리소스를 배포하는 데에 한계가 있다. 특히 계정 생성 이후 리소스 배포를 수동으로 진행하면 실수나 누락이 발생하기 쉽고, 운영 효율성도 떨어지게 된다.
이런 문제를 해결하기 위해 CfCT(Customizations for AWS Control Tower)를 사용하면, 조직의 정책이나 표준 리소스를 자동으로 모든 계정에 배포할 수 있어 일관성과 효율성을 확보할 수 있다.
CfCT는 AWS Control Tower와 CloudFormation StackSets를 기반으로 하여, 계정 생성 시점을 트리거로 다양한 리소스를 자동 배포하게 해준다. 이를 통해 보안 정책(SCP), 공통 IAM Role & Policy, Config Rule 추가로, 공식적으로 지원하지 않는 PermissionSets 등 표준 구성을 코드로 정의하고, GitOps 방식으로 관리할 수 있는 방법을 정리하였다.
AWS Control Tower를 사용하면 여러 AWS 계정을 쉽게 생성하고, 기본적인 보안 및 거버넌스 정책(Guardrails)을 자동으로 적용할 수 있다.
하지만 조직마다 필요한 리소스나 보안 정책은 서로 다르며, Control Tower만으로는 이러한 조직별 맞춤 설정까지 자동화하기 어렵다.
이를 보완하기 위해 AWS에서는 Customizations for AWS Control Tower (CfCT) 라는 오픈소스 프레임워크를 제공한다.
CfCT는 AWS Control Tower 환경 내에서 조직 고유의 리소스를 계정 생성 시 자동으로 배포 할 수 있도록 해주는 도구다.
내부적으로는 다음과 같은 구성 요소를 활용한다:
CfCT는 Control Tower의 계정 생성 흐름에 자연스럽게 연결되어, 조직 표준 리소스를 Git 기반으로 관리하고 자동으로 배포할 수 있게 한다.
이를 통해 다음과 같은 효과를 얻을 수 있다:
CfCT는 보안·운영팀이 계정 표준화를 자동화하고 싶은 경우 강력한 도구가 될 수 있다.
기존 Control Tower만으로는 여러 계정 생성 이후 리소스 관리가 수동으로 이뤄져 운영 리소스가 많이 사용된다. CfCT는 이를 보완해 GitOps 방식으로 표준 리소스를 자동 배포할 수 있게 해준다.
AWS IAM Identity Center (구 AWS SSO)의 구성 요소로, 사용자가 AWS 계정에 로그인할 때 부여받는 IAM Role의 권한 세트다.
AWS 리소스의 구성 변경 사항을 추적하고, 규정 준수 상태를 평가하는 서비스다.
AWS Organizations의 기능으로, 조직 계정(OU 또는 루트)에 적용되는 서비스 접근 제어 정책이다.
S3
접근을 제한하여 계정 차원에서 접근하지 못하게 할 수 있다. (IAM 정책보다 상위 개념)AWS Control Tower 전용 정책으로, 어떤 리전을 사용할 수 있는지 제어한다. 내부적으로 SCP 기반으로 동작하지만, Control Tower에서 관리하기 쉽게 만든 추상화
manifest.yaml
은 CfCT 에서 어떤 리소스를, 어떤 방식으로, 어떤 조건에 따라 배포할지를 정의하는 중앙 설정 파일이다.
이 파일의 핵심 섹션 중 하나가 resources:
이며, 여기에 여러 리소스 항목을 정의하여 배포한다.
각 리소스는 resources:
아래에 하나의 항목으로 정의된다.
# 예시 manifest.yaml 파일
resources: # List of resources
- name: [String]
resource_file: [String] [Local File Path, S3 URI, S3 URL]
deployment_targets: # account and/or organizational unit names
accounts: # array of strings, [0-9]{12}
- 012345678912
- AccountName1
organizational_units: #array of strings
- OuName1
- OuName2
deploy_method: scp | stack_set | rcp
parameters: # List of parameters [SSM, Alfred, Values]
- parameter_key: [String]
parameter_value: [String]
export_outputs: # list of ssm parameters to store output values
- name: /org/member/test-ssm/app-id
value: $[output_ApplicationId]
regions: #list of strings
- [String]
필드명 | 설명 |
---|---|
name | 리소스의 이름으로, manifest 내에서 구분을 위한 ID |
resource_file | 배포할 템플릿 파일의 경로 (로컬, S3 URI 등) |
deploy_method | 배포 방식 (예: stack_set , scp , rcp ) |
deployment_targets | OU 이름 또는 계정 ID, 계정 별칭 |
parameters | 템플릿에 전달할 매개변수 리스트 |
export_outputs | Outputs 로 생성된 값을 SSM 파라미터로 저장 |
regions | 리소스를 배포할 리전 목록 |
더 자세한 리소스에 대한 설명은 CfCT 매니페스트 파일의 리소스 섹션 참고하여, 작성을 진행하면 된다.
Customizations for AWS Control Tower(CfCT) 는 AWS Control Tower와 여러 서비스를 결합하여, 멀티 계정 환경에 조직 맞춤형 리소스를 자동으로 배포할 수 있도록 지원하는 솔루션이다.
AWS Control Tower와 CfCT(Customizations for AWS Control Tower) 를 활용하여 조직의 IAM Role, PermissionSet, SCP, AWS Config Rule 등을 GitOps 방식으로 자동 배포하는 구조이다.
manifest
, template
파일이 존재하여 성공을 하지만, 원격 저장소에 파일이 없다면 에러가 발생Parameter 설명
Configuration Parameter | Default 값 | 설명 |
---|---|---|
ARN of the Code Connection | 코드 연결에 사용할 리소스 ARN | |
GitHub User or Organization | git-username | GitHub 저장소를 소유한 사용자 |
GitHub Repository Name | custom-control-tower-configuration | Custom Control Tower 구성이 포함된 GitHub 저장소의 이름 |
GitHub Branch Name | main | Custom Control Tower 구성이 포함된 GitHub 저장소의 브랜치 이름 |
개발자 도구 > 설정 > 연결
Github를 통한 관리를 한다면 위와 같은 경로에서 먼저 생성을 진행한다. Github 소스 설정
연결이 끝났다면, 작성자는 custom-control-tower-configuration
이름의 Private 레포지토리만 접근을 가능하도록 구성하였다.
custom-control-tower-configuration/
├── config/
│ └── rule-accesskeys-rotated.template
├── iam/
│ └── role-ec2_describe.template
├── manifest.yaml
├── permissionsets/
│ ├── permissionset-ec2_describe-assignment.template
│ ├── permissionset-ec2_describe.template
│ └── policy-ec2_describe.template
└── scp/
└── preventive-controls.json
CfCT 구성을 위한 디렉토리 구조를 정리하였고, 각 폴더는 config, iam, scp, permission set 등 구분을 위해 분리 하였다.
소스 코드는 Github-CfCT에 업로드 하였으며 필요 시 참고하면 된다.
프로젝트에서 배포할 리소스, 정책, 권한세트, 역할 등을 정의하는 매니페스트
각 리소스별로 파일 경로, 배포 방식(StackSet, SCP 등), 대상 OU/계정, 파라미터, 리전 등을 지정할 수 있다.
배포할 계정에서 AWS Config의 커스텀 규칙을 정의하는 CloudFormation 템플릿
이 규칙은 활성화된 액세스 키가 지정된 기간(기본 90일) 내에 교체 되었는지 점검하는 Rule을 정의하였다. maxAccessKeyAge
파라미터로 최대 허용 일수를 지정할 수 있다.
배포할 계정에서 EC2 인스턴스에 대한 조회(Describe) 권한만을 가진 IAM Role을 정의하는 CloudFormation 템플릿
마스터 계정에서 SCP로 설정으로 조직 내에서 특정 위험한 작업(예: S3 퍼블릭 액세스 차단 해제, Glacier/Vault 삭제, KMS 키 삭제 등)을 방지하는 정책
마스터 계정에서 특정 계정(012345678901)의 사용자에게 EC2 Describe 권한을 부여하는 Permission Set Assignment 템플릿
마스터 계정에서 EC2 Describe
권한과 ReadOnlyAccess
권한을 가진 Permission Set을 정의하는 CloudFormation 템플릿
배포할 계정에서 EC2 Describe 권한만을 가진 커스텀 IAM Policy를 정의하는 CloudFormation 템플릿
git을 통해 원격 저장소에 업로드를 하게 된다면, CodePipeline이 트리거 되어 구성된 CI/CD가 실행이 된다.
모든 buildspec은 동일한 기본 구조를 가지고 있으며, STAGE_NAME
환경변수에 따라 다른 작업을 수행한다.
마스터 계정 > AWS Organizations > 정책 > 서비스 제어 정책(SCP)
배포한 계정 > AWS Config > 규칙
배포한 계정 > AWS IAM > 역할
배포한 계정 > AWS IAM > 정책
마스터 계정 > Identity Center > 다중 계정 권한 > 권한 세트
인프라 배포 시 CodePipeline 실행이 되는데, 평균적으로 완료까지 10~12분이 걸린다. 코드를 수정하며, 약 40번의 배포를 통해 현재의 구성을 완성하였다. 이 과정에서 있었던 이슈 사항들을 정리하였다.
AWS 공식 Github CfCT 링크에서 최신 버전의 template 다운로드 후 배포하면 되지만, 작성자 기준 v2.8.2 에서 계속되는 템플릿에 에러가 발생하였고, 이전 버전인 v2.8.1을 사용해 해결하였다.
공식적으로 올라온 릴리즈라도, 무조건 동작하는 것은 아니라는 것을 느꼈다.
Custom-Control-Tower-CodePipeline
파이프라인에서 Custom-Control-Tower-StackSet-CodeBuild
빌드 단계의 환경 변수에서 EXECUTION_ROLE_NAME
확인 시 사용하는 IAM Role을 확인 할 수 있는데, 값이 AWSControlTowerExecution
으로 되어 있다.
# Trust Relationship
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "controltower.amazonaws.com",
"AWS": "arn:aws:iam::<your-masteraccount>:root"
},
"Action": "sts:AssumeRole"
}
]
}
만약, Master 계정에 배포를 하고 싶다면 AWSControlTowerExecution
IAM Role 을 생성해야 한다.
필요한 권한은 AdministratorAccess 이며, 신뢰 관계는 위와 같이 구성이 필요 한다.
Cloudformation 에서 만들어질 Resources
내부 Logical ID에는 하이픈(-
)이나 언더스코어(_
) 등 사용이 불가하지만, Name, PolicyName 등의 값에서 -
, _
등 사용이 가능하다.
Resources
내부의 Logical ID는 -
, _
등 특수문자 사용 불가PermissionsetEc2Describe
Permissionset-EC2-Describe
or Permissionset_EC2_Describe
# permissionsets/permissionset-ec2-describe.template
Outputs:
PermissionSetArn:
Value: !GetAtt PermissionsetEc2Describe.PermissionSetArn
Export: # PermissionSet Stack에서 Export 정의
Name: PermissionSetArn
# permissionsets/permissionset-ec2-describe-assignment.template
PermissionSetArn: !ImportValue PermissionSetArn # Assignment Stack에서 ImportValue 사용
서로 다른 template 파일을 통해 stack_set 을 만든다는 조건에서 Type: AWS::SSO::Assignment
에서 PermissionSetArn
을 지정해야 하는데, 한 번의 배포로 리소스를 만들기 위해서 ARN을 동적으로 값을 받아야한다.
manifest.yaml 파일에서는 값을 전달 해줄 수 없으며, 이런 경우 변수를 받아야 할 template 파일에서!ImportValue
을 통해서 값을 받아올 수 있다.
PermissionSet
과 Assignment
을 별도 템플릿으로 나눌 경우 Outputs + Export
→ !ImportValue
방식 사용만약, 여러 계정에 배포할 경우, 파라미터를 사용하지 못하고 parameter_value
를 리스트 형태나 다르게 줄 수 없다는 단점이 있다. 하나의 PermissionSet를 배포할 Account가 2개 이상이라면 하드코딩을 통한 사용을 해야한다.
이는 코드의 가독성이 떨어질 수 있을 것이고, 복잡한 환경일수록 템플릿을 분리하는 구조가 좋을 것이라고 생각한다.
# permissionsets/permissionset-ec2-describe.template
# 하나의 파일에 PermissionSet, Assignment 적용한 예시 template
Resources:
PermissionsetEc2Describe:
Type: AWS::SSO::PermissionSet
Properties:
Name: permissionset-ec2-describe
Description: EC2 Describe Permission Set
InstanceArn: !Ref InstanceArn
SessionDuration: PT8H
ManagedPolicies:
- arn:aws:iam::aws:policy/ReadOnlyAccess
CustomerManagedPolicyReferences:
- Name: policy-ec2-describe
Path: /
PermissionsetEc2DescribeAssignment:
Type: AWS::SSO::Assignment
Properties:
InstanceArn: !Ref InstanceArn
PermissionSetArn: !GetAtt PermissionsetEc2Describe.PermissionSetArn
PrincipalId: !Ref TargetUser
PrincipalType: USER
TargetId: !Ref TargetAccountId
TargetType: AWS_ACCOUNT
DependsOn: EC2DescribePermissionSet
# manifest.yaml
- name: cfct-ec2-describe-permission-set
resource_file: permissionsets/permissionset-ec2-describe.template
parameters:
- parameter_key: InstanceArn
parameter_value: "<your-ssoinstanceId>"
- parameter_key: TargetUser
parameter_value: "<your-permissionset-userId>"
- parameter_key: TargetAccountId
parameter_value: "<your-account>"
deploy_method: stack_set
deployment_targets:
accounts:
- <your-masteraccount>
regions:
- ap-northeast-2
25년 7월 기준 CloudFormation에서는 AWS::IdentityStore::User
리소스를 지원하지 않는 것으로 확인된다.
따라서 TargetUser ID
는 수동으로 생성 후 manifest.yaml에 직접 입력해야 한다.
공식 문서 : CloudFormation IdentityStore 리소스 참조
CfCT 배포 시 GitHub 소스를 사용하려면 AWS의 CodeStar Connection 설정이 선행되어야 한다. 연결 후 CfCT CloudFormation 사용 시 입력 할 파라미터의 ConnectionArn
값을 정확히 입력해야 한다. (입력한 ARN을 가지고 IAM을 생성)
CfCT는 매우 강력한 도구이지만, 초기 진입 장벽이 높다고 느꼈다. 최초 CfCT 배포 시 사용하는 서비스가 많고, 동작에 대한 이해가 필요하기 때문에 한 눈에 파악이 꽤나 힘들었다.
또한, CloudFormation을 사용해본 경험이 부족했던 터라, 템플릿 문법에 익숙해지는 데 시간이 걸렸다. AI 도구도 활용했지만 환각(hallucination) 이슈로 인해 공식 문서를 많이 참고할 수밖에 없었다.
그럼에도 불구하고 CfCT의 구조와 배포 방식, IAM 권한 설정, Stack 간 참조 구조를 이해하고 나니, 멀티 계정을 자동화하는 데 있어 매우 유용한 프레임워크라는 점을 체감할 수 있었다.
이번에 겪은 여러 시행착오와 이슈들을 미리 알고 있다면, 배포 시간을 단축하고 오류에도 훨씬 빠르게 대응할 수 있을 것이다. 이 글이 CfCT를 처음 도입하거나 배포 환경을 고도화를 위한 분들에게 도움이 되었으면 좋겠다.