Documentation
¶
Overview ¶
Package service holds Tether's tenant management on top of a store.
Index ¶
- Constants
- type CreateTenantInput
- type Credentials
- type Devices
- func (s *Devices) Delete(ctx context.Context, tenantID id.TenantID, deviceID id.DeviceID) error
- func (s *Devices) Get(ctx context.Context, tenantID id.TenantID, deviceID id.DeviceID) (*device.Device, error)
- func (s *Devices) List(ctx context.Context, f device.ListFilter) ([]*device.Device, error)
- func (s *Devices) Provision(ctx context.Context, tenantID id.TenantID, in ProvisionInput) (*Credentials, error)
- func (s *Devices) Revoke(ctx context.Context, tenantID id.TenantID, deviceID id.DeviceID, reason string) error
- func (s *Devices) Rotate(ctx context.Context, tenantID id.TenantID, deviceID id.DeviceID, ...) (*Credentials, error)
- func (s *Devices) SetRoles(ctx context.Context, tenantID id.TenantID, deviceID id.DeviceID, ...) error
- type DevicesOption
- type KeyManager
- type ProvisionInput
- type RoleAssigner
- type SessionDeleter
- type Tenants
- func (s *Tenants) Create(ctx context.Context, in CreateTenantInput) (*tenant.Tenant, error)
- func (s *Tenants) Delete(ctx context.Context, tenantID id.TenantID) error
- func (s *Tenants) Evict(tenantID id.TenantID)
- func (s *Tenants) Get(ctx context.Context, tenantID id.TenantID) (*tenant.Tenant, error)
- func (s *Tenants) List(ctx context.Context, f tenant.ListFilter) ([]*tenant.Tenant, error)
- func (s *Tenants) ResolveTenant(ctx context.Context, appID, externalID string) (*tenant.Tenant, error)
- func (s *Tenants) Update(ctx context.Context, tenantID id.TenantID, in UpdateTenantInput) (*tenant.Tenant, error)
- type TenantsOption
- type UpdateTenantInput
- type WardenRoles
Constants ¶
const ( // KeyPrefix is the keysmith prefix of every device key. KeyPrefix = "tdk" // MetadataDeviceID is the key metadata field that holds the Tether device // ID the key was issued for. MetadataDeviceID = "tether_device_id" )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type CreateTenantInput ¶
CreateTenantInput describes a tenant to create. AppID and ExternalID name the host application's own tenant and are required.
type Credentials ¶
Credentials is what a device connects with. Username is the keysmith key ID and Password the raw key. Password is shown once: Provision and Rotate return it and nothing stores it.
type Devices ¶
type Devices struct {
// contains filtered or unexported fields
}
Devices manages the devices of a tenant: the keysmith key a device authenticates with, the warden roles that decide what it may publish and subscribe to, and the record that ties them to a tenant.
Every key and role call runs under the tenant's own scope (its AppID and ExternalID), whatever scope the caller's context carries. Devices only ever get keys this service created, and there is no way to attach an existing key.
The auth layer caches a validated key for a short time. After Revoke, Delete or Rotate with a zero grace, call (*auth.Keysmith).Evict with the device's KeyID so the change takes effect at once.
func NewDevices ¶
func NewDevices(s device.Store, t *Tenants, k KeyManager, r RoleAssigner, opts ...DevicesOption) *Devices
NewDevices returns a device service. s holds the records, t resolves a tenant ID to the scope for keysmith and warden, k issues keys and r assigns roles.
func (*Devices) Delete ¶
Delete revokes the device's key, removes its role assignments and deletes the record. A key that is already revoked or gone does not stop it. If a step fails the record is kept, so calling Delete again finishes the job. With WithSessions, the session of a device pinned to a client ID is deleted once the record is.
func (*Devices) Get ¶
func (s *Devices) Get(ctx context.Context, tenantID id.TenantID, deviceID id.DeviceID) (*device.Device, error)
Get returns the device. A device of another tenant wraps tether.ErrNotFound.
func (*Devices) Provision ¶
func (s *Devices) Provision(ctx context.Context, tenantID id.TenantID, in ProvisionInput) (*Credentials, error)
Provision creates a device: a keysmith key (prefix "tdk", environment "live", metadata "tether_device_id"), the requested roles on that key, and the device record, in that order. If a role cannot be assigned or the record cannot be stored, the roles are unassigned and the key is revoked before the error is returned.
A missing Name, an invalid ClientID or an empty role slug wraps tether.ErrInvalidInput, and an unknown tenant wraps tether.ErrNotFound.
func (*Devices) Revoke ¶
func (s *Devices) Revoke(ctx context.Context, tenantID id.TenantID, deviceID id.DeviceID, reason string) error
Revoke permanently disables the device's key. The record stays, so an operator still sees the device, but it can no longer connect. Revoking a device whose key is already revoked, or gone, succeeds. With WithSessions, the session of a device pinned to a client ID is deleted too.
func (*Devices) Rotate ¶
func (s *Devices) Rotate(ctx context.Context, tenantID id.TenantID, deviceID id.DeviceID, grace time.Duration) (*Credentials, error)
Rotate issues a new password for the device. The username does not change. The old password keeps working for grace, then stops. A zero grace uses keysmith's default (the key's policy grace period, else 24 hours), and a negative grace wraps tether.ErrInvalidInput. A revoked device cannot be rotated.
func (*Devices) SetRoles ¶
func (s *Devices) SetRoles(ctx context.Context, tenantID id.TenantID, deviceID id.DeviceID, roles []string) error
SetRoles replaces the device's roles with the given slugs and stores them on the device record. It applies only the difference: every new role is assigned first (a role the key already holds is a no-op), then every other role the key holds in the tenant's root namespace is unassigned. A role in both lists is never unassigned, so a connected device keeps its grants and subscriptions for it throughout (Broker.ReloadPrincipal runs on every assignment event and prunes what the grants no longer allow).
If a step fails, the previous roles are put back the same way and the error is returned. Calls for one device (and Delete) run one at a time in this process; with several processes on one store they can still interleave.
type DevicesOption ¶
type DevicesOption func(*Devices)
DevicesOption configures a Devices service.
func WithLogger ¶
func WithLogger(l *slog.Logger) DevicesOption
WithLogger is where Devices logs what it cannot report as an error: a session it could not delete after the device's key was already revoked. Nil discards.
func WithSessions ¶
func WithSessions(d SessionDeleter) DevicesOption
WithSessions makes Revoke and Delete delete the session of a device pinned to a client ID, through d. Without it, or for an unpinned device (whose client IDs Tether does not know), the session stays until its expiry runs out.
type KeyManager ¶
type KeyManager interface {
CreateKey(ctx context.Context, in *keysmith.CreateKeyInput) (*key.CreateResult, error)
RotateKey(ctx context.Context, keyID kid.KeyID, reason rotation.Reason, opts ...keysmith.RotateOption) (*key.CreateResult, error)
RevokeKey(ctx context.Context, keyID kid.KeyID, reason string) error
}
KeyManager is the part of keysmith's engine that Devices uses. It is satisfied by *keysmith.Engine.
type ProvisionInput ¶
ProvisionInput describes a device to create. Name is required. ClientID, if set, pins the device to one MQTT client ID and must be a single topic level: it becomes "%c" in the device's grants.
type RoleAssigner ¶
type RoleAssigner interface {
AssignRoleToAPIKey(ctx context.Context, tenantID, keyID, roleSlug string) error
UnassignRoleFromAPIKey(ctx context.Context, tenantID, keyID string) error
UnassignOtherRolesFromAPIKey(ctx context.Context, tenantID, keyID string, keep []string) error
}
RoleAssigner assigns warden roles to API keys. It is satisfied by *WardenRoles (NewWardenRoles). tenantID is the warden tenant, which is the Tether tenant's ExternalID.
AssignRoleToAPIKey must be a no-op for a role the key already holds, and UnassignOtherRolesFromAPIKey must leave the roles in keep assigned: SetRoles relies on both so a role a device keeps is never unassigned, not even for a moment.
type SessionDeleter ¶
SessionDeleter deletes an MQTT session: the client's subscriptions and queued messages. It is satisfied by *broker.Broker. tenantID is the Tether tenant ID and clientID the raw client ID the device connects with.
type Tenants ¶
type Tenants struct {
// contains filtered or unexported fields
}
Tenants manages tenants and resolves the host application's (app, external) pair to one.
ResolveTenant keeps a 30 s cache that Update and Delete evict. The cache is per process: when several broker nodes share a store, a change made through one node reaches the others within 30 s, or at once when every node calls Evict for it (a cell's control bus, events.EvictTenantsOnUpdate).
func NewTenants ¶
func NewTenants(s store.Store, opts ...TenantsOption) *Tenants
NewTenants returns a tenant service over s.
func (*Tenants) Create ¶
Create stores a new, enabled tenant. A missing AppID or ExternalID wraps tether.ErrInvalidInput. An ExternalID already used by another tenant, under this app or any other, wraps tether.ErrConflict: keysmith and warden isolate by tenant ID alone, so two tenants sharing an ExternalID would share keys and roles.
The check reads every tenant and is not atomic across processes: broker nodes that create tenants on one store at the same moment can both pass it. Create tenants through one node, or let the host application guarantee that its tenant IDs are unique across apps.
func (*Tenants) Delete ¶
Delete removes the tenant. It returns an error wrapping tether.ErrConflict while the tenant still has devices.
func (*Tenants) Evict ¶
Evict drops the tenant from the resolve cache. Update and Delete do it themselves. When several broker nodes share the store, each node calls it for a change another node made (events.EvictTenantsOnUpdate on the cell's control bus), so the change does not wait out the 30 s cache there.
func (*Tenants) ResolveTenant ¶
func (s *Tenants) ResolveTenant(ctx context.Context, appID, externalID string) (*tenant.Tenant, error)
ResolveTenant returns the tenant mapped to (appID, externalID), served from a 30 s cache. A tenant that does not exist wraps tether.ErrNotFound and is not cached. A disabled tenant is returned as is: the caller checks Enabled. The returned value is a copy the caller may modify.
type TenantsOption ¶
type TenantsOption func(*Tenants)
TenantsOption configures a Tenants.
func WithEvictHook ¶
func WithEvictHook(fn func(tenantID id.TenantID)) TenantsOption
WithEvictHook calls fn with the tenant's ID every time the resolve cache drops it: Update (on both sides of the write), Delete and Evict. A cache kept beside the service hangs off it, so it is dropped on the same path, a cell's tenant.updated included. fn runs after the service's own lock is released, on the caller's goroutine, and must be quick.
type UpdateTenantInput ¶
UpdateTenantInput patches a tenant. A nil field is left unchanged.
type WardenRoles ¶
type WardenRoles struct {
*keysmithbridge.Bridge
// contains filtered or unexported fields
}
WardenRoles is the RoleAssigner over a warden engine. Assigning and unassigning every role go through warden's keysmithbridge. Removing all but some roles (UnassignOtherRolesFromAPIKey) is Tether's own, because the bridge can only remove every role of a key at once.
func NewWardenRoles ¶
func NewWardenRoles(eng *warden.Engine) *WardenRoles
NewWardenRoles returns a WardenRoles over eng. Writes are attributed to the actor on the context, or else to the service actor "tether".
func (*WardenRoles) UnassignOtherRolesFromAPIKey ¶
func (w *WardenRoles) UnassignOtherRolesFromAPIKey(ctx context.Context, tenantID, keyID string, keep []string) error
UnassignOtherRolesFromAPIKey removes every assignment the API key keyID holds in the tenant's root namespace whose role is not one of the slugs in keep. Assignments of roles in keep are never touched. A slug in keep that names no role keeps nothing.
It mirrors keysmithbridge's UnassignRoleFromAPIKey for each assignment it removes: it emits RoleUnassigned and an "assignment.deleted" audit event, treats an assignment someone else removed first as done, and drops the key's cached warden decisions once anything was removed, whatever happens after.