Update DDM protocol endpoints to use host UUID in support of profile labels (#17719)

This commit is contained in:
Sarah Gillespie 2024-03-20 09:14:21 -05:00 committed by GitHub
parent 8a137ffe1f
commit d56d0cde31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 118 additions and 96 deletions

View file

@ -3082,59 +3082,64 @@ WHERE h.uuid = ?
return nil
}
func (ds *Datastore) MDMAppleDDMSynchronizationTokens(ctx context.Context, teamID uint) (*fleet.MDMAppleDDMSyncTokens, error) {
func (ds *Datastore) MDMAppleDDMDeclarationsToken(ctx context.Context, hostUUID string) (*fleet.MDMAppleDDMDeclarationsToken, error) {
const stmt = `
SELECT
md5_checksum,
latest_created_timestamp
md5((count(0) + group_concat(hex(mad.md5_checksum)
ORDER BY
mad.uploaded_at DESC separator ''))) AS md5_checksum,
max(mad.created_at) AS latest_created_timestamp
FROM
team_declaration_checksum_view
host_mdm_apple_declarations hmad
JOIN mdm_apple_declarations mad ON hmad.declaration_uuid = mad.declaration_uuid
WHERE
team_id = ?`
hmad.host_uuid = ?`
var res fleet.MDMAppleDDMSyncTokens
if err := sqlx.GetContext(ctx, ds.reader(ctx), &res, stmt, teamID); err != nil {
return nil, ctxerr.Wrap(ctx, err, "get DDM checksum by team id")
var res fleet.MDMAppleDDMDeclarationsToken
if err := sqlx.GetContext(ctx, ds.reader(ctx), &res, stmt, hostUUID); err != nil {
return nil, ctxerr.Wrap(ctx, err, "get DDM declarations token")
}
return &res, nil
}
func (ds *Datastore) MDMAppleDDMDeclarationItems(ctx context.Context, teamID uint) ([]fleet.MDMAppleDDMDeclarationItem, error) {
// TODO: Confirm whether we can use JSON functions in the query (if 5.7 officially unsupported
// by Fleet)
func (ds *Datastore) MDMAppleDDMDeclarationItems(ctx context.Context, hostUUID string) ([]fleet.MDMAppleDDMDeclarationItem, error) {
const stmt = `
SELECT
mad.md5_checksum as server_token,
identifier,
declaration_type,
tv.md5_checksum as declarations_token
mad.md5_checksum,
mad.identifier,
mad.declaration_type
FROM
mdm_apple_declarations mad
JOIN team_declaration_checksum_view tv ON mad.team_id = tv.team_id
host_mdm_apple_declarations hmad
JOIN mdm_apple_declarations mad ON mad.declaration_uuid = hmad.declaration_uuid
WHERE
mad.team_id = ?`
hmad.host_uuid = ? AND operation_type = ?`
var res []fleet.MDMAppleDDMDeclarationItem
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &res, stmt, teamID); err != nil {
return nil, ctxerr.Wrap(ctx, err, "get DDM checksum by team id")
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &res, stmt, hostUUID, fleet.MDMOperationTypeInstall); err != nil {
return nil, ctxerr.Wrap(ctx, err, "get DDM declaration items")
}
return res, nil
}
func (ds *Datastore) MDMAppleDDMDeclarationPayload(ctx context.Context, declarationType fleet.MDMAppleDeclarationType, identifier string, teamID uint) (json.RawMessage, error) {
func (ds *Datastore) MDMAppleDDMDeclarationsResponse(ctx context.Context, declarationType fleet.MDMAppleDeclarationType, identifier string, hostUUID string) (json.RawMessage, error) {
// TODO: When hosts table is indexed by uuid, consider joining on hosts to ensure that the
// declaration for the host's current team is returned. In the case where the specified
// identifier is not unique to the team, the cron should ensure that any conflicting
// declarations are removed, but the join would provide an extra layer of safety.
const stmt = `
SELECT
declaration
mad.declaration
FROM
mdm_apple_declarations
host_mdm_apple_declarations hmad
JOIN mdm_apple_declarations mad ON hmad.declaration_uuid = mad.declaration_uuid
WHERE
team_id = ? AND identifier = ? AND declaration_type = ?`
host_uuid = ? AND identifier = ? AND declaration_type = ? AND operation_type = ?`
var res json.RawMessage
if err := sqlx.GetContext(ctx, ds.reader(ctx), &res, stmt, teamID, identifier, declarationType); err != nil {
return nil, ctxerr.Wrap(ctx, err, "get ddm declaration")
if err := sqlx.GetContext(ctx, ds.reader(ctx), &res, stmt, hostUUID, identifier, declarationType, fleet.MDMOperationTypeInstall); err != nil {
return nil, ctxerr.Wrap(ctx, err, "get ddm declarations response")
}
return res, nil

View file

@ -614,13 +614,13 @@ type MDMAppleHostDeclaration struct {
//
// https://developer.apple.com/documentation/devicemanagement/tokensresponse
type MDMAppleDDMTokensResponse struct {
SyncTokens MDMAppleDDMSyncTokens
SyncTokens MDMAppleDDMDeclarationsToken
}
// MDMAppleDDMSyncTokens is dictionary describes the state of declarations on the server.
// MDMAppleDDMDeclarationsToken is dictionary describes the state of declarations on the server.
//
// https://developer.apple.com/documentation/devicemanagement/synchronizationtokens
type MDMAppleDDMSyncTokens struct {
type MDMAppleDDMDeclarationsToken struct {
DeclarationsToken string `db:"md5_checksum"`
Timestamp time.Time `db:"latest_created_timestamp"`
}
@ -657,10 +657,9 @@ type MDMAppleDDMManifest struct {
//
// https://developer.apple.com/documentation/devicemanagement/declarationitemsresponse
type MDMAppleDDMDeclarationItem struct {
Identifier string `db:"identifier"`
DeclarationType string `db:"declaration_type"`
DeclarationsToken string `db:"declarations_token"`
ServerToken string `db:"server_token"`
Identifier string `db:"identifier"`
DeclarationType string `db:"declaration_type"`
ServerToken string `db:"md5_checksum"`
}
// MDMAppleDDMDeclarationResponse represents a declaration in the datastore. It is used for the DDM

View file

@ -1157,13 +1157,13 @@ type Datastore interface {
// serials.
UpdateDEPAssignProfileRetryPending(ctx context.Context, jobID uint, serials []string) error
// MDMAppleDDMSynchronizationTokens returns the token used to synchronize declarations for the
// specified team or no team.
MDMAppleDDMSynchronizationTokens(ctx context.Context, teamID uint) (*MDMAppleDDMSyncTokens, error)
// MDMAppleDDMDeclarationItems returns the declaration items for the specified team or no team.
MDMAppleDDMDeclarationItems(ctx context.Context, teamID uint) ([]MDMAppleDDMDeclarationItem, error)
// MDMAppleDDMDeclarationsToken returns the token used to synchronize declarations for the
// specified host UUID.
MDMAppleDDMDeclarationsToken(ctx context.Context, hostUUID string) (*MDMAppleDDMDeclarationsToken, error)
// MDMAppleDDMDeclarationItems returns the declaration items for the specified host UUID.
MDMAppleDDMDeclarationItems(ctx context.Context, hostUUID string) ([]MDMAppleDDMDeclarationItem, error)
// MDMAppleDDMDeclarationPayload returns the declaration payload for the specified identifier and team.
MDMAppleDDMDeclarationPayload(ctx context.Context, declarationType MDMAppleDeclarationType, identifier string, teamID uint) (json.RawMessage, error)
MDMAppleDDMDeclarationsResponse(ctx context.Context, declarationType MDMAppleDeclarationType, identifier string, hostUUID string) (json.RawMessage, error)
///////////////////////////////////////////////////////////////////////////////
// Microsoft MDM

View file

@ -758,11 +758,11 @@ type GetDEPAssignProfileExpiredCooldownsFunc func(ctx context.Context) (map[uint
type UpdateDEPAssignProfileRetryPendingFunc func(ctx context.Context, jobID uint, serials []string) error
type MDMAppleDDMSynchronizationTokensFunc func(ctx context.Context, teamID uint) (*fleet.MDMAppleDDMSyncTokens, error)
type MDMAppleDDMDeclarationsTokenFunc func(ctx context.Context, hostUUID string) (*fleet.MDMAppleDDMDeclarationsToken, error)
type MDMAppleDDMDeclarationItemsFunc func(ctx context.Context, teamID uint) ([]fleet.MDMAppleDDMDeclarationItem, error)
type MDMAppleDDMDeclarationItemsFunc func(ctx context.Context, hostUUID string) ([]fleet.MDMAppleDDMDeclarationItem, error)
type MDMAppleDDMDeclarationPayloadFunc func(ctx context.Context, declarationType fleet.MDMAppleDeclarationType, identifier string, teamID uint) (json.RawMessage, error)
type MDMAppleDDMDeclarationsResponseFunc func(ctx context.Context, declarationType fleet.MDMAppleDeclarationType, identifier string, hostUUID string) (json.RawMessage, error)
type WSTEPStoreCertificateFunc func(ctx context.Context, name string, crt *x509.Certificate) error
@ -1977,14 +1977,14 @@ type DataStore struct {
UpdateDEPAssignProfileRetryPendingFunc UpdateDEPAssignProfileRetryPendingFunc
UpdateDEPAssignProfileRetryPendingFuncInvoked bool
MDMAppleDDMSynchronizationTokensFunc MDMAppleDDMSynchronizationTokensFunc
MDMAppleDDMSynchronizationTokensFuncInvoked bool
MDMAppleDDMDeclarationsTokenFunc MDMAppleDDMDeclarationsTokenFunc
MDMAppleDDMDeclarationsTokenFuncInvoked bool
MDMAppleDDMDeclarationItemsFunc MDMAppleDDMDeclarationItemsFunc
MDMAppleDDMDeclarationItemsFuncInvoked bool
MDMAppleDDMDeclarationPayloadFunc MDMAppleDDMDeclarationPayloadFunc
MDMAppleDDMDeclarationPayloadFuncInvoked bool
MDMAppleDDMDeclarationsResponseFunc MDMAppleDDMDeclarationsResponseFunc
MDMAppleDDMDeclarationsResponseFuncInvoked bool
WSTEPStoreCertificateFunc WSTEPStoreCertificateFunc
WSTEPStoreCertificateFuncInvoked bool
@ -4732,25 +4732,25 @@ func (s *DataStore) UpdateDEPAssignProfileRetryPending(ctx context.Context, jobI
return s.UpdateDEPAssignProfileRetryPendingFunc(ctx, jobID, serials)
}
func (s *DataStore) MDMAppleDDMSynchronizationTokens(ctx context.Context, teamID uint) (*fleet.MDMAppleDDMSyncTokens, error) {
func (s *DataStore) MDMAppleDDMDeclarationsToken(ctx context.Context, hostUUID string) (*fleet.MDMAppleDDMDeclarationsToken, error) {
s.mu.Lock()
s.MDMAppleDDMSynchronizationTokensFuncInvoked = true
s.MDMAppleDDMDeclarationsTokenFuncInvoked = true
s.mu.Unlock()
return s.MDMAppleDDMSynchronizationTokensFunc(ctx, teamID)
return s.MDMAppleDDMDeclarationsTokenFunc(ctx, hostUUID)
}
func (s *DataStore) MDMAppleDDMDeclarationItems(ctx context.Context, teamID uint) ([]fleet.MDMAppleDDMDeclarationItem, error) {
func (s *DataStore) MDMAppleDDMDeclarationItems(ctx context.Context, hostUUID string) ([]fleet.MDMAppleDDMDeclarationItem, error) {
s.mu.Lock()
s.MDMAppleDDMDeclarationItemsFuncInvoked = true
s.mu.Unlock()
return s.MDMAppleDDMDeclarationItemsFunc(ctx, teamID)
return s.MDMAppleDDMDeclarationItemsFunc(ctx, hostUUID)
}
func (s *DataStore) MDMAppleDDMDeclarationPayload(ctx context.Context, declarationType fleet.MDMAppleDeclarationType, identifier string, teamID uint) (json.RawMessage, error) {
func (s *DataStore) MDMAppleDDMDeclarationsResponse(ctx context.Context, declarationType fleet.MDMAppleDeclarationType, identifier string, hostUUID string) (json.RawMessage, error) {
s.mu.Lock()
s.MDMAppleDDMDeclarationPayloadFuncInvoked = true
s.MDMAppleDDMDeclarationsResponseFuncInvoked = true
s.mu.Unlock()
return s.MDMAppleDDMDeclarationPayloadFunc(ctx, declarationType, identifier, teamID)
return s.MDMAppleDDMDeclarationsResponseFunc(ctx, declarationType, identifier, hostUUID)
}
func (s *DataStore) WSTEPStoreCertificate(ctx context.Context, name string, crt *x509.Certificate) error {

View file

@ -2978,15 +2978,6 @@ func (svc *MDMAppleDDMService) DeclarativeManagement(r *mdm.Request, dm *mdm.Dec
return nil, ctxerr.New(r.Context, "missing device id")
}
h, err := svc.ds.HostLiteByIdentifier(r.Context, dm.UDID)
if err != nil {
return nil, ctxerr.Wrap(r.Context, err, "getting host by identifier")
}
var tid uint
if h.TeamID != nil {
tid = *h.TeamID
}
switch {
case dm.Endpoint == "tokens":
level.Debug(svc.logger).Log("msg", "received tokens request")
@ -2995,11 +2986,11 @@ func (svc *MDMAppleDDMService) DeclarativeManagement(r *mdm.Request, dm *mdm.Dec
return nil, ctxerr.Wrap(r.Context, err, "recording declarative checkin")
}
return svc.handleTokens(r.Context, tid)
return svc.handleTokens(r.Context, dm.UDID)
case dm.Endpoint == "declaration-items":
level.Debug(svc.logger).Log("msg", "received declaration-items request")
return svc.handleDeclarationItems(r.Context, tid)
return svc.handleDeclarationItems(r.Context, dm.UDID)
case dm.Endpoint == "status":
level.Debug(svc.logger).Log("msg", "received status request")
@ -3009,32 +3000,15 @@ func (svc *MDMAppleDDMService) DeclarativeManagement(r *mdm.Request, dm *mdm.Dec
case strings.HasPrefix(dm.Endpoint, "declarations"):
level.Debug(svc.logger).Log("msg", "received declarations request")
parts := strings.Split(dm.Endpoint, "/")
if len(parts) != 3 {
return nil, ctxerr.New(r.Context, "unrecognized declarations endpoint")
}
declarationType := parts[1]
declarationIdentifier := parts[2]
level.Debug(svc.logger).Log("msg", "parsed declarations request", "type", declarationType, "identifier", declarationIdentifier)
// TODO: Validate declarationType?
d, err := svc.ds.MDMAppleDDMDeclarationPayload(r.Context, fleet.MDMAppleDeclarationType("com.apple."+declarationType), declarationIdentifier, tid)
if err != nil {
return nil, ctxerr.Wrap(r.Context, err, "getting declaration")
}
b, err := json.Marshal(d)
if err != nil {
return nil, ctxerr.Wrap(r.Context, err, "marshaling declaration")
}
return b, nil
return svc.handleDeclarationsResponse(r.Context, dm.Endpoint, dm.UDID)
default:
return nil, ctxerr.New(r.Context, "unrecognized ddm endpoint")
}
}
func (svc *MDMAppleDDMService) handleTokens(ctx context.Context, teamID uint) ([]byte, error) {
tok, err := svc.ds.MDMAppleDDMSynchronizationTokens(ctx, teamID)
func (svc *MDMAppleDDMService) handleTokens(ctx context.Context, hostUUID string) ([]byte, error) {
tok, err := svc.ds.MDMAppleDDMDeclarationsToken(ctx, hostUUID)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "getting synchronization tokens")
}
@ -3049,22 +3023,15 @@ func (svc *MDMAppleDDMService) handleTokens(ctx context.Context, teamID uint) ([
return b, nil
}
func (svc *MDMAppleDDMService) handleDeclarationItems(ctx context.Context, teamID uint) ([]byte, error) {
di, err := svc.ds.MDMAppleDDMDeclarationItems(ctx, teamID)
func (svc *MDMAppleDDMService) handleDeclarationItems(ctx context.Context, hostUUID string) ([]byte, error) {
di, err := svc.ds.MDMAppleDDMDeclarationItems(ctx, hostUUID)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "getting synchronization tokens")
}
var dTok string
activations := []fleet.MDMAppleDDMManifest{}
configurations := []fleet.MDMAppleDDMManifest{}
for _, d := range di {
if dTok == "" {
dTok = d.DeclarationsToken
} else if dTok != d.DeclarationsToken {
level.Debug(svc.logger).Log("msg", "inconsistent declarations token", "expected", dTok, "got", d.DeclarationsToken)
}
manifest := fleet.MDMAppleDDMManifest{Identifier: d.Identifier, ServerToken: d.ServerToken}
switch d.DeclarationType {
case string(fleet.MDMAppleDeclarativeActivation):
@ -3077,6 +3044,12 @@ func (svc *MDMAppleDDMService) handleDeclarationItems(ctx context.Context, teamI
}
}
// TODO: Look for ways to optimize the declaration item query so that we don't have to get the declarations token separately.
dTok, err := svc.ds.MDMAppleDDMDeclarationsToken(ctx, hostUUID)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "getting declarations token")
}
b, err := json.Marshal(fleet.MDMAppleDDMDeclarationItemsResponse{
Declarations: fleet.MDMAppleDDMManifestItems{
Activations: activations,
@ -3084,7 +3057,7 @@ func (svc *MDMAppleDDMService) handleDeclarationItems(ctx context.Context, teamI
Assets: []fleet.MDMAppleDDMManifest{},
Management: []fleet.MDMAppleDDMManifest{},
},
DeclarationsToken: dTok,
DeclarationsToken: dTok.DeclarationsToken,
})
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "marshaling synchronization tokens")
@ -3092,3 +3065,23 @@ func (svc *MDMAppleDDMService) handleDeclarationItems(ctx context.Context, teamI
return b, nil
}
func (svc *MDMAppleDDMService) handleDeclarationsResponse(ctx context.Context, endpoint string, hostUUID string) ([]byte, error) {
parts := strings.Split(endpoint, "/")
if len(parts) != 3 {
return nil, ctxerr.New(ctx, "unrecognized declarations endpoint")
}
level.Debug(svc.logger).Log("msg", "parsed declarations request", "type", parts[1], "identifier", parts[2])
// TODO: Validate declarationType?
d, err := svc.ds.MDMAppleDDMDeclarationsResponse(ctx, fleet.MDMAppleDeclarationType("com.apple."+parts[1]), parts[2], hostUUID)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "getting declaration")
}
b, err := json.Marshal(d)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "marshaling declaration")
}
return b, nil
}

View file

@ -12519,7 +12519,6 @@ func (s *integrationMDMTestSuite) TestIsServerBitlockerStatus() {
require.Equal(t, fleet.DiskEncryptionEnforcing, *hr.Host.MDM.OSSettings.DiskEncryption.Status)
}
// TODO(sarah): Build out this test
func (s *integrationMDMTestSuite) TestMDMAppleDeviceManagementRequests() {
t := s.T()
_, mdmDevice := createHostThenEnrollMDM(s.ds, s.server.URL, t)
@ -12554,6 +12553,28 @@ INSERT INTO mdm_apple_declarations (
})
}
insertHostDeclaration := func(t *testing.T, hostUUID string, decl fleet.MDMAppleDeclaration) {
stmt := `
INSERT INTO host_mdm_apple_declarations (
host_uuid,
status,
operation_type,
md5_checksum,
declaration_uuid
) VALUES (?,?,?,?,?)`
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
_, err := q.ExecContext(context.Background(), stmt,
hostUUID,
fleet.MDMDeliveryPending,
fleet.MDMOperationTypeInstall,
decl.MD5Checksum,
decl.DeclarationUUID,
)
return err
})
}
// initialize a time to use for our first declaration, subsequent declarations will be
// incremented by a minute
then := time.Now().UTC().Truncate(time.Second).Add(-1 * time.Hour)
@ -12573,6 +12594,7 @@ INSERT INTO mdm_apple_declarations (
},
}
insertDeclaration(t, noTeamDeclsByUUID["123"])
insertHostDeclaration(t, mdmDevice.UUID, noTeamDeclsByUUID["123"])
mapDeclsByChecksum := func(byUUID map[string]fleet.MDMAppleDeclaration) map[string]fleet.MDMAppleDeclaration {
byChecksum := make(map[string]fleet.MDMAppleDeclaration)
@ -12680,6 +12702,7 @@ INSERT INTO mdm_apple_declarations (
UploadedAt: then.Add(1 * time.Minute),
}
insertDeclaration(t, noTeamDeclsByUUID["456"])
insertHostDeclaration(t, mdmDevice.UUID, noTeamDeclsByUUID["456"])
// get tokens again, timestamp and token should have changed
r, err = mdmDevice.DeclarativeManagement("tokens")
@ -12707,6 +12730,7 @@ INSERT INTO mdm_apple_declarations (
UploadedAt: then.Add(2 * time.Minute),
}
insertDeclaration(t, noTeamDeclsByUUID["789"])
insertHostDeclaration(t, mdmDevice.UUID, noTeamDeclsByUUID["789"])
// get tokens again, timestamp and token should have changed
r, err = mdmDevice.DeclarativeManagement("tokens")
@ -12752,6 +12776,7 @@ INSERT INTO mdm_apple_declarations (
UploadedAt: then.Add(3 * time.Minute),
}
insertDeclaration(t, noTeamDeclsByUUID["abc"])
insertHostDeclaration(t, mdmDevice.UUID, noTeamDeclsByUUID["abc"])
want = noTeamDeclsByUUID["abc"]
wantBytes, err = json.Marshal(want.Declaration)
require.NoError(t, err)