burstdocs
DEVELOPERS / Get started

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
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.key

Open 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:

sh
umask 077
openssl genpkey -algorithm ED25519 -out node.key
openssl req -new -key node.key -subj /CN=node1 -out node.csr

Put 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):

sh
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/node

The 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 routeAuthority / behavior
GET /healthzPublic process liveness; not a dependency-readiness guarantee
GET /Minimal browser console
GET /auth/loginBegin OIDC code flow with S256 PKCE, nonce and one-use browser-bound state
GET /auth/callbackVerify state, exchange code, verify identity, rotate browser session
POST /auth/logoutSession + CSRF; revoke current server-side session
GET /v1/meSession; principal ID, CSRF token and session expiry
GET /v1/networksSession; networks visible through operator/owner entitlement
GET/POST /v1/networks/{network}/spacesNetwork 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}/enrollmentsOperator; {node_id, token} with 64 lowercase hex token characters
POST /v1/networks/{network}/enrollments/{enrollment}/approveOperator; {}; explicit approval
POST /v1/networks/{network}/enrollments/{enrollment}/consumeApproved, unexpired bootstrap credential; {token, csr_pem}
GET /v1/networks/{network}/nodeClient certificate plus current, unquarantined membership and pinned certificate fingerprint
POST /v1/networks/{network}/nodes/{node}/quarantineOperator; {}; 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

  • burstauth owns 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.
  • burstsecurity owns 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.
  • burstnet membership checks run on every node request. Certificates prove identity, not permission. This slice pins a certificate fingerprint until rotation support exists.
  • burst composes 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.

Built from plans/identity-slice.md in the Burst Kit workspace. Check implementation status before deployment.

Search the documentation