AWS 기초 #6 보안 기본: MFA, 키 회전, 최소 권한
#2 IAM에서 권한 모델을, #5에서 SSO까지 봤습니다. 이 글은 그 위에 운영에서 통하는 보안 가드레일을 한 번에 정리하겠습니다.
AWS 보안 사고의 90%는 다음 중 하나입니다.
- 루트 / 사용자 비밀번호 탈취 (피싱). MFA가 없어서
- 액세스 키가 git / 슬랙 / 로그에 노출
- 너무 넓은 권한이 침해 시 피해를 키움
- CloudTrail / GuardDuty가 꺼져 있어 사건을 못 발견
이 네 가지에 가드레일을 세우면 사고 확률이 한 자릿수로 떨어집니다.
MFA: 가장 중요한 한 가지 #
비밀번호 한 줄로 끝나는 인증은 2026년의 운영 수준이 아닙니다. 피싱 한 번이면 비밀번호는 흘러갑니다. **MFA (Multi-Factor Authentication)**는 두 번째 요소 (보통 휴대폰 앱)의 6자리 코드를 추가로 요구합니다.
MFA 종류 #
| 종류 | 무엇 | 추천 |
|---|---|---|
| Virtual MFA (TOTP) | 휴대폰 앱 (Google Authenticator, 1Password, Authy) | 표준. 거의 모든 경우 |
| 하드웨어 MFA | YubiKey 같은 USB 키 | 루트 / 고특권. 최강 |
| U2F / WebAuthn | 브라우저 + 하드웨어 키 | 운영 자격 격상 |
| SMS | 문자 메시지 | 사용 금지 (SIM swap 공격) |
루트는 하드웨어 MFA가 이상적이고, 일반 사용자는 virtual MFA로 충분합니다.
루트 사용자 MFA 활성화 #
가입 직후 첫 작업으로 즉시.
콘솔 (루트 로그인) → 우측 상단 사용자 메뉴 → Security credentials
→ Multi-factor authentication (MFA) → Assign MFA device
→ Virtual MFA / Hardware MFA 선택
→ QR 코드를 휴대폰 앱으로 스캔
→ 연속 두 코드 입력 (앱이 30초마다 새 코드)이후 루트 로그인 때마다 비밀번호 + 6자리 코드.
IAM 사용자에 MFA 강제 #
루트만 켜는 건 부족합니다. 모든 IAM 사용자에 강제해야 합니다. 두 가지 방법.
방법 1: 정책으로. “MFA가 켜진 세션이 아니면 거의 모든 액션 거부”
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSelfManageCredentials",
"Effect": "Allow",
"Action": [
"iam:ChangePassword",
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:GetUser",
"iam:ListMFADevices",
"iam:ResyncMFADevice"
],
"Resource": [
"arn:aws:iam::*:user/${aws:username}",
"arn:aws:iam::*:mfa/${aws:username}"
]
},
{
"Sid": "DenyAllExceptListedIfNoMFA",
"Effect": "Deny",
"NotAction": [
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:GetUser",
"iam:ListMFADevices",
"iam:ResyncMFADevice",
"iam:ChangePassword",
"sts:GetSessionToken"
],
"Resource": "*",
"Condition": {
"BoolIfExists": { "aws:MultiFactorAuthPresent": "false" }
}
}
]
}이 정책을 모든 사용자가 속한 그룹에 붙이면 MFA 없이는 사실상 아무것도 못 합니다. MFA 등록만 예외입니다.
방법 2: SSO (#5)
IAM Identity Center 사용자에는 콘솔 / CLI 로그인 시 MFA가 자동 강제됩니다. 정책을 안 짜도 됩니다.
첫 로그인 시 MFA 등록 강제 흐름 #
신규 IAM 사용자가 처음 로그인하면 위 정책으로 MFA 등록 외엔 아무것도 못 합니다. 자기 MFA를 등록하면 그 다음부터 정상 사용할 수 있습니다. 이 흐름이 표준입니다.
액세스 키 회전: 90일이 표준 #
#4에서 본 액세스 키. 이 키는 시간이 지나면 노출 위험이 누적됩니다.
회전 정책 #
| 항목 | 권장 회전 주기 |
|---|---|
| 사용자 액세스 키 | 90일 |
| CI / CD 키 | 60일 (또는 OIDC로 전환) |
| 서비스 계정 키 | 30~60일 |
| 임시 자격 (SSO / Role) | 회전 불필요 (자동으로 단기 발급) |
회전 절차 #
키를 한 번에 두 개까지 가질 수 있는 IAM의 특성을 활용합니다.
# 1) 새 키 발급 (이제 활성 키 2개)
aws iam create-access-key --user-name curtis
# 2) 모든 환경에서 새 키로 교체 (CI 환경 변수, ~/.aws/credentials, etc.)
# 3) 며칠 모니터링. 옛 키 사용이 있는지
# CloudTrail 또는 IAM credential report로 확인
# 4) 옛 키 비활성화 (삭제는 아직 X, 롤백용)
aws iam update-access-key --user-name curtis --access-key-id AKIA-OLD --status Inactive
# 5) 일주일 더 모니터링 → 정말 안 쓰면 삭제
aws iam delete-access-key --user-name curtis --access-key-id AKIA-OLDIAM Credential Report: 회전 점검 #
CSV 한 장으로 모든 사용자의 키 / MFA / 활동 상태를 봅니다.
aws iam generate-credential-report
aws iam get-credential-report --query Content --output text | base64 -d > report.csvuser
mfa_active
access_key_1_active
access_key_1_last_rotated
access_key_1_last_used_date
access_key_2_active
access_key_2_last_rotated
password_last_used90일 넘은 키, MFA 안 켠 사용자, 한 달 미사용 사용자를 한눈에 확인할 수 있습니다.
노출된 키 발견 시 #
코드에 키를 push 했다면. 봇이 발견하기까지 보통 분 단위.
즉시 해야 할 일 (시간 순) #
aws iam update-access-key --user-name <user> --access-key-id <키ID> --status Inactiveaws iam create-access-key --user-name <user># 콘솔 → CloudTrail → Event history
# AccessKeyId로 검색 → 의도하지 않은 사용 있는지aws iam delete-access-key --user-name <user> --access-key-id <키ID># BFG Repo-Cleaner 또는 git filter-repo로 history에서 키 제거
# (이미 push 됐다면 history 청소만으론 안전 X, 키 삭제가 더 중요)AWS가 자동으로 도와주는 도구 #
AWS는 GitHub 등 공개 저장소를 스캔해 노출 키를 발견하면:
- 사용자에게 즉시 이메일
- 일부 경우 자동으로 키 비활성화 + 정책 첨부
이건 보조 안전망입니다. 직접 발견이 더 빠릅니다.
IAM Access Analyzer: 너무 넓은 권한 찾기 #
내 계정의 정책 / 자원 정책 (S3 버킷 정책, KMS 키 정책 등)을 분석해 외부 접근이 가능한 부분을 찾는 도구입니다. 무료.
활성화 #
콘솔 → IAM → Access Analyzer → Create analyzer
- Type: Account / Organization
- Name: my-account-analyzer활성화 후 24시간 안에 외부 접근 가능 자원의 목록이 나옵니다.
무엇을 잡아 주는가 #
| 자원 | 위험 |
|---|---|
| S3 버킷. public read | 누구나 객체 읽기 |
| S3 버킷. 다른 계정에 권한 | 의도한 설정인지 확인 필요 |
| KMS 키. 외부 사용 | 암호화 키 노출 |
| IAM Role. 외부 trust | 다른 계정이 빌릴 수 있음 |
| Lambda. 외부 호출 권한 | 누구나 호출 가능 |
| RDS 스냅샷 / SQS / SNS / Secrets Manager / EBS / ECR. public | 데이터 / 메시지 노출 |
정책 검증 #
새 정책을 만들 때 Access Analyzer가 권장 사항도 보여줍니다.
- 사용 안 하는 권한
- 너무 넓은 와일드카드
- 조건 없는 경우에 추가 권장
Action Last Accessed: 미사용 권한 찾기 #
각 IAM 사용자 / 역할에 대해 마지막으로 사용한 액션을 보여 줍니다. 90일 미사용 권한은 좁힐 후보입니다.
aws iam generate-service-last-accessed-details --arn arn:aws:iam::123:role/MyRole
# (잠시 후)
aws iam get-service-last-accessed-details --job-id ...최소 권한: 통하는 패턴 #
“필요한 만큼만, 필요한 곳에만.” 이상은 쉬운데, 운영에서 어떻게 실천하는지가 관건입니다.
패턴 1: 넓게 시작 → 좁히기 #
처음부터 완벽한 권한을 짜기는 어렵습니다. 다음 흐름이 현실적입니다.
- 시작은
PowerUserAccess/ 서비스의*FullAccess - 일주일 운영 후 Access Analyzer의 Action Last Accessed 확인
- 안 쓰는 서비스 / 액션 제거
- 와일드카드를 ARN으로 좁힘
- 조건 (Condition) 추가
이 사이클을 분기마다 반복합니다.
패턴 2: 사용자 ↔ 역할 분리 #
#2 패턴의 재확인.
- 사람 = SSO (#5)
- 머신 = Role (인스턴스 프로파일, 실행 역할, OIDC)
- CI/CD = OIDC + Role (GitHub Actions, GitLab)
영구 액세스 키가 거의 사라지는 구조입니다.
패턴 3: 권한 경계 (Permission Boundary) #
“이 사용자는 무엇을 하든 이 한도 안에서만.” 신입 개발자에게 IAM 권한을 주되, 그가 새 정책 / 사용자를 만들어 자기 권한을 늘리는 사고를 차단합니다.
{
"Effect": "Allow",
"Action": [
"ec2:*",
"s3:*",
"rds:*",
"logs:*"
],
"Resource": "*",
"Condition": {
"StringEquals": { "aws:RequestTag/env": "dev" }
}
}이 경계가 붙은 사용자는 dev 환경 자원 외엔 만들 수 없습니다. 자기 정책으로도 마찬가지입니다.
패턴 4: 환경 분리 #
- 계정 분리 (Organizations). 가장 강한 방어선
- VPC 분리. 같은 계정이라도 prod VPC와 dev VPC를 분리 (중급 #1)
- 태그 분리.
env=prod태그로 권한 / 비용 분리 (#3의 태그 전략)
패턴 5: Break-glass 역할 #
평소엔 ReadOnly만, 사고 시에 잠시 Admin으로 격상. SSO Permission Set을 두 개로 분리.
| 항목 | 평소 | 사고 시 |
|---|---|---|
| 사용 | ReadOnly | Break-glass-Admin (1시간) |
| 알림 | (없음) | Slack 채널에 자동 알림 |
| 감사 | (없음) | CloudTrail로 모든 액션 기록 |
CloudTrail: 누가 무엇을 했는가 #
CloudTrail은 계정 안의 모든 API 호출을 기록합니다. 가입 직후 자동으로 활성화 (지난 90일 이벤트 무료 조회). 운영용은 Trail을 만들어 S3에 영구 저장.
가입 직후 점검 #
콘솔 → CloudTrail → Trails
→ Multi-region Trail 한 개 만들기 (운영 표준)
- 이름: my-trail
- Storage: 새 S3 버킷
- Log file SSE-KMS encryption: 켜기
- Log file validation: 켜기 (변조 감지)CloudTrail의 두 종류 이벤트 #
| 종류 | 무엇 | 비용 |
|---|---|---|
| Management events | API 호출 (RunInstances, DeleteBucket 등) | 무료 (한 번) |
| Data events | S3 객체 접근, Lambda 호출 등 | 유료 (양 많음) |
대부분의 Trail은 management만. data events는 특정 버킷 / 함수에만 켭니다.
자주 쓰는 쿼리 #
콘솔 Event history에서 검색:
- AccessKeyId. 노출 키 사용 흔적
- UserName. 한 사용자가 한 일
- EventName.
ConsoleLogin,DeleteBucket,RunInstances - 시간 범위
CloudTrail Lake 또는 Athena로 SQL 분석도 가능합니다 (큰 조직).
GuardDuty: 위협 탐지 자동화 #
GuardDuty는 CloudTrail / VPC Flow Logs / DNS 로그를 머신러닝으로 분석해 의심스러운 활동을 잡아 줍니다.
잡아 주는 예 #
| 패턴 | 설명 |
|---|---|
| EC2가 비정상 지역에서 통신 | 침입 / C&C |
| 액세스 키가 비정상 지역에서 사용 | 키 노출 후 사용 |
| Cryptocurrency 채굴 트래픽 | 침입 후 채굴 |
| EC2의 비정상 포트 스캔 | 측면 이동 |
| Tor exit node와의 통신 | 의심 트래픽 |
| IAM 사용자의 비정상 행동 | 자격 도용 |
활성화 #
콘솔 → GuardDuty → Get started → Enable
- 30일 무료 평가
- 무료 평가 후 데이터 양 기반 과금 (보통 \$10~50 / 월 / 작은 운영)운영 단계에선 반드시 켜기. GuardDuty 1년 비용보다 사고 한 건의 피해가 훨씬 큽니다.
Security Hub: 보안 상태 통합 #
여러 보안 도구의 결과를 한곳에 모음. CIS Benchmark, AWS Foundational Security Best Practices 등 표준 점검을 자동 실행.
콘솔 → Security Hub → Enable
- 권장 표준 모두 켜기
- GuardDuty / Access Analyzer / Inspector 결과가 통합됨가입 직후엔 살짝 과합니다. 한 분기 후 자원이 일정 수준 이상이 되면 켜기.
자주 만나는 사고 사례 #
사례 1: GitHub에 액세스 키 push → 가상화폐 채굴 #
가장 흔한 시나리오. 분 단위로 봇 발견, 시간 단위로 비용 누적.
대응: 즉시 키 비활성화 → 새 키 → CloudTrail로 사용 흔적 → 가능하면 계정 자체 격리. 청구 분쟁 시 AWS 지원에 연락. 대부분 무료 티어 사고는 소급 면제 (반복은 안 됨).
예방: pre-commit hook (gitleaks, truffleHog), GitHub secret scanning, 로컬에 키 자체 안 두기 (SSO).
사례 2: 너무 넓은 S3 버킷 정책 #
“우리 회사 사람들이 다 볼 수 있게” 하려고 Principal: "*"를 추가하면 public read가 되고, 검색 엔진에 인덱싱됩니다.
대응: Access Analyzer가 잡아 줌. S3 Block Public Access가 계정 / 버킷 단위로 강제.
예방: S3 Block Public Access 항상 켜기, 정말 public이 필요한 경우는 CloudFront + Origin Access Control 패턴.
사례 3: MFA 없는 루트 비밀번호 피싱 #
“AWS 결제 알림” 가짜 메일 → 가짜 로그인 페이지 → 비밀번호 입력으로 이어집니다. 루트 MFA가 없으면 그대로 침해됩니다.
대응: 비밀번호 즉시 변경, 모든 자원 점검, AWS 지원 연락.
예방: 루트 MFA 필수 (이상적으로 하드웨어), 일상 작업은 IAM / SSO로.
사례 4: 퇴사자의 액세스 키가 살아 있음 #
퇴사 처리 후에도 IAM 사용자 / 키가 그대로 남습니다. 몇 달 뒤 사고로 이어집니다.
대응: 즉시 사용자 비활성화 / 삭제 + Trail 점검.
예방: 퇴사 체크리스트에 IAM 정리 포함. SSO면 IdP에서 비활성화하면 끝.
사례 5: CloudTrail이 꺼짐 #
침해 조사를 시작하려는데 사건 시점의 로그가 없습니다. 누군가 (의도적으로) Trail을 껐거나 처음부터 안 켠 것입니다.
대응: 이미 늦음. 가능한 다른 로그 (CloudWatch, GuardDuty findings)로 부분 복원.
예방: Trail 활성화 + Log file validation + S3 객체 잠금. SCP (Service Control Policy)로 CloudTrail 비활성화 자체를 거부.
자주 만나는 함정 #
1) MFA만 켜고 끝 #
MFA가 켜져 있어도 액세스 키가 노출되면 키는 그대로 작동합니다 (CLI 호출은 MFA와 무관합니다, #4의 자격 체인). 키도 같이 신경 쓰거나, SSO로 키 자체를 줄입니다.
2) 회전을 약속만 하고 안 함 #
Credential Report로 정기 점검을 자동화합니다. 90일 넘은 키는 Slack으로 알립니다.
3) iam:PassRole의 광범위 허용
#
iam:PassRole = *은 사실상 권한 상승이 가능합니다. 역할 ARN으로 좁힙니다.
4) 조건을 너무 강하게 → 본인이 잠김 #
aws:SourceIp로 회사 IP만 허용했는데 본인이 외출 중일 수 있습니다. 콘솔 IP 가드는 마지막에 적용하고, 우회 통로 (VPN / 비상 사용자)를 둡니다.
5) GuardDuty 끄기 #
“비용 아까워서” 끄는 경우가 있는데, 한 사건이 GuardDuty 1년 비용보다 큽니다. 운영에선 항상 켭니다.
6) Security Hub 발견을 안 봄 #
켜고 알림을 안 보면 의미가 없습니다. 주간 리뷰나 EventBridge로 Slack에 알립니다.
정리 #
이번 글에서 잡은 것:
- MFA. 모든 사용자에 필수입니다. 루트는 가능하면 하드웨어, 일반은 virtual TOTP를 쓰고, SMS는 금지합니다.
- MFA 강제 정책. IAM 사용자에 정책으로 강제합니다. SSO면 자동입니다.
- 액세스 키 회전 90일. 새 키 발급 → 교체 → 옛 키 비활성 → 일주일 후 삭제 순입니다. Credential Report로 점검합니다.
- 노출 시. 즉시 비활성화하고, CloudTrail로 사용 흔적을 확인하고, 새 키로 바꾼 뒤 삭제합니다.
- IAM Access Analyzer. 외부 접근 가능 자원, 정책 검증, Action Last Accessed (미사용 권한)를 잡아 줍니다.
- 최소 권한 패턴. 넓게 시작 → 좁히기, 사용자/역할 분리, 권한 경계, 환경 분리, Break-glass를 씁니다.
- CloudTrail. multi-region Trail 한 개 + S3 + log validation으로 구성합니다. SCP로 비활성화를 차단합니다.
- GuardDuty. 머신러닝 위협 탐지입니다. 운영 필수입니다.
- Security Hub. 표준 점검 통합입니다. 자원이 일정 수준 이상일 때 켭니다.
- 사고 사례. 키 push, 넓은 S3 정책, 루트 피싱, 퇴사자 키, Trail 꺼짐입니다.
- 함정. MFA만으로 안전하다는 착각, 회전 약속만 하기, PassRole 와일드카드, 너무 강한 IP 조건, GuardDuty 끄기, Hub 결과 미관리입니다.
다음: CloudWatch #
이 시리즈의 마지막입니다. 모든 운영의 눈인 CloudWatch를 정리하겠습니다.
#7 CloudWatch 입문: 로그 / 메트릭에서는 Logs / Metrics / Alarms / Dashboards의 구성, 로그 그룹과 retention, Metric Filter, Logs Insights 쿼리 기초까지 다루겠습니다.