mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Merge branch 'master' into teams
This commit is contained in:
commit
a5bd03e5d7
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,4 +1,4 @@
|
||||
blank_issues_enabled: false
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Chat with other developers
|
||||
url: https://osquery.slack.com/join/shared_invite/zt-h29zm0gk-s2DBtGUTW4CFel0f0IjTEw#/
|
||||
@ -6,7 +6,4 @@ contact_links:
|
||||
- name: Documentation
|
||||
url: https://fleetdm.com/documentation
|
||||
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").
|
||||
|
||||
|
11
.github/ISSUE_TEMPLATE/feature-request.md
vendored
11
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@ -2,16 +2,19 @@
|
||||
name: 💡 Feature request
|
||||
about: Propose a new feature or enhancement in Fleet.
|
||||
title: ''
|
||||
labels: 'enhancement'
|
||||
assignees: 'noahtalerman'
|
||||
labels: 'idea'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Goal
|
||||
|
||||
TODO
|
||||
<!-- 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. -->
|
||||
|
||||
- [ ]
|
||||
|
4
.github/workflows/deploy-fleet-website.yml
vendored
4
.github/workflows/deploy-fleet-website.yml
vendored
@ -43,7 +43,7 @@ jobs:
|
||||
# > delete the top level .eslintrc file too.
|
||||
- run: rm -f .eslintrc.js
|
||||
|
||||
# Get dependencies (including dev deps)
|
||||
# Download dependencies (including dev deps)
|
||||
- run: cd website/ && npm install
|
||||
|
||||
# Run sanity checks
|
||||
@ -56,7 +56,7 @@ jobs:
|
||||
# (This commit will never be pushed to GitHub- only to Heroku.)
|
||||
# > The local config flags make this work in GitHub's environment.
|
||||
- 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.'
|
||||
|
||||
# Configure the Heroku app we'll be deploying to
|
||||
|
3
.github/workflows/docs.yml
vendored
3
.github/workflows/docs.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: Check Documentation
|
||||
name: Check for bad links in documentation
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
@ -9,5 +9,6 @@ jobs:
|
||||
- uses: actions/checkout@master
|
||||
- uses: gaurav-nelson/github-action-markdown-link-check@v1
|
||||
with:
|
||||
check-modified-files-only: 'yes'
|
||||
use-quiet-mode: 'yes'
|
||||
config-file: .github/workflows/markdown-link-check-config.json
|
||||
|
@ -12,9 +12,6 @@
|
||||
{
|
||||
"pattern": "fleet.corp.example.com"
|
||||
},
|
||||
{
|
||||
"pattern": "hello@fleetdm.com"
|
||||
},
|
||||
{
|
||||
"pattern": "/server/datastore/mysql/migrations/"
|
||||
},
|
||||
|
@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
|
||||
|
||||
## 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.
|
||||
|
||||
|
@ -32,7 +32,7 @@ To open your browser's **network requests**, press Control Shift J (Windows, Lin
|
||||
### Report a security vulnerability
|
||||
|
||||
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
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
FROM alpine
|
||||
MAINTAINER Fleet Developers <engineering@fleetdm.com>
|
||||
MAINTAINER Fleet Developers <hello@fleetdm.com>
|
||||
|
||||
RUN apk --update add ca-certificates
|
||||
|
||||
|
6
Makefile
6
Makefile
@ -224,10 +224,10 @@ e2e-reset-db:
|
||||
e2e-setup:
|
||||
./build/fleetctl config set --context e2e --address https://localhost:8642
|
||||
./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 --email=test+1@fleetdm.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+1@example.com --username=test1 --password=admin123#
|
||||
./build/fleetctl user create --context e2e --email=test+2@example.com --username=test2 --password=admin123#
|
||||
|
||||
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
|
||||
|
@ -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).
|
||||
|
||||
#### 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>
|
||||
|
@ -2,6 +2,6 @@
|
||||
|
||||
## 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).
|
||||
|
BIN
assets/images/icon-close-dark-blue-grey-16x16@2x.png
Normal file
BIN
assets/images/icon-close-dark-blue-grey-16x16@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 443 B |
@ -350,7 +350,21 @@ func previewStopCommand() *cli.Command {
|
||||
out, err := exec.Command("docker-compose", "stop").CombinedOutput()
|
||||
if err != nil {
|
||||
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.")
|
||||
@ -385,7 +399,21 @@ func previewResetCommand() *cli.Command {
|
||||
out, err := exec.Command("docker-compose", "rm", "-sf").CombinedOutput()
|
||||
if err != nil {
|
||||
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.")
|
||||
|
@ -14,7 +14,7 @@ describe("User invite and activation", () => {
|
||||
|
||||
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();
|
||||
|
||||
@ -30,9 +30,9 @@ describe("User invite and activation", () => {
|
||||
cy.getEmails().then((response) => {
|
||||
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].Domain).to.equal("fleetdm.com");
|
||||
expect(response.body.items[0].From.Mailbox).to.equal("gabriel+dev");
|
||||
expect(response.body.items[0].From.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("fleet");
|
||||
expect(response.body.items[0].From.Domain).to.equal("example.com");
|
||||
const match = response.body.items[0].Content.Body.match(regex);
|
||||
inviteLink.url = match[0];
|
||||
});
|
||||
|
@ -16,17 +16,17 @@ describe("Manage Users", () => {
|
||||
|
||||
cy.wait("@getUsers");
|
||||
|
||||
cy.findByText("test@fleetdm.com").should("exist");
|
||||
cy.findByText("test+1@fleetdm.com").should("exist");
|
||||
cy.findByText("test+2@fleetdm.com").should("exist");
|
||||
cy.findByText("test@example.com").should("exist");
|
||||
cy.findByText("test+1@example.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.findByText("test@fleetdm.com").should("exist");
|
||||
cy.findByText("test+1@fleetdm.com").should("not.exist");
|
||||
cy.findByText("test+2@fleetdm.com").should("not.exist");
|
||||
cy.findByText("test@example.com").should("exist");
|
||||
cy.findByText("test+1@example.com").should("not.exist");
|
||||
cy.findByText("test+2@example.com").should("not.exist");
|
||||
});
|
||||
|
||||
// it('Creating a user', () => {
|
||||
@ -40,7 +40,7 @@ describe("Manage Users", () => {
|
||||
// .type('New User');
|
||||
//
|
||||
// cy.findByPlaceholderText('Email')
|
||||
// .type('new-user@fleetdm.com');
|
||||
// .type('new-user@example.com');
|
||||
//
|
||||
// cy.findByRole('checkbox', { name: 'Test Team' })
|
||||
// .click({ force: true }); // we use `force` as the checkbox button is not fully accessible yet.
|
||||
|
@ -48,7 +48,7 @@ describe("Settings flow", () => {
|
||||
|
||||
cy.findByLabelText(/sender address/i)
|
||||
.click()
|
||||
.type("rachel@fleetdm.com");
|
||||
.type("rachel@example.com");
|
||||
|
||||
cy.findByLabelText(/smtp server/i)
|
||||
.click()
|
||||
@ -121,7 +121,7 @@ describe("Settings flow", () => {
|
||||
|
||||
cy.findByLabelText(/sender address/i).should(
|
||||
"have.value",
|
||||
"rachel@fleetdm.com"
|
||||
"rachel@example.com"
|
||||
);
|
||||
|
||||
cy.findByLabelText(/smtp server/i).should("have.value", "localhost");
|
||||
@ -140,9 +140,9 @@ describe("Settings flow", () => {
|
||||
cy.getEmails().then((response) => {
|
||||
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].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.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(
|
||||
"Hello from Fleet"
|
||||
);
|
||||
|
@ -10,7 +10,7 @@ describe("Sessions", () => {
|
||||
cy.contains(/forgot password/i);
|
||||
|
||||
// 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("button").click();
|
||||
|
||||
@ -27,7 +27,7 @@ describe("Sessions", () => {
|
||||
|
||||
it("Fails login with invalid password", () => {
|
||||
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(".button").click();
|
||||
|
||||
|
@ -12,7 +12,7 @@ describe("SSO Sessions", () => {
|
||||
cy.contains(/forgot password/i);
|
||||
|
||||
// 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.contains("button", "Login").click();
|
||||
|
||||
|
@ -20,7 +20,7 @@ describe("Setup", () => {
|
||||
.last()
|
||||
.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();
|
||||
|
||||
|
@ -59,28 +59,7 @@ Cypress.Commands.add("setupSMTP", () => {
|
||||
authentication_type: "authtype_none",
|
||||
enable_smtp: true,
|
||||
port: 1025,
|
||||
sender_address: "gabriel+dev@fleetdm.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",
|
||||
sender_address: "fleet@example.com",
|
||||
server: "localhost",
|
||||
},
|
||||
};
|
||||
|
@ -318,6 +318,8 @@ spec:
|
||||
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
|
||||
|
||||
When managing multiple Fleet environments, you may want to move queries and/or packs from one "exporter" environment to a another "importer" environment.
|
||||
|
@ -93,7 +93,7 @@ Then, use that API token to authenticate all subsequent API requests by sending
|
||||
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
|
||||
|
||||
@ -487,15 +487,17 @@ This is the callback endpoint that the identity provider will use to send securi
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | In | Description |
|
||||
| ----------------------- | ------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| page | integer | query | Page number of the results to fetch. |
|
||||
| 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_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`. |
|
||||
| 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. |
|
||||
| Name | Type | In | Description |
|
||||
| ----------------------- | ------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| page | integer | query | Page number of the results to fetch. |
|
||||
| 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_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`. |
|
||||
| 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. Use `*` to get all stored fields. |
|
||||
|
||||
If `additional_info_filters` is not specified, no `additional` information will be returned.
|
||||
|
||||
#### Example
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
# 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)
|
||||
- [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)
|
||||
@ -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)
|
||||
- [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?
|
||||
|
||||
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 @@ It’s possible in Fleet to retrieve each host’s kernel version, using the Fle
|
||||
|
||||
## 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.
|
||||
|
@ -7,7 +7,7 @@ spec:
|
||||
description: Count the number of Apple applications installed on the machine.
|
||||
query: SELECT COUNT(*) FROM apps WHERE bundle_identifier LIKE 'com.apple.%';
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
contributors: mike-j-thomas,noahtalerman,mikermcneil
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -17,7 +17,6 @@ spec:
|
||||
description: Retrieves the OpenSSL version.
|
||||
query: SELECT name AS name, version AS version, 'deb_packages' AS source FROM deb_packages WHERE name LIKE 'openssl%' UNION SELECT name AS name, version AS version, 'apt_sources' AS source FROM apt_sources WHERE name LIKE 'openssl%' UNION SELECT name AS name, version AS version, 'rpm_packages' AS source FROM rpm_packages WHERE name LIKE 'openssl%';
|
||||
purpose: Detection
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -27,7 +26,6 @@ spec:
|
||||
description: Gatekeeper tries to ensure only trusted software is run on a mac machine.
|
||||
query: SELECT * FROM gatekeeper WHERE assessments_enabled = 0;
|
||||
purpose: Detection
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -42,12 +40,22 @@ spec:
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
spec:
|
||||
name: Get authorized keys
|
||||
name: Get authorized keys for Local Accounts
|
||||
platforms: macOS, Linux
|
||||
description: List authorized_keys for each user on the system.
|
||||
query: SELECT * FROM users CROSS JOIN authorized_keys USING (uid);
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
contributors: anelshaer
|
||||
---
|
||||
apiVersion: v1
|
||||
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
|
||||
kind: query
|
||||
@ -57,7 +65,6 @@ spec:
|
||||
description: Retrieve application, system, and mobile app crash logs.
|
||||
query: SELECT uid, datetime, responsible, exception_type, identifier, version, crash_path FROM users CROSS JOIN crashes USING (uid);
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -67,7 +74,6 @@ spec:
|
||||
description: List installed Chrome Extensions for all users.
|
||||
query: SELECT uid, datetime, responsible, exception_type, identifier, version, crash_path FROM users CROSS JOIN crashes USING (uid);
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -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.
|
||||
query: SELECT name AS name, version AS version, 'Browser plugin (Chrome)' AS type, 'chrome_extensions' AS source FROM chrome_extensions UNION SELECT name AS name, version AS version, 'Browser plugin (Firefox)' AS type, 'firefox_addons' AS source FROM firefox_addons UNION SELECT name AS name, version AS version, 'Package (Atom)' AS type, 'atom_packages' AS source FROM atom_packages UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages UNION SELECT name AS name, version AS version, 'Package (pkg)' AS type, 'pkg_packages' AS source FROM pkg_packages;
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -87,7 +92,6 @@ spec:
|
||||
description: Get the installed homebrew package database.
|
||||
query: SELECT * FROM homebrew_packages;
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -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.
|
||||
query: SELECT name AS name, version AS version, 'Package (APT)' AS type, 'apt_sources' AS source FROM apt_sources UNION SELECT name AS name, version AS version, 'Package (deb)' AS type, 'deb_packages' AS source FROM deb_packages UNION SELECT package AS name, version AS version, 'Package (Portage)' AS type, 'portage_packages' AS source FROM portage_packages UNION SELECT name AS name, version AS version, 'Package (RPM)' AS type, 'rpm_packages' AS source FROM rpm_packages UNION SELECT name AS name, '' AS version, 'Package (YUM)' AS type, 'yum_sources' AS source FROM yum_sources UNION SELECT name AS name, version AS version, 'Package (NPM)' AS type, 'npm_packages' AS source FROM npm_packages UNION SELECT name AS name, version AS version, 'Package (Atom)' AS type, 'atom_packages' AS source FROM atom_packages UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages;
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -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.
|
||||
query: SELECT name AS name, bundle_short_version AS version, 'Application (macOS)' AS type, 'apps' AS source FROM apps UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages UNION SELECT name AS name, version AS version, 'Browser plugin (Chrome)' AS type, 'chrome_extensions' AS source FROM chrome_extensions UNION SELECT name AS name, version AS version, 'Browser plugin (Firefox)' AS type, 'firefox_addons' AS source FROM firefox_addons UNION SELECT name As name, version AS version, 'Browser plugin (Safari)' AS type, 'safari_extensions' AS source FROM safari_extensions UNION SELECT name AS name, version AS version, 'Package (Homebrew)' AS type, 'homebrew_packages' AS source FROM homebrew_packages;
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -117,7 +119,6 @@ spec:
|
||||
description: Retrieves the list of installed Safari Extensions for all users in the target system.
|
||||
query: SELECT safari_extensions.* FROM users join safari_extensions USING (uid);
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -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.
|
||||
query: SELECT name AS name, version AS version, 'Program (Windows)' AS type, 'programs' AS source FROM programs UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages UNION SELECT name AS name, version AS version, 'Browser plugin (IE)' AS type, 'ie_extensions' AS source FROM ie_extensions UNION SELECT name AS name, version AS version, 'Browser plugin (Chrome)' AS type, 'chrome_extensions' AS source FROM chrome_extensions UNION SELECT name AS name, version AS version, 'Browser plugin (Firefox)' AS type, 'firefox_addons' AS source FROM firefox_addons UNION SELECT name AS name, version AS version, 'Package (Chocolatey)' AS type, 'chocolatey_packages' AS source FROM chocolatey_packages UNION SELECT name AS name, version AS version, 'Package (Atom)' AS type, 'atom_packages' AS source FROM atom_packages UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages;
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -137,7 +137,6 @@ spec:
|
||||
description:
|
||||
query: SELECT * FROM battery WHERE health != 'Good' AND condition NOT IN ('', 'Normal');
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -147,17 +146,6 @@ spec:
|
||||
description: Displays the percentage of free space available on the primary disk partition.
|
||||
query: SELECT (blocks_available * 100 / blocks) AS pct, * FROM mounts WHERE path = '/';
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
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
|
||||
kind: query
|
||||
@ -167,7 +155,6 @@ spec:
|
||||
description: Shows system mounted devices and filesystems (not process specific).
|
||||
query: SELECT device, device_alias, path, type, blocks_size FROM mounts;
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -177,7 +164,6 @@ spec:
|
||||
description: Shows system mounted devices and filesystems (not process specific).
|
||||
query: SELECT * FROM os_version;
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -187,7 +173,6 @@ spec:
|
||||
description: Shows information about the host platform
|
||||
query: SELECT vendor, version, date, revision from platform_info;
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -197,7 +182,6 @@ spec:
|
||||
description: Shows applications and binaries set as user/login startup items.
|
||||
query: SELECT * FROM startup_items;
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -207,7 +191,16 @@ spec:
|
||||
description: Get a list of system logins and logouts.
|
||||
query: SELECT * FROM last;
|
||||
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
|
||||
kind: query
|
||||
@ -217,7 +210,6 @@ spec:
|
||||
description: Shows the system uptime.
|
||||
query: SELECT * FROM uptime;
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -227,7 +219,6 @@ spec:
|
||||
description: Shows all USB devices that are actively plugged into the host system.
|
||||
query: SELECT * FROM usb_devices;
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -237,7 +228,6 @@ spec:
|
||||
description: Shows information about the wifi network that a host is currently connected to.
|
||||
query: SELECT * FROM wifi_status;
|
||||
purpose: Informational
|
||||
remediation: N/A
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
@ -247,4 +237,159 @@ spec:
|
||||
description:
|
||||
query: SELECT * FROM bitlocker_info WHERE protection_status = 0;
|
||||
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
|
||||
|
@ -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 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
|
||||
|
||||
@ -10,7 +10,7 @@ General information and flag documentation can be accessed by running `orbit --h
|
||||
|
||||
### 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:
|
||||
|
||||
@ -32,20 +32,20 @@ Use the `--fleet-url` and `--enroll-secret` flags to connect to a Fleet server.
|
||||
|
||||
For example:
|
||||
|
||||
``` sh
|
||||
orbit --fleet-url=https://localhost:8080 --enroll-secret=the_secret_value
|
||||
```sh
|
||||
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):
|
||||
|
||||
``` sh
|
||||
orbit --fleet-url=https://localhost:8080 --enroll-secret=the_secret_value --fleet-certificate=cert.pem
|
||||
```sh
|
||||
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:
|
||||
|
||||
``` sh
|
||||
orbit --fleet-url=https://localhost:8080 --enroll-secret=the_secret_value --insecure
|
||||
```sh
|
||||
orbit --fleet-url=https://localhost:8080 --enroll-secret=the_secret_value --insecure
|
||||
```
|
||||
|
||||
### 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:
|
||||
|
||||
``` sh
|
||||
```sh
|
||||
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.
|
||||
|
||||
### 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
|
||||
|
||||
- **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`.
|
||||
|
||||
- **Windows** (coming soon) - `.msi` package generation - Persistence via Services.
|
||||
- **Windows** - `.msi` package generation - Persistence via Services.
|
||||
|
||||
### 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.
|
||||
|
||||
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.
|
||||
|
||||
A minimal invocation for communicating with Fleet:
|
||||
|
||||
``` sh
|
||||
```sh
|
||||
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.
|
||||
|
||||
| Channel | Versions |
|
||||
| ------------------------------------ | ------ |
|
||||
| `4` | 4.x.x |
|
||||
| `4.6` | 4.6.x |
|
||||
| `4.6.0` | 4.6.0 |
|
||||
| Channel | Versions |
|
||||
| ------- | -------- |
|
||||
| `4` | 4.x.x |
|
||||
| `4.6` | 4.6.x |
|
||||
| `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.
|
||||
|
||||
@ -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:
|
||||
|
||||
``` 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
|
||||
```sh
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -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 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:
|
||||
|
||||
- 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.
|
||||
|
||||
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:
|
||||
|
||||
- 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.
|
||||
|
||||
- Default value: `""`
|
||||
- Environment variable: `FLEET_MYSQL_PASSWORD_PATH`
|
||||
- Config file format:
|
||||
|
||||
```
|
||||
@ -333,7 +338,7 @@ The database to use when connecting to the Redis instance.
|
||||
redis:
|
||||
database: 14
|
||||
```
|
||||
|
||||
|
||||
###### `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.
|
||||
@ -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.
|
||||
|
||||
- Default value: `""`
|
||||
- Environment variable: `FLEET_AUTH_JWT_KEY_PATH`
|
||||
- Config file format:
|
||||
|
||||
```
|
||||
auth:
|
||||
jwt_key_path: '/run/secrets/fleetdm-jwt-token
|
||||
jwt_key_path: '/run/secrets/fleetdm-jwt-token'
|
||||
```
|
||||
|
||||
##### `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.
|
||||
|
||||
- Default value: `12`
|
||||
- Environment variable: `FLEET_AUTH_BCRYT_COST`
|
||||
- Environment variable: `FLEET_AUTH_BCRYPT_COST`
|
||||
- Config file format:
|
||||
|
||||
```
|
||||
@ -521,7 +527,7 @@ Size of generated app tokens.
|
||||
How long invite tokens should be valid for.
|
||||
|
||||
- Default value: `5 days`
|
||||
- Environment variable: `FLEET_APP_TOKEN_VALIDITY_PERIOD`
|
||||
- Environment variable: `FLEET_APP_INVITE_TOKEN_VALIDITY_PERIOD`
|
||||
- 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.
|
||||
|
||||
- Default value: `0` (off)
|
||||
- Environment variable: `FLEET_ENROLL_COOLDOWN`
|
||||
- Environment variable: `FLEET_OSQUERY_ENROLL_COOLDOWN`
|
||||
- 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`.
|
||||
|
||||
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:
|
||||
|
||||
- `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`.
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
|
@ -26,6 +26,7 @@
|
||||
- [Deploying Fleet](#deploying-fleet)
|
||||
- [Deploying the load balancer](#deploying-the-load-balancer)
|
||||
- [Configure DNS](#configure-dns)
|
||||
- [Community projects](#community-projects)
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
@ -80,5 +80,6 @@
|
||||
|
||||
&__label {
|
||||
font-size: $x-small;
|
||||
padding-left: $pad-small;
|
||||
}
|
||||
}
|
||||
|
@ -46,36 +46,31 @@
|
||||
background-color: $ui-light-grey;
|
||||
border-radius: $border-radius;
|
||||
height: 40px;
|
||||
|
||||
.Select-value {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.Select-value {
|
||||
font-size: $x-small;
|
||||
border-radius: $border-radius;
|
||||
background-color: $ui-light-grey;
|
||||
border: solid 1px $ui-fleet-blue-15;
|
||||
background-color: $ui-off-white;
|
||||
border: solid 1px $core-dark-blue-grey;
|
||||
|
||||
.Select-value-icon {
|
||||
border: 0;
|
||||
float: right;
|
||||
position: relative;
|
||||
line-height: 34px;
|
||||
width: 25px;
|
||||
line-height: 28px;
|
||||
width: 20px;
|
||||
padding: 0;
|
||||
margin: 0 5px;
|
||||
text-indent: -999em;
|
||||
|
||||
&::after {
|
||||
@extend %kolidecon;
|
||||
transition: color 150ms ease-in-out;
|
||||
transform: translate(-50%, -50%);
|
||||
content: "\f036";
|
||||
content: url(../assets/images/icon-close-dark-blue-grey-16x16@2x.png);
|
||||
transform: scale(0.5);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
top: -5px;
|
||||
left: -5px;
|
||||
visibility: visible;
|
||||
font-size: $small;
|
||||
color: $ui-gray;
|
||||
@ -84,9 +79,9 @@
|
||||
}
|
||||
|
||||
.Select-value-label {
|
||||
font-size: $small;
|
||||
font-weight: $regular;
|
||||
color: $core-fleet-black;
|
||||
font-size: $x-small;
|
||||
font-weight: $bold;
|
||||
color: $core-dark-blue-grey;
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
@ -156,8 +151,8 @@
|
||||
&.is-pseudo-focused > .Select-control {
|
||||
.Select-value {
|
||||
.Select-value-label {
|
||||
color: $core-fleet-black;
|
||||
font-size: $small;
|
||||
color: $core-dark-blue-grey;
|
||||
font-size: $x-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -242,12 +237,11 @@
|
||||
}
|
||||
|
||||
.Select-value {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 3px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.Select-value-label {
|
||||
padding: 0 0 0 1rem;
|
||||
padding: 0 0 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ class EditPackForm extends Component {
|
||||
/>
|
||||
<SelectTargetsDropdown
|
||||
{...fields.targets}
|
||||
label="select pack targets"
|
||||
label="Select pack targets"
|
||||
name="selected-pack-targets"
|
||||
onFetchTargets={onFetchTargets}
|
||||
onSelect={fields.targets.onChange}
|
||||
|
@ -227,25 +227,21 @@ class QueryResultsTable extends Component {
|
||||
{!hasNoResults && renderTable()}
|
||||
</div>
|
||||
{hasErrors && (
|
||||
<>
|
||||
<div className={`${baseClass}__error-table-container`}>
|
||||
<header className={`${baseClass}__button-wrap`}>
|
||||
<div>
|
||||
<Button
|
||||
className={`${baseClass}__export-btn`}
|
||||
onClick={onExportErrorsResults}
|
||||
variant="inverse"
|
||||
>
|
||||
Export errors
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
<span className={`${baseClass}__table-title`}>Errors</span>
|
||||
<div className={`${baseClass}__error-table-wrapper`}>
|
||||
{renderErrorsTable()}
|
||||
</div>
|
||||
<div className={`${baseClass}__error-table-container`}>
|
||||
<header className={`${baseClass}__button-wrap`}>
|
||||
<Button
|
||||
className={`${baseClass}__export-btn`}
|
||||
onClick={onExportErrorsResults}
|
||||
variant="inverse"
|
||||
>
|
||||
Export errors
|
||||
</Button>
|
||||
</header>
|
||||
<span className={`${baseClass}__table-title`}>Errors</span>
|
||||
<div className={`${baseClass}__error-table-wrapper`}>
|
||||
{renderErrorsTable()}
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -39,7 +39,7 @@
|
||||
border-radius: 3px;
|
||||
overflow: scroll;
|
||||
margin-top: $pad-xsmall;
|
||||
min-height: 200px;
|
||||
min-height: 100px;
|
||||
max-height: 400px;
|
||||
width: 100%;
|
||||
|
||||
@ -57,17 +57,19 @@
|
||||
|
||||
&__error-table-container {
|
||||
margin-top: $pad-large;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
&__error-table-wrapper {
|
||||
display: flex;
|
||||
border: solid 1px $ui-fleet-blue-15;
|
||||
border-radius: 3px;
|
||||
overflow: scroll;
|
||||
margin-bottom: $pad-xxlarge;
|
||||
margin-top: $pad-xsmall;
|
||||
max-height: 200px;
|
||||
min-height: 200px;
|
||||
max-height: 400px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&__table {
|
||||
@ -144,7 +146,11 @@
|
||||
}
|
||||
|
||||
.query-results-table__error-table-container {
|
||||
display: none;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.query-results-table__error-table-wrapper {
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ const currentUser = {
|
||||
global_role: "admin",
|
||||
};
|
||||
const invitedUser = {
|
||||
email: "test+4@fleetdm.com",
|
||||
email: "test+4@example.com",
|
||||
global_role: "observer",
|
||||
id: 6,
|
||||
invited_by: 1,
|
||||
|
@ -5,6 +5,7 @@ $core-vibrant-blue: #6a67fe;
|
||||
$core-vibrant-red: #ff5c83;
|
||||
$core-fleet-purple: #ae6ddf;
|
||||
$core-white: #ffffff;
|
||||
$core-dark-blue-grey: #506e92;
|
||||
|
||||
// UI
|
||||
$ui-fleet-black-50: #8b8fa2;
|
||||
|
@ -3,6 +3,9 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<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="shortcut icon" href="{{.URLPrefix}}/assets/favicon.ico">
|
||||
|
||||
|
@ -28,7 +28,7 @@ Zach partnered with our CEO, Mike McNeil, to found a new, independent company: F
|
||||
### Culture
|
||||
|
||||
### 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
|
||||
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.
|
||||
|
73
handbook/release-process.md
Normal file
73
handbook/release-process.md
Normal 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 shouldn’t 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 Fleet’s 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 issue’s 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
|
@ -69,8 +69,8 @@ func testSaveHosts(t *testing.T, ds kolide.Datastore) {
|
||||
additionalJSON := json.RawMessage(`{"foobar": "bim"}`)
|
||||
host.Additional = &additionalJSON
|
||||
|
||||
err = ds.SaveHost(host)
|
||||
require.Nil(t, err)
|
||||
require.NoError(t, ds.SaveHost(host))
|
||||
require.NoError(t, ds.SaveHostAdditional(host))
|
||||
|
||||
host, err = ds.Host(host.ID)
|
||||
require.Nil(t, err)
|
||||
@ -291,26 +291,24 @@ func testListHostsFilterAdditional(t *testing.T, ds kolide.Datastore) {
|
||||
// Add additional
|
||||
additional := json.RawMessage(`{"field1": "v1", "field2": "v2"}`)
|
||||
h.Additional = &additional
|
||||
err = ds.SaveHost(h)
|
||||
require.Nil(t, err)
|
||||
require.NoError(t, ds.SaveHostAdditional(h))
|
||||
|
||||
additional = json.RawMessage(`{"field1": "v1", "field2": "v2"}`)
|
||||
hosts, err := ds.ListHosts(kolide.HostListOptions{})
|
||||
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"}})
|
||||
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)
|
||||
assert.Equal(t, additional, *hosts[0].Additional)
|
||||
assert.Equal(t, &additional, hosts[0].Additional)
|
||||
|
||||
additional = json.RawMessage(`{"field1": "v1", "missing": null}`)
|
||||
hosts, err = ds.ListHosts(kolide.HostListOptions{AdditionalFilters: []string{"field1", "missing"}})
|
||||
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) {
|
||||
@ -844,9 +842,9 @@ func testHostAdditional(t *testing.T, ds kolide.Datastore) {
|
||||
// Add additional
|
||||
additional := json.RawMessage(`{"additional": "result"}`)
|
||||
h.Additional = &additional
|
||||
err = ds.SaveHost(h)
|
||||
require.Nil(t, err)
|
||||
require.NoError(t, ds.SaveHostAdditional(h))
|
||||
|
||||
// Additional should not be loaded for authenticatehost
|
||||
h, err = ds.AuthenticateHost("nodekey")
|
||||
require.Nil(t, err)
|
||||
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)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, additional, *h.Additional)
|
||||
assert.Equal(t, &additional, h.Additional)
|
||||
|
||||
// Update besides additional. Additional should be unchanged.
|
||||
h, err = ds.AuthenticateHost("nodekey")
|
||||
@ -870,14 +868,14 @@ func testHostAdditional(t *testing.T, ds kolide.Datastore) {
|
||||
|
||||
h, err = ds.Host(h.ID)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, additional, *h.Additional)
|
||||
assert.Equal(t, &additional, h.Additional)
|
||||
|
||||
// Update additional
|
||||
additional = json.RawMessage(`{"other": "additional"}`)
|
||||
h, err = ds.AuthenticateHost("nodekey")
|
||||
require.Nil(t, err)
|
||||
h.Additional = &additional
|
||||
err = ds.SaveHost(h)
|
||||
err = ds.SaveHostAdditional(h)
|
||||
require.Nil(t, err)
|
||||
|
||||
h, err = ds.AuthenticateHost("nodekey")
|
||||
@ -887,7 +885,7 @@ func testHostAdditional(t *testing.T, ds kolide.Datastore) {
|
||||
|
||||
h, err = ds.Host(h.ID)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, additional, *h.Additional)
|
||||
assert.Equal(t, &additional, h.Additional)
|
||||
}
|
||||
|
||||
func testHostByIdentifier(t *testing.T, ds kolide.Datastore) {
|
||||
|
@ -88,7 +88,6 @@ func (d *Datastore) SaveHost(host *kolide.Host) error {
|
||||
distributed_interval = ?,
|
||||
config_tls_refresh = ?,
|
||||
logger_tls_period = ?,
|
||||
additional = COALESCE(?, additional),
|
||||
team_id = ?,
|
||||
primary_ip = ?,
|
||||
primary_mac = ?,
|
||||
@ -123,7 +122,6 @@ func (d *Datastore) SaveHost(host *kolide.Host) error {
|
||||
host.DistributedInterval,
|
||||
host.ConfigTLSRefresh,
|
||||
host.LoggerTLSPeriod,
|
||||
host.Additional,
|
||||
host.TeamID,
|
||||
host.PrimaryIP,
|
||||
host.PrimaryMac,
|
||||
@ -267,9 +265,10 @@ func (d *Datastore) DeleteHost(hid uint) error {
|
||||
|
||||
func (d *Datastore) Host(id uint) (*kolide.Host, error) {
|
||||
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)
|
||||
WHERE h.id = ? LIMIT 1
|
||||
WHERE h.id = ?
|
||||
LIMIT 1
|
||||
`
|
||||
host := &kolide.Host{}
|
||||
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.team_id,
|
||||
h.refetch_requested,
|
||||
t.name AS team_name,
|
||||
t.name AS team_name
|
||||
`
|
||||
|
||||
var params []interface{}
|
||||
|
||||
// Filter additional info by extracting into a new json object.
|
||||
if len(opt.AdditionalFilters) > 0 {
|
||||
sql += `JSON_OBJECT(
|
||||
// Only include "additional" if filter provided.
|
||||
if len(opt.AdditionalFilters) == 1 && opt.AdditionalFilters[0] == "*" {
|
||||
// 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 {
|
||||
sql += `?, JSON_EXTRACT(additional, ?), `
|
||||
@ -336,12 +341,8 @@ func (d *Datastore) ListHosts(opt kolide.HostListOptions) ([]*kolide.Host, error
|
||||
}
|
||||
sql = sql[:len(sql)-2]
|
||||
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)
|
||||
@ -766,3 +767,16 @@ func (d *Datastore) AddHostsToTeam(teamID *uint, hostIDs []uint) error {
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -76,6 +76,9 @@ type HostStore interface {
|
||||
// AddHostsToTeam adds hosts to an existing team, clearing their team
|
||||
// settings if teamID is nil.
|
||||
AddHostsToTeam(teamID *uint, hostIDs []uint) error
|
||||
// SaveHostAdditional saves the information generated by the
|
||||
// additional_queries.
|
||||
SaveHostAdditional(host *Host) error
|
||||
}
|
||||
|
||||
type HostService interface {
|
||||
@ -154,13 +157,15 @@ type Host struct {
|
||||
DistributedInterval uint `json:"distributed_interval" db:"distributed_interval"`
|
||||
ConfigTLSRefresh uint `json:"config_tls_refresh" db:"config_tls_refresh"`
|
||||
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
|
||||
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
|
||||
|
@ -42,6 +42,8 @@ type HostIDsByNameFunc func(hostnames []string) ([]uint, error)
|
||||
|
||||
type AddHostsToTeamFunc func(teamID *uint, hostIDs []uint) error
|
||||
|
||||
type SaveHostAdditionalFunc func(host *kolide.Host) error
|
||||
|
||||
type HostStore struct {
|
||||
NewHostFunc NewHostFunc
|
||||
NewHostFuncInvoked bool
|
||||
@ -90,6 +92,9 @@ type HostStore struct {
|
||||
|
||||
AddHostsToTeamFunc AddHostsToTeamFunc
|
||||
AddHostsToTeamFuncInvoked bool
|
||||
|
||||
SaveHostAdditionalFunc SaveHostAdditionalFunc
|
||||
SaveHostAdditionalFuncInvoked bool
|
||||
}
|
||||
|
||||
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
|
||||
return s.AddHostsToTeamFunc(teamID, hostIDs)
|
||||
}
|
||||
|
||||
func (s *HostStore) SaveHostAdditional(host *kolide.Host) error {
|
||||
s.SaveHostAdditionalFuncInvoked = true
|
||||
return s.SaveHostAdditionalFunc(host)
|
||||
}
|
||||
|
@ -228,7 +228,7 @@ func (svc service) StreamCampaignResults(ctx context.Context, conn *websocket.Co
|
||||
if lastTotals != totals {
|
||||
lastTotals = totals
|
||||
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 {
|
||||
lastStatus = status
|
||||
if err = conn.WriteJSONMessage("status", status); err != nil {
|
||||
return errors.New("write status")
|
||||
return errors.Wrap(err, "write status")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1125,6 +1125,12 @@ func (svc service) SubmitDistributedQueryResults(ctx context.Context, results ko
|
||||
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
|
||||
|
@ -646,6 +646,11 @@ func TestDetailQueriesWithEmptyStrings(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
|
||||
ds.SaveHostAdditionalFunc = func(host *kolide.Host) error {
|
||||
gotHost.Additional = host.Additional
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify that results are ingested properly
|
||||
svc.SubmitDistributedQueryResults(ctx, results, map[string]kolide.OsqueryStatus{}, map[string]string{})
|
||||
|
||||
@ -816,6 +821,12 @@ func TestDetailQueries(t *testing.T) {
|
||||
gotHost = host
|
||||
return nil
|
||||
}
|
||||
|
||||
ds.SaveHostAdditionalFunc = func(host *kolide.Host) error {
|
||||
gotHost.Additional = host.Additional
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify that results are ingested properly
|
||||
svc.SubmitDistributedQueryResults(ctx, results, map[string]kolide.OsqueryStatus{}, map[string]string{})
|
||||
|
||||
@ -1176,12 +1187,12 @@ func TestNewDistributedQueryCampaign(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, gotQuery.ID, gotCampaign.QueryID)
|
||||
assert.Equal(t, []*kolide.DistributedQueryCampaignTarget{
|
||||
&kolide.DistributedQueryCampaignTarget{
|
||||
{
|
||||
Type: kolide.TargetHost,
|
||||
DistributedQueryCampaignID: campaign.ID,
|
||||
TargetID: 2,
|
||||
},
|
||||
&kolide.DistributedQueryCampaignTarget{
|
||||
{
|
||||
Type: kolide.TargetLabel,
|
||||
DistributedQueryCampaignID: campaign.ID,
|
||||
TargetID: 1,
|
||||
|
@ -1,5 +1,5 @@
|
||||
FROM golang:1.9-alpine
|
||||
MAINTAINER Fleet Developers <engineering@fleetdm.com>
|
||||
MAINTAINER Fleet Developers <hello@fleetdm.com>
|
||||
|
||||
ENV NPM_CONFIG_LOGLEVEL info
|
||||
ENV NODE_VERSION 8.7.0
|
||||
|
31
website/api/controllers/docs/view-basic-documentation.js
vendored
Normal file
31
website/api/controllers/docs/view-basic-documentation.js
vendored
Normal 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 {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
88
website/api/controllers/download-sitemap.js
vendored
Normal file
88
website/api/controllers/download-sitemap.js
vendored
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
};
|
31
website/api/controllers/handbook/view-basic-handbook.js
vendored
Normal file
31
website/api/controllers/handbook/view-basic-handbook.js
vendored
Normal 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 {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
35
website/api/controllers/view-query-detail.js
vendored
35
website/api/controllers/view-query-detail.js
vendored
@ -7,19 +7,38 @@ module.exports = {
|
||||
description: 'Display "Query detail" page.',
|
||||
|
||||
|
||||
exits: {
|
||||
|
||||
success: {
|
||||
viewTemplatePath: 'pages/query-detail'
|
||||
}
|
||||
|
||||
inputs: {
|
||||
slug: { type: 'string', required: true, description: 'A slug uniquely identifying this query in the library.', example: 'get-macos-disk-free-space-percentage' },
|
||||
},
|
||||
|
||||
|
||||
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.
|
||||
return {};
|
||||
return {
|
||||
query,
|
||||
queryLibraryYmlRepoPath: sails.config.builtStaticContent.queryLibraryYmlRepoPath
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
30
website/api/controllers/view-query-library.js
vendored
Normal file
30
website/api/controllers/view-query-library.js
vendored
Normal 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
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
17
website/api/helpers/compile-markdown-content.js
vendored
17
website/api/helpers/compile-markdown-content.js
vendored
@ -4,7 +4,17 @@ module.exports = {
|
||||
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.',
|
||||
// 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: {
|
||||
@ -40,7 +50,10 @@ module.exports = {
|
||||
|
||||
let path = require('path');
|
||||
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);
|
||||
|
||||
@ -179,7 +192,7 @@ module.exports = {
|
||||
fileInfo.path = fileInfo.fullPathAndFileName;// « for clarity (it's not technically the full path)
|
||||
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.data.lastModified;// « for clarity (this isn't the timestamp you're expecting, so we delete it)
|
||||
|
@ -1,7 +1,7 @@
|
||||
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.',
|
||||
@ -21,6 +21,7 @@ module.exports = {
|
||||
|
||||
|
||||
fn: function ({ text }) {
|
||||
// TODO: make this smarter about: "Fleet REST API" => "Fleet rEST aPI")
|
||||
return text
|
||||
.split(/[\s-_]+/)
|
||||
.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
54
website/api/responses/badConfig.js
vendored
Normal 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);
|
||||
|
||||
};
|
BIN
website/assets/images/lightbulb-blue-24x24@2x.png
vendored
Normal file
BIN
website/assets/images/lightbulb-blue-24x24@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 754 B |
25
website/assets/js/pages/docs/basic-documentation.page.js
vendored
Normal file
25
website/assets/js/pages/docs/basic-documentation.page.js
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
parasails.registerPage('basic-documentation', {
|
||||
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
|
||||
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
|
||||
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
|
||||
data: {
|
||||
//…
|
||||
},
|
||||
|
||||
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
|
||||
// ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
|
||||
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
|
||||
beforeMount: function() {
|
||||
//…
|
||||
},
|
||||
mounted: async function() {
|
||||
//…
|
||||
},
|
||||
|
||||
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
|
||||
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
|
||||
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
|
||||
methods: {
|
||||
//…
|
||||
}
|
||||
});
|
25
website/assets/js/pages/handbook/basic-handbook.page.js
vendored
Normal file
25
website/assets/js/pages/handbook/basic-handbook.page.js
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
parasails.registerPage('basic-handbook', {
|
||||
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
|
||||
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
|
||||
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
|
||||
data: {
|
||||
//…
|
||||
},
|
||||
|
||||
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
|
||||
// ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
|
||||
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
|
||||
beforeMount: function() {
|
||||
//…
|
||||
},
|
||||
mounted: async function() {
|
||||
//…
|
||||
},
|
||||
|
||||
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
|
||||
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
|
||||
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
|
||||
methods: {
|
||||
//…
|
||||
}
|
||||
});
|
2
website/assets/js/pages/query-detail.page.js
vendored
2
website/assets/js/pages/query-detail.page.js
vendored
@ -20,6 +20,6 @@ parasails.registerPage('query-detail', {
|
||||
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
|
||||
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
|
||||
methods: {
|
||||
//…
|
||||
|
||||
}
|
||||
});
|
||||
|
80
website/assets/js/pages/query-library.page.js
vendored
Normal file
80
website/assets/js/pages/query-library.page.js
vendored
Normal 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
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
});
|
3
website/assets/styles/importer.less
vendored
3
website/assets/styles/importer.less
vendored
@ -44,3 +44,6 @@
|
||||
@import 'pages/498.less';
|
||||
|
||||
@import 'pages/query-detail.less';
|
||||
@import 'pages/query-library.less';
|
||||
@import 'pages/docs/basic-documentation.less';
|
||||
@import 'pages/handbook/basic-handbook.less';
|
||||
|
5
website/assets/styles/layout.less
vendored
5
website/assets/styles/layout.less
vendored
@ -22,7 +22,7 @@ html, body {
|
||||
right: 0;
|
||||
.header-btn {
|
||||
color: #ffffff;
|
||||
cursor: pointer !important;//lesshint-disable-line importantRule
|
||||
cursor: unset;
|
||||
font-family: @navigation-font;
|
||||
font-weight: @bold;
|
||||
}
|
||||
@ -106,7 +106,7 @@ html, body {
|
||||
border-bottom: 1px solid #e2e4ea;
|
||||
.header-btn {
|
||||
color: #192147;
|
||||
cursor: pointer !important;//lesshint-disable-line importantRule
|
||||
cursor: unset;
|
||||
font-family: @navigation-font;
|
||||
font-weight: @bold;
|
||||
}
|
||||
@ -213,6 +213,7 @@ body.detected-mobile {
|
||||
// …
|
||||
}
|
||||
|
||||
|
||||
.dropdown:hover > .btn.btn-link {
|
||||
color: #6a67fe;
|
||||
}
|
||||
|
@ -4,8 +4,10 @@
|
||||
|
||||
@core-fleet-black: #192147;
|
||||
@core-vibrant-red: #FF5C83;
|
||||
@core-vibrant-blue: #6A67FE;
|
||||
|
||||
@brand: #14acc2;
|
||||
@ui-off-white: #F9FAFC;
|
||||
|
||||
@error: @core-vibrant-red;
|
||||
|
||||
|
13
website/assets/styles/pages/docs/basic-documentation.less
vendored
Normal file
13
website/assets/styles/pages/docs/basic-documentation.less
vendored
Normal 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.)
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
//…
|
||||
|
||||
}
|
13
website/assets/styles/pages/handbook/basic-handbook.less
vendored
Normal file
13
website/assets/styles/pages/handbook/basic-handbook.less
vendored
Normal 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.)
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
//…
|
||||
|
||||
}
|
32
website/assets/styles/pages/query-detail.less
vendored
32
website/assets/styles/pages/query-detail.less
vendored
@ -1,5 +1,35 @@
|
||||
#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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
79
website/assets/styles/pages/query-library.less
vendored
Normal file
79
website/assets/styles/pages/query-library.less
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
2
website/config/custom.js
vendored
2
website/config/custom.js
vendored
@ -22,7 +22,7 @@ module.exports.custom = {
|
||||
* > but it can also be used for user-uploaded images, webhooks, etc. *
|
||||
* *
|
||||
**************************************************************************/
|
||||
baseUrl: 'http://localhost:1337',
|
||||
baseUrl: 'http://localhost:2024',
|
||||
|
||||
/**************************************************************************
|
||||
* *
|
||||
|
3
website/config/env/development.js
vendored
3
website/config/env/development.js
vendored
@ -17,7 +17,8 @@ module.exports = {
|
||||
// Add any dev-only routes for local development of not-yet-released pages.
|
||||
// e.g. http://localhost:2024/sandbox/example-query
|
||||
routes: {
|
||||
'GET /sandbox/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
|
||||
},
|
||||
|
||||
};
|
||||
|
2
website/config/env/staging.js
vendored
2
website/config/env/staging.js
vendored
@ -46,7 +46,7 @@ module.exports = Object.assign({}, PRODUCTION_CONFIG, {
|
||||
// /\ Hard-code a staging-only override for allowed origins.
|
||||
// || (or set this array via JSON-encoded system env var)
|
||||
// ```
|
||||
// sails_sockets__onlyAllowOrigins='["http://localhost:1337", "…"]'
|
||||
// sails_sockets__onlyAllowOrigins='["https://staging.example.com"]'
|
||||
// ```
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
|
4
website/config/policies.js
vendored
4
website/config/policies.js
vendored
@ -24,5 +24,9 @@ module.exports.policies = {
|
||||
'legal/view-privacy': true,
|
||||
'deliver-contact-form-message': true,
|
||||
'view-query-detail': true,
|
||||
'view-query-library': true,
|
||||
'docs/*': true,
|
||||
'handbook/*': true,
|
||||
'download-sitemap': true,
|
||||
|
||||
};
|
||||
|
59
website/config/routes.js
vendored
59
website/config/routes.js
vendored
@ -14,43 +14,26 @@ module.exports.routes = {
|
||||
// ║║║║╣ ╠╩╗╠═╝╠═╣║ ╦║╣ ╚═╗
|
||||
// ╚╩╝╚═╝╚═╝╩ ╩ ╩╚═╝╚═╝╚═╝
|
||||
'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',
|
||||
|
||||
'/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 /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)
|
||||
'/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
|
||||
// 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' },
|
||||
|
||||
};
|
||||
|
11021
website/package-lock.json
generated
vendored
11021
website/package-lock.json
generated
vendored
File diff suppressed because it is too large
Load Diff
8
website/package.json
vendored
8
website/package.json
vendored
@ -8,7 +8,7 @@
|
||||
"@sailshq/connect-redis": "^3.2.1",
|
||||
"@sailshq/lodash": "^3.10.3",
|
||||
"@sailshq/socket.io-redis": "^5.2.0",
|
||||
"sails": "^1.2.5",
|
||||
"sails": "^1.4.3",
|
||||
"sails-hook-apianalytics": "^2.0.3",
|
||||
"sails-hook-organics": "^2.0.0",
|
||||
"sails-hook-orm": "^2.1.1",
|
||||
@ -17,16 +17,16 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"cheerio": "0.18.0",
|
||||
"doc-templater": "^0.4.0",
|
||||
"eslint": "5.16.0",
|
||||
"grunt": "1.0.4",
|
||||
"htmlhint": "0.11.0",
|
||||
"lesshint": "6.3.6",
|
||||
"sails-hook-grunt": "^4.0.0"
|
||||
"sails-hook-grunt": "^4.0.0",
|
||||
"yaml": "1.10.2"
|
||||
},
|
||||
"scripts": {
|
||||
"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.'",
|
||||
"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",
|
||||
|
44
website/scripts/build-from-markdown.js
vendored
44
website/scripts/build-from-markdown.js
vendored
@ -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
167
website/scripts/build-static-content.js
vendored
Normal 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,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
4
website/views/layouts/layout.ejs
vendored
4
website/views/layouts/layout.ejs
vendored
@ -47,7 +47,6 @@
|
||||
<link rel="stylesheet" href="/dependencies/bootstrap-4/bootstrap-4.css">
|
||||
<link rel="stylesheet" href="/dependencies/fontawesome.css">
|
||||
<link rel="stylesheet" href="/styles/importer.css">
|
||||
<link rel="stylesheet" href="/styles/mixins-and-variables/typography.css">
|
||||
<!--STYLES END-->
|
||||
</head>
|
||||
<body>
|
||||
@ -221,17 +220,20 @@
|
||||
<script src="/js/pages/account/edit-profile.page.js"></script>
|
||||
<script src="/js/pages/contact.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/forgot-password.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/signup.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/legal/privacy.page.js"></script>
|
||||
<script src="/js/pages/legal/terms.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-library.page.js"></script>
|
||||
<!--SCRIPTS END-->
|
||||
|
||||
<% /* Display an overlay if the current browser is not supported.
|
||||
|
11
website/views/pages/docs/basic-documentation.ejs
vendored
Normal file
11
website/views/pages/docs/basic-documentation.ejs
vendored
Normal 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() %>
|
11
website/views/pages/handbook/basic-handbook.ejs
vendored
Normal file
11
website/views/pages/handbook/basic-handbook.ejs
vendored
Normal 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() %>
|
46
website/views/pages/query-detail.ejs
vendored
46
website/views/pages/query-detail.ejs
vendored
@ -1,11 +1,49 @@
|
||||
<div id="query-detail" v-cloak>
|
||||
|
||||
<h1>TODO: Implement this page.</h1>
|
||||
<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="d-flex justify-content-center">
|
||||
<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>
|
||||
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>
|
||||
|
75
website/views/pages/query-library.ejs
vendored
Normal file
75
website/views/pages/query-library.ejs
vendored
Normal 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() %>
|
Loading…
Reference in New Issue
Block a user