Fixing AccessDenied on secretsmanager:GetSecretValue Caused by aws:PrincipalOrgID Condition Mismatch
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 10–20 mins
TL;DR
- What broke: A Secrets Manager resource policy has an
aws:PrincipalOrgIDcondition that doesn't match the AWS Organization ID of the calling principal, causing a hardAccessDeniedregardless of identity-based policies. - How to fix it: Verify the exact Organization ID (
o-xxxxxxxxxx) attached to the secret's resource policy matches the org of the calling account; correct the value or scope the condition to the right OU. - Shortcut: Use our Client-Side Sandbox below to auto-refactor this — paste your resource policy and get the corrected condition block without leaking ARNs to a third-party server.
The Incident (What Does the Error Mean?)
Raw error output from CLI or CloudWatch Logs:
An error occurred (AccessDenied) when calling the GetSecretValue operation:
User: arn:aws:iam::123456789012:role/my-app-role is not authorized to perform:
secretsmanager:GetSecretValue on resource: arn:aws:secretsmanager:us-east-1:987654321098:secret:prod/db-credentials
because no resource-based policy allows the GetSecretValue action
Immediate consequence: The calling Lambda, ECS task, or EC2 instance role receives a hard deny. There is no fallback. The application cannot retrieve the secret. If this is a database credential or API key fetch at startup, the service fails to initialize entirely — pods crash-loop, Lambda invocations return 500s, and on-call gets paged.
The deceptive part: the IAM identity policy on the role may be perfectly correct. The block is happening at the resource policy evaluation layer, specifically at the Condition block. AWS evaluates resource policy conditions as an explicit deny gate when the principal is cross-account.
The Attack Vector / Blast Radius
aws:PrincipalOrgID is a powerful guardrail — it restricts secret access to principals that belong to your AWS Organization. When misconfigured, the blast radius splits two ways:
Scenario A — Value Typo or Stale Org ID:
The resource policy was copy-pasted from another secret or another region. The aws:PrincipalOrgID value references an old or incorrect org ID (e.g., from a pre-merger org structure or a sandbox org). Every principal — including production workloads — is locked out. Downtime is immediate and total.
Scenario B — Overly Permissive Fallback Temptation:
Engineers under pressure remove the aws:PrincipalOrgID condition entirely to restore service. This is the dangerous fix. The secret is now accessible to any authenticated AWS principal with the right identity policy, including principals in external accounts if the Principal block is set to "*" or "AWS": "*". This is a credential exfiltration vector — an attacker with any foothold in an account that has secretsmanager:GetSecretValue in an identity policy can now pull your production DB password.
Blast radius of a fully open resource policy on a secret:
- Database credentials exposed cross-account
- API keys for third-party services accessible outside your trust boundary
- Audit trail becomes meaningless — you can't distinguish legitimate from malicious access by org membership
How to Fix It (The Solution)
Step 1 — Confirm Your Actual Org ID
aws organizations describe-organization --query 'Organization.Id' --output text
# Returns: o-ab12cd34ef
Also confirm the calling principal's account is actually IN that org:
aws organizations describe-account --account-id 123456789012
If it returns an error or a different master account, the calling account is not in the org the secret expects.
Basic Fix — Correct the Org ID Value
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowOrgAccess",
"Effect": "Allow",
"Principal": "*",
"Action": "secretsmanager:GetSecretValue",
"Resource": "*",
"Condition": {
"StringEquals": {
- "aws:PrincipalOrgID": "o-xxxxWRONGxxxx"
+ "aws:PrincipalOrgID": "o-ab12cd34ef"
}
}
}
]
}
Enterprise Best Practice — Scope to OU + Specific Actions + Explicit Principal
Never use "Principal": "*" in a Secrets Manager resource policy in production. Combine aws:PrincipalOrgID with aws:PrincipalOrgPaths for OU-level scoping and lock down the Principal to specific roles.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "LeastPrivilegeOrgAccess",
"Effect": "Allow",
- "Principal": "*",
+ "Principal": {
+ "AWS": "arn:aws:iam::123456789012:role/my-app-role"
+ },
"Action": [
- "secretsmanager:*"
+ "secretsmanager:GetSecretValue",
+ "secretsmanager:DescribeSecret"
],
"Resource": "*",
"Condition": {
"StringEquals": {
- "aws:PrincipalOrgID": "o-xxxxWRONGxxxx"
+ "aws:PrincipalOrgID": "o-ab12cd34ef"
+ },
+ "ForAnyValue:StringLike": {
+ "aws:PrincipalOrgPaths": "o-ab12cd34ef/r-xxxx/ou-xxxx-yyyyyyyy/*"
}
}
}
]
}
Key hardening points:
- Explicit Principal ARN — eliminates the wildcard blast radius
aws:PrincipalOrgPaths— restricts to a specific OU subtree, not the entire org- Minimal actions —
GetSecretValue+DescribeSecretonly; neversecretsmanager:*
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. Checkov — Catch Wildcard Principals at PR Time
Add to your pipeline:
checkov -d . --check CKV_AWS_149
# CKV_AWS_149: Ensure that Secrets Manager secret is not publicly accessible
Checkov will flag any resource policy with "Principal": "*" lacking a restrictive condition.
2. OPA / Conftest Policy for Org ID Validation
If you manage secrets via Terraform, enforce the org ID at plan time:
# policy/secrets_manager.rego
package aws.secretsmanager
violation[msg] {
res := input.resource_changes[_]
res.type == "aws_secretsmanager_secret_policy"
policy := json.unmarshal(res.change.after.policy)
stmt := policy.Statement[_]
condition := stmt.Condition.StringEquals
not startswith(condition["aws:PrincipalOrgID"], "o-")
msg := sprintf("Secret policy for %v has invalid or missing PrincipalOrgID", [res.address])
}
3. Terraform — Parameterize the Org ID, Never Hardcode
variable "org_id" {
description = "AWS Organization ID"
type = string
sensitive = false
}
data "aws_iam_policy_document" "secret_policy" {
statement {
effect = "Allow"
actions = ["secretsmanager:GetSecretValue"]
principals {
type = "AWS"
identifiers = [var.app_role_arn]
}
resources = ["*"]
condition {
test = "StringEquals"
variable = "aws:PrincipalOrgID"
values = [var.org_id]
}
}
}
Feed org_id from a remote state output or SSM Parameter Store — never from a hardcoded string in the repo.
4. AWS Config Rule — Continuous Compliance
Enable the managed rule secretsmanager-secret-unused and pair it with a custom Config rule that validates aws:PrincipalOrgID is present and matches your known org ID pattern (o-[a-z0-9]{10,32}). Alert via SNS to your security channel on any drift.