{"id":32861539,"url":"https://github.com/reaandrew/politest","last_synced_at":"2025-11-08T21:02:52.985Z","repository":{"id":319404192,"uuid":"1078217268","full_name":"reaandrew/politest","owner":"reaandrew","description":"A Go CLI tool for testing AWS IAM policies before deployment using scenario-based YAML configurations and the SimulateCustomPolicy API","archived":false,"fork":false,"pushed_at":"2025-11-05T11:52:58.000Z","size":1959,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-05T13:21:49.505Z","etag":null,"topics":["aws","aws-iam","ci-cd","cli-tool","devops","go","golang","iam","policy-testing","policy-validation","scp","security","yaml"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/reaandrew.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-17T11:41:14.000Z","updated_at":"2025-11-05T11:53:00.000Z","dependencies_parsed_at":"2025-10-19T02:28:51.092Z","dependency_job_id":"c276cf07-423b-4d88-8b6f-7d571e2f98fd","html_url":"https://github.com/reaandrew/politest","commit_stats":null,"previous_names":["reaandrew/politest"],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/reaandrew/politest","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reaandrew%2Fpolitest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reaandrew%2Fpolitest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reaandrew%2Fpolitest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reaandrew%2Fpolitest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/reaandrew","download_url":"https://codeload.github.com/reaandrew/politest/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reaandrew%2Fpolitest/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":283418035,"owners_count":26832617,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-08T02:00:06.281Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","aws-iam","ci-cd","cli-tool","devops","go","golang","iam","policy-testing","policy-validation","scp","security","yaml"],"created_at":"2025-11-08T21:01:24.827Z","updated_at":"2025-11-08T21:02:52.975Z","avatar_url":"https://github.com/reaandrew.png","language":"Go","readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"images/logo.png\" alt=\"politest logo\" width=\"200\"/\u003e\n\u003c/p\u003e\n\n# politest\n\n## Build \u0026 Quality\n\n[![CI](https://github.com/reaandrew/politest/actions/workflows/ci.yml/badge.svg)](https://github.com/reaandrew/politest/actions/workflows/ci.yml)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=reaandrew_politest\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=reaandrew_politest)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=reaandrew_politest\u0026metric=coverage)](https://sonarcloud.io/summary/new_code?id=reaandrew_politest)\n[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=reaandrew_politest\u0026metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=reaandrew_politest)\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=reaandrew_politest\u0026metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=reaandrew_politest)\n[![Go Report Card](https://goreportcard.com/badge/github.com/reaandrew/politest)](https://goreportcard.com/report/github.com/reaandrew/politest)\n[![Dependabot](https://img.shields.io/badge/Dependabot-Enabled-025e8c?logo=dependabot)](https://github.com/reaandrew/politest/network/updates)\n\n## Security\n\n[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/reaandrew/politest/badge)](https://scorecard.dev/viewer/?uri=github.com/reaandrew/politest)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=reaandrew_politest\u0026metric=security_rating)](https://sonarcloud.io/summary/new_code?id=reaandrew_politest)\n[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=reaandrew_politest\u0026metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=reaandrew_politest)\n[![CodeQL](https://github.com/reaandrew/politest/actions/workflows/codeql.yml/badge.svg)](https://github.com/reaandrew/politest/actions/workflows/codeql.yml)\n[![Semgrep](https://img.shields.io/badge/Semgrep-Enabled-blueviolet?logo=semgrep)](https://semgrep.dev/)\n[![GitGuardian](https://img.shields.io/badge/GitGuardian-Monitored-blue?logo=git)](https://www.gitguardian.com/)\n\n## Supply Chain Security\n\n[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](https://slsa.dev/)\n[![Dependency Review](https://github.com/reaandrew/politest/actions/workflows/dependency-review.yml/badge.svg)](https://github.com/reaandrew/politest/actions/workflows/dependency-review.yml)\n[![StepSecurity](https://img.shields.io/badge/StepSecurity-Enabled-success?logo=security)](https://www.stepsecurity.io/)\n\nAll release binaries are built with [SLSA Level 3](https://slsa.dev/) provenance attestations, providing:\n\n- **Verifiable Builds** - Cryptographic proof that binaries were built from this repository's source code\n- **Tamper Protection** - Detect if binaries were modified after build\n- **Build Transparency** - Complete audit trail showing exact source, build time, and build process\n- **Supply Chain Defense** - Protection against compromised build servers and malicious insider modifications\n\nUsers can verify binary authenticity using [slsa-verifier](https://github.com/slsa-framework/slsa-verifier):\n\n```bash\nslsa-verifier verify-artifact politest-linux-amd64 \\\n  --provenance-path politest-linux-amd64.intoto.jsonl \\\n  --source-uri github.com/reaandrew/politest\n```\n\n## Project Info\n\n[![Go Version](https://img.shields.io/github/go-mod/go-version/reaandrew/politest)](https://go.dev/)\n[![Release](https://img.shields.io/github/v/release/reaandrew/politest)](https://github.com/reaandrew/politest/releases)\n[![License](https://img.shields.io/github/license/reaandrew/politest)](LICENSE)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)\n\nA single-binary Go tool for testing AWS IAM policies using scenario-based YAML configurations.\n\n## Features\n\n- **YAML-based scenarios**\n\n  - Inheritance via `extends:`\n\n- **Multiple variable formats**\n\n  - `{{.VAR}}`, `${VAR}`, `$VAR`, `\u003cVAR\u003e` syntax support\n\n- **Policy templates**\n\n  - Use `policy_template` for policies with variables\n  - Or `policy_json` for pre-rendered JSON policies\n  - Automatically strips non-IAM fields (metadata, comments)\n  - Optional --strict-policy flag enforces schema compliance\n\n- **Test collection format**\n\n  - `tests` array with named test cases\n  - Test-level context keys override scenario-level values\n  - Filter specific tests with --test flag\n\n- **SCP/RCP merging**\n\n  - From multiple files/globs into permissions boundaries\n\n- **AWS IAM SimulateCustomPolicy integration**\n\n  - Test policies before deployment\n  - Pretty-printed JSON for readable error messages with line numbers\n\n- **Expectation assertions**\n\n  - For CI/CD integration\n\n- **Clean table output**\n\n  - With optional raw JSON export\n\n- **Enhanced failure diagnostics**\n  - Shows matched statement source files with line numbers\n  - Displays full statement JSON from source for failed tests\n  - Optional --show-matched-success flag for passing tests\n\n## ⚠️ Understanding What politest Tests\n\n**politest is a pre-deployment validation tool that helps you catch IAM policy issues early, but it is NOT a replacement for integration testing in real AWS environments.**\n\n### What politest Does\n\npolitest uses AWS's `SimulateCustomPolicy` API to evaluate policies **before deployment**. This provides:\n\n✅ **Fast feedback loop**\n\n- Test policy changes in seconds without deploying\n\n✅ **Blended testing**\n\n- See how identity policies interact with SCPs/RCPs\n\n✅ **Fail fast**\n\n- Catch obvious misconfigurations early in development\n\n✅ **CI/CD integration**\n\n- Automated policy validation on every commit\n\n### Important Limitations\n\n⚠️ **politest \"bends the rules\" for testing convenience:**\n\n- **SCPs/RCPs in SimulateCustomPolicy**\n\n  - The API wasn't designed for testing organizational policies alongside identity policies\n  - politest uses the `PermissionsBoundaryPolicyInputList` parameter to simulate SCP/RCP behavior\n  - This **approximates** real-world behavior but may not be 100% accurate\n\n- **Simulation vs Reality**\n\n  - `SimulateCustomPolicy` provides a **best-effort simulation**\n  - Some complex conditions, resource policy interactions, and edge cases may behave differently in production\n\n- **Missing Context**\n  - Real AWS environments have additional factors not fully captured in simulation\n  - Resource ownership, trust policies, session policies, permission boundaries\n\n### What You Still Need\n\n✅ **Integration testing in actual AWS accounts**\n\n- Deploy policies to dev/staging and test real resource access\n\n✅ **Production validation**\n\n- Verify permissions work as expected with real workloads\n\n✅ **Security reviews**\n\n- Have security teams review policies before production deployment\n\n**Remember:** politest helps you **fail faster during development** by catching obvious mistakes before deployment. Use it as **unit tests for IAM policies** - essential for development velocity, but always validate with real integration tests in actual AWS environments.\n\n## Installation\n\n```bash\n# Build the binary\ngo build -o politest\n\n# Or run directly\ngo run . --scenario path/to/scenario.yml\n```\n\n## Quick Start\n\n### 1. Create a base scenario (`scenarios/_common.yml`)\n\n```yaml\nvars:\n  account_id: \"123456789012\"\n  region: \"us-east-1\"\n\nscp_paths:\n  - \"../scp/010-base.json\"\n  - \"../scp/020-guardrails.json\"\n\ncontext:\n  - ContextKeyName: \"aws:RequestedRegion\"\n    ContextKeyValues: [\"{{ .region }}\"]\n    ContextKeyType: \"string\"\n```\n\n### 2. Create a specific test scenario (`scenarios/athena_test.yml`)\n\n```yaml\nextends: \"_common.yml\"\n\nvars:\n  workgroup: \"primary\"\n\npolicy_template: \"../policies/athena_policy.json.tmpl\"\n\ntests:\n  - name: \"BatchGetNamedQuery should be allowed\"\n    action: \"athena:BatchGetNamedQuery\"\n    resource: \"arn:aws:athena:{{ .region }}:{{ .account_id }}:workgroup/{{ .workgroup }}\"\n    context:\n      - ContextKeyName: \"aws:CalledVia\"\n        ContextKeyValues: [\"athena.amazonaws.com\"]\n        ContextKeyType: \"stringList\"\n    expect: \"allowed\"\n\n  - name: \"GetQueryExecution should be allowed\"\n    action: \"athena:GetQueryExecution\"\n    resource: \"arn:aws:athena:{{ .region }}:{{ .account_id }}:workgroup/{{ .workgroup }}\"\n    context:\n      - ContextKeyName: \"aws:CalledVia\"\n        ContextKeyValues: [\"athena.amazonaws.com\"]\n        ContextKeyType: \"stringList\"\n    expect: \"allowed\"\n```\n\n### 3. Create a policy template (`policies/athena_policy.json.tmpl`)\n\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"AthenaAccess\",\n      \"Effect\": \"Allow\",\n      \"Action\": [\"athena:BatchGetNamedQuery\", \"athena:GetQueryExecution\"],\n      \"Resource\": \"arn:aws:athena:{{ .region }}:{{ .account_id }}:workgroup/{{ .workgroup }}\",\n      \"Condition\": {\n        \"StringEquals\": {\n          \"aws:RequestedRegion\": \"{{ .region }}\"\n        }\n      }\n    }\n  ]\n}\n```\n\n### 4. Create SCP files (optional, `scp/010-base.json`)\n\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": \"*\",\n      \"Resource\": \"*\"\n    }\n  ]\n}\n```\n\n### 5. Run the test\n\n```bash\n# Run with expectations (fails on mismatch)\n./politest --scenario scenarios/athena_test.yml\n\n# Run without assertions\n./politest --scenario scenarios/athena_test.yml --no-assert\n\n# Save raw AWS response\n./politest --scenario scenarios/athena_test.yml --save /tmp/response.json\n```\n\n## Usage\n\n```bash\npolitest [flags]\n\nFlags:\n  --scenario string         Path to scenario YAML (required)\n  --save string             Path to save raw JSON response (optional)\n  --no-assert               Do not fail on expectation mismatches (optional)\n  --no-warn                 Suppress SCP/RCP simulation approximation warning (optional)\n  --test string             Comma-separated list of test names to run (runs all if empty)\n  --show-matched-success    Show matched statement details for passing tests (optional)\n  --strict-policy           Fail if policies contain non-IAM schema fields (optional)\n```\n\n## Scenario Configuration\n\n### Required Fields\n\n**Policy** - One of:\n\n- `policy_template: \"path/to/policy.json.tpl\"`\n  - Path to a policy file with template variables\n  - Supports `{{.VAR}}`, `${VAR}`, `$VAR`, and `\u003cVAR\u003e` variable formats\n  - Variables are substituted before policy is used\n- `policy_json: \"path/to/policy.json\"`\n  - Path to a plain JSON policy file\n  - Use when policy has no variables or is already rendered\n\n**Tests** - Required:\n\n- `tests: [{action, resource, expect}]`\n  - Array of test cases with individual settings\n  - Each test can have its own action, resources, context, and expectations\n  - Supports both `action` (single) and `actions` (array expansion)\n  - Supports both `resource` (single) and `resources` (array)\n  - See examples below for detailed syntax\n\n### Optional Fields\n\n- `extends: \"parent.yml\"`\n  - Path to parent scenario (supports inheritance)\n- `vars_file: \"vars.yml\"`\n  - Path to YAML file with variables\n- `vars: {key: value}`\n  - Inline variables (overrides vars_file)\n- `scp_paths: [\"scp/*.json\"]`\n  - List of SCP file paths or globs to merge\n- `context: [{ContextKeyName, ContextKeyValues, ContextKeyType}]`\n  - List of context entries for conditions\n\n### Action and Resource Fields\n\n**Single vs Array in Tests:**\n\n- `action: \"s3:GetObject\"`\n  - Single action to test\n- `actions: [\"s3:GetObject\", \"s3:PutObject\", \"s3:ListBucket\"]`\n  - Multiple actions - expands into separate tests (one test per action)\n  - All other test properties (resource, context, expect) are copied to each expanded test\n- `resource: \"arn:aws:s3:::bucket/*\"`\n  - Single resource ARN\n- `resources: [\"arn:aws:s3:::bucket1/*\", \"arn:aws:s3:::bucket2/*\"]`\n  - Multiple resource ARNs tested together\n\n**Note:** You can use either `action` or `actions` (not both), and either `resource` or `resources` in each test case.\n\n### Inheritance with `extends:`\n\nChild scenarios inherit all fields from parent and can override:\n\n- **Variables**\n  - Deep-merged (child overrides parent)\n- **Other fields**\n  - Completely replaced (not merged)\n- **Relative paths**\n  - Resolved from the scenario file's directory\n\n### Variables\n\nVariables can be defined in three places (priority order):\n\n1. **Inline `vars:` in the scenario**\n2. **External `vars_file:` YAML**\n3. **Inherited from parent via `extends:`**\n\n**Variable Formats:**\n\npolitest supports multiple variable syntax formats for flexibility:\n\n- `{{.variable_name}}`\n  - Go template syntax (original format)\n- `${VARIABLE_NAME}`\n  - Shell/environment variable style with braces\n- `$VARIABLE_NAME`\n  - Environment variable style without braces\n- `\u003cVARIABLE_NAME\u003e`\n  - Custom angle bracket style\n\nAll formats are converted to Go templates internally, so you can mix and match in the same file:\n\n```yaml\nvars:\n  account_id: \"123456789012\"\n  ACCOUNT_ID: \"123456789012\" # Can use different case for different formats\n\npolicy_template: \"policy.json\" # Contains ${ACCOUNT_ID} and \u003cACCOUNT_ID\u003e\nresources:\n  - \"arn:aws:iam::{{.account_id}}:role/MyRole\" # Go template syntax\n```\n\n### Context Entries\n\n```yaml\ncontext:\n  - ContextKeyName: \"aws:RequestedRegion\"\n    ContextKeyValues: [\"us-east-1\", \"eu-west-1\"]\n    ContextKeyType: \"stringList\" # string, stringList, numeric, numericList, boolean, booleanList\n```\n\n**Supported Context Types:**\n\n- `string`\n\n  - Single string value\n\n- `stringList`\n\n  - List of strings\n\n- `numeric`\n\n  - Single numeric value\n\n- `numericList`\n\n  - List of numeric values\n\n- `boolean`\n\n  - Single boolean value\n\n- `booleanList`\n  - List of boolean values\n\n**Note:** IpAddress and IpAddressList types are not supported by the AWS SDK.\n\n**Context Override Behavior:**\n\nWhen both scenario-level and test-level context entries are defined:\n\n- Test-level context entries **override** scenario-level entries with the same `ContextKeyName`\n- Test-level context entries with **new** `ContextKeyName` values are **added** to the scenario context\n\n```yaml\n# Scenario-level context (default)\ncontext:\n  - ContextKeyName: \"aws:SourceIp\"\n    ContextKeyType: \"string\"\n    ContextKeyValues: [\"10.0.1.0/24\"]\n\ntests:\n  - name: \"Uses scenario context\"\n    action: \"s3:GetObject\"\n    resource: \"arn:aws:s3:::bucket/*\"\n    # No test-level context = uses scenario IP\n    expect: \"allowed\"\n\n  - name: \"Overrides scenario context\"\n    action: \"s3:GetObject\"\n    resource: \"arn:aws:s3:::bucket/*\"\n    context:\n      - ContextKeyName: \"aws:SourceIp\" # OVERRIDES scenario IP\n        ContextKeyType: \"string\"\n        ContextKeyValues: [\"192.168.1.1\"]\n    expect: \"implicitDeny\"\n\n  - name: \"Adds to scenario context\"\n    action: \"s3:DeleteObject\"\n    resource: \"arn:aws:s3:::bucket/*\"\n    context:\n      - ContextKeyName: \"aws:MultiFactorAuthPresent\" # ADDS MFA (keeps scenario IP)\n        ContextKeyType: \"boolean\"\n        ContextKeyValues: [\"true\"]\n    expect: \"allowed\"\n```\n\n### SCP Merging\n\nMultiple SCP files are merged into a single permissions boundary:\n\n```yaml\nscp_paths:\n  - \"../scp/010-base.json\"\n  - \"../scp/*.json\" # globs supported\n  - \"../scp/specific-restriction.json\"\n```\n\nAll statements from all files are combined into one policy document.\n\n## Output\n\n### Table Output\n\n```\nAction                         Decision  Matched (details)\n----------------------------  --------  ----------------------------------------\nathena:BatchGetNamedQuery     allowed   PolicyInputList.1\nathena:GetQueryExecution      allowed   PolicyInputList.1\n```\n\n### Exit Codes\n\n- `0`\n  - Success (all expectations met or no expectations)\n- `1`\n  - Error (invalid scenario, AWS error, etc.)\n- `2`\n  - Expectation failures (unless `--no-assert` used)\n\n## Examples\n\n### Example 1: Simple Policy Test\n\n```yaml\n# scenarios/s3_read.yml\npolicy_json: \"../policies/s3_read.json\"\n\ntests:\n  - name: \"GetObject should be allowed\"\n    action: \"s3:GetObject\"\n    resource: \"arn:aws:s3:::my-bucket/*\"\n    expect: \"allowed\"\n\n  - name: \"ListBucket should be denied\"\n    action: \"s3:ListBucket\"\n    resource: \"arn:aws:s3:::my-bucket/*\"\n    expect: \"implicitDeny\"\n```\n\n### Example 2: Template with Variables\n\n```yaml\n# scenarios/dynamodb_test.yml\nvars:\n  table_name: \"users-table\"\n  region: \"us-west-2\"\n  account_id: \"123456789012\"\n\npolicy_template: \"../policies/dynamodb.json.tmpl\"\n\ntests:\n  - name: \"DynamoDB access\"\n    actions: # Using actions array - expands to multiple tests\n      - \"dynamodb:GetItem\"\n      - \"dynamodb:PutItem\"\n    resource: \"arn:aws:dynamodb:{{ .region }}:{{ .account_id }}:table/{{ .table_name }}\"\n    expect: \"allowed\"\n```\n\n### Example 3: With SCPs and Context\n\n```yaml\n# scenarios/ec2_restricted.yml\nextends: \"_common.yml\"\n\npolicy_template: \"../policies/ec2.json.tmpl\"\n\nscp_paths:\n  - \"../scp/region-restriction.json\"\n  - \"../scp/instance-type-restriction.json\"\n\ntests:\n  - name: \"RunInstances should be denied by SCP\"\n    action: \"ec2:RunInstances\"\n    resource: \"*\"\n    context:\n      - ContextKeyName: \"aws:RequestedRegion\"\n        ContextKeyValues: [\"us-east-1\"]\n        ContextKeyType: \"string\"\n      - ContextKeyName: \"ec2:InstanceType\"\n        ContextKeyValues: [\"t3.micro\"]\n        ContextKeyType: \"string\"\n    expect: \"explicitDeny\"\n```\n\n## AWS Credentials\n\nThe tool uses the AWS SDK v2 default credential chain:\n\n- **Environment variables**\n  - `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`\n- **Shared credentials file**\n  - `~/.aws/credentials`\n- **IAM role**\n  - When running on EC2/ECS/Lambda\n\nRequired IAM permission: `iam:SimulateCustomPolicy`\n\n## Development\n\n### Running Tests\n\n```bash\n# Run unit tests (if any exist)\ngo test -race -coverprofile=coverage.out -covermode=atomic ./...\n\n# Run integration tests (requires AWS credentials)\ncd test \u0026\u0026 bash run-tests.sh\n```\n\nIntegration tests are located in the `test/` directory and cover:\n\n- Policy-only allow scenarios\n- Policy allows, SCP denies\n- Policy allows, RCP denies\n- Multiple SCPs merging\n- Explicit deny in policy\n- Template variables\n- Context conditions\n\n### Pre-commit Hooks\n\nThis project uses [lefthook](https://github.com/evilmartians/lefthook) for Git hooks:\n\n```bash\n# Install hooks\nlefthook install\n\n# Hooks run automatically on commit:\n# - gofmt -w (auto-format)\n# - go vet (static analysis)\n# - staticcheck (linting)\n# - go test (unit tests)\n# - go mod tidy (dependency cleanup)\n# - trailing whitespace check\n```\n\n### CI/CD\n\nThe GitHub Actions workflow (`.github/workflows/ci.yml`) runs:\n\n- Linting and testing\n- Dependency scanning (Trivy)\n- Secret detection (GitGuardian)\n- Code quality analysis (SonarCloud)\n- Security scanning (Semgrep)\n- Cross-platform builds\n- Integration tests against real AWS API\n- Semantic versioning releases\n- SLSA Level 3 provenance generation for all release binaries\n\n## Tips\n\n1. **Organize scenarios**\n\n   - Use `_common.yml` for shared config, extend in specific tests\n\n2. **Use templates**\n\n   - Policy templates with variables make tests reusable across accounts/regions\n\n3. **CI Integration**\n\n   - Use `expect:` assertions and check exit codes\n\n4. **Debug**\n\n   - Use `--save` to inspect raw AWS responses and examine `MatchedStatements`\n\n5. **Glob SCPs**\n\n   - Use wildcards to merge multiple SCP files automatically\n\n6. **Case-insensitive decisions**\n   - Expected decisions are compared case-insensitively (e.g., \"allowed\" matches \"Allowed\")\n\n## Project Structure Example\n\n```\n.\n├── politest              # binary\n├── scenarios/\n│   ├── _common.yml       # base configuration\n│   ├── athena_test.yml\n│   ├── s3_test.yml\n│   └── ec2_test.yml\n├── policies/\n│   ├── athena_policy.json.tmpl\n│   ├── s3_policy.json\n│   └── ec2_policy.json.tmpl\n└── scp/\n    ├── 010-base.json\n    ├── 020-region-restriction.json\n    └── 030-service-restriction.json\n```\n\n## License\n\nMIT\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freaandrew%2Fpolitest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Freaandrew%2Fpolitest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freaandrew%2Fpolitest/lists"}