39265: Add API/gitops support for Microsoft Tenant IDs (#39631)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #39265 # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes ## Testing - [x] Added/updated automated tests - [x] Where appropriate, [automated tests simulate multiple hosts and test for host isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing) (updates to one hosts's records do not affect another) - [x] QA'd all new/changed functionality manually ## New Fleet configuration settings - [ ] Setting(s) is/are explicitly excluded from GitOps If you didn't check the box above, follow this checklist for GitOps-enabled settings: - [x] Verified that the setting is exported via `fleetctl generate-gitops` - [x] Verified the setting is documented in a separate PR to [the GitOps documentation](https://github.com/fleetdm/fleet/blob/main/docs/Configuration/yaml-files.md#L485) - [x] Verified that the setting is cleared on the server if it is not supplied in a YAML file (or that it is documented as being optional) - [x] Verified that any relevant UI is disabled when GitOps mode is enabled
This commit is contained in:
@@ -3326,6 +3326,38 @@ func (a ActivityTypeDeletedCertificate) Documentation() (activity string, detail
|
||||
}`
|
||||
}
|
||||
|
||||
type ActivityTypeAddedMicrosoftEntraTenant struct {
|
||||
TenantID string `json:"tenant_id"`
|
||||
}
|
||||
|
||||
func (a ActivityTypeAddedMicrosoftEntraTenant) ActivityName() string {
|
||||
return "added_microsoft_entra_tenant"
|
||||
}
|
||||
|
||||
func (a ActivityTypeAddedMicrosoftEntraTenant) Documentation() (activity string, details string, detailsExample string) {
|
||||
return `Generated when Entra tenant is added.`,
|
||||
`This activity contains the following field:
|
||||
- "tenant_id": the ID of the Entra tenant.`, `{
|
||||
"tenant_id": "ada00076-06f6-459b-8c45-88a843a2271f"
|
||||
}`
|
||||
}
|
||||
|
||||
type ActivityTypeDeletedMicrosoftEntraTenant struct {
|
||||
TenantID string `json:"tenant_id"`
|
||||
}
|
||||
|
||||
func (a ActivityTypeDeletedMicrosoftEntraTenant) ActivityName() string {
|
||||
return "deleted_microsoft_entra_tenant"
|
||||
}
|
||||
|
||||
func (a ActivityTypeDeletedMicrosoftEntraTenant) Documentation() (activity string, details string, detailsExample string) {
|
||||
return `Generated when Entra tenant is deleted.`,
|
||||
`This activity contains the following field:
|
||||
- "tenant_id": the ID of the Entra tenant.`, `{
|
||||
"tenant_id": "ada00076-06f6-459b-8c45-88a843a2271f"
|
||||
}`
|
||||
}
|
||||
|
||||
type ActivityTypeEditedEnrollSecrets struct {
|
||||
TeamID *uint `json:"team_id"`
|
||||
TeamName *string `json:"team_name"`
|
||||
|
||||
@@ -427,6 +427,11 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
|
||||
appConfig.MDM.WindowsMigrationEnabled = false
|
||||
}
|
||||
|
||||
if oldAppConfig.MDM.WindowsEnabledAndConfigured != appConfig.MDM.WindowsEnabledAndConfigured &&
|
||||
!appConfig.MDM.WindowsEnabledAndConfigured && len(newAppConfig.MDM.WindowsEntraTenantIDs.Value) == 0 {
|
||||
appConfig.MDM.WindowsEntraTenantIDs.Value = []string{}
|
||||
}
|
||||
|
||||
// EnableDiskEncryption is an optjson.Bool field in order to support the
|
||||
// legacy field under "mdm.macos_settings". If the field provided to the
|
||||
// PATCH endpoint is set but invalid (that is, "enable_disk_encryption":
|
||||
@@ -800,6 +805,38 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addedEntraTenantIDs := make([]string, 0)
|
||||
removedEntraTenantIDs := make([]string, 0)
|
||||
oldTenantIDSet := make(map[string]struct{})
|
||||
newTenantIDSet := make(map[string]struct{})
|
||||
|
||||
for _, tenantID := range oldAppConfig.MDM.WindowsEntraTenantIDs.Value {
|
||||
oldTenantIDSet[tenantID] = struct{}{}
|
||||
}
|
||||
for _, tenantID := range appConfig.MDM.WindowsEntraTenantIDs.Value {
|
||||
newTenantIDSet[tenantID] = struct{}{}
|
||||
if _, found := oldTenantIDSet[tenantID]; !found {
|
||||
addedEntraTenantIDs = append(addedEntraTenantIDs, tenantID)
|
||||
}
|
||||
}
|
||||
for _, tenantID := range oldAppConfig.MDM.WindowsEntraTenantIDs.Value {
|
||||
if _, found := newTenantIDSet[tenantID]; !found {
|
||||
removedEntraTenantIDs = append(removedEntraTenantIDs, tenantID)
|
||||
}
|
||||
}
|
||||
for _, tenantID := range addedEntraTenantIDs {
|
||||
act := fleet.ActivityTypeAddedMicrosoftEntraTenant{TenantID: tenantID}
|
||||
if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), act); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "create activity for added Microsoft Entra tenant")
|
||||
}
|
||||
}
|
||||
for _, tenantID := range removedEntraTenantIDs {
|
||||
act := fleet.ActivityTypeDeletedMicrosoftEntraTenant{TenantID: tenantID}
|
||||
if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), act); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "create activity for deleted Microsoft Entra tenant")
|
||||
}
|
||||
}
|
||||
|
||||
// only create activities when config change has been persisted
|
||||
|
||||
switch {
|
||||
@@ -1277,6 +1314,9 @@ func (svc *Service) validateMDM(
|
||||
if mdm.EnableTurnOnWindowsMDMManually && !lic.IsPremium() {
|
||||
invalid.Append("enable_turn_on_windows_mdm_manually", ErrMissingLicense.Error())
|
||||
}
|
||||
if len(mdm.WindowsEntraTenantIDs.Value) > 0 && !lic.IsPremium() {
|
||||
invalid.Append("windows_entra_tenant_ids", ErrMissingLicense.Error())
|
||||
}
|
||||
|
||||
// we want to use `oldMdm` here as this boolean is set by the fleet
|
||||
// server at startup and can't be modified by the user
|
||||
@@ -1471,6 +1511,20 @@ func (svc *Service) validateMDM(
|
||||
invalid.Append("mdm.enable_turn_on_windows_mdm_manually", "Couldn't enable Turn on Windows MDM Manually, Windows MDM is not enabled.")
|
||||
}
|
||||
|
||||
if !mdm.WindowsEnabledAndConfigured && len(mdm.WindowsEntraTenantIDs.Value) > 0 {
|
||||
invalid.Append("mdm.windows_entra_tenant_ids", "Couldn't set Windows Entra tenant IDs, Windows MDM is not enabled.")
|
||||
}
|
||||
|
||||
// validate Windows Entra tenant IDs are in the correct format (GUIDs). We can't use the standard UUID parser here
|
||||
// as Azure tenants should be in the 8-4-4-4-12 format but the usual UUID parser will allow certain non-standard
|
||||
// forms
|
||||
guidRegex := regexp.MustCompile("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$")
|
||||
for _, tenantID := range mdm.WindowsEntraTenantIDs.Value {
|
||||
if !guidRegex.MatchString(tenantID) {
|
||||
invalid.Append("mdm.windows_entra_tenant_ids", fmt.Sprintf("Invalid Entra tenant ID: %s", tenantID))
|
||||
}
|
||||
}
|
||||
|
||||
if mdm.WindowsMigrationEnabled && mdm.EnableTurnOnWindowsMDMManually {
|
||||
invalid.Append("mdm.enable_turn_on_windows_mdm_manually", "Couldn't enable Turn on Windows MDM Manually, Windows MDM migration is also enabled. Please enable only one.")
|
||||
}
|
||||
|
||||
@@ -2030,6 +2030,10 @@ func (c *Client) DoGitOps(
|
||||
if incoming.Controls.WindowsMigrationEnabled == nil {
|
||||
mdmAppConfig["windows_migration_enabled"] = false
|
||||
}
|
||||
mdmAppConfig["windows_entra_tenant_ids"] = incoming.Controls.WindowsEntraTenantIDs
|
||||
if incoming.Controls.WindowsEntraTenantIDs == nil {
|
||||
mdmAppConfig["windows_entra_tenant_ids"] = []any{}
|
||||
}
|
||||
// Put in default values for enable_turn_on_windows_mdm_manually
|
||||
mdmAppConfig["enable_turn_on_windows_mdm_manually"] = incoming.Controls.EnableTurnOnWindowsMDMManually
|
||||
if incoming.Controls.EnableTurnOnWindowsMDMManually == nil {
|
||||
|
||||
@@ -7473,6 +7473,38 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() {
|
||||
}
|
||||
}
|
||||
|
||||
// Set an Entra tenant ID
|
||||
acResp = appConfigResponse{}
|
||||
// Should fail with invalid value
|
||||
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
|
||||
"mdm": { "windows_entra_tenant_ids": ["tenant-id-1234"] }
|
||||
}`), http.StatusUnprocessableEntity, &acResp)
|
||||
|
||||
// Should pass and create activity with valid value
|
||||
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
|
||||
"mdm": { "windows_entra_tenant_ids": ["1a86b496-e2a4-43ef-ba00-20004e29b13b"] }
|
||||
}`), http.StatusOK, &acResp)
|
||||
s.lastActivityMatches(fleet.ActivityTypeAddedMicrosoftEntraTenant{}.ActivityName(), `{"tenant_id": "1a86b496-e2a4-43ef-ba00-20004e29b13b"}`, 0)
|
||||
|
||||
// Should pass and create activity with multiple valid values
|
||||
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
|
||||
"mdm": { "windows_entra_tenant_ids": ["1a86b496-e2a4-43ef-ba00-20004e29b13b", "6dca58c4-c817-4730-831b-f3348931df05"] }
|
||||
}`), http.StatusOK, &acResp)
|
||||
s.lastActivityMatches(fleet.ActivityTypeAddedMicrosoftEntraTenant{}.ActivityName(), `{"tenant_id": "6dca58c4-c817-4730-831b-f3348931df05"}`, 0)
|
||||
|
||||
// Should fail and not create a new activity with multiple valid values and one new invalid one
|
||||
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
|
||||
"mdm": { "windows_entra_tenant_ids": ["1a86b496-e2a4-43ef-ba00-20004e29b13b", "6dca58c4-c817-4730-831b-f3348931df05", "fleetie-was-here"] }
|
||||
}`), http.StatusUnprocessableEntity, &acResp)
|
||||
// Same activity as before
|
||||
s.lastActivityMatches(fleet.ActivityTypeAddedMicrosoftEntraTenant{}.ActivityName(), `{"tenant_id": "6dca58c4-c817-4730-831b-f3348931df05"}`, 0)
|
||||
|
||||
// Should pass and create a deleted activity with less valid values than before
|
||||
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
|
||||
"mdm": { "windows_entra_tenant_ids": ["6dca58c4-c817-4730-831b-f3348931df05"] }
|
||||
}`), http.StatusOK, &acResp)
|
||||
s.lastActivityMatches(fleet.ActivityTypeDeletedMicrosoftEntraTenant{}.ActivityName(), `{"tenant_id": "1a86b496-e2a4-43ef-ba00-20004e29b13b"}`, 0)
|
||||
|
||||
// enable Windows MDM manual enrollment
|
||||
acResp = appConfigResponse{}
|
||||
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
|
||||
|
||||
Reference in New Issue
Block a user