Add banners for Windows hosts that need to set BitLocker PIN (#31453)

for #31196 

# 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`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
Will add changelog when feature is complete.

## Testing

> Note - to enable the "require bitlocker pin" ui, compile front end
with:
```
SHOW_BITLOCKER_PIN_OPTION=true NODE_ENV=development yarn run webpack --progress --watch
```
- [X] Added/updated automated tests
- [X] QA'd all new/changed functionality manually

I had to add/remove the `tpm_pin_set` flag in the db manually, but by
doing that I was able to get the banners to appear, when running
https://github.com/fleetdm/fleet/pull/31451.

<img width="1158" height="128" alt="image"
src="https://github.com/user-attachments/assets/f3e4dc62-5e1f-410a-a684-11aee3e29f50"
/>

---

<img width="833" height="375" alt="image"
src="https://github.com/user-attachments/assets/b178f203-1399-4dd1-b743-558bd77b175b"
/>

---

<img width="1160" height="199" alt="image"
src="https://github.com/user-attachments/assets/cf86380c-d84c-47fd-b62f-349f0a4bb718"
/>

---------

Co-authored-by: Gabriel Hernandez <ghernandez345@gmail.com>
This commit is contained in:
Scott Gress 2025-08-06 13:13:36 -05:00 committed by GitHub
parent afd4bd1b35
commit 6a268f173c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 108 additions and 9 deletions

View file

@ -4,6 +4,7 @@ import { useQuery } from "react-query";
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
import { pick } from "lodash";
import Modal from "components/Modal";
import { NotificationContext } from "context/notification";
@ -123,6 +124,7 @@ const DeviceUserPage = ({
NotificationContext
);
const [showBitLockerPINModal, setShowBitLockerPINModal] = useState(false);
const [showInfoModal, setShowInfoModal] = useState(false);
const [showEnrollMdmModal, setShowEnrollMdmModal] = useState(false);
const [refetchStartTime, setRefetchStartTime] = useState<number | null>(null);
@ -466,6 +468,7 @@ const DeviceUserPage = ({
host.mdm.macos_settings?.action_required ?? null
}
onTurnOnMdm={toggleEnrollMdmModal}
onClickCreatePIN={() => setShowBitLockerPINModal(true)}
onTriggerEscrowLinuxKey={onTriggerEscrowLinuxKey}
diskEncryptionOSSetting={host.mdm.os_settings?.disk_encryption}
diskIsEncrypted={host.disk_encryption_enabled}
@ -611,6 +614,43 @@ const DeviceUserPage = ({
token={deviceAuthToken}
/>
))}
{showBitLockerPINModal && (
<Modal
title="Create PIN"
onExit={() => setShowBitLockerPINModal(false)}
onEnter={() => setShowBitLockerPINModal(false)}
className={baseClass}
width="large"
>
<div>
<p>
<ol>
<li>
<p>
Open the <b>Start menu</b>.
</p>
</li>
<li>
<p>Type &ldquo;Manage BitLocker&rdquo; and launch.</p>
</li>
<li>
<p>
<b>Choose Enter a PIN (recommended)</b> and follow the
prompts to create a PIN.
</p>
</li>
<li>
<p>
Close this window and select <b>Refetch</b> on your{" "}
<b>My device</b> page. This informs your organization
that you have set a BitLocker PIN.
</p>
</li>
</ol>
</p>
</div>
</Modal>
)}
</div>
)}
{!!host && showPolicyDetailsModal && (

View file

@ -8,6 +8,7 @@ describe("Device User Banners", () => {
const turnOnMdmExpcetedText = /Mobile device management \(MDM\) is off\./;
const resetNonLinuxDiskEncryptKeyExpectedText = /Disk encryption: Log out of your device or restart it to safeguard your data in case your device is lost or stolen\./;
const createNewLinuxDiskEncryptKeyExpectedText = /Disk encryption: Create a new disk encryption key\. This lets your organization help you unlock your device if you forget your passphrase\./;
const createPINExepectedText = /Disk encryption: Create a BitLocker PIN to safeguard your data/;
it("renders the turn on mdm banner correctly", () => {
render(
@ -20,6 +21,7 @@ describe("Device User Banners", () => {
diskEncryptionActionRequired={null}
onTurnOnMdm={noop}
onTriggerEscrowLinuxKey={noop}
onClickCreatePIN={noop}
/>
);
expect(screen.getByText(turnOnMdmExpcetedText)).toBeInTheDocument();
@ -36,6 +38,7 @@ describe("Device User Banners", () => {
diskEncryptionActionRequired="rotate_key"
onTurnOnMdm={noop}
onTriggerEscrowLinuxKey={noop}
onClickCreatePIN={noop}
/>
);
expect(
@ -57,6 +60,7 @@ describe("Device User Banners", () => {
diskEncryptionActionRequired={null}
onTurnOnMdm={noop}
onTriggerEscrowLinuxKey={noop}
onClickCreatePIN={noop}
/>
);
expect(
@ -79,6 +83,7 @@ describe("Device User Banners", () => {
diskEncryptionActionRequired={null}
onTurnOnMdm={noop}
onTriggerEscrowLinuxKey={noop}
onClickCreatePIN={noop}
/>
);
expect(
@ -86,6 +91,27 @@ describe("Device User Banners", () => {
).toBeInTheDocument();
});
it("renders the create PIN banner correctly for Windows", () => {
render(
<DeviceUserBanners
hostPlatform="windows"
diskEncryptionOSSetting={{ status: "action_required", detail: "" }}
diskIsEncrypted
// explicit for testing purposes
diskEncryptionKeyAvailable={false}
mdmEnrollmentStatus="On (automatic)"
mdmEnabledAndConfigured
connectedToFleetMdm
macDiskEncryptionStatus={null}
diskEncryptionActionRequired={null}
onTurnOnMdm={noop}
onTriggerEscrowLinuxKey={noop}
onClickCreatePIN={noop}
/>
);
expect(screen.getByText(createPINExepectedText)).toBeInTheDocument();
});
it("renders no banner correctly for a mac that is verifying its disk encryption", () => {
render(
<DeviceUserBanners
@ -98,6 +124,7 @@ describe("Device User Banners", () => {
onTurnOnMdm={noop}
onTriggerEscrowLinuxKey={noop}
diskEncryptionOSSetting={{ status: "verifying", detail: "" }}
onClickCreatePIN={noop}
/>
);
@ -121,6 +148,7 @@ describe("Device User Banners", () => {
diskEncryptionActionRequired={null}
onTurnOnMdm={noop}
onTriggerEscrowLinuxKey={noop}
onClickCreatePIN={noop}
/>
);

View file

@ -13,6 +13,7 @@ interface IDeviceUserBannersProps extends IHostBannersBaseProps {
mdmEnabledAndConfigured: boolean;
diskEncryptionActionRequired: MacDiskEncryptionActionRequired | null;
onTurnOnMdm: () => void;
onClickCreatePIN: () => void;
onTriggerEscrowLinuxKey: () => void;
}
@ -25,6 +26,7 @@ const DeviceUserBanners = ({
macDiskEncryptionStatus,
diskEncryptionActionRequired,
onTurnOnMdm,
onClickCreatePIN,
diskEncryptionOSSetting,
diskIsEncrypted,
diskEncryptionKeyAvailable,
@ -123,6 +125,26 @@ const DeviceUserBanners = ({
}
}
if (
hostPlatform === "windows" &&
diskEncryptionOSSetting?.status === "action_required"
) {
return (
<InfoBanner
color="yellow"
cta={
<Button variant="text-link-dark" onClick={onClickCreatePIN}>
Create PIN
</Button>
}
>
Disk encryption: Create a BitLocker PIN to safeguard your data in case
your device is lost or stolen. After, select <strong>Refetch</strong>{" "}
to clear this banner.
</InfoBanner>
);
}
return null;
};

View file

@ -55,6 +55,16 @@ const HostDetailsBanners = ({
connectedToFleetMdm &&
macDiskEncryptionStatus === "action_required";
const actionRequiredBanner = (
<div className={baseClass}>
<InfoBanner color="yellow">
Disk encryption: Requires action from the end user. Ask the user to
follow <b>Disk encryption</b> instructions on their <b>My device</b>{" "}
page.
</InfoBanner>
</div>
);
if (showTurnOnMdmInfoBanner) {
return (
<div className={baseClass}>
@ -108,17 +118,16 @@ const HostDetailsBanners = ({
// linux host's disk is encrypted, but Fleet doesn't yet have a disk
// encryption key escrowed (note that this state is also possible for Windows hosts, which we
// don't show this banner for currently)
return (
<div className={baseClass}>
<InfoBanner color="yellow">
Disk encryption: Requires action from the end user. Ask the user to
follow <b>Disk encryption</b> instructions on their <b>My device</b>{" "}
page.
</InfoBanner>
</div>
);
return actionRequiredBanner;
}
}
if (
hostPlatform === "windows" &&
diskEncryptionOSSetting?.status === "action_required"
) {
return actionRequiredBanner;
}
return null;
};