Documentation
¶
Overview ¶
Package identity provisions the PocketID owner, the device credential and the Owner recovery (recovery set and recovery code) at the Authority.
PocketID v2 is passkey-only: there is no password field on user creation. The owner-activation flow is therefore three calls:
- POST /api/users -- create the user record (no password).
- PUT /api/users/:id/user-groups -- add to the "owners" group.
- POST /api/users/:id/one-time-access-token -- issue an enrollment token.
The token is rendered into a setup URL of the form
https://id.<domain>/lc/<token>?redirect=%2Fsettings%2Faccount
which the owner clicks once to register a WebAuthn credential. The token is single-use and consumed by PocketID on first redemption.
The local provisioner only supports Source=="local" (a daily-admin owner provisioned locally on the first node). Source=="cloud" is orchestrator managed by TechStack/kombify Cloud and must not call this local provisioner.
Index ¶
- Constants
- Variables
- func AwaitHomeOwnerStepUp(ctx context.Context, root string, request HomeOwnerStepUpRequest, ...) (string, error)
- func BindHomePublicTrust(root string, trust HomePublicTrust) error
- func CancelDeviceRecovery(root string, request RecoveryDeviceRequest, now time.Time) (bool, error)
- func CancelRecoveryAtConsole(root string, now time.Time) (bool, error)
- func ClearHomeVPNSelection(root string) error
- func ConfigureHomeIdentityOrigins(root string, origins ...string) error
- func ForgetHomeVPNPeers(root string, settled []HomeVPNPeer, now time.Time) error
- func FormatRecoveryCode(code string) string
- func HomeAccountGroups(root, account string, now time.Time) ([]string, bool)
- func HomeDeviceEnrollmentNonce(thumbprint, proofID string) string
- func HomeDeviceRenewalHandler(root string) http.Handler
- func HomeDeviceUserVerificationNonce(thumbprint, proofID string) string
- func HomeEnrollmentCertificateSource(root string, signer HomeAuthoritySigner) func(*tls.ClientHelloInfo) (*tls.Certificate, error)
- func HomeEnrollmentHandler(root string) http.Handler
- func HomeLaunchURL(root, target, state string) (string, error)
- func HomeVPNBoundAddresses(root, tool string, now time.Time) ([]string, error)
- func HomeVPNDeviceActive(root, deviceID string, now time.Time) (bool, error)
- func HomeVerifierHandler(root string) http.Handler
- func IsHomeIdentityDeviceRoute(r *http.Request) bool
- func KeepHomePublicTrustCurrent(ctx context.Context, root string)
- func NewHomeEnrollmentServer(root, listen string, ...) (*http.Server, error)
- func NewHomeHubServer(ctx context.Context, root, listen string, options HomeHubOptions) (*http.Server, error)
- func ObservedLANAddress(address string) bool
- func ParseHomeDeviceAuthorization(header http.Header) (credential, proof string, err error)
- func PendingHomeEnrollment(root, deviceID string, now time.Time) (HomeDevice, HomeStepUpAction, error)
- func ReadHomeVPNSecret(root, name string) ([]byte, error)
- func ReadStaticAPIKey(baseDir string) (string, error)
- func RecordHomeSiteNetwork(root string, now time.Time) error
- func RecoveryBundleName(nodeRef string) string
- func RecoveryDeviceHandler(root string, issue RecoveryPasskeyIssuer) http.Handler
- func RecoveryNoticesHandler(root string) http.Handler
- func RecoveryRecipient(root string) (*age.HybridRecipient, error)
- func RefreshHomeAccountGroups(ctx context.Context, root string, now time.Time) error
- func RegisterHomeHubRoutes(mux *http.ServeMux, root string, options HomeHubOptions)
- func RegisterHomeIdentityRoutes(mux *http.ServeMux, root string)
- func RenderHomeOverlayConfig(root string, now time.Time) ([]byte, error)
- func ResetRecoveryAfterRestore(root string, now time.Time) error
- func RevokeHomeAppPass(root, id string, actor HomeActor, now time.Time) error
- func RotateRecoveryCode(root string, now time.Time) (string, error)
- func SaveHomeVPNSelection(root string, selection HomeVPNSelection) error
- func ServeHomeAuthoritySigner(ctx context.Context, root, socketPath string, peerUID int) error
- func SyncHomeVPNAnnouncement(root string) error
- func SyncRecoveryNoticeProjection(root string) error
- func ValidHomeDeviceName(value string) bool
- func ValidateHomeVPNEndpoint(endpoint string) error
- func ValidateHomeVPNSelection(selection HomeVPNSelection) error
- func VerifyHomeStepUp(root string, action HomeStepUpAction, approver HomeStepUpApprover, ...) (string, error)
- func VerifyOwnerStepUp(root, idToken, nonce string, now time.Time) (string, error)
- func WriteHomeVPNSecret(root, name string, raw []byte) error
- type HomeActor
- type HomeAppPass
- type HomeAuthentication
- type HomeAuthoritySigner
- type HomeBrowserSession
- type HomeConfirmation
- type HomeDevice
- type HomeDeviceApproval
- type HomeDeviceCredential
- type HomeDeviceRequest
- type HomeEnrollmentBootstrap
- type HomeEnrollmentNotice
- type HomeEnrollmentOutcome
- type HomeEnrollmentPolicy
- type HomeEnrollmentSubmission
- type HomeHubOptions
- type HomeLaunch
- type HomeOverlayNode
- type HomeOverlayPeer
- type HomeOwnerStepUpRequest
- type HomePocketIDTrust
- type HomePublicTrust
- type HomeRenewalRequest
- type HomeRevocation
- type HomeRevokedArtifact
- type HomeStepUpAction
- type HomeStepUpApprover
- type HomeStepUpClass
- type HomeVPNAnnouncement
- type HomeVPNLiveNode
- type HomeVPNPangolinSelection
- type HomeVPNPeer
- func BindHomeVPNPeer(root, deviceID, tool string, node HomeVPNLiveNode, now time.Time) (HomeVPNPeer, error)
- func HomeVPNPeerOf(root, deviceID, tool string) (HomeVPNPeer, error)
- func IssueHomeVPNPeer(root, deviceID, tool string, now time.Time, issue func() (HomeVPNPeer, error)) (HomeVPNPeer, error)
- func ListHomeVPNPeers(root string) ([]HomeVPNPeer, error)
- func RemoveAllHomeVPNPeers(root, tool string) ([]HomeVPNPeer, error)
- type HomeVPNRemovals
- type HomeVPNSelection
- type HomeVPNTailscaleSelection
- type HomeVerifier
- func (v *HomeVerifier) Authenticate(request HomeDeviceRequest) (HomeAuthentication, error)
- func (v *HomeVerifier) AuthenticateLaunch(launch HomeLaunch) (HomeAuthentication, error)
- func (v *HomeVerifier) ConfirmEnrollmentFromDevice(caller HomeAuthentication, deviceID, challenge, idToken string) (HomeDevice, error)
- func (v *HomeVerifier) Enroll(submission HomeEnrollmentSubmission) (HomeEnrollmentOutcome, error)
- func (v *HomeVerifier) RenewDeviceCredential(request HomeRenewalRequest) (HomeDeviceCredential, error)
- func (v *HomeVerifier) VerifyHomeAppPass(secret, host string) (HomeAppPass, error)
- func (v *HomeVerifier) VerifyHomeBrowserSession(value, host string, remote bool) (HomeBrowserSession, error)
- type OwnerProvisioner
- type OwnerSpec
- type PocketIDClient
- type ProvisionResult
- type RecoveryDeviceRequest
- type RecoveryNotice
- type RecoveryPasskeyIssuer
- type RecoveryPasskeyLink
- type RecoveryRequest
- type RecoverySecrets
- type RecoverySetEntry
- type RecoverySetManifest
- type RecoverySetRestore
- type RecoverySetVolume
- type RecoveryStatus
- type RecoveryVolumeSource
Constants ¶
const ( RecoveryRequestPath = "/api/v1/identity/recovery/request" RecoveryCompletePath = "/api/v1/identity/recovery/complete" RecoveryCancelPath = "/api/v1/identity/recovery/cancel" RecoveryNoticesPath = "/api/v1/identity/recovery/notices" // RecoveryDeviceNoticesPath lets an enrolled Owner device fetch the // notices addressed to it with a DPoP proof (GET), until push delivery // (Companion Feed, Hub Web Push) carries them. RecoveryDeviceNoticesPath = "/api/v1/identity/recovery/device-notices" // RecoveryDeviceDelay is how long a recovery started from an enrolled // Owner device waits, with notices to every Owner device, before its code // registers a new passkey. It is not a caller choice. RecoveryDeviceDelay = 24 * time.Hour // RecoveryRequestWindow is how long a pending device recovery stays // completable once it became eligible. RecoveryRequestWindow = 72 * time.Hour // RecoveryPasskeyLinkLifetime bounds the Pocket ID one-time link a // redeemed recovery code yields. // Pocket ID issues a six-character typed login code for a lifetime of // 15 minutes or less, which its one-time link page does not redeem, so the // link lives 20 minutes and carries a full token. RecoveryPasskeyLinkLifetime = 20 * time.Minute // Notice kinds; each notice lists the Owner devices it is addressed to. RecoveryNoticeRequested = "recovery-requested" RecoveryNoticeCompleted = "recovery-completed" RecoveryNoticeCancelled = "recovery-cancelled" RecoveryNoticeRedeemed = "recovery-redeemed-at-console" RecoveryNoticeCodeRejected = "recovery-code-rejected" RecoveryNoticeCodeRotated = "recovery-code-rotated" RecoveryNoticeRotated = "recovery-rotated" RecoveryNoticeRestored = "recovery-restored" )
const ( RecoverySetSchema = "stackkit.identity-recovery-set/v1" // The Docker volumes of the identity runtime, by compose volume name. RecoveryVolumePocketID = "pocketid-data" RecoveryVolumeStepCA = "step-ca-db" // DefaultRecoveryBundleDir holds the Authority's local copy of its latest // set, break-glass-<node>.age. It is safe to copy anywhere. DefaultRecoveryBundleDir = "/var/lib/stackkit/recovery" )
const ( HomeAppPassHeader = "Stackkit-App-Pass" HomeAppPassLifetime = 30 * 24 * time.Hour // HomeAppPassesPath lists (GET) and issues (POST) the caller's passes; // POST HomeAppPassesPath/{id}/revoke revokes one. HomeAppPassesPath = "/api/v1/identity/home/app-passes" )
App passes (homelab identity target §6.3, §12): where the Owner allowed app access away for a service (appAccessAway on its access policy), the Hub issues a device's app (Immich, a WebDAV client ...) a pass of at most 30 days for that one host. The app sends it in the HomeAppPassHeader, which the router strips before the backend; the Access Gate admits it on that host's app routes only while the pass and its device are active, so revoking the device ends its passes. The Authority keeps only a hash of each pass.
const ( // HomeLaunchPath is the reserved path on every gated host at which the // Gate exchanges a launch proof for a session. HomeLaunchPath = "/.well-known/stackkit/launch" // HomeLaunchProofType is the JWS "typ" of a launch proof. HomeLaunchProofType = "stackkit-launch+jwt" // HomeLaunchProofWindow is #HomelabIdentityLifetimesV1.launchProofSeconds. HomeLaunchProofWindow = 60 * time.Second // HomeBrowserSessionCookie and HomeLaunchStateCookie are host-only // cookies of the gated host. HomeBrowserSessionCookie = "__Host-stackkit-session" HomeLaunchStateCookie = "__Host-stackkit-launch" HomeLaunchStateLifetime = 5 * time.Minute // The session lifetimes of #HomelabIdentityLifetimesV1. HomeBrowserSessionAway = time.Hour HomeBrowserSessionHome = 4 * time.Hour )
Browser sessions (homelab identity target §6.3, §12): a browser carries no per-request proof, so the Access Gate sends it to the Hub, which signs a 60 second launch proof with the device key for exactly one host; the Gate exchanges it for a per-host "__Host-" session recorded against the device: one hour away, four hours at home, never longer than the device stays active. Renewal is the same launch, silently, wherever the Hub key is reachable (on iOS by reopening from the Hub).
const ( HomeApprovalHome = "home" HomeApprovalOwnDevice = "own-device" HomeApprovalOwner = "owner" HomeApprovalPending = "pending" )
Approval modes of a registry entry (#DeviceEnrollmentPolicy): approved automatically at home, confirmed away by a step-up from the person's own enrolled device or by the Owner at the Authority, or still pending.
const ( HomeCustodyHardware = "hardware" HomeCustodyOSProtected = "os-protected" HomeCustodySoftware = "software" )
Custody classes of a device key (#DeviceCustodyClassV1, Connect D4).
const ( // HomeDeviceCredentialType is the JWS "typ" of the device credential. HomeDeviceCredentialType = "stackkit-device-credential+jwt" // HomeDeviceProofType is the JWS "typ" of a DPoP proof. HomeDeviceProofType = "dpop+jwt" // The lifetimes of #HomelabIdentityLifetimesV1; none is a caller choice. HomeDeviceCredentialLifetime = time.Hour HomeDeviceProofWindow = 30 * time.Second HomeDeviceUserVerificationInterval = 24 * time.Hour HomeDeviceRegistryEntryUnused = 180 * 24 * time.Hour HomeRevocationTarget = 60 * time.Second // HomeUserVerificationFreshness bounds the age of the passkey assertion a // renewal presents, as for an Owner step-up. HomeUserVerificationFreshness = 60 * time.Second // HomeClockLeeway tolerates clock skew between device, issuer and verifier. HomeClockLeeway = 10 * time.Second )
The device credential of the homelab identity target (§6.1, §12): the client generates the device key, the Authority signs a key-bound credential that lives one hour, and the client renews it with a DPoP proof (RFC 9449). Every request carries the credential and a fresh proof; the server running the service verifies both. There is no X.509 device certificate.
const ( // HomeEnrollmentPort is the catalog listener stackkit-server-home-enroll // (foundation/architecture_v2_catalog.cue): TLS 1.3 on the node site // address, exposure "lan". HomeEnrollmentPort = 9443 // HomeEnrollmentServerName is the enrollment listener's certificate name. HomeEnrollmentServerName = localevidence.HomeEnrollmentServerName // HomeEnrollmentURIScheme prefixes the enrollment bootstrap a device scans. HomeEnrollmentURIScheme = "stackkit-enroll" )
const ( HomeEnrollmentBootstrapPath = "/api/v1/identity/home/enroll/bootstrap" HomeDevicesPath = "/api/v1/identity/home/devices" HomeNoticesPath = "/api/v1/identity/home/notices" // HomeOwnerEnrollConfirmPath is the Owner's confirmation page for an // away enrollment on the Authority's step-up origin; it takes ?id=<device // ID> and is served only when the Owner is the confirmer. HomeOwnerEnrollConfirmPath = "/api/v1/identity/step-up/enroll-confirm" )
Routes of the Authority's node API that a device client calls with its own credential and proof (or, to enroll, its person's passkey sign-in) instead of the node API key. The notice, confirmation and undo routes are what the Hub, the Companion and the State Console deliver to people.
const ( HomeOverlayInterfaceName = "wg-stackkit" HomeOverlayListenPort = 51820 HomeOverlayKeepaliveSeconds = 25 )
This is the WireGuard tool of the optional VPN add-on (#VPNAddOnV1 in foundation/network.cue, D-2026-11-02-12). A VPN is never the default and never the realization of private remote access; the Owner switches it on in addition to the standard reach paths. Its peers are exactly the enrolled devices that registered a WireGuard public key and are neither revoked nor expired. The node private key is generated here into local custody and never leaves it: the rendered interface configuration loads it from the custody file at interface start.
const ( HomeNoticeEnrolledAtHome = "enrolled-at-home" HomeNoticeEnrollmentPending = "enrollment-pending" HomeNoticeOpen = "open" HomeNoticeUndone = "undone" HomeNoticeConfirmed = "confirmed" HomeNoticeClosed = "closed" // Confirmers of an away enrollment, in the contract's order. HomeConfirmerOwnDevice = "own-enrolled-device" HomeConfirmerOwner = "owner" )
Notice kinds and states (records the device clients and the State Console deliver; Web Push and the Companion Feed are later slices).
const ( HomeArtifactAccount = "homelab.account" HomeArtifactDevice = "homelab.device" HomeArtifactSession = "homelab.session" HomeArtifactAppCredential = "homelab.app_credential" HomeArtifactVPNPeer = "homelab.vpn_peer" )
Revocation registry artifact IDs (workspace revocation-registry.v1.json).
const ( HomeRevokedByPerson = "person" HomeRevokedByOwner = "owner" HomeRevokedByUndo = "undo" HomeRevokedByRemoval = "account-removed" )
Revocation reasons recorded on a registry entry.
const ( HomeVerifierPath = "/api/v1/identity/home/verify" HomeEnrollmentPath = "/api/v1/identity/home/enroll" HomeDeviceRenewalPath = "/api/v1/identity/home/device/renew" )
const ( // HomeVPNPeerServicePort is the peer service's TLS listener on the node // site address and the VPN. HomeVPNPeerServicePort = 9444 // HomeVPNServerName is the first DNS name of the peer service's // certificate, which native clients pin with the step-ca root. HomeVPNServerName = localevidence.HomeVPNServerName HomeVPNInfoPath = "/api/v1/vpn" HomeVPNPeerPath = "/api/v1/vpn/peer" HomeVPNConfirmPath = "/api/v1/vpn/peer/confirm" // HomeVPNUnboundGrace bounds how long a join key and an unbound node of a // coordinated tool live before the reconciler removes them. HomeVPNUnboundGrace = 15 * time.Minute HomeVPNToolWireGuard = "wireguard" HomeVPNToolHeadscale = "headscale" HomeVPNToolTailscale = "tailscale" HomeVPNToolPangolin = "pangolin" )
The optional VPN add-on (#VPNAddOnV1 in foundation/network.cue, the tool catalog in addons/vpn, homelab identity target §6.2 and §8). It is never a default: it exists only after the Owner's opt-in at the Authority's console, which this file records. Its peers are exactly the enrolled, unrevoked, unexpired devices of the registry:
- an active device asks the host's peer service for its peer with its device credential and a DPoP proof (IssueHomeVPNPeer); a pending away enrollment is not active and gets none;
- a coordinated tool's node binds to the device that received its join key, either by that key (Headscale) or by the device's probe through the VPN (BindHomeVPNPeer);
- every registry change and a periodic pass reconcile the tool's live nodes with the registry (ReconcileHomeVPNPeers): the nodes of revoked, expired or removed devices, and nodes no active device bound within the grace, are deleted. A ledger entry is forgotten only after its node and key are gone, so a failed deletion is retried, never lost.
The WireGuard tool keeps its peers in the overlay peer registry (home_overlay.go); this ledger holds the coordinated tools.
const ( HomeVPNPeerIssued = "issued" HomeVPNPeerBound = "bound" HomeVPNPeerRemoving = "removing" )
Ledger states of one device's peer.
const HomeDeviceClientID = localowner.HomeDeviceClientID
HomeDeviceClientID is the Pocket ID OIDC client through which a device client signs its person in with a passkey: enrollment, the 24 hour user verification of renewal and a person's step-up. Its ID tokens are the only accepted person assertions.
const HomeDeviceRegistryFile = homeDeviceRegistryPath
HomeDeviceRegistryFile is the device registry below the workspace root; the VPN add-on resync watches it for revocations.
const HomeEnrollmentPendingLifetime = 24 * time.Hour
HomeEnrollmentPendingLifetime bounds how long an away enrollment waits for its confirmation before it lapses.
const HomeEnrollmentUndoWindow = 24 * time.Hour
HomeEnrollmentUndoWindow is #HomelabIdentityLifetimesV1.enrollmentUndoSeconds.
const ( // HomeHubPathPrefix is the Hub's path on the Hub origin. The device // sign-in client's redirect (localowner.HomeDeviceCallbackPath) is inside // it, so the installed app's scope covers its own sign-in. HomeHubPathPrefix = "/device/" )
The Homelab Hub (homelab identity target §6.1, §6.3, D-2026-11-02-10) is the device client of everyone without the kombify app: an installable PWA on the Hub origin (https://base.<domain>) that holds a WebCrypto non-extractable device key, enrolls it with the person's own passkey, renews its device credential with DPoP proofs, lists and revokes the person's devices, confirms away enrollments and receives notices by Web Push. Its routes are a sign-in surface: the shell grants nothing, and every identity route authenticates the device proof or the passkey sign-in itself.
const HomeOverlayNodeKeyFile = homeOverlayNodePrivateKeyPath
HomeOverlayNodeKeyFile is the overlay node private key in custody (wg's base64 form), below the workspace root. Only the root resync reads it, to set the key on a running interface again after wg syncconf.
const HomeOverlayPeerRegistryFile = homeOverlayPeerRegistryPath
HomeOverlayPeerRegistryFile is the VPN add-on peer registry below the workspace root.
const (
HomePublicTrustSchema = "stackkit.home-public-trust/v2"
)
const (
HomePushSubscriptionPath = "/api/v1/identity/home/push/subscription"
)
Web Push delivery of the notice records (homelab identity target §6.2): the Hub subscribes its own enrolled device through its push service, and the Authority sends each new enrollment or recovery notice to the subscriptions of exactly the devices the record names, encrypted to the subscription (RFC 8291) and signed with the Authority's VAPID key (RFC 8292). A payload carries the notice kind, its id and the device name, never a credential.
const (
HomeSignerListenerCertificatePath = "/v1/listener-certificate"
)
The host-side identity signer (homelab identity target §4, §9; StackKits enpy). It runs on the Authority's host with the Owner custody and is the only party that uses the Owner key, the step-ca root and the Pocket ID admin key for the Basement core: it keeps the Owner-signed Home trust, the public Authority record, the recovery notice projection and the household groups current, and answers exactly two requests from the core over a Unix socket that only the core server's account may use:
POST /v1/listener-certificate a CSR for the LAN enrollment listener's own
names only; answer: the verified chain (PEM)
POST <recovery routes> the Owner device recovery routes, which
authenticate the device proof and the code
themselves and mint the recovery link
It never signs data the core chooses and never returns a key.
const (
HomeStepUpRequestsPath = "/api/v1/identity/home/step-up/requests"
)
Owner step-up requests carry a console action that changes identity custody (homelab identity target §9, §10, §12: rotating the recovery key, issuing a new recovery code) to the Owner's passkey. The console raises the request with the action digest and a fresh challenge; the Owner approves it in the Hub on an enrolled Owner device with a passkey sign-in whose nonce binds exactly that action and challenge. The Hub only delivers the ID token: the console itself verifies it (VerifyHomeStepUp, single use, at most 60 s old) before it acts, so a server that stored a token decides nothing.
const HomeVPNCustodyDir = homeVPNCustodyDir
HomeVPNCustodyDir is the add-on's custody directory below the workspace root: the selection, the peer ledger, the tools' secrets and state.
const HomeVPNSelectionFile = homeVPNSelectionPath
HomeVPNSelectionFile is the Owner's opt-in below the workspace root; the overlay resync watches it with the registries.
const StaticAPIKeyFilename = "pocketid-static-api-key"
StaticAPIKeyFilename is the basename of the on-disk file that holds the PocketID STATIC_API_KEY for a homelab. It lives under <homelab>/.stackkit/.
Variables ¶
var ( ErrRecoveryNotConfigured = errors.New("identity: recovery is not set up on this Authority; run `stackkit break-glass setup --owner-approve`") ErrRecoveryConfigured = errors.New("identity: recovery is already set up; replace it with `stackkit break-glass setup --rotate --owner-approve`") // ErrRecoveryDenied is the one answer to a wrong, consumed or throttled // code and to a refused device proof, so a refusal reveals no reason. ErrRecoveryDenied = errors.New("identity: recovery code or Owner device proof refused") ErrRecoveryNotYet = errors.New("identity: a device recovery becomes eligible 24 hours after it was requested") ErrRecoveryPending = errors.New("identity: another Owner device recovery is pending; cancel it first") )
var ( // ErrRecoverySetRefused is the one answer to a set that does not open // with the given key, fails its MAC or digests, or is malformed. ErrRecoverySetRefused = errors.New("identity: the recovery set does not open with this recovery key or was altered") // ErrRecoveryRestoreConflict refuses a restore onto a workspace that // already holds another Owner or another identity origin. ErrRecoveryRestoreConflict = errors.New("identity: this workspace already holds another Owner or identity origin; restore onto a fresh Authority") )
var ( // ErrHomeRegistryFull refuses a change that would grow the registry past // its bound; revoking devices still works. ErrHomeRegistryFull = errors.New("identity: the device registry is full; revoke unused devices first") ErrHomeDeviceRevoked = errors.New("identity: this device key was revoked and cannot enroll again; generate a new key") ErrHomeDeviceConflict = errors.New("identity: this device key is already enrolled or pending, or another device of this account uses this name") ErrHomeDeviceNotFound = errors.New("identity: no enrolled device has this id") )
var ( // ErrHomeAccessDenied is the one answer to every refused credential, // proof, renewal or enrollment, so a refusal reveals no reason. ErrHomeAccessDenied = errors.New("identity: a current device credential and request-bound proof are required") // ErrHomeUserVerificationRequired means the renewal was otherwise valid // but the last user verification is older than 24 hours: the client signs // the person in with a passkey through HomeDeviceClientID and renews again // with the ID token and a new proof. ErrHomeUserVerificationRequired = errors.New("identity: renewal needs a passkey user verification") )
var ( ErrHomeOverlayDeviceInactive = errors.New("identity: only an enrolled, unrevoked, unexpired device can join the overlay") ErrHomeOverlayKeyInvalid = errors.New("identity: overlay public key must be a base64 32-byte WireGuard key") ErrHomeOverlayKeyInUse = errors.New("identity: another device already registered this overlay key") ErrHomeOverlayPoolExhausted = errors.New("identity: the overlay address pool has no free address") // ErrHomeOverlayCustodyAbsent: this workspace holds no VPN add-on custody // (the core's identity workspace, or a host without the add-on). ErrHomeOverlayCustodyAbsent = errors.New("identity: no VPN add-on custody in this workspace") )
var ( ErrHomeEnrollmentRateLimited = errors.New("identity: too many enrollment attempts for this account; try again later") ErrHomeDeviceCapReached = errors.New("identity: this account has reached its device limit; revoke a device first") ErrHomeEnrollmentPending = errors.New("identity: this device's enrollment waits for its confirmation") ErrHomeNoticeNotFound = errors.New("identity: no open notice with this id is visible to this caller") // ErrHomeActorInUndoWindow refuses a device enrolled at home less than // 24 hours ago when it acts on another device: within its own undo window // it may revoke only itself, so a device enrolled by someone else cannot // lock the person out before they undo it. ErrHomeActorInUndoWindow = errors.New("identity: a device enrolled at home within the last 24 hours can revoke only itself") )
var ( // ErrHomeStepUpDenied is the one answer to a step-up that does not prove a // fresh passkey authentication of the required approver for exactly this // action, or that was already used. ErrHomeStepUpDenied = errors.New("identity: a fresh passkey step-up for exactly this action is required") // ErrOwnerStepUpDenied is returned for any Owner step-up ID token that does // not prove a fresh passkey authentication of the pinned Home Owner. ErrOwnerStepUpDenied = errors.New("identity: a fresh Owner passkey step-up for this Home is required") )
var ( ErrHomeVPNOff = errors.New("identity: the VPN add-on is off; the Owner switches it on with stackkit vpn enable") ErrHomeVPNDeviceInactive = errors.New("identity: only an enrolled, unrevoked, unexpired device receives a VPN peer") ErrHomeVPNNodeBound = errors.New("identity: this VPN node belongs to another device") ErrHomeVPNNoPeer = errors.New("identity: this device has no VPN peer of the selected tool; ask for one first") ErrHomeVPNLedgerFull = errors.New("identity: the VPN peer ledger is full") )
var ErrHomeAppPassFreshPasskey = errors.New("identity: issuing an app pass needs a passkey sign-in on this device within the last 10 minutes")
ErrHomeEnrollmentUnavailable means the applied plan carries no admissible #DeviceEnrollmentPolicy, so this server enrolls nothing.
var ( // ErrHomeStepUpRequestExpired ends a console wait without an approval. ErrHomeStepUpRequestExpired = errors.New("identity: the Owner did not approve the step-up in time") )
var HomeVPNTools = []string{HomeVPNToolWireGuard, HomeVPNToolHeadscale, HomeVPNToolTailscale, HomeVPNToolPangolin}
HomeVPNTools are the add-on's alternative tools (#VPNAddOnToolV1).
Functions ¶
func AwaitHomeOwnerStepUp ¶
func AwaitHomeOwnerStepUp(ctx context.Context, root string, request HomeOwnerStepUpRequest, poll time.Duration) (string, error)
AwaitHomeOwnerStepUp waits until the Owner delivered a step-up for request and verifies it here: the pinned Owner's passkey through the device sign-in client, for exactly this action and challenge, at most 60 seconds old and never used before. The request is removed whatever the outcome.
func BindHomePublicTrust ¶
func BindHomePublicTrust(root string, trust HomePublicTrust) error
BindHomePublicTrust is local Owner publication of the current Pocket ID pin. It does not enroll a device or mint a credential. Every device of a revoked Homelab account is revoked in the registry before the pin is published, so the account's credentials stop renewing at once and stop verifying within the revocation refresh.
func CancelDeviceRecovery ¶
CancelDeviceRecovery ends a pending device recovery from any enrolled Owner device; no code is needed to stop a recovery.
func CancelRecoveryAtConsole ¶
CancelRecoveryAtConsole ends a pending device recovery from the console.
func ClearHomeVPNSelection ¶
ClearHomeVPNSelection switches the add-on off. The caller removes the tool's peers first.
func ConfigureHomeIdentityOrigins ¶
ConfigureHomeIdentityOrigins sets the origins, besides the Hub origin of the identity runtime custody, on which this server serves its identity routes (renewal, away enrollment, the person routes), such as its Owner step-up origin. A proof for any other origin is refused.
func ForgetHomeVPNPeers ¶
func ForgetHomeVPNPeers(root string, settled []HomeVPNPeer, now time.Time) error
ForgetHomeVPNPeers settles entries after the tool deleted their node and key: an entry is dropped, except an unbound one still inside the grace, which stays as a tombstone without key (see planHomeVPNRemovals). An entry that changed meanwhile is kept as it is.
func FormatRecoveryCode ¶
FormatRecoveryCode prints a code in groups of four for the Owner to write down.
func HomeAccountGroups ¶
HomeAccountGroups returns the household groups of account and whether they are known: the projection is readable, at most homeAccountGroupsMaxAge old, and lists the account.
func HomeDeviceEnrollmentNonce ¶
HomeDeviceEnrollmentNonce is the OIDC nonce of the passkey sign-in that enrolls a device: base64url(SHA-256("stackkit-device-enrollment/v1" 0x00 thumbprint 0x00 jti)) of the device key and the one DPoP proof that presents it.
func HomeDeviceRenewalHandler ¶
HomeDeviceRenewalHandler renews a device credential on the Authority. The request carries the DPoP proof in the "DPoP" header and, when a user verification is due or offered, {"userVerification": "<Pocket ID ID token>"} as its body. The proof is the authentication; no API key applies.
func HomeDeviceUserVerificationNonce ¶
HomeDeviceUserVerificationNonce is the OIDC nonce a device client sends to Pocket ID for the passkey assertion of a renewal. It binds the assertion to the device key (its JWK thumbprint) and to the one DPoP proof (its jti) that presents it: base64url(SHA-256("stackkit-device-user-verification/v1" 0x00 thumbprint 0x00 jti)).
func HomeEnrollmentCertificateSource ¶ added in v0.53.14
func HomeEnrollmentCertificateSource(root string, signer HomeAuthoritySigner) func(*tls.ClientHelloInfo) (*tls.Certificate, error)
HomeEnrollmentCertificateSource obtains the listener certificate on first use and renews it an hour before expiry. The key is generated here and never leaves the process; the host-side signer, which alone holds the Owner custody, has step-ca sign the request for exactly the listener's names. The chain is checked against the Authority's step-ca root before it is served. Without the signer the TLS handshake fails, so the listener stays closed.
func HomeEnrollmentHandler ¶
HomeEnrollmentHandler is the enrollment route of every other listener (the node API behind the reach paths). A submission there is always away and waits for its confirmation, whatever address, header or name it carries.
func HomeLaunchURL ¶
HomeLaunchURL is the Hub page a browser without a session is sent to: the Hub signs the launch for target and state there.
func HomeVPNBoundAddresses ¶
HomeVPNBoundAddresses are the VPN addresses of the bound peers of active devices: the only sources the interface guard of a coordinated tool lets past the peer service.
func HomeVPNDeviceActive ¶
HomeVPNDeviceActive reports whether a device is an active registry entry.
func HomeVerifierHandler ¶
HomeVerifierHandler is the verification endpoint for a component that fronts a route in another process. The caller states the request it observed, with the route origin from its own route table. The endpoint sits behind the node API boundary; it treats no API key, workload TLS or forwarded header as identity.
func IsHomeIdentityDeviceRoute ¶
IsHomeIdentityDeviceRoute reports the identity routes that authenticate a device proof or a person's passkey sign-in themselves (or serve only the public enrollment bootstrap), so the node API key does not apply to them.
func KeepHomePublicTrustCurrent ¶
KeepHomePublicTrustCurrent refreshes the Home trust at once and then every hour on a server that holds Owner custody, retrying a failed refresh every minute, until ctx ends. A server without Owner custody (yet) is checked again every homeTrustIdleInterval without a warning. While a refresh fails, the last trust stays valid to its own expiry; after that every passkey verification, enrollment and step-up fails closed.
func NewHomeEnrollmentServer ¶ added in v0.53.14
func NewHomeEnrollmentServer(root, listen string, certificate func(*tls.ClientHelloInfo) (*tls.Certificate, error)) (*http.Server, error)
NewHomeEnrollmentServer is the Authority's dedicated LAN enrollment listener, the only attestation of home: a submission it accepts directly from a peer on the site network (see homeSiteNetwork) is enrolled at once. It must run in the host's network namespace on the node site address. It serves only POST HomeEnrollmentPath over TLS 1.3 with a step-ca issued server certificate, binds every proof to the enrollment URL of the node's own custody, never the client's Host, and reads no forwarded header.
func NewHomeHubServer ¶
func NewHomeHubServer(ctx context.Context, root, listen string, options HomeHubOptions) (*http.Server, error)
NewHomeHubServer is the Hub listener of the Authority: the Hub PWA and the device-facing identity routes, behind the core routers on the Hub host. It carries no node API key, MCP or lifecycle route.
func ObservedLANAddress ¶ added in v0.53.13
ObservedLANAddress classifies a transport peer address the server observed (a connection RemoteAddr): private (RFC 1918 / ULA) or link-local peers only, never loopback, never read from a request header or body. It narrows who may use the LAN enrollment listener; it never establishes home on its own, which only the listener the connection arrived on does.
func ParseHomeDeviceAuthorization ¶
ParseHomeDeviceAuthorization reads the device credential from exactly one "Authorization: DPoP <credential>" header and the proof from exactly one "DPoP" header. Any other shape is refused, never guessed.
func PendingHomeEnrollment ¶
func PendingHomeEnrollment(root, deviceID string, now time.Time) (HomeDevice, HomeStepUpAction, error)
PendingHomeEnrollment returns a pending entry and its enroll-confirm action for the Owner's confirmation page, only while the Owner is its confirmer.
func ReadHomeVPNSecret ¶
ReadHomeVPNSecret reads one secret of the selected tool from custody.
func ReadStaticAPIKey ¶
ReadStaticAPIKey returns the existing STATIC_API_KEY for a homelab without generating one when the file is missing. This supports reading immutable v0.6 artifacts during migration; current native generation uses owner custody.
func RecordHomeSiteNetwork ¶
RecordHomeSiteNetwork records the host's site network for the LAN enrollment listener of the Basement core. It runs on the host (Apply), where the site interface is visible, and fails when the node site address is on no interface of the host.
func RecoveryBundleName ¶
RecoveryBundleName is the file name of a node's set.
func RecoveryDeviceHandler ¶
func RecoveryDeviceHandler(root string, issue RecoveryPasskeyIssuer) http.Handler
RecoveryDeviceHandler serves the device recovery routes. The DPoP proof in the "DPoP" header is the authentication (no node API key); the body is {"code": "..."} for request and complete, empty for cancel, and the notices route is a GET.
func RecoveryNoticesHandler ¶
RecoveryNoticesHandler lists the recovery notices behind the node API key, for the State Console and the notice delivery of igti.6.
func RecoveryRecipient ¶
func RecoveryRecipient(root string) (*age.HybridRecipient, error)
RecoveryRecipient is the public recipient every recovery set is encrypted to. The Authority holds nothing that decrypts a set.
func RefreshHomeAccountGroups ¶
RefreshHomeAccountGroups reads the groups of every account that holds a device entry from the Authority's Pocket ID (host only: it needs the admin key) and writes the projection. A removed or disabled account has none.
func RegisterHomeHubRoutes ¶
func RegisterHomeHubRoutes(mux *http.ServeMux, root string, options HomeHubOptions)
RegisterHomeHubRoutes mounts the Hub shell, its configuration and code exchange, and every device-facing identity route on mux.
func RegisterHomeIdentityRoutes ¶
RegisterHomeIdentityRoutes mounts the device-facing identity routes:
GET HomeEnrollmentBootstrapPath the LAN enrollment bootstrap (no secret)
GET HomeDevicesPath the caller's devices; the Owner's devices see all
POST HomeDevicesPath/{id}/revoke revoke own device (Owner: any) through the chain
GET HomeNoticesPath enrollment notices of the caller's account (Owner: all)
POST HomeNoticesPath/{id}/undo undo an at-home enrollment or decline a pending one
POST /api/v1/identity/home/enrollments/{id}/confirm confirm a pending enrollment from an own device:
{"challenge", "stepUp"} with the person's passkey step-up
Every route but the bootstrap needs "Authorization: DPoP <credential>" and a DPoP proof for exactly its method and path.
func RenderHomeOverlayConfig ¶ added in v0.53.13
RenderHomeOverlayConfig renders the node's wg-quick interface configuration. It carries public keys and addresses only: the private key is loaded from local custody by PostUp. Peers of revoked or expired devices are omitted, so the next render and interface reload removes their access.
func ResetRecoveryAfterRestore ¶
ResetRecoveryAfterRestore runs when a set's custody was installed on a replacement Authority. The set may predate a cancellation, a new code or a device revocation, so it must not revive a pending device recovery, the failure count, or a code the Owner may since have replaced: the pending recovery ends and the restored code counts as used until the Owner issues a new one (stackkit break-glass code).
func RevokeHomeAppPass ¶
RevokeHomeAppPass revokes one pass of the actor's account (Owner: any).
func RotateRecoveryCode ¶
RotateRecoveryCode replaces the recovery code, for example after it was consumed, and returns the new code for a single display. A pending device recovery ends, because it was bound to the old code.
func SaveHomeVPNSelection ¶
func SaveHomeVPNSelection(root string, selection HomeVPNSelection) error
SaveHomeVPNSelection records the Owner's opt-in after checking it.
func ServeHomeAuthoritySigner ¶
ServeHomeAuthoritySigner runs the signer until ctx ends: the background duties and the socket. peerUID is the only account that may call it (the Basement core server's); root is the Authority's workspace with the Owner custody.
func SyncHomeVPNAnnouncement ¶
SyncHomeVPNAnnouncement projects the selected tool into the verification custody, or removes the projection while the add-on is off (host only).
func SyncRecoveryNoticeProjection ¶
SyncRecoveryNoticeProjection rewrites the notice projection from the recovery state (host only); without a state there is nothing to project.
func ValidHomeDeviceName ¶
ValidHomeDeviceName bounds the name a person gives a device: a lowercase DNS-label-like name, never a URL or principal.
func ValidateHomeVPNEndpoint ¶
ValidateHomeVPNEndpoint admits an Owner-declared host:port: a DNS name or an IP address and a port, nothing else.
func ValidateHomeVPNSelection ¶
func ValidateHomeVPNSelection(selection HomeVPNSelection) error
ValidateHomeVPNSelection checks an opt-in before anything is recorded.
func VerifyHomeStepUp ¶
func VerifyHomeStepUp(root string, action HomeStepUpAction, approver HomeStepUpApprover, challenge, idToken string, now time.Time) (string, error)
VerifyHomeStepUp accepts a step-up only when the ID token proves a passkey authentication (amr "phr") of the required approver, at most HomeStepUpWindow old, for exactly this action and challenge, against the current Owner-signed Home trust; it then records the step-up as used, so the same token never approves twice. It returns the approver's subject. While the Home trust is not current it fails closed.
func VerifyOwnerStepUp ¶ added in v0.54.8
VerifyOwnerStepUp verifies an ID token of the local Owner step-up client for an Owner decision that is not bound to a homelab identity action, such as answering a StackKits MCP agent's write approval request. It checks the active bound Home trust, Owner custody of this Home, the pinned Owner subject, signing keys, audience, the flow nonce, a passkey authentication, revocation and HomeOwnerStepUpFreshness, and returns the Owner subject. It changes no state.
func WriteHomeVPNSecret ¶
WriteHomeVPNSecret keeps one secret of the selected tool (an OAuth client, an API key, Newt's secret) in the add-on's custody, 0600.
Types ¶
type HomeActor ¶
HomeActor is who acts on registry entries: a device of a Homelab account (verified by its credential and proof), or the Owner, who is either a device of the Owner's account or the local CLI at the Authority.
type HomeAppPass ¶
type HomeAppPass struct {
ID string `json:"id"`
Host string `json:"host"`
Name string `json:"name"`
Account string `json:"account"`
Device string `json:"device"`
CreatedAt time.Time `json:"createdAt"`
ExpiresAt time.Time `json:"expiresAt"`
RevokedAt *time.Time `json:"revokedAt,omitempty"`
}
HomeAppPass is one pass, without its secret.
func IssueHomeAppPass ¶
func IssueHomeAppPass(root string, caller HomeAuthentication, host, name string, appHosts []string, now time.Time) (string, HomeAppPass, error)
IssueHomeAppPass issues a pass for host to the calling device, whose last passkey user verification must be at most 10 minutes old. host must be one the Owner opened to app passes. The secret is returned once.
func ListHomeAppPasses ¶
func ListHomeAppPasses(root string, actor HomeActor) ([]HomeAppPass, error)
ListHomeAppPasses returns the actor's passes (the Owner's devices: all).
type HomeAuthentication ¶
type HomeAuthentication struct {
Subject string `json:"subject"`
Device string `json:"device"`
CredentialID string `json:"credentialId"`
ExpiresAt time.Time `json:"expiresAt"`
UserVerifiedAt time.Time `json:"userVerifiedAt"`
}
HomeAuthentication is request-scoped admission of a Homelab account on an enrolled device, not privileged approval. Subject is the account (Pocket ID subject); Device is the device ID (key thumbprint).
func AuthenticateHomeVPNRequest ¶
func AuthenticateHomeVPNRequest(root string, r *http.Request, origins []string) (HomeAuthentication, error)
AuthenticateHomeVPNRequest admits a peer service request only with the current credential of an active device and a fresh, unused DPoP proof for exactly this method, path and one of the service's own origins (from custody, never the request's Host).
type HomeAuthoritySigner ¶
type HomeAuthoritySigner interface {
// IssueListenerCertificate has step-ca sign a certificate request for the
// LAN enrollment listener and returns the verified chain (PEM).
IssueListenerCertificate(ctx context.Context, csrDER []byte) ([]byte, error)
// RecoveryHandler serves the Owner device recovery routes at the signer.
RecoveryHandler() http.Handler
}
HomeAuthoritySigner is the core's view of the host-side signer.
func NewHomeAuthoritySignerClient ¶
func NewHomeAuthoritySignerClient(socketPath string) HomeAuthoritySigner
NewHomeAuthoritySignerClient is the core's client of the host-side signer.
type HomeBrowserSession ¶
type HomeBrowserSession struct {
Account string
Device string
Host string
Home bool
IssuedAt time.Time
ExpiresAt time.Time
}
HomeBrowserSession is a verified session of one device on one host.
func IssueHomeBrowserSession ¶
func IssueHomeBrowserSession(root string, caller HomeAuthentication, host string, home bool, now time.Time) (string, HomeBrowserSession, error)
IssueHomeBrowserSession mints the session value for a launch the Gate admitted: home when the launch came through the LAN listener.
type HomeConfirmation ¶
type HomeConfirmation struct {
JWKThumbprint string `json:"jkt"`
}
HomeConfirmation is the key binding of a device credential (RFC 7800).
type HomeDevice ¶ added in v0.53.13
type HomeDevice struct {
ID string `json:"id"`
Name string `json:"name"`
AccountSubject string `json:"accountSubject"`
Custody string `json:"custody"`
PublicKey jose.JSONWebKey `json:"publicKey"`
EnrolledAt time.Time `json:"enrolledAt"`
LastUsedAt time.Time `json:"lastUsedAt"`
LastUserVerifiedAt time.Time `json:"lastUserVerifiedAt"`
Approval HomeDeviceApproval `json:"approval"`
Revoked bool `json:"revoked"`
RevokedAt *time.Time `json:"revokedAt,omitempty"`
RevokedBy string `json:"revokedBy,omitempty"`
}
HomeDevice is the registry entry of one device key at the Authority. ID is the RFC 7638 thumbprint of the device-generated public key; the entry binds to one Homelab account (the Pocket ID subject of the person who enrolled it) and expires 180 days after its last use. It holds no private key or credential.
func ConfirmHomeEnrollmentByOwner ¶
func ConfirmHomeEnrollmentByOwner(root, deviceID, challenge, idToken string, now time.Time) (HomeDevice, error)
ConfirmHomeEnrollmentByOwner confirms a pending enrollment with the Owner's passkey step-up at the Authority (the Owner step-up client) for the entry's enroll-confirm action and challenge. The Owner confirms only for a person who has no enrolled device of their own; otherwise that device confirms.
func ListHomeDevices ¶ added in v0.53.13
func ListHomeDevices(root string) ([]HomeDevice, error)
ListHomeDevices returns every registry entry, newest first.
func (HomeDevice) Active ¶
func (d HomeDevice) Active(now time.Time) bool
Active reports whether the entry is approved, not revoked and not lapsed.
func (HomeDevice) ExpiresAt ¶
func (d HomeDevice) ExpiresAt() time.Time
ExpiresAt is when the entry lapses without another use.
type HomeDeviceApproval ¶
type HomeDeviceApproval struct {
Mode string `json:"mode"`
By string `json:"by,omitempty"`
At *time.Time `json:"at,omitempty"`
PendingUntil *time.Time `json:"pendingUntil,omitempty"`
}
HomeDeviceApproval records how an entry was admitted. A pending entry (an away enrollment) has PendingUntil and no credential until it is confirmed; By is the confirming device ID or the Owner subject.
type HomeDeviceCredential ¶
type HomeDeviceCredential struct {
Credential string `json:"credential"`
ExpiresAt time.Time `json:"expiresAt"`
UserVerificationDueAt time.Time `json:"userVerificationDueAt"`
Device HomeDevice `json:"device"`
}
HomeDeviceCredential is what enrollment and renewal return to the device. Credential is a bearer-shaped token bound to the device key: it must never be logged, and it is useless without a proof signed by that key.
type HomeDeviceRequest ¶
type HomeDeviceRequest struct {
Method string `json:"method"`
RouteOrigin string `json:"routeOrigin"`
Path string `json:"path"`
Credential string `json:"credential"`
Proof string `json:"proof"`
}
HomeDeviceRequest is one request to a service, as the server running the service observed it. RouteOrigin is the https origin of the matched route taken from the server's own route table, never from a request header; Path is the request target in origin form (a query is allowed and is not part of the proof). Credential is the token of "Authorization: DPoP <credential>", Proof the "DPoP" header (see ParseHomeDeviceAuthorization).
type HomeEnrollmentBootstrap ¶ added in v0.53.14
type HomeEnrollmentBootstrap struct {
EnrollmentURL string `json:"enrollmentUrl"`
HubEnrollmentURL string `json:"hubEnrollmentUrl,omitempty"`
TLSServerName string `json:"tlsServerName"`
CARootSHA256 string `json:"caRootSha256"`
URI string `json:"uri"`
// VPN is the VPN add-on's peer service while the Owner has it on.
VPN *HomeVPNAnnouncement `json:"vpn,omitempty"`
}
HomeEnrollmentBootstrap is what a device client needs to reach the Authority's LAN enrollment listener directly: its URL on the node site address, the server name of its certificate and the step-ca root fingerprint to pin. It contains no secret and grants nothing; the person's own passkey sign-in is the enrollment's credential.
HubEnrollmentURL is the same listener under the Hub host, which its certificate also names: the installed Hub PWA, a browser that cannot pin a root or set a server name, opens it from the home network where the Hub host resolves to the node site address. It is empty without an identity runtime address.
func LoadHomeEnrollmentBootstrap ¶ added in v0.53.14
func LoadHomeEnrollmentBootstrap(root string) (HomeEnrollmentBootstrap, error)
LoadHomeEnrollmentBootstrap derives the bootstrap from the Authority's public record: the node site address the LAN resolver custody binds and the established step-ca root. It fails closed when either is missing or the address is not a LAN address.
type HomeEnrollmentNotice ¶
type HomeEnrollmentNotice struct {
ID string `json:"id"`
Kind string `json:"kind"`
AccountSubject string `json:"accountSubject"`
DeviceID string `json:"deviceId"`
DeviceName string `json:"deviceName"`
Custody string `json:"custody"`
CreatedAt time.Time `json:"createdAt"`
ActionUntil time.Time `json:"actionUntil"`
NotifyDevices []string `json:"notifyDevices"`
NotifyOwner bool `json:"notifyOwner"`
Confirmer string `json:"confirmer,omitempty"`
StepUp *HomeStepUpAction `json:"stepUp,omitempty"`
State string `json:"state"`
ResolvedAt *time.Time `json:"resolvedAt,omitempty"`
ResolvedBy string `json:"resolvedBy,omitempty"`
}
HomeEnrollmentNotice tells the person's other devices and the Owner about one enrollment. An at-home notice offers the undo until ActionUntil; a pending notice asks for the confirmation until ActionUntil, by the Confirmer (computed when it is listed). StepUp is the action a confirmation step-up approves. NotifyDevices are the person's devices that were enrolled when the notice was created.
func ListHomeEnrollmentNotices ¶
func ListHomeEnrollmentNotices(root string, actor HomeActor, now time.Time) ([]HomeEnrollmentNotice, error)
ListHomeEnrollmentNotices returns the notices visible to actor, newest first: those of the actor's own account and, for the Owner, every notice. A notice is open only while its action window lasts and its device is still in the state the notice is about; Confirmer reflects the current registry.
type HomeEnrollmentOutcome ¶
type HomeEnrollmentOutcome struct {
Status string `json:"status"`
Device HomeDevice `json:"device"`
Credential *HomeDeviceCredential `json:"credential,omitempty"`
Notice HomeEnrollmentNotice `json:"notice"`
// VPN announces the VPN add-on's peer service to a device that received
// its credential, so it asks for its peer in the same enrollment.
VPN *HomeVPNAnnouncement `json:"vpn,omitempty"`
}
HomeEnrollmentOutcome is the answer to a submission: at home the active entry and its first credential, away the pending entry. Notice is the record the person's other devices and the Owner receive.
type HomeEnrollmentPolicy ¶
type HomeEnrollmentPolicy struct {
MaxDevicesPerAccount int
RateLimitAttempts int
RateLimitWindow time.Duration
CustodyClasses []string
}
HomeEnrollmentPolicy is the runtime form of the applied plan's #DeviceEnrollmentPolicy (identity.deviceEnrollment): the per-account device cap, the per-account rate limit of enrollment submissions and the admitted device key custody classes. Every other field of the contract is fixed and checked, never configurable here.
func LoadHomeEnrollmentPolicy ¶
func LoadHomeEnrollmentPolicy(root string) (HomeEnrollmentPolicy, error)
LoadHomeEnrollmentPolicy reads identity.deviceEnrollment from the applied plan and admits it only when it is exactly the person enrollment of the homelab identity target within the contract's bounds; anything else fails closed with ErrHomeEnrollmentUnavailable.
type HomeEnrollmentSubmission ¶
type HomeEnrollmentSubmission struct {
Name string
Custody string
Proof string
Person string
Home bool
Targets []string
}
HomeEnrollmentSubmission is one enrollment as the server observed it. Proof is a DPoP proof (htm POST) by the device-generated key; the key's thumbprint becomes the device ID. Person is a Pocket ID ID token of the enrolling person from a passkey sign-in through HomeDeviceClientID with the nonce HomeDeviceEnrollmentNonce(thumbprint, proof jti). Home is set only by the LAN enrollment listener, for a connection it accepted directly; Targets are then that listener's enrollment URLs from the server's own custody (the site address and the Hub host on the listener port), one of which the proof must bind.
type HomeHubOptions ¶
type HomeHubOptions struct {
// Signer is the host-side identity signer. Without it the recovery routes
// answer recovery_unavailable.
Signer HomeAuthoritySigner
// AppHosts are the hosts with a route the Owner opened to app passes away
// (the Access Gate's table); the Hub issues app passes for these only.
AppHosts []string
// LaunchHosts are the hosts a browser session opens (user routes of the
// Access Gate's table); the Hub signs launch proofs for these only.
LaunchHosts []string
}
HomeHubOptions wire the Hub to what its process does not hold itself.
type HomeLaunch ¶
HomeLaunch is one launch as the Gate observed it: the gated host the browser came to, the credential and proof the Hub put into the launch URL, and the state of the Gate's own launch cookie on that host.
type HomeOverlayNode ¶ added in v0.53.13
type HomeOverlayNode struct {
InterfaceName string `json:"interfaceName"`
PublicKey string `json:"publicKey"`
Address string `json:"address"`
ListenPort int `json:"listenPort"`
KeepaliveSeconds int `json:"persistentKeepaliveSeconds"`
PrivateKeyInCustody bool `json:"privateKeyInLocalCustody"`
}
HomeOverlayNode is what a device needs to configure its side of the tunnel. The endpoint stays owner-declared and is not part of this record.
func EnsureHomeOverlayNodeKey ¶ added in v0.53.13
func EnsureHomeOverlayNodeKey(root string) (HomeOverlayNode, error)
EnsureHomeOverlayNodeKey generates the node's WireGuard private key into local custody on first use and returns the node's public overlay identity. The private key is never returned.
type HomeOverlayPeer ¶ added in v0.53.13
type HomeOverlayPeer struct {
DeviceID string `json:"deviceId"`
PublicKey string `json:"publicKey"`
Address string `json:"address"`
}
HomeOverlayPeer is the public peer record of one enrolled device. It holds only the device's WireGuard public key and its overlay address.
func BindHomeOverlayPeer ¶ added in v0.53.13
func BindHomeOverlayPeer(root, deviceID, publicKey string, now time.Time) (HomeOverlayPeer, error)
BindHomeOverlayPeer registers the device-generated WireGuard public key of an active enrolled device and assigns it one overlay address. Rebinding the same device replaces its key and keeps its address. The device is checked under the peer registry lock, which every revocation also takes after it revoked the entry, so a peer of a revoked device never survives.
type HomeOwnerStepUpRequest ¶
type HomeOwnerStepUpRequest struct {
ID string `json:"id"`
Summary string `json:"summary"`
Action HomeStepUpAction `json:"stepUp"`
Challenge string `json:"challenge"`
CreatedAt time.Time `json:"createdAt"`
ExpiresAt time.Time `json:"expiresAt"`
IDToken string `json:"idToken,omitempty"`
}
HomeOwnerStepUpRequest is one pending console action awaiting the Owner's passkey step-up. Summary is the text the Hub shows the Owner.
func RequestHomeOwnerStepUp ¶
func RequestHomeOwnerStepUp(root string, action HomeStepUpAction, summary string, now time.Time) (HomeOwnerStepUpRequest, error)
RequestHomeOwnerStepUp records a console action for the Owner's approval.
type HomePocketIDTrust ¶
type HomePocketIDTrust struct {
Issuer string `json:"issuer"`
ClientID string `json:"clientId"`
Subject string `json:"subject"`
Keys jose.JSONWebKeySet `json:"keys"`
}
HomePocketIDTrust is the live PocketID OIDC authority. The CUE human issuer URN cannot alias this HTTPS issuer or its OIDC client audience. ClientID is always HomeDeviceClientID, the device sign-in client the pin was bound with.
type HomePublicTrust ¶
type HomePublicTrust struct {
Schema string `json:"schema"`
HomeSiteRef string `json:"homeSiteRef"`
PocketID HomePocketIDTrust `json:"pocketId"`
RevokedSubjects []string `json:"revokedSubjects"`
IssuedAt time.Time `json:"issuedAt"`
ExpiresAt time.Time `json:"expiresAt"`
Signature localevidence.OwnerPolicyStateSignature `json:"signature"`
}
HomePublicTrust is the Owner-signed Pocket ID pin of this Home: issuer, signing keys, the Owner subject and the revoked Homelab accounts. It backs every passkey verification (enrollment, renewal user verification, every step-up). RefreshHomePublicTrust binds it in production. Device credentials are verified against the device issuer and registry, not against this pin.
func RefreshHomePublicTrust ¶
func RefreshHomePublicTrust(ctx context.Context, root string, now time.Time) (HomePublicTrust, error)
RefreshHomePublicTrust is the production path of the Owner-signed Home trust. It registers the device sign-in client in the local Pocket ID on first use (or verifies the registered one), reads the live issuer, signing keys and Owner subject, and binds them as the Home trust for 24 hours. The new trust carries every account the previous trust revoked and every account the removal chain recorded, so a refresh never readmits one.
type HomeRenewalRequest ¶
HomeRenewalRequest is one credential renewal at the Authority. Proof is a DPoP proof (htm POST, htu https://<host>HomeDeviceRenewalPath) signed by the enrolled device key. UserVerification is optional until the last user verification is 24 hours old: then it must be a Pocket ID ID token of the device's account from a passkey sign-in through HomeDeviceClientID with the nonce HomeDeviceUserVerificationNonce(thumbprint, proof jti).
type HomeRevocation ¶
type HomeRevocation struct {
Devices []HomeDevice `json:"devices"`
Artifacts []HomeRevokedArtifact `json:"artifacts"`
}
HomeRevocation is the outcome of one revocation: the revoked entries and one line per revocation-registry artifact.
func RevokeHomeAccount ¶
func RevokeHomeAccount(root, account string, now time.Time) (HomeRevocation, error)
RevokeHomeAccount is the local part of removing a member: every device of the account (active, pending or lapsed) is revoked with all it holds, and the account joins the revoked accounts of the Owner-signed Home trust, so no passkey assertion of it is accepted again. The Owner's account is refused. Deleting the Pocket ID user is the caller's next step.
func RevokeHomeDevice ¶ added in v0.53.13
func RevokeHomeDevice(root, deviceID string, actor HomeActor, now time.Time) (HomeRevocation, error)
RevokeHomeDevice permanently revokes one device key for actor: a person revokes only their own account's devices, the Owner any device. A device of another account is reported as not found. Revocation only removes access, so it needs no fresh step-up; the key can never enroll again.
func UndoHomeEnrollment ¶
func UndoHomeEnrollment(root, noticeID string, actor HomeActor, now time.Time) (HomeRevocation, error)
UndoHomeEnrollment undoes an enrollment within its notice's action window: it revokes the enrolled device (an at-home enrollment within its 24 hour undo, or a pending one it declines) through the revocation chain. The person's devices and the Owner may undo; nobody else sees the notice.
type HomeRevokedArtifact ¶
type HomeRevokedArtifact struct {
ID string `json:"id"`
Status string `json:"status"`
Detail string `json:"detail"`
}
HomeRevokedArtifact is what the chain did for one artifact. Status is "revoked", "removed", "none-issued" (no code issues this artifact yet) or "not-current" (the step could not run and the detail says what remains).
type HomeStepUpAction ¶
type HomeStepUpAction struct {
Class HomeStepUpClass `json:"class"`
Digest string `json:"digest"`
}
HomeStepUpAction is the one action a step-up approves: its class and the SHA-256 digest (lowercase hex) of the action's canonical JSON.
func NewHomeStepUpAction ¶
func NewHomeStepUpAction(class HomeStepUpClass, action any) (HomeStepUpAction, error)
NewHomeStepUpAction derives the action digest from the class and the action's JSON encoding. The caller passes the exact object it will act on, so a step-up for one action can never approve another.
func (HomeStepUpAction) Nonce ¶
func (a HomeStepUpAction) Nonce(challenge string) string
Nonce is the OIDC nonce of the passkey sign-in that approves this action: base64url(SHA-256("stackkit.homelab-step-up/v1" 0x00 class 0x00 digest 0x00 challenge)). The challenge is a fresh random value (32 to 128 base64url characters) of the approving flow, so a failed attempt can be retried with a new sign-in; every nonce is accepted once.
type HomeStepUpApprover ¶
HomeStepUpApprover is who must approve an action: the pinned Owner through the Owner step-up client at the Authority, or one person (Account) through the device sign-in client on their own enrolled device.
type HomeStepUpClass ¶
type HomeStepUpClass string
Step-up classes of the homelab identity target (§6.2, §10, §12). An act on homelab identity is a passkey step-up valid for 60 seconds and bound to the digest of the one action it approves; each step-up is accepted once.
const ( // HomeStepUpEnrollConfirm confirms an away enrollment. HomeStepUpEnrollConfirm HomeStepUpClass = "enroll-confirm" // HomeStepUpJoin approves a further server joining the Authority (P2). HomeStepUpJoin HomeStepUpClass = "join" // HomeStepUpClientRegistration approves an OIDC client registration at // the Authority's Pocket ID for another server or an app. HomeStepUpClientRegistration HomeStepUpClass = "client-registration" // HomeStepUpCustodyChange approves a change of identity custody (recovery // set, recovery code, Owner passkey reset). HomeStepUpCustodyChange HomeStepUpClass = "custody-change" // HomeStepUpWindow is #HomelabIdentityLifetimesV1.ownerStepUpSeconds: the // passkey authentication must be at most this old when it is presented. HomeStepUpWindow = 60 * time.Second // HomeOwnerStepUpFreshness bounds every Owner passkey step-up. HomeOwnerStepUpFreshness = HomeStepUpWindow )
type HomeVPNAnnouncement ¶
type HomeVPNAnnouncement struct {
Tool string `json:"tool"`
PeerService string `json:"peerService"`
TLSServerName string `json:"tlsServerName"`
}
HomeVPNAnnouncement is where an enrolled device asks for its VPN peer.
type HomeVPNLiveNode ¶
type HomeVPNLiveNode struct {
Ref string `json:"ref"`
KeyRef string `json:"keyRef,omitempty"`
Addresses []string `json:"addresses,omitempty"`
CreatedAt time.Time `json:"createdAt"`
}
HomeVPNLiveNode is one node the tool's coordination holds under the add-on's device tag (or, for Pangolin, one of the add-on's clients).
type HomeVPNPangolinSelection ¶
type HomeVPNPangolinSelection struct {
Endpoint string `json:"endpoint"`
API string `json:"api"`
OrgID string `json:"orgId"`
SiteID int `json:"siteId"`
NewtID string `json:"newtId"`
ResourceID int `json:"resourceId,omitempty"`
}
HomeVPNPangolinSelection names the Owner's Pangolin server (the origin Newt and the devices' Olm connect to), its Integration API, the organization, the Newt site of this Authority and the site address resource the add-on created there.
type HomeVPNPeer ¶
type HomeVPNPeer struct {
DeviceID string `json:"deviceId"`
Tool string `json:"tool"`
State string `json:"state"`
KeyRef string `json:"keyRef,omitempty"`
NodeRef string `json:"nodeRef,omitempty"`
Addresses []string `json:"addresses,omitempty"`
IssuedAt time.Time `json:"issuedAt"`
BoundAt *time.Time `json:"boundAt,omitempty"`
}
HomeVPNPeer is one device's peer of a coordinated tool. NodeRef is the tool's node (Headscale node ID, Tailscale device ID, Pangolin client ID), KeyRef its join key. Neither is a secret.
func BindHomeVPNPeer ¶
func BindHomeVPNPeer(root, deviceID, tool string, node HomeVPNLiveNode, now time.Time) (HomeVPNPeer, error)
BindHomeVPNPeer binds a coordinated node to the active device whose probe arrived from it through the VPN. A node bound to another device, a node joined with another device's key and a device without a current peer are refused.
func HomeVPNPeerOf ¶
func HomeVPNPeerOf(root, deviceID, tool string) (HomeVPNPeer, error)
HomeVPNPeerOf returns the device's current peer of a tool.
func IssueHomeVPNPeer ¶
func IssueHomeVPNPeer(root, deviceID, tool string, now time.Time, issue func() (HomeVPNPeer, error)) (HomeVPNPeer, error)
IssueHomeVPNPeer gives an active device a new join key of a coordinated tool. The device is checked under the ledger lock and issue runs only for an active device; a peer the device held before is marked for removal, so a device holds at most one peer.
func ListHomeVPNPeers ¶
func ListHomeVPNPeers(root string) ([]HomeVPNPeer, error)
ListHomeVPNPeers returns the ledger.
func RemoveAllHomeVPNPeers ¶
func RemoveAllHomeVPNPeers(root, tool string) ([]HomeVPNPeer, error)
RemoveAllHomeVPNPeers marks every peer of a tool for removal (the Owner switches the add-on off) and returns them.
type HomeVPNRemovals ¶
type HomeVPNRemovals struct {
Nodes []HomeVPNLiveNode `json:"nodes"`
Keys []string `json:"keys"`
Settled []HomeVPNPeer `json:"settled"`
}
HomeVPNRemovals is what the tool must delete now: nodes and join keys. Settled are the ledger entries to forget once both succeeded.
func ReconcileHomeVPNPeers ¶
func ReconcileHomeVPNPeers(root, tool string, live []HomeVPNLiveNode, now time.Time) (HomeVPNRemovals, error)
ReconcileHomeVPNPeers compares the tool's live nodes with the registry. It binds a node that joined with a device's own key, marks the peers of inactive devices and expired join keys for removal, and returns every node and key the tool must delete now: those of entries being removed, nodes no active device bound within the grace, and, because a Tailscale node does not name its key, every unbound node that appeared after a removed device's unbound key was issued.
type HomeVPNSelection ¶
type HomeVPNSelection struct {
Schema string `json:"schema"`
Tool string `json:"tool"`
EnabledAt time.Time `json:"enabledAt"`
// Endpoint is the Owner-declared host:port devices dial from away: the
// WireGuard UDP endpoint, or the peer service that carries Headscale's
// control endpoint. Tailscale and Pangolin have none.
Endpoint string `json:"endpoint,omitempty"`
Tailscale *HomeVPNTailscaleSelection `json:"tailscale,omitempty"`
Pangolin *HomeVPNPangolinSelection `json:"pangolin,omitempty"`
}
HomeVPNSelection is the Owner's opt-in: the one tool and what it needs that is not secret. Secrets of a tool (an OAuth client, an API key, Newt's secret) are separate custody files of the tool, never in this record.
func LoadHomeVPNSelection ¶
func LoadHomeVPNSelection(root string) (HomeVPNSelection, error)
LoadHomeVPNSelection reads the Owner's opt-in; ErrHomeVPNOff without one.
type HomeVPNTailscaleSelection ¶
type HomeVPNTailscaleSelection struct {
Tailnet string `json:"tailnet"`
}
HomeVPNTailscaleSelection names the Owner's tailnet.
type HomeVerifier ¶
type HomeVerifier struct {
// contains filtered or unexported fields
}
HomeVerifier verifies device credentials and DPoP proofs for the services of this server and renews credentials on the Authority. It keeps the replay state of every proof it accepted until the proof's window has passed, so one long-lived verifier serves a process; use HomeVerifierFor. A proof whose time, less the clock leeway, is not after the verifier's start is refused, so a restart cannot reopen a replay; the cost is that proofs of the first seconds after a start are refused and the client retries.
func HomeVerifierFor ¶
func HomeVerifierFor(root string) (*HomeVerifier, error)
HomeVerifierFor returns the process-wide verifier of a workspace.
func (*HomeVerifier) Authenticate ¶
func (v *HomeVerifier) Authenticate(request HomeDeviceRequest) (HomeAuthentication, error)
Authenticate admits one request only when its credential is a current credential of an active registry entry and its proof is a fresh, unused DPoP proof signed by the credential's key for exactly this method, route origin and path. Every refusal is ErrHomeAccessDenied. While the registry cannot be refreshed, issued credentials stay valid to their lifetime against the last-known state; a device the verifier has never seen stays refused.
func (*HomeVerifier) AuthenticateLaunch ¶
func (v *HomeVerifier) AuthenticateLaunch(launch HomeLaunch) (HomeAuthentication, error)
AuthenticateLaunch admits one launch proof: signed by the key of an active device whose current credential it presents (ath), for GET on exactly this host's launch path, at most 60 seconds old, used once, and bound to the Gate's launch state and the target on this host. A state the Gate did not set in this browser is refused, so nobody can log a browser in as someone else.
func (*HomeVerifier) ConfirmEnrollmentFromDevice ¶
func (v *HomeVerifier) ConfirmEnrollmentFromDevice(caller HomeAuthentication, deviceID, challenge, idToken string) (HomeDevice, error)
ConfirmEnrollmentFromDevice confirms the pending enrollment deviceID from an enrolled device: one of the same person's (the confirmer while the person has one), or else one of the Owner's (D-2026-11-02-11: own device first, the Owner otherwise). caller is the result of Authenticate for the confirming request; idToken is the confirmer's passkey step-up through HomeDeviceClientID for the entry's enroll-confirm action and challenge.
func (*HomeVerifier) Enroll ¶
func (v *HomeVerifier) Enroll(submission HomeEnrollmentSubmission) (HomeEnrollmentOutcome, error)
Enroll decides one enrollment submission. It verifies the proof of possession and the person's fresh passkey sign-in against the current Home trust, applies the rate limit and the device cap of the applied #DeviceEnrollmentPolicy, and records the entry: active with its first credential at home, pending away. Every failure before the decision is ErrHomeAccessDenied; the decision's own refusals are their named errors. It fails closed whenever the trust, the registry or the policy is not current (partition rule).
func (*HomeVerifier) RenewDeviceCredential ¶
func (v *HomeVerifier) RenewDeviceCredential(request HomeRenewalRequest) (HomeDeviceCredential, error)
RenewDeviceCredential signs a new one-hour credential for an active registry entry whose key signed the proof. After 24 hours without user verification it returns ErrHomeUserVerificationRequired unless the request carries a valid passkey assertion; every other refusal is ErrHomeAccessDenied. It runs only on the Authority (the holder of the device issuer key and registry) and fails closed whenever the registry, or for a user verification the Pocket ID pin, is not current. A key that is not an active registry entry is refused before it reaches replay state or the registry lock.
func (*HomeVerifier) VerifyHomeAppPass ¶
func (v *HomeVerifier) VerifyHomeAppPass(secret, host string) (HomeAppPass, error)
VerifyHomeAppPass admits a pass only for the host it was issued for, before its end, unrevoked, and while its device is an active registry entry of its account.
func (*HomeVerifier) VerifyHomeBrowserSession ¶
func (v *HomeVerifier) VerifyHomeBrowserSession(value, host string, remote bool) (HomeBrowserSession, error)
VerifyHomeBrowserSession admits a session value only for the host it was issued for, before its end, and while its device is an active registry entry of its account, so revoking the device ends its sessions within the registry refresh. Away (remote) a home session counts only for the away lifetime.
type OwnerProvisioner ¶
type OwnerProvisioner struct {
// Client is the PocketID admin-API client (real or fake).
Client PocketIDClient
// PocketIDURL is the public origin of the PocketID instance, used to
// build SetupURL. Example: "https://id.example.com" (no trailing slash).
PocketIDURL string
// OwnersGroup is the name of the group new owners are added to.
// Defaults to "owners" when empty.
OwnersGroup string
}
OwnerProvisioner creates the owner record in PocketID and adds them to the owners group, then issues a one-time-access token for WebAuthn enrollment.
func (*OwnerProvisioner) Provision ¶
func (p *OwnerProvisioner) Provision(ctx context.Context, spec OwnerSpec) (*ProvisionResult, error)
Provision creates the owner user in PocketID, adds them to the owners group, and returns a ProvisionResult containing the user's UUID and a setup URL the owner clicks once to enroll a WebAuthn credential.
Source=="local" is the only locally provisioned path. Source=="cloud" returns an orchestrator-managed error. Any other Source value is rejected as invalid.
type OwnerSpec ¶
type OwnerSpec struct {
// Source controls the provisioning path. This local provisioner supports
// "local" only; "cloud" is handled by TechStack/kombify Cloud.
Source string
// Email is the owner's address (also used as the WebAuthn account label).
Email string
// Username is the PocketID login handle.
Username string
// DisplayName is what PocketID renders in the UI; defaults to Username
// when empty. PocketID v2 stores this in the FirstName column on the
// underlying user record.
DisplayName string
// ForeignSubjectID is the external IdP subject for Source=="cloud".
// Ignored when Source=="local".
ForeignSubjectID string
}
OwnerSpec describes the daily-admin owner of a homelab instance.
type PocketIDClient ¶
type PocketIDClient interface {
CreateUser(ctx context.Context, req pocketid.CreateUserRequest) (*pocketid.User, error)
AddUserToGroup(ctx context.Context, userID, groupID string) error
GetGroupIDByName(ctx context.Context, groupName string) (string, error)
CreateOneTimeAccessToken(ctx context.Context, userID string, ttl time.Duration) (string, error)
}
PocketIDClient is the subset of pocketid.Client used by provisioners. Defined as an interface so tests can supply fakes without standing up an HTTP server.
type ProvisionResult ¶
type ProvisionResult struct {
// UserID is the PocketID-assigned UUID for the new owner record.
UserID string
// SetupURL is the one-time link the owner clicks to enroll a WebAuthn
// credential. It embeds a single-use token with a 24-hour TTL. Local
// source only.
SetupURL string
}
ProvisionResult is what Provision returns on success.
type RecoveryDeviceRequest ¶
RecoveryDeviceRequest is a recovery call by an enrolled Owner device: a DPoP proof by its registry key for the exact route, and the recovery code where the route takes one. No device credential is needed: the device may no longer pass a user verification once the passkey is gone.
type RecoveryNotice ¶
type RecoveryNotice struct {
ID string `json:"id"`
Kind string `json:"kind"`
Account string `json:"account"`
Devices []string `json:"devices"`
Origin string `json:"origin"`
RequestID string `json:"requestId,omitempty"`
EligibleAt *time.Time `json:"eligibleAt,omitempty"`
CreatedAt time.Time `json:"createdAt"`
}
RecoveryNotice is one recovery event addressed to the Owner devices that were enrolled when it happened. Delivery (Companion Feed, Hub Web Push, State Console) reads these records; they hold no secret.
func DeviceRecoveryNotices ¶
func DeviceRecoveryNotices(root string, request RecoveryDeviceRequest, now time.Time) ([]RecoveryNotice, error)
DeviceRecoveryNotices returns, newest first, the notices addressed to the enrolled Owner device whose key signed the proof (GET with DPoP), so an Owner device learns of a recovery started elsewhere within its 24 hours.
func ListRecoveryNotices ¶
func ListRecoveryNotices(root string) ([]RecoveryNotice, error)
ListRecoveryNotices returns the recovery notices, newest first. This is the record igti.6 delivers to every listed Owner device.
The recovery state stays in the host's custody; its notices, which hold no secret, are projected into the verification custody (recoveryNoticesProjectionPath) on every write, so the core's Hub delivers them by Web Push and lists them to Owner devices without reading the state.
type RecoveryPasskeyIssuer ¶
type RecoveryPasskeyIssuer func(ctx context.Context, subject string) (RecoveryPasskeyLink, error)
RecoveryPasskeyIssuer mints the one-time link for the bound Owner subject.
func LocalRecoveryPasskeyIssuer ¶
func LocalRecoveryPasskeyIssuer(root string) RecoveryPasskeyIssuer
LocalRecoveryPasskeyIssuer mints the Owner's one-time Pocket ID link through the Authority's local Pocket ID admin API, for the public identity origin.
type RecoveryPasskeyLink ¶
type RecoveryPasskeyLink struct {
URL string `json:"passkeyRegistrationUrl"`
ExpiresAt time.Time `json:"expiresAt"`
}
RecoveryPasskeyLink is the Pocket ID one-time link a redeemed code yields.
func CompleteDeviceRecovery ¶
func CompleteDeviceRecovery(ctx context.Context, root string, request RecoveryDeviceRequest, issue RecoveryPasskeyIssuer, now time.Time) (RecoveryPasskeyLink, error)
CompleteDeviceRecovery finishes a pending device recovery: the same device key, the same valid code, no earlier than 24 hours after the request and before it expired. The code is consumed only after the link exists.
func RedeemRecoveryCodeAtConsole ¶
func RedeemRecoveryCodeAtConsole(ctx context.Context, root, code string, issue RecoveryPasskeyIssuer, now time.Time) (RecoveryPasskeyLink, error)
RedeemRecoveryCodeAtConsole is the local console path: a valid, unconsumed code yields the Owner's passkey registration link at once. Any pending device recovery ends. The code is consumed only after the link exists.
type RecoveryRequest ¶
type RecoveryRequest struct {
ID string `json:"id"`
Device string `json:"device"`
Account string `json:"account"`
RequestedAt time.Time `json:"requestedAt"`
EligibleAt time.Time `json:"eligibleAt"`
ExpiresAt time.Time `json:"expiresAt"`
}
RecoveryRequest is a pending recovery started from an enrolled Owner device.
func RequestDeviceRecovery ¶
func RequestDeviceRecovery(root string, request RecoveryDeviceRequest, now time.Time) (RecoveryRequest, error)
RequestDeviceRecovery starts the delayed path from an enrolled Owner device: the proof's key must be an active registry entry of the Owner's account and the code must be valid. The recovery becomes eligible after 24 hours; every Owner device receives a notice and the console or any Owner device can cancel it meanwhile.
type RecoverySecrets ¶
type RecoverySecrets struct {
// RecoveryKey decrypts every recovery set ("AGE-SECRET-KEY-PQ-1...").
RecoveryKey string `json:"recoveryKey"`
// RecoveryCode registers a new Owner passkey once.
RecoveryCode string `json:"recoveryCode"`
// Recipient is the public half the Authority keeps to write sets.
Recipient string `json:"recipient"`
}
RecoverySecrets are shown to the Owner once and never persisted.
func SetupRecovery ¶
SetupRecovery creates the recovery key and code once and returns both for a single display; only the public recipient and a slow verifier of the code are stored. With rotate it replaces an existing key and code (the Owner lost one of them but still runs the Authority): every pending device recovery ends and sets written before stay readable only with the old key.
type RecoverySetEntry ¶
type RecoverySetEntry struct {
Path string `json:"path"`
Dir bool `json:"dir,omitempty"`
Mode uint32 `json:"mode"`
UID int `json:"uid"`
GID int `json:"gid"`
Size int64 `json:"size"`
SHA256 string `json:"sha256,omitempty"`
}
RecoverySetEntry is one file or directory of the set.
type RecoverySetManifest ¶
type RecoverySetManifest struct {
Schema string `json:"schema"`
CreatedAt time.Time `json:"createdAt"`
OwnerRef string `json:"ownerRef"`
SiteRef string `json:"siteRef"`
NodeRef string `json:"nodeRef"`
IdentityOrigin string `json:"identityOrigin"`
Recipient string `json:"recipient"`
Files []RecoverySetEntry `json:"files"`
Volumes []RecoverySetVolume `json:"volumes"`
}
RecoverySetManifest describes one set. It holds no secret.
func WriteRecoverySet ¶
func WriteRecoverySet(root string, volumes []RecoveryVolumeSource, out io.Writer, now time.Time) (RecoverySetManifest, error)
WriteRecoverySet streams the Authority's identity custody and the given runtime volumes as one encrypted set to out. The Pocket ID volume is required; the step-ca database exists on kits that run step-ca.
type RecoverySetRestore ¶
type RecoverySetRestore struct {
Manifest RecoverySetManifest
// contains filtered or unexported fields
}
RecoverySetRestore is an opened, fully verified set staged below the workspace. The caller places its volumes into fresh Docker volumes, then installs its custody. Close removes the stage.
func OpenRecoverySet ¶
func OpenRecoverySet(root string, encrypted io.Reader, recoveryKey string) (*RecoverySetRestore, error)
OpenRecoverySet opens a set with the Owner's recovery key and verifies it completely (age decryption, manifest MAC, every digest, the staged Owner custody and identity origin) without changing the workspace. It refuses a workspace that holds another Owner or another identity origin.
func (*RecoverySetRestore) Close ¶
func (r *RecoverySetRestore) Close() error
Close removes the staged volumes.
func (*RecoverySetRestore) InstallCustody ¶
func (r *RecoverySetRestore) InstallCustody() error
InstallCustody installs the set's custody into the workspace, keeping the identity origin: the restored runtime custody carries the same Pocket ID host, so passkeys registered for it keep working. The restored recovery state ends any pending device recovery and marks the code used (ResetRecoveryAfterRestore). Installing the same set again is allowed.
func (*RecoverySetRestore) PlaceVolume ¶
func (r *RecoverySetRestore) PlaceVolume(name, target string) error
PlaceVolume copies one staged volume into an empty target directory (the mountpoint of a freshly created Docker volume), keeping modes, owners when running as root, and sparse regions.
type RecoverySetVolume ¶
type RecoverySetVolume struct {
Name string `json:"name"`
DockerVolume string `json:"dockerVolume"`
Entries []RecoverySetEntry `json:"entries"`
}
RecoverySetVolume is one Docker volume of the identity runtime.
type RecoveryStatus ¶
type RecoveryStatus struct {
Configured bool `json:"configured"`
Recipient string `json:"recipient,omitempty"`
ConfiguredAt time.Time `json:"configuredAt,omitzero"`
CodeIssuedAt time.Time `json:"codeIssuedAt,omitzero"`
CodeConsumed bool `json:"codeConsumed"`
Pending *RecoveryRequest `json:"pending,omitempty"`
}
RecoveryStatus is the secret-free recovery state of the Authority.
func LoadRecoveryStatus ¶
func LoadRecoveryStatus(root string) (RecoveryStatus, error)
LoadRecoveryStatus reads the secret-free recovery state.
type RecoveryVolumeSource ¶
RecoveryVolumeSource is one identity runtime volume read from its host directory while its writer is paused.
Source Files
¶
- breakglass.go
- bundle.go
- home_account_groups.go
- home_app_pass.go
- home_authority.go
- home_browser_session.go
- home_device.go
- home_device_credential.go
- home_enrollment.go
- home_enrollment_policy.go
- home_hub.go
- home_identity_http.go
- home_overlay.go
- home_person_enrollment.go
- home_push.go
- home_revocation.go
- home_signer.go
- home_signer_peer.go
- home_signer_peer_linux.go
- home_site_network.go
- home_step_up.go
- home_step_up_requests.go
- home_trust_binding.go
- home_verifier.go
- home_verifier_http.go
- home_vpn_addon.go
- home_vpn_announcement.go
- owner.go
- static_api_key.go