ecsimsw

AWS 자원 생성 시 태깅을 강제하는 IAM Policy 본문

AWS 자원 생성 시 태깅을 강제하는 IAM Policy

JinHwan Kim 2023. 3. 13. 19:06

AWS 자원 생성 시에 태깅을 강제하자

AWS의 비용을 계산하고 유휴 자원을 관리하려고 한다. 문제는 어디에 사용되었는지 모르는 레거시 자원들과 여러 팀이 얽힌 프로젝트, 퇴사자들로 이 자원이 현재 사용되고 있는지, 바로 삭제해 버려도 되는지 자원의 출처와 사용처를 알기 어려웠다.

 

이런 배경 아래, 자원 생성하는 과정에서 태깅을 강제하는 정책을 고민했고 이를 IAM Policy로 풀었던 경험과 필요 개념, 간단한 팁을 공유하고자 한다.

 

0. 요구 사항 

0. 기존 Policy 정책/사용자 그룹은 가능한 건드리지 않도록 한다. 

1. 사용자는 정책으로 지정한 자원을 생성하는 경우 태그를 붙여야만 한다.

2. 단순히 태그의 Key 값만 일치하면 생성을 허용할 수 있는 조건이 있을 수 있다.

3. 태그의 Key에 미리 지정된 Values 만이 Value가 되어야 생성을 허용하는 조건이 있을 수 있다.

 

1. 개념 

1-1) IAM policy 기본 구조 

 

AWS의 IAM policy 는 다음과 같은 꼴을 갖는다.

 

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowIfTeamKeyExist",
            "Effect": "Allow",
            "Action": [
               "ec2:RunInstances",
            ], 
            "Resource": "arn:aws:ec2:*:*:instance/*",
            "Condition": {
                "ForAnyValue:StringEquals": {
                    "aws:TagKeys": "team"
                }
            }
        }
    ]
}

 

이는 아래와 같이 읽을 수 있다. 

 

1. Sid : 해당 Statement의 이름을 말한다.

2. Effect : 이 정책이 특정 작업을 허용하는 Statement인지, 작업을 거부하는 Statement인지를 결정한다.

3. Action : 어떤 작업에서 이 정책이 적용될 것이지 결정한다.

4. Resource : 이 정책이 적용될 자원을 말한다. 

5. Condition : 상태에 만족하는 경우에 Statement가 유효하다.

 

예를 들면 위 정책은 EC2의 instance 자원이 'Run Instance' 이벤트에서 Tag key로 'team'을 갖고 있는지를 검사한다. team이라는 태그를 갖고 있는 경우에만 EC2 인스턴스를 실행할 수 있게 된다.

 

아래 AWS 공식 docs는 tag 정책으로 작업을 허용하는 규칙을 만드는 방법을 설명한다. 아래 문서 정도면 쉽게, 연습하기 수준으로 개념을 익힐 수 있을 것이다. 

 

https://aws.amazon.com/ko/premiumsupport/knowledge-center/iam-policy-tags-restrict/

 

 

1-2) Encoded Authorization failure Message Decoding

 

 

IAM policy로 리소스 제어에 제한이 생길 경우 다음처럼 에러 메시지가 암호화되어 출력된다. 

 

aws sts decode-authorization-message --encoded-message ${MESSAGE}

 

위 AWS CLI 명령어로 decode 할 수 있다. 이 안에는 어떤 정책 때문에, 어떤 리소스를, 어떻게 제어하려고 했는지, 즉 작업 요청한 요청 내용이 모두 표시된다.

 

Tip 1 : 허용 정책보다 제한  정책

앞선 기본 개념을 사용하면 제한을 원하는 자원이 특정 조건을 만족하는 경우에만 지정한 액션을 실행할 수 있도록 강제할 수 있다. 적은 리소스를 관리하거나 아예 새로 IAM policy를 정의하는 상황이라면 여기까지도 충분하다. 다만 그렇지 않은 경우, 예를 들면 리소스가 엄청 많거나 기존에 회사 정책에 따라 이미 많은 IAM policy가 정의되어 있다면 이 방법으로 관리하기가 쉽지 않다.

 

내 상황을 예시로 들면 기존의 태그 정책을 제외한 IAM Policy가 이미 엄청 많았다. 특정 Group이나 조건에서만 사용할 수 있는 리소스나 액션 Allow 정책이 많았고 특히 IaC (Terraform, CloudFormation, Ansible) 으로 관리되고 있는 자원의 경우 코드에서 사용되는 User role을 잘못 건들게 되면 해당 User role의 policy를 잘못 수정하게 되면 문제가 되니 기존 정책들을 건들지 않으면서 작업해야 했다.

 

이런 상황이 흔할 것이라고 생각한다. 나는 'Deny가 Allow보다 우선 순위가 높다'는 점을 이용하여 문제를 풀이했다. User나 Group에 Deny 정책으로 태그 조건을 추가하여 적용하면 기존 User Allow 정책들이나 User가 속해있는 Group의 Allow 정책을 변경하지 않고 태그 조건을 강제할 수 있게 된다. 내 경우에는 'TagRestrictionPolicy'라는 제한 정책을 만들어 이미 존재하는 사용자 그룹에 추가하거나, 'TagRestrictionGroup'이라는 새로운 UserGroup을 만들고 태그 제한 정책이 포함되어야 하는 User를 이 그룹에 추가하는 것으로 기존의 시스템을 더럽히지 않고 작업할 수 있었다.

 

 

Tip 2 : 쓸만한 예시들

이 정책을 준비하면서 사용할만한 제한 정책을 예시로 만들어 보았다. 예시에서 Action은 'ec2:RunInstances', 리소스는 'arn:aws:ec2:*:*:instance/*' 으로 고정하였는데 원하는 리소스와 액션을 추가하며 연습해 보자.

 

2-1) 여러 키를 강제해야 할 때

 

- service, purpose를 키로 하는 tag가 각각 있어야 한다.
- 다른 태그가 있어도 상관없다.

 

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowRunInstancesWithRestrictions1",
            "Effect": "Deny",
            "Action": [ "ec2:RunInstances" ],
            "Resource": [ "arn:aws:ec2:*:*:instance/*" ],
            "Condition": {
                "ForAllValues:StringNotEquals": {
                    "aws:TagKeys": "service"
                }
            }
        },
        {
            "Sid": "AllowRunInstancesWithRestrictions2",
            "Effect": "Deny",
            "Action": [ "ec2:RunInstances" ],
            "Resource": [ "arn:aws:ec2:*:*:instance/*" ],
            "Condition": {
                "ForAllValues:StringNotEquals": {
                    "aws:TagKeys": "purpose"
                }
            }
        }
    ]
}

 

 

2-2) 태그와 값까지도 범위 내에서 제한해야 할 때

 

- team을 key로 하는 tag를 갖고 있어야 한다 
- team value 값은 ["devops", "backend"] 중 하나이어야 한다.
- 다른 태그가 있어도 된다.

 

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowRunInstancesWithRestrictions1",
            "Effect": "Deny",
            "Action": [ "ec2:RunInstances" ],
            "Resource": [ "arn:aws:ec2:*:*:instance/*" ],
            "Condition": {
                "ForAllValues:StringNotEquals": {
                    "aws:TagKeys": "team"
                }
            }
        },
        {
            "Sid": "AllowRunInstancesWithRestrictions2",
            "Effect": "Deny",
            "Action": [ "ec2:RunInstances" ],
            "Resource": [ "arn:aws:ec2:*:*:instance/*" ],
            "Condition": {
                "StringNotEquals": {
                    "aws:RequestTag/team": [ "devops","backend" ] 
                }
            }
        }
    ]
}

 

 

2-3) 비슷한 유형의 태그 중 하나 이상을 강제할 때

 

- createdBy, userBy, from 중 하나는 태그로 있어야 한다.
- 다른 태그가 있어도 상관없다.

 

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowRunInstancesWithRestriction",
            "Effect": "Deny",
            "Action": [ "ec2:RunInstances" ],
            "Resource": [ "arn:aws:ec2:*:*:instance/*" ],
            "Condition": {
                "ForAllValues:StringNotEquals": {
                    "aws:TagKeys": [ "createdBy", "userBy", "from" ]
                }
            }
        }
    ]
}

 

 

2-4) 특정 태그만이 존재하도록 제한하고자 할 때 

 

- 테스트 환경의 리소스의 종료 날짜를 대충이라도 기입하길 원한다. 

-  isEternal, endDate 외에 다른 태그를 생성할 수 없다.
- isEternal, endDate가 없어도 생성은 가능하다. (확인 시 즉시 제거 가능 여부를 나타낸다)

 

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowRunInstancesWithRestrictions1",
            "Effect": "Deny",
            "Action": [ "ec2:RunInstances" ],
            "Resource": [ "arn:aws:ec2:*:*:instance/*" ],
            "Condition": {
                "ForAnyValue:StringNotEquals": {
                    "aws:TagKeys": [ "isEternal", "endDate" ]
                }
            }
        }
    ]
}

 

추가로 AWS 공식 문서에 좋은 예시들이 많다.

 

https://repost.aws/ko/knowledge-center/iam-policy-tags-deny

 

Tip 3 : 관리를 추천하는 리소스와 액션

이 정책이 적용되면 좋을 것 같은 리소스들을 찾아봤다. 일단 비싸고 자주 생성하는 자원들을 우선으로 골라봤다. 

 

RDS, S3, ELB, EC2, ECS, EFS, EKS, ES, ElastiCache, MSK

 

이 중 RDS는 콘솔에서 RDS를 생성할 때 생성과 동시에 Tag를 지정할 수 없고, S3는 Create bucket을 요청하는 요청 정보에 Tag가 포함되지 않아 위 방식으로 태깅 제한이 불가능했다.

 

(CLI나, IaC 툴을 사용하는 팀도 있으나, 콘솔로 직접 리소스를 만들거나 테스트하는 경우도 많기에 모든 경우에서 하나라도 생성이 불가능한 자원의 경우 일단 패스한다)

 

그 외 리소스의 생성 Action은 다음과 같다. 

 

ELB : create Load balancer
EC2 : create VPC, run instance
EFS : create file system
EKS : create cluster
ElastiCache : create cache cluster
MSK : kafka:CreateCluster, kafka:CreateClusterV2
OpenSearchService : es:CreateDomain

 

다음 예시는 위 자원들을 생성할 때 Team을 key로 하고 Value를 ["a", "b", "c"] 중 하나를 선택하도록 강제한 IAM Policy 예시이다.

 

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "TaggingRestriction1",
            "Effect": "Deny",
            "Action": [
                "ec2:CreateVpc",
                "ec2:RunInstances",
                "elasticloadbalancing:CreateLoadBalancer",
                "elasticache:CreateCacheCluster",
                "es:CreateDomain"
            ],
            "Resource": [
                "arn:aws:ec2:*:*:instance/*",
                "arn:aws:ec2:*:*:vpc/*",
                "arn:aws:elasticloadbalancing:*:*:loadbalancer/*",
                "arn:aws:elasticache:*:*:cluster:*",
                "arn:aws:es:*:*:domain/*"
            ],
            "Condition": {
                "ForAllValues:StringNotEquals": {
                    "aws:TagKeys": "Team"
                }
            }
        },
        {
            "Sid": "TaggingRestriction2",
            "Effect": "Deny",
            "Action": [
                "ec2:CreateVpc",
                "ec2:RunInstances",
                "elasticloadbalancing:CreateLoadBalancer",
                "elasticache:CreateCacheCluster",
                "es:CreateDomain"
            ],
            "Resource": [
                "arn:aws:ec2:*:*:instance/*",
                "arn:aws:ec2:*:*:vpc/*",
                "arn:aws:elasticloadbalancing:*:*:loadbalancer/*",
                "arn:aws:elasticache:*:*:cluster:*",
                "arn:aws:es:*:*:domain/*"
            ],
            "Condition": {
                "StringNotEquals": {
                    "aws:RequestTag/Team": [ "a", "b", "c" ] 
                }
            }
        },
        {
            "Sid": "TaggingRestriction3",
            "Effect": "Deny",
            "Action": [
                "elasticfilesystem:CreateFileSystem",
                "eks:CreateCluster",
                "kafka:CreateCluster",
                "kafka:CreateClusterV2"
            ],
            "Resource": "*",
            "Condition": {
                "ForAllValues:StringNotEquals": {
                    "aws:TagKeys": "Team"
                }
            }
        },
        {
            "Sid": "TaggingRestriction4",
            "Effect": "Deny",
            "Action": [
                "elasticfilesystem:CreateFileSystem",
                "eks:CreateCluster",
                "kafka:CreateCluster",
                "kafka:CreateClusterV2"
            ],
            "Resource": "*",
            "Condition": {
                "StringNotEquals": {
                    "aws:RequestTag/Team": [ "a", "b", "c" ] 
                }
            }
        }
    ]
}

 

나는 굳이 태그를 제거하는 제한 정책까지는 만들지 않았다. 리소스 생성 시에만 잠시 태그 하고, 그 후에 태그를 지우는 것은 자유로워 사실 완벽한 태깅 강제는 아니지만, 위 예시들과 태그 삭제 이벤트를 찾는다면 태깅을 제거하는 것에 제한 정책 역시 금방 적용할 수 있을 것이다. 

Comments