배경

SAA를 준비하면서 AWS를 모르는 동기들에게 내용을 전달하면서 나 스스로도 IAM에 대한 개념을 확실하게 하기 위해 PDF로 정리했다. 이 문서는 그 PDF를 줄 글로 재정리해보려고 한다.

처음부터 User, Policy, Group, Role을 하나씩 설명하면 이름만 많아서 헷갈릴 수 있다. 그래서 한 사람이 AWS에 가입하고, 파일을 저장하고, 서버에서도 파일을 올려보는 순서로 따라가 보려고 한다. 중간에 막히는 곳에서 필요한 개념을 하나씩 붙여보자.

Root Account
-> IAM User 생성
-> Policy로 권한 부여
-> 같은 권한을 쓰는 User는 Group으로 묶기
-> EC2의 S3 접근에는 Role 사용
-> 사람이 Role로 전환하려면 신뢰 관계 확인

이번에는 개념을 따라가기 위해 IAM User로 실습했다. 실제 사람의 AWS 접근에는 IAM Identity Center나 외부 IdP를 통한 임시 자격 증명 사용이 권장된다. AWS IAM 보안 권장 사항

AWS에 가입하면 Root Account부터

AWS에 회원가입하면 처음 만들어지는 것이 Root Account다. 계정의 주인이고, 그만큼 강한 권한을 가지고 있다.

그렇다고 매번 Root로 들어가서 작업하는 건 좋지 않다. 필요한 초기 설정을 마치고 나면 일반 작업에는 IAM User나 Role을 사용한다. 아래 실습도 필요한 IAM 관리 권한이 있으면 진행할 수 있으니, 관리 작업마다 Root로 돌아갈 필요는 없다.

User만 만들면 S3를 사용할 수 있을까?

우선 IAM User를 하나 만들었다. AWS의 관리 화면인 콘솔에도 로그인할 수 있으니 S3 버킷도 만들어보자.

S3는 파일을 저장해두는 서비스이고, 버킷은 그 파일을 담는 공간이라고 생각하면 된다.

그런데 바로 막혔다.

IAM User로 버킷을 생성할 때 s3:CreateBucket 권한이 없다는 오류

화면에 s3:CreateBucket 권한이 없다고 나온다. 새로 만든 User에는 아직 권한을 붙이지 않았기 때문이다.

User는 로그인할 때 쓰는 계정이라고 생각하면 이해하기 편하다. 콘솔이나 CLI를 사용할 수는 있지만, 로그인할 수 있다는 것과 S3를 사용할 권한이 있다는 것은 별개다. 이제 이 User가 무엇을 할 수 있는지 정해줘야 한다.

Policy를 붙여보자

이때 필요한 것이 Policy다. 어떤 작업을 허용하거나 거부할지 적어 둔 문서라고 보면 된다.

S3에서 파일은 객체라고 부른다. 특정 버킷의 파일 목록을 보고 파일을 올릴 수 있게 하려면 아래처럼 작성할 수 있다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:PutObject"
      ],
      "Resource": [
        "arn:aws:s3:::example-bucket",
        "arn:aws:s3:::example-bucket/*"
      ]
    }
  ]
}

Effect는 허용할지 거부할지, Action은 어떤 작업인지, Resource는 그 작업을 어디에 할 수 있는지를 정한다. Resource에 들어간 ARN은 AWS 리소스를 구분하는 이름이다.

위 정책은 “이 버킷의 파일 목록을 보고 파일을 올릴 수 있다”는 내용이다. 이렇게 만든 Policy를 User, Group, Role에 연결해서 권한을 준다.

실습에서는 먼저 User에 AmazonS3FullAccess를 연결했다. 그러자 버킷 생성과 조회가 가능해졌다.

그럼 여기서 버킷 목록만 못 보게 하면 어떻게 될까? 아래처럼 s3:ListAllMyBuckets를 Deny하는 정책을 추가했다.

s3:ListAllMyBuckets를 명시적으로 Deny하는 실습 정책

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Deny",
      "Action": "s3:ListAllMyBuckets",
      "Resource": "*"
    }
  ]
}

정책 추가 후 S3 콘솔에서 버킷 목록 조회가 거부된 화면

Full Access가 그대로 붙어 있는데도 버킷 목록을 볼 수 없었다. 같은 작업에 Allow와 명시적 Deny가 있으면 Deny가 우선하기 때문이다.

여기서 막은 건 계정에 있는 버킷들의 목록이다. 특정 버킷 안의 객체 목록을 보는 s3:ListBucket, 파일을 읽는 s3:GetObject는 각각 다른 작업이다. 이름이 비슷해서 헷갈리는데, 뒤에서 EC2로도 이 차이를 확인해본다.

User가 여러 명이면?

한 명이면 User에 Policy를 직접 붙여도 된다. 그런데 같은 S3 권한이 필요한 사람이 여러 명이라면 어떨까?

User를 만들 때마다 같은 Policy를 붙이고, 권한이 바뀌면 다시 하나씩 수정해야 한다. 꽤 번거롭다.

이럴 때 Group으로 묶는다.

여러 IAM User를 하나의 Group으로 묶은 기존 실습 도식

S3를 사용하는 User들을 Group에 넣고, Group에 S3 Policy를 연결하면 된다. 나중에 들어온 User도 해당 Group에 넣으면 같은 정책이 적용된다.

Group은 User들을 묶어서 권한을 관리하는 용도다. Group 자체로 로그인하거나 Role로 전환하는 건 아니다.

이번에는 EC2에서 S3에 파일을 올려보자

여기까지는 사람이 콘솔에 들어가서 작업했다. 이번에는 EC2를 하나 만들고, 그 안에서 S3에 파일을 올려보려고 한다. EC2는 AWS에서 가상 서버를 만들어 사용하는 서비스다.

실습에서는 EC2FullAccess를 가진 User로 인스턴스를 만들었다. 그런데 EC2를 만든 User에게 권한이 있다고 해서, 인스턴스 안에서 실행하는 프로그램도 그 권한을 받는 건 아니다. S3에 파일을 올리는 건 EC2 안의 프로그램이다.

그러면 EC2에도 User를 붙여줘야 할까?

이럴 때 사용하는 것이 Role이다. Role에 S3 Policy를 연결하고 EC2에서 사용하게 하면, 프로그램은 Role의 임시 자격 증명으로 S3에 접근할 수 있다. EC2에서는 instance profile을 통해 이 자격 증명을 제공한다.

목록 조회와 업로드만 허용하기

EC2에는 S3 전체 권한을 주지 않고, after-policy 버킷의 객체 목록 조회와 업로드만 허용해봤다. 다운로드에 필요한 s3:GetObject는 넣지 않았다.

after-policy 버킷의 ListBucket과 객체 PutObject만 허용한 원래 실습 정책

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::after-policy"
    },
    {
      "Effect": "Allow",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::after-policy/*"
    }
  ]
}

같은 버킷을 쓰는데 Resource가 두 가지다. ListBucket은 버킷을 대상으로 하고, PutObject는 그 안의 객체를 대상으로 하기 때문이다. 파일을 올릴 수 있는 범위에는 /*가 붙는다.

이 put-s3 정책을 put-s3-role-for-ec2에 연결하고, 해당 Role을 EC2에 연결했다.

EC2 인스턴스에 put-s3-role-for-ec2가 연결된 실습 화면

이제 인스턴스에서 직접 확인해보자. 아래 결과 이미지는 원래 터미널 화면에서 호스트 주소가 들어간 프롬프트만 제외했다.

모든 버킷 목록은 볼 수 없다

aws s3 ls

첫 명령부터 AccessDenied가 나왔다. 계정의 모든 버킷 목록을 보는 s3:ListAllMyBuckets는 허용하지 않았다.

지정한 버킷 안의 객체 목록은 볼 수 있다

aws s3 ls after-policy

이번에는 boring 객체가 보였다. after-policy 버킷에 대한 s3:ListBucket은 허용했기 때문이다. 같은 대상을 S3 URI로 적으면 aws s3 ls s3://after-policy/다.

파일 업로드도 된다

aws s3 cp HelloWorld.txt s3://after-policy/HelloWorld.txt

HelloWorld.txt도 정상적으로 올라갔다. after-policy/*에 s3:PutObject를 허용한 결과다.

그런데 다운로드는 안 된다

aws s3 cp s3://after-policy/boring .

같은 버킷인데 다운로드는 실패했다. 출력에는 HeadObject 호출에서 403 Forbidden이 발생했다고 나온다. 이 정책에는 객체를 읽는 s3:GetObject가 없다.

실행한 작업관련 Action당시 결과
모든 버킷 목록 조회s3:ListAllMyBuckets실패
after-policy의 객체 목록 조회s3:ListBucket성공
HelloWorld.txt 업로드s3:PutObject성공
boring 다운로드s3:GetObject실패

S3에 접근할 수 있다고 해서 모든 작업이 되는 건 아니다. 이번에는 원하는 버킷에서 목록을 보고 업로드하는 것까지만 허용한 셈이다.

사람도 Role을 사용할 수 있다

Role을 EC2에 연결했으니 서비스에서만 쓰는 것처럼 보일 수 있다. 하지만 사람도 Role로 전환해서 작업할 수 있다. 다른 계정의 사용자나 외부 인증을 거친 사용자도 Role을 사용할 수 있다.

Role을 설명할 때는 완장에 비유했다. 평소에는 자신의 권한으로 작업하다가, 필요한 작업을 할 때 잠깐 다른 Role의 완장을 차는 식이다.

이번에는 EC2 권한만 가진 User가 S3를 사용하게 해보자. access_with_s3_rds라는 Role을 만들고 AmazonS3FullAccess와 AmazonRDSFullAccess를 연결했다. 그리고 신뢰 정책을 수정한 뒤 User에서 Switch Role로 전환했다. 신뢰 정책 이야기는 바로 다음에서 이어간다.

S3와 RDS 권한을 가진 Role로 전환한 뒤 EC2 콘솔에서 API 오류가 나타난 실습 화면

전환하고 나니 원래 사용하던 EC2 콘솔에서 여러 API 조회가 막혔다. 대신 S3에서는 boring과 앞에서 올린 HelloWorld.txt가 보였다.

Role 세션으로 S3의 boring과 HelloWorld.txt를 조회한 실습 화면

여기서 완장 비유에 한 가지를 덧붙여야 한다. Role로 전환하면 원래 User의 권한에 Role의 권한이 더해지는 게 아니다. 전환한 Role의 권한으로 작업한다. 그래서 EC2 권한은 사용할 수 없고 S3 권한은 사용할 수 있었다.

RDS Policy도 연결했지만, 당시 자료에는 RDS 작업 결과가 남아 있지 않다. 화면으로 확인한 건 EC2와 S3다.

Role 전환을 끝내고 원래 User로 돌아오면 어떻게 될까?

원래 IAM User로 돌아온 뒤 s3:ListBucket 권한 부족으로 객체 조회가 거부된 화면

다시 S3 객체 목록을 볼 수 없었다. User의 정책이 삭제된 것이 아니라 원래 User의 권한으로 돌아온 것이다. 잠깐 사용하던 완장을 내려놓았다고 보면 된다.

Role을 알아도 아무나 전환할 수는 없다

Role 이름과 계정 정보를 알고 있다고 해서 바로 사용할 수 있는 건 아니다. 그 Role을 누가 사용할 수 있는지도 정해야 한다. 이렇게 Role을 사용하는 작업을 AssumeRole이라고 부른다.

이 설정이 Trust Relationship, 신뢰 관계다. Role의 신뢰 정책에서 특정 AWS 서비스나 사용자, 계정 등을 허용한다. Role에 붙인 권한 정책이 “무슨 작업을 할 수 있는지”라면, 신뢰 정책은 “누가 이 Role을 사용할 수 있는지”에 해당한다.

실습에서도 처음에는 Role 전환이 실패했다. 계정 ARN을 신뢰하는 상태였고, 같은 계정의 ec2-user ARN을 Principal에 추가한 뒤에는 전환할 수 있었다.

8-1. 원래 자료에서 root ARN을 잘못 이해했다

당시에는 arn:aws:iam::ACCOUNT_ID:root가 Root 사용자만 허용하므로 User ARN을 추가해야 한다고 설명했다. 이 부분은 잘못 설명했다. 해당 Principal은 그 AWS 계정에 권한 위임을 허용한다는 뜻이다. “양쪽에서 무조건 Allow해야 한다”는 설명도 모든 경우에 적용되지는 않는다.

신뢰 정책에서 Principal은 Role을 사용할 주체를 뜻한다. 같은 계정의 IAM User를 허용하는 경우에는 아래 두 방식을 구분하면 된다.

신뢰 정책의 PrincipalUser 쪽에서 필요한 설정
arn:aws:iam::ACCOUNT_ID:root대상 Role ARN에 대한 sts:AssumeRole 허용
arn:aws:iam::ACCOUNT_ID:user/ec2-user같은 계정의 User를 직접 허용하면 별도의 identity-based Allow 없이도 가능

User를 직접 허용하는 신뢰 정책은 아래와 같다. ACCOUNT_ID는 계정 ID로 바꾸는 자리다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::ACCOUNT_ID:user/ec2-user"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

다만 처음 실패했을 때 User에 붙어 있던 전체 정책은 자료에 남아 있지 않다. 계정을 신뢰하는 방식이었다면 User의 sts:AssumeRole 허용 여부를 확인해야 하지만, 그 설정 때문에 실패했다고 단정할 수는 없다. 명시적 Deny나 다른 권한 제한도 함께 봐야 한다. AWS AssumeRole 문서

원래 실습에서는 계정 ARN을 남겨 둔 채 User ARN을 추가했다. 특정 User만 허용하려는 목적이라면 계정 전체에 대한 위임도 계속 유지할지 확인할 필요가 있다.

계정 Alias 예시도 바로잡기

원래 자료에서는 두 계정의 Alias와 Role 이름이 우연히 같아지는 상황을 예로 들었다. 하지만 계정 Alias는 같은 AWS 파티션 안에서 유일하므로 그 전제는 성립하지 않는다. 신뢰 정책은 Alias 충돌을 막는 설정이 아니라 Role을 사용할 주체를 정하는 설정이다. AWS 계정 Alias 문서

마무리

처음에는 User에 권한을 붙여서 S3를 사용했다. 같은 권한을 여러 User에게 줄 때는 Group으로 묶었고, EC2에서 파일을 올릴 때는 Role을 사용했다. 사람도 필요한 작업을 할 때 Role로 잠깐 전환할 수 있었다.

권한이 막힐 때도 이 흐름을 따라가면 된다. 지금 작업하는 게 User인지 Role인지, 필요한 Action을 허용했는지, Role을 사용할 수 있는 주체인지 하나씩 확인해보자.

참고