+12
-1
@@ -1014,6 +1014,7 @@ func newMDMProfileManager(
|
||||
commander *apple_mdm.MDMAppleCommander,
|
||||
logger kitlog.Logger,
|
||||
loggingDebug bool,
|
||||
cfg config.MDMConfig,
|
||||
) (*schedule.Schedule, error) {
|
||||
const (
|
||||
name = string(fleet.CronMDMAppleProfileManager)
|
||||
@@ -1022,12 +1023,22 @@ func newMDMProfileManager(
|
||||
// cron interval as we scale to more hosts.
|
||||
defaultInterval = 30 * time.Second
|
||||
)
|
||||
|
||||
if !cfg.IsAppleSCEPSet() {
|
||||
return nil, ctxerr.New(ctx, "SCEP configuration is required")
|
||||
}
|
||||
|
||||
cert, _, _, err := cfg.AppleSCEP()
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "getting Apple SCEP keypair")
|
||||
}
|
||||
|
||||
logger = kitlog.With(logger, "cron", name)
|
||||
s := schedule.New(
|
||||
ctx, name, instanceID, defaultInterval, ds, ds,
|
||||
schedule.WithLogger(logger),
|
||||
schedule.WithJob("manage_apple_profiles", func(ctx context.Context) error {
|
||||
return service.ReconcileAppleProfiles(ctx, ds, commander, logger)
|
||||
return service.ReconcileAppleProfiles(ctx, ds, commander, logger, cert)
|
||||
}),
|
||||
schedule.WithJob("manage_apple_declarations", func(ctx context.Context) error {
|
||||
return service.ReconcileAppleDeclarations(ctx, ds, commander, logger)
|
||||
|
||||
+6
-5
@@ -546,7 +546,7 @@ the way that the Fleet server works.
|
||||
} else {
|
||||
mdmPushService = nanomdm_pushsvc.New(mdmStorage, mdmStorage, pushProviderFactory, nanoMDMLogger)
|
||||
}
|
||||
commander := apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPushService)
|
||||
commander := apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPushService, config.MDM)
|
||||
mdmCheckinAndCommandService = service.NewMDMAppleCheckinAndCommandService(ds, commander, logger)
|
||||
ddmService = service.NewMDMAppleDDMService(ds, logger)
|
||||
appCfg.MDM.EnabledAndConfigured = true
|
||||
@@ -640,7 +640,7 @@ the way that the Fleet server works.
|
||||
mailService,
|
||||
clock.C,
|
||||
depStorage,
|
||||
apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPushService),
|
||||
apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPushService, config.MDM),
|
||||
mdmPushCertTopic,
|
||||
ssoSessionStore,
|
||||
profileMatcher,
|
||||
@@ -697,7 +697,7 @@ the way that the Fleet server works.
|
||||
func() (fleet.CronSchedule, error) {
|
||||
var commander *apple_mdm.MDMAppleCommander
|
||||
if appCfg.MDM.EnabledAndConfigured {
|
||||
commander = apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPushService)
|
||||
commander = apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPushService, config.MDM)
|
||||
}
|
||||
return newCleanupsAndAggregationSchedule(
|
||||
ctx, instanceID, ds, logger, redisWrapperDS, &config, commander,
|
||||
@@ -740,7 +740,7 @@ the way that the Fleet server works.
|
||||
if err := cronSchedules.StartCronSchedule(func() (fleet.CronSchedule, error) {
|
||||
var commander *apple_mdm.MDMAppleCommander
|
||||
if appCfg.MDM.EnabledAndConfigured {
|
||||
commander = apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPushService)
|
||||
commander = apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPushService, config.MDM)
|
||||
}
|
||||
return newWorkerIntegrationsSchedule(ctx, instanceID, ds, logger, depStorage, commander)
|
||||
}); err != nil {
|
||||
@@ -761,9 +761,10 @@ the way that the Fleet server works.
|
||||
ctx,
|
||||
instanceID,
|
||||
ds,
|
||||
apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPushService),
|
||||
apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPushService, config.MDM),
|
||||
logger,
|
||||
config.Logging.Debug,
|
||||
config.MDM,
|
||||
)
|
||||
}); err != nil {
|
||||
initFatal(err, "failed to register mdm_apple_profile_manager schedule")
|
||||
|
||||
@@ -1156,5 +1156,11 @@ func (svc *Service) GetMDMManualEnrollmentProfile(ctx context.Context) ([]byte,
|
||||
return nil, ctxerr.Wrap(ctx, err)
|
||||
}
|
||||
|
||||
// NOTE: the profile returned by this endpoint is intentionally not
|
||||
// signed so it can be modified and signed by the IT admin with a
|
||||
// custom certificate.
|
||||
//
|
||||
// Per @marko-lisica, we can add a parameter like `signed=true` if the
|
||||
// need arises.
|
||||
return mobileConfig, nil
|
||||
}
|
||||
|
||||
@@ -214,7 +214,23 @@ func (c *TestAppleMDMClient) fetchEnrollmentProfile(path string) error {
|
||||
if err := response.Body.Close(); err != nil {
|
||||
return fmt.Errorf("close body: %w", err)
|
||||
}
|
||||
enrollInfo, err := ParseEnrollmentProfile(body)
|
||||
|
||||
rawProfile := body
|
||||
if !bytes.HasPrefix(rawProfile, []byte("<?xml")) {
|
||||
p7, err := pkcs7.Parse(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("enrollment profile is not XML nor PKCS7 parseable: %w", err)
|
||||
}
|
||||
|
||||
err = p7.Verify()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawProfile = p7.Content
|
||||
}
|
||||
|
||||
enrollInfo, err := ParseEnrollmentProfile(rawProfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse enrollment profile: %w", err)
|
||||
}
|
||||
|
||||
@@ -366,9 +366,9 @@ COALESCE(status, '%s') AS status,
|
||||
COALESCE(operation_type, '') AS operation_type,
|
||||
COALESCE(detail, '') AS detail
|
||||
FROM
|
||||
host_mdm_apple_profiles
|
||||
host_mdm_apple_profiles
|
||||
WHERE
|
||||
host_uuid = ? AND NOT (operation_type = '%s' AND COALESCE(status, '%s') IN('%s', '%s'))
|
||||
host_uuid = ? AND NOT (operation_type = '%s' AND COALESCE(status, '%s') IN('%s', '%s'))
|
||||
|
||||
UNION ALL
|
||||
SELECT
|
||||
@@ -383,9 +383,9 @@ COALESCE(status, '%s') AS status,
|
||||
COALESCE(operation_type, '') AS operation_type,
|
||||
COALESCE(detail, '') AS detail
|
||||
FROM
|
||||
host_mdm_apple_declarations
|
||||
host_mdm_apple_declarations
|
||||
WHERE
|
||||
host_uuid = ? AND declaration_name NOT IN (?) AND NOT (operation_type = '%s' AND COALESCE(status, '%s') IN('%s', '%s'))`,
|
||||
host_uuid = ? AND declaration_name NOT IN (?) AND NOT (operation_type = '%s' AND COALESCE(status, '%s') IN('%s', '%s'))`,
|
||||
fleet.MDMDeliveryPending,
|
||||
fleet.MDMOperationTypeRemove,
|
||||
fleet.MDMDeliveryPending,
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/VividCortex/mysqlerr"
|
||||
"github.com/fleetdm/fleet/v4/pkg/optjson"
|
||||
"github.com/fleetdm/fleet/v4/server/config"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
fleetmdm "github.com/fleetdm/fleet/v4/server/mdm"
|
||||
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
|
||||
@@ -2999,7 +3000,7 @@ func createMDMAppleCommanderAndStorage(t *testing.T, ds *Datastore) (*apple_mdm.
|
||||
mdmStorage, err := ds.NewMDMAppleMDMStorage(testCertPEM, testKeyPEM)
|
||||
require.NoError(t, err)
|
||||
|
||||
return apple_mdm.NewMDMAppleCommander(mdmStorage, pusherFunc(okPusherFunc)), mdmStorage
|
||||
return apple_mdm.NewMDMAppleCommander(mdmStorage, pusherFunc(okPusherFunc), config.MDMConfig{}), mdmStorage
|
||||
}
|
||||
|
||||
func okPusherFunc(ctx context.Context, ids []string) (map[string]*push.Response, error) {
|
||||
|
||||
@@ -32,6 +32,11 @@ func TestMDMAppleConfigProfile(t *testing.T) {
|
||||
mobileconfig: MobileconfigForTest("ValidName", "ValidIdentifier", uuid.NewString(), ""),
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
testName: "TestParseConfigProfileLeadingSpace",
|
||||
mobileconfig: append([]byte{' '}, []byte(MobileconfigForTest("ValidName", "ValidIdentifier", uuid.NewString(), ""))...),
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
testName: "TestParseConfigProfileNoIdentifier",
|
||||
mobileconfig: MobileconfigForTest("ValidName", "", uuid.NewString(), ""),
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/config"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/mdm/apple/appmanifest"
|
||||
@@ -29,21 +30,28 @@ type commandPayload struct {
|
||||
// the caller.
|
||||
type MDMAppleCommander struct {
|
||||
storage fleet.MDMAppleStore
|
||||
config config.MDMConfig
|
||||
pusher nanomdm_push.Pusher
|
||||
}
|
||||
|
||||
// NewMDMAppleCommander creates a new commander instance.
|
||||
func NewMDMAppleCommander(mdmStorage fleet.MDMAppleStore, mdmPushService nanomdm_push.Pusher) *MDMAppleCommander {
|
||||
func NewMDMAppleCommander(mdmStorage fleet.MDMAppleStore, mdmPushService nanomdm_push.Pusher, config config.MDMConfig) *MDMAppleCommander {
|
||||
return &MDMAppleCommander{
|
||||
storage: mdmStorage,
|
||||
pusher: mdmPushService,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// InstallProfile sends the homonymous MDM command to the given hosts, it also
|
||||
// takes care of the base64 encoding of the provided profile bytes.
|
||||
func (svc *MDMAppleCommander) InstallProfile(ctx context.Context, hostUUIDs []string, profile mobileconfig.Mobileconfig, uuid string) error {
|
||||
base64Profile := base64.StdEncoding.EncodeToString(profile)
|
||||
signedProfile, err := mobileconfig.Sign(profile, svc.config)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "signing profile")
|
||||
}
|
||||
|
||||
base64Profile := base64.StdEncoding.EncodeToString(signedProfile)
|
||||
raw := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
@@ -59,7 +67,7 @@ func (svc *MDMAppleCommander) InstallProfile(ctx context.Context, hostUUIDs []st
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>`, uuid, base64Profile)
|
||||
err := svc.EnqueueCommand(ctx, hostUUIDs, raw)
|
||||
err = svc.EnqueueCommand(ctx, hostUUIDs, raw)
|
||||
return ctxerr.Wrap(ctx, err, "commander install profile")
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ package apple_mdm
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/config"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/mdm/nanomdm/log/stdlogfmt"
|
||||
"github.com/fleetdm/fleet/v4/server/mdm/nanomdm/mdm"
|
||||
@@ -15,7 +15,10 @@ import (
|
||||
"github.com/fleetdm/fleet/v4/server/mock"
|
||||
svcmock "github.com/fleetdm/fleet/v4/server/service/mock"
|
||||
"github.com/google/uuid"
|
||||
"github.com/groob/plist"
|
||||
micromdm "github.com/micromdm/micromdm/mdm/mdm"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.mozilla.org/pkcs7"
|
||||
)
|
||||
|
||||
func TestMDMAppleCommander(t *testing.T) {
|
||||
@@ -28,7 +31,10 @@ func TestMDMAppleCommander(t *testing.T) {
|
||||
pushFactory,
|
||||
stdlogfmt.New(),
|
||||
)
|
||||
cmdr := NewMDMAppleCommander(mdmStorage, pusher)
|
||||
cmdr := NewMDMAppleCommander(mdmStorage, pusher, config.MDMConfig{
|
||||
AppleSCEPCert: "../../service/testdata/server.pem",
|
||||
AppleSCEPKey: "../../service/testdata/server.key",
|
||||
})
|
||||
|
||||
// TODO(roberto): there's a data race in the mock when more
|
||||
// than one host ID is provided because the pusher uses one
|
||||
@@ -41,7 +47,11 @@ func TestMDMAppleCommander(t *testing.T) {
|
||||
mdmStorage.EnqueueCommandFunc = func(ctx context.Context, id []string, cmd *mdm.Command) (map[string]error, error) {
|
||||
require.NotNil(t, cmd)
|
||||
require.Equal(t, cmd.Command.RequestType, "InstallProfile")
|
||||
require.Contains(t, string(cmd.Raw), base64.StdEncoding.EncodeToString(mc))
|
||||
var fullCmd micromdm.CommandPayload
|
||||
require.NoError(t, plist.Unmarshal(cmd.Raw, &fullCmd))
|
||||
p7, err := pkcs7.Parse(fullCmd.Command.InstallProfile.Payload)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(p7.Content), string(mc))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/config"
|
||||
"github.com/fleetdm/fleet/v4/server/mdm"
|
||||
"github.com/micromdm/micromdm/pkg/crypto/profileutil"
|
||||
"go.mozilla.org/pkcs7"
|
||||
"howett.net/plist"
|
||||
)
|
||||
@@ -20,6 +22,9 @@ const (
|
||||
// by fleetd to read configuration values from the system.
|
||||
FleetdConfigPayloadIdentifier = "com.fleetdm.fleetd.config"
|
||||
|
||||
// FleetCARootConfigPayloadIdentifier TODO
|
||||
FleetCARootConfigPayloadIdentifier = "com.fleetdm.caroot"
|
||||
|
||||
// FleetEnrollmentPayloadIdentifier is the value for the PayloadIdentifier used
|
||||
// by Fleet to enroll a device with the MDM server.
|
||||
FleetEnrollmentPayloadIdentifier = "com.fleetdm.fleet.mdm.apple.mdm"
|
||||
@@ -43,8 +48,9 @@ const (
|
||||
// files around due to import cycles.
|
||||
func FleetPayloadIdentifiers() map[string]struct{} {
|
||||
return map[string]struct{}{
|
||||
FleetFileVaultPayloadIdentifier: {},
|
||||
FleetdConfigPayloadIdentifier: {},
|
||||
FleetFileVaultPayloadIdentifier: {},
|
||||
FleetdConfigPayloadIdentifier: {},
|
||||
FleetCARootConfigPayloadIdentifier: {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +89,7 @@ type Parsed struct {
|
||||
// Adapted from https://github.com/micromdm/micromdm/blob/main/platform/profile/profile.go
|
||||
func (mc Mobileconfig) ParseConfigProfile() (*Parsed, error) {
|
||||
mcBytes := mc
|
||||
if !bytes.HasPrefix(mcBytes, []byte("<?xml")) {
|
||||
if !bytes.HasPrefix(bytes.TrimSpace(mcBytes), []byte("<?xml")) {
|
||||
p7, err := pkcs7.Parse(mcBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("mobileconfig is not XML nor PKCS7 parseable: %w", err)
|
||||
@@ -247,3 +253,23 @@ var (
|
||||
ErrEmptyPayloadContent = errors.New("empty PayloadContent")
|
||||
ErrEncryptedPayloadContent = errors.New("encrypted PayloadContent")
|
||||
)
|
||||
|
||||
// Sign signs an enrollment profile using the SCEP certificate from the
|
||||
// provided MDM config.
|
||||
func Sign(profile []byte, cfg config.MDMConfig) ([]byte, error) {
|
||||
if !cfg.IsAppleSCEPSet() {
|
||||
return nil, errors.New("SCEP configuration is required")
|
||||
}
|
||||
|
||||
cert, _, _, err := cfg.AppleSCEP()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving SCEP certificate from config: %w", err)
|
||||
}
|
||||
|
||||
signed, err := profileutil.Sign(cert.PrivateKey, cert.Leaf, profile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("signing profile with the specified key: %w", err)
|
||||
}
|
||||
|
||||
return signed, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package mobileconfig
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSign(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config config.MDMConfig
|
||||
profile []byte
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "SCEP not set",
|
||||
config: config.MDMConfig{},
|
||||
profile: []byte("profile data"),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Error with invalid certificate",
|
||||
config: config.MDMConfig{AppleSCEPCertBytes: "foo", AppleSCEPKeyBytes: "bar"},
|
||||
profile: []byte("profile data"),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Successful signing",
|
||||
config: config.MDMConfig{
|
||||
AppleSCEPCertBytes: string(testCert),
|
||||
AppleSCEPKeyBytes: string(testKey),
|
||||
},
|
||||
profile: []byte("profile data"),
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := Sign(tc.profile, tc.config)
|
||||
if tc.expectError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
testCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIID6DCCAdACFGX99Sw4aF2qKGLucoIWQRAXHrs1MA0GCSqGSIb3DQEBCwUAMDUx
|
||||
EzARBgNVBAoMClJlZGlzIFRlc3QxHjAcBgNVBAMMFUNlcnRpZmljYXRlIEF1dGhv
|
||||
cml0eTAeFw0yMTEwMTkxNzM0MzlaFw0yMjEwMTkxNzM0MzlaMCwxEzARBgNVBAoM
|
||||
ClJlZGlzIFRlc3QxFTATBgNVBAMMDEdlbmVyaWMtY2VydDCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBAKSHcH8EjSvp3Nm4IHAFxG9DZm8+0h1BwU0OX0VH
|
||||
cJ+Cf+f6h0XYMcMo9LFEpnUJRRMjKrM4mkI75NIIufNBN+GrtqqTPTid8wfOGu/U
|
||||
fa5EEU1hb2j7AiMlpM6i0+ZysXSNo+Vc/cNZT0PXfyOtJnYm6p9WZM84ID1t2ea0
|
||||
bLwC12cTKv5oybVGtJHh76TRxAR3FeQ9+SY30vUAxYm6oWyYho8rRdKtUSe11pXj
|
||||
6OhxxfTZnsSWn4lo0uBpXai63XtieTVpz74htSNC1bunIGv7//m5F60sH5MrF5JS
|
||||
kPxfCfgqski84ICDSRNlvpT+eMPiygAAJ8zY8wYUXRYFYTUCAwEAATANBgkqhkiG
|
||||
9w0BAQsFAAOCAgEAAAw+6Uz2bAcXgQ7fQfdOm+T6FLRBcr8PD4ajOvSu/T+HhVVj
|
||||
E26Qt2IBwFEYve2FvDxrBCF8aQYZcyQqnP8bdKebnWAaqL8BbTwLWW+fDuZLO2b4
|
||||
QHjAEdEKKdZC5/FRpQrkerf5CCPTHE+5M17OZg41wdVYnCEwJOkP5pUAVsmwtrSw
|
||||
VeIquy20TZO0qbscDQETf7NIJgW0IXg82wBe53Rv4/wL3Ybq13XVRGYiJrwpaNTf
|
||||
UNgsDWqgwlQ5L2GOLDgg8S2NoF9mWVgCGSp3a2eHW+EmBRQ1OP6EYQtIhKdGLrSn
|
||||
dAOMJ2ER1pgHWUFKkWQaZ9i37Dx2j7P5c4/XNeVozcRQcLwKwN+n8k+bwIYcTX0H
|
||||
MOVFYm+WiFi/gjI860Tx853Sc0nkpOXmBCeHSXigGUscgjBYbmJz4iExXuwgawLX
|
||||
KLDKs0yyhLDnKEjmx/Vhz03JpsVFJ84kSWkTZkYsXiG306TxuJCX9zAt1z+6Clie
|
||||
TTGiFY+D8DfkC4H82rlPEtImpZ6rInsMUlAykImpd58e4PMSa+w/wSHXDvwFP7py
|
||||
1Gvz3XvcbGLmpBXblxTUpToqC7zSQJhHOMBBt6XnhcRwd6G9Vj/mQM3FvJIrxtKk
|
||||
8O7FwMJloGivS85OEzCIur5A+bObXbM2pcI8y4ueHE4NtElRBwn859AdB2k=
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
testKey = []byte(testingKey(`-----BEGIN RSA TESTING KEY-----
|
||||
MIIEogIBAAKCAQEApIdwfwSNK+nc2bggcAXEb0Nmbz7SHUHBTQ5fRUdwn4J/5/qH
|
||||
Rdgxwyj0sUSmdQlFEyMqsziaQjvk0gi580E34au2qpM9OJ3zB84a79R9rkQRTWFv
|
||||
aPsCIyWkzqLT5nKxdI2j5Vz9w1lPQ9d/I60mdibqn1ZkzzggPW3Z5rRsvALXZxMq
|
||||
/mjJtUa0keHvpNHEBHcV5D35JjfS9QDFibqhbJiGjytF0q1RJ7XWlePo6HHF9Nme
|
||||
xJafiWjS4GldqLrde2J5NWnPviG1I0LVu6cga/v/+bkXrSwfkysXklKQ/F8J+Cqy
|
||||
SLzggINJE2W+lP54w+LKAAAnzNjzBhRdFgVhNQIDAQABAoIBAAtUbFHC3XnVq+iu
|
||||
PkWYkBNdX9NvTwbGvWnyAGuD5OSHFwnBfck4fwzCaD9Ay/mpPsF3nXwj/LNs7m/s
|
||||
O+ndZty6d2S9qOyaK98wuTgkuNbkRxC+Ee73wgjrkbLNEax/32p4Sn4D7lGid8vj
|
||||
LhUl2k0ult+MEnsWkVnJk8TITeiQaT2AHhMr3HKdaI86hJJfam3wEBiLBglnnKqA
|
||||
TInMqHoudnFOn/C8iVCFuHCE0oo1dMalbc4rlZuRBqezVhbSMWPLypMVXQb7eixM
|
||||
ScJ3m8+DooGDSIe+EW/afhN2VnFbrhQC9/DlxGfwTwsUseWv7pgp53ufyyAzzydn
|
||||
2plW/4ECgYEA1Va5RzSUDxr75JX003YZiBcYrG268vosiNYWRhE7frvn5EorZBRW
|
||||
t4R70Y2gcXA10aPHzpbq40t6voWtpkfynU3fyRzbBmwfiWLEgckrYMwtcNz8nhG2
|
||||
ETAg4LXO9CufbwuDa66h76TpkBzQVNc5TSbBUr/apLDWjKPMz6qW7VUCgYEAxW4K
|
||||
Yqp3NgJkC5DhuD098jir9AH96hGhUryOi2CasCvmbjWCgWdolD7SRZJfxOXFOtHv
|
||||
7Dkp9glA1Cg/nSmEHKslaTJfBIWK+5rqVD6k6kZE/+4QQWQtUxXXVgGINnGrnPvo
|
||||
6MlRJxqGUtYJ0GRTFJP4Py0gwuzf5BMIwe+fpGECgYAOhLRfMCjTTlbOG5ZpvaPH
|
||||
Kys2sNEEMBpPxaIGaq3N1iPV2WZSjT/JhW6XuDevAJ/pAGhcmtCpXz2fMaG7qzHL
|
||||
mr0cBqaxLTKIOvx8iKA3Gi4NfDyE1Ve6m7fhEv5eh4l2GSZ8cYn7sRFkCVH0NCFm
|
||||
KrkFVKEgjBhNwefySf2zcQKBgHDVPgw7nlv4q9LMX6RbI98eMnAG/2XZ45gUeWcA
|
||||
tAeBX3WXEVoBjoxDBwuJ5z/xjXHbb8JSvT+G9E0MH6cjhgSYb44aoqFD7TV0yP2S
|
||||
u8/Ej0SxewrURO8aKXJW99Edz9WtRuRbwgyWJTSMbRlzbOPy2UrJ8NJWbHK9yiCE
|
||||
YXmhAoGAA3QUiCCl11c1C4VsF68Fa2i7qwnty3fvFidZpW3ds0tzZdIvkpRLp5+u
|
||||
XAJ5+zStdEGdnu0iXALQlY7ektawXguT/zYKg3nfS9RMGW6CxZotn4bqfQwDuttf
|
||||
b1xn1jGQd/o0xFf9ojpDNy6vNojidQGHh6E3h0GYvxbnQmVNq5U=
|
||||
-----END RSA TESTING KEY-----`))
|
||||
)
|
||||
|
||||
// prevent static analysis tools from raising issues due to detection of private key
|
||||
// in code.
|
||||
func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
|
||||
@@ -60,3 +60,52 @@ var FleetdProfileTemplate = template.Must(template.New("").Option("missingkey=er
|
||||
</dict>
|
||||
</plist>
|
||||
`))
|
||||
|
||||
// FleetCARootTemplateOptions are the keys required to execute a
|
||||
// FleetCARootTemplate.
|
||||
type FleetCARootTemplateOptions struct {
|
||||
PayloadName string
|
||||
PayloadIdentifier string
|
||||
Certificate string
|
||||
}
|
||||
|
||||
var FleetCARootTemplate = template.Must(template.New("").Option("missingkey=error").Parse(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PayloadContent</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>PayloadCertificateFileName</key>
|
||||
<string>CertificateRoot</string>
|
||||
<key>PayloadContent</key>
|
||||
<data>{{ .Certificate }}</data>
|
||||
<key>PayloadDescription</key>
|
||||
<string>{{ .PayloadName }}</string>
|
||||
<key>PayloadDisplayName</key>
|
||||
<string>{{ .PayloadName }}</string>
|
||||
<key>PayloadIdentifier</key>
|
||||
<string>{{ .PayloadIdentifier }}.certpayload</string>
|
||||
<key>PayloadType</key>
|
||||
<string>com.apple.security.root</string>
|
||||
<key>PayloadUUID</key>
|
||||
<string>B295992E-861A-4F92-902-17BCF4E33C61</string>
|
||||
<key>PayloadVersion</key>
|
||||
<integer>1</integer>
|
||||
<key>AllowAllAppsAccess</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PayloadDisplayName</key>
|
||||
<string>{{ .PayloadName }}</string>
|
||||
<key>PayloadIdentifier</key>
|
||||
<string>{{ .PayloadIdentifier }}</string>
|
||||
<key>PayloadType</key>
|
||||
<string>Configuration</string>
|
||||
<key>PayloadUUID</key>
|
||||
<string>4F5428DE-05B6-4965-87AD-532CAFC35FCF</string>
|
||||
<key>PayloadVersion</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
`))
|
||||
|
||||
+6
-1
@@ -86,6 +86,10 @@ const (
|
||||
// fleetd to read configuration values from the system.
|
||||
FleetdConfigProfileName = "Fleetd configuration"
|
||||
|
||||
// FleetCAConfigProfileName is the value for the PayloadDisplayName used by
|
||||
// fleetd to read configuration values from the system.
|
||||
FleetCAConfigProfileName = "Fleet root certificate authority (CA)"
|
||||
|
||||
// FleetdFileVaultProfileName is the value for the PayloadDisplayName used
|
||||
// by Fleet to configure FileVault and FileVault Escrow.
|
||||
FleetFileVaultProfileName = "Disk encryption"
|
||||
@@ -107,6 +111,7 @@ func FleetReservedProfileNames() map[string]struct{} {
|
||||
FleetFileVaultProfileName: {},
|
||||
FleetWindowsOSUpdatesProfileName: {},
|
||||
FleetMacOSUpdatesProfileName: {},
|
||||
FleetCAConfigProfileName: {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +124,7 @@ func ListFleetReservedWindowsProfileNames() []string {
|
||||
// ListFleetReservedMacOSProfileNames returns a list of PayloadDisplayName strings
|
||||
// that are reserved by Fleet for macOS.
|
||||
func ListFleetReservedMacOSProfileNames() []string {
|
||||
return []string{FleetFileVaultProfileName, FleetdConfigProfileName}
|
||||
return []string{FleetFileVaultProfileName, FleetdConfigProfileName, FleetCAConfigProfileName}
|
||||
}
|
||||
|
||||
// ListFleetReservedMacOSDeclarationNames returns a list of declaration names
|
||||
|
||||
@@ -3,6 +3,8 @@ package service
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -1330,16 +1332,22 @@ func (svc *Service) GetMDMAppleEnrollmentProfileByToken(ctx context.Context, tok
|
||||
return nil, ctxerr.Wrap(ctx, err, "adding reference to fleet URL")
|
||||
}
|
||||
|
||||
mobileconfig, err := apple_mdm.GenerateEnrollmentProfileMobileconfig(
|
||||
enrollmentProf, err := apple_mdm.GenerateEnrollmentProfileMobileconfig(
|
||||
appConfig.OrgInfo.OrgName,
|
||||
enrollURL,
|
||||
svc.config.MDM.AppleSCEPChallenge,
|
||||
svc.mdmPushCertTopic,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err)
|
||||
return nil, ctxerr.Wrap(ctx, err, "generating enrollment profile")
|
||||
}
|
||||
return mobileconfig, nil
|
||||
|
||||
signed, err := mobileconfig.Sign(enrollmentProf, svc.config.MDM)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "signing profile")
|
||||
}
|
||||
|
||||
return signed, nil
|
||||
}
|
||||
|
||||
type mdmAppleCommandRemoveEnrollmentProfileRequest struct {
|
||||
@@ -2710,7 +2718,7 @@ func mdmAppleDeliveryStatusFromCommandStatus(cmdStatus string) *fleet.MDMDeliver
|
||||
}
|
||||
}
|
||||
|
||||
// ensureFleetdConfig ensures there's a fleetd configuration profile in
|
||||
// ensureFleetProfiles ensures there's a fleetd configuration profile in
|
||||
// mdm_apple_configuration_profiles for each team and for "no team"
|
||||
//
|
||||
// We try our best to use each team's secret but we default to creating a
|
||||
@@ -2720,12 +2728,26 @@ func mdmAppleDeliveryStatusFromCommandStatus(cmdStatus string) *fleet.MDMDeliver
|
||||
// This profile will be installed to all hosts in the team (or "no team",) but it
|
||||
// will only be used by hosts that have a fleetd installation without an enroll
|
||||
// secret and fleet URL (mainly DEP enrolled hosts).
|
||||
func ensureFleetdConfig(ctx context.Context, ds fleet.Datastore, logger kitlog.Logger) error {
|
||||
func ensureFleetProfiles(ctx context.Context, ds fleet.Datastore, logger kitlog.Logger, signingCert *tls.Certificate) error {
|
||||
appCfg, err := ds.AppConfig(ctx)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "fetching app config")
|
||||
}
|
||||
|
||||
var rootCAProfContents bytes.Buffer
|
||||
params := mobileconfig.FleetCARootTemplateOptions{
|
||||
PayloadIdentifier: mobileconfig.FleetCARootConfigPayloadIdentifier,
|
||||
PayloadName: mdm_types.FleetCAConfigProfileName,
|
||||
Certificate: base64.StdEncoding.EncodeToString(signingCert.Certificate[0]),
|
||||
}
|
||||
|
||||
if err := mobileconfig.FleetCARootTemplate.Execute(&rootCAProfContents, params); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "executing fleet root CA config template")
|
||||
}
|
||||
|
||||
b := rootCAProfContents.Bytes()
|
||||
fmt.Println(string(b))
|
||||
|
||||
enrollSecrets, err := ds.AggregateEnrollSecretPerTeam(ctx)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "getting enroll secrets aggregates")
|
||||
@@ -2767,11 +2789,15 @@ func ensureFleetdConfig(ctx context.Context, ds fleet.Datastore, logger kitlog.L
|
||||
|
||||
cp, err := fleet.NewMDMAppleConfigProfile(contents.Bytes(), es.TeamID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "building configuration profile")
|
||||
return ctxerr.Wrap(ctx, err, "building fleetd configuration profile")
|
||||
}
|
||||
|
||||
profiles = append(profiles, cp)
|
||||
|
||||
rootCAProf, err := fleet.NewMDMAppleConfigProfile(b, es.TeamID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "building root CA configuration profile")
|
||||
}
|
||||
profiles = append(profiles, rootCAProf)
|
||||
}
|
||||
|
||||
if err := ds.BulkUpsertMDMAppleConfigProfiles(ctx, profiles); err != nil {
|
||||
@@ -2813,6 +2839,7 @@ func ReconcileAppleProfiles(
|
||||
ds fleet.Datastore,
|
||||
commander *apple_mdm.MDMAppleCommander,
|
||||
logger kitlog.Logger,
|
||||
signingCert *tls.Certificate,
|
||||
) error {
|
||||
appConfig, err := ds.AppConfig(ctx)
|
||||
if err != nil {
|
||||
@@ -2821,7 +2848,7 @@ func ReconcileAppleProfiles(
|
||||
if !appConfig.MDM.EnabledAndConfigured {
|
||||
return nil
|
||||
}
|
||||
if err := ensureFleetdConfig(ctx, ds, logger); err != nil {
|
||||
if err := ensureFleetProfiles(ctx, ds, logger, signingCert); err != nil {
|
||||
logger.Log("err", "unable to ensure a fleetd configuration profiles are in place", "details", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -41,7 +42,10 @@ import (
|
||||
"github.com/fleetdm/fleet/v4/server/test"
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"github.com/google/uuid"
|
||||
"github.com/groob/plist"
|
||||
micromdm "github.com/micromdm/micromdm/mdm/mdm"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.mozilla.org/pkcs7"
|
||||
)
|
||||
|
||||
type nopProfileMatcher struct{}
|
||||
@@ -57,6 +61,9 @@ func (nopProfileMatcher) RetrieveProfiles(ctx context.Context, extHostID string)
|
||||
func setupAppleMDMService(t *testing.T, license *fleet.LicenseInfo) (fleet.Service, context.Context, *mock.Store) {
|
||||
ds := new(mock.Store)
|
||||
cfg := config.TestConfig()
|
||||
testCertPEM, testKeyPEM, err := generateCertWithAPNsTopic()
|
||||
require.NoError(t, err)
|
||||
config.SetTestMDMConfig(t, &cfg, testCertPEM, testKeyPEM, testBMToken, "../../server/service/testdata")
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case strings.Contains(r.URL.Path, "/server/devices"):
|
||||
@@ -1165,7 +1172,7 @@ func TestMDMTokenUpdate(t *testing.T) {
|
||||
pushFactory,
|
||||
NewNanoMDMLogger(kitlog.NewJSONLogger(os.Stdout)),
|
||||
)
|
||||
cmdr := apple_mdm.NewMDMAppleCommander(mdmStorage, pusher)
|
||||
cmdr := apple_mdm.NewMDMAppleCommander(mdmStorage, pusher, config.MDMConfig{})
|
||||
svc := MDMAppleCheckinAndCommandService{ds: ds, commander: cmdr, logger: kitlog.NewNopLogger()}
|
||||
uuid, serial, model, wantTeamID := "ABC-DEF-GHI", "XYZABC", "MacBookPro 16,1", uint(12)
|
||||
|
||||
@@ -2088,14 +2095,17 @@ func TestMDMAppleReconcileAppleProfiles(t *testing.T) {
|
||||
pushFactory,
|
||||
NewNanoMDMLogger(kitlog.NewNopLogger()),
|
||||
)
|
||||
cmdr := apple_mdm.NewMDMAppleCommander(mdmStorage, pusher)
|
||||
mdmConfig := config.MDMConfig{
|
||||
AppleSCEPCert: "./testdata/server.pem",
|
||||
AppleSCEPKey: "./testdata/server.key",
|
||||
}
|
||||
cmdr := apple_mdm.NewMDMAppleCommander(mdmStorage, pusher, mdmConfig)
|
||||
hostUUID, hostUUID2 := "ABC-DEF", "GHI-JKL"
|
||||
contents1 := []byte("test-content-1")
|
||||
contents1Base64 := base64.StdEncoding.EncodeToString(contents1)
|
||||
contents2 := []byte("test-content-2")
|
||||
contents2Base64 := base64.StdEncoding.EncodeToString(contents2)
|
||||
contents4 := []byte("test-content-4")
|
||||
contents4Base64 := base64.StdEncoding.EncodeToString(contents4)
|
||||
signingCert, _, _, err := mdmConfig.AppleSCEP()
|
||||
require.NoError(t, err)
|
||||
|
||||
p1, p2, p3, p4 := "a"+uuid.NewString(), "a"+uuid.NewString(), "a"+uuid.NewString(), "a"+uuid.NewString()
|
||||
ds.ListMDMAppleProfilesToInstallFunc = func(ctx context.Context) ([]*fleet.MDMAppleProfilePayload, error) {
|
||||
@@ -2130,6 +2140,7 @@ func TestMDMAppleReconcileAppleProfiles(t *testing.T) {
|
||||
}
|
||||
|
||||
var enqueueFailForOp fleet.MDMOperationType
|
||||
var mu sync.Mutex
|
||||
mdmStorage.EnqueueCommandFunc = func(ctx context.Context, id []string, cmd *mdm.Command) (map[string]error, error) {
|
||||
require.NotNil(t, cmd)
|
||||
require.NotEmpty(t, cmd.CommandUUID)
|
||||
@@ -2143,10 +2154,18 @@ func TestMDMAppleReconcileAppleProfiles(t *testing.T) {
|
||||
require.Len(t, id, 1)
|
||||
}
|
||||
|
||||
if !strings.Contains(string(cmd.Raw), contents1Base64) && !strings.Contains(string(cmd.Raw), contents2Base64) &&
|
||||
!strings.Contains(string(cmd.Raw), contents4Base64) {
|
||||
var fullCmd micromdm.CommandPayload
|
||||
require.NoError(t, plist.Unmarshal(cmd.Raw, &fullCmd))
|
||||
// the p7 library doesn't support concurrent calls to Parse
|
||||
mu.Lock()
|
||||
p7, err := pkcs7.Parse(fullCmd.Command.InstallProfile.Payload)
|
||||
mu.Unlock()
|
||||
require.NoError(t, err)
|
||||
|
||||
if !bytes.Equal(p7.Content, contents1) && !bytes.Equal(p7.Content, contents2) &&
|
||||
!bytes.Equal(p7.Content, contents4) {
|
||||
require.Failf(t, "profile contents don't match", "expected to contain %s, %s or %s but got %s",
|
||||
contents1Base64, contents2Base64, contents4Base64, string(cmd.Raw))
|
||||
contents1, contents2, contents4, p7.Content)
|
||||
}
|
||||
case "RemoveProfile":
|
||||
require.ElementsMatch(t, []string{hostUUID, hostUUID2}, id)
|
||||
@@ -2299,7 +2318,7 @@ func TestMDMAppleReconcileAppleProfiles(t *testing.T) {
|
||||
failedCount++
|
||||
require.Len(t, payload, 0)
|
||||
}
|
||||
err := ReconcileAppleProfiles(ctx, ds, cmdr, kitlog.NewNopLogger())
|
||||
err := ReconcileAppleProfiles(ctx, ds, cmdr, kitlog.NewNopLogger(), signingCert)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, failedCount)
|
||||
checkAndReset(t, true, &ds.ListMDMAppleProfilesToInstallFuncInvoked)
|
||||
@@ -2335,7 +2354,7 @@ func TestMDMAppleReconcileAppleProfiles(t *testing.T) {
|
||||
}
|
||||
|
||||
enqueueFailForOp = fleet.MDMOperationTypeRemove
|
||||
err := ReconcileAppleProfiles(ctx, ds, cmdr, kitlog.NewNopLogger())
|
||||
err := ReconcileAppleProfiles(ctx, ds, cmdr, kitlog.NewNopLogger(), signingCert)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, failedCount)
|
||||
checkAndReset(t, true, &ds.ListMDMAppleProfilesToInstallFuncInvoked)
|
||||
@@ -2388,7 +2407,7 @@ func TestMDMAppleReconcileAppleProfiles(t *testing.T) {
|
||||
}
|
||||
|
||||
enqueueFailForOp = fleet.MDMOperationTypeInstall
|
||||
err := ReconcileAppleProfiles(ctx, ds, cmdr, kitlog.NewNopLogger())
|
||||
err := ReconcileAppleProfiles(ctx, ds, cmdr, kitlog.NewNopLogger(), signingCert)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, failedCount)
|
||||
checkAndReset(t, true, &ds.ListMDMAppleProfilesToInstallFuncInvoked)
|
||||
@@ -2420,6 +2439,12 @@ func TestEnsureFleetdConfig(t *testing.T) {
|
||||
testURL := "https://example.com"
|
||||
testTeamName := "test-team"
|
||||
logger := kitlog.NewNopLogger()
|
||||
mdmConfig := config.MDMConfig{
|
||||
AppleSCEPCert: "./testdata/server.pem",
|
||||
AppleSCEPKey: "./testdata/server.key",
|
||||
}
|
||||
signingCert, _, _, err := mdmConfig.AppleSCEP()
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("no enroll secret found", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
@@ -2435,7 +2460,7 @@ func TestEnsureFleetdConfig(t *testing.T) {
|
||||
require.Empty(t, ps)
|
||||
return nil
|
||||
}
|
||||
err := ensureFleetdConfig(ctx, ds, logger)
|
||||
err := ensureFleetProfiles(ctx, ds, logger, signingCert)
|
||||
require.NoError(t, err)
|
||||
require.True(t, ds.BulkUpsertMDMAppleConfigProfilesFuncInvoked)
|
||||
require.True(t, ds.AggregateEnrollSecretPerTeamFuncInvoked)
|
||||
@@ -2460,7 +2485,7 @@ func TestEnsureFleetdConfig(t *testing.T) {
|
||||
require.Empty(t, ps)
|
||||
return nil
|
||||
}
|
||||
err := ensureFleetdConfig(ctx, ds, logger)
|
||||
err := ensureFleetProfiles(ctx, ds, logger, signingCert)
|
||||
require.NoError(t, err)
|
||||
require.True(t, ds.BulkUpsertMDMAppleConfigProfilesFuncInvoked)
|
||||
require.True(t, ds.AggregateEnrollSecretPerTeamFuncInvoked)
|
||||
@@ -2485,16 +2510,28 @@ func TestEnsureFleetdConfig(t *testing.T) {
|
||||
return secrets, nil
|
||||
}
|
||||
ds.BulkUpsertMDMAppleConfigProfilesFunc = func(ctx context.Context, ps []*fleet.MDMAppleConfigProfile) error {
|
||||
require.Len(t, ps, len(secrets))
|
||||
for i, p := range ps {
|
||||
// fleetd + CA profiles
|
||||
require.Len(t, ps, len(secrets)*2)
|
||||
var fleetd, fleetCA []*fleet.MDMAppleConfigProfile
|
||||
for _, p := range ps {
|
||||
switch p.Identifier {
|
||||
case mobileconfig.FleetdConfigPayloadIdentifier:
|
||||
fleetd = append(fleetd, p)
|
||||
case mobileconfig.FleetCARootConfigPayloadIdentifier:
|
||||
fleetCA = append(fleetCA, p)
|
||||
}
|
||||
}
|
||||
require.Len(t, fleetd, 3)
|
||||
require.Len(t, fleetCA, 3)
|
||||
|
||||
for i, p := range fleetd {
|
||||
require.Contains(t, string(p.Mobileconfig), testURL)
|
||||
require.Contains(t, string(p.Mobileconfig), secrets[i].Secret)
|
||||
require.Equal(t, mobileconfig.FleetdConfigPayloadIdentifier, p.Identifier)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err := ensureFleetdConfig(ctx, ds, logger)
|
||||
err := ensureFleetProfiles(ctx, ds, logger, signingCert)
|
||||
require.NoError(t, err)
|
||||
require.True(t, ds.AggregateEnrollSecretPerTeamFuncInvoked)
|
||||
require.True(t, ds.BulkUpsertMDMAppleConfigProfilesFuncInvoked)
|
||||
@@ -2517,15 +2554,27 @@ func TestEnsureFleetdConfig(t *testing.T) {
|
||||
return secrets, nil
|
||||
}
|
||||
ds.BulkUpsertMDMAppleConfigProfilesFunc = func(ctx context.Context, ps []*fleet.MDMAppleConfigProfile) error {
|
||||
require.Len(t, ps, len(secrets))
|
||||
for i, p := range ps {
|
||||
// fleetd + CA profiles
|
||||
require.Len(t, ps, len(secrets)*2)
|
||||
var fleetd, fleetCA []*fleet.MDMAppleConfigProfile
|
||||
for _, p := range ps {
|
||||
switch p.Identifier {
|
||||
case mobileconfig.FleetdConfigPayloadIdentifier:
|
||||
fleetd = append(fleetd, p)
|
||||
case mobileconfig.FleetCARootConfigPayloadIdentifier:
|
||||
fleetCA = append(fleetCA, p)
|
||||
}
|
||||
}
|
||||
require.Len(t, fleetd, 2)
|
||||
require.Len(t, fleetCA, 2)
|
||||
|
||||
for i, p := range fleetd {
|
||||
require.Contains(t, string(p.Mobileconfig), testURL)
|
||||
require.Contains(t, string(p.Mobileconfig), secrets[i].Secret)
|
||||
require.Equal(t, mobileconfig.FleetdConfigPayloadIdentifier, p.Identifier)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
err := ensureFleetdConfig(ctx, ds, logger)
|
||||
err := ensureFleetProfiles(ctx, ds, logger, signingCert)
|
||||
require.NoError(t, err)
|
||||
require.True(t, ds.AppConfigFuncInvoked)
|
||||
require.True(t, ds.AggregateEnrollSecretPerTeamFuncInvoked)
|
||||
@@ -2538,7 +2587,7 @@ func TestEnsureFleetdConfig(t *testing.T) {
|
||||
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||||
return nil, testError
|
||||
}
|
||||
err := ensureFleetdConfig(ctx, ds, logger)
|
||||
err := ensureFleetProfiles(ctx, ds, logger, signingCert)
|
||||
require.ErrorIs(t, err, testError)
|
||||
})
|
||||
|
||||
@@ -2551,7 +2600,7 @@ func TestEnsureFleetdConfig(t *testing.T) {
|
||||
ds.AggregateEnrollSecretPerTeamFunc = func(ctx context.Context) ([]*fleet.EnrollSecret, error) {
|
||||
return nil, testError
|
||||
}
|
||||
err := ensureFleetdConfig(ctx, ds, logger)
|
||||
err := ensureFleetProfiles(ctx, ds, logger, signingCert)
|
||||
require.ErrorIs(t, err, testError)
|
||||
})
|
||||
|
||||
@@ -2571,7 +2620,7 @@ func TestEnsureFleetdConfig(t *testing.T) {
|
||||
ds.BulkUpsertMDMAppleConfigProfilesFunc = func(ctx context.Context, p []*fleet.MDMAppleConfigProfile) error {
|
||||
return testError
|
||||
}
|
||||
err := ensureFleetdConfig(ctx, ds, logger)
|
||||
err := ensureFleetProfiles(ctx, ds, logger, signingCert)
|
||||
require.ErrorIs(t, err, testError)
|
||||
require.True(t, ds.AppConfigFuncInvoked)
|
||||
require.True(t, ds.AggregateEnrollSecretPerTeamFuncInvoked)
|
||||
@@ -2852,7 +2901,10 @@ func setupTest(t *testing.T) (context.Context, kitlog.Logger, *mock.Store, *conf
|
||||
pushFactory,
|
||||
stdlogfmt.New(),
|
||||
)
|
||||
commander := apple_mdm.NewMDMAppleCommander(mdmStorage, pusher)
|
||||
commander := apple_mdm.NewMDMAppleCommander(mdmStorage, pusher, config.MDMConfig{
|
||||
AppleSCEPCert: "./testdata/server.pem",
|
||||
AppleSCEPKey: "./testdata/server.key",
|
||||
})
|
||||
|
||||
return ctx, logger, ds, &cfg, mdmStorage, commander
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/logging"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
|
||||
"github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
)
|
||||
|
||||
@@ -500,16 +501,22 @@ func (svc *Service) GetDeviceMDMAppleEnrollmentProfile(ctx context.Context) ([]b
|
||||
return nil, ctxerr.Wrap(ctx, err)
|
||||
}
|
||||
|
||||
mobileConfig, err := apple_mdm.GenerateEnrollmentProfileMobileconfig(
|
||||
enrollmentProf, err := apple_mdm.GenerateEnrollmentProfileMobileconfig(
|
||||
appConfig.OrgInfo.OrgName,
|
||||
appConfig.ServerSettings.ServerURL,
|
||||
svc.config.MDM.AppleSCEPChallenge,
|
||||
svc.mdmPushCertTopic,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err)
|
||||
return nil, ctxerr.Wrap(ctx, err, "generating manual enrollment profile")
|
||||
}
|
||||
return mobileConfig, nil
|
||||
|
||||
signed, err := mobileconfig.Sign(enrollmentProf, svc.config.MDM)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "signing profile")
|
||||
}
|
||||
|
||||
return signed, nil
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -272,12 +272,12 @@ func (s *integrationMDMTestSuite) runDEPEnrollReleaseDeviceTest(t *testing.T, de
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// expected commands: install fleetd, install bootstrap, install profiles
|
||||
// expected commands: install fleetd, install bootstrap, install CA, install profiles
|
||||
// (custom one and fleetd configuration) (not expected: account
|
||||
// configuration, since enrollment_reference not set)
|
||||
require.Len(t, cmds, 4)
|
||||
require.Len(t, cmds, 5)
|
||||
var installProfileCount, installEnterpriseCount, otherCount int
|
||||
var profileCustomSeen, profileFleetdSeen bool
|
||||
var profileCustomSeen, profileFleetdSeen, profileFleetCASeen bool
|
||||
for _, cmd := range cmds {
|
||||
switch cmd.Command.RequestType {
|
||||
case "InstallProfile":
|
||||
@@ -286,6 +286,8 @@ func (s *integrationMDMTestSuite) runDEPEnrollReleaseDeviceTest(t *testing.T, de
|
||||
profileCustomSeen = true
|
||||
} else if strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("<string>%s</string>", mobileconfig.FleetdConfigPayloadIdentifier)) {
|
||||
profileFleetdSeen = true
|
||||
} else if strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("<string>%s</string>", mobileconfig.FleetCARootConfigPayloadIdentifier)) {
|
||||
profileFleetCASeen = true
|
||||
}
|
||||
|
||||
case "InstallEnterpriseApplication":
|
||||
@@ -294,11 +296,12 @@ func (s *integrationMDMTestSuite) runDEPEnrollReleaseDeviceTest(t *testing.T, de
|
||||
otherCount++
|
||||
}
|
||||
}
|
||||
require.Equal(t, 2, installProfileCount)
|
||||
require.Equal(t, 3, installProfileCount)
|
||||
require.Equal(t, 2, installEnterpriseCount)
|
||||
require.Equal(t, 0, otherCount)
|
||||
require.True(t, profileCustomSeen)
|
||||
require.True(t, profileFleetdSeen)
|
||||
require.True(t, profileFleetCASeen)
|
||||
|
||||
if enableReleaseManually {
|
||||
// get the worker's pending job from the future, there should not be any
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -141,7 +141,7 @@ func NewService(
|
||||
mdmStorage: mdmStorage,
|
||||
mdmPushService: mdmPushService,
|
||||
mdmPushCertTopic: mdmPushCertTopic,
|
||||
mdmAppleCommander: apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPushService),
|
||||
mdmAppleCommander: apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPushService, config.MDM),
|
||||
cronSchedulesService: cronSchedulesService,
|
||||
wstepCertManager: wstepCertManager,
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ func newTestServiceWithConfig(t *testing.T, ds fleet.Datastore, fleetConfig conf
|
||||
mailer,
|
||||
c,
|
||||
depStorage,
|
||||
apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPusher),
|
||||
apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPusher, fleetConfig.MDM),
|
||||
"",
|
||||
ssoStore,
|
||||
profMatcher,
|
||||
@@ -344,7 +344,7 @@ func RunServerForTestsWithDS(t *testing.T, ds fleet.Datastore, opts ...*TestServ
|
||||
logger,
|
||||
&MDMAppleCheckinAndCommandService{
|
||||
ds: ds,
|
||||
commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPusher),
|
||||
commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPusher, cfg.MDM),
|
||||
logger: kitlog.NewNopLogger(),
|
||||
},
|
||||
&MDMAppleDDMService{
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/pkg/optjson"
|
||||
"github.com/fleetdm/fleet/v4/server/config"
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
|
||||
@@ -151,7 +152,7 @@ func TestAppleMDM(t *testing.T) {
|
||||
mdmWorker := &AppleMDM{
|
||||
Datastore: ds,
|
||||
Log: nopLog,
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}),
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}, config.MDMConfig{}),
|
||||
}
|
||||
w := NewWorker(ds, nopLog)
|
||||
w.Register(mdmWorker)
|
||||
@@ -184,7 +185,7 @@ func TestAppleMDM(t *testing.T) {
|
||||
mdmWorker := &AppleMDM{
|
||||
Datastore: ds,
|
||||
Log: nopLog,
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}),
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}, config.MDMConfig{}),
|
||||
}
|
||||
w := NewWorker(ds, nopLog)
|
||||
w.Register(mdmWorker)
|
||||
@@ -221,7 +222,7 @@ func TestAppleMDM(t *testing.T) {
|
||||
mdmWorker := &AppleMDM{
|
||||
Datastore: ds,
|
||||
Log: nopLog,
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}),
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}, config.MDMConfig{}),
|
||||
}
|
||||
w := NewWorker(ds, nopLog)
|
||||
w.Register(mdmWorker)
|
||||
@@ -262,7 +263,7 @@ func TestAppleMDM(t *testing.T) {
|
||||
mdmWorker := &AppleMDM{
|
||||
Datastore: ds,
|
||||
Log: nopLog,
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}),
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}, config.MDMConfig{}),
|
||||
}
|
||||
w := NewWorker(ds, nopLog)
|
||||
w.Register(mdmWorker)
|
||||
@@ -313,7 +314,7 @@ func TestAppleMDM(t *testing.T) {
|
||||
mdmWorker := &AppleMDM{
|
||||
Datastore: ds,
|
||||
Log: nopLog,
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}),
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}, config.MDMConfig{}),
|
||||
}
|
||||
w := NewWorker(ds, nopLog)
|
||||
w.Register(mdmWorker)
|
||||
@@ -365,7 +366,7 @@ func TestAppleMDM(t *testing.T) {
|
||||
mdmWorker := &AppleMDM{
|
||||
Datastore: ds,
|
||||
Log: nopLog,
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}),
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}, config.MDMConfig{}),
|
||||
}
|
||||
w := NewWorker(ds, nopLog)
|
||||
w.Register(mdmWorker)
|
||||
@@ -402,7 +403,7 @@ func TestAppleMDM(t *testing.T) {
|
||||
mdmWorker := &AppleMDM{
|
||||
Datastore: ds,
|
||||
Log: nopLog,
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}),
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}, config.MDMConfig{}),
|
||||
}
|
||||
w := NewWorker(ds, nopLog)
|
||||
w.Register(mdmWorker)
|
||||
@@ -444,7 +445,7 @@ func TestAppleMDM(t *testing.T) {
|
||||
mdmWorker := &AppleMDM{
|
||||
Datastore: ds,
|
||||
Log: nopLog,
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}),
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}, config.MDMConfig{}),
|
||||
}
|
||||
w := NewWorker(ds, nopLog)
|
||||
w.Register(mdmWorker)
|
||||
@@ -499,7 +500,7 @@ func TestAppleMDM(t *testing.T) {
|
||||
mdmWorker := &AppleMDM{
|
||||
Datastore: ds,
|
||||
Log: nopLog,
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}),
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}, config.MDMConfig{}),
|
||||
}
|
||||
w := NewWorker(ds, nopLog)
|
||||
w.Register(mdmWorker)
|
||||
@@ -535,7 +536,7 @@ func TestAppleMDM(t *testing.T) {
|
||||
mdmWorker := &AppleMDM{
|
||||
Datastore: ds,
|
||||
Log: nopLog,
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}),
|
||||
Commander: apple_mdm.NewMDMAppleCommander(mdmStorage, mockPusher{}, config.MDMConfig{}),
|
||||
}
|
||||
w := NewWorker(ds, nopLog)
|
||||
w.Register(mdmWorker)
|
||||
|
||||
Reference in New Issue
Block a user