Access requests and least privilege

How to turn "please give me access" into a time-bound, least-privilege grant through code, with an approver, an expiry and evidence for audit.

Updated 2026-11-11Raw markdown

Strategy sections
On this page

Use this page for any request for cloud, database, bucket, repo or tool access, and for periodic access reviews. Access requests are high volume and low complexity, which makes them a good candidate for automation, as long as a human approves every grant.

Every request must answer five questions

Question Example answer
Who? A named person or a service role, never a shared login
What resource? <bucket> read, <database> read-only schema reports
What level? read, write, admin (admin needs a strong reason)
Why? Ticket and business reason
Until when? 30 days, end of project, or permanent with quarterly review

If any answer is missing, ask once in the ticket and wait.

Prefer groups and roles over direct grants

  • Add the person to an existing group or permission set that fits. Do not create a new policy per person.
  • For AWS, grant through IAM Identity Center permission sets assigned to groups.
  • For databases, grant roles (for example reports_read) and add users to roles.
  • Production write and admin access is just-in-time where possible, with automatic expiry.

Write the smallest policy that works

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadReportsBucket",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::<bucket>",
        "arn:aws:s3:::<bucket>/reports/*"
      ]
    }
  ]
}
  • No "Action": "*" and no "Resource": "*" for write actions.
  • Use conditions where they help: aws:PrincipalTag, aws:SourceVpc, aws:RequestedRegion.
  • Check with IAM Access Analyzer policy validation before the MR.
aws --profile <readonly-profile> accessanalyzer validate-policy \
  --policy-type IDENTITY_POLICY --policy-document file://policy.json \
  --query 'findings[].{type:findingType,issue:issueCode,msg:findingDetails}'

Database access pattern

-- Run by an admin through the approved change process, not by the agent
CREATE ROLE reports_read NOLOGIN;
GRANT CONNECT ON DATABASE <database> TO reports_read;
GRANT USAGE ON SCHEMA reports TO reports_read;
GRANT SELECT ON ALL TABLES IN SCHEMA reports TO reports_read;
ALTER DEFAULT PRIVILEGES IN SCHEMA reports GRANT SELECT ON TABLES TO reports_read;
GRANT reports_read TO <user>;

Prefer IAM database authentication or short-lived credentials from a secret manager over static passwords.

The flow

  • Ticket has the five answers and an approver (the resource owner, not the requester's friend).
  • Change is an MR to the access-as-code repo (Terraform for IAM, Identity Center assignments, groups).
  • Plan shows only the expected grant.
  • Approver approves the MR or the ticket; the link goes in the MR.
  • Apply, then ask the requester to confirm it works.
  • Record the expiry and set a reminder or automated revoke.

Access reviews

  • Quarterly: list who has admin or production write, per account and system.
  • Flag unused access (IAM last-accessed data, database login history).
  • Revoke via MR, with the owner's sign-off.
aws --profile <readonly-profile> iam generate-service-last-accessed-details --arn <role-arn>
aws --profile <readonly-profile> iam get-service-last-accessed-details --job-id <job-id>

Pitfalls

  • Granting AdministratorAccess "temporarily". Temporary grants without an expiry are permanent.
  • Copying another user's permissions wholesale. Ask what they actually need.
  • Long-lived IAM user access keys for people. Use SSO. If keys exist, follow secret rotation to retire them.