Files
fleet/server/fleet/errors.go
T
Rajendra Kadam 5e95589554 Support custom activations and management declarations for DDM profiles (#50280)
**Related issue:** Resolves #49970

Adds custom activations to the single-profile paths for declaration
(DDM) profiles — create, edit, delete and read — and unblocks management
declarations. Part of #48222.

Batch/GitOps is #49972; serving the custom activation to devices is
#49971.

### Custom activations

- `POST /configuration_profiles` and `PATCH
/configuration_profiles/{uuid}` accept an optional `activation` file
part, rejected for any profile type other than an Apple declaration.
- Validation requires an activation `Type` (any
`com.apple.activation.*`, so future Apple types need no Fleet change),
an `Identifier`, and exactly one `StandardConfigurations` entry naming
the configuration it ships with. `Predicate` and every other key are
stored and served verbatim for the device to evaluate.
- Premium-only, unconditionally. `parseAndValidateAppleDeclaration`
requires premium only when a fleet or labels are involved, so an
unassigned unlabeled DDM profile is free today; the activation carries
its own gate.
- The activation's Fleet variables are validated against
`fleetVarsSupportedInDDMDeclarations` — already exactly the set
specified for activations — and associated via
`mdm_configuration_profile_variables.apple_ddm_activation_uuid`.
- Returned base64-encoded on both the list and single-profile endpoints,
per the API reference draft (#49768), and omitted entirely when absent.

What an edit does to a stored activation:

| Request | Result |
| --- | --- |
| activation supplied | replaces the stored one |
| new profile content, no activation | stored one is cleared — this is
how it's removed |
| labels-only edit | stored one is carried forward |

The third row matters: the datastore clears the activation of any
declaration written without one, so a labels-only edit rebuilding the
declaration from the existing row would otherwise silently wipe it.
`GetMDMAppleDeclaration` loads the activation so it can be carried
forward, and there's a test asserting it.

### Management declarations

`com.apple.management.*` uploads are unblocked via a prefix check, so
future management declarations work without a product change. Types to
block go in the existing `ForbiddenDeclTypes` deny list, which is
already evaluated ahead of the prefix. An activation supplied alongside
a management declaration is rejected — those are never activated.

Routing them to the manifest's Management section is #49971's work.

### Notes for review

**Where the non-declaration guard lives differs by path, deliberately.**
Create resolves the profile type in the endpoint from the uploaded file;
edit resolves it in the service from the UUID prefix. The check sits
wherever the type becomes known. Both use the same message so the
mistake reads identically.

**Endpoint-level errors must be returned from behind an authz check.**
The create-path guard originally returned the error straight from the
endpoint, which skips authorization and surfaces to the client as a bare
`forbidden` rather than the validation message. It now goes through
`NewMDMActivationUnsupportedProfile`, alongside the existing
`NewMDMUnsupportedConfigProfile` and `NewMDMInvalidJSONConfigProfile`,
which exist for the same reason. This was caught by the integration
tests, not the unit tests — service-level tests bypass the authz
middleware.

**Activation rows are keyed on `declaration_uuid`, not inserted fresh.**
An edit reuses the row, so the Fleet variable associations hanging off
it survive. The UUID is read back after the upsert rather than reusing
the generated one, since `ON DUPLICATE KEY UPDATE` keeps the existing
row.

**Secrets are expanded for validation but stored unexpanded**, so
validation runs against the document the device receives without
persisting secret values.

`MDMAppleCustomActivation` is the storage type; `MDMAppleDDMActivation`
was already taken by Apple's wire format.

# 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.

No changes file: the feature isn't reachable by users until the DDM sync
work in #49971 lands.

## Testing

- [x] Added/updated automated tests
- [ ] QA'd all new/changed functionality manually

**Unit** (`server/fleet`): `GetRawActivationValues` and
`ValidateUserProvided` — valid activation, unknown type under the
activation prefix, missing `Type`, a configuration type supplied as an
activation, missing `Identifier`, zero/multiple/mismatched
`StandardConfigurations`, all problems reported at once, plus
`IsManagementDeclaration`.

**Service** (`server/service`): activation accepted, mismatched
configuration rejected, malformed JSON rejected, rejected on a
management declaration, supported Fleet variables recorded, unsupported
rejected, premium required even where the declaration is free. On edit:
activation-only edit keeps content, labels-only edit preserves the
activation, new content without an activation clears it, and exactly one
`edited_declaration_profile` activity fires.

**Datastore** (`server/datastore/mysql`): write, read-back through list
and single get, edit reusing the row, Fleet variable association, and
removal cascading to the variable rows.

**Integration** (`integration_mdm_ddm_test.go`): multipart upload with
an activation, read back and asserted base64-decoded against the raw
response body; the key omitted entirely for a declaration without one;
two management declarations uploaded and coexisting; activation on a
`.mobileconfig` rejected on both create and edit; activation-only
`PATCH` replacing the activation while leaving the declaration
untouched.

The multipart test helper now supports more than one file part — nothing
could build that request before, which is why the decode path was
previously untested. Single-file callers are unchanged.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added optional custom activations for Apple DDM configuration
declarations.
- Activations support secrets, Fleet variables, and custom host vitals.
- Activation data appears when viewing or downloading applicable
profiles.
- Activation files can be added, updated, preserved during label-only
edits, or removed during content replacement.
- Management declarations can coexist with supported configuration
declarations.

- **Validation**
- Added checks for declaration matching, supported profile types, file
limits, and Premium licensing.
- Clear errors are provided when activations are used with management
declarations or non-DDM profiles.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-05 15:29:52 +05:30

612 lines
24 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package fleet
import (
"errors"
"fmt"
"net/http"
"time"
platform_errors "github.com/fleetdm/fleet/v4/server/platform/errors"
platform_http "github.com/fleetdm/fleet/v4/server/platform/http"
"github.com/rs/zerolog"
)
var (
ErrNoContext = errors.New("context key not set")
ErrPasswordResetRequired = platform_http.ErrPasswordResetRequired
ErrMissingLicense = &licenseError{}
ErrMDMNotConfigured = &MDMNotConfiguredError{}
ErrWindowsMDMNotConfigured = &WindowsMDMNotConfiguredError{}
ErrAndroidMDMNotConfigured = &AndroidMDMNotConfiguredError{}
ErrNotConfigured = &NotConfiguredError{}
MDMNotConfiguredMessage = "MDM features aren't turned on in Fleet. For more information about setting up MDM, please visit https://fleetdm.com/docs/using-fleet"
WindowsMDMNotConfiguredMessage = "Windows MDM isn't turned on. For more information about setting up MDM, please visit https://fleetdm.com/learn-more-about/windows-mdm"
AndroidMDMNotConfiguredMessage = "Android MDM isn't turned on. For more information about setting up MDM, please visit https://fleetdm.com/learn-more-about/how-to-connect-android-enterprise"
AppleMDMNotConfiguredMessage = "macOS MDM isn't turned on. Visit https://fleetdm.com/docs/using-fleet to learn how to turn on MDM."
AppleABMDefaultTeamDeprecatedMessage = "mdm.apple_bm_default_team has been deprecated. Please use the new mdm.apple_business key documented here: https://fleetdm.com/learn-more-about/apple-business-manager-gitops"
AppleOSVersionUnsupportedMessage = "The minimum version isn't supported by Apple."
AppleOSVersionDeadlineInvalidMessage = "The deadline isn't a valid date."
CantTurnOffMDMForWindowsHostsMessage = "Can't turn off MDM for Windows hosts."
CantTurnOffMDMForPersonalHostsMessage = "Couldn't turn off MDM. This command isn't available for personal hosts."
CantWipePersonalHostsMessage = "Couldn't wipe. This command isn't available for personal hosts."
CantLockPersonalHostsMessage = "Couldn't lock. This command isn't available for personal hosts."
CantClearPasscodePersonalHostsMessage = "Unlock token is not available for this device. Unable to issue ClearPasscode command."
CantLockManualIOSIpadOSHostsMessage = "Couldn't lock. This command isn't available for manually enrolled iOS/iPadOS hosts."
CantDisableDiskEncryptionIfPINRequiredErrMsg = "Couldn't disable disk encryption, you need to disable the BitLocker PIN requirement first."
CantEnablePINRequiredIfDiskEncryptionEnabled = "Couldn't enable BitLocker PIN requirement, you must enable disk encryption first."
CantResendAppleDeclarationProfilesMessage = "Can't resend declaration (DDM) profiles. Unlike configuration profiles (.mobileconfig), the host automatically checks in to get the latest DDM profiles."
CantAddSoftwareConflictMessage = "Couldn't add software. %s already has an installer available for the %s fleet."
// Args: the two conflicting app names (order not significant).
CantAddConflictingFMAMessage = "Couldn't add software. Only one of %s or %s can be added to the same fleet."
// AddMaintainedAppTimeoutErrMsg is returned when Fleet's own 15-minute installer
// download timeout is exceeded (context.DeadlineExceeded).
AddMaintainedAppTimeoutErrMsg = "Couldn't add. Downloading the installer took longer than Fleet's 15-minute limit. This can happen with very large installers or a slow connection to the vendor's content delivery network (CDN). Try again, and make sure any proxy, gateway, or load balancer in front of Fleet allows the request to run at least that long."
// AddMaintainedAppCanceledErrMsg is returned when an upstream proxy, gateway, or
// load balancer cancels the request before the download finishes (context.Canceled).
AddMaintainedAppCanceledErrMsg = "Couldn't add. The request was canceled before the installer finished downloading. This usually means a proxy, gateway, or load balancer in front of Fleet (for example, Envoy, or an AWS/GCP load balancer) closed the connection first. Increase its request and idle timeout above the time it takes to download large installers."
SoftwarePackageHashConflictMessage = "%s package is already added (same SHA-256 hash)."
SoftwarePackageTitleMismatchMessage = "Couldn't add. %s doesn't match the software title. To add it, go to Software and add it as new software."
SoftwareAlreadyHasVPPAppMessage = "%s already has an Apple App Store (VPP) on the %s fleet."
SoftwareAlreadyHasFleetMaintainedAppMessage = "%s already has a Fleet-maintained app on the %s fleet."
SoftwareAlreadyHasPackageMessage = "%s already has a software package on the %s fleet."
SoftwarePackageLimitMessage = "%s already has %d packages. Before adding, delete one you no longer use."
SoftwareSelfServiceCategoriesConflictMessage = "Couldn't add software (%q). self_service and categories can be specified either in the fleet-level file or in the package YAML file."
SoftwareSetupExperienceFleetLevelOnlyMessage = "Couldn't add software (%q). setup_experience can be specified only in the fleet-level file."
SoftwareLabelsPackageLevelOnlyMessage = "Couldn't add software (%q). Labels can be specified only in the package-level file when adding multiple packages of the same software."
SoftwareLabelsConflictMessage = "Couldn't add software (%q). Labels can be specified either in the fleet-level file or in the package YAML file."
ConfigProfileLabelScopingPremiumCauseMsg = "Scoping configuration profiles with labels"
DDMCustomActivationPremiumCauseMsg = "Custom activations for declaration (DDM) profiles"
)
// ErrWithStatusCode is an interface for errors that should set a specific HTTP
// status code.
type ErrWithStatusCode interface {
error
StatusCode() int
}
// ErrWithInternal is an alias for platform_http.ErrWithInternal.
type ErrWithInternal = platform_http.ErrWithInternal
// ErrWithLogFields is an alias for platform_http.ErrWithLogFields.
type ErrWithLogFields = platform_http.ErrWithLogFields
// ErrWithRetryAfter is an alias for platform_http.ErrWithRetryAfter.
type ErrWithRetryAfter = platform_http.ErrWithRetryAfter
// ErrWithIsClientError is an alias for platform_errors.ErrWithIsClientError.
type ErrWithIsClientError = platform_errors.ErrWithIsClientError
type invalidArgWithStatusError struct {
*InvalidArgumentError
code int
}
func (e *invalidArgWithStatusError) Status() int {
if e.code == 0 {
// 422 is the default code for invalid args
return http.StatusUnprocessableEntity
}
return e.code
}
// ErrorUUIDer is an alias for platform_http.ErrorUUIDer.
type ErrorUUIDer = platform_http.ErrorUUIDer
// ErrorWithUUID is an alias for platform_http.ErrorWithUUID.
type ErrorWithUUID = platform_http.ErrorWithUUID
// InvalidArgumentError is the error returned when invalid data is presented to
// a service method. It is a client error.
type InvalidArgumentError struct {
Errors []InvalidArgument
ErrorWithUUID
}
// InvalidArgument is the details about a single invalid argument.
type InvalidArgument struct {
name string
reason string
}
// NewInvalidArgumentError returns a InvalidArgumentError with at least
// one error.
func NewInvalidArgumentError(name, reason string) *InvalidArgumentError {
var invalid InvalidArgumentError
invalid.Append(name, reason)
return &invalid
}
func (e *InvalidArgumentError) IsClientError() bool {
return true
}
func (e *InvalidArgumentError) Append(name, reason string) {
e.Errors = append(e.Errors, InvalidArgument{
name: name,
reason: reason,
})
}
func (e *InvalidArgumentError) AppendInvalidArgument(invalidArg InvalidArgument) {
e.Errors = append(e.Errors, invalidArg)
}
func (e *InvalidArgumentError) Appendf(name, reasonFmt string, args ...interface{}) {
e.Append(name, fmt.Sprintf(reasonFmt, args...))
}
// WithStatus returns an error that combines the InvalidArgumentError
// with a custom status code.
func (e *InvalidArgumentError) WithStatus(code int) error {
return &invalidArgWithStatusError{e, code}
}
func (e *InvalidArgumentError) HasErrors() bool {
return len(e.Errors) != 0
}
// Error implements the error interface.
func (e *InvalidArgumentError) Error() string {
switch len(e.Errors) {
case 0:
return "validation failed"
case 1:
return fmt.Sprintf("validation failed: %s %s", e.Errors[0].name, e.Errors[0].reason)
default:
return fmt.Sprintf("validation failed: %s %s and %d other errors", e.Errors[0].name, e.Errors[0].reason,
len(e.Errors))
}
}
func (e *InvalidArgumentError) Invalid() []map[string]string {
var invalid []map[string]string
for _, i := range e.Errors {
invalid = append(invalid, map[string]string{"name": i.name, "reason": i.reason})
}
return invalid
}
// BadRequestError is an alias for platform_http.BadRequestError.
type BadRequestError = platform_http.BadRequestError
// AuthFailedError is an alias for platform_http.AuthFailedError.
type AuthFailedError = platform_http.AuthFailedError
// NewAuthFailedError is an alias for platform_http.NewAuthFailedError.
var NewAuthFailedError = platform_http.NewAuthFailedError
// AuthRequiredError is an alias for platform_http.AuthRequiredError.
type AuthRequiredError = platform_http.AuthRequiredError
// NewAuthRequiredError is an alias for platform_http.NewAuthRequiredError.
var NewAuthRequiredError = platform_http.NewAuthRequiredError
// AuthHeaderRequiredError is an alias for platform_http.AuthHeaderRequiredError.
type AuthHeaderRequiredError = platform_http.AuthHeaderRequiredError
// NewAuthHeaderRequiredError is an alias for platform_http.NewAuthHeaderRequiredError.
var NewAuthHeaderRequiredError = platform_http.NewAuthHeaderRequiredError
// PermissionError, set when user is authenticated, but not allowed to perform action
type PermissionError struct {
message string
ErrorWithUUID
}
func NewPermissionError(message string) *PermissionError {
return &PermissionError{message: message}
}
func (e *PermissionError) Error() string {
return e.message
}
func (e *PermissionError) PermissionError() []map[string]string {
var forbidden []map[string]string
return forbidden
}
// IsClientError implements ErrWithIsClientError.
func (e *PermissionError) IsClientError() bool {
return true
}
// OTAForbiddenError is a special kind of forbidden error that intentionally
// exposes information about the error so it can be shown in iPad/iPhone native
// dialogs during OTA enrollment.
//
// I couldn't find any documentation but the way it works is:
//
// - if the response has a status code 403
// - and the body has a `message` field
//
// the content of `message` will be displayed to the end user.
type OTAForbiddenError struct {
ErrorWithUUID
InternalErr error
}
func (e *OTAForbiddenError) Error() string {
return "Couldn't install the profile. Invalid enroll secret. Please contact your IT admin."
}
func (e *OTAForbiddenError) StatusCode() int {
return http.StatusForbidden
}
func (e *OTAForbiddenError) Internal() string {
if e.InternalErr == nil {
return ""
}
return e.InternalErr.Error()
}
// IsClientError implements ErrWithIsClientError.
func (e *OTAForbiddenError) IsClientError() bool {
return true
}
// licenseError is returned when the application is not properly licensed.
type licenseError struct {
ErrorWithUUID
cause *string
}
func NewLicenseErrorWithCause(cause string) *licenseError {
return &licenseError{cause: &cause}
}
func (e *licenseError) Is(target error) bool {
_, ok := target.(*licenseError)
return ok
}
func (e *licenseError) Error() string {
if e.cause != nil {
return fmt.Sprintf("%s requires Fleet Premium license", *e.cause)
}
return "Requires Fleet Premium license"
}
func (e *licenseError) StatusCode() int {
return http.StatusPaymentRequired
}
// IsClientError implements ErrWithIsClientError.
func (e *licenseError) IsClientError() bool {
return true
}
// MDMNotConfiguredError is used when an MDM endpoint or resource is accessed
// without having MDM correctly configured.
type MDMNotConfiguredError struct{}
// Status implements the kithttp.StatusCoder interface so we can customize the
// HTTP status code of the response returning this error.
func (e *MDMNotConfiguredError) StatusCode() int {
return http.StatusBadRequest
}
func (e *MDMNotConfiguredError) Error() string {
return MDMNotConfiguredMessage
}
// IsClientError implements ErrWithIsClientError.
func (e *MDMNotConfiguredError) IsClientError() bool {
return true
}
// WindowsMDMNotConfiguredError is used when an MDM endpoint or resource is accessed
// without having Windows MDM correctly configured.
type WindowsMDMNotConfiguredError struct{}
// Status implements the kithttp.StatusCoder interface so we can customize the
// HTTP status code of the response returning this error.
func (e *WindowsMDMNotConfiguredError) StatusCode() int {
return http.StatusBadRequest
}
func (e *WindowsMDMNotConfiguredError) Error() string {
return WindowsMDMNotConfiguredMessage
}
// IsClientError implements ErrWithIsClientError.
func (e *WindowsMDMNotConfiguredError) IsClientError() bool {
return true
}
// AndroidMDMNotConfiguredError is used when an MDM endpoint or resource is accessed
// without having Android MDM correctly configured.
type AndroidMDMNotConfiguredError struct{}
// Status implements the kithttp.StatusCoder interface so we can customize the
// HTTP status code of the response returning this error.
func (e *AndroidMDMNotConfiguredError) StatusCode() int {
return http.StatusBadRequest
}
func (e *AndroidMDMNotConfiguredError) Error() string {
return AndroidMDMNotConfiguredMessage
}
// IsClientError implements ErrWithIsClientError.
func (e *AndroidMDMNotConfiguredError) IsClientError() bool {
return true
}
// NotConfiguredError is a generic "not configured" error that can be used
// when expected configuration is missing.
type NotConfiguredError struct{}
func (e *NotConfiguredError) Error() string {
return "not configured"
}
// GatewayError is an error type that generates a 502 or 504 status code.
type GatewayError struct {
Message string
err error
code int
ErrorWithUUID
}
// NewBadGatewayError returns a GatewayError with the message and
// error specified and that returns a 502 status code.
func NewBadGatewayError(message string, err error) *GatewayError {
return &GatewayError{
Message: message,
err: err,
code: http.StatusBadGateway,
}
}
// NewGatewayTimeoutError returns a GatewayError with the message and
// error specified and that returns a 504 status code.
func NewGatewayTimeoutError(message string, err error) *GatewayError {
return &GatewayError{
Message: message,
err: err,
code: http.StatusGatewayTimeout,
}
}
// StatusCode implements the kithttp.StatusCoder interface so we can customize the
// HTTP status code of the response returning this error.
func (e *GatewayError) StatusCode() int {
return e.code
}
// Error returns the error message.
func (e *GatewayError) Error() string {
msg := e.Message
if e.err != nil {
msg += ": " + e.err.Error()
}
return msg
}
// Error is an alias for platform_http.Error.
// It's meant to be used for errors that are related to fleet logic specifically.
type Error = platform_http.Error
const (
// ErrNoRoleNeeded is the error number for valid role needed
ErrNoRoleNeeded = 1
// ErrNoOneAdminNeeded is the error number when all admins are about to be removed
ErrNoOneAdminNeeded = 2
// ErrNoUnknownTranslate is returned when an item type in the translate payload is unknown
ErrNoUnknownTranslate = 3
// ErrAPIOnlyRole is returned when a selected role for a user is for API only users.
ErrAPIOnlyRole = 4
)
// NewError returns a fleet error with the code and message specified
func NewError(code int, message string) error {
return &Error{
Code: code,
Message: message,
}
}
// NewErrorf returns a fleet error with the code, and message formatted
// based on the format string and args specified
func NewErrorf(code int, format string, args ...interface{}) error {
return &Error{
Code: code,
Message: fmt.Sprintf(format, args...),
}
}
// UserMessageError is an alias for platform_http.UserMessageError.
type UserMessageError = platform_http.UserMessageError
// NewUserMessageError is an alias for platform_http.NewUserMessageError.
var NewUserMessageError = platform_http.NewUserMessageError
// IsJSONUnknownFieldError returns true if err is a JSON unknown field error.
// There is no exported type or value for this error, so we have to match the
// error message.
func IsJSONUnknownFieldError(err error) bool {
return platform_http.IsJSONUnknownFieldError(err)
}
// GetJSONUnknownField returns the unknown field name from a JSON unknown field error.
func GetJSONUnknownField(err error) *string {
return platform_http.GetJSONUnknownField(err)
}
// Cause returns the root error in err's chain.
var Cause = platform_errors.Cause
// FleetdError is an error that can be reported by any of the fleetd
// components.
type FleetdError struct {
ErrorSource string `json:"error_source"`
ErrorSourceVersion string `json:"error_source_version"`
ErrorTimestamp time.Time `json:"error_timestamp"`
ErrorMessage string `json:"error_message"`
ErrorAdditionalInfo map[string]any `json:"error_additional_info"`
// Vital errors are always reported to Fleet server.
Vital bool `json:"vital"`
}
// Error implements the error interface
func (fe FleetdError) Error() string {
return fe.ErrorMessage
}
// MarshalZerologObject implements `zerolog.LogObjectMarshaler` so all details
// about the error can be logged by the components that use zerolog (Orbit,
// Fleet Desktop)
func (fe FleetdError) MarshalZerologObject(e *zerolog.Event) {
e.Bool("vital", fe.Vital)
e.Str("error_source", fe.ErrorSource)
e.Str("error_source_version", fe.ErrorSourceVersion)
e.Time("error_timestamp", fe.ErrorTimestamp)
e.Str("error_message", fe.ErrorMessage)
e.Interface("error_additional_info", fe.ErrorAdditionalInfo)
}
// ToMap returns a map representation of the error
func (fe FleetdError) ToMap() map[string]any {
return map[string]any{
"vital": fe.Vital,
"error_source": fe.ErrorSource,
"error_source_version": fe.ErrorSourceVersion,
"error_timestamp": fe.ErrorTimestamp,
"error_message": fe.ErrorMessage,
"error_additional_info": fe.ErrorAdditionalInfo,
}
}
// OrbitError is used for orbit endpoints, to return an error message along
// with a failed request's response.
type OrbitError struct {
Message string
code int
}
// Error implements the error interface for the OrbitError.
func (e OrbitError) Error() string {
return e.Message
}
// StatusCode implements the ErrWithStatusCode interface for the OrbitError.
func (e OrbitError) StatusCode() int {
if e.code == 0 {
return http.StatusInternalServerError
}
return e.code
}
// IsClientError implements ErrWithIsClientError.
// Returns true for 4xx status codes, false for 5xx.
func (e OrbitError) IsClientError() bool {
code := e.StatusCode()
return code >= 400 && code < 500
}
func NewOrbitIDPAuthRequiredError() *OrbitError {
return &OrbitError{
Message: "END_USER_AUTH_REQUIRED",
code: http.StatusUnauthorized,
}
}
// Messages that may be surfaced by the server or the fleetctl client.
const (
// Hosts, general
HostNotFoundErrMsg = "Host doesn't exist. Make sure you provide a valid hostname, UUID, or serial number. Learn more about host identifiers: https://fleetdm.com/learn-more-about/host-identifiers"
NoHostsTargetedErrMsg = "No hosts targeted. Make sure you provide a valid hostname, UUID, or serial number. Learn more about host identifiers: https://fleetdm.com/learn-more-about/host-identifiers"
TargetedHostsDontExistErrMsg = "One or more targeted hosts don't exist. Make sure you provide a valid hostname, UUID, or serial number. Learn more about host identifiers: https://fleetdm.com/learn-more-about/host-identifiers"
// Scripts
RunScriptInvalidTypeErrMsg = "File type not supported. Only .sh (Shell), .py (Python), and .ps1 (PowerShell) file types are allowed."
RunScriptHostOfflineErrMsg = "Script can't run on offline host."
RunScriptForbiddenErrMsg = "You don't have the right permissions in Fleet to run the script."
RunScriptAlreadyRunningErrMsg = "A script is already running on this host. Please wait about 5 minutes to let it finish."
RunScriptHostTimeoutErrMsg = "Fleet didn't hear back from the host in under 5 minutes (timeout for live scripts). Fleet doesn't know if the script ran because it didn't receive the result. Go to Fleet and check Host details > Activities to see script results."
RunScriptScriptsDisabledGloballyErrMsg = "Running scripts is disabled in organization settings."
RunScriptDisabledErrMsg = "Scripts are disabled for this host. To run scripts, deploy the fleetd agent with scripts enabled."
RunScriptsOrbitDisabledErrMsg = "Couldn't run script. To run a script, deploy the fleetd agent with --enable-scripts."
RunScriptAsyncScriptEnqueuedMsg = "Script is running or will run when the host comes online."
RunScriptSavedMaxLenErrMsg = "Script is too large. It's limited to 500,000 characters (approximately 10,000 lines)."
RunScripUnsavedMaxLenErrMsg = "Script is too large. It's limited to 10,000 characters (approximately 125 lines)."
RunScriptGatewayTimeoutErrMsg = "Gateway timeout. Fleet didn't hear back from the host and doesn't know if the script ran. Please make sure your load balancer timeout isn't shorter than the Fleet server timeout."
RunScriptFleetVarsFailedErrMsg = "Fleet couldn't resolve variables in this script. See the script output for details."
// Software
InstallSoftwarePersonalAppleDeviceErrMsg = "Couldn't install. Currently, software install isn't supported on personal (BYOD) iOS and iPadOS hosts."
// End user authentication
EndUserAuthDEPWebURLConfiguredErrMsg = `End user authentication can't be configured when the configured automatic enrollment (DEP) profile specifies a configuration_web_url.` // #nosec G101
// Labels
InvalidLabelSpecifiedErrMsg = "Invalid label name(s):"
// Config
InvalidServerURLMsg = `Fleet server URL must use “https” or “http”.`
// macOS setup experience
BootstrapPkgNotDistributionErrMsg = "Couldnt add. Bootstrap package must be a distribution package. Learn more at: https://fleetdm.com/learn-more-about/macos-distribution-packages"
// NDES/SCEP validation
MultipleSCEPPayloadsErrMsg = "Add only one SCEP payload."
SCEPVariablesNotInSCEPPayloadErrMsg = "Variables prefixed with \"$FLEET_VAR_SCEP_\", \"$FLEET_VAR_CUSTOM_SCEP_\", \"$FLEET_VAR_NDES_SCEP\" and \"$FLEET_VAR_SMALLSTEP_\" must only be in the SCEP payload."
// Invalid list options combinations
FilterTitlesByPlatformNeedsTeamIdErrMsg = "The 'platform' and 'team_id' parameters must be used together to filter the software available for install."
)
// Error message variables
var (
NDESSCEPVariablesMissingErrMsg = fmt.Sprintf("SCEP profile for NDES certificate authority requires: $FLEET_VAR_%s, $FLEET_VAR_%s, and $FLEET_VAR_%s variables.", FleetVarNDESSCEPChallenge, FleetVarNDESSCEPProxyURL, FleetVarCertificateRenewalID)
SCEPRenewalIDWithoutURLChallengeErrMsg = "Variable \"$FLEET_VAR_" + string(FleetVarCertificateRenewalID) + "\" can't be used if variables for SCEP URL and Challenge are not specified."
)
const (
// DeleteCAReferencedByTemplatesErrMsg is the error substring used when a CA cannot be deleted because certificate templates still reference it.
DeleteCAReferencedByTemplatesErrMsg = "Certificate templates still reference it"
)
// ConflictError is used to indicate a conflict, such as a UUID conflict in the DB.
type ConflictError struct {
Message string
}
// Error implements the error interface for the ConflictError.
func (e ConflictError) Error() string {
return e.Message
}
// StatusCode implements the kithttp.StatusCoder interface.
func (e ConflictError) StatusCode() int {
return http.StatusConflict
}
// IsConflict implements the conflict interface for middleware compatibility
func (e ConflictError) IsConflict() bool {
return true
}
// IsClientError implements ErrWithIsClientError.
func (e ConflictError) IsClientError() bool {
return true
}
// Errorer is an alias for platform_http.Errorer.
type Errorer = platform_http.Errorer
type VPPIconAvailable struct {
IconURL string
}
func (e *VPPIconAvailable) Error() string {
return fmt.Sprintf("VPP icon available at: %s", e.IconURL)
}