Skip to content

feat(aws): add check secretsmanager_has_restrictive_resource_policy#6985

Open
kagahd wants to merge 16 commits intoprowler-cloud:masterfrom
kagahd:secretsmanager_has_restrictive_resource_policy
Open

feat(aws): add check secretsmanager_has_restrictive_resource_policy#6985
kagahd wants to merge 16 commits intoprowler-cloud:masterfrom
kagahd:secretsmanager_has_restrictive_resource_policy

Conversation

@kagahd
Copy link
Contributor

@kagahd kagahd commented Feb 19, 2025

Context

This feature request offers a new AWS check secretsmanager_has_restrictive_resource_policy.
The check evaluates resource-based policies for AWS Secrets Manager secrets.

Description

To pass the check, resource-based policies attached to secrets must meet all of the following criteria:

1. Explicit "Deny" Statement for Unauthorized Principals

The resource policy must include an explicit Deny statement that applies to all principals, all actions, and all resources, unless exceptions are defined in the StringNotEquals Condition block.

Example

{
  "Sid": "DenyUnauthorizedPrincipals",
  "Effect": "Deny",
  "Principal": "*",
  "Action": "*",
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "aws:PrincipalArn": [
        "<ASSUMED-ROLE-ARN-FOR-SECURY-AUDIT>",
        "arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:role/SomeOtherRole"
      ]
    }
  }
}
...<further required policy statements>...

This structure ensures that unauthorized access is denied across the board.

Principal: "*"
Applies the denial to all entities, internal or external, preventing unauthorized principals from accessing the secret.

Action: "*" or "secretsmanager:*"
Covers all possible actions, ensuring no unauthorized operation—directly or indirectly—can be performed on the secret. Using "secretsmanager:*" is acceptable to limit the denial to actions related specifically to Secrets Manager.

Resource: "*"
Because resource-based policies are inherently tied to the secret they protect, this field can either use a wildcard ("*" ) for broader applicability or the exact ARN of the secret for stricter targeting.

The StringNotEquals Condition block refines the denial by allowing exceptions, such as specific roles defined in aws:PrincipalArn.

One mandatory exception must be given to <ASSUMED-ROLE-ARN-FOR-SECURY-AUDIT> because this role, which is deployed in every organization's AWS account, must be allowed to audit the secret by the IT security team.

All Principal ARNs that will have their own policy entry must also be listed here. Otherwise, this rule would already block any access attempts.

Using wildcards in Principal ARNs

The check allows using an ArnNotLike condition with key aws:PrincipalArn within the explicit Deny statement for unauthorized principals. The role name must contain at least 12 characters before the wildcard.

Example

{
  "Sid": "DenyUnauthorizedPrincipals",
  "Effect": "Deny",
  "Principal": "*",
  "Action": "*",
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "aws:PrincipalArn": [
        "<ASSUMED-ROLE-ARN-FOR-SECURY-AUDIT>",
        "arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:role/SomeOtherRole"
      ]
    },
    "ArnNotLike": {
      "aws:PrincipalArn": [
        "arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:role/<PREFIX>*",
        "arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:role/<PREFIX>*"
      ]
    }
  }
}
...<further required policy statements>...

Excluding AWS services from the Deny

If AWS services must also be excluded from the Deny statement, the StringNotEqualsIfExists condition operator must be used instead of StringNotEquals. This ensures that both principals and services can be excluded even though only one of them may exist in the request.

Additionally, a Null condition block must be included which contains both aws:PrincipalArn and aws:PrincipalServiceName with value "true". This ensures that requests missing both keys are denied.

Example

{
  "Sid": "DenyUnauthorizedPrincipals",
  "Effect": "Deny",
  "Principal": "*",
  "Action": "*",
  "Resource": "*",
  "Condition": {
    "StringNotEqualsIfExists": {
      "aws:PrincipalArn": [
        "<ASSUMED-ROLE-ARN-FOR-SECURY-AUDIT>",
        "arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:role/SomeOtherRole"
      ],
      "aws:PrincipalServiceName": "appflow.amazonaws.com"
    },
    "Null": {
      "aws:PrincipalArn": "true",
      "aws:PrincipalServiceName": "true"
    }
  }
}
...<further required policy statements>...

2. Restrict Access Outside the Organization

The second mandatory Deny statement ensures that principals outside the AWS Organization are denied access to the secret.

{
  "Sid": "DenyOutsideOrganization",
  "Effect": "Deny",
  "Principal": "*",
  "Action": "*",
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "aws:PrincipalOrgID": "<YOUR_ORG_ID>"
    }
  }
}
...<further required policy statements>...

The StringNotEquals condition ensures that the denial applies to any request coming from outside of the organization, as identified by the aws:PrincipalOrgID condition key.

The value(s) of aws:PrincipalOrgID can be configured via the key organizations_trusted_ids in prowler's config.yaml.

If the AWS account is not part of an organization, this value can be left empty in the configuration. In that case, the check secretsmanager_has_restrictive_resource_policy will not require the DenyOutsideOrganization statement.

Dealing with AWS Services

AWS services (e.g. "appflow.amazonaws.com") do not send a PrincipalOrgID in their requests. Therefore the service name must also be added to the condition block.

Example

{
  "Sid": "DenyOutsideOrganization",
  "Effect": "Deny",
  "Principal": "*",
  "Action": "*",
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "aws:PrincipalOrgID": "<YOUR_ORG_ID>",
      "aws:PrincipalServiceName": "appflow.amazonaws.com"
    }
  }
}
...<further required policy statements>...

This statement alone does not guarantee that the service runs within the same AWS account or organization.
Therefore the service must additionally be restricted in its Allow statement using aws:SourceAccount (see section 4. Allow Statement for AWS services).

3. Deny Statement to Permit Only Specific Actions for Designated Principals

These Deny statements ensure that specific principals are restricted to performing only specific actions.

These statements are optional, but if omitted the principals listed in DenyUnauthorizedPrincipals will effectively be denied all actions.

Any principal listed in the StringNotEquals condition of DenyUnauthorizedPrincipals must have a corresponding Deny statement with a tailored NotAction clause.

Wildcards (*) are not allowed in NotAction. Refer to the supported AWS Secrets Manager actions.

The IT security team must be able to audit the resource-based policy. Therefore the principal <ASSUMED-ROLE-ARN-FOR-SECURY-AUDIT> must be permitted the actions:

  • secretsmanager:DescribeSecret
  • secretsmanager:GetResourcePolicy

Example

{
  "Sid": "AllowAuditPolicyRead",
  "Effect": "Deny",
  "Principal": {
    "AWS": "<ASSUMED-ROLE-ARN-FOR-SECURY-AUDIT>"
  },
  "NotAction": [
    "secretsmanager:DescribeSecret",
    "secretsmanager:GetResourcePolicy"
  ],
  "Resource": "*"
},
{
  "Sid": "AllowSecretAccessForSomeOtherRole",
  "Effect": "Deny",
  "Principal": {
    "AWS": "arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:role/SomeOtherRole"
  },
  "NotAction": [
    "secretsmanager:GetSecretValue"
  ],
  "Resource": "*"
}
...<further required policy statements>...

Using wildcards for principals

The check allows using ArnLike with aws:PrincipalArn to define the allowed actions for principals that were specified via ArnNotLike in the DenyUnauthorizedPrincipals statement.

The role name must again contain at least 12 characters before the wildcard.

Example

{
  "Sid": "AllowReadOnlyRolePrefixAccess",
  "Effect": "Deny",
  "Principal": "*",
  "NotAction": [
    "secretsmanager:GetSecretValue",
    "secretsmanager:DescribeSecret"
  ],
  "Resource": "*",
  "Condition": {
    "ArnLike": {
      "aws:PrincipalArn": [
        "arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:role/<PREFIX>*",
        "arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:role/<PREFIX>*"
      ]
    }
  }
}
...<further required policy statements>...

4. Allow Statement for AWS Services

AWS services require an explicit Allow statement to perform actions on the secret.

Two requirements apply:

  1. No wildcard (*) in any Action
  2. The AWS SourceAccount must match the current account to prevent access from services running in external AWS accounts.

Example

{
  "Sid": "AllowAppFlowAccess",
  "Effect": "Allow",
  "Principal": {
    "Service": "appflow.amazonaws.com"
  },
  "Action": [
    "secretsmanager:GetSecretValue",
    "secretsmanager:PutSecretValue",
    "secretsmanager:DeleteSecret",
    "secretsmanager:DescribeSecret",
    "secretsmanager:UpdateSecret"
  ],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "aws:SourceAccount": "<YOUR_AWS_ACCOUNT_ID>"
    }
  }
}
...<further required policy statements>...

Cross-account access

Cross-account access to secrets is not allowed.
Resource-based policies that allow cross-account access will cause the check to fail on purpose. This behavior is intentional to ensure that any cross-account access is made explicitly visible during reviews and compliance checks, since from an IT Security perspective it expands the trust boundary and therefore requires deliberate assessment and clear justification.

Complete Resource-Based Policy Example

Below is a full example resource policy for scenarios where only IAM principals require access.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyUnauthorizedPrincipals",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:PrincipalArn": [
            "<ASSUMED-ROLE-ARN-FOR-SECURY-AUDIT>",
            "arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:role/SomeOtherRole"
          ]
        }
      }
    },
    {
      "Sid": "DenyOutsideOrganization",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:PrincipalOrgID": "<YOUR_ORG_ID>"
        }
      }
    },
    {
      "Sid": "AllowAuditPolicyRead",
      "Effect": "Deny",
      "Principal": {
        "AWS": "<ASSUMED-ROLE-ARN-FOR-SECURY-AUDIT>"
      },
      "NotAction": [
        "secretsmanager:DescribeSecret",
        "secretsmanager:GetResourcePolicy"
      ],
      "Resource": "*"
    },
    {
      "Sid": "AllowSecretAccessForSomeOtherRole",
      "Effect": "Deny",
      "Principal": {
        "AWS": "arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:role/SomeOtherRole"
      },
      "NotAction": [
        "secretsmanager:GetSecretValue"
      ],
      "Resource": "*"
    }
  ]
}

Unit Tests

As shown in secretsmanager_has_restrictive_resource_policy_test.py, several unit tests were created.
Three of them alone cover already 105 different scenarios by modifying parts of an otherwise valid resource policy to ensure the check correctly fails.

  • test_secretsmanager_policies_for_principals = 73 test cases
  • test_secretsmanager_policies_for_principals_and_services = 29 test cases
  • test_policy_with_arn_not_like = 3 test cases

Each negative test case is followed by a counter-test that removes the invalid change to ensure the policy passes, verifying that the test logic itself is correct.

@pytest.mark.parametrize is used to reduce redundancy while maintaining readability.


Checklist

  • Are there new checks included in this PR? Yes
    • If so, do we need to update permissions for the provider? No
  • Review if the code is being covered by tests.
  • Review if code is documented following the Google Python style guide.
  • Review if backport is needed.
  • Review if changes to README.md are required.

API

  • Verify if API specs need to be regenerated.
  • Check if version updates are required.
  • Ensure new entries are added to CHANGELOG.md if applicable.

License

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@kagahd kagahd requested review from a team as code owners February 19, 2025 16:15
@github-actions github-actions bot added the provider/aws Issues/PRs related with the AWS provider label Feb 19, 2025
@jfagoagas jfagoagas changed the title feat(aws) add check secretsmanager_has_restrictive_resource_policy feat(aws): add check secretsmanager_has_restrictive_resource_policy Feb 19, 2025
@codecov
Copy link

codecov bot commented Feb 21, 2025

Codecov Report

❌ Patch coverage is 93.75000% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 3.98%. Comparing base (872e6e2) to head (b3a3fa7).
⚠️ Report is 2 commits behind head on master.

❗ There is a different number of reports uploaded between BASE (872e6e2) and HEAD (b3a3fa7). Click for more details.

HEAD has 1 upload less than BASE
Flag BASE (872e6e2) HEAD (b3a3fa7)
api 1 0
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #6985       +/-   ##
==========================================
- Coverage   93.37%   3.98%   -89.40%     
==========================================
  Files         219     836      +617     
  Lines       30415   24095     -6320     
==========================================
- Hits        28399     959    -27440     
- Misses       2016   23136    +21120     
Flag Coverage Δ
api ?
prowler-py3.10-aws 3.55% <93.75%> (?)
prowler-py3.10-config 3.98% <93.75%> (?)
prowler-py3.11-aws 3.55% <93.75%> (?)
prowler-py3.11-config 3.98% <93.75%> (?)
prowler-py3.12-aws 3.55% <93.75%> (?)
prowler-py3.12-config 3.98% <93.75%> (?)
prowler-py3.9-aws 3.55% <93.75%> (?)
prowler-py3.9-config 3.98% <93.75%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
prowler 3.98% <93.75%> (∅)
api ∅ <ø> (∅)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@MrCloudSec MrCloudSec changed the title feat(aws): add check secretsmanager_has_restrictive_resource_policy feat(aws): add check secretsmanager_has_restrictive_resource_policy Feb 26, 2025
@github-actions github-actions bot added the github_actions Pull requests that update GitHub Actions code label May 12, 2025
@kagahd kagahd force-pushed the secretsmanager_has_restrictive_resource_policy branch 2 times, most recently from 1f9ea7d to 0b693ef Compare May 13, 2025 15:34
@jfagoagas
Copy link
Member

Hello @kagahd I apologise because this PR got lost in our inbox. The team is going to review it as soon as possible.

Thanks!

@andoniaf andoniaf added the community Opened by the Community label Oct 30, 2025
@jfagoagas jfagoagas added the status/awaiting-reponse Waiting response from owner label Mar 9, 2026
@HugoPBrito
Copy link
Member

Hi @kagahd,

Are you planning on continuing this development? Just to know if we should proceed to close it/try to finish it ourselves.

Thanks!

@kagahd
Copy link
Contributor Author

kagahd commented Mar 13, 2026

Are you planning on continuing this development? Just to know if we should proceed to close it/try to finish it ourselves.

Thanks @HugoPBrito for reminding me.
Yes, we are running already a "finalized" version of this check in our company and I think it's valuable to share it with the community. As soon as I have time for it, I will update this PR or do you think it's better to create a new one?

@HugoPBrito
Copy link
Member

Are you planning on continuing this development? Just to know if we should proceed to close it/try to finish it ourselves.

Thanks @HugoPBrito for reminding me.
Yes, we are running already a "finalized" version of this check in our company and I think it's valuable to share it with the community. As soon as I have time for it, I will update this PR or do you think it's better to create a new one?

Hi @kagahd!

As you prefer. As long as they're linked in case you create a new one, that's up to what's best for you.

@github-actions github-actions bot added metadata-review and removed github_actions Pull requests that update GitHub Actions code labels Mar 16, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 16, 2026

Conflict Markers Resolved

All conflict markers have been successfully resolved in this pull request.

@kagahd
Copy link
Contributor Author

kagahd commented Mar 16, 2026

Hi @HugoPBrito, I have now updated the PR to the latest version, including the revised description.
We are aware that this check is quite complex and extensive, and that some engineers responsible for keeping AWS environments compliant with IT Security requirements may find it challenging to implement and remediate all related findings. Nevertheless, we consider this check highly valuable from a security perspective, because it helps make risky access patterns visible and enforces a more deliberate review of Secrets Manager resource policies.
For that reason, we continue to strongly support this check and will work closely with the affected teams to help them understand the requirements and remediate the identified risks.

@danibarranqueroo danibarranqueroo added status/waiting-for-revision Waiting for maintainer's revision and removed status/awaiting-reponse Waiting response from owner labels Mar 17, 2026
@HugoPBrito
Copy link
Member

Hi @kagahd,

I get your point, but we will need to evaluate it. I believe this will end up merged, since it brings a lot of value, and if someone don't want to deal with it, they can always mute it.

Since the pr is huge and complex, the review will be most likely slow. In the meantime, please adapt the metadata to the new standard, defined in our docs on the metadata guidelines page.

Please feel free to add information you find valuable to the Notes field.

@kagahd
Copy link
Contributor Author

kagahd commented Mar 19, 2026

Hi @HugoPBrito

In the meantime, please adapt the metadata to the new standard, defined in our docs on the metadata guidelines page.

It's done. Let me know if anything needs further adjustment. Here's a summary of the changes:

  • CheckTitle: Rewritten as a declarative, singular statement (removed "Ensure" verb)
  • CheckType: Added Effects/Data Exposure
  • Description: Rewritten to focus on the finding using markdown formatting, describing the four evaluated controls
  • Risk: Now explains specific CIA triad impact (confidentiality, integrity, lateral movement, privilege escalation)
  • Recommendation: Principle-based guidance (deny-by-default, least privilege) instead of imperative "Ensure…"
  • Remediation Code: Added CLI, CloudFormation, Terraform, and manual console steps (were all empty before)
  • Categories: Changed from access-control to secrets and trust-boundaries
  • Notes: Added a description of the four layered controls and the intentional cross-account failure behavior
  • Added ResourceGroup, AdditionalURLs fields to match the current upstream format

kagahd added 10 commits March 19, 2026 18:10
- Add cross-account access detection in Allow statements
- Implement validation for ArnNotLike condition patterns with minimum 12-char prefix
- Refactor Deny statement validation with stricter condition checks (case 1: both PrincipalArn and PrincipalServiceName with StringNotEqualsIfExists + Null; case 2: only PrincipalArn with StringNotEquals)
- Add detailed error messages with up to 3 principals/services displayed
- Integrate is_condition_block_restrictive from IAM policy utilities for consistent validation
- Add improved handling for wildcard principals and service-based access
- Include comprehensive validation for Allow statements with Service Principals
@kagahd kagahd force-pushed the secretsmanager_has_restrictive_resource_policy branch from 58c8701 to 683b4f8 Compare March 19, 2026 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community Opened by the Community metadata-review provider/aws Issues/PRs related with the AWS provider status/waiting-for-revision Waiting for maintainer's revision

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants