Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion cfbs.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,23 @@
},
"inventory-windows-services": {
"description": "Inventory running Windows services.",
"subdirectory": "inventory",
"subdirectory": "inventory/inventory-windows-services",
"steps": [
"copy inventory-windows-services.cf services/cfbs/inventory-windows-services/",
"policy_files services/cfbs/inventory-windows-services/",
"bundles inventory_windows_services_running"
]
},
"inventory-fde": {
"description": "Inventory full disk encryption status (LUKS, FileVault, BitLocker).",
"tags": ["inventory", "security"],
"subdirectory": "inventory/inventory-fde",
"steps": [
"copy inventory-fde.cf services/cfbs/inventory-fde/",
"policy_files services/cfbs/inventory-fde/",
"bundles inventory_fde:main"
]
},
"library-for-promise-types-in-bash": {
"description": "Library enabling promise types implemented in bash.",
"subdirectory": "libraries/bash",
Expand Down
36 changes: 36 additions & 0 deletions inventory/inventory-fde/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Full disk encryption (FDE) protects data at rest by encrypting entire block devices.
This module detects mounted volumes backed by dm-crypt (LUKS1, LUKS2, or plain dm-crypt) on Linux systems and reports whether all, some, or none of the non-virtual block device filesystems are encrypted.

Detection is performed entirely through virtual filesystem reads (`/sys/block/` and `/proc/mounts`), with no dependency on external commands like `dmsetup` or `findmnt`.

## How it works

1. Enumerates device-mapper block devices from `/sys/block/dm-*`
2. Reads each device's DM subsystem UUID from `/sys/block/dm-N/dm/uuid`
3. Identifies crypt devices by the `CRYPT-` prefix in the UUID
4. Parses `/proc/mounts` to find all non-virtual block device mounts (excluding loop devices)
5. Classifies each mount as encrypted or unencrypted by checking if its device matches a crypt device path

## Inventory

- **Full disk encryption enabled** -- `yes` if all non-virtual block device filesystems are encrypted, `partial` if some are encrypted and some are not, `no` if none are encrypted.
- **Full disk encryption method** -- The encryption type(s) detected, e.g. `LUKS2`, `LUKS1`, `PLAIN`, or `none`. Multiple types are comma-separated if different methods are in use.
- **Full disk encryption volumes** -- List of mountpoints backed by encrypted devices.
- **Unencrypted volumes** -- List of mountpoints on non-virtual block devices that are not encrypted.

## Example

A system with LUKS2-encrypted root but unencrypted `/boot` and `/boot/efi`:

```
$ sudo cf-agent -Kf ./inventory-fde.cf --show-evaluated-vars=inventory_fde
Variable name Variable value Meta tags Comment
inventory_fde:main.fde_enabled partial source=promise,inventory,attribute_name=Full disk encryption enabled
inventory_fde:main.fde_method LUKS2 source=promise,inventory,attribute_name=Full disk encryption method
inventory_fde:main.fde_volumes {"/"} source=promise,inventory,attribute_name=Full disk encryption volumes
inventory_fde:main.unencrypted_volumes {"/boot","/boot/efi"} source=promise,inventory,attribute_name=Unencrypted volumes
```

## Platform

- Linux only (requires `/sys/block/` and `/proc/mounts`)
143 changes: 143 additions & 0 deletions inventory/inventory-fde/inventory-fde.cf
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
body file control
{
namespace => "inventory_fde";
}

bundle agent main
# @brief Inventory full disk encryption status
# @inventory Full disk encryption enabled - Whether all non-virtual mounted filesystems use dm-crypt encryption (yes, partial, or no).
# @inventory Full disk encryption method - The encryption type(s) in use, e.g. LUKS2, LUKS1, PLAIN, or none.
# @inventory Full disk encryption volumes - List of mountpoints backed by encrypted devices, e.g. /.
# @inventory Unencrypted volumes - List of mountpoints on non-virtual block devices that are not encrypted, e.g. /boot, /boot/efi.
{
classes:
linux::
# Flag each dm device that has a CRYPT uuid
"_dm_is_crypt_${_dm_devices}"
expression => regcmp("CRYPT-.*", "${_dm_uuid[${_dm_devices}]}");

# Classify each mount: real block device? (starts with /dev/, not a loop device)
"_is_real_block_${_mnt_idx}"
expression => regcmp("/dev/(?!loop)\S+", "${_mnt_data[${_mnt_idx}][0]}");

# Classify each real block mount: is device in the crypt paths list?
"_is_encrypted_${_mnt_idx}"
expression => regcmp("(${_crypt_paths_regex})", "${_mnt_data[${_mnt_idx}][0]}"),
if => canonify("_is_real_block_${_mnt_idx}");

# Summary classes
"_has_encrypted"
expression => isgreaterthan(length(_encrypted_mountpoints), 0);
"_has_unencrypted"
expression => isgreaterthan(length(_unencrypted_mountpoints), 0);

vars:
linux::
# Enumerate all device-mapper block devices
"_dm_devices" slist => lsdir("/sys/block", "dm-\d+", false);

# Read the DM subsystem uuid and name for each dm device
"_dm_uuid[${_dm_devices}]"
string => readfile("/sys/block/${_dm_devices}/dm/uuid"),
if => fileexists("/sys/block/${_dm_devices}/dm/uuid");
"_dm_name[${_dm_devices}]"
string => readfile("/sys/block/${_dm_devices}/dm/name"),
if => fileexists("/sys/block/${_dm_devices}/dm/name");

# Build list of crypt device paths (both /dev/mapper/<name> and /dev/<dm-N>)
"_crypt_mapper_path[${_dm_devices}]"
string => "/dev/mapper/${_dm_name[${_dm_devices}]}",
if => canonify("_dm_is_crypt_${_dm_devices}");
"_crypt_dm_path[${_dm_devices}]"
string => "/dev/${_dm_devices}",
if => canonify("_dm_is_crypt_${_dm_devices}");
"_all_crypt_paths"
slist => { getvalues(_crypt_mapper_path), getvalues(_crypt_dm_path) };

# Build a regex alternation of all crypt device paths for matching
"_crypt_paths_regex"
string => join("|", maplist(regex_replace("$(this)", "([./])", "\\\1", "g"), _all_crypt_paths));

# Extract the encryption type for crypt devices
# UUID format: CRYPT-<TYPE>-<uuid>-<name>
"_dm_crypt_type[${_dm_devices}]"
string => regex_replace("${_dm_uuid[${_dm_devices}]}", "^CRYPT-([^-]+)-.*", "\1", ""),
if => canonify("_dm_is_crypt_${_dm_devices}");

# Parse /proc/mounts into indexed array
# Columns: 0=device, 1=mountpoint, 2=fstype, 3=options, 4=dump, 5=pass
"_n_mnt_lines"
int => readstringarrayidx("_mnt_data", "/proc/mounts", "\s*#[^\n]*", "\s+", inf, inf);
"_mnt_idx" slist => getindices(_mnt_data);

# Collect all real block device mountpoints
"_all_real_mountpoint[${_mnt_idx}]"
string => "${_mnt_data[${_mnt_idx}][1]}",
if => canonify("_is_real_block_${_mnt_idx}");

# Collect encrypted mountpoints (subset of real block mounts)
"_encrypted_mountpoint[${_mnt_idx}]"
string => "${_mnt_data[${_mnt_idx}][1]}",
if => and(
canonify("_is_real_block_${_mnt_idx}"),
canonify("_is_encrypted_${_mnt_idx}")
);

# Derive unencrypted mountpoints as the difference
"_all_real_mountpoints" slist => getvalues(_all_real_mountpoint);
"_encrypted_mountpoints" slist => getvalues(_encrypted_mountpoint);
"_unencrypted_mountpoints"
slist => difference(_all_real_mountpoints, _encrypted_mountpoints);

# Inventory: full encryption (encrypted volumes exist, no unencrypted ones)
_has_encrypted.!_has_unencrypted::
"fde_enabled"
string => "yes",
meta => { "inventory", "attribute_name=Full disk encryption enabled" };

# Inventory: partial encryption
_has_encrypted._has_unencrypted::
"fde_enabled"
string => "partial",
meta => { "inventory", "attribute_name=Full disk encryption enabled" };

# Inventory: no encryption
linux.!_has_encrypted::
"fde_enabled"
string => "no",
meta => { "inventory", "attribute_name=Full disk encryption enabled" };

# Method and volume details
_has_encrypted::
"fde_method"
string => join(", ", unique(getvalues(_dm_crypt_type))),
meta => { "inventory", "attribute_name=Full disk encryption method" };
"fde_volumes"
slist => unique(_encrypted_mountpoints),
meta => { "inventory", "attribute_name=Full disk encryption volumes" };

linux.!_has_encrypted::
"fde_method"
string => "none",
meta => { "inventory", "attribute_name=Full disk encryption method" };

_has_unencrypted::
"unencrypted_volumes"
slist => unique(_unencrypted_mountpoints),
meta => { "inventory", "attribute_name=Unencrypted volumes" };

reports:
!linux.verbose_mode::
"$(this.promise_filename): $(this.namespace):$(this.bundle) is currently only instrumented for Linux. Please consider making a pull request or filing a ticket to request your specific platform.";
}

body file control
{
namespace => "default";
}

bundle agent __main__
{
methods:
"inventory_fde:main";
}