Documentation
¶
Overview ¶
Package bootstrap contains the domain concept definitions needed to support Magistrala bootstrap service functionality.
Index ¶
- Constants
- Variables
- func ReconcileAtom(ctx context.Context, svc Service, projector atom.Projector) error
- type BindingContext
- type BindingRequest
- type BindingResolver
- type BindingSlot
- type BindingSnapshot
- type BindingStore
- type BootstrapChallenge
- type BootstrapChallengeRepository
- type BootstrapChallengeResponse
- type Config
- type ConfigReader
- type ConfigRepository
- type ConfigsPage
- type ContentFormat
- type ContentType
- type DeviceBootstrapProof
- type DeviceContext
- type EncryptedBootstrapConfig
- type Filter
- type PreviousKey
- type Profile
- type ProfileRepository
- type ProfilesPage
- type RenderContext
- type Renderer
- type ResolveRequest
- type SecretCipher
- type Service
- type Status
Constants ¶
const ( DeviceBootstrapVersion = "v1" BootstrapKeySize = 32 BootstrapKeyMinLength = 10 BootstrapNonceSize = 32 BootstrapProofSize = 32 DefaultChallengeTTL = time.Minute )
const ( Disabled = "disabled" Enabled = "enabled" All = "all" Unknown = "unknown" )
String representation of bootstrap status values.
Variables ¶
var ( // ErrDeviceBootstrapAuth hides which part of device authentication failed. ErrDeviceBootstrapAuth = errors.NewAuthZError("failed to authenticate bootstrap device") // ErrBootstrap indicates error in getting bootstrap configuration. ErrBootstrap = errors.New("failed to read bootstrap configuration") // ErrAddBootstrap indicates error in adding bootstrap configuration. ErrAddBootstrap = errors.NewServiceError("failed to add bootstrap configuration") // ErrBootstrapStatus indicates an invalid bootstrap status. ErrBootstrapStatus = errors.NewRequestError("invalid bootstrap status") // ErrBootstrapKey indicates an invalid per-device Bootstrap key. ErrBootstrapKey = errors.NewRequestError("invalid bootstrap key") // ErrSecretEncryption indicates that encrypted secret storage is unavailable. ErrSecretEncryption = errors.NewServiceError("secret encryption is not configured") )
var ErrRenderFailed = errors.New("failed to render profile template")
ErrRenderFailed is returned when template execution or output validation fails.
Functions ¶
func ReconcileAtom ¶ added in v1.0.0
ReconcileAtom backfills Bootstrap's non-secret Atom projections and removes projections whose PostgreSQL records no longer exist. It must run before the authorization decorator so its empty session is never externally reachable.
Types ¶
type BindingContext ¶ added in v0.30.0
BindingContext holds the resolved resource data available inside templates for a specific slot.
type BindingRequest ¶ added in v0.30.0
type BindingRequest struct {
Slot string `json:"slot"`
Type string `json:"type"` // "device" | "channel" | "cert"
ResourceID string `json:"resource_id"` // ID of the resource in its owning service
}
BindingRequest carries a user's intent to bind a named profile slot to a concrete resource.
type BindingResolver ¶ added in v0.30.0
type BindingResolver interface {
Resolve(ctx context.Context, req ResolveRequest) ([]BindingSnapshot, error)
}
BindingResolver validates that requested resources exist in their owning services, verifies type and slot compatibility, and returns snapshots ready for storage. It is called at binding time only; the render path must not call it.
func NewAtomResolver ¶ added in v1.0.0
func NewAtomResolver(client atomResourceReader) BindingResolver
NewAtomResolver resolves device and channel bindings from Atom. It copies only explicitly allow-listed, non-secret fields into bootstrap snapshots.
type BindingSlot ¶ added in v0.30.0
type BindingSlot struct {
Name string `json:"name"`
Type string `json:"type"`
Required bool `json:"required"`
Fields []string `json:"fields,omitempty"`
}
BindingSlot declares a named resource placeholder that a profile template can use.
type BindingSnapshot ¶ added in v0.30.0
type BindingSnapshot struct {
ConfigID string `json:"config_id"`
Slot string `json:"slot"`
Type string `json:"type"`
ResourceID string `json:"resource_id"`
Snapshot map[string]any `json:"snapshot,omitempty"`
SecretSnapshot map[string]any `json:"secret_snapshot,omitempty"` // encrypted at rest
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
BindingSnapshot is a Bootstrap-owned point-in-time copy of the resource fields needed for template rendering. It is populated at binding time so that the render path never calls external services.
type BindingStore ¶ added in v0.30.0
type BindingStore interface {
// Save upserts all given snapshots for the config.
Save(ctx context.Context, configID string, bindings []BindingSnapshot) error
// Retrieve returns all snapshots for the given config.
Retrieve(ctx context.Context, configID string) ([]BindingSnapshot, error)
// Delete removes the snapshot for a specific slot of a config.
Delete(ctx context.Context, configID, slot string) error
}
BindingStore is the persistence interface for BindingSnapshots.
type BootstrapChallenge ¶ added in v1.0.0
type BootstrapChallenge struct {
ID string
ConfigID string
KeyVersion uint64
ServerNonce []byte
CreatedAt time.Time
ExpiresAt time.Time
ConsumedAt *time.Time
}
BootstrapChallenge is a short-lived server challenge bound to one enrollment and one version of its device Bootstrap key.
type BootstrapChallengeRepository ¶ added in v1.0.0
type BootstrapChallengeRepository interface {
Create(ctx context.Context, challenge BootstrapChallenge) error
Retrieve(ctx context.Context, challengeID, configID string) (BootstrapChallenge, error)
Consume(ctx context.Context, challengeID, configID string, now time.Time) error
}
BootstrapChallengeRepository persists short-lived device challenges.
type BootstrapChallengeResponse ¶ added in v1.0.0
type BootstrapChallengeResponse struct {
ChallengeID string `json:"challenge_id"`
ServerNonce string `json:"server_nonce"`
ExpiresAt time.Time `json:"expires_at"`
KeyVersion uint64 `json:"key_version"`
}
BootstrapChallengeResponse is returned to a device before it proves possession of its enrollment-specific Bootstrap key.
type Config ¶
type Config struct {
ID string `json:"id"`
WorkspaceID string `json:"workspace_id,omitempty"`
Name string `json:"name,omitempty"`
ClientCert string `json:"client_cert,omitempty"`
ClientKey string `json:"client_key,omitempty"`
CACert string `json:"ca_cert,omitempty"`
ExternalID string `json:"external_id"`
ExternalKey string `json:"external_key"`
BootstrapKeyVersion uint64 `json:"bootstrap_key_version"`
Content string `json:"content,omitempty"`
ContentType ContentType `json:"content_type,omitempty"`
Status Status `json:"status"`
ProfileID string `json:"profile_id,omitempty"`
RenderContext map[string]any `json:"render_context,omitempty"`
BootstrapRootKey []byte `json:"-"`
BootstrapChallengeID string `json:"-"`
BootstrapServerNonce string `json:"-"`
BootstrapDeviceNonce string `json:"-"`
}
Config represents a bootstrap enrollment.
type ConfigReader ¶
ConfigReader is used to parse Config into format which will be encoded as a JSON and consumed from the client side. The purpose of this interface is to provide convenient way to generate custom configuration response based on the specific Config which will be consumed by the client.
func NewConfigReader ¶
func NewConfigReader() ConfigReader
NewConfigReader return new reader which is used to generate response from the config.
type ConfigRepository ¶
type ConfigRepository interface {
// Save persists the Config. Successful operation is indicated by non-nil
// error response.
Save(ctx context.Context, cfg Config) (string, error)
// RetrieveByID retrieves the Config having the provided identifier, that is owned
// by the specified user.
RetrieveByID(ctx context.Context, workspaceID, id string) (Config, error)
// RetrieveAll retrieves a subset of Configs that belong to the given
// workspace, with given filter parameters. A non-nil error is returned
// to indicate operation failure, so that a lookup failure is never
// mistaken for an empty workspace.
RetrieveAll(ctx context.Context, workspaceID string, filter Filter, offset, limit uint64) (ConfigsPage, error)
// RetrieveByExternalID returns Config for given external ID.
RetrieveByExternalID(ctx context.Context, externalID string) (Config, error)
// Update updates an existing Config. A non-nil error is returned
// to indicate operation failure.
Update(ctx context.Context, cfg Config) error
// AssignProfile sets the profile reference for the given Config.
AssignProfile(ctx context.Context, workspaceID, id, profileID string) error
// UpdateCerts updates and returns an existing Config certificate and workspaceID.
// A non-nil error is returned to indicate operation failure.
UpdateCert(ctx context.Context, workspaceID, id, clientCert, clientKey, caCert string) (Config, error)
// Remove removes the Config having the provided identifier, that is owned
// by the specified user.
Remove(ctx context.Context, workspaceID, id string) error
// ChangeStatus changes the Status of the Config owned by the specific user.
ChangeStatus(ctx context.Context, workspaceID, id string, status Status) error
}
ConfigRepository specifies a Config persistence API.
type ConfigsPage ¶
type ConfigsPage struct {
Total uint64 `json:"total"`
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
Configs []Config `json:"configs"`
}
ConfigsPage contains page related metadata as well as list of Configs that belong to this page.
type ContentFormat ¶ added in v0.30.0
type ContentFormat string
ContentFormat enumerates the supported output formats for rendered profile templates.
const ( ContentFormatGoTemplate ContentFormat = "go-template" ContentFormatRaw ContentFormat = "raw" ContentFormatJSON ContentFormat = "json" ContentFormatYAML ContentFormat = "yaml" ContentFormatTOML ContentFormat = "toml" )
type ContentType ¶ added in v1.0.0
type ContentType string
ContentType identifies how a device should parse rendered profile content.
const ( ContentTypeJSON ContentType = "application/json" ContentTypeYAML ContentType = "application/yaml" ContentTypeTOML ContentType = "application/toml" ContentTypeTextPlain ContentType = "text/plain" )
type DeviceBootstrapProof ¶ added in v1.0.0
type DeviceBootstrapProof struct {
ChallengeID string `json:"challenge_id"`
DeviceNonce string `json:"device_nonce"`
Proof string `json:"proof"`
}
DeviceBootstrapProof authenticates a device response to a server challenge.
type DeviceContext ¶ added in v0.30.0
DeviceContext holds enrollment identity fields available inside templates.
type EncryptedBootstrapConfig ¶ added in v1.0.0
type EncryptedBootstrapConfig struct {
Version string `json:"version"`
KeyVersion uint64 `json:"key_version"`
ChallengeID string `json:"challenge_id"`
Nonce string `json:"nonce"`
Ciphertext string `json:"ciphertext"`
}
EncryptedBootstrapConfig is the application-encrypted device configuration.
func (EncryptedBootstrapConfig) Code ¶ added in v1.0.0
func (res EncryptedBootstrapConfig) Code() int
func (EncryptedBootstrapConfig) Empty ¶ added in v1.0.0
func (res EncryptedBootstrapConfig) Empty() bool
func (EncryptedBootstrapConfig) Headers ¶ added in v1.0.0
func (res EncryptedBootstrapConfig) Headers() map[string]string
type PreviousKey ¶ added in v1.0.0
PreviousKey is a retired master key, kept so that secrets sealed under it remain readable after the active key has been rotated.
type Profile ¶ added in v0.30.0
type Profile struct {
ID string `json:"id"`
WorkspaceID string `json:"workspace_id,omitempty"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
ContentFormat ContentFormat `json:"content_format"`
ContentType ContentType `json:"content_type"`
ContentTemplate string `json:"content_template,omitempty"`
Defaults map[string]any `json:"defaults,omitempty"`
BindingSlots []BindingSlot `json:"binding_slots,omitempty"`
Version int `json:"version,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
Profile is a user-managed device configuration template.
type ProfileRepository ¶ added in v0.30.0
type ProfileRepository interface {
// Save persists a new Profile and returns it with server-assigned fields set.
Save(ctx context.Context, p Profile) (Profile, error)
// RetrieveByID returns the Profile with the given ID inside the given workspace.
RetrieveByID(ctx context.Context, workspaceID, id string) (Profile, error)
// RetrieveAll returns a page of Profiles belonging to the given workspace, optionally filtered by name.
RetrieveAll(ctx context.Context, workspaceID string, offset, limit uint64, name string) (ProfilesPage, error)
// Update updates editable fields of the given Profile and returns the updated Profile.
Update(ctx context.Context, p Profile) (Profile, error)
// Delete removes the Profile with the given ID from the given workspace.
Delete(ctx context.Context, workspaceID, id string) error
}
ProfileRepository specifies the persistence API for Profiles.
type ProfilesPage ¶ added in v0.30.0
type ProfilesPage struct {
Total uint64 `json:"total"`
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
Profiles []Profile `json:"profiles"`
}
ProfilesPage contains pagination metadata and a slice of Profiles.
type RenderContext ¶ added in v0.30.0
type RenderContext struct {
Device DeviceContext
Vars map[string]any
Bindings map[string]BindingContext
}
RenderContext is the typed value injected into Go templates during rendering.
type Renderer ¶ added in v0.30.0
type Renderer interface {
Render(profile Profile, enrollment Config, bindings []BindingSnapshot) ([]byte, error)
}
Renderer renders a Profile's content template into a concrete device configuration. All input data must already be stored in Bootstrap — no external service calls are allowed inside Render.
func NewRenderer ¶ added in v0.30.0
func NewRenderer() Renderer
NewRenderer returns the default Renderer implementation using Go text/template.
type ResolveRequest ¶ added in v0.30.0
type ResolveRequest struct {
Enrollment Config
Token string
Requested []BindingRequest
}
ResolveRequest carries everything the BindingResolver needs to snapshot a set of resource bindings.
type SecretCipher ¶ added in v1.0.0
type SecretCipher struct {
// contains filtered or unexported fields
}
SecretCipher encrypts Bootstrap secrets at rest. A purpose-specific AES key is derived from the database master key so config keys and binding snapshots do not share the same encryption key.
func NewSecretCipher ¶ added in v1.0.0
func NewSecretCipher(masterKey []byte, keyID string, previous ...PreviousKey) (*SecretCipher, error)
NewSecretCipher creates an at-rest secret cipher. Bootstrap deliberately requires a 256-bit master key.
Rotation is performed by promoting the new key to (masterKey, keyID) and passing the outgoing one as a PreviousKey: new writes use the active key while existing envelopes stay readable. Without that, every secret sealed under the old key ID would fail to open and the service would not start.
func (*SecretCipher) KeyID ¶ added in v1.0.0
func (sc *SecretCipher) KeyID() string
type Service ¶
type Service interface {
// Add adds new Device Config to the user identified by the provided token.
Add(ctx context.Context, session smqauthn.Session, token string, cfg Config) (Config, error)
// View returns Device Config with given ID belonging to the user identified by the given token.
View(ctx context.Context, session smqauthn.Session, id string) (Config, error)
// Update updates editable fields of the provided Config.
Update(ctx context.Context, session smqauthn.Session, cfg Config) error
// UpdateCert updates an existing Config certificate and token.
// A non-nil error is returned to indicate operation failure.
UpdateCert(ctx context.Context, session smqauthn.Session, id, clientCert, clientKey, caCert string) (Config, error)
// List returns subset of Configs with given search params that belong to the
// user identified by the given token.
List(ctx context.Context, session smqauthn.Session, filter Filter, offset, limit uint64) (ConfigsPage, error)
// Remove removes Config with specified token that belongs to the user identified by the given token.
Remove(ctx context.Context, session smqauthn.Session, id string) error
// IssueBootstrapChallenge creates a short-lived challenge for an enrollment.
IssueBootstrapChallenge(ctx context.Context, externalID string) (BootstrapChallengeResponse, error)
// Bootstrap authenticates a device proof and returns its rendered Config.
Bootstrap(ctx context.Context, externalID string, proof DeviceBootstrapProof) (Config, error)
// EnableConfig enables the Config so its device can successfully bootstrap.
EnableConfig(ctx context.Context, session smqauthn.Session, id string) (Config, error)
// DisableConfig disables the Config, preventing its device from bootstrapping.
DisableConfig(ctx context.Context, session smqauthn.Session, id string) (Config, error)
// CreateProfile persists a new device Profile.
CreateProfile(ctx context.Context, session smqauthn.Session, p Profile) (Profile, error)
// ViewProfile returns the Profile with the given ID.
ViewProfile(ctx context.Context, session smqauthn.Session, profileID string) (Profile, error)
// UpdateProfile updates editable fields of the given Profile and returns the updated Profile.
UpdateProfile(ctx context.Context, session smqauthn.Session, p Profile) (Profile, error)
// ListProfiles returns a page of Profiles belonging to the workspace.
ListProfiles(ctx context.Context, session smqauthn.Session, offset, limit uint64, name string) (ProfilesPage, error)
// DeleteProfile removes the Profile with the given ID.
DeleteProfile(ctx context.Context, session smqauthn.Session, profileID string) error
// AssignProfile sets the ProfileID on an existing enrollment (Config).
AssignProfile(ctx context.Context, session smqauthn.Session, configID, profileID string) error
// BindResources resolves the requested bindings through their owning services,
// stores snapshots, and marks the enrollment renderable when all required slots
// are satisfied.
BindResources(ctx context.Context, session smqauthn.Session, token, configID string, bindings []BindingRequest) error
// ListBindings returns all stored binding snapshots for an enrollment.
ListBindings(ctx context.Context, session smqauthn.Session, configID string) ([]BindingSnapshot, error)
// RefreshBindings re-resolves all existing bindings for an enrollment and
// updates the stored snapshots.
RefreshBindings(ctx context.Context, session smqauthn.Session, token, configID string) error
}
Service specifies an API that must be fulfilled by the domain service implementation, and all of its decorators (e.g. logging & metrics).
func New ¶
func New( configs ConfigRepository, profiles ProfileRepository, bindings BindingStore, challenges BootstrapChallengeRepository, resolver BindingResolver, renderer Renderer, encKey []byte, encKeyID string, idp magistrala.IDProvider, previousKeys ...PreviousKey, ) (Service, error)
New returns a new Bootstrap service.
An unusable encryption key is reported here rather than deferred: without a cipher the service constructs cleanly and then fails every enrollment write at runtime.
previousKeys carries retired encryption keys so that secrets sealed before a key rotation remain readable; see NewSecretCipher.
challenges may be nil for a deployment that does not serve device bootstrap; the challenge and bootstrap endpoints then fail closed.
type Status ¶ added in v0.30.0
type Status uint8
Status represents bootstrap enrollment availability.
const ( EnabledStatus Status = iota DisabledStatus // AllStatus is used for querying purposes to list configs irrespective // of their status. It is never stored in the database. AllStatus )
Possible bootstrap enrollment statuses.
func ToStatus ¶ added in v0.30.0
ToStatus converts a string or legacy numeric string value to Status.
func (Status) MarshalJSON ¶ added in v0.30.0
MarshalJSON renders bootstrap status as a string literal.
func (*Status) UnmarshalJSON ¶ added in v0.30.0
UnmarshalJSON accepts both string and legacy numeric bootstrap statuses.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package api contains implementation of bootstrap service HTTP API.
|
Package api contains implementation of bootstrap service HTTP API. |
|
Package events provides the domain concept definitions needed to support bootstrap events functionality.
|
Package events provides the domain concept definitions needed to support bootstrap events functionality. |
|
producer
Package producer contains the domain events needed to support event sourcing of Bootstrap service actions.
|
Package producer contains the domain events needed to support event sourcing of Bootstrap service actions. |
|
Package postgres contains repository implementations using PostgreSQL as the underlying database.
|
Package postgres contains repository implementations using PostgreSQL as the underlying database. |
|
Package tracing provides tracing instrumentation for Magistrala Users service.
|
Package tracing provides tracing instrumentation for Magistrala Users service. |