Skip to content

Conversation

@mostlikelee
Copy link
Contributor

@mostlikelee mostlikelee commented Jan 7, 2026

Related issue: Resolves #37566

Large PR, so I think the easiest way to review is by commit history.

  1. Migration to add certificate validity and serial(for possible future use)
  2. Update /status endpoint to accept/store validity/serial
  3. renew certificates in cron
  4. integration tests and small refactor

Summary by CodeRabbit

  • New Features

    • Added automatic renewal capability for Android certificate templates that are nearing expiration.
    • Implemented certificate validity tracking, including expiration dates and serial numbers.
  • Chores

    • Updated database schema to support certificate validity metadata and added performance indexes.

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

@codecov
Copy link

codecov bot commented Jan 7, 2026

Codecov Report

❌ Patch coverage is 84.93976% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.84%. Comparing base (5059107) to head (ea877d9).
⚠️ Report is 24 commits behind head on main.

Files with missing lines Patch % Lines
...00_AddValidityColumnsToHostCertificateTemplates.go 61.53% 6 Missing and 4 partials ⚠️
...rver/datastore/mysql/host_certificate_templates.go 93.54% 3 Missing and 3 partials ⚠️
server/mdm/android/service/certificate_renewal.go 63.63% 2 Missing and 2 partials ⚠️
cmd/fleet/cron.go 0.00% 3 Missing ⚠️
server/service/certificates.go 93.93% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #37959      +/-   ##
==========================================
+ Coverage   65.81%   65.84%   +0.02%     
==========================================
  Files        2386     2389       +3     
  Lines      190086   190315     +229     
  Branches     8323     8323              
==========================================
+ Hits       125108   125305     +197     
- Misses      53597    53620      +23     
- Partials    11381    11390       +9     
Flag Coverage Δ
backend 67.68% <84.93%> (+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.

@mostlikelee
Copy link
Contributor Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 7, 2026

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 7, 2026

Walkthrough

This pull request adds Android certificate template renewal functionality to Fleet's server. A new cron job initiates renewal for Android certificate templates nearing expiration. Certificate validity metadata (not_valid_before, not_valid_after, serial) is now tracked and updated through the status flow. Database schema expands with validity columns and renewal queries, while service and datastore layers support passing validity data during certificate updates.

Changes

Cohort / File(s) Summary
Cron Job Configuration
cmd/fleet/cron.go
Adds new renew_android_certificate_templates job to the cleanup/aggregation schedule to trigger Android certificate renewal.
Database Schema & Migrations
server/datastore/mysql/migrations/tables/20260106000000_AddValidityColumnsToHostCertificateTemplates.go, server/datastore/mysql/migrations/tables/20260106000000_AddValidityColumnsToHostCertificateTemplates_test.go, server/datastore/mysql/schema.sql
Migration adds three nullable datetime/varchar columns (not_valid_before, not_valid_after, serial) to host_certificate_templates, creates an index on not_valid_after, and updates migration tracking.
Model & Type Definitions
server/fleet/host_certificate_template.go
Adds validity fields to HostCertificateTemplate struct and introduces new types: HostCertificateValidity (for passing validity data) and HostCertificateTemplateForRenewal (for renewal queries).
Datastore Interface Updates
server/fleet/datastore.go, server/fleet/service.go
Updates interface signatures: UpsertCertificateStatus now accepts optional HostCertificateValidity parameter; adds two new methods GetAndroidCertificateTemplatesForRenewal and SetAndroidCertificateTemplatesForRenewal for renewal workflows.
Datastore MySQL Implementation
server/datastore/mysql/host_certificate_templates.go
Implements renewal query/update logic: UpsertCertificateStatus conditionally updates validity fields when provided; GetAndroidCertificateTemplatesForRenewal queries templates nearing expiration; SetAndroidCertificateTemplatesForRenewal marks templates for renewal, clears validity fields, and regenerates UUIDs.
Datastore Tests
server/datastore/mysql/host_certificate_templates_test.go
Extends UpsertCertificateStatus test calls with new parameter; adds two new test cases for GetAndroidCertificateTemplatesForRenewal and SetAndroidCertificateTemplatesForRenewal covering renewal scenarios and field mutations.
Service Layer Certificate Updates
server/service/certificates.go
Updates UpdateCertificateStatus signature to accept HostCertificateValidity; adds NotValidBefore, NotValidAfter, Serial fields to request struct; constructs and propagates validity data through update flow.
Android Certificate Renewal Service
server/mdm/android/service/certificate_renewal.go
Implements RenewCertificateTemplates function that fetches templates due for renewal and initiates renewal via datastore methods.
Mock Implementations
server/mock/datastore_mock.go, server/mock/service/service_mock.go
Adds function types and fields for new renewal methods; updates UpsertCertificateStatus and UpdateCertificateStatus mock signatures to include validity parameter.
Integration Tests
server/service/integration_android_certificate_templates_test.go, server/service/integration_mdm_test.go
Adds TestCertificateTemplateRenewal integration test with multiple validity scenarios; adds cleanup schedule job for enterprise verification in test suite setup.

Sequence Diagram(s)

sequenceDiagram
    participant Cron as Cron Scheduler
    participant AndroidSvc as Android Service
    participant DS as Datastore
    participant DB as Database
    participant Device as Device/App

    rect rgb(240, 248, 255)
    Note over Cron,Device: Certificate Renewal Initiation
    Cron->>AndroidSvc: RenewCertificateTemplates()
    AndroidSvc->>DS: GetAndroidCertificateTemplatesForRenewal(limit: 1000)
    DS->>DB: SELECT templates WHERE not_valid_after < NOW() + 30 days
    DB-->>DS: []HostCertificateTemplateForRenewal
    DS-->>AndroidSvc: templates list
    end

    alt Templates found
        rect rgb(240, 255, 240)
        Note over AndroidSvc,DB: Mark for Renewal
        AndroidSvc->>DS: SetAndroidCertificateTemplatesForRenewal(templates)
        DS->>DB: UPDATE status='pending',<br/>CLEAR validity fields,<br/>GENERATE new UUID
        DB-->>DS: success
        DS-->>AndroidSvc: success
        AndroidSvc->>AndroidSvc: log count processed
        end
    else No templates
        AndroidSvc->>AndroidSvc: return
    end

    rect rgb(255, 250, 240)
    Note over AndroidSvc,Device: Device Certificate Update
    Device->>Device: Poll for config/certificate
    Device->>Device: Detect new UUID
    Device->>Device: Request new certificate
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • getvictor
  • ksykulev
  • dantecatalfamo
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The PR description includes the issue number and outlines major changes (migration, endpoint updates, cron job, tests) but lacks detail on implementation specifics, testing scope, and database migration considerations required by the template.
Linked Issues check ✅ Passed The PR implements all coding requirements from issue #37566: receives certificate expiry info in status updates, implements cron job for renewal, marks certificates as pending with UUID bumps, and excludes manual resend functionality.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #37566 objectives: database schema additions for validity tracking, endpoint updates to handle certificate data, cron job implementation, and comprehensive integration tests.
Title check ✅ Passed The title 'Renew android certificates backend' clearly and directly summarizes the main objective of the pull request, which is to add backend support for Android certificate renewal.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 37566-renew-backend

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
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 (4)
server/datastore/mysql/host_certificate_templates_test.go (1)

639-639: Consider adding a test case for the new validity parameter.

The nil value correctly tests backwards compatibility, but it would be valuable to add a test case that validates the new validity parameter with actual data to ensure it's correctly stored and retrieved.

server/datastore/mysql/migrations/tables/20260106000000_AddValidityColumnsToHostCertificateTemplates_test.go (1)

10-163: Migration test thoroughly exercises new validity columns and renewal query shape

This test does a nice job of:

  • Verifying NULL defaults on existing rows post‑migration.
  • Inserting and updating rows with not_valid_before, not_valid_after, and serial, with tolerant time assertions.
  • Exercising the “expiring within 30 days” query using not_valid_after, status IN ('delivered','verified'), and operation_type = 'install', which keeps the result set well‑scoped as expected for renewal.

No issues detected; this is solid coverage for the migration behavior.

As per coding guidelines, your SQL filtering here is already appropriately constrained; if the production renewal query evolves, mirroring it here will keep this test aligned.

server/datastore/mysql/migrations/tables/20260106000000_AddValidityColumnsToHostCertificateTemplates.go (1)

13-47: Migration structure is correct.

The columns and index are appropriate for the renewal feature. Based on learnings, the empty Down migration is intentional in the Fleet codebase.

Consider combining ALTER statements (optional).

For large tables, multiple ALTER TABLE statements can be slower than a single combined statement. This is a minor optimization opportunity.

♻️ Optional: Combine ALTER statements
 func Up_20260106000000(tx *sql.Tx) error {
-	// Add not_valid_before column for certificate validity tracking
-	if _, err := tx.Exec(`
-		ALTER TABLE host_certificate_templates
-		ADD COLUMN not_valid_before DATETIME(6) NULL
-	`); err != nil {
-		return errors.Wrap(err, "adding not_valid_before column to host_certificate_templates")
-	}
-
-	// Add not_valid_after column for certificate expiration tracking (used for renewal)
-	if _, err := tx.Exec(`
-		ALTER TABLE host_certificate_templates
-		ADD COLUMN not_valid_after DATETIME(6) NULL
-	`); err != nil {
-		return errors.Wrap(err, "adding not_valid_after column to host_certificate_templates")
-	}
-
-	// Add serial column to store certificate serial number
-	if _, err := tx.Exec(`
-		ALTER TABLE host_certificate_templates
-		ADD COLUMN serial VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL
-	`); err != nil {
-		return errors.Wrap(err, "adding serial column to host_certificate_templates")
-	}
-
-	// Add index on not_valid_after for efficient renewal queries
-	if _, err := tx.Exec(`
-		ALTER TABLE host_certificate_templates
-		ADD INDEX idx_host_certificate_templates_not_valid_after (not_valid_after)
-	`); err != nil {
-		return errors.Wrap(err, "adding index on not_valid_after to host_certificate_templates")
-	}
+	if _, err := tx.Exec(`
+		ALTER TABLE host_certificate_templates
+		ADD COLUMN not_valid_before DATETIME(6) NULL,
+		ADD COLUMN not_valid_after DATETIME(6) NULL,
+		ADD COLUMN serial VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,
+		ADD INDEX idx_host_certificate_templates_not_valid_after (not_valid_after)
+	`); err != nil {
+		return errors.Wrap(err, "adding validity columns and index to host_certificate_templates")
+	}
 
 	return nil
 }
server/datastore/mysql/host_certificate_templates.go (1)

645-683: LGTM - Renewal update implementation is correct.

The batch update correctly:

  • Generates a new UUID per row (MySQL evaluates UUID() for each affected row)
  • Clears validity fields to prepare for fresh certificate data
  • Sets status to 'pending' to trigger re-delivery

The tuple-based WHERE clause ensures precise targeting of specific host/template combinations.

Optional consideration: Adding a status filter (AND status IN ('delivered', 'verified')) in the WHERE clause would add defense-in-depth against race conditions, though the risk is minimal given the cron execution pattern.

📜 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 5059107 and f42769d.

📒 Files selected for processing (15)
  • cmd/fleet/cron.go
  • server/datastore/mysql/host_certificate_templates.go
  • server/datastore/mysql/host_certificate_templates_test.go
  • server/datastore/mysql/migrations/tables/20260106000000_AddValidityColumnsToHostCertificateTemplates.go
  • server/datastore/mysql/migrations/tables/20260106000000_AddValidityColumnsToHostCertificateTemplates_test.go
  • server/datastore/mysql/schema.sql
  • server/fleet/datastore.go
  • server/fleet/host_certificate_template.go
  • server/fleet/service.go
  • server/mdm/android/service/certificate_renewal.go
  • server/mock/datastore_mock.go
  • server/mock/service/service_mock.go
  • server/service/certificates.go
  • server/service/integration_android_certificate_templates_test.go
  • server/service/integration_mdm_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/service/integration_mdm_test.go
  • server/mock/service/service_mock.go
  • server/mdm/android/service/certificate_renewal.go
  • server/service/certificates.go
  • server/datastore/mysql/migrations/tables/20260106000000_AddValidityColumnsToHostCertificateTemplates_test.go
  • server/fleet/host_certificate_template.go
  • cmd/fleet/cron.go
  • server/fleet/service.go
  • server/datastore/mysql/host_certificate_templates_test.go
  • server/fleet/datastore.go
  • server/service/integration_android_certificate_templates_test.go
  • server/datastore/mysql/migrations/tables/20260106000000_AddValidityColumnsToHostCertificateTemplates.go
  • server/datastore/mysql/host_certificate_templates.go
  • server/mock/datastore_mock.go
🧠 Learnings (12)
📓 Common learnings
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.
📚 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/service/integration_mdm_test.go
  • server/service/integration_android_certificate_templates_test.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/certificate_renewal.go
📚 Learning: 2026-01-02T22:48:09.865Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 37640
File: android/app/src/main/java/com/fleetdm/agent/ApiClient.kt:518-522
Timestamp: 2026-01-02T22:48:09.865Z
Learning: In the Fleet Android app's SCEP proxy URL format (android/app/src/main/java/com/fleetdm/agent/ApiClient.kt), the `g` prefix before the certificate ID (e.g., `g$id`) is intentional and stands for "Google Android" to differentiate from other platforms like Apple and Windows.

Applied to files:

  • server/fleet/host_certificate_template.go
📚 Learning: 2025-08-08T08:32:31.529Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 31695
File: server/datastore/mysql/apple_mdm_test.go:132-132
Timestamp: 2025-08-08T08:32:31.529Z
Learning: Datastore.NewMDMWindowsConfigProfile signature is: NewMDMWindowsConfigProfile(ctx context.Context, cp fleet.MDMWindowsConfigProfile, usesFleetVars []string) (*fleet.MDMWindowsConfigProfile, error). Passing nil for usesFleetVars in tests denotes “no Fleet variables referenced” and is used consistently across the repo.

Applied to files:

  • server/fleet/host_certificate_template.go
📚 Learning: 2025-12-11T23:02:59.284Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 36978
File: server/mdm/android/service/profiles.go:441-449
Timestamp: 2025-12-11T23:02:59.284Z
Learning: Fleet’s server/mdm/android/service.Service has fields ds (type fleet.AndroidDatastore) and fleetDS (type fleet.Datastore). The fleet.Datastore interface embeds AndroidDatastore (server/fleet/datastore.go), so assigning r.DS (a fleet.Datastore) to Service.ds is valid in Go due to interface-to-interface assignment using the dynamic type.

Applied to files:

  • server/fleet/datastore.go
  • server/mock/datastore_mock.go
📚 Learning: 2025-07-07T22:21:15.748Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 30589
File: server/datastore/mysql/schema.sql:501-517
Timestamp: 2025-07-07T22:21:15.748Z
Learning: In the host_identity_scep_certificates table schema, the VARBINARY(100) size for public_key_raw, the nullable host_id without a foreign key constraint, and the use of plain DATETIME instead of DATETIME(6) are intentional design decisions, not issues to be addressed.

Applied to files:

  • server/datastore/mysql/schema.sql
📚 Learning: 2025-08-01T15:08:16.858Z
Learnt from: sgress454
Repo: fleetdm/fleet PR: 31508
File: server/datastore/mysql/schema.sql:102-116
Timestamp: 2025-08-01T15:08:16.858Z
Learning: The schema.sql file in server/datastore/mysql/ is auto-generated from migrations for use with tests, so it cannot be manually edited. Any changes must be made through migrations.

Applied to files:

  • server/datastore/mysql/schema.sql
📚 Learning: 2025-08-08T07:40:05.301Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 31726
File: server/datastore/mysql/labels_test.go:2031-2031
Timestamp: 2025-08-08T07:40:05.301Z
Learning: Fleet repo targets Go 1.24.5 (root go.mod), which supports testing.T.Context(). Do not flag usage of t.Context() or suggest replacing it with context.Background() in tests (e.g., server/datastore/mysql/labels_test.go Line 2031 and similar).

Applied to files:

  • server/service/integration_android_certificate_templates_test.go
📚 Learning: 2025-08-08T07:40:05.301Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 31726
File: server/datastore/mysql/labels_test.go:2031-2031
Timestamp: 2025-08-08T07:40:05.301Z
Learning: In fleetdm/fleet repository tests (server/datastore/mysql/labels_test.go and similar), using testing.T.Context() is valid because the project targets a recent Go version where testing.T.Context() exists. Do not suggest replacing t.Context() with context.Background() in this codebase.

Applied to files:

  • server/service/integration_android_certificate_templates_test.go
📚 Learning: 2025-08-13T18:20:42.136Z
Learnt from: titanous
Repo: fleetdm/fleet PR: 31075
File: tools/redis-tests/elasticache/iam_auth.go:4-10
Timestamp: 2025-08-13T18:20:42.136Z
Learning: For test harnesses and CLI tools in the Fleet codebase, resource cleanup on error paths (like closing connections before log.Fatalf) may not be necessary since the OS handles cleanup when the process exits. These tools prioritize simplicity over defensive programming patterns used in production code.

Applied to files:

  • server/service/integration_android_certificate_templates_test.go
📚 Learning: 2025-07-08T16:13:39.114Z
Learnt from: getvictor
Repo: fleetdm/fleet PR: 30589
File: server/datastore/mysql/migrations/tables/20250707095725_HostIdentitySCEPCertificates.go:53-55
Timestamp: 2025-07-08T16:13:39.114Z
Learning: In the Fleet codebase, Down migration functions are intentionally left empty/no-op. The team does not implement rollback functionality for database migrations, so empty Down_* functions in migration files are correct and should not be flagged as issues.

Applied to files:

  • server/datastore/mysql/migrations/tables/20260106000000_AddValidityColumnsToHostCertificateTemplates.go
🧬 Code graph analysis (11)
server/service/integration_mdm_test.go (2)
server/service/schedule/schedule.go (1)
  • WithJob (124-131)
server/mdm/android/service/certificate_renewal.go (1)
  • RenewCertificateTemplates (14-28)
server/mock/service/service_mock.go (2)
server/fleet/mdm.go (1)
  • MDMDeliveryStatus (466-466)
server/fleet/host_certificate_template.go (1)
  • HostCertificateValidity (59-63)
server/mdm/android/service/certificate_renewal.go (1)
server/contexts/ctxerr/ctxerr.go (1)
  • Wrap (186-189)
server/service/certificates.go (3)
server/ptr/ptr.go (1)
  • Time (51-53)
server/fleet/host_certificate_template.go (1)
  • HostCertificateValidity (59-63)
server/fleet/mdm.go (1)
  • MDMDeliveryStatus (466-466)
server/datastore/mysql/migrations/tables/20260106000000_AddValidityColumnsToHostCertificateTemplates_test.go (1)
server/ptr/ptr.go (2)
  • T (86-88)
  • Time (51-53)
server/fleet/host_certificate_template.go (1)
server/ptr/ptr.go (1)
  • Time (51-53)
cmd/fleet/cron.go (2)
server/service/schedule/schedule.go (1)
  • WithJob (124-131)
server/mdm/android/service/certificate_renewal.go (1)
  • RenewCertificateTemplates (14-28)
server/fleet/service.go (3)
cmd/fleetctl/fleetctl/config.go (1)
  • Context (24-32)
server/fleet/mdm.go (1)
  • MDMDeliveryStatus (466-466)
server/fleet/host_certificate_template.go (1)
  • HostCertificateValidity (59-63)
server/fleet/datastore.go (2)
server/fleet/mdm.go (2)
  • MDMDeliveryStatus (466-466)
  • MDMOperationType (528-528)
server/fleet/host_certificate_template.go (2)
  • HostCertificateValidity (59-63)
  • HostCertificateTemplateForRenewal (66-70)
server/datastore/mysql/host_certificate_templates.go (2)
server/fleet/host_certificate_template.go (2)
  • HostCertificateValidity (59-63)
  • HostCertificateTemplateForRenewal (66-70)
server/fleet/certificate_templates.go (3)
  • CertificateTemplateDelivered (53-53)
  • CertificateTemplateVerified (55-55)
  • CertificateTemplatePending (51-51)
server/mock/datastore_mock.go (2)
server/fleet/mdm.go (2)
  • MDMDeliveryStatus (466-466)
  • MDMOperationType (528-528)
server/fleet/host_certificate_template.go (2)
  • HostCertificateValidity (59-63)
  • HostCertificateTemplateForRenewal (66-70)
🔇 Additional comments (25)
server/datastore/mysql/host_certificate_templates_test.go (3)

40-41: LGTM!

The new test cases are properly registered and follow the existing test suite conventions.


1516-1644: LGTM!

Comprehensive test coverage for the renewal query with well-chosen scenarios:

  • Different validity periods (1 year vs. 14 days)
  • Different expiration timeframes
  • Status filtering (pending vs. delivered/verified)
  • Limit/pagination
  • Ordering by urgency (earliest expiration first)

The assertions are thorough and validate the renewal logic correctly.


1646-1777: LGTM!

Excellent test coverage for the renewal operation. The test validates all critical aspects:

  1. Status transition to pending
  2. UUID regeneration (essential for triggering device re-enrollment)
  3. Clearing of validity fields (not_valid_before, not_valid_after, serial)
  4. Empty slice handling for robustness

The helper function insertHostCertTemplate is clean and reusable for other tests.

server/service/integration_mdm_test.go (1)

386-394: LGTM!

The new renew_android_certificate_templates scheduled job follows the established WithJob pattern and correctly integrates with the cleanup schedule. The placement of onCleanupScheduleDone in the renewal job ensures both Android cleanup jobs complete before signaling done, which is appropriate for test synchronization.

cmd/fleet/cron.go (1)

992-994: Android renewal cron job is correctly wired into the cleanup schedule

The new renew_android_certificate_templates job follows the existing renewal pattern (RenewSCEPCertificates, RenewMDMManagedCertificates), passes the right dependencies, and is placed appropriately in newCleanupsAndAggregationSchedule. No issues spotted.

server/mock/service/service_mock.go (1)

410-410: Mock UpdateCertificateStatus correctly extended to carry validity

The mock’s function type and method now include validity *fleet.HostCertificateValidity and correctly forward all parameters while preserving the invoked flag behavior. This keeps the mock aligned with the updated service/datastore API and ensures any stale test stubs will be caught at compile time.

Also applies to: 3519-3523

server/mdm/android/service/certificate_renewal.go (1)

12-27: RenewCertificateTemplates is a clean, datastore‑driven renewal helper

The function cleanly delegates selection and update logic to the datastore, uses a reasonable batch size, handles empty/results and errors correctly, and logs only when work is performed. This is a good fit for being called from cron.

server/service/integration_android_certificate_templates_test.go (1)

1033-1238: LGTM! Comprehensive renewal test coverage.

The test covers the key renewal scenarios well:

  • Long-lived certificates with 30-day threshold
  • Short-lived certificates with half-validity threshold
  • Boundary conditions

The table-driven approach with clear descriptions makes the test cases easy to understand and maintain.

server/fleet/service.go (1)

653-653: Interface signature update is appropriate.

The new validity *HostCertificateValidity parameter is correctly placed as the last parameter and follows the nullable pointer pattern for optional data.

server/fleet/host_certificate_template.go (2)

20-22: Field additions are consistent with migration schema.

The new fields correctly map to the database columns added by the migration.


56-70: New types are well-structured.

  • HostCertificateValidity correctly uses pointer types for optional/nullable fields
  • HostCertificateTemplateForRenewal correctly uses non-pointer time.Time for NotValidAfter since the renewal query only returns rows where this value exists
server/service/certificates.go (3)

590-595: Validity object construction is correct.

The validity object is properly constructed from the request fields and passed to the service method. Downstream code will correctly handle the case where all fields are nil.


603-659: Service method implementation is correct.

The implementation properly:

  1. Validates status and operation type
  2. Handles the remove+verified case by deleting the record
  3. Only accepts updates for "delivered" certificates
  4. Propagates validity to the datastore layer

240-244: Correctly passes nil validity for failed certificates.

When a certificate fails due to variable replacement issues, there's no validity data to store, so passing nil is appropriate.

server/datastore/mysql/schema.sql (3)

521-523: LGTM: Certificate validity columns follow existing schema patterns.

The new not_valid_before, not_valid_after, and serial columns are consistent with similar certificate management tables (host_mdm_managed_certificates uses the same data types). The datetime(6) precision provides microsecond accuracy for certificate lifecycle tracking, and nullable defaults are appropriate for backward compatibility with existing records.


527-527: LGTM: Index supports efficient certificate renewal queries.

The index on not_valid_after is well-positioned to optimize the renewal cron job's queries for certificates approaching expiration. This follows the pattern used in host_certificates (line 558) which has a similar index for certificate expiration tracking.


1752-1754: LGTM: Migration metadata correctly updated.

The AUTO_INCREMENT value and migration status entry properly reflect the new migration 20260106000000_AddValidityColumnsToHostCertificateTemplates.

Note: Per learnings, this schema.sql file is auto-generated from migrations, so these changes are the expected result of running the migration.

server/fleet/datastore.go (1)

2560-2562: LGTM - Interface changes are well-defined.

The new validity *HostCertificateValidity parameter is appropriately optional (pointer type), maintaining backward compatibility. The new Android certificate renewal methods have clear documentation explaining the renewal threshold logic, which mirrors the existing Apple/Windows behavior.

Also applies to: 2625-2635

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

251-329: LGTM - UpsertCertificateStatus handles validity correctly.

The conditional logic properly handles both the update and insert paths:

  • When validity is provided with at least one field, the validity columns are included in the update/insert
  • When validity is nil or empty, only the core status fields are updated

The parameterization is correct and the column/placeholder counts match.


607-643: LGTM - Renewal query logic is sound.

The query correctly implements the documented renewal threshold logic:

  • Certificates with >30 day validity period renew within 30 days of expiration
  • Certificates with ≤30 day validity period renew within half the period

The filtering is appropriately scoped (status, operation_type, NOT NULL checks) and results are ordered by expiration urgency. The DATEDIFF integer division is acceptable here as it provides conservative renewal timing.

server/mock/datastore_mock.go (5)

1670-1670: UpsertCertificateStatus mock signature correctly extended with validity

The additional validity *fleet.HostCertificateValidity parameter keeps the mock type aligned with the datastore interface and downstream usage. No issues.


1714-1716: New Android renewal func types are consistent with model types

GetAndroidCertificateTemplatesForRenewalFunc / SetAndroidCertificateTemplatesForRenewalFunc use fleet.HostCertificateTemplateForRenewal as expected and follow existing naming and typing conventions for this mock.


4259-4263: DataStore fields for Android renewal funcs wired like existing mocks

The new func fields and ...Invoked booleans for Android certificate template renewal mirror the established pattern across the struct and will integrate cleanly with tests that assert invocation.


10034-10038: UpsertCertificateStatus method correctly propagates validity argument

The method signature and the delegated call both include the new validity parameter, matching UpsertCertificateStatusFunc and ensuring the mock can exercise validity-aware behavior in tests.


10188-10200: Android certificate renewal methods follow standard mock pattern

Both GetAndroidCertificateTemplatesForRenewal and SetAndroidCertificateTemplatesForRenewal:

  • Take the correct parameters (limit / templates),
  • Mark their ...Invoked flags under the shared mutex,
  • Delegate to the corresponding func fields.

This is consistent with the rest of the generated mock and should behave as expected in tests.

@mostlikelee mostlikelee marked this pull request as ready for review January 7, 2026 21:25
@mostlikelee mostlikelee requested a review from a team as a code owner January 7, 2026 21:25
Copy link
Member

@getvictor getvictor left a comment

Choose a reason for hiding this comment

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

Still reviewing, but see comments so far.

// Build update statement - include validity fields if provided
var updateStmt string
var updateParams []any
if validity != nil && (validity.NotValidBefore != nil || validity.NotValidAfter != nil || validity.Serial != nil) {
Copy link
Member

@getvictor getvictor Jan 7, 2026

Choose a reason for hiding this comment

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

This condition seems somewhat difficult to understand what exactly is it trying to check. Would doing just validity != nil make sense? Or validity.isPresent() method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this was overkill, i refactored this out

}

func testGetAndroidCertificateTemplatesForRenewal(t *testing.T, ds *Datastore) {
ctx := context.Background()
Copy link
Member

Choose a reason for hiding this comment

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

Nit. We can use t.Context() now instead.

)
ORDER BY not_valid_after ASC
LIMIT ?
`, fleet.CertificateTemplateDelivered, fleet.CertificateTemplateVerified, fleet.MDMOperationTypeInstall)
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need to check CertificateTemplateDelivered here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

great catch, i removed that filter

Comment on lines 1545 to 1548
DetailUpdatedAt: time.Now(),
LabelUpdatedAt: time.Now(),
PolicyUpdatedAt: time.Now(),
SeenTime: time.Now(),
Copy link
Member

Choose a reason for hiding this comment

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

Nit. Do we need to set all these fields? Other tests in this file don't set all of these when creating new hosts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no, i replaced with a test helper

Copy link
Member

@getvictor getvictor left a comment

Choose a reason for hiding this comment

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

Looks good overall. No major issues. We should fix the DB types before merging.

// Add not_valid_before column for certificate validity tracking
if _, err := tx.Exec(`
ALTER TABLE host_certificate_templates
ADD COLUMN not_valid_before DATETIME(6) NULL
Copy link
Member

Choose a reason for hiding this comment

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

I like DATETIME(6), but in this case, the cert valid times have a max precision of one second, per: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.5.1

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch, is this also an issue in host_mdm_managed_certificates?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, it is also an issue there, an may have been my fault :(

// Add index on not_valid_after for efficient renewal queries
if _, err := tx.Exec(`
ALTER TABLE host_certificate_templates
ADD INDEX idx_host_certificate_templates_not_valid_after (not_valid_after)
Copy link
Member

Choose a reason for hiding this comment

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

We're using DATEDIFF, so I'm not sure if this index will be helpful.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good point, tho i think it's still partially useful for ORDER BY not_valid_after ASC

Co-authored-by: Victor Lyuboslavsky <[email protected]>
@mostlikelee mostlikelee changed the title 37566 renew backend Renew android certificates backend Jan 8, 2026
@mostlikelee mostlikelee merged commit 65adddb into main Jan 8, 2026
67 of 69 checks passed
@mostlikelee mostlikelee deleted the 37566-renew-backend branch January 8, 2026 20:02
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 renewal: Fleet server

3 participants