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 {