IAM 사용자의 Access Key나 콘솔 비밀번호가 새어 나가면 그걸 가진 사람은 어디서든 AWS에 요청을 보낼 수 있다. 권한을 아무리 잘게 나눠도 “어디서 보낸 요청인가"는 따지지 않는다. 그래서 정해 둔 IP에서만 콘솔과 CLI를 쓸 수 있도록 IAM 정책을 하나 만들어 사용자에게 붙였다.

허용하는 대신 나머지를 거부한다

IAM 콘솔에서 정책 > 정책 생성 > JSON 탭으로 들어가 아래 내용을 넣고 정책을 만든다. 그다음 대상 사용자의 권한 목록에 이 정책을 추가하면 끝이다.

{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Deny",
        "Action": "*",
        "Resource": "*",
        "Condition": {
            "NotIpAddress": {
                "aws:SourceIp": [ "{허용 대상 IP}/32" ]
            },
            "Bool": {
                "aws:ViaAWSService": "false"
            }
        }
    }
}

{허용 대상 IP} 자리에 접속을 허용할 IP를 넣는다. /32는 그 주소 하나만이라는 뜻이고, 사무실 대역처럼 여러 주소를 열어야 하면 203.0.113.0/24 같은 CIDR로 적거나 배열에 항목을 더한다.

읽는 법은 이렇다. 요청을 보낸 IP(aws:SourceIp)가 목록에 없고(NotIpAddress), 그 요청이 AWS 서비스가 대신 보낸 것이 아니라면(aws:ViaAWSService: false), 모든 리소스에 대한 모든 Action을 거부한다.

“허용 IP에서만 Allow"가 아니라 “허용 IP 밖이면 Deny"로 쓴 데는 이유가 있다. IAM에서 명시적 Deny는 다른 어떤 Allow보다 먼저 적용된다. 사용자에게 AdministratorAccess 같은 넓은 권한이 붙어 있어도 이 정책 하나가 IP 조건으로 전부 덮는다. 기존 권한 정책은 손대지 않고 정책 하나만 더 붙이면 되는 것도 이 방식의 장점이다.

aws:ViaAWSService 조건이 없으면 곤란한 일이 생긴다. CloudFormation처럼 사용자의 요청을 받아 AWS 서비스가 다른 서비스를 대신 호출하면 그 호출은 내 IP가 아니라 AWS 쪽에서 나간다. 이 조건으로 그런 요청은 거부 대상에서 뺀다.

적용 후 콘솔에서 보인 것

이미 콘솔에 로그인해 있던 세션에서는 바로 달라지지 않았다. 로그아웃하고 다시 들어간 뒤부터 정책이 제대로 걸렸다.

허용하지 않은 IP에서도 콘솔 로그인 자체는 된다. 대신 로그인한 뒤 어떤 서비스 화면을 열어도 리소스를 보거나 건드릴 수 없다. 콘솔 화면도 결국 뒤에서 서비스 API를 부르는데, 그 호출이 전부 거부되기 때문이다.

EC2 대시보드는 로드 밸런서, 보안 그룹, 볼륨 같은 리소스 칸마다 “API 오류"를 띄운다.

인스턴스 목록에는 You are not authorized to perform this operation.이 뜬다. CLI도 같은 정책을 거치므로 허용하지 않은 IP에서 보낸 요청은 권한 오류로 끝난다.

주의 사항

  • 허용 IP를 잘못 넣으면 정책을 붙인 사용자는 자기 자신도 막힌다. 이 정책을 뗄 수 있는 다른 관리자 계정을 남겨 두고 적용한다.

  • VPC 엔드포인트를 거쳐 들어오는 요청에는 aws:SourceIp가 공인 IP로 잡히지 않는다. EC2 안에서 VPC 엔드포인트로 AWS API를 부르는 사용자라면 이 정책에 막힐 수 있다.