Skip to content

ACME: migration to add hardware_serial to DEP assignments#42833

Merged
JordanMontgomery merged 13 commits intofeature-ACMEfrom
mna-41491-migrate-serials-dep-for-acme
Apr 2, 2026
Merged

ACME: migration to add hardware_serial to DEP assignments#42833
JordanMontgomery merged 13 commits intofeature-ACMEfrom
mna-41491-migrate-serials-dep-for-acme

Conversation

@mna
Copy link
Copy Markdown
Member

@mna mna commented Apr 1, 2026

Related issue: Resolves #41491

Checklist for submitter

  • Input data is properly validated, SELECT * is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.

Testing

  • Added/updated automated tests

Database migrations

  • Checked schema for all modified table for columns that will auto-update timestamps during migration.

This should not update any existing timestamp in that table, per their definition:

  `added_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, <- only set at creation time if null
  `deleted_at` timestamp NULL DEFAULT NULL, <- not set automatically
  `response_updated_at` timestamp NULL DEFAULT NULL, <- not set automatically
  `mdm_migration_deadline` timestamp(6) NULL DEFAULT NULL, <- not set automatically
  `mdm_migration_completed` timestamp(6) NULL DEFAULT NULL, <- not set automatically
  • Ensured the correct collation is explicitly set for character columns (COLLATE utf8mb4_unicode_ci).

}
if !strings.HasSuffix(certPEM, "\n") {
certPEM += "\n"
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix for a Claude feedback on my latest get certificate PR.

Comment on lines 1766 to +1767
func (ds *Datastore) GetDeviceInfoForACMERenewal(ctx context.Context, hostUUIDs []string) ([]fleet.DeviceInfoForACMERenewal, error) {
// TODO(mna): anyone knows what those TODOs (from Sarah's PRs) were for?
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's about host_dep_assignments, but not about the new hardware_serial field, so I 'm not sure if there's anything to change here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the suggestion was we c ould put the model information here as well? I think it is OK not to for now


stmt := `
INSERT INTO host_dep_assignments (host_id, abm_token_id, mdm_migration_deadline)
INSERT INTO host_dep_assignments (host_id, abm_token_id, mdm_migration_deadline, hardware_serial)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that while I've added it to the insertion here, I haven't modified the fleet.HostDEPAssignment struct to add that field (nor to load it when it is read). Wasn't clear to me if that was needed and I wanted to change as little as necessary to avoid impacting more than ACME.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem needed at the moment so I think that is OK

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 1, 2026

Codecov Report

❌ Patch coverage is 86.04651% with 6 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (feature-ACME@07e788b). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...401153503_AddHardwareSerialToHostDEPAssignments.go 71.42% 4 Missing and 2 partials ⚠️
Additional details and impacted files
@@               Coverage Diff               @@
##             feature-ACME   #42833   +/-   ##
===============================================
  Coverage                ?   66.50%           
===============================================
  Files                   ?     2536           
  Lines                   ?   202687           
  Branches                ?     9047           
===============================================
  Hits                    ?   134797           
  Misses                  ?    55654           
  Partials                ?    12236           
Flag Coverage Δ
backend 68.28% <86.04%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mna mna marked this pull request as ready for review April 1, 2026 21:09
@mna mna requested a review from a team as a code owner April 1, 2026 21:09
Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@mna
Copy link
Copy Markdown
Member Author

mna commented Apr 1, 2026

@claude review once

Comment on lines +13 to +30
func Up_20260401153503(tx *sql.Tx) error {
_, err := tx.Exec(`
ALTER TABLE host_dep_assignments
ADD COLUMN hardware_serial varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT ''
`)
if err != nil {
return errors.Wrap(err, "add host_dep_assignments.hardware_serial column")
}

_, err = tx.Exec(`
UPDATE host_dep_assignments hda
JOIN hosts h ON h.id = hda.host_id
SET hda.hardware_serial = h.hardware_serial
WHERE hda.deleted_at IS NULL
`)
if err != nil {
return errors.Wrap(err, "backfill host_dep_assignments.hardware_serial column")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 The migration adds hardware_serial to host_dep_assignments but omits an index on the column, causing GetHostDEPAssignmentsBySerial to perform a full table scan on every ACME certificate lookup. Add ALTER TABLE host_dep_assignments ADD INDEX idx_hdep_hardware_serial (hardware_serial) to the migration to restore query performance.

Extended reasoning...

What the bug is and how it manifests

The migration Up_20260401153503 adds a hardware_serial VARCHAR(255) column to host_dep_assignments and backfills it, but never creates an index on the new column. The PR simultaneously changes GetHostDEPAssignmentsBySerial to query directly on hdep.hardware_serial = ? instead of using the previous subselect approach.

The specific code path that triggers it

In apple_mdm.go, GetHostDEPAssignmentsBySerial now executes:

SELECT host_id, added_at, deleted_at, abm_token_id, mdm_migration_deadline, mdm_migration_completed
FROM host_dep_assignments hdep
WHERE hdep.hardware_serial = ? AND hdep.deleted_at IS NULL

This function is called in hot paths: ACME device attestation challenges and enrollment profile generation. Both are invoked for every ACME certificate issuance and renewal request.

Why existing code does not prevent it

The schema.sql diff confirms the table indexes after this migration are:

  • PRIMARY KEY (host_id)
  • KEY idx_hdep_response (assign_profile_response, response_updated_at)
  • KEY fk_host_dep_assignments_abm_token_id (abm_token_id)

There is no index on hardware_serial. The previous query used WHERE hdep.host_id IN (SELECT id FROM hosts WHERE hardware_serial = ?), which leveraged the idx_hosts_hardware_serial index on the hosts table plus a primary-key lookup on host_dep_assignments. The new approach eliminates both of those optimizations.

What the impact would be

For large enterprise deployments with tens or hundreds of thousands of DEP-enrolled devices, every ACME certificate lookup/renewal performs a full table scan of host_dep_assignments. This is a direct performance regression on a critical, frequently-invoked code path. The established pattern in this codebase is to index hardware_serial wherever it is queried (the hosts table has idx_hosts_hardware_serial for this reason).

How to fix it

Add a third ALTER TABLE statement to Up_20260401153503:

_, err = tx.Exec("ALTER TABLE host_dep_assignments ADD INDEX idx_hdep_hardware_serial (hardware_serial)")
if err != nil {
    return errors.Wrap(err, "add index on host_dep_assignments.hardware_serial")
}

Also update schema.sql to include the new key definition.

Step-by-step proof

  1. Before this PR: GetHostDEPAssignmentsBySerial("ABC123") runs WHERE hdep.host_id IN (SELECT id FROM hosts WHERE hardware_serial = ?). MySQL uses idx_hosts_hardware_serial to find the host id (index seek, ~1 row), then does a primary-key lookup on host_dep_assignments -- effectively O(1).
  2. This PR: the migration adds hardware_serial to host_dep_assignments with no index. The query becomes WHERE hdep.hardware_serial = ?. With no index on this column, MySQL must scan every row in host_dep_assignments to find matches.
  3. On a deployment with 100,000 DEP-enrolled devices, each ACME certificate lookup now reads ~100,000 rows instead of ~1 row. With frequent certificate renewals, this quickly becomes a bottleneck.
  4. EXPLAIN for the new query would show type: ALL (full table scan) on host_dep_assignments; with the index added it would show type: ref using idx_hdep_hardware_serial.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an index

@JordanMontgomery JordanMontgomery merged commit 873d82a into feature-ACME Apr 2, 2026
42 of 45 checks passed
@JordanMontgomery JordanMontgomery deleted the mna-41491-migrate-serials-dep-for-acme branch April 2, 2026 18:18
MagnusHJensen pushed a commit that referenced this pull request Apr 2, 2026
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #41491

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.

- [x] Added/updated automated tests

- [x] Checked schema for all modified table for columns that will
auto-update timestamps during migration.

This should not update any existing timestamp in that table, per their
definition:

```
  `added_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, <- only set at creation time if null
  `deleted_at` timestamp NULL DEFAULT NULL, <- not set automatically
  `response_updated_at` timestamp NULL DEFAULT NULL, <- not set automatically
  `mdm_migration_deadline` timestamp(6) NULL DEFAULT NULL, <- not set automatically
  `mdm_migration_completed` timestamp(6) NULL DEFAULT NULL, <- not set automatically
```

- [x] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).

---------

Co-authored-by: Jordan Montgomery <elijah.jordan.montgomery@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants