지금까지 학습한 것을 통해서 만들어볼 것을 생각해보다가 IAM 정책을 모니터링하는 프로젝트를 진행해보기로 하였다.
Lambda가 실행되었을 때 IAM 정책을 돌면서 admin 권한을 가진 유저를 찾아내고 로깅 및 이메일로 전달해주는 시스템을 만들어 볼 것이다.
추가적으로 공부 목적으로 RDS(mysql), SNS, CloudWatch, EventBridge 를 전부 얕게 실습해 볼 것이다. 중간중간 정말 별 것도 아닌 것에 굉장히 오랫동안 시간이 끌렸지만 이런 것들도 시간을 버렸다고 생각하지 않기로 했다. 간단하게 한 과정들을 적어보겠다.
RDS 생성 및 설정
-RDS는 관계형 데이터베이스를 제공하는 서비스이며 EC2 인스턴스 타입과 스토리지 EBS 타입을 내부적으로 활용한다.
사용자가 데이터베이스의 운영 체제(OS) 수준에 접근하거나 직접 관리할 수 없다.
-AWS가 운영 체제 패치, 데이터베이스 소프트웨어 업데이트, 그리고 유지 관리 작업을 자동으로 처리한다.
이외에도 Multi AZ(다른 AZ의 대기DB에 자동으로 동기화), 암호화, 자동백업, 스냅샷생성 등의 기능을 제공한다.
먼저 mysql을 엔진으로 하여 데이터베이스를 생성하였다.
엔진 버전은 최신 버전 중 가장 안정된 버전으로 선택하고 프리티어용으로 템플릿을 작성하였다.
마스터 사용자 이름 (username) 과 마스터 암호(password)를 넣어주고 기존에 사용하던 VPC에 위치시켰다.
퍼블릭 IP를 부여하여 외부에서도 접근 가능하게 하며 접근 시에는 도메인 이름(DNS)을 통해 접근한다.
RDS 와 Lambda 의 Connection에서 최대한 에러를 나게 하지 않으려면 가장 중요한 부분은 보안그룹이다.
보안그룹 내에서의 보안그룹을 소스나 타켓으로 설정할 수 있다.
RDS의 보안그룹은 람다 리소스가 속한 보안그룹 id를 소스로 하여 인바운드 규칙에 포함시킨다.
해당 보안그룹의 리소스들은 인바운드 쪽 트래픽상으로 허용된다. 즉 람다에서 오는 트래픽은 허용된다.
나는 Workbench로도 시험을 해볼 것이라서 본인 IP도 넣어주었다.
/*
참고로 보안그룹은 stateful해서 outbound로 나간 것에 대한 응답은 받는다
Outbound 규칙에서 외부로 요청을 보낼 수 있도록 허용했다면, 그에 대한 응답 트래픽(Inbound)은 자동으로 허용한다.
보안그룹에 있어서
인바운드 / 아웃바운드 설정이 매우 중요하다. 이 설정은 OR 조건으로 하나라도 성립될 경우 트래픽이 허용된다.
그리고 소스나 대상을 보안그룹으로 해놓을 경우 보안그룹의 조건을 생각하는 것이 아니라 보안그룹에 속한 리소스들을 기준으로 통신을 생각하면 된다.
보안그룹은 네트워크 인터페이스(ENI) 수준에서 작동하며 서브넷 전체에 적용되는 네트워크 ACL(NACL)과는 다르다.
보안그룹은 허용 규칙만 설정할 수 있다
*/
Workbench를 통해 자신의 IP 에서 접속되는지 확인해본다. 해당 RDS의 도메인네임을 통해 접근시킨다.
안되면 텔넷으로도 해당 도메인네임으로 입력해서 접속해보고 안될 시 퍼블릭 엑세스 부분이나 보안그룹 등 설정 확인
참고로 로컬에서 확인해볼 때는 퍼블릭 서브넷에 위치시킨 후 퍼블릭 엑세스까지 가능하게 설정해야 접속가능하다.
테스트를 마친 후에는 퍼블릭 엑세스를 금지시키고 프라이빗 서브넷으로 위치시켰다.
Lambda 생성 및 설정
- 서버를 프로비저닝하거나 관리하지 않고도 모든 유형의 애플리케이션 또는 백엔드 서비스에 대한 코드를 실행할 수 있는 이벤트 중심의 Severless 서비스
- 필요한 만큼만 컴퓨팅 파워를 빌릴 수 있는 서비스
- 실행 시간 단위로 과금되어 온디맨드 서비스
코드는 최대 15분간 실행 가능하며, 되도록 작게 쪼개어 수행하는 것이 좋다. 5분 이상의 로직은 다른 방법으로 처리하는 것이 권장된다.
- 매 실행마다 처음 실행하는 것처럼 동작하여 Stateless하며
aws에서 관리하는 런타임 언어, 메모리, vCPU, 레이어, 임시 스토리지 등으로 구성된 실행환경에서 수행된다.
- 람다는 인라인으로는 파일당 3MB 그리고 zip 파일은 최대 250MB까지 가능하고 그 이상은 컨테이너 이미지로 구동할 수 있다고도 한다.
pip install --target lambda-rds pymysql --no-user
zip 파일을 만들고 생성한 lambda 함수에 업로드를 시킨다.
런타임 설정의 핸들러에 따라 파일이름과 함수 이름을 만들어야 한다.
lambda_function 파일의 lambda_handler를 실행시키겠다는 의미이다.
lambda에서 구성탭을 들거보면 일반 구성에서 제한시간, 메모리, 임시 스토리지를 설정할 수 있다.
제한시간을 짧게 잡으면 timeout에 걸릴 수 있다.
권한 탭에서는 lambda의 권한을 설정할 수 있다.
서비스별로 어떤 리소스에 대하여 어떤 작업을 허용하였는지 상세하게 볼 수 있다.
만약 권한 설정이 미흡하게 되어있다면 역할 이름을 기억하거나 혹은 해당 링크를 클릭하여 설정해주면 된다.
해당 프로젝트에서는 SNS 와 IAM 관련 인라인 정책을 직접 JSON으로 넣어 lambda에게 권한을 부여하였다.
또한 RDS가 속한 VPC로 설정해준다. RDS(Mysql)는 동일한 VPC 내에 있다보니까 connect 된다.
그리고 고가용성을 위해서 서브넷에는 2개 이상을 넣어주는 것이 권장된다.
외부와 통신하려면 리소스의 ENI에 퍼블릭 IP가 있어야 하는데 Lambda의 ENI는 퍼블릭 IP를 자동으로 가지지 않아 아무 설정 없이 퍼블릭 서브넷에 lambda를 두면 timeout 되면서 lambda와 iam이 계속 connect 되지 않는 현상이 발생한다.
나는 프라이빗 서브넷에 Lambda를 두고 퍼블릭 서브넷의 NAT Gateway를 통해 외부 인터넷에 액세스할 수 있도록 설정하였다. (참고로 iam 과 같은 aws 서비스에만 접근할거라면 no vpc에서도 충분히 가능하다. 하지만 RDS 를 사용할 것이기 때문에 동일한 VPC로 설정을 해주었다.)
위의 이미지는 프라이빗 서브넷의 설정이다.
nat-gateway에 탄력적 IP를 할당하고 프라이빗 서브넷의 라우팅 테이블에 nat-gateway로 가는 길을 열어두었다.
퍼블릭 서브넷 내 리소스가 인터넷과 통신하려면 라우팅 테이블에 0.0.0.0/0 -> IGW가 설정되어 있으며 (퍼블릭 서브넷의 조건), 퍼블릭 IP가 할당되어 있어야 한다.
서브넷에 퍼블릭 IP를 자동 할당을 해야 트래픽이 IGW를 통해 나갈 수 있다.
서브넷에서 퍼블릭 IP를 자동 할당하도록 설정하면, 해당 서브넷에 생성되는 EC2 인스턴스나 네트워크 인터페이스(ENI)가 자동으로 퍼블릭 IP를 할당받을 수 있다. ( Lambda 함수의 ENI는 퍼블릭 IP를 자동으로 할당받지 않는다.)
그래서 프라이빗 서브넷에 lambda를 , 퍼블릭 서브넷에는 nat-gateway(생성 시 퍼블릭서브넷 선택)를 두고 선택했다.
VPC 밖에 있는 IAM과 통신해야 하므로 443포트도 열어주고
또한 RDS에서와 마찬가지로 이번에는 아웃바운드 규칙에 RDS가 속한 보안그룹 ID를 넣어준다.
Lambda 테스트
환경변수를 입력해주고 Deploy 후 테스트를 진행하였다.
실행되는 시간이 길면 timeout 등 에러가 걸렸고 성공 시에는 5초 이내로 바로 잘 작동하는 것을 볼 수 있었다.
def analyze_user_policies(user):
user_name = user['UserName']
flagged = []
try:
policies = iam_client.list_attached_user_policies(UserName=user_name)['AttachedPolicies']
logger.info(f"User '{user_name}' has {len(policies)} attached policies.")
for policy in policies:
policy_arn = policy['PolicyArn']
policy_version = iam_client.get_policy(PolicyArn=policy_arn)['Policy']['DefaultVersionId']
policy_doc = iam_client.get_policy_version(PolicyArn=policy_arn, VersionId=policy_version)['PolicyVersion']['Document']
statements = policy_doc.get('Statement', [])
for statement in statements:
if statement['Effect'] == 'Allow' and statement['Action'] == '*' and statement['Resource'] == '*':
flagged.append({
"user_name": user_name,
"policy_name": policy['PolicyName'],
"issue": "User has full admin privileges"
})
logger.warning(f"Flagged user: {user_name}, Policy: {policy['PolicyName']}")
break
except Exception as e:
logger.error(f"Error analyzing policies for user '{user_name}': {str(e)}")
return flagged
lambda에 넣은 코드 중 일부이다.
policy가 policies 리스트를 각각 돌면서 PolicyArn 키를 찾아 관리형 정책의 ARN을 사용해 해당 정책을 조회하고, 이를 통해 정책 문서를 분석하는 코드이다. effect는 allow, action은 전부, resource가 전부인 정책은 flagged에 추가시키고 해당 사용자와 함께 admin 권한을 가졌다고 로깅해준다.
테스트한 계정은 인라인 정책을 갖고 있지 않아 결과에 문제가 없었지만
boto3 문서를 확인해보니 list_attached_user_policies 는 메소드로 관리형 정책은 갖고 오지만 인라인 정책은
list_user_policies 메소드를 통해서 가져올 수 있는데 해당 코드는 list_user policies 메소드를 넣지 않아 인라인 정책은 가져오지 못한다는 문제점이 있었다. 올바르게 코드를 짤 경우 해당 메소드를 넣어 인라인 정책까지 가져와야 한다.
출처=>
실행결과
MySQL
CloudWatch Log
/*
MySQL에서는 위의 한 행이 람다를 실행해서 성공한 횟수만큼 있었는데 다 삭제하고 한행만 남기고 스크린샷을 찍은 모습이고 Cloud watch 로그 이벤트와 시간이 다른 것은 여러 번 변화를 주면서 시도하여 로그 이벤트와의 시점이 다르기 때문이다. 로그 자체는 실행할 때마다 똑같이 생성되었다.
*/
CloudWatch Logs 관련
이렇게 stats 를 통해 결과를 집계하거나 원하는 조건을 걸어 그에 맞는 결과를 가져올 수 있었다.
Splunk 같은 서치형 DB와 굉장히 유사한 느낌을 받았다.
이외에도 metric을 통해서 원하는 지표를 확인하고 분석하는 기능이 존재했다.
AWS EventBridge를 통해 규칙 생성, lambda 실행
일정과 기간을 설정하여 Lambda를 실행시킬 수 있다. 일회성 혹은 cron 등으로 주기적으로 실행시킬 수 있다.
AWS SNS 통해서 이메일 확인
코드에서 topic arn을 통해 SNS 토픽(주제)에 메세지를 publish하면 구독한 ID들에게 메세지를 보낸다.
프로토콜은 이메일, lambda, http 등 다양하게 지원한다.
이벤트 실행 시 성공적으로 이메일을 받을 수 있었다.
'Cloud > AWS' 카테고리의 다른 글
AWS KMS (3) | 2024.12.13 |
---|---|
AWS WAF (Web Application Firewall) (0) | 2024.12.05 |
S3 vs EBS vs EFS (0) | 2024.11.25 |
VPC Endpoints & VPC (0) | 2024.11.23 |
Autoscaling Group & ALB (0) | 2024.11.20 |