Skip to content

Added support of $FLEET_VAR_HOST_UUID in Windows MDM configuration profiles - #31695

Merged
getvictor merged 13 commits into
mainfrom
victor/30879-host-uuid-for-windows-profiles
Aug 10, 2025
Merged

Added support of $FLEET_VAR_HOST_UUID in Windows MDM configuration profiles#31695
getvictor merged 13 commits into
mainfrom
victor/30879-host-uuid-for-windows-profiles

Conversation

@getvictor

@getvictor getvictor commented Aug 7, 2025

Copy link
Copy Markdown
Member

Fixes #30879

Demo video: https://www.youtube.com/watch?v=jVyh5x8EMnc

I added a FleetVarName type, which should improve safety/maintainability, but that resulted in a lot of files touched.

I also added the following. However, these are not strictly needed for this feature (only useful for debug right now). But we are following the pattern created by MDM team.

  1. Add the migration to insert HOST_UUID into fleet_variables
  2. Update the Windows profile save logic to populate mdm_configuration_profile_variables

Checklist for submitter

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.

Testing

  • Added/updated automated tests
  • Where appropriate, [automated tests simulate multiple hosts and test for host isolation]
  • QA'd all new/changed functionality manually

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added support for the $FLEET_VAR_HOST_UUID variable in Windows MDM configuration profiles, enabling per-host customization during profile deployment.
    • Enhanced profile delivery by substituting Fleet variables with actual host data in Windows profiles.
    • Introduced a database migration to register the new Fleet variable for host UUID.
  • Bug Fixes

    • Improved validation and error handling to reject unsupported Fleet variables in Windows MDM profiles with detailed messages.
    • Ensured robust handling of errors during profile command insertion without aborting the entire reconciliation process.
  • Tests

    • Added extensive tests covering validation, substitution, error handling, and reconciliation workflows for Windows MDM profiles using Fleet variables.

@codecov

codecov Bot commented Aug 7, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.66667% with 34 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.77%. Comparing base (c316fd4) to head (23f8b6b).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
server/service/microsoft_mdm.go 78.94% 12 Missing and 4 partials ⚠️
.../tables/20250808000000_AddHostUUIDFleetVariable.go 63.63% 6 Missing and 2 partials ⚠️
server/datastore/mysql/microsoft_mdm.go 82.92% 5 Missing and 2 partials ⚠️
server/service/mdm.go 86.95% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main   #31695   +/-   ##
=======================================
  Coverage   63.77%   63.77%           
=======================================
  Files        1963     1964    +1     
  Lines      192101   192175   +74     
  Branches     6327     6289   -38     
=======================================
+ Hits       122505   122566   +61     
- Misses      60027    60031    +4     
- Partials     9569     9578    +9     
Flag Coverage Δ
backend 65.16% <86.66%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@getvictor

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 7, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai

coderabbitai Bot commented Aug 7, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This change introduces support for the $FLEET_VAR_HOST_UUID variable in Windows MDM configuration profiles, enabling its substitution with the host's UUID during profile delivery. The update includes validation, substitution logic, and comprehensive tests to ensure only this variable is allowed and correctly processed in Windows profiles.

Changes

Cohort / File(s) Change Summary
Fleet variable constants
server/fleet/mdm.go
Added new constant FleetVarHostUUID representing the supported host UUID variable for use in configuration profiles.
Apple MDM profile variable validation
server/service/apple_mdm.go
Renamed fleetVarsSupportedInConfigProfiles to fleetVarsSupportedInAppleConfigProfiles for clarity; no logic changes.
Windows MDM profile variable validation
server/service/mdm.go, server/service/mdm_test.go
Added fleetVarsSupportedInWindowsProfiles listing allowed variables (currently only host UUID). Updated validateWindowsProfileFleetVariables to allow only $FLEET_VAR_HOST_UUID and return detailed errors for unsupported variables. Added unit tests for validation logic.
Windows MDM profile variable substitution and command delivery
server/service/microsoft_mdm.go
Added preprocessWindowsProfileContents to substitute $FLEET_VAR_HOST_UUID with the actual host UUID (XML-escaped). Updated ReconcileWindowsProfiles to process and deliver per-host customized profiles if variables are present. Enhanced error handling and status updates for per-host failures.
Windows MDM profile substitution and error handling tests
server/service/microsoft_mdm_test.go
Added tests for profile content preprocessing and error handling in reconciliation, including variable substitution and command insertion failures.
Integration tests for Windows MDM Fleet variables
server/service/integration_mdm_profiles_test.go
Added integration tests verifying validation and substitution of $FLEET_VAR_HOST_UUID in Windows MDM profiles, ensuring correct acceptance, rejection, and variable replacement in delivered profiles.
Datastore interface and implementation updates
server/fleet/datastore.go, server/datastore/mysql/microsoft_mdm.go, server/datastore/mysql/mdm.go, server/datastore/mysql/mdm_test.go, server/datastore/mysql/microsoft_mdm_test.go, server/mock/datastore_mock.go, server/datastore/mysql/teams_test.go, server/datastore/mysql/apple_mdm_test.go, server/datastore/mysql/hosts_test.go
Updated NewMDMWindowsConfigProfile and batch set functions to accept and handle Fleet variables used in Windows profiles, including persisting associations in the database. Test code updated to reflect new function signatures with additional parameters.
Database migration and schema updates
server/datastore/mysql/migrations/tables/20250808000000_AddHostUUIDFleetVariable.go, server/datastore/mysql/schema.sql
Added migration to insert new Fleet variable FLEET_VAR_HOST_UUID into the database with timestamp. Updated schema auto-increment values and inserted migration status entry.

Sequence Diagram(s)

sequenceDiagram
    participant Admin as IT Admin
    participant Fleet as Fleet Server
    participant Host as Windows Host

    Admin->>Fleet: Uploads Windows MDM profile with $FLEET_VAR_HOST_UUID
    Fleet->>Fleet: Validate profile variables (only allow HOST_UUID)
    alt Valid
        Fleet->>Host: Delivers profile (with $FLEET_VAR_HOST_UUID)
        Host->>Fleet: Requests profile
        Fleet->>Fleet: Substitute $FLEET_VAR_HOST_UUID with host UUID
        Fleet->>Host: Sends customized profile
    else Invalid variable
        Fleet-->>Admin: Rejects profile upload with error
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~35 minutes

Assessment against linked issues

Objective Addressed Explanation
Support use of $FLEET_VAR_HOST_UUID in Windows configuration profiles (#30879)
Substitute $FLEET_VAR_HOST_UUID with host UUID during profile delivery (#30879)
Validate and reject unsupported Fleet variables in Windows profiles (#30879)
Comprehensive tests for validation and substitution of host UUID variable in Windows profiles (#30879)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes found.

Suggested reviewers

  • rachaelshaw
  • sharon-fdm

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch victor/30879-host-uuid-for-windows-profiles

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
server/service/microsoft_mdm.go (1)

2232-2235: Consider simplifying the XML escaping approach

The current buffer allocation and escaping can be simplified. Since xml.EscapeText may need to grow the buffer anyway when special characters are present, the initial capacity doesn't provide much benefit.

-			// Use XML escaping for the replacement value to be safe and prevent XML injection
-			b := make([]byte, 0, len(hostUUID))
-			buf := bytes.NewBuffer(b)
-			_ = xml.EscapeText(buf, []byte(hostUUID))
-			escapedUUID := buf.String()
+			// Use XML escaping for the replacement value to be safe and prevent XML injection
+			var buf bytes.Buffer
+			_ = xml.EscapeText(&buf, []byte(hostUUID))
+			escapedUUID := buf.String()
server/service/integration_mdm_profiles_test.go (2)

7010-7016: Consider adding cleanup for the created team.

While the test suite may handle cleanup automatically, it's good practice to ensure the team is cleaned up after the test completes to avoid potential test pollution.

Add a cleanup defer statement after team creation:

 func (s *integrationMDMTestSuite) TestWindowsProfilesWithFleetVariables() {
 	t := s.T()
 	ctx := t.Context()
 
 	// Create a team for team-scoped tests
 	tm, err := s.ds.NewTeam(ctx, &fleet.Team{Name: "test_windows_fleet_vars"})
 	require.NoError(t, err)
+	t.Cleanup(func() {
+		_ = s.ds.DeleteTeam(ctx, tm.ID)
+	})

7167-7173: Add cleanup for the created team.

Similar to the previous test, consider adding cleanup for the team to prevent test pollution.

 func (s *integrationMDMTestSuite) TestWindowsProfilesFleetVariableSubstitution() {
 	t := s.T()
 	ctx := context.Background()
 
 	// Create a team
 	tm, err := s.ds.NewTeam(ctx, &fleet.Team{Name: t.Name() + "team"})
 	require.NoError(t, err)
+	t.Cleanup(func() {
+		_ = s.ds.DeleteTeam(ctx, tm.ID)
+	})
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa02ec6 and c484ed0.

📒 Files selected for processing (8)
  • changes/30879-host-uuid-for-windows-profiles (1 hunks)
  • server/fleet/mdm.go (1 hunks)
  • server/service/apple_mdm.go (2 hunks)
  • server/service/integration_mdm_profiles_test.go (1 hunks)
  • server/service/mdm.go (1 hunks)
  • server/service/mdm_test.go (1 hunks)
  • server/service/microsoft_mdm.go (4 hunks)
  • server/service/microsoft_mdm_test.go (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

⚙️ CodeRabbit Configuration File

When reviewing SQL queries that are added or modified, ensure that appropriate filtering criteria are applied—especially when a query is intended to return data for a specific entity (e.g., a single host). Check for missing WHERE clauses or incorrect filtering that could lead to incorrect or non-deterministic results (e.g., returning the first row instead of the correct one). Flag any queries that may return unintended results due to lack of precise scoping.

Files:

  • server/service/microsoft_mdm.go
  • server/service/mdm.go
  • server/fleet/mdm.go
  • server/service/microsoft_mdm_test.go
  • server/service/apple_mdm.go
  • server/service/mdm_test.go
  • server/service/integration_mdm_profiles_test.go
🧠 Learnings (2)
📚 Learning: in the host_identity_scep_certificates table schema, the varbinary(100) size for public_key_raw, the...
Learnt from: getvictor
PR: fleetdm/fleet#30589
File: server/datastore/mysql/schema.sql:501-517
Timestamp: 2025-07-07T22:21:15.748Z
Learning: In the host_identity_scep_certificates table schema, the VARBINARY(100) size for public_key_raw, the nullable host_id without a foreign key constraint, and the use of plain DATETIME instead of DATETIME(6) are intentional design decisions, not issues to be addressed.

Applied to files:

  • server/fleet/mdm.go
📚 Learning: in ee/server/service/hostidentity/depot/depot.go, the scep depot interface methods like put() do not...
Learnt from: getvictor
PR: fleetdm/fleet#30589
File: ee/server/service/hostidentity/depot/depot.go:108-111
Timestamp: 2025-07-08T16:12:48.797Z
Learning: In ee/server/service/hostidentity/depot/depot.go, the SCEP depot interface methods like Put() do not accept context parameters, and the common_mysql.WithRetryTxx callback function type TxFn only receives a transaction parameter, not a context. Therefore, using context.Background() in tx.ExecContext calls within the transaction callback is the correct approach.

Applied to files:

  • server/service/microsoft_mdm_test.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (30)
  • GitHub Check: publish
  • GitHub Check: test-packaging (ubuntu-latest, remote)
  • GitHub Check: test-packaging (ubuntu-latest, local)
  • GitHub Check: build-binaries
  • GitHub Check: test-preview (ubuntu-latest)
  • GitHub Check: test-db-changes
  • GitHub Check: Analyze (javascript)
  • GitHub Check: lint (ubuntu-latest)
  • GitHub Check: Analyze (go)
  • GitHub Check: lint (windows-latest)
  • GitHub Check: test-go (integration-mdm, ubuntu-latest, mysql:9.3.0, false)
  • GitHub Check: test-go (integration-enterprise, ubuntu-latest, mysql:9.3.0, false)
  • GitHub Check: lint (macos-latest)
  • GitHub Check: test-go (integration-mdm, ubuntu-latest, mysql:8.0.36, false)
  • GitHub Check: build-and-check
  • GitHub Check: test-go (fleetctl, ubuntu-latest, mysql:9.3.0, false)
  • GitHub Check: test-go (mysql, ubuntu-latest, mysql:8.0.36, false)
  • GitHub Check: test-go (fleetctl, ubuntu-latest, mysql:8.0.36, false)
  • GitHub Check: test-go (mysql, ubuntu-latest, mysql:9.3.0, false)
  • GitHub Check: test-go (main, ubuntu-latest, mysql:8.0.36, false)
  • GitHub Check: test-go (vuln, ubuntu-latest, mysql:8.0.36, false)
  • GitHub Check: test-go (main, ubuntu-latest, mysql:9.3.0, false)
  • GitHub Check: test-go (integration-core, ubuntu-latest, mysql:8.0.36, false)
  • GitHub Check: test-go (service, ubuntu-latest, mysql:9.3.0, false)
  • GitHub Check: test-go (integration-core, ubuntu-latest, mysql:9.3.0, false)
  • GitHub Check: test-go (vuln, ubuntu-latest, mysql:9.3.0, false)
  • GitHub Check: test-go (service, ubuntu-latest, mysql:8.0.36, false)
  • GitHub Check: test-go (integration-enterprise, ubuntu-latest, mysql:8.0.36, false)
  • GitHub Check: test-go (fast, ubuntu-latest, mysql:8.0.36, false)
  • GitHub Check: check-doc-gen
🔇 Additional comments (10)
changes/30879-host-uuid-for-windows-profiles (1)

1-1: LGTM! Clear and accurate changelog entry.

The changelog entry concisely describes the new feature and correctly references the $FLEET_VAR_HOST_UUID variable format that users will interact with.

server/service/apple_mdm.go (1)

82-86: Rename looks good – no further action

Local variable was renamed for clarity and the only in-file reference was updated. No stale occurrences detected.

Also applies to: 494-495

server/service/mdm_test.go (1)

2251-2360: LGTM! Comprehensive test coverage for Windows profile Fleet variable validation.

This test function provides excellent coverage for the validateWindowsProfileFleetVariables function with well-structured test cases covering:

  • Valid scenarios: no variables, supported $FLEET_VAR_HOST_UUID variable (with and without braces), multiple occurrences
  • Invalid scenarios: unsupported variables, mixed supported/unsupported variables, unknown variables
  • Proper error message validation to ensure specific unsupported variables are identified

The test implementation follows Go testing best practices and aligns perfectly with the PR objective to add $FLEET_VAR_HOST_UUID support for Windows MDM profiles.

server/service/mdm.go (5)

1519-1523: Well-structured variable declaration for supported Fleet variables.

The explicit allowlist approach using a slice is clean and makes it easy to extend support for additional variables in the future. The variable name clearly indicates its purpose and scope.


1525-1538: Improved validation logic with better error handling.

The refactored validation function is a significant improvement:

  • Early return when no variables are found avoids unnecessary processing
  • Iterates through found variables to check against the allowlist
  • Provides specific error messages indicating which variable is unsupported
  • Uses slices.Contains for clean membership checking

The logic correctly handles the case where multiple unsupported variables exist by returning on the first unsupported one found, which is appropriate for validation scenarios.


1534-1534: Excellent error message formatting.

The error message is clear, specific, and actionable. It follows the established pattern of Fleet error messages and includes the exact variable name that caused the validation failure.


1522-1522: Confirmed fleet.FleetVarHostUUID definition

The constant is defined in server/fleet/mdm.go:

// server/fleet/mdm.go
const (
    FleetVarHostUUID = "HOST_UUID"
    // …
)

No changes are required—this reference is valid and accessible.


1526-1526: findFleetVariables is already defined in this package

The call in server/service/mdm.go is valid—findFleetVariables(contents) is implemented in server/service/apple_mdm.go:5867 (and no import is necessary for unexported functions in the same package). You can safely remove the import-check suggestion.

Likely an incorrect or invalid review comment.

server/service/integration_mdm_profiles_test.go (2)

7018-7140: Excellent test coverage for Fleet variable validation!

The test cases provide comprehensive coverage of supported and unsupported Fleet variables, including edge cases like variables with/without braces, mixed variable scenarios, and proper error message validation.


7208-7273: Well-structured end-to-end test with thorough verification!

The test effectively validates:

  • UUID substitution in profile content for both global and team hosts
  • Proper removal of Fleet variable patterns (both $FLEET_VAR_HOST_UUID and ${FLEET_VAR_HOST_UUID})
  • Correct profile status updates in the database
  • Proper MDM session handling with status responses

The helper function verifyProfileSubstitution is particularly well-designed for reusability across different host scenarios.

Comment thread server/fleet/mdm.go Outdated
Comment thread server/service/microsoft_mdm_test.go Outdated
Comment thread server/service/microsoft_mdm_test.go
Comment thread server/service/microsoft_mdm.go Outdated
@getvictor

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 8, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (27)
changes/30879-host-uuid-for-windows-profiles (1)

1-2: Clarify scope in changelog (Windows-only, Premium).

Recommend explicitly stating platform and edition to reduce ambiguity in release notes.

-Added support of $FLEET_VAR_HOST_UUID in Windows MDM configuration profiles.
+Added support for $FLEET_VAR_HOST_UUID in Windows MDM configuration profiles.
+Note: Windows-only. Available in Fleet Premium.
server/service/apple_mdm.go (1)

494-549: Optional: use a precomputed set for O(1) lookups instead of slices.Contains

Not required, but you can avoid repeated linear scans by computing a set once and checking membership in O(1). Example diffs:

  • Build a set alongside the slice:
 var (
@@
-   fleetVarsSupportedInAppleConfigProfiles = []string{
+   fleetVarsSupportedInAppleConfigProfiles = []string{
       fleet.FleetVarNDESSCEPChallenge, fleet.FleetVarNDESSCEPProxyURL, fleet.FleetVarHostEndUserEmailIDP,
       fleet.FleetVarHostHardwareSerial, fleet.FleetVarHostEndUserIDPUsername, fleet.FleetVarHostEndUserIDPUsernameLocalPart,
       fleet.FleetVarHostEndUserIDPGroups, fleet.FleetVarHostEndUserIDPDepartment, fleet.FleetVarSCEPRenewalID,
     }
+   // Precomputed set for O(1) membership checks in validation paths.
+   fleetVarsSupportedInAppleConfigProfilesSet = func() map[string]struct{} {
+     m := make(map[string]struct{}, len(fleetVarsSupportedInAppleConfigProfiles))
+     for _, v := range fleetVarsSupportedInAppleConfigProfiles {
+       m[v] = struct{}{}
+     }
+     return m
+   }()
 )
  • Use the set in validation:
-    if !slices.Contains(fleetVarsSupportedInAppleConfigProfiles, k) {
+    if _, ok := fleetVarsSupportedInAppleConfigProfilesSet[k]; !ok {
       found := false
       switch {
server/datastore/mysql/mdm_test.go (3)

675-676: Signature update LGTM; consider explicit empty slice over nil

Passing nil works for []string, but using []string{} makes “no Fleet vars used” explicit and avoids surprises if downstream code distinguishes nil vs empty (e.g., JSON/null vs [] or conditional len checks).

-            nil,
+            []string{},

(Apply to both occurrences in this hunk.)

Also applies to: 681-682


766-767: Confirm nil implies “no variables recorded” semantics (and consider asserting it)

If nil is intended to mean “no Fleet vars used,” ensure the datastore treats it identically to an empty slice (i.e., no rows inserted into mdm_configuration_profile_variables). If relevant to this test’s scope, consider asserting that zero vars are recorded for the created profile.

I can draft a focused assertion or a small companion test that checks the variables table is empty when usesFleetVars is nil.


6473-6474: Consistent use of nil for usesFleetVars; ensure no unintended DB artifacts

For top-level profiles, passing nil should not create any rows in mdm_configuration_profile_variables. If the implementation differentiates nil vs empty, prefer []string{} for clarity.

-        nil,
+        []string{},
server/fleet/datastore.go (1)

1729-1729: Document usesFleetVars semantics and nil vs empty behavior

Please add a short comment explaining allowed values, whether order matters, and how nil vs empty should be treated (both meaning “no variables” is typical). This avoids ambiguity for implementers and tests.

For example (non-diff snippet):

// NewMDMWindowsConfigProfile creates and returns a new configuration profile.
// usesFleetVars is the set of Fleet variable names referenced by the profile payload.
// Valid values are constrained/validated upstream; both nil and empty slice mean "no variables".
NewMDMWindowsConfigProfile(ctx context.Context, cp MDMWindowsConfigProfile, usesFleetVars []string) (*MDMWindowsConfigProfile, error)
server/datastore/mysql/apple_mdm_test.go (1)

146-146: Team-scoped call updated correctly; consider adding non-nil variable coverage.

This change aligns with the new signature. As a follow-up, add or confirm a Windows-specific test that:

  • Creates a profile with variables containing $FLEET_VAR_HOST_UUID.
  • Verifies mdm_configuration_profile_variables is populated accordingly.
  • Asserts substitution occurs (and that updates to the host UUID trigger resend as planned).

I can draft a subtest skeleton if helpful.

server/datastore/mysql/migrations/tables/20250808000000_AddHostUUIDFleetVariable.go (3)

16-21: Simplify binding and drop sqlx dependency (optional)

You only bind a single value. Switching to positional binds removes the sqlx dependency and reduces complexity.

- insStmt := `
- INSERT INTO fleet_variables (
-     name, is_prefix, created_at
- ) VALUES
-     ('FLEET_VAR_HOST_UUID', 0, :created_at)
- `
- // use a constant time so that the generated schema is deterministic
- createdAt := time.Date(2025, 8, 8, 0, 0, 0, 0, time.UTC)
- stmt, args, err := sqlx.Named(insStmt, map[string]any{"created_at": createdAt})
- if err != nil {
-     return fmt.Errorf("Failed to prepare insert for FLEET_VAR_HOST_UUID: %w", err)
- }
- _, err = tx.Exec(stmt, args...)
+ insStmt := `
+ INSERT INTO fleet_variables (
+     name, is_prefix, created_at
+ ) VALUES
+     ('FLEET_VAR_HOST_UUID', 0, ?)
+ `
+ // use a constant time so that the generated schema is deterministic
+ createdAt := time.Date(2025, 8, 8, 0, 0, 0, 0, time.UTC)
+ _, err := tx.Exec(insStmt, createdAt)
  if err != nil {
-     return fmt.Errorf("Failed to insert FLEET_VAR_HOST_UUID into fleet_variables: %w", err)
+     return fmt.Errorf("failed to insert FLEET_VAR_HOST_UUID into fleet_variables: %w", err)
  }

If you apply this, also remove the sqlx import.

Also applies to: 24-31


3-9: Remove unused dependency if you adopt positional binds

If you take the optional simplification, drop the sqlx import:

 import (
     "database/sql"
     "fmt"
     "time"
-
-    "github.com/jmoiron/sqlx"
 )

26-30: Nit: error message casing

Go errors should start lowercase. Also, after simplifying binds, only one error path remains.

- return fmt.Errorf("Failed to prepare insert for FLEET_VAR_HOST_UUID: %w", err)
+ return fmt.Errorf("failed to prepare insert for FLEET_VAR_HOST_UUID: %w", err)
- return fmt.Errorf("Failed to insert FLEET_VAR_HOST_UUID into fleet_variables: %w", err)
+ return fmt.Errorf("failed to insert FLEET_VAR_HOST_UUID into fleet_variables: %w", err)
server/mock/datastore_mock.go (1)

7439-7444: Add nil-guard and copy function under lock to avoid race/panic in tests.

Current code dereferences s.NewMDMWindowsConfigProfileFunc without checking for nil and reads it outside the lock. Suggest capturing it under the mutex and panicking with a clear message if unset.

 func (s *DataStore) NewMDMWindowsConfigProfile(ctx context.Context, cp fleet.MDMWindowsConfigProfile, usesFleetVars []string) (*fleet.MDMWindowsConfigProfile, error) {
-  s.mu.Lock()
-  s.NewMDMWindowsConfigProfileFuncInvoked = true
-  s.mu.Unlock()
-  return s.NewMDMWindowsConfigProfileFunc(ctx, cp, usesFleetVars)
+  s.mu.Lock()
+  s.NewMDMWindowsConfigProfileFuncInvoked = true
+  fn := s.NewMDMWindowsConfigProfileFunc
+  s.mu.Unlock()
+  if fn == nil {
+    panic("datastore mock: NewMDMWindowsConfigProfileFunc not set")
+  }
+  return fn(ctx, cp, usesFleetVars)
 }
server/datastore/mysql/mdm.go (1)

328-330: Comment is accurate; clarify cross-platform variable keying to avoid confusion.

Since Apple uses identifier and Windows uses name, consider adding a brief doc comment (in the type that carries these variables) clarifying what “identifier” represents per platform to prevent misuse.

server/datastore/mysql/microsoft_mdm_test.go (2)

2242-2246: Consider adding a variables-bearing profile in this labels test

These calls are fine. To guard against regressions, add one Windows profile here with a non-nil variables slice to ensure:

  • variable associations are persisted alongside include/exclude label logic, and
  • ListMDMWindowsProfilesToInstall behavior remains unaffected by mere presence of variables.

If helpful, I can sketch an assertion similar to checkProfileVariables used later in this file to confirm associations for Windows.


2423-2431: Exercise batchSetMDMWindowsProfilesDB with variables to validate Windows associations

Add a subtest in server/datastore/mysql/microsoft_mdm_test.go (after the existing applyAndExpect block) that:

  • Calls ds.batchSetMDMWindowsProfilesDB with a non-nil variables slice (e.g. one fleet.MDMProfileUUIDFleetVariables entry).
  • Queries mdm_configuration_profile_variables joined with mdm_windows_configuration_profiles and fleet_variables to assert the expected windows_profile_uuid → fleet_variable_id mapping.

Example sketch:

t.Run("batch set persists Windows profile variables", func(t *testing.T) {
  tmID := ptr.Uint(1)
  newSet := []*fleet.MDMWindowsConfigProfile{ windowsConfigProfileForTest(t, "N-vars", "loc") }
  err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
    updated, err := ds.batchSetMDMWindowsProfilesDB(ctx, tx, tmID, newSet, []fleet.MDMProfileUUIDFleetVariables{
      {
        ProfileUUID: newSet[0].ProfileUUID,
        FleetVariables: []string{fleet.FleetVarHostUUID},
      },
    })
    require.NoError(t, err)
    require.True(t, updated)
    return nil
  })
  require.NoError(t, err)

  var names []string
  require.NoError(t, sqlx.SelectContext(ctx, ds.DB(), &names, `
    SELECT fv.name
      FROM mdm_windows_configuration_profiles mwcp
      JOIN mdm_configuration_profile_variables mcpv
        ON mwcp.profile_uuid = mcpv.windows_profile_uuid
      JOIN fleet_variables fv
        ON mcpv.fleet_variable_id = fv.id
     WHERE mwcp.name = ? AND mwcp.team_id = ?`, "N-vars", *tmID))
  require.Contains(t, names, string(fleet.FleetVarHostUUID))
})
server/service/microsoft_mdm.go (3)

2241-2255: Consider a strings.Replacer for cleaner, single-pass substitution

strings.ReplaceAll is invoked twice for every variable occurrence, which means two full scans of the string per host.
Building a strings.Replacer once (e.g. strings.NewReplacer("$FLEET_VAR_HOST_UUID", escapedUUID, "${FLEET_VAR_HOST_UUID}", escapedUUID)) performs all replacements in a single traversal and keeps the intent obvious.

Not critical, but worth it if large profiles are processed for thousands of hosts.


2247-2250: Capture the (unlikely) xml.EscapeText error

_ = xml.EscapeText(...) discards the error; although failure is rare, it still returns io.ErrShortWrite/ErrWriteAfterClose.
Either handle it or drop the assignment completely and add a short comment so the reader knows it’s intentionally ignored.


2294-2297: Map key risks with sentinel delimiter

hostProfilesMap uses the key hostUUID + "|" + profileUUID.
While UUIDs won’t normally contain |, string concatenation with a sentinel is brittle and easy to forget when the key shape changes. Consider a small struct key or fmt.Sprintf("%s:%s", …) to avoid accidental collisions.

server/service/microsoft_mdm_test.go (2)

280-284: Brittle expectation on XML-escaped quotes

The test expects &#34; for a double-quote. If Go’s encoding/xml ever switches to &quot;, the test will fail even though the code is correct. Matching strings.Contains(result, "&quot;") || strings.Contains(result, "&#34;") (or simply confirming < / > / & are escaped) would make the test less fragile.


528-536: receivedCommand overwrites on multiple inserts

receivedCommand captures only the last call to MDMWindowsInsertCommandForHostsFunc.
If more than one host were processed, earlier (possibly failing) commands would be lost, making the assertion silently pass.
Collect the commands in a slice or assert the call count to keep the test accurate when additional hosts are added.

server/service/integration_mdm_profiles_test.go (2)

3555-3558: Avoid “mystery nil” – pass a clearly-named empty map instead

nil is accepted here but gives no hint what the extra parameter represents (profile variables).
Using map[string]string{} (or a helper like fleet.NoProfileVars) keeps the call site self-documenting and avoids accidental panics if the callee ever dereferences the map.


7010-7140: Close HTTP response bodies in the table-driven loop

resp.Body is read but never closed; on macOS/Linux this leaks an FD per test-case.
Add a defer resp.Body.Close() immediately after each call to s.Do(...).

resp := s.Do(...)

+defer resp.Body.Close()
server/service/mdm_test.go (2)

1208-1214: Assert usesFleetVars propagation in the mock for stronger guarantees

Add assertions to verify that when $FLEET_VAR_HOST_UUID appears in valid profiles, the service extracts and passes it to the datastore; otherwise, the slice is empty. This tightens the contract for the save flow.

Apply this diff:

 ds.NewMDMWindowsConfigProfileFunc = func(ctx context.Context, cp fleet.MDMWindowsConfigProfile, usesFleetVars []string) (*fleet.MDMWindowsConfigProfile, error) {
-    if bytes.Contains(cp.SyncML, []byte("duplicate")) {
-        return nil, &alreadyExistsError{}
-    }
-    cp.ProfileUUID = uuid.New().String()
-    return &cp, nil
+    if bytes.Contains(cp.SyncML, []byte("duplicate")) {
+        return nil, &alreadyExistsError{}
+    }
+
+    // Verify extracted Fleet vars are propagated correctly.
+    hasUUIDVar := bytes.Contains(cp.SyncML, []byte("$FLEET_VAR_HOST_UUID")) ||
+        bytes.Contains(cp.SyncML, []byte("${FLEET_VAR_HOST_UUID}"))
+    if hasUUIDVar {
+        require.ElementsMatch(t,
+            []string{string(fleet.FleetVarHostUUID)},
+            usesFleetVars,
+        )
+    } else {
+        require.Len(t, usesFleetVars, 0)
+    }
+
+    cp.ProfileUUID = uuid.New().String()
+    return &cp, nil
 }

2251-2360: Optionally add a couple more cases for robustness

Consider adding:

  • Mixed forms in a single value (e.g., $FLEET_VAR_HOST_UUID and ${FLEET_VAR_HOST_UUID}).
  • Multiple items using the allowed var to ensure scanning across the document.

Apply this diff to extend the tests table:

@@
  tests := []struct {
@@
   }{
+    {
+      name: "HOST_UUID mixed forms in same value",
+      profileXML: `<Replace>
+        <Item>
+          <Target><LocURI>./Device/Vendor/MSFT/Policy/Config/System/AllowLocation</LocURI></Target>
+          <Data>$FLEET_VAR_HOST_UUID--${FLEET_VAR_HOST_UUID}</Data>
+        </Item>
+      </Replace>`,
+      wantErr: false,
+    },
+    {
+      name: "multiple items with HOST_UUID",
+      profileXML: `<Replace>
+        <Item>
+          <Target><LocURI>./Device/Vendor/MSFT/Policy/Config/System/AllowLocation</LocURI></Target>
+          <Data>$FLEET_VAR_HOST_UUID</Data>
+        </Item>
+        <Item>
+          <Target><LocURI>./Device/Vendor/MSFT/Policy/Config/System/AllowTelemetry</LocURI></Target>
+          <Data>${FLEET_VAR_HOST_UUID}</Data>
+        </Item>
+      </Replace>`,
+      wantErr: false,
+    },
   }
server/datastore/mysql/microsoft_mdm.go (1)

1766-1847: Persisting Fleet variables on profile creation looks correct; consider dedupe/sort for stability

The transactional insert and follow-up association via batchSetProfileVariableAssociationsDB(..., "windows") is solid. Minor improvement: dedupe and sort usesFleetVars to avoid duplicate rows and ensure deterministic writes.

Apply this diff within the function before building profilesVarsToUpsert:

@@
-        // Save Fleet variables associated with this Windows profile
+        // Save Fleet variables associated with this Windows profile
+        // (dedupe/sort for deterministic associations)
         if len(usesFleetVars) > 0 {
+            if len(usesFleetVars) > 1 {
+                seen := make(map[string]struct{}, len(usesFleetVars))
+                uniq := make([]string, 0, len(usesFleetVars))
+                for _, v := range usesFleetVars {
+                    if _, ok := seen[v]; !ok {
+                        seen[v] = struct{}{}
+                        uniq = append(uniq, v)
+                    }
+                }
+                // optional: keep order stable for repeatable writes
+                sort.Strings(uniq)
+                usesFleetVars = uniq
+            }
             profilesVarsToUpsert := []fleet.MDMProfileUUIDFleetVariables{
                 {
                     ProfileUUID:    profileUUID,
                     FleetVariables: usesFleetVars,
                 },
             }
             if err := batchSetProfileVariableAssociationsDB(ctx, tx, profilesVarsToUpsert, "windows"); err != nil {
                 return ctxerr.Wrap(ctx, err, "inserting windows profile variable associations")
             }
         }

Add the import if you sort:

import "sort"
server/service/mdm.go (3)

1485-1492: Make variable collection deterministic (and reuse the already-built set).

Order from ranging over a map is random; sort to avoid flakiness and improve reproducibility.

-	// Collect Fleet variables used in the profile
-	foundVars := findFleetVariables(string(cp.SyncML))
-	var usesFleetVars []string
-	for varName := range foundVars {
-		usesFleetVars = append(usesFleetVars, varName)
-	}
+	// Collect Fleet variables used in the profile (deterministic order)
+	foundVars := findFleetVariables(string(cp.SyncML))
+	usesFleetVars := maps.Keys(foundVars)
+	slices.Sort(usesFleetVars)

1526-1531: Use a set for supported variables to simplify membership checks and future growth.

A map[string]struct{} avoids linear scans and simplifies the validator.

-// fleetVarsSupportedInWindowsProfiles lists the Fleet variables that are
-// supported in Windows configuration profiles.
-var fleetVarsSupportedInWindowsProfiles = []string{
-	fleet.FleetVarHostUUID,
-}
+// fleetVarsSupportedInWindowsProfiles lists the Fleet variables that are
+// supported in Windows configuration profiles.
+// Use a set for O(1) membership checks.
+var fleetVarsSupportedInWindowsProfiles = map[string]struct{}{
+	fleet.FleetVarHostUUID: {},
+}

1533-1542: Tighten validation by using set membership; keep error message unchanged.

Switch to O(1) membership with the set suggested above. Logic stays the same.

-	foundVars := findFleetVariables(contents)
-	if len(foundVars) == 0 {
-		return nil
-	}
-
-	// Check if all found variables are supported
-	for varName := range foundVars {
-		if !slices.Contains(fleetVarsSupportedInWindowsProfiles, varName) {
-			return fleet.NewInvalidArgumentError("profile", fmt.Sprintf("Fleet variable $FLEET_VAR_%s is not supported in Windows profiles.", varName))
-		}
-	}
+	foundVars := findFleetVariables(contents)
+	if len(foundVars) == 0 {
+		return nil
+	}
+	// Check if all found variables are supported
+	for varName := range foundVars {
+		if _, ok := fleetVarsSupportedInWindowsProfiles[varName]; !ok {
+			return fleet.NewInvalidArgumentError("profile", fmt.Sprintf("Fleet variable $FLEET_VAR_%s is not supported in Windows profiles.", varName))
+		}
+	}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 58eef86 and 9ab8c1b.

📒 Files selected for processing (19)
  • changes/30879-host-uuid-for-windows-profiles (1 hunks)
  • server/datastore/mysql/apple_mdm_test.go (3 hunks)
  • server/datastore/mysql/hosts_test.go (1 hunks)
  • server/datastore/mysql/mdm.go (1 hunks)
  • server/datastore/mysql/mdm_test.go (6 hunks)
  • server/datastore/mysql/microsoft_mdm.go (5 hunks)
  • server/datastore/mysql/microsoft_mdm_test.go (9 hunks)
  • server/datastore/mysql/migrations/tables/20250808000000_AddHostUUIDFleetVariable.go (1 hunks)
  • server/datastore/mysql/schema.sql (2 hunks)
  • server/datastore/mysql/teams_test.go (1 hunks)
  • server/fleet/datastore.go (1 hunks)
  • server/fleet/mdm.go (1 hunks)
  • server/mock/datastore_mock.go (2 hunks)
  • server/service/apple_mdm.go (2 hunks)
  • server/service/integration_mdm_profiles_test.go (4 hunks)
  • server/service/mdm.go (3 hunks)
  • server/service/mdm_test.go (3 hunks)
  • server/service/microsoft_mdm.go (4 hunks)
  • server/service/microsoft_mdm_test.go (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

⚙️ CodeRabbit Configuration File

When reviewing SQL queries that are added or modified, ensure that appropriate filtering criteria are applied—especially when a query is intended to return data for a specific entity (e.g., a single host). Check for missing WHERE clauses or incorrect filtering that could lead to incorrect or non-deterministic results (e.g., returning the first row instead of the correct one). Flag any queries that may return unintended results due to lack of precise scoping.

Files:

  • server/fleet/mdm.go
  • server/datastore/mysql/teams_test.go
  • server/datastore/mysql/migrations/tables/20250808000000_AddHostUUIDFleetVariable.go
  • server/fleet/datastore.go
  • server/mock/datastore_mock.go
  • server/datastore/mysql/mdm.go
  • server/datastore/mysql/apple_mdm_test.go
  • server/service/microsoft_mdm_test.go
  • server/datastore/mysql/mdm_test.go
  • server/datastore/mysql/microsoft_mdm_test.go
  • server/datastore/mysql/hosts_test.go
  • server/service/mdm.go
  • server/datastore/mysql/microsoft_mdm.go
  • server/service/microsoft_mdm.go
  • server/service/mdm_test.go
  • server/service/apple_mdm.go
  • server/service/integration_mdm_profiles_test.go
🧠 Learnings (9)
📚 Learning: 2025-07-08T16:13:39.114Z
Learnt from: getvictor
PR: fleetdm/fleet#30589
File: server/datastore/mysql/migrations/tables/20250707095725_HostIdentitySCEPCertificates.go:53-55
Timestamp: 2025-07-08T16:13:39.114Z
Learning: In the Fleet codebase, Down migration functions are intentionally left empty/no-op. The team does not implement rollback functionality for database migrations, so empty Down_* functions in migration files are correct and should not be flagged as issues.

Applied to files:

  • server/fleet/mdm.go
  • server/datastore/mysql/migrations/tables/20250808000000_AddHostUUIDFleetVariable.go
  • server/datastore/mysql/schema.sql
📚 Learning: 2025-07-07T22:21:15.748Z
Learnt from: getvictor
PR: fleetdm/fleet#30589
File: server/datastore/mysql/schema.sql:501-517
Timestamp: 2025-07-07T22:21:15.748Z
Learning: In the host_identity_scep_certificates table schema, the VARBINARY(100) size for public_key_raw, the nullable host_id without a foreign key constraint, and the use of plain DATETIME instead of DATETIME(6) are intentional design decisions, not issues to be addressed.

Applied to files:

  • server/fleet/mdm.go
  • server/datastore/mysql/schema.sql
📚 Learning: 2025-08-08T07:40:05.274Z
Learnt from: getvictor
PR: fleetdm/fleet#31726
File: server/datastore/mysql/labels_test.go:2031-2031
Timestamp: 2025-08-08T07:40:05.274Z
Learning: In fleetdm/fleet repository tests (server/datastore/mysql/labels_test.go and similar), using testing.T.Context() is valid because the project targets a recent Go version where testing.T.Context() exists. Do not suggest replacing t.Context() with context.Background() in this codebase.

Applied to files:

  • server/datastore/mysql/teams_test.go
  • server/mock/datastore_mock.go
  • server/datastore/mysql/apple_mdm_test.go
  • server/service/microsoft_mdm_test.go
  • server/datastore/mysql/mdm_test.go
  • server/datastore/mysql/microsoft_mdm_test.go
  • server/datastore/mysql/hosts_test.go
  • server/service/mdm_test.go
📚 Learning: 2025-08-08T07:40:05.274Z
Learnt from: getvictor
PR: fleetdm/fleet#31726
File: server/datastore/mysql/labels_test.go:2031-2031
Timestamp: 2025-08-08T07:40:05.274Z
Learning: Fleet repo targets Go 1.24.5 (root go.mod), which supports testing.T.Context(). Do not flag usage of t.Context() or suggest replacing it with context.Background() in tests (e.g., server/datastore/mysql/labels_test.go Line 2031 and similar).

Applied to files:

  • server/datastore/mysql/teams_test.go
  • server/datastore/mysql/migrations/tables/20250808000000_AddHostUUIDFleetVariable.go
  • server/mock/datastore_mock.go
  • server/service/microsoft_mdm_test.go
  • server/datastore/mysql/mdm_test.go
  • server/datastore/mysql/microsoft_mdm_test.go
  • server/service/mdm_test.go
📚 Learning: 2025-07-08T16:11:49.555Z
Learnt from: getvictor
PR: fleetdm/fleet#30589
File: ee/server/service/hostidentity/depot/depot.go:115-115
Timestamp: 2025-07-08T16:11:49.555Z
Learning: In ee/server/service/hostidentity/depot/depot.go, the error from result.RowsAffected() is intentionally ignored because the information is only used for logging purposes, not for critical program logic.

Applied to files:

  • server/datastore/mysql/migrations/tables/20250808000000_AddHostUUIDFleetVariable.go
  • server/service/microsoft_mdm.go
📚 Learning: 2025-08-01T15:08:16.858Z
Learnt from: sgress454
PR: fleetdm/fleet#31508
File: server/datastore/mysql/schema.sql:102-116
Timestamp: 2025-08-01T15:08:16.858Z
Learning: The schema.sql file in server/datastore/mysql/ is auto-generated from migrations for use with tests, so it cannot be manually edited. Any changes must be made through migrations.

Applied to files:

  • server/datastore/mysql/migrations/tables/20250808000000_AddHostUUIDFleetVariable.go
  • server/datastore/mysql/schema.sql
📚 Learning: 2025-08-08T07:40:05.274Z
Learnt from: getvictor
PR: fleetdm/fleet#31726
File: server/datastore/mysql/labels_test.go:2031-2031
Timestamp: 2025-08-08T07:40:05.274Z
Learning: In fleetdm/fleet, tests may validly use testing.T.Context() when the module/toolchain targets Go 1.24+. Do not flag t.Context() usage in this codebase if go.mod/toolchain indicates Go >= 1.24.

Applied to files:

  • server/mock/datastore_mock.go
  • server/service/microsoft_mdm_test.go
  • server/datastore/mysql/microsoft_mdm_test.go
  • server/service/mdm_test.go
📚 Learning: 2025-07-08T16:12:48.797Z
Learnt from: getvictor
PR: fleetdm/fleet#30589
File: ee/server/service/hostidentity/depot/depot.go:108-111
Timestamp: 2025-07-08T16:12:48.797Z
Learning: In ee/server/service/hostidentity/depot/depot.go, the SCEP depot interface methods like Put() do not accept context parameters, and the common_mysql.WithRetryTxx callback function type TxFn only receives a transaction parameter, not a context. Therefore, using context.Background() in tx.ExecContext calls within the transaction callback is the correct approach.

Applied to files:

  • server/datastore/mysql/mdm.go
  • server/service/microsoft_mdm_test.go
📚 Learning: 2025-07-08T16:06:54.576Z
Learnt from: getvictor
PR: fleetdm/fleet#30589
File: ee/server/service/hostidentity/depot/depot.go:104-119
Timestamp: 2025-07-08T16:06:54.576Z
Learning: In ee/server/service/hostidentity/depot/depot.go, the security concern where shared challenges allow certificate revocation (lines 104-119) is a known issue that will be addressed in a later feature, not an immediate concern to fix.

Applied to files:

  • server/service/microsoft_mdm.go
🔇 Additional comments (26)
server/fleet/mdm.go (1)

44-44: LGTM—HOST_UUID migration and Windows wiring confirmed

Confirmed that:

  • The migration server/datastore/mysql/migrations/tables/20250808000000_AddHostUUIDFleetVariable.go inserts FLEET_VAR_HOST_UUID into fleet_variables.
  • fleetVarsSupportedInWindowsProfiles in server/service/mdm.go includes fleet.FleetVarHostUUID, and the substitution logic in server/service/microsoft_mdm.go correctly replaces $FLEET_VAR_HOST_UUID.
  • The NewMDMWindowsConfigProfile method in server/service/mdm.go accepts and passes through the detected fleet variables.

No further changes required.

server/datastore/mysql/teams_test.go (1)

93-98: LGTM: test updated to new signature.

Adding the extra nil argument matches the updated NewMDMWindowsConfigProfile signature that accepts Fleet variables.

server/datastore/mysql/hosts_test.go (1)

3834-3834: Signature update handled correctly

Passing nil as the third arg to NewMDMWindowsConfigProfile is appropriate to indicate no Fleet variables used. LGTM.

server/service/apple_mdm.go (2)

82-86: Rename clarifies Apple-only scope — LGTM

Renaming to fleetVarsSupportedInAppleConfigProfiles improves intent and avoids conflating with Windows profile handling. No behavior change.


82-86: All references updated to fleetVarsSupportedInAppleConfigProfiles
I ran a search for both the old and new symbols and found only the new fleetVarsSupportedInAppleConfigProfiles in:

  • server/service/apple_mdm.go:82–86 (declaration)
  • server/service/apple_mdm.go:494 (usage check)

No stale references to fleetVarsSupportedInConfigProfiles remain.

server/datastore/mysql/mdm_test.go (3)

780-781: LGTM; consistent with prior calls

Same note as above: nil should be handled as an empty set of vars by the datastore layer. No changes requested here.


6483-6484: LGTM

No issues; matches the updated function signature.


703-704: All NewMDMWindowsConfigProfile call sites updated with usesFleetVars argument
No 2-arg invocations remain; every occurrence includes the new parameter.

server/fleet/datastore.go (1)

1729-1729: Signature change aligns Windows with Apple — good consistency

Adding usesFleetVars []string to NewMDMWindowsConfigProfile matches the Apple counterpart and enables persisting used Fleet variables for Windows profiles.

server/datastore/mysql/apple_mdm_test.go (1)

168-174: Duplicate-name negative cases unaffected by new arg—good.

The new nil arg shouldn't affect name uniqueness logic. Ensure the uniqueness constraints remain platform/team-scoped as intended (Apple vs Windows, team vs no-team).

If not already covered elsewhere, add an assertion that the variables association count is zero for these nil-variable creations to guard against regressions.

server/datastore/mysql/schema.sql (2)

1442-1444: Migration status bump to 409 with version_id 20250808000000 — OK; confirm version matches the migration filename

  • Entry for 20250808000000 with is_applied=1 aligns with the described change.
  • Please verify the migration filename and version_id are consistent with this row (the script in the previous comment covers this).

323-325: Migration and wiring for FLEET_VAR_HOST_UUID validated — schema.sql is up-to-date
The new migration (20250808000000_AddHostUUIDFleetVariable.go) inserts FLEET_VAR_HOST_UUID with the deterministic timestamp, schema.sql reflects the AUTO_INCREMENT bump and new entry, and the fleet.FleetVarHostUUID constant plus replacement logic in server/service (with comprehensive tests) are all in place.

server/datastore/mysql/migrations/tables/20250808000000_AddHostUUIDFleetVariable.go (3)

22-24: Deterministic schema timestamp — good

Using a fixed UTC timestamp keeps schema.sql generation stable. LGTM.


36-38: No-op Down migration — correct per Fleet practice

Leaving Down_* empty matches our established migrations approach. No changes needed.


16-21: Approve current INSERT for consistency

Existing migrations that seed fleet_variables (e.g. AddSCEPRenewalIdFleetVar, SCIMAddDepartment) all use a plain INSERT and rely on the one-time application of migrations. Introducing INSERT IGNORE or ON DUPLICATE KEY UPDATE here would diverge from those patterns. No change required.

server/datastore/mysql/mdm.go (1)

323-326: batchSetMDMWindowsProfilesDB handles variable associations correctly

I’ve confirmed that:

  • Empty or nil profilesVariablesByIdentifier is a no-op (len == 0 returns early).
  • The platform switch only allows "windows" (non-Windows inputs error out).
  • Stale associations are deleted via the DELETE FROM mdm_configuration_profile_variables WHERE windows_profile_uuid IN (…) step.
  • New variables are upserted using INSERT … ON DUPLICATE KEY UPDATE.
  • All call sites for the updated signature are in sync (including tests).
server/datastore/mysql/microsoft_mdm_test.go (6)

1966-1969: LGTM: Windows team-scoped profile call updated

The extra nil arg matches the updated signature and maintains previous behavior for this test.


1979-1994: LGTM: Duplicate-name constraints still exercised with updated signature

The nil variables arg preserves intent while aligning to the new function shape.


2004-2012: LGTM: Labels FK check remains valid with the new argument

The added nil variables arg does not alter the foreign key validation behavior being tested.


2024-2033: LGTM: Labels happy-path case remains valid

Retains the original assertions; the trailing nil variables arg is correct for the updated signature.


2254-2259: LGTM

No behavior change beyond adapting to the new parameter.


2266-2270: LGTM

Signature alignment only; test intent preserved.

server/service/integration_mdm_profiles_test.go (1)

7255-7261: Double-check expectation: team host may also receive the global profile

The test asserts that the team host only sees the team-scoped profile. In Fleet today, global Windows profiles are delivered to all hosts (team or not).
If that behaviour hasn’t changed, this assertion could pass by chance (first profile inspected) and mask an unintended duplicate delivery.

Please verify the intended semantics and, if global-plus-team delivery is correct, extend the test to assert the presence of both profiles rather than exactly one.

server/service/mdm_test.go (2)

1130-1132: Mock signature update to capture used Fleet variables: LGTM

The updated mock (usesFleetVars []string) aligns with the datastore interface change and is appropriate for this authz-focused test.


2251-2360: Windows Fleet variable validation tests: solid coverage

Covers allowed $FLEET_VAR_HOST_UUID (with/without braces, multiple) and rejects unsupported/unknown vars with clear messages. Nice.

server/datastore/mysql/microsoft_mdm.go (1)

1916-1917: batchSetMDMWindowsProfilesDB invocations updated

All call sites now include the new profilesVariablesByIdentifier parameter:

  • server/datastore/mysql/mdm.go:324 – service layer passes profilesVariablesByIdentifier
  • server/datastore/mysql/microsoft_mdm_test.go:2427 – test stub now passes nil for the new slice

No further changes required.

Comment thread server/datastore/mysql/apple_mdm_test.go
Comment thread server/datastore/mysql/microsoft_mdm_test.go
Comment thread server/datastore/mysql/microsoft_mdm.go
Comment thread server/datastore/mysql/microsoft_mdm.go
Comment thread server/fleet/datastore.go Outdated
Comment thread server/mock/datastore_mock.go Outdated
Comment thread server/service/mdm.go
@getvictor
getvictor marked this pull request as ready for review August 8, 2025 10:49
@getvictor
getvictor requested a review from a team as a code owner August 8, 2025 10:49
@getvictor
getvictor requested a review from lucasmrod August 8, 2025 10:49
@JordanMontgomery

Copy link
Copy Markdown
Member

What about verification? Have we thought about how Verification on Windows functions in the presence of Fleet vars? For this test for instance: https://github.com/fleetdm/fleet/pull/31695/files#diff-fb609ca1ba21d8c37cd6a5fae0e36315c271b263053730f76c0c6620c740ef43R2085 I think we'd have to substitute in the variables during generation of the queries to verify profiles on the device since we have to generate Get SyncML commands for all LocURIs in a given profile during verification. Likewise some profile types do a byte or string compare of the contents to the expected portion(basically what we think we sent)

On macOS this is not a problem since we do a timestamp based verification but Windows is different and that's not possible AFAIK.

@getvictor

Copy link
Copy Markdown
Member Author

What about verification? Have we thought about how Verification on Windows functions in the presence of Fleet vars? For this test for instance: https://github.com/fleetdm/fleet/pull/31695/files#diff-fb609ca1ba21d8c37cd6a5fae0e36315c271b263053730f76c0c6620c740ef43R2085 I think we'd have to substitute in the variables during generation of the queries to verify profiles on the device since we have to generate Get SyncML commands for all LocURIs in a given profile during verification. Likewise some profile types do a byte or string compare of the contents to the expected portion(basically what we think we sent)

On macOS this is not a problem since we do a timestamp based verification but Windows is different and that's not possible AFAIK.

You're right. I missed it since I did not manually test it. Verification is a big enough task that it would go to a new PR that I will work on next week. Rough plan is:

  • During verification: Either:
    • dynamically substitute variables during verification comparison
    • OR grab the raw command from windows_mdm_commands
    • OR save the profile with the substituted info (probably overkill)

@JordanMontgomery JordanMontgomery left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's always tough to fully review a PR this big but this looks good to me.

On the subject of verification and validation it may be worth considering limiting where in the profile Fleet Vars can occur to only the data sections. That would likely make validation easier and lessen the likelihood of users being able to do things that might cause weird problems like putting variables into the LocURI and probably doesn't limit what users can do with this very much.

@getvictor
getvictor merged commit 9d24f20 into main Aug 10, 2025
45 checks passed
@getvictor
getvictor deleted the victor/30879-host-uuid-for-windows-profiles branch August 10, 2025 10:24
BCTBB pushed a commit that referenced this pull request Aug 19, 2025
…ofiles (#31695)

Fixes #30879 

Demo video: https://www.youtube.com/watch?v=jVyh5x8EMnc

I added a `FleetVarName` type, which should improve
safety/maintainability, but that resulted in a lot of files touched.

I also added the following. However, these are not strictly needed for
this feature (only useful for debug right now). But we are following the
pattern created by MDM team.

  1. Add the migration to insert HOST_UUID into fleet_variables
2. Update the Windows profile save logic to populate
mdm_configuration_profile_variables


# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.

## Testing

- [x] Added/updated automated tests
- [x] Where appropriate, [automated tests simulate multiple hosts and
test for host isolation]
- [x] QA'd all new/changed functionality manually



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

## Summary by CodeRabbit

* **New Features**
* Added support for the `$FLEET_VAR_HOST_UUID` variable in Windows MDM
configuration profiles, enabling per-host customization during profile
deployment.
* Enhanced profile delivery by substituting Fleet variables with actual
host data in Windows profiles.
* Introduced a database migration to register the new Fleet variable for
host UUID.

* **Bug Fixes**
* Improved validation and error handling to reject unsupported Fleet
variables in Windows MDM profiles with detailed messages.
* Ensured robust handling of errors during profile command insertion
without aborting the entire reconciliation process.

* **Tests**
* Added extensive tests covering validation, substitution, error
handling, and reconciliation workflows for Windows MDM profiles using
Fleet variables.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Host vital variable in Windows configuration profiles: UUID

3 participants