Merge branch 'master' into teams

This commit is contained in:
Zach Wasserman 2021-05-31 10:56:50 -07:00
commit a5bd03e5d7
81 changed files with 1470 additions and 11366 deletions

View File

@ -1,4 +1,4 @@
blank_issues_enabled: false blank_issues_enabled: true
contact_links: contact_links:
- name: Chat with other developers - name: Chat with other developers
url: https://osquery.slack.com/join/shared_invite/zt-h29zm0gk-s2DBtGUTW4CFel0f0IjTEw#/ url: https://osquery.slack.com/join/shared_invite/zt-h29zm0gk-s2DBtGUTW4CFel0f0IjTEw#/
@ -6,7 +6,4 @@ contact_links:
- name: Documentation - name: Documentation
url: https://fleetdm.com/documentation url: https://fleetdm.com/documentation
about: Read about how to use, deploy, and configure Fleet. about: Read about how to use, deploy, and configure Fleet.
- name: Project
url: https://github.com/fleetdm/fleet/issues/new?milestone=x.x.x&labels=project&assignees=noahtalerman&body=%23%23%23%20Goal%0A%0ATODO%20(%E2%89%A42%20sentences)%0A%0A%23%23%23%20How%3F%0A%0A-%20%5B%20%5D%20TODO
about: Schedule a company-sponsored project to ship improvements to Fleet ("Self-managed autoupdates").

View File

@ -2,16 +2,19 @@
name: 💡  Feature request name: 💡  Feature request
about: Propose a new feature or enhancement in Fleet. about: Propose a new feature or enhancement in Fleet.
title: '' title: ''
labels: 'enhancement' labels: 'idea'
assignees: 'noahtalerman' assignees: ''
--- ---
### Goal ### Goal
TODO
<!-- Thanks for filing an issue! Please provide as much context as you can about your use case and motivations. --> <!-- Thanks for filing an issue! Please provide as much context as you can about your use case and motivations. -->
### Solution ### How?
<!-- You can leave this blank, or propose a solution. Please attach any screenshots or wireframes that might help convey your meaning. --> <!-- You can leave this blank, or propose a solution. You can also attach any screenshots or other visuals that might help convey your meaning. -->
- [ ]

View File

@ -43,7 +43,7 @@ jobs:
# > delete the top level .eslintrc file too. # > delete the top level .eslintrc file too.
- run: rm -f .eslintrc.js - run: rm -f .eslintrc.js
# Get dependencies (including dev deps) # Download dependencies (including dev deps)
- run: cd website/ && npm install - run: cd website/ && npm install
# Run sanity checks # Run sanity checks
@ -56,7 +56,7 @@ jobs:
# (This commit will never be pushed to GitHub- only to Heroku.) # (This commit will never be pushed to GitHub- only to Heroku.)
# > The local config flags make this work in GitHub's environment. # > The local config flags make this work in GitHub's environment.
- run: git add website/.www - run: git add website/.www
- run: git add -f website/views/partials/built-from-markdown # « for new HTML files compiled from markdown content - run: git add website/views/partials/built-from-markdown > /dev/null 2>&1 || echo '* * * WARNING - Silently ignoring the fact that there are no HTML partials generated from markdown to include in automated commit...'
- run: git -c "user.name=Fleetwood" -c "user.email=github@example.com" commit -am 'AUTOMATED COMMIT - Deployed the latest, including generated collateral such as compiled documentation, modified HTML layouts, and a .sailsrc file that references minified client-side code assets.' - run: git -c "user.name=Fleetwood" -c "user.email=github@example.com" commit -am 'AUTOMATED COMMIT - Deployed the latest, including generated collateral such as compiled documentation, modified HTML layouts, and a .sailsrc file that references minified client-side code assets.'
# Configure the Heroku app we'll be deploying to # Configure the Heroku app we'll be deploying to

View File

@ -1,4 +1,4 @@
name: Check Documentation name: Check for bad links in documentation
on: [push, pull_request] on: [push, pull_request]
@ -9,5 +9,6 @@ jobs:
- uses: actions/checkout@master - uses: actions/checkout@master
- uses: gaurav-nelson/github-action-markdown-link-check@v1 - uses: gaurav-nelson/github-action-markdown-link-check@v1
with: with:
check-modified-files-only: 'yes'
use-quiet-mode: 'yes' use-quiet-mode: 'yes'
config-file: .github/workflows/markdown-link-check-config.json config-file: .github/workflows/markdown-link-check-config.json

View File

@ -12,9 +12,6 @@
{ {
"pattern": "fleet.corp.example.com" "pattern": "fleet.corp.example.com"
}, },
{
"pattern": "hello@fleetdm.com"
},
{ {
"pattern": "/server/datastore/mysql/migrations/" "pattern": "/server/datastore/mysql/migrations/"
}, },

View File

@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
## Enforcement ## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by [contacting the Fleet team](hello@fleetdm.com). The team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by [contacting the Fleet team](https://fleetdm.com/contact). The team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

View File

@ -32,7 +32,7 @@ To open your browser's **network requests**, press Control Shift J (Windows, Lin
### Report a security vulnerability ### Report a security vulnerability
Sensitive security-related issues should be reported to Sensitive security-related issues should be reported to
[security@fleetdm.com](mailto:security@fleetdm.com) before a public issue is made. [fleetdm.com/contact](https://fleetdm.com/contact) before a public issue is made.
## Contributing to documentation ## Contributing to documentation

View File

@ -1,5 +1,5 @@
FROM alpine FROM alpine
MAINTAINER Fleet Developers <engineering@fleetdm.com> MAINTAINER Fleet Developers <hello@fleetdm.com>
RUN apk --update add ca-certificates RUN apk --update add ca-certificates

View File

@ -224,10 +224,10 @@ e2e-reset-db:
e2e-setup: e2e-setup:
./build/fleetctl config set --context e2e --address https://localhost:8642 ./build/fleetctl config set --context e2e --address https://localhost:8642
./build/fleetctl config set --context e2e --tls-skip-verify true ./build/fleetctl config set --context e2e --tls-skip-verify true
./build/fleetctl setup --context e2e --email=test@fleetdm.com --username=test --password=admin123# --org-name='Fleet Test' ./build/fleetctl setup --context e2e --email=test@example.com --username=test --password=admin123# --org-name='Fleet Test'
./build/fleetctl user create --context e2e --username=user1 --email=user1@example.com --sso=true ./build/fleetctl user create --context e2e --username=user1 --email=user1@example.com --sso=true
./build/fleetctl user create --context e2e --email=test+1@fleetdm.com --username=test1 --password=admin123# ./build/fleetctl user create --context e2e --email=test+1@example.com --username=test1 --password=admin123#
./build/fleetctl user create --context e2e --email=test+2@fleetdm.com --username=test2 --password=admin123# ./build/fleetctl user create --context e2e --email=test+2@example.com --username=test2 --password=admin123#
e2e-serve: e2e-serve:
./build/fleet serve --mysql_address=localhost:3307 --mysql_username=root --mysql_password=toor --auth_jwt_key=insecure --mysql_database=e2e --server_address=localhost:8642 ./build/fleet serve --mysql_address=localhost:3307 --mysql_username=root --mysql_password=toor --auth_jwt_key=insecure --mysql_database=e2e --server_address=localhost:8642

View File

@ -51,13 +51,4 @@ Please join us in the #fleet channel on [osquery Slack](https://osquery.slack.co
Contributions are welcome, whether you answer questions on Slack/GitHub/StackOverflow/Twitter, improve the documentation or website, write a tutorial, give a talk, start a local osquery meetup, troubleshoot reported issues, or [submit a patch](https://github.com/fleetdm/fleet/blob/master/CONTRIBUTING.md). The Fleet code of conduct is [on GitHub](https://github.com/fleetdm/fleet/blob/master/CODE_OF_CONDUCT.md). Contributions are welcome, whether you answer questions on Slack/GitHub/StackOverflow/Twitter, improve the documentation or website, write a tutorial, give a talk, start a local osquery meetup, troubleshoot reported issues, or [submit a patch](https://github.com/fleetdm/fleet/blob/master/CONTRIBUTING.md). The Fleet code of conduct is [on GitHub](https://github.com/fleetdm/fleet/blob/master/CODE_OF_CONDUCT.md).
#### Community projects
Below are some projects created by Fleet community members. Please submit a pull request if you'd like your project featured.
- [Kolide Cloud ("K2")](https://kolide.com) is a cloud-hosted, user-driven security SaaS application. To be clear: Kolide ≠ Fleet.
- [davidrecordon/terraform-aws-kolide-fleet](https://github.com/davidrecordon/terraform-aws-kolide-fleet) - Deploy Fleet into AWS using Terraform.
- [deeso/fleet-deployment](https://github.com/deeso/fleet-deployment) - Install Fleet on a Ubuntu box.
- [gjyoung1974/kolide-fleet-chart](https://github.com/gjyoung1974/kolide-fleet-chart) - Kubernetes Helm chart for deploying Fleet.
<a href="https://fleetdm.com"><img alt="Banner featuring a futuristic cloud city with the Fleet logo" src="https://user-images.githubusercontent.com/618009/98254443-eaf21100-1f41-11eb-9e2c-63a0545601f3.jpg"/></a> <a href="https://fleetdm.com"><img alt="Banner featuring a futuristic cloud city with the Fleet logo" src="https://user-images.githubusercontent.com/618009/98254443-eaf21100-1f41-11eb-9e2c-63a0545601f3.jpg"/></a>

View File

@ -2,6 +2,6 @@
## Reporting a Vulnerability ## Reporting a Vulnerability
Please report any vulnerabilities discovered in Fleet products to [security@fleetdm.com](mailto:security@fleetdm.com). Please report any vulnerabilities discovered in Fleet products to [fleetdm.com/contact](https://fleetdm.com/contact).
Fleet endeavors to acknowledge and fix any reported vulnerabilities ASAP. Acknowledgement is typically within 1 business day, and patches usually go out within 5 business days (depending on severity and timing). Fleet endeavors to acknowledge and fix any reported vulnerabilities ASAP. Acknowledgement is typically within 1 business day, and patches usually go out within 5 business days (depending on severity and timing).

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

View File

@ -350,7 +350,21 @@ func previewStopCommand() *cli.Command {
out, err := exec.Command("docker-compose", "stop").CombinedOutput() out, err := exec.Command("docker-compose", "stop").CombinedOutput()
if err != nil { if err != nil {
fmt.Println(string(out)) fmt.Println(string(out))
return errors.Errorf("Failed to run docker-compose stop") return errors.Errorf("Failed to run docker-compose stop for Fleet server and dependencies")
}
cmd := exec.Command("docker-compose", "stop")
cmd.Dir = filepath.Join(previewDir, "osquery")
cmd.Env = append(cmd.Env,
// Note that these must be set even though they are unused while
// stopping because docker-compose will error otherwise.
"ENROLL_SECRET=empty",
"FLEET_URL=empty",
)
out, err = cmd.CombinedOutput()
if err != nil {
fmt.Println(string(out))
return errors.Errorf("Failed to run docker-compose stop for simulated hosts")
} }
fmt.Println("Fleet preview server and dependencies stopped. Start again with fleetctl preview.") fmt.Println("Fleet preview server and dependencies stopped. Start again with fleetctl preview.")
@ -385,7 +399,21 @@ func previewResetCommand() *cli.Command {
out, err := exec.Command("docker-compose", "rm", "-sf").CombinedOutput() out, err := exec.Command("docker-compose", "rm", "-sf").CombinedOutput()
if err != nil { if err != nil {
fmt.Println(string(out)) fmt.Println(string(out))
return errors.Errorf("Failed to run docker-compose rm -sf") return errors.Errorf("Failed to run docker-compose rm -sf for Fleet server and dependencies.")
}
cmd := exec.Command("docker-compose", "rm", "-sf")
cmd.Dir = filepath.Join(previewDir, "osquery")
cmd.Env = append(cmd.Env,
// Note that these must be set even though they are unused while
// stopping because docker-compose will error otherwise.
"ENROLL_SECRET=empty",
"FLEET_URL=empty",
)
out, err = cmd.CombinedOutput()
if err != nil {
fmt.Println(string(out))
return errors.Errorf("Failed to run docker-compose rm -sf for simulated hosts.")
} }
fmt.Println("Fleet preview server and dependencies reset. Start again with fleetctl preview.") fmt.Println("Fleet preview server and dependencies reset. Start again with fleetctl preview.")

View File

@ -14,7 +14,7 @@ describe("User invite and activation", () => {
cy.findByLabelText(/name/i).click().type("Ash Ketchum"); cy.findByLabelText(/name/i).click().type("Ash Ketchum");
cy.findByLabelText(/email/i).click().type("ash@fleetdm.com"); cy.findByLabelText(/email/i).click().type("ash@example.com");
cy.findByRole("button", { name: /^create$/i }).click(); cy.findByRole("button", { name: /^create$/i }).click();
@ -30,9 +30,9 @@ describe("User invite and activation", () => {
cy.getEmails().then((response) => { cy.getEmails().then((response) => {
expect(response.body.items[0].To[0]).to.have.property("Domain"); expect(response.body.items[0].To[0]).to.have.property("Domain");
expect(response.body.items[0].To[0].Mailbox).to.equal("ash"); expect(response.body.items[0].To[0].Mailbox).to.equal("ash");
expect(response.body.items[0].To[0].Domain).to.equal("fleetdm.com"); expect(response.body.items[0].To[0].Domain).to.equal("example.com");
expect(response.body.items[0].From.Mailbox).to.equal("gabriel+dev"); expect(response.body.items[0].From.Mailbox).to.equal("fleet");
expect(response.body.items[0].From.Domain).to.equal("fleetdm.com"); expect(response.body.items[0].From.Domain).to.equal("example.com");
const match = response.body.items[0].Content.Body.match(regex); const match = response.body.items[0].Content.Body.match(regex);
inviteLink.url = match[0]; inviteLink.url = match[0];
}); });

View File

@ -16,17 +16,17 @@ describe("Manage Users", () => {
cy.wait("@getUsers"); cy.wait("@getUsers");
cy.findByText("test@fleetdm.com").should("exist"); cy.findByText("test@example.com").should("exist");
cy.findByText("test+1@fleetdm.com").should("exist"); cy.findByText("test+1@example.com").should("exist");
cy.findByText("test+2@fleetdm.com").should("exist"); cy.findByText("test+2@example.com").should("exist");
cy.findByPlaceholderText("Search").type("test@fleetdm.com"); cy.findByPlaceholderText("Search").type("test@example.com");
cy.wait("@getUsers"); cy.wait("@getUsers");
cy.findByText("test@fleetdm.com").should("exist"); cy.findByText("test@example.com").should("exist");
cy.findByText("test+1@fleetdm.com").should("not.exist"); cy.findByText("test+1@example.com").should("not.exist");
cy.findByText("test+2@fleetdm.com").should("not.exist"); cy.findByText("test+2@example.com").should("not.exist");
}); });
// it('Creating a user', () => { // it('Creating a user', () => {
@ -40,7 +40,7 @@ describe("Manage Users", () => {
// .type('New User'); // .type('New User');
// //
// cy.findByPlaceholderText('Email') // cy.findByPlaceholderText('Email')
// .type('new-user@fleetdm.com'); // .type('new-user@example.com');
// //
// cy.findByRole('checkbox', { name: 'Test Team' }) // cy.findByRole('checkbox', { name: 'Test Team' })
// .click({ force: true }); // we use `force` as the checkbox button is not fully accessible yet. // .click({ force: true }); // we use `force` as the checkbox button is not fully accessible yet.

View File

@ -48,7 +48,7 @@ describe("Settings flow", () => {
cy.findByLabelText(/sender address/i) cy.findByLabelText(/sender address/i)
.click() .click()
.type("rachel@fleetdm.com"); .type("rachel@example.com");
cy.findByLabelText(/smtp server/i) cy.findByLabelText(/smtp server/i)
.click() .click()
@ -121,7 +121,7 @@ describe("Settings flow", () => {
cy.findByLabelText(/sender address/i).should( cy.findByLabelText(/sender address/i).should(
"have.value", "have.value",
"rachel@fleetdm.com" "rachel@example.com"
); );
cy.findByLabelText(/smtp server/i).should("have.value", "localhost"); cy.findByLabelText(/smtp server/i).should("have.value", "localhost");
@ -140,9 +140,9 @@ describe("Settings flow", () => {
cy.getEmails().then((response) => { cy.getEmails().then((response) => {
expect(response.body.items[0].To[0]).to.have.property("Domain"); expect(response.body.items[0].To[0]).to.have.property("Domain");
expect(response.body.items[0].To[0].Mailbox).to.equal("test"); expect(response.body.items[0].To[0].Mailbox).to.equal("test");
expect(response.body.items[0].To[0].Domain).to.equal("fleetdm.com"); expect(response.body.items[0].To[0].Domain).to.equal("example.com");
expect(response.body.items[0].From.Mailbox).to.equal("rachel"); expect(response.body.items[0].From.Mailbox).to.equal("rachel");
expect(response.body.items[0].From.Domain).to.equal("fleetdm.com"); expect(response.body.items[0].From.Domain).to.equal("example.com");
expect(response.body.items[0].Content.Headers.Subject[0]).to.equal( expect(response.body.items[0].Content.Headers.Subject[0]).to.equal(
"Hello from Fleet" "Hello from Fleet"
); );

View File

@ -10,7 +10,7 @@ describe("Sessions", () => {
cy.contains(/forgot password/i); cy.contains(/forgot password/i);
// Log in // Log in
cy.get("input").first().type("test@fleetdm.com"); cy.get("input").first().type("test@example.com");
cy.get("input").last().type("admin123#"); cy.get("input").last().type("admin123#");
cy.get("button").click(); cy.get("button").click();
@ -27,7 +27,7 @@ describe("Sessions", () => {
it("Fails login with invalid password", () => { it("Fails login with invalid password", () => {
cy.visit("/"); cy.visit("/");
cy.get("input").first().type("test@fleetdm.com"); cy.get("input").first().type("test@example.com");
cy.get("input").last().type("bad_password"); cy.get("input").last().type("bad_password");
cy.get(".button").click(); cy.get(".button").click();

View File

@ -12,7 +12,7 @@ describe("SSO Sessions", () => {
cy.contains(/forgot password/i); cy.contains(/forgot password/i);
// Log in // Log in
cy.get("input").first().type("test@fleetdm.com"); cy.get("input").first().type("test@example.com");
cy.get("input").last().type("admin123#"); cy.get("input").last().type("admin123#");
cy.contains("button", "Login").click(); cy.contains("button", "Login").click();

View File

@ -20,7 +20,7 @@ describe("Setup", () => {
.last() .last()
.type("admin123#"); .type("admin123#");
cy.findByPlaceholderText(/email/i).type("test@fleetdm.com"); cy.findByPlaceholderText(/email/i).type("test@example.com");
cy.contains("button:enabled", /next/i).click(); cy.contains("button:enabled", /next/i).click();

View File

@ -59,28 +59,7 @@ Cypress.Commands.add("setupSMTP", () => {
authentication_type: "authtype_none", authentication_type: "authtype_none",
enable_smtp: true, enable_smtp: true,
port: 1025, port: 1025,
sender_address: "gabriel+dev@fleetdm.com", sender_address: "fleet@example.com",
server: "localhost",
},
};
cy.request({
url: "/api/v1/fleet/config",
method: "PATCH",
body,
auth: {
bearer: window.localStorage.getItem("KOLIDE::auth_token"),
},
});
});
Cypress.Commands.add("setupSMTP", () => {
const body = {
smtp_settings: {
authentication_type: "authtype_none",
enable_smtp: true,
port: 1025,
sender_address: "gabriel+dev@fleetdm.com",
server: "localhost", server: "localhost",
}, },
}; };

View File

@ -318,6 +318,8 @@ spec:
removed: false removed: false
``` ```
The `targets` field allows you to specify the `labels` field. With the `labels` field, the hosts that become members of the specified labels, upon enrolling to Fleet, will automatically become targets of the given pack.
#### Moving queries and packs from one Fleet environment to another #### Moving queries and packs from one Fleet environment to another
When managing multiple Fleet environments, you may want to move queries and/or packs from one "exporter" environment to a another "importer" environment. When managing multiple Fleet environments, you may want to move queries and/or packs from one "exporter" environment to a another "importer" environment.

View File

@ -93,7 +93,7 @@ Then, use that API token to authenticate all subsequent API requests by sending
Authorization: Bearer <your token> Authorization: Bearer <your token>
``` ```
> For SSO users, username/password login is disabled. The API token can instead be retrieved from the "Settings" page in the UI. > For SSO users, username/password login is disabled. The API token can instead be retrieved from the "Account settings" page in the UI (/profile). Choose "Get API token".
### Log in ### Log in
@ -487,15 +487,17 @@ This is the callback endpoint that the identity provider will use to send securi
#### Parameters #### Parameters
| Name | Type | In | Description | | Name | Type | In | Description |
| ----------------------- | ------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ----------------------- | ------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| page | integer | query | Page number of the results to fetch. | | page | integer | query | Page number of the results to fetch. |
| per_page | integer | query | Results per page. | | per_page | integer | query | Results per page. |
| order_key | string | query | What to order results by. Can be any column in the hosts table. | | order_key | string | query | What to order results by. Can be any column in the hosts table. |
| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include `asc` and `desc`. Default is `asc`. | | order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include `asc` and `desc`. Default is `asc`. |
| status | string | query | Indicates the status of the hosts to return. Can either be `new`, `online`, `offline`, or `mia`. | | status | string | query | Indicates the status of the hosts to return. Can either be `new`, `online`, `offline`, or `mia`. |
| query | string | query | Search query keywords. Searchable fields include `hostname`, `machine_serial`, `uuid`, and `ipv4`. | | query | string | query | Search query keywords. Searchable fields include `hostname`, `machine_serial`, `uuid`, and `ipv4`. |
| additional_info_filters | string | query | A comma-delimited list of fields to include in each host's additional information object. See [Fleet Configuration Options](https://github.com/fleetdm/fleet/blob/master/docs/1-Using-Fleet/2-fleetctl-CLI.md#fleet-configuration-options) for an example configuration with hosts' additional information. | | additional_info_filters | string | query | A comma-delimited list of fields to include in each host's additional information object. See [Fleet Configuration Options](https://github.com/fleetdm/fleet/blob/master/docs/1-Using-Fleet/2-fleetctl-CLI.md#fleet-configuration-options) for an example configuration with hosts' additional information. Use `*` to get all stored fields. |
If `additional_info_filters` is not specified, no `additional` information will be returned.
#### Example #### Example

View File

@ -1,5 +1,6 @@
# Using Fleet FAQ # Using Fleet FAQ
- [What do I need to do to switch from Kolide Fleet to FleetDM Fleet?](#waht-do-i-need-to-do-to-switch-from-kolide-fleet-to-fleetdm-fleet)
- [Has anyone stress tested Fleet? How many clients can the Fleet server handle?](#has-anyone-stress-tested-fleet-how-many-clients-can-the-fleet-server-handle) - [Has anyone stress tested Fleet? How many clients can the Fleet server handle?](#has-anyone-stress-tested-fleet-how-many-clients-can-the-fleet-server-handle)
- [Can I target my hosts using their enroll secrets?](#can-I-target-my-hosts-using-their-enroll-secrets) - [Can I target my hosts using their enroll secrets?](#can-I-target-my-hosts-using-their-enroll-secrets)
- [How often do labels refresh? Is the refresh frequency configurable?](#how-often-do-labels-refresh-is-the-refresh-frequency-configurable) - [How often do labels refresh? Is the refresh frequency configurable?](#how-often-do-labels-refresh-is-the-refresh-frequency-configurable)
@ -13,6 +14,14 @@
- [Can I use the Fleet API to fetch results from a scheduled query pack?](#can-i-use-the-fleet-api-to-fetch-results-from-a-scheduled-query-pack) - [Can I use the Fleet API to fetch results from a scheduled query pack?](#can-i-use-the-fleet-api-to-fetch-results-from-a-scheduled-query-pack)
- [How do I automatically add hosts to packs when the hosts enroll to Fleet?](#how-do-i-automatically-add-hosts-to-packs-when-the-hosts-enroll-to-Fleet) - [How do I automatically add hosts to packs when the hosts enroll to Fleet?](#how-do-i-automatically-add-hosts-to-packs-when-the-hosts-enroll-to-Fleet)
## What do I need to do to switch from Kolide Fleet to FleetDM Fleet?
The upgrade from kolide/fleet to fleetdm/fleet works the same as any minor version upgrade has in the past.
Minor version upgrades in Kolide Fleet often included database migrations and the recommendation to back up the database before migrating. The same goes for FleetDM Fleet versions.
To migrate from Kolide Fleet to FleetDM Fleet, please follow the steps outlined in the [Updating Fleet section](./7-Updating-Fleet.md) of the documentation.
## Has anyone stress tested Fleet? How many clients can the Fleet server handle? ## Has anyone stress tested Fleet? How many clients can the Fleet server handle?
Fleet has been stress tested to 150,000 online hosts and 400,000 total enrolled hosts. Production deployments exist with over 100,000 hosts and numerous production deployments manage tens of thousands of hosts. Fleet has been stress tested to 150,000 online hosts and 400,000 total enrolled hosts. Production deployments exist with over 100,000 hosts and numerous production deployments manage tens of thousands of hosts.
@ -108,6 +117,8 @@ Its possible in Fleet to retrieve each hosts kernel version, using the Fle
## How do I automatically add hosts to packs when the hosts enroll to Fleet? ## How do I automatically add hosts to packs when the hosts enroll to Fleet?
You can do this by setting the `targets` field in the [YAML configuration file](./2-fleetctl-CLI.md#query-packs) that manages the packs that are added to your Fleet instance. You can accomplish this by adding specific labels as targets of your pack. First, identify an already existing label or create a new label that will include the hosts you intend to enroll to Fleet. Next, add this label as a target of the pack in the Fleet UI.
The `targets` field allows you to specify the `labels` field. With the `labels` field, the hosts that become members of the specified labels, upon enrolling to Fleet, will automatically become targets of the given pack. When your hosts enroll to Fleet, they will become a member of the label and, because the label is a target of your pack, these hosts will automatically become targets of the pack.
You can also do this by setting the `targets` field in the [YAML configuration file](./2-fleetctl-CLI.md#query-packs) that manages the packs that are added to your Fleet instance.

View File

@ -7,7 +7,7 @@ spec:
description: Count the number of Apple applications installed on the machine. description: Count the number of Apple applications installed on the machine.
query: SELECT COUNT(*) FROM apps WHERE bundle_identifier LIKE 'com.apple.%'; query: SELECT COUNT(*) FROM apps WHERE bundle_identifier LIKE 'com.apple.%';
purpose: Informational purpose: Informational
remediation: N/A contributors: mike-j-thomas,noahtalerman,mikermcneil
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -17,7 +17,6 @@ spec:
description: Retrieves the OpenSSL version. 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%'; 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 purpose: Detection
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -27,7 +26,6 @@ spec:
description: Gatekeeper tries to ensure only trusted software is run on a mac machine. description: Gatekeeper tries to ensure only trusted software is run on a mac machine.
query: SELECT * FROM gatekeeper WHERE assessments_enabled = 0; query: SELECT * FROM gatekeeper WHERE assessments_enabled = 0;
purpose: Detection purpose: Detection
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -42,12 +40,22 @@ spec:
apiVersion: v1 apiVersion: v1
kind: query kind: query
spec: spec:
name: Get authorized keys name: Get authorized keys for Local Accounts
platforms: macOS, Linux platforms: macOS, Linux
description: List authorized_keys for each user on the system. description: List authorized_keys for each user on the system.
query: SELECT * FROM users CROSS JOIN authorized_keys USING (uid); query: SELECT * FROM users CROSS JOIN authorized_keys USING (uid);
purpose: Informational purpose: Informational
remediation: N/A contributors: anelshaer
---
apiVersion: v1
kind: query
spec:
name: Get authorized keys for Domain Joined Accounts
platforms: macOS, Linux
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
contributors: anelshaer
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -57,7 +65,6 @@ spec:
description: Retrieve application, system, and mobile app crash logs. 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); query: SELECT uid, datetime, responsible, exception_type, identifier, version, crash_path FROM users CROSS JOIN crashes USING (uid);
purpose: Informational purpose: Informational
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -67,7 +74,6 @@ spec:
description: List installed Chrome Extensions for all users. 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); query: SELECT uid, datetime, responsible, exception_type, identifier, version, crash_path FROM users CROSS JOIN crashes USING (uid);
purpose: Informational purpose: Informational
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -77,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. 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; 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 purpose: Informational
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -87,7 +92,6 @@ spec:
description: Get the installed homebrew package database. description: Get the installed homebrew package database.
query: SELECT * FROM homebrew_packages; query: SELECT * FROM homebrew_packages;
purpose: Informational purpose: Informational
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -97,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. 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; 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 purpose: Informational
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -107,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. 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; 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 purpose: Informational
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -117,7 +119,6 @@ spec:
description: Retrieves the list of installed Safari Extensions for all users in the target system. 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); query: SELECT safari_extensions.* FROM users join safari_extensions USING (uid);
purpose: Informational purpose: Informational
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -127,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. 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; 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 purpose: Informational
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -137,7 +137,6 @@ spec:
description: description:
query: SELECT * FROM battery WHERE health != 'Good' AND condition NOT IN ('', 'Normal'); query: SELECT * FROM battery WHERE health != 'Good' AND condition NOT IN ('', 'Normal');
purpose: Informational purpose: Informational
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -147,17 +146,6 @@ spec:
description: Displays the percentage of free space available on the primary disk partition. 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 = '/'; query: SELECT (blocks_available * 100 / blocks) AS pct, * FROM mounts WHERE path = '/';
purpose: Informational purpose: Informational
remediation: N/A
---
apiVersion: v1
kind: query
spec:
name: Get macOS disk free space percentage
platforms: macOS
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 apiVersion: v1
kind: query kind: query
@ -167,7 +155,6 @@ spec:
description: Shows system mounted devices and filesystems (not process specific). description: Shows system mounted devices and filesystems (not process specific).
query: SELECT device, device_alias, path, type, blocks_size FROM mounts; query: SELECT device, device_alias, path, type, blocks_size FROM mounts;
purpose: Informational purpose: Informational
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -177,7 +164,6 @@ spec:
description: Shows system mounted devices and filesystems (not process specific). description: Shows system mounted devices and filesystems (not process specific).
query: SELECT * FROM os_version; query: SELECT * FROM os_version;
purpose: Informational purpose: Informational
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -187,7 +173,6 @@ spec:
description: Shows information about the host platform description: Shows information about the host platform
query: SELECT vendor, version, date, revision from platform_info; query: SELECT vendor, version, date, revision from platform_info;
purpose: Informational purpose: Informational
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -197,7 +182,6 @@ spec:
description: Shows applications and binaries set as user/login startup items. description: Shows applications and binaries set as user/login startup items.
query: SELECT * FROM startup_items; query: SELECT * FROM startup_items;
purpose: Informational purpose: Informational
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -207,7 +191,16 @@ spec:
description: Get a list of system logins and logouts. description: Get a list of system logins and logouts.
query: SELECT * FROM last; query: SELECT * FROM last;
purpose: Informational purpose: Informational
remediation: N/A ---
apiVersion: v1
kind: query
spec:
name: Get current users with active shell/console on the system
platforms: macOS, Linux, Windows, FreeBSD
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
contributors: anelshaer
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -217,7 +210,6 @@ spec:
description: Shows the system uptime. description: Shows the system uptime.
query: SELECT * FROM uptime; query: SELECT * FROM uptime;
purpose: Informational purpose: Informational
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -227,7 +219,6 @@ spec:
description: Shows all USB devices that are actively plugged into the host system. description: Shows all USB devices that are actively plugged into the host system.
query: SELECT * FROM usb_devices; query: SELECT * FROM usb_devices;
purpose: Informational purpose: Informational
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -237,7 +228,6 @@ spec:
description: Shows information about the wifi network that a host is currently connected to. description: Shows information about the wifi network that a host is currently connected to.
query: SELECT * FROM wifi_status; query: SELECT * FROM wifi_status;
purpose: Informational purpose: Informational
remediation: N/A
--- ---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -247,4 +237,159 @@ spec:
description: description:
query: SELECT * FROM bitlocker_info WHERE protection_status = 0; query: SELECT * FROM bitlocker_info WHERE protection_status = 0;
purpose: Informational purpose: Informational
remediation: N/A ---
apiVersion: v1
kind: query
spec:
name: Get disk encryption status
platforms: macOS, Linux
description: Disk encryption status and information.
query: SELECT * FROM disk_encryption;
purpose: Informational
contributors: anelshaer
---
apiVersion: v1
kind: query
spec:
name: Detect unencrypted SSH keys for local accounts
platforms: macOS, Linux, Windows, FreeBSD
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: First, make the user aware about the impact of SSH keys. Then rotate the unencrypted keys detected.
contributors: anelshaer
---
apiVersion: v1
kind: query
spec:
name: Detect unencrypted SSH keys for domain joined accounts
platforms: macOS, Linux, Windows, FreeBSD
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: First, make the user aware about the impact of SSH keys. Then rotate the unencrypted keys detected.
contributors: anelshaer
---
apiVersion: v1
kind: query
spec:
name: Get crontab jobs
platforms: macOS, Linux
description: Line parsed values from system and user cron/tab.
query: SELECT * FROM crontab;
purpose: Informational
contributors: anelshaer
---
apiVersion: v1
kind: query
spec:
name: Get suid binaries
platforms: macOS, Linux
description: suid binaries in common locations.
query: SELECT * FROM suid_bin;
purpose: Informational
---
apiVersion: v1
kind: query
spec:
name: Detect dynamic linker hijacking on Linux (MITRE. T1574.006)
platforms: Linux
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 and confirm with the system's owner.
contributors: anelshaer
---
apiVersion: v1
kind: query
spec:
name: Detect dynamic linker hijacking on macOS (MITRE. T1574.006)
platforms: macOS
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 and confirm with the system's owner.
contributors: anelshaer
---
apiVersion: v1
kind: query
spec:
name: Get etc hosts entries
platforms: macOS, Linux
description: Line-parsed /etc/hosts
query: SELECT * FROM etc_hosts WHERE address not in ('127.0.0.1', '::1');
purpose: Informational
contributors: anelshaer
---
apiVersion: v1
kind: query
spec:
name: Get network interfaces
platforms: macOS, Linux, Windows, FreeBSD
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
contributors: anelshaer
---
apiVersion: v1
kind: query
spec:
name: Get local user accounts
platforms: macOS, Linux, Windows, FreeBSD
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
contributors: anelshaer
---
apiVersion: v1
kind: query
spec:
name: Detect active user accounts on servers
platforms: Linux
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
contributors: anelshaer
---
apiVersion: v1
kind: query
spec:
name: Detect Nmap scanner
platforms: macOS, Linux, Windows, FreeBSD
description: Detect Nmap scanner process, identify the user, parent, process details.
query: SELECT p.pid, name, p.path, cmdline, cwd, start_time, parent,
(SELECT name FROM processes WHERE pid=p.parent) AS parent_name,
(SELECT username FROM users WHERE uid=p.uid) AS username
FROM processes as p WHERE cmdline like 'nmap%';
purpose: Detection
contributors: anelshaer
---
apiVersion: v1
kind: query
spec:
name: Get docker images on a system
platforms: macOS, Linux
description: Docker images information, can be used on normal system or a kubenode.
query: SELECT * FROM docker_images;
purpose: Informational
contributors: anelshaer
---
apiVersion: v1
kind: query
spec:
name: Get docker running containers on a system
platforms: macOS, Linux
description: Docker containers information, can be used on normal system or a kubenode.
query: SELECT * FROM docker_containers;
purpose: Informational
contributors: anelshaer
---
apiVersion: v1
kind: query
spec:
name: Get docker running process on a system
platforms: macOS, Linux
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
contributors: anelshaer

View File

@ -2,7 +2,7 @@
Orbit is an [osquery](https://github.com/osquery/osquery) runtime and autoupdater. With Orbit, it's easy to deploy osquery, manage configurations, and stay up to date. Orbit eases the deployment of osquery connected with a [Fleet server](https://github.com/fleetdm/fleet), and is a (near) drop-in replacement for osquery in a variety of deployment scenarios. Orbit is an [osquery](https://github.com/osquery/osquery) runtime and autoupdater. With Orbit, it's easy to deploy osquery, manage configurations, and stay up to date. Orbit eases the deployment of osquery connected with a [Fleet server](https://github.com/fleetdm/fleet), and is a (near) drop-in replacement for osquery in a variety of deployment scenarios.
Orbit is the recommended agent for Fleet. But Orbit can be used with or without Fleet, and Fleet can be used with or without Orbit. Orbit is the recommended agent for Fleet. But Orbit can be used with or without Fleet, and Fleet can be used with or without Orbit.
## Usage ## Usage
@ -10,7 +10,7 @@ General information and flag documentation can be accessed by running `orbit --h
### Permissions ### Permissions
Orbit generally expects root permissions to be able to create and access it's working files. Orbit generally expects root permissions to be able to create and access it's working files.
To get root level permissions: To get root level permissions:
@ -32,20 +32,20 @@ Use the `--fleet-url` and `--enroll-secret` flags to connect to a Fleet server.
For example: For example:
``` sh ```sh
orbit --fleet-url=https://localhost:8080 --enroll-secret=the_secret_value orbit --fleet-url=https://localhost:8080 --enroll-secret=the_secret_value
``` ```
Use `--fleet_certificate` to provide a path to a certificate bundle when necessary for osquery to verify the authenticity of the Fleet server (typically when using a Windows client or self-signed certificates): Use `--fleet_certificate` to provide a path to a certificate bundle when necessary for osquery to verify the authenticity of the Fleet server (typically when using a Windows client or self-signed certificates):
``` sh ```sh
orbit --fleet-url=https://localhost:8080 --enroll-secret=the_secret_value --fleet-certificate=cert.pem orbit --fleet-url=https://localhost:8080 --enroll-secret=the_secret_value --fleet-certificate=cert.pem
``` ```
Add the `--insecure` flag for connections using otherwise invalid certificates: Add the `--insecure` flag for connections using otherwise invalid certificates:
``` sh ```sh
orbit --fleet-url=https://localhost:8080 --enroll-secret=the_secret_value --insecure orbit --fleet-url=https://localhost:8080 --enroll-secret=the_secret_value --insecure
``` ```
### Osquery flags ### Osquery flags
@ -54,7 +54,7 @@ Orbit can be used as near drop-in replacement for `osqueryd`, enhancing standard
For example, the following would be a typical drop-in usage of Orbit: For example, the following would be a typical drop-in usage of Orbit:
``` sh ```sh
orbit -- --flagfile=flags.txt orbit -- --flagfile=flags.txt
``` ```
@ -62,27 +62,33 @@ orbit -- --flagfile=flags.txt
Orbit, like standalone osquery, is typically deployed via OS-specific packages. Tooling is provided with this repository to generate installation packages. Orbit, like standalone osquery, is typically deployed via OS-specific packages. Tooling is provided with this repository to generate installation packages.
### Dependencies
Orbit currently supports building packages on macOS and Linux.
Before building packages, clone or download this repository and [install Go](https://golang.org/doc/install).
Building Windows packages requires Docker to be installed.
### Packaging support ### Packaging support
- **macOS** - `.pkg` package generation with (optional) [Notarization](https://developer.apple.com/documentation/xcode/notarizing_macos_software_before_distribution) and codesigning - Persistence via `launchd`. - **macOS** - `.pkg` package generation with (optional) [Notarization](https://developer.apple.com/documentation/xcode/notarizing_macos_software_before_distribution) and codesigning - Persistence via `launchd`.
- **Linux** - `.deb` (Debian, Ubuntu, etc.) & `.rpm` (RHEL, CentOS, etc.) package generation - Persistence via `systemd`. - **Linux** - `.deb` (Debian, Ubuntu, etc.) & `.rpm` (RHEL, CentOS, etc.) package generation - Persistence via `systemd`.
- **Windows** (coming soon) - `.msi` package generation - Persistence via Services. - **Windows** - `.msi` package generation - Persistence via Services.
### Building packages ### Building packages
Before building packages, clone or download this repository and [install Go](https://golang.org/doc/install).
Use `go run ./cmd/package` from this directory to run the packaging tools. Use `go run ./cmd/package` from this directory to run the packaging tools.
The only required parameter is `--type`, use one of `deb`, `rpm`, or `pkg` (`msi` coming soon). The only required parameter is `--type`, use one of `deb`, `rpm`, `pkg`, or `msi`.
Configure osquery to connect to a Fleet (or other TLS) server with the `--fleet-url` and `--enroll-secret` flags. Configure osquery to connect to a Fleet (or other TLS) server with the `--fleet-url` and `--enroll-secret` flags.
A minimal invocation for communicating with Fleet: A minimal invocation for communicating with Fleet:
``` sh ```sh
go run ./cmd/package --type deb --fleet-url=fleet.example.com --enroll-secret=notsosecret go run ./cmd/package --type deb --fleet-url=fleet.example.com --enroll-secret=notsosecret
``` ```
@ -98,11 +104,11 @@ Orbit uses the concept of "update channels" to determine the version of Orbit, o
Configure update channels for Orbit and osqueryd with the `--orbit-channel` and `--osqueryd-channel` flags when packaging. Configure update channels for Orbit and osqueryd with the `--orbit-channel` and `--osqueryd-channel` flags when packaging.
| Channel | Versions | | Channel | Versions |
| ------------------------------------ | ------ | | ------- | -------- |
| `4` | 4.x.x | | `4` | 4.x.x |
| `4.6` | 4.6.x | | `4.6` | 4.6.x |
| `4.6.0` | 4.6.0 | | `4.6.0` | 4.6.0 |
Additionally `stable` and `edge` are special channel names. `stable` will always return the version Fleet deems to be stable, while `edge` will provide newer releases for beta testing. Additionally `stable` and `edge` are special channel names. `stable` will always return the version Fleet deems to be stable, while `edge` will provide newer releases for beta testing.
@ -116,13 +122,13 @@ For Notarization, valid App Store Connect credentials must be available on the b
Build a signed and notarized macOS package with an invocation like the following: Build a signed and notarized macOS package with an invocation like the following:
``` sh ```sh
AC_USERNAME=zach@fleetdm.com AC_PASSWORD=llpk-sije-kjlz-jdzw go run ./cmd/package --type=pkg --fleet-url=fleet.example.com --enroll-secret=63SBzTT+2UyW --sign-identity 3D7260BF99539C6E80A94835A8921A988F4E6498 --notarize AC_USERNAME=zach@example.com AC_PASSWORD=llpk-sije-kjlz-jdzw go run ./cmd/package --type=pkg --fleet-url=fleet.example.com --enroll-secret=63SBzTT+2UyW --sign-identity 3D7260BF99539C6E80A94835A8921A988F4E6498 --notarize
``` ```
This process may take several minutes to complete as the Notarization process completes on Apple's servers. This process may take several minutes to complete as the Notarization process completes on Apple's servers.
After successful notarization, the generated "ticket" is automatically stapled to the package. After successful notarization, the generated "ticket" is automatically stapled to the package.
## FAQs ## FAQs

View File

@ -25,13 +25,17 @@ Your Fleet server's two main purposes are:
- To serve as your [osquery TLS server](https://osquery.readthedocs.io/en/stable/deployment/remote/) - To serve as your [osquery TLS server](https://osquery.readthedocs.io/en/stable/deployment/remote/)
- To serve the Fleet web UI, which allows you to manage osquery configuration, query hosts, etc. - To serve the Fleet web UI, which allows you to manage osquery configuration, query hosts, etc.
The Fleet server allows you persist configuration, manage users, etc. Thus, it needs a database. Fleet uses MySQL and requires you to supply configurations to connect to a MySQL server. Fleet also uses Redis to perform some more high-speed data access action throughout the lifecycle of the application (for example, distributed query result ingestion). Thus, Fleet also requires that you supply Redis connention configurations. The Fleet server allows you persist configuration, manage users, etc. Thus, it needs a database. Fleet uses MySQL and requires you to supply configurations to connect to a MySQL server. Fleet also uses Redis to perform some more high-speed data access action throughout the lifecycle of the application (for example, distributed query result ingestion). Thus, Fleet also requires that you supply Redis connection configurations.
> Fleet does not support Redis Cluster. Fleet can scale to hundreds of thousands of devices with a single Redis instance.
Since Fleet is a web application, when you run Fleet there are some other configurations that are worth defining, such as: Since Fleet is a web application, when you run Fleet there are some other configurations that are worth defining, such as:
- The TLS certificates that Fleet should use to terminate TLS. - The TLS certificates that Fleet should use to terminate TLS.
- The [JWT](https://jwt.io/) Key which is used to sign and verify session tokens. - The [JWT](https://jwt.io/) Key which is used to sign and verify session tokens.
When deploying Fleet, mitigate DoS attacks as you would when deploying any app.
Since Fleet is an osquery TLS server, you are also able to define configurations that can customize your experience there, such as: Since Fleet is an osquery TLS server, you are also able to define configurations that can customize your experience there, such as:
- The destination of the osquery status and result logs on the local filesystem - The destination of the osquery status and result logs on the local filesystem
@ -182,6 +186,7 @@ The password to use when connecting to the MySQL instance.
File path to a file that contains the password to use when connecting to the MySQL instance. File path to a file that contains the password to use when connecting to the MySQL instance.
- Default value: `""` - Default value: `""`
- Environment variable: `FLEET_MYSQL_PASSWORD_PATH`
- Config file format: - Config file format:
``` ```
@ -333,7 +338,7 @@ The database to use when connecting to the Redis instance.
redis: redis:
database: 14 database: 14
``` ```
###### `redis_duplicate_results` ###### `redis_duplicate_results`
Whether or not to duplicate Live Query results to another Redis channel named `LQDuplicate`. This is useful in a scenario that would involve shipping the Live Query results outside of Fleet, near-realtime. Whether or not to duplicate Live Query results to another Redis channel named `LQDuplicate`. This is useful in a scenario that would involve shipping the Live Query results outside of Fleet, near-realtime.
@ -468,11 +473,12 @@ The [JWT](https://jwt.io/) key to use when signing and validating session keys.
File path to a file that contains the [JWT](https://jwt.io/) key to use when signing and validating session keys. File path to a file that contains the [JWT](https://jwt.io/) key to use when signing and validating session keys.
- Default value: `""` - Default value: `""`
- Environment variable: `FLEET_AUTH_JWT_KEY_PATH`
- Config file format: - Config file format:
``` ```
auth: auth:
jwt_key_path: '/run/secrets/fleetdm-jwt-token jwt_key_path: '/run/secrets/fleetdm-jwt-token'
``` ```
##### `auth_bcrypt_cost` ##### `auth_bcrypt_cost`
@ -480,7 +486,7 @@ File path to a file that contains the [JWT](https://jwt.io/) key to use when sig
The bcrypt cost to use when hashing user passwords. The bcrypt cost to use when hashing user passwords.
- Default value: `12` - Default value: `12`
- Environment variable: `FLEET_AUTH_BCRYT_COST` - Environment variable: `FLEET_AUTH_BCRYPT_COST`
- Config file format: - Config file format:
``` ```
@ -521,7 +527,7 @@ Size of generated app tokens.
How long invite tokens should be valid for. How long invite tokens should be valid for.
- Default value: `5 days` - Default value: `5 days`
- Environment variable: `FLEET_APP_TOKEN_VALIDITY_PERIOD` - Environment variable: `FLEET_APP_INVITE_TOKEN_VALIDITY_PERIOD`
- Config file format: - Config file format:
``` ```
@ -598,7 +604,7 @@ The cooldown period for host enrollment. If a host (uniquely identified by the `
This flag can be used to control load on the database in scenarios in which many hosts are using the same identifier. Often configuring `osquery_host_identifier` to `instance` may be a better solution. This flag can be used to control load on the database in scenarios in which many hosts are using the same identifier. Often configuring `osquery_host_identifier` to `instance` may be a better solution.
- Default value: `0` (off) - Default value: `0` (off)
- Environment variable: `FLEET_ENROLL_COOLDOWN` - Environment variable: `FLEET_OSQUERY_ENROLL_COOLDOWN`
- Config file format: - Config file format:
``` ```
@ -1200,7 +1206,7 @@ The identifier of the pubsub topic that osquery status logs will be published to
This flag only has effect if `osquery_status_log_plugin` is set to `pubsub`. This flag only has effect if `osquery_status_log_plugin` is set to `pubsub`.
Add Pub/Sub attributes to messages. When enabled, the plugin parses the osquery result Add Pub/Sub attributes to messages. When enabled, the plugin parses the osquery result
messages, and adds the following Pub/Sub message attributes: messages, and adds the following Pub/Sub message attributes:
- `name` - the `name` attribute from the message body - `name` - the `name` attribute from the message body
@ -1428,6 +1434,8 @@ Feature flags on the server are controlled by environment variables prefixed wit
Enable by setting the environment variable `FLEET_BETA_SOFTWARE_INVENTORY=1`. Enable by setting the environment variable `FLEET_BETA_SOFTWARE_INVENTORY=1`.
When enabled, Fleet will store a "software inventory" for hosts, updated along with the other host details. Note that it will take some time for the data to be available after setting this flag (it will be updated when the host details are next updated, configurable by [--osquery_detail_update_interval](#osquery_detail_update_interval)). When enabled, Fleet will store a "software inventory" for hosts, updated along with the other host vitals. Note that it will take some time for the data to be available after setting this flag (it will be updated when the host details are next updated, configurable by [--osquery_detail_update_interval](#osquery_detail_update_interval)).
This is currently feature flagged because we would like to evaluate the performance characteristics on larger deployments. This is currently feature flagged because we would like to evaluate the performance characteristics on larger deployments.
To read more about the software inventory feature, [check out the Fleet 3.11.0 release blog post](https://medium.com/fleetdm/fleet-3-11-0-released-with-software-inventory-25d5a1efe19c).

View File

@ -26,6 +26,7 @@
- [Deploying Fleet](#deploying-fleet) - [Deploying Fleet](#deploying-fleet)
- [Deploying the load balancer](#deploying-the-load-balancer) - [Deploying the load balancer](#deploying-the-load-balancer)
- [Configure DNS](#configure-dns) - [Configure DNS](#configure-dns)
- [Community projects](#community-projects)
## Fleet on CentOS ## Fleet on CentOS
@ -553,3 +554,11 @@ kubectl get services fleet-loadbalancer
In this output, you should see an "EXTERNAL-IP" column. If this column says `<pending>`, then give it a few minutes. Sometimes acquiring a public IP address can take a moment. In this output, you should see an "EXTERNAL-IP" column. If this column says `<pending>`, then give it a few minutes. Sometimes acquiring a public IP address can take a moment.
Once you have the public IP address for the load balancer, create an A record in your DNS server of choice. You should now be able to browse to your Fleet server from the internet! Once you have the public IP address for the load balancer, create an A record in your DNS server of choice. You should now be able to browse to your Fleet server from the internet!
#### Community projects
Below are some projects created by Fleet community members. These projects provide additional solutions for deploying Fleet. Please submit a pull request if you'd like your project featured.
- [davidrecordon/terraform-aws-kolide-fleet](https://github.com/davidrecordon/terraform-aws-kolide-fleet) - Deploy Fleet into AWS using Terraform.
- [deeso/fleet-deployment](https://github.com/deeso/fleet-deployment) - Install Fleet on a Ubuntu box.
- [gjyoung1974/kolide-fleet-chart](https://github.com/gjyoung1974/kolide-fleet-chart) - Kubernetes Helm chart for deploying Fleet.

View File

@ -80,5 +80,6 @@
&__label { &__label {
font-size: $x-small; font-size: $x-small;
padding-left: $pad-small;
} }
} }

View File

@ -46,36 +46,31 @@
background-color: $ui-light-grey; background-color: $ui-light-grey;
border-radius: $border-radius; border-radius: $border-radius;
height: 40px; height: 40px;
.Select-value {
border: 0;
}
} }
.Select-value { .Select-value {
font-size: $x-small; font-size: $x-small;
border-radius: $border-radius; border-radius: $border-radius;
background-color: $ui-light-grey; background-color: $ui-off-white;
border: solid 1px $ui-fleet-blue-15; border: solid 1px $core-dark-blue-grey;
.Select-value-icon { .Select-value-icon {
border: 0; border: 0;
float: right; float: right;
position: relative; position: relative;
line-height: 34px; line-height: 28px;
width: 25px; width: 20px;
padding: 0; padding: 0;
margin: 0 5px; margin: 0 5px;
text-indent: -999em; text-indent: -999em;
&::after { &::after {
@extend %kolidecon;
transition: color 150ms ease-in-out; transition: color 150ms ease-in-out;
transform: translate(-50%, -50%); content: url(../assets/images/icon-close-dark-blue-grey-16x16@2x.png);
content: "\f036"; transform: scale(0.5);
position: absolute; position: absolute;
top: 50%; top: -5px;
left: 50%; left: -5px;
visibility: visible; visibility: visible;
font-size: $small; font-size: $small;
color: $ui-gray; color: $ui-gray;
@ -84,9 +79,9 @@
} }
.Select-value-label { .Select-value-label {
font-size: $small; font-size: $x-small;
font-weight: $regular; font-weight: $bold;
color: $core-fleet-black; color: $core-dark-blue-grey;
line-height: 28px; line-height: 28px;
} }
} }
@ -156,8 +151,8 @@
&.is-pseudo-focused > .Select-control { &.is-pseudo-focused > .Select-control {
.Select-value { .Select-value {
.Select-value-label { .Select-value-label {
color: $core-fleet-black; color: $core-dark-blue-grey;
font-size: $small; font-size: $x-small;
} }
} }
} }
@ -242,12 +237,11 @@
} }
.Select-value { .Select-value {
margin-top: 3px; margin-top: 4px;
margin-bottom: 3px;
} }
.Select-value-label { .Select-value-label {
padding: 0 0 0 1rem; padding: 0 0 0 10px;
} }
} }
} }

View File

@ -52,7 +52,7 @@ class EditPackForm extends Component {
/> />
<SelectTargetsDropdown <SelectTargetsDropdown
{...fields.targets} {...fields.targets}
label="select pack targets" label="Select pack targets"
name="selected-pack-targets" name="selected-pack-targets"
onFetchTargets={onFetchTargets} onFetchTargets={onFetchTargets}
onSelect={fields.targets.onChange} onSelect={fields.targets.onChange}

View File

@ -227,25 +227,21 @@ class QueryResultsTable extends Component {
{!hasNoResults && renderTable()} {!hasNoResults && renderTable()}
</div> </div>
{hasErrors && ( {hasErrors && (
<> <div className={`${baseClass}__error-table-container`}>
<div className={`${baseClass}__error-table-container`}> <header className={`${baseClass}__button-wrap`}>
<header className={`${baseClass}__button-wrap`}> <Button
<div> className={`${baseClass}__export-btn`}
<Button onClick={onExportErrorsResults}
className={`${baseClass}__export-btn`} variant="inverse"
onClick={onExportErrorsResults} >
variant="inverse" Export errors
> </Button>
Export errors </header>
</Button> <span className={`${baseClass}__table-title`}>Errors</span>
</div> <div className={`${baseClass}__error-table-wrapper`}>
</header> {renderErrorsTable()}
<span className={`${baseClass}__table-title`}>Errors</span>
<div className={`${baseClass}__error-table-wrapper`}>
{renderErrorsTable()}
</div>
</div> </div>
</> </div>
)} )}
</div> </div>
); );

View File

@ -39,7 +39,7 @@
border-radius: 3px; border-radius: 3px;
overflow: scroll; overflow: scroll;
margin-top: $pad-xsmall; margin-top: $pad-xsmall;
min-height: 200px; min-height: 100px;
max-height: 400px; max-height: 400px;
width: 100%; width: 100%;
@ -57,17 +57,19 @@
&__error-table-container { &__error-table-container {
margin-top: $pad-large; margin-top: $pad-large;
overflow: scroll;
} }
&__error-table-wrapper { &__error-table-wrapper {
display: flex; display: flex;
border: solid 1px $ui-fleet-blue-15; border: solid 1px $ui-fleet-blue-15;
border-radius: 3px; border-radius: 3px;
overflow: scroll;
margin-bottom: $pad-xxlarge; margin-bottom: $pad-xxlarge;
margin-top: $pad-xsmall; margin-top: $pad-xsmall;
max-height: 200px; min-height: 200px;
max-height: 400px;
width: 100%; width: 100%;
box-sizing: border-box;
} }
&__table { &__table {
@ -144,7 +146,11 @@
} }
.query-results-table__error-table-container { .query-results-table__error-table-container {
display: none; max-height: none;
}
.query-results-table__error-table-wrapper {
max-height: none;
} }
} }

View File

@ -16,7 +16,7 @@ const currentUser = {
global_role: "admin", global_role: "admin",
}; };
const invitedUser = { const invitedUser = {
email: "test+4@fleetdm.com", email: "test+4@example.com",
global_role: "observer", global_role: "observer",
id: 6, id: 6,
invited_by: 1, invited_by: 1,

View File

@ -5,6 +5,7 @@ $core-vibrant-blue: #6a67fe;
$core-vibrant-red: #ff5c83; $core-vibrant-red: #ff5c83;
$core-fleet-purple: #ae6ddf; $core-fleet-purple: #ae6ddf;
$core-white: #ffffff; $core-white: #ffffff;
$core-dark-blue-grey: #506e92;
// UI // UI
$ui-fleet-black-50: #8b8fa2; $ui-fleet-black-50: #8b8fa2;

View File

@ -3,6 +3,9 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex">
<link rel="stylesheet" type="text/css" href="{{.URLPrefix}}<%= htmlWebpackPlugin.files.css[0] %>"> <link rel="stylesheet" type="text/css" href="{{.URLPrefix}}<%= htmlWebpackPlugin.files.css[0] %>">
<link rel="shortcut icon" href="{{.URLPrefix}}/assets/favicon.ico"> <link rel="shortcut icon" href="{{.URLPrefix}}/assets/favicon.ico">

View File

@ -28,7 +28,7 @@ Zach partnered with our CEO, Mike McNeil, to found a new, independent company: F
### Culture ### Culture
### All remote ### All remote
Fleet is an all-remote conpany, with teammates spread across 3 continents and 5 time zones. Fleet is an all-remote company, with teammates spread across 3 continents and 5 time zones.
### Openness ### Openness
The majority of the code, documentation, and content we create at Fleet is public and source-available, and we strive to be broadly open and transparent in the way we run the business; as much as confidentiality agreements (and time) allow. We perform better with an audience, and our audience performs better with us. The majority of the code, documentation, and content we create at Fleet is public and source-available, and we strive to be broadly open and transparent in the way we run the business; as much as confidentiality agreements (and time) allow. We perform better with an audience, and our audience performs better with us.

View File

@ -0,0 +1,73 @@
# Releases
This document outlines the release process at Fleet.
The current release cadence is once every 3 weeks and concentrated around Wednesdays.
- [Milestones](#milestones)
- [Release issue](#release-issue)
- [Social assets](#social-assets)
- [Release week](#release-week)
- [Release day](#release-day)
## Milestones
At Fleet, we use the Milestone GitHub feature to tag GitHub issues that include features and tasks to be included in a release.
The individual tasked with managing releases is responsible for creating a new milestone for the upcoming release. As soon as the release process for the previous release is complete, a new milestone should be created for the upcoming release.
The title of the milestone corresponds to the version number of Fleet that is to be released. For example, if the upcoming version number of Fleet is 3.12.0, a milestone with the title “3.12.0” should be created.
## Release issue
One week before the release date, the individual tasked with managing the release will create a release issue to start preparing for the release. [Click here](https://github.com/fleetdm/confidential/issues/new?milestone=ASAP&body=%3E%20**This%20issue%20contains%20confidential%20information.**%0A%0A%23%23%20Fleet%20%3CVERSION%3E%0A%0A%23%23%23%20Release%20date%0A%0ATODO%0A%0A%23%23%23%20Summary%0A%0ATODO%0A%0A%23%23%23%20CHANGELOG%0A%0ATODO%0A%0A%23%23%23%20Core%20tasks%0A%0A-%20%5B%20%5D%20TODO%0A%0A%23%23%23%20Growth%20tasks%0A%0A-%20%5B%20%5D%20TODO) to open the release issue template.
### Changelog
The Changelog section of the release issue acts as a short term roadmap and will be used as the public facing Changelog included in the release.
To construct the Changelog, first, head to the [commit history for fleetdm/fleet](https://github.com/fleetdm/fleet/commits/master). Next, navigate to the commit made to prepare for the previous release. This commit is usually titled something like “Prepare for `<release number>`.” Finally, add a bullet point to the Changelog for each commit, according to the following:
1. Only include changes that are relevant to Fleet users. This is because the Changelog serves as a tool to both inform _and_ excite users of Fleet. This means that changes made to the development infrastructure, documentation, and contribution experience shouldnt be included.
2. Each bullet should start with a verb. For example, “Add,” or “Fix.”
3. Each bullet should be a complete sentence.
4. The bulleted list should be ordered from most exciting items (on the top) to least exciting items (on the bottom). New features and performance improvements live at the top of the list, while bug fixes like to live at the bottom.
The number of the commits will largely outweigh the number of bullets included in the Changelog. This makes sense because as a Fleet user I want to see an informative collection of the changes I care about rather than a list of all the commits that have been made since the last release.
### Summary
The Summary section of the release issue servers as an outline for the release Medium blog post and fodder for the release Tweet. The summary should be one sentence and reflect the most exciting additions included in the release.
## Blog post
The individual tasked with managing the release is responsible for drafting the release Medium blog post.
Using the release summary as an outline, write one brief section (3-6 sentences) that describe each item. Check out Fleets Medium blog for example release blog posts: https://medium.com/fleetdm
## Social assets
The release Medium blog post and release Tweet require new assets. Three days prior to the release date, the individual managing the release will send a message to #grupo-growth channel that requests the release assets.
This message should include the release title, summary, and a link to the release issue. The summary serves as an outline for what the release assets should depict.
If the Growth team requires additional information, they should reference the Changelog section of the release issue.
## Release week
On the first day of the week of the scheduled release, typically a Monday, the individual tasked with managing the release should send a message to the #grupo-core channel that includes the changes planned for the release that are still outstanding. This can be in the form of a list where each list item includes the GitHub issues title and link. This way, there the team can discuss which changes are still feasible.
Following discussion, any items that are now pushed to a later release should be removed from the current release milestone.
At this point, the individual managing the release should reserve 30 minutes to complete the release process. This meeting typically occurs on a Wednesday at 9a PST.
### Manual QA
After all changes required for release have been merged into the `master` branch, the individual tasked with managing the release should perform a manual quality assurance pass.
Documentation on conducting the manual QA pass can be found here: https://github.com/fleetdm/fleet/blob/f725a4e7f5ef994ecf18145fa284497dcfbb4333/handbook/manual-qa.md
## Release day
Documentation on completing the release process can be found here: https://github.com/fleetdm/fleet/blob/f725a4e7f5ef994ecf18145fa284497dcfbb4333/docs/4-Contribution/5-Releasing-Fleet.md

View File

@ -69,8 +69,8 @@ func testSaveHosts(t *testing.T, ds kolide.Datastore) {
additionalJSON := json.RawMessage(`{"foobar": "bim"}`) additionalJSON := json.RawMessage(`{"foobar": "bim"}`)
host.Additional = &additionalJSON host.Additional = &additionalJSON
err = ds.SaveHost(host) require.NoError(t, ds.SaveHost(host))
require.Nil(t, err) require.NoError(t, ds.SaveHostAdditional(host))
host, err = ds.Host(host.ID) host, err = ds.Host(host.ID)
require.Nil(t, err) require.Nil(t, err)
@ -291,26 +291,24 @@ func testListHostsFilterAdditional(t *testing.T, ds kolide.Datastore) {
// Add additional // Add additional
additional := json.RawMessage(`{"field1": "v1", "field2": "v2"}`) additional := json.RawMessage(`{"field1": "v1", "field2": "v2"}`)
h.Additional = &additional h.Additional = &additional
err = ds.SaveHost(h) require.NoError(t, ds.SaveHostAdditional(h))
require.Nil(t, err)
additional = json.RawMessage(`{"field1": "v1", "field2": "v2"}`)
hosts, err := ds.ListHosts(kolide.HostListOptions{}) hosts, err := ds.ListHosts(kolide.HostListOptions{})
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, additional, *hosts[0].Additional) assert.Nil(t, hosts[0].Additional)
hosts, err = ds.ListHosts(kolide.HostListOptions{AdditionalFilters: []string{"field1", "field2"}}) hosts, err = ds.ListHosts(kolide.HostListOptions{AdditionalFilters: []string{"field1", "field2"}})
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, additional, *hosts[0].Additional) assert.Equal(t, &additional, hosts[0].Additional)
hosts, err = ds.ListHosts(kolide.HostListOptions{}) hosts, err = ds.ListHosts(kolide.HostListOptions{AdditionalFilters: []string{"*"}})
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, additional, *hosts[0].Additional) assert.Equal(t, &additional, hosts[0].Additional)
additional = json.RawMessage(`{"field1": "v1", "missing": null}`) additional = json.RawMessage(`{"field1": "v1", "missing": null}`)
hosts, err = ds.ListHosts(kolide.HostListOptions{AdditionalFilters: []string{"field1", "missing"}}) hosts, err = ds.ListHosts(kolide.HostListOptions{AdditionalFilters: []string{"field1", "missing"}})
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, additional, *hosts[0].Additional) assert.Equal(t, &additional, hosts[0].Additional)
} }
func testListHostsStatus(t *testing.T, ds kolide.Datastore) { func testListHostsStatus(t *testing.T, ds kolide.Datastore) {
@ -844,9 +842,9 @@ func testHostAdditional(t *testing.T, ds kolide.Datastore) {
// Add additional // Add additional
additional := json.RawMessage(`{"additional": "result"}`) additional := json.RawMessage(`{"additional": "result"}`)
h.Additional = &additional h.Additional = &additional
err = ds.SaveHost(h) require.NoError(t, ds.SaveHostAdditional(h))
require.Nil(t, err)
// Additional should not be loaded for authenticatehost
h, err = ds.AuthenticateHost("nodekey") h, err = ds.AuthenticateHost("nodekey")
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, "foobar.local", h.HostName) assert.Equal(t, "foobar.local", h.HostName)
@ -854,7 +852,7 @@ func testHostAdditional(t *testing.T, ds kolide.Datastore) {
h, err = ds.Host(h.ID) h, err = ds.Host(h.ID)
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, additional, *h.Additional) assert.Equal(t, &additional, h.Additional)
// Update besides additional. Additional should be unchanged. // Update besides additional. Additional should be unchanged.
h, err = ds.AuthenticateHost("nodekey") h, err = ds.AuthenticateHost("nodekey")
@ -870,14 +868,14 @@ func testHostAdditional(t *testing.T, ds kolide.Datastore) {
h, err = ds.Host(h.ID) h, err = ds.Host(h.ID)
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, additional, *h.Additional) assert.Equal(t, &additional, h.Additional)
// Update additional // Update additional
additional = json.RawMessage(`{"other": "additional"}`) additional = json.RawMessage(`{"other": "additional"}`)
h, err = ds.AuthenticateHost("nodekey") h, err = ds.AuthenticateHost("nodekey")
require.Nil(t, err) require.Nil(t, err)
h.Additional = &additional h.Additional = &additional
err = ds.SaveHost(h) err = ds.SaveHostAdditional(h)
require.Nil(t, err) require.Nil(t, err)
h, err = ds.AuthenticateHost("nodekey") h, err = ds.AuthenticateHost("nodekey")
@ -887,7 +885,7 @@ func testHostAdditional(t *testing.T, ds kolide.Datastore) {
h, err = ds.Host(h.ID) h, err = ds.Host(h.ID)
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, additional, *h.Additional) assert.Equal(t, &additional, h.Additional)
} }
func testHostByIdentifier(t *testing.T, ds kolide.Datastore) { func testHostByIdentifier(t *testing.T, ds kolide.Datastore) {

View File

@ -88,7 +88,6 @@ func (d *Datastore) SaveHost(host *kolide.Host) error {
distributed_interval = ?, distributed_interval = ?,
config_tls_refresh = ?, config_tls_refresh = ?,
logger_tls_period = ?, logger_tls_period = ?,
additional = COALESCE(?, additional),
team_id = ?, team_id = ?,
primary_ip = ?, primary_ip = ?,
primary_mac = ?, primary_mac = ?,
@ -123,7 +122,6 @@ func (d *Datastore) SaveHost(host *kolide.Host) error {
host.DistributedInterval, host.DistributedInterval,
host.ConfigTLSRefresh, host.ConfigTLSRefresh,
host.LoggerTLSPeriod, host.LoggerTLSPeriod,
host.Additional,
host.TeamID, host.TeamID,
host.PrimaryIP, host.PrimaryIP,
host.PrimaryMac, host.PrimaryMac,
@ -267,9 +265,10 @@ func (d *Datastore) DeleteHost(hid uint) error {
func (d *Datastore) Host(id uint) (*kolide.Host, error) { func (d *Datastore) Host(id uint) (*kolide.Host, error) {
sqlStatement := ` sqlStatement := `
SELECT h.*, t.name AS team_name SELECT h.*, t.name AS team_name, (SELECT additional FROM host_additional WHERE host_id = h.id) AS additional
FROM hosts h LEFT JOIN teams t ON (h.team_id = t.id) FROM hosts h LEFT JOIN teams t ON (h.team_id = t.id)
WHERE h.id = ? LIMIT 1 WHERE h.id = ?
LIMIT 1
` `
host := &kolide.Host{} host := &kolide.Host{}
err := d.db.Get(host, sqlStatement, id) err := d.db.Get(host, sqlStatement, id)
@ -321,14 +320,20 @@ func (d *Datastore) ListHosts(opt kolide.HostListOptions) ([]*kolide.Host, error
h.label_update_time, h.label_update_time,
h.team_id, h.team_id,
h.refetch_requested, h.refetch_requested,
t.name AS team_name, t.name AS team_name
` `
var params []interface{} var params []interface{}
// Filter additional info by extracting into a new json object. // Only include "additional" if filter provided.
if len(opt.AdditionalFilters) > 0 { if len(opt.AdditionalFilters) == 1 && opt.AdditionalFilters[0] == "*" {
sql += `JSON_OBJECT( // All info requested.
sql += `
, (SELECT additional FROM host_additional WHERE host_id = h.id) AS additional
`
} else if len(opt.AdditionalFilters) > 0 {
// Filter specific columns.
sql += `, (SELECT JSON_OBJECT(
` `
for _, field := range opt.AdditionalFilters { for _, field := range opt.AdditionalFilters {
sql += `?, JSON_EXTRACT(additional, ?), ` sql += `?, JSON_EXTRACT(additional, ?), `
@ -336,12 +341,8 @@ func (d *Datastore) ListHosts(opt kolide.HostListOptions) ([]*kolide.Host, error
} }
sql = sql[:len(sql)-2] sql = sql[:len(sql)-2]
sql += ` sql += `
) AS additional ) FROM host_additional WHERE host_id = h.id) AS additional
` `
} else {
sql += `
additional
`
} }
sql += `FROM hosts h LEFT JOIN teams t ON (h.team_id = t.id) sql += `FROM hosts h LEFT JOIN teams t ON (h.team_id = t.id)
@ -766,3 +767,16 @@ func (d *Datastore) AddHostsToTeam(teamID *uint, hostIDs []uint) error {
return nil return nil
} }
func (d *Datastore) SaveHostAdditional(host *kolide.Host) error {
sql := `
INSERT INTO host_additional (host_id, additional)
VALUES (?, ?)
ON DUPLICATE KEY UPDATE additional = VALUES(additional)
`
if _, err := d.db.Exec(sql, host.ID, host.Additional); err != nil {
return errors.Wrap(err, "insert additional")
}
return nil
}

View File

@ -0,0 +1,46 @@
package tables
import (
"database/sql"
"github.com/pkg/errors"
)
func init() {
MigrationClient.AddMigration(Up_20210526113559, Down_20210526113559)
}
func Up_20210526113559(tx *sql.Tx) error {
sql := `
CREATE TABLE host_additional (
host_id int unsigned NOT NULL PRIMARY KEY,
additional json DEFAULT NULL,
FOREIGN KEY (host_id) REFERENCES hosts (id) ON DELETE CASCADE ON UPDATE CASCADE
)
`
if _, err := tx.Exec(sql); err != nil {
return errors.Wrap(err, "create host_additional")
}
sql = `
INSERT INTO host_additional (host_id, additional)
SELECT id, additional FROM hosts
`
if _, err := tx.Exec(sql); err != nil {
return errors.Wrap(err, "migration additional data")
}
sql = `
ALTER TABLE hosts
DROP COLUMN additional
`
if _, err := tx.Exec(sql); err != nil {
return errors.Wrap(err, "migration additional data")
}
return nil
}
func Down_20210526113559(tx *sql.Tx) error {
return nil
}

View File

@ -76,6 +76,9 @@ type HostStore interface {
// AddHostsToTeam adds hosts to an existing team, clearing their team // AddHostsToTeam adds hosts to an existing team, clearing their team
// settings if teamID is nil. // settings if teamID is nil.
AddHostsToTeam(teamID *uint, hostIDs []uint) error AddHostsToTeam(teamID *uint, hostIDs []uint) error
// SaveHostAdditional saves the information generated by the
// additional_queries.
SaveHostAdditional(host *Host) error
} }
type HostService interface { type HostService interface {
@ -154,13 +157,15 @@ type Host struct {
DistributedInterval uint `json:"distributed_interval" db:"distributed_interval"` DistributedInterval uint `json:"distributed_interval" db:"distributed_interval"`
ConfigTLSRefresh uint `json:"config_tls_refresh" db:"config_tls_refresh"` ConfigTLSRefresh uint `json:"config_tls_refresh" db:"config_tls_refresh"`
LoggerTLSPeriod uint `json:"logger_tls_period" db:"logger_tls_period"` LoggerTLSPeriod uint `json:"logger_tls_period" db:"logger_tls_period"`
Additional *json.RawMessage `json:"additional,omitempty" db:"additional"` TeamID *uint `json:"team_id" db:"team_id"`
TeamID *uint `json:"team_id" db:"team_id"`
// TeamName is the name of the team, loaded by JOIN to the teams table.
TeamName *string `json:"team_name" db:"team_name"`
// Loaded via JOIN in DB // Loaded via JOIN in DB
PackStats []PackStats `json:"pack_stats"` PackStats []PackStats `json:"pack_stats"`
// TeamName is the name of the team, loaded by JOIN to the teams table.
TeamName *string `json:"team_name" db:"team_name"`
// Additional is the additional information from the host
// additional_queries. This should be stored in a separate DB table.
Additional *json.RawMessage `json:"additional,omitempty" db:"additional"`
} }
// HostDetail provides the full host metadata along with associated labels and // HostDetail provides the full host metadata along with associated labels and

View File

@ -42,6 +42,8 @@ type HostIDsByNameFunc func(hostnames []string) ([]uint, error)
type AddHostsToTeamFunc func(teamID *uint, hostIDs []uint) error type AddHostsToTeamFunc func(teamID *uint, hostIDs []uint) error
type SaveHostAdditionalFunc func(host *kolide.Host) error
type HostStore struct { type HostStore struct {
NewHostFunc NewHostFunc NewHostFunc NewHostFunc
NewHostFuncInvoked bool NewHostFuncInvoked bool
@ -90,6 +92,9 @@ type HostStore struct {
AddHostsToTeamFunc AddHostsToTeamFunc AddHostsToTeamFunc AddHostsToTeamFunc
AddHostsToTeamFuncInvoked bool AddHostsToTeamFuncInvoked bool
SaveHostAdditionalFunc SaveHostAdditionalFunc
SaveHostAdditionalFuncInvoked bool
} }
func (s *HostStore) NewHost(host *kolide.Host) (*kolide.Host, error) { func (s *HostStore) NewHost(host *kolide.Host) (*kolide.Host, error) {
@ -171,3 +176,8 @@ func (s *HostStore) AddHostsToTeam(teamID *uint, hostIDs []uint) error {
s.AddHostsToTeamFuncInvoked = true s.AddHostsToTeamFuncInvoked = true
return s.AddHostsToTeamFunc(teamID, hostIDs) return s.AddHostsToTeamFunc(teamID, hostIDs)
} }
func (s *HostStore) SaveHostAdditional(host *kolide.Host) error {
s.SaveHostAdditionalFuncInvoked = true
return s.SaveHostAdditionalFunc(host)
}

View File

@ -228,7 +228,7 @@ func (svc service) StreamCampaignResults(ctx context.Context, conn *websocket.Co
if lastTotals != totals { if lastTotals != totals {
lastTotals = totals lastTotals = totals
if err = conn.WriteJSONMessage("totals", totals); err != nil { if err = conn.WriteJSONMessage("totals", totals); err != nil {
return errors.New("write totals") return errors.Wrap(err, "write totals")
} }
} }
@ -240,7 +240,7 @@ func (svc service) StreamCampaignResults(ctx context.Context, conn *websocket.Co
if lastStatus != status { if lastStatus != status {
lastStatus = status lastStatus = status
if err = conn.WriteJSONMessage("status", status); err != nil { if err = conn.WriteJSONMessage("status", status); err != nil {
return errors.New("write status") return errors.Wrap(err, "write status")
} }
} }

View File

@ -1125,6 +1125,12 @@ func (svc service) SubmitDistributedQueryResults(ctx context.Context, results ko
return osqueryError{message: "failed to save host software: " + err.Error()} return osqueryError{message: "failed to save host software: " + err.Error()}
} }
} }
if detailUpdated {
if err := svc.ds.SaveHostAdditional(&host); err != nil {
return osqueryError{message: "failed to save host additional: " + err.Error()}
}
}
} }
return nil return nil

View File

@ -646,6 +646,11 @@ func TestDetailQueriesWithEmptyStrings(t *testing.T) {
return nil return nil
} }
ds.SaveHostAdditionalFunc = func(host *kolide.Host) error {
gotHost.Additional = host.Additional
return nil
}
// Verify that results are ingested properly // Verify that results are ingested properly
svc.SubmitDistributedQueryResults(ctx, results, map[string]kolide.OsqueryStatus{}, map[string]string{}) svc.SubmitDistributedQueryResults(ctx, results, map[string]kolide.OsqueryStatus{}, map[string]string{})
@ -816,6 +821,12 @@ func TestDetailQueries(t *testing.T) {
gotHost = host gotHost = host
return nil return nil
} }
ds.SaveHostAdditionalFunc = func(host *kolide.Host) error {
gotHost.Additional = host.Additional
return nil
}
// Verify that results are ingested properly // Verify that results are ingested properly
svc.SubmitDistributedQueryResults(ctx, results, map[string]kolide.OsqueryStatus{}, map[string]string{}) svc.SubmitDistributedQueryResults(ctx, results, map[string]kolide.OsqueryStatus{}, map[string]string{})
@ -1176,12 +1187,12 @@ func TestNewDistributedQueryCampaign(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, gotQuery.ID, gotCampaign.QueryID) assert.Equal(t, gotQuery.ID, gotCampaign.QueryID)
assert.Equal(t, []*kolide.DistributedQueryCampaignTarget{ assert.Equal(t, []*kolide.DistributedQueryCampaignTarget{
&kolide.DistributedQueryCampaignTarget{ {
Type: kolide.TargetHost, Type: kolide.TargetHost,
DistributedQueryCampaignID: campaign.ID, DistributedQueryCampaignID: campaign.ID,
TargetID: 2, TargetID: 2,
}, },
&kolide.DistributedQueryCampaignTarget{ {
Type: kolide.TargetLabel, Type: kolide.TargetLabel,
DistributedQueryCampaignID: campaign.ID, DistributedQueryCampaignID: campaign.ID,
TargetID: 1, TargetID: 1,

View File

@ -1,5 +1,5 @@
FROM golang:1.9-alpine FROM golang:1.9-alpine
MAINTAINER Fleet Developers <engineering@fleetdm.com> MAINTAINER Fleet Developers <hello@fleetdm.com>
ENV NPM_CONFIG_LOGLEVEL info ENV NPM_CONFIG_LOGLEVEL info
ENV NODE_VERSION 8.7.0 ENV NODE_VERSION 8.7.0

View File

@ -0,0 +1,31 @@
module.exports = {
friendlyName: 'View basic documentation',
description: 'Display "Basic documentation" page.',
exits: {
success: {
viewTemplatePath: 'pages/docs/basic-documentation'
}
},
fn: async function () {
// Serve appropriate doc page content.
// > Inspired by https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/controllers/documentation/view-documentation.js
// TODO
// Respond with view.
return {};
}
};

View File

@ -0,0 +1,88 @@
module.exports = {
friendlyName: 'Download sitemap',
description: 'Download sitemap file (returning a stream).',
extendedDescription: `Notes:
Sitemap building inspired by https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/controllers/documentation/refresh.js#L112-L180 and https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/helpers/get-pages-for-sitemap.js
Why escape XML? See http://stackoverflow.com/questions/3431280/validation-problem-entityref-expecting-what-should-i-do and https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/controllers/documentation/refresh.js#L161-L172
`,
exits: {
success: { outputFriendlyName: 'Sitemap (XML)', outputType: 'string' },
badConfig: { responseType: 'badConfig' },
notFound: { responseType: 'notFound' }// « TODO: delete this and the code that calls it below when all pages are ready
},
fn: async function ({}) {
if (sails.config.environment === 'staging') {
// This explicit check for staging allows for the sitemap to still be developed/tested locally,
// and for the real thing to be served in production, while explicitly preventing the "whoops,
// i deployed staging and search engine crawlers got fixated on the wrong sitemap" dilemma.
throw new Error('Since this is the staging environment, prevented sitemap.xml from being served to avoid search engine accidents.');
}//•
if (sails.config.environment === 'production') {// TODO: Remove this once the pages are ready.
// Don't serve a sitemap until the pages actually work.
throw 'notFound';
}
if (!_.isObject(sails.config.builtStaticContent)) {
throw {badConfig: 'builtStaticContent'};
} else if (!_.isArray(sails.config.builtStaticContent.queries)) {
throw {badConfig: 'builtStaticContent.queries'};
} else if (!_.isArray(sails.config.builtStaticContent.markdownPages)) {
throw {badConfig: 'builtStaticContent.markdownPages'};
}
// Start with sitemap.xml preamble + the root relative URLs of other webpages that aren't being generated from markdown
let sitemapXml = '<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// ╦ ╦╔═╗╔╗╔╔╦╗ ╔═╗╔═╗╔╦╗╔═╗╔╦╗ ╔═╗╔═╗╔═╗╔═╗╔═╗
// ╠═╣╠═╣║║║ ║║───║ ║ ║ ║║║╣ ║║ ╠═╝╠═╣║ ╦║╣ ╚═╗
// ╩ ╩╩ ╩╝╚╝═╩╝ ╚═╝╚═╝═╩╝╚═╝═╩╝ ╩ ╩ ╩╚═╝╚═╝╚═╝
let HAND_CODED_HTML_PAGES = [
'/',
'/get-started',
'/company/contact',
'/queries',
// FUTURE: Do something smarter to get hand-coded HTML pages from routes.js, like how rebuild-cloud-sdk works, to avoid this manual duplication.
// See also https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/helpers/get-pages-for-sitemap.js#L27
];
for (let url of HAND_CODED_HTML_PAGES) {
let trimmedRootRelativeUrl = _.trimRight(url,'/');// « really only necessary for home page; run on everything as a failsafe against accidental dupes due to trailing slashes in the list above
sitemapXml += `<url><loc>${_.escape(sails.config.custom.baseUrl+trimmedRootRelativeUrl)}</loc></url>`;
}//∞
// ╔╦╗╦ ╦╔╗╔╔═╗╔╦╗╦╔═╗ ╔═╗╔═╗╦═╗ ╔═╗ ╦ ╦╔═╗╦═╗╦ ╦ ╔═╗╔═╗╔═╗╔═╗╔═╗
// ║║╚╦╝║║║╠═╣║║║║║ ╠═╝║╣ ╠╦╝───║═╬╗║ ║║╣ ╠╦╝╚╦╝ ╠═╝╠═╣║ ╦║╣ ╚═╗
// ═╩╝ ╩ ╝╚╝╩ ╩╩ ╩╩╚═╝ ╩ ╚═╝╩╚═ ╚═╝╚╚═╝╚═╝╩╚═ ╩ ╩ ╩ ╩╚═╝╚═╝╚═╝
for (let query of sails.config.builtStaticContent.queries) {
sitemapXml +=`<url><loc>${_.escape(sails.config.custom.baseUrl+`/queries/${query.slug}`)}</loc></url>`;// note we omit lastmod for some sitemap entries. This is ok, to mix w/ other entries that do have lastmod. Why? See https://docs.google.com/document/d/1SbpSlyZVXWXVA_xRTaYbgs3750jn252oXyMFLEQxMeU/edit
}//∞
// ╔╦╗╦ ╦╔╗╔╔═╗╔╦╗╦╔═╗ ╔═╗╔═╗╔═╗╔═╗╔═╗ ╔═╗╦═╗╔═╗╔╦╗ ╔╦╗╔═╗╦═╗╦╔═╔╦╗╔═╗╦ ╦╔╗╔
// ║║╚╦╝║║║╠═╣║║║║║ ╠═╝╠═╣║ ╦║╣ ╚═╗ ╠╣ ╠╦╝║ ║║║║ ║║║╠═╣╠╦╝╠╩╗ ║║║ ║║║║║║║
// ═╩╝ ╩ ╝╚╝╩ ╩╩ ╩╩╚═╝ ╩ ╩ ╩╚═╝╚═╝╚═╝ ╚ ╩╚═╚═╝╩ ╩ ╩ ╩╩ ╩╩╚═╩ ╩═╩╝╚═╝╚╩╝╝╚╝
if (sails.config.environment === 'development') {
for (let pageInfo of sails.config.builtStaticContent.markdownPages) {
sitemapXml +=`<url><loc>${_.escape(sails.config.custom.baseUrl+pageInfo.url)}</loc><lastmod>${_.escape(new Date(pageInfo.lastModifiedAt).toJSON())}</lastmod></url>`;
}//∞
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sitemapXml += '</urlset>';
// Set MIME type for content-type response header.
this.res.type('text/xml');
// Respond with XML.
return sitemapXml;
}
};

View File

@ -0,0 +1,31 @@
module.exports = {
friendlyName: 'View basic handbook',
description: 'Display "Basic handbook" page.',
exits: {
success: {
viewTemplatePath: 'pages/handbook/basic-handbook'
}
},
fn: async function () {
// Serve appropriate handbook page content.
// > Inspired by https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/controllers/documentation/view-documentation.js
// TODO
// Respond with view.
return {};
}
};

View File

@ -7,19 +7,38 @@ module.exports = {
description: 'Display "Query detail" page.', description: 'Display "Query detail" page.',
exits: { inputs: {
slug: { type: 'string', required: true, description: 'A slug uniquely identifying this query in the library.', example: 'get-macos-disk-free-space-percentage' },
success: {
viewTemplatePath: 'pages/query-detail'
}
}, },
fn: async function () { exits: {
success: { viewTemplatePath: 'pages/query-detail' },
notFound: { responseType: 'notFound' },
badConfig: { responseType: 'badConfig' },
},
fn: async function ({ slug }) {
if (!_.isObject(sails.config.builtStaticContent) || !_.isArray(sails.config.builtStaticContent.queries)) {
throw {badConfig: 'builtStaticContent.queries'};
} else if (!_.isString(sails.config.builtStaticContent.queryLibraryYmlRepoPath)) {
throw {badConfig: 'builtStaticContent.queryLibraryYmlRepoPath'};
}
// Serve appropriate content for query.
// > Inspired by https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/controllers/documentation/view-documentation.js
let query = _.find(sails.config.builtStaticContent.queries, { slug: slug });
if (!query) {
throw 'notFound';
}
// Respond with view. // Respond with view.
return {}; return {
query,
queryLibraryYmlRepoPath: sails.config.builtStaticContent.queryLibraryYmlRepoPath
};
} }

View File

@ -0,0 +1,30 @@
module.exports = {
friendlyName: 'View query library',
description: 'Display "Query library" page.',
exits: {
success: { viewTemplatePath: 'pages/query-library' },
badConfig: { responseType: 'badConfig' },
},
fn: async function () {
if (!_.isObject(sails.config.builtStaticContent) || !_.isArray(sails.config.builtStaticContent.queries)) {
throw {badConfig: 'builtStaticContent.queries'};
}
// Respond with view.
return {
queries: sails.config.builtStaticContent.queries
};
}
};

View File

@ -4,7 +4,17 @@ module.exports = {
friendlyName: 'Compile markdown content', friendlyName: 'Compile markdown content',
// TODO: Make this explanation better or refactor, because actually this does a lot more than just that, including cloning the source git repo
description: 'Compile documentation templates from markdown.', description: 'Compile documentation templates from markdown.',
// Also, FUTURE: dissect some of the code from here https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L16-L22
// and use those building blocks directly instead of depending on doctemplater later and thus unnecessarily duplicating work. Also the other related code in sailsjs docs mentioned in https://github.com/fleetdm/fleet/issues/706
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Clone repo
// let topLvlCachePath = path.resolve(sails.config.paths.tmp, `built-static-content/`);
// await sails.helpers.fs.rmrf(topLvlCachePath);
// let repoCachePath = path.join(topLvlCachePath, `cloned-repo-${Date.now()}-${Math.round(Math.random()*100)}`);
// await sails.helpers.process.executeCommand(`git clone git://github.com/fleetdm/fleet.git ${repoCachePath}`);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
inputs: { inputs: {
@ -40,7 +50,10 @@ module.exports = {
let path = require('path'); let path = require('path');
let cheerio = require('cheerio'); let cheerio = require('cheerio');
let DocTemplater = require('doc-templater'); let DocTemplater = {};// require('doc-templater');
if (true) {
throw new Error('This helper has been retired. TODO: delete it.');
}
sails.log.info('Compiling `%s` docs from the `%s` branch of `%s`...', repoPath, repoBranch, repoUrl); sails.log.info('Compiling `%s` docs from the `%s` branch of `%s`...', repoPath, repoBranch, repoUrl);
@ -179,7 +192,7 @@ module.exports = {
fileInfo.path = fileInfo.fullPathAndFileName;// « for clarity (it's not technically the full path) fileInfo.path = fileInfo.fullPathAndFileName;// « for clarity (it's not technically the full path)
delete fileInfo.fullPathAndFileName; delete fileInfo.fullPathAndFileName;
fileInfo.fallbackTitle = sails.helpers.toSentenceCase(path.basename(fileInfo.templateTitle, '.ejs'));// « for clarity (the page isn't a template, necessarily, and this title is just a guess. Display title will, more likely than not, come from a <docmeta> tag -- see the bottom of the original, raw unformatted markdown of any page in the sailsjs docs for an example of how to use docmeta tags) fileInfo.fallbackTitle = sails.helpers.strings.toSentenceCase(path.basename(fileInfo.templateTitle, '.ejs'));// « for clarity (the page isn't a template, necessarily, and this title is just a guess. Display title will, more likely than not, come from a <docmeta> tag -- see the bottom of the original, raw unformatted markdown of any page in the sailsjs docs for an example of how to use docmeta tags)
delete fileInfo.templateTitle; delete fileInfo.templateTitle;
delete fileInfo.data.lastModified;// « for clarity (this isn't the timestamp you're expecting, so we delete it) delete fileInfo.data.lastModified;// « for clarity (this isn't the timestamp you're expecting, so we delete it)

View File

@ -1,7 +1,7 @@
module.exports = { module.exports = {
friendlyName: 'To sentence case', friendlyName: 'To sentence case',// FUTURE: bring this into machinepack-strings at some point
description: 'Make a best-effort conversion of the specified text into sentence case.', description: 'Make a best-effort conversion of the specified text into sentence case.',
@ -21,6 +21,7 @@ module.exports = {
fn: function ({ text }) { fn: function ({ text }) {
// TODO: make this smarter about: "Fleet REST API" => "Fleet rEST aPI")
return text return text
.split(/[\s-_]+/) .split(/[\s-_]+/)
.filter((word, idx) => !(idx === 0 && word.match(/[0-9]+/))) // « strip off any leading numbers so first word is actually capitalized .filter((word, idx) => !(idx === 0 && word.match(/[0-9]+/))) // « strip off any leading numbers so first word is actually capitalized

54
website/api/responses/badConfig.js vendored Normal file
View File

@ -0,0 +1,54 @@
/**
* badConfig.js
*
* A custom response.
*
* Example usage:
* ```
* return res.badConfig();
* // -or-
* return res.badConfig('builtStaticContent.queries');
* ```
*
* AKA with actions2:
* ```
* exits: {
* badConfig: { responseType: 'badConfig' }
* }
* ```
*
* ```
* throw 'badConfig';
* // -or-
* throw { badConfig: 'builtStaticContent.queries' }
* ```
*/
module.exports = function badConfig(configKeyPath) {
let res = this.res;
sails.log.verbose('Ran custom response: res.badConfig()');
if (configKeyPath !== undefined && (!_.isString(configKeyPath) || configKeyPath === '' || configKeyPath.match(/^sails\.config/))) {
throw new Error('Invalid usage of "badConfig" custom response: If specified, data sent through into the "badConfig" response should be keypath on sails.config; like "custom.internalEmailAddress", not "sails.config.custom.internalEmailAddress". But instead, got: '+configKeyPath);
}
// Determine a reasonable explanation ± any further info/troubleshooting tips.
let explanation = 'Missing, incomplete, or invalid configuration';
if (configKeyPath === undefined) {
explanation += `. Please check your server logs see which action in api/controllers/ this error is coming from, find where this custom response is being called and determine which config assertion is failing, then update the relevant Sails config, and re-lift the server.`;
} else {
explanation += ` (sails.config.${configKeyPath}). Please `;
// Now for an imperative mood phrase that comes after "Please ":
if (configKeyPath.match(/^builtStaticContent/)) {
explanation += 'try doing `sails run build-static-content`, and then re-lifting the server.';
} else {
explanation += 'update this configuration, and then re-lift the server.\n [?] Unsure? Check out: https://sailsjs.com/documentation/concepts/configuration';
}
}
// Note that we don't instantiate an Error instance here because its stack trace would be cliffed out.
return res.serverError(explanation);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

View File

@ -0,0 +1,25 @@
parasails.registerPage('basic-documentation', {
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
data: {
//…
},
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
// ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
beforeMount: function() {
//…
},
mounted: async function() {
//…
},
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
methods: {
//…
}
});

View File

@ -0,0 +1,25 @@
parasails.registerPage('basic-handbook', {
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
data: {
//…
},
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
// ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
beforeMount: function() {
//…
},
mounted: async function() {
//…
},
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
methods: {
//…
}
});

View File

@ -20,6 +20,6 @@ parasails.registerPage('query-detail', {
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗ // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝ // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
methods: { methods: {
//…
} }
}); });

View File

@ -0,0 +1,80 @@
parasails.registerPage('query-library', {
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
data: {
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")
},
computed: {
filteredQueries: function () {
return _.filter(this.queries, (query) => this.isIncluded(query.platforms, this.selectedPlatform) && this.isIncluded(query.purpose, this.selectedPurpose));
},
searchResults: function () {
return this.search(this.filteredQueries, this.searchString);
},
queriesList: function () {
return this.searchResults;
}
},
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
// ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
beforeMount: function() {
//…
},
mounted: async function() {
//…
},
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
methods: {
isIncluded: function (queryProperty, selectedOption) {
if (selectedOption.startsWith('all') || selectedOption === '') {
return true;
}
if (_.isArray(queryProperty)) {
queryProperty = queryProperty.join(', ');
}
return _.isString(queryProperty) && queryProperty.toLowerCase().includes(selectedOption.toLowerCase());
},
search: function (library, searchString) {
const searchTerms = _.isString(searchString) ? searchString.toLowerCase().split(' ') : [];
return library.filter((item) => {
const description = _.isString(item.description) ? item.description.toLowerCase() : '';
return _.some(searchTerms, (term) => description.includes(term));
});
},
setSearchString: function () {
this.searchString = this.inputTextValue;
},
delayInput: function(callback, ms, label) {
let inputTimers = this.inputTimers;
return function () {
label = label || 'defaultTimer';
_.has(inputTimers, label) ? clearTimeout(inputTimers[label]) : 0;
inputTimers[label] = setTimeout(callback, ms);
};
},
clickCard: function (querySlug) {
window.location = '/queries/' + querySlug;// we can trust the query slug is url-safe
},
}
});

View File

@ -44,3 +44,6 @@
@import 'pages/498.less'; @import 'pages/498.less';
@import 'pages/query-detail.less'; @import 'pages/query-detail.less';
@import 'pages/query-library.less';
@import 'pages/docs/basic-documentation.less';
@import 'pages/handbook/basic-handbook.less';

View File

@ -22,7 +22,7 @@ html, body {
right: 0; right: 0;
.header-btn { .header-btn {
color: #ffffff; color: #ffffff;
cursor: pointer !important;//lesshint-disable-line importantRule cursor: unset;
font-family: @navigation-font; font-family: @navigation-font;
font-weight: @bold; font-weight: @bold;
} }
@ -106,7 +106,7 @@ html, body {
border-bottom: 1px solid #e2e4ea; border-bottom: 1px solid #e2e4ea;
.header-btn { .header-btn {
color: #192147; color: #192147;
cursor: pointer !important;//lesshint-disable-line importantRule cursor: unset;
font-family: @navigation-font; font-family: @navigation-font;
font-weight: @bold; font-weight: @bold;
} }
@ -213,6 +213,7 @@ body.detected-mobile {
// … // …
} }
.dropdown:hover > .btn.btn-link { .dropdown:hover > .btn.btn-link {
color: #6a67fe; color: #6a67fe;
} }

View File

@ -4,8 +4,10 @@
@core-fleet-black: #192147; @core-fleet-black: #192147;
@core-vibrant-red: #FF5C83; @core-vibrant-red: #FF5C83;
@core-vibrant-blue: #6A67FE;
@brand: #14acc2; @brand: #14acc2;
@ui-off-white: #F9FAFC;
@error: @core-vibrant-red; @error: @core-vibrant-red;

View File

@ -0,0 +1,13 @@
#basic-documentation {
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// For a starting point on how to approach styling this generated HTML, please see @RachelElysia's draft in:
// https://github.com/fleetdm/fleet/commit/bfb758d9dc81c423538f75fc86244b06cc810c1a
// https://github.com/fleetdm/fleet/commit/e4c77b981627bde0e6225c805c89143467a9bca1
//
// Note that handbook and documentation can probably share many of the same styles. (Consider a dedicated mixin.)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//…
}

View File

@ -0,0 +1,13 @@
#basic-handbook {
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// For a starting point on how to approach styling this generated HTML, please see @RachelElysia's draft in:
// https://github.com/fleetdm/fleet/commit/bfb758d9dc81c423538f75fc86244b06cc810c1a
// https://github.com/fleetdm/fleet/commit/e4c77b981627bde0e6225c805c89143467a9bca1
//
// Note that handbook and documentation can probably share many of the same styles. (Consider a dedicated mixin.)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//…
}

View File

@ -1,5 +1,35 @@
#query-detail { #query-detail {
//… h5 {
font-size: 18px;
font-weight: bold;
}
h6 {
font-size: 16px;
line-height: 24px;
}
a {
font-size: 18px;
color: @core-vibrant-blue;
}
.query-tip {
background-color: @ui-off-white;
.lightbulb {
height: 24px;
width: auto;
margin-left: 4px;
margin-right: 20px;
}
}
.query-sidebar {
border-color: #E2E4EA;
}
} }

View File

@ -0,0 +1,79 @@
#query-library {
h2 {
padding: 0px 30px 0px 30px;
}
h5 {
font-size: 18px;
font-weight: bold;
line-height: 25px;
}
h6 {
font-size: 16px;
line-height: 22px;
padding: 0px 30px 0px 30px;
}
a {
font-size: 18px;
color: @core-vibrant-blue;
}
select {
color: @core-vibrant-blue;
border: none;
}
input {
max-width: 176px;
}
.filter-and-search-bar {
margin-left: 30px;
margin-right: 30px;
}
.contributors, .platforms {
p {
font-size: 13px;
line-height: 20px;
}
}
.row {
min-height: 70px;
align-items: center;
}
.card.results {
box-shadow: none;
border: none;
border-radius: 8px;
border-bottom: 1px solid;
border-color: #E2E4EA;
&:hover {
background-color: #F1F0FF;
cursor: pointer;
}
}
.card.call-to-action {
background-color: @ui-off-white;
border-radius: 16px;
border-color: @ui-off-white;
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.1);
margin-bottom: 90px;
}
.card-body {
padding: 30px;
}
}

View File

@ -22,7 +22,7 @@ module.exports.custom = {
* > but it can also be used for user-uploaded images, webhooks, etc. * * > but it can also be used for user-uploaded images, webhooks, etc. *
* * * *
**************************************************************************/ **************************************************************************/
baseUrl: 'http://localhost:1337', baseUrl: 'http://localhost:2024',
/************************************************************************** /**************************************************************************
* * * *

View File

@ -17,7 +17,8 @@ module.exports = {
// Add any dev-only routes for local development of not-yet-released pages. // Add any dev-only routes for local development of not-yet-released pages.
// e.g. http://localhost:2024/sandbox/example-query // e.g. http://localhost:2024/sandbox/example-query
routes: { routes: {
'GET /sandbox/example-query': { action: 'view-query-detail' }, '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
}, },
}; };

View File

@ -46,7 +46,7 @@ module.exports = Object.assign({}, PRODUCTION_CONFIG, {
// /\ Hard-code a staging-only override for allowed origins. // /\ Hard-code a staging-only override for allowed origins.
// || (or set this array via JSON-encoded system env var) // || (or set this array via JSON-encoded system env var)
// ``` // ```
// sails_sockets__onlyAllowOrigins='["http://localhost:1337", "…"]' // sails_sockets__onlyAllowOrigins='["https://staging.example.com"]'
// ``` // ```
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------

View File

@ -24,5 +24,9 @@ module.exports.policies = {
'legal/view-privacy': true, 'legal/view-privacy': true,
'deliver-contact-form-message': true, 'deliver-contact-form-message': true,
'view-query-detail': true, 'view-query-detail': true,
'view-query-library': true,
'docs/*': true,
'handbook/*': true,
'download-sitemap': true,
}; };

View File

@ -14,43 +14,26 @@ module.exports.routes = {
// ║║║║╣ ╠╩╗╠═╝╠═╣║ ╦║╣ ╚═╗ // ║║║║╣ ╠╩╗╠═╝╠═╣║ ╦║╣ ╚═╗
// ╚╩╝╚═╝╚═╝╩ ╩ ╩╚═╝╚═╝╚═╝ // ╚╩╝╚═╝╚═╝╩ ╩ ╩╚═╝╚═╝╚═╝
'GET /': { action: 'view-homepage-or-redirect', locals: { isHomepage: true } }, 'GET /': { action: 'view-homepage-or-redirect', locals: { isHomepage: true } },
'GET /company/contact': { action: 'view-contact' },
'GET /get-started': { action: 'view-pricing' },
'GET /install': 'https://github.com/fleetdm/fleet/blob/master/README.md', // « FUTURE: When ready, bring back { action: 'view-get-started' }
'/documentation': 'https://github.com/fleetdm/fleet/tree/master/docs',
'/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',
'/security': 'https://github.com/fleetdm/fleet/security/policy',
'/blog': 'https://medium.com/fleetdm', '/blog': 'https://medium.com/fleetdm',
'/legal/terms': 'https://docs.google.com/document/d/1OM6YDVIs7bP8wg6iA3VG13X086r64tWDqBSRudG4a0Y/edit',
'/security': 'https://github.com/fleetdm/fleet/security/policy',
'/company/about': '/blog', // FUTURE: brief "about" page explaining the origins of the company
'/company/stewardship': 'https://github.com/fleetdm/fleet', // FUTURE: page about how we approach open source and our commitments to the community
'GET /company/contact': { action: 'view-contact' },
'GET /apply': 'https://fleet-device-management.breezy.hr', 'GET /apply': 'https://fleet-device-management.breezy.hr',
'GET /get-started': { action: 'view-pricing' },
'GET /install': 'https://github.com/fleetdm/fleet/blob/master/README.md', // « FUTURE: When ready, bring back { action: 'view-get-started' }
'/documentation': 'https://github.com/fleetdm/fleet/tree/master/docs',
'/contribute': 'https://github.com/fleetdm/fleet/tree/master/docs/4-Contribution',
'/hall-of-fame': 'https://github.com/fleetdm/fleet/pulse',
// 'GET /welcome/:unused?': { action: 'dashboard/view-welcome' },
// 'GET /faq': { action: 'view-faq' },
// 'GET /legal/terms': { action: 'legal/view-terms' },
// 'GET /legal/privacy': { action: 'legal/view-privacy' },
// 'GET /signup': { action: 'entrance/view-signup' },
// 'GET /email/confirm': { action: 'entrance/confirm-email' },
// 'GET /email/confirmed': { action: 'entrance/view-confirmed-email' },
// 'GET /login': { action: 'entrance/view-login' },
// 'GET /password/forgot': { action: 'entrance/view-forgot-password' },
// 'GET /password/new': { action: 'entrance/view-new-password' },
// 'GET /account': { action: 'account/view-account-overview' },
// 'GET /account/password': { action: 'account/view-edit-password' },
// 'GET /account/profile': { action: 'account/view-edit-profile' },
// ╔╦╗╦╔═╗╔═╗ ╦═╗╔═╗╔╦╗╦╦═╗╔═╗╔═╗╔╦╗╔═╗ ┬ ╔╦╗╔═╗╦ ╦╔╗╔╦ ╔═╗╔═╗╔╦╗╔═╗ // ╔╦╗╦╔═╗╔═╗ ╦═╗╔═╗╔╦╗╦╦═╗╔═╗╔═╗╔╦╗╔═╗ ┬ ╔╦╗╔═╗╦ ╦╔╗╔╦ ╔═╗╔═╗╔╦╗╔═╗
// ║║║║╚═╗║ ╠╦╝║╣ ║║║╠╦╝║╣ ║ ║ ╚═╗ ┌┼─ ║║║ ║║║║║║║║ ║ ║╠═╣ ║║╚═╗ // ║║║║╚═╗║ ╠╦╝║╣ ║║║╠╦╝║╣ ║ ║ ╚═╗ ┌┼─ ║║║ ║║║║║║║║ ║ ║╠═╣ ║║╚═╗
@ -68,6 +51,10 @@ module.exports.routes = {
// Legacy (to avoid breaking links) // Legacy (to avoid breaking links)
'/try-fleet': '/get-started', '/try-fleet': '/get-started',
// Sitemap
'GET /sitemap.xml': { action: 'download-sitemap' },
// ╦ ╦╔═╗╔╗ ╦ ╦╔═╗╔═╗╦╔═╔═╗ // ╦ ╦╔═╗╔╗ ╦ ╦╔═╗╔═╗╦╔═╔═╗
// ║║║║╣ ╠╩╗╠═╣║ ║║ ║╠╩╗╚═╗ // ║║║║╣ ╠╩╗╠═╣║ ║║ ║╠╩╗╚═╗
// ╚╩╝╚═╝╚═╝╩ ╩╚═╝╚═╝╩ ╩╚═╝ // ╚╩╝╚═╝╚═╝╩ ╩╚═╝╚═╝╩ ╩╚═╝
@ -79,14 +66,6 @@ module.exports.routes = {
// ╩ ╩╩ ╩ ╚═╝╝╚╝═╩╝╩ ╚═╝╩╝╚╝ ╩ ╚═╝ // ╩ ╩╩ ╩ ╚═╝╝╚╝═╩╝╩ ╚═╝╩╝╚╝ ╩ ╚═╝
// Note that, in this app, these API endpoints may be accessed using the `Cloud.*()` methods // Note that, in this app, these API endpoints may be accessed using the `Cloud.*()` methods
// from the Parasails library, or by using those method names as the `action` in <ajax-form>. // from the Parasails library, or by using those method names as the `action` in <ajax-form>.
// '/api/v1/account/logout': { action: 'account/logout' },
// 'PUT /api/v1/account/update-password': { action: 'account/update-password' },
// 'PUT /api/v1/account/update-profile': { action: 'account/update-profile' },
// 'PUT /api/v1/account/update-billing-card': { action: 'account/update-billing-card' },
// 'PUT /api/v1/entrance/login': { action: 'entrance/login' },
// 'POST /api/v1/entrance/signup': { action: 'entrance/signup' },
// 'POST /api/v1/entrance/send-password-recovery-email': { action: 'entrance/send-password-recovery-email' },
// 'POST /api/v1/entrance/update-password-and-login': { action: 'entrance/update-password-and-login' },
'POST /api/v1/deliver-contact-form-message': { action: 'deliver-contact-form-message' }, 'POST /api/v1/deliver-contact-form-message': { action: 'deliver-contact-form-message' },
}; };

11021
website/package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@
"@sailshq/connect-redis": "^3.2.1", "@sailshq/connect-redis": "^3.2.1",
"@sailshq/lodash": "^3.10.3", "@sailshq/lodash": "^3.10.3",
"@sailshq/socket.io-redis": "^5.2.0", "@sailshq/socket.io-redis": "^5.2.0",
"sails": "^1.2.5", "sails": "^1.4.3",
"sails-hook-apianalytics": "^2.0.3", "sails-hook-apianalytics": "^2.0.3",
"sails-hook-organics": "^2.0.0", "sails-hook-organics": "^2.0.0",
"sails-hook-orm": "^2.1.1", "sails-hook-orm": "^2.1.1",
@ -17,16 +17,16 @@
}, },
"devDependencies": { "devDependencies": {
"cheerio": "0.18.0", "cheerio": "0.18.0",
"doc-templater": "^0.4.0",
"eslint": "5.16.0", "eslint": "5.16.0",
"grunt": "1.0.4", "grunt": "1.0.4",
"htmlhint": "0.11.0", "htmlhint": "0.11.0",
"lesshint": "6.3.6", "lesshint": "6.3.6",
"sails-hook-grunt": "^4.0.0" "sails-hook-grunt": "^4.0.0",
"yaml": "1.10.2"
}, },
"scripts": { "scripts": {
"custom-tests": "echo \"(No other custom tests yet.)\" && echo", "custom-tests": "echo \"(No other custom tests yet.)\" && echo",
"build-for-prod": "echo 'Now compiling markdown content and building+minifying assets for production...' && echo '(Hang tight, this could take a while.)' && echo && ./node_modules/sails/bin/sails.js run build-from-markdown && echo && node node_modules/grunt/bin/grunt buildProd || (echo && echo '------------------------------------------' && echo 'IMPORTANT! IMPORTANT! IMPORTANT!' && echo 'ERROR: Could not compile assets for production!' && echo && echo 'Please fix the issues logged above' && echo 'and push that up. Then, try deploying again.' && echo '------------------------------------------' && echo) && mv www .www && node -e 'sailsrc = JSON.parse(require(\"fs\").readFileSync(\"./.sailsrc\", \"utf8\")); if (sailsrc.paths&&sailsrc.paths.public !== undefined || sailsrc.hooks&&sailsrc.hooks.grunt !== undefined) { throw new Error(\"Cannot complete deployment script: .sailsrc file has conflicting contents! Please remove the conflicting stuff from .sailsrc, then commit and push that up.\"); } sailsrc.paths = sailsrc.paths || {}; sailsrc.paths.public = \"./.www\"; sailsrc.hooks = sailsrc.hooks || {}; sailsrc.hooks.grunt = false; require(\"fs\").writeFileSync(\"./.sailsrc\", JSON.stringify(sailsrc))' && echo 'Build is complete. Ready to deploy.'", "build-for-prod": "echo 'Now compiling markdown content and building+minifying assets for production...' && echo '(Hang tight, this could take a while.)' && echo && ./node_modules/sails/bin/sails.js run build-static-content && echo && node node_modules/grunt/bin/grunt buildProd || (echo && echo '------------------------------------------' && echo 'IMPORTANT! IMPORTANT! IMPORTANT!' && echo 'ERROR: Could not compile assets for production!' && echo && echo 'Please fix the issues logged above' && echo 'and push that up. Then, try deploying again.' && echo '------------------------------------------' && echo) && mv www .www && node -e 'sailsrc = JSON.parse(require(\"fs\").readFileSync(\"./.sailsrc\", \"utf8\")); if (sailsrc.paths&&sailsrc.paths.public !== undefined || sailsrc.hooks&&sailsrc.hooks.grunt !== undefined) { throw new Error(\"Cannot complete deployment script: .sailsrc file has conflicting contents! Please remove the conflicting stuff from .sailsrc, then commit and push that up.\"); } sailsrc.paths = sailsrc.paths || {}; sailsrc.paths.public = \"./.www\"; sailsrc.hooks = sailsrc.hooks || {}; sailsrc.hooks.grunt = false; require(\"fs\").writeFileSync(\"./.sailsrc\", JSON.stringify(sailsrc))' && echo 'Build is complete. Ready to deploy.'",
"build": "echo '\"npm run build\" deliberately left unimplemented to prevent its use, since different platforms like Heroku and GitHub Actions all like to try and run it by default, which can lead to inadvertent duplication and unnecessary lock-in, since one has to find the config to turn that off.'", "build": "echo '\"npm run build\" deliberately left unimplemented to prevent its use, since different platforms like Heroku and GitHub Actions all like to try and run it by default, which can lead to inadvertent duplication and unnecessary lock-in, since one has to find the config to turn that off.'",
"lint": "./node_modules/eslint/bin/eslint.js . --max-warnings=0 --report-unused-disable-directives && echo '✔ Your .js files look so good.' && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/**/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/**/**/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/pages/**/**/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/pages/**/**/**/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/pages/**/**/**/**/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/pages/**/**/**/**/**/*.ejs && echo '✔ So do your .ejs files.' && ./node_modules/lesshint/bin/lesshint assets/styles/ --max-warnings=0 && echo '✔ Your .less files look good, too.'", "lint": "./node_modules/eslint/bin/eslint.js . --max-warnings=0 --report-unused-disable-directives && echo '✔ Your .js files look so good.' && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/**/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/**/**/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/pages/**/**/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/pages/**/**/**/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/pages/**/**/**/**/*.ejs && ./node_modules/htmlhint/bin/htmlhint -c ./.htmlhintrc views/pages/**/**/**/**/**/*.ejs && echo '✔ So do your .ejs files.' && ./node_modules/lesshint/bin/lesshint assets/styles/ --max-warnings=0 && echo '✔ Your .less files look good, too.'",
"start": "NODE_ENV=production node app.js", "start": "NODE_ENV=production node app.js",

View File

@ -1,44 +0,0 @@
module.exports = {
friendlyName: 'Build from markdown',
description: 'Generate HTML partials from markdown content in fleetdm/fleet repo, and configure metadata about the generate files so it is available in `sails.config.builtFromMarkdown`.',
fn: async function () {
let path = require('path');
let filesGeneratedBySection = {
documentation: await sails.helpers.compileMarkdownContent('docs/'),
queryLibrary: await sails.helpers.compileMarkdownContent('handbook/queries/')
};
// FUTURE: Make this work in parallel as shown here by improving doctemplater to avoid the alreadyExists error
// (this actually only fails the very first time, but still, thinking is that it's not worth leaving a hack in
// here for a trivial build perf boost right now, especially since it only affects website deploys)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// let filesGeneratedBySection = await sails.helpers.flow.simultaneously({
// documentation: async() => await sails.helpers.compileMarkdownContent('docs/'),
// queryLibrary: async() => await sails.helpers.compileMarkdownContent('handbook/queries/')
// });
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// console.log(filesGeneratedBySection);
// Compile and generate XML sitemap
// (see "refresh" action in sailsjs.com repo for example)
// TODO
// Now take the compiled menu file and inject it into the .sailsrc file so it
// can be accessed for the purposes of config using `sails.config.builtFromMarkdown`.
let sailsrcPath = path.resolve(sails.config.appPath, '.sailsrc');
let sailsrcContent = await sails.helpers.fs.readJson(sailsrcPath);
sailsrcContent.builtFromMarkdown = filesGeneratedBySection;
await sails.helpers.fs.writeJson(sailsrcPath, sailsrcContent, true);
}
};

167
website/scripts/build-static-content.js vendored Normal file
View File

@ -0,0 +1,167 @@
module.exports = {
friendlyName: 'Build static content',
description: 'Generate HTML partials from source files in fleetdm/fleet repo (e.g. docs in markdown, or queries in YAML), and configure metadata about the generated files so it is available in `sails.config.builtStaticContent`.',
inputs: {
dry: { type: 'boolean', description: 'Whether to make this a dry run. (.sailsrc file will not be overwritten. HTML files will not be generated.)' },
},
fn: async function ({ dry }) {
let path = require('path');
let YAML = require('yaml');
// FUTURE: If we ever need to gather source files from other places or branches, etc, see git history of this file circa 2021-05-19 for an example of a different strategy we might use to do that.
let topLvlRepoPath = path.resolve(sails.config.appPath, '../');
// The data we're compiling will get built into this dictionary and then written on top of the .sailsrc file.
let builtStaticContent = {};
await sails.helpers.flow.simultaneously([
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());
}//•
// Attach to Sails app configuration.
builtStaticContent.queries = queries;
builtStaticContent.queryLibraryYmlRepoPath = RELATIVE_PATH_TO_QUERY_LIBRARY_YML_IN_FLEET_REPO;
},
async()=>{// Parse markdown pages, compile & generate HTML files, and bake documentation's directory tree into the Sails app's configuration.
// Note:
// • path maths inspired by https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L107-L132
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// // Original way that works: (versus new stuff below)
// builtStaticContent.markdownPages = await sails.helpers.compileMarkdownContent('docs/'); // TODO remove this and helper once everything works again
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
builtStaticContent.markdownPages = [];// « dir tree representation that will be injected into Sails app's configuration
let SECTION_REPO_PATHS = ['docs/', 'handbook/'];
for (let sectionRepoPath of SECTION_REPO_PATHS) {
let thinTree = await sails.helpers.fs.ls.with({
dir: path.join(topLvlRepoPath, sectionRepoPath),
depth: 100,
includeDirs: false,
includeSymlinks: false,
});
let rootRelativeUrlPathsSeen = [];
for (let pageSourcePath of thinTree) {
// Perform path maths (determine this using sectionRepoPath, etc)
// > Inspired by https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L308-L313
// > And https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L107-L132
let rootRelativeUrlPath = `/todo-${_.trimRight(sectionRepoPath,'/')}-${pageSourcePath.slice(-30).replace(/[^a-z0-9\-]/ig,'')}-${Math.floor(Math.random()*10000000)}`;
sails.log.verbose(`Building page ${rootRelativeUrlPath} from ${pageSourcePath} (${sectionRepoPath})`);
// ^^TODO: replace that with the actual desired root relative URL path
// Assert uniqueness of URL paths.
if (rootRelativeUrlPathsSeen.includes(rootRelativeUrlPath)) {
throw new Error('Failed compiling markdown content: Files as currently named would result in colliding (duplicate) URLs for the website. To resolve, rename the pages whose names are too similar. Duplicate detected: ' + rootRelativeUrlPath);
}//•
rootRelativeUrlPathsSeen.push(rootRelativeUrlPath);
// Get last modified timestamp using git
// > Inspired by https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L265-L273
let lastModifiedAt = Date.now();// TODO
let fallbackTitle = sails.helpers.strings.toSentenceCase(path.basename(pageSourcePath, '.ejs'));// « for clarity (the page isn't a template, necessarily, and this title is just a guess. Display title will, more likely than not, come from a <docmeta> tag -- see the bottom of the original, raw unformatted markdown of any page in the sailsjs docs for an example of how to use docmeta tags)
// If markdown: Compile to HTML and parse docpage metadata
// > Parsing docmeta tags (consider renaming them to just <meta>- or by now there's probably a more standard way of embedding semantics in markdown files; prefer to use that): https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L180-L183
// > Compiling: https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L198-L202
// TODO
// Skip this page, if appropriate
// > Inspired by https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/compile-markdown-tree-from-remote-git-repo.js#L275-L276
// TODO
// Generate HTML file
let htmlOutputPath = '';//TODO
if (dry) {
sails.log('Dry run: Would have generated file:', htmlOutputPath);
} else {
// TODO
}
// TODO: Figure out what to do about embedded images (they'll get cached by CDN so probably ok to point at github, but markdown img srcs will break if relative. Also GitHub could just change image URLs whenever.)
// Append to Sails app configuration.
builtStaticContent.markdownPages.push({
url: rootRelativeUrlPath,
title: '' || fallbackTitle,// TODO use metadata title if available
lastModifiedAt: lastModifiedAt
});
}//∞ </each source file>
}//∞ </each section repo path>
// Decorate markdownPages tree with easier-to-use properties related to metadata embedded in the markdown and parent/child relationships.
// Note: Maybe skip the parent/child relationships.
// > Inspired by https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/helpers/marshal-doc-page-metadata.js
// > And https://github.com/uncletammy/doc-templater/blob/2969726b598b39aa78648c5379e4d9503b65685e/lib/build-jsmenu.js
// TODO
// Sort siblings in the markdownPages tree so it's ready to use in menus.
// > Note: consider doing this on the frontend-- though there's a reason it was here. See:
// > • https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/helpers/compare-doc-page-metadatas.js
// > • https://github.com/sailshq/sailsjs.com/blob/b53c6e6a90c9afdf89e5cae00b9c9dd3f391b0e7/api/helpers/marshal-doc-page-metadata.js#L191-L208
// TODO
},
]);
// ██████╗ ███████╗██████╗ ██╗ █████╗ ██████╗███████╗ ███████╗ █████╗ ██╗██╗ ███████╗██████╗ ██████╗
// ██╔══██╗██╔════╝██╔══██╗██║ ██╔══██╗██╔════╝██╔════╝ ██╔════╝██╔══██╗██║██║ ██╔════╝██╔══██╗██╔════╝██╗
// ██████╔╝█████╗ ██████╔╝██║ ███████║██║ █████╗ ███████╗███████║██║██║ ███████╗██████╔╝██║ ╚═╝
// ██╔══██╗██╔══╝ ██╔═══╝ ██║ ██╔══██║██║ ██╔══╝ ╚════██║██╔══██║██║██║ ╚════██║██╔══██╗██║ ██╗
// ██║ ██║███████╗██║ ███████╗██║ ██║╚██████╗███████╗ ██╗███████║██║ ██║██║███████╗███████║██║ ██║╚██████╗╚═╝
// ╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝╚══════╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝
//
// Replace .sailsrc file.
// > This takes the compiled menu file from doc-templater and injects it into the .sailsrc file so it
// > can be accessed for the purposes of config using `sails.config.builtStaticContent`.
if (dry) {
sails.log('Dry run: Would have folded the following onto .sailsrc as "builtStaticContent":', builtStaticContent);
} else {
let sailsrcPath = path.resolve(sails.config.appPath, '.sailsrc');
let oldSailsrcJson = await sails.helpers.fs.readJson(sailsrcPath);
await sails.helpers.fs.writeJson.with({
force: true,
destination: sailsrcPath,
json: {
...oldSailsrcJson,
builtStaticContent: builtStaticContent,
}
});
}
}
};

View File

@ -47,7 +47,6 @@
<link rel="stylesheet" href="/dependencies/bootstrap-4/bootstrap-4.css"> <link rel="stylesheet" href="/dependencies/bootstrap-4/bootstrap-4.css">
<link rel="stylesheet" href="/dependencies/fontawesome.css"> <link rel="stylesheet" href="/dependencies/fontawesome.css">
<link rel="stylesheet" href="/styles/importer.css"> <link rel="stylesheet" href="/styles/importer.css">
<link rel="stylesheet" href="/styles/mixins-and-variables/typography.css">
<!--STYLES END--> <!--STYLES END-->
</head> </head>
<body> <body>
@ -221,17 +220,20 @@
<script src="/js/pages/account/edit-profile.page.js"></script> <script src="/js/pages/account/edit-profile.page.js"></script>
<script src="/js/pages/contact.page.js"></script> <script src="/js/pages/contact.page.js"></script>
<script src="/js/pages/dashboard/welcome.page.js"></script> <script src="/js/pages/dashboard/welcome.page.js"></script>
<script src="/js/pages/docs/basic-documentation.page.js"></script>
<script src="/js/pages/entrance/confirmed-email.page.js"></script> <script src="/js/pages/entrance/confirmed-email.page.js"></script>
<script src="/js/pages/entrance/forgot-password.page.js"></script> <script src="/js/pages/entrance/forgot-password.page.js"></script>
<script src="/js/pages/entrance/login.page.js"></script> <script src="/js/pages/entrance/login.page.js"></script>
<script src="/js/pages/entrance/new-password.page.js"></script> <script src="/js/pages/entrance/new-password.page.js"></script>
<script src="/js/pages/entrance/signup.page.js"></script> <script src="/js/pages/entrance/signup.page.js"></script>
<script src="/js/pages/faq.page.js"></script> <script src="/js/pages/faq.page.js"></script>
<script src="/js/pages/handbook/basic-handbook.page.js"></script>
<script src="/js/pages/homepage.page.js"></script> <script src="/js/pages/homepage.page.js"></script>
<script src="/js/pages/legal/privacy.page.js"></script> <script src="/js/pages/legal/privacy.page.js"></script>
<script src="/js/pages/legal/terms.page.js"></script> <script src="/js/pages/legal/terms.page.js"></script>
<script src="/js/pages/pricing.page.js"></script> <script src="/js/pages/pricing.page.js"></script>
<script src="/js/pages/query-detail.page.js"></script> <script src="/js/pages/query-detail.page.js"></script>
<script src="/js/pages/query-library.page.js"></script>
<!--SCRIPTS END--> <!--SCRIPTS END-->
<% /* Display an overlay if the current browser is not supported. <% /* Display an overlay if the current browser is not supported.

View File

@ -0,0 +1,11 @@
<div id="basic-documentation" v-cloak>
<h1>TODO: Implement this page.</h1>
<p>(See also <em>assets/styles/pages/docs/basic-documentation.less</em>, <em>assets/js/pages/docs/basic-documentation.page.js</em>, and <em>api/controllers/docs/view-basic-documentation.js</em>.)</p>
<p>This paragraph, and the content above it, are just here for reference. The HTML generated from markdown is below.</p>
<%- partial('../../partials/built-from-markdown/docs/1-Using-Fleet/1-Fleet-UI.ejs') %>
</div>
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>

View File

@ -0,0 +1,11 @@
<div id="basic-handbook" v-cloak>
<h1>TODO: Implement this page.</h1>
<p>(See also <em>assets/styles/pages/handbook/basic-handbook.less</em>, <em>assets/js/pages/handbook/basic-handbook.page.js</em>, and <em>api/controllers/handbook/view-basic-handbook.js</em>.)</p>
<p>This paragraph, and the content above it, are just here for reference. The HTML generated from markdown is below.</p>
<%- partial('../../partials/built-from-markdown/handbook/README.ejs') %>
</div>
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>

View File

@ -1,11 +1,49 @@
<div id="query-detail" v-cloak> <div id="query-detail" v-cloak>
<h1>TODO: Implement this page.</h1> <div class="d-flex justify-content-center">
<p>(See also <em>assets/styles/pages/query-detail.less</em>, <em>assets/js/pages/query-detail.page.js</em>, and <em>api/controllers/view-query-detail.js</em>.)</p> <div class="col-6 my-5">
<h2 class="mb-3">{{query.name}}</h2>
<h6 class="font-weight-light pb-3">{{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>
<div v-if="query.purpose === 'Detection' && query.remediation">
<h3 class="pt-5 pb-3">Remediation</h3>
<ul class="px-4">
<li>{{query.remediation}}</li>
</ul>
</div>
</div>
<p>This paragraph, and the content above it, are just here for reference. The HTML generated from markdown is below.</p> <div class="col-3 mx-5 my-5 d-none d-md-block">
<!-- TODO: refactor as page script to type-check and normalize"-->
<div class="query-sidebar border-bottom mb-3">
<h5>Platforms</h5>
<p>
<span v-if="query.platforms.includes('macOS')"><i class="fa fa-apple fa-lg mr-3" alt="Mac"></i></span>
<span v-if="query.platforms.includes('Windows')"><i class="fa fa-windows fa-lg mr-3" alt="Windows"></i></span>
<span v-if="query.platforms.includes('Linux')"><i class="fa fa-linux fa-lg mr" alt="Linux"></i></span>
</p>
</div>
<%- partial('../partials/built-from-markdown/handbook-queries/get-installed-macos-software.ejs') %> <div class="query-sidebar border-bottom mb-3">
<h5>Purpose</h5>
<p>{{query.purpose}}</p>
</div>
<div class="query-sidebar border-bottom mb-3" v-if="query.contributors && query.contributors.length">
<h5>Contributors</h5>
<!-- TODO: display github avatars"-->
<p>{{query.contributors}}</p>
</div>
<h5>Contribute to this page</h5>
<a target="_blank" :href="'https://github.com/fleetdm/fleet/edit/master/'+queryLibraryYmlRepoPath">View source</a>
</div>
</div>
</div> </div>
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %> <%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>

75
website/views/pages/query-library.ejs vendored Normal file
View File

@ -0,0 +1,75 @@
<div id="query-library" v-cloak>
<div class="d-flex justify-content-center">
<div class="col-8 my-5">
<h2 class="mb-3">Standard query library</h2>
<h6 class="font-weight-light pb-3">Fleet's standard query library includes a growing collection of useful queries for organizations deploying Fleet and osquery.</h6>
<div class="filter-and-search-bar container">
<div class="row justify-content-between">
<div class="col-8 d-flex px-0">
<div class="filter-purpose">
<span>Show
<select class="mr-1" v-model="selectedPurpose">
<option value="all" selected>all queries</option>
<option value="information">informational</option>
<option value="detection">detection</option>
</select>
</span>
</div>
<div class="filter-platform">
<span> compatible with
<select class="mr-1" 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>
</div>
<div class="col-3 px-0 d-none d-lg-block justify-content-end">
<div class="search ">
<input v-model="inputTextValue" placeholder="Search" @keydown.self="delayInput(setSearchString, 1000, 'defaultTimer')()"/>
</div>
</div>
</div>
</div>
<div class="results">
<div class="category__informational">
<div v-for="query of queriesList">
<div class="card results" @click="clickCard(query.slug)">
<div class="card-body">
<div class="row justify-content-between align-items-center">
<div class="col-10">
<h5 class="card-title m-0">{{query.name}}</h5>
<h6 class="font-italic mb-1 p-0">{{query.description}}</h6>
<div class="contributors" v-if="query.contributors && query.contributors.length">
<p class="mb-0">contributed by {{query.contributors}}</p>
</div>
</div>
<div class="col-2">
<div class="text-right m-0">
<span v-if="query.platforms.includes('macOS')"><i class="fa fa-apple fa-md ml-1" alt="Mac"></i></span>
<span v-if="query.platforms.includes('Windows')"><i class="fa fa-windows fa-md ml-1" alt="Windows"></i></span>
<span v-if="query.platforms.includes('Linux')"><i class="fa fa-linux fa-md ml-1" alt="Linux"></i></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-center">
<div class="card call-to-action col-8">
<div class="card-body flex-fill">
<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/tree/master/handbook/queries" >over on GitHub</a>.</p>
</div>
</div>
</div>
</div>
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>