From ad517ab731c8f68ed8d55e7d14e0d0b0f7e1d64f Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Thu, 6 Nov 2025 15:31:05 -0600 Subject: [PATCH] Make end-user auth check backwards-compatible (#35293) **Related issue:** Resolves #35214 # Details Pursuant to the discussion in https://fleetdm.slack.com/archives/C084F4MKYSJ/p1762352268815269, this PR updates the `/orbit/enroll` API handler such that: * IF end-user auth is configured for the team the host is enrolling to, * AND the host's user has not completed authentication, * AND the Orbit version making the enroll request does not support prompting for end-user authentication, * THEN the host will not be blocked from enrolling. # Checklist for submitter If some of the following don't apply, delete the relevant line. ## Testing - [ ] Added/updated automated tests working on this, will post when done - [X] QA'd all new/changed functionality manually * Set up my local Fleet instance with end-user auth enabled for setup experience on a team * With this branch running as Fleet server, ran Orbit also on this branch and attempted to enroll to that team * Verified that the SSO window was opened in my browser * With Fleet server still running this branch, switched my local working tree to `rc-minor-fleet-v4.75.0` and ran Orbit again * Verified that the host enrolled successfully and error messages appeared in the fleet server logs. --- server/fleet/capabilities.go | 4 ++++ server/service/endpoint_utils.go | 20 ++++++++++++++++++++ server/service/handler.go | 4 +++- server/service/orbit.go | 12 +++++++++++- 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/server/fleet/capabilities.go b/server/fleet/capabilities.go index 44ce9c2a46..2d345d2595 100644 --- a/server/fleet/capabilities.go +++ b/server/fleet/capabilities.go @@ -94,6 +94,9 @@ const ( // CapabilityMacOSWebSetupExperience denotes the ability of the server to support // a web-based setup experience UI for macOS devices CapabilityMacOSWebSetupExperience Capability = "macos_web_setup_experience" + // CapabilityEndUserAuth denotes the ability of the client to authenticate + // the end user against the Fleet server (e.g. SSO) before enrolling + CapabilityEndUserAuth Capability = "end_user_auth" ) func GetServerOrbitCapabilities() CapabilityMap { @@ -121,6 +124,7 @@ func GetOrbitClientCapabilities() CapabilityMap { return CapabilityMap{ CapabilityEscrowBuddy: {}, CapabilitySetupExperience: {}, + CapabilityEndUserAuth: {}, } } diff --git a/server/service/endpoint_utils.go b/server/service/endpoint_utils.go index a7e91f025f..bc225dff61 100644 --- a/server/service/endpoint_utils.go +++ b/server/service/endpoint_utils.go @@ -139,6 +139,26 @@ func newNoAuthEndpointer(svc fleet.Service, opts []kithttp.ServerOption, r *mux. } } +func newOrbitNoAuthEndpointer(svc fleet.Service, opts []kithttp.ServerOption, r *mux.Router, + versions ...string, +) *eu.CommonEndpointer[eu.HandlerFunc] { + // Add the capabilities reported by Orbit to the request context + opts = append(opts, capabilitiesContextFunc()) + + return &eu.CommonEndpointer[eu.HandlerFunc]{ + EP: &endpointer{ + svc: svc, + }, + MakeDecoderFn: makeDecoder, + EncodeFn: encodeResponse, + Opts: opts, + AuthFunc: auth.UnauthenticatedRequest, + FleetService: svc, + Router: r, + Versions: versions, + } +} + func badRequest(msg string) error { return &fleet.BadRequestError{Message: msg} } diff --git a/server/service/handler.go b/server/service/handler.go index 9bd7b98b59..0bc17eb064 100644 --- a/server/service/handler.go +++ b/server/service/handler.go @@ -987,7 +987,9 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC // This endpoint is unauthenticated and is used by to retrieve the MDM enrollment Terms of Use neWindowsMDM.GET(microsoft_mdm.MDE2TOSPath, mdmMicrosoftTOSEndpoint, MDMWebContainer{}) - ne.POST("/api/fleet/orbit/enroll", enrollOrbitEndpoint, contract.EnrollOrbitRequest{}) + // These endpoints are unauthenticated and made from orbit, and add the orbit capabilities header. + neOrbit := newOrbitNoAuthEndpointer(svc, opts, r, apiVersions...) + neOrbit.POST("/api/fleet/orbit/enroll", enrollOrbitEndpoint, contract.EnrollOrbitRequest{}) ne.GET("/api/_version_/fleet/software/titles/{title_id:[0-9]+}/in_house_app", getInHouseAppPackageEndpoint, getInHouseAppPackageRequest{}) ne.GET("/api/_version_/fleet/software/titles/{title_id:[0-9]+}/in_house_app/manifest", getInHouseAppManifestEndpoint, getInHouseAppManifestRequest{}) diff --git a/server/service/orbit.go b/server/service/orbit.go index add62446a2..ba60bd37c7 100644 --- a/server/service/orbit.go +++ b/server/service/orbit.go @@ -197,7 +197,17 @@ func (svc *Service) EnrollOrbit(ctx context.Context, hostInfo fleet.OrbitHostInf return "", fleet.OrbitError{Message: "failed to get IdP account: " + err.Error()} } if idpAccount == nil { - return "", fleet.NewOrbitIDPAuthRequiredError() + // If the Orbit client doesn't support end user auth, complain loudly and let the host enroll. + mp, ok := capabilities.FromContext(ctx) + //nolint:gocritic // ignore ifElseChain + if !ok { + level.Error(svc.logger).Log("msg", "!!! ERR_ALLOWING_UNAUTHENTICATED: host is not authenticated, but fleet could not determine whether orbit supports end-user authentication. proceeding with enrollment. !!! ", "host_uuid", hostInfo.HardwareUUID) + } else if !mp.Has(fleet.CapabilityEndUserAuth) { + level.Error(svc.logger).Log("msg", "!!! ERR_ALLOWING_UNAUTHENTICATED: host is not authenticated, but connected with an orbit version that does not support end user authentication. proceeding with enrollment. !!! ", "host_uuid", hostInfo.HardwareUUID) + } else { + // Otherwise report the unauthenticated host and let Orbit handle it (e.g. by prompting the user to authenticate). + return "", fleet.NewOrbitIDPAuthRequiredError() + } } }