diff --git a/cfbs.json b/cfbs.json index a9787f2..f8af42b 100644 --- a/cfbs.json +++ b/cfbs.json @@ -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", diff --git a/inventory/inventory-fde/README.md b/inventory/inventory-fde/README.md new file mode 100644 index 0000000..e9d765a --- /dev/null +++ b/inventory/inventory-fde/README.md @@ -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`) diff --git a/inventory/inventory-fde/inventory-fde.cf b/inventory/inventory-fde/inventory-fde.cf new file mode 100644 index 0000000..c7f3ec8 --- /dev/null +++ b/inventory/inventory-fde/inventory-fde.cf @@ -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/ and /dev/) + "_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--- + "_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"; +} diff --git a/inventory/inventory-windows-services.cf b/inventory/inventory-windows-services/inventory-windows-services.cf similarity index 100% rename from inventory/inventory-windows-services.cf rename to inventory/inventory-windows-services/inventory-windows-services.cf