From fcdd01d78d6334ca3d53ce7fed39753a925a16e1 Mon Sep 17 00:00:00 2001 From: Scott Gress Date: Wed, 23 Jul 2025 14:36:28 -0500 Subject: [PATCH] Add "Require BitLocker PIN" checkbox to disk encryption page (#31132) for #31064 # Details This PR adds a "Require BitLocker PIN" checkbox under a new "Advanced" section on the Disk Encryption page. This UI will only be visible if: * "Turn on disk encryption" is checked * The front-end was compiled using the `SHOW_BITLOCKER_PIN_OPTION=true` env var, e.g.: ``` SHOW_BITLOCKER_PIN_OPTION=true NODE_ENV=development yarn run webpack --progress --watch ``` See Figma for reference: https://www.figma.com/design/XbhlPuEJxQtOgTZW9EOJZp/-28133-Enforce-BitLocker-PIN?node-id=5334-1026&t=NuPo1M5fJepyCCRy-0 With encryption off: image With encryption on: image Expanded: image Tooltip: image # Checklist for submitter If some of the following don't apply, delete the relevant line. - [ ] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. - will add changelog when feature is complete - [x] Manual QA for all new/changed functionality --- frontend/__mocks__/configMock.ts | 1 + frontend/interfaces/config.ts | 1 + frontend/interfaces/team.ts | 1 + .../cards/DiskEncryption/DiskEncryption.tsx | 58 ++++++++++++++++++- frontend/services/entities/disk_encryption.ts | 8 ++- webpack.config.js | 3 + 6 files changed, 69 insertions(+), 3 deletions(-) diff --git a/frontend/__mocks__/configMock.ts b/frontend/__mocks__/configMock.ts index fb7c6d9926..3d95dc4492 100644 --- a/frontend/__mocks__/configMock.ts +++ b/frontend/__mocks__/configMock.ts @@ -3,6 +3,7 @@ import { IConfig, ILicense, IMdmConfig } from "interfaces/config"; const DEFAULT_CONFIG_MDM_MOCK: IMdmConfig = { apple_server_url: "", enable_disk_encryption: false, + windows_require_bitlocker_pin: false, windows_enabled_and_configured: true, apple_bm_default_team: "Apples", apple_bm_enabled_and_configured: true, diff --git a/frontend/interfaces/config.ts b/frontend/interfaces/config.ts index 502eb660e5..bef8b5e17e 100644 --- a/frontend/interfaces/config.ts +++ b/frontend/interfaces/config.ts @@ -47,6 +47,7 @@ export interface IMdmConfig { /** Update this URL if you're self-hosting Fleet and you want your hosts to talk to a different URL for MDM features. (If not configured, hosts will use the base URL of the Fleet instance.) */ apple_server_url: string; enable_disk_encryption: boolean; + windows_require_bitlocker_pin: boolean; /** `enabled_and_configured` only tells us if Apples MDM has been enabled and configured correctly. The naming is slightly confusing but at one point we only supported apple mdm, so thats why it's name the way it is. */ diff --git a/frontend/interfaces/team.ts b/frontend/interfaces/team.ts index 8f5bdc8bc0..541bfbcfbf 100644 --- a/frontend/interfaces/team.ts +++ b/frontend/interfaces/team.ts @@ -49,6 +49,7 @@ export interface ITeam extends ITeamSummary { role?: UserRole; // role value is included when the team is in the context of a user mdm?: { enable_disk_encryption: boolean; + windows_require_bitlocker_pin: boolean; macos_updates: IAppleDeviceUpdates; ios_updates: IAppleDeviceUpdates; ipados_updates: IAppleDeviceUpdates; diff --git a/frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/DiskEncryption.tsx b/frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/DiskEncryption.tsx index a128b377f9..7f6b4374b5 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/DiskEncryption.tsx +++ b/frontend/pages/ManageControlsPage/OSSettings/cards/DiskEncryption/DiskEncryption.tsx @@ -21,6 +21,7 @@ import Spinner from "components/Spinner"; import SectionHeader from "components/SectionHeader"; import TooltipWrapper from "components/TooltipWrapper"; import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper"; +import RevealButton from "components/buttons/RevealButton"; import DiskEncryptionTable from "./components/DiskEncryptionTable"; @@ -43,12 +44,20 @@ const DiskEncryption = ({ ? false : config?.mdm.enable_disk_encryption ?? false; + const defaultRequireBitLockerPIN = currentTeamId + ? false + : config?.mdm.windows_require_bitlocker_pin ?? false; + const [isLoadingTeam, setIsLoadingTeam] = useState(true); const [showAggregate, setShowAggregate] = useState(defaultShowDiskEncryption); const [diskEncryptionEnabled, setDiskEncryptionEnabled] = useState( defaultShowDiskEncryption ); + const [requireBitLockerPIN, setRequireBitLockerPIN] = useState( + defaultRequireBitLockerPIN + ); + const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); // because we pull the default state for no teams from the config, // we need to update the config when the user toggles the checkbox @@ -64,10 +73,14 @@ const DiskEncryption = ({ } }; - const onToggleCheckbox = (value: boolean) => { + const onToggleDiskEncryption = (value: boolean) => { setDiskEncryptionEnabled(value); }; + const onToggleRequireBitLockerPIN = (value: boolean) => { + setRequireBitLockerPIN(value); + }; + useQuery( ["team", currentTeamId], () => teamsAPI.load(currentTeamId), @@ -79,6 +92,8 @@ const DiskEncryption = ({ onSuccess: (res) => { const enableDiskEncryption = res.mdm?.enable_disk_encryption ?? false; setDiskEncryptionEnabled(enableDiskEncryption); + const pinRequired = res.mdm?.windows_require_bitlocker_pin ?? false; + setRequireBitLockerPIN(pinRequired); setShowAggregate(enableDiskEncryption); setIsLoadingTeam(false); }, @@ -89,6 +104,7 @@ const DiskEncryption = ({ try { await diskEncryptionAPI.updateDiskEncryption( diskEncryptionEnabled, + requireBitLockerPIN, currentTeamId ); renderFlash( @@ -196,7 +212,7 @@ const DiskEncryption = ({ )} @@ -211,6 +227,44 @@ const DiskEncryption = ({ newTab />

+ {diskEncryptionEnabled && featureFlags.showBitLockerPINOption && ( +
+ setShowAdvancedOptions(!showAdvancedOptions)} + /> + {showAdvancedOptions && ( + + +

+ If enabled, end users on Windows hosts will be + required to set a BitLocker PIN. +

+
+

+ When the PIN is set, it’s required to unlock + Windows hosts during startup. +

+
+ } + > + Require BitLocker PIN + +
+ )} + + )} ( diff --git a/frontend/services/entities/disk_encryption.ts b/frontend/services/entities/disk_encryption.ts index 1d41103f3d..5d3cf8e71a 100644 --- a/frontend/services/entities/disk_encryption.ts +++ b/frontend/services/entities/disk_encryption.ts @@ -27,7 +27,11 @@ const diskEncryptionService = { } return sendRequest("GET", path); }, - updateDiskEncryption: (enableDiskEncryption: boolean, teamId?: number) => { + updateDiskEncryption: ( + enableDiskEncryption: boolean, + requireBitLockerPIN: boolean, + teamId?: number + ) => { // TODO - use same endpoint for both once issue with new endpoint for no team is resolved const { UPDATE_DISK_ENCRYPTION: teamsEndpoint, @@ -37,11 +41,13 @@ const diskEncryptionService = { return sendRequest("PATCH", noTeamsEndpoint, { mdm: { enable_disk_encryption: enableDiskEncryption, + windows_require_bitlocker_pin: requireBitLockerPIN, }, }); } return sendRequest("POST", teamsEndpoint, { enable_disk_encryption: enableDiskEncryption, + windows_require_bitlocker_pin: requireBitLockerPIN, // TODO - it would be good to be able to use an API_CONTEXT_NO_TEAM_ID here, but that is // currently set to 0, which should actually be undefined since the server expects teamId == // nil for no teams, not 0. diff --git a/webpack.config.js b/webpack.config.js index e99b2b67ca..0d4a250a9f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -27,6 +27,9 @@ let plugins = [ new webpack.DefinePlugin({ featureFlags: { // e.g.: allowGitOpsMode: JSON.stringify(process.env.ALLOW_GITOPS_MODE), + showBitLockerPINOption: JSON.stringify( + process.env.SHOW_BITLOCKER_PIN_OPTION + ), }, }), ];