2018년 Access Key. 아직 Production에서 활성 상태. 전체 AWS 조직에서 이를 찾은 Python 스크립트

발행: (2026년 3월 7일 PM 07:00 GMT+9)
15 분 소요
원문: Dev.to

I’m happy to translate the article for you, but I’ll need the full text of the post (the paragraphs, headings, etc.) in order to do the translation. Could you please paste the article content here? I’ll keep the source line exactly as you provided and translate everything else while preserving the formatting.

소개

몇 주 전, 나는 다중 계정 AWS 조직의 IAM 상태를 검토하기 위해 자리를 잡았다. 이것은 몇 주에 걸친 계획이 필요한 공식 감사가 아니었다. 언제든지 모든 클라우드 보안 엔지니어가 답할 수 있어야 하는 간단한 질문이었다:

누가 접근 권한을 가지고 있으며, 어떤 자격 증명을 사용하고, 언제부터인가?

그 답변은 나를 놀라게 했다. 찾기 어려워서가 아니라—자동화가 얼마나 쉬웠는지, 그리고 자동화하면서 드러난 사실들 때문에였다.

AWS를 수년간 운영해 온 조직들은 자신도 모르게 보안 부채를 쌓아간다. 처음엔 하나의 계정, 그 다음은 두 개, 팀이 자체 환경을 요청하고, 또 다른 프로젝트가 생기면서, 어느새 수십 개의 계정과 각각 고유한 IAM 이력을 가진 AWS 조직이 된다.

문제는 규모가 아니라—가시성이다. 아니면, 가시성이 부족한 것이다.

다중 계정 환경에서는 누가 어떤 접근 권한을 가지고 있는지에 대한 통합된 뷰가 없다.

  • 인프라 팀은 자신들이 배포한 것을 안다.
  • 개발 팀은 그때 필요했던 것을 안다.

하지만 더 이상 존재하지 않는 통합 프로세스를 위해 3년 전에 생성된 Access Keys는 회전되지 않았고 여전히 활성 상태이며—이러한 키는 어떤 대시보드에도 나타나지 않는다. 알림도 발생하지 않는다. 누구에게도 불편을 주지 않는다. 그저 기다릴 뿐이다.

바로 그것이 내가 발견한 바였다: 여러 프로덕션 계정을 가진 활성 AWS 조직과 수년간 중앙 감사를 받지 않은 자격 증명들. 팀이 부주의했기 때문이 아니라—모두를 한 번에 볼 수 있는 메커니즘을 아무도 구축하지 않았기 때문이다.

나는 그 탐색을 자동화하기로 결정했다.

Source:

액세스 키가 중요한 이유

IAM 액세스 키는 장기 사용 자격 증명입니다. IAM 역할과 달리—자동으로 만료되는 임시 자격 증명을 생성하는 역할—액세스 키에는 만료 날짜가 없습니다. 오늘 키를 만들고 회전시키지 않으면 2030년에도 여전히 유효합니다.

이 때문에 AWS 환경에서 가장 흔한 공격 벡터 중 하나가 됩니다. 설계상 보안이 취약해서가 아니라 시간 자체가 위험을 누적시키기 때문입니다. 수년간 활성 상태였던 키는 조용히 위험을 축적합니다:

  • 코드 저장소에 노출됐지만 아무도 눈치채지 못했을 수 있습니다.
  • 조직을 떠난 직원이 여전히 보유하고 있을 수 있습니다.
  • 일회성 프로젝트를 위해 부여된 권한이 검토되지 않은 채 남아 있을 수 있습니다.
  • 공격자가 수개월 동안 사용했을 수 있으며—키를 회전시키지 않으면 아무도 알 수 없습니다.

AWS Security Maturity Model v2는 이 점을 정확히 짚고 있습니다.

  • Phase 1 – Quick Wins (Identity and Access Management 도메인): 핵심 제어 중 하나는 다중 인증(MFA) — 콘솔에 접근하는 모든 사용자가 MFA를 활성화하도록 하는 것입니다.
  • Phase 2 – Foundational: 모델은 임시 자격 증명 사용을 권장합니다 — IAM 역할과 단기 자격 증명으로 전환하여 장기 액세스 키 사용을 중단하도록 하는 것입니다.

이 스크립트는 두 제어를 한 번에 감사합니다: 어떤 사용자가 활성 액세스 키를 가지고 있는지, 얼마나 오래 활성 상태였는지, 그리고 MFA가 설정되어 있는지 여부를 확인합니다. 2018년에 생성된 활성 키와 MFA가 없는 사용자를 결합하면 단순한 기술 부채가 아니라 수년간 열려 있던 공격 표면이 됩니다.

코드 작성 전 설계 결정

한 줄도 작성하기 전에, 스크립트 작동 방식을 정의하는 세 가지 결정을 내렸습니다.

  1. Cross‑account via STS, not IAM users
    sts:AssumeRole: 스크립트는 각 계정에서 기존 역할을 가정하고, 임시 자격 증명을 얻어 감사하며, 자격 증명은 스스로 만료됩니다.

  2. Start from AWS Organizations – 모든 멤버 계정을 자동으로 탐색합니다.

  3. Always paginateget_paginator(). 사용자가 많은 계정에서는 첫 번째 결과만 보이고 나머지는 알 수 없습니다. 이는 감사의 신뢰성을 무너뜨리는 조용한 버그 유형입니다.

이 세 가지 결정을 명확히 하면, 코드는 거의 스스로 작성됩니다.

메인 흐름

def main():
    args = parse_args()
    session = boto3.Session(profile_name=args.profile)
    org_client = session.client('organizations')

    # Get all active accounts from the Organization
    accounts = get_accounts(org_client)

    all_findings = []
    for account in accounts:
        print(f"Auditing account: {account['name']} ({account['id']})")
        try:
            credentials = assume_role(
                session,
                account['id'],
                args.role,
                'SecurityAudit'
            )
            iam_client = boto3.client(
                'iam',
                aws_access_key_id=credentials['AccessKeyId'],
                aws_secret_access_key=credentials['SecretAccessKey'],
                aws_session_token=credentials['SessionToken']
            )
            findings = get_iam_users_with_keys(iam_client, account['id'], account['name'])
            all_findings.extend(findings)
        except Exception as e:
            print(f"Error in account {account['name']}: {e}")

    return all_findings

한 계정에서 오류가 발생해도 스크립트는 계속 실행됩니다. 이는 의도된 동작이며, 실제 조직에서는 역할이 배포되지 않았거나 권한이 다른 계정을 만나게 됩니다. try/except 구문은 한 계정에서 오류가 발생해도 전체 감사를 중단하지 않도록 보장합니다.

스크립트의 핵심 – 사용자당 감사 항목

def get_iam_users_with_keys(iam_client, account_id, account_name):
    findings = []
    paginator = iam_client.get_paginator('list_users')

    for page in paginator.paginate():
        for user in page['Users']:
            # User's Access Keys
            keys_response = iam_client.list_access_keys(UserName=user['UserName'])

            # MFA status
            mfa_response = iam_client.list_mfa_devices(UserName=user['UserName'])
            mfa_devices = mfa_response['MFADevices']

            # Console access
            try:
                iam_client.get_login_profile(UserName=user['UserName'])
                password_status = 'Configured'
            except iam_client.exceptions.NoSuchEntityException:
                password_status = 'Not configured'

            for key in keys_response['AccessKeyMetadata']:
                last_used_response = iam_client.get_access_key_last_used(
                    AccessKeyId=key['AccessKeyId']
                )
                # Build a finding record
                finding = {
                    'AccountId': account_id,
                    'AccountName': account_name,
                    'UserName': user['UserName'],
                    'AccessKeyId': key['AccessKeyId'],
                    'KeyStatus': key['Status'],
                    'CreateDate': key['CreateDate'].isoformat(),
                    'LastUsedDate': last_used_response['AccessKeyLastUsed'].get('LastUsedDate'),
                    'LastUsedRegion': last_used_response['AccessKeyLastUsed'].get('Region'),
                    'LastUsedService': last_used_response['AccessKeyLastUsed'].get('ServiceName'),
                    'MFAEnabled': bool(mfa_devices),
                    'ConsolePassword': password_status,
                }
                findings.append(finding)

    return findings

단일 계정에서 오류가 발생하더라도 스크립트는 계속 실행됩니다. 대규모이면서 다양한 조직을 다룰 때 이러한 복원력은 필수적입니다.

마무리 생각

AWS 조직 전체에 걸쳐 IAM 자격 증명 위생을 자동화하면 수년간 눈에 띄지 않았을 위험을 발견할 수 있습니다. 다음과 같이:

  • STS를 통한 역할 가정 (영구적인 교차 계정 사용자가 없음),
  • AWS Organizations에서 시작 (자동 계정 탐색),
  • 모든 API 호출에 페이지네이션 적용 (전체 데이터),

이를 통해 장기 사용 Access Key, 누락된 MFA, 사용되지 않은 콘솔 비밀번호 등을 신뢰성 있게 찾아낼 수 있습니다 — 이는 AWS 보안 성숙도 모델이 빠른 개선점 및 기본 제어 항목으로 표시하는 바로 그 요소들입니다.

이 감사를 정기적인 스케줄 작업으로 구현하면 일회성 탐지를 지속적인 보안 위생으로 전환할 수 있으며, 조직이 성장에 따라 발생하는 불가피한 변동을 앞서 나가도록 도와줍니다.

IAM 감사 스크립트 개요

이 스크립트는 AWS 조직의 모든 멤버 계정을 순회하면서 각 계정에 역할을 가정(assume)하고, 계정당 한 번의 패스로 IAM 관련 데이터를 수집합니다.

각 사용자에 대해 스크립트가 캡처하는 항목

ItemDescription
Access Keys상태(Active/Inactive)와 생성 날짜가 포함된 모든 키
Last Used각 키가 마지막으로 사용된 시점 및 사용된 AWS 서비스
MFAMFA가 활성화되어 있는지와 유형(Virtual, Hardware, 또는 None)
Console Access사용자가 AWS Management Console에 로그인할 수 있는지 여부

스크립트 실행

python iam_audit.py --profile your-mgmt-profile --role OrganizationAccountAccessRole

요구 사항

  1. 관리 계정에 대한 권한이 있는 AWS 프로파일.
  2. 각 멤버 계정에서 Assume‑role 권한 (해당 역할은 모든 자식 계정에 배포되어 있어야 함).

관리 계정을 위한 최소 IAM 정책

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "organizations:ListAccounts",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::*:role/ROLE-NAME-IN-CHILD-ACCOUNTS"
    }
  ]
}

위 권한만 있으면 충분합니다.

AWS Control Tower를 사용하는 경우, AWSControlTowerExecution 역할이 모든 계정에 이미 존재하므로 시작점으로 활용할 수 있습니다. 다만 프로덕션 워크로드에서는 읽기 전용 IAM 권한을 가진 전용 감사 역할을 만드는 것이 권장됩니다.

최소 권한 원칙이 중요한 이유

감사 도구 자체에 최소 권한 원칙을 적용하면 공격 표면을 줄일 수 있습니다. 이 스크립트는 IAM 리소스를 절대 수정하지 않으며, 각 자식 계정에서 가정한 역할을 통해 읽기만 수행합니다.

시간에 따른 수정 상황 추적

현재 상태 외에도, 스크립트는 각 계정의 CloudTrail을 조회하여 다음 IAM 이벤트를 확인합니다:

  • DeleteAccessKey
  • CreateAccessKey
  • DeleteUser

이 이벤트들을 통해 발견 사항이 실제로 수정되고 있는지, 아니면 보고서에만 남아 있는지를 파악할 수 있습니다.

출력

실행 결과는 두 개의 CSV 파일을 생성합니다:

  1. IAM findings – 사용자/키당 한 행씩, 캡처된 모든 속성을 포함합니다.
  2. CloudTrail events – IAM 변경 내역을 시간 순으로 정리하여 수정 진행 상황을 모니터링합니다.

Sample Findings

FindingResult
Accounts audited20개 이상의 활성 계정
Access Keys found수십 개
Oldest key2018년에 생성 – 아직도 프로덕션에서 활성
Users without MFA + console access수십 명
Total execution time몇 분

Note: 감사 역할이 없어서 두 계정은 감사되지 않았습니다. 이것 자체가 발견 사항이며, 접근할 수 없는 것은 감사할 수 없습니다.

Key takeaway

가장 영향력 있는 데이터 포인트는 age(연령) 입니다. 2018년에 생성된 채로 아직도 활성 상태인 액세스 키는 눈에 띄지 않는 위험 구역을 의미합니다. 이는 아무도 찾아보지 않았기 때문에 모든 변경을 살아남은 것입니다. 스크립트는 이를 몇 분 만에 찾아냈습니다.

Next Steps

  1. Run the script in your own AWS Organization (Python + boto3 + audit role).
  2. Import the CSVs into a dashboard tool (e.g., QuickSight, Power BI) to visualize:
    • Risk widgets per account
    • Remediation trends over time
    • Consolidated MFA status
  3. Iterate – add more IAM events or integrate with a SIEM if desired.

리포지토리

전체 코드는 GitHub에서 확인할 수 있습니다:

🔗 GitHub: gerardokaztro/iam-audit

About the Author

Gerardo Castro – AWS Security Hero & Cloud Security Engineer (LATAM)
그는 클라우드 보안을 배우는 가장 좋은 방법은 프레임워크를 외우는 것이 아니라 실제 솔루션을 구축하는 것이라고 믿습니다. Hashnode에서 그의 글을 팔로우하세요:

🌐 Blog: roadtocloudsec.hashnode.dev

0 조회
Back to Blog

관련 글

더 보기 »