models

package
v0.13.0 Latest Latest
Warning

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

Go to latest
Published: Jul 31, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Index

Constants

View Source
const (
	OperatorRoleAdmin = "admin"
	OperatorRoleUser  = "user"
)

Operator roles. Operator.Role is a plain string; these are the only two values the server accepts.

View Source
const MaxAddressesPerHost = 16

MaxAddressesPerHost is the maximum number of overlay addresses a host can have. This is a soft limit to prevent cert bloat; Nebula v2 certs have no hard limit but practical deployments should not exceed this without good reason.

View Source
const MaxGroupNameLen = 64

MaxGroupNameLen bounds a group name. Group names are embedded in the signed Nebula certificate and distributed to every peer, so without a cap an operator could bloat certs mesh-wide (#186). It lives here, in the domain layer, because both certificate group validation and firewall rule validation must enforce the same bound.

View Source
const MaxHostFirewallRules = 64

MaxHostFirewallRules caps advanced.firewall_inbound per host. Generous relative to real use, but bounded so a single host cannot bloat its rendered config without limit.

View Source
const MaxUnsafeNetworksPerHost = 16

MaxUnsafeNetworksPerHost caps the prefixes a single host may advertise. Like MaxAddressesPerHost this is a cert-bloat guard rather than a Nebula limit: unsafe networks ride inside the signed certificate and are handed to every peer that completes a handshake.

Variables

View Source
var ErrMobileRoleRestricted = errors.New("mobile hosts must have role=host (lighthouse/relay not supported)")

ErrMobileRoleRestricted is returned when a mobile host is assigned a role other than host (e.g., lighthouse or relay). Mobile Nebula clients cannot reliably listen on a public socket in the background, so these roles are not supported for mobile hosts.

View Source
var ErrMobileVariantRequired = errors.New("mobile hosts must have variant set to ios or android")

ErrMobileVariantRequired is returned when a mobile host is created without specifying a variant (ios or android). The variant field is required to distinguish mobile client types.

View Source
var ErrRoleRequiresListenPort = errors.New("listen_port is required when role is lighthouse or relay")

ErrRoleRequiresListenPort is the listen_port counterpart of ErrRoleRequiresPublicIP: peers compose static_host_map entries as "public_ip:listen_port", so a zero port produces the same silent failure.

View Source
var ErrRoleRequiresPublicIP = errors.New("public_ip is required when role is lighthouse or relay")

ErrRoleRequiresPublicIP is returned when a host with role=lighthouse or role=relay is created without a non-empty public_ip. Such a host would never be advertised to peers (see internal/api/enroll.go where static_host_map / lighthouse.hosts only include hosts whose PublicIP is set), so accept-and-silently-drop is replaced with reject-at-create.

Functions

func CertIdentityChanged added in v0.11.0

func CertIdentityChanged(before, after *Host) bool

CertIdentityChanged reports whether an edit touched a field that is carried inside the host's Nebula certificate: Name, NebulaIPs, Groups or UnsafeNetworks.

Those four are the host's identity as far as the mesh is concerned. Peers authorize each other on the certificate rather than on the management server's host row, and firewall rules select their counterparties by group, so editing any of them is inert until a new certificate is issued. Callers use this to schedule that re-issuance. Every other field (Role, PublicIP, ListenPort, Advanced) only shapes the rendered config, which the agent picks up on its next poll without a new certificate.

Both host-edit paths — the API's PATCH handler and the web UI's form — must agree on this, hence one definition rather than a copy each. It deliberately reuses the same comparators as HostDiff above, so what the audit trail records as a change and what triggers a re-issuance cannot drift apart.

A nil side is treated as the zero Host, matching HostDiff.

func FriendlyAddrError

func FriendlyAddrError(field, value string) string

FriendlyAddrError returns a stable user-facing message for an IP-parse failure. The Go stdlib's netip.ParseAddr error text is intentionally dropped — strings like `ParseAddr("10.42.0.22.333"): IPv4 address too long` are diagnostic for Go authors but useless to operators typing into a form. Callers pass the form field name (or empty for unqualified messages) and the operator-supplied value so the message identifies both what is wrong and where.

func FriendlyPrefixError

func FriendlyPrefixError(field, value string) string

FriendlyPrefixError is the CIDR counterpart of FriendlyAddrError.

func HostDiff

func HostDiff(before, after *Host) ([]byte, bool, error)

HostDiff computes the difference between two hosts and returns a JSON-encoded map of changed fields. Returns (nil, false, nil) if no fields differ.

For basic fields (Name, NebulaIPs, Groups, UnsafeNetworks, Role, PublicIP, ListenPort), the diff key is the field name in snake_case.

For Advanced sub-fields (ListenHost, MTU, TunDevice, Punchy, UnsafeRoutes), the diff key uses dot-notation: "advanced.mtu", "advanced.punchy", etc.

The JSON format for each changed field is: {"field_name": {"before": <value>, "after": <value>}}

If before is nil, zero values are used. If before.Advanced is nil, zero values are used for sub-field comparisons.

func ParseUnsafeNetworks added in v0.13.0

func ParseUnsafeNetworks(networks []string) ([]netip.Prefix, error)

ParseUnsafeNetworks parses the prefixes a host advertises as routable through itself (Host.UnsafeNetworks) into the form the signer needs.

Each entry must be in canonical masked form (10.0.0.0/24, not 10.0.0.1/24). Canonical form is enforced rather than silently masked because the operator who types 192.168.1.1/24 is describing the gateway's own address, not the prefix it serves, and the distinction matters: Nebula stores what it is given and matches the packet's local address against it.

Both the write paths (via ValidateUnsafeNetworks) and the signing paths use this, so a certificate can never carry a prefix in a shape validation would have rejected.

func ValidKind

func ValidKind(k HostKind) bool

ValidKind reports whether k is a known host kind.

func ValidMeshImportStatus added in v0.8.0

func ValidMeshImportStatus(status MeshImportStatus) bool

func ValidOperatorRole added in v0.4.0

func ValidOperatorRole(r string) bool

ValidOperatorRole reports whether r is a known operator role.

func ValidRole

func ValidRole(r HostRole) bool

ValidRole reports whether r is a known host role or empty (meaning "use default").

func ValidVariant

func ValidVariant(v HostVariant) bool

ValidVariant reports whether v is a known host variant (including empty).

func ValidateCIDR

func ValidateCIDR(field, value string) (netip.Prefix, error)

ValidateCIDR is a thin wrapper around netip.ParsePrefix that converts a failure into a user-facing FriendlyPrefixError.

func ValidateFirewallCIDR added in v0.12.0

func ValidateFirewallCIDR(field, value string) error

ValidateFirewallCIDR validates a Nebula firewall `cidr` / `local_cidr` value: empty (field unset), the literal "any" (any address of any family), or a parseable prefix such as 10.0.0.0/24 or fd00::/8.

func ValidateFirewallSelectors added in v0.12.0

func ValidateFirewallSelectors(group, cidr string) error

ValidateFirewallSelectors enforces that a firewall rule carries exactly one peer selector. Nebula OR's `group` and `cidr` (a rule listing both matches peers in the group *or* peers in the prefix, which is wider than either), so the two are mutually exclusive here; expressing that union takes two rules. An empty pair is rejected because Nebula treats a rule with no selector as match-any, a broader allow than any operator intends.

func ValidateHostAddresses

func ValidateHostAddresses(addrs []string) error

ValidateHostAddresses validates a list of overlay addresses for a host. It checks that: - The list is non-empty - Each address is parseable - There are no duplicate addresses - The list does not exceed MaxAddressesPerHost

Note: This function does not check containment in parent network CIDRs. That validation is the responsibility of the caller (API or web handler) which has the parent network context and can provide better error messages.

func ValidateHostAdvanced

func ValidateHostAdvanced(adv *HostAdvanced) error

ValidateHostAdvanced rejects obviously broken advanced overrides before they reach the database. Empty / zero-value fields mean "inherit network default" and pass validation. All error messages use the user-facing friendly wrappers so the inline form (web) and the JSON API surface identical, stable strings.

func ValidateIPAddr

func ValidateIPAddr(field, value string) (netip.Addr, error)

ValidateIPAddr is a thin wrapper around netip.ParseAddr that converts a failure into a user-facing FriendlyAddrError. Returns the parsed address on success.

func ValidateMobileConstraints

func ValidateMobileConstraints(kind HostKind, variant HostVariant, role HostRole) error

ValidateMobileConstraints enforces role and variant constraints for mobile hosts. For kind=mobile, role must be host (or empty) and variant must be ios or android. For kind=agent, no constraints are applied (returns nil).

func ValidateNetworkCIDRs

func ValidateNetworkCIDRs(cidrs []string) error

ValidateNetworkCIDRs validates a list of CIDRs for a network. It checks that: - The list is non-empty - Each CIDR is parseable - There are no duplicate CIDRs - There are no overlapping CIDRs

func ValidateRoleReachability

func ValidateRoleReachability(role HostRole, publicIP string, listenPort int) error

ValidateRoleReachability rejects role/reachability combinations that would result in a silently-useless host. role=lighthouse and role=relay must both ship with a routable public_ip and a non-zero listen_port — otherwise peer config.yml renders an empty static_host_map and the host is never dialed (issue #94).

func ValidateUnsafeNetworks added in v0.13.0

func ValidateUnsafeNetworks(networks, overlayCIDRs []string) error

ValidateUnsafeNetworks validates the prefixes a host advertises as routable through itself, against the overlay CIDRs of the network it belongs to. It checks that:

  • Each entry is a parseable CIDR in canonical masked form
  • There are no duplicates and no overlaps between entries
  • No entry overlaps one of the network's own overlay CIDRs
  • The list does not exceed MaxUnsafeNetworksPerHost

An empty list is valid and means "this host routes nothing but itself".

The overlay check is folded in rather than offered separately so no caller can run half the validation: an unsafe network overlapping the overlay would shadow real mesh peers, because Nebula builds one routing table from the certificate's networks and unsafe networks together. Callers with no overlay in hand — the mesh importer reading prefixes off an existing certificate — pass nil.

Containment checks against the parent network are deliberately absent. An unsafe network is by definition outside the overlay — that is what makes it unsafe.

Types

type AgentProfile added in v0.8.0

type AgentProfile struct {
	NebulaConfigPath string `json:"nebula_config_path"`
	NebulaCAPath     string `json:"nebula_ca_path"`
	NebulaCertPath   string `json:"nebula_cert_path"`
	NebulaKeyPath    string `json:"nebula_key_path"`
	ConfigAckV1      bool   `json:"config_ack_v1"`
}

func DefaultAgentProfile added in v0.8.0

func DefaultAgentProfile() AgentProfile

func (AgentProfile) IsZero added in v0.8.0

func (p AgentProfile) IsZero() bool

func (AgentProfile) Validate added in v0.8.0

func (p AgentProfile) Validate() error

func (AgentProfile) WithDefaults added in v0.8.0

func (p AgentProfile) WithDefaults() (AgentProfile, error)

type AuditEntry

type AuditEntry struct {
	ID        string    `json:"id"`
	Timestamp time.Time `json:"timestamp"`
	Actor     string    `json:"actor"`
	Action    string    `json:"action"`
	Resource  string    `json:"resource"`
	Details   string    `json:"details,omitempty"`
}

type CA

type CA struct {
	ID                   string    `json:"id"`
	Name                 string    `json:"name"`
	OwnerOperatorID      string    `json:"owner_operator_id"`
	CertPEM              string    `json:"cert_pem"`
	Fingerprint          string    `json:"fingerprint"`
	NotBefore            time.Time `json:"not_before"`
	NotAfter             time.Time `json:"not_after"`
	Status               CAStatus  `json:"status"`
	PredecessorID        *string   `json:"predecessor_id,omitempty"`
	EncryptedKeyDEK      []byte    `json:"-"`
	NonceDEK             []byte    `json:"-"`
	EncryptedKeyMaterial []byte    `json:"-"`
	NonceKey             []byte    `json:"-"`
	CreatedAt            time.Time `json:"created_at"`
	UpdatedAt            time.Time `json:"updated_at"`
}

CA is a per-operator certificate authority. Private key material lives only inside EncryptedKeyMaterial / NonceKey, wrapped under a per-CA DEK stored in EncryptedKeyDEK / NonceDEK (envelope encryption — see ADR 0002).

type CAStatus

type CAStatus string

CAStatus is the lifecycle status of a CA.

const (
	CAStatusActive  CAStatus = "active"
	CAStatusRetired CAStatus = "retired"
)

type CertificateIdentity added in v0.11.0

type CertificateIdentity struct {
	Name           string
	NebulaIPs      []string
	Groups         []string
	UnsafeNetworks []string
}

CertificateIdentity is the subset of a Host that is carried inside its Nebula certificate.

It is intentionally separate from Host so callers that sign a certificate can retain precisely the identity they signed without also retaining mutable lifecycle or configuration state.

func CertificateIdentityFromHost added in v0.11.0

func CertificateIdentityFromHost(host *Host) CertificateIdentity

CertificateIdentityFromHost returns an independent snapshot of the fields that are carried in a Host's Nebula certificate. A nil Host is the zero identity, matching HostDiff's nil-host convention.

func (CertificateIdentity) Equal added in v0.11.0

func (identity CertificateIdentity) Equal(other CertificateIdentity) bool

Equal reports whether two certificate identities describe the same Nebula certificate subject. Empty and nil slices are equivalent, as they are when a Host is diffed after a store or JSON round trip.

type CertificateInfo

type CertificateInfo struct {
	ID          string    `json:"id"`
	HostID      string    `json:"host_id"`
	Fingerprint string    `json:"fingerprint"`
	PEM         string    `json:"pem"`
	NotBefore   time.Time `json:"not_before"`
	NotAfter    time.Time `json:"not_after"`
	IsCurrent   bool      `json:"is_current"`
	CreatedAt   time.Time `json:"created_at"`
}

type EnrollmentToken

type EnrollmentToken struct {
	ID        string     `json:"id"`
	HostID    string     `json:"host_id"`
	TokenHash string     `json:"-"` // versioned keyed verifier; raw value is never persisted
	Used      bool       `json:"used"`
	ExpiresAt time.Time  `json:"expires_at"`
	UsedAt    *time.Time `json:"used_at,omitempty"`
	CreatedAt time.Time  `json:"created_at"`
}

type Host

type Host struct {
	ID                  string        `json:"id"`
	NetworkID           string        `json:"network_id"`
	CAID                string        `json:"ca_id,omitempty"`
	Name                string        `json:"name"`
	NebulaIPs           []string      `json:"nebula_ips"`
	Groups              []string      `json:"groups"`
	UnsafeNetworks      []string      `json:"unsafe_networks,omitempty"`
	Role                HostRole      `json:"role"`
	IsLighthouse        bool          `json:"is_lighthouse"`
	IsRelay             bool          `json:"is_relay"`
	PublicIP            string        `json:"public_ip,omitempty"`
	ListenPort          int           `json:"listen_port,omitempty"`
	Status              HostStatus    `json:"status"`
	CertFingerprint     string        `json:"cert_fingerprint,omitempty"`
	PrevCertFingerprint string        `json:"prev_cert_fingerprint,omitempty"`
	CertExpiresAt       *time.Time    `json:"cert_expires_at,omitempty"`
	CertRotatedAt       *time.Time    `json:"cert_rotated_at,omitempty"`
	PendingRekey        bool          `json:"pending_rekey,omitempty"`
	SigningPubPEM       string        `json:"signing_pub_pem,omitempty"`
	LastSeenAt          *time.Time    `json:"last_seen_at,omitempty"`
	Advanced            *HostAdvanced `json:"advanced,omitempty"`
	Kind                HostKind      `json:"kind"`
	Variant             HostVariant   `json:"variant,omitempty"`
	CreatedAt           time.Time     `json:"created_at"`
	UpdatedAt           time.Time     `json:"updated_at"`
}

type HostAdvanced

type HostAdvanced struct {
	Punchy          *bool              `json:"punchy,omitempty" yaml:"punchy,omitempty"`
	ListenHost      string             `json:"listen_host,omitempty" yaml:"listen_host,omitempty"`
	MTU             int                `json:"mtu,omitempty" yaml:"mtu,omitempty"`
	TunDevice       string             `json:"tun_device,omitempty" yaml:"tun_device,omitempty"`
	UnsafeRoutes    []UnsafeRoute      `json:"unsafe_routes,omitempty" yaml:"unsafe_routes,omitempty"`
	FirewallInbound []HostFirewallRule `json:"firewall_inbound,omitempty" yaml:"firewall_inbound,omitempty"`
}

HostAdvanced groups optional per-host overrides for the rendered Nebula config. All fields are optional. A field set to its zero value means "inherit network default"; a field set to a non-zero value overrides.

Punchy is a tri-state pointer so an operator can explicitly disable hole-punching for a host (false) without it being indistinguishable from "not set".

type HostAgentProfile added in v0.8.0

type HostAgentProfile struct {
	HostID               string    `json:"host_id"`
	MeshImportID         string    `json:"mesh_import_id"`
	NebulaConfigPath     string    `json:"nebula_config_path"`
	NebulaCAPath         string    `json:"nebula_ca_path"`
	NebulaCertPath       string    `json:"nebula_cert_path"`
	NebulaKeyPath        string    `json:"nebula_key_path"`
	ConfigAckV1          bool      `json:"config_ack_v1"`
	PendingConfigVersion int       `json:"pending_config_version"`
	CreatedAt            time.Time `json:"created_at"`
	UpdatedAt            time.Time `json:"updated_at"`
}

func (HostAgentProfile) AgentProfile added in v0.8.0

func (p HostAgentProfile) AgentProfile() AgentProfile

type HostFirewallRule added in v0.10.0

type HostFirewallRule struct {
	Port  string `json:"port" yaml:"port"`   // "any", a port, or a range "a-b"
	Proto string `json:"proto" yaml:"proto"` // any | tcp | udp | icmp
	Group string `json:"group" yaml:"group"`
	// Cidr restricts the rule to peers whose Nebula address falls inside the
	// prefix: a CIDR, or "any" for any address of any family.
	Cidr string `json:"cidr,omitempty" yaml:"cidr,omitempty"`
	// LocalCidr restricts the rule to traffic whose local address falls
	// inside the prefix: a CIDR, or "any". Needed to reach addresses served
	// via unsafe_routes, which Nebula excludes by default.
	LocalCidr string `json:"local_cidr,omitempty" yaml:"local_cidr,omitempty"`
}

HostFirewallRule is a single per-host inbound firewall rule, appended after the network-wide policy in the rendered Nebula config. Group "any" renders as `host: any` (match every peer), mirroring the network policy.

Cidr and LocalCidr map to Nebula's `cidr` and `local_cidr` rule fields. Nebula OR's the peer selectors (`host` / `group` / `groups` / `cidr`) and AND's `local_cidr` into whichever selector matched, so a rule carries exactly one peer selector — either Group or Cidr, never both, since the two together would widen the rule to "group OR cidr" rather than narrow it. LocalCidr is an independent constraint on the local (this host) address and may accompany either selector.

type HostKind

type HostKind string
const (
	HostKindAgent  HostKind = "agent"
	HostKindMobile HostKind = "mobile"
)

type HostRole

type HostRole string
const (
	HostRoleHost            HostRole = "host"
	HostRoleLighthouse      HostRole = "lighthouse"
	HostRoleRelay           HostRole = "relay"
	HostRoleLighthouseRelay HostRole = "lighthouse+relay"
)

func (HostRole) Lighthouse added in v0.10.0

func (r HostRole) Lighthouse() bool

Lighthouse reports whether the role includes lighthouse duty.

func (HostRole) Relay added in v0.10.0

func (r HostRole) Relay() bool

Relay reports whether the role includes relay duty.

type HostStatus

type HostStatus string
const (
	HostStatusPending   HostStatus = "pending"
	HostStatusEnrolled  HostStatus = "enrolled"
	HostStatusBlocked   HostStatus = "blocked"
	HostStatusImporting HostStatus = "importing"
)

type HostVariant

type HostVariant string
const (
	HostVariantNone    HostVariant = ""
	HostVariantIOS     HostVariant = "ios"
	HostVariantAndroid HostVariant = "android"
)

type MeshImport added in v0.8.0

type MeshImport struct {
	ID                           string           `json:"id"`
	NetworkID                    string           `json:"network_id"`
	CAID                         string           `json:"ca_id"`
	OwnerOperatorID              string           `json:"owner_operator_id"`
	CAFingerprint                string           `json:"ca_fingerprint"`
	Status                       MeshImportStatus `json:"status"`
	ExpectedHosts                *int             `json:"expected_hosts,omitempty"`
	Revision                     int64            `json:"revision"`
	TokenHash                    string           `json:"-"`
	TokenExpiresAt               time.Time        `json:"token_expires_at"`
	CapturedNetworkConfigVersion int              `json:"captured_network_config_version"`
	TerminalReason               string           `json:"terminal_reason,omitempty"`
	CreatedAt                    time.Time        `json:"created_at"`
	UpdatedAt                    time.Time        `json:"updated_at"`
	FinalizedAt                  *time.Time       `json:"finalized_at,omitempty"`
	CanceledAt                   *time.Time       `json:"canceled_at,omitempty"`
}

type MeshImportChallenge added in v0.8.0

type MeshImportChallenge struct {
	ID                     string     `json:"id"`
	MeshImportID           string     `json:"mesh_import_id"`
	TokenHash              string     `json:"-"`
	CertificateFingerprint string     `json:"certificate_fingerprint"`
	AgentSigningPubPEM     string     `json:"agent_signing_pub_pem"`
	PayloadHash            string     `json:"payload_hash"`
	ServerNonce            string     `json:"server_nonce"`
	ExpiresAt              time.Time  `json:"expires_at"`
	ConsumedAt             *time.Time `json:"consumed_at,omitempty"`
	CreatedAt              time.Time  `json:"created_at"`
}

type MeshImportRegistration added in v0.8.0

type MeshImportRegistration struct {
	ChallengeID          string
	CertificateNotBefore time.Time
	CertificateNotAfter  time.Time
	Host                 *Host
	Snapshot             *MeshImportSnapshot
	Profile              *HostAgentProfile
}

type MeshImportRegistrationResult added in v0.8.0

type MeshImportRegistrationResult struct {
	Host     *Host
	Snapshot *MeshImportSnapshot
	Created  bool
}

type MeshImportSnapshot added in v0.8.0

type MeshImportSnapshot struct {
	ID                     string    `json:"id"`
	MeshImportID           string    `json:"mesh_import_id"`
	HostID                 string    `json:"host_id"`
	CertificateFingerprint string    `json:"certificate_fingerprint"`
	CertificatePEM         string    `json:"certificate_pem"`
	AgentSigningPubPEM     string    `json:"agent_signing_pub_pem"`
	PayloadHash            string    `json:"payload_hash"`
	SnapshotJSON           string    `json:"snapshot_json"`
	CreatedAt              time.Time `json:"created_at"`
	UpdatedAt              time.Time `json:"updated_at"`
}

type MeshImportStatus added in v0.8.0

type MeshImportStatus string
const (
	MeshImportStatusCollecting MeshImportStatus = "collecting"
	MeshImportStatusFinalized  MeshImportStatus = "finalized"
	MeshImportStatusCanceled   MeshImportStatus = "canceled"
)

type MeshImportTombstone added in v0.8.0

type MeshImportTombstone struct {
	CertificateFingerprint string    `json:"certificate_fingerprint"`
	FormerHostID           string    `json:"former_host_id"`
	MeshImportID           string    `json:"mesh_import_id"`
	AgentSigningPubPEM     string    `json:"agent_signing_pub_pem"`
	TerminalReason         string    `json:"terminal_reason"`
	CreatedAt              time.Time `json:"created_at"`
	UpdatedAt              time.Time `json:"updated_at"`
}

type Network

type Network struct {
	ID        string    `json:"id"`
	Name      string    `json:"name"`
	CIDRs     []string  `json:"cidrs"`
	CAID      string    `json:"ca_id,omitempty"`
	CreatedAt time.Time `json:"created_at"`
}

type Operator

type Operator struct {
	ID           string               `json:"id"`
	Username     string               `json:"username"`
	DisplayName  string               `json:"display_name"`
	PasswordHash string               `json:"-"`
	AuthProvider OperatorAuthProvider `json:"auth_provider"`
	Status       OperatorStatus       `json:"status"`
	Role         string               `json:"role"`
	TOTPSecret   string               `json:"-"`
	TOTPEnabled  bool                 `json:"totp_enabled"`
	OIDCIssuer   string               `json:"oidc_issuer,omitempty"`
	OIDCSubject  string               `json:"oidc_subject,omitempty"`
	CreatedAt    time.Time            `json:"created_at"`
	UpdatedAt    time.Time            `json:"updated_at"`
	LastLoginAt  *time.Time           `json:"last_login_at,omitempty"`

	// FailedLoginAttempts counts consecutive failed password logins; it
	// resets to 0 on a successful login or when an expired lock is cleared
	// (#263). LockedUntil, when non-nil and in the future, blocks login
	// regardless of credentials.
	FailedLoginAttempts int        `json:"-"`
	LockedUntil         *time.Time `json:"-"`
}

Operator is an administrative user of the management server.

type OperatorAPIKey

type OperatorAPIKey struct {
	ID         string     `json:"id"`
	OperatorID string     `json:"operator_id"`
	Name       string     `json:"name"`
	KeyHash    string     `json:"-"`
	CreatedAt  time.Time  `json:"created_at"`
	LastUsedAt *time.Time `json:"last_used_at,omitempty"`
	RevokedAt  *time.Time `json:"revoked_at,omitempty"`
}

OperatorAPIKey is a per-operator API key. Only the hash is stored.

type OperatorAuthProvider

type OperatorAuthProvider string

OperatorAuthProvider identifies the authentication backend for an operator.

const (
	OperatorAuthLocal OperatorAuthProvider = "local"
	OperatorAuthOIDC  OperatorAuthProvider = "oidc"
)

type OperatorSession

type OperatorSession struct {
	// Token is the raw session token carried in the operator's cookie. It is
	// transient: the Store persists only a keyed verifier, never the raw value.
	Token      string
	OperatorID string
	State      SessionState
	ExpiresAt  time.Time
	CreatedAt  time.Time
}

OperatorSession represents a UI session. A session in `pending_totp` state is awaiting a second-factor verification and is not yet authenticated.

type OperatorStatus

type OperatorStatus string

OperatorStatus represents the active/disabled state of an operator.

const (
	OperatorStatusActive   OperatorStatus = "active"
	OperatorStatusDisabled OperatorStatus = "disabled"
)

type SessionState

type SessionState string

SessionState is the lifecycle phase of an operator session.

const (
	SessionStateAuthenticated SessionState = "authenticated"
	SessionStatePendingTOTP   SessionState = "pending_totp"
)

type UnsafeRoute

type UnsafeRoute struct {
	Route string `json:"route" yaml:"route"` // CIDR
	Via   string `json:"via" yaml:"via"`     // Nebula IP of the gateway host
}

UnsafeRoute is a single "unsafe route" entry: traffic for `Route` is sent through the host with Nebula IP `Via`. See Nebula's tun.unsafe_routes.

This is the *consumer* half of an unsafe route: it tells this host to send traffic for Route into the tunnel toward Via. The *provider* half lives in Host.UnsafeNetworks on the gateway, because Nebula authorizes routing on the certificate: a via node that does not carry the prefix in its cert silently refuses to route, and its peers drop the reply as ErrInvalidLocalIP. The two halves are configured independently and both are required.

type WebhookSubscription added in v0.6.0

type WebhookSubscription struct {
	ID              string   `json:"id"`
	OwnerOperatorID string   `json:"owner_operator_id"`
	URL             string   `json:"url"`
	Events          []string `json:"events"` // empty = all events
	Active          bool     `json:"active"`
	AllowPrivate    bool     `json:"allow_private"`

	// Envelope-encrypted HMAC secret. All nil => unsigned deliveries.
	EncryptedSecretDEK []byte `json:"-"`
	NonceDEK           []byte `json:"-"`
	EncryptedSecret    []byte `json:"-"`
	NonceSecret        []byte `json:"-"`

	// HasSecret is a computed, response-only flag (the secret itself is never
	// returned). It is not persisted as a column.
	HasSecret bool `json:"has_secret"`

	// Per-subscription delivery observability.
	LastDeliveryAt      *time.Time `json:"last_delivery_at,omitempty"`
	LastStatus          string     `json:"last_status,omitempty"`
	LastError           string     `json:"last_error,omitempty"`
	ConsecutiveFailures int        `json:"consecutive_failures"`

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

WebhookSubscription is an operator-owned outbound webhook target (#256 phase 2). The HMAC secret is stored envelope-encrypted (a per-row DEK wrapped under the master key, the secret sealed under the DEK), so the encrypted fields never serialize to JSON; API responses expose only HasSecret.

Jump to

Keyboard shortcuts

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