You are a coding agent working on the OpenStack Keystone Rust implementation.
- Use
cargo test -p <crate>for unit tests. - Use
cargo test -p test_integrationfor integration tests,cargo nextest -p test_integration --profile raftfor integration tests with raft drivers enabled. - Always specify crate:
cargo <cmd> -p <crate_name>when targeting specific crates - Follow Domain-Driven Design: domains (identity, catalog, role, etc.) are in separate crates
- Policy files follow convention:
policy/<domain>/<resource>/<action>.rego - When checking code, always run
cargo check --message-format=shortor pipe the output to only show errors, e.g.,cargo check 2>&1 | grep -i "error".
crates/keystone/: Main service binary and API handlers (src/api/vX/)crates/core/: Domain providers and backend trait definitionscrates/core-types/: Shared data structures across workspacecrates/api-types/: API request/response models and conversionscrates/*-sql/: Sea-ORM persistence drivers (e.g.,identity-sql,catalog-sql)crates/*-raft/: OpenRaft distributed storage driverscrates/storage/: Distributed storage implementation (OpenRaft)crates/config/: Configuration parsingcrates/webauthn/: WebAuthn/Passkey support extensionpolicy/: OPA Rego policy filesdoc/src/adr/: Architecture Decision Records
- Build:
cargo build -p <crate>orcargo build(workspace) - Unit tests:
cargo test -p <crate> - Integration tests (raft):
cargo nextest run -p test_integration --profile raft - API tests:
cargo nextest run --profile api -p test_api(requires SPIRE, OPA) - Format:
cargo fmt - Lint:
cargo clippy -p <crate> --fix --allow-dirty
- Forbidden:
unwrap(),expect(),println!,unsafe(workspace lints) - Required: Apache-2.0 license header on every source file
- Error handling: Use
thiserrorfor error types, propagate withResult<T, E> - Async: Heavily async codebase, built on
tokio - Pass by reference when receiver doesn't need ownership
Backend traits in crates/core/src/backend.rs follow CRUD naming:
- Create:
create_<resource> - Read single:
get_<resource> - Read multiple:
list_<resources> - Update:
update_<resource> - Delete:
delete_<resource>
- One HTTP handler per module
- Unit tests live in same module's
testssubmodule - CRUD handlers require >=3 tests: valid auth + positive/negative policy, invalid auth
- Policy enforcement via OPA Rego in
policy/directory - If a module's
testssubmodule grows past ~2000 lines, split it into<module>/tests.rsand declare it with#[cfg(test)] #[path = "<module>/tests.rs"] mod tests;in the parent file. Keeps large test suites from inflating the code file every read/edit touches (e.g.crates/core/src/auth.rs).
cargo nextest run --profile api -p test_apispawns SPIRE + OPA + a realkeystoneserver viatools/start-api.shas a nextest setup script, and by default leaves them running after the run finishes (no auto-teardown). Re-running without cleanup can pile up stale daemons across sessions; checkps aux | grep -E 'tmp/nextest|spire-ci-test-harness'and kill leftovers (or runtools/teardown-api.sh) before starting a fresh run if a prior run was interrupted/killed.- Never pipe the run through
tail/tail -N(e.g.cargo nextest run ... | tail -200).tailwithout-fbuffers until EOF, and the setup script's long-running daemons (keystone/spire/opa) inherit the pipe's write fd, so it never closes even after nextest itself exits — the whole invocation looks permanently hung even though the test run actually finished. Redirect to a file instead (cargo nextest run ... > /tmp/out.log 2>&1 &) and read the file. - The
defaultdomain is seeded directly in the DB at bootstrap, not created throughPOST /v3/domains.Oauth2KeyHook(ADR 0026) only provisions a domain's OAuth2 signing keys on the domain-creation event, sodefaultnever gets keys and/v4/oauth2/default/jwksand/v4/oauth2/default/.well-known/openid-configuration404 forever. Tests needing real OAuth2 signing/jwks/discovery must create a fresh domain via the API first (key provisioning is async — poll before asserting). AuthenticationResult.principal.identityfromauthenticate_by_password/password auth is alwaysIdentityInfo::User, neverIdentityInfo::Principal(that variant is for SPIFFE/workload identities only). Handler code and its unit-test mocks must agree on this — a mismatch here doesn't fail to compile, it 500s silently at runtime with no log line (an unloggedErr(_) => error_page(500, ...)branch), so crate-level unit tests with a mock built the same wrong way won't catch it. Only a live-server request through the real password-auth path surfaces it.
tools/run-loadtest-local.sh [goose args...]sets up postgres (docker), SPIRE, an embedded-OPA rustkeystone, bootstraps the admin user, seeds data, buildstests/loadtest, and runs it againsthttp://localhost:8080. Extra args are forwarded toload_test, e.g.:tools/run-loadtest-local.sh --users 20 --hatch-rate 4 --run-time 30s --report-file reports/run.md.tools/teardown-loadtest.shtears everything down; the runner calls it itself at the start of every run, so a stale environment from an interrupted run is cleaned up automatically.- Requires a working
dockerCLI (postgres container). On a podman-only host, aliasdockertopodmanand use fully-qualified image refs (docker.io/postgres:17) — an unqualifiedpostgres:17fails to resolve without configured unqualified-search registries. tests/loadtestis excluded from the main workspace (excludein rootCargo.toml) — it's a standalone crate. Build it withcd tests/loadtest && cargo build, nevercargo build -p load_testfrom the repo root.- Never pipe the runner through
tail— same root cause as thetest_apiwarning above: the script backgrounds SPIRE/OPA/keystone daemons that inherit the pipe's write fd and never let it close, so the whole thing looks hung even after the actual test finished. Run it vianohup ... > /tmp/out.log 2>&1 &and read the file, or poll withkill -0 <pid>in a loop. keystone-manage db upalone is not enough against a fresh postgres — it only applies versioned migrations for the drivers that ship them (credential, federation, k8s-auth, token-restriction, webauthn). Identity, role, assignment, resource, catalog etc. use entity-based schema creation viakeystone-manage db syncinstead. Run both,syncbeforeup.openstack_sdk's auth-cache ($HOME/.osc/, enabled by default, keyed by cloud-config hash) survives across server restarts. Since each run bootstraps a brand-new admin user, a stale cache entry would make the SDK replay a token for a user id that no longer exists in the fresh DB — surfaces as widespread, seemingly random 401/500s that have nothing to do with the actual server. The runner works around this by pointingload_testat an isolated$HOME(underSTATE_DIR, wiped every run) viaHOME=/OS_CLIENT_CONFIG_PATH=rather than touching your real~/.oscor~/.config/openstack. If you invoke the SDK some other way and hit "user cannot be found:<id>" against a freshly bootstrapped keystone, check~/.oscfor a stale entry before assuming a real bug.AsyncOpenStack/CloudConfigaccepts either aclouds.yamlentry (OS_CLOUD=<name>) or plainOS_*auth env vars (OS_AUTH_URL/OS_USERNAME/OS_PASSWORD/...) viaCloudConfig::from_env()—tests/loadtest/src/main.rs'sload_cloud_config()prefers the env-var form wheneverOS_AUTH_URLis set, falling back toOS_CLOUD+ clouds.yaml otherwise, so a clouds.yaml file isn't a hard requirement to run the suite.- Goose logs any non-2xx response into its
ERRORS/status-code tables at the HTTP layer regardless of the transaction's own pass/fail logic. Negative-test transactions (wrong password, revoke-then-validate, rescope to a nonexistent project) must calluser.set_success(&mut goose.request)once they've confirmed the rejection was the expected outcome, or the top-line failure percentage reads misleadingly high even though nothing is actually broken. - Loadtest also runs against a keystone deployed via skaffold to local k3s
(see
doc/src/contributor/development.mdfor that setup) — skiptools/run-loadtest-local.shentirely and pointload_teststraight at the cluster's exposed URL/port with--host, usingOS_CLOUD/OS_*env vars matching that deployment's admin credentials. tests/loadtest/sample_metrics.sh <output-file> [interval-seconds]pollskubectl top(keystone-rs/keystone-py pods + node) and host free/loadavg on a loop, for correlating CPU/memory against a concurrent loadtest run in the skaffold k3s deployment. Requires metrics-server already working (kubectl top nodessucceeds standalone). Run it in the background bracketing theload_testinvocation, thenkillit:./sample_metrics.sh /tmp/metrics.log 3 & SAMPLER=$!; load_test ...; kill "$SAMPLER". See the script's header comment for the output format and anawkone-liner to derive per-pod min/max/avg memory from it.
- Format: Conventional Commits (
type: subject body) enforced bycommittedpre-commit hook - Style: See
committed.toml:style="conventional" - Types: Use standard conventional commit types:
feat,fix,chore,docs,test, etc. - Scope: Optional scope in parentheses:
feat(identity): message - Subject line: <=72 characters, capitalized, imperative mood, no period at end
- Body: <=72 characters per line, each line separated by blank line
- DCO: Always include
Signed-off-by:line usinggit commit -s - Merge commits: Not allowed (
merge_commit = false) - Pre-commit: Run
committedhook viapre-commit run --all-filesto validate
MUST READ doc/src/contributor/security-model.md before any changes to:
- Authentication, authorization, scope, delegation, rescope, reauth
- Tokens, credentials, EC2, application credentials, trusts
- Policy input or OPA integration
Key security invariants from the contributor security model:
- Security decisions MUST be keyed on authentication chain (immutable), NEVER on token scope
- Delegation facts must come from
sc.authentication_context(), not scope - Delegated policy rules must compare to
input.credentials.delegated_project_id - Scope-drift tripwire:
credentials.project_id == credentials.delegated_project_id - Effective roles are always bounded by the delegation
- Secrets must be stripped from policy input (no EC2 keys/TOTP seeds in OPA)
- List endpoints must re-check each item individually with per-item read policy
For detailed setup and environment configuration, see:
- CONTRIBUTING.md: Development commands, workspace structure, design patterns
- doc/src/contributor/development.md: Kubernetes/skaffold setup, OSC configuration
- doc/src/contributor/security-model.md: Security model, invariants, and reviewer checklist for auth/authorization
- doc/src/adr/: Architecture Decision Records
- .pre-commit-config.yaml: Linting hooks
- committed.toml: Commit message format