Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions server/fleet/microsoft_mdm.go
Original file line number Diff line number Diff line change
Expand Up @@ -1590,6 +1590,14 @@ func BuildMDMWindowsProfilePayloadFromMDMResponse(
}
}
}

if commandStatus == MDMDeliveryVerifying {
// Check a single LocURI for SCEP path, and move straight to verified.
if strings.Contains(string(cmdWithSecret.RawCommand), "/Vendor/MSFT/ClientCertificateInstall/SCEP") {
commandStatus = MDMDeliveryVerified
}
}

detail := strings.Join(details, ", ")
return &MDMWindowsProfilePayload{
HostUUID: hostUUID,
Expand Down
24 changes: 24 additions & 0 deletions server/fleet/microsoft_mdm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,30 @@ func TestBuildMDMWindowsProfilePayloadFromMDMResponse(t *testing.T) {
CommandUUID: "foo",
},
},
{
name: "scep profile gets verified",
cmd: MDMWindowsCommand{
CommandUUID: "foo",
RawCommand: []byte(`
<Atomic>
<CmdID>foo</CmdID>
<Replace><CmdID>bar</CmdID><Target><LocURI>./Device/Vendor/MSFT/ClientCertificateInstall/SCEP</LocURI></Target></Replace>
<Add><CmdID>baz</CmdID><Target><LocURI>./Device/Vendor/MSFT/ClientCertificateInstall/SCEP</LocURI></Target></Add>
</Atomic>`),
},
statuses: map[string]SyncMLCmd{
"foo": {CmdID: CmdID{Value: "foo"}, Data: ptr.String("200")},
"bar": {CmdID: CmdID{Value: "bar"}, Data: ptr.String("200")},
"baz": {CmdID: CmdID{Value: "baz"}, Data: ptr.String("200")},
},
hostUUID: "host-uuid",
expectedPayload: &MDMWindowsProfilePayload{
HostUUID: "host-uuid",
Status: &MDMDeliveryVerified,
Detail: "",
CommandUUID: "foo",
},
},
}

for _, tt := range tests {
Expand Down
12 changes: 6 additions & 6 deletions server/mdm/microsoft/profile_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ type PreprocessingParameters struct {
// This function is similar to PreprocessWindowsProfileContentsForDeployment, but it does not require
// a datastore or logger since it only replaces certain fleet variables to avoid datastore unnecessary work.
func PreprocessWindowsProfileContentsForVerification(ctx context.Context, logger kitlog.Logger, ds fleet.Datastore, hostUUID string, profileUUID string, profileContents string, params PreprocessingParameters) string {
replacedContents, _ := preprocessWindowsProfileContents(ctx, logger, ds, nil, true, hostUUID, "", profileUUID, nil, profileContents, nil, params)
replacedContents, _ := preprocessWindowsProfileContents(ctx, logger, ds, nil, true, hostUUID, profileUUID, nil, profileContents, nil, params)
// ^ We ignore the error here, and rely on the fact that the function will return the original contents if no replacements were made.
// So verification fails on individual profile level, instead of entire verification failing.
return replacedContents
Expand All @@ -314,7 +314,7 @@ func PreprocessWindowsProfileContentsForVerification(ctx context.Context, logger
// PreprocessWindowsProfileContentsForDeployment processes Windows configuration profiles to replace Fleet variables
// with their actual values for each host during profile deployment.
func PreprocessWindowsProfileContentsForDeployment(ctx context.Context, logger kitlog.Logger, ds fleet.Datastore,
appConfig *fleet.AppConfig, hostUUID string, hostCmdUUID string, profileUUID string,
appConfig *fleet.AppConfig, hostCmdUUID string, profileUUID string,
groupedCAs *fleet.GroupedCertificateAuthorities, profileContents string,
managedCertificatePayloads *[]*fleet.MDMManagedCertificate,
params PreprocessingParameters,
Expand All @@ -325,7 +325,7 @@ func PreprocessWindowsProfileContentsForDeployment(ctx context.Context, logger k
customSCEPCAs[ca.Name] = &ca
}

return preprocessWindowsProfileContents(ctx, logger, ds, appConfig, false, hostUUID, hostCmdUUID, profileUUID, customSCEPCAs, profileContents, managedCertificatePayloads, params)
return preprocessWindowsProfileContents(ctx, logger, ds, appConfig, false, hostCmdUUID, profileUUID, customSCEPCAs, profileContents, managedCertificatePayloads, params)
}

// This error type is used to indicate errors during Microsoft profile processing, such as variable replacement failures.
Expand All @@ -347,7 +347,7 @@ func (e *MicrosoftProfileProcessingError) Error() string {
// Currently supported variables:
// - $FLEET_VAR_HOST_UUID or ${FLEET_VAR_HOST_UUID}: Replaced with the host's UUID
// - $FLEET_VAR_HOST_END_USER_EMAIL_IDP or ${FLEET_VAR_HOST_END_USER_EMAIL_IDP}: Replaced with the host's end user email from the IDP
// - $FLEET_VAR_SCEP_WINDOWS_CERTIFICATE_ID or ${FLEET_VAR_SCEP_WINDOWS_CERTIFICATE_ID}: Replaced with the host command UUID for SCEP certificate
// - $FLEET_VAR_SCEP_WINDOWS_CERTIFICATE_ID or ${FLEET_VAR_SCEP_WINDOWS_CERTIFICATE_ID}: Replaced with the profile UUID for SCEP certificate
// - $FLEET_VAR_CUSTOM_SCEP_CHALLENGE_<CA_NAME> or ${FLEET_VAR_CUSTOM_SCEP_CHALLENGE_<CA_NAME>}: Replaced with the challenge for the specified custom SCEP CA
// - $FLEET_VAR_CUSTOM_SCEP_PROXY_URL_<CA_NAME> or ${FLEET_VAR_CUSTOM_SCEP_PROXY_URL_<CA_NAME>}: Replaced with the proxy URL for the specified custom SCEP CA
//
Expand All @@ -361,7 +361,7 @@ func (e *MicrosoftProfileProcessingError) Error() string {
// 4. XML escaping: We need XML-specific escaping for values, which is simpler to control with direct
// string replacement rather than template functions.
func preprocessWindowsProfileContents(ctx context.Context, logger kitlog.Logger, ds fleet.Datastore, appConfig *fleet.AppConfig,
isVerifying bool, hostUUID string, hostCmdUUID string, profileUUID string,
isVerifying bool, hostUUID string, profileUUID string,
customSCEPCAs map[string]*fleet.CustomSCEPProxyCA, profileContents string,
managedCertificatePayloads *[]*fleet.MDMManagedCertificate,
params PreprocessingParameters,
Expand Down Expand Up @@ -399,7 +399,7 @@ func preprocessWindowsProfileContents(ctx context.Context, logger kitlog.Logger,

switch {
case fleetVar == string(fleet.FleetVarSCEPWindowsCertificateID):
result = profiles.ReplaceFleetVariableInXML(fleet.FleetVarSCEPWindowsCertificateIDRegexp, result, hostCmdUUID)
result = profiles.ReplaceFleetVariableInXML(fleet.FleetVarSCEPWindowsCertificateIDRegexp, result, profileUUID)
case strings.HasPrefix(fleetVar, string(fleet.FleetVarCustomSCEPChallengePrefix)):
caName := strings.TrimPrefix(fleetVar, string(fleet.FleetVarCustomSCEPChallengePrefix))
err := profiles.IsCustomSCEPConfigured(ctx, customSCEPCAs, caName, fleetVar, func(errMsg string) error {
Expand Down
13 changes: 2 additions & 11 deletions server/mdm/microsoft/profile_verifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,6 @@ func TestPreprocessWindowsProfileContentsForDeployment(t *testing.T) {
tests := []struct {
name string
hostUUID string
hostCmdUUID string
profileContents string
expectedContents string
expectError bool
Expand All @@ -1015,14 +1014,12 @@ func TestPreprocessWindowsProfileContentsForDeployment(t *testing.T) {
{
name: "scep windows certificate id",
hostUUID: "test-host-1234-uuid",
hostCmdUUID: "cmd-uuid-5678",
profileContents: `<Replace><Data>SCEP: $FLEET_VAR_SCEP_WINDOWS_CERTIFICATE_ID</Data></Replace>`,
expectedContents: `<Replace><Data>SCEP: cmd-uuid-5678</Data></Replace>`,
expectedContents: fmt.Sprintf(`<Replace><Data>SCEP: %s</Data></Replace>`, profileUUID),
},
{
name: "custom scep proxy url not usable in free tier",
hostUUID: "test-host-1234-uuid",
hostCmdUUID: "cmd-uuid-5678",
profileContents: `<Replace><Data>CA: $FLEET_VAR_CUSTOM_SCEP_PROXY_URL_CERTIFICATE</Data></Replace>`,
expectError: true,
processingError: "Custom SCEP integration requires a Fleet Premium license.",
Expand All @@ -1031,15 +1028,13 @@ func TestPreprocessWindowsProfileContentsForDeployment(t *testing.T) {
{
name: "custom scep proxy url ca not found",
hostUUID: "test-host-1234-uuid",
hostCmdUUID: "cmd-uuid-5678",
profileContents: `<Replace><Data>CA: $FLEET_VAR_CUSTOM_SCEP_PROXY_URL_CERTIFICATE</Data></Replace>`,
expectError: true,
processingError: "Fleet couldn't populate $CUSTOM_SCEP_PROXY_URL_CERTIFICATE because CERTIFICATE certificate authority doesn't exist.",
},
{
name: "custom scep proxy url ca found and replaced",
hostUUID: "test-host-1234-uuid",
hostCmdUUID: "cmd-uuid-5678",
profileContents: `<Replace><Data> $FLEET_VAR_CUSTOM_SCEP_PROXY_URL_CERTIFICATE</Data></Replace>`,
expectedContents: `<Replace><Data>https://test-fleet.com/mdm/scep/proxy/test-host-1234-uuid%2C` + profileUUID + `%2CCERTIFICATE%2Csupersecret</Data></Replace>`,
setup: func() {
Expand Down Expand Up @@ -1067,7 +1062,6 @@ func TestPreprocessWindowsProfileContentsForDeployment(t *testing.T) {
{
name: "custom scep challenge not usable in free tier",
hostUUID: "test-host-1234-uuid",
hostCmdUUID: "cmd-uuid-5678",
profileContents: `<Replace><Data>CA: $FLEET_VAR_CUSTOM_SCEP_CHALLENGE_CERTIFICATE</Data></Replace>`,
expectError: true,
processingError: "Custom SCEP integration requires a Fleet Premium license.",
Expand All @@ -1076,15 +1070,13 @@ func TestPreprocessWindowsProfileContentsForDeployment(t *testing.T) {
{
name: "custom scep proxy challenge ca not found",
hostUUID: "test-host-1234-uuid",
hostCmdUUID: "cmd-uuid-5678",
profileContents: `<Replace><Data>CA: $FLEET_VAR_CUSTOM_SCEP_CHALLENGE_CERTIFICATE</Data></Replace>`,
expectError: true,
processingError: "Fleet couldn't populate $CUSTOM_SCEP_CHALLENGE_CERTIFICATE because CERTIFICATE certificate authority doesn't exist.",
},
{
name: "custom scep proxy challenge ca found and replaced",
hostUUID: "test-host-1234-uuid",
hostCmdUUID: "cmd-uuid-5678",
profileContents: `<Replace><Data> $FLEET_VAR_CUSTOM_SCEP_CHALLENGE_CERTIFICATE</Data></Replace>`,
expectedContents: `<Replace><Data>supersecret</Data></Replace>`,
setup: func() {
Expand All @@ -1107,7 +1099,6 @@ func TestPreprocessWindowsProfileContentsForDeployment(t *testing.T) {
{
name: "all idp variables",
hostUUID: "idp-host-uuid",
hostCmdUUID: "cmd-uuid-5678",
profileContents: `<Replace><Item><Target><LocURI>./Device/Test</LocURI></Target><Data>User: $FLEET_VAR_HOST_END_USER_IDP_USERNAME - $FLEET_VAR_HOST_END_USER_IDP_USERNAME_LOCAL_PART - $FLEET_VAR_HOST_END_USER_IDP_GROUPS - $FLEET_VAR_HOST_END_USER_IDP_DEPARTMENT - $FLEET_VAR_HOST_END_USER_IDP_FULL_NAME</Data></Item></Replace>`,
expectedContents: `<Replace><Item><Target><LocURI>./Device/Test</LocURI></Target><Data>User: test@idp.com - test - Group One,Group Two - Department - First Last</Data></Item></Replace>`,
},
Expand Down Expand Up @@ -1170,7 +1161,7 @@ func TestPreprocessWindowsProfileContentsForDeployment(t *testing.T) {

managedCertificates := &[]*fleet.MDMManagedCertificate{}

result, err := PreprocessWindowsProfileContentsForDeployment(ctx, log.NewNopLogger(), ds, appConfig, tt.hostUUID, tt.hostCmdUUID, profileUUID, groupedCAs, tt.profileContents, managedCertificates, params)
result, err := PreprocessWindowsProfileContentsForDeployment(ctx, log.NewNopLogger(), ds, appConfig, tt.hostUUID, profileUUID, groupedCAs, tt.profileContents, managedCertificates, params)
if tt.expectError {
require.Error(t, err)
if tt.processingError != "" {
Expand Down
Loading
Loading