Query library improvements -- Round 2 (#973)

-Modify build-static-content back-end script to implement GitHub Users API and build contributor profile information into query library pages
-Remove related functionality from client-side page scripts
-Add dropdown menu to select filters
-Refine html and css
This commit is contained in:
gillespi314 2021-06-10 12:57:37 -05:00 committed by GitHub
parent fccc58536b
commit 676f3f0bd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 383 additions and 228 deletions

View File

@ -17,6 +17,7 @@ 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
contributors: zwass
---
apiVersion: v1
kind: query
@ -26,6 +27,7 @@ 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
contributors: zwass
---
apiVersion: v1
kind: query
@ -36,6 +38,7 @@ spec:
query: SELECT username, authorized_keys. * FROM users CROSS JOIN authorized_keys USING (uid);
purpose: Detection
remediation: Check out the linked table (https://github.com/fleetdm/fleet/blob/32b4d53e7f1428ce43b0f9fa52838cbe7b413eed/handbook/queries/detect-hosts-with-high-severity-vulnerable-versions-of-openssl.md#table-of-vulnerable-openssl-versions) to determine if the installed version is a high severity vulnerability and view the corresponding CVE(s)
contributors: mike-j-thomas
---
apiVersion: v1
kind: query
@ -65,6 +68,7 @@ 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
contributors: zwass
---
apiVersion: v1
kind: query
@ -74,6 +78,7 @@ 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
contributors: zwass
---
apiVersion: v1
kind: query
@ -83,6 +88,7 @@ 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
contributors: zwass
---
apiVersion: v1
kind: query
@ -92,6 +98,7 @@ spec:
description: Get the installed homebrew package database.
query: SELECT * FROM homebrew_packages;
purpose: Informational
contributors: zwass
---
apiVersion: v1
kind: query
@ -101,6 +108,7 @@ 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
contributors: zwass
---
apiVersion: v1
kind: query
@ -110,6 +118,7 @@ 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
contributors: zwass
---
apiVersion: v1
kind: query
@ -119,6 +128,7 @@ 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
contributors: zwass
---
apiVersion: v1
kind: query
@ -128,6 +138,7 @@ 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
contributors: zwass
---
apiVersion: v1
kind: query
@ -137,6 +148,7 @@ spec:
description:
query: SELECT * FROM battery WHERE health != 'Good' AND condition NOT IN ('', 'Normal');
purpose: Informational
contributors: zwass
---
apiVersion: v1
kind: query
@ -146,6 +158,7 @@ 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
contributors: zwass
---
apiVersion: v1
kind: query
@ -155,6 +168,7 @@ spec:
description: Shows system mounted devices and filesystems (not process specific).
query: SELECT device, device_alias, path, type, blocks_size FROM mounts;
purpose: Informational
contributors: zwass
---
apiVersion: v1
kind: query
@ -164,6 +178,7 @@ spec:
description: Shows system mounted devices and filesystems (not process specific).
query: SELECT * FROM os_version;
purpose: Informational
contributors: zwass
---
apiVersion: v1
kind: query
@ -173,6 +188,7 @@ spec:
description: Shows information about the host platform
query: SELECT vendor, version, date, revision from platform_info;
purpose: Informational
contributors: zwass
---
apiVersion: v1
kind: query
@ -182,6 +198,7 @@ spec:
description: Shows applications and binaries set as user/login startup items.
query: SELECT * FROM startup_items;
purpose: Informational
contributors: zwass
---
apiVersion: v1
kind: query
@ -191,6 +208,7 @@ spec:
description: Get a list of system logins and logouts.
query: SELECT * FROM last;
purpose: Informational
contributors: zwass
---
apiVersion: v1
kind: query
@ -210,6 +228,7 @@ spec:
description: Shows the system uptime.
query: SELECT * FROM uptime;
purpose: Informational
contributors: zwass
---
apiVersion: v1
kind: query
@ -219,6 +238,7 @@ spec:
description: Shows all USB devices that are actively plugged into the host system.
query: SELECT * FROM usb_devices;
purpose: Informational
contributors: zwass
---
apiVersion: v1
kind: query
@ -228,6 +248,7 @@ spec:
description: Shows information about the wifi network that a host is currently connected to.
query: SELECT * FROM wifi_status;
purpose: Informational
contributors: zwass
---
apiVersion: v1
kind: query
@ -237,6 +258,7 @@ spec:
description:
query: SELECT * FROM bitlocker_info WHERE protection_status = 0;
purpose: Informational
contributors: zwass
---
apiVersion: v1
kind: query
@ -288,6 +310,7 @@ spec:
description: suid binaries in common locations.
query: SELECT * FROM suid_bin;
purpose: Informational
contributors: zwass
---
apiVersion: v1
kind: query

Binary file not shown.

Before

Width:  |  Height:  |  Size: 814 B

After

Width:  |  Height:  |  Size: 798 B

View File

@ -13,35 +13,19 @@ parasails.registerPage('query-detail', {
//…
},
mounted: async function () {
if (this.query && this.query.contributors) {
this.contributors = await Promise.all(
this.query.contributors
.split(',')
.map(async (contributor) => this.getGitHubUserData(contributor))
);
}
},
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
methods: {
getGitHubUserData: async function (userName) {
const url =
'https://api.github.com/users/' + encodeURIComponent(userName);
return await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/vnd.github.v3+json',
},
})
.then((response) => response.json())
.catch((error) => console.log(error));
},
clickAvatar: function (contributor) {
window.location = contributor.html_url;
window.location = contributor.htmlUrl;
},
getDisplayName: function (contributor) {
return !contributor.name ? contributor.handle : contributor.name;
}
},
});

View File

@ -3,12 +3,11 @@ parasails.registerPage('query-library', {
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
data: {
contributorsDictionary: {},
inputTextValue: '',
inputTimers: {},
searchString: '', // The user input string to be searched against the query library
selectedPurpose: 'all', // Initially set to all, the user may select a different option to filter queries by purpose (e.g., "all queries", "information", "detection")
selectedPlatform: 'all', // Initially set to all, the user may select a different option to filter queries by platform (e.g., "all platforms", "macOS", "Windows", "Linux")
selectedPurpose: 'all queries', // Initially set to all, the user may select a different option to filter queries by purpose (e.g., "all queries", "information", "detection")
selectedPlatform: 'all platforms', // Initially set to all, the user may select a different option to filter queries by platform (e.g., "all platforms", "macOS", "Windows", "Linux")
},
computed: {
@ -36,43 +35,50 @@ parasails.registerPage('query-library', {
//…
},
mounted: async function () {
const uniqueContributors = this._getUniqueContributors(this.queries);
this.contributorsDictionary = Object.assign(
{},
await this._threadGitHubAPICalls(uniqueContributors)
);
//…
},
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
methods: {
clickSelectPurpose(purpose) {
this.selectedPurpose = purpose;
},
clickSelectPlatform(platform) {
this.selectedPlatform = platform;
},
clickCard: function (querySlug) {
window.location = '/queries/' + querySlug; // we can trust the query slug is url-safe
},
clickAvatar: function (contributor) {
window.location = contributor.html_url;
window.location = contributor.htmlUrl;
},
getAvatarUrl: function (contributorData) {
return contributorData ? contributorData.avatar_url : '';
return contributorData ? contributorData.avatarUrl : '';
},
getContributorsString: function (list, dictionary) {
getContributorsString: function (contributors) {
if (!contributors) {
return;
}
const displayName = (contributorData) => {
if (contributorData) {
return !contributorData.name
? contributorData.login
? contributorData.handle
: contributorData.name;
}
};
let contributorString = displayName(dictionary[list[0]]);
if (list.length > 2) {
contributorString += ` and ${list.length - 1} others`;
let contributorString = displayName(contributors[0]);
if (contributors.length > 2) {
contributorString += ` and ${contributors.length - 1} others`;
}
if (list.length === 2) {
contributorString += ` and ${displayName(dictionary[list[1]])}`;
if (contributors.length === 2) {
contributorString += ` and ${displayName(contributors[1])}`;
}
return contributorString;
},
@ -90,15 +96,22 @@ parasails.registerPage('query-library', {
this.searchString = this.inputTextValue;
},
_search: function (library, searchString) {
const searchTerms = _.isString(searchString)
? searchString.toLowerCase().split(' ')
: [];
return library.filter((item) => {
const description = _.isString(item.description)
? item.description.toLowerCase()
: '';
return searchTerms.some((term) => description.includes(term));
_search: function (queries, searchString) {
if (_.isEmpty(searchString)) {
return queries;
}
const normalize = (value) => _.isString(value) ? value.toLowerCase() : '';
const searchTerms = normalize(searchString).split(' ');
return queries.filter((query) => {
let textToSearch = normalize(query.name) + ', ' + normalize(query.description);
if (query.contributors) {
query.contributors.forEach((contributor) => {
textToSearch += ', ' + normalize(contributor.name) + ', ' + normalize(contributor.handle);
});
}
return (searchTerms.some((term) => textToSearch.includes(term)));
});
},
@ -114,57 +127,6 @@ parasails.registerPage('query-library', {
);
},
_threadGitHubAPICalls: async function (contributorsList) {
// create threads object with a thread for each contributor each thread is a promise that will resolve
// when the async call to the GitHub API resolves for that contributor
const threads = contributorsList.reduce((threads, contributor) => {
threads[contributor] = this._getGitHubUserData(contributor);
return threads;
}, {});
// each thread resolves with a key-value pair where the key is the contributor's GitHub handle and the value
// is the deserialized JSON response returned by the GitHub API for that contributor
const resolvedThreads = await Promise.all(
Object.keys(threads).map((key) =>
Promise.resolve(threads[key]).then((result) => ({ [key]: result }))
)
).then((resultsArray) => {
const resolvedThreads = resultsArray.reduce(
(resolvedThreads, result) => {
Object.assign(resolvedThreads, result);
return resolvedThreads;
},
{}
);
return resolvedThreads;
});
return resolvedThreads;
},
_getUniqueContributors: function (queries) {
return queries.reduce((uniqueContributors, query) => {
if (query.contributors) {
uniqueContributors = _.union(
uniqueContributors,
query.contributors.split(',')
);
}
return uniqueContributors;
}, []);
},
_getGitHubUserData: async function (gitHubHandle) {
const url =
'https://api.github.com/users/' + encodeURIComponent(gitHubHandle);
const userData = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/vnd.github.v3+json',
},
})
.then((response) => response.json())
.catch(() => {});
return userData;
},
},
});

View File

@ -125,9 +125,6 @@ a.text-danger:hover, a.text-danger:focus {
box-shadow: 0px 6px 20px rgba(0, 0, 0, 0.05);
}
.dropdown:hover > .dropdown-menu {
display: block;
}
.dropdown:hover > .btn {
color: #6a67fe;
}

View File

@ -20,15 +20,15 @@ html, body {
position: absolute;
left: 0;
right: 0;
.dropdown:hover > .dropdown-menu {
display: block;
}
.header-btn {
color: #ffffff;
cursor: unset;
font-family: @navigation-font;
font-weight: @bold;
}
// .header-btn[aria-expanded='true'] {
// color: #6a67fe;
// }
.header-link {
color: #ffffff;
font-weight: @bold;
@ -104,6 +104,9 @@ html, body {
left: 0;
right: 0;
border-bottom: 1px solid #e2e4ea;
.dropdown:hover > .dropdown-menu {
display: block;
}
.header-btn {
color: #192147;
cursor: unset;

View File

@ -71,10 +71,10 @@
.platforms, .purpose, .contributors, .contribute {
min-height: 36px;
line-height: 24px;
p, a {
font-family: 'Nunito';
font-size: 16px;
line-height: 24px;
}
}
@ -89,6 +89,19 @@
}
}
@media (max-width: 700px) {
.contribute {
flex-wrap: wrap;
min-height: 24px;
h5 {
padding: 6px 0px 6px 0px;
}
}
}
@media (max-width: 575px) {
h2 {

View File

@ -1,5 +1,4 @@
#query-library {
h2 {
padding: 0px 30px 0px 30px;
}
@ -14,7 +13,6 @@
font-size: 16px;
line-height: 22px;
padding: 0px 30px 0px 30px;
}
a {
@ -27,24 +25,83 @@
height: 16px;
width: 16px;
}
&.search {
transform: scale(0.5);
}
}
input {
height: 54px;
&::placeholder {
font-size: 16px;
line-height: 24px;
}
}
.input-group {
&.search {
width: 250px;
border: 1px solid #C5C7D1;
}
}
.input-group-text {
color: #8b8fa2;
border-color: #c5c7d1;
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
.form-control {
font-size: 16px;
border-color: #c5c7d1;
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
&:focus {
border: 1px solid #c5c7d1;
}
}
.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus, .show > .btn-secondary.dropdown-toggle:focus {
box-shadow: none;
}
.btn-secondary {
font-family: Nunito;
color: @core-vibrant-blue;
background-color: transparent;
border: 0;
cursor: pointer;
&:focus {
border: 0;
box-shadow: none;
}
}
.filters {
height: 54px;
p {
font-family: Nunito;
font-size: 16px;
line-height: 25px;
}
}
.dropdown-menu {
border-radius: 8px;
padding: 15px;
&.mobile {
width: 100%;
margin-right: 0;
margin-left: 0;
cursor: pointer;
}
.dropdown-item {
border-radius: 8px;
cursor: pointer;
&:hover {
background-color: lightness(@core-vibrant-blue, 10%);
margin-right: 15px;
}
}
select {
color: @core-vibrant-blue;
border: 0px;
outline: 0;
&:focus {
border: 0px;
}
@ -65,7 +122,9 @@
}
.library {
max-width: 860px;
max-width: 960px;
margin-top: 80px;
margin-bottom: 0;
}
.description {
@ -81,7 +140,7 @@
width: 100%;
margin-right: 0;
margin-left: 0;
border: 1px solid #C5C7D1;
border: 1px solid #c5c7d1;
border-radius: 8px;
padding-right: 15px;
}
@ -101,10 +160,11 @@
.filter-and-search-bar {
padding-left: 45px;
padding-right: 45px;
margin-bottom: 0;
min-height: 54px;
}
.contributors, .platforms {
p {
font-size: 13px;
line-height: 20px;
@ -120,7 +180,7 @@
margin-left: 30px;
margin-right: 30px;
border-bottom: 1px solid;
border-color: #E2E4EA;
border-color: #e2e4ea;
}
.card.results {
@ -129,7 +189,7 @@
border-radius: 8px;
&:hover {
background-color: #F1F0FF;
background-color: #f1f0ff;
cursor: pointer;
}
}
@ -141,7 +201,6 @@
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.1);
margin-bottom: 90px;
width: 100%;
}
.card-body {
@ -168,20 +227,36 @@
}
}
@media (max-width: 575px) {
@media (max-width: 768px) {
.library {
max-width: 720px;
margin-top: 60px;
}
.results {
margin-top: 16px;
}
}
@media (max-width: 575px) {
h2 {
font-size: 28px;
line-height: 36px;
}
.contributors, .platforms {
.library {
max-width: none;
margin-bottom: 0px;
}
.results {
margin-top: 16px;
}
.contributors, .platforms {
p {
font-size: 13px;
line-height: 20px;
}
}
}
}

View File

@ -29,6 +29,7 @@ module.exports = {
let yaml = await sails.helpers.fs.read(path.join(topLvlRepoPath, RELATIVE_PATH_TO_QUERY_LIBRARY_YML_IN_FLEET_REPO));
let queriesWithProblematicRemediations = [];
let queriesWithProblematicContributors = [];
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
@ -38,6 +39,12 @@ module.exports = {
} 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.
}
// GitHub usernames may only contain alphanumeric characters or single hyphens, and cannot begin or end with a hyphen.
if (!query.contributors || (query.contributors !== undefined && !_.isString(query.contributors)) || query.contributors.split(',').some((contributor) => contributor.match('^[^A-za-z0-9].*|[^A-Za-z0-9-]|.*[^A-za-z0-9]$'))) {
queriesWithProblematicContributors.push(query);
}
return query;
});
// Report any errors that were detected along the way in one fell swoop to avoid endless resubmitting of PRs.
@ -48,6 +55,41 @@ module.exports = {
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());
}//•
// Report any errors that were detected along the way in one fell swoop to avoid endless resubmitting of PRs.
if (queriesWithProblematicContributors.length >= 1) {
throw new Error('Failed parsing YAML for query library: The "contributors" of a query should be a single string of valid GitHub user names (e.g. "zwass", or "zwass,noahtalerman,mikermcneil"). But one or more queries have an invalid "contributors" value: ' + _.pluck(queriesWithProblematicContributors, 'slug').sort());
}//•
// Get a distinct list of all GitHub usernames from all of our queries.
// Map all queries to build a list of unique contributor names then build a dictionary of user profile information from the GitHub Users API
const githubUsernames = queries.reduce((list, query) => {
if (!queriesWithProblematicContributors.find((element) => element.slug === query.slug)) {
list = _.union(list, query.contributors.split(','));
}
return list;
}, []);
// Talk to GitHub and get additional information about each contributor.
let githubDataByUsername = {};
await sails.helpers.flow.simultaneouslyForEach(githubUsernames, async(username)=>{
githubDataByUsername[username] = await sails.helpers.http.get('https://api.github.com/users/' + encodeURIComponent(username), {}, { 'User-Agent': 'Fleet-Standard-Query-Library', Accept: 'application/vnd.github.v3+json' });
});//∞
// Now expand queries with relevant profile data for the contributors.
for (let query of queries) {
let usernames = query.contributors.split(',');
let contributorProfiles = [];
for (let username of usernames) {
contributorProfiles.push({
name: githubDataByUsername[username].name,
handle: githubDataByUsername[username].login,
avatarUrl: githubDataByUsername[username].avatar_url,
htmlUrl: githubDataByUsername[username].html_url,
});
}
query.contributors = contributorProfiles;
}
// Attach to Sails app configuration.
builtStaticContent.queries = queries;
builtStaticContent.queryLibraryYmlRepoPath = RELATIVE_PATH_TO_QUERY_LIBRARY_YML_IN_FLEET_REPO;

View File

@ -1,43 +1,48 @@
<div id="query-detail" v-cloak>
<div class="d-none d-md-flex justify-content-center">
<div class="col-6 my-5">
<div class="d-none d-md-flex p-md-4 justify-content-center">
<div class="d-flex container-xl justify-content-center">
<div class="d-flex container-fluid">
<div class="col-8 pl-md-4 my-5">
<h2 class="mb-3">{{query.name}}</h2>
<h6 class="pb-3">{{query.description}}</h6>
<h6 class="pb-3">{{query.description ? query.description : '--'}}</h6>
<div v-if="!!query.tip">
<div class="container query-tip d-flex align-items-center border-left border-primary p-4 my-5">
<img alt="lightbulb" class="lightbulb" src="/images/lightbulb-blue-24x24@2x.png"/><p class="d-flex m-0">{{query.tip}}</p>
</div>
</div>
<h3 class="py-3">Query</h3>
<code class="pb-3">{{query.query}}</code>
<code class="pb-3">{{query.query ? query.query : '--'}}</code>
<div class="remediation" v-if="query.purpose === 'Detection' && query.remediation">
<h3 class="pt-5 pb-3">Remediation</h3>
<p>{{query.remediation}}</p>
<p>{{query.remediation ? query.remediation : 'N/A'}}</p>
</div>
</div>
<div class="col-3 mx-5 my-5 d-none d-md-block">
<div class="col-4 pr-md-4 ml-5 my-5">
<div class="query-sidebar border-bottom mb-3">
<h5>Platforms</h5>
<p>
<span v-if="query.platforms.includes('macOS')"><img class="d-inline mr-3 logo" src="/images/os-macos-black-16x16@2x.png" alt="macOS"/></span>
<span v-if="query.platforms.includes('Windows')"><img class="d-inline mr-3 logo" src="/images/os-windows-black-16x16@2x.png" alt="Windows"/></span>
<span v-if="query.platforms.includes('Linux')"><img class="d-inline mr-3 logo" src="/images/os-linux-black-16x16@2x.png" alt="Linux"/></span>
</p>
<p v-if="!query.platforms || !query.platforms.length">--</p>
<div class="d-flex mb-3" v-else>
<img class="d-inline-flex mr-3 logo" src="/images/os-macos-black-24x24@2x.png" alt="macOS" v-if="query.platforms.includes('macOS')"/>
<img class="d-inline-flex mr-3 logo" src="/images/os-windows-black-24x24@2x.png" alt="Windows" v-if="query.platforms.includes('Windows')"/>
<img class="d-inline-flex mr-3 logo" src="/images/os-linux-black-24x24@2x.png" alt="Linux" v-if="query.platforms.includes('Linux')"/>
<img class="d-inline-flex mr-3 logo" src="/images/os-freebsd-black-24x24@2x.png" alt="FreeBSD" v-if="query.platforms.includes('FreeBSD')"/>
</div>
</div>
<div class="query-sidebar border-bottom mb-3">
<h5>Purpose</h5>
<p>{{query.purpose}}</p>
<p>{{query.purpose ? query.purpose : '--'}}</p>
</div>
<div class="query-sidebar" v-if="query.contributors && query.contributors.length">
<div class="query-sidebar">
<h5>Contributors</h5>
<div class="d-flex mb-3">
<div v-for="contributor in contributors">
<span class="mb-1" v-if="!query.contributors || !query.contributors.length">--</span>
<div v-for="contributor in query.contributors">
<div class="d-flex m-1 avatar-frame" @click="clickAvatar(contributor)">
<img alt="GitHub profile image" :alt="contributor.name" :src="contributor.avatar_url"/>
<img alt="a GitHub user avatar" :alt="contributor.name ? contributor.name : contributor.handle" :src="contributor.avatarUrl"/>
</div>
</div>
</div>
@ -46,53 +51,59 @@
<a target="_blank" :href="'https://github.com/fleetdm/fleet/edit/main/'+queryLibraryYmlRepoPath">View source</a>
</div>
</div>
<div class="d-block d-md-none px-4 py-5 justify-content-center">
</div>
</div>
<div class="d-block d-md-none px-3 py-5 justify-content-center">
<h2 class="mb-3">{{query.name}}</h2>
<h6 class="pb-3">{{query.description}}</h6>
<h6 class="pb-0 mb-0">{{query.description ? query.description : '--'}}</h6>
</div>
<div class="d-block d-md-none">
<div class="col d-sm-flex">
<div class="col pr-sm-4 platforms-purpose">
<div class="col d-sm-flex px-3">
<div class="col p-0 pr-sm-4 platforms-purpose">
<div class="d-flex align-items-center justify-content-between platforms">
<h5 class="m-0">Platforms</h5>
<p class="m-0 platform">
<span v-if="query.platforms.includes('macOS')"><img class="d-inline ml-3 logo" src="/images/os-macos-black-16x16@2x.png" alt="macOS"/></span>
<span v-if="query.platforms.includes('Windows')"><img class="d-inline ml-3-3 logo" src="/images/os-windows-black-16x16@2x.png" alt="Windows"/></span>
<span v-if="query.platforms.includes('Linux')"><img class="d-inline ml-3 logo" src="/images/os-linux-black-16x16@2x.png" alt="Linux"/></span>
</p>
<p class="text-right mb-1" v-if="!query.platforms || !query.platforms.length">--</p>
<div class="d-flex m-0" v-else>
<img class="d-inline-flex ml-3 logo" src="/images/os-macos-black-24x24@2x.png" alt="macOS" v-if="query.platforms.includes('macOS')"/>
<img class="d-inline-flex ml-3 logo" src="/images/os-windows-black-24x24@2x.png" alt="Windows" v-if="query.platforms.includes('Windows')"/>
<img class="d-inline-flex ml-3 logo" src="/images/os-linux-black-24x24@2x.png" alt="Linux" v-if="query.platforms.includes('Linux')"/>
<img class="d-inline-flex ml-3 logo" src="/images/os-freebsd-black-24x24@2x.png" alt="FreeBSD" v-if="query.platforms.includes('FreeBSD')"/>
</div>
</div>
<div class="my-3 divider"></div>
<div class="d-flex align-items-center justify-content-between purpose">
<h5 class="m-0">Purpose</h5>
<p class="m-0">{{query.purpose}}</p>
<p class="m-0">{{query.purpose ? query.purpose : '--'}}</p>
</div>
</div>
<div class="d-sm-none my-3 divider"></div>
<div class="col pl-sm-4 contributors-contribute">
<div class="d-sm-none m-3 divider"></div>
<div class="col p-0 pl-sm-4 contributors-contribute">
<div class="d-flex align-items-center justify-content-between contributors">
<h5 class="m-0">Contributors</h5>
<div class="d-flex align-items-center">
<div class="d-flex" v-for="contributor in contributors">
<span class="mb-1" v-if="!query.contributors || !query.contributors.length">--</span>
<div class="d-flex" v-for="contributor in query.contributors">
<div class="d-flex m-1 avatar-frame" @click="clickAvatar(contributor)">
<img alt="GitHub profile image" :alt="contributor.name" :src="contributor.avatar_url"/>
<img alt="a GitHub user avatar" :alt="contributor.name ? contributor.name : contributor.handle" :src="contributor.avatarUrl"/>
</div>
</div>
</div>
</div>
<div class="my-3 divider"></div>
<div class="d-flex align-items-center justify-content-between contribute">
<h5 class="m-0">Contribute to this page</h5>
<a class="text-right m-0" target="_blank" :href="'https://github.com/fleetdm/fleet/edit/main/'+queryLibraryYmlRepoPath">View source</a>
<h5 class="d-flex align-items-center m-0 mr-3 contribute">Contribute to this page</h5>
<p class="d-flex align-items-center m-0 contribute"><a class="d-flex align-items-center text-right m-0 contribute" target="_blank" :href="'https://github.com/fleetdm/fleet/edit/main/'+queryLibraryYmlRepoPath">View source</a></p>
</div>
</div>
</div>
</div>
<div class="d-block d-md-none py-5 justify-content-center" style="padding-left: 30px; padding-right: 30px;">
<h3 class="py-3">Query</h3>
<code class="pb-3">{{query.query}}</code>
<div class="d-block d-md-none px-3 pt-5 pb-4 justify-content-center" style="padding-left: 30px; padding-right: 30px;">
<h3 class="pb-3 m-0">Query</h3>
<code class="pb-3">{{query.query ? query.query : '--'}}</code>
<div class="remediation" v-if="query.purpose === 'Detection'">
<h3 class="pt-5 pb-3">Remediation</h3>
<p>{{!query.remediation ? "N/A" : query.remediation}}</p>
<p>{{query.remediation ? query.remediation : 'N/A'}}</p>
</div>
</div>
</div>

View File

@ -1,19 +1,27 @@
<div id="query-library" v-cloak>
<div class="d-flex justify-content-center">
<div class="col-6-grow justify-content-center my-5 library">
<div class="container justify-content-center library">
<h2 class="mb-3">Standard query library</h2>
<p class="pb-3 description">Fleet's standard query library includes a growing collection of useful queries for organizations deploying Fleet and osquery.</p>
<p class="pb-3 description">Fleet's standard query library includes a growing collection of useful queries
for organizations deploying Fleet and osquery.</p>
<div class="p-0 m-0">
<div class="d-sm-none">
<div class="d-flex search-mobile">
<input class="mobile" v-model="inputTextValue" placeholder="Search queries" @keydown.self="delayInput(setSearchString, 500, 'defaultTimer')()"/>
<div class="d-md-none">
<div class="d-flex">
<div class="input-group search-mobile">
<div class="input-group-prepend border-right-0">
<span class="input-group-text bg-transparent border-right-0 pl-2 pr-1"><img class="search" alt="search"
src="/images/icon-search-16x16@2x.png"></span>
</div>
<input class="form-control border-left-0 px-0" placeholder="Search queries" aria-label="Search queries"
v-model="inputTextValue" @keydown.self="delayInput(setSearchString, 400, 'defaultTimer')()" />
</div>
</div>
<div class="d-flex select-mobile">
<div class="select-mobile-border">
<select class="select-purpose mobile font-weight-bold" v-model="selectedPurpose">
<option value="all" selected>All queries</option>
<option value="information">Informational queries</option>
<option value="all queries" selected>All queries</option>
<option value="informational">Informational queries</option>
<option value="detection">Detection queries</option>
</select>
</div>
@ -21,40 +29,64 @@
<div class="d-flex select-mobile">
<div class="select-mobile-border">
<select class="select-purpose mobile font-weight-bold" v-model="selectedPlatform">
<option value="all" selected>All platforms</option>
<option value="all platforms" selected>All platforms</option>
<option value="macOS">macOS</option>
<option value="Windows">Windows</option>
<option value="Linux">Linux</option>
<option value="FreeBSD">FreeBSD</option>
</select>
</div>
</div>
</div>
<div class="filter-and-search-bar d-none d-sm-flex row justify-content-between">
<div class="d-flex col col-xs-7 px-0">
<div class="filter-purpose">
<span>Show
<select class="mr-1 select-purpose" v-model="selectedPurpose">
<option value="all" selected>all queries</option>
<option value="information">information</option>
<option value="detection">detection</option>
</select>
</span>
</div>
<div class="filter-platform">
<span> compatible with
<select class="mr-1 select-platform" v-model="selectedPlatform">
<option value="all" selected>all platforms</option>
<option value="macOS">macOS</option>
<option value="Windows">Windows</option>
<option value="Linux">Linux</option>
</select>
</span>
<div class="filter-and-search-bar d-none d-md-flex row align-items-center justify-content-between mb-3">
<div class="d-flex px-0 align-items-center filters">
<div class="d-flex">
<div class="dropdown flex-wrap filter purpose">
<p class="d-inline-flex mb-0">Show</p>
<button class="btn btn-secondary btn-sm dropdown-toggle px-1 py-0" type="button"
id="dropdownMenuSelectPurpose" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{selectedPurpose}}
</button>
<div class="dropdown-menu px-2 py-1" aria-labelledby="dropdownMenuSelectPurpose">
<button class="dropdown-item p-3" type="button" @click="clickSelectPurpose('all queries')">all
queries</button>
<button class="dropdown-item p-3" type="button"
@click="clickSelectPurpose('informational')">informational</button>
<button class="dropdown-item p-3" type="button"
@click="clickSelectPurpose('detection')">detection</button>
</div>
</div>
<div class="col col-xs-5 px-0 justify-content-end">
<div class="search d-flex justify-content-end">
<input v-model="inputTextValue" placeholder="Search queries" @keydown.self="delayInput(setSearchString, 500, 'defaultTimer')()"/>
</div>
<div class="d-flex">
<div class="dropdown d-flex flex-wrap filter platform">
<p class="d-inline-flex flex-wrap pl-1 mb-0">compatible with</p>
<button class="btn btn-secondary btn-sm dropdown-toggle pl-2 pr-1 py-0" type="button"
id="dropdownMenuSelectPlatform" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{selectedPlatform}}
</button>
<div class="dropdown-menu px-2 py-1" aria-labelledby="dropdownMenuSelectPlatform">
<button class="dropdown-item p-3" type="button" @click="clickSelectPlatform('all platforms')">all platforms</button>
<button class="dropdown-item p-3" type="button" @click="clickSelectPlatform('macOS')">macOS</button>
<button class="dropdown-item p-3" type="button"
@click="clickSelectPlatform('Windows')">Windows</button>
<button class="dropdown-item p-3" type="button" @click="clickSelectPlatform('Linux')">Linux</button>
<button class="dropdown-item p-3" type="button"
@click="clickSelectPlatform('FreeBSD')">FreeBSD</button>
</div>
</div>
</div>
</div>
<div class="d-flex px-0">
<div class="d-flex justify-content-end">
<div class="input-group search">
<div class="input-group-prepend border-right-0 search">
<span class="input-group-text bg-transparent border-right-0 pl-2 pr-1"><img class="search"
alt="search" src="/images/icon-search-16x16@2x.png"></span>
</div>
<input class="form-control border-left-0 px-0" placeholder="Search queries" aria-label="Search queries"
v-model="inputTextValue" @keydown.self="delayInput(setSearchString, 400, 'defaultTimer')()" />
</div>
</div>
</div>
</div>
@ -65,25 +97,34 @@
<div class="card results" @click="clickCard(query.slug)">
<div class="card-body query-card">
<div class="row justify-content-between align-items-center">
<div class="col-sm-9 col-md-10">
<div class="col-sm-9 col-md-9">
<h5 class="card-title m-0">{{query.name}}</h5>
<p class="font-italic mb-1 p-0 description">{{query.description}}</p>
<div class="contributors" v-if="query.contributors && query.contributors.length">
<div class="d-flex mb-2 mb-sm-1 align-items-center">
<div v-for="contributor in query.contributors.split(',')">
<div v-for="contributor in query.contributors">
<div class="d-flex m-1 avatar-frame" @click="clickAvatar(contributor)">
<img alt="GitHub profile image" :alt="contributor" :src="getAvatarUrl(contributorsDictionary[contributor])"/>
<img alt="a GitHub user avatar" :alt="contributor" :src="contributor.avatarUrl" />
</div>
</div>
<p class="mb-0 ml-1">contributed by {{getContributorsString(query.contributors.split(','), contributorsDictionary)}}</p>
<p class="mb-0 ml-1">contributed by {{getContributorsString(query.contributors)}}</p>
</div>
</div>
</div>
<div class="col-sm-3 col-md-2">
<div class="col-sm-3 col-md-auto">
<div class="text-sm-right m-0">
<span v-if="query.platforms.includes('macOS')"><img class="d-inline mr-1 mr-sm-0 ml-sm-1 ml-md-2 logo" src="/images/os-macos-black-16x16@2x.png" alt="macOS"/></span>
<span v-if="query.platforms.includes('Windows')"><img class="d-inline mr-1 mr-sm-0 ml-sm-1 ml-md-2 logo" src="/images/os-windows-black-16x16@2x.png" alt="Windows"/></span>
<span v-if="query.platforms.includes('Linux')"><img class="d-inline mr-1 mr-ms-0 ml-sm-1 ml-md-2 logo" src="/images/os-linux-black-16x16@2x.png" alt="Linux"/></span>
<img class="d-inline-flex mr-1 mr-sm-0 ml-sm-1 ml-md-2 logo"
src="/images/os-macos-black-16x16@2x.png" alt="macOS"
v-if="query.platforms.includes('macOS')" />
<img class="d-inline-flex mr-1 mr-sm-0 ml-sm-1 ml-md-2 logo"
src="/images/os-windows-black-16x16@2x.png" alt="Windows"
v-if="query.platforms.includes('Windows')" />
<img class="d-inline-flex mr-1 mr-ms-0 ml-sm-1 ml-md-2 logo"
src="/images/os-linux-black-16x16@2x.png" alt="Linux"
v-if="query.platforms.includes('Linux')" />
<img class="d-inline-flex mr-1 mr-sm-0 ml-sm-1 ml-md-2 logo"
src="/images/os-freebsd-black-16x16@2x.png" alt="FreeBSD"
v-if="query.platforms.includes('FreeBSD')" />
</div>
</div>
</div>
@ -99,7 +140,11 @@
<div class="card call-to-action col-6-grow my-5 library">
<div class="card-body">
<h3 class="mb-3">Contributors</h3>
<p><strong>Want to add your own query?</strong> Please submit a pull request <a href="https://github.com/fleetdm/fleet/edit/main/docs/1-Using-Fleet/standard-query-library/standard-query-library.yml" >over on GitHub</a>.</p>
<p><strong>Want to add your own query?</strong> Please submit a pull request
<a href="https://github.com/fleetdm/fleet/edit/main/docs/1-Using-Fleet/standard-query-library/standard-query-library.yml">
over on GitHub
</a>.
</p>
</div>
</div>
</div>