identity

package
v1.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 12, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

Documentation

Overview

Package identity implements the registry's identity, key-lifecycle, and identity-provider handlers. It is extracted from pkg/registry/server as part of the R2.3 registry decomposition.

Thread safety: all exported methods are safe for concurrent use.

Index

Constants

View Source
const JwksCacheTTL = 5 * time.Minute

JwksCacheTTL is the default TTL for cached JWKS keys.

Variables

View Source
var (
	LoadBlueprint     = wire.LoadBlueprint
	ValidateBlueprint = wire.ValidateBlueprint
)

LoadBlueprint and ValidateBlueprint are re-exported for callers that historically imported them from pkg/registry/server.

View Source
var ErrKeyRotatedConcurrently = fmt.Errorf("rotate_key: key rotated concurrently, retry")

ErrKeyRotatedConcurrently is returned by NodeView.UpdateNodeKey when a concurrent rotation landed between Phase 1 (snapshot) and Phase 3 (commit).

Functions

func DecodeJWT

func DecodeJWT(token string) (*JwtHeader, *JwtClaims, string, error)

func HashOwner

func HashOwner(owner string) string

HashOwner returns a truncated SHA-256 hash of the owner for safe logging.

func ValidateJWTClaims

func ValidateJWTClaims(claims *JwtClaims, expectedIssuer, expectedAudience string) error

func VerifyJWTSignatureHS256

func VerifyJWTSignatureHS256(signingInput string, signatureB64 string, secret []byte) error

func VerifyJWTSignatureRS256

func VerifyJWTSignatureRS256(signingInput, signatureB64 string, key *JwksKey) error

VerifyJWTSignatureRS256 verifies an RS256 JWT signature using the given JwksKey.

Types

type BlueprintAuditExport

type BlueprintAuditExport = wire.BlueprintAuditExport

Blueprint type aliases. The canonical definitions live in pkg/registry/wire so plugin tests and the client can validate blueprints without importing the server. Aliases keep existing callers readable.

type BlueprintIdentityProvider

type BlueprintIdentityProvider = wire.BlueprintIdentityProvider

BlueprintIdentityProvider is a type alias so callers don't need to import wire.

type BlueprintPolicy

type BlueprintPolicy = wire.BlueprintPolicy

Blueprint type aliases. The canonical definitions live in pkg/registry/wire so plugin tests and the client can validate blueprints without importing the server. Aliases keep existing callers readable.

type BlueprintRole

type BlueprintRole = wire.BlueprintRole

Blueprint type aliases. The canonical definitions live in pkg/registry/wire so plugin tests and the client can validate blueprints without importing the server. Aliases keep existing callers readable.

type BlueprintWebhooks

type BlueprintWebhooks = wire.BlueprintWebhooks

Blueprint type aliases. The canonical definitions live in pkg/registry/wire so plugin tests and the client can validate blueprints without importing the server. Aliases keep existing callers readable.

type Callbacks

type Callbacks struct {
	// Save triggers a debounced snapshot write.
	Save func()

	// Audit records an audit-log entry.
	Audit func(action string, attrs ...any)

	// IncKeyRotations increments the key-rotation counter.
	IncKeyRotations func()

	// IncIDPVerifications increments the IDP-verification counter.
	IncIDPVerifications func()

	// RecordWAL writes a key-rotation WAL entry.
	RecordWAL WALRecorder

	// OnKeyRotated is the callback that updates pubKeyIdx in the parent Server.
	OnKeyRotated KeyRotationCallback

	// Bus is the event bus used to publish "key.rotated" events.
	Bus events.Bus
}

Callbacks bundles the side-effect functions the Store calls on state changes. All functions must be safe for concurrent use.

type JWKSCache

type JWKSCache struct {
	Keys      []JwksKey
	URL       string
	FetchedAt time.Time
	TTL       time.Duration
	// contains filtered or unexported fields
}

JWKSCache holds a cached set of JWKS keys for a single issuer URL. Fields are exported so that white-box tests in package server can construct a cache with pre-populated state.

func NewJWKSCache

func NewJWKSCache() *JWKSCache

NewJWKSCache returns a new JWKSCache with the default TTL.

func (*JWKSCache) GetKey

func (c *JWKSCache) GetKey(jwksURL, kid string) (*JwksKey, error)

GetKey returns the JWKS key for the given URL and kid, fetching if stale.

type JwksKey

type JwksKey struct {
	Kty string `json:"kty"`
	Kid string `json:"kid"`
	Alg string `json:"alg"`
	K   string `json:"k,omitempty"`
	N   string `json:"n,omitempty"`
	E   string `json:"e,omitempty"`
}

func FetchJWKSKeys

func FetchJWKSKeys(jwksURL string) ([]JwksKey, error)

FetchJWKSKeys fetches the list of JWKS keys from the given URL.

type JwtAud

type JwtAud []string

func (*JwtAud) UnmarshalJSON

func (a *JwtAud) UnmarshalJSON(data []byte) error

type JwtClaims

type JwtClaims struct {
	Issuer    string `json:"iss"`
	Subject   string `json:"sub"`
	Audience  JwtAud `json:"aud"`
	Expiry    int64  `json:"exp"`
	IssuedAt  int64  `json:"iat"`
	NotBefore int64  `json:"nbf"`
}

type JwtHeader

type JwtHeader struct {
	Alg string `json:"alg"`
	Typ string `json:"typ"`
	Kid string `json:"kid"`
}

type KeyInfo

type KeyInfo struct {
	CreatedAt   time.Time `json:"created_at"`
	RotatedAt   time.Time `json:"rotated_at,omitempty"`
	RotateCount int       `json:"rotate_count"`
	ExpiresAt   time.Time `json:"expires_at,omitempty"`
}

KeyInfo mirrors server.KeyInfo; we declare it here to keep the sub-package independent of the parent package. The two must stay in sync.

type KeyRotationCallback

type KeyRotationCallback func(nodeID uint32, oldPubKey, newPubKey string)

KeyRotationCallback is called after a successful key rotation so that the caller (Server) can update its pubKeyIdx index.

nodeID     — node whose key was rotated
oldPubKey  — base64-encoded old public key (key to delete from index)
newPubKey  — base64-encoded new public key (key to add to index)

type NetworkBlueprint

type NetworkBlueprint = wire.NetworkBlueprint

Blueprint type aliases. The canonical definitions live in pkg/registry/wire so plugin tests and the client can validate blueprints without importing the server. Aliases keep existing callers readable.

type NodeView

type NodeView interface {
	// LookupNodeKey returns the current public key for a node.
	// ok is false if the node does not exist.
	LookupNodeKey(id uint32) (pubKey []byte, ok bool)

	// LookupNodeFull returns full identity-related fields for a node.
	// ok is false if the node does not exist.
	LookupNodeFull(id uint32) (pubKey []byte, keyMeta KeyInfo, networks []uint16, externalID, owner string, ok bool)

	// UpdateNodeKey atomically swaps the node's public key if it still matches
	// expectedPubKey (stale-check for concurrent-rotation detection).
	// Returns the old public key (base64-encoded) and nil on success.
	// Returns ErrKeyRotatedConcurrently if the key was rotated concurrently.
	// Returns protocol.ErrNodeNotFound if the node was deregistered.
	UpdateNodeKey(id uint32, expectedPubKey, newPubKey []byte, rotatedAt time.Time) (oldPubKeyB64 string, err error)

	// UpdateNodeKeyExpiry sets/clears the key expiry for a node.
	// Returns the old expiry value. ok is false if the node does not exist.
	UpdateNodeKeyExpiry(id uint32, expiresAt time.Time) (oldExpiry time.Time, ok bool)

	// UpdateNodeExternalID sets the external identity string on a node.
	// Returns the old external ID. ok is false if the node does not exist.
	UpdateNodeExternalID(id uint32, externalID string) (oldID string, ok bool)

	// NodeIsEnterprise returns true if the node belongs to at least one
	// enterprise-flagged network.
	NodeIsEnterprise(id uint32) bool

	// AdminToken returns the global admin token.
	AdminToken() string

	// CheckAdminToken returns nil if the message carries a valid admin token.
	CheckAdminToken(msg map[string]interface{}) error

	// VerifyHeartbeatSignature verifies a heartbeat-style Ed25519 signature.
	VerifyHeartbeatSignature(pubKey []byte, adminToken string, msg map[string]interface{}, challenge string) error

	// Now returns the current time (may be overridden in tests).
	Now() time.Time
}

NodeView is the read/write interface the Store uses to access node data. All methods must be safe for concurrent use.

type ProvisionCallbacks

type ProvisionCallbacks struct {
	// FindOrCreateNetwork looks up a network by name; if not found it creates
	// one using the supplied blueprint fields. Returns (networkID, created, err).
	FindOrCreateNetwork func(name string, enterprise bool, joinRule, joinToken, networkAdminToken, adminToken string) (uint16, bool, error)

	// EnableEnterprise sets the Enterprise flag on network netID if not already set.
	EnableEnterprise func(netID uint16)

	// ApplyNetworkPolicy sets the structural policy on a network.
	ApplyNetworkPolicy func(netID uint16, pol *BlueprintPolicy) error

	// ApplyExprPolicy sets the programmable (expr) policy on a network.
	ApplyExprPolicy func(netID uint16, data json.RawMessage) error

	// SetAuditWebhookURL configures the server's audit webhook URL.
	SetAuditWebhookURL func(url string)

	// StoreRBACPreAssignments saves pre-assignments for future node joins.
	StoreRBACPreAssignments func(netID uint16, roles []BlueprintRole)

	// ConfigureAuditExport sets up the audit exporter.
	ConfigureAuditExport func(cfg *BlueprintAuditExport)

	// IncProvisionsTotal increments the provisions counter.
	IncProvisionsTotal func()
}

ProvisionCallbacks holds the server-owned operations that ApplyBlueprint needs. They are passed per-call rather than stored on the Store so that provisioning can be called with different server contexts in tests.

type ProvisionResult

type ProvisionResult struct {
	NetworkID uint16   `json:"network_id"`
	Name      string   `json:"name"`
	Created   bool     `json:"created"` // true if network was created (vs updated)
	Actions   []string `json:"actions"` // human-readable list of actions taken
}

ProvisionResult describes what the provisioning operation did.

type RBACPreAssignCallbacks

type RBACPreAssignCallbacks struct {
	// GetRoles returns the pre-assigned roles for netID and the node's
	// ExternalID. found is false if there are no pre-assignments or the
	// node doesn't exist.
	GetRoles func(netID uint16, nodeID uint32) (roles []BlueprintRole, externalID string, found bool)

	// CommitRole sets the member's role in the network.
	CommitRole func(netID uint16, nodeID uint32, role string)

	// IncCounter increments the RBAC pre-assignment metric counter.
	IncCounter func()
}

RBACPreAssignCallbacks holds the state-access callbacks needed by ApplyRBACPreAssignment.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store holds the mutable identity and key-lifecycle state.

Lock ordering:

mu (RWMutex) — protects identityWebhookURL, idpConfig.
jwksCache has its own internal mutex.

These are independent of the parent Server's mu. Store methods must not acquire the parent mutex.

func NewStore

func NewStore(nodes NodeView, cb Callbacks) *Store

NewStore creates an empty, ready-to-use Store.

func (*Store) ApplyBlueprint

func (st *Store) ApplyBlueprint(bp *NetworkBlueprint, adminToken string, pcb ProvisionCallbacks) (*ProvisionResult, error)

ApplyBlueprint provisions a network from a blueprint. It creates the network if it doesn't exist, then applies policy, RBAC, webhooks, and audit config. adminToken is the global registry admin token.

The provision callbacks (pcb) provide access to server-owned state. Nil callbacks are silently skipped, except FindOrCreateNetwork which is required.

func (*Store) ApplyRBACPreAssignment

func (st *Store) ApplyRBACPreAssignment(netID uint16, nodeID uint32, rcb RBACPreAssignCallbacks)

ApplyRBACPreAssignment checks if a newly joined node matches any pre-assigned roles and applies the first match. Caller must NOT hold the server's global mutex (GetRoles and CommitRole acquire their own locks internally).

func (*Store) ClearIDPConfig

func (st *Store) ClearIDPConfig()

ClearIDPConfig removes the identity provider configuration and webhook URL.

func (*Store) GetIDPConfig

func (st *Store) GetIDPConfig() *BlueprintIdentityProvider

GetIDPConfig returns the current identity provider configuration.

func (*Store) GetWebhookURL

func (st *Store) GetWebhookURL() string

GetWebhookURL returns the currently configured identity webhook URL.

func (*Store) HandleGetIDPConfig

func (st *Store) HandleGetIDPConfig(msg map[string]interface{}) (map[string]interface{}, error)

HandleGetIDPConfig implements the "get_idp_config" protocol command.

func (*Store) HandleGetIdentity

func (st *Store) HandleGetIdentity(msg map[string]interface{}) (map[string]interface{}, error)

HandleGetIdentity implements the "get_identity" protocol command.

func (*Store) HandleGetKeyInfo

func (st *Store) HandleGetKeyInfo(msg map[string]interface{}) (map[string]interface{}, error)

HandleGetKeyInfo implements the "get_key_info" protocol command.

func (*Store) HandleGetProvisionStatus

func (st *Store) HandleGetProvisionStatus(
	msg map[string]interface{},
	networkSummary func() []map[string]interface{},
	webhookEnabled bool,
	auditExportFormat string,
) (map[string]interface{}, error)

HandleGetProvisionStatus implements the "get_provision_status" protocol command. The networkSummary callback produces the per-network list so the Store does not need to access s.networks directly.

func (*Store) HandleProvisionNetwork

func (st *Store) HandleProvisionNetwork(msg map[string]interface{}, adminToken string, pcb ProvisionCallbacks) (map[string]interface{}, error)

HandleProvisionNetwork handles the "provision_network" protocol command. adminToken is the global registry admin token (from s.authz.AdminToken()). pcb provides the server-owned operation callbacks.

func (*Store) HandleRotateKey

func (st *Store) HandleRotateKey(msg map[string]interface{}) (map[string]interface{}, error)

HandleRotateKey implements the "rotate_key" protocol command.

3-PHASE LOCK PATTERN (delegated through NodeView):

Phase 1: snapshot current pubkey via LookupNodeKey (under NodeView's RLock).
Phase 2: Ed25519 verify outside all locks (~28µs).
Phase 3: UpdateNodeKey re-checks pubkey and commits (under NodeView's Lock).

func (*Store) HandleSetExternalID

func (st *Store) HandleSetExternalID(msg map[string]interface{}) (map[string]interface{}, error)

HandleSetExternalID implements the "set_external_id" protocol command.

func (*Store) HandleSetIDPConfig

func (st *Store) HandleSetIDPConfig(msg map[string]interface{}) (map[string]interface{}, error)

HandleSetIDPConfig implements the "set_idp_config" protocol command. URL validation (SSRF prevention) must be performed by the caller before invoking this method.

func (*Store) HandleSetIdentityWebhook

func (st *Store) HandleSetIdentityWebhook(msg map[string]interface{}) (map[string]interface{}, error)

HandleSetIdentityWebhook implements the "set_identity_webhook" protocol command. URL validation (SSRF prevention) must be performed by the caller before invoking this method.

func (*Store) HandleSetKeyExpiry

func (st *Store) HandleSetKeyExpiry(msg map[string]interface{}) (map[string]interface{}, error)

HandleSetKeyExpiry implements the "set_key_expiry" protocol command.

func (*Store) HandleValidateToken

func (st *Store) HandleValidateToken(msg map[string]interface{}) (map[string]interface{}, error)

HandleValidateToken implements the "validate_token" protocol command.

func (*Store) SetIDPConfig

func (st *Store) SetIDPConfig(cfg *BlueprintIdentityProvider)

SetIDPConfig stores the identity provider configuration.

func (*Store) SetWebhookURL

func (st *Store) SetWebhookURL(url string)

SetWebhookURL sets the identity verification webhook URL. An empty string disables identity verification.

func (*Store) VerifyToken

func (st *Store) VerifyToken(token string) (string, error)

VerifyToken sends the token to the configured identity webhook and returns the verified external ID. Returns ("", nil) if no webhook is configured.

type WALRecorder

type WALRecorder func(nodeID uint32, newPubKeyB64, rotatedAt string)

WALRecorder is called to write a WAL entry after a key rotation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL