An instance role's credentials show up in the audit trail from an outside address. What is misconfigured?
Audit events show one instance role calling the storage API from the instance's own address and, minutes later, from an unrelated public address. The instance profile is attached to a web application that fetches URLs supplied by users.
Constraints:
- The instance metadata options and the role policy are given below as they exist today.
- Propose configuration changes only — no application rewrite is available in this window.
- Every change must be verifiable from configuration alone.
ts=2026-04-11T09:14:02Z principal=AROA…:i-0af31 event=GetObject sourceIP=10.0.4.19 userAgent=aws-sdk-python
ts=2026-04-11T09:21:48Z principal=AROA…:i-0af31 event=ListBuckets sourceIP=203.0.113.77 userAgent=aws-cli/2.15
ts=2026-04-11T09:22:10Z principal=AROA…:i-0af31 event=GetObject sourceIP=203.0.113.77 userAgent=aws-cli/2.15
instance metadata options: HttpTokens=optional HttpPutResponseHopLimit=2 HttpEndpoint=enabled
role policy statement: Effect=Allow Action=s3:* Resource=*
Name the misconfiguration and give the remediation.
The instance still accepts the token-less v1 metadata protocol and carries a wildcard role, so a request-forgery flaw in the app let its credentials be read and replayed off-host — the foreign address using the instance role is the tell. Require IMDSv2 and hop limit one, scope the role.
- ✗Reading the foreign source address as a NAT gateway rather than credential replay
- ✗Leaving HttpTokens optional because IMDSv2 is described as backwards compatible
- ✗Fixing only the metadata options and leaving the wildcard role in place
- →Which audit-trail query finds every instance role used from outside your ranges?
- →Why does a hop limit of one matter once tokens are already required?
A foreign public address calling the API as the same instance role means the role credentials were obtained on the instance and replayed from outside. Two settings made that possible: HttpTokens=optional keeps the token-less v1 metadata protocol reachable by the application's request-forgery flaw, and HttpPutResponseHopLimit=2 lets even a container reach it. The s3:* on * policy turns one leaked credential into access to all storage in the account.
Remediation, configuration only:
# 1. Require a session token (IMDSv2) and remove the extra network hop
aws ec2 modify-instance-metadata-options --instance-id i-0af31 \
--http-tokens required --http-put-response-hop-limit 1 --http-endpoint enabled
# 2. Revoke the sessions already issued for the role (time-conditioned policy)
aws iam put-role-policy --role-name app-instance-role \
--policy-name RevokeOlderSessions --policy-document file://revoke-older-sessions.json
# 3. Narrow the role to one bucket and the specific actions it needs,
# instead of Action s3:* / Resource *
Then detection: alert on an instance role used from outside your address ranges, inventory instances still on HttpTokens=optional via configuration scanning, and block creation of wildcard-resource roles with an organisation-level guardrail.