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:
<img width="569" height="233" alt="image"
src="https://github.com/user-attachments/assets/558e74cc-ce3d-47e3-aa14-1391e1cb4146"
/>

With encryption on:
<img width="551" height="285" alt="image"
src="https://github.com/user-attachments/assets/adfe2ead-4c5c-43a0-a5aa-9566635aba5f"
/>

Expanded:
<img width="534" height="297" alt="image"
src="https://github.com/user-attachments/assets/ac0620a2-528f-4118-ae46-992a646c97d8"
/>

Tooltip:
<img width="579" height="317" alt="image"
src="https://github.com/user-attachments/assets/23d13820-9bcb-49fb-b32b-2b5c60e7e55c"
/>



# Checklist for submitter

If some of the following don't apply, delete the relevant line.

<!-- Note that API documentation changes are now addressed by the
product design team. -->

- [ ] 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
This commit is contained in:
Scott Gress 2025-07-23 14:36:28 -05:00 committed by GitHub
parent e841dd326b
commit fcdd01d78d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 69 additions and 3 deletions

View file

@ -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,

View file

@ -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. */

View file

@ -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;

View file

@ -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<ILoadTeamResponse, Error, ITeamConfig>(
["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 = ({
)}
<Checkbox
disabled={config?.gitops.gitops_mode_enabled}
onChange={onToggleCheckbox}
onChange={onToggleDiskEncryption}
value={diskEncryptionEnabled}
className={`${baseClass}__checkbox`}
>
@ -211,6 +227,44 @@ const DiskEncryption = ({
newTab
/>
</p>
{diskEncryptionEnabled && featureFlags.showBitLockerPINOption && (
<div>
<RevealButton
className={`${baseClass}__accordion-title`}
isShowing={showAdvancedOptions}
showText="Advanced options"
hideText="Advanced options"
caretPosition="after"
onClick={() => setShowAdvancedOptions(!showAdvancedOptions)}
/>
{showAdvancedOptions && (
<Checkbox
disabled={config?.gitops.gitops_mode_enabled}
onChange={onToggleRequireBitLockerPIN}
value={requireBitLockerPIN}
className={`${baseClass}__checkbox`}
>
<TooltipWrapper
tipContent={
<div>
<p>
If enabled, end users on Windows hosts will be
required to set a BitLocker PIN.
</p>
<br />
<p>
When the PIN is set, it&rsquo;s required to unlock
Windows hosts during startup.
</p>
</div>
}
>
Require BitLocker PIN
</TooltipWrapper>
</Checkbox>
)}
</div>
)}
<GitOpsModeTooltipWrapper
tipOffset={-12}
renderChildren={(d) => (

View file

@ -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.

View file

@ -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
),
},
}),
];