Skip to content
103 changes: 94 additions & 9 deletions server/datastore/mysql/apple_mdm.go
Original file line number Diff line number Diff line change
Expand Up @@ -5560,7 +5560,7 @@ func batchSetDeclarationLabelAssociationsDB(ctx context.Context, tx sqlx.ExtCont
func (ds *Datastore) MDMAppleDDMDeclarationsToken(ctx context.Context, hostUUID string, scope fleet.PayloadScope) (*fleet.MDMAppleDDMDeclarationsToken, error) {
const stmt = `
SELECT
COALESCE(MD5(CONCAT(COUNT(0), GROUP_CONCAT(CONCAT(CONCAT(HEX(mad.token), IFNULL(hmad.variables_updated_at, '')), IFNULL(hmad.assets_updated_at, ''))
COALESCE(MD5(CONCAT(COUNT(0), GROUP_CONCAT(CONCAT(CONCAT(CONCAT(HEX(mad.token), IFNULL(hmad.variables_updated_at, '')), IFNULL(hmad.assets_updated_at, '')), IFNULL(hmad.activation_updated_at, ''))
ORDER BY
mad.uploaded_at DESC, mad.declaration_uuid ASC separator ''))), '') AS token,
COALESCE(MAX(mad.created_at), NOW()) AS latest_created_timestamp
Expand Down Expand Up @@ -5598,6 +5598,8 @@ SELECT
mad.identifier, mad.declaration_uuid, status, operation_type, mad.uploaded_at,
hmad.variables_updated_at,
hmad.assets_updated_at,
hmad.activation_updated_at,
JSON_UNQUOTE(JSON_EXTRACT(mad.raw_json, '$.Type')) AS declaration_type,
IF(hmad.variables_updated_at IS NOT NULL AND operation_type = ?, mad.raw_json, NULL) as raw_json
FROM
host_mdm_apple_declarations hmad
Expand All @@ -5606,21 +5608,104 @@ WHERE
hmad.host_uuid = ? AND hmad.scope = ?`

var res []fleet.MDMAppleDDMDeclarationItem
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &res, stmt, fleet.MDMOperationTypeInstall, hostUUID, scope); err != nil {
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &res, stmt,
fleet.MDMOperationTypeInstall, hostUUID, scope); err != nil {
return nil, ctxerr.Wrap(ctx, err, "get DDM declaration items")
}

return res, nil
}

// ListCustomActivationsForDeclarations returns the custom activations attached
// to the given declarations. Declarations without one are simply absent, and
// the caller synthesizes Fleet's activation for those.
func (ds *Datastore) ListCustomActivationsForDeclarations(ctx context.Context, declUUIDs []string) ([]*fleet.MDMAppleDDMActivationItem, error) {
if len(declUUIDs) == 0 {
return nil, nil
}

// Custom host vitals aren't recorded in mdm_configuration_profile_variables,
// so they only show up by scanning the body -- same approach as the reconciler.
const stmt = `
SELECT
act.declaration_uuid,
act.identifier,
HEX(act.token) AS token,
act.raw_json,
(
EXISTS(SELECT 1 FROM mdm_configuration_profile_variables v WHERE v.apple_ddm_activation_uuid = act.activation_uuid)
OR INSTR(act.raw_json, ?) > 0
) AS has_fleet_variables
FROM
mdm_apple_ddm_activations act
WHERE
act.declaration_uuid IN (?)`

q, args, err := sqlx.In(stmt, fleet.CustomHostVitalPrefix, declUUIDs)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "build custom activations query")
}

var res []*fleet.MDMAppleDDMActivationItem
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &res, q, args...); err != nil {
return nil, ctxerr.Wrap(ctx, err, "list custom activations for declarations")
}

return res, nil
}

// MDMAppleDDMActivationResponse resolves an activation request to either the
// stored custom activation or the declaration Fleet synthesizes one for,
// restricted to declarations the host is scoped to. RawJSON is nil for the
// synthesized case.
//
// Generated activations are named after the declaration's UUID rather than its
// identifier so an admin-chosen activation identifier can never collide with
// one Fleet invents.
func (ds *Datastore) MDMAppleDDMActivationResponse(ctx context.Context, identifier string, hostUUID string, scope fleet.PayloadScope) (*fleet.MDMAppleDDMActivationForDelivery, error) {
const stmt = `
SELECT
act.raw_json,
mad.identifier AS configuration_identifier,
HEX(mad.token) AS token,
HEX(act.token) AS activation_token,
hmad.variables_updated_at,
hmad.assets_updated_at,
hmad.activation_updated_at,
mad.declaration_uuid
FROM
host_mdm_apple_declarations hmad
JOIN mdm_apple_declarations mad ON mad.declaration_uuid = hmad.declaration_uuid
LEFT JOIN mdm_apple_ddm_activations act ON act.declaration_uuid = mad.declaration_uuid
WHERE
hmad.host_uuid = ? AND hmad.scope = ? AND hmad.operation_type = ?
AND (
act.identifier = ?
OR (act.activation_uuid IS NULL AND CONCAT(mad.declaration_uuid, ?) = ?)
)`

var res fleet.MDMAppleDDMActivationForDelivery
if err := sqlx.GetContext(ctx, ds.reader(ctx), &res, stmt,
hostUUID, scope, fleet.MDMOperationTypeInstall,
identifier, fleet.MDMAppleGeneratedActivationSuffix, identifier,
); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, notFound("MDMAppleActivation").WithName(identifier)
}
return nil, ctxerr.Wrap(ctx, err, "get ddm activation response")
}
return &res, nil
}

func (ds *Datastore) MDMAppleDDMDeclarationsResponse(ctx context.Context, identifier string, hostUUID string, scope fleet.PayloadScope) (*fleet.MDMAppleDeclaration, 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
mad.declaration_uuid, mad.raw_json, HEX(mad.token) as token, hmad.variables_updated_at, hmad.assets_updated_at
mad.declaration_uuid, mad.raw_json, HEX(mad.token) as token,
hmad.variables_updated_at, hmad.assets_updated_at, hmad.activation_updated_at
FROM
host_mdm_apple_declarations hmad
JOIN mdm_apple_declarations mad ON hmad.declaration_uuid = mad.declaration_uuid
Expand Down Expand Up @@ -5769,14 +5854,14 @@ func cleanUpDuplicateRemoveInstall(ctx context.Context, tx sqlx.ExtContext, prof
// scoped to that channel or the other channel's rows would look "removed".
func (ds *Datastore) MDMAppleStoreDDMStatusReport(ctx context.Context, hostUUID string, scope fleet.PayloadScope, updates []*fleet.MDMAppleHostDeclaration) error {
getHostDeclarationsStmt := `
SELECT host_uuid, status, operation_type, HEX(token) as token, secrets_updated_at, variables_updated_at, assets_updated_at, declaration_uuid, declaration_identifier, declaration_name
SELECT host_uuid, status, operation_type, HEX(token) as token, secrets_updated_at, variables_updated_at, assets_updated_at, activation_updated_at, declaration_uuid, declaration_identifier, declaration_name
FROM host_mdm_apple_declarations
WHERE host_uuid = ? AND scope = ?
`

updateHostDeclarationsStmt := `
INSERT INTO host_mdm_apple_declarations
(host_uuid, declaration_uuid, status, operation_type, detail, declaration_name, declaration_identifier, token, secrets_updated_at)
(host_uuid, declaration_uuid, status, operation_type, detail, declaration_name, declaration_identifier, token, secrets_updated_at, scope)
VALUES
%s
ON DUPLICATE KEY UPDATE
Expand Down Expand Up @@ -5805,10 +5890,10 @@ ON DUPLICATE KEY UPDATE
for _, c := range current {
// Skip updates for 'remove' operations because it is possible that IT admin removed a profile and then re-added it.
// Pending removes are cleaned up after we update status of installs.
if u, ok := updatesByToken[fleet.EffectiveDDMToken(c.Token, c.VariablesUpdatedAt, c.AssetsUpdatedAt)]; ok && c.OperationType != fleet.MDMOperationTypeRemove {
insertVals.WriteString("(?, ?, ?, ?, ?, ?, ?, UNHEX(?), ?),")
args = append(args, hostUUID, c.DeclarationUUID, u.Status, u.OperationType, u.Detail, c.Identifier, c.Name, c.Token,
c.SecretsUpdatedAt)
if u, ok := updatesByToken[fleet.EffectiveDDMToken(c.Token, c.VariablesUpdatedAt, c.AssetsUpdatedAt, c.ActivationUpdatedAt)]; ok && c.OperationType != fleet.MDMOperationTypeRemove {
insertVals.WriteString("(?, ?, ?, ?, ?, ?, ?, UNHEX(?), ?, ?),")
args = append(args, hostUUID, c.DeclarationUUID, u.Status, u.OperationType, u.Detail, c.Name, c.Identifier, c.Token,
c.SecretsUpdatedAt, scope)
}
}

Expand Down
98 changes: 92 additions & 6 deletions server/datastore/mysql/apple_mdm_batched.go
Original file line number Diff line number Diff line change
Expand Up @@ -646,8 +646,20 @@ func (ds *Datastore) listAppleDeclarationsForReconcileTransaction(ctx context.Co
declUUIDs = append(declUUIDs, u)
}
if len(declUUIDs) > 0 {
const varsStmt = `SELECT DISTINCT apple_declaration_uuid FROM mdm_configuration_profile_variables WHERE apple_declaration_uuid IN (?)`
q, args, err := sqlx.In(varsStmt, declUUIDs)
// A variable may live only in the custom activation, which is recorded
// against apple_ddm_activation_uuid. Missing those leaves the owning
// declaration unstamped, so the host never re-fetches when the value
// changes.
const varsStmt = `
SELECT DISTINCT apple_declaration_uuid AS declaration_uuid
FROM mdm_configuration_profile_variables
WHERE apple_declaration_uuid IN (?)
UNION
SELECT DISTINCT act.declaration_uuid
FROM mdm_configuration_profile_variables v
JOIN mdm_apple_ddm_activations act ON act.activation_uuid = v.apple_ddm_activation_uuid
WHERE act.declaration_uuid IN (?)`
q, args, err := sqlx.In(varsStmt, declUUIDs, declUUIDs)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "build apple declaration variables query")
}
Expand Down Expand Up @@ -684,6 +696,24 @@ func (ds *Datastore) listAppleDeclarationsForReconcileTransaction(ctx context.Co
}
}

// Same scan against custom activations: they're expanded at delivery like
// the declaration body, so a vital referenced only from the activation
// still has to stamp variables_updated_at on the owning declaration.
const actVitalsStmt = `SELECT declaration_uuid FROM mdm_apple_ddm_activations WHERE declaration_uuid IN (?) AND INSTR(raw_json, ?) > 0`
q, args, err = sqlx.In(actVitalsStmt, declUUIDs, fleet.CustomHostVitalPrefix)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "build activation custom host vitals query")
}
var actWithVitals []string
if err := sqlx.SelectContext(ctx, tx, &actWithVitals, q, args...); err != nil {
return nil, ctxerr.Wrap(ctx, err, "select activations with custom host vitals")
}
for _, u := range actWithVitals {
if d, ok := byUUID[u]; ok {
d.HasFleetVariables = true
}
}

// For declarations that reference DDM assets, load the most recent
// uploaded_at across their referenced assets. The reconciler stamps this
// onto host_mdm_apple_declarations.assets_updated_at so that editing an
Expand Down Expand Up @@ -714,6 +744,56 @@ func (ds *Datastore) listAppleDeclarationsForReconcileTransaction(ctx context.Co
d.AssetsUpdatedAt = &t
}
}

// Same idea for a custom activation: editing it bumps its uploaded_at,
// which re-syncs the declaration it activates.
// GREATEST so a secret re-stamp counts as a change even when the
// activation itself wasn't re-uploaded. COALESCE because GREATEST
// returns NULL if any argument is NULL.
const activationsStmt = `
SELECT declaration_uuid,
GREATEST(uploaded_at, COALESCE(secrets_updated_at, uploaded_at)) AS activation_updated_at
FROM mdm_apple_ddm_activations
WHERE declaration_uuid IN (?)`
cq, cargs, err := sqlx.In(activationsStmt, declUUIDs)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "build apple declaration activations query")
}
type activationRow struct {
DeclarationUUID string `db:"declaration_uuid"`
ActivationUpdatedAt sql.NullTime `db:"activation_updated_at"`
}
var activationRows []activationRow
if err := sqlx.SelectContext(ctx, tx, &activationRows, cq, cargs...); err != nil {
return nil, ctxerr.Wrap(ctx, err, "select apple declarations with custom activations")
}
for _, cr := range activationRows {
if d, ok := byUUID[cr.DeclarationUUID]; ok && cr.ActivationUpdatedAt.Valid {
t := cr.ActivationUpdatedAt.Time
d.ActivationUpdatedAt = &t
}
}

// A declaration whose activation uses Fleet variables needs the same
// per-host token busting as one that uses them itself.
const actVarsStmt = `
SELECT DISTINCT a.declaration_uuid
FROM mdm_apple_ddm_activations a
JOIN mdm_configuration_profile_variables v ON v.apple_ddm_activation_uuid = a.activation_uuid
WHERE a.declaration_uuid IN (?)`
avq, avargs, err := sqlx.In(actVarsStmt, declUUIDs)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "build apple activation variables query")
}
var withActVars []string
if err := sqlx.SelectContext(ctx, tx, &withActVars, avq, avargs...); err != nil {
return nil, ctxerr.Wrap(ctx, err, "select apple activations with fleet variables")
}
for _, u := range withActVars {
if d, ok := byUUID[u]; ok {
d.HasFleetVariables = true
}
}
Comment thread
raju249 marked this conversation as resolved.
Resolved
}

return out, nil
Expand Down Expand Up @@ -752,6 +832,7 @@ func (ds *Datastore) bulkGetHostMDMAppleDeclarationsByUUIDsTransaction(
secrets_updated_at,
variables_updated_at,
assets_updated_at,
activation_updated_at,
scope
FROM host_mdm_apple_declarations
WHERE host_uuid IN (?)
Expand Down Expand Up @@ -915,7 +996,8 @@ func (ds *Datastore) BulkUpsertMDMAppleHostDeclarations(
const baseStmt = `
INSERT INTO host_mdm_apple_declarations
(host_uuid, declaration_uuid, declaration_identifier, declaration_name,
status, operation_type, token, secrets_updated_at, variables_updated_at, assets_updated_at, scope)
status, operation_type, token, secrets_updated_at, variables_updated_at, assets_updated_at,
activation_updated_at, scope)
VALUES %s
ON DUPLICATE KEY UPDATE
status = VALUES(status),
Expand All @@ -926,6 +1008,7 @@ func (ds *Datastore) BulkUpsertMDMAppleHostDeclarations(
secrets_updated_at = VALUES(secrets_updated_at),
variables_updated_at = VALUES(variables_updated_at),
assets_updated_at = VALUES(assets_updated_at),
activation_updated_at = VALUES(activation_updated_at),
scope = VALUES(scope)
`

Expand All @@ -935,18 +1018,21 @@ func (ds *Datastore) BulkUpsertMDMAppleHostDeclarations(
batch := rows[i:end]

valueParts := make([]string, 0, len(batch))
args := make([]any, 0, len(batch)*10)
// Keep the per-row placeholder count under 60: MySQL caps a prepared
// statement at 65535 placeholders, and each batch binds batchSize rows.
args := make([]any, 0, len(batch)*12)
Comment thread
raju249 marked this conversation as resolved.
Resolved
batchByKey := make(map[string]*fleet.MDMAppleHostDeclaration, len(batch))
for _, r := range batch {
// Scope defaults to System
scope := r.Scope
if scope == "" {
scope = fleet.PayloadScopeSystem
}
valueParts = append(valueParts, "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
valueParts = append(valueParts, "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
args = append(args,
r.HostUUID, r.DeclarationUUID, r.Identifier, r.Name,
r.Status, r.OperationType, r.Token, r.SecretsUpdatedAt, r.VariablesUpdatedAt, r.AssetsUpdatedAt, scope,
r.Status, r.OperationType, r.Token, r.SecretsUpdatedAt, r.VariablesUpdatedAt, r.AssetsUpdatedAt,
r.ActivationUpdatedAt, scope,
)
batchByKey[fmt.Sprintf("%s\n%s", r.HostUUID, r.DeclarationUUID)] = r
}
Expand Down
9 changes: 7 additions & 2 deletions server/datastore/mysql/custom_host_vitals.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,10 +382,15 @@ func resendMDMProfilesForCustomHostVital(ctx context.Context, tx sqlx.ExtContext
JOIN mdm_windows_configuration_profiles mwcp ON mwcp.profile_uuid = hmwp.profile_uuid
WHERE hmwp.host_uuid = ? AND hmwp.operation_type = ? AND hmwp.status IS NOT NULL AND INSTR(mwcp.syncml, ?) > 0`

customHostVitalResendAppleDeclarationsSelectStmt = `SELECT hmad.declaration_uuid AS uuid, mad.raw_json AS contents
// A custom activation is expanded at delivery like the declaration, so a
// vital referenced only there has to resend the owning declaration too.
customHostVitalResendAppleDeclarationsSelectStmt = `SELECT hmad.declaration_uuid AS uuid,
CONCAT(mad.raw_json, ' ', COALESCE(act.raw_json, '')) AS contents
FROM host_mdm_apple_declarations hmad
JOIN mdm_apple_declarations mad ON mad.declaration_uuid = hmad.declaration_uuid
WHERE hmad.host_uuid = ? AND hmad.operation_type = ? AND hmad.status IS NOT NULL AND INSTR(mad.raw_json, ?) > 0`
LEFT JOIN mdm_apple_ddm_activations act ON act.declaration_uuid = mad.declaration_uuid
WHERE hmad.host_uuid = ? AND hmad.operation_type = ? AND hmad.status IS NOT NULL
AND INSTR(CONCAT(mad.raw_json, ' ', COALESCE(act.raw_json, '')), ?) > 0`

customHostVitalResendAndroidProfilesSelectStmt = `SELECT hmap.profile_uuid AS uuid, macp.raw_json AS contents
FROM host_mdm_android_profiles hmap
Expand Down
3 changes: 3 additions & 0 deletions server/datastore/mysql/scim.go
Original file line number Diff line number Diff line change
Expand Up @@ -1446,8 +1446,11 @@ func triggerResendProfilesUsingVariables(ctx context.Context, tx sqlx.ExtContext
JOIN mdm_apple_declarations mad
ON (mad.team_id = h.team_id OR (COALESCE(mad.team_id, 0) = 0 AND h.team_id IS NULL)) AND
mad.declaration_uuid = hmad.declaration_uuid
LEFT JOIN mdm_apple_ddm_activations act
ON act.declaration_uuid = mad.declaration_uuid
JOIN mdm_configuration_profile_variables mcpv
ON mcpv.apple_declaration_uuid = mad.declaration_uuid
OR mcpv.apple_ddm_activation_uuid = act.activation_uuid
JOIN fleet_variables fv
ON mcpv.fleet_variable_id = fv.id
SET
Expand Down
Loading
Loading