🔥 IAM Access Analyzer로 과권한을 탐지하는 법

1194자
14분

정책을 열심히 짰다. User, Group, Role: 세 가지 주체의 차이를 나눴고, Policy: JSON으로 권한을 표현하는 법의 Principal·Resource·Condition도 다 맞췄고, 정책 평가 흐름: Allow와 Deny가 만나면도 머릿속에 넣었다. 그럼 권한 설계는 끝난 걸까.

나는 한동안 그렇게 생각했다. 리뷰에서 s3:*s3:GetObject로 줄였고, Resource: "*"를 버킷 ARN으로 좁혔다. 그러면 됐다고 믿었다. 그러다 어느 시점에 깨달았다. 정책을 쓸 때 좁히는 일과 정책이 지금도 좁은지 계속 확인하는 일은 서로 다른 작업이었다.

AWS는 그 두 번째 문제를 Access Analyzer로 푼다.

과권한: 리뷰를 통과한 정책도 시간이 지나면 권한이 늘어난다

과권한(over-privilege)은 두 갈래로 나뉜다. 하나는 처음부터 넓게 준 경우. 다른 하나는 처음엔 타이트했는데 시간이 지나면서 넓어진 경우다. 후자가 훨씬 흔하다.

예를 들어, 6개월 전에 특정 Lambda가 DynamoDB 테이블에 쓰도록 Role을 붙였다. 그 뒤로 Lambda 코드는 두 번 바뀌었고 누군가 테이블 하나를 다른 테이블에 합쳤다. Role의 정책은? 한 번도 건드리지 않았다. 그래서 지금 그 Role은 예전에 쓰던 dynamodb:UpdateItem 권한을 여전히 쥐고 있지만, 실제로는 한 번도 호출되지 않는다. 이건 '잘못 쓴 정책'이 아니라 '관리되지 않은 정책'이다.

외부로 열려 있는 과권한도 같은 패턴이다. 어느 날 외부 계정에 S3 버킷을 잠깐 공유했다. 작업이 끝났다. 버킷 정책의 그 문장은 지워지지 않은 채 남았다. 버킷 이름을 기억하는 사람이 얼마 뒤 하나둘 회사를 떠나면, 그 문장은 아무도 모르는 과권한이 된다.

AWS가 IAM Access Analyzer를 만든 이유가 여기에 있다. 처음 짠 정책이 정확한지를 보는 도구가 아니라, 이미 붙어 있는 권한이 지금도 타당한지 계속 확인하는 도구다.

Access Analyzer의 세 종류: External, Internal, Unused

IAM Access Analyzer 세 종류 비교 다이어그램. External은 외부에서 Zone of Trust 안으로 들어오는 경로, Internal은 내부 Principal에서 특정 리소스로 가는 경로, Unused는 IAM Role에 붙은 권한 중 실제로 쓰이지 않는 액션을 각각 다른 관점에서 본다

한 이름 아래 사실상 서로 다른 Analyzer 세 개가 들어 있다. 2019-12-02에 External Access 하나로 시작했고, 2023-11-26에 Unused Access가 붙었다. Internal Access는 2025-06-17에 뒤늦게 합류한 형제다. 각자 보는 것이 다르다.

External Access Analyzer: Zone of Trust 바깥 접근 경로를 찾는다

Analyzer를 만들 때 Zone of Trust를 정한다. 보통은 "이 조직 전체" 아니면 "이 계정 하나"다. 이 경계 안의 Principal이 하는 접근은 신뢰된 접근으로 친다. Analyzer는 리소스-기반 정책(S3 버킷 정책, KMS 키 정책, Lambda 리소스 정책 등)을 읽어서, Zone 바깥의 누군가가 접근할 수 있는 경로를 전부 찾아낸다.

"바깥의 누군가"는 다른 AWS 계정일 수도 있고, Root 사용자일 수도 있고, 페더레이션된 사용자일 수도 있고, 익명 사용자일 수도 있다. 마지막이 제일 무섭다. Principal: "*"로 열어놓은 S3 버킷이 Analyzer의 단골 손님이다.

스캔 대상 리소스 타입은 꾸준히 늘어왔다. 2026년 4월 기준으로 S3 버킷·S3 Directory Bucket, IAM Role, KMS Key, Lambda 함수·레이어, SQS, Secrets Manager, SNS, EBS 볼륨 스냅샷, RDS DB 스냅샷·DB 클러스터 스냅샷, ECR, EFS, DynamoDB 테이블·스트림을 본다. 중요한 건 리소스 기반 정책을 가진 리소스만 대상이라는 점이다. IAM User의 Identity-based 정책은 External Access Analyzer의 대상이 아니다.

요금은 0원이다. 단, 리전마다 하나씩 만들어야 한다. ap-northeast-2에서 Analyzer를 켰다면 us-east-1의 버킷은 보지 못한다.

Unused Access Analyzer: 쓰지 않는 권한을 찾는다

이쪽은 완전히 다른 질문에 답한다. "이 Role이 붙어 있는 권한 중에, 최근에 실제로 쓴 건 뭐고 안 쓴 건 뭔가?"

내부적으로는 CloudTrail의 last-accessed 데이터를 본다. Role/User가 마지막으로 어느 서비스·어느 액션을 호출했는지, 어떤 Access Key가 마지막으로 사용된 게 언제인지를 추적한다. 그러고 나면 세 종류의 finding이 나온다.

  • 아예 쓰이지 않는 Role 자체
  • Role은 쓰이는데 그 안의 특정 Action이 안 쓰이는 경우
  • Access Key·콘솔 비밀번호가 오래 방치된 경우

이 분석 도구는 External과 달리 리전 개념이 없다. IAM은 글로벌 서비스니까, 계정(또는 조직)당 하나만 켜면 된다. 대신 공짜가 아니다. 분석 대상 IAM Role 수와 User 수를 합한 값에 $0.20/월을 곱한 만큼 요금이 붙는다(2026-04 기준). 조직 전체에 Role이 5,000개라면 월 $1,000. 시험 삼아 켜기 전에 iam:ListRoles로 대상 규모를 먼저 세어보는 게 습관이 된 이유다.

Internal Access Analyzer: 내부 과도 접근 경로를 찾는다

External이 "바깥에서 들어올 수 있는 길"을 찾는다면, Internal은 "안에서 특정 리소스에 접근할 수 있는 Principal"을 역으로 나열한다. S3 버킷 하나를 지정하면 그 버킷에 닿는 내부 Role·User 목록을 뽑아준다. 대상 리소스 타입은 External보다 좁다. 2026년 4월 기준으로 S3 버킷·S3 Directory Bucket, RDS DB 스냅샷·DB 클러스터 스냅샷, DynamoDB 테이블·스트림까지다.

"최소권한을 실제로 만들었는가"를 리소스 관점에서 검증하는 용도다. Role에서 권한을 줄이는 건 Unused의 일이고, "이 테이블에 원래 접근 권한이 있어야 할 사람만 있는가"는 Internal의 일이다.

Custom Policy Check: 정책이 머지되기 전에 막는 세 개의 API

Custom Policy Check CI 파이프라인 흐름도. PR이 열리면 세 개의 API인 CheckNoNewAccess, CheckAccessNotGranted, CheckNoPublicAccess가 순서대로 돌아가며 하나라도 FAIL이면 머지가 차단되고, 모두 PASS면 초록 병합 버튼이 열린다

지금까지는 이미 배포된 정책을 검사했다. Custom Policy Check는 방향이 반대다. 아직 배포되지 않은 정책을 CI에서 검사해서, 위험한 변경이 머지되기 전에 막는 쪽이다.

세 개의 API가 있다. 앞의 둘은 2023-11-26에 같이 출시됐고, CheckNoPublicAccess는 2024-06-11에 뒤늦게 합류했다.

  • CheckNoNewAccess: 기존 정책과 새 정책을 둘 다 넣으면, 새 정책이 새로운 접근을 만드는지를 PASS/FAIL로 돌려준다. 정책을 '조이는' 변경은 통과하고, '넓히는' 변경은 걸린다. PR diff 단계에 거는 게 가장 자연스럽다.
  • CheckAccessNotGranted: 후보 정책이 특정 Action(또는 Action+Resource 조합)을 허용하지 않는지를 검사한다. iam:PassRole을 절대 허용하지 않아야 하는 Role이라면, 이걸 CI에 걸어두면 된다.
  • CheckNoPublicAccess: 리소스 기반 정책이 퍼블릭 접근을 만드는지 검사한다. 대상 리소스 타입은 S3(일반·Directory·Outposts·Access Point), SNS, SQS, KMS, Lambda, Secrets Manager, DynamoDB, EFS, OpenSearch, Kinesis, API Gateway REST API, Backup Vault 등 20여 종이 걸린다(전체 목록은 공식 API 레퍼런스 참조).

이 세 개는 호출당 과금이다. 배포된 분석기와 별개로, CI가 돌 때마다 API 요청 건수만큼 요금이 붙는다. 단가는 크지 않지만, 모노레포에서 PR마다 수십 번씩 부를 거면 예산 계산에 넣어야 한다.

덤으로 Policy Generation이라는 기능도 있다. CloudTrail 로그 + 기간을 주면 그 Role이 실제로 호출한 액션만 모아서 least-privilege 정책 초안을 뽑아준다. 공짜이고, Unused Access의 결과를 '다음 정책'으로 옮기는 다리 역할을 한다.

언제 Access Analyzer로 해결되지 않는가

좋은 도구는 잘 작동하는 범위가 분명하다. Access Analyzer는 세 가지 지점에서 조용히 무력하다.

첫째, 런타임 행동은 보지 않는다. 정책상 kms:Decrypt가 허용돼 있고 실제로 쓰고 있다면, Unused Access는 '쓰고 있음'으로 리포트한다. 그 호출이 정말 정당한 복호화였는지는 묻지 않는다. 그건 CloudTrail + GuardDuty + 애플리케이션 로그의 영역이다.

둘째, data-plane 권한은 읽지 못한다. S3 버킷 정책에 퍼블릭 Get이 열려 있으면 External이 잡는다. 그런데 버킷 안 특정 객체만 퍼블릭인 ACL 설정이나 Presigned URL로 뿌려둔 링크는 정책 문장이 아니니, 이 도구가 다루는 범위 밖이다.

셋째, 애플리케이션 로직이 만드는 과권한은 모른다. 한 Role이 sts:AssumeRole로 다른 Role을 계속 거쳐 가는 연쇄 구조라면, 정적 분석으로는 "이 Role이 실제로 어디까지 닿는가"를 다 따라가기 어렵다. 여기서부터는 정책 평가 흐름: Allow와 Deny가 만나면에서 이야기한 크로스 계정 교집합 규칙과, 사람이 그린 흐름도가 같이 있어야 한다.

"관리형"이라는 단어에 속으면 안 된다. 이 도구는 계속 상태를 보여주는 대시보드를 주지만, 발견 항목을 누가 보고 누가 조치하느냐는 여전히 조직의 루틴에 달려 있다. 켜놓기만 하고 아무도 발견 항목을 처리하지 않으면 켜지 않은 것과 크게 다르지 않다.

마무리

관점 하나가 바뀌었다. 예전엔 IAM을 '정책 한 줄을 어떻게 짜느냐'의 문제로만 봤다. 지금은 '정책이 시간 위에서 어떻게 넓어지는가'를 같이 보는 쪽에 와 있다. External·Internal·Unused 세 종류의 Access Analyzer는 그 시간축을 나눠서 보는 도구고, Custom Policy Check는 시간축이 시작되기 전, 그러니까 머지 이전을 틀어막는 도구다.

이 섹션은 개별 계정을 넘어 조직 단위로 권한을 제어하는 쪽으로 흘러간다. Organizations와 SCP는 권한 경계보다 한 단계 위에서 권한 천장을 건다.

참고 자료

YouTube 영상

채널 보기
벡터의 정의와 덧셈 연산 | 선형대수학
마지막편, 트라이 노드를 50% 이상 줄이는 방법? 압축 트라이 성능 분석 | Trie 자료구조 이야기
트라이(Trie)에서 단어를 삭제하는 방법 | Trie 자료구조 이야기
AI를 위한 선형대수학 - 소개 | 선형대수학
AI는 데이터를 어떻게 분류할까? 벡터의 거리와 KNN 알고리즘 | 선형대수학
스칼라 곱셈과 내적의 기하학적 의미 | 선형대수학
트라이(Trie)를 이용한 자동 완성 알고리즘 | Trie 자료구조 이야기
AI 추천 시스템의 원리, 벡터 사이의 각도와 코사인 유사도 | 선형대수학