fleet/orbit/pkg/bitlocker/bitlocker_management.go
Victor Lyuboslavsky 58563852f0
Bitlocker: do not decrypt already encrypted drive. (#43130)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #40809

**Orbit agent: key rotation replaces decrypt-then-re-encrypt:**
- When the disk is already encrypted, orbit now adds a new Fleet-managed
recovery key protector, removes old ones, and escrows the new key. The
disk is never decrypted.
- If key escrow fails, the rotated key is cached in memory and retried
on subsequent ticks without rotating again.
- Removes `DecryptVolume` and `decrypt()` (no longer called from
production code).

**Server: osquery query returns both protection_status and
conversion_status:**
- The `disk_encryption_windows` query now returns both columns instead
of just checking `protection_status = 1`. This lets the server correctly
identify a disk as encrypted via `conversion_status = 1` even when
`protection_status = 0`.
- New `directIngestDiskEncryptionWindows` function parses both values,
handles parse errors, and normalizes `protection_status = 2` (unknown)
to NULL.

**Server: new `bitlocker_protection_status` column and status logic:**
- Adds `bitlocker_protection_status` column to `host_disks` (DB
migration).
- When a disk is encrypted and key is escrowed but protection is off,
the host shows "Action required" with a detail message explaining the
issue, instead of misleadingly showing "Verified."
- `protection_status = 2` (unknown) and `NULL` (older orbit hosts) are
treated as protection on for backward compatibility.
- The `profiles_verified` and `profiles_verifying` branches in the
combined profiles+BitLocker summary now handle
`bitlocker_action_required`, counting those hosts as "pending".

Contributor docs updates: https://github.com/fleetdm/fleet/pull/43241
Public docs updates: https://github.com/fleetdm/fleet/pull/43243/changes

# Checklist for submitter

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

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

## Database migrations

- [x] Checked schema for all modified table for columns that will
auto-update timestamps during migration.

## fleetd/orbit/Fleet Desktop

- [x] Verified compatibility with the latest released version of Fleet
(see [Must
rule](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/workflows/fleetd-development-and-release-strategy.md))
- [x] If the change applies to only one platform, confirmed that
`runtime.GOOS` is used as needed to isolate changes
- [x] Verified that fleetd runs on macOS, Linux and Windows
- [x] Verified auto-update works from the released version of component
to the new version (see [tools/tuf/test](../tools/tuf/test/README.md))


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **Bug Fixes**
* Fixed Windows BitLocker encryption/decryption request loop on systems
with secondary drives and auto-unlock.

* **New Features**
* Added BitLocker recovery key rotation capability, allowing safe key
updates without full disk re-encryption.
* Enhanced BitLocker protection status tracking to correctly display
"Action required" when protection is disabled.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-09 18:33:03 -04:00

77 lines
3.2 KiB
Go

package bitlocker
// Volume encryption/decryption status.
//
// Values and their meanings were taken from:
// https://learn.microsoft.com/en-us/windows/win32/secprov/getconversionstatus-win32-encryptablevolume
const (
ConversionStatusFullyDecrypted int32 = 0
ConversionStatusFullyEncrypted int32 = 1
ConversionStatusEncryptionInProgress int32 = 2
ConversionStatusDecryptionInProgress int32 = 3
ConversionStatusEncryptionPaused int32 = 4
ConversionStatusDecryptionPaused int32 = 5
)
const (
// Error codes from Win32_EncryptableVolume WMI methods. The Microsoft docs
// define these as uint32, but the COM VARIANT transport delivers them as
// VT_I4 (signed 32-bit), which go-ole surfaces as int32. The bit patterns
// are identical (e.g., 0x80310019 as uint32 == -2144272327 as int32).
ErrorCodeInvalidArg int32 = -2147024809 // E_INVALIDARG: encryption flags conflict with Group Policy
ErrorCodeIODevice int32 = -2147023779
ErrorCodeDriveIncompatibleVolume int32 = -2144272206
ErrorCodeNoTPMWithPassphrase int32 = -2144272212
ErrorCodePassphraseTooLong int32 = -2144272214
ErrorCodePolicyPassphraseNotAllowed int32 = -2144272278
ErrorCodeNotDecrypted int32 = -2144272327
ErrorCodeInvalidPasswordFormat int32 = -2144272331
ErrorCodeBootableCDOrDVD int32 = -2144272336
ErrorCodeProtectorExists int32 = -2144272335
)
// EncryptionError represents an error that occurs during the encryption
// process.
type EncryptionError struct {
msg string // msg is the error message describing what went wrong.
code int32 // code is the Bitlocker-specific error code.
}
func NewEncryptionError(msg string, code int32) *EncryptionError {
return &EncryptionError{
msg: msg,
code: code,
}
}
// Error returns the error message of the EncryptionError.
// This method makes EncryptionError compatible with the Go built-in error
// interface.
func (e *EncryptionError) Error() string {
return e.msg
}
// Code returns the Bitlocker-specific error code.
// These codes are defined by Microsoft and are used to identify specific types
// of encryption errors.
func (e *EncryptionError) Code() int32 {
return e.code
}
// EncryptionStatus represents the encryption status of a volume as returned by
// the GetConversionStatus method of the Win32_EncryptableVolume class.
type EncryptionStatus struct {
ProtectionStatus int32 // indicates whether the volume and its encryption key are secured.
ConversionStatus int32 // represents the encryption or decryption status of the volume.
EncryptionPercentage string // percentage of the volume that is encrypted.
EncryptionFlags string // flags describing the encryption behavior.
WipingStatus int32 // status of the free space wiping on the volume.
WipingPercentage string // percentage of free space that has been wiped.
}
// VolumeStatus provides the encryption status for a specific drive volume.
// It ties a volume (identified by its drive letter) to its EncryptionStatus.
type VolumeStatus struct {
DriveVolume string // driveVolume is the identifier of the drive (e.g., "C:").
Status *EncryptionStatus // status holds the encryption status of the volume.
}