// Package api holds the JSON shapes of Steward's REST API, shared by the // server and the Go client (and so the CLI). package api import ( "encoding/json" "time" "steward/internal/dhcp4" "steward/internal/model" "steward/internal/validate" "steward/internal/version" ) // Prefix starts every API path. const Prefix = "/api/v1" // ErrorBody is every error response. type ErrorBody struct { Error ErrorInfo `json:"error"` } // ErrorInfo describes an error. type ErrorInfo struct { Code string `json:"code"` Message string `json:"message"` Problems []validate.Problem `json:"problems,omitempty"` // Result is the preview of a refused change. Result json.RawMessage `json:"result,omitempty"` } // Whoami describes the caller. type Whoami struct { User *model.User `json:"user"` Admin bool `json:"admin"` Method string `json:"method"` Permissions []string `json:"permissions"` // TokenID is the token the caller authenticated with (0: none). TokenID int64 `json:"token_id,omitempty"` CSRFToken string `json:"csrf_token,omitempty"` Build version.Build `json:"build"` } // Login is the login request. type Login struct { Username string `json:"username"` Password string `json:"password"` } // PasswordChange changes the caller's password. type PasswordChange struct { Current string `json:"current"` Password string `json:"password"` } // OptionInput sets an option: by name ("routers") or number ("43"), with // a type for numbers the catalog doesn't know (default hex). type OptionInput struct { Option string `json:"option"` Value string `json:"value"` Type string `json:"type,omitempty"` } // SettingsInput changes the settings (unset fields stay). type SettingsInput struct { LeaseTime *int `json:"lease_time,omitempty"` Options *[]OptionInput `json:"options,omitempty"` PingCheck *bool `json:"ping_check,omitempty"` PingTimeout *int `json:"ping_timeout_ms,omitempty"` DeclineHold *int `json:"decline_hold,omitempty"` NotAuthoritative *bool `json:"not_authoritative,omitempty"` // AddressOrder is sequential, reverse or random. AddressOrder *string `json:"address_order,omitempty"` } // SubnetInput creates or changes a subnet (unset fields stay). Pools are // "FIRST-LAST" or prefixes, with ":ORDER" for a pool's own address order. type SubnetInput struct { Name *string `json:"name,omitempty"` Interface *string `json:"interface,omitempty"` Prefix *string `json:"prefix,omitempty"` Pools *[]string `json:"pools,omitempty"` // PoolOrder is listed or random; AddressOrder sequential, reverse, // random or "" (the settings'). PoolOrder *string `json:"pool_order,omitempty"` AddressOrder *string `json:"address_order,omitempty"` // Access is deny-list or allow-list; Allow and Deny replace the // lists (entries: a MAC address, aa:bb:cc:* or "id HEX"). Access *string `json:"access,omitempty"` Allow *[]model.ClientMatch `json:"allow,omitempty"` Deny *[]model.ClientMatch `json:"deny,omitempty"` LeaseTime *int `json:"lease_time,omitempty"` Options *[]OptionInput `json:"options,omitempty"` Disabled *bool `json:"disabled,omitempty"` Comment *string `json:"comment,omitempty"` } // ReservationInput creates or changes a reservation (unset fields stay). type ReservationInput struct { MAC *string `json:"mac,omitempty"` ClientID *string `json:"client_id,omitempty"` IP *string `json:"ip,omitempty"` Hostname *string `json:"hostname,omitempty"` Options *[]OptionInput `json:"options,omitempty"` // LeaseTime in seconds; 0 for the subnet's. LeaseTime *int `json:"lease_time,omitempty"` Disabled *bool `json:"disabled,omitempty"` Comment *string `json:"comment,omitempty"` } // Subnet is a subnet with its usage. type Subnet struct { *model.Subnet Usage Usage `json:"usage"` } // Usage counts a subnet's addresses. type Usage struct { // PoolSize is the number of addresses in the pools; Leased of them // are held by clients, Declined held back. PoolSize int `json:"pool_size"` Leased int `json:"leased"` Declined int `json:"declined"` // Reservations in the subnet, and how many of them have a lease now. Reservations int `json:"reservations"` ReservationsLeased int `json:"reservations_leased"` } // Reservation is a reservation with where it is. type Reservation struct { *model.Reservation SubnetID int64 `json:"subnet_id"` SubnetName string `json:"subnet"` // Lease is the reservation's current lease, if any. Lease *Lease `json:"lease,omitempty"` } // Lease is a lease with its state now. type Lease struct { *model.Lease SubnetName string `json:"subnet"` // Reservation is the ID of the reservation for the address, if any. Reservation int64 `json:"reservation,omitempty"` // ReservedHostname is the reservation's host name. ReservedHostname string `json:"reserved_hostname,omitempty"` } // MutationResponse answers a configuration change. type MutationResponse struct { Applied bool `json:"applied"` DryRun bool `json:"dry_run,omitempty"` HistoryID int64 `json:"history_id,omitempty"` Changes []model.Change `json:"changes"` Warnings []validate.Problem `json:"warnings,omitempty"` // Object is the created or changed object. Object json.RawMessage `json:"object,omitempty"` } // HostInterface is one of the server's network interfaces. type HostInterface struct { Name string `json:"name"` Addresses []string `json:"addresses"` // Subnets served on it. Subnets []int64 `json:"subnets"` } // InterfaceStatus says whether DHCP is served on an interface. type InterfaceStatus struct { Name string `json:"name"` Listening bool `json:"listening"` Error string `json:"error,omitempty"` Since time.Time `json:"since"` Addresses []string `json:"addresses"` Subnets []int64 `json:"subnets"` } // Status is the dashboard's overview. type Status struct { Build version.Build `json:"build"` Started time.Time `json:"started"` Interfaces []InterfaceStatus `json:"interfaces"` Subnets []Subnet `json:"subnets"` Problems []validate.Problem `json:"problems"` // Leases counts leases by state. Leases map[string]int `json:"leases"` // Messages counts DHCP messages since the start, by type. Messages map[string]uint64 `json:"messages"` // PingProblem says why addresses can't be pinged before an offer. PingProblem string `json:"ping_problem,omitempty"` Database string `json:"database"` } // TokenInput creates a token. type TokenInput struct { Name string `json:"name"` // Grants ("leases:read") or a role restrict the token; none: all of // the user's permissions. Grants []string `json:"grants,omitempty"` Role string `json:"role,omitempty"` ExpiresIn string `json:"expires_in,omitempty"` // 90d, 720h ExpiresAt *time.Time `json:"expires_at,omitempty"` } // TokenRotation gives a token a new secret. Grace keeps the old secret // working for a while (up to 7d). The token gets its lifetime again, // counted from now, unless ExpiresIn or NoExpiry says otherwise. type TokenRotation struct { Grace string `json:"grace,omitempty"` ExpiresIn string `json:"expires_in,omitempty"` NoExpiry bool `json:"no_expiry,omitempty"` } // Token is a token; Secret is only set when it is created. type Token struct { *model.Token GrantStrings []string `json:"grant_strings"` Secret string `json:"secret,omitempty"` } // UserInput creates or changes a user. type UserInput struct { Username *string `json:"username,omitempty"` Password *string `json:"password,omitempty"` Admin *bool `json:"admin,omitempty"` Grants *[]string `json:"grants,omitempty"` Role *string `json:"role,omitempty"` Disabled *bool `json:"disabled,omitempty"` Comment *string `json:"comment,omitempty"` } // User is a user with grants written out. type User struct { *model.User GrantStrings []string `json:"grant_strings"` } // OptionDef is an option of the catalog. type OptionDef struct { Code int `json:"code"` Name string `json:"name"` Type string `json:"type"` Help string `json:"help"` Common bool `json:"common,omitempty"` } // Catalog lists the options Steward knows, the value types and the codes // that can't be set. type Catalog struct { Options []OptionDef `json:"options"` Types []string `json:"types"` Reserved map[int]string `json:"reserved"` Roles map[string][]string `json:"roles"` } // Event is a DHCP message in or out. type Event struct { Time time.Time `json:"time"` Interface string `json:"interface"` Type string `json:"type"` MAC string `json:"mac"` IP string `json:"ip,omitempty"` Hostname string `json:"hostname,omitempty"` Detail string `json:"detail,omitempty"` // ID numbers the events since stewardd started. ID uint64 `json:"id"` // Direction is "in" for a client's message, "out" for Steward's // answer, "" for an event of Steward's own (CONFLICT, NO ADDRESS). Direction string `json:"direction,omitempty"` ClientID string `json:"client_id,omitempty"` // SubnetID is the subnet that refused a client (DENIED). SubnetID int64 `json:"subnet_id,omitempty"` // Fields and Options are the message, in words (GET /events/{id}, or // ?details=1). Fields []dhcp4.Field `json:"fields,omitempty"` Options []dhcp4.Field `json:"options,omitempty"` } // ImportResult reports a Kea import. type ImportResult struct { MutationResponse Leases int `json:"leases"` Notes []string `json:"notes"` Skipped []string `json:"skipped"` } // CertStep is one step of the preflight check. type CertStep struct { Name string `json:"name"` Status string `json:"status"` // ok, fail, warn, skip Detail string `json:"detail"` // Queries are the DNS questions asked, as dig commands. Queries []string `json:"queries,omitempty"` } // CertCheck is the result of a preflight check: can Steward publish its // challenge through Jarl, and does the internet see it? type CertCheck struct { Time time.Time `json:"time"` Domain string `json:"domain"` OK bool `json:"ok"` Steps []CertStep `json:"steps"` } // CertStatus describes the certificate, for the API and the dashboard. type CertStatus struct { Domain string `json:"domain"` Directory string `json:"directory"` Staging bool `json:"staging"` State string `json:"state"` // none, ok, working, error NotBefore *time.Time `json:"not_before,omitempty"` NotAfter *time.Time `json:"not_after,omitempty"` Issuer string `json:"issuer,omitempty"` // IssuedBy is the directory the current certificate came from (it may // differ from Directory until the next renewal). IssuedBy string `json:"issued_by,omitempty"` RenewAt *time.Time `json:"renew_at,omitempty"` LastError string `json:"last_error,omitempty"` LastAttempt *time.Time `json:"last_attempt,omitempty"` NextAttempt *time.Time `json:"next_attempt,omitempty"` Check *CertCheck `json:"check,omitempty"` } // CertificateResponse is GET /certificate. type CertificateResponse struct { // Configured is false when tls.acme is off. Configured bool `json:"configured"` // Source is acme, file or none. Source string `json:"source"` Status *CertStatus `json:"status,omitempty"` }