S3 Wildcard Bucket Principals and Kubernetes Workloads: Proving Impact
A bucket policy that says "Principal": "*" is not automatically a critical finding. Sometimes it is paired with a hard Deny unless a VPC endpoint or source account matches. Sometimes it is a leftover from a demo and still live. Your job is to show which identities can actually touch objects, and whether a Kubernetes workload in scope can reach them.
I've seen reports that stop at "wildcard principal exists." Triage closes those. Show a path.
Where the policy shows up
Public recon rarely hands you the full JSON. Look for:
- Terraform or Helm charts that mount an S3 CSI driver, IRSA annotations, or a GCS service account key
- Error messages that name a bucket when a pod cannot list objects
- Application configs that point at
s3://,gs://, or a custom object URL - Screenshots or docs in the target's public repos describing "shared media bucket"
If the program allows cloud asset review and you have temporary credentials from another finding, pull the policy with GetBucketPolicy only on buckets clearly owned by the target. Stay inside scope. Anonymous GetObject attempts against guessed keys are fine when the asset is already public-facing; mass key enumeration against customer prefixes is not.
Wildcard is the start, conditions are the fight
Read every Condition block. Common patterns that look open but are gated:
aws:SourceVpceoraws:SourceVpcaws:PrincipalOrgIDaws:SecureTransport- Prefix restrictions under
s3:prefix
A policy with Principal: "*" and Action: s3:GetObject on arn:aws:s3:::company-assets/* with no useful condition is the classic high-signal case. Try a simple anonymous GET against a known public-looking key first. If that fails, try from a pod or role the app already runs as.
Kubernetes changes the story because the pod identity may already be trusted. An IRSA role that can s3:* on that bucket turns a "public policy" debate into "any compromised pod in this namespace can rewrite customer uploads." That is a clearer severity argument. Same idea on GKE with Workload Identity: the mapped Google SA plus a GCS object ACL that still allows allUsers is a double fail.
Ask yourself: does the CSI volume mount assume the bucket is private because the cluster network is private? Network locality is not authorization.
Proof that survives triage
Do not download real customer objects. Upload or request a canary you control:
- Create a uniquely named object under a writable prefix if write is allowed, or request a non-sensitive key such as health or static assets if they exist.
- Capture the exact request: anonymous HTTPS GET, signed URL, or API call from the pod role.
- Show the policy snippet that authorized it and the ARN or service account used.
If only list is possible, a ListBucket response that reveals object keys under a private prefix can still be medium—document the exposure carefully and avoid pasting PII key names into the report. A screenshot of key prefixes with customer emails redacted is enough.
Cross-check Block Public Access settings. I've watched teams fix the policy JSON while leaving an ACL grant that still served anonymous reads. Your second request after they "fixed" it should be the same anonymous GET.
What to recommend
Narrow principals to specific role ARNs or service accounts. Prefer bucket policies that deny by default and allow named identities. For cluster workloads, use IRSA or Workload Identity with prefix-scoped actions, not account-wide s3:*. Enable Block Public Access. Rotate any keys that lived in policy history or pod env dumps.
A wildcard principal is a smell. Pair it with a canary read or write from an identity the target already runs, and you have a cloud finding that reads like engineering, not a policy linter screenshot.