AWS Builder Center

How I Used Amazon Q to Fix an AWS IAM Security Risk in 15 Minutes

Using Amazon Q Developer inside the AWS Management Console represents a major shift in cloud administration and DevOps workflows. Historically, diagnosing cloud issues meant jumping between AWS documentation, searching error codes on StackOverflow, sifting through Amazon CloudWatch logs, and cross-checking Identity and Access Management (IAM) permissions.

These days, almost everyone is adopting AI tools to speed up their daily engineering workflows. For the past few months, I’ve been using Amazon Q directly inside the AWS Management Console.
In my experience, it has become one of the best tools for troubleshooting issues and learning about various AWS services on the fly.
Here is a real-world example of how I used Amazon Q to resolve an IAM security issue.
The Incident & Resolution
During a routine security audit, we discovered an old IAM user that was created over a year ago with full AdministratorAccess (access key and secret key enabled). Leaving admin keys active like this is a major security risk.
The Goal: Restrict this user's permissions to only the specific S3 bucket and actions they actually need (least-privilege access).

Step-by-Step Fix with Amazon Q

While there are multiple ways to identify user activity, I used Amazon Q to quickly determine the exact bucket names, permissions, and API calls associated with this user.

1. Enable Data Events in CloudTrail

First, I enabled AWS CloudTrail Data Events for S3 and configured the logs to deliver to an S3 bucket (aws-cloudtrail-logs-AccountID).

2. Query Logs Using Amazon Q & Athena

I prompted Amazon Q with the log path:

"Logs are stored here: aws-cloudtrail-logs-AccountID/AWSLogs/AccountID/CloudTrail/us-east-1/2026/09/25/. Find the user's service access and permissions."

Amazon Q analyzed the environment and provided the exact Amazon Athena setup steps and SQL queries required to parse the CloudTrail logs.
Step A: Create Database & Table in Athena
Go to Athena - > Query Editor
Make sure the Query result location is set. Go to setting and set it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
CREATE DATABASE IF NOT EXISTS cloudtrail_data_events;

CREATE EXTERNAL TABLE cloudtrail_data_events.trail_logs (
eventVersion STRING,
userIdentity STRUCT<
type : STRING,
principalId : STRING,
arn : STRING,
accountId : STRING,
invokedBy : STRING,
accessKeyId : STRING,
userName : STRING,
sessionContext : STRUCT<
attributes : STRUCT<
mfaAuthenticated : STRING,
creationDate : STRING
>,
sessionIssuer : STRUCT<
type : STRING,
principalId : STRING,
arn : STRING,
accountId : STRING,
userName : STRING
>
>
>,
eventTime STRING,
eventSource STRING,
eventName STRING,
awsRegion STRING,
sourceIPAddress STRING,
userAgent STRING,
errorCode STRING,
errorMessage STRING,
requestParameters STRING,
responseElements STRING,
requestId STRING,
eventId STRING,
resources ARRAY<STRUCT<
arn : STRING,
accountId : STRING,
type : STRING
>>,
eventType STRING,
readOnly STRING,
recipientAccountId STRING,
sharedEventId STRING,
vpcEndpointId STRING
)
PARTITIONED BY (region STRING, year STRING, month STRING, day STRING)
ROW FORMAT SERDE 'com.amazon.emr.hive.serde.CloudTrailSerde'
STORED AS INPUTFORMAT 'com.amazon.emr.cloudtrail.CloudTrailInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://aws-cloudtrail-logs-AccountID/AWSLogs/AccountID/CloudTrail/';
Step B: Load the Partition
1
2
3
ALTER TABLE cloudtrail_data_events.trail_logs
ADD PARTITION (region='us-east-1', year='2026', month='09', day='25')
LOCATION 's3://aws-cloudtrail-logs-AccountID/AWSLogs/AccountID/CloudTrail/us-east-1/2026/09/25/';
Step C: Run the Query to Find Bucket Access
1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT
json_extract_scalar(requestparameters, '$.bucketName') AS bucket_name,
eventname AS action,
COUNT() AS call_count
FROM cloudtrail_data_events.trail_logs
WHERE useridentity.username = 'engineering.cloudjet.host'
AND eventsource = 's3.amazonaws.com'
AND region = 'us-east-1'
AND year = '2026' AND month = '09' AND day = '25'
GROUP BY
json_extract_scalar(requestparameters, '$.bucketName'),
eventname
ORDER BY call_count DESC;


3. Athena Results & IAM Policy Generation

The query returned the following usage for engineering.cloudjet.host

Bucket NameActionCall Count
BUCKET_NAMEHeadObject2
BUCKET_NAMEPutObject1
BUCKET_NAMEHeadBucket1
I fed these results back to Amazon Q to generate a minimal, least-privilege policy.
Amazon Q highlighted key mapping details:
  • HeadObject maps to s3:GetObject in IAM.
  • HeadBucket maps to s3:ListBucket in IAM.
  • Bucket-level actions apply to arn:aws:s3:::BUCKET_NAME, while object-level actions apply to arn:aws:s3:::BUCKET_NAME/.Minimal Policy Generated (Option A):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3ObjectAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::BUCKET_NAME*"
},
{
"Sid": "AllowS3BucketAccess",
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::BUCKET_NAME
}
]
}
Do We Need s3:GetBucketLocation or s3:ListBucket in Code?
When reviewing the permissions, a common question comes up: Does the application code need s3:ListBucket or s3:GetBucketLocation?
  • s3:ListBucket: Needed if your code uses head_bucket() to check bucket existence or lists objects (list_objects_v2()). Since CloudTrail captured a HeadBucket call, this permission is required.
  • s3:GetBucketLocation: Only needed if the AWS SDK dynamically looks up the bucket's region on startup. Since CloudTrail didn't record this call, we omitted it for maximum restriction.
Key Takeaway & Next Steps
Amazon Q saved significant time by instantly bridging CloudTrail log parsing with Athena query generation and precise IAM scoping.
Recommended Migration Plan:
  • Attach the new host-s3-userdata-access policy to engineering.cloudjet.host.
  • Monitor CloudTrail logs for 7–14 days to ensure no necessary actions were missed.
  • Once verified, safely detach AdministratorAccess.

Conclusion: Why Amazon Q is a Game-Changer

What impressed me most about this experience was how simple it was to solve a complex problem without advanced SQL skills.
Normally, figuring out the right Athena table schema, writing SQL queries to parse JSON log parameters, and building an IAM policy from scratch would take hours of reading documentation and fixing syntax errors.
With Amazon Q, the entire process took just 15 minutes once the event logs were ready. It handled all the heavy lifting—writing the SQL queries, reading the CloudTrail logs, and generating a tight IAM policy ready to go.
Amazon Q turns hours of manual troubleshooting into a quick, effortless conversation.

What do you think?

Have you tried using Amazon Q inside the AWS console yet? How is it working for your everyday tasks? Let me know in the comments!

Community Poll: How are you using Amazon Q Developer in your team? Please take 5 seconds to vote on our community poll here:
https://builder.aws.com/poll/3KAJBYpcskyqepQkYNt0HivR6k2_po
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article