Skip to content

Deleting/adding Android certs to host on team transfer#37616

Merged
getvictor merged 20 commits intomainfrom
victor/37580-android-certs-teams
Jan 6, 2026
Merged

Deleting/adding Android certs to host on team transfer#37616
getvictor merged 20 commits intomainfrom
victor/37580-android-certs-teams

Conversation

@getvictor
Copy link
Copy Markdown
Member

@getvictor getvictor commented Dec 22, 2025

Related issue: Resolves #37580

Resolves unreleased 4.79 bug and needs to be cherry picked. Also includes fixes from manually going through the test plan at: #30876

Checklist for submitter

Testing

  • Added/updated automated tests
  • QA'd all new/changed functionality manually

For unreleased bug fixes in a release candidate, one of:

  • Confirmed that the fix is not expected to adversely impact load test results

Summary by CodeRabbit

Release Notes

  • New Features

    • Per-template versioning and explicit operation/status fields for host certificate templates; delivery payloads now include per-template details.
  • Bug Fixes

    • Removal preparation broadened to also clear failed entries and handle per-host removals; delivery/transition ordering adjusted to avoid race conditions.
  • Tests

    • Extensive tests added for team-transfer flows, per-host removal/preparation, and end-to-end Android certificate template scenarios.

✏️ Tip: You can customize this high-level summary in your review settings.

@codecov
Copy link
Copy Markdown

codecov bot commented Dec 22, 2025

Codecov Report

❌ Patch coverage is 91.80328% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.84%. Comparing base (a3cc31d) to head (c7997c0).
⚠️ Report is 61 commits behind head on main.

Files with missing lines Patch % Lines
...rver/datastore/mysql/host_certificate_templates.go 92.10% 4 Missing and 2 partials ⚠️
server/worker/software_worker.go 33.33% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #37616      +/-   ##
==========================================
+ Coverage   65.82%   65.84%   +0.02%     
==========================================
  Files        2367     2366       -1     
  Lines      187665   187663       -2     
  Branches     8012     7900     -112     
==========================================
+ Hits       123523   123564      +41     
+ Misses      52845    52796      -49     
- Partials    11297    11303       +6     
Flag Coverage Δ
backend 67.68% <91.80%> (+0.02%) ⬆️

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.

@getvictor
Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Dec 22, 2025

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Dec 22, 2025

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ⚠️ Warning The PR description is incomplete and missing several required template sections including changes file, validation, testing details, and database migration checklist items. Add a changes file entry, provide details on input validation and SQL injection prevention, clarify which automated tests were added/updated, confirm host isolation in tests, and complete the database migration checklist with timestamp and collation checks.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main change: handling Android certificate deletion/addition when a host transfers between teams, which is the core objective of the PR.
Linked Issues check ✅ Passed The PR implementation fully addresses the linked issue #37580 by adding logic to delete Android certificates via SetHostCertificateTemplatesToPendingRemoveForHost when a host transfers teams, and adding new certificates via CreatePendingCertificateTemplatesForNewHost, with comprehensive tests validating the behavior.
Out of Scope Changes check ✅ Passed All changes are scoped to supporting Android certificate management during team transfers. Minor scope expansion includes version tracking for certificates and enhanced test coverage, which are necessary to implement the core requirement and validate correctness.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch victor/37580-android-certs-teams

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
server/mdm/android/android.go (1)

62-66: Consider using typed enums (or at least constants) for Status and Operation.

AgentCertificateTemplate.Status and .Operation are plain strings, while the rest of the codebase already has strongly-typed MDMDeliveryStatus/MDMOperationType. Wiring these fields from those types (or exposing them as those types with string JSON tags) would reduce the chance of drifting/invalid values being sent to the Android agent.

server/worker/software_worker.go (1)

470-521: Cert template update order is good; consider transactional grouping for failure safety.

Running SetHostCertificateTemplatesToPendingRemoveForHost and then CreatePendingCertificateTemplatesForNewHost per host before building the managed config is logically sound for team transfers and keeps the agent config aligned with cert templates. The only robustness gap is that these two datastore calls are independent: if the second fails after the first succeeds, the host ends up with templates marked for removal but no new pending ones for the destination team.

If/when feasible, consider wrapping these two operations in a single DB transaction (or adding a compensating path) in the datastore implementation so the host’s certificate state transitions atomically.

server/datastore/mysql/host_certificate_templates_test.go (1)

1032-1082: Template-scoped removal test now encodes failed-row deletion semantics; update docs accordingly.

The updated "deletes pending and failed rows and updates others to pending remove" test clearly asserts that:

  • Rows in pending or failed status are deleted for the given template ID.
  • Remaining rows (e.g., delivered/verified) are flipped to status=pending and operation_type=remove.

That behavior is reasonable, but it’s now stricter than the brief Datastore comment that only mentions deleting pending rows and updating “all other rows.” When you touch the implementation next, consider aligning the SetHostCertificateTemplatesToPendingRemove doc comment in server/fleet/datastore.go with this test so callers know failed rows are removed rather than transitioned.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59f318c and 9269eeb.

📒 Files selected for processing (11)
  • server/datastore/mysql/host_certificate_templates.go
  • server/datastore/mysql/host_certificate_templates_test.go
  • server/fleet/certificate_templates.go
  • server/fleet/datastore.go
  • server/mdm/android/android.go
  • server/mdm/android/service/profiles_test.go
  • server/mdm/android/service/service.go
  • server/mock/datastore_mock.go
  • server/service/integration_android_certificate_templates_test.go
  • server/worker/software_worker.go
  • server/worker/software_worker_test.go
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

⚙️ CodeRabbit configuration file

When reviewing SQL queries that are added or modified, ensure that appropriate filtering criteria are applied—especially when a query is intended to return data for a specific entity (e.g., a single host). Check for missing WHERE clauses or incorrect filtering that could lead to incorrect or non-deterministic results (e.g., returning the first row instead of the correct one). Flag any queries that may return unintended results due to lack of precise scoping.

Files:

  • server/mdm/android/service/profiles_test.go
  • server/worker/software_worker_test.go
  • server/datastore/mysql/host_certificate_templates_test.go
  • server/service/integration_android_certificate_templates_test.go
  • server/mock/datastore_mock.go
  • server/datastore/mysql/host_certificate_templates.go
  • server/fleet/certificate_templates.go
  • server/fleet/datastore.go
  • server/mdm/android/android.go
  • server/mdm/android/service/service.go
  • server/worker/software_worker.go
🧠 Learnings (5)
📚 Learning: 2025-10-03T18:16:11.482Z
Learnt from: MagnusHJensen
Repo: fleetdm/fleet PR: 33805
File: server/service/integration_mdm_test.go:1248-1251
Timestamp: 2025-10-03T18:16:11.482Z
Learning: In server/service/integration_mdm_test.go, the helper createAppleMobileHostThenEnrollMDM(platform string) is exclusively for iOS/iPadOS hosts (mobile). Do not flag macOS model/behavior issues based on changes within this helper; macOS provisioning uses different helpers such as createHostThenEnrollMDM.

Applied to files:

  • server/mdm/android/service/profiles_test.go
  • server/worker/software_worker_test.go
  • server/service/integration_android_certificate_templates_test.go
  • server/mdm/android/service/service.go
  • server/worker/software_worker.go
📚 Learning: 2025-12-11T23:02:52.191Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 36978
File: server/mdm/android/service/profiles.go:441-449
Timestamp: 2025-12-11T23:02:52.191Z
Learning: In server/mdm/android/service, Go allows assigning an interface value of type fleet.Datastore to a field ds of type fleet.Datastore because the underlying dynamic type implements both the embedded AndroidDatastore (via fleet.Datastore embedding AndroidDatastore). When reviewing code in profiles.go and related files, verify assignments between interfaces respect embedding and ensure the concrete type actually implements all required interfaces. This pattern is valid wherever fleet.Datastore embeds AndroidDatastore and the receiver expects fleetDS or similar fields; prefer keeping interface boundaries clear and rely on the dynamic type to satisfy both interfaces.

Applied to files:

  • server/mdm/android/service/profiles_test.go
  • server/mdm/android/service/service.go
📚 Learning: 2025-07-08T16:11:49.555Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 30589
File: ee/server/service/hostidentity/depot/depot.go:115-115
Timestamp: 2025-07-08T16:11:49.555Z
Learning: In ee/server/service/hostidentity/depot/depot.go, the error from result.RowsAffected() is intentionally ignored because the information is only used for logging purposes, not for critical program logic.

Applied to files:

  • server/datastore/mysql/host_certificate_templates_test.go
📚 Learning: 2025-07-08T16:06:54.576Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 30589
File: ee/server/service/hostidentity/depot/depot.go:104-119
Timestamp: 2025-07-08T16:06:54.576Z
Learning: In ee/server/service/hostidentity/depot/depot.go, the security concern where shared challenges allow certificate revocation (lines 104-119) is a known issue that will be addressed in a later feature, not an immediate concern to fix.

Applied to files:

  • server/datastore/mysql/host_certificate_templates_test.go
📚 Learning: 2025-11-26T18:58:18.865Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 36139
File: android/app/src/main/java/com/fleetdm/agent/scep/ScepClientImpl.kt:75-76
Timestamp: 2025-11-26T18:58:18.865Z
Learning: In Fleet's Android MDM agent SCEP implementation (android/app/src/main/java/com/fleetdm/agent/scep/ScepClientImpl.kt), OptimisticCertificateVerifier is intentionally used because: (1) SCEP URL is provided by authenticated MDM server, (2) challenge password authenticates enrollment, (3) enterprise SCEP servers use internal CAs not in system trust stores, (4) enrolled certificate is validated when used.

Applied to files:

  • server/service/integration_android_certificate_templates_test.go
🧬 Code graph analysis (8)
server/mdm/android/service/profiles_test.go (3)
server/mdm/android/android.go (2)
  • AgentManagedConfiguration (55-60)
  • AgentCertificateTemplate (62-66)
server/fleet/certificate_templates.go (5)
  • CertificateTemplateDelivering (51-51)
  • CertificateTemplateStatus (47-47)
  • CertificateTemplateVerified (54-54)
  • CertificateTemplateDelivered (52-52)
  • CertificateTemplateFailed (53-53)
server/fleet/mdm.go (2)
  • MDMOperationTypeInstall (531-531)
  • MDMOperationType (528-528)
server/worker/software_worker_test.go (1)
server/mock/datastore_mock.go (2)
  • SetHostCertificateTemplatesToPendingRemoveForHostFunc (1702-1702)
  • CreatePendingCertificateTemplatesForNewHostFunc (1540-1540)
server/datastore/mysql/host_certificate_templates_test.go (2)
server/fleet/datastore.go (1)
  • Datastore (50-2622)
server/fleet/certificate_templates.go (5)
  • CertificateTemplate (10-15)
  • CertificateTemplatePending (50-50)
  • CertificateTemplateDelivered (52-52)
  • CertificateTemplateFailed (53-53)
  • CertificateTemplateDelivering (51-51)
server/service/integration_android_certificate_templates_test.go (6)
server/ptr/ptr.go (2)
  • T (76-78)
  • String (10-12)
server/fleet/certificate_authorities.go (2)
  • CertificateAuthority (61-96)
  • CATypeCustomSCEPProxy (49-49)
server/fleet/hosts.go (2)
  • Host (276-405)
  • AndroidHost (455-458)
server/datastore/mysql/testing_utils.go (1)
  • ExecAdhocSQL (420-424)
server/fleet/certificate_templates.go (6)
  • CertificateTemplateStatus (47-47)
  • CertificateTemplatePending (50-50)
  • CertificateTemplateDelivering (51-51)
  • CertificateTemplateDelivered (52-52)
  • CertificateTemplateVerified (54-54)
  • CertificateTemplateFailed (53-53)
server/fleet/mdm.go (3)
  • MDMOperationType (528-528)
  • MDMOperationTypeInstall (531-531)
  • MDMOperationTypeRemove (532-532)
server/datastore/mysql/host_certificate_templates.go (3)
server/fleet/certificate_templates.go (4)
  • CertificateTemplatePending (50-50)
  • HostCertificateTemplateForDelivery (73-77)
  • CertificateTemplateDelivering (51-51)
  • CertificateTemplateFailed (53-53)
server/fleet/mdm.go (2)
  • MDMOperationTypeInstall (531-531)
  • MDMOperationTypeRemove (532-532)
server/contexts/ctxerr/ctxerr.go (1)
  • Wrap (199-202)
server/fleet/certificate_templates.go (1)
server/fleet/mdm.go (1)
  • MDMOperationType (528-528)
server/mdm/android/service/service.go (2)
server/mdm/android/android.go (2)
  • AgentCertificateTemplate (62-66)
  • AgentManagedConfiguration (55-60)
server/fleet/certificate_templates.go (1)
  • HostCertificateTemplateForDelivery (73-77)
server/worker/software_worker.go (2)
server/ptr/ptr.go (1)
  • ValOrZero (82-90)
server/fleet/datastore.go (1)
  • Datastore (50-2622)
🔇 Additional comments (16)
server/fleet/datastore.go (1)

2604-2606: Host-scoped removal API shape looks appropriate.

SetHostCertificateTemplatesToPendingRemoveForHost cleanly mirrors the existing template-scoped variant and gives the worker/service layers a focused primitive for handling per-host team transfers. The interface surface and signature look good.

server/datastore/mysql/host_certificate_templates_test.go (3)

19-36: Adding the host-scoped test case wiring is correct.

Registering testSetHostCertificateTemplatesToPendingRemoveForHost in TestHostCertificateTemplates matches the new datastore API and keeps all related tests under the same umbrella. No issues here.


827-845: Extra assertions on Templates strengthen delivery state coverage.

The new require.Len(t, certTemplates.Templates, 2) checks (for both calls to GetAndTransitionCertificateTemplatesToDelivering) ensure the richer per-template delivery payload stays in sync with DeliveringTemplateIDs. This is a nice sanity check around the new structure.


1142-1236: Host-scoped removal test covers the right edge cases and looks correct.

The new testSetHostCertificateTemplatesToPendingRemoveForHost verifies all the important behaviors:

  • For the target host:
    • pending and failed install rows are deleted.
    • delivered install rows are converted to pending/remove.
    • delivering/remove rows are left untouched (removal already in progress).
  • For a different host:
    • Rows are unaffected.

This matches the intended semantics for team transfers and ensures you don’t interfere with in-flight removals or other hosts. The setup and assertions are tight and self-contained; no issues found.

server/worker/software_worker_test.go (1)

68-73: Mock wiring for new datastore methods is sufficient and non-intrusive.

Providing no-op implementations for SetHostCertificateTemplatesToPendingRemoveForHostFunc and CreatePendingCertificateTemplatesForNewHostFunc keeps the test focused on Fleet Agent preservation while satisfying the new worker dependencies. This is an appropriate level of mocking.

server/mdm/android/service/service.go (3)

1007-1017: LGTM!

The nil checks for Status and OperationType before dereferencing are correctly implemented, preventing potential panics when these optional fields are not set.


1243-1244: LGTM!

The refactored buildHostConfig correctly accepts pre-fetched templates and maps them to AgentCertificateTemplate with the appropriate Status and Operation fields. The direct type conversions are valid since HostCertificateTemplateForDelivery uses value types (not pointers) for these fields.

Also applies to: 1259-1264


1291-1291: LGTM!

The call sites are correctly updated to pass the full Templates slice to buildHostConfig, aligning with the new signature that enables per-template status and operation tracking.

Also applies to: 1305-1305

server/mdm/android/service/profiles_test.go (2)

919-929: LGTM!

The test correctly validates that certificate templates sent to the Android Management API have the expected Status ("delivering") and Operation ("install") values. This aligns with the state machine where templates transition from "pending" to "delivering" before the API call.


1220-1237: LGTM!

The test comprehensively validates per-template status and operation tracking across all lifecycle states (verified, delivered, delivering, failed, pending). The inline helper assertCertTemplate improves readability while maintaining clear assertions. The comment explaining the pending → delivering transition is helpful documentation.

server/mock/datastore_mock.go (1)

1702-1703: Mock wiring for new host-scoped certificate template method is consistent and correct

The added function type, DataStore fields, and method implementation follow the existing SetHostCertificateTemplatesToPendingRemove* pattern (lock, mark *Invoked, delegate to *Func). This should integrate cleanly with the updated fleet.Datastore interface and existing tests that depend on this mock.

Also applies to: 4227-4229, 10111-10116

server/fleet/certificate_templates.go (1)

71-88: LGTM! Enhanced delivery tracking with per-template status.

The new HostCertificateTemplateForDelivery structure and replacement of OtherTemplateIDs with the Templates slice provides better granularity for tracking individual template status and operations during delivery workflows.

server/datastore/mysql/host_certificate_templates.go (2)

336-364: LGTM! Correct population of Templates with per-item status.

The logic correctly populates the Templates slice for all template statuses (pending, delivering, delivered, verified, failed) with their appropriate post-transition status and operation type.


519-550: LGTM! Host-scoped removal method with proper SQL filtering.

The new SetHostCertificateTemplatesToPendingRemoveForHost method correctly implements host-scoped template removal preparation. Both SQL queries are properly filtered with host_uuid = ? to ensure they only affect the specified host's templates.

server/service/integration_android_certificate_templates_test.go (2)

119-172: LGTM! Well-structured test helpers.

The createTestCertificateAuthority and createEnrolledAndroidHost helper methods are clean, reusable, and properly set up test fixtures for Android host certificate template testing.


712-1034: LGTM! Comprehensive team transfer test coverage.

The TestCertificateTemplateTeamTransfer test provides excellent coverage of certificate template behavior during team transfers:

  1. All status/operation combinations (9 cases): Verifies that pending/failed install records are deleted while others are marked for removal, and remove operations remain unchanged
  2. Host without certs → team with certs: Confirms new pending templates are created
  3. Host with certs → team with different certs: Validates old templates are marked for removal and new ones are added

The test structure is clear, uses database helpers appropriately, and validates both expected deletions and state transitions.

@getvictor getvictor marked this pull request as ready for review December 23, 2025 15:37
@getvictor getvictor requested a review from a team as a code owner December 23, 2025 15:37
@getvictor getvictor marked this pull request as draft December 23, 2025 19:20
@getvictor getvictor marked this pull request as ready for review December 23, 2025 19:54
@getvictor
Copy link
Copy Markdown
Member Author

@coderabbitai full review

getvictor added a commit that referenced this pull request Dec 30, 2025
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #37580

This migration is needed for the larger unreleased bug PR: #37616

Doing the migration separately to merge quickly and minimize merge
conflicts.
return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
// Delete rows with status in (pending, failed) and operation_type=install
// These certificates were never successfully installed on the device
deleteStmt := fmt.Sprintf(`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what's the advantage to the delete as opposed to updating the existing row's UUID/operation_type?

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.

If status is pending, that means it has not been sent to the device yet. Failed means (I'm assuming), that we are in the terminal state and the device does NOT have the cert, so there is nothing to delete on the device. Deleting the row here is a simplification so we don't have to deal with it anymore.

Are you suggesting to do something better?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No that makes sense to me

@getvictor getvictor merged commit af1e150 into main Jan 6, 2026
48 checks passed
@getvictor getvictor deleted the victor/37580-android-certs-teams branch January 6, 2026 16:20
getvictor added a commit that referenced this pull request Jan 7, 2026
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #37580

This migration is needed for the larger unreleased bug PR: #37616

Doing the migration separately to merge quickly and minimize merge
conflicts.

(cherry picked from commit fa9c868)
getvictor added a commit that referenced this pull request Jan 7, 2026
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #37580 

Resolves unreleased 4.79 bug and needs to be cherry picked. Also
includes fixes from manually going through the test plan at:
[#30876](#30876)

# Checklist for submitter

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

For unreleased bug fixes in a release candidate, one of:

- [x] Confirmed that the fix is not expected to adversely impact load
test results

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

* **New Features**
* Per-template versioning and explicit operation/status fields for host
certificate templates; delivery payloads now include per-template
details.

* **Bug Fixes**
* Removal preparation broadened to also clear failed entries and handle
per-host removals; delivery/transition ordering adjusted to avoid race
conditions.

* **Tests**
* Extensive tests added for team-transfer flows, per-host
removal/preparation, and end-to-end Android certificate template
scenarios.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
getvictor added a commit that referenced this pull request Jan 7, 2026
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #30876

Cherry picks:

| Order | Commit | PR | Description |

|-------|------------|--------|--------------------------------------------------------|
| 1 | 155cd56 | #37763 | Add uuid column to
host_certificate_templates |
| 2 | c9221b3 | #37502 | Set android host cert statuses on gitops
delete |
| 3 | b41effa | #37616 | Deleting/adding Android certs to host on
team transfer |
| 4 | a91bb56 | #37770 | Point to com.fleetdm.agent Android agent by
default |

---------

Co-authored-by: Tim Lee <timlee@fleetdm.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.

Android certs: add/remove certs when host moves teams

2 participants