Building Secure AWS S3 Access for Departments at DataSafe Solutions

Here’s the situation: I needed to set up secure access to S3 buckets for different departments, HR, Finance, and Marketing, without creating a permissions spaghetti mess.
I used Terraform and AWS IAM to do it.
But instead of pretending it all went smoothly, I’m going to show you the real bumps I hit, how I untangled them, and why this setup works.
GitHub repo: github.com/hassanathussain/datasafe-solutions
Core Concepts Used
We’re using IAM users + roles + policies to create a clean RBAC flow:
Users don’t get direct permissions to S3.
They assume a role instead.
The role has actual access.
This way, we separate identity from permissions. Cleaner. Safer. Easier to audit.
1. IAM User
Represents a person or system.
Created in
users.tf
2. IAM Role
A set of permissions that can be assumed.
Created in
roles.tf
3. IAM Policy
Defines allowed actions and resources.
Defined in
*_policy.tf
andassume_policies.tf
4. Trust Policy
- Set in
assume_role_policy
defines who can assume the role.
5. Policy Attachment
- Binds users or roles to the defined permissions.
How the Files Talk to Each Other
users.tf
Defines users like
hr_user
,finance_user
, etc.Attaches an assume-role policy to each user, enabling them to assume their department-specific role.
roles.tf
Defines roles like
HRAccessRole
,FinanceAccessRole
.Sets the trust policy to allow assumption by IAM users in the same account.
Attaches permissions via policies defined elsewhere.
assume_policies.tf
- Creates policies that allow
sts:AssumeRole
for each user.
*_policy.tf
Defines actual access levels:
HR: full access to
S3://bucket/HR/
Finance: full access to
Finance/
, read-only toHR/
Marketing: full access to
Marketing/
🔐 RBAC in Action
IAM User | Assumed Role | Permissions |
hr_user | HRAccessRole | Full access to HR/ |
finance_user | FinanceAccessRole | Full Finance/ , read-only HR/ |
marketing_user | MarketingAccessRole | Full access to Marketing/ |
Users don’t get direct access to S3. Instead:
They assume roles with
sts:AssumeRole
.Roles carry the actual permissions.
This is the core of RBAC.
Real Mistakes I Made (So You Don’t Have To)
❌ Trust Policy Too Open
What I did:
Set "Principal": {"AWS": "*"}
In my trust policy.
Why that’s bad:
That lets any AWS identity assume your role. It’s like leaving your house key under the doormat... and tweeting about it.
Fix:
I locked it down to a specific user:
"Principal": {
"AWS": "arn:aws:iam::${account_id}:user/finance_user"
}
❌ Forgot s3:ListBucket
Symptom:
Users could upload/download, but not list files.
Fix:
Added this:
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::bucket-name",
"Condition": {
"StringLike": {
"s3:prefix": ["Finance/*"]
}
}
}
Now they can see what’s actually in the bucket.
❌ Bad Resource ARNs
What I did:
Used arn:aws:s3:::bucket-name/Finance
instead of Finance/*
Result:
Access denied errors even though everything looked fine.
Fix:
Be explicit: arn:aws:s3:::bucket-name/Finance/*
❌ Gave Users Too Much Power
Early mistake:
Gave users direct S3 permissions and gave roles S3 permissions. Total overkill. And confusing.
Cleaned it up:
Users only get permission to assume roles.
Roles hold all the access power.
✅ Why This Works
Least privilege: No one gets more than they need.
Clear separation: Identity ≠ permissions.
Easy to audit: Who assumed what role, and when? CloudTrail knows.
Scalable: Adding a new team is just a new role + policy + user.
Walkthrough: How finance_user
Gets Access
Logs in with IAM credentials
Uses
sts:AssumeRole
to grabFinanceAccessRole
AWS checks:
Does this user have permission to assume the role? ✅
Does the role trust the user? ✅
AWS returns a temporary access token
The user uses that token to access S3 like a boss
Final Thoughts
This isn’t just about S3 permissions.
It’s about learning to think clearly about who should have access to what, and building that clarity into your infrastructure. Tools like Terraform make it repeatable, but you’ve got to know where the traps are.
Now you do.
If you want a walkthrough of the Terraform code structure next, let me know happy to break that down too.
Subscribe to my newsletter
Read articles from Hassan Hussain directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Hassan Hussain
Hassan Hussain
Cloud Architect | AWS Certified | DevOps Enthusiast I help developers and teams navigate cloud migrations and efficient CI/CD pipelines. Currently designing scalable infrastructure, automation, and monitoring on AWS (Terraform, GitHub Actions, ECS, Prometheus/Grafana) Documenting weekly learning and projects publicly