{"id":3029,"date":"2026-03-05T13:25:29","date_gmt":"2026-03-05T04:25:29","guid":{"rendered":"https:\/\/manvscloud.com\/?p=3029"},"modified":"2026-03-05T13:25:29","modified_gmt":"2026-03-05T04:25:29","slug":"aws-bedrock-agent%ec%99%80-step-functions%eb%a1%9c-%ea%b5%ac%ed%98%84%ed%95%98%eb%8a%94-aws-iam-%eb%b3%b4%ec%95%88-%ec%82%ac%ea%b3%a0-%ec%9e%90%eb%8f%99-%eb%8c%80%ec%9d%91-%ed%8c%8c%ec%9d%b4","status":"publish","type":"post","link":"https:\/\/manvscloud.com\/?p=3029","title":{"rendered":"[AWS] Bedrock Agent\uc640 Step Functions\ub85c \uad6c\ud604\ud558\ub294 AWS IAM \ubcf4\uc548 \uc0ac\uace0 \uc790\ub3d9 \ub300\uc751 \ud30c\uc774\ud504\ub77c\uc778"},"content":{"rendered":"\n<p>\uc548\ub155\ud558\uc138\uc694. MANVSCLOUD \uae40\uc218\ud604\uc785\ub2c8\ub2e4.<\/p>\n\n\n\n<p>AWS \ud658\uacbd\uc5d0\uc11c \uac1c\ubc1c\ud558\ub2e4 \ubcf4\uba74 \uac00\uc7a5 \uc870\uc2ec\ud574\uc57c \ud560 \uac83 \uc911 \ud558\ub098\uac00 \ubc14\ub85c IAM Access Key \uad00\ub9ac\uc785\ub2c8\ub2e4. \uc2e4\uc218\ub85c GitHub\uc5d0 .env \ud30c\uc77c\uc744 \ud478\uc2dc\ud558\uac70\ub098 \uacf5\uac1c \uc800\uc7a5\uc18c\uc5d0 \ud0a4\uac00 \ub178\ucd9c\ub418\ub294 \uc21c\uac04 \uacf5\uaca9\uc790\ub294 \uc9e7\uc740 \uc2dc\uac04 \ub0b4\uc5d0 \uc774\ub97c \uc545\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc2e4\uc81c\ub85c GitHub\uc5d0 \uc5c5\ub85c\ub4dc\ub41c AWS Credential\uc740 \ud3c9\uade0 5\ubd84 \uc774\ub0b4\uc5d0 \uc2a4\uce94\ub418\uace0 \uc545\uc6a9\ub41c\ub2e4\uace0 \ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<p>\uc0ac\uc2e4 \uc774\ub7ec\ud55c \ubb38\uc81c\ub97c \ud574\uacb0\ud558\uae30 \uc704\ud574 AWS\ub294 \ud0a4\uac00 \ub178\ucd9c\ub420 \uacbd\uc6b0 AWS Health \uc11c\ube44\uc2a4\ub97c \uc774\uc6a9\ud558\uc5ec \uc0ac\uc6a9\uc790\uc5d0\uac8c \uc54c\ub824\uc8fc\uace0 \uc788\uc2b5\ub2c8\ub2e4. <\/p>\n\n\n\n<p>\ud558\uc9c0\ub9cc \ud3c9\uc18c\uc5d0 \uba54\uc77c\uc744 \uc798 \ud655\uc778\ud558\uc9c0 \uc54a\uac70\ub098 \uad00\ub9ac\ud558\ub294 \uacc4\uc815\uc774 \ub9ce\uc544\uc11c \uc790\uce6b \uba54\uc77c\uc744 \ub193\uce60 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ub530\ub77c\uc11c \uba54\uc77c \uc54c\ub9bc\uc744 \ud655\uc778\ud558\uae30\uae4c\uc9c0 \uc2dc\uac04\uc774 \uc18c\uc694\ub418\uac70\ub098 \uc720\ucd9c\ub41c \uc0ac\uc2e4 \uc870\ucc28 \ubaa8\ub978\ucc44 \uc9c0\ub098\uac08 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ub610\ud55c \ub4a4\ub2a6\uac8c \ud0a4 \uc720\ucd9c \uc2dc \uc2dc\uc2a4\ud15c\uc5d0 \uc0ac\uc6a9\ub418\ub294 \uc815\uc0c1\uc801\uc778 \ud638\ucd9c\ub9cc \ub418\uc5c8\ub294\uc9c0,  \uc545\uc6a9\ub41c \ud638\ucd9c\uc774 \uc788\ub294\uc9c0 CloudTrail \ub85c\uadf8\ub97c \uc218\ub3d9\uc73c\ub85c \ud655\uc778\ud558\uba70 \uc601\ud5a5 \ubc94\uc704\ub97c \ud30c\uc545\ud558\uae30\uc5d0\ub294 \uc624\ub79c \uc2dc\uac04\uc774 \uc18c\uc694\ub429\ub2c8\ub2e4. \ubfd0\ub9cc \uc544\ub2c8\ub77c \uc57c\uac04\uc774\ub098 \ud734\uc77c\uc5d0 \uc774\ub7ec\ud55c \uc0ac\uace0\uac00 \ubc1c\uc0dd\ud55c\ub2e4\uba74 \uc0ac\uace0 \ub300\uc751 \uc9c0\uc5f0\uc73c\ub85c \uc774\uc5b4\uc9c8 \uc218\ub3c4 \uc788\uad6c\uc694.<\/p>\n\n\n\n<p>\uc624\ub298\uc740 \uc774\ub7ec\ud55c \ubb38\uc81c\ub97c \ud574\uacb0\ud558\uae30 \uc704\ud574 AWS\uc758 \uad00\ub9ac\ud615 \uc11c\ube44\uc2a4\ub4e4\uc744 \uc870\ud569\ud558\uc5ec IAM Access Key \uc720\ucd9c \uc0ac\uace0\ub97c \uc790\ub3d9\uc73c\ub85c <strong>\ud0d0\uc9c0-\ubd84\uc11d-\ub300\uc751<\/strong>\ud558\ub294 End to End \ud30c\uc774\ud504\ub77c\uc778\uc744 \uad6c\ucd95\ud558\ub294 \ubc29\ubc95\uc744 \uc18c\uac1c\ud558\uaca0\uc2b5\ub2c8\ub2e4. \ud2b9\ud788 Bedrock Agent\ub97c \ud65c\uc6a9\ud55c AI \uae30\ubc18 \uc0ac\uace0 \ubd84\uc11d\uacfc Step Functions\uc758 JSONata\ub97c \ud65c\uc6a9\ud55c \uc720\uc5f0\ud55c \uc6cc\ud06c\ud50c\ub85c\uc6b0 \uc81c\uc5b4\uc5d0 \uc911\uc810\uc744 \ub450\uc5c8\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<ul>\n<li>\ucc38\uace0 : <a href=\"https:\/\/github.com\/aws-samples\/notify-and-remediate-exposed-access-key\">https:\/\/github.com\/aws-samples\/notify-and-remediate-exposed-access-key<\/a><\/li>\n<\/ul>\n\n\n\n<p>\uc774 \uc2dc\uc2a4\ud15c\uc744 \uad6c\uc131\ud558\uae30 \uc704\ud574 aws-samples\uc5d0 \uc788\ub294 \uc704 &#8216;AWS Access Key Exposure Detection and Remediation System&#8217; URL\uc744 \ucc38\uace0\ud558\uc600\uace0, \uc800\uc640 \uac19\uc774 Email\uc774 \uc544\ub2cc \ub2e4\ub978 \uc194\ub8e8\uc158\uc73c\ub85c \uc54c\ub9bc\uc744 \ubc1b\uae38 \uc6d0\ud558\uc2dc\uac70\ub098, CloudFormation \ud615\ud0dc\uac00 \uc544\ub2c8\ub77c \uc2e4\uc81c \ucf58\uc194\uc5d0\uc11c \uc9c1\uc811 \uad6c\ucd95\ud558\uc5ec \uad00\ub9ac\ub97c \uc6d0\ud558\uc2dc\ub294 \ubd84\ub4e4\uc744 \uc704\ud574 \uc870\uae08 \ub354 \uc5c5\uadf8\ub808\uc774\ub4dc\ud558\uc5ec \ud3ec\uc2a4\ud305\uc744 \uc9c4\ud589\ud588\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading has-white-color has-luminous-vivid-amber-background-color has-text-color has-background has-link-color wp-elements-4fd37bc1061761cf61d5d15dab6651c7\"> \uc2dc\uc2a4\ud15c \uc544\ud0a4\ud14d\ucc98 \uac1c\uc694<\/h3>\n\n\n\n<p>\uc804\uccb4 \uc2dc\uc2a4\ud15c\uc740 \uc774\ubca4\ud2b8 \uae30\ubc18(Event-Driven) \uc544\ud0a4\ud14d\ucc98\ub85c \uc124\uacc4\ub418\uc5c8\uc73c\uba70 Cloud-Native \uc11c\ube44\uc2a4\ub9cc\uc73c\ub85c \uad6c\uc131\ub418\uc5b4 \ubcc4\ub3c4\uc758 \uc778\ud504\ub77c \uad00\ub9ac \ubd80\ub2f4 \uc5c6\uc774 \uc6b4\uc601\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"669\" height=\"720\" src=\"https:\/\/cdn.manvscloud.com\/wp-content\/uploads\/2026\/01\/20100235\/image.png\" alt=\"\" class=\"wp-image-3033\" srcset=\"https:\/\/cdn.manvscloud.com\/wp-content\/uploads\/2026\/01\/20100235\/image.png 669w, https:\/\/cdn.manvscloud.com\/wp-content\/uploads\/2026\/01\/20100235\/image-279x300.png 279w\" sizes=\"(max-width: 669px) 100vw, 669px\" \/><\/figure>\n\n\n\n<p>\uc2dc\uc2a4\ud15c\uc758 \uc804\uccb4 \ud750\ub984\uc740 \ub2e4\uc74c\uacfc \uac19\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<ol>\n<li>AWS Health\uac00 IAM Access Key \uc720\ucd9c\uc744 \uac10\uc9c0\ud558\uba74 <code><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-red-color\">AWS_RISK_CREDENTIALS_EXPOSED<\/mark><\/code> \uc774\ubca4\ud2b8\ub97c \ubc1c\uc0dd\uc2dc\ud0b5\ub2c8\ub2e4.<\/li>\n\n\n\n<li>EventBridge\uac00 \uc774 \uc774\ubca4\ud2b8\ub97c \ud3ec\ucc29\ud558\uc5ec Step Functions \uc6cc\ud06c\ud50c\ub85c\uc6b0\ub97c \uc2dc\uc791\ud569\ub2c8\ub2e4.<\/li>\n\n\n\n<li>Step Functions\uac00 Lambda \ud568\uc218\ub4e4\uc744 \uc21c\ucc28\uc801\uc73c\ub85c \uc624\ucf00\uc2a4\ud2b8\ub808\uc774\uc158\ud569\ub2c8\ub2e4.<\/li>\n\n\n\n<li>NotifyIncident Lambda\uac00 CloudTrail\uc744 \ubd84\uc11d\ud558\uace0 Bedrock Agent\ub85c AI \ubd84\uc11d\uc744 \uc218\ud589\ud55c \ud6c4 Slack\uc73c\ub85c \uc54c\ub9bc\uc744 \uc804\uc1a1\ud569\ub2c8\ub2e4.<\/li>\n\n\n\n<li>\ud658\uacbd\ubcc0\uc218 \uc124\uc815\uc5d0 \ub530\ub77c \uc790\ub3d9\uc73c\ub85c Access Key\ub97c \ube44\ud65c\uc131\ud654\ud558\uac70\ub098 \uc218\ub3d9 \uc870\uce58\ub97c \ub300\uae30\ud569\ub2c8\ub2e4.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading has-white-color has-luminous-vivid-amber-background-color has-text-color has-background has-link-color wp-elements-13b1252739da7d02a291e773edac002f\"> \ud575\uc2ec \ucef4\ud3ec\ub10c\ud2b8 \uad6c\ud604<\/h3>\n\n\n\n<p><strong>1. EventBridge Rule \uc124\uc815<\/strong><\/p>\n\n\n\n<p>EventBridge Rule\uc740 AWS Health \uc774\ubca4\ud2b8 \uc911 IAM \ud06c\ub808\ub374\uc15c \uc720\ucd9c \uc774\ubca4\ud2b8\ub9cc \ud544\ud130\ub9c1\ud558\uc5ec Step Functions\ub97c \ud2b8\ub9ac\uac70\ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">{\n  \"source\": [\"aws.health\"],\n  \"detail-type\": [\"AWS Health Event\"],\n  \"detail\": {\n    \"eventTypeCode\": [\"AWS_RISK_CREDENTIALS_EXPOSED\"]\n  }\n}<\/pre>\n\n\n\n<p>\ud574\ub2f9 \uc774\ubca4\ud2b8 \ud328\ud134\uc744 \ud1b5\ud574 IAM \uad00\ub828 \ubcf4\uc548 \uc0ac\uace0\ub9cc \uc120\ubcc4\uc801\uc73c\ub85c \ucc98\ub9ac\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<p><strong>2. Step Functions \uc6cc\ud06c\ud50c\ub85c\uc6b0<\/strong><\/p>\n\n\n\n<p>Step Functions\ub294 \uc804\uccb4 \ub300\uc751 \ud504\ub85c\uc138\uc2a4\ub97c \uc2dc\uac01\uc801\uc73c\ub85c \uad00\ub9ac\ud558\uace0 \uc624\ucf00\uc2a4\ud2b8\ub808\uc774\uc158\ud569\ub2c8\ub2e4. <br>\ucf58\uc194\uc5d0\uc11c \uc2dc\uac01\uc801\uc73c\ub85c \ud558\ub098\uc529 \uc120\ud0dd\ud558\uc5ec \uc5f0\uacb0\ud560 \uc218\ub3c4 \uc788\uaca0\uc9c0\ub9cc \ubcf8 \ud3ec\uc2a4\ud305\uc5d0\uc11c\ub294 \uc791\uc131\ub41c Amazon States Language(ASL)\uc744 \uacf5\uc720\ud558\uaca0\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">{\n  \"Comment\": \"A description of my state machine\",\n  \"StartAt\": \"SetAutoRemediationFlag\",\n  \"States\": {\n    \"SetAutoRemediationFlag\": {\n      \"Type\": \"Pass\",\n      \"Next\": \"NotifyIncident\"\n    },\n    \"NotifyIncident\": {\n      \"Type\": \"Task\",\n      \"Resource\": \"arn:aws:states:::lambda:invoke\",\n      \"Arguments\": {\n        \"FunctionName\": \"arn:aws:lambda:us-east-1:888888888888:function:manvscloud-risk-credentials-exposed-lmd:$LATEST\",\n        \"Payload\": \"{% $states.input %}\"\n      },\n      \"Output\": \"{% $states.result.Payload %}\",\n      \"Retry\": [\n        {\n          \"ErrorEquals\": [\n            \"Lambda.ServiceException\",\n            \"Lambda.AWSLambdaException\",\n            \"Lambda.SdkClientException\",\n            \"Lambda.TooManyRequestsException\"\n          ],\n          \"IntervalSeconds\": 1,\n          \"MaxAttempts\": 3,\n          \"BackoffRate\": 2,\n          \"JitterStrategy\": \"FULL\"\n        }\n      ],\n      \"Next\": \"CheckAutoRemediation\"\n    },\n    \"CheckAutoRemediation\": {\n      \"Type\": \"Choice\",\n      \"Choices\": [\n        {\n          \"Next\": \"DisableAccessKey\",\n          \"Condition\": \"{% $states.input.enableAutoRemediation = true %}\"\n        }\n      ],\n      \"Default\": \"WorkflowComplete\"\n    },\n    \"DisableAccessKey\": {\n      \"Type\": \"Task\",\n      \"Resource\": \"arn:aws:states:::lambda:invoke\",\n      \"Arguments\": {\n        \"FunctionName\": \"arn:aws:lambda:us-east-1:888888888888:function:manvscloud-disable-accesskey-lmd:$LATEST\",\n        \"Payload\": \"{% $states.input %}\"\n      },\n      \"Output\": \"{% $states.result.Payload %}\",\n      \"Retry\": [\n        {\n          \"ErrorEquals\": [\n            \"Lambda.ServiceException\",\n            \"Lambda.AWSLambdaException\",\n            \"Lambda.SdkClientException\",\n            \"Lambda.TooManyRequestsException\"\n          ],\n          \"IntervalSeconds\": 1,\n          \"MaxAttempts\": 3,\n          \"BackoffRate\": 2,\n          \"JitterStrategy\": \"FULL\"\n        }\n      ],\n      \"Next\": \"WorkflowComplete\"\n    },\n    \"WorkflowComplete\": {\n      \"Type\": \"Succeed\"\n    }\n  },\n  \"QueryLanguage\": \"JSONata\"\n}<\/pre>\n\n\n\n<p>\uc774 \uacfc\uc815\uc5d0\uc11c JSONata\ub97c \uc0ac\uc6a9\ud558\uba74 Lambda \ud568\uc218\uc5d0 \uc804\ub2ec\ud560 \ub370\uc774\ud130\ub97c \ucf54\ub4dc \uc5c6\uc774 \ubcc0\ud658\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">{\n  \"accessKeyId\": $.detail.affectedEntities[0].entityValue,\n  \"userName\": $.detail.affectedEntities[0].tags.userName,\n  \"eventTime\": $.time\n}<\/pre>\n\n\n\n<p>\uc6cc\ud06c\ud50c\ub85c\uc6b0\uc758 \uc8fc\uc694 \ub2e8\uacc4\ub294 \ub2e4\uc74c\uacfc \uac19\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<p>1) <strong>NotifyIncident \ub2e8\uacc4<\/strong>: CloudTrail \ubd84\uc11d \ubc0f AI \uae30\ubc18 \uc0ac\uace0 \uc694\uc57d \uc0dd\uc131<br>2) <strong>CheckAutoRemediation \ub2e8\uacc4<\/strong>: \ud658\uacbd\ubcc0\uc218 \uae30\ubc18\uc73c\ub85c \uc790\ub3d9 \ubcf5\uad6c \uc5ec\ubd80 \ud655\uc778<br>3) <strong>DisableAccessKey \ub2e8\uacc4<\/strong>: \uc790\ub3d9 \ubaa8\ub4dc\uc778 \uacbd\uc6b0 Access Key \ube44\ud65c\uc131\ud654<\/p>\n\n\n\n<p><strong>3. Lambda \ud568\uc218<\/strong><\/p>\n\n\n\n<ul>\n<li><strong>Credentials Exposed Notification<\/strong><\/li>\n<\/ul>\n\n\n\n<p>\uc774 Lambda \ud568\uc218\ub294 \ubcf4\uc548 \uc0ac\uace0 \ub300\uc751\uc758 \ud575\uc2ec \ub85c\uc9c1\uc744 \ub2f4\ub2f9\ud558\uba70 \ud06c\uac8c \ub124 \uac00\uc9c0 \uc5ed\ud560\uc744 \uc218\ud589\ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<p>1) CloudTrail \ub85c\uadf8 \ubd84\uc11d<br> : \uc720\ucd9c\ub41c Access Key\ub85c \uc218\ud589\ub41c \ucd5c\uadfc 7\uc77c\uac04\uc758 \ubaa8\ub4e0 API \ud638\ucd9c\uc744 \ubd84\uc11d\ud569\ub2c8\ub2e4. \uc774\ub97c \ud1b5\ud574 \ub2e4\uc74c \uc815\ubcf4\ub97c \uc218\uc9d1\ud569\ub2c8\ub2e4. <\/p>\n\n\n\n<p>&#8211; \uc0ac\uc6a9\ub41c AWS \ub9ac\uc804 \ubaa9\ub85d<br>&#8211; \ud638\ucd9c\ub41c API \uc791\uc5c5 \uc720\ud615 \ubc0f \ube48\ub3c4<br>&#8211; \uc811\uadfc \uc18c\uc2a4 IP \uc8fc\uc18c \ubd84\uc11d<br>&#8211; \uc5d0\ub7ec \ubc1c\uc0dd \uc5ec\ubd80 (\uad8c\ud55c \ubd80\uc871 \uc2dc\ub3c4 \ub4f1 \uc758\uc2ec\uc2a4\ub7ec\uc6b4 \ud589\ub3d9 \ud328\ud134)<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># CloudTrail API \ud638\ucd9c \uc608\uc2dc\ncloudtrail_client = boto3.client('cloudtrail')\nresponse = cloudtrail_client.lookup_events(\n    LookupAttributes=[\n        {\n            'AttributeKey': 'AccessKeyId',\n            'AttributeValue': access_key_id\n        }\n    ],\n    StartTime=datetime.now() - timedelta(days=7),\n    MaxResults=50\n)<\/pre>\n\n\n\n<p>\uc704 \ubd84\uc11d\uc744 \ud1b5\ud574 \uacf5\uaca9\uc790\uac00 \uc774\ubbf8 \ud574\ub2f9 \ud0a4\ub97c \uc545\uc6a9\ud588\ub294\uc9c0, \uc5b4\ub5a4 \ub9ac\uc18c\uc2a4\uc5d0 \uc811\uadfc\uc744 \uc2dc\ub3c4\ud588\ub294\uc9c0 \ud30c\uc545\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<p>2) Bedrock Agent \uae30\ubc18 AI \ubd84\uc11d<br> : \ud604\uc7ac \uad6c\ud604\ud558\uace0\uc790 \ud558\ub294 \uc2dc\uc2a4\ud15c\uc5d0\uc11c Bedrock Foundation \ubaa8\ub378\uc744 \uadf8\ub0e5 \uc0ac\uc6a9\ud574\ub3c4 \ub418\uc9c0\ub9cc \uad73\uc774 Bedrock Agent\ub97c \uc0ac\uc6a9\ud55c \uc774\uc720\ub294 Lambda \ucf54\ub4dc\uc5d0 \uc9c0\uc800\ubd84\ud558\uac8c \ud504\ub86c\ud504\ud2b8\ub97c \uae4d\uc544\uac00\uba70 \uad00\ub9ac\ud558\uc9c0 \uc54a\uace0 Agent\uc758 \uc5d0\uc774\uc804\ud2b8 \uc9c0\uce68\uc73c\ub85c \ud504\ub86c\ud504\ud2b8\ub97c \uad00\ub9ac\ud558\uba70 Versioning\uc744 \ud65c\uc6a9\ud558\uc5ec \uad50\uccb4\ud558\ub294 \ubc29\uc2dd\uc73c\ub85c \uc0ac\uc6a9\ud558\uace0 \uc2f6\uc5c8\uae30\ub54c\ubb38\uc785\ub2c8\ub2e4.<\/p>\n\n\n\n<p>\uc218\uc9d1\ub41c CloudTrail \ub370\uc774\ud130\ub97c Bedrock Agent\uc5d0\uac8c \uc804\ub2ec\ud558\uc5ec AI \uae30\ubc18 \ubd84\uc11d\uc744 \uc218\ud589\ud569\ub2c8\ub2e4. <br>Bedrock Agent\ub294 \ub2e8\uc21c \ud328\ud134 \ub9e4\uce6d\uc744 \ub118\uc5b4 \uc804\uccb4 \ucee8\ud14d\uc2a4\ud2b8\ub97c \uc774\ud574\ud558\uace0 \uc885\ud569\uc801\uc778 \ubd84\uc11d\uc744 \uc81c\uacf5\ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<p>&#8211; \uc0ac\uace0\uc758 \uc2ec\uac01\ub3c4 \ud3c9\uac00 (Critical\/High\/Medium\/Low)<br>&#8211; \uc601\ud5a5 \ubc1b\uc740 \uc11c\ube44\uc2a4 \ubc0f \ub9ac\uc18c\uc2a4 \uc2dd\ubcc4<br>&#8211; \uc7a0\uc7ac\uc801 \ud53c\ud574 \ubc94\uc704 \uc608\uce21<br>&#8211; \uad8c\uc7a5 \ud6c4\uc18d \uc870\uce58 \uc81c\uc548<\/p>\n\n\n\n<p>\uc608\ub97c \ub4e4\uc5b4 S3 \ubc84\ud0b7 \uc811\uadfc \uc2dc\ub3c4\uac00 \uc788\uc5c8\ub2e4\uba74 \ub370\uc774\ud130 \uc720\ucd9c \uac00\ub2a5\uc131\uc744, EC2 \uc778\uc2a4\ud134\uc2a4 \uc0dd\uc131 \uc2dc\ub3c4\uac00 \uc788\uc5c8\ub2e4\uba74 \uc554\ud638\ud654\ud3d0 \ucc44\uad74 \uc704\ud5d8\uc744 \uc790\ub3d9\uc73c\ub85c \uc5b8\uae09\ud569\ub2c8\ub2e4.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># Bedrock Agent \ud638\ucd9c \uc608\uc2dc\nbedrock_agent = boto3.client('bedrock-agent-runtime')\nresponse = bedrock_agent.invoke_agent(\n    agentId=agent_id,\n    agentAliasId=agent_alias_id,\n    sessionId=session_id,\n    inputText=f\"\ub2e4\uc74c CloudTrail \ub85c\uadf8\ub97c \ubd84\uc11d\ud574\uc8fc\uc138\uc694: {cloudtrail_summary}\"\n)\n```\n\nBedrock Agent\ub97c \ud6a8\uacfc\uc801\uc73c\ub85c \ud65c\uc6a9\ud558\uae30 \uc704\ud574\uc11c\ub294 \uc801\uc808\ud55c \ud504\ub86c\ud504\ud2b8 \uc124\uc815\uc774 \uc911\uc694\ud569\ub2c8\ub2e4:\n```\n\ub2f9\uc2e0\uc740 AWS \ubcf4\uc548 \uc804\ubb38\uac00\uc785\ub2c8\ub2e4. \ub2e4\uc74c CloudTrail \ub85c\uadf8\ub97c \ubd84\uc11d\ud558\uc5ec:\n1. \uc0ac\uace0 \uc2ec\uac01\ub3c4\ub97c \ud3c9\uac00\ud558\uc138\uc694 (Critical\/High\/Medium\/Low)\n2. \uacf5\uaca9\uc790\uac00 \uc811\uadfc\ud55c \ub9ac\uc18c\uc2a4\ub97c \uc2dd\ubcc4\ud558\uc138\uc694\n3. \uc7a0\uc7ac\uc801 \ud53c\ud574 \ubc94\uc704\ub97c \uc608\uce21\ud558\uc138\uc694\n4. \uad8c\uc7a5 \ud6c4\uc18d \uc870\uce58\ub97c \uc81c\uc548\ud558\uc138\uc694\n\nJSON \ud615\uc2dd\uc73c\ub85c \uad6c\uc870\ud654\ub41c \uc751\ub2f5\uc744 \uc81c\uacf5\ud558\uc138\uc694.<\/pre>\n\n\n\n<p>3) Slack \uc54c\ub9bc \uc804\uc1a1<\/p>\n\n\n\n<p>\ubd84\uc11d \uacb0\uacfc\ub97c Slack \ucc44\ub110\ub85c \uc2e4\uc2dc\uac04 \uc804\uc1a1\ud569\ub2c8\ub2e4. \uc0ac\uc2e4 \uc694\uc998 \uc774\uba54\uc77c\uc740 \uc8fc\uae30\uc801\uc73c\ub85c \uc798 \ud655\uc778\uc744 \uc548\ud558\uac8c \ub418\uc5b4 Slack\uc744 \uc120\ud0dd\ud588\uc2b5\ub2c8\ub2e4. Slack \uc54c\ub9bc\uc5d0\ub294 \ub2e4\uc74c \uc815\ubcf4\uac00 \ud3ec\ud568\ub429\ub2c8\ub2e4.<\/p>\n\n\n\n<ul>\n<li>\uc720\ucd9c\ub41c Access Key \uc815\ubcf4 (Key ID, IAM User)<\/li>\n\n\n\n<li>CloudTrail \ubd84\uc11d \uacb0\uacfc \uc694\uc57d (\uc0ac\uc6a9\ub41c \ub9ac\uc804, API \ud638\ucd9c \ud1b5\uacc4, \uc18c\uc2a4 IP)<\/li>\n\n\n\n<li>AI\uac00 \uc0dd\uc131\ud55c \uc704\ud5d8\ub3c4 \ud3c9\uac00 \ubc0f \ubd84\uc11d<\/li>\n\n\n\n<li>\uc790\ub3d9 \uc870\uce58 \uc2e4\ud589 \uc5ec\ubd80<\/li>\n\n\n\n<li>Step Functions \uc2e4\ud589 \ub9c1\ud06c (\uc0c1\uc138 \ucd94\uc801\uc6a9)<\/li>\n<\/ul>\n\n\n\n<p>4) \ud658\uacbd\ubcc0\uc218 \uae30\ubc18 \ub3d9\uc791 \uc81c\uc5b4<\/p>\n\n\n\n<p>\uc774 \ubd80\ubd84\uc740 \uc120\ud0dd\uc801 \uc694\uc18c\uc778\ub370 <code><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-red-color\">ENABLE_AUTO_REMEDIATION<\/mark><\/code> \ud658\uacbd\ubcc0\uc218\ub97c \ud1b5\ud574 \uc790\ub3d9\/\uc218\ub3d9 \ub300\uc751 \ubaa8\ub4dc\ub97c \uc81c\uc5b4\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<p>&#8211; <code>true<\/code> : Access Key\ub97c \uc989\uc2dc \uc790\ub3d9 \ube44\ud65c\uc131\ud654<br>&#8211; <code>false<\/code> : \uc54c\ub9bc\ub9cc \uc804\uc1a1\ud558\uace0 \uc218\ub3d9 \uc870\uce58 \ub300\uae30<\/p>\n\n\n\n<p>\uc81c \ucf54\ub4dc\uc5d0 \uc801\uc6a9\ub41c \ud658\uacbd \ubcc0\uc218\ub294 \uc544\ub798\uc640 \uac19\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<ul>\n<li>BEDROCK_AGENT_ALIAS_ID<\/li>\n\n\n\n<li>BEDROCK_AGENT_ID<\/li>\n\n\n\n<li>ENABLE_AUTO_REMEDIATION<\/li>\n\n\n\n<li>MANAGED_PRIMARY<\/li>\n\n\n\n<li>MANAGED_SECONDARY<\/li>\n\n\n\n<li>SLACK_WEBHOOK_URL<\/li>\n\n\n\n<li>SYSTEM_OWNER<\/li>\n<\/ul>\n\n\n\n<p>5) IAM \uc5ed\ud560\uc5d0 \ud3ec\ud568\ub41c \uc815\ucc45<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": \"logs:CreateLogGroup\",\n            \"Resource\": \"arn:aws:logs:us-east-1:888888888888:*\"\n        },\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"logs:CreateLogStream\",\n                \"logs:PutLogEvents\"\n            ],\n            \"Resource\": [\n                \"arn:aws:logs:us-east-1:888888888888:log-group:\/aws\/lambda\/manvscloud-risk-credentials-exposed-lmd:*\"\n            ]\n        },\n        {\n            \"Sid\": \"EC2DescribeRegions\",\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"ec2:DescribeRegions\"\n            ],\n            \"Resource\": \"*\"\n        },\n        {\n            \"Sid\": \"CloudTrailLookupEvents\",\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"cloudtrail:LookupEvents\"\n            ],\n            \"Resource\": \"*\"\n        },\n        {\n            \"Sid\": \"BedrockInvokeModel\",\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"bedrock:InvokeAgent\"\n            ],\n            \"Resource\": [\n                \"arn:aws:bedrock:us-east-1:888888888888:agent\/*\",\n                \"arn:aws:bedrock:us-east-1:888888888888:agent-alias\/*\"\n            ]\n        }\n    ]\n}<\/pre>\n\n\n\n<p>\uc774\ub97c \ud1b5\ud574 \ud504\ub85c\ub355\uc158 \ud658\uacbd\uc758 \uc911\uc694 \ud0a4\ub294 \uc218\ub3d9 \uc2b9\uc778 \ud6c4 \ucc98\ub9ac\ud558\uace0 \uac1c\ubc1c \ud658\uacbd\uc740 \uc989\uc2dc \uc790\ub3d9 \ucc28\ub2e8\ud558\ub294 \ub4f1 \uc720\uc5f0\ud55c \uc815\ucc45 \uc801\uc6a9\uc774 \uac00\ub2a5\ud569\ub2c8\ub2e4. \ud658\uacbd\ubcc4\ub85c Lambda \ud568\uc218\ub97c \ubd84\ub9ac\ud558\uac70\ub098 \ud0dc\uadf8 \uae30\ubc18\uc73c\ub85c \uc815\ucc45\uc744 \ub2ec\ub9ac \uc801\uc6a9\ud560 \uc218\ub3c4 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># manvscloud-risk-credentials-exposed-lmd\n\nimport json\nimport boto3\nfrom datetime import datetime, timedelta, timezone\nfrom collections import defaultdict\nimport os\nimport urllib.request\nimport urllib.error\nimport traceback\n\n\ndef analyze_cloudtrail_events(access_key_id, start_time=None):\n    results = {\n        'events': [],\n        'total_events': 0\n    }\n\n    try:\n        ec2 = boto3.client('ec2')\n        regions = [region['RegionName'] for region in ec2.describe_regions()['Regions']]\n        regions.append('us-east-1')\n    except Exception as e:\n        print(f\"Error getting regions: {str(e)}\")\n        regions = ['us-east-1']\n    \n    if not start_time:\n        start_time = datetime.now(timezone.utc) - timedelta(days=7)\n\n    for region in set(regions):\n        try:\n            cloudtrail = boto3.client('cloudtrail', region_name=region)\n            paginator = cloudtrail.get_paginator('lookup_events')\n            \n            for page in paginator.paginate(\n                LookupAttributes=[{\n                    'AttributeKey': 'AccessKeyId',\n                    'AttributeValue': access_key_id\n                }],\n                StartTime=start_time\n            ):\n                for event in page.get('Events', []):\n                    try:\n                        event_data = json.loads(event.get('CloudTrailEvent', '{}'))\n                        api_name = event_data.get('eventName', 'Unknown')\n                        source_ip = event_data.get('sourceIPAddress', 'Unknown')\n                        \n                        results['events'].append({\n                            'region': region,\n                            'api_name': api_name,\n                            'source_ip': source_ip\n                        })\n                        results['total_events'] += 1\n                    except Exception as e:\n                        print(f\"Error parsing CloudTrail event: {str(e)}\")\n                        continue\n        \n        except Exception as e:\n            print(f\"Error in region {region}: {str(e)}\")\n            continue\n\n    return results\n\n\ndef get_ai_incident_summary_with_agent(incident_details, event_summary):\n    \"\"\"Bedrock Agent\ub97c \uc0ac\uc6a9\ud558\uc5ec \uc0ac\uace0 \ubd84\uc11d \uc0dd\uc131\"\"\"\n    try:\n        print(\"=== INITIALIZING BEDROCK AGENT RUNTIME CLIENT ===\")\n        bedrock_agent_runtime = boto3.client('bedrock-agent-runtime', region_name='us-east-1')\n        \n        agent_id = os.environ.get('BEDROCK_AGENT_ID')\n        agent_alias_id = os.environ.get('BEDROCK_AGENT_ALIAS_ID')\n        \n        if not agent_id:\n            raise ValueError(\"BEDROCK_AGENT_ID environment variable is not set\")\n        if not agent_alias_id:\n            raise ValueError(\"BEDROCK_AGENT_ALIAS_ID environment variable is not set\")\n        \n        print(f\"Agent ID: {agent_id}\")\n        print(f\"Agent Alias ID: {agent_alias_id}\")\n        \n        # CloudTrail \uc774\ubca4\ud2b8 \ub370\uc774\ud130 \ud3ec\ub9f7\ud305\n        event_counts = {}\n        for event in event_summary['events']:\n            key = (event['region'], event['api_name'], event['source_ip'])\n            event_counts[key] = event_counts.get(key, 0) + 1\n        \n        event_activities_list = []\n        for (region, api, ip), count in sorted(event_counts.items(), key=lambda x: x[1], reverse=True):\n            event_activities_list.append(f\"Region: {region}, API: {api}, IP: {ip}, Count: {count}\")\n        \n        event_activities = \"\\n\".join(event_activities_list) if event_activities_list else \"\ucd5c\uadfc 7\uc77c\uac04 \ud65c\ub3d9 \uae30\ub85d \uc5c6\uc74c\"\n        \n        # Agent\uc5d0\uac8c \uc804\ub2ec\ud560 \ud504\ub86c\ud504\ud2b8\n        prompt = f\"\"\"AWS IAM Credential \uc720\ucd9c \uc0ac\uace0\ub97c \ubd84\uc11d\ud574\uc8fc\uc138\uc694.\n\n\uc0ac\uace0 \uc815\ubcf4:\n{incident_details}\n\nCloudTrail API \ud65c\ub3d9 \ubd84\uc11d (\ucd5c\uadfc 7\uc77c):\n\ucd1d \uc774\ubca4\ud2b8 \uc218: {event_summary['total_events']}\n\n\ud65c\ub3d9 \ub0b4\uc5ed:\n{event_activities}\n\n\ub2e4\uc74c \ud56d\ubaa9\ub4e4\uc744 \ud3ec\ud568\ud558\uc5ec \ubd84\uc11d\ud574\uc8fc\uc138\uc694:\n1. \uc0ac\uace0 \uc694\uc57d (2-3\ubb38\uc7a5)\n2. \uc8fc\uc694 API \ud65c\ub3d9 \ubd84\uc11d\n3. \ub9ac\uc804\ubcc4 \ud65c\ub3d9 \ubd84\uc11d\n4. \uc704\ud5d8\ub3c4 \ud3c9\uac00 (HIGH\/MEDIUM\/LOW)\n\n\ubaa8\ub4e0 \uc751\ub2f5\uc740 \ud55c\uad6d\uc5b4\ub85c \uc791\uc131\ud574\uc8fc\uc138\uc694.\"\"\"\n\n        # \uc138\uc158 ID \uc0dd\uc131\n        session_id = f\"incident-{datetime.now().strftime('%Y%m%d-%H%M%S')}\"\n        \n        print(f\"=== INVOKING BEDROCK AGENT ===\")\n        print(f\"Session ID: {session_id}\")\n        print(f\"Prompt length: {len(prompt)} characters\")\n        \n        # Bedrock Agent \ud638\ucd9c\n        response = bedrock_agent_runtime.invoke_agent(\n            agentId=agent_id,\n            agentAliasId=agent_alias_id,\n            sessionId=session_id,\n            inputText=prompt,\n            enableTrace=True,  # \ub514\ubc84\uae45\uc744 \uc704\ud574 trace \ud65c\uc131\ud654\n            endSession=False\n        )\n        \n        print(\"=== AGENT INVOCATION SUCCESSFUL ===\")\n        print(f\"Response keys: {list(response.keys())}\")\n        \n        # \uc751\ub2f5 \uc2a4\ud2b8\ub9bc \ucc98\ub9ac\n        completion_text = \"\"\n        chunk_count = 0\n        \n        print(\"=== PROCESSING AGENT RESPONSE STREAM ===\")\n        \n        if 'completion' not in response:\n            raise Exception(\"No 'completion' field in agent response\")\n        \n        event_stream = response['completion']\n        \n        for event in event_stream:\n            try:\n                # chunk \uc774\ubca4\ud2b8 \ucc98\ub9ac\n                if 'chunk' in event:\n                    chunk = event['chunk']\n                    if 'bytes' in chunk:\n                        chunk_text = chunk['bytes'].decode('utf-8')\n                        completion_text += chunk_text\n                        chunk_count += 1\n                        print(f\"Chunk {chunk_count}: {len(chunk_text)} bytes\")\n                \n                # trace \uc774\ubca4\ud2b8 \ucc98\ub9ac (\ub514\ubc84\uae45\uc6a9)\n                elif 'trace' in event:\n                    trace = event['trace']\n                    trace_type = trace.get('trace', {}).get('type', 'unknown')\n                    print(f\"Trace event type: {trace_type}\")\n                \n                # \uc5d0\ub7ec \uc774\ubca4\ud2b8 \ucc98\ub9ac\n                elif 'internalServerException' in event:\n                    error = event['internalServerException']\n                    raise Exception(f\"Agent internal server error: {error}\")\n                \n                elif 'validationException' in event:\n                    error = event['validationException']\n                    raise Exception(f\"Agent validation error: {error}\")\n                \n                elif 'throttlingException' in event:\n                    error = event['throttlingException']\n                    raise Exception(f\"Agent throttling error: {error}\")\n                \n            except Exception as stream_error:\n                print(f\"Error processing stream event: {str(stream_error)}\")\n                print(f\"Event content: {json.dumps(event, default=str)}\")\n                # \uc2a4\ud2b8\ub9bc \uc5d0\ub7ec\ub294 \uacc4\uc18d \uc9c4\ud589\n                continue\n        \n        ai_summary = completion_text.strip()\n        \n        print(f\"=== AGENT RESPONSE COMPLETE ===\")\n        print(f\"Total chunks received: {chunk_count}\")\n        print(f\"Total response length: {len(ai_summary)} characters\")\n        \n        if not ai_summary:\n            raise Exception(\"Agent returned empty response\")\n        \n        print(f\"Response preview (first 200 chars): {ai_summary[:200]}\")\n        \n        return ai_summary\n        \n    except Exception as e:\n        error_msg = f\"Error in get_ai_incident_summary_with_agent: {str(e)}\"\n        print(error_msg)\n        print(\"=== FULL TRACEBACK ===\")\n        print(traceback.format_exc())\n        return f\"AI \ubd84\uc11d \uc0dd\uc131 \uc911 \uc624\ub958 \ubc1c\uc0dd: {str(e)}\"\n\n\ndef format_slack_message(access_key_id, user, account_id, exposed_url, event_time, \n                         ai_summary, event_summary):\n    current_time = datetime.now(timezone.utc)\n    \n    event_counts = {}\n    for event in event_summary['events']:\n        key = (event['region'], event['api_name'], event['source_ip'])\n        event_counts[key] = event_counts.get(key, 0) + 1\n    \n    api_activity = \"\"\n    for (region, api_name, source_ip), count in sorted(event_counts.items(), \n                                                        key=lambda x: x[1], \n                                                        reverse=True)[:20]:\n        api_activity += f\"\u2022 `{region}` | `{api_name}` | `{source_ip}` | {count}\ud68c\\n\"\n    \n    if not api_activity:\n        api_activity = \"\u2022 \ucd5c\uadfc 7\uc77c\uac04 \ud65c\ub3d9 \uae30\ub85d \uc5c6\uc74c\\n\"\n    \n    managed_primary = os.environ.get('MANAGED_PRIMARY', 'test1@example.com')\n    managed_secondary = os.environ.get('MANAGED_SECONDARY', 'test2@example.com')\n    system_owner = os.environ.get('SYSTEM_OWNER', 'test3@example.com')\n    \n    slack_message = {\n        \"blocks\": [\n            {\n                \"type\": \"header\",\n                \"text\": {\n                    \"type\": \"plain_text\",\n                    \"text\": \"\ud83d\udea8 [CRITICAL] AWS IAM Credential \uc720\ucd9c \ud655\uc815 \uac10\uc9c0\"\n                }\n            },\n            {\n                \"type\": \"section\",\n                \"text\": {\n                    \"type\": \"mrkdwn\",\n                    \"text\": \"*AWS Health Event\ub97c \ud1b5\ud574 AWS\uac00 \uacf5\uc2dd\uc801\uc73c\ub85c IAM Credential \uc720\ucd9c\uc744 \ud655\uc815\ud588\uc2b5\ub2c8\ub2e4.*\"\n                }\n            },\n            {\n                \"type\": \"divider\"\n            },\n            {\n                \"type\": \"section\",\n                \"fields\": [\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": f\"*\uc774\ubca4\ud2b8 \uc720\ud615*\\n`AWS_RISK_CREDENTIALS_EXPOSED`\"\n                    },\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": f\"*\uac10\uc9c0 \uc2dc\uac01*\\n`{current_time.strftime('%Y-%m-%d %H:%M:%S UTC')}`\"\n                    }\n                ]\n            },\n            {\n                \"type\": \"section\",\n                \"text\": {\n                    \"type\": \"mrkdwn\",\n                    \"text\": f\"*\uc601\ud5a5\ubc1b\uc740 IAM Credential*\\n```{access_key_id}```\"\n                }\n            },\n            {\n                \"type\": \"divider\"\n            },\n            {\n                \"type\": \"section\",\n                \"text\": {\n                    \"type\": \"mrkdwn\",\n                    \"text\": \"*\uc0ac\uace0 \uc815\ubcf4*\"\n                }\n            },\n            {\n                \"type\": \"section\",\n                \"fields\": [\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": f\"*Access Key ID*\\n`{access_key_id}`\"\n                    },\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": f\"*\uc0ac\uc6a9\uc790\uba85*\\n`{user}`\"\n                    },\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": f\"*\uacc4\uc815 ID*\\n`{account_id}`\"\n                    },\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": f\"*\ubc1c\uc0dd \uc2dc\uac04*\\n`{event_time}`\"\n                    }\n                ]\n            },\n            {\n                \"type\": \"section\",\n                \"text\": {\n                    \"type\": \"mrkdwn\",\n                    \"text\": f\"*\ub178\ucd9c URL*\\n{exposed_url}\"\n                }\n            },\n            {\n                \"type\": \"divider\"\n            },\n            {\n                \"type\": \"section\",\n                \"text\": {\n                    \"type\": \"mrkdwn\",\n                    \"text\": f\"*\uc0ac\uace0 \uc694\uc57d (AI \uc0dd\uc131)*\\n{ai_summary}\"\n                }\n            },\n            {\n                \"type\": \"section\",\n                \"text\": {\n                    \"type\": \"mrkdwn\",\n                    \"text\": f\"*API \ud65c\ub3d9 \ubd84\uc11d (\ucd5c\uadfc 7\uc77c, \uc0c1\uc704 20\uac1c)*\\n\ucd1d \uc774\ubca4\ud2b8: {event_summary['total_events']}\ud68c\\n{api_activity}\"\n                }\n            },\n            {\n                \"type\": \"divider\"\n            },\n            {\n                \"type\": \"section\",\n                \"text\": {\n                    \"type\": \"mrkdwn\",\n                    \"text\": \"*\uc989\uc2dc \ub300\uc751 \ud544\uc694*\"\n                }\n            },\n            {\n                \"type\": \"section\",\n                \"fields\": [\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": f\"*Managed \ub2f4\ub2f9(\uc815)*\\n{managed_primary}\"\n                    },\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": f\"*Managed \ub2f4\ub2f9(\ubd80)*\\n{managed_secondary}\"\n                    },\n                    {\n                        \"type\": \"mrkdwn\",\n                        \"text\": f\"*\uc2dc\uc2a4\ud15c \ub2f4\ub2f9\uc790*\\n{system_owner}\"\n                    }\n                ]\n            },\n            {\n                \"type\": \"divider\"\n            },\n            {\n                \"type\": \"section\",\n                \"text\": {\n                    \"type\": \"mrkdwn\",\n                    \"text\": \"*\uad8c\uc7a5 \ub300\uc751 \uc21c\uc11c*\\n:one: \ud574\ub2f9 Access Key \uc989\uc2dc \ube44\ud65c\uc131\ud654 \ub610\ub294 \uc0ad\uc81c\\n:two: IAM User \uad8c\ud55c \uc810\uac80 \ubc0f Quarantine \uc5ec\ubd80 \ud655\uc778\\n:three: \ucd5c\uadfc CloudTrail \ub85c\uadf8 \ubd84\uc11d\\n:four: \ube44\uc6a9 \uc774\uc0c1 \uc5ec\ubd80 \ud655\uc778\\n:five: \ud544\uc694 \uc2dc \ubcf4\uc548 \uc0ac\uace0\ub85c \uc5d0\uc2a4\uceec\ub808\uc774\uc158\"\n                }\n            }\n        ]\n    }\n    \n    return slack_message\n\n\ndef send_slack_notification(webhook_url, message):\n    try:\n        data = json.dumps(message).encode('utf-8')\n        req = urllib.request.Request(\n            webhook_url,\n            data=data,\n            headers={'Content-Type': 'application\/json'}\n        )\n        \n        with urllib.request.urlopen(req) as response:\n            if response.status == 200:\n                print(\"Slack notification sent successfully\")\n                return {\n                    'statusCode': 200,\n                    'body': json.dumps('Slack notification sent successfully')\n                }\n            else:\n                print(f\"Slack API returned status: {response.status}\")\n                return {\n                    'statusCode': response.status,\n                    'body': json.dumps(f'Slack API returned status: {response.status}')\n                }\n                \n    except urllib.error.HTTPError as e:\n        error_msg = f'HTTP Error sending to Slack: {e.code} - {e.reason}'\n        print(error_msg)\n        return {\n            'statusCode': e.code,\n            'body': json.dumps(error_msg)\n        }\n    except Exception as e:\n        error_msg = f'Error sending to Slack: {str(e)}'\n        print(error_msg)\n        return {\n            'statusCode': 500,\n            'body': json.dumps(error_msg)\n        }\n\n\ndef lambda_handler(event, context):\n    try:\n        print(\"=== LAMBDA HANDLER STARTED ===\")\n        print(\"=== RECEIVED EVENT ===\")\n        print(json.dumps(event, indent=2, default=str))\n        \n        detail = None\n        \n        if 'event' in event and isinstance(event.get('event'), dict) and 'detail' in event.get('event', {}):\n            print(\"=== CASE 1: EventBridge format ===\")\n            health_event = event.get('event', {})\n            detail = health_event.get('detail', {})\n        elif 'detail' in event:\n            print(\"=== CASE 2: Direct Step Functions format ===\")\n            detail = event.get('detail', {})\n        elif 'detail-type' in event and event.get('detail-type') == 'AWS Health Event':\n            print(\"=== CASE 3: Raw AWS Health Event format ===\")\n            detail = event.get('detail', {})\n        else:\n            print(\"=== ERROR: Invalid event structure ===\")\n            print(f\"Event keys: {list(event.keys())}\")\n            return {\n                'statusCode': 400,\n                'body': json.dumps('Invalid event structure. Missing \"detail\" field.')\n            }\n        \n        print(\"=== DETAIL STRUCTURE ===\")\n        print(json.dumps(detail, indent=2, default=str))\n        print(f\"Detail keys: {list(detail.keys())}\")\n        \n        if 'eventMetadata' not in detail:\n            print(\"=== ERROR: eventMetadata not found in detail ===\")\n            return {\n                'statusCode': 400,\n                'body': json.dumps(f'eventMetadata not found in detail. Available keys: {list(detail.keys())}')\n            }\n        \n        event_metadata = detail.get('eventMetadata', {})\n        print(f\"=== EVENT METADATA ===\")\n        print(json.dumps(event_metadata, indent=2, default=str))\n        \n        access_key_id = event_metadata.get('publicKey', 'N\/A')\n        user = event_metadata.get('userName', 'N\/A')\n        account_id = event_metadata.get('accountId', 'N\/A')\n        exposed_url = event_metadata.get('exposedUrl', 'N\/A')\n        event_time = detail.get('startTime', 'N\/A')\n        \n        print(f\"=== EXTRACTED VALUES ===\")\n        print(f\"Access Key ID: {access_key_id}\")\n        print(f\"User: {user}\")\n        print(f\"Account ID: {account_id}\")\n        print(f\"Exposed URL: {exposed_url}\")\n        print(f\"Event Time: {event_time}\")\n        \n        print(\"=== STARTING CLOUDTRAIL ANALYSIS ===\")\n        try:\n            event_summary = analyze_cloudtrail_events(access_key_id)\n            print(f\"CloudTrail analysis completed. Total events: {event_summary['total_events']}\")\n        except Exception as e:\n            print(f\"ERROR in CloudTrail analysis: {str(e)}\")\n            print(traceback.format_exc())\n            event_summary = {'events': [], 'total_events': 0}\n\n        incident_summary = f\"\"\"Access Key ID: {access_key_id}\nUser: {user}\nAccount ID: {account_id}\nExposure Source: {exposed_url}\nDetection Time: {event_time}\"\"\"\n        \n        print(\"=== CALLING BEDROCK AGENT FOR AI SUMMARY ===\")\n        try:\n            ai_summary = get_ai_incident_summary_with_agent(incident_summary, event_summary)\n            print(f\"AI summary generated successfully. Length: {len(ai_summary)}\")\n            print(f\"AI Summary preview: {ai_summary[:200]}...\")\n        except Exception as e:\n            print(f\"ERROR generating AI summary: {str(e)}\")\n            print(traceback.format_exc())\n            ai_summary = f\"AI \ubd84\uc11d \uc0dd\uc131 \uc911 \uc624\ub958 \ubc1c\uc0dd: {str(e)}\"\n        \n        print(\"=== FORMATTING SLACK MESSAGE ===\")\n        try:\n            slack_message = format_slack_message(\n                access_key_id, user, account_id, exposed_url, event_time,\n                ai_summary, event_summary\n            )\n            print(\"Slack message formatted successfully\")\n            print(f\"Message blocks count: {len(slack_message['blocks'])}\")\n        except Exception as e:\n            print(f\"ERROR formatting Slack message: {str(e)}\")\n            print(traceback.format_exc())\n            return {\n                'statusCode': 500,\n                'body': json.dumps(f'Error formatting message: {str(e)}')\n            }\n        \n        print(\"=== PREPARING TO SEND SLACK NOTIFICATION ===\")\n        webhook_url = os.environ.get('SLACK_WEBHOOK_URL')\n        if not webhook_url:\n            print(\"ERROR: SLACK_WEBHOOK_URL environment variable not set\")\n            return {\n                'statusCode': 500,\n                'body': json.dumps('SLACK_WEBHOOK_URL environment variable not set')\n            }\n        \n        print(f\"Webhook URL found (length: {len(webhook_url)})\")\n        print(\"=== SENDING SLACK NOTIFICATION ===\")\n        \n        try:\n            result = send_slack_notification(webhook_url, slack_message)\n            print(f\"Slack notification result: {result}\")\n            \n            # \ud658\uacbd\ubcc0\uc218\uc5d0\uc11c \uc790\ub3d9 \ubcf5\uad6c \uc124\uc815 \uc77d\uae30\n            enable_auto_remediation = os.environ.get('ENABLE_AUTO_REMEDIATION', 'false').lower() == 'true'\n            print(f\"=== AUTO REMEDIATION SETTING: {enable_auto_remediation} ===\")\n            \n            # Step Functions\ub85c \uc804\ub2ec\ud560 \uc751\ub2f5 \uad6c\uc131\n            response = {\n                'statusCode': result['statusCode'],\n                'body': result['body'],\n                'enableAutoRemediation': enable_auto_remediation,\n                'accessKeyId': access_key_id,\n                'userName': user,\n                'accountId': account_id\n            }\n            \n            print(\"=== LAMBDA HANDLER COMPLETED SUCCESSFULLY ===\")\n            print(f\"Response: {json.dumps(response, indent=2)}\")\n            return response\n            \n        except Exception as e:\n            print(f\"ERROR sending Slack notification: {str(e)}\")\n            print(traceback.format_exc())\n            return {\n                'statusCode': 500,\n                'body': json.dumps(f'Error sending notification: {str(e)}'),\n                'enableAutoRemediation': False\n            }\n    \n    except Exception as e:\n        # \ucd5c\uc0c1\uc704 \uc608\uc678 \ucc98\ub9ac\n        print(f\"=== UNHANDLED EXCEPTION IN LAMBDA HANDLER ===\")\n        print(f\"Error: {str(e)}\")\n        print(traceback.format_exc())\n        return {\n            'statusCode': 500,\n            'body': json.dumps(f'Unhandled exception: {str(e)}'),\n            'enableAutoRemediation': False\n        }\n\t\t<\/pre>\n\n\n\n<p>Step Functions \uacfc\uc815\uc5d0\uc11c \uc704 Lambda\uac00 \ub3d9\uc791\ud558\uba74 \uc544\ub798\uc640 \uac19\uc774 Slack\uc73c\ub85c \uc54c\ub9bc\uc744 \ubc1b\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<br>\uc720\ucd9c \uac10\uc9c0 \uc2dc\uac01, \uc720\ucd9c\ub41c \uacc4\uc815 \ubc0f \ud0a4 \uc815\ubcf4, \uc720\ucd9c\ub41c URL \uadf8\ub9ac\uace0 Bedrock\uc774 Cloudtrail\uc744 \uba3c\uc800 \ube60\ub974\uac8c \ubd84\uc11d\ud558\uc5ec \uc54c\ub824\uc8fc\ub294 \uc815\ubcf4\uae4c\uc9c0 Slack\uc73c\ub85c \uacb0\uacfc\ub9cc \ubcf4\uace0 \ubc1b\uace0 \uc0ac\uc6a9\uc790\ub294 \ub2e4\uc74c \ud589\ub3d9\uc744 \ud310\ub2e8\ud558\uba74 \ub429\ub2c8\ub2e4.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"580\" height=\"1024\" src=\"https:\/\/cdn.manvscloud.com\/wp-content\/uploads\/2026\/01\/20100854\/image-2-580x1024.png\" alt=\"\" class=\"wp-image-3035\" srcset=\"https:\/\/cdn.manvscloud.com\/wp-content\/uploads\/2026\/01\/20100854\/image-2-580x1024.png 580w, https:\/\/cdn.manvscloud.com\/wp-content\/uploads\/2026\/01\/20100854\/image-2-170x300.png 170w, https:\/\/cdn.manvscloud.com\/wp-content\/uploads\/2026\/01\/20100854\/image-2-768x1355.png 768w, https:\/\/cdn.manvscloud.com\/wp-content\/uploads\/2026\/01\/20100854\/image-2.png 853w\" sizes=\"(max-width: 580px) 100vw, 580px\" \/><\/figure>\n\n\n\n<ul>\n<li><strong>DisableAccessKey<\/strong><\/li>\n<\/ul>\n\n\n\n<p>\uc790\ub3d9 \ubcf5\uad6c \ubaa8\ub4dc\uac00 \ud65c\uc131\ud654\ub41c \uacbd\uc6b0 \uc774 Lambda \ud568\uc218\uac00 \uc720\ucd9c\ub41c Access Key\ub97c \uc989\uc2dc \ube44\ud65c\uc131\ud654\ud569\ub2c8\ub2e4.<br>(\uc694\uad6c \uc0ac\ud56d\uc5d0 \ub530\ub77c Access Key \ube44\ud65c\uc131\ud654\uac00 \uc544\ub2c8\ub77c \ub2e4\ub978 \uc870\uce58\uac00 \uc9c4\ud589\ub418\ub3c4\ub85d \ubcc0\uacbd\ud560 \uc218\ub3c4 \uc788\uc2b5\ub2c8\ub2e4.)<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import boto3\n\ndef lambda_handler(event, context):\n    iam_client = boto3.client('iam')\n    \n    user_name = event['userName']\n    access_key_id = event['accessKeyId']\n    \n    # Access Key \ube44\ud65c\uc131\ud654\n    iam_client.update_access_key(\n        UserName=user_name,\n        AccessKeyId=access_key_id,\n        Status='Inactive'\n    )\n    \n    # \uc644\ub8cc \uc54c\ub9bc \uc804\uc1a1\n    send_slack_notification(f\"Access Key {access_key_id}\uac00 \uc790\ub3d9\uc73c\ub85c \ube44\ud65c\uc131\ud654\ub418\uc5c8\uc2b5\ub2c8\ub2e4.\")\n    \n    return {\n        'statusCode': 200,\n        'body': 'Access Key disabled successfully'\n    }<\/pre>\n\n\n\n<p>1) \uc801\uc6a9\ub41c \ud658\uacbd \ubcc0\uc218<\/p>\n\n\n\n<ul>\n<li>DENY_POLICY_ARN<\/li>\n\n\n\n<li>SLACK_WEBHOOK_URL<\/li>\n<\/ul>\n\n\n\n<p>2) IAM \uc5ed\ud560\uc5d0 \ud3ec\ud568\ub41c \uc815\ucc45<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": \"logs:CreateLogGroup\",\n            \"Resource\": \"arn:aws:logs:us-east-1:888888888888:*\"\n        },\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"logs:CreateLogStream\",\n                \"logs:PutLogEvents\"\n            ],\n            \"Resource\": [\n                \"arn:aws:logs:us-east-1:888888888888:log-group:\/aws\/lambda\/manvscloud-disable-accesskey-lmd:*\"\n            ]\n        },\n        {\n            \"Sid\": \"DisableAccessKey\",\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"iam:UpdateAccessKey\"\n            ],\n            \"Resource\": \"arn:aws:iam::*:user\/*\"\n        },\n        {\n            \"Sid\": \"AttachDenyPolicy\",\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"iam:AttachUserPolicy\"\n            ],\n            \"Resource\": \"arn:aws:iam::*:user\/*\",\n            \"Condition\": {\n                \"ArnEquals\": {\n                    \"iam:PolicyARN\": \"arn:aws:iam::888888888888:policy\/manvscloud-CompromisedUserDenyAll-policy\"\n                }\n            }\n        }\n    ]\n}<\/pre>\n\n\n\n<p>\ube44\ud65c\uc131\ud654 \ud6c4\uc5d0\ub294 CloudWatch Logs\uc5d0 \uc870\uce58\ub97c \uae30\ub85d\ud558\uace0 Slack\uc73c\ub85c \uc644\ub8cc \uc54c\ub9bc\uc744 \uc804\uc1a1\ud569\ub2c8\ub2e4. \uba71\ub4f1\uc131\uc774 \ubcf4\uc7a5\ub418\uc5b4 \uc774\ubbf8 \ube44\ud65c\uc131\ud654\ub41c \ud0a4\uc5d0 \ub300\ud574\uc11c\ub3c4 \uc5d0\ub7ec \uc5c6\uc774 \ucc98\ub9ac\ub429\ub2c8\ub2e4.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># manvscloud-disable-accesskey-lmd\n\nimport json\nimport boto3\nfrom datetime import datetime, timezone\nimport os\nimport urllib3\nimport traceback\n\nhttp = urllib3.PoolManager()\n\ndef attach_existing_deny_policy(username):\n    \"\"\"Attach pre-created deny policy to the compromised user\"\"\"\n    try:\n        iam = boto3.client('iam')\n        \n        # \ud658\uacbd \ubcc0\uc218\uc5d0\uc11c \uc815\ucc45 ARN \uac00\uc838\uc624\uae30\n        policy_arn = os.environ.get('DENY_POLICY_ARN')\n        \n        if not policy_arn:\n            return False, \"DENY_POLICY_ARN environment variable not set\"\n        \n        print(f\"Attempting to attach policy {policy_arn} to user {username}\")\n        \n        # \uae30\uc874 \uc815\ucc45\uc744 \uc0ac\uc6a9\uc790\uc5d0\uac8c \uc5f0\uacb0\n        iam.attach_user_policy(\n            UserName=username,\n            PolicyArn=policy_arn\n        )\n        \n        print(f\"Successfully attached policy to {username}\")\n        return True, f\"Successfully attached deny policy: {policy_arn}\"\n    except Exception as e:\n        error_msg = f\"Failed to attach deny policy: {str(e)}\\n{traceback.format_exc()}\"\n        print(error_msg)\n        return False, error_msg\n\ndef send_slack_notification(username, access_key_id, account_id, policy_attached, policy_message):\n    \"\"\"Send Slack notification about remediation actions\"\"\"\n    try:\n        webhook_url = os.environ.get('SLACK_WEBHOOK_URL')\n        \n        if not webhook_url:\n            print(\"SLACK_WEBHOOK_URL environment variable not set\")\n            return False\n        \n        current_time = datetime.now(timezone.utc)\n        \n        # Slack \uba54\uc2dc\uc9c0 \uad6c\uc131 (\ud55c\uad6d\uc5b4)\n        slack_message = {\n            \"text\": \"\ud83d\udee1\ufe0f AWS \uc561\uc138\uc2a4 \ud0a4 \uc790\ub3d9 \ube44\ud65c\uc131\ud654\",\n            \"blocks\": [\n                {\n                    \"type\": \"header\",\n                    \"text\": {\n                        \"type\": \"plain_text\",\n                        \"text\": \"\ud83d\udee1\ufe0f AWS \uc561\uc138\uc2a4 \ud0a4 \uc790\ub3d9 \ube44\ud65c\uc131\ud654\",\n                        \"emoji\": True\n                    }\n                },\n                {\n                    \"type\": \"section\",\n                    \"text\": {\n                        \"type\": \"mrkdwn\",\n                        \"text\": \"*\uc790\ub3d9 \ubcf4\uc548 \uc870\uce58 \uc644\ub8cc*\\n\ub178\ucd9c\ub41c AWS \uc561\uc138\uc2a4 \ud0a4\uac00 \uc790\ub3d9\uc73c\ub85c \ube44\ud65c\uc131\ud654\ub418\uc5c8\uc73c\uba70, \ucd94\uac00 \ubcf4\uc548 \uc870\uce58\uac00 \uc801\uc6a9\ub418\uc5c8\uc2b5\ub2c8\ub2e4.\"\n                    }\n                },\n                {\n                    \"type\": \"divider\"\n                },\n                {\n                    \"type\": \"section\",\n                    \"fields\": [\n                        {\n                            \"type\": \"mrkdwn\",\n                            \"text\": f\"*AWS \uacc4\uc815 ID:*\\n`{account_id}`\"\n                        },\n                        {\n                            \"type\": \"mrkdwn\",\n                            \"text\": f\"*IAM \uc0ac\uc6a9\uc790:*\\n`{username}`\"\n                        },\n                        {\n                            \"type\": \"mrkdwn\",\n                            \"text\": f\"*\uc561\uc138\uc2a4 \ud0a4 ID:*\\n`{access_key_id}`\"\n                        },\n                        {\n                            \"type\": \"mrkdwn\",\n                            \"text\": f\"*\uc561\uc138\uc2a4 \ud0a4 \uc0c1\ud0dc:*\\n`\ube44\ud65c\uc131\ud654\ub428`\"\n                        },\n                        {\n                            \"type\": \"mrkdwn\",\n                            \"text\": f\"*Deny \uc815\ucc45 \uc0c1\ud0dc:*\\n{'\u2705 \uc801\uc6a9\ub428' if policy_attached else '\u274c \uc2e4\ud328'}\"\n                        },\n                        {\n                            \"type\": \"mrkdwn\",\n                            \"text\": f\"*\uc870\uce58 \uc2dc\uac01:*\\n`{current_time.strftime('%Y\ub144 %m\uc6d4 %d\uc77c %H:%M:%S UTC')}`\"\n                        }\n                    ]\n                },\n                {\n                    \"type\": \"divider\"\n                },\n                {\n                    \"type\": \"section\",\n                    \"text\": {\n                        \"type\": \"mrkdwn\",\n                        \"text\": \"*\ub2e4\uc74c \uc870\uce58 \uc0ac\ud56d:*\\n\u2022 AccessKey \ube44\ud65c\uc131\ud654\ub85c \uc778\ud55c \uc11c\ube44\uc2a4 \uc774\uc0c1 \uc720\ubb34 \ud655\uc778\\n\u2022 \ud544\uc694 \uc2dc \uc0c8\ub85c\uc6b4 \uc561\uc138\uc2a4 \ud0a4 \uc0dd\uc131\\n\u2022 \uae30\uc874 \uc561\uc138\uc2a4 \ud0a4\ub97c \uc0ac\uc6a9\ud558\ub294 \uc560\ud50c\ub9ac\ucf00\uc774\uc158 \uc5c5\ub370\uc774\ud2b8\\n\u2022 \uc561\uc138\uc2a4 \ud0a4 \uc678\ubd80 \uc5c5\ub85c\ub4dc \ubc0f \uc720\ucd9c \uae08\uc9c0\"\n                    }\n                },\n                {\n                    \"type\": \"context\",\n                    \"elements\": [\n                        {\n                            \"type\": \"mrkdwn\",\n                            \"text\": \"_\uc790\ub3d9 \ubcf4\uc548 \uc870\uce58 \uc54c\ub9bc\uc785\ub2c8\ub2e4._\"\n                        }\n                    ]\n                }\n            ]\n        }\n        \n        # \uc815\ucc45 \ucca8\ubd80 \uc2e4\ud328 \uc2dc \ucd94\uac00 \uc815\ubcf4 \ud45c\uc2dc\n        if not policy_attached:\n            slack_message[\"blocks\"].insert(5, {\n                \"type\": \"section\",\n                \"text\": {\n                    \"type\": \"mrkdwn\",\n                    \"text\": f\"\u26a0\ufe0f *\uc815\ucc45 \uc801\uc6a9 \uc624\ub958:*\\n```{policy_message}```\"\n                }\n            })\n        \n        print(f\"Sending Slack notification to {webhook_url[:30]}...\")\n        \n        encoded_data = json.dumps(slack_message).encode('utf-8')\n        \n        response = http.request(\n            'POST',\n            webhook_url,\n            body=encoded_data,\n            headers={'Content-Type': 'application\/json'}\n        )\n        \n        print(f\"Slack response status: {response.status}\")\n        \n        if response.status == 200:\n            print(\"Slack notification sent successfully\")\n            return True\n        else:\n            print(f\"Failed to send Slack notification. Status: {response.status}, Response: {response.data}\")\n            return False\n            \n    except Exception as e:\n        error_msg = f\"Failed to send Slack notification: {str(e)}\\n{traceback.format_exc()}\"\n        print(error_msg)\n        return False\n\ndef lambda_handler(event, context):\n    print(f\"Received event: {json.dumps(event)}\")\n    \n    try:\n        # \uc774\ubca4\ud2b8 \uad6c\uc870 \ud30c\uc2f1 - \ub450 \uac00\uc9c0 \ud615\ud0dc \uc9c0\uc6d0\n        # 1. EventBridge\uc5d0\uc11c \uc9c1\uc811 \uc628 \uacbd\uc6b0: event['detail']['eventMetadata']\n        # 2. \ub2e4\ub978 Lambda\uc5d0\uc11c \uc804\ub2ec\ub41c \uacbd\uc6b0: event \ucd5c\uc0c1\uc704 \ub808\ubca8\uc5d0 \ub370\uc774\ud130\n        \n        detail = event.get('detail', {})\n        event_metadata = detail.get('eventMetadata', {})\n        \n        # EventBridge \ud615\ud0dc\uc778\uc9c0 \ud655\uc778\n        if event_metadata:\n            access_key_id = event_metadata.get('publicKey', '').strip()\n            username = event_metadata.get('userName', '')\n            account_id = event.get('account', '')\n        else:\n            # \ucd5c\uc0c1\uc704 \ub808\ubca8\uc5d0\uc11c \ub370\uc774\ud130 \ucd94\ucd9c\n            access_key_id = event.get('accessKeyId', '').strip()\n            username = event.get('userName', '')\n            account_id = event.get('accountId', '')\n        \n        print(f\"Processing remediation for account: {account_id}, user: {username}, key: {access_key_id}\")\n        \n        if not access_key_id or not username:\n            raise ValueError(f\"Invalid event data - username: {username}, access_key_id: {access_key_id}\")\n        \n        # enableAutoRemediation \ud50c\ub798\uadf8 \ud655\uc778 (\uc788\ub2e4\uba74)\n        enable_auto_remediation = event.get('enableAutoRemediation', True)\n        if not enable_auto_remediation:\n            print(\"Auto-remediation is disabled. Skipping...\")\n            return {\n                'statusCode': 200,\n                'body': {\n                    'message': 'Auto-remediation is disabled',\n                    'user': username,\n                    'keyId': access_key_id,\n                    'accountId': account_id\n                }\n            }\n        \n        # Disable access key\n        print(f\"Attempting to disable access key {access_key_id} for user {username}\")\n        iam = boto3.client('iam')\n        iam.update_access_key(\n            UserName=username,\n            AccessKeyId=access_key_id,\n            Status='Inactive'\n        )\n        print(f\"Access key {access_key_id} disabled successfully\")\n        \n        # Attach pre-created deny policy\n        policy_attached, policy_message = attach_existing_deny_policy(username)\n        \n        # Send Slack notification\n        slack_sent = send_slack_notification(username, access_key_id, account_id, policy_attached, policy_message)\n        \n        result = {\n            'statusCode': 200,\n            'body': {\n                'message': 'Remediation actions completed',\n                'user': username,\n                'keyId': access_key_id,\n                'accountId': account_id,\n                'keyStatus': 'INACTIVE',\n                'policyAttached': policy_attached,\n                'slackSent': slack_sent\n            }\n        }\n        \n        print(f\"Remediation result: {json.dumps(result)}\")\n        return result\n        \n    except Exception as e:\n        error_msg = f\"Failed to complete remediation actions: {str(e)}\\n{traceback.format_exc()}\"\n        print(error_msg)\n        return {\n            'statusCode': 500,\n            'body': {\n                'error': str(e),\n                'message': 'Failed to complete remediation actions',\n                'traceback': traceback.format_exc()\n            }\n        }<\/pre>\n\n\n\n<p>Step Functions \uacfc\uc815\uc5d0\uc11c \ud574\ub2f9 Lambda\uac00 \ub3d9\uc791\ud560 \uacbd\uc6b0 \uc720\ucd9c\ub41c Access Key\uc5d0 Deny \uc815\ucc45 \ucd94\uac00 \ubc0f \uc561\uc138\uc2a4 \ud0a4 \ube44\ud65c\uc131\ud654\ub9cc \uc9c4\ud589\ub418\ub294 \uac83\uc774 \uc544\ub2c8\ub77c \uc544\ub798\uc640 \uac19\uc774 Slack\uc73c\ub85c \uc54c\ub9bc\uc744 \ubc1b\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"835\" height=\"410\" src=\"https:\/\/cdn.manvscloud.com\/wp-content\/uploads\/2026\/01\/20100523\/image-1.png\" alt=\"\" class=\"wp-image-3034\" srcset=\"https:\/\/cdn.manvscloud.com\/wp-content\/uploads\/2026\/01\/20100523\/image-1.png 835w, https:\/\/cdn.manvscloud.com\/wp-content\/uploads\/2026\/01\/20100523\/image-1-300x147.png 300w, https:\/\/cdn.manvscloud.com\/wp-content\/uploads\/2026\/01\/20100523\/image-1-768x377.png 768w\" sizes=\"(max-width: 835px) 100vw, 835px\" \/><\/figure>\n\n\n\n<p>\uc774 \uc544\ud0a4\ud14d\ucc98\ub294 \ub2e4\uc911 \uacc4\uc815 \ud658\uacbd\uc73c\ub85c\ub3c4 \ud655\uc7a5\uc774 \uac00\ub2a5\ud569\ub2c8\ub2e4. AWS Organizations\uc758 \uc704\uc784 \uad00\ub9ac\uc790 \uacc4\uc815\uc5d0 \uc911\uc559 \uc9d1\uc911\uc2dd\uc73c\ub85c \ubc30\ud3ec\ud558\uace0 EventBridge\uc758 \ud06c\ub85c\uc2a4 \uacc4\uc815 \uc774\ubca4\ud2b8 \uc804\ub2ec\uc744 \ud65c\uc6a9\ud558\uba74 \uc5ec\ub7ec AWS \uacc4\uc815\uc758 \ubcf4\uc548 \uc0ac\uace0\ub97c \ud55c \uacf3\uc5d0\uc11c \uad00\ub9ac\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading has-white-color has-cyan-bluish-gray-background-color has-text-color has-background has-link-color wp-elements-790b4ede7486cf11fc8601a67c8dcb47\"> Personal Comments<\/h3>\n\n\n\n<p>\uc624\ub298 \ud3ec\uc2a4\ud305\uc744 \ud1b5\ud574 AWS IAM AccessKey \uc720\ucd9c \uc0ac\uace0\ub97c \uc790\ub3d9\uc73c\ub85c \ud0d0\uc9c0\ud558\uace0 \ub300\uc751\ud558\ub294 \uc2dc\uc2a4\ud15c\uc744 \uad6c\ucd95\ud558\ub294 \ubc29\ubc95\uc744 \uc54c\uc544\ubcf4\uc558\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<p>\uc774 \uc2dc\uc2a4\ud15c\uc758 \uac00\uc7a5 \ud070 \uc7a5\uc810\uc740 \uba54\uc77c\uc774 \uc544\ub2cc \uc6d0\ud558\ub294 \ud50c\ub7ab\ud3fc\uc758 WEBHOOK URL\ub85c \uc54c\ub9bc\uc744 \ubc1b\uc544 \ube60\ub974\uac8c \ud0a4 \uc720\ucd9c\ub85c\ubd80\ud130 \ub300\uc751\ud560 \uc218 \uc788\uace0 \ud2b9\ud788 \uc57c\uac04\uc774\ub098 \ud734\uc77c\uc5d0 \ubc1c\uc0dd\ud55c \uc0ac\uace0\ub3c4 \uc790\ub3d9\uc73c\ub85c \ub300\uc751\ud560 \uc218 \uc788\uc5b4 \uc704\ud5d8 \ubd80\ub2f4\uc744 \ud06c\uac8c \uc904\uc77c \uc218 \uc788\ub2e4\ub294 \uac83\uc785\ub2c8\ub2e4.<\/p>\n\n\n\n<p>\ub610\ud55c Bedrock Agent\ub97c \ud65c\uc6a9\ud55c AI \ubd84\uc11d\uc740 \ub2e8\uc21c \ub85c\uadf8 \uc9d1\uacc4\ub97c \ub118\uc5b4 \uc0ac\uace0\uc758 \ub9e5\ub77d\uc744 \uc774\ud574\ud558\uace0 \uc704\ud5d8\ub3c4\ub97c \ud3c9\uac00\ud558\ub294 \ub370 \ud070 \ub3c4\uc6c0\uc774 \ub429\ub2c8\ub2e4. \ub610\ud55c \uc774\ub97c \ud1b5\ud574 \ubcf4\uc548 \ubb38\uc81c \ubc1c\uc0dd \uc2dc \ubd84\uc11d \uc2dc\uac04\uc744 \ub2e8\ucd95\ud558\uace0 \ub2e8\uc21c \ubc18\ubcf5 \uc791\uc5c5\uc5d0\uc11c \ubc97\uc5b4\ub098 \ub354 \uc804\ub7b5\uc801\uc778 \uc5c5\ubb34\uc5d0 \uc9d1\uc911\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<\/p>\n\n\n\n<p>\ubb3c\ub860 \uc774 \ud3ec\uc2a4\ud305\uc744 \ud1b5\ud574  IAM AccessKey \uc720\ucd9c\uc5d0 \ub300\ud55c \ub300\uc751 \ubc29\ubc95\uc774 \uc788\ub2e4\ub294 \uac83\uc744 \ub9d0\ud558\uace0\uc790 \ud558\ub294 \uac83\uc774 \uc544\ub2d9\ub2c8\ub2e4. \uc6b0\ub9ac\ub294 IAM AccessKey \uc0ac\uc6a9\uc744 \uc9c0\uc591\ud558\uace0 IAM Role\uc744 \uc801\uadf9\uc801\uc73c\ub85c \uc0ac\uc6a9\ud560 \ud544\uc694\uac00 \uc788\uc2b5\ub2c8\ub2e4. \ub2e4\ub9cc, IAM AccessKey\ub97c \ubc18\ub4dc\uc2dc \uc0ac\uc6a9\ud560 \uc218 \ubc16\uc5d0 \uc5c6\ub294 \uc0c1\ud669\uc774\ub77c\ub294 \uac8c \uacb0\uad6d \ucc3e\uc544\uc628\ub2e4\uba74 \uc774\ub7ec\ud55c \ub300\uc548\uc744 \ubbf8\ub9ac \uad6c\uc131\ud574\ub454\ub2e4\uba74 \ubcf4\uc548 \uc704\ud611\uc73c\ub85c\ubd80\ud130 \uc870\uae08\uc774\ub098\ub9c8 \uc548\uc804\ud558\uac8c \uc11c\ube44\uc2a4\ub97c \uc6b4\uc601\ud560 \uc218 \uc788\uc744 \uac83\uc774\ub77c \uc0dd\uac01\ub429\ub2c8\ub2e4.<\/p>\n\n\n\n<p>\uc774 \uae00\uc744 \ud1b5\ud574 AWS \ud658\uacbd\uc5d0\uc11c \ubcf4\uc548 \uc790\ub3d9\ud654\ub97c \uad6c\ud604\ud558\ub294 \ub370 \ub3c4\uc6c0\uc774 \ub418\uae38 \ubc14\ub78d\ub2c8\ub2e4.<\/p>\n\n\n\n<p>\uae34 \uae00 \uc77d\uc5b4\uc8fc\uc154\uc11c \uac10\uc0ac\ud569\ub2c8\ub2e4.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\uc548\ub155\ud558\uc138\uc694. MANVSCLOUD \uae40\uc218\ud604\uc785\ub2c8\ub2e4. AWS \ud658\uacbd\uc5d0\uc11c \uac1c\ubc1c\ud558\ub2e4 \ubcf4\uba74 \uac00\uc7a5 \uc870\uc2ec\ud574\uc57c \ud560 \uac83 \uc911 \ud558\ub098\uac00 \ubc14\ub85c IAM Access Key \uad00\ub9ac\uc785\ub2c8\ub2e4. \uc2e4\uc218\ub85c GitHub\uc5d0 .env \ud30c\uc77c\uc744 \ud478\uc2dc\ud558\uac70\ub098 \uacf5\uac1c \uc800\uc7a5\uc18c\uc5d0 \ud0a4\uac00 \ub178\ucd9c\ub418\ub294 \uc21c\uac04 \uacf5\uaca9\uc790\ub294 \uc9e7\uc740 \uc2dc\uac04 \ub0b4\uc5d0 \uc774\ub97c \uc545\uc6a9\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc2e4\uc81c\ub85c GitHub\uc5d0 \uc5c5\ub85c\ub4dc\ub41c AWS Credential\uc740 \ud3c9\uade0 5\ubd84 \uc774\ub0b4\uc5d0 \uc2a4\uce94\ub418\uace0 \uc545\uc6a9\ub41c\ub2e4\uace0 \ud569\ub2c8\ub2e4. \uc0ac\uc2e4 \uc774\ub7ec\ud55c \ubb38\uc81c\ub97c \ud574\uacb0\ud558\uae30 \uc704\ud574 AWS\ub294 \ud0a4\uac00 \ub178\ucd9c\ub420 \uacbd\uc6b0 AWS Health \uc11c\ube44\uc2a4\ub97c \uc774\uc6a9\ud558\uc5ec \uc0ac\uc6a9\uc790\uc5d0\uac8c \uc54c\ub824\uc8fc\uace0 \uc788\uc2b5\ub2c8\ub2e4. \ud558\uc9c0\ub9cc \ud3c9\uc18c\uc5d0 \uba54\uc77c\uc744 \uc798 \ud655\uc778\ud558\uc9c0 \uc54a\uac70\ub098 \uad00\ub9ac\ud558\ub294 \uacc4\uc815\uc774 \ub9ce\uc544\uc11c \uc790\uce6b \uba54\uc77c\uc744 \ub193\uce60 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ub530\ub77c\uc11c \uba54\uc77c \uc54c\ub9bc\uc744 \ud655\uc778\ud558\uae30\uae4c\uc9c0 \uc2dc\uac04\uc774 \uc18c\uc694\ub418\uac70\ub098 \uc720\ucd9c\ub41c \uc0ac\uc2e4 \uc870\ucc28 \ubaa8\ub978\ucc44 \uc9c0\ub098\uac08 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \ub610\ud55c \ub4a4\ub2a6\uac8c \ud0a4 \uc720\ucd9c \uc2dc \uc2dc\uc2a4\ud15c\uc5d0 \uc0ac\uc6a9\ub418\ub294 \uc815\uc0c1\uc801\uc778 \ud638\ucd9c\ub9cc \ub418\uc5c8\ub294\uc9c0, \uc545\uc6a9\ub41c \ud638\ucd9c\uc774 \uc788\ub294\uc9c0 CloudTrail \ub85c\uadf8\ub97c \uc218\ub3d9\uc73c\ub85c \ud655\uc778\ud558\uba70 \uc601\ud5a5 \ubc94\uc704\ub97c \ud30c\uc545\ud558\uae30\uc5d0\ub294 \uc624\ub79c \uc2dc\uac04\uc774 \uc18c\uc694\ub429\ub2c8\ub2e4. \ubfd0\ub9cc \uc544\ub2c8\ub77c \uc57c\uac04\uc774\ub098 \ud734\uc77c\uc5d0 \uc774\ub7ec\ud55c \uc0ac\uace0\uac00 \ubc1c\uc0dd\ud55c\ub2e4\uba74 \uc0ac\uace0 \ub300\uc751 \uc9c0\uc5f0\uc73c\ub85c \uc774\uc5b4\uc9c8 \uc218\ub3c4 \uc788\uad6c\uc694. \uc624\ub298\uc740 \uc774\ub7ec\ud55c \ubb38\uc81c\ub97c \ud574\uacb0\ud558\uae30 \uc704\ud574 AWS\uc758 \uad00\ub9ac\ud615 \uc11c\ube44\uc2a4\ub4e4\uc744 \uc870\ud569\ud558\uc5ec IAM Access Key \uc720\ucd9c \uc0ac\uace0\ub97c \uc790\ub3d9\uc73c\ub85c \ud0d0\uc9c0-\ubd84\uc11d-\ub300\uc751\ud558\ub294 End to End \ud30c\uc774\ud504\ub77c\uc778\uc744 \uad6c\ucd95\ud558\ub294 \ubc29\ubc95\uc744 \uc18c\uac1c\ud558\uaca0\uc2b5\ub2c8\ub2e4. \ud2b9\ud788 Bedrock Agent\ub97c \ud65c\uc6a9\ud55c AI \uae30\ubc18 \uc0ac\uace0 \ubd84\uc11d\uacfc Step Functions\uc758 JSONata\ub97c \ud65c\uc6a9\ud55c \uc720\uc5f0\ud55c \uc6cc\ud06c\ud50c\ub85c\uc6b0 \uc81c\uc5b4\uc5d0 \uc911\uc810\uc744 \ub450\uc5c8\uc2b5\ub2c8\ub2e4. \uc774 \uc2dc\uc2a4\ud15c\uc744 \uad6c\uc131\ud558\uae30 \uc704\ud574 aws-samples\uc5d0 \uc788\ub294 \uc704 &#8216;AWS Access Key Exposure Detection and Remediation System&#8217; URL\uc744 \ucc38\uace0\ud558\uc600\uace0, \uc800\uc640 \uac19\uc774 Email\uc774 \uc544\ub2cc \ub2e4\ub978 \uc194\ub8e8\uc158\uc73c\ub85c \uc54c\ub9bc\uc744 \ubc1b\uae38 \uc6d0\ud558\uc2dc\uac70\ub098, CloudFormation \ud615\ud0dc\uac00 \uc544\ub2c8\ub77c \uc2e4\uc81c \ucf58\uc194\uc5d0\uc11c \uc9c1\uc811 \uad6c\ucd95\ud558\uc5ec \uad00\ub9ac\ub97c [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[1022,6,1024,1019,1020,1023,143,32,1021],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/manvscloud.com\/index.php?rest_route=\/wp\/v2\/posts\/3029"}],"collection":[{"href":"https:\/\/manvscloud.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/manvscloud.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/manvscloud.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/manvscloud.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3029"}],"version-history":[{"count":6,"href":"https:\/\/manvscloud.com\/index.php?rest_route=\/wp\/v2\/posts\/3029\/revisions"}],"predecessor-version":[{"id":3038,"href":"https:\/\/manvscloud.com\/index.php?rest_route=\/wp\/v2\/posts\/3029\/revisions\/3038"}],"wp:attachment":[{"href":"https:\/\/manvscloud.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3029"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/manvscloud.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3029"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/manvscloud.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3029"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}