@k9securityio/k9-cdk
Version:
Provision strong AWS security policies easily using the AWS CDK.
114 lines (83 loc) • 6.9 kB
Markdown
# AGENTS.md
This file provides guidance to coding agents such as Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
k9-cdk (`@k9securityio/k9-cdk`) is an AWS CDK construct library that generates least-privilege AWS resource policies using the k9 Security access capability model. It maps high-level access capabilities (ADMINISTER_RESOURCE, READ_CONFIG, READ_DATA, WRITE_DATA, DELETE_DATA) to specific IAM actions for each supported AWS service.
Supported services: S3, KMS, DynamoDB, SQS, EventBridge.
## Build & Test Commands
This project uses **projen** (configured in `.projenrc.js`) to manage build tooling. Do not edit `package.json`, `tsconfig.json`, or `.eslintrc.json` directly — edit `.projenrc.js` and run `npx projen`.
```bash
# Full build (compile + lint + test)
npx projen build
# Compile TypeScript only
npx projen compile
# Run all tests
npx projen test
# Run tests in watch mode
npx projen test:watch
# Run a single test file
npx jest test/k9policy.test.ts
# Run a single test by name
npx jest -t "test name pattern"
# Lint
npx projen eslint
# Update snapshots after intentional changes
npx jest -u
```
The `Makefile` provides:
* `make init` — install dependencies via `yarn install` (requires yarn installed globally)
* `make build` — same as `npx projen build`
* `make examples` — builds the library then builds and synthesizes the examples app
* `make all` — init + build + deploy
The `examples/Makefile` provides `make examples` which compiles the parent library, installs example dependencies, builds, and synthesizes.
## Architecture
### Core Module: `src/k9policy.ts`
The foundation of the library. Contains:
- `AccessCapability` enum — the five k9 access capabilities
- `IAccessSpec` interface — maps principal ARNs to capabilities
- `K9PolicyFactory` class — generates IAM Allow/Deny statements from access specs
- `resources/capability_summary.json` — maps each AWS service's IAM actions to access capabilities
### Service Modules: `src/s3.ts`, `src/kms.ts`, `src/dynamodb.ts`, `src/sqs.ts`, `src/events.ts`
Each adapts `K9PolicyFactory` to its service's CDK API:
- **S3/SQS/EventBridge**: Use `grantAccessViaResourcePolicy()` — adds statements to existing resource policy via `addToResourcePolicy()`
- **KMS/DynamoDB**: Use `makeResourcePolicy()` — returns a `PolicyDocument` to pass as a construct prop
- **EventBridge**: Supports only ADMINISTER_RESOURCE, READ_CONFIG, WRITE_DATA (not READ_DATA or DELETE_DATA). Uses PascalCase SIDs (like DynamoDB).
### Utility: `src/aws-iam-utils.ts`
`getAllowedPrincipalArns()` extracts existing allowed principals from a PolicyDocument, used when merging with existing policies.
### Policy Generation Pattern
Every generated policy follows the same structure:
1. Allow statements per capability (principals × actions for that capability)
2. Service-specific enforcement (e.g., DenyInsecureCommunications, DenyUnencryptedStorage for S3)
3. DenyEveryoneElse statement — explicit deny for principals not in allow list
4. DenyUntrustedOrgs statement — explicit deny for principals outside specified orgs (only when `restrictToPrincipalOrgIDs` is set on any access spec)
### Key Design Details
- S3 module preserves existing bucket policy statements (important for CDK features like `autoDeleteObjects`)
- SQS has a 7-action-per-statement limit; the code partitions large allow statements into numbered sub-statements
- KMS supports two modes: `trustAccountIdentities` (true/false) controlling whether account root gets full key access
- DenyEveryoneElse uses `aws:PrincipalArn` conditions (SQS/DynamoDB) to avoid blocking AWS service principals
- When wildcard `*` principal is used with `restrictToPrincipalOrgIDs`, DenyEveryoneElse is skipped (cannot meaningfully except `*` from a deny). Currently only EventBridge implements this check.
- `restrictToPrincipalOrgIDs` on `IAccessSpec` adds `StringEquals: aws:PrincipalOrgID` to Allow statements and generates a `DenyUntrustedOrgs` deny statement with `StringNotEquals: aws:PrincipalOrgID` + `Bool: aws:PrincipalIsAWSService=false`
- DenyUntrustedOrgs is generated by `K9PolicyFactory._makeDenyUntrustedOrgsStatement()` (internal method) and is independent of DenyEveryoneElse (e.g., KMS generates it even when `trustAccountIdentities: false` omits DenyEveryoneElse)
## Testing Patterns
- Tests import from `../lib` (compiled output), not `../src`
- `test/k9.test.ts` — integration tests covering S3, KMS, DynamoDB via the public API
- `test/k9policy.test.ts` — unit tests for K9PolicyFactory internals
- `test/sqs.test.ts` — SQS-specific tests (statement partitioning, etc.)
- `test/events.test.ts` — EventBridge-specific tests (org-scoping, wildcard principals, multi-org)
- `test/helpers.ts` — `stringifyStatement()`, `stringifyPolicy()` for readable policy output in tests
- Snapshot tests via `SynthUtils.toCloudFormation(stack)` — update with `npx jest -u`
- CDK assertions via `@aws-cdk/assert` (`haveResource`, `expectCDK`)
## Important Conventions
- Default release branch is `v2-main` (not `main`)
- JSII compatibility required — this library is compiled with jsii for cross-language support. Avoid TypeScript features not supported by jsii (mapped types, conditional types, `Partial<T>`, `Map<K,V>` as public params, union types like `T | null`). For internal methods that need these types, use `/** @internal */` and prefix with `_`.
- ESLint import ordering: warns on misordered imports but does not enforce alphabetical sorting within groups
- Example CDK app in `bin/k9-cdk.ts` demonstrates library usage and is deployed for integration testing via `cdk deploy`
- Examples app in `examples/example.ts` demonstrates all service modules; depends on the local build via `"file:.."` in `examples/package.json`. The examples directory must NOT have its own `aws-cdk-lib` dependency — it resolves from the parent's `node_modules` to avoid duplicate type conflicts.
## Node.js and Toolchain
- Node.js version is pinned to v22 LTS (`lts/jod`) via `.nvmrc` (local) and `workflowNodeVersion` in `.projenrc.js` (CI)
- jsii version is `~5.9.0` — the current supported release line. jsii 5.9 officially supports Node ^20 and ^22.
- Package manager is **yarn** (v1/Classic). `yarn.lock` is tracked in git. Do not use `npm install` in the project root — it creates a conflicting `package-lock.json`.
- `make clean` removes `node_modules`; run `make init` (which requires globally-installed yarn) to bootstrap.
## Releases
- Releases are automated via the `release` GitHub Actions workflow, triggered on push to `v2-main`
- Version bumps are determined automatically by projen/standard-version from conventional commit prefixes (`feat:` → minor, `fix:`/`build:` → patch)
- The workflow publishes to npm (requires `NPM_TOKEN` secret) and creates a GitHub Release