First working identity and enrollment slice
Status: original identity increment, implemented and integration-tested. Later authorization, storage and managed identity behavior is documented in ecosystem slice, which supersedes the original limitations below.
Run
Requirements: Go 1.25+, a C compiler with CGO enabled (SQLite driver), OpenSSL for the optional development PKI script, and a configured OIDC provider. Register a relying-party client with exactly https://127.0.0.1:8443/auth/callback as its redirect URI. Determine the intended operator's exact sub at the configured issuer; email is not a principal ID.
From the workspace root:
sh scripts/dev-pki.sh
export BURST_OIDC_CLIENT_SECRET='your-client-secret'
go run ./burst/cmd/burst \
-oidc-issuer https://your-provider.example/issuer \
-oidc-client-id your-client-id \
-operator-subject your-exact-subject \
-dev-ca-cert .burst/pki/ca.crt \
-dev-ca-key .burst/pki/ca.key \
-tls-cert .burst/pki/server.crt \
-tls-key .burst/pki/server.keyOpen https://127.0.0.1:8443 using a browser that trusts the configured local HTTPS certificate. The PKI script creates a development CA and server certificate; it refuses overwrites and does not install anything in OS/browser trust stores. You may supply your existing local HTTPS certificate using the TLS flags instead. This CA is intentionally an explicit development dependency, not a production CA service.
Sign in, create a space, create a node enrollment and approve it in the console. The configured bootstrap subject gets the devnet operator entitlement. Other authenticated users get a session but no network administration rights. Bootstrap issuer/subject are pinned in the database: changing them for an existing network fails startup. User/group grant management is not exposed in this slice.
The private .burst/control.db database is created automatically. Its parent must be private (0700), and its file must be private (0600). Do not put it directly in a shared /tmp directory. The listener is restricted to loopback. An explicit -allow-loopback-http mode exists for local OIDC/browser experiments; certificate-authenticated node calls require HTTPS.
Enroll and call as a node
The console gives you an enrollment ID and a 256-bit bootstrap credential, valid for 15 minutes. Transfer it privately to the intended node. The credential is generated in the operator's browser and only its digest is stored; this also keeps raw bearer credentials out of idempotency outcomes. Node private keys are generated on the node and never submitted to Burst.
For a local node demonstration, in a private working directory:
umask 077
openssl genpkey -algorithm ED25519 -out node.key
openssl req -new -key node.key -subj /CN=node1 -out node.csrPut the bootstrap credential into a private bootstrap-token file without a trailing newline. Create and submit the JSON request using jq and curl (substitute the enrollment ID from the console):
jq -n --rawfile token bootstrap-token --rawfile csr node.csr \
'{token: $token, csr_pem: $csr}' > consume.json
curl --fail-with-body --cacert /path/to/.burst/pki/ca.crt \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: node1-enrollment-attempt-1' \
--data-binary @consume.json \
https://127.0.0.1:8443/v1/networks/devnet/enrollments/ENROLLMENT_ID/consume \
> enrolled.json
jq -r .certificate_pem enrolled.json > node.crt
curl --fail-with-body --cacert /path/to/.burst/pki/ca.crt \
--cert node.crt --key node.key \
https://127.0.0.1:8443/v1/networks/devnet/nodeThe one-hour client certificate identifies spiffe://burst.local/network/devnet/node/node1. Burst chooses the certificate identity from the approved binding, ignoring requested CSR names. Keep bootstrap files private and remove the credential/request files after successful enrollment. Repeating the same request with the same idempotency key returns the original public certificate; a changed CSR conflicts and a new key cannot consume the credential again. No certificate renewal endpoint exists yet.
API contract
All API responses are JSON. Mutations use JSON objects, reject unknown fields, and have a 64 KiB body limit. Identifiers match [A-Za-z0-9_-]{1,64}. Listed collections are currently capped at 100; pagination is pending.
| Method and route | Authority / behavior |
|---|---|
GET /healthz | Public process liveness; not a dependency-readiness guarantee |
GET / | Minimal browser console |
GET /auth/login | Begin OIDC code flow with S256 PKCE, nonce and one-use browser-bound state |
GET /auth/callback | Verify state, exchange code, verify identity, rotate browser session |
POST /auth/logout | Session + CSRF; revoke current server-side session |
GET /v1/me | Session; principal ID, CSRF token and session expiry |
GET /v1/networks | Session; networks visible through operator/owner entitlement |
GET/POST /v1/networks/{network}/spaces | Network operator; list or create a named space |
GET /v1/networks/{network}/spaces/{space} | Space owner or network operator; cross-scope lookup returns 404 |
POST /v1/networks/{network}/enrollments | Operator; {node_id, token} with 64 lowercase hex token characters |
POST /v1/networks/{network}/enrollments/{enrollment}/approve | Operator; {}; explicit approval |
POST /v1/networks/{network}/enrollments/{enrollment}/consume | Approved, unexpired bootstrap credential; {token, csr_pem} |
GET /v1/networks/{network}/node | Client certificate plus current, unquarantined membership and pinned certificate fingerprint |
POST /v1/networks/{network}/nodes/{node}/quarantine | Operator; {}; next node request is denied, including on an existing TLS connection |
Every operator mutation requires the browser session cookie, exact service Origin, X-CSRF-Token from /v1/me, and Idempotency-Key (8–128 characters). Logout requires session/Origin/CSRF but is naturally idempotent and does not require a mutation key. Consumption requires a mutation key and the bootstrap credential, not a browser session. Reusing a key with changed input is a 409. Stable outcomes are scoped to actor, network, resource scope and operation. Authentication/permission checks happen again before serving cached outcomes.
Errors use {"error":"code"}: 400 malformed input/missing idempotency key, 401 missing/expired identity or invalid bootstrap credential, 403 permission/CSRF/membership failure, 404 invisible/missing space or node, 409 state/key conflict, 415 non-JSON mutation, 429 pending login capacity, 503 database/issuer failure. Internals, bearer credentials and provider token errors are not returned to clients.
Persistence and security boundaries
burstauthowns the OIDC client using pinned go-oidc/oauth2 libraries; tokens are not accepted as browser sessions. The issuer, signature, audience, expiry, nonce, subject and authorized-party claim are checked.burstsecurityowns CSR validation and the development signing adapter. It uses standard X.509 signing and never exposes the CA key to clients. Production SPIRE/managed issuance, hardware attestation, rotation and trust-bundle distribution are pending.burstnetmembership checks run on every node request. Certificates prove identity, not permission. This slice pins a certificate fingerprint until rotation support exists.burstcomposes services and a SQLite adapter. One immediate transaction covers authenticated authorization, the mutation, its outcome and audit record. Consumption, certificate bytes and membership commit together; signing is local and has no external side effect. An external issuer will require the durable job/outbox design in control-metadata.md.- SQLite WAL plus FULL synchronous commits support reopening this local database. This is not PostgreSQL, HA, a crash/power-loss certification, or a production migration path. The PostgreSQL migration remains separately unverified.
- Sessions expire after eight hours; login attempts after five minutes; enrollments after fifteen minutes; node certificates after one hour. Logout and disabled principals deny future requests. Remote identity-provider logout/revocation is not propagated; session back-channel logout, distributed invalidation and renewal remain pending.
- Login attempts are bounded to 1,024 pending records. Production per-principal/IP quotas, complete denial audits, metrics, and maintenance/retention for other state remain pending.
Validation
sh scripts/check.sh runs all nine modules' race tests and vet, plus Protobuf validation when available. The control integration suite exercises real HTTP/TLS and a signed OIDC discovery/code/token/JWKS fixture. It uses net.Pipe connections because this sandbox disallows socket listeners; no TLS/signature verification is skipped. The fixture exists only in _test.go files and is absent from the executable.
Coverage includes login rejection (issuer/audience/signature/expiry/nonce/subject/azp), state binding/replay, CSRF/Origin, stable principal mapping, ordinary-user denial, cross-network access, concurrent idempotent space creation, transaction rollback on audit failure, durable pending approvals, concurrent enrollment retries with one certificate issuance, bootstrap-token hashing, restart persistence, certificate-without-membership denial, session expiry/logout and immediate quarantine.
Not verified here: real-provider/browser interoperability, native socket listener behavior, browser UI automation, PostgreSQL runtime behavior, external certificate issuance or a distributed three-node data lifecycle.