AWS 기술 블로그

SF 시리즈: Amazon Bedrock 기반 IAM Policy 자동 생성 및 할당 🚀

SF 시리즈 소개

과거 공상과학(SF) 작품에서만 가능할 것 같았던 기술들이 이제는 AWS의 다양한 서비스와 기능을 통해 현실이 되고 있습니다. 이러한 혁신적인 기능들은 여러 서비스의 기능 조합과 복잡한 패턴을 필요로 하며, 이를 효과적으로 오케스트레이션할 수 있는 도구가 필수적입니다.

AWS에서는 이러한 복잡한 프로세스를 손쉽게 설계하고 자동화할 수 있도록 AWS Step Functions을 제공합니다. Step Functions을 활용하면 복잡한 비즈니스 로직과 14,000개 이상의 AWS 기능들을 유기적으로 연결하여, 마치 SF 영화 속에서나 볼 법한 비즈니스 혁신 기능 및 자동화된 시스템을 구현할 수 있습니다.

SF (Step Functions) 블로그 시리즈에서는 AWS Step Functions을 사용한 다양한 워크플로 예제를 소개합니다.
자세한 구현 방안보다는 설계된 워크플로를 확인하여 AWS 서비스 및 기능을 조합하여 어떤 작업을 수행할 수 있는지, 그것이 비즈니스에 어떻게 도움을 줄 수 있는지를 확인하실 수 있습니다. 자세한 Step Functions의 가이드는 아래 링크들을 통해 확인하실 수 있으며 모든 워크플로는 상황에 맞게 자유롭게 커스텀하실 수 잇습니다. 시리즈의 예시를 확인하고 비즈니스 핵심 기능 구축에 참고하시길 바랍니다.

워크플로 소개

이 포스팅에서는 AWS Step Functions을 활용하여 Amazon Bedrock 기반 IAM Policy 자동 생성 및 할당을 구현하는 방법을 다룹니다. 해당 워크플로는 요청된 AWS 작업 내용에 따라 자동으로 IAM Policy를 생성하고 이를 원하는 시간만큼 지정된 IAM 사용자 및 역할에 할당하고 지정 시간 이후 정책을 제거합니다. 또한 이러한 워크플로 실행의 결과를 Amazon SNS를 통해 관리자 또는 클라이언트에게 재요청 메세지를 보냄으로써 최소 권한 부여 원칙에 따른 안전한 클라우드 사용 및 보안팀의 워크로드 효율성을 증가시키는데 활용될 수 있습니다.

워크플로 이미지

아래는 해당 워크플로의 개요를 나타내는 다이어그램입니다.


<Amazon Bedrock 기반 IAM Policy 자동 생성 및 할당 워크플로 >

워크플로의 각 상태 설명

  • Temp state for variables: 초기 변수를 설정하는 Pass 상태. 입력 데이터를 기반으로 IAM 정책을 생성하는 데 필요한 변수들을 할당합니다.
  • Create IAM Policy By Bedrock: Amazon Bedrock을 호출하여 IAM 정책을 자동 생성. Claude 모델을 사용하여 JSON 형식의 정책을 반환하도록 프롬프트 작성합니다.
  • ValidatePolicy: IAM Access Analyzer를 사용하여 생성된 정책의 유효성을 검사합니다.
  • Is validated policy 검증 결과에 따라 정책이 유효한지 확인하는 Choice 상태입니다.

Case #1 . 잘못된 정책이 생성된 경우

  • Is try count over 1: 정책 검증 실패 시, 두 번 이상 생성을 시도했는지 확인하는 Choice 상태입니다.
  • Create request information message for regeneration: Bedrock을 사용하여 정책 생성이 실패한 원인을 포함한 사용자 메시지를 생성합니다.
  • Publish message to client SNS를 통해 요청 클라이언트를 위해 설정된 Topic에 메세지를 Publish합니다.

Case #2 . 올바른 정책이 생성된 경우

  • CreatePolicy 검증된 정책을 AWS IAM에 실제 정책으로 생성합니다.
  • What is target identity: 생성한 IAM 정책이 User에 적용될지 Role에 적용될지 결정하는 Choice 상태입니다.
  • AttachUserPolicy / AttachRolePolicy: 생성된 정책을 User 또는 Role에 Attach 합니다.
  • Wait for policy detach [USER/ROLE]: 특정 시간 동안 정책을 유지하는 Wait 상태입니다.
  • DetachUserPolicy / DetachRolePolicy: 정책을 User 또는 Role에서 Detach 합니다.
  • DeletePolicy: 최종적으로 생성된 IAM 정책을 삭제합니다.
  • Send message to administrator: SNS를 통해 관리자를 위해 설정된 Topic에 최종적인 메세지를 Publish합니다.

워크플로 주요 활용 사례

  • 기업 보안팀(Security & Compliance Team): 가능한 최소 권한 원칙(Least Privilege Principle)을 유지하면서, IAM 정책 자동 생성 및 검증을 통해 보안 강화를 목표로 하는 팀
  • DevSecOps 팀: CI/CD 환경에서 개발자들이 요청하는 IAM 권한을 안전하게 부여하고, 자동 검증 후 배포하는 팀

워크플로 입력 예시

다음은 해당 워크플로의 실행 시 입력으로 제공되는 JSON 예시입니다.

{
  "requested_task": "Translate, Transcribe를 사용하고 싶은데 콘솔에서 테스트가 안됩니다. 권한 요청드립니다.",
  "target_iam_identity": "USER",
  "target_iam_identity_arn": "arn:aws:iam::ACCOUNT_ID:user/IAM_USER_NAM",
  "required_attach_time": 3600,
  "account_id": "YOUR_ACCOUNT_ID",
  "region": "YOUR_AWS_REGION"
}
JSON
  • requested_task: 개발자 및 컴퓨팅 리소스 등의 원하는 AWS 작업에 대한 설명
  • target_iam_identity: 타겟 IAM Identity, “USER” 또는 “ROLE”
  • target_iam_identity_arn: 타겟 IAM Identity의 ARN
  • required_attach_time: IAM 정책이 타겟에 Attach 되어 있을 시간(초단위)

워크플로 출력 예시

워크플로 실행이 완료되면 케이스별로 아래와 같은 작업이 진행됩니다.

Case #1. 프롬프트 조정을 통해 잘못된 Policy 생성 유도.

    1. [ValidatePolicy]에서 리턴된 ‘error_findings_details’를 다시 Bedrock에게 전달하여 재생성을 요청합니다. (try_count 증가)
    2. 재시도에서도 [ValidatePolicy]를 통과하지 못하기 때문에 Bedrock에게 ‘error_findings_details’를 전달하여 재생성을 위한 클라이언트에게 대상 추가 정보 요청 메세지 생성합니다. (여러번의 재시도가 필요한 경우, Choice 상태에서 try_count 조건을 증가시키면 됩니다.)

Case #2. 정상 동작 케이스

  1. 요청에 따른 IAM Policy 생성 후, 지정된 Entity에 할당 후, ‘required_attach_time’의 값(초)까지 대기합니다.
  2. ‘required_attach_time’의 값(초) 대기 후, Entity에서 Policy 할당 해제 및 삭제하고 관리자에게 작업 메세지 전달합니다.

워크플로 배포 가이드

[참고사항]

해당 워크플로의 Amazon Bedrock 호출은 서울(ap-northeast-2) 리전의 Claude 3.5 Sonnet을 사용합니다. 다른 리전이 필요한 경우, InvokeModel의 ModelId 설정을 변경해주시기 바랍니다.

  1. 해당 링크Amazon Bedrock 파운데이션 모델에 대한 액세스 요청 부분을 확인하여 모델 액세스를 얻습니다.
  2. 해당 링크 또는 AWS Step Functions 서비스 콘솔에서 State machines → Create state machine을 클릭합니다.
  3. State machine name에 적절한 상태머신 이름을 입력하고 State machine type Standard로 선택합니다.
  4. 상태머신 편집기의 이름 오른쪽에 있는 토글 메뉴에서 Code를 선택하고 아래 코드를 복사하여 붙여넣습니다.
    1. {
        "QueryLanguage": "JSONata",
        "Comment": "IAM Policy Generator With Amazon Bedrock",
        "StartAt": "Temp state for variables",
        "States": {
          "Temp state for variables": {
            "Type": "Pass",
            "Next": "Create IAM Policy By Bedrock",
            "Assign": {
              "requested_task": "{% $states.input.requested_task %}",
              "account_id": "{% $states.input.account_id %}",
              "region": "{% $states.input.region %}",
              "target_iam_identity_type": "{% $states.input.target_iam_identity %}",
              "target_iam_identity_arn": "{% $states.input.target_iam_identity_arn %}",
              "target_iam_identity_name": "{% ($split($states.input.target_iam_identity_arn, '/'))[-1] %}",
              "required_attach_time": "{% $states.input.required_attach_time %}",
              "try_count": 0
            }
          },
          "Create IAM Policy By Bedrock": {
            "Type": "Task",
            "Resource": "arn:aws:states:::bedrock:invokeModel",
            "Arguments": {
              "ModelId": "arn:aws:bedrock:ap-northeast-2::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0",
              "Body": {
                "anthropic_version": "bedrock-2023-05-31",
                "max_tokens": 4096,
                "messages": [
                  {
                    "role": "user",
                    "content": [
                      {
                        "type": "text",
                        "text": "{% 'You are a competent AWS security professional.\n\nCheck the contents of the <requested-task> tag below to create your IAM policy.\n\n1. the <account-id> tag is requester AWS account ID, and the <region> tag is the region that the requester is required.\n2. the <target-iam-identity-type> tag is the type of Entity that the generated IAM policy will be associated with (ROLE or USER).\n3. the <target-iam-identity-arn> tag is the ARN of the Entity to which the generated IAM policy will be associated.\n4. the <finding> tag is the problem with the policy that the IAM Policy Validator found with the policy generated in the previous request. (For the initial run, NONE)\n\n## Notes\n1. the response must output JSON only, as shown in the response example: {\"policy\": \"YOU_CREATED_POLICY_JSON\", \"policy_type\": \"RESOURCE_POLICY or IDENTITY_POLICY\", \"description\": \"SHORT_DESCRIPTION_FOR_POLICY\"}\n2. follow the principle of least possible authorization, but use wildcards where necessary.\n3. if there are not enough details to generate, output the following: {\"error\": \"ERROR_REASON\"}\n\n<requested-task>' & $requested_task & '</requested-task>\n\n<account-id>' & $account_id & '</account-id>\n<region>' & $region & '</region>\n<target-iam-identity-type>' & $target_iam_identity_type & '</target-iam-identity-type>\n<target-iam-identity-arn>' & $target_iam_identity_arn & '</target-iam-identity-arn>\n<finding>' & ($try_count > 0 ? $string($error_findings_details) : 'NONE') & '</finding>' %}"
                      }
                    ]
                  }
                ]
              }
            },
            "Next": "ValidatePolicy",
            "Assign": {
              "try_count": "{% $try_count + 1 %}"
            },
            "Output": {
              "created_policy": "{% $parse($states.result.Body.content[0].text).policy %}",
              "created_policy_type": "{% $parse($states.result.Body.content[0].text).policy_type %}",
              "created_policy_desc": "{% $parse($states.result.Body.content[0].text).description %}"
            }
          },
          "ValidatePolicy": {
            "Type": "Task",
            "Arguments": {
              "PolicyDocument": "{% $states.input.created_policy %}",
              "PolicyType": "{% $states.input.created_policy_type %}"
            },
            "Resource": "arn:aws:states:::aws-sdk:accessanalyzer:validatePolicy",
            "Next": "Is validated policy",
            "Assign": {
              "created_policy": "{% $states.input.created_policy %}",
              "created_policy_type": "{% $states.input.created_policy_type %}",
              "created_policy_desc": "{% $states.input.created_policy_desc %}",
              "error_findings_details": "{% $exists($states.result.Findings) and $exists($states.result.Findings[FindingType=\"ERROR\"]) ? $join(\n  $map($states.result.Findings[FindingType=\"ERROR\"], function($v, $i) { \n    ($i+1) & \". \" & $v.FindingDetails \n  }), \", \"\n) : \"\" %}"
            },
            "Output": "{% $exists($states.result.Findings[FindingType = \"ERROR\"]) %}"
          },
          "Is validated policy": {
            "Type": "Choice",
            "Choices": [
              {
                "Next": "CreatePolicy",
                "Condition": "{% $states.input = false %}"
              }
            ],
            "Default": "Is try count over 1"
          },
          "CreatePolicy": {
            "Type": "Task",
            "Arguments": {
              "PolicyDocument": "{% $created_policy %}",
              "Description": "{% $created_policy_desc %}",
              "PolicyName": "{% 'generated_by_workflow_' & $millis() %}"
            },
            "Resource": "arn:aws:states:::aws-sdk:iam:createPolicy",
            "Next": "What is target identity",
            "Assign": {
              "created_policy_arn": "{% $states.result.Policy.Arn %}"
            }
          },
          "Is try count over 1": {
            "Type": "Choice",
            "Choices": [
              {
                "Next": "Create request information message for regeneration",
                "Condition": "{% $try_count > 1 %}"
              }
            ],
            "Default": "Create IAM Policy By Bedrock"
          },
          "Create request information message for regeneration": {
            "Type": "Task",
            "Resource": "arn:aws:states:::bedrock:invokeModel",
            "Arguments": {
              "ModelId": "arn:aws:bedrock:ap-northeast-2::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0",
              "Body": {
                "anthropic_version": "bedrock-2023-05-31",
                "max_tokens": 4096,
                "messages": [
                  {
                    "role": "user",
                    "content": [
                      {
                        "type": "text",
                        "text": "{% 'You are a competent AWS security professional.\n\nGiven the information below, you have created an IAM policy based on generative AI, but an error occurred due to the reason in the <error_findings_details> tag. Please write a message to the applicant so we can get more information.\n\n<requested-task>' & $requested_task & '</requested-task>\n\n<account-id>' & $account_id & '</account-id>\n<region>' & $region & '</region>\n<target-iam-identity-type>' & $target_iam_identity_type & '</target-iam-identity-type>\n<target-iam-identity-arn>' & $target_iam_identity_arn & '</target-iam-identity-arn>\n\n<error_findings_details>' & $error_findings_details & '</error_findings_details>' %}"
                      }
                    ]
                  }
                ]
              }
            },
            "Next": "Publish message to client",
            "Output": "{% $states.result.Body.content[0].text %}"
          },
          "Publish message to client": {
            "Type": "Task",
            "Resource": "arn:aws:states:::sns:publish",
            "Arguments": {
              "Message": "{% $states.input %}",
              "TopicArn": "arn:aws:sns:ap-northeast-2:617709266586:TEMP"
            },
            "End": true
          },
          "What is target identity": {
            "Type": "Choice",
            "Choices": [
              {
                "Next": "AttachUserPolicy",
                "Condition": "{% $target_iam_identity_type = 'USER' %}"
              }
            ],
            "Default": "AttachRolePolicy"
          },
          "AttachUserPolicy": {
            "Type": "Task",
            "Arguments": {
              "PolicyArn": "{% $created_policy_arn %}",
              "UserName": "{% $target_iam_identity_name %}"
            },
            "Resource": "arn:aws:states:::aws-sdk:iam:attachUserPolicy",
            "Next": "Wait for policy detach [USER]"
          },
          "Wait for policy detach [USER]": {
            "Type": "Wait",
            "Seconds": "{% $required_attach_time %}",
            "Next": "DetachUserPolicy"
          },
          "DetachUserPolicy": {
            "Type": "Task",
            "Arguments": {
              "PolicyArn": "{% $created_policy_arn %}",
              "UserName": "{% $target_iam_identity_name %}"
            },
            "Resource": "arn:aws:states:::aws-sdk:iam:detachUserPolicy",
            "Next": "DeletePolicy"
          },
          "Send message to administrator": {
            "Type": "Task",
            "Resource": "arn:aws:states:::sns:publish",
            "Arguments": {
              "Message": "{% $states.input %}",
              "TopicArn": "arn:aws:sns:ap-northeast-2:617709266586:TEMP"
            },
            "End": true
          },
          "AttachRolePolicy": {
            "Type": "Task",
            "Arguments": {
              "PolicyArn": "{% $created_policy_arn %}",
              "RoleName": "{% $target_iam_identity_name %}"
            },
            "Resource": "arn:aws:states:::aws-sdk:iam:attachRolePolicy",
            "Next": "Wait for policy detach [ROLE]"
          },
          "Wait for policy detach [ROLE]": {
            "Type": "Wait",
            "Seconds": "{% $required_attach_time %}",
            "Next": "DetachRolePolicy"
          },
          "DetachRolePolicy": {
            "Type": "Task",
            "Arguments": {
              "PolicyArn": "{% $created_policy_arn %}",
              "RoleName": "{% $target_iam_identity_name %}"
            },
            "Resource": "arn:aws:states:::aws-sdk:iam:detachRolePolicy",
            "Next": "DeletePolicy"
          },
          "DeletePolicy": {
            "Type": "Task",
            "Arguments": {
              "PolicyArn": "{% $created_policy_arn %}"
            },
            "Resource": "arn:aws:states:::aws-sdk:iam:deletePolicy",
            "Next": "Send message to administrator"
          }
        }
      }
      JSON
  5. Publish message to client, Send message to administrator 상태를 각각 클릭하여 Configuration 탭의 아래에 Topic 섹션을 확인하고 메세지를 게시할 SNS Topic으로 변경해줍니다. (메세지 게시가 필요없는 경우, 상태를 제거해도 상관없습니다. 상태 선택 후, 키보드의 [Delete]키를 눌러 삭제합니다.)
  6. 우측 상단의 Create 를 클릭하여 상태머신을 생성합니다. (AWS Step Functions는 상태머신에서 사용된 서비스의 기능에 대한 권한을 자동으로 생성합니다. 하지만 일부 서비스의 정책들은 직접 설정해주어야합니다.)
  7. 상태머신 편집기의 이름 오른쪽에 있는 토글 메뉴에서 Config를 선택하고 Permissions 섹션의 Execution role 오른쪽의 View in IAM을 클릭합니다.
  8. IAM 콘솔에서 Permission 탭의 Add permissionsCreate inline policy를 선택합니다.
      1. Policy editor의 우측 토글에서 JSON으로 변경 후, 아래 정책을 복사하여 붙여넣고 생성합니다.
      2. ❗주의가 필요합니다❗ 해당 정책은 IAM Policy 생성 및 삭제 등의 중요 권한을 포함합니다. 사용에 유의하여주세요. (사용하지 않는 경우, Policy를 삭제해주세요.)
      3. {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Sid": "IAMPolicyGeneratorPolicy",
                    "Effect": "Allow",
                    "Action": [
                        "access-analyzer:ValidatePolicy",
                        "iam:CreatePolicy",
                        "iam:DetachRolePolicy",
                        "iam:AttachUserPolicy",
                        "iam:DeletePolicy",
                        "iam:AttachRolePolicy",
                        "iam:DetachUserPolicy"
                    ],
                    "Resource": "*"
                }
            ]
        }
        JSON
  9. 다시 Step Functions 콘솔에서 만든 상태머신으로 돌아와 우측 상단 Start execution을 클릭하고 위의 워크플로 입력 예시를 참고하여 입력값을 주고 실행해봅니다.
  10.  [선택사항] 필요한 경우, 아래 스크린샷을 참고하여 프롬프트를 수정하여 Policy 생성에 필요한 정보, 제약사항 등을 수정하고 저장 후, 테스트해봅니다. (참고: Transforming data with JSONata in Step Functions)

워크플로 타입과 실행 방법

해당 포스팅의 워크플로는 Standard 타입 기반으로 생성되었습니다.

AWS Step Functions의 타입에는 Standard와 Express 두가지가 있습니다. 간략히 Standard는 비동기식으로 실행되며 실행 이후, 결과값을 바로 받을 수 없습니다. Express는 실행 이후, 결과값을 동기식으로 바로 받을 수 있거나 비동기식으로 실행할 수도 있습니다. 아래 비교 표를 참고하여 적합한 타입을 선택할 수 있습니다. 비용에 대한 자세한 내용은 해당 링크를 통해 확인하실 수 있습니다.


<AWS Step Functions 상태머신 타입에 따른 비교>

생성한 워크플로는 콘솔 뿐만 아니라 아래와 같이 다양한 방식으로 실행될 수 있습니다.

  • AWS Management Console 사용: Step Functions 콘솔에서 상태 머신을 선택하고 실행 시작(Start execution)을 클릭합니다.
  • AWS CLI 사용: 아래 명령어로 상태 머신을 실행합니다.
    • aws stepfunctions start-execution --state-machine-arn [상태 머신 ARN] --input '{"key": "value"}'
      Bash
  • AWS SDK 사용: 예를 들어, 아래 예제 코드와 같이 Python의 boto3 라이브러리를 사용하여 상태 머신을 실행합니다:
    • import boto3
      
      client = boto3.client('stepfunctions')
      
      response = client.start_execution(
          stateMachineArn='[상태 머신 ARN]',
          input='{"key": "value"}'
      )
      Python
  • Amazon API Gateway 사용: API Gateway를 설정하여 HTTP 요청을 통해 상태 머신을 실행할 수 있습니다.
  • Amazon EventBridge 사용: EventBridge 규칙을 설정하여 특정 이벤트 발생 시 상태 머신을 자동으로 실행할 수 있습니다.

마무리

소개된 워크플로는 클라우드 보안 관리 및 권한 제어에서 적용될 수 있으며, 권한 승인 프로세스를 추가하거나 정책 생성에 대한 조건을 추가하는 프롬프트로 조정하거나 관리자에게 알림을 받는 채널 다양화 등 필요에 따라 수정하여 사용하실 수 있습니다.

비즈니스 활용 방안을 정리하면 아래와 같습니다.

  • 보안 강화: 최소 권한 원칙을 자동으로 적용하여 필요한 기간 동안만 정확한 권한을 부여함으로써 보안 위험 최소화
  • 운영 효율성: 보안팀의 수동 작업 감소 및 권한 요청-승인-회수 프로세스 자동화로 업무 효율 향상
  • 규정 준수: 임시 권한 부여 및 자동 회수를 통한 감사 추적 개선과 규정 준수 요구사항 충족

이처럼 AWS Step Functions을 활용하여 여러분의 비즈니스에 핵심이 되는 기능을 제작하거나 효율적인 자동화 워크플로를 다양하게 구축해 보세요! 🚀

Sangbeom Ma

Sangbeom Ma

마상범 솔루션즈 아키텍트는 영상 이커머스 스타트업 경험을 바탕으로, 클라우드를 통한 애플리케이션 운영, 미디어, AIML 등 다양한 영역에서 고객이 최적의 아키텍처를 구성하도록 돕고 고객의 비즈니스 성과를 달성하도록 AWS 클라우드 전환을 지원하는 업무를 담당하고 있습니다.