Create new Fleet osquery extension table to read escrowed FileVault key (#12198)

This commit is contained in:
gillespi314
2023-06-15 10:23:59 -05:00
committed by GitHub
parent 5f30add863
commit 87fe00db71
8 changed files with 576 additions and 351 deletions
+2
View File
@@ -0,0 +1,2 @@
- Fixed bug when reading filevault key in osquery and created new Fleet osquery
extension table to read the file directly rather than via filelines table.
+2
View File
@@ -8,6 +8,7 @@ import (
"github.com/fleetdm/fleet/v4/orbit/pkg/table/diskutil/apfs"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/diskutil/corestorage"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/dscl"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/filevault_prk"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/firmware_eficheck_integrity_check"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/nvram_info"
"github.com/fleetdm/fleet/v4/orbit/pkg/table/pmset"
@@ -44,6 +45,7 @@ func PlatformTables() []osquery.OsqueryPlugin {
table.NewPlugin("apfs_physical_stores", apfs.PhysicalStoresColumns(), apfs.PhysicalStoresGenerate),
table.NewPlugin("corestorage_logical_volumes", corestorage.LogicalVolumesColumns(), corestorage.LogicalVolumesGenerate),
table.NewPlugin("corestorage_logical_volume_families", corestorage.LogicalVolumeFamiliesColumns(), corestorage.LogicalVolumeFamiliesGenerate),
table.NewPlugin("filevault_prk", filevault_prk.Columns(), filevault_prk.Generate),
// Macadmins extension tables
table.NewPlugin("filevault_users", filevaultusers.FileVaultUsersColumns(), filevaultusers.FileVaultUsersGenerate),
@@ -0,0 +1,38 @@
//go:build darwin
// +build darwin
package filevault_prk
import (
"context"
"encoding/base64"
"errors"
"fmt"
"io/fs"
"os"
"github.com/osquery/osquery-go/plugin/table"
)
// Columns is the schema of the table.
func Columns() []table.ColumnDefinition {
return []table.ColumnDefinition{
table.TextColumn("base64_encrypted"),
}
}
// Generate is called to return the results for the table at query time.
//
// Constraints for generating can be retrieved from the queryContext.
func Generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) {
encryptedKey, err := os.ReadFile("/var/db/FileVaultPRK.dat")
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return nil, nil
}
return nil, fmt.Errorf("generate failed: %w", err)
}
encoded := base64.StdEncoding.EncodeToString(encryptedKey)
return []map[string]string{{"base64_encrypted": encoded}}, nil
}
+316 -297
View File
@@ -27212,73 +27212,6 @@
],
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/yum_sources.yml"
},
{
"name": "apfs_physical_stores",
"platforms": [
"darwin"
],
"description": "Information about APFS physical stores from the `diskutil apfs list -plist` command.",
"columns": [
{
"name": "container_uuid",
"type": "text",
"required": false,
"description": "The UUID of the APFS Contianer"
},
{
"name": "container_designated_physical_store",
"type": "text",
"required": false,
"description": "The disk displayed as the backing store of the container. There may be multiple,\nuse `apfs_physical_stores` to see all actual physical stores\n"
},
{
"name": "container_reference",
"type": "text",
"required": false,
"description": "The current reference for the APFS container, e.g. \"disk3\""
},
{
"name": "container_fusion",
"type": "text",
"required": false,
"description": "Whether this container is on a \"fusion drive\" (i.e. SSHD)"
},
{
"name": "container_capacity_ceiling",
"type": "bigint",
"required": false,
"description": "The total amount of space in the container"
},
{
"name": "container_capacity_free",
"type": "bigint",
"required": false,
"description": "The amount of remaining free space in the container"
},
{
"name": "uuid",
"type": "text",
"required": false,
"description": "The UUID of the physical store"
},
{
"name": "identifier",
"type": "text",
"required": false,
"description": "The current identifier of the physical store (e.g. disk1s2)"
},
{
"name": "size",
"type": "bigint",
"required": false,
"description": "The size of the physical store in byptes"
}
],
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).",
"evented": false,
"url": "https://fleetdm.com/tables/apfs_physical_stores",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/apfs_physical_stores.yml"
},
{
"name": "apfs_volumes",
"platforms": [
@@ -27394,6 +27327,73 @@
"url": "https://fleetdm.com/tables/apfs_volumes",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/apfs_volumes.yml"
},
{
"name": "apfs_physical_stores",
"platforms": [
"darwin"
],
"description": "Information about APFS physical stores from the `diskutil apfs list -plist` command.",
"columns": [
{
"name": "container_uuid",
"type": "text",
"required": false,
"description": "The UUID of the APFS Contianer"
},
{
"name": "container_designated_physical_store",
"type": "text",
"required": false,
"description": "The disk displayed as the backing store of the container. There may be multiple,\nuse `apfs_physical_stores` to see all actual physical stores\n"
},
{
"name": "container_reference",
"type": "text",
"required": false,
"description": "The current reference for the APFS container, e.g. \"disk3\""
},
{
"name": "container_fusion",
"type": "text",
"required": false,
"description": "Whether this container is on a \"fusion drive\" (i.e. SSHD)"
},
{
"name": "container_capacity_ceiling",
"type": "bigint",
"required": false,
"description": "The total amount of space in the container"
},
{
"name": "container_capacity_free",
"type": "bigint",
"required": false,
"description": "The amount of remaining free space in the container"
},
{
"name": "uuid",
"type": "text",
"required": false,
"description": "The UUID of the physical store"
},
{
"name": "identifier",
"type": "text",
"required": false,
"description": "The current identifier of the physical store (e.g. disk1s2)"
},
{
"name": "size",
"type": "bigint",
"required": false,
"description": "The size of the physical store in byptes"
}
],
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).",
"evented": false,
"url": "https://fleetdm.com/tables/apfs_physical_stores",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/apfs_physical_stores.yml"
},
{
"name": "authdb",
"platforms": [
@@ -27820,6 +27820,51 @@
"url": "https://fleetdm.com/tables/dscl",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/dscl.yml"
},
{
"name": "filevault_prk",
"platforms": [
"darwin"
],
"description": "Returns contents of `/var/db/FileVaultPRK.dat`.",
"columns": [
{
"name": "base64_encrypted",
"type": "text",
"required": false,
"description": "The base64-encoded contents of the encrypted FileVault personal recovery key stored at `/var/db/FileVaultPRK.dat` (see also https://developer.apple.com/documentation/devicemanagement/fderecoverykeyescrow)"
}
],
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).",
"evented": false,
"url": "https://fleetdm.com/tables/filevault_prk",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/filevault_prk.yml"
},
{
"name": "filevault_users",
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).",
"description": "Information on the users able to unlock the current boot volume if protected with FileVault.",
"platforms": [
"darwin"
],
"evented": false,
"examples": "List the usernames able to unlock and boot a computer protected by FileVault, joined to [users.username](http://fleetdm.com/tables/users) to obtain the description of the operating system account that owns it.\n```\nSELECT fu.username, u.description FROM filevault_users fu JOIN users u ON fu.uuid=u.uuid;\n```",
"columns": [
{
"name": "username",
"description": "Username of the FileVault user.",
"required": false,
"type": "text"
},
{
"name": "uuid",
"description": "UUID of the FileVault user, which can be joined to [users.uuid](http://fleetdm.com/tables/users).",
"required": false,
"type": "text"
}
],
"url": "https://fleetdm.com/tables/filevault_users",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/filevault_users.yml"
},
{
"name": "file_lines",
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).",
@@ -27849,30 +27894,65 @@
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/file_lines.yml"
},
{
"name": "filevault_users",
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).",
"description": "Information on the users able to unlock the current boot volume if protected with FileVault.",
"name": "firmware_eficheck_integrity_check",
"platforms": [
"darwin"
],
"evented": false,
"examples": "List the usernames able to unlock and boot a computer protected by FileVault, joined to [users.username](http://fleetdm.com/tables/users) to obtain the description of the operating system account that owns it.\n```\nSELECT fu.username, u.description FROM filevault_users fu JOIN users u ON fu.uuid=u.uuid;\n```",
"description": "Performs eficheck's integrity check on macOS Intel T1 chips (CIS 5.9).",
"columns": [
{
"name": "username",
"description": "Username of the FileVault user.",
"name": "chip",
"type": "text",
"required": false,
"type": "text"
"description": "Contains the chip type, values are \"apple\", \"intel-t1\" and \"intel-t2\".\nIf chip type is \"apple\" or \"intel-t2\" then no eficheck integrity check is executed.\n"
},
{
"name": "uuid",
"description": "UUID of the FileVault user, which can be joined to [users.uuid](http://fleetdm.com/tables/users).",
"name": "output",
"type": "text",
"required": false,
"type": "text"
"description": "Output of the `/usr/libexec/firmwarecheckers/eficheck/eficheck --integrity-check` command.\nThis value is only valid when chip is \"intel-t1\".\n"
}
],
"url": "https://fleetdm.com/tables/filevault_users",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/filevault_users.yml"
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).",
"evented": false,
"url": "https://fleetdm.com/tables/firmware_eficheck_integrity_check",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/firmware_eficheck_integrity_check.yml"
},
{
"name": "geolocation",
"evented": false,
"platforms": [
"chrome"
],
"description": "Last reported geolocation",
"columns": [
{
"name": "ip",
"type": "text",
"required": false,
"description": "IP address"
},
{
"name": "city",
"type": "text",
"required": false,
"description": "City"
},
{
"name": "country",
"type": "text",
"required": false,
"description": "Country"
},
{
"name": "region",
"type": "text",
"required": false,
"description": "Region"
}
],
"url": "https://fleetdm.com/tables/geolocation",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/geolocation.yml"
},
{
"name": "google_chrome_profiles",
@@ -27914,42 +27994,6 @@
"url": "https://fleetdm.com/tables/google_chrome_profiles",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/google_chrome_profiles.yml"
},
{
"name": "geolocation",
"evented": false,
"platforms": [
"chrome"
],
"description": "Last reported geolocation",
"columns": [
{
"name": "ip",
"type": "text",
"required": false,
"description": "IP address"
},
{
"name": "city",
"type": "text",
"required": false,
"description": "City"
},
{
"name": "country",
"type": "text",
"required": false,
"description": "Country"
},
{
"name": "region",
"type": "text",
"required": false,
"description": "Region"
}
],
"url": "https://fleetdm.com/tables/geolocation",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/geolocation.yml"
},
{
"name": "icloud_private_relay",
"platforms": [
@@ -27969,31 +28013,6 @@
"url": "https://fleetdm.com/tables/icloud_private_relay",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/icloud_private_relay.yml"
},
{
"name": "firmware_eficheck_integrity_check",
"platforms": [
"darwin"
],
"description": "Performs eficheck's integrity check on macOS Intel T1 chips (CIS 5.9).",
"columns": [
{
"name": "chip",
"type": "text",
"required": false,
"description": "Contains the chip type, values are \"apple\", \"intel-t1\" and \"intel-t2\".\nIf chip type is \"apple\" or \"intel-t2\" then no eficheck integrity check is executed.\n"
},
{
"name": "output",
"type": "text",
"required": false,
"description": "Output of the `/usr/libexec/firmwarecheckers/eficheck/eficheck --integrity-check` command.\nThis value is only valid when chip is \"intel-t1\".\n"
}
],
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).",
"evented": false,
"url": "https://fleetdm.com/tables/firmware_eficheck_integrity_check",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/firmware_eficheck_integrity_check.yml"
},
{
"name": "macadmins_unified_log",
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).",
@@ -28344,44 +28363,6 @@
"url": "https://fleetdm.com/tables/mdm_bridge",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/mdm_bridge.yml"
},
{
"name": "munki_installs",
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).<p> Code based on work by [Kolide](https://github.com/kolide/launcher).",
"description": "Software packages and other items [Munki](https://github.com/munki/munki) is managing.",
"platforms": [
"darwin"
],
"evented": false,
"examples": "See the version of software that has been deployed by Munki.\n```\nSELECT name, installed_version FROM munki_installs WHERE installed='true';\n```",
"columns": [
{
"name": "end_time",
"description": "The end time of the last Munki run.",
"required": false,
"type": "text"
},
{
"name": "installed",
"description": "Shows if Munki installed an item (true) or if it is simply available but not installed (false).",
"required": false,
"type": "text"
},
{
"name": "installed_version",
"description": "The version number of installed items.",
"required": false,
"type": "text"
},
{
"name": "name",
"description": "The name of items managed by Munki.",
"required": false,
"type": "text"
}
],
"url": "https://fleetdm.com/tables/munki_installs",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/munki_installs.yml"
},
{
"name": "munki_info",
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).<p> Code based on work by [Kolide](https://github.com/kolide/launcher).",
@@ -28450,6 +28431,44 @@
"url": "https://fleetdm.com/tables/munki_info",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/munki_info.yml"
},
{
"name": "munki_installs",
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).<p> Code based on work by [Kolide](https://github.com/kolide/launcher).",
"description": "Software packages and other items [Munki](https://github.com/munki/munki) is managing.",
"platforms": [
"darwin"
],
"evented": false,
"examples": "See the version of software that has been deployed by Munki.\n```\nSELECT name, installed_version FROM munki_installs WHERE installed='true';\n```",
"columns": [
{
"name": "end_time",
"description": "The end time of the last Munki run.",
"required": false,
"type": "text"
},
{
"name": "installed",
"description": "Shows if Munki installed an item (true) or if it is simply available but not installed (false).",
"required": false,
"type": "text"
},
{
"name": "installed_version",
"description": "The version number of installed items.",
"required": false,
"type": "text"
},
{
"name": "name",
"description": "The name of items managed by Munki.",
"required": false,
"type": "text"
}
],
"url": "https://fleetdm.com/tables/munki_installs",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/munki_installs.yml"
},
{
"name": "network_interfaces",
"evented": false,
@@ -28582,6 +28601,124 @@
"url": "https://fleetdm.com/tables/pmset",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/pmset.yml"
},
{
"name": "puppet_info",
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).",
"description": "Information on the last [Puppet](https://puppet.com/) run. This table uses data from the `last_run_report` that Puppet creates.",
"platforms": [
"darwin",
"windows",
"linux"
],
"evented": false,
"examples": "List all the information available about the last Puppet run.\n```\nSELECT * FROM puppet_info;\n```",
"columns": [
{
"name": "cached_catalog_status",
"description": "The status of Puppet catalogs cached on the system.",
"required": false,
"type": "text"
},
{
"name": "catalog_uuid",
"description": "The [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) of the catalog downloaded by Puppet.",
"required": false,
"type": "text"
},
{
"name": "code_id",
"description": "The `code_id` links the catalog with the compile-time version of file resources using the `puppet:///` URI.",
"required": false,
"type": "text"
},
{
"name": "configuration_version",
"description": "The version of the Puppet configuration.",
"required": false,
"type": "text"
},
{
"name": "corrective_change",
"description": "A corrective change is triggered when Puppet detects a discrepency between the current state and the expected state of a value.",
"required": false,
"type": "text"
},
{
"name": "environment",
"description": "The environment name.",
"required": false,
"type": "text"
},
{
"name": "host",
"description": "The host on which Puppet is used.",
"required": false,
"type": "text"
},
{
"name": "kind",
"description": "Kind of Puppet run.",
"required": false,
"type": "text"
},
{
"name": "master_used",
"description": "The Puppet server used.",
"required": false,
"type": "text"
},
{
"name": "noop",
"description": "Indicates if Puppet was run in [noop](https://puppet.com/docs/puppet/latest/metaparameter.html#noop) mode.",
"required": false,
"type": "text"
},
{
"name": "noop_prending",
"description": "Items pending from a [noop](https://puppet.com/docs/puppet/latest/metaparameter.html#noop) run.",
"required": false,
"type": "text"
},
{
"name": "puppet_version",
"description": "The version of Puppet used during the last run.",
"required": false,
"type": "text"
},
{
"name": "report_format",
"description": "The format the Puppet report was exported as.",
"required": false,
"type": "text"
},
{
"name": "status",
"description": "The status of Puppet on this system.",
"required": false,
"type": "text"
},
{
"name": "time",
"description": "The time of the last Puppet run.",
"required": false,
"type": "text"
},
{
"name": "transaction_completed",
"description": "Indicates if the transaction completed or not.",
"required": false,
"type": "text"
},
{
"name": "transaction_uuid",
"description": "The [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) of the transaction.",
"required": false,
"type": "text"
}
],
"url": "https://fleetdm.com/tables/puppet_info",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/puppet_info.yml"
},
{
"name": "puppet_logs",
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).",
@@ -28728,124 +28865,6 @@
"url": "https://fleetdm.com/tables/puppet_state",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/puppet_state.yml"
},
{
"name": "puppet_info",
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).",
"description": "Information on the last [Puppet](https://puppet.com/) run. This table uses data from the `last_run_report` that Puppet creates.",
"platforms": [
"darwin",
"windows",
"linux"
],
"evented": false,
"examples": "List all the information available about the last Puppet run.\n```\nSELECT * FROM puppet_info;\n```",
"columns": [
{
"name": "cached_catalog_status",
"description": "The status of Puppet catalogs cached on the system.",
"required": false,
"type": "text"
},
{
"name": "catalog_uuid",
"description": "The [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) of the catalog downloaded by Puppet.",
"required": false,
"type": "text"
},
{
"name": "code_id",
"description": "The `code_id` links the catalog with the compile-time version of file resources using the `puppet:///` URI.",
"required": false,
"type": "text"
},
{
"name": "configuration_version",
"description": "The version of the Puppet configuration.",
"required": false,
"type": "text"
},
{
"name": "corrective_change",
"description": "A corrective change is triggered when Puppet detects a discrepency between the current state and the expected state of a value.",
"required": false,
"type": "text"
},
{
"name": "environment",
"description": "The environment name.",
"required": false,
"type": "text"
},
{
"name": "host",
"description": "The host on which Puppet is used.",
"required": false,
"type": "text"
},
{
"name": "kind",
"description": "Kind of Puppet run.",
"required": false,
"type": "text"
},
{
"name": "master_used",
"description": "The Puppet server used.",
"required": false,
"type": "text"
},
{
"name": "noop",
"description": "Indicates if Puppet was run in [noop](https://puppet.com/docs/puppet/latest/metaparameter.html#noop) mode.",
"required": false,
"type": "text"
},
{
"name": "noop_prending",
"description": "Items pending from a [noop](https://puppet.com/docs/puppet/latest/metaparameter.html#noop) run.",
"required": false,
"type": "text"
},
{
"name": "puppet_version",
"description": "The version of Puppet used during the last run.",
"required": false,
"type": "text"
},
{
"name": "report_format",
"description": "The format the Puppet report was exported as.",
"required": false,
"type": "text"
},
{
"name": "status",
"description": "The status of Puppet on this system.",
"required": false,
"type": "text"
},
{
"name": "time",
"description": "The time of the last Puppet run.",
"required": false,
"type": "text"
},
{
"name": "transaction_completed",
"description": "Indicates if the transaction completed or not.",
"required": false,
"type": "text"
},
{
"name": "transaction_uuid",
"description": "The [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) of the transaction.",
"required": false,
"type": "text"
}
],
"url": "https://fleetdm.com/tables/puppet_info",
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/puppet_info.yml"
},
{
"name": "pwd_policy",
"platforms": [
+11
View File
@@ -0,0 +1,11 @@
name: filevault_prk
platforms:
- darwin
description: Returns contents of `/var/db/FileVaultPRK.dat`.
columns:
- name: base64_encrypted
type: text
required: false
description: The base64-encoded contents of the encrypted FileVault personal recovery key stored at `/var/db/FileVaultPRK.dat` (see also https://developer.apple.com/documentation/devicemanagement/fderecoverykeyescrow)
notes: This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).
evented: false
+2 -1
View File
@@ -1433,7 +1433,8 @@ func TestMDMQueries(t *testing.T) {
discoveryTable string
}{
{"fleet_detail_query_mdm_config_profiles_darwin", "macos_profiles"},
{"fleet_detail_query_mdm_disk_encryption_key_darwin", "file_lines"},
{"fleet_detail_query_mdm_disk_encryption_key_file_darwin", "filevault_prk"},
{"fleet_detail_query_mdm_disk_encryption_key_file_lines_darwin", "file_lines"},
}
mdmEnabled := true
+106 -31
View File
@@ -2,6 +2,8 @@ package osquery_utils
import (
"context"
"encoding/base64"
"encoding/hex"
"fmt"
"net"
"net/url"
@@ -575,37 +577,57 @@ var extraDetailQueries = map[string]DetailQuery{
// They are only sent to the device when Fleet's MDM is on and properly
// configured
var mdmQueries = map[string]DetailQuery{
"mdm_disk_encryption_key_darwin": {
// This query has two pre-requisites:
//
// 1. FileVault must be enabled with a personal recovery key.
// 2. The "FileVault Recovery Key Escrow" profile must be configured
// in the host.
//
// This file is safe to access and well [documented by Apple][1]:
//
// > If FileVault is enabled after this payload is installed on the system,
// > the FileVault PRK will be encrypted with the specified certificate,
// > wrapped with a CMS envelope and stored at /var/db/FileVaultPRK.dat. The
// > encrypted data will be made available to the MDM server as part of the
// > SecurityInfo command.
// >
// > Alternatively, if a site uses its own administration
// > software, it can extract the PRK from the foregoing
// > location at any time.
//
// [1]: https://developer.apple.com/documentation/devicemanagement/fderecoverykeyescrow
Query: fmt.Sprintf(`SELECT to_base64(group_concat(line, x'0a')) as filevault_key, COALESCE((%s), 0) as encrypted FROM file_lines WHERE path='/var/db/FileVaultPRK.dat'`, usesMacOSDiskEncryptionQuery),
Platforms: []string{"darwin"},
DirectIngestFunc: directIngestDiskEncryptionKeyDarwin,
Discovery: discoveryTable("file_lines"),
},
"mdm_config_profiles_darwin": {
Query: `SELECT display_name, identifier, install_date FROM macos_profiles where type = "Configuration";`,
Platforms: []string{"darwin"},
DirectIngestFunc: directIngestMacOSProfiles,
Discovery: discoveryTable("macos_profiles"),
},
// There are two mutually-exclusive queries used to read the FileVaultPRK depending on which
// extension tables are discovered on the agent. The preferred query uses the newer custom
// `filevault_prk` extension table rather than the macadmins `file_lines` table. It is preferred
// because the `file_lines` implementation uses bufio.ScanLines which drops end of line
// characters.
//
// Both queries depend on the same pre-requisites:
//
// 1. FileVault must be enabled with a personal recovery key.
// 2. The "FileVault Recovery Key Escrow" profile must be configured
// in the host.
//
// This file is safe to access and well [documented by Apple][1]:
//
// > If FileVault is enabled after this payload is installed on the system,
// > the FileVault PRK will be encrypted with the specified certificate,
// > wrapped with a CMS envelope and stored at /var/db/FileVaultPRK.dat. The
// > encrypted data will be made available to the MDM server as part of the
// > SecurityInfo command.
// >
// > Alternatively, if a site uses its own administration
// > software, it can extract the PRK from the foregoing
// > location at any time.
//
// [1]: https://developer.apple.com/documentation/devicemanagement/fderecoverykeyescrow
"mdm_disk_encryption_key_file_lines_darwin": {
Query: fmt.Sprintf(`
WITH
de AS (SELECT IFNULL((%s), 0) as encrypted),
fl AS (SELECT line FROM file_lines WHERE path = '/var/db/FileVaultPRK.dat')
SELECT encrypted, hex(line) as hex_line FROM de LEFT JOIN fl;`, usesMacOSDiskEncryptionQuery),
Platforms: []string{"darwin"},
DirectIngestFunc: directIngestDiskEncryptionKeyFileLinesDarwin,
Discovery: fmt.Sprintf(`SELECT 1 WHERE EXISTS (%s) AND NOT EXISTS (%s);`, strings.Trim(discoveryTable("file_lines"), ";"), strings.Trim(discoveryTable("filevault_prk"), ";")),
},
"mdm_disk_encryption_key_file_darwin": {
Query: fmt.Sprintf(`
WITH
de AS (SELECT IFNULL((%s), 0) as encrypted),
fv AS (SELECT base64_encrypted as filevault_key FROM filevault_prk)
SELECT encrypted, filevault_key FROM de LEFT JOIN fv;`, usesMacOSDiskEncryptionQuery),
Platforms: []string{"darwin"},
DirectIngestFunc: directIngestDiskEncryptionKeyFileDarwin,
Discovery: discoveryTable("filevault_prk"),
},
}
// discoveryTable returns a query to determine whether a table exists or not.
@@ -1373,7 +1395,9 @@ func directIngestDiskEncryption(ctx context.Context, logger log.Logger, host *fl
return ds.SetOrUpdateHostDisksEncryption(ctx, host.ID, encrypted)
}
func directIngestDiskEncryptionKeyDarwin(
// directIngestDiskEncryptionKeyFileDarwin ingests the FileVault key from the `filevault_prk`
// extension table. It is the preferred method when a host has the extension table available.
func directIngestDiskEncryptionKeyFileDarwin(
ctx context.Context,
logger log.Logger,
host *fleet.Host,
@@ -1384,7 +1408,7 @@ func directIngestDiskEncryptionKeyDarwin(
// assume the extension is not there
level.Debug(logger).Log(
"component", "service",
"method", "directIngestDiskEncryptionKeyDarwin",
"method", "directIngestDiskEncryptionKeyFileDarwin",
"msg", "no rows or failed",
"host", host.Hostname,
)
@@ -1394,16 +1418,16 @@ func directIngestDiskEncryptionKeyDarwin(
if len(rows) > 1 {
level.Debug(logger).Log(
"component", "service",
"method", "directIngestDiskEncryptionKeyDarwin",
"msg", fmt.Sprintf("/var/db/FileVaultPRK.dat should have a single line, but got %d", len(rows)),
"method", "directIngestDiskEncryptionKeyFileDarwin",
"msg", fmt.Sprintf("filevault_prk should have a single row, but got %d", len(rows)),
"host", host.Hostname,
)
}
if rows[0]["encrypted"] == "0" {
if rows[0]["encrypted"] != "1" {
level.Debug(logger).Log(
"component", "service",
"method", "directIngestDiskEncryptionKeyDarwin",
"method", "directIngestDiskEncryptionKeyFileDarwin",
"msg", "host does not use disk encryption",
"host", host.Hostname,
)
@@ -1415,6 +1439,57 @@ func directIngestDiskEncryptionKeyDarwin(
return ds.SetOrUpdateHostDiskEncryptionKey(ctx, host.ID, rows[0]["filevault_key"])
}
// directIngestDiskEncryptionKeyFileLinesDarwin ingests the FileVault key from the `file_lines`
// extension table. It is the fallback method in cases where the preferred `filevault_prk` extension
// table is not available on the host.
func directIngestDiskEncryptionKeyFileLinesDarwin(
ctx context.Context,
logger log.Logger,
host *fleet.Host,
ds fleet.Datastore,
rows []map[string]string,
) error {
if len(rows) == 0 {
// assume the extension is not there
level.Debug(logger).Log(
"component", "service",
"method", "directIngestDiskEncryptionKeyFileLinesDarwin",
"msg", "no rows or failed",
"host", host.Hostname,
)
return nil
}
var hexLines []string
for _, row := range rows {
if row["encrypted"] != "1" {
level.Debug(logger).Log(
"component", "service",
"method", "directIngestDiskEncryptionKeyDarwin",
"msg", "host does not use disk encryption",
"host", host.Hostname,
)
return nil
}
hexLines = append(hexLines, row["hex_line"])
}
// We concatenate the lines in Go rather than using SQL `group_concat` because the order in
// which SQL appends the lines is not deterministic, nor guaranteed to be the right order.
// We assume that hexadecimal 0A (i.e. new line) was the delimiter used to split all lines;
// however, there are edge cases where this will not be true. It is a known limitation
// with the `file_lines` extension table and its reliance on bufio.ScanLines that carriage
// returns will be lost if the source file contains hexadecimal 0D0A (i.e. carriage
// return preceding new line). In such cases, the stored key will be incorrect.
b, err := hex.DecodeString(strings.Join(hexLines, "0A"))
if err != nil {
return ctxerr.Wrap(ctx, err, "decoding hex string")
}
// it's okay if the key comes empty, this can happen and if the disk is
// encrypted it means we need to reset the encryption key
return ds.SetOrUpdateHostDiskEncryptionKey(ctx, host.ID, base64.StdEncoding.EncodeToString(b))
}
func directIngestMacOSProfiles(
ctx context.Context,
logger log.Logger,
+99 -22
View File
@@ -1,7 +1,11 @@
package osquery_utils
import (
"bufio"
"bytes"
"context"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
@@ -541,7 +545,6 @@ func TestDirectIngestMDMMac(t *testing.T) {
}
})
}
}
func TestDirectIngestMDMWindows(t *testing.T) {
@@ -1007,37 +1010,111 @@ func TestDirectIngestDiskEncryptionKeyDarwin(t *testing.T) {
ds := new(mock.Store)
ctx := context.Background()
logger := log.NewNopLogger()
wantKey := "OTM5ODRDQTYtOUY1Mi00NERELTkxOUEtMDlBN0ZBOUUzNUY5Cg=="
host := &fleet.Host{ID: 1}
var wantKey string
mockFileLines := func(wantKey string, wantEncrypted string) []map[string]string {
var output []map[string]string
scanner := bufio.NewScanner(bytes.NewBuffer([]byte(wantKey)))
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := scanner.Text()
item := make(map[string]string)
item["hex_line"] = hex.EncodeToString([]byte(line))
item["encrypted"] = wantEncrypted
output = append(output, item)
}
return output
}
mockFilevaultPRK := func(wantKey string, wantEncrypted string) []map[string]string {
return []map[string]string{
{"filevault_key": base64.StdEncoding.EncodeToString([]byte(wantKey)), "encrypted": wantEncrypted},
}
}
ds.SetOrUpdateHostDiskEncryptionKeyFunc = func(ctx context.Context, hostID uint, encryptedBase64Key string) error {
require.Empty(t, encryptedBase64Key)
require.Equal(t, host.ID, hostID)
if base64.StdEncoding.EncodeToString([]byte(wantKey)) != encryptedBase64Key {
return errors.New("key mismatch")
}
if host.ID != hostID {
return errors.New("host ID mismatch")
}
return nil
}
err := directIngestDiskEncryptionKeyDarwin(ctx, logger, host, ds, []map[string]string{})
require.NoError(t, err)
require.False(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
t.Run("empty key", func(t *testing.T) {
err := directIngestDiskEncryptionKeyFileLinesDarwin(ctx, logger, host, ds, []map[string]string{})
require.NoError(t, err)
require.False(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
err = directIngestDiskEncryptionKeyDarwin(ctx, logger, host, ds, []map[string]string{{"encrypted": "0"}})
require.NoError(t, err)
require.False(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
err = directIngestDiskEncryptionKeyFileDarwin(ctx, logger, host, ds, []map[string]string{})
require.NoError(t, err)
require.False(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
err = directIngestDiskEncryptionKeyDarwin(ctx, logger, host, ds, []map[string]string{{"filevault_key": ""}})
require.NoError(t, err)
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
err = directIngestDiskEncryptionKeyFileLinesDarwin(ctx, logger, host, ds, []map[string]string{{"encrypted": "0"}})
require.NoError(t, err)
require.False(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
ds.SetOrUpdateHostDiskEncryptionKeyFunc = func(ctx context.Context, hostID uint, encryptedBase64Key string) error {
require.Equal(t, wantKey, encryptedBase64Key)
require.Equal(t, host.ID, hostID)
return nil
}
err = directIngestDiskEncryptionKeyFileDarwin(ctx, logger, host, ds, []map[string]string{{"encrypted": "0"}})
require.NoError(t, err)
require.False(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
err = directIngestDiskEncryptionKeyDarwin(ctx, logger, host, ds, []map[string]string{{"filevault_key": wantKey}})
require.NoError(t, err)
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
err = directIngestDiskEncryptionKeyFileLinesDarwin(ctx, logger, host, ds, []map[string]string{{"encrypted": "1"}})
require.NoError(t, err)
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
err = directIngestDiskEncryptionKeyFileDarwin(ctx, logger, host, ds, []map[string]string{{"encrypted": "1"}})
require.NoError(t, err)
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
err = directIngestDiskEncryptionKeyFileLinesDarwin(ctx, logger, host, ds, []map[string]string{{"encrypted": "1", "hex_line": ""}})
require.NoError(t, err)
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
err = directIngestDiskEncryptionKeyFileDarwin(ctx, logger, host, ds, []map[string]string{{"encrypted": "1", "filevault_key": ""}})
require.NoError(t, err)
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
})
t.Run("key contains new lines and carriage return", func(t *testing.T) {
wantKey = "This is only a \n\r\n\n test."
err := directIngestDiskEncryptionKeyFileLinesDarwin(ctx, logger, host, ds, mockFileLines(wantKey, "1"))
// it is a known limitation with the current file_lines implementation that causes this to fail
// because it relies on bufio.ScanLines, which drops "\r" from "\r\n"
require.ErrorContains(t, err, "key mismatch")
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
err = directIngestDiskEncryptionKeyFileDarwin(ctx, logger, host, ds, mockFilevaultPRK(wantKey, "1"))
// filevault_prk does not have the scan lines limitation
require.NoError(t, err)
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
})
t.Run("key contains new lines", func(t *testing.T) {
wantKey = "This is only a \n\n\n test."
err := directIngestDiskEncryptionKeyFileLinesDarwin(ctx, logger, host, ds, mockFileLines(wantKey, "1"))
// new lines are not a problem if they are not preceded by carriage return
require.NoError(t, err)
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
err = directIngestDiskEncryptionKeyFileDarwin(ctx, logger, host, ds, mockFilevaultPRK(wantKey, "1"))
// filevault_prk does not have the scan lines limitation
require.NoError(t, err)
require.True(t, ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked)
ds.SetOrUpdateHostDiskEncryptionKeyFuncInvoked = false
})
}
func TestDirectIngestHostMacOSProfiles(t *testing.T) {