Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

**CMK (Customer-Managed-Keys)** is a Go microservice for the Key Management Service layer of OpenKCM. It manages cryptographic keys, certificates, workflows, tenants, and async tasks across multiple components.

Module: `github.com/openkcm/cmk`
Go: 1.25.6 (toolchain 1.26.1)

## Build & Development Commands

```bash
# Run unit tests (uses gotestsum, clears cache, produces coverage)
make test

# Run integration tests (spins up containers for PostgreSQL, Redis, RabbitMQ)
make integration_test

# Run linter (golangci-lint v2 with --fix)
make lint

# Format all Go files (gofmt + goimports + golines + gci)
make go-imports

# Format only changed files
make go-imports-changed

# Generate OpenAPI server code from spec
make codegen api=cmk

# Run the API server locally
make run

# Run benchmarks
make benchmark

# Full local K3d cluster setup (PostgreSQL, Redis, RabbitMQ, OTEL, CMK)
make start-cmk

# Build dev Docker image
make docker-dev-build

# Generate signing keys for local testing
make generate-signing-keys

# Create empty secret files from blueprints
make create-empty-secrets

# Build tenant-manager CLI locally
make build-tenant-cli

# Provision test tenants locally
make provision-tenants-locally

# Tidy Go modules
make tidy
```
Comment on lines +14 to +59
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find CLAUDE.md and Makefile(s)
CLAUDE_FILE="$(fd -HI '^CLAUDE\.md$' -t f | head -n1)"
if [[ -z "${CLAUDE_FILE:-}" ]]; then
  echo "CLAUDE.md not found"; exit 1
fi

echo "Using CLAUDE file: $CLAUDE_FILE"
echo

# Extract make targets referenced in the command section
echo "Documented make targets:"
TARGETS=$(awk '
  /## Build & Development Commands/{inblk=1}
  inblk && /^```bash/{code=1; next}
  code && /^```/{exit}
  code && $1=="make"{print $2}
' "$CLAUDE_FILE" | sort -u)

printf '%s\n' "$TARGETS"
echo

# Collect declared targets from all Makefiles
echo "Discovered Makefile targets:"
MAKEFILES="$(fd -HI '(^|/)Makefile$' -t f)"
if [[ -z "${MAKEFILES:-}" ]]; then
  echo "No Makefile found"; exit 1
fi

DECLARED=$(for mf in $MAKEFILES; do
  awk -F: '
    /^[A-Za-z0-9_.-]+:[^=]/ {
      if ($1 !~ /^\./) print $1
    }
  ' "$mf"
done | sort -u)

printf '%s\n' "$DECLARED"
echo

echo "Missing documented targets:"
comm -23 <(printf '%s\n' "$TARGETS" | sort -u) <(printf '%s\n' "$DECLARED" | sort -u) || true

Repository: openkcm/cmk

Length of output: 694


🏁 Script executed:

fd -HI '^Makefile$' -t f

Repository: openkcm/cmk

Length of output: 63


🏁 Script executed:

cat -n Makefile | head -100

Repository: openkcm/cmk

Length of output: 4053


Remove or implement the 6 undocumented make targets before merging.

This documentation block references targets that do not exist in the Makefile: build-tenant-cli, create-empty-secrets, generate-signing-keys, go-imports-changed, provision-tenants-locally, and start-cmk. Remove these entries or add the targets to the Makefile. Mismatches will break onboarding and CI debugging workflows.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLAUDE.md` around lines 14 - 59, The README/CLAUDE.md lists six Makefile
targets that don't exist: build-tenant-cli, create-empty-secrets,
generate-signing-keys, go-imports-changed, provision-tenants-locally, and
start-cmk; update the doc to match the Makefile by either removing these entries
or adding corresponding rules to the Makefile (implement targets with
appropriate recipes and comments), and ensure targets like go-imports and
codegen (api=cmk) remain accurate; verify by running make <target> for each
listed target and updating CLAUDE.md to reflect the final set of supported
targets.


### Running a single test

```bash
go test -run TestFunctionName ./internal/path/to/package/...
```

## Architecture

### Service Components (cmd/)

| Binary | Purpose |
|--------|---------|
| `api-server` | HTTP REST API server — the main service entry point |
| `task-scheduler` | Cron-based periodic task scheduling (Redis/Asynq) |
| `task-worker` | Async task processor (cert rotation, system refresh, HYOK sync, etc.) |
| `event-reconciler` | Processes events from AMQP message brokers via Orbital |
| `tenant-manager` | Tenant lifecycle management (listens to AMQP for tenant events) |
| `db-migrator` | Database schema and data migrations (Goose) |
| `tenant-manager-cli` | CLI for tenant CRUD operations |
| `task-cli` | CLI for async task management |

### Internal Packages (internal/)

| Package | Purpose |
|---------|---------|
| `api/` | Generated OpenAPI server code (from `apis/cmk/cmk-ui.yaml`) |
| `apierrors/` | Error constants and error-to-HTTP-status mappings per operation |
| `async/` | Async task framework (Asynq-based) with task handlers |
| `auditor/` | Audit logging to OTLP endpoint |
| `authz/` | RBAC authorization — policies for API endpoints and repos |
| `clients/` | gRPC clients for external services |
| `config/` | Configuration loading (YAML + env overrides) |
| `controllers/` | HTTP request handlers mapped to OpenAPI operations |
| `daemon/` | Server lifecycle, HTTP server setup, graceful shutdown |
| `db/` | Database connection, multitenancy setup, read replicas |
| `errs/` | Error types and mapping logic |
| `event-processor/` | Event factory, handlers, and reconciliation pipeline |
| `handlers/` | HTTP error response handlers |
| `log/` | Context-based logging via slogctx |
| `manager/` | Business logic managers (Key, Certificate, Workflow, System, Tenant, User, Group, Pool, etc.) |
| `middleware/` | HTTP middleware chain: tracing, request ID, multitenancy, panic recovery, logging, OAPI validation, client data, authz |
| `model/` | Data models and domain types |
| `notifier/` | Notification service client |
| `operator/` | Operator management |
| `pluginregistry/` | Plugin catalog — loads/manages keystore, cert-issuer, notification, identity plugins |
| `plugins/` | Plugin implementations |
| `repo/` | Data access layer (repository pattern over GORM) |
| `testutils/` | Test helpers: `NewTestDB`, `NewAPIServer`, `MakeHTTPRequest`, model factories |
| `workflow/` | Workflow state machine (FSM) for approval-based operations |

### Key Infrastructure

- **Database**: PostgreSQL with GORM, multitenancy via per-tenant schemas (`bartventer/gorm-multitenancy`), read replicas via `dbresolver`
- **Task Queue**: Redis-backed via Asynq — scheduler enqueues, worker processes
- **Events**: AMQP via Orbital for event distribution
- **Plugins**: Go plugin architecture via `openkcm/plugin-sdk` (keystores, cert issuers, notifications, identity management)
- **Observability**: OpenTelemetry (traces, metrics, logs), Prometheus metrics
- **Auth**: Signed client data headers (RSA), JWT validation, RBAC policy engine
- **API**: OpenAPI 3.0 spec → `oapi-codegen` generated server with validation middleware

## Database Migrations

Located in `migrations/` with shared (public schema) and tenant (per-tenant schema) directories. Each has:
- `schema/` — SQL migrations (run first, block cluster startup)
- `data/` — Go-based migrations using Goose `AddMigrationContext` (run in parallel, non-blocking)

Destructive changes use the **Expand & Contract** pattern across two releases.

## Error Mapping Pattern

Errors are mapped per operation in `internal/apierrors/`. Each `ErrorMap` pairs internal error(s) with an HTTP status and error code. The system matches by most specific error chain. To add a new error:
1. Define error constant in `apierrors`
2. Add `ErrorMap` entry with matching error(s) and `DetailedError` (code, message, status)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note, for quite some commits now it's no longer DetailedError neither ErroMap. Now there is a generic ExposedErrors which expects a type. For API APIError where we set an internal error chain to match and exposed error. Might also be useful to provide the AI with the matching algorithm found on the generic implementation


## Testing

- **Unit tests**: Use helpers from `internal/testutils/` — `NewTestDB`, `NewAPIServer`, `MakeHTTPRequest`, model factories (`New<ModelType>`)
- **Integration tests**: In `test/integration/` using testcontainers (PostgreSQL, Redis, RabbitMQ)
- **DB migration tests**: In `test/db-migration/`
- **Security tests**: In `test/security/`

## Conventions

- **Commits**: Conventional Commits (`feat:`, `fix:`, `docs:`, `chore:`, `ci:`, `build:`). Release-please automates versioning.
- **Import ordering** (gci): standard → default → `prefix(github.com/openkcm/cmk)` → blank → dot → alias → localmodule
- **Linting**: golangci-lint v2 with `default: all` minus disabled linters. JSON tags use `goCamel` case. Exhaustive switch requires `default` to count as exhaustive.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Config in .golangcilint

- **Logging**: Context-based via slogctx. Static info via values.yaml labels. Dynamic info injected into logger context. PII fields are masked.
- **Configuration**: `config.yaml` with environment variable overrides. Secrets loaded from files in `env/secret/`.
Loading