diff --git a/changes/12483-puppet-run-id b/changes/12483-puppet-run-id new file mode 100644 index 0000000000..25fb523e10 --- /dev/null +++ b/changes/12483-puppet-run-id @@ -0,0 +1 @@ +* Fix delays applying profiles when the Puppet module is used in distributed scenarios. diff --git a/server/mdm/apple/profile_matcher.go b/server/mdm/apple/profile_matcher.go index 807122a60d..4092edc9a4 100644 --- a/server/mdm/apple/profile_matcher.go +++ b/server/mdm/apple/profile_matcher.go @@ -18,6 +18,11 @@ const ( // must be reasonably longer than the expected time to make all // PreassignProfile calls and the final matcher call. preassignKeyExpiration = 1 * time.Hour + // in distributed scenarios, the external host identifier might come up + // with a suffix that varies from server to server. To account for that + // we only take into account the first 36 runes from the identifier. + // See https://github.com/fleetdm/fleet/issues/12483 for more info. + preassignKeySuffixMaxLen = 36 ) type profileMatcher struct { @@ -151,5 +156,17 @@ func (p *profileMatcher) RetrieveProfiles(ctx context.Context, externalHostIdent } func keyForExternalHostIdentifier(externalHostIdentifier string) string { - return preassignKeyPrefix + externalHostIdentifier + return preassignKeyPrefix + firstNRunes(externalHostIdentifier, preassignKeySuffixMaxLen) +} + +// firstNRunes grabs the first N runes from the provided string +func firstNRunes(s string, n int) string { + i := 0 + for j := range s { + if i == n { + return s[:j] + } + i++ + } + return s } diff --git a/server/mdm/apple/profile_matcher_test.go b/server/mdm/apple/profile_matcher_test.go index 3b7fd70311..d99c02b85e 100644 --- a/server/mdm/apple/profile_matcher_test.go +++ b/server/mdm/apple/profile_matcher_test.go @@ -326,6 +326,23 @@ func TestPreassignProfileValidation(t *testing.T) { } } +func TestKeyForExternalHostIdentifier(t *testing.T) { + cases := []struct { + in string + want string + }{ + {"", ""}, + {"abcd", "abcd"}, + {"6f36ab2c-1a40-429b-9c9d-07c9029f4aa8", "6f36ab2c-1a40-429b-9c9d-07c9029f4aa8"}, + {"6f36ab2c-1a40-429b-9c9d-07c9029f4aa8-puppetcompiler06.test.example.com", "6f36ab2c-1a40-429b-9c9d-07c9029f4aa8"}, + } + + for _, c := range cases { + got := keyForExternalHostIdentifier(c.in) + require.Equal(t, preassignKeyPrefix+c.want, got) + } +} + func generateProfile(name, ident, typ, uuid string) []byte { return []byte(fmt.Sprintf(` diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index 2d7a726b01..7b378d2c4d 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -672,11 +672,15 @@ func (s *integrationMDMTestSuite) TestPuppetMatchPreassignProfiles() { }}, http.StatusNoContent, "team_id", fmt.Sprint(tm4.ID)) // preassign the MDM host to prof1 and prof4, should match existing team tm2 - s.Do("POST", "/api/latest/fleet/mdm/apple/profiles/preassign", preassignMDMAppleProfileRequest{MDMApplePreassignProfilePayload: fleet.MDMApplePreassignProfilePayload{ExternalHostIdentifier: "mdm1", HostUUID: mdmHost.UUID, Profile: prof1, Group: "g1"}}, http.StatusNoContent) - s.Do("POST", "/api/latest/fleet/mdm/apple/profiles/preassign", preassignMDMAppleProfileRequest{MDMApplePreassignProfilePayload: fleet.MDMApplePreassignProfilePayload{ExternalHostIdentifier: "mdm1", HostUUID: mdmHost.UUID, Profile: prof4, Group: "g4"}}, http.StatusNoContent) + // + // additionally, use external host identifiers with different + // suffixes to simulate real world distributed scenarios where more + // than one puppet server might be running at the time. + s.Do("POST", "/api/latest/fleet/mdm/apple/profiles/preassign", preassignMDMAppleProfileRequest{MDMApplePreassignProfilePayload: fleet.MDMApplePreassignProfilePayload{ExternalHostIdentifier: "6f36ab2c-1a40-429b-9c9d-07c9029f4aa8-puppetcompiler06.test.example.com", HostUUID: mdmHost.UUID, Profile: prof1, Group: "g1"}}, http.StatusNoContent) + s.Do("POST", "/api/latest/fleet/mdm/apple/profiles/preassign", preassignMDMAppleProfileRequest{MDMApplePreassignProfilePayload: fleet.MDMApplePreassignProfilePayload{ExternalHostIdentifier: "6f36ab2c-1a40-429b-9c9d-07c9029f4aa8-puppetcompiler01.test.example.com", HostUUID: mdmHost.UUID, Profile: prof4, Group: "g4"}}, http.StatusNoContent) // match with the mdm host succeeds and assigns it to tm2 - s.Do("POST", "/api/latest/fleet/mdm/apple/profiles/match", matchMDMApplePreassignmentRequest{ExternalHostIdentifier: "mdm1"}, http.StatusNoContent) + s.Do("POST", "/api/latest/fleet/mdm/apple/profiles/match", matchMDMApplePreassignmentRequest{ExternalHostIdentifier: "6f36ab2c-1a40-429b-9c9d-07c9029f4aa8-puppetcompiler03.test.example.com"}, http.StatusNoContent) // the host is now part of that team h, err = s.ds.Host(ctx, mdmHost.ID)