From 7bb202f9613cd725c8a1ac9bc19a3e31033e2e5e Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Fri, 28 May 2021 17:51:47 -0500 Subject: [PATCH] Publish fleetdm.com/queries (#899) * /sandbox/queries becomes /queries, etc * Publish fleetdm.com/queries Expose query library routes the rest of the way, move remediation data sanitization to the point of entry, and update query library to match (pairing w/ @gillespi314) * Fix accidental commit of sailsrc (again) --- .../standard-query-library.yml | 52 ++----------------- website/assets/js/pages/query-detail.page.js | 9 ---- website/assets/js/pages/query-library.page.js | 2 +- website/config/env/development.js | 2 - website/config/routes.js | 3 ++ website/scripts/build-static-content.js | 12 +++++ website/views/pages/query-detail.ejs | 22 ++++---- 7 files changed, 30 insertions(+), 72 deletions(-) diff --git a/docs/1-Using-Fleet/standard-query-library/standard-query-library.yml b/docs/1-Using-Fleet/standard-query-library/standard-query-library.yml index 3c4c983b87..7011818680 100644 --- a/docs/1-Using-Fleet/standard-query-library/standard-query-library.yml +++ b/docs/1-Using-Fleet/standard-query-library/standard-query-library.yml @@ -7,7 +7,6 @@ spec: description: Count the number of Apple applications installed on the machine. query: SELECT COUNT(*) FROM apps WHERE bundle_identifier LIKE 'com.apple.%'; purpose: Informational - remediation: N/A contributors: mike-j-thomas,noahtalerman,mikermcneil --- apiVersion: v1 @@ -18,7 +17,6 @@ spec: description: Retrieves the OpenSSL version. query: SELECT name AS name, version AS version, 'deb_packages' AS source FROM deb_packages WHERE name LIKE 'openssl%' UNION SELECT name AS name, version AS version, 'apt_sources' AS source FROM apt_sources WHERE name LIKE 'openssl%' UNION SELECT name AS name, version AS version, 'rpm_packages' AS source FROM rpm_packages WHERE name LIKE 'openssl%'; purpose: Detection - remediation: N/A --- apiVersion: v1 kind: query @@ -28,7 +26,6 @@ spec: description: Gatekeeper tries to ensure only trusted software is run on a mac machine. query: SELECT * FROM gatekeeper WHERE assessments_enabled = 0; purpose: Detection - remediation: N/A --- apiVersion: v1 kind: query @@ -48,7 +45,6 @@ spec: description: List authorized_keys for each user on the system. query: SELECT * FROM users CROSS JOIN authorized_keys USING (uid); purpose: Informational - remediation: N/A contributors: anelshaer --- apiVersion: v1 @@ -59,7 +55,6 @@ spec: description: List authorized_keys for each user on the system. query: SELECT * FROM users CROSS JOIN authorized_keys USING(uid) WHERE username IN (SELECT distinct(username) FROM last); purpose: Informational - remediation: N/A contributors: anelshaer --- apiVersion: v1 @@ -70,7 +65,6 @@ spec: description: Retrieve application, system, and mobile app crash logs. query: SELECT uid, datetime, responsible, exception_type, identifier, version, crash_path FROM users CROSS JOIN crashes USING (uid); purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -80,7 +74,6 @@ spec: description: List installed Chrome Extensions for all users. query: SELECT uid, datetime, responsible, exception_type, identifier, version, crash_path FROM users CROSS JOIN crashes USING (uid); purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -90,7 +83,6 @@ spec: description: Get all software installed on a FreeBSD computer, including browser plugins and installed packages. Note, this does not included other running processes in the processes table. query: SELECT name AS name, version AS version, 'Browser plugin (Chrome)' AS type, 'chrome_extensions' AS source FROM chrome_extensions UNION SELECT name AS name, version AS version, 'Browser plugin (Firefox)' AS type, 'firefox_addons' AS source FROM firefox_addons UNION SELECT name AS name, version AS version, 'Package (Atom)' AS type, 'atom_packages' AS source FROM atom_packages UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages UNION SELECT name AS name, version AS version, 'Package (pkg)' AS type, 'pkg_packages' AS source FROM pkg_packages; purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -100,7 +92,6 @@ spec: description: Get the installed homebrew package database. query: SELECT * FROM homebrew_packages; purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -110,7 +101,6 @@ spec: description: Get all software installed on a Linux computer, including browser plugins and installed packages. Note, this does not included other running processes in the processes table. query: SELECT name AS name, version AS version, 'Package (APT)' AS type, 'apt_sources' AS source FROM apt_sources UNION SELECT name AS name, version AS version, 'Package (deb)' AS type, 'deb_packages' AS source FROM deb_packages UNION SELECT package AS name, version AS version, 'Package (Portage)' AS type, 'portage_packages' AS source FROM portage_packages UNION SELECT name AS name, version AS version, 'Package (RPM)' AS type, 'rpm_packages' AS source FROM rpm_packages UNION SELECT name AS name, '' AS version, 'Package (YUM)' AS type, 'yum_sources' AS source FROM yum_sources UNION SELECT name AS name, version AS version, 'Package (NPM)' AS type, 'npm_packages' AS source FROM npm_packages UNION SELECT name AS name, version AS version, 'Package (Atom)' AS type, 'atom_packages' AS source FROM atom_packages UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages; purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -120,7 +110,6 @@ spec: description: Get all software installed on a macOS computer, including apps, browser plugins, and installed packages. Note, this does not included other running processes in the processes table. query: SELECT name AS name, bundle_short_version AS version, 'Application (macOS)' AS type, 'apps' AS source FROM apps UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages UNION SELECT name AS name, version AS version, 'Browser plugin (Chrome)' AS type, 'chrome_extensions' AS source FROM chrome_extensions UNION SELECT name AS name, version AS version, 'Browser plugin (Firefox)' AS type, 'firefox_addons' AS source FROM firefox_addons UNION SELECT name As name, version AS version, 'Browser plugin (Safari)' AS type, 'safari_extensions' AS source FROM safari_extensions UNION SELECT name AS name, version AS version, 'Package (Homebrew)' AS type, 'homebrew_packages' AS source FROM homebrew_packages; purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -130,7 +119,6 @@ spec: description: Retrieves the list of installed Safari Extensions for all users in the target system. query: SELECT safari_extensions.* FROM users join safari_extensions USING (uid); purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -140,7 +128,6 @@ spec: description: Get all software installed on a Windows computer, including programs, browser plugins, and installed packages. Note, this does not included other running processes in the processes table. query: SELECT name AS name, version AS version, 'Program (Windows)' AS type, 'programs' AS source FROM programs UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages UNION SELECT name AS name, version AS version, 'Browser plugin (IE)' AS type, 'ie_extensions' AS source FROM ie_extensions UNION SELECT name AS name, version AS version, 'Browser plugin (Chrome)' AS type, 'chrome_extensions' AS source FROM chrome_extensions UNION SELECT name AS name, version AS version, 'Browser plugin (Firefox)' AS type, 'firefox_addons' AS source FROM firefox_addons UNION SELECT name AS name, version AS version, 'Package (Chocolatey)' AS type, 'chocolatey_packages' AS source FROM chocolatey_packages UNION SELECT name AS name, version AS version, 'Package (Atom)' AS type, 'atom_packages' AS source FROM atom_packages UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages; purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -150,7 +137,6 @@ spec: description: query: SELECT * FROM battery WHERE health != 'Good' AND condition NOT IN ('', 'Normal'); purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -160,7 +146,6 @@ spec: description: Displays the percentage of free space available on the primary disk partition. query: SELECT (blocks_available * 100 / blocks) AS pct, * FROM mounts WHERE path = '/'; purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -170,7 +155,6 @@ spec: description: Shows system mounted devices and filesystems (not process specific). query: SELECT device, device_alias, path, type, blocks_size FROM mounts; purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -180,7 +164,6 @@ spec: description: Shows system mounted devices and filesystems (not process specific). query: SELECT * FROM os_version; purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -190,7 +173,6 @@ spec: description: Shows information about the host platform query: SELECT vendor, version, date, revision from platform_info; purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -200,7 +182,6 @@ spec: description: Shows applications and binaries set as user/login startup items. query: SELECT * FROM startup_items; purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -210,7 +191,6 @@ spec: description: Get a list of system logins and logouts. query: SELECT * FROM last; purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -220,7 +200,6 @@ spec: description: Get current users with active shell/console on the system and associated process query: SELECT user,host,time, p.name, p.cmdline, p.cwd, p.root FROM logged_in_users liu, processes p WHERE liu.pid = p.pid and liu.type='user' and liu.user <> '' ORDER BY time; purpose: Informational - remediation: N/A contributors: anelshaer --- apiVersion: v1 @@ -231,7 +210,6 @@ spec: description: Shows the system uptime. query: SELECT * FROM uptime; purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -241,7 +219,6 @@ spec: description: Shows all USB devices that are actively plugged into the host system. query: SELECT * FROM usb_devices; purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -251,7 +228,6 @@ spec: description: Shows information about the wifi network that a host is currently connected to. query: SELECT * FROM wifi_status; purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -261,7 +237,6 @@ spec: description: query: SELECT * FROM bitlocker_info WHERE protection_status = 0; purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -271,7 +246,6 @@ spec: description: Disk encryption status and information. query: SELECT * FROM disk_encryption; purpose: Informational - remediation: N/A contributors: anelshaer --- apiVersion: v1 @@ -282,9 +256,7 @@ spec: description: Identify SSH keys created without a passphrase which can be used in Lateral Movement (MITRE. TA0008) query: SELECT uid, username, description, path, encrypted FROM users CROSS JOIN user_ssh_keys using (uid) WHERE encrypted=0; purpose: Detection - remediation: - - User awareness about the impact of SSH Keys - - Rotate the identified Keys + remediation: First, make the user aware about the impact of SSH keys. Then rotate the unencrypted keys detected. contributors: anelshaer --- apiVersion: v1 @@ -295,9 +267,7 @@ spec: description: Identify SSH keys created without a passphrase which can be used in Lateral Movement (MITRE. TA0008) query: SELECT uid, username, description, path, encrypted FROM users CROSS JOIN user_ssh_keys using (uid) WHERE encrypted=0 and username in (SELECT distinct(username) FROM last); purpose: Detection - remediation: - - User awareness about the impact of SSH Keys - - Rotate the identified Keys + remediation: First, make the user aware about the impact of SSH keys. Then rotate the unencrypted keys detected. contributors: anelshaer --- apiVersion: v1 @@ -308,7 +278,6 @@ spec: description: Line parsed values from system and user cron/tab. query: SELECT * FROM crontab; purpose: Informational - remediation: N/A contributors: anelshaer --- apiVersion: v1 @@ -319,7 +288,6 @@ spec: description: suid binaries in common locations. query: SELECT * FROM suid_bin; purpose: Informational - remediation: N/A --- apiVersion: v1 kind: query @@ -329,9 +297,7 @@ spec: description: Detect any processes that run with LD_PRELOAD environment variable query: SELECT env.pid, env.key, env.value, p.name,p.path, p.cmdline, p.cwd FROM process_envs env join processes p USING (pid) WHERE key='LD_PRELOAD'; purpose: Detection - remediation: - - Identify the process/binary detected - - Confirm with system owner + remediation: Identify the process/binary detected and confirm with the system's owner. contributors: anelshaer --- apiVersion: v1 @@ -342,9 +308,7 @@ spec: description: Detect any processes that run with DYLD_INSERT_LIBRARIES environment variable query: SELECT env.pid, env.key, env.value, p.name,p.path, p.cmdline, p.cwd FROM process_envs env join processes p USING (pid) WHERE key='DYLD_INSERT_LIBRARIES'; purpose: Detection - remediation: - - Identify the process/binary detected - - Confirm with system owner + remediation: Identify the process/binary detected and confirm with the system's owner. contributors: anelshaer --- apiVersion: v1 @@ -355,7 +319,6 @@ spec: description: Line-parsed /etc/hosts query: SELECT * FROM etc_hosts WHERE address not in ('127.0.0.1', '::1'); purpose: Informational - remediation: N/A contributors: anelshaer --- apiVersion: v1 @@ -366,7 +329,6 @@ spec: description: Network interfaces MAC address query: SELECT a.interface, a.address, d.mac FROM interface_addresses a JOIN interface_details d USING (interface) WHERE address not in ('127.0.0.1', '::1'); purpose: Informational - remediation: N/A contributors: anelshaer --- apiVersion: v1 @@ -377,7 +339,6 @@ spec: description: Local user accounts (including domain accounts that have logged on locally (Windows)). query: SELECT uid, gid, username, description,directory, shell FROM users; purpose: Informational - remediation: N/A contributors: anelshaer --- apiVersion: v1 @@ -388,7 +349,6 @@ spec: description: Domain Joined environment normally have root or other service account only and users are SSH-ing using their Domain Accounts. query: SELECT * FROM shadow WHERE password_status='active' and username!='root'; purpose: Detection - remediation: N/A contributors: anelshaer --- apiVersion: v1 @@ -402,7 +362,6 @@ spec: (SELECT username FROM users WHERE uid=p.uid) AS username FROM processes as p WHERE cmdline like 'nmap%'; purpose: Detection - remediation: N/A contributors: anelshaer --- apiVersion: v1 @@ -413,7 +372,6 @@ spec: description: Docker images information, can be used on normal system or a kubenode. query: SELECT * FROM docker_images; purpose: Informational - remediation: N/A contributors: anelshaer --- apiVersion: v1 @@ -424,7 +382,6 @@ spec: description: Docker containers information, can be used on normal system or a kubenode. query: SELECT * FROM docker_containers; purpose: Informational - remediation: N/A contributors: anelshaer --- apiVersion: v1 @@ -435,5 +392,4 @@ spec: description: Docker containers Processes, can be used on normal system or a kubenode. query: SELECT c.id, c.name, c.image, c.image_id, c.command, c.created, c.state, c.status, p.cmdline FROM docker_containers c CROSS JOIN docker_container_processes p using(id); purpose: Informational - remediation: N/A contributors: anelshaer diff --git a/website/assets/js/pages/query-detail.page.js b/website/assets/js/pages/query-detail.page.js index 1cd398736e..e44781c79a 100644 --- a/website/assets/js/pages/query-detail.page.js +++ b/website/assets/js/pages/query-detail.page.js @@ -20,15 +20,6 @@ parasails.registerPage('query-detail', { // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗ // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝ methods: { - castRemediationArray: function (remediation) { - if (_.isArray(remediation) && remediation.length) { - return remediation.filter((item) => _.isString(item) && item.length); - } - if (_.isString(remediation) && remediation.length) { - return [remediation]; - } - return ['N/A']; - } } }); diff --git a/website/assets/js/pages/query-library.page.js b/website/assets/js/pages/query-library.page.js index 3ffb4df8b3..046e961d8f 100644 --- a/website/assets/js/pages/query-library.page.js +++ b/website/assets/js/pages/query-library.page.js @@ -72,7 +72,7 @@ parasails.registerPage('query-library', { }, clickCard: function (querySlug) { - window.location = '/sandbox/queries/' + querySlug.toLowerCase(); // TODO remove sandbox from path before deploy to production + window.location = '/queries/' + querySlug;// we can trust the query slug is url-safe }, } diff --git a/website/config/env/development.js b/website/config/env/development.js index 0fae9366c4..914034cded 100644 --- a/website/config/env/development.js +++ b/website/config/env/development.js @@ -17,8 +17,6 @@ module.exports = { // Add any dev-only routes for local development of not-yet-released pages. // e.g. http://localhost:2024/sandbox/example-query routes: { - 'GET /sandbox/queries': { action: 'view-query-library' },// « to see it, check out /sandbox/queries - 'GET /sandbox/queries/:slug': { action: 'view-query-detail' },// « to see it, check out /sandbox/queries/adg 'GET /sandbox/documentation/*': { skipAssets: false, action: 'docs/view-basic-documentation' },// « to see it, check out http://localhost:2024/sandbox/documentation/adsg 'GET /sandbox/handbook/*': { skipAssets: false, action: 'handbook/view-basic-handbook' },// « to see it, check out http://localhost:2024/sandbox/handbook/adsg }, diff --git a/website/config/routes.js b/website/config/routes.js index e0e885da01..795349f726 100644 --- a/website/config/routes.js +++ b/website/config/routes.js @@ -22,6 +22,9 @@ module.exports.routes = { '/hall-of-fame': 'https://github.com/fleetdm/fleet/pulse', '/company/about': '/blog', // FUTURE: brief "about" page explaining the origins of the company + 'GET /queries': { action: 'view-query-library' }, + 'GET /queries/:slug': { action: 'view-query-detail' }, + '/contribute': 'https://github.com/fleetdm/fleet/tree/master/docs/4-Contribution', '/company/stewardship': 'https://github.com/fleetdm/fleet', // FUTURE: page about how we approach open source and our commitments to the community '/legal/terms': 'https://docs.google.com/document/d/1OM6YDVIs7bP8wg6iA3VG13X086r64tWDqBSRudG4a0Y/edit', diff --git a/website/scripts/build-static-content.js b/website/scripts/build-static-content.js index a9d0b489a6..b2611d4510 100644 --- a/website/scripts/build-static-content.js +++ b/website/scripts/build-static-content.js @@ -27,11 +27,23 @@ module.exports = { async()=>{// Parse query library from YAML and bake them into the Sails app's configuration. let RELATIVE_PATH_TO_QUERY_LIBRARY_YML_IN_FLEET_REPO = 'docs/1-Using-Fleet/standard-query-library/standard-query-library.yml'; let yaml = await sails.helpers.fs.read(path.join(topLvlRepoPath, RELATIVE_PATH_TO_QUERY_LIBRARY_YML_IN_FLEET_REPO)); + + let queriesWithProblematicRemediations = []; let queries = YAML.parseAllDocuments(yaml).map((yamlDocument)=>{ let query = yamlDocument.toJSON().spec; query.slug = _.kebabCase(query.name);// « unique slug to use for routing to this query's detail page + if ((query.remediation !== undefined && !_.isString(query.remediation)) || (query.purpose !== 'Detection' && _.isString(query.remediation))) { + // console.log(typeof query.remediation); + queriesWithProblematicRemediations.push(query); + } else if (query.remediation === undefined) { + query.remediation = 'N/A';// « We set this to a string here so that the data type is always string. We use N/A so folks can see there's no remediation and contribute if desired. + } return query; }); + // Report any errors that were detected along the way in one fell swoop to avoid endless resubmitting of PRs. + if (queriesWithProblematicRemediations.length >= 1) { + throw new Error('Failed parsing YAML for query library: The "remediation" of a query should either be absent (undefined) or a single string (not a list of strings). And "remediation" should only be present when a query\'s purpose is "Detection". But one or more queries have an invalid "remediation": ' + _.pluck(queriesWithProblematicRemediations, 'slug').sort()); + }//• // Assert uniqueness of slugs. if (queries.length !== _.uniq(_.pluck(queries, 'slug')).length) { throw new Error('Failed parsing YAML for query library: Queries as currently named would result in colliding (duplicate) slugs. To resolve, rename the queries whose names are too similar. Note the duplicates: ' + _.pluck(queries, 'slug').sort()); diff --git a/website/views/pages/query-detail.ejs b/website/views/pages/query-detail.ejs index ce7057c84e..c5bf2e9e80 100644 --- a/website/views/pages/query-detail.ejs +++ b/website/views/pages/query-detail.ejs @@ -5,18 +5,16 @@

{{query.name}}

{{query.description}}
-
+
lightbulb

{{query.tip}}

-

Query

+

Query

{{query.query}} -
-

Remediation

+
+

Remediation

    -
  • - {{item}} -
  • +
  • {{query.remediation}}
@@ -24,7 +22,7 @@
-
Platforms
+
Platforms

@@ -33,16 +31,16 @@

-
Purpose
+
Purpose

{{query.purpose}}

- +
-
Contributors
+
Contributors

{{query.contributors}}

-
Contribute to this page
+
Contribute to this page
View source