AWS CfCT 를 통한 PermissionSets, SCP, Config 관리 - GitOps 방식의 인프라 운영

신동수·2025년 7월 10일
0

AWS

목록 보기
33/33

개요

Control Tower만으로는 조직별 요구사항을 일관되게 반영하거나 여러 계정에 공통된 리소스를 배포하는 데에 한계가 있다. 특히 계정 생성 이후 리소스 배포를 수동으로 진행하면 실수나 누락이 발생하기 쉽고, 운영 효율성도 떨어지게 된다.
이런 문제를 해결하기 위해 CfCT(Customizations for AWS Control Tower)를 사용하면, 조직의 정책이나 표준 리소스를 자동으로 모든 계정에 배포할 수 있어 일관성과 효율성을 확보할 수 있다.
CfCT는 AWS Control Tower와 CloudFormation StackSets를 기반으로 하여, 계정 생성 시점을 트리거로 다양한 리소스를 자동 배포하게 해준다. 이를 통해 보안 정책(SCP), 공통 IAM Role & Policy, Config Rule 추가로, 공식적으로 지원하지 않는 PermissionSets 등 표준 구성을 코드로 정의하고, GitOps 방식으로 관리할 수 있는 방법을 정리하였다.

CfCT란?

AWS Control Tower를 사용하면 여러 AWS 계정을 쉽게 생성하고, 기본적인 보안 및 거버넌스 정책(Guardrails)을 자동으로 적용할 수 있다.
하지만 조직마다 필요한 리소스나 보안 정책은 서로 다르며, Control Tower만으로는 이러한 조직별 맞춤 설정까지 자동화하기 어렵다.
이를 보완하기 위해 AWS에서는 Customizations for AWS Control Tower (CfCT) 라는 오픈소스 프레임워크를 제공한다.

CfCT는 AWS Control Tower 환경 내에서 조직 고유의 리소스를 계정 생성 시 자동으로 배포 할 수 있도록 해주는 도구다.

내부적으로는 다음과 같은 구성 요소를 활용한다:

  • AWS CodePipeline: Git 저장소의 변경 사항을 감지하고 자동 배포를 실행
  • AWS CloudFormation StackSets: 여러 계정(Master 계정 포함) 및 리전에 리소스를 일괄 배포
  • manifest.yaml: 어떤 리소스를 어떤 조건으로 배포할지 정의하는 설정 파일

CfCT는 Control Tower의 계정 생성 흐름에 자연스럽게 연결되어, 조직 표준 리소스를 Git 기반으로 관리하고 자동으로 배포할 수 있게 한다.

이를 통해 다음과 같은 효과를 얻을 수 있다:

  • 조직 정책에 맞는 리소스를 자동 배포하여 일관성 유지
  • 사람이 수동으로 배포하면서 발생하는 실수 방지
  • GitOps 기반으로 형상 관리 및 변경 이력 추적 가능

CfCT는 보안·운영팀이 계정 표준화를 자동화하고 싶은 경우 강력한 도구가 될 수 있다.

왜 CfCT를 사용 하는가?

기존 Control Tower만으로는 여러 계정 생성 이후 리소스 관리가 수동으로 이뤄져 운영 리소스가 많이 사용된다. CfCT는 이를 보완해 GitOps 방식으로 표준 리소스를 자동 배포할 수 있게 해준다.

  • AWS Control Tower 기반 멀티 계정 환경에 조직 표준 리소스 자동 배포
  • 계정 생성 시점에 자동 트리거되는 IaC 기반 구성
  • GitHub → CodePipeline → Build → SCP/RCP or StackSet → 계정 구조로 GitOps 운영
  • 보안, 운영, 감사 정책을 코드로 통제 (SCP, Config, IAM, SSO 등)
  • Git 기반 변경 이력 관리 가능

서비스 별 설명

Permission Set

AWS IAM Identity Center (구 AWS SSO)의 구성 요소로, 사용자가 AWS 계정에 로그인할 때 부여받는 IAM Role의 권한 세트다.

  • 목적:
    - 역할 기반 접근 제어(RBAC) 를 중앙에서 관리하며, 여러 계정에 동일한 권한을 쉽게 배포 가능하도록 한다.
  • 적용 방식:
    - IAM Identity Center에서 Permission Set을 생성하여 특정 AWS 계정에 매핑 후 사용자/그룹이 로그인 시, 해당 Permission Set에 따라 권한이 부여됨 (실제로는 AssumeRole을 통해 역할 전환)

AWS Config

AWS 리소스의 구성 변경 사항을 추적하고, 규정 준수 상태를 평가하는 서비스다.

  • 목적:
    - 모든 리소스의 상태와 변경 이력을 기록하고, 규칙 기반으로 컴플라이언스(준수 여부) 모니터링
  • 적용 방식:
    - 리전별로 활성화 필요 (Control Tower에서는 자동 구성)
    - Config Rule을 설정하여 리소스가 정책을 따르는지 평가

SCP (Service Control Policy)

AWS Organizations의 기능으로, 조직 계정(OU 또는 루트)에 적용되는 서비스 접근 제어 정책이다.

  • 목적 :
    - 조직 내 계정이 사용할 수 있는 AWS 서비스 또는 작업을 허용하거나 제한
  • 적용 대상:
    - 계정(Account), 조직 단위(OU)
  • 효과:
    - 특정 계정에서는 S3접근을 제한하여 계정 차원에서 접근하지 못하게 할 수 있다. (IAM 정책보다 상위 개념)

RCP (Region Control Policy)

AWS Control Tower 전용 정책으로, 어떤 리전을 사용할 수 있는지 제어한다. 내부적으로 SCP 기반으로 동작하지만, Control Tower에서 관리하기 쉽게 만든 추상화

  • 목적:
    - 조직 내 계정이 사용할 수 있는 AWS 리전을 제한하기 위해 사용
  • 적용 대상:
    - 조직 단위(OU)
  • 효과:
    - 지정한 리전 외의 리전은 모든 서비스에 대해 사용 불가능하게 차단

CfCT 구성 요소

manifest.yaml 파일 역할

manifest.yamlCfCT 에서 어떤 리소스를, 어떤 방식으로, 어떤 조건에 따라 배포할지를 정의하는 중앙 설정 파일이다.
이 파일의 핵심 섹션 중 하나가 resources:이며, 여기에 여러 리소스 항목을 정의하여 배포한다.

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_targetsOU 이름 또는 계정 ID, 계정 별칭
parameters템플릿에 전달할 매개변수 리스트
export_outputsOutputs로 생성된 값을 SSM 파라미터로 저장
regions리소스를 배포할 리전 목록

더 자세한 리소스에 대한 설명은 CfCT 매니페스트 파일의 리소스 섹션 참고하여, 작성을 진행하면 된다.

CfCT 적용

Architecture


Customizations for AWS Control Tower(CfCT) 는 AWS Control Tower와 여러 서비스를 결합하여, 멀티 계정 환경에 조직 맞춤형 리소스를 자동으로 배포할 수 있도록 지원하는 솔루션이다.
AWS Control Tower와 CfCT(Customizations for AWS Control Tower) 를 활용하여 조직의 IAM RolePermissionSetSCPAWS Config Rule 등을 GitOps 방식으로 자동 배포하는 구조이다.

Cloudformation 적용 전 확인 사항

  1. 원격 저장소 여부
    • 기본 저장소로 S3를 사용한다면 문제가 없지만, 사용이 불편하여 Github, BitBucket 등 외부 저장소와 연결하여 사용하는 것을 추천
  2. CfCT Cloudformation Parameter 설정
    • Cloudformation에서 파라미터 입력 시 Github 관련 변수 값을 정확하게 입력
    • 아래의 값을 참고하여, 생성을 진행
  3. 최초 배포 시 Error 발생 여부
    • 작성자가 제공한 Github 저장소를 먼저 Clone 후 Cloudformation template 배포 시 원격 저장소에 manifest, template 파일이 존재하여 성공을 하지만, 원격 저장소에 파일이 없다면 에러가 발생

Parameter 설명

Configuration ParameterDefault 값설명
ARN of the Code Connection코드 연결에 사용할 리소스 ARN
GitHub User or Organizationgit-usernameGitHub 저장소를 소유한 사용자
GitHub Repository Namecustom-control-tower-configurationCustom Control Tower 구성이 포함된 GitHub 저장소의 이름
GitHub Branch NamemainCustom Control Tower 구성이 포함된 GitHub 저장소의 브랜치 이름

Github 연결


개발자 도구 > 설정 > 연결
Github를 통한 관리를 한다면 위와 같은 경로에서 먼저 생성을 진행한다. Github 소스 설정


연결이 끝났다면, 작성자는 custom-control-tower-configuration 이름의 Private 레포지토리만 접근을 가능하도록 구성하였다.

CfCT Code 구조

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에 업로드 하였으며 필요 시 참고하면 된다.

  • manifest.yaml: 전체 배포/구성 매니페스트 파일
  • config/: AWS Config 관련 템플릿 파일
  • iam/: AWS IAM 역할 관련 템플릿 파일
  • permissionsets/: AWS Identity Center Permission Set 및 관련 정책 템플릿
  • scp/: AWS Organization Service Control Policy 파일
  • README.md: 프로젝트 설명 파일

manifest.yaml

프로젝트에서 배포할 리소스, 정책, 권한세트, 역할 등을 정의하는 매니페스트
각 리소스별로 파일 경로, 배포 방식(StackSet, SCP 등), 대상 OU/계정, 파라미터, 리전 등을 지정할 수 있다.

  • 주요 항목:
    - Preventive SCP, Config Rule, IAM Role, Permission Set Policy, Permission Set, Permission Set Assignment 등
    - 각 리소스별로 실제 템플릿 파일 경로와 배포 대상, 파라미터를 명시

config/rule-accesskeys-rotated.template

배포할 계정에서 AWS Config의 커스텀 규칙을 정의하는 CloudFormation 템플릿
이 규칙은 활성화된 액세스 키가 지정된 기간(기본 90일) 내에 교체 되었는지 점검하는 Rule을 정의하였다. maxAccessKeyAge 파라미터로 최대 허용 일수를 지정할 수 있다.

  • 주요 리소스:
    - 규칙 이름: ACCESS_KEYS_ROTATED
    - AWS에서 제공하는 ACCESS_KEYS_ROTATED 식별자를 사용

iam/role-ec2-describe.template

배포할 계정에서 EC2 인스턴스에 대한 조회(Describe) 권한만을 가진 IAM Role을 정의하는 CloudFormation 템플릿

  • 주요 리소스:
    - EC2 서비스가 이 역할을 Assume(가정)할 수 있도록 설정
    - 인라인 정책으로 ec2:Describe* 권한을 전체 리소스에 허용

scp/preventive-controls.json

마스터 계정에서 SCP로 설정으로 조직 내에서 특정 위험한 작업(예: S3 퍼블릭 액세스 차단 해제, Glacier/Vault 삭제, KMS 키 삭제 등)을 방지하는 정책

  • 주요 정책:
    - S3 퍼블릭 액세스 차단 해제 금지
    - Glacier 아카이브/볼트 삭제 금지
    - KMS 키 삭제/스케줄 삭제 등 금지

permissionsets/permissionset-ec2-describe-assignment-012345678901.template

마스터 계정에서 특정 계정(012345678901)의 사용자에게 EC2 Describe 권한을 부여하는 Permission Set Assignment 템플릿

  • 주요 리소스:
    - SSO 인스턴스, Permission Set ARN, 사용자, 계정 ID를 파라미터로 받아 할당
    - Permission Set ARN은 ImportValue로 외부에서 가져옴

permissionsets/permissionset-ec2-describe.template

마스터 계정에서 EC2 Describe 권한과 ReadOnlyAccess 권한을 가진 Permission Set을 정의하는 CloudFormation 템플릿

  • 주요 리소스:
    - ReadOnlyAccess 관리형 정책과, 직접 정의한 policy-ec2-describe(커스텀 정책)를 함께 부여
    - SSO 인스턴스 ARN 파라미터화

permissionsets/policy-ec2-describe.template

배포할 계정에서 EC2 Describe 권한만을 가진 커스텀 IAM Policy를 정의하는 CloudFormation 템플릿

  • 주요 리소스:
    - ec2:Describe* 액션을 전체 리소스에 허용

배포


git을 통해 원격 저장소에 업로드를 하게 된다면, CodePipeline이 트리거 되어 구성된 CI/CD가 실행이 된다.

CodePipeline 각 단계 설명

모든 buildspec은 동일한 기본 구조를 가지고 있으며, STAGE_NAME 환경변수에 따라 다른 작업을 수행한다.

1. install

  • 현재 작업 디렉터리(current)를 설정
  • manifest.yaml이 현재 디렉터리에 있으면 현재 경로를, custom-control-tower-configuration/manifest.yaml이 있으면 해당 경로를 사용
    - 소스 코드 설정에 따른 설정으로 상이함
  • 패키지 업데이트 및 필수 패키지 설치

2. pre_build

  • CfCT 스크립트 다운로드
    - 압축 해제 후 codebuild_scripts 디렉터리의 스크립트들을 현재 디렉터리로 복사
    - 단계별 의존성 설치
  • install_stage_dependencies.sh 스크립트를 실행하여, $STAGE_NAME에 맞는 의존성 설치

3. build

  • 빌드 시작 로그 출력
  • execute_stage_scripts.sh 스크립트 실행
    - STAGE_NAME, LOG_LEVEL, WAIT_TIME, SM_ARN, ARTIFACT_BUCKET, KMS_KEY_ALIAS_NAME, BOOL_VALUES, NONE_TYPE_VALUES 등 다양한 환경 변수 전달
    - 이 스크립트가 실제로 StackSet, SCP, RCP 등 각 단계별 리소스 배포, 정책 적용, 검증 등을 수행
  • 빌드 완료 로그 출력

4. post_build

  • 후처리 작업
  • 빌드 종료 로그 출력

5. artifacts

  • 산출물 지정
  • 빌드 후 생성된 모든 파일을 아티팩트로 저장

요약

  • 각 CodeBuild는 CfCT 파이프라인의 특정 리소스(스택셋, SCP, RCP 등) 배포를 담당한다.
  • 모든 단계는 STAGE_NAME에 따라 공통적으로 환경 준비 → 스크립트 다운로드/설치 → 실제 배포 스크립트 실행 → 로그 출력 순으로 동작한다.
  • 실제 리소스 배포, 정책 적용, 검증 등은 S3에서 받아온 스크립트(execute_stage_scripts.sh 등)에서 수행한다.
  • manifest.yaml 파일이 각 단계에서 어떤 리소스를 배포할지 정의하는 핵심 역할이다.

결과

SCP


마스터 계정 > AWS Organizations > 정책 > 서비스 제어 정책(SCP)

Config


배포한 계정 > AWS Config > 규칙

IAM Role & Policy


배포한 계정 > AWS IAM > 역할


배포한 계정 > AWS IAM > 정책

Permission Set

Permission Set
마스터 계정 > Identity Center > 다중 계정 권한 > 권한 세트

이슈 사항

인프라 배포 시 CodePipeline 실행이 되는데, 평균적으로 완료까지 10~12분이 걸린다. 코드를 수정하며, 약 40번의 배포를 통해 현재의 구성을 완성하였다. 이 과정에서 있었던 이슈 사항들을 정리하였다.

Cloudformation template 배포 실패 시 구버전 활용 권장

AWS 공식 Github CfCT 링크에서 최신 버전의 template 다운로드 후 배포하면 되지만, 작성자 기준 v2.8.2 에서 계속되는 템플릿에 에러가 발생하였고, 이전 버전인 v2.8.1을 사용해 해결하였다.
공식적으로 올라온 릴리즈라도, 무조건 동작하는 것은 아니라는 것을 느꼈다.

  • AWS에서 제공하는 최신 CfCT CloudFormation 템플릿(v2.8.2 기준)에서 간헐적인 배포 오류 발생
  • 이전 버전(v2.8.1)을 사용할 경우 대부분 정상적으로 배포
  • aws-solutions Github 릴리즈 확인

Master 계정 배포 시 IAM Role 구성 필요

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 리소스 Logical ID 네이밍 규칙

Cloudformation 에서 만들어질 Resources 내부 Logical ID에는 하이픈(-)이나 언더스코어(_) 등 사용이 불가하지만, Name, PolicyName 등의 값에서 -, _ 등 사용이 가능하다.

  • Resources 내부의 Logical ID-, _ 등 특수문자 사용 불가
  • 예시:
    • 가능 ✅ : PermissionsetEc2Describe
    • 불가능 ❌ : Permissionset-EC2-Describe or Permissionset_EC2_Describe
  • Name, PolicyName 등 속성 값에는 하이픈/언더스코어 사용 가능

Stack_set 간 종속 관계 처리 방법

# 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 을 통해서 값을 받아올 수 있다.

  • PermissionSetAssignment을 별도 템플릿으로 나눌 경우 Outputs + Export!ImportValue 방식 사용
  • manifest.yaml에서는 동적으로 값을 받아서 다른 template 파일로 전달이 불가

PermissionSet + Assignment을 하나의 템플릿으로 구성할 경우

만약, 여러 계정에 배포할 경우, 파라미터를 사용하지 못하고 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

IAM Identity Center 사용자 리소스는 CfCT 미지원

25년 7월 기준 CloudFormation에서는 AWS::IdentityStore::User 리소스를 지원하지 않는 것으로 확인된다.
따라서 TargetUser ID는 수동으로 생성 후 manifest.yaml에 직접 입력해야 한다.
공식 문서 : CloudFormation IdentityStore 리소스 참조

GitHub 연동 시 CodeStar Connections 설정 필요

CfCT 배포 시 GitHub 소스를 사용하려면 AWS의 CodeStar Connection 설정이 선행되어야 한다. 연결 후 CfCT CloudFormation 사용 시 입력 할 파라미터의 ConnectionArn 값을 정확히 입력해야 한다. (입력한 ARN을 가지고 IAM을 생성)

마무리

CfCT는 매우 강력한 도구이지만, 초기 진입 장벽이 높다고 느꼈다. 최초 CfCT 배포 시 사용하는 서비스가 많고, 동작에 대한 이해가 필요하기 때문에 한 눈에 파악이 꽤나 힘들었다.
또한, CloudFormation을 사용해본 경험이 부족했던 터라, 템플릿 문법에 익숙해지는 데 시간이 걸렸다. AI 도구도 활용했지만 환각(hallucination) 이슈로 인해 공식 문서를 많이 참고할 수밖에 없었다.

그럼에도 불구하고 CfCT의 구조와 배포 방식, IAM 권한 설정, Stack 간 참조 구조를 이해하고 나니, 멀티 계정을 자동화하는 데 있어 매우 유용한 프레임워크라는 점을 체감할 수 있었다.

이번에 겪은 여러 시행착오와 이슈들을 미리 알고 있다면, 배포 시간을 단축하고 오류에도 훨씬 빠르게 대응할 수 있을 것이다. 이 글이 CfCT를 처음 도입하거나 배포 환경을 고도화를 위한 분들에게 도움이 되었으면 좋겠다.

profile
조금씩 성장하는 DevOps 엔지니어가 되겠습니다. 😄

0개의 댓글