Skip to content

Add macOS controls.filevault.prompt_enablement_at to defer FileVault enablement to logout - #48437

Draft
hurricanehrndz wants to merge 19 commits into
fleetdm:mainfrom
hurricanehrndz:filevault-prompt-enablement-at
Draft

Add macOS controls.filevault.prompt_enablement_at to defer FileVault enablement to logout#48437
hurricanehrndz wants to merge 19 commits into
fleetdm:mainfrom
hurricanehrndz:filevault-prompt-enablement-at

Conversation

@hurricanehrndz

@hurricanehrndz hurricanehrndz commented Jun 29, 2026

Copy link
Copy Markdown

Related issue: N/A

Summary

Adds a macOS-only controls.filevault.prompt_enablement_at setting (login | logout,
default login), layered on the existing enable_disk_encryption shorthand. By deferring
FileVault enablement to logout, Fleet lets an external login-time authorization plugin
(e.g. XCreds, which enables FileVault and writes the Personal Recovery Key to
/var/db/FileVaultPRK.dat) turn FileVault on silently — well before a user would ever
see a logout-time prompt — enabling fully silent enablement owned by that plugin.

  • login (default / unset): byte-identical to today's FileVault profile (force at
    login + Setup Assistant).
  • logout: drops only the two FileVault2 force keys
    (DeferForceAtUserLoginMaxBypassAttempts, ForceEnableInSetupAssistant). Fleet still
    escrows the Personal Recovery Key, keeps dontAllowFDEDisable, and the cert root — so
    the external login-time plugin owns turning FileVault on.

enable_disk_encryption remains the sole on/off (off ⇒ no profile, regardless of this
setting). Exposed via PATCH /config, team-modify, and GitOps. Premium-gated and
validated. Windows BitLocker and Linux LUKS are unaffected. The dedicated
POST /disk_encryption endpoint and the UI stay shorthand-only.

Checklist for submitter

  • Changes file added for user-visible changes in changes/.
  • Input data is properly validated (prompt_enablement_at accepts only
    login/logout; premium-gated on every write path). No SQL/JS/shell concerns.
  • If paths of existing endpoints are modified without backwards compatibility,
    checked the frontend/CLI — no endpoint paths changed; login/unset is byte-stable so
    existing installs don't churn.

Testing

  • Added/updated automated tests (resolver/validator unit tests; profile-template
    test proving logout == login minus exactly the two force keys; GitOps round-trip;
    generate-gitops export; global + team reconcile re-push on prompt-only change; an
    integration test asserting byte-stability and in-place re-push).
  • QA'd all new/changed functionality manually - plutil -lint on both rendered profiles; confirmed logout keeps
    Defer/Enable=On/escrow/cert/dontAllowFDEDisable with no force keys.

Database migrations

N/A — app/team MDM config is stored as JSON; no schema changes.

New Fleet configuration settings

  • Setting is explicitly excluded from GitOps — No, it is GitOps-enabled.

GitOps checklist:

  • Exported via fleetctl generate-gitops (emitted only when explicitly set).
  • Documented in the GitOps reference (docs/Configuration/yaml-files.md).
  • Cleared/optional semantics: omitting the block leaves it unset (resolves to
    login); documented as optional.
  • Relevant UI: the DiskEncryption card has no control for this setting, so no
    GitOps-mode UI gating is needed (interfaces only tolerate the field on reads).

fleetd/orbit/Fleet Desktop

N/A — server + config profile only; no fleetd/orbit changes.

Summary by CodeRabbit

  • New Features

    • Added macOS FileVault prompt timing control (mdm.filevault.prompt_enablement_at) with supported values: login (current behavior) or logout.
    • GitOps, team PATCH, and global config updates now preserve and apply this setting when generating/applying FileVault policies.
  • Bug Fixes

    • FileVault MDM profile updates are now correctly re-synced when only the prompt timing changes (when disk encryption is enabled).
    • Validation now rejects unsupported prompt values and prevents use on non-premium licenses.

@hurricanehrndz
hurricanehrndz requested review from a team and rachaelshaw as code owners June 29, 2026 18:04

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 67.34694% with 32 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.26%. Comparing base (36f7388) to head (5979ced).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
ee/server/service/teams.go 65.85% 10 Missing and 4 partials ⚠️
cmd/fleetctl/fleetctl/generate_gitops.go 0.00% 4 Missing ⚠️
ee/server/service/mdm.go 71.42% 2 Missing and 2 partials ⚠️
server/fleet/teams.go 33.33% 3 Missing and 1 partial ⚠️
server/service/appconfig.go 76.47% 2 Missing and 2 partials ⚠️
server/service/client.go 0.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #48437      +/-   ##
==========================================
- Coverage   67.95%   66.26%   -1.69%     
==========================================
  Files        3677     3677              
  Lines      233664   233744      +80     
  Branches    12444    12298     -146     
==========================================
- Hits       158785   154890    -3895     
- Misses      60636    64891    +4255     
+ Partials    14243    13963     -280     
Flag Coverage Δ
backend 67.58% <67.34%> (-2.02%) ⬇️
frontend 58.95% <ø> (ø)

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

☔ View full report in Codecov by Harness.
📢 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.

…, validation

Phase 1: introduce macOS-only MDMFileVaultSettings (prompt_enablement_at enum) on
app + team MDM config with a login-default resolver, premium gating, and value
validation. No behavior change yet; profile template still emits force keys.
Phase 2: wrap the two FileVault2 force keys in a whitespace-controlled
{{ if eq .PromptEnablementAt "login" }} block (login/unset byte-identical to
today; logout drops only those two keys). Resolve the setting team-over-global
at the MDMAppleEnableFileVaultAndEscrow build site.
…g & team APIs

Phase 3: round-trip the macOS-only prompt override via GitOps and the general
config / team-modify APIs, and re-push the FileVault profile on a prompt-only
change while disk encryption stays on (global + all team write paths). A
prompt-only change re-pushes without emitting an on/off disk-encryption activity;
the dedicated POST /disk_encryption endpoint stays shorthand-only.
…est & UI tolerance

Phase 4: tolerate the new macOS-only filevault block in the config/team TS
interfaces (not rendered), add an integration test asserting login is byte-stable
and logout drops exactly the two FileVault2 force keys in place, document the
control in the GitOps reference, and add a changelog fragment.
…e-gitops

Emit the macOS-only filevault block in generated GitOps only when
prompt_enablement_at is explicitly set, so omitted/default (login) stays out of
the YAML and existing output is unchanged. Completes the GitOps round-trip
(generate -> apply) for the new setting.
@hurricanehrndz
hurricanehrndz force-pushed the filevault-prompt-enablement-at branch from 83c0db1 to 3d02d0b Compare June 29, 2026 18:11
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f5349c6c-1fbb-4a45-86d0-eaa3f2950d5d

📥 Commits

Reviewing files that changed from the base of the PR and between 83c0db18e0a2a15b0a42e2db7126f8216fcf8fbc and 3d02d0b.

⛔ Files ignored due to path filters (1)
  • docs/Configuration/yaml-files.md is excluded by !**/*.md
📒 Files selected for processing (22)
  • changes/filevault-prompt-enablement-at
  • cmd/fleetctl/fleetctl/generate_gitops.go
  • cmd/fleetctl/fleetctl/generate_gitops_test.go
  • ee/server/service/mdm.go
  • ee/server/service/mdm_external_test.go
  • ee/server/service/mdm_profiles.go
  • ee/server/service/mdm_profiles_test.go
  • ee/server/service/mdm_test.go
  • ee/server/service/teams.go
  • ee/server/service/teams_test.go
  • frontend/interfaces/config.ts
  • frontend/interfaces/team.ts
  • pkg/spec/gitops.go
  • pkg/spec/gitops_test.go
  • server/fleet/app.go
  • server/fleet/filevault_test.go
  • server/fleet/teams.go
  • server/service/appconfig.go
  • server/service/appconfig_test.go
  • server/service/client.go
  • server/service/integration_mdm_lifecycle_test.go
  • server/service/mdm_test.go
🚧 Files skipped from review as they are similar to previous changes (22)
  • changes/filevault-prompt-enablement-at
  • ee/server/service/mdm_test.go
  • server/fleet/filevault_test.go
  • frontend/interfaces/config.ts
  • server/service/mdm_test.go
  • ee/server/service/mdm_profiles_test.go
  • ee/server/service/mdm_profiles.go
  • ee/server/service/mdm_external_test.go
  • pkg/spec/gitops.go
  • cmd/fleetctl/fleetctl/generate_gitops.go
  • pkg/spec/gitops_test.go
  • server/service/client.go
  • frontend/interfaces/team.ts
  • server/fleet/app.go
  • cmd/fleetctl/fleetctl/generate_gitops_test.go
  • server/service/integration_mdm_lifecycle_test.go
  • ee/server/service/mdm.go
  • server/service/appconfig.go
  • ee/server/service/teams.go
  • server/fleet/teams.go
  • ee/server/service/teams_test.go
  • server/service/appconfig_test.go

Walkthrough

Adds a new macOS Fleet Premium configuration control controls.filevault.prompt_enablement_at with accepted values "login" and "logout". The change adds FileVault settings types and helpers, threads them through team, app, frontend, and GitOps config structures, updates FileVault profile XML rendering to conditionally emit force keys, and updates service reconciliation to validate the value and re-push profiles on prompt-only changes.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.11% 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
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding the macOS FileVault prompt enablement setting and its logout behavior.
Description check ✅ Passed The description follows the template well, covering summary, checklist, testing, migrations, GitOps settings, and platform scope.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🧹 Nitpick comments (3)
cmd/fleetctl/fleetctl/generate_gitops_test.go (1)

1579-1597: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the global export path too.

These assertions only hit the no_team branch. The PR also adds global filevault export behavior, so a regression in the global controls path could still pass here. Add the same include/omit checks for generateControls(nil, "", &mdmConfig) with mdmConfig.FileVault populated from the app config.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/fleetctl/fleetctl/generate_gitops_test.go` around lines 1579 - 1597, The
current test only validates the no_team branch of generateControls, so it misses
regressions in the global filevault export path. Extend the coverage in
generate_gitops_test.go by adding the same include/omit assertions for
generateControls(nil, "", &mdmConfig) using mdmConfig.FileVault populated from
the app config, and verify prompt_enablement_at is exported when set and omitted
when unset in that global path.
ee/server/service/teams_test.go (1)

848-889: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that the prompt value is persisted on successful updates.

These cases prove the re-push/activity behavior, but they never verify that ModifyTeam actually stores mdm.filevault.prompt_enablement_at on the team. A regression that re-pushes from the request payload but drops team.Config.MDM.FileVault would still pass here. Capture the saved/returned team and assert PromptEnablementAt in the success cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ee/server/service/teams_test.go` around lines 848 - 889, The prompt-only
ModifyTeam test cases verify repush/activity behavior but do not confirm that
the team’s FileVault prompt value is actually persisted. In the test setup
around ModifyTeam, SaveTeamFunc and the returned team should be captured so the
success path can assert team.Config.MDM.FileVault.PromptEnablementAt matches the
requested value. Add this assertion for the successful cases to ensure
ModifyTeam stores mdm.filevault.prompt_enablement_at on the team, not just in
the re-push payload.
server/service/appconfig_test.go (1)

219-283: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Verify prompt-only PATCHes stay activity-silent.

ModifyAppConfig now has separate logic to suppress enabled_macos_disk_encryption / disabled_macos_disk_encryption activities when only filevault.prompt_enablement_at changes, but this test only checks the profile push. Count those activity types here too; otherwise that regression path can slip through while the test still passes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/service/appconfig_test.go` around lines 219 - 283, The prompt-only
PATCH path in TestModifyAppConfigFileVaultPromptReconcile only verifies the
FileVault profile is re-pushed, but it does not assert that ModifyAppConfig
stays silent for enabled_macos_disk_encryption and
disabled_macos_disk_encryption activities. Extend the test setup to capture
activity creation and add assertions for those activity types in both subtests,
so a prompt-only change cannot trigger the disk-encryption activity path
unnoticed. Use the existing ModifyAppConfig flow and the test helper setup in
TestModifyAppConfigFileVaultPromptReconcile to locate where to count these
activity events.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/fleetctl/fleetctl/generate_gitops.go`:
- Line 492: The new FileVault seed in generateGitOps is pulling from
cmd.AppConfig.MDM.FileVault, which causes team.ID == 0 to emit the global MDM
value instead of the Unassigned fleet’s actual config. Update the GitOps
generation path in generate_gitops.go so the FileVault field comes from the same
team-scoped MDM source used for other settings in Run, ensuring
fleets/unassigned.yml round-trips team 0 correctly without overriding its prompt
setting.

In `@server/service/appconfig.go`:
- Around line 1729-1734: The FileVault premium check in validateMDM is too broad
and can reject unrelated config updates after a downgrade because it only looks
at the merged value. Update the gate around mdm.FileVault.PromptEnablementAt to
be change-sensitive, like the nearby SSO/setup checks, by comparing the stored
value against the incoming request before appending ErrMissingLicense. Keep the
existing validation for invalid values, but only enforce the premium license
restriction when the PATCH actually modifies filevault.prompt_enablement_at in
appconfig.go’s validateMDM path.

---

Nitpick comments:
In `@cmd/fleetctl/fleetctl/generate_gitops_test.go`:
- Around line 1579-1597: The current test only validates the no_team branch of
generateControls, so it misses regressions in the global filevault export path.
Extend the coverage in generate_gitops_test.go by adding the same include/omit
assertions for generateControls(nil, "", &mdmConfig) using mdmConfig.FileVault
populated from the app config, and verify prompt_enablement_at is exported when
set and omitted when unset in that global path.

In `@ee/server/service/teams_test.go`:
- Around line 848-889: The prompt-only ModifyTeam test cases verify
repush/activity behavior but do not confirm that the team’s FileVault prompt
value is actually persisted. In the test setup around ModifyTeam, SaveTeamFunc
and the returned team should be captured so the success path can assert
team.Config.MDM.FileVault.PromptEnablementAt matches the requested value. Add
this assertion for the successful cases to ensure ModifyTeam stores
mdm.filevault.prompt_enablement_at on the team, not just in the re-push payload.

In `@server/service/appconfig_test.go`:
- Around line 219-283: The prompt-only PATCH path in
TestModifyAppConfigFileVaultPromptReconcile only verifies the FileVault profile
is re-pushed, but it does not assert that ModifyAppConfig stays silent for
enabled_macos_disk_encryption and disabled_macos_disk_encryption activities.
Extend the test setup to capture activity creation and add assertions for those
activity types in both subtests, so a prompt-only change cannot trigger the
disk-encryption activity path unnoticed. Use the existing ModifyAppConfig flow
and the test helper setup in TestModifyAppConfigFileVaultPromptReconcile to
locate where to count these activity events.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 14a588a2-e438-49d4-a289-d2e70ba7db30

📥 Commits

Reviewing files that changed from the base of the PR and between b547f1a and 83c0db18e0a2a15b0a42e2db7126f8216fcf8fbc.

⛔ Files ignored due to path filters (1)
  • docs/Configuration/yaml-files.md is excluded by !**/*.md
📒 Files selected for processing (22)
  • changes/filevault-prompt-enablement-at
  • cmd/fleetctl/fleetctl/generate_gitops.go
  • cmd/fleetctl/fleetctl/generate_gitops_test.go
  • ee/server/service/mdm.go
  • ee/server/service/mdm_external_test.go
  • ee/server/service/mdm_profiles.go
  • ee/server/service/mdm_profiles_test.go
  • ee/server/service/mdm_test.go
  • ee/server/service/teams.go
  • ee/server/service/teams_test.go
  • frontend/interfaces/config.ts
  • frontend/interfaces/team.ts
  • pkg/spec/gitops.go
  • pkg/spec/gitops_test.go
  • server/fleet/app.go
  • server/fleet/filevault_test.go
  • server/fleet/teams.go
  • server/service/appconfig.go
  • server/service/appconfig_test.go
  • server/service/client.go
  • server/service/integration_mdm_lifecycle_test.go
  • server/service/mdm_test.go

Comment thread cmd/fleetctl/fleetctl/generate_gitops.go
Comment thread server/service/appconfig.go Outdated
Regenerated via 'go run ./tools/cloner-check/main.go --update' after adding the
macOS FileVault settings to AppConfig.MDM and TeamMDM (value types; existing
value-copy Clone/Copy remains correct).
@melpike

melpike commented Jun 29, 2026

Copy link
Copy Markdown
Member

Thank you, @hurricanehrndz!

I'll set this as draft and review per the community PR process: https://fleetdm.com/handbook/engineering#review-a-community-pull-request

@melpike
melpike marked this pull request as draft June 29, 2026 18:48
@melpike melpike self-assigned this Jun 29, 2026
Reset disk encryption + delete any existing no-team FileVault profile before
enabling, and turn it back off on cleanup, so the shared integration suite doesn't
hit a 409 'profile already exists' when this test enables FileVault.
@hurricanehrndz

Copy link
Copy Markdown
Author

Thank you, @hurricanehrndz!

I'll set this as draft and review per the community PR process: https://fleetdm.com/handbook/engineering#review-a-community-pull-request

Thank you

…gitops

The FileVault seed in generateGitOps was pulling from
cmd.AppConfig.MDM.FileVault, causing team.ID == 0 (Unassigned) to emit
the global MDM value instead of the team's actual config. This broke
round-trip: generate → apply would override the unassigned fleet's
prompt setting with the global value.

Use team.Config.MDM.FileVault for team 0, matching how regular teams
use their own config. Also adds include/omit test assertions for the
global filevault export path.
The premium gate on filevault.prompt_enablement_at only checked the
merged config value, rejecting unrelated PATCH requests after a license
downgrade when the field was previously set.

Compare oldMdm vs mdm before appending ErrMissingLicense, matching the
pattern used by adjacent SSO/setup checks. The value-validation for
invalid enum values remains unconditional.

Also adds activity assertions to the reconcile test (prompt-only change
must not emit disk-encryption activities) and a dedicated premium-gate
test verifying both the allow and reject paths.
The ModifyTeam prompt-only test verified re-push behavior but did not
confirm the team's FileVault prompt value was actually stored. Capture
savedTeam from SaveTeamFunc and assert PromptEnablementAt matches the
requested value on success paths.
@hurricanehrndz
hurricanehrndz force-pushed the filevault-prompt-enablement-at branch from 34312cb to 3638dac Compare June 29, 2026 19:40
@hurricanehrndz
hurricanehrndz force-pushed the filevault-prompt-enablement-at branch from 59a8ce1 to 6b85a5c Compare June 29, 2026 21:04
Change MDM.FileVault, TeamMDM.FileVault, TeamPayloadMDM.FileVault, and
TeamSpecMDM.FileVault from value type MDMFileVaultSettings to
*MDMFileVaultSettings with json:"filevault,omitempty".

When nil (unset), the field is omitted from JSON entirely, so:
- Golden/expectation tests pass unchanged (no spurious
  "filevault":{"prompt_enablement_at":null} in every payload)
- DB round-trip tests pass (no Set:true vs Set:false mismatch)
- Integration tests comparing team/config JSON output pass

Also:
- Nil-guard the resolver and all access sites
- Deep-copy the pointer in AppConfig.Copy() and TeamMDM.Copy()
- Update cloner-check goldens
- Fix lint: ptr.Uint(0)→new(uint), ptr.String→new(...), interface{}→any
@hurricanehrndz
hurricanehrndz force-pushed the filevault-prompt-enablement-at branch from 6b85a5c to 65cd5ef Compare June 29, 2026 21:07
hurricanehrndz and others added 2 commits June 29, 2026 15:08
MDMAppleEnableFileVaultAndEscrow used a bare INSERT which 409s when the
profile already exists (e.g. prompt-only re-push while disk encryption
is already on). Delete the existing profile first so the insert always
succeeds — matching the disable+enable pattern used elsewhere.

Update mocks and test assertions that now see the additional delete call.
@hurricanehrndz
hurricanehrndz force-pushed the filevault-prompt-enablement-at branch from 7ddd8ba to 57c936e Compare June 30, 2026 01:46
Simplify the team.ID == 0 handling by always assigning
team.Config.MDM to mdmConfig (not just FileVault), so all MDM
fields come from the team-scoped source. Only agent_options
remains gated behind team.ID != 0.
…ablement-at

* upstream/main:
  Website: update report details page (fleetdm#48462)
  Foreign vitals mapping: Update SCIM integration instructions (fleetdm#48413)
  Two Product Designers for #g-power-to-pc (fleetdm#48456)
  Revise user story handling post-design review (fleetdm#48457)
…ablement-at

* upstream/main:
  Zay is DRI of usage statistics (fleetdm#48425)
@hurricanehrndz
hurricanehrndz force-pushed the filevault-prompt-enablement-at branch from 57c936e to 693bd6f Compare June 30, 2026 01:53
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.

2 participants