<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #48721 Part 2 of https://github.com/fleetdm/fleet/issues/43488 # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually ## Database migrations - [x] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Windows devices can now create and securely escrow managed local account passwords during enrollment. * Added Windows managed local account status and password availability to host details. * Device-reported setup errors are surfaced with helpful details. * Account creation is automatically requested when supported by the device, plan, and configuration. * **Bug Fixes** * Windows accounts are excluded from password rotation workflows. * Re-enrollment correctly triggers account creation when needed. * Passwords remain available when settings change after enrollment. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
418 lines
14 KiB
Go
418 lines
14 KiB
Go
package fleet
|
|
|
|
import (
|
|
"context"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
)
|
|
|
|
// EnrollOrbitRequest is the request Orbit instances use to enroll to Fleet.
|
|
type EnrollOrbitRequest struct {
|
|
// EnrollSecret is the secret to authenticate the enroll request.
|
|
EnrollSecret string `json:"enroll_secret"`
|
|
// HardwareUUID is the device's hardware UUID.
|
|
HardwareUUID string `json:"hardware_uuid"`
|
|
// HardwareSerial is the device's serial number.
|
|
HardwareSerial string `json:"hardware_serial"`
|
|
// Hostname is the device's hostname.
|
|
Hostname string `json:"hostname"`
|
|
// Platform is the device's platform as defined by osquery.
|
|
Platform string `json:"platform"`
|
|
// PlatformLike is the device's platform_like as defined by osquery.
|
|
PlatformLike string `json:"platform_like"`
|
|
// OsqueryIdentifier holds the identifier used by osquery.
|
|
// If not set, then the hardware UUID is used to match orbit and osquery.
|
|
OsqueryIdentifier string `json:"osquery_identifier"`
|
|
// ComputerName is the device's friendly name (optional).
|
|
ComputerName string `json:"computer_name"`
|
|
// HardwareModel is the device's hardware model.
|
|
HardwareModel string `json:"hardware_model"`
|
|
// EUAToken is a Fleet-signed JWT containing the user's UPN and Windows MDM device ID.
|
|
EUAToken string `json:"eua_token,omitempty"`
|
|
}
|
|
|
|
// SetOrbitNodeKeyer is the interface implemented by orbit request types that
|
|
// carry an orbit node key for authentication.
|
|
type SetOrbitNodeKeyer interface {
|
|
SetOrbitNodeKey(nodeKey string)
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// Orbit config
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type OrbitGetConfigRequest struct {
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
}
|
|
|
|
func (r *OrbitGetConfigRequest) SetOrbitNodeKey(nodeKey string) {
|
|
r.OrbitNodeKey = nodeKey
|
|
}
|
|
|
|
func (r *OrbitGetConfigRequest) OrbitHostNodeKey() string {
|
|
return r.OrbitNodeKey
|
|
}
|
|
|
|
// DecodeBody implements the bodyDecoder interface for custom request body decoding.
|
|
// This endpoint is susceptible to client read timeouts (poll.DeadlineExceededError).
|
|
// By implementing DecodeBody, we classify those network errors as client errors.
|
|
func (r *OrbitGetConfigRequest) DecodeBody(_ context.Context, reader io.Reader, _ url.Values, _ []*x509.Certificate) error {
|
|
if err := json.NewDecoder(reader).Decode(r); err != nil {
|
|
if errors.Is(err, os.ErrDeadlineExceeded) {
|
|
return &BadRequestError{
|
|
Message: "request body read timeout",
|
|
InternalErr: err,
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type OrbitGetConfigResponse struct {
|
|
OrbitConfig
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r OrbitGetConfigResponse) Error() error { return r.Err }
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// Enroll Orbit
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type EnrollOrbitResponse struct {
|
|
OrbitNodeKey string `json:"orbit_node_key,omitempty"`
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r EnrollOrbitResponse) Error() error { return r.Err }
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// Ping orbit endpoint
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type OrbitPingRequest struct{}
|
|
|
|
type OrbitPingResponse struct{}
|
|
|
|
func (r OrbitPingResponse) Error() error { return nil }
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// SetOrUpdateDeviceToken endpoint
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type SetOrUpdateDeviceTokenRequest struct {
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
DeviceAuthToken string `json:"device_auth_token"`
|
|
}
|
|
|
|
func (r *SetOrUpdateDeviceTokenRequest) SetOrbitNodeKey(nodeKey string) {
|
|
r.OrbitNodeKey = nodeKey
|
|
}
|
|
|
|
func (r *SetOrUpdateDeviceTokenRequest) OrbitHostNodeKey() string {
|
|
return r.OrbitNodeKey
|
|
}
|
|
|
|
type SetOrUpdateDeviceTokenResponse struct {
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r SetOrUpdateDeviceTokenResponse) Error() error { return r.Err }
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// Get Orbit pending script execution request
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type OrbitGetScriptRequest struct {
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
ExecutionID string `json:"execution_id"`
|
|
}
|
|
|
|
func (r *OrbitGetScriptRequest) SetOrbitNodeKey(nodeKey string) {
|
|
r.OrbitNodeKey = nodeKey
|
|
}
|
|
|
|
func (r *OrbitGetScriptRequest) OrbitHostNodeKey() string {
|
|
return r.OrbitNodeKey
|
|
}
|
|
|
|
type OrbitGetScriptResponse struct {
|
|
Err error `json:"error,omitempty"`
|
|
*HostScriptResult
|
|
}
|
|
|
|
func (r OrbitGetScriptResponse) Error() error { return r.Err }
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// Post Orbit script execution result
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type OrbitPostScriptResultRequest struct {
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
*HostScriptResultPayload
|
|
}
|
|
|
|
func (r *OrbitPostScriptResultRequest) SetOrbitNodeKey(nodeKey string) {
|
|
r.OrbitNodeKey = nodeKey
|
|
}
|
|
|
|
func (r *OrbitPostScriptResultRequest) OrbitHostNodeKey() string {
|
|
return r.OrbitNodeKey
|
|
}
|
|
|
|
type OrbitPostScriptResultResponse struct {
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r OrbitPostScriptResultResponse) Error() error { return r.Err }
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// Post Orbit device mapping (custom email)
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type OrbitPutDeviceMappingRequest struct {
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
Email string `json:"email"`
|
|
}
|
|
|
|
func (r *OrbitPutDeviceMappingRequest) SetOrbitNodeKey(nodeKey string) {
|
|
r.OrbitNodeKey = nodeKey
|
|
}
|
|
|
|
func (r *OrbitPutDeviceMappingRequest) OrbitHostNodeKey() string {
|
|
return r.OrbitNodeKey
|
|
}
|
|
|
|
type OrbitPutDeviceMappingResponse struct {
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r OrbitPutDeviceMappingResponse) Error() error { return r.Err }
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// Post Orbit disk encryption key
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type OrbitPostDiskEncryptionKeyRequest struct {
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
EncryptionKey []byte `json:"encryption_key"`
|
|
ClientError string `json:"client_error"`
|
|
}
|
|
|
|
func (r *OrbitPostDiskEncryptionKeyRequest) SetOrbitNodeKey(nodeKey string) {
|
|
r.OrbitNodeKey = nodeKey
|
|
}
|
|
|
|
func (r *OrbitPostDiskEncryptionKeyRequest) OrbitHostNodeKey() string {
|
|
return r.OrbitNodeKey
|
|
}
|
|
|
|
type OrbitPostDiskEncryptionKeyResponse struct {
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r OrbitPostDiskEncryptionKeyResponse) Error() error { return r.Err }
|
|
func (r OrbitPostDiskEncryptionKeyResponse) Status() int { return http.StatusNoContent }
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// Post Orbit LUKS (Linux disk encryption) data
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// LUKS key type values reported by orbit in OrbitPostLUKSRequest.KeyType. An
|
|
// empty value is treated as LUKSKeyTypePassphrase for backward compatibility
|
|
// with older orbit agents that predate TPM-backed FDE support.
|
|
const (
|
|
// LUKSKeyTypePassphrase is a Fleet-generated passphrase added to a
|
|
// dedicated LUKS key slot (the legacy path). It carries a Salt and a
|
|
// numeric KeySlot.
|
|
LUKSKeyTypePassphrase = "passphrase"
|
|
// LUKSKeyTypeRecoveryKey is a snapd/secboot-managed recovery key escrowed
|
|
// from a host using TPM-backed full-disk encryption (e.g. Ubuntu 26). It
|
|
// has no Salt and no numeric KeySlot because snapd owns the key slots.
|
|
LUKSKeyTypeRecoveryKey = "recovery_key"
|
|
)
|
|
|
|
type OrbitPostLUKSRequest struct {
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
Passphrase string `json:"passphrase"`
|
|
Salt string `json:"salt"`
|
|
KeySlot *uint `json:"key_slot"`
|
|
ClientError string `json:"client_error"`
|
|
// KeyType identifies how the escrowed secret unlocks the volume. Empty or
|
|
// LUKSKeyTypePassphrase means the legacy passphrase-in-a-key-slot path;
|
|
// LUKSKeyTypeRecoveryKey means a TPM-backed FDE recovery key (no Salt/KeySlot).
|
|
KeyType string `json:"key_type"`
|
|
}
|
|
|
|
func (r *OrbitPostLUKSRequest) SetOrbitNodeKey(nodeKey string) {
|
|
r.OrbitNodeKey = nodeKey
|
|
}
|
|
|
|
func (r *OrbitPostLUKSRequest) OrbitHostNodeKey() string {
|
|
return r.OrbitNodeKey
|
|
}
|
|
|
|
type OrbitPostLUKSResponse struct {
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r OrbitPostLUKSResponse) Error() error { return r.Err }
|
|
func (r OrbitPostLUKSResponse) Status() int { return http.StatusNoContent }
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// Post Orbit Windows managed local account password
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// OrbitPostManagedLocalAccountRequest carries the device-generated password that Windows fleetd escrows after creating
|
|
// the managed local admin account. ClientError, when set, reports a device-side failure so the server can log it without
|
|
// recording a password.
|
|
type OrbitPostManagedLocalAccountRequest struct {
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
Password string `json:"password"`
|
|
ClientError string `json:"client_error"`
|
|
}
|
|
|
|
func (r *OrbitPostManagedLocalAccountRequest) SetOrbitNodeKey(nodeKey string) {
|
|
r.OrbitNodeKey = nodeKey
|
|
}
|
|
|
|
func (r *OrbitPostManagedLocalAccountRequest) OrbitHostNodeKey() string {
|
|
return r.OrbitNodeKey
|
|
}
|
|
|
|
type OrbitPostManagedLocalAccountResponse struct {
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r OrbitPostManagedLocalAccountResponse) Error() error { return r.Err }
|
|
func (r OrbitPostManagedLocalAccountResponse) Status() int { return http.StatusNoContent }
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// Get Orbit software install details
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type OrbitGetSoftwareInstallRequest struct {
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
OrbotNodeKey string `json:"orbot_node_key"` // legacy typo -- keep for backwards compatibility with orbit <= 1.38.0
|
|
InstallUUID string `json:"install_uuid"`
|
|
}
|
|
|
|
func (r *OrbitGetSoftwareInstallRequest) SetOrbitNodeKey(nodeKey string) {
|
|
r.OrbitNodeKey = nodeKey
|
|
r.OrbotNodeKey = nodeKey // legacy typo -- keep for backwards compatability with fleet server < 4.63.0
|
|
}
|
|
|
|
func (r *OrbitGetSoftwareInstallRequest) OrbitHostNodeKey() string {
|
|
if r.OrbitNodeKey != "" {
|
|
return r.OrbitNodeKey
|
|
}
|
|
return r.OrbotNodeKey
|
|
}
|
|
|
|
type OrbitGetSoftwareInstallResponse struct {
|
|
Err error `json:"error,omitempty"`
|
|
*SoftwareInstallDetails
|
|
}
|
|
|
|
func (r OrbitGetSoftwareInstallResponse) Error() error { return r.Err }
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// Download Orbit software installer request
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type OrbitDownloadSoftwareInstallerRequest struct {
|
|
Alt string `query:"alt"`
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
InstallerID uint `json:"installer_id"`
|
|
}
|
|
|
|
func (r *OrbitDownloadSoftwareInstallerRequest) SetOrbitNodeKey(nodeKey string) {
|
|
r.OrbitNodeKey = nodeKey
|
|
}
|
|
|
|
func (r *OrbitDownloadSoftwareInstallerRequest) OrbitHostNodeKey() string {
|
|
return r.OrbitNodeKey
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// Post Orbit software install result
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type OrbitPostSoftwareInstallResultRequest struct {
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
*HostSoftwareInstallResultPayload
|
|
}
|
|
|
|
func (r *OrbitPostSoftwareInstallResultRequest) SetOrbitNodeKey(nodeKey string) {
|
|
r.OrbitNodeKey = nodeKey
|
|
}
|
|
|
|
func (r *OrbitPostSoftwareInstallResultRequest) OrbitHostNodeKey() string {
|
|
return r.OrbitNodeKey
|
|
}
|
|
|
|
type OrbitPostSoftwareInstallResultResponse struct {
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r OrbitPostSoftwareInstallResultResponse) Error() error { return r.Err }
|
|
func (r OrbitPostSoftwareInstallResultResponse) Status() int { return http.StatusNoContent }
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// Get Orbit setup experience status
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type GetOrbitSetupExperienceStatusRequest struct {
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
ForceRelease bool `json:"force_release"`
|
|
// Whether to re-enqueue canceled setup experience steps after a previous
|
|
// software install failure on MacOS.
|
|
ResetFailedSetupSteps bool `json:"reset_failed_setup_steps"`
|
|
}
|
|
|
|
func (r *GetOrbitSetupExperienceStatusRequest) SetOrbitNodeKey(nodeKey string) {
|
|
r.OrbitNodeKey = nodeKey
|
|
}
|
|
|
|
func (r *GetOrbitSetupExperienceStatusRequest) OrbitHostNodeKey() string {
|
|
return r.OrbitNodeKey
|
|
}
|
|
|
|
type GetOrbitSetupExperienceStatusResponse struct {
|
|
Results *SetupExperienceStatusPayload `json:"setup_experience_results,omitempty"`
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r GetOrbitSetupExperienceStatusResponse) Error() error { return r.Err }
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
// Setup experience init
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
type OrbitSetupExperienceInitRequest struct {
|
|
OrbitNodeKey string `json:"orbit_node_key"`
|
|
}
|
|
|
|
func (r *OrbitSetupExperienceInitRequest) SetOrbitNodeKey(nodeKey string) {
|
|
r.OrbitNodeKey = nodeKey
|
|
}
|
|
|
|
func (r *OrbitSetupExperienceInitRequest) OrbitHostNodeKey() string {
|
|
return r.OrbitNodeKey
|
|
}
|
|
|
|
type OrbitSetupExperienceInitResponse struct {
|
|
Result SetupExperienceInitResult `json:"result"`
|
|
Err error `json:"error,omitempty"`
|
|
}
|
|
|
|
func (r OrbitSetupExperienceInitResponse) Error() error {
|
|
return r.Err
|
|
}
|