diff --git a/changes/47699-entra-conditional-access-self-hosted b/changes/47699-entra-conditional-access-self-hosted new file mode 100644 index 0000000000..e44106ed1a --- /dev/null +++ b/changes/47699-entra-conditional-access-self-hosted @@ -0,0 +1 @@ +- Enabled Microsoft Entra conditional access for self-hosted Fleet Premium instances (previously available only on Fleet Cloud). The `microsoft_compliance_partner.proxy_api_key` server configuration has been removed; the feature is now gated on the Fleet Premium license tier. diff --git a/cmd/fleet/serve.go b/cmd/fleet/serve.go index 5ed1090c6d..f5a38cc4a9 100644 --- a/cmd/fleet/serve.go +++ b/cmd/fleet/serve.go @@ -428,23 +428,21 @@ func runServeCmd(cmd *cobra.Command, configManager configpkg.Manager, debug, dev } }) - var conditionalAccessMicrosoftProxy *conditional_access_microsoft_proxy.Proxy - if config.MicrosoftCompliancePartner.IsSet() { - var err error - conditionalAccessMicrosoftProxy, err = conditional_access_microsoft_proxy.New( - config.MicrosoftCompliancePartner.ProxyURI, - config.MicrosoftCompliancePartner.ProxyAPIKey, - func() (string, error) { - appCfg, err := ds.AppConfig(ctx) - if err != nil { - return "", fmt.Errorf("failed to load appconfig: %w", err) - } - return appCfg.ServerSettings.ServerURL, nil - }, - ) - if err != nil { - initFatal(err, "new microsoft compliance proxy") - } + // The Microsoft Compliance Partner proxy is available to all Fleet Premium + // instances (including self-hosted). The feature itself is gated on the + // license tier at the service layer. + conditionalAccessMicrosoftProxy, err := conditional_access_microsoft_proxy.New( + config.MicrosoftCompliancePartner.ProxyURI, + func() (string, error) { + appCfg, err := ds.AppConfig(ctx) + if err != nil { + return "", fmt.Errorf("failed to load appconfig: %w", err) + } + return appCfg.ServerSettings.ServerURL, nil + }, + ) + if err != nil { + initFatal(err, "new microsoft compliance proxy") } eh := errorstore.NewHandler(ctx, redisPool, logger, config.Logging.ErrorRetentionPeriod) diff --git a/cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json b/cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json index c98142b270..7e38625c23 100644 --- a/cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json +++ b/cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json @@ -232,8 +232,7 @@ }, "license": { "tier": "free", - "expiration": "0001-01-01T00:00:00Z", - "managed_cloud": false + "expiration": "0001-01-01T00:00:00Z" }, "logging": { "debug": true, diff --git a/cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml b/cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml index 85ee6ae886..9ea06161bd 100644 --- a/cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml +++ b/cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml @@ -120,7 +120,6 @@ spec: license: expiration: "0001-01-01T00:00:00Z" tier: free - managed_cloud: false logging: debug: true json: false diff --git a/cmd/fleetctl/fleetctl/testing_utils/testing_utils.go b/cmd/fleetctl/fleetctl/testing_utils/testing_utils.go index ce2abbd630..f97cd38bc2 100644 --- a/cmd/fleetctl/fleetctl/testing_utils/testing_utils.go +++ b/cmd/fleetctl/fleetctl/testing_utils/testing_utils.go @@ -88,6 +88,12 @@ func RunServerWithMockedDS(t *testing.T, opts ...*service.TestServerOpts) (*http ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { return &fleet.AppConfig{}, nil } + // On Premium, AppConfig assembly reads the Microsoft conditional access + // integration. Default to an empty (not set up) integration so tests that + // don't care about it don't panic on a nil mock. + ds.ConditionalAccessMicrosoftGetFunc = func(ctx context.Context) (*fleet.ConditionalAccessMicrosoftIntegration, error) { + return &fleet.ConditionalAccessMicrosoftIntegration{}, nil + } ds.NewGlobalPolicyFunc = func(ctx context.Context, authorID *uint, args fleet.PolicyPayload) (*fleet.Policy, error) { return &fleet.Policy{ PolicyData: fleet.PolicyData{ diff --git a/frontend/__mocks__/configMock.ts b/frontend/__mocks__/configMock.ts index 5e831304d7..e686161d61 100644 --- a/frontend/__mocks__/configMock.ts +++ b/frontend/__mocks__/configMock.ts @@ -72,7 +72,6 @@ export const DEFAULT_LICENSE_MOCK: ILicense = { device_count: 4, note: "", organization: "", - managed_cloud: true, allow_disable_telemetry: false, }; diff --git a/frontend/__mocks__/licenseMock.ts b/frontend/__mocks__/licenseMock.ts index 93d60cda4f..549df76e36 100644 --- a/frontend/__mocks__/licenseMock.ts +++ b/frontend/__mocks__/licenseMock.ts @@ -4,7 +4,6 @@ const DEFAULT_LICENSE_MOCK = { expiration: "2050-01-01T00:00:00Z", note: "test license", organization: "test org", - managed_cloud: false, allow_disable_telemetry: false, }; diff --git a/frontend/interfaces/config.ts b/frontend/interfaces/config.ts index 879d05cf81..6d57fc343c 100644 --- a/frontend/interfaces/config.ts +++ b/frontend/interfaces/config.ts @@ -15,8 +15,6 @@ export interface ILicense { expiration: string; note: string; organization: string; - // Whether the Fleet instance is managed by FleetDM - managed_cloud: boolean; allow_disable_telemetry: boolean; } diff --git a/frontend/pages/admin/IntegrationsPage/IntegrationPage.tests.tsx b/frontend/pages/admin/IntegrationsPage/IntegrationPage.tests.tsx index e10a432862..6069361284 100644 --- a/frontend/pages/admin/IntegrationsPage/IntegrationPage.tests.tsx +++ b/frontend/pages/admin/IntegrationsPage/IntegrationPage.tests.tsx @@ -39,7 +39,7 @@ describe("Integrations Page", () => { describe("Conditional access", () => { it("Does not render the conditional access sidenav for self-hosted Fleet instances", () => { const mockConfig = createMockConfig({ - license: { ...DEFAULT_LICENSE_MOCK, managed_cloud: false }, + license: { ...DEFAULT_LICENSE_MOCK }, }); const render = createCustomRenderer({ diff --git a/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx b/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx index 0f1451b53c..610178b22d 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx @@ -249,9 +249,6 @@ const ConditionalAccess = () => { const oktaConfigured = isOktaConditionalAccessConfigured(config); - // Check if this is a managed cloud deployment (Microsoft Entra requires proxy infrastructure) - const isManagedCloud = config?.license?.managed_cloud || false; - // Check Entra configuration state // Note: entraPhase is intentionally included in the dependency array to allow // manual phase overrides (e.g., AwaitingOAuth) to persist until config changes @@ -490,7 +487,7 @@ const ConditionalAccess = () => { return (
{renderOktaContent()} - {isManagedCloud && renderEntraContent()} + {renderEntraContent()}
); }; diff --git a/frontend/pages/policies/ManagePoliciesPage/components/AutomationsModal/AutomationsModal.tsx b/frontend/pages/policies/ManagePoliciesPage/components/AutomationsModal/AutomationsModal.tsx index d12031221f..0507ead721 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/AutomationsModal/AutomationsModal.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/AutomationsModal/AutomationsModal.tsx @@ -57,7 +57,7 @@ const AutomationsModal = ({ onExit, }: IAutomationsModalProps): JSX.Element | null => { const queryClient = useQueryClient(); - const { setConfig } = useContext(AppContext); + const { setConfig, isPremiumTier } = useContext(AppContext); const otherFormRef = useRef< IAutomationFormHandle @@ -104,8 +104,7 @@ const AutomationsModal = ({ ? globalConfig?.integrations.conditional_access_enabled : teamConfig?.integrations.conditional_access_enabled) ?? false; - const isManagedCloud = globalConfig?.license?.managed_cloud || false; - const conditionalAccessProviderText = isManagedCloud + const conditionalAccessProviderText = isPremiumTier ? "Okta or Microsoft Entra" : "Okta"; diff --git a/server/config/config.go b/server/config/config.go index 1b54269c9a..ca46c5d404 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -863,20 +863,11 @@ func (c ConditionalAccessConfig) Validate(initFatal func(err error, msg string)) } // MicrosoftCompliancePartnerConfig holds the server configuration for the "Conditional access" feature. -// Currently only set on Cloud environments. type MicrosoftCompliancePartnerConfig struct { - // ProxyAPIKey is a shared key required to use the Microsoft Compliance Partner proxy API (fleetdm.com). - ProxyAPIKey string `yaml:"proxy_api_key"` // ProxyURI is the URI of the Microsoft Compliance Partner proxy (for development/testing). ProxyURI string `yaml:"proxy_uri"` } -// IsSet returns if the compliance partner configuration is set. -// Currently only set on Cloud environments. -func (m MicrosoftCompliancePartnerConfig) IsSet() bool { - return m.ProxyAPIKey != "" -} - type MDMConfig struct { AppleAPNsCert string `yaml:"apple_apns_cert"` AppleAPNsCertBytes string `yaml:"apple_apns_cert_bytes"` @@ -1830,7 +1821,6 @@ func (man Manager) addConfigs() { man.addConfigBool("partnerships.enable_secureframe", false, "Point transparency URL at Secureframe landing page") // Microsoft Compliance Partner - man.addConfigString("microsoft_compliance_partner.proxy_api_key", "", "Shared key required to use the Microsoft Compliance Partner proxy API") man.addConfigString("microsoft_compliance_partner.proxy_uri", "https://fleetdm.com", "URI of the Microsoft Compliance Partner proxy (for development/testing)") man.addConfigBool("partnerships.enable_primo", false, "Disables the ability to manage multiple fleets in an instance, even in premium tier") @@ -2173,8 +2163,7 @@ func (man Manager) LoadConfig() FleetConfig { EnablePrimo: man.getConfigBool("partnerships.enable_primo"), }, MicrosoftCompliancePartner: MicrosoftCompliancePartnerConfig{ - ProxyAPIKey: man.getConfigString("microsoft_compliance_partner.proxy_api_key"), - ProxyURI: man.getConfigString("microsoft_compliance_partner.proxy_uri"), + ProxyURI: man.getConfigString("microsoft_compliance_partner.proxy_uri"), }, ConditionalAccess: ConditionalAccessConfig{ CertSerialFormat: man.getConfigString("conditional_access.cert_serial_format"), diff --git a/server/datastore/mysql/statistics.go b/server/datastore/mysql/statistics.go index 48ed9d2fc1..05cd0f44bb 100644 --- a/server/datastore/mysql/statistics.go +++ b/server/datastore/mysql/statistics.go @@ -186,7 +186,7 @@ func (ds *Datastore) ShouldSendStatistics(ctx context.Context, frequency time.Du stats.ConditionalAccessBypassDisabled = !appConfig.ConditionalAccess.BypassEnabled() } - stats.EntraConditionalAccessConfigured, err = ds.entraConditionalAccessConfigured(ctx, config) + stats.EntraConditionalAccessConfigured, err = ds.entraConditionalAccessConfigured(ctx) if err != nil { return ctxerr.Wrap(ctx, err, "entra conditional access configured") } @@ -325,9 +325,11 @@ func fleetMaintainedAppsInUseDB(ctx context.Context, db sqlx.QueryerContext) (ma return macOSApps, windowsApps, nil } -func (ds *Datastore) entraConditionalAccessConfigured(ctx context.Context, fleetConfig config.FleetConfig) (bool, error) { - // Check if the needed server configuration for Conditional Access is set. - if !fleetConfig.MicrosoftCompliancePartner.IsSet() { +func (ds *Datastore) entraConditionalAccessConfigured(ctx context.Context) (bool, error) { + // Conditional access is a Fleet Premium feature. Gate on the current license + // tier so that an integration left over from a previous Premium license + // (e.g. after a downgrade or expiry) isn't reported as configured. + if !license.IsPremium(ctx) { return false, nil } diff --git a/server/datastore/mysql/statistics_test.go b/server/datastore/mysql/statistics_test.go index 90e6b747ef..4ae85afd9f 100644 --- a/server/datastore/mysql/statistics_test.go +++ b/server/datastore/mysql/statistics_test.go @@ -574,9 +574,6 @@ func testConditionalAccessStatistics(t *testing.T, ds *Datastore) { markStatisticsStale(t, ctx, ds) // Test Entra conditional access: create the integration but without setup done - fleetConfig.MicrosoftCompliancePartner = config.MicrosoftCompliancePartnerConfig{ - ProxyAPIKey: "test-key", - } err = ds.ConditionalAccessMicrosoftCreateIntegration(ctx, "test-tenant", "test-secret") require.NoError(t, err) @@ -598,9 +595,10 @@ func testConditionalAccessStatistics(t *testing.T, ds *Datastore) { markStatisticsStale(t, ctx, ds) - // Without the fleet config proxy key, should be false even with setup done - fleetConfig.MicrosoftCompliancePartner = config.MicrosoftCompliancePartnerConfig{} - stats, shouldSend, err = ds.ShouldSendStatistics(license.NewContext(ctx, premiumLicense), time.Millisecond, fleetConfig) + // On Fleet Free (e.g. after a license downgrade/expiry) the leftover + // integration row must not be reported as configured. + freeLicense := &fleet.LicenseInfo{Tier: fleet.TierFree} + stats, shouldSend, err = ds.ShouldSendStatistics(license.NewContext(ctx, freeLicense), time.Millisecond, fleetConfig) require.NoError(t, err) assert.True(t, shouldSend) assert.False(t, stats.EntraConditionalAccessConfigured) diff --git a/server/fleet/app.go b/server/fleet/app.go index b9a6bb7905..7757c309fa 100644 --- a/server/fleet/app.go +++ b/server/fleet/app.go @@ -1932,9 +1932,6 @@ type LicenseInfo struct { Note string `json:"note,omitempty"` // AllowDisableTelemetry allows specific customers to not send analytics AllowDisableTelemetry bool `json:"allow_disable_telemetry,omitempty"` - // ManagedCloud indicates whether this Fleet instance is a cloud instance. - // Currently only used to display UI features only present on cloud instances. - ManagedCloud bool `json:"managed_cloud"` } func (l *LicenseInfo) IsPremium() bool { diff --git a/server/service/conditional_access_microsoft.go b/server/service/conditional_access_microsoft.go index 20d30be349..a55818ab9e 100644 --- a/server/service/conditional_access_microsoft.go +++ b/server/service/conditional_access_microsoft.go @@ -6,6 +6,7 @@ import ( "github.com/fleetdm/fleet/v4/server/authz" "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" + "github.com/fleetdm/fleet/v4/server/contexts/license" "github.com/fleetdm/fleet/v4/server/fleet" ) @@ -40,8 +41,8 @@ func (svc *Service) ConditionalAccessMicrosoftCreateIntegration(ctx context.Cont return "", ctxerr.Wrap(ctx, err, "failed to authorize") } - if !svc.config.MicrosoftCompliancePartner.IsSet() { - return "", &fleet.BadRequestError{Message: "microsoft conditional access configuration not set"} + if lic, _ := license.FromContext(ctx); lic == nil || !lic.IsPremium() { + return "", fleet.ErrMissingLicense } // Load current integration, if any. @@ -116,8 +117,8 @@ func (svc *Service) ConditionalAccessMicrosoftConfirm(ctx context.Context) (conf return false, "", ctxerr.Wrap(ctx, err, "failed to authorize") } - if !svc.config.MicrosoftCompliancePartner.IsSet() { - return false, "", &fleet.BadRequestError{Message: "microsoft conditional access configuration not set"} + if lic, _ := license.FromContext(ctx); lic == nil || !lic.IsPremium() { + return false, "", fleet.ErrMissingLicense } // Load current integration. @@ -182,8 +183,8 @@ func (svc *Service) ConditionalAccessMicrosoftDelete(ctx context.Context) error return ctxerr.Wrap(ctx, err, "failed to authorize") } - if !svc.config.MicrosoftCompliancePartner.IsSet() { - return &fleet.BadRequestError{Message: "microsoft conditional access configuration not set"} + if lic, _ := license.FromContext(ctx); lic == nil || !lic.IsPremium() { + return fleet.ErrMissingLicense } // Load current integration. @@ -231,7 +232,7 @@ func (svc *Service) ConditionalAccessMicrosoftGet(ctx context.Context) (*fleet.C return nil, ctxerr.Wrap(ctx, err, "failed to authorize") } - if !svc.config.MicrosoftCompliancePartner.IsSet() { + if lic, _ := license.FromContext(ctx); lic == nil || !lic.IsPremium() { return nil, nil } diff --git a/server/service/conditional_access_microsoft_proxy/conditional_access_microsoft_proxy.go b/server/service/conditional_access_microsoft_proxy/conditional_access_microsoft_proxy.go index 8de9258b1a..0d838d8940 100644 --- a/server/service/conditional_access_microsoft_proxy/conditional_access_microsoft_proxy.go +++ b/server/service/conditional_access_microsoft_proxy/conditional_access_microsoft_proxy.go @@ -18,20 +18,18 @@ import ( // Proxy holds functionality to send requests to Entra via Fleet's MS proxy. type Proxy struct { uri string - apiKey string originGetter func() (string, error) c *http.Client } -// New creates a Proxy that will use the given URI and API key. -func New(uri string, apiKey string, originGetter func() (string, error)) (*Proxy, error) { +// New creates a Proxy that will use the given URI. +func New(uri string, originGetter func() (string, error)) (*Proxy, error) { if _, err := url.Parse(uri); err != nil { return nil, fmt.Errorf("parse uri: %w", err) } return &Proxy{ - uri: uri, - apiKey: apiKey, + uri: uri, originGetter: originGetter, @@ -339,7 +337,6 @@ func (p *Proxy) setHeaders(r *http.Request) error { if origin == "" { return fmt.Errorf("missing origin: %w", err) } - r.Header.Add("MS-API-Key", p.apiKey) r.Header.Add("Origin", origin) return nil } diff --git a/server/service/conditional_access_microsoft_proxy/conditional_access_microsoft_proxy_test.go b/server/service/conditional_access_microsoft_proxy/conditional_access_microsoft_proxy_test.go index 70dff5be67..11c3dafffd 100644 --- a/server/service/conditional_access_microsoft_proxy/conditional_access_microsoft_proxy_test.go +++ b/server/service/conditional_access_microsoft_proxy/conditional_access_microsoft_proxy_test.go @@ -19,7 +19,7 @@ func TestProxyStatusErrorCapturesBody(t *testing.T) { })) defer srv.Close() - p, err := New(srv.URL, "key", func() (string, error) { return "https://fleet.example.com", nil }) + p, err := New(srv.URL, func() (string, error) { return "https://fleet.example.com", nil }) require.NoError(t, err) _, err = p.SetComplianceStatus(t.Context(), "tenant", "secret", "device", "upn", true, "name", "macOS", "14.0", false, time.Now()) @@ -48,7 +48,7 @@ func TestProxyStatusErrorCapturesBody(t *testing.T) { })) defer srv.Close() - p, err := New(srv.URL, "key", func() (string, error) { return "https://fleet.example.com", nil }) + p, err := New(srv.URL, func() (string, error) { return "https://fleet.example.com", nil }) require.NoError(t, err) _, err = p.SetComplianceStatus(t.Context(), "tenant", "secret", "device", "upn", true, "name", "macOS", "14.0", false, time.Now()) diff --git a/server/service/integration_core_test.go b/server/service/integration_core_test.go index ba8075ae5c..cd1490b569 100644 --- a/server/service/integration_core_test.go +++ b/server/service/integration_core_test.go @@ -15883,24 +15883,19 @@ func (s *integrationTestSuite) TestHostReenrollWithSameHostRowRefetchOsquery() { } } -func (s *integrationTestSuite) TestConditionalAccessOnlyCloud() { - t := s.T() - - var resp appConfigResponse - s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &resp) - require.False(t, resp.License.ManagedCloud) - - // Microsoft compliance partner APIs should fail if the setting is not set (only set on Cloud). +func (s *integrationTestSuite) TestConditionalAccessRequiresPremium() { + // Microsoft compliance partner APIs should fail on Fleet Free (this suite + // runs without a premium license). var r conditionalAccessMicrosoftCreateResponse s.DoJSON("POST", "/api/latest/fleet/conditional-access/microsoft", conditionalAccessMicrosoftCreateRequest{ MicrosoftTenantID: "foobar", - }, http.StatusBadRequest, &r) + }, http.StatusPaymentRequired, &r) var c conditionalAccessMicrosoftConfirmResponse s.DoJSON("POST", "/api/latest/fleet/conditional-access/microsoft/confirm", conditionalAccessMicrosoftConfirmRequest{}, - http.StatusBadRequest, &c) + http.StatusPaymentRequired, &c) var d conditionalAccessMicrosoftDeleteResponse - s.DoJSON("POST", "/api/latest/fleet/conditional-access/microsoft/confirm", conditionalAccessMicrosoftConfirmRequest{}, - http.StatusBadRequest, &d) + s.DoJSON("DELETE", "/api/latest/fleet/conditional-access/microsoft", nil, + http.StatusPaymentRequired, &d) } func (s *integrationTestSuite) TestUpdateHostCertificateTemplate() { diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index cfefa9caa3..dab48b8bdc 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -23545,10 +23545,7 @@ func (s *integrationEnterpriseTestSuite) TestConditionalAccessBasicSetup() { s.clearOktaConditionalAccess() }) - // Test license.managed_cloud is set on Cloud environments. var acResp appConfigResponse - s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp) - require.True(t, acResp.License.ManagedCloud) // Test global maintainer fails to create the integration. u := &fleet.User{ diff --git a/server/service/osquery.go b/server/service/osquery.go index e3127947bb..52cd5a4de9 100644 --- a/server/service/osquery.go +++ b/server/service/osquery.go @@ -2622,8 +2622,10 @@ func (svc *Service) processScriptsForNewlyFailingPolicies( } func (svc *Service) conditionalAccessConfiguredAndEnabledForTeam(ctx context.Context, hostTeamID *uint) (configured bool, enabledForTeam bool, err error) { - // Check if the needed server configuration for Conditional Access is set. - if !svc.config.MicrosoftCompliancePartner.IsSet() { + // Conditional access is a Fleet Premium feature. Gate on the current license + // tier so that an integration left over from a previous Premium license + // (e.g. after a downgrade or expiry) doesn't keep the feature active. + if !license.IsPremium(ctx) { return false, false, nil } diff --git a/server/service/service_appconfig.go b/server/service/service_appconfig.go index 497203ca0e..14d03a7ba2 100644 --- a/server/service/service_appconfig.go +++ b/server/service/service_appconfig.go @@ -86,16 +86,9 @@ func (svc *Service) License(ctx context.Context) (*fleet.LicenseInfo, error) { } licChecker, _ := license.FromContext(ctx) - // Type assert to get the concrete type for modification and return + // Type assert to get the concrete type to return. lic, _ := licChecker.(*fleet.LicenseInfo) - // Currently we use the presence of Microsoft Compliance Partner settings - // (only configured in cloud instances) to determine if a Fleet instance - // is a cloud managed instance. - if lic != nil && svc.config.MicrosoftCompliancePartner.IsSet() { - lic.ManagedCloud = true - } - return lic, nil } diff --git a/server/service/svctest/service.go b/server/service/svctest/service.go index 35acea1f58..da33c4618a 100644 --- a/server/service/svctest/service.go +++ b/server/service/svctest/service.go @@ -170,7 +170,9 @@ func newTestServiceWithConfig(t *testing.T, ds fleet.Datastore, fleetConfig conf } if len(opts) > 0 && opts[0].ConditionalAccessMicrosoftProxy != nil { conditionalAccessMicrosoftProxy = opts[0].ConditionalAccessMicrosoftProxy - fleetConfig.MicrosoftCompliancePartner.ProxyAPIKey = "insecure" // setting this so the feature is "enabled". + // The Conditional Access feature is gated on Fleet Premium; callers that + // exercise it must provide a premium license via opts[0].License. + require.True(t, lic.IsPremium(), "ConditionalAccessMicrosoftProxy requires a premium license via opts.License") } if len(opts) > 0 && opts[0].AndroidModule != nil { diff --git a/server/service/testing_utils_test.go b/server/service/testing_utils_test.go index 864bdacc0b..0a096bc905 100644 --- a/server/service/testing_utils_test.go +++ b/server/service/testing_utils_test.go @@ -98,6 +98,15 @@ func newTestServiceWithConfig(t *testing.T, ds fleet.Datastore, fleetConfig conf if mockDS.ValidateReferencedCustomHostVitalsFunc == nil { mockDS.ValidateReferencedCustomHostVitalsFunc = func(ctx context.Context, documents []string) error { return nil } } + // On Premium, AppConfig assembly and the osquery detail-query flow read the + // Microsoft conditional access integration. Default to an empty (not set up) + // integration so premium tests that don't care about it don't panic on a nil + // mock. Tests that assert on it can override. + if mockDS.ConditionalAccessMicrosoftGetFunc == nil { + mockDS.ConditionalAccessMicrosoftGetFunc = func(ctx context.Context) (*fleet.ConditionalAccessMicrosoftIntegration, error) { + return &fleet.ConditionalAccessMicrosoftIntegration{}, nil + } + } } lic := &fleet.LicenseInfo{Tier: fleet.TierFree} @@ -224,7 +233,9 @@ func newTestServiceWithConfig(t *testing.T, ds fleet.Datastore, fleetConfig conf } if len(opts) > 0 && opts[0].ConditionalAccessMicrosoftProxy != nil { conditionalAccessMicrosoftProxy = opts[0].ConditionalAccessMicrosoftProxy - fleetConfig.MicrosoftCompliancePartner.ProxyAPIKey = "insecure" // setting this so the feature is "enabled". + // The Conditional Access feature is gated on Fleet Premium; callers that + // exercise it must provide a premium license via opts[0].License. + require.True(t, lic.IsPremium(), "ConditionalAccessMicrosoftProxy requires a premium license via opts.License") } if len(opts) > 0 && opts[0].AndroidModule != nil {