Merge branch 'main' into feat-create-policies-from-fleet-apps

This commit is contained in:
Gabriel Hernandez 2024-11-22 11:58:00 +00:00
commit cab2ebb016
354 changed files with 9611 additions and 4062 deletions

View file

@ -19,9 +19,9 @@ It is [planned and ready](https://fleetdm.com/handbook/company/development-group
| I want to _________________________________________
| so that I can _________________________________________.
## Objective
## Key result
<!-- What quarterly objective does this story contribute to, if any? If it doesn't contribute to an objective, explain why it's being prioritized. -->
<!-- What quarterly key result (KR) does this story contribute to, if any? If it doesn't contribute to a KR, explain why it's being prioritized. -->
## Original requests

View file

@ -186,9 +186,112 @@ jobs:
name: ${{ matrix.suite }}-${{ env.MATRIX_MYSQL_ID }}-summary-test-log
path: /tmp/summary.txt
# Based on https://github.com/micromdm/nanomdm/blob/main/.github/workflows/on-push-pr.yml#L87
test-go-nanomdm:
runs-on: 'ubuntu-latest'
services:
mysql:
image: mysql:8.0.36
env:
MYSQL_RANDOM_ROOT_PASSWORD: yes
MYSQL_DATABASE: testdb
MYSQL_USER: testuser
MYSQL_PASSWORD: testpw
ports:
- 3800:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
env:
MYSQL_PWD: testpw
PORT: 3800
RACE_ENABLED: true
GO_TEST_TIMEOUT: 20m
steps:
- name: Harden Runner
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
with:
egress-policy: audit
- name: Checkout Code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Install Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version-file: 'go.mod'
- name: verify mysql
run: |
while ! mysqladmin ping --host=localhost --port=$PORT --protocol=TCP --silent; do
sleep 1
done
- name: mysql schema
run: |
mysql --version
mysql --user=testuser --host=localhost --port=$PORT --protocol=TCP testdb < ./server/mdm/nanomdm/storage/mysql/schema.sql
- name: set test dsn
run: echo "NANOMDM_MYSQL_STORAGE_TEST_DSN=testuser:testpw@tcp(localhost:$PORT)/testdb" >> $GITHUB_ENV
- name: Run Go tests
run: |
go test -v -parallel 8 -race=$RACE_ENABLED -timeout=$GO_TEST_TIMEOUT \
-coverprofile=coverage.txt -covermode=atomic -coverpkg=github.com/fleetdm/fleet/v4/server/mdm/nanomdm/... \
./server/mdm/nanomdm/storage/mysql 2>&1 | tee /tmp/gotest.log
- name: Save coverage
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
with:
name: nanomdm-coverage
path: ./coverage.txt
if-no-files-found: error
- name: Generate summary of errors
if: failure()
run: |
c1grep() { grep "$@" || test $? = 1; }
c1grep -oP 'FAIL: .*$' /tmp/gotest.log > /tmp/summary.txt
c1grep 'test timed out after' /tmp/gotest.log >> /tmp/summary.txt
c1grep 'fatal error:' /tmp/gotest.log >> /tmp/summary.txt
c1grep -A 10 'panic: runtime error: ' /tmp/gotest.log >> /tmp/summary.txt
c1grep ' FAIL\t' /tmp/gotest.log >> /tmp/summary.txt
GO_FAIL_SUMMARY=$(head -n 5 /tmp/summary.txt | sed ':a;N;$!ba;s/\n/\\n/g')
echo "GO_FAIL_SUMMARY=$GO_FAIL_SUMMARY"
if [[ -z "$GO_FAIL_SUMMARY" ]]; then
GO_FAIL_SUMMARY="unknown, please check the build URL"
fi
GO_FAIL_SUMMARY=$GO_FAIL_SUMMARY envsubst < .github/workflows/config/slack_payload_template.json > ./payload.json
- name: Slack Notification
if: github.event.schedule == '0 4 * * *' && failure()
uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0
with:
payload-file-path: ./payload.json
env:
JOB_STATUS: ${{ job.status }}
EVENT_URL: ${{ github.event.pull_request.html_url || github.event.head.html_url }}
RUN_URL: https://github.com/fleetdm/fleet/actions/runs/${{ github.run_id }}\n${{ github.event.pull_request.html_url || github.event.head.html_url }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_G_HELP_ENGINEERING_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: Upload test log
if: always()
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
with:
name: nanomdm-test-log
path: /tmp/gotest.log
if-no-files-found: error
- name: Upload summary test log
if: always()
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
with:
name: nanomdm-summary-test-log
path: /tmp/summary.txt
# We upload all backend coverage in one step so that we're less like to end up in a situation with a partial coverage report.
upload-coverage:
needs: [test-go]
needs: [test-go, test-go-nanomdm]
runs-on: ubuntu-latest
steps:
- name: Checkout Code

View file

@ -1,3 +1,9 @@
## Fleet 4.59.1 (Nov 18, 2024)
### Bug fixes
* Added `team_identifier` signature information to Apple macOS applications to the `/api/latest/fleet/hosts/:id/software` API endpoint.
## Fleet 4.59.0 (Nov 12, 2024)
### Endpoint operations

View file

@ -52,7 +52,7 @@ Upon failure of the selected policy, the selected software installation will be
* After configuring Fleet to auto-install a specific software the rest will be done automatically.
* The policy check mechanism runs on a typical 1 hour cadence on all online hosts.
* Fleet will send install requests to the hosts on the first policy failure (first "No" result for the host) or if a policy goes from "Yes" to "No". On this iteration it will not send a install request if a policy is already failing and continues to fail ("No" -> "No"). See the following flowchart for details.
* Fleet will send install requests to the hosts on the first policy failure (first "No" result for the host) or if a policy goes from "Yes" to "No". On this iteration it will not send an install request if a policy is already failing and continues to fail ("No" -> "No"). See the following flowchart for details.
![Flowchart](../website/assets/images/articles/automatic-software-install-workflow.png)
*Detailed flowchart*

View file

@ -29,6 +29,11 @@ Learn more about automatically installing software in a separate guide [here](ht
* Choose a file to upload. `.pkg`, `.msi`, `.exe`, `.rpm`, and `.deb` files are supported.
> Software installer uploads will fail if Fleet is unable to extract information from the installer package such as bundle ID and version number.
> - [.pkg extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/xar.go#:~:text=func%20ExtractXARMetadata)
> - [.msi extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/msi.go#:~:text=func%20ExtractMSIMetadata)
> - [.exe extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/pe.go#:~:text=func%20ExtractPEMetadata)
> - [.deb extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/deb.go#:~:text=func%20ExtractDebMetadata)
> - [.rpm extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/rpm.go#:~:text=func%20ExtractRPMMetadata)
* To allow users to install the software from Fleet Desktop, check the “Self-service” checkbox.

View file

@ -35,7 +35,7 @@ Learn more about customizing the [macOS Setup Assistant](https://fleetdm.com/doc
## More new features, improvements, and bug fixes
* Added support to add a EULA as part of the AEP/DEP unboxing flow.
* Added support to add an EULA as part of the AEP/DEP unboxing flow.
* DEP enrollments configured with SSO now pre-populate the username/fullname fields during account creation.
* Integrated the macOS setup assistant feature with Apple DEP so that the setup assistants are assigned to the enrolled devices.
* Re-assign and update the macOS setup assistants (and the default one) whenever required, such as when it is modified, when a host is transferred, a team is deleted, etc.

View file

@ -4,7 +4,9 @@ _Available in Fleet Premium_
In Fleet, you can install Fleet-maintained apps on macOS hosts without the need for manual uploads or extra configuration. This simplifies the process and adds another source of applications for your fleet.
Fleet starts with some of the most common and popular apps, enabling you to pull directly from this curated list and install them on your hosts without any additional configuration.
Fleet maintains these [celebrity apps](https://github.com/fleetdm/fleet/blob/main/server/mdm/maintainedapps/apps.json), enabling you to pull directly from this curated list and install them on your hosts without any additional configuration.
> Currently, these apps are only supported for Apple Silicon Macs: 1Password, Brave, Docker Desktop, Figma, Microsoft Visual Studio (VS) Code, Notion, Postman, Slack, and Zoom.
## Add a Fleet-maintained app

View file

@ -0,0 +1,42 @@
# Introducing Workbrew: bringing enterprise control to Homebrew deployments
![Fleet and Workbrew](../website/assets/images/articles/fleet-and-workbrew-1600x900@2x.png)
[Workbrew recently](https://workbrew.com/) made waves with its official launch, highlighted in a [TechCrunch article](https://techcrunch.com/2024/11/19/workbrew-makes-open-source-package-manager-homebrew-enterprise-friendly/). Backed by $5 million in funding from developer-focused VC firms like Heavybit and Operator Collective, Workbrew is tackling a critical challenge: transforming Homebrew from a developer-centric tool into a secure, enterprise-ready solution.
## Workbrews mission: From single-player to multiplayer
Homebrew has become an essential part of the developers toolkit by simplifying the installation and maintenance of software packages. However, as organizations grow, managing Homebrew installations across an entire fleet of devices introduces complexity, security risks, and compliance challenges.
Enter Workbrew. Their platform provides a centralized way for IT and security teams to manage and deploy Homebrew across their organizations. With features like:
- **Fleet-wide dashboards** to monitor devices, packages, and licenses.
- **MDM integrations (including Fleet!)** for automated synchronization of device data.
- **Vulnerability detection** and policy enforcement to enhance security.
- **Remote management** to install, upgrade, and remove any of the tens of thousands of packages in the `brew` ecosystem with ease.
Workbrew enables companies to maintain the agility developers love while ensuring security and compliance standards are met.
As noted in the article, Workbrew brings a customizable solution to organizations struggling with “[shadow IT](https://techcrunch.com/2015/09/25/its-time-to-embrace-not-fear-shadow-it/)” risks. By offering a fleet dashboard, vulnerability detection, and deep integrations with tools like [Fleet](https://fleetdm.com/device-management), Workbrew helps companies maintain visibility and control over Homebrew deployments at scale. Whether its ensuring compliance in regulated industries or automating package installations for remote teams, Workbrew is paving the way for safer, smarter IT management.
![Workbrew console](../website/assets/images/articles/workbrew-console-3412x2020px.png)
At Fleet, were excited to support Workbrews efforts. Our [integration](https://fleetdm.com/integrations) ensures that Workbrew users can easily sync device data, enabling seamless management across teams. Workbrews approach resonates with our belief in open-source and transparent tools for IT and security.
## Why this matters for IT teams
Managing Homebrew deployments used to be a manual process fraught with unknowns. IT professionals often found themselves asking:
- Whats actually installed on our devices?
- Are there unpatched vulnerabilities in our software?
- How do we enforce security policies without stifling developer productivity?
With Workbrew, these questions have answers. And for organizations already using Fleet, the integration creates a powerful synergy that brings even more value to your existing workflows. Together, Fleet and Workbrew give you the tools to confidently oversee and manage every device, app, and package across your organization. Its an essential step for any organization looking to balance developer flexibility with operational controls.
To learn more about how Workbrew and Fleet can work together, visit [Workbrews website](https://www.workbrew.com)
<meta name="authorGitHubUsername" value="drew-p-drawers">
<meta name="authorFullName" value="Drew Baker">
<meta name="publishedOn" value="2024-11-19">
<meta name="articleTitle" value="Introducing Workbrew: bringing enterprise control to Homebrew deployments">
<meta name="category" value="announcements">
<meta name="articleImageUrl" value="../website/assets/images/articles/fleet-and-workbrew-1600x900@2x.png">

View file

@ -18,7 +18,7 @@ where a host might have been lost or stolen, or to remotely prepare a device to
## Wipe a host
1. Navigate to the **Hosts** page by clicking the "Hosts" tab in the main navigation header. Find the device you want to lock. You can search by name, hostname, UUID, serial number, or private IP address in the search box in the upper right corner.
1. Navigate to the **Hosts** page by clicking the "Hosts" tab in the main navigation header. Find the device you want to wipe. You can search by name, hostname, UUID, serial number, or private IP address in the search box in the upper right corner.
2. Click the host to open the **Host Overview** page.
3. Click the **Actions** dropdown, then click **Wipe**.
4. Confirm that you want to wipe the device in the dialog. The host will now be marked with a "Wipe pending" badge. Once the wipe command is acknowledged by the host, the badge will update to "Wiped".
@ -29,12 +29,12 @@ where a host might have been lost or stolen, or to remotely prepare a device to
To unlock a locked host:
1. Navigate to the **Hosts** page by clicking the "Hosts" tab in the main navigation header. Find the device you want to lock. You can search by name, hostname, UUID, serial number, or private IP address in the search box in the upper right corner.
1. Navigate to the **Hosts** page by clicking the "Hosts" tab in the main navigation header. Find the device you want to unlock. You can search by name, hostname, UUID, serial number, or private IP address in the search box in the upper right corner.
2. Click the host to open the **Host Overview** page.
3. Click the **Actions** menu, then click **Unlock**.
- **macOS**: A dialog with the PIN will appear. Type the PIN into the device to unlock it.
- **Windows and Linux**: The command to unlock the host will be queued and the host will unlock once it receives the command (no PIN needed).
5. When you click **Unlock**, the host will be marked with an "Unlock pending" badge. Once the host is unlocked and checks back in with Fleet, the "Unlock pending" badge will be removed.
5. When you click **Unlock**, Windows and Linux hosts will be marked with an "Unlock pending" badge. Once the host is unlocked and checks back in with Fleet, the "Unlock pending" badge will be removed. macOS hosts do not have an "Unlock pending" badge as they cannot be remotely unlocked (the PIN has to be typed into the device).
## Lock and wipe using `fleetctl`

View file

@ -128,8 +128,7 @@ A common use case for SCEP is connecting devices to a corporate WiFi network. Th
1. Send the root CA certificate to the device using a [CertificateRoot profile](https://developer.apple.com/documentation/devicemanagement/certificateroot?language=objc).
2. Create a profile with a SCEP payload and a [WiFi payload](https://developer.apple.com/documentation/devicemanagement/wifi?language=objc), and send it to the device.
- The `PayloadCertificateUUID` in the WiFi payload should reference the `PayloadUUID` of the SCEP payload.
- For more information on connecting your Apple devices to 802.1X networks, see [this guide from Apple](https://support.apple.com/en-my/guide/deployment/depabc994b84/web).
## Assumptions and limitations
* NDES SCEP proxy is currently supported for macOS devices via Apple config profiles. Support for DDM (Declarative Device Management) is coming soon, as is support for iOS, iPadOS, Windows, and Linux.

View file

@ -43,7 +43,7 @@ I thought using Apples Automated Device Enrollment (or Device Enrollment Prog
Technically, I was not wrong, but there are non-technical challenges.
1. The requirements to establish a ADE account vary by country. In the US, for example, it requires a [DUNS](https://en.wikipedia.org/wiki/Data_Universal_Numbering_System) number. Getting a DUNS number is simple for US companies, but what is not easy is to fulfill similar requirements in every country where you would like to use ADE. We could not register for ADE in Canada. We have people in many other countries with a similar situation.
1. The requirements to establish an ADE account vary by country. In the US, for example, it requires a [DUNS](https://en.wikipedia.org/wiki/Data_Universal_Numbering_System) number. Getting a DUNS number is simple for US companies, but what is not easy is to fulfill similar requirements in every country where you would like to use ADE. We could not register for ADE in Canada. We have people in many other countries with a similar situation.
2. The delays for obtaining hardware are very long. When planning endpoint deployment strategies, we must consider this, as supply chain issues will not disappear soon.
3. The benchmarks made by the Center for Internet Security (CIS) are excellent but are incredibly long (700+ pages) and written for experts. We wanted to be transparent about why we configured company devices a certain way and explain it so everyone could understand without Googling for hours.

View file

@ -0,0 +1 @@
Added missing APM instrumentation for Fleet API routes.

View file

@ -0,0 +1 @@
* Improve performance of updating the `nano_enrollments.last_seen_at` timestamp of Apple MDM devices by an order of magnitude under load.

View file

@ -0,0 +1 @@
- fix responsive styles for the adm table

View file

@ -0,0 +1 @@
- Added UI features supporting disk encryption for Ubuntu and Fedora Linux.

View file

@ -0,0 +1 @@
Added activity item for fleetd enrollment with host serial and display name.

1
changes/23540-pe-sfx Normal file
View file

@ -0,0 +1 @@
Fixed name/version parsing issue with PE (EXE) installer self-extracting archives such as Opera.

View file

@ -0,0 +1,2 @@
- Fixes a bug where the name of the setup experience script was not showing up in the activity for
that script execution.

View file

@ -0,0 +1 @@
* Improved label validation when running live queries. Previously, when passing label(s) that do not exist, the labels were ignored. Now, an error is returned indicating which labels were not found. This change affects both the API and `fleetctl query` command.

View file

@ -0,0 +1 @@
* Fixed bug in `fleetdm/fleetctl` docker image where the `build` directory does not exist when generating deb/rpm packages.

View file

@ -0,0 +1 @@
Update nanomdm dependency with latest bug fixes and improvements.

1
changes/23942-wrong-link Normal file
View file

@ -0,0 +1 @@
- Updates a link in the Fleet-maintained apps UI to point to the correct place.

View file

@ -0,0 +1 @@
* doc: document firefox_preferences table for Linux and Windows platforms

View file

@ -1 +0,0 @@
* Added `team_identifier` signature information to Apple macOS applications to the `/api/latest/fleet/hosts/:id/software` API endpoint.

View file

@ -4,11 +4,11 @@ name: fleet
keywords:
- fleet
- osquery
version: v6.2.1
version: v6.2.2
home: https://github.com/fleetdm/fleet
sources:
- https://github.com/fleetdm/fleet.git
appVersion: v4.59.0
appVersion: v4.59.1
dependencies:
- name: mysql
condition: mysql.enabled

View file

@ -3,7 +3,7 @@
hostName: fleet.localhost
replicas: 3 # The number of Fleet instances to deploy
imageRepository: fleetdm/fleet
imageTag: v4.59.0 # Version of Fleet to deploy
imageTag: v4.59.1 # Version of Fleet to deploy
podAnnotations: {} # Additional annotations to add to the Fleet pod
serviceAccountAnnotations: {} # Additional annotations to add to the Fleet service account
resources:

View file

@ -64,6 +64,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/cobra"
"go.elastic.co/apm/module/apmhttp/v2"
_ "go.elastic.co/apm/module/apmsql/v2"
_ "go.elastic.co/apm/module/apmsql/v2/mysql"
"go.opentelemetry.io/otel"
@ -1252,7 +1253,12 @@ the way that the Fleet server works.
liveQueryRestPeriod += 10 * time.Second
// Create the handler based on whether tracing should be there
handler := launcher.Handler(rootMux)
var handler http.Handler
if config.Logging.TracingEnabled && config.Logging.TracingType == "elasticapm" {
handler = launcher.Handler(apmhttp.Wrap(rootMux))
} else {
handler = launcher.Handler(rootMux)
}
srv := config.Server.DefaultHTTPServer(ctx, handler)
if liveQueryRestPeriod > srv.WriteTimeout {

View file

@ -3781,7 +3781,9 @@ spec:
macos_settings:
enable_disk_encryption: true
`,
wantErr: `Couldn't edit enable_disk_encryption. Neither macOS MDM nor Windows is turned on`,
// Since Linux disk encryption does not use MDM, we allow enabling it even without MDM enabled and configured
wantOutput: `[+] applied fleet config`,
},
{
desc: "app config macos_settings.enable_disk_encryption false",

View file

@ -5,6 +5,7 @@ import (
"fmt"
"io"
"os"
"regexp"
"strings"
"time"
@ -138,6 +139,12 @@ func queryCommand() *cli.Command {
if strings.Contains(err.Error(), "no hosts targeted") {
return errors.New(fleet.NoHostsTargetedErrMsg)
}
if strings.Contains(err.Error(), fleet.InvalidLabelSpecifiedErrMsg) {
pattern := fmt.Sprintf("(%s.*)$", regexp.QuoteMeta(fleet.InvalidLabelSpecifiedErrMsg))
regex := regexp.MustCompile(pattern)
match := regex.FindString(err.Error())
return errors.New(match)
}
return err
}

View file

@ -219,8 +219,9 @@ func TestAdHocLiveQuery(t *testing.T) {
return []uint{1234}, nil
}
ds.LabelIDsByNameFunc = func(ctx context.Context, labels []string) (map[string]uint, error) {
return nil, nil
return map[string]uint{"label1": uint(1)}, nil
}
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return &fleet.AppConfig{}, nil
}
@ -299,6 +300,14 @@ func TestAdHocLiveQuery(t *testing.T) {
)
}()
// test label not found
_, err = runAppNoChecks([]string{"query", "--hosts", "1234", "--labels", "iamnotalabel", "--query", "select 42, * from time"})
assert.ErrorContains(t, err, "Invalid label name(s): iamnotalabel.")
// test if some labels were not found
_, err = runAppNoChecks([]string{"query", "--labels", "label1, mac, windows", "--hosts", "1234", "--query", "select 42, * from time"})
assert.ErrorContains(t, err, "Invalid label name(s): mac, windows.")
expected := `{"host":"somehostname","rows":[{"bing":"fds","host_display_name":"somehostname","host_hostname":"somehostname"}]}
`
assert.Equal(t, expected, runAppForTest(t, []string{"query", "--hosts", "1234", "--query", "select 42, * from time"}))

View file

@ -1413,6 +1413,7 @@ func (a *agent) orbitEnroll() error {
EnrollSecret: a.EnrollSecret,
HardwareUUID: a.UUID,
HardwareSerial: a.SerialNumber,
Hostname: a.CachedString("hostname"),
}
jsonBytes, err := json.Marshal(params)
if err != nil {

View file

@ -521,6 +521,23 @@ This activity contains the following fields:
}
```
## fleet_enrolled
Generated when a host is enrolled to Fleet (Fleet's agent fleetd is installed).
This activity contains the following fields:
- "host_serial": Serial number of the host.
- "host_display_name": Display name of the host.
#### Example
```json
{
"host_serial": "B04FL3ALPT21",
"host_display_name": "WIN-DESKTOP-JGS78KJ7C"
}
```
## mdm_enrolled
Generated when a host is enrolled in Fleet's MDM.

View file

@ -4,7 +4,7 @@
Fleet offers managed cloud hosting for [Fleet Premium](https://fleetdm.com/pricing) customers with large deployments.
> While organizations of all kinds use Fleet, from Fortune 500 companies to school districts to hobbyists, we are only currently able to provide cost-effective hosting for deployments larger than 1000 hosts. (Instead, you can [buy a license](https://fleetdm.com/customers/register) and self-host Fleet Premium with support.)
> While organizations of all kinds use Fleet, from Fortune 500 companies to school districts to hobbyists, today we are only currently able to provide fully-managed hosting for deployments larger than 300 hosts. (Instead, you can [buy a license](https://fleetdm.com/customers/register) and self-host Fleet Premium with support.)
Fleet is simple enough to [spin up for yourself](https://fleetdm.com/docs/deploy/introduction). Premium features are [available](https://fleetdm.com/pricing) either way.
@ -142,9 +142,9 @@ If you opt not to renew Fleet Premium, you can continue using only the free capa
We arent able to sell licenses and support separately.
## Do you offer pricing for ephemeral hosts which may scale up or down?
## Do you offer pricing for unmanaged hosts? What about ephemeral hosts which may scale up or down?
For now, the number of hosts is the maximum cap of distinct agents enrolled at any given time.
For now, the number of hosts is the maximum cap of hosts enrolled at any given time. Umanaged hosts ("Pending" MDM status in Fleet) are not included in the enrolled hosts count.
## When run locally, what resources does the Fleet app typically consume on an individual instance, and when run in HA, at high volume? And how is latency on an individual instance vs clustered deployment?

View file

@ -497,7 +497,7 @@ for pagination. For a comprehensive list of activity types and detailed informat
"host_display_name": "Marko's MacBook Pro",
"software_title": "Adobe Acrobat.app",
"script_execution_id": "eeeddb94-52d3-4071-8b18-7322cd382abb",
"status": "failed"
"status": "failed_install"
}
},
{
@ -2545,8 +2545,6 @@ the `software` table.
| os_settings_disk_encryption | string | query | Filters the hosts by the status of the disk encryption setting applied to the hosts. Valid options are 'verified', 'verifying', 'action_required', 'enforcing', 'failed', or 'removing_enforcement'. **Note: If this filter is used in Fleet Premium without a team ID filter, the results include only hosts that are not assigned to any team.** |
| populate_software | boolean | query | If `true`, the response will include a list of installed software for each host, including vulnerability data. (Note that software lists can be large, so this may cause significant CPU and RAM usage depending on page size and request concurrency.) |
| populate_policies | boolean | query | If `true`, the response will include policy data for each host. |
| populate_users | boolean | query | If `true`, the response will include user data for each host. |
| populate_labels | boolean | query | If `true`, the response will include labels for each host. |
> `software_id` is deprecated as of Fleet 4.42. It is maintained for backwards compatibility. Please use the `software_version_id` instead.
@ -2566,7 +2564,7 @@ If `after` is being used with `created_at` or `updated_at`, the table must be sp
#### Example
`GET /api/v1/fleet/hosts?page=0&per_page=100&order_key=hostname&query=2ce&populate_software=true&populate_policies=true&populate_users=true&populate_labels=true`
`GET /api/v1/fleet/hosts?page=0&per_page=100&order_key=hostname&query=2ce&populate_software=true&populate_policies=true`
##### Request query parameters
@ -2712,57 +2710,6 @@ If `after` is being used with `created_at` or `updated_at`, the table must be sp
"response": "fail",
"critical": false
}
],
"users": [
{
"uid": 0,
"username": "root",
"type": "",
"groupname": "root",
"shell": "/bin/bash"
},
{
"uid": 1,
"username": "bin",
"type": "",
"groupname": "bin",
"shell": "/sbin/nologin"
}
],
"labels": [
{
"created_at": "2021-08-19T02:02:17Z",
"updated_at": "2021-08-19T02:02:17Z",
"id": 6,
"name": "All Hosts",
"description": "All hosts which have enrolled in Fleet",
"query": "SELECT 1;",
"platform": "",
"label_type": "builtin",
"label_membership_type": "dynamic"
},
{
"created_at": "2021-08-19T02:02:17Z",
"updated_at": "2021-08-19T02:02:17Z",
"id": 9,
"name": "CentOS Linux",
"description": "All CentOS hosts",
"query": "SELECT 1 FROM os_version WHERE platform = 'centos' OR name LIKE '%centos%'",
"platform": "",
"label_type": "builtin",
"label_membership_type": "dynamic"
},
{
"created_at": "2021-08-19T02:02:17Z",
"updated_at": "2021-08-19T02:02:17Z",
"id": 12,
"name": "All Linux",
"description": "All Linux distributions",
"query": "SELECT 1 FROM osquery_info WHERE build_platform LIKE '%ubuntu%' OR build_distro LIKE '%centos%';",
"platform": "",
"label_type": "builtin",
"label_membership_type": "dynamic"
}
]
}
]
@ -3244,6 +3191,8 @@ Returns the information of the specified host.
> Note: `installed_paths` may be blank depending on installer package. For example, on Linux, RPM-installed packages do not provide installed path information.
> Note: `signature_information` is only set for macOS (.app) applications.
> Note:
> - `orbit_version: null` means this agent is not a fleetd agent
> - `fleet_desktop_version: null` means this agent is not a fleetd agent, or this agent is version <=1.23.0 which is not collecting the desktop version
@ -4359,13 +4308,20 @@ Resends a configuration profile for the specified host.
},
"app_store_app": null,
"source": "apps",
"status": "failed",
"status": "failed_install",
"installed_versions": [
{
"version": "121.0",
"bundle_identifier": "com.google.Chrome",
"last_opened_at": "2024-04-01T23:03:07Z",
"vulnerabilities": ["CVE-2023-1234","CVE-2023-4321","CVE-2023-7654"],
"installed_paths": ["/Applications/Google Chrome.app"]
"installed_paths": ["/Applications/Google Chrome.app"],
"signature_information": [
{
"installed_path": "/Applications/Google Chrome.app",
"team_identifier": "EQHXZ8M8AV"
}
]
}
]
},
@ -4405,9 +4361,16 @@ Resends a configuration profile for the specified host.
"installed_versions": [
{
"version": "118.0",
"bundle_identifier": "com.apple.logic10",
"last_opened_at": "2024-04-01T23:03:07Z",
"vulnerabilities": ["CVE-2023-1234"],
"installed_paths": ["/Applications/Logic Pro.app"]
"installed_paths": ["/Applications/Logic Pro.app"],
"signature_information": [
{
"installed_path": "/Applications/Logic Pro.app",
"team_identifier": ""
}
]
}
]
},
@ -4679,7 +4642,7 @@ To wipe a macOS, iOS, iPadOS, or Windows host, the host must have MDM turned on.
"host_display_name": "Markos MacBook Pro",
"software_title": "Adobe Acrobat.app",
"script_execution_id": "ecf22dba-07dc-40a9-b122-5480e948b756",
"status": "failed"
"status": "failed_uninstall"
}
},
{
@ -10066,7 +10029,7 @@ To get the results of an App Store app install, use the [List MDM commands](#lis
"software_package": "FalconSensor-6.44.pkg",
"host_id": 123,
"host_display_name": "Marko's MacBook Pro",
"status": "failed",
"status": "failed_install",
"output": "Installing software...\nError: The operation cant be completed because the item “Falcon” is in use.",
"pre_install_query_output": "Query returned result\nSuccess",
"post_install_script_output": "Running script...\nExit code: 1 (Failed)\nRolling back software install...\nSuccess"

View file

@ -166,3 +166,57 @@ func (svc *Service) GetFleetDesktopSummary(ctx context.Context) (fleet.DesktopSu
return sum, nil
}
func (svc *Service) TriggerLinuxDiskEncryptionEscrow(ctx context.Context, host *fleet.Host) error {
if svc.ds.IsHostPendingEscrow(ctx, host.ID) {
return nil
}
if err := svc.validateReadyForLinuxEscrow(ctx, host); err != nil {
_ = svc.ds.ReportEscrowError(ctx, host.ID, err.Error())
return err
}
return svc.ds.QueueEscrow(ctx, host.ID)
}
func (svc *Service) validateReadyForLinuxEscrow(ctx context.Context, host *fleet.Host) error {
if !host.IsLUKSSupported() {
return &fleet.BadRequestError{Message: "Fleet does not yet support creating LUKS disk encryption keys on this platform."}
}
ac, err := svc.ds.AppConfig(ctx)
if err != nil {
return err
}
if host.TeamID == nil {
if !ac.MDM.EnableDiskEncryption.Value {
return &fleet.BadRequestError{Message: "Disk encryption is not enabled for hosts not assigned to a team."}
}
} else {
tc, err := svc.ds.TeamMDMConfig(ctx, *host.TeamID)
if err != nil {
return err
}
if !tc.EnableDiskEncryption {
return &fleet.BadRequestError{Message: "Disk encryption is not enabled for this host's team."}
}
}
if host.DiskEncryptionEnabled == nil || !*host.DiskEncryptionEnabled {
return &fleet.BadRequestError{Message: "Host's disk is not encrypted. Please encrypt your disk first."}
}
// We have to pull Orbit info because the auth context doesn't fill in host.OrbitVersion
orbitInfo, err := svc.ds.GetHostOrbitInfo(ctx, host.ID)
if err != nil {
return err
}
if orbitInfo == nil || !fleet.IsAtLeastVersion(orbitInfo.Version, fleet.MinOrbitLUKSVersion) {
return &fleet.BadRequestError{Message: "Your version of fleetd does not support creating disk encryption keys on Linux. Please upgrade fleetd, then click Refetch, then try again."}
}
return svc.ds.AssertHasNoEncryptionKeyStored(ctx, host.ID)
}

View file

@ -1013,10 +1013,16 @@ func (svc *Service) GetMDMDiskEncryptionSummary(ctx context.Context, teamID *uin
windows = *w
}
linux, err := svc.ds.GetLinuxDiskEncryptionSummary(ctx, teamID)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "getting linux disk encryption summary")
}
return &fleet.MDMDiskEncryptionSummary{
Verified: fleet.MDMPlatformsCounts{
MacOS: macOS.Verified,
Windows: windows.Verified,
Linux: linux.Verified,
},
Verifying: fleet.MDMPlatformsCounts{
MacOS: macOS.Verifying,
@ -1025,6 +1031,7 @@ func (svc *Service) GetMDMDiskEncryptionSummary(ctx context.Context, teamID *uin
ActionRequired: fleet.MDMPlatformsCounts{
MacOS: macOS.ActionRequired,
Windows: windows.ActionRequired,
Linux: linux.ActionRequired,
},
Enforcing: fleet.MDMPlatformsCounts{
MacOS: macOS.Enforcing,
@ -1033,6 +1040,7 @@ func (svc *Service) GetMDMDiskEncryptionSummary(ctx context.Context, teamID *uin
Failed: fleet.MDMPlatformsCounts{
MacOS: macOS.Failed,
Windows: windows.Failed,
Linux: linux.Failed,
},
RemovingEnforcement: fleet.MDMPlatformsCounts{
MacOS: macOS.RemovingEnforcement,

View file

@ -45,6 +45,9 @@ func setupMockDatastorePremiumService(t testing.TB) (*mock.Store, *eeservice.Ser
AppleSCEPCertBytes: eeservice.TestCert,
AppleSCEPKeyBytes: eeservice.TestKey,
},
Server: config.ServerConfig{
PrivateKey: "foo",
},
}
depStorage := &nanodep_mock.Storage{}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

View file

@ -220,9 +220,10 @@ func (svc *Service) SetupExperienceNextStep(ctx context.Context, hostUUID string
return false, ctxerr.Errorf(ctx, "setup experience script missing content id: %d", *script.SetupExperienceScriptID)
}
req := &fleet.HostScriptRequestPayload{
HostID: host.ID,
ScriptName: script.Name,
ScriptContentID: *script.ScriptContentID,
HostID: host.ID,
ScriptName: script.Name,
ScriptContentID: *script.ScriptContentID,
SetupExperienceScriptID: script.SetupExperienceScriptID,
}
res, err := svc.ds.NewHostScriptExecutionRequest(ctx, req)
if err != nil {

View file

@ -1046,13 +1046,9 @@ func (svc *Service) createTeamFromSpec(
}
invalid := &fleet.InvalidArgumentError{}
if enableDiskEncryption && !appCfg.MDM.AtLeastOnePlatformEnabledAndConfigured() {
invalid.Append(
"mdm",
`Couldn't edit enable_disk_encryption. Neither macOS MDM nor Windows is turned on. Visit https://fleetdm.com/docs/using-fleet to learn how to turn on MDM.`,
)
if enableDiskEncryption && svc.config.Server.PrivateKey == "" {
return nil, ctxerr.New(ctx, "Missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key")
}
validateTeamCustomSettings(invalid, "macos", macOSSettings.CustomSettings)
validateTeamCustomSettings(invalid, "windows", spec.MDM.WindowsSettings.CustomSettings.Value)
@ -1210,11 +1206,10 @@ func (svc *Service) editTeamFromSpec(
team.Config.MDM.EnableDiskEncryption = *de
}
didUpdateDiskEncryption := team.Config.MDM.EnableDiskEncryption != oldEnableDiskEncryption
if !appCfg.MDM.AtLeastOnePlatformEnabledAndConfigured() && didUpdateDiskEncryption && team.Config.MDM.EnableDiskEncryption {
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("mdm",
`Couldn't edit enable_disk_encryption. Neither macOS MDM nor Windows is turned on. Visit https://fleetdm.com/docs/using-fleet to learn how to turn on MDM.`))
}
if didUpdateDiskEncryption && team.Config.MDM.EnableDiskEncryption && svc.config.Server.PrivateKey == "" {
return ctxerr.New(ctx, "Missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key")
}
if !team.Config.MDM.MacOSSetup.EnableReleaseDeviceManually.Valid {
team.Config.MDM.MacOSSetup.EnableReleaseDeviceManually = optjson.SetBool(false)
}
@ -1523,6 +1518,9 @@ func unmarshalWithGlobalDefaults(b *json.RawMessage) (fleet.Features, error) {
func (svc *Service) updateTeamMDMDiskEncryption(ctx context.Context, tm *fleet.Team, enable *bool) error {
var didUpdate, didUpdateMacOSDiskEncryption bool
if enable != nil {
if svc.config.Server.PrivateKey == "" {
return ctxerr.New(ctx, "Missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key")
}
if tm.Config.MDM.EnableDiskEncryption != *enable {
tm.Config.MDM.EnableDiskEncryption = *enable
didUpdate = true

View file

@ -15,10 +15,11 @@ export interface IInfoBannerProps {
/** default 4px */
borderRadius?: "large" | "xlarge";
pageLevel?: boolean;
/** cta and link are mutually exclusive */
/** Add this element to the end of the banner message. Mutually exclusive with `link`. */
cta?: JSX.Element;
/** closable and link are mutually exclusive */
closable?: boolean;
/** Makes the entire banner clickable */
link?: string;
icon?: IconNames;
}

View file

@ -26,7 +26,7 @@
margin-top: $pad-large;
font-size: $x-small;
max-height: 800px;
overflow-y: auto;
overflow: visible;
.input-field {
width: 100%;

View file

@ -7,24 +7,32 @@ interface ISectionHeaderProps {
title: string;
subTitle?: React.ReactNode;
details?: JSX.Element;
className?: string;
wrapperCustomClass?: string;
alignLeftHeaderVertically?: boolean;
greySubtitle?: boolean;
}
const SectionHeader = ({
title,
subTitle,
details,
className,
wrapperCustomClass,
alignLeftHeaderVertically,
greySubtitle,
}: ISectionHeaderProps) => {
const classNames = classnames(baseClass, className);
const wrapperClassnames = classnames(baseClass, wrapperCustomClass);
const leftHeaderClassnames = classnames(`${baseClass}__left-header`, {
[`${baseClass}__left-header--vertical`]: alignLeftHeaderVertically,
});
const subTitleClassnames = classnames(`${baseClass}__sub-title`, {
[`${baseClass}__sub-title--grey`]: greySubtitle,
});
return (
<div className={classNames}>
<div className={`${baseClass}__left-header`}>
<div className={wrapperClassnames}>
<div className={leftHeaderClassnames}>
<h2>{title}</h2>
{subTitle && (
<div className={`${baseClass}__sub-title`}>{subTitle}</div>
)}
{subTitle && <div className={subTitleClassnames}>{subTitle}</div>}
</div>
{details && <div className={`${baseClass}__right-header`}>{details}</div>}
</div>

View file

@ -7,6 +7,15 @@
display: flex;
align-items: center;
gap: $pad-small;
&--vertical {
flex-direction: column;
}
}
&__sub-title {
&--grey {
@include grey-text;
}
}
h2 {

View file

@ -113,10 +113,6 @@ const TeamsDropdown = ({
};
const customStyles: StylesConfig<INumberDropdownOption, false> = {
container: (provided) => ({
...provided,
width: "80px",
}),
control: (provided, state) => ({
...provided,
display: "flex",

View file

@ -225,6 +225,10 @@
animation: fade-in 150ms ease-out;
}
.Select-menu {
max-height: 190px;
}
.Select-noresults {
font-size: $x-small;
}

View file

@ -33,6 +33,7 @@ export enum ActivityType {
UserDeletedGlobalRole = "deleted_user_global_role",
UserChangedTeamRole = "changed_user_team_role",
UserDeletedTeamRole = "deleted_user_team_role",
FleetEnrolled = "fleet_enrolled",
MdmEnrolled = "mdm_enrolled",
MdmUnenrolled = "mdm_unenrolled",
EditedMacosMinVersion = "edited_macos_min_version",

View file

@ -1,5 +1,4 @@
import { IConfigServerSettings } from "./config";
import { ITeamSummary } from "./team";
export interface IMdmApple {
common_name: string;
@ -93,7 +92,7 @@ export interface IMdmSummaryResponse {
mobile_device_management_solution: IMdmSummaryMdmSolution[] | null;
}
export type ProfilePlatform = "darwin" | "windows" | "ios" | "ipados";
export type ProfilePlatform = "darwin" | "windows" | "ios" | "ipados" | "linux";
export interface IProfileLabel {
name: string;
@ -129,10 +128,11 @@ export interface IHostMdmProfile {
name: string;
operation_type: ProfileOperationType | null;
platform: ProfilePlatform;
status: MdmProfileStatus | MdmDDMProfileStatus;
status: MdmProfileStatus | MdmDDMProfileStatus | LinuxDiskEncryptionStatus;
detail: string;
}
// TODO - move disk encryption related types to dedicated file
export type DiskEncryptionStatus =
| "verified"
| "verifying"
@ -143,14 +143,14 @@ export type DiskEncryptionStatus =
/** Currently windows disk enxryption status will only be one of these four
values. In the future we may add more. */
export type IWindowsDiskEncryptionStatus = Extract<
export type WindowsDiskEncryptionStatus = Extract<
DiskEncryptionStatus,
"verified" | "verifying" | "enforcing" | "failed"
>;
export const isWindowsDiskEncryptionStatus = (
status: DiskEncryptionStatus
): status is IWindowsDiskEncryptionStatus => {
): status is WindowsDiskEncryptionStatus => {
switch (status) {
case "verified":
case "verifying":
@ -162,6 +162,16 @@ export const isWindowsDiskEncryptionStatus = (
}
};
export type LinuxDiskEncryptionStatus = Extract<
DiskEncryptionStatus,
"verified" | "failed" | "action_required"
>;
export const isLinuxDiskEncryptionStatus = (
status: DiskEncryptionStatus
): status is LinuxDiskEncryptionStatus =>
["verified", "failed", "action_required"].includes(status);
export const FLEET_FILEVAULT_PROFILE_DISPLAY_NAME = "Disk encryption";
export interface IMdmSSOReponse {

View file

@ -64,9 +64,9 @@ export const MACADMINS_EXTENSION_TABLES: Record<string, QueryablePlatform[]> = {
*/
export const HOST_LINUX_PLATFORMS = [
"linux",
"ubuntu",
"ubuntu", // covers Kubuntu
"debian",
"rhel",
"rhel", // covers Fedora
"centos",
"sles",
"kali",
@ -111,3 +111,55 @@ export const isAppleDevice = (platform: string) => {
export const isIPadOrIPhone = (platform: string | HostPlatform) =>
["ios", "ipados"].includes(platform);
export const DISK_ENCRYPTION_SUPPORTED_LINUX_PLATFORMS = [
"ubuntu", // covers Kubuntu
"rhel", // *included here to support Fedora systems. Necessary to cross-check with `os_versions` as well to confrim host is Fedora and not another, non-support rhel-like platform.
] as const;
export const isDiskEncryptionSupportedLinuxPlatform = (
platform: HostPlatform,
os_version: string
) => {
const isFedora =
platform === "rhel" && os_version.toLowerCase().includes("fedora");
return isFedora || platform === "ubuntu";
};
const DISK_ENCRYPTION_SUPPORTED_PLATFORMS = [
"darwin",
"windows",
"chrome",
...DISK_ENCRYPTION_SUPPORTED_LINUX_PLATFORMS,
] as const;
export type DiskEncryptionSupportedPlatform = typeof DISK_ENCRYPTION_SUPPORTED_PLATFORMS[number];
export const platformSupportsDiskEncryption = (
platform: HostPlatform,
/** os_version necessary to differentiate Fedora from other rhel-like platforms */
os_version?: string
) => {
if (platform === "rhel") {
return !!os_version && os_version.toLowerCase().includes("fedora");
}
return DISK_ENCRYPTION_SUPPORTED_PLATFORMS.includes(
platform as DiskEncryptionSupportedPlatform
);
};
const OS_SETTINGS_DISPLAY_PLATFORMS = [
...DISK_ENCRYPTION_SUPPORTED_PLATFORMS,
"ios",
"ipados",
];
export const isOsSettingsDisplayPlatform = (
platform: HostPlatform,
os_version: string
) => {
if (platform === "rhel") {
return !!os_version && os_version.toLowerCase().includes("fedora");
}
return OS_SETTINGS_DISPLAY_PLATFORMS.includes(platform);
};

View file

@ -279,6 +279,14 @@ const TAGGED_TEMPLATES = {
</>
);
},
fleetEnrolled: (activity: IActivity) => {
const hostDisplayName = activity.details?.host_display_name ? (
<b>{activity.details.host_display_name}</b>
) : (
"A host"
);
return <>{hostDisplayName} enrolled in Fleet.</>;
},
mdmEnrolled: (activity: IActivity) => {
if (activity.details?.mdm_platform === "microsoft") {
return (
@ -1167,6 +1175,9 @@ const getDetail = (
case ActivityType.UserDeletedTeamRole: {
return TAGGED_TEMPLATES.userDeletedTeamRole(activity);
}
case ActivityType.FleetEnrolled: {
return TAGGED_TEMPLATES.fleetEnrolled(activity);
}
case ActivityType.MdmEnrolled: {
return TAGGED_TEMPLATES.mdmEnrolled(activity);
}

View file

@ -9,7 +9,6 @@ import mdmAPI from "services/entities/mdm";
import OS_SETTINGS_NAV_ITEMS from "./OSSettingsNavItems";
import ProfileStatusAggregate from "./ProfileStatusAggregate";
import TurnOnMdmMessage from "../../../components/TurnOnMdmMessage";
const baseClass = "os-settings";
@ -29,7 +28,7 @@ const OSSettings = ({
params,
}: IOSSettingsProps) => {
const { section } = params;
const { config, currentTeam } = useContext(AppContext);
const { currentTeam } = useContext(AppContext);
// TODO: consider using useTeamIdParam hook here instead in the future
const teamId =
@ -51,14 +50,6 @@ const OSSettings = ({
}
);
// MDM is not on so show messaging for user to enable it.
if (
!config?.mdm.enabled_and_configured &&
!config?.mdm.windows_enabled_and_configured
) {
return <TurnOnMdmMessage router={router} />;
}
const DEFAULT_SETTINGS_SECTION = OS_SETTINGS_NAV_ITEMS[0];
const currentFormSection =

View file

@ -14,6 +14,7 @@ import CustomLink from "components/CustomLink";
import SectionHeader from "components/SectionHeader";
import Spinner from "components/Spinner";
import DataError from "components/DataError";
import TurnOnMdmMessage from "components/TurnOnMdmMessage";
import Pagination from "pages/ManageControlsPage/components/Pagination";
@ -46,7 +47,11 @@ const CustomSettings = ({
onMutation,
}: ICustomSettingsProps) => {
const { renderFlash } = useContext(NotificationContext);
const { isPremiumTier } = useContext(AppContext);
const { config, isPremiumTier } = useContext(AppContext);
const mdmEnabled =
config?.mdm.enabled_and_configured ||
config?.mdm.windows_enabled_and_configured;
const [showAddProfileModal, setShowAddProfileModal] = useState(false);
const [
@ -78,6 +83,7 @@ const CustomSettings = ({
per_page: PROFILES_PER_PAGE,
}),
{
enabled: mdmEnabled,
refetchOnWindowFocus: false,
}
);
@ -185,7 +191,14 @@ const CustomSettings = ({
url="https://fleetdm.com/learn-more-about/custom-os-settings"
/>
</p>
<>{renderProfileList()}</>
{!mdmEnabled ? (
<TurnOnMdmMessage
router={router}
info="MDM must be turned on to apply custom settings."
/>
) : (
renderProfileList()
)}
{showAddProfileModal && (
<AddProfileModal
currentTeamId={currentTeamId}

View file

@ -1,7 +1,8 @@
.custom-settings {
&__description {
font-size: $x-small;
margin: $pad-large 0;
margin-bottom: $pad-large;
@include grey-text;
}
&__pagination-controls {
@ -30,4 +31,16 @@
}
}
}
.turn-on-mdm-message {
border: px-to-rem(1);
border-color: $ui-fleet-black-10;
border-style: solid;
border-radius: px-to-rem(6);
padding-top: px-to-rem(64);
padding-bottom: px-to-rem(64);
width: 100%;
margin: px-to-rem(32) 0 0;
max-width: none;
}
}

View file

@ -5,7 +5,11 @@ import { InjectedRouter } from "react-router";
import { AppContext } from "context/app";
import { NotificationContext } from "context/notification";
import { ITeamConfig } from "interfaces/team";
import mdmAPI from "services/entities/mdm";
import { getErrorReason } from "interfaces/errors";
import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants";
import diskEncryptionAPI from "services/entities/disk_encryption";
import teamsAPI, { ILoadTeamResponse } from "services/entities/teams";
import configAPI from "services/entities/config";
@ -15,6 +19,7 @@ import Checkbox from "components/forms/fields/Checkbox";
import PremiumFeatureMessage from "components/PremiumFeatureMessage";
import Spinner from "components/Spinner";
import SectionHeader from "components/SectionHeader";
import TooltipWrapper from "components/TooltipWrapper";
import DiskEncryptionTable from "./components/DiskEncryptionTable";
@ -81,7 +86,10 @@ const DiskEncryption = ({
const onUpdateDiskEncryption = async () => {
try {
await mdmAPI.updateAppleMdmSettings(diskEncryptionEnabled, currentTeamId);
await diskEncryptionAPI.updateDiskEncryption(
diskEncryptionEnabled,
currentTeamId
);
renderFlash(
"success",
"Successfully updated disk encryption enforcement!"
@ -91,11 +99,24 @@ const DiskEncryption = ({
if (currentTeamId === 0) {
getUpdatedAppConfig();
}
} catch {
renderFlash(
"error",
"Could not update the disk encryption enforcement. Please try again."
);
} catch (e) {
if (getErrorReason(e).includes("Missing required private key")) {
const link =
"https://fleetdm.com/learn-more-about/fleet-server-private-key";
renderFlash(
"error",
<>
Could&apos;t enable disk encryption. Missing required private key.
Learn how to configure the private key here:{" "}
<a href={link}>{link}</a>
</>
);
} else {
renderFlash(
"error",
"Could not update the disk encryption enforcement. Please try again."
);
}
}
};
@ -103,18 +124,43 @@ const DiskEncryption = ({
setIsLoadingTeam(false);
}
const createDescriptionText = () => {
// table is showing disk encryption status.
if (showAggregate) {
return "If turned on, hosts' disk encryption keys will be stored in Fleet. ";
}
return `Also known as “FileVault” on macOS and “BitLocker” on Windows. If turned on, hosts' disk encryption keys will be stored in Fleet. `;
const getTipContent = (platform: "windows" | "macOS") => {
const [AppleOrWindows, DEMethod] =
platform === "windows"
? ["Windows", "BitLocker"]
: ["Apple", "FileVault"];
return (
<>
{AppleOrWindows} MDM must be turned on in{" "}
<a href="/settings/integrations/mdm">
<b>Settings</b> &gt; <b>Integrations</b> &gt;{" "}
<b>Mobile Device Management (MDM)</b>
</a>{" "}
to enforce disk encryption via {DEMethod}.
</>
);
};
const subTitle = (
<>
Disk encryption is available on{" "}
<TooltipWrapper tipContent={getTipContent("macOS")}>macOS</TooltipWrapper>
,{" "}
<TooltipWrapper tipContent={getTipContent("windows")}>
Windows
</TooltipWrapper>
, Ubuntu Linux, and Fedora Linux hosts.
</>
);
return (
<div className={baseClass}>
<SectionHeader title="Disk encryption" />
<SectionHeader
title="Disk encryption"
subTitle={subTitle}
alignLeftHeaderVertically
greySubtitle
/>
{!isPremiumTier ? (
<PremiumFeatureMessage
className={`${baseClass}__premium-feature-message`}
@ -139,10 +185,11 @@ const DiskEncryption = ({
Turn on disk encryption
</Checkbox>
<p>
{createDescriptionText()}
If turned on, hosts&apos; disk encryption keys will be stored in
Fleet{" "}
<CustomLink
text="Learn more"
url="https://fleetdm.com/docs/using-fleet/mdm-disk-encryption"
url={`${LEARN_MORE_ABOUT_BASE_LINK}/mdm-disk-encryption`}
newTab
/>
</p>

View file

@ -1,7 +1,7 @@
.disk-encryption {
&__premium-feature-message {
margin-top: 80px;
text-align: center;
.premium-feature-message-container {
justify-content: initial;
align-items: initial;
}
> p {
@ -15,4 +15,8 @@
.disk-encryption-content {
animation: fade-in 250ms ease-out;
}
.section-header__sub-title a {
font-size: inherit;
color: inherit;
}
}

View file

@ -7,7 +7,9 @@ import PATHS from "router/paths";
import { buildQueryStringFromParams } from "utilities/url";
import mdmAPI, { IDiskEncryptionSummaryResponse } from "services/entities/mdm";
import diskEncryptionAPI, {
IDiskEncryptionSummaryResponse,
} from "services/entities/disk_encryption";
import { HOSTS_QUERY_PARAMS } from "services/entities/hosts";
import TableContainer from "components/TableContainer";
@ -43,7 +45,7 @@ const DiskEncryptionTable = ({
error: diskEncryptionStatusError,
} = useQuery<IDiskEncryptionSummaryResponse, Error>(
["disk-encryption-summary", currentTeamId],
() => mdmAPI.getDiskEncryptionSummary(currentTeamId),
() => diskEncryptionAPI.getDiskEncryptionSummary(currentTeamId),
{
refetchOnWindowFocus: false,
retry: false,

View file

@ -4,7 +4,7 @@ import { DiskEncryptionStatus } from "interfaces/mdm";
import {
IDiskEncryptionStatusAggregate,
IDiskEncryptionSummaryResponse,
} from "services/entities/mdm";
} from "services/entities/disk_encryption";
import TextCell from "components/TableContainer/DataTable/TextCell";
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
@ -108,6 +108,21 @@ const defaultTableHeaders: IDataColumn[] = [
return <TextCell value={aggregateCount} />;
},
},
{
title: "Linux hosts",
Header: (cellProps: IHeaderProps) => (
<HeaderCell
value={cellProps.column.title}
isSortedDesc={cellProps.column.isSortedDesc}
disableSortBy
/>
),
disableSortBy: true,
accessor: "linuxHosts",
Cell: ({ cell: { value: aggregateCount } }: ICellProps) => {
return <TextCell value={aggregateCount} />;
},
},
{
title: "",
Header: "",
@ -200,6 +215,7 @@ export const generateTableData = (
status: STATUS_CELL_VALUES[status],
macosHosts: statusAggregate.macos,
windowsHosts: statusAggregate.windows,
linuxHosts: statusAggregate.linux,
teamId: currentTeamId,
});

View file

@ -136,7 +136,7 @@ const CurrentVersionSection = ({
<SectionHeader
title="Current versions"
subTitle={generateSubTitleText()}
className={`${baseClass}__header`}
wrapperCustomClass={`${baseClass}__header`}
/>
{renderTable()}
</div>

View file

@ -194,7 +194,10 @@ const TargetSection = ({
return (
<div className={baseClass}>
<SectionHeader title="Target" className={`${baseClass}__header`} />
<SectionHeader
title="Target"
wrapperCustomClass={`${baseClass}__header`}
/>
{renderTargetForms()}
</div>
);

View file

@ -27,7 +27,7 @@ const EmptyFleetAppsTable = () => (
Can&apos;t find app?{" "}
<CustomLink
newTab
url="https://github.com/fleetdm/fleet/issues/new/choose"
url="https://fleetdm.com/feature-request"
text="File an issue on GitHub"
/>
</>

View file

@ -173,13 +173,19 @@ const SoftwareOSTable = ({
router.push(path);
};
// Determines if a user should be able to filter the table
const hasData = data?.os_versions && data?.os_versions.length > 0;
const hasPlatformFilter = platform !== "all";
const showFilterHeaders = isSoftwareEnabled && (hasData || hasPlatformFilter);
const renderSoftwareCount = () => {
if (!data) return null;
return (
<>
<TableCount name="items" count={data?.count} />
{data?.os_versions && data?.counts_updated_at && (
{showFilterHeaders && data?.counts_updated_at && (
<LastUpdatedText
lastUpdatedAt={data.counts_updated_at}
customTooltipText={
@ -257,12 +263,10 @@ const SoftwareOSTable = ({
pageSize={perPage}
showMarkAllPages={false}
isAllPagesSelected={false}
customControl={renderPlatformDropdown}
customFiltersButton={() => <></>}
customControl={showFilterHeaders ? renderPlatformDropdown : undefined}
disableNextPage={!data?.meta.has_next_results}
searchable={false}
onQueryChange={onQueryChange}
stackControls
renderCount={renderSoftwareCount}
renderTableHelpText={renderTableHelpText}
disableMultiRowSelect

View file

@ -1,5 +1,16 @@
.apple-business-manager-table {
.data-table-block .data-table {
td.apple_id__cell {
max-width: 180px;
}
td.macos_team__cell, td.ios_team__cell, td.ipados_team__cell {
max-width: 150px;
}
}
// The desired behavior is to hide the header and team cell one by one
// as the viewport gets smaller. This is achieved by using the max-width
// media query with the breakpoint values taken from when the table content

View file

@ -21,7 +21,7 @@ const SettingsSection = ({
return (
<section className={classes}>
<SectionHeader title={title} className={`${baseClass}__title`} />
<SectionHeader title={title} wrapperCustomClass={`${baseClass}__title`} />
<>{children}</>
</section>
);

View file

@ -0,0 +1,57 @@
import Button from "components/buttons/Button";
import Modal from "components/Modal";
import React from "react";
const baseClass = "create-linux-key-modal";
interface ICreateLinuxKeyModal {
isTriggeringCreateLinuxKey: boolean;
onExit: () => void;
}
const CreateLinuxKeyModal = ({
isTriggeringCreateLinuxKey,
onExit,
}: ICreateLinuxKeyModal) => {
const renderModalBody = () => (
<>
<ol>
<li>
Wait 30 seconds for the <b>Enter disk encryption passphrase</b> pop-up
to open.
</li>
<li>
In the pop-up, enter the passphrase used to encrypt your device during
setup.
</li>
<li>
Close this window and select <b>Refetch</b> on your <b>My device</b>{" "}
page. This shares the new key with your organization.
</li>
</ol>
<div className="modal-cta-wrap">
<Button
type="submit"
variant="brand"
onClick={onExit}
className="save-loading"
>
Done
</Button>
</div>
</>
);
return (
<Modal
title="Create key"
onExit={onExit}
onEnter={onExit}
className={baseClass}
isLoading={isTriggeringCreateLinuxKey}
>
{renderModalBody()}
</Modal>
);
};
export default CreateLinuxKeyModal;

View file

@ -0,0 +1,8 @@
.create-linux-key-modal {
ol {
display: flex;
flex-direction: column;
gap: $pad-medium;
line-height: $medium;
}
}

View file

@ -0,0 +1 @@
export { default } from "./CreateLinuxKeyModal";

View file

@ -7,6 +7,7 @@ import { pick, findIndex } from "lodash";
import { NotificationContext } from "context/notification";
import deviceUserAPI from "services/entities/device_user";
import diskEncryptionAPI from "services/entities/disk_encryption";
import {
IDeviceMappingResponse,
IMacadminsResponse,
@ -46,6 +47,7 @@ import FleetIcon from "../../../../../assets/images/fleet-avatar-24x24@2x.png";
import PolicyDetailsModal from "../cards/Policies/HostPoliciesTable/PolicyDetailsModal";
import AutoEnrollMdmModal from "./AutoEnrollMdmModal";
import ManualEnrollMdmModal from "./ManualEnrollMdmModal";
import CreateLinuxKeyModal from "./CreateLinuxKeyModal";
import OSSettingsModal from "../OSSettingsModal";
import BootstrapPackageModal from "../HostDetailsPage/modals/BootstrapPackageModal";
import { parseHostSoftwareQueryParams } from "../cards/Software/HostSoftware";
@ -108,10 +110,14 @@ const DeviceUserPage = ({
const [showBootstrapPackageModal, setShowBootstrapPackageModal] = useState(
false
);
const [showCreateLinuxKeyModal, setShowCreateLinuxKeyModal] = useState(false);
const [globalConfig, setGlobalConfig] = useState<IDeviceGlobalConfig | null>(
null
);
const [hasSelfService, setSelfService] = useState(false);
const [isTriggeringCreateLinuxKey, setIsTriggeringCreateLinuxKey] = useState(
false
);
const { data: deviceMapping, refetch: refetchDeviceMapping } = useQuery(
["deviceMapping", deviceAuthToken],
@ -321,6 +327,22 @@ const DeviceUserPage = ({
);
};
const onTriggerEscrowLinuxKey = async () => {
setIsTriggeringCreateLinuxKey(true);
// modal opens in loading state
setShowCreateLinuxKeyModal(true);
try {
await diskEncryptionAPI.triggerLinuxDiskEncryptionKeyEscrow(
deviceAuthToken
);
} catch (e) {
renderFlash("error", "Failed to trigger key creation.");
setShowCreateLinuxKeyModal(false);
} finally {
setIsTriggeringCreateLinuxKey(false);
}
};
const renderDeviceUserPage = () => {
const failingPoliciesCount = host?.issues?.failing_policies_count || 0;
@ -353,22 +375,25 @@ const DeviceUserPage = ({
mdmEnabledAndConfigured={
!!globalConfig?.mdm.enabled_and_configured
}
mdmConnectedToFleet={!!host.mdm.connected_to_fleet}
diskEncryptionStatus={
connectedToFleetMdm={!!host.mdm.connected_to_fleet}
macDiskEncryptionStatus={
host.mdm.macos_settings?.disk_encryption ?? null
}
diskEncryptionActionRequired={
host.mdm.macos_settings?.action_required ?? null
}
onTurnOnMdm={toggleEnrollMdmModal}
onTriggerEscrowLinuxKey={onTriggerEscrowLinuxKey}
diskEncryptionOSSetting={host.mdm.os_settings?.disk_encryption}
diskIsEncrypted={host.disk_encryption_enabled}
diskEncryptionKeyAvailable={host.mdm.encryption_key_available}
/>
<HostSummaryCard
summaryData={summaryData}
bootstrapPackageData={bootstrapPackageData}
isPremiumTier={isPremiumTier}
toggleOSSettingsModal={toggleOSSettingsModal}
hostMdmProfiles={host?.mdm.profiles ?? []}
isConnectedToFleetMdm={host?.mdm.connected_to_fleet}
hostSettings={host?.mdm.profiles ?? []}
showRefetchSpinner={showRefetchSpinner}
onRefetchHost={onRefetchHost}
renderActionDropdown={renderActionButtons}
@ -474,6 +499,14 @@ const DeviceUserPage = ({
onClose={() => setShowBootstrapPackageModal(false)}
/>
)}
{showCreateLinuxKeyModal && !!host && (
<CreateLinuxKeyModal
isTriggeringCreateLinuxKey={isTriggeringCreateLinuxKey}
onExit={() => {
setShowCreateLinuxKeyModal(false);
}}
/>
)}
</div>
);
};

View file

@ -6,7 +6,8 @@ import DeviceUserBanners from "./DeviceUserBanners";
describe("Device User Banners", () => {
const turnOnMdmExpcetedText = /Mobile device management \(MDM\) is off\./;
const resetKeyDiskEncryptExpcetedText = /Disk encryption: Log out of your device or restart it to safeguard your data in case your device is lost or stolen\./;
const resetNonLinuxDiskEncryptKeyExpectedText = /Disk encryption: Log out of your device or restart it to safeguard your data in case your device is lost or stolen\./;
const createNewLinuxDiskEncryptKeyExpectedText = /Disk encryption: Create a new disk encryption key\. This lets your organization help you unlock your device if you forget your passphrase\./;
it("renders the turn on mdm banner correctly", () => {
render(
@ -14,52 +15,121 @@ describe("Device User Banners", () => {
hostPlatform="darwin"
mdmEnrollmentStatus="Off"
mdmEnabledAndConfigured
mdmConnectedToFleet
diskEncryptionStatus={null}
connectedToFleetMdm
macDiskEncryptionStatus={null}
diskEncryptionActionRequired={null}
onTurnOnMdm={noop}
onTriggerEscrowLinuxKey={noop}
/>
);
expect(screen.getByText(turnOnMdmExpcetedText)).toBeInTheDocument();
});
it("renders the reset key for disk encryption banner correctly", () => {
it("renders the reset key for non-linux disk encryption banner correctly", () => {
render(
<DeviceUserBanners
hostPlatform="darwin"
mdmEnrollmentStatus="On (automatic)"
mdmEnabledAndConfigured
mdmConnectedToFleet
diskEncryptionStatus="action_required"
connectedToFleetMdm
macDiskEncryptionStatus="action_required"
diskEncryptionActionRequired="rotate_key"
onTurnOnMdm={noop}
onTriggerEscrowLinuxKey={noop}
/>
);
expect(
screen.getByText(resetKeyDiskEncryptExpcetedText)
screen.getByText(resetNonLinuxDiskEncryptKeyExpectedText)
).toBeInTheDocument();
});
it("renders the create new linux disk encryption key banner correctly for Ubuntu", () => {
render(
<DeviceUserBanners
hostPlatform="ubuntu"
diskEncryptionOSSetting={{ status: "action_required", detail: "" }}
diskIsEncrypted
// explicit for testing purposes
diskEncryptionKeyAvailable={false}
mdmEnrollmentStatus="On (automatic)"
mdmEnabledAndConfigured
connectedToFleetMdm
macDiskEncryptionStatus={null}
diskEncryptionActionRequired={null}
onTurnOnMdm={noop}
onTriggerEscrowLinuxKey={noop}
/>
);
expect(
screen.getByText(createNewLinuxDiskEncryptKeyExpectedText)
).toBeInTheDocument();
});
it("renders the create new linux disk encryption key banner correctly for Fedora", () => {
render(
<DeviceUserBanners
hostPlatform="rhel"
hostOsVersion="somethingsomethingfedorasomething"
diskEncryptionOSSetting={{ status: "action_required", detail: "" }}
diskIsEncrypted
// explicit for testing purposes
diskEncryptionKeyAvailable={false}
mdmEnrollmentStatus="On (automatic)"
mdmEnabledAndConfigured
connectedToFleetMdm
macDiskEncryptionStatus={null}
diskEncryptionActionRequired={null}
onTurnOnMdm={noop}
onTriggerEscrowLinuxKey={noop}
/>
);
expect(
screen.getByText(createNewLinuxDiskEncryptKeyExpectedText)
).toBeInTheDocument();
});
it("renders no banner correctly", () => {
it("renders no banner correctly for a mac that is verifying its disk encryption", () => {
render(
<DeviceUserBanners
hostPlatform="darwin"
mdmEnrollmentStatus="On (manual)"
mdmEnabledAndConfigured
connectedToFleetMdm
macDiskEncryptionStatus="verifying"
diskEncryptionActionRequired={null}
onTurnOnMdm={noop}
onTriggerEscrowLinuxKey={noop}
diskEncryptionOSSetting={{ status: "verifying", detail: "" }}
/>
);
expect(screen.queryByText(turnOnMdmExpcetedText)).not.toBeInTheDocument();
expect(
screen.queryByText(resetNonLinuxDiskEncryptKeyExpectedText)
).not.toBeInTheDocument();
expect(
screen.queryByText(resetNonLinuxDiskEncryptKeyExpectedText)
).not.toBeInTheDocument();
});
it("renders no banner correctly for a mac without MDM set up", () => {
// setup so mdm is not enabled and configured.
render(
<DeviceUserBanners
hostPlatform="darwin"
mdmEnrollmentStatus={null}
mdmEnabledAndConfigured={false}
mdmConnectedToFleet={false}
diskEncryptionStatus={null}
connectedToFleetMdm={false}
macDiskEncryptionStatus={null}
diskEncryptionActionRequired={null}
onTurnOnMdm={noop}
onTriggerEscrowLinuxKey={noop}
/>
);
expect(screen.queryByText(turnOnMdmExpcetedText)).not.toBeInTheDocument();
expect(
screen.queryByText(resetKeyDiskEncryptExpcetedText)
screen.queryByText(resetNonLinuxDiskEncryptKeyExpectedText)
).not.toBeInTheDocument();
expect(
screen.queryByText(resetKeyDiskEncryptExpcetedText)
screen.queryByText(resetNonLinuxDiskEncryptKeyExpectedText)
).not.toBeInTheDocument();
});
});

View file

@ -2,42 +2,48 @@ import React from "react";
import InfoBanner from "components/InfoBanner";
import Button from "components/buttons/Button";
import { DiskEncryptionStatus, MdmEnrollmentStatus } from "interfaces/mdm";
import { MacDiskEncryptionActionRequired } from "interfaces/host";
import { IHostBannersBaseProps } from "pages/hosts/details/HostDetailsPage/components/HostDetailsBanners/HostDetailsBanners";
import CustomLink from "components/CustomLink";
import {
isDiskEncryptionSupportedLinuxPlatform,
platformSupportsDiskEncryption,
} from "interfaces/platform";
const baseClass = "device-user-banners";
interface IDeviceUserBannersProps {
hostPlatform: string;
mdmEnrollmentStatus: MdmEnrollmentStatus | null;
interface IDeviceUserBannersProps extends IHostBannersBaseProps {
mdmEnabledAndConfigured: boolean;
mdmConnectedToFleet: boolean;
diskEncryptionStatus: DiskEncryptionStatus | null;
diskEncryptionActionRequired: MacDiskEncryptionActionRequired | null;
onTurnOnMdm: () => void;
onTriggerEscrowLinuxKey: () => void;
}
const DeviceUserBanners = ({
hostPlatform,
hostOsVersion,
mdmEnrollmentStatus,
mdmEnabledAndConfigured,
mdmConnectedToFleet,
diskEncryptionStatus,
connectedToFleetMdm,
macDiskEncryptionStatus,
diskEncryptionActionRequired,
onTurnOnMdm,
diskEncryptionOSSetting,
diskIsEncrypted,
diskEncryptionKeyAvailable,
onTriggerEscrowLinuxKey,
}: IDeviceUserBannersProps) => {
const isMdmUnenrolled =
mdmEnrollmentStatus === "Off" || mdmEnrollmentStatus === null;
const diskEncryptionBannersEnabled =
mdmEnabledAndConfigured && mdmConnectedToFleet;
const mdmEnabledAndConnected = mdmEnabledAndConfigured && connectedToFleetMdm;
const showTurnOnMdmBanner =
const showTurnOnAppleMdmBanner =
hostPlatform === "darwin" && isMdmUnenrolled && mdmEnabledAndConfigured;
const showDiskEncryptionKeyResetRequired =
diskEncryptionBannersEnabled &&
diskEncryptionStatus === "action_required" &&
const showMacDiskEncryptionKeyResetRequired =
mdmEnabledAndConnected &&
macDiskEncryptionStatus === "action_required" &&
diskEncryptionActionRequired === "rotate_key";
const turnOnMdmButton = (
@ -47,7 +53,7 @@ const DeviceUserBanners = ({
);
const renderBanner = () => {
if (showTurnOnMdmBanner) {
if (showTurnOnAppleMdmBanner) {
return (
<InfoBanner color="yellow" cta={turnOnMdmButton}>
Mobile device management (MDM) is off. MDM allows your organization to
@ -58,7 +64,7 @@ const DeviceUserBanners = ({
);
}
if (showDiskEncryptionKeyResetRequired) {
if (showMacDiskEncryptionKeyResetRequired) {
return (
<InfoBanner color="yellow">
Disk encryption: Log out of your device or restart it to safeguard
@ -68,6 +74,60 @@ const DeviceUserBanners = ({
);
}
// setting applies to a supported Linux host
if (
hostPlatform &&
isDiskEncryptionSupportedLinuxPlatform(
hostPlatform,
hostOsVersion ?? ""
) &&
diskEncryptionOSSetting?.status
) {
// host not in compliance with setting
if (!diskIsEncrypted) {
// banner 1
return (
<InfoBanner
cta={
<CustomLink
url="https://fleetdm.com/learn-more-about/encrypt-linux-device"
text="Guide"
color="core-fleet-black"
iconColor="core-fleet-black"
/>
}
color="yellow"
>
Disk encryption: Follow the instructions in the guide to encrypt
your device. This lets your organization help you unlock your device
if you forget your password.
</InfoBanner>
);
}
// host disk is encrypted, so in compliance with the setting
if (!diskEncryptionKeyAvailable) {
// key is not escrowed: banner 3
return (
<InfoBanner
cta={
<Button
variant="unstyled"
onClick={onTriggerEscrowLinuxKey}
className="create-key-button"
>
Create key
</Button>
}
color="yellow"
>
Disk encryption: Create a new disk encryption key. This lets your
organization help you unlock your device if you forget your
passphrase.
</InfoBanner>
);
}
}
return null;
};

View file

@ -0,0 +1,6 @@
.device-user-banners {
.create-key-button {
color: $core-fleet-black;
font-weight: $bold;
}
}

View file

@ -93,7 +93,6 @@ import OSSettingsModal from "../OSSettingsModal";
import BootstrapPackageModal from "./modals/BootstrapPackageModal";
import ScriptModalGroup from "./modals/ScriptModalGroup";
import SelectQueryModal from "./modals/SelectQueryModal";
import { isSupportedPlatform } from "./modals/DiskEncryptionKeyModal/DiskEncryptionKeyModal";
import HostDetailsBanners from "./components/HostDetailsBanners";
import { IShowActivityDetailsData } from "../cards/Activity/Activity";
import LockModal from "./modals/LockModal";
@ -740,11 +739,6 @@ const HostDetailsPage = ({
}
};
// const hostDeviceStatusUIState = getHostDeviceStatusUIState(
// host.mdm.device_status,
// host.mdm.pending_action
// );
const renderActionDropdown = () => {
if (!host) {
return null;
@ -851,10 +845,13 @@ const HostDetailsPage = ({
<MainContent className={baseClass}>
<>
<HostDetailsBanners
hostMdmEnrollmentStatus={host?.mdm.enrollment_status}
mdmEnrollmentStatus={host?.mdm.enrollment_status}
hostPlatform={host?.platform}
diskEncryptionStatus={host?.mdm.macos_settings?.disk_encryption}
macDiskEncryptionStatus={host?.mdm.macos_settings?.disk_encryption}
connectedToFleetMdm={host?.mdm.connected_to_fleet}
diskEncryptionOSSetting={host?.mdm.os_settings?.disk_encryption}
diskIsEncrypted={host?.disk_encryption_enabled}
diskEncryptionKeyAvailable={host?.mdm.encryption_key_available}
/>
<div className={`${baseClass}__header-links`}>
<BackLink
@ -868,8 +865,7 @@ const HostDetailsPage = ({
isPremiumTier={isPremiumTier}
toggleOSSettingsModal={toggleOSSettingsModal}
toggleBootstrapPackageModal={toggleBootstrapPackageModal}
hostMdmProfiles={host?.mdm.profiles ?? []}
isConnectedToFleetMdm={host?.mdm?.connected_to_fleet}
hostSettings={host?.mdm.profiles ?? []}
showRefetchSpinner={showRefetchSpinner}
onRefetchHost={onRefetchHost}
renderActionDropdown={renderActionDropdown}
@ -1034,7 +1030,7 @@ const HostDetailsPage = ({
)}
{showOSSettingsModal && (
<OSSettingsModal
canResendProfiles
canResendProfiles={host.platform === "darwin"}
hostId={host.id}
platform={host.platform}
hostMDMData={host.mdm}
@ -1045,15 +1041,13 @@ const HostDetailsPage = ({
{showUnenrollMdmModal && !!host && (
<UnenrollMdmModal hostId={host.id} onClose={toggleUnenrollMdmModal} />
)}
{showDiskEncryptionModal &&
host &&
isSupportedPlatform(host.platform) && (
<DiskEncryptionKeyModal
platform={host.platform}
hostId={host.id}
onCancel={() => setShowDiskEncryptionModal(false)}
/>
)}
{showDiskEncryptionModal && host && (
<DiskEncryptionKeyModal
platform={host.platform}
hostId={host.id}
onCancel={() => setShowDiskEncryptionModal(false)}
/>
)}
{showBootstrapPackageModal &&
bootstrapPackageData.details &&
bootstrapPackageData.name && (

View file

@ -2,27 +2,43 @@ import React, { useContext } from "react";
import { AppContext } from "context/app";
import { DiskEncryptionStatus, MdmEnrollmentStatus } from "interfaces/mdm";
import { hasLicenseExpired, willExpireWithinXDays } from "utilities/helpers";
import { hasLicenseExpired } from "utilities/helpers";
import InfoBanner from "components/InfoBanner";
import { IOSSettings } from "interfaces/host";
import {
HostPlatform,
platformSupportsDiskEncryption,
} from "interfaces/platform";
const baseClass = "host-details-banners";
interface IHostDetailsBannersProps {
hostMdmEnrollmentStatus?: MdmEnrollmentStatus | null;
hostPlatform?: string;
diskEncryptionStatus: DiskEncryptionStatus | null | undefined;
export interface IHostBannersBaseProps {
macDiskEncryptionStatus: DiskEncryptionStatus | null | undefined;
mdmEnrollmentStatus: MdmEnrollmentStatus | null;
connectedToFleetMdm?: boolean;
hostPlatform?: HostPlatform;
// used to identify Fedora hosts, whose platform is "rhel"
hostOsVersion?: string;
/** Disk encryption setting status and detail, if any, that apply to this host (via a team or the "no team" team) */
diskEncryptionOSSetting?: IOSSettings["disk_encryption"];
/** Whether or not this host's disk is encrypted */
diskIsEncrypted?: boolean;
/** Whether or not Fleet has escrowed the host's disk encryption key */
diskEncryptionKeyAvailable?: boolean;
}
/**
* Handles the displaying of banners on the host details page
*/
const HostDetailsBanners = ({
hostMdmEnrollmentStatus,
mdmEnrollmentStatus,
hostPlatform,
hostOsVersion,
connectedToFleetMdm,
diskEncryptionStatus,
}: IHostDetailsBannersProps) => {
macDiskEncryptionStatus,
diskEncryptionOSSetting,
diskIsEncrypted,
diskEncryptionKeyAvailable,
}: IHostBannersBaseProps) => {
const {
config,
isPremiumTier,
@ -53,8 +69,7 @@ const HostDetailsBanners = ({
willVppExpire ||
isFleetLicenseExpired);
const isMdmUnenrolled =
hostMdmEnrollmentStatus === "Off" || !hostMdmEnrollmentStatus;
const isMdmUnenrolled = mdmEnrollmentStatus === "Off" || !mdmEnrollmentStatus;
const showTurnOnMdmInfoBanner =
!showingAppWideBanner &&
@ -62,31 +77,53 @@ const HostDetailsBanners = ({
isMdmUnenrolled &&
config?.mdm.enabled_and_configured;
const showDiskEncryptionUserActionRequired =
const showMacDiskEncryptionUserActionRequired =
!showingAppWideBanner &&
config?.mdm.enabled_and_configured &&
connectedToFleetMdm &&
diskEncryptionStatus === "action_required";
macDiskEncryptionStatus === "action_required";
if (showTurnOnMdmInfoBanner || showDiskEncryptionUserActionRequired) {
if (showTurnOnMdmInfoBanner) {
return (
<div className={baseClass}>
{showTurnOnMdmInfoBanner && (
<InfoBanner color="yellow">
To enforce settings, OS updates, disk encryption, and more, ask the
end user to follow the <strong>Turn on MDM</strong> instructions on
their <strong>My device</strong> page.
</InfoBanner>
)}
{showDiskEncryptionUserActionRequired && (
<InfoBanner color="yellow">
Disk encryption: Requires action from the end user. Ask the end user
to log out of their device or restart it.
</InfoBanner>
)}
<InfoBanner color="yellow">
To enforce settings, OS updates, disk encryption, and more, ask the
end user to follow the <strong>Turn on MDM</strong> instructions on
their <strong>My device</strong> page.
</InfoBanner>
</div>
);
}
if (showMacDiskEncryptionUserActionRequired) {
return (
<div className={baseClass}>
<InfoBanner color="yellow">
Disk encryption: Requires action from the end user. Ask the end user
to log out of their device or restart it.
</InfoBanner>
</div>
);
}
// setting applies
if (
hostPlatform &&
platformSupportsDiskEncryption(hostPlatform, hostOsVersion) &&
diskEncryptionOSSetting?.status
) {
// host either not in compliance with setting, or is but Fleet doesn't yet have a disk
// encryption key escrowed for the host (possible for Linux hosts)
if (!diskIsEncrypted || !diskEncryptionKeyAvailable) {
return (
<div className={baseClass}>
<InfoBanner color="yellow">
Disk encryption: Requires action from the end user. Ask the user to
follow <b>Disk encryption</b> instructions on their <b>My device</b>{" "}
page.
</InfoBanner>
</div>
);
}
}
return null;
};

View file

@ -8,26 +8,14 @@ import Modal from "components/Modal";
import Button from "components/buttons/Button";
import InputFieldHiddenContent from "components/forms/fields/InputFieldHiddenContent";
import DataError from "components/DataError";
import { QueryablePlatform } from "interfaces/platform";
import CustomLink from "components/CustomLink";
import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants";
import { HostPlatform } from "interfaces/platform";
const baseClass = "disk-encryption-key-modal";
// currently these are the only supported platforms for the disk encryption
// key modal.
export type ModalSupportedPlatform = Extract<
QueryablePlatform,
"darwin" | "windows"
>;
// Checks to see if the platform is supported by the modal.
export const isSupportedPlatform = (
platform: string
): platform is ModalSupportedPlatform => {
return ["darwin", "windows"].includes(platform);
};
interface IDiskEncryptionKeyModal {
platform: ModalSupportedPlatform;
platform: HostPlatform;
hostId: number;
onCancel: () => void;
}
@ -37,7 +25,7 @@ const DiskEncryptionKeyModal = ({
hostId,
onCancel,
}: IDiskEncryptionKeyModal) => {
const { data: encrpytionKey, error: encryptionKeyError } = useQuery<
const { data: encryptionKey, error: encryptionKeyError } = useQuery<
IHostEncrpytionKeyResponse,
unknown,
string
@ -49,14 +37,10 @@ const DiskEncryptionKeyModal = ({
select: (data) => data.encryption_key.key,
});
const isMacOS = platform === "darwin";
const descriptionText = isMacOS
? "The disk encryption key refers to the FileVault recovery key for macOS."
: "The disk encryption key refers to the BitLocker recovery key for Windows.";
const recoveryText = isMacOS
? "Use this key to log in to the host if you forgot the password."
: "Use this key to unlock the encrypted drive.";
const recoveryText =
platform === "darwin"
? "Use this key to log in to the host if you forgot the password."
: "Use this key to unlock the encrypted drive.";
return (
<Modal
@ -69,9 +53,15 @@ const DiskEncryptionKeyModal = ({
<DataError />
) : (
<>
<InputFieldHiddenContent value={encrpytionKey ?? ""} />
<p>{descriptionText}</p>
<p>{recoveryText} </p>
<InputFieldHiddenContent value={encryptionKey ?? ""} />
<p>
{recoveryText}{" "}
<CustomLink
newTab
url={`${LEARN_MORE_ABOUT_BASE_LINK}/mdm-disk-encryption`}
text="Learn more"
/>
</p>
<div className="modal-cta-wrap">
<Button onClick={onCancel}>Done</Button>
</div>

View file

@ -29,8 +29,7 @@ const OSSettingsModal = ({
onClose,
onProfileResent,
}: IOSSettingsModalProps) => {
// the caller should ensure that hostMDMData is not undefined and that platform is "windows" or
// "darwin", otherwise we will allow an empty modal will be rendered.
// the caller should ensure that hostMDMData is not undefined and that platform is supported otherwise we will allow an empty modal will be rendered.
// https://fleetdm.com/handbook/company/why-this-way#why-make-it-obvious-when-stuff-breaks
const memoizedTableData = useMemo(

View file

@ -4,7 +4,11 @@ import { uniqueId } from "lodash";
import Icon from "components/Icon";
import TextCell from "components/TableContainer/DataTable/TextCell";
import { ProfileOperationType } from "interfaces/mdm";
import {
LinuxDiskEncryptionStatus,
ProfileOperationType,
ProfilePlatform,
} from "interfaces/mdm";
import { COLORS } from "styles/var/colors";
import {
@ -14,6 +18,7 @@ import {
import TooltipContent from "./components/Tooltip/TooltipContent";
import {
isDiskEncryptionProfile,
LINUX_DISK_ENCRYPTION_DISPLAY_CONFIG,
PROFILE_DISPLAY_CONFIG,
ProfileDisplayOption,
WINDOWS_DISK_ENCRYPTION_DISPLAY_CONFIG,
@ -25,22 +30,27 @@ interface IOSSettingStatusCellProps {
status: OsSettingsTableStatusValue;
operationType: ProfileOperationType | null;
profileName: string;
hostPlatform?: ProfilePlatform;
}
const OSSettingStatusCell = ({
status,
operationType,
profileName = "",
hostPlatform,
}: IOSSettingStatusCellProps) => {
let displayOption: ProfileDisplayOption = null;
// windows hosts do not have an operation type at the moment and their display options are
// different than mac hosts.
if (!operationType && isMdmProfileStatus(status)) {
displayOption = WINDOWS_DISK_ENCRYPTION_DISPLAY_CONFIG[status];
if (hostPlatform === "linux") {
displayOption =
LINUX_DISK_ENCRYPTION_DISPLAY_CONFIG[status as LinuxDiskEncryptionStatus];
}
if (operationType) {
// windows hosts do not have an operation type at the moment and their display options are
// different than mac hosts.
else if (!operationType && isMdmProfileStatus(status)) {
displayOption = WINDOWS_DISK_ENCRYPTION_DISPLAY_CONFIG[status];
} else if (operationType) {
displayOption = PROFILE_DISPLAY_CONFIG[operationType]?.[status];
}

View file

@ -135,3 +135,27 @@ export const WINDOWS_DISK_ENCRYPTION_DISPLAY_CONFIG: WindowsDiskEncryptionDispla
tooltip: null,
},
};
type LinuxDiskEncryptionDisplayConfig = Omit<
OperationTypeOption,
"success" | "pending" | "acknowledged" | "verifying"
>;
export const LINUX_DISK_ENCRYPTION_DISPLAY_CONFIG: LinuxDiskEncryptionDisplayConfig = {
verified: {
statusText: "Verified",
iconName: "success",
tooltip: () =>
"The host turned disk encryption on and sent the key to Fleet. Fleet verified.",
},
failed: {
statusText: "Failed",
iconName: "error",
tooltip: null,
},
action_required: {
statusText: "Action required (pending)",
iconName: "pending-outline",
tooltip: TooltipInnerContentActionRequired as TooltipInnerContentFunc,
},
};

View file

@ -9,12 +9,17 @@ import {
IHostMdmProfile,
MdmDDMProfileStatus,
MdmProfileStatus,
isLinuxDiskEncryptionStatus,
isWindowsDiskEncryptionStatus,
} from "interfaces/mdm";
import { isDiskEncryptionSupportedLinuxPlatform } from "interfaces/platform";
import TooltipTruncatedTextCell from "components/TableContainer/DataTable/TooltipTruncatedTextCell";
import OSSettingStatusCell from "./OSSettingStatusCell";
import { generateWinDiskEncryptionProfile } from "../../helpers";
import {
generateLinuxDiskEncryptionSetting,
generateWinDiskEncryptionSetting,
} from "../../helpers";
import OSSettingsErrorCell from "./OSSettingsErrorCell";
export const isMdmProfileStatus = (
@ -69,6 +74,7 @@ const generateTableConfig = (
status={cellProps.row.original.status}
operationType={cellProps.row.original.operation_type}
profileName={cellProps.row.original.name}
hostPlatform={cellProps.row.original.platform}
/>
);
},
@ -101,7 +107,33 @@ const makeWindowsRows = ({ profiles, os_settings }: IHostMdmData) => {
isWindowsDiskEncryptionStatus(os_settings.disk_encryption.status)
) {
rows.push(
generateWinDiskEncryptionProfile(
generateWinDiskEncryptionSetting(
os_settings.disk_encryption.status,
os_settings.disk_encryption.detail
)
);
}
if (rows.length === 0 && !profiles) {
return null;
}
return rows;
};
const makeLinuxRows = ({ profiles, os_settings }: IHostMdmData) => {
const rows: IHostMdmProfileWithAddedStatus[] = [];
if (profiles) {
rows.push(...profiles);
}
if (
os_settings?.disk_encryption?.status &&
isLinuxDiskEncryptionStatus(os_settings.disk_encryption.status)
) {
rows.push(
generateLinuxDiskEncryptionSetting(
os_settings.disk_encryption.status,
os_settings.disk_encryption.detail
)
@ -145,6 +177,10 @@ export const generateTableData = (
return makeWindowsRows(hostMDMData);
case "darwin":
return makeDarwinRows(hostMDMData);
case "ubuntu":
return makeLinuxRows(hostMDMData);
case "rhel":
return makeLinuxRows(hostMDMData);
case "ios":
return hostMDMData.profiles;
case "ipados":

View file

@ -1,18 +1,22 @@
.os-settings-table {
// stylings for the table cells. This was the explicit width we want
// for these cells in the table. Total width of the table cell will be
// 240px including the padding.
.data-table-block .data-table tbody td {
.os-settings-name-cell {
width: 135px;
max-width: none;
.data-table-block .data-table {
&__wrapper {
width: initial;
}
.os-settings-status-cell {
width: 200px;
}
.os-settings-error-cell {
width: 237px;
tbody td {
.os-settings-name-cell {
width: 135px;
max-width: none;
}
.os-settings-status-cell {
width: 200px;
}
.os-settings-error-cell {
width: 237px;
}
}
}

View file

@ -6,9 +6,17 @@ import {
IHostMdmProfile,
BootstrapPackageStatus,
isWindowsDiskEncryptionStatus,
isLinuxDiskEncryptionStatus,
} from "interfaces/mdm";
import { IOSSettings, IHostMaintenanceWindow } from "interfaces/host";
import { IAppleDeviceUpdates } from "interfaces/config";
import {
DiskEncryptionSupportedPlatform,
isDiskEncryptionSupportedLinuxPlatform,
isOsSettingsDisplayPlatform,
platformSupportsDiskEncryption,
} from "interfaces/platform";
import getHostStatusTooltipText from "pages/hosts/helpers";
import TooltipWrapper from "components/TooltipWrapper";
@ -37,7 +45,8 @@ import BootstrapPackageIndicator from "./BootstrapPackageIndicator/BootstrapPack
import {
HostMdmDeviceStatusUIState,
generateWinDiskEncryptionProfile,
generateLinuxDiskEncryptionSetting,
generateWinDiskEncryptionSetting,
} from "../../helpers";
import { DEVICE_STATUS_TAGS, REFETCH_TOOLTIP_MESSAGES } from "./helpers";
@ -118,8 +127,7 @@ interface IHostSummaryProps {
isPremiumTier?: boolean;
toggleOSSettingsModal?: () => void;
toggleBootstrapPackageModal?: () => void;
hostMdmProfiles?: IHostMdmProfile[];
isConnectedToFleetMdm?: boolean;
hostSettings?: IHostMdmProfile[];
showRefetchSpinner: boolean;
onRefetchHost: (
evt: React.MouseEvent<HTMLButtonElement, React.MouseEvent>
@ -131,7 +139,7 @@ interface IHostSummaryProps {
hostMdmDeviceStatus?: HostMdmDeviceStatusUIState;
}
const MAC_WINDOWS_DISK_ENCRYPTION_MESSAGES = {
const DISK_ENCRYPTION_MESSAGES = {
darwin: {
enabled: (
<>
@ -155,20 +163,28 @@ const MAC_WINDOWS_DISK_ENCRYPTION_MESSAGES = {
),
disabled: "The disk is unencrypted.",
},
linux: {
enabled: "The disk is encrypted.",
unknown: "The disk may be encrypted.",
},
};
const getHostDiskEncryptionTooltipMessage = (
platform: "darwin" | "windows" | "chrome", // TODO: improve this type
platform: DiskEncryptionSupportedPlatform, // TODO: improve this type
diskEncryptionEnabled = false
) => {
if (platform === "chrome") {
return "Fleet does not check for disk encryption on Chromebooks, as they are encrypted by default.";
}
if (!["windows", "darwin"].includes(platform)) {
return "Disk encryption is enabled.";
if (platform === "rhel" || platform === "ubuntu") {
return DISK_ENCRYPTION_MESSAGES.linux[
diskEncryptionEnabled ? "enabled" : "unknown"
];
}
return MAC_WINDOWS_DISK_ENCRYPTION_MESSAGES[platform][
// mac or windows
return DISK_ENCRYPTION_MESSAGES[platform][
diskEncryptionEnabled ? "enabled" : "disabled"
];
};
@ -179,8 +195,7 @@ const HostSummary = ({
isPremiumTier,
toggleOSSettingsModal,
toggleBootstrapPackageModal,
hostMdmProfiles,
isConnectedToFleetMdm,
hostSettings,
showRefetchSpinner,
onRefetchHost,
renderActionDropdown,
@ -192,6 +207,7 @@ const HostSummary = ({
const {
status,
platform,
os_version,
disk_encryption_enabled: diskEncryptionEnabled,
} = summaryData;
@ -281,8 +297,7 @@ const HostSummary = ({
);
};
const renderDiskEncryptionSummary = () => {
// TODO: improve this typing, platforms!
if (!["darwin", "windows", "chrome"].includes(platform)) {
if (!platformSupportsDiskEncryption(platform, os_version)) {
return <></>;
}
const tooltipMessage = getHostDiskEncryptionTooltipMessage(
@ -301,6 +316,11 @@ const HostSummary = ({
case diskEncryptionEnabled === false:
statusText = "Off";
break;
case (diskEncryptionEnabled === null ||
diskEncryptionEnabled === undefined) &&
platformSupportsDiskEncryption(platform, os_version):
statusText = "Unknown";
break;
default:
// something unexpected happened on the way to this component, display whatever we got or
// "Unknown" to draw attention to the issue.
@ -441,21 +461,35 @@ const HostSummary = ({
};
const renderSummary = () => {
// for windows hosts we have to manually add a profile for disk encryption
// for windows and linux hosts we have to manually add a profile for disk encryption
// as this is not currently included in the `profiles` value from the API
// response for windows hosts.
// response for windows and linux hosts.
if (
platform === "windows" &&
osSettings?.disk_encryption?.status &&
isWindowsDiskEncryptionStatus(osSettings.disk_encryption.status)
) {
const winDiskEncryptionProfile: IHostMdmProfile = generateWinDiskEncryptionProfile(
const winDiskEncryptionSetting: IHostMdmProfile = generateWinDiskEncryptionSetting(
osSettings.disk_encryption.status,
osSettings.disk_encryption.detail
);
hostMdmProfiles = hostMdmProfiles
? [...hostMdmProfiles, winDiskEncryptionProfile]
: [winDiskEncryptionProfile];
hostSettings = hostSettings
? [...hostSettings, winDiskEncryptionSetting]
: [winDiskEncryptionSetting];
}
if (
isDiskEncryptionSupportedLinuxPlatform(platform, os_version) &&
osSettings?.disk_encryption?.status &&
isLinuxDiskEncryptionStatus(osSettings.disk_encryption.status)
) {
const linuxDiskEncryptionSetting: IHostMdmProfile = generateLinuxDiskEncryptionSetting(
osSettings.disk_encryption.status,
osSettings.disk_encryption.detail
);
hostSettings = hostSettings
? [...hostSettings, linuxDiskEncryptionSetting]
: [linuxDiskEncryptionSetting];
}
return (
@ -484,19 +518,15 @@ const HostSummary = ({
renderIssues()}
{isPremiumTier && renderHostTeam()}
{/* Rendering of OS Settings data */}
{(platform === "darwin" ||
platform === "windows" ||
platform === "ios" ||
platform === "ipados") &&
{isOsSettingsDisplayPlatform(platform, os_version) &&
isPremiumTier &&
isConnectedToFleetMdm && // show if 1 - host is enrolled in Fleet MDM, and
hostMdmProfiles &&
hostMdmProfiles.length > 0 && ( // 2 - host has at least one setting (profile) enforced
hostSettings &&
hostSettings.length > 0 && (
<DataSet
title="OS settings"
value={
<OSSettingsIndicator
profiles={hostMdmProfiles}
profiles={hostSettings}
onClick={toggleOSSettingsModal}
/>
}

View file

@ -47,7 +47,7 @@ const countHostProfilesByStatus = (
(acc, { status }) => {
if (status === "failed") {
acc.failed += 1;
} else if (status === "pending") {
} else if (status === "pending" || status === "action_required") {
acc.pending += 1;
} else if (status === "verifying") {
acc.verifying += 1;

View file

@ -2,12 +2,13 @@
import { HostMdmDeviceStatus, HostMdmPendingAction } from "interfaces/host";
import {
IHostMdmProfile,
IWindowsDiskEncryptionStatus,
WindowsDiskEncryptionStatus,
MdmProfileStatus,
LinuxDiskEncryptionStatus,
} from "interfaces/mdm";
const convertWinDiskEncryptionStatusToProfileStatus = (
diskEncryptionStatus: IWindowsDiskEncryptionStatus
const convertWinDiskEncryptionStatusToSettingStatus = (
diskEncryptionStatus: WindowsDiskEncryptionStatus
): MdmProfileStatus => {
return diskEncryptionStatus === "enforcing"
? "pending"
@ -15,20 +16,40 @@ const convertWinDiskEncryptionStatusToProfileStatus = (
};
/**
* Manually generates a profile for the windows disk encryption status. We need
* Manually generates a setting for the windows disk encryption status. We need
* this as we don't have a windows disk encryption profile in the `profiles`
* attribute coming back from the GET /hosts/:id API response.
*/
// eslint-disable-next-line import/prefer-default-export
export const generateWinDiskEncryptionProfile = (
diskEncryptionStatus: IWindowsDiskEncryptionStatus,
export const generateWinDiskEncryptionSetting = (
diskEncryptionStatus: WindowsDiskEncryptionStatus,
detail: string
): IHostMdmProfile => {
return {
profile_uuid: "0", // This s the only type of profile that can have this value
platform: "windows",
name: "Disk Encryption",
status: convertWinDiskEncryptionStatusToProfileStatus(diskEncryptionStatus),
status: convertWinDiskEncryptionStatusToSettingStatus(diskEncryptionStatus),
detail,
operation_type: null,
};
};
/**
* Manually generates a setting for the linux disk encryption status. We need
* this as we don't have a linux disk encryption setting in the `profiles`
* attribute coming back from the GET /hosts/:id API response.
*/
// eslint-disable-next-line import/prefer-default-export
export const generateLinuxDiskEncryptionSetting = (
diskEncryptionStatus: LinuxDiskEncryptionStatus,
detail: string
): IHostMdmProfile => {
return {
profile_uuid: "0", // This s the only type of profile that can have this value
platform: "linux",
name: "Disk Encryption",
status: diskEncryptionStatus,
detail,
operation_type: null,
};

View file

@ -0,0 +1,60 @@
import sendRequest from "services";
import endpoints from "utilities/endpoints";
import { buildQueryStringFromParams } from "utilities/url";
// TODO - move disk encryption types like this to dedicated file
import { DiskEncryptionStatus } from "interfaces/mdm";
import { APP_CONTEXT_NO_TEAM_ID } from "interfaces/team";
export interface IDiskEncryptionStatusAggregate {
macos: number;
windows: number;
linux: number;
}
export type IDiskEncryptionSummaryResponse = Record<
DiskEncryptionStatus,
IDiskEncryptionStatusAggregate
>;
const diskEncryptionService = {
getDiskEncryptionSummary: (teamId?: number) => {
let { MDM_DISK_ENCRYPTION_SUMMARY: path } = endpoints;
if (teamId) {
path = `${path}?${buildQueryStringFromParams({ team_id: teamId })}`;
}
return sendRequest("GET", path);
},
updateDiskEncryption: (enableDiskEncryption: boolean, teamId?: number) => {
// TODO - use same endpoint for both once issue with new endpoint for no team is resolved
const {
UPDATE_DISK_ENCRYPTION: teamsEndpoint,
CONFIG: noTeamsEndpoint,
} = endpoints;
if (teamId === 0) {
return sendRequest("PATCH", noTeamsEndpoint, {
mdm: {
enable_disk_encryption: enableDiskEncryption,
},
});
}
return sendRequest("POST", teamsEndpoint, {
enable_disk_encryption: enableDiskEncryption,
// TODO - it would be good to be able to use an API_CONTEXT_NO_TEAM_ID here, but that is
// currently set to 0, which should actually be undefined since the server expects teamId ==
// nil for no teams, not 0.
team_id: teamId === APP_CONTEXT_NO_TEAM_ID ? undefined : teamId,
});
},
triggerLinuxDiskEncryptionKeyEscrow: (token: string) => {
const { DEVICE_TRIGGER_LINUX_DISK_ENCRYPTION_KEY_ESCROW } = endpoints;
return sendRequest(
"POST",
DEVICE_TRIGGER_LINUX_DISK_ENCRYPTION_KEY_ESCROW(token)
);
},
};
export default diskEncryptionService;

View file

@ -1,5 +1,4 @@
import {
DiskEncryptionStatus,
IHostMdmProfile,
IMdmCommandResult,
IMdmProfile,
@ -21,16 +20,6 @@ export interface IEulaMetadataResponse {
export type ProfileStatusSummaryResponse = Record<MdmProfileStatus, number>;
export interface IDiskEncryptionStatusAggregate {
macos: number;
windows: number;
}
export type IDiskEncryptionSummaryResponse = Record<
DiskEncryptionStatus,
IDiskEncryptionStatusAggregate
>;
export interface IGetProfilesApiParams {
page?: number;
per_page?: number;
@ -188,37 +177,6 @@ const mdmService = {
return sendRequest("GET", path);
},
getDiskEncryptionSummary: (teamId?: number) => {
let { MDM_DISK_ENCRYPTION_SUMMARY: path } = endpoints;
if (teamId) {
path = `${path}?${buildQueryStringFromParams({ team_id: teamId })}`;
}
return sendRequest("GET", path);
},
// TODO: API INTEGRATION: change when API is implemented that works for windows
// disk encryption too.
updateAppleMdmSettings: (enableDiskEncryption: boolean, teamId?: number) => {
const {
MDM_UPDATE_APPLE_SETTINGS: teamsEndpoint,
CONFIG: noTeamsEndpoint,
} = endpoints;
if (teamId === 0) {
return sendRequest("PATCH", noTeamsEndpoint, {
mdm: {
// TODO: API INTEGRATION: remove macos_settings when API change is merged in.
macos_settings: { enable_disk_encryption: enableDiskEncryption },
// enable_disk_encryption: enableDiskEncryption,
},
});
}
return sendRequest("PATCH", teamsEndpoint, {
enable_disk_encryption: enableDiskEncryption,
team_id: teamId,
});
},
initiateMDMAppleSSO: () => {
const { MDM_APPLE_SSO } = endpoints;
return sendRequest("POST", MDM_APPLE_SSO, {});

View file

@ -34,6 +34,9 @@ export default {
DEVICE_USER_MDM_ENROLLMENT_PROFILE: (token: string): string => {
return `/${API_VERSION}/fleet/device/${token}/mdm/apple/manual_enrollment_profile`;
},
DEVICE_TRIGGER_LINUX_DISK_ENCRYPTION_KEY_ESCROW: (token: string): string => {
return `/${API_VERSION}/fleet/device/${token}/mdm/linux/trigger_escrow`;
},
// Host endpoints
HOST_SUMMARY: `/${API_VERSION}/fleet/host_summary`,
@ -138,6 +141,9 @@ export default {
ME: `/${API_VERSION}/fleet/me`,
// Disk encryption endpoints
UPDATE_DISK_ENCRYPTION: `/${API_VERSION}/fleet/disk_encryption`,
// Setup experiece endpoints
MDM_SETUP_EXPERIENCE: `/${API_VERSION}/fleet/setup_experience`,
MDM_SETUP_EXPERIENCE_SOFTWARE: `/${API_VERSION}/fleet/setup_experience/software`,

22
go.mod
View file

@ -45,7 +45,7 @@ require (
github.com/go-kit/kit v0.12.0
github.com/go-kit/log v0.2.1
github.com/go-ole/go-ole v1.2.6
github.com/go-sql-driver/mysql v1.7.1
github.com/go-sql-driver/mysql v1.8.1
github.com/gocarina/gocsv v0.0.0-20220310154401-d4df709ca055
github.com/golang-jwt/jwt/v4 v4.5.1
github.com/gomodule/oauth1 v0.2.0
@ -67,9 +67,10 @@ require (
github.com/jmoiron/sqlx v1.3.5
github.com/josephspurrier/goversioninfo v1.4.0
github.com/kevinburke/go-bindata v3.24.0+incompatible
github.com/klauspost/compress v1.17.9
github.com/kolide/launcher v1.0.12
github.com/lib/pq v1.10.9
github.com/macadmins/osquery-extension v1.2.1
github.com/macadmins/osquery-extension v1.2.3
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201213122252-bcd7e1b9601e
github.com/mattn/go-sqlite3 v1.14.22
github.com/micromdm/micromdm v1.9.0
@ -92,11 +93,12 @@ require (
github.com/quasilyte/go-ruleguard/dsl v0.3.22
github.com/rs/zerolog v1.32.0
github.com/russellhaering/goxmldsig v1.2.0
github.com/saferwall/pe v1.5.2
github.com/saferwall/pe v1.5.5
github.com/sassoftware/relic/v8 v8.0.1
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9
github.com/sethvargo/go-password v0.3.0
github.com/shirou/gopsutil/v3 v3.24.3
github.com/siderolabs/go-blockdevice/v2 v2.0.3
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/smallstep/pkcs7 v0.0.0-20240723090913-5e2c6a136dfa
github.com/smallstep/scep v0.0.0-20240214080410-892e41795b99
@ -111,9 +113,10 @@ require (
github.com/urfave/cli/v2 v2.23.5
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8
github.com/ziutek/mymysql v1.5.4
go.elastic.co/apm/module/apmgorilla/v2 v2.3.0
go.elastic.co/apm/module/apmsql/v2 v2.4.3
go.elastic.co/apm/v2 v2.4.3
go.elastic.co/apm/module/apmgorilla/v2 v2.6.2
go.elastic.co/apm/module/apmhttp/v2 v2.6.2
go.elastic.co/apm/module/apmsql/v2 v2.6.2
go.elastic.co/apm/v2 v2.6.2
go.etcd.io/bbolt v1.3.9
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.56.0
@ -152,6 +155,7 @@ require (
cloud.google.com/go/storage v1.39.1 // indirect
code.gitea.io/sdk/gitea v0.15.0 // indirect
dario.cat/mergo v1.0.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/AlekSi/pointer v1.2.0 // indirect
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
@ -181,6 +185,7 @@ require (
github.com/alecthomas/jsonschema v0.0.0-20211022214203-8b29eab41725 // indirect
github.com/antchfx/xpath v1.2.2 // indirect
github.com/apache/thrift v0.18.1 // indirect
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/atc0005/go-teams-notify/v2 v2.6.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.26.1 // indirect
@ -266,7 +271,6 @@ require (
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/kolide/kit v0.0.0-20221107170827-fb85e3d59eab // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
@ -275,6 +279,7 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mattn/go-tty v0.0.3 // indirect
github.com/micromdm/nanolib v0.2.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
@ -292,9 +297,11 @@ require (
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d // indirect
github.com/secure-systems-lab/go-securesystemslib v0.5.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/siderolabs/go-cmd v0.1.1 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
github.com/slack-go/slack v0.9.4 // indirect
github.com/spf13/afero v1.6.0 // indirect
@ -315,7 +322,6 @@ require (
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yashtewari/glob-intersection v0.1.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.elastic.co/apm/module/apmhttp/v2 v2.3.0 // indirect
go.elastic.co/fastjson v1.1.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect

68
go.sum
View file

@ -81,6 +81,8 @@ contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0Wk
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
fyne.io/systray v1.10.1-0.20240111184411-11c585fff98d h1:NjHwOOuOgGswUOPzDlsEDJOqKdjOjwL8Vi1mj9qx9+o=
fyne.io/systray v1.10.1-0.20240111184411-11c585fff98d/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
github.com/AbGuthrie/goquery/v2 v2.0.1 h1:h0tIhmeRroyqYjT9zxXPXOrheNp1xqNTV+XFWuDI+eA=
@ -233,6 +235,8 @@ github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloDxZfhMm0xrLXZS8+COSu2bXmEQs=
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
@ -422,11 +426,8 @@ github.com/e-dard/netbug v0.0.0-20151029172837-e64d308a0b20 h1:eDPsdileewX4H5a2J
github.com/e-dard/netbug v0.0.0-20151029172837-e64d308a0b20/go.mod h1:WXFUXJ0Y/SzNqXmhUU7VkE7a2Pag0zZnE2b6I87YWIs=
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
github.com/elastic/go-licenser v0.4.0/go.mod h1:V56wHMpmdURfibNBggaSBfqgPxyT1Tldns1i87iTEvU=
github.com/elastic/go-sysinfo v1.7.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0=
github.com/elastic/go-sysinfo v1.11.2 h1:mcm4OSYVMyws6+n2HIVMGkln5HOpo5Ie1ZmbbNn0jg4=
github.com/elastic/go-sysinfo v1.11.2/go.mod h1:GKqR8bbMK/1ITnez9NIsIfXQr25aLhRJa7AfT8HpBFQ=
github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0=
github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss=
github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
@ -460,6 +461,8 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897 h1:E52jfcE64UG42SwLmrW0QByONfGynWuzBvm86BoB9z8=
github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4=
github.com/freddierice/go-losetup/v2 v2.0.1 h1:wPDx/Elu9nDV8y/CvIbEDz5Xi5Zo80y4h7MKbi3XaAI=
github.com/freddierice/go-losetup/v2 v2.0.1/go.mod h1:TEyBrvlOelsPEhfWD5rutNXDmUszBXuFnwT1kIQF4J8=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
@ -523,8 +526,8 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO
github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.7.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@ -691,8 +694,6 @@ github.com/goreleaser/goreleaser v1.1.0 h1:YySqqYTX9kxRU0e/fGQrhivXk8/zD4iUOlL7h
github.com/goreleaser/goreleaser v1.1.0/go.mod h1:Xi4DvX/N7e2hXC5tJlXsKEb+XEo83tkSqcinWunNtjs=
github.com/goreleaser/nfpm/v2 v2.10.0 h1:SshT2D1MTzCifmjaagQA+5XW9Iq+qvXUavrgP0HvmWg=
github.com/goreleaser/nfpm/v2 v2.10.0/go.mod h1:Bj/ztLvdnBnEgMae0fl/bLF6By1+yFFKeL97WiS6ZJg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
@ -779,7 +780,6 @@ github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4
github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jcchavezs/porto v0.1.0/go.mod h1:fESH0gzDHiutHRdX2hv27ojnOVFco37hg1W6E9EZF4A=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
@ -817,8 +817,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kolide/kit v0.0.0-20221107170827-fb85e3d59eab h1:KVR7cs+oPyy85i+8t1ZaNSy1bymCy5FuWyt51pdrXu4=
github.com/kolide/kit v0.0.0-20221107170827-fb85e3d59eab/go.mod h1:OYYulo9tUqRadRLwB0+LE914sa1ui2yL7OrcU3Q/1XY=
github.com/kolide/launcher v1.0.12 h1:f2uT1kKYGIbj/WVsHDc10f7MIiwu8MpmgwaGaT7D09k=
@ -848,6 +848,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/macadmins/osquery-extension v1.2.1 h1:p7tAAhfEjUjoMQJNb+X7Qc3FraVqGZqMhZ1BYJbrlaw=
github.com/macadmins/osquery-extension v1.2.1/go.mod h1:q0BnBuYocHBRB+m3AQwdQNETH5a2KzVT3S8TKMHo9Lk=
github.com/macadmins/osquery-extension v1.2.3 h1:PAAQVRBnpOwnzEUROiJbrjDf9RPwcAfJrNAkXUcjS3Y=
github.com/macadmins/osquery-extension v1.2.3/go.mod h1:cNd/9INYpAYJFjfmAEJKgiuHgDkGuFMPu6GVrn7oups=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
@ -891,6 +893,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/micromdm/micromdm v1.9.0 h1:FAsIKOpnGcq21UQCrHCUxZwSW4NwBLGOoUtzbURxds8=
github.com/micromdm/micromdm v1.9.0/go.mod h1:YsAtsEvfEIwpjYTUPpWkJXSfH0hhp9mMHW1BgIZgRt8=
github.com/micromdm/nanolib v0.2.0 h1:g5GHQuUpS82WIAB15LyenjF/0/WSUNJMe5XZfCJSXq4=
github.com/micromdm/nanolib v0.2.0/go.mod h1:FwBKCvvphgYvbdUZ+qw5kay7NHJcg6zPi8W7kXNajmE=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
@ -1005,9 +1009,7 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
@ -1034,14 +1036,16 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/saferwall/pe v1.5.2 h1:h5lLtLsyxGHQ9dN6cd8EfeLEBEo5gdqJpkuw4o4vTMY=
github.com/saferwall/pe v1.5.2/go.mod h1:SNzv3cdgk8SBI0UwHfyTcdjawfdnN+nbydnEL7GZ25s=
github.com/saferwall/pe v1.5.5 h1:GGbzKjXDm7i+1K6riOgtgblyTdRmTbr3r11IzjovAK8=
github.com/saferwall/pe v1.5.5/go.mod h1:mJx+PuptmNpoPFBNhWs/uDMFL/kTHVZIkg0d4OUJFbQ=
github.com/sassoftware/relic/v8 v8.0.1 h1:uYUoaoTQMs67up8/46NgrSxSftgfY4VWBusDVg56k7I=
github.com/sassoftware/relic/v8 v8.0.1/go.mod h1:s/MwugRcovgYcNJNOyvLfqRHDX7iArHtFtUR9kEodz8=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d h1:RQqyEogx5J6wPdoxqL132b100j8KjcVHO1c0KLRoIhc=
github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d/go.mod h1:PegD7EVqlN88z7TpCqH92hHP+GBpfomGCCnw1PFtNOA=
github.com/secure-systems-lab/go-securesystemslib v0.5.0 h1:oTiNu0QnulMQgN/hLK124wJD/r2f9ZhIUuKIeBsCBT8=
github.com/secure-systems-lab/go-securesystemslib v0.5.0/go.mod h1:uoCqUC0Ap7jrBSEanxT+SdACYJTVplRXWLkGMuDjXqk=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@ -1057,6 +1061,12 @@ github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/siderolabs/gen v0.5.0 h1:Afdjx+zuZDf53eH5DB+E+T2JeCwBXGinV66A6osLgQI=
github.com/siderolabs/gen v0.5.0/go.mod h1:1GUMBNliW98Xeq8GPQeVMYqQE09LFItE8enR3wgMh3Q=
github.com/siderolabs/go-blockdevice/v2 v2.0.3 h1:IEgDqd3H3gPphahrdvfAzU8RmD4r5eQdWC+vgFQQoEg=
github.com/siderolabs/go-blockdevice/v2 v2.0.3/go.mod h1:74htzCV913UzaLZ4H+NBXkwWlYnBJIq5m/379ZEcu8w=
github.com/siderolabs/go-cmd v0.1.1 h1:nTouZUSxLeiiEe7hFexSVvaTsY/3O8k1s08BxPRrsps=
github.com/siderolabs/go-cmd v0.1.1/go.mod h1:6hY0JG34LxEEwYE8aH2iIHkHX/ir12VRLqfwAf2yJIY=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@ -1187,8 +1197,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
@ -1198,15 +1206,14 @@ github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.einride.tech/aip v0.66.0 h1:XfV+NQX6L7EOYK11yoHHFtndeaWh3KbD9/cN/6iWEt8=
go.einride.tech/aip v0.66.0/go.mod h1:qAhMsfT7plxBX+Oy7Huol6YUvZ0ZzdUz26yZsQwfl1M=
go.elastic.co/apm/module/apmgorilla/v2 v2.3.0 h1:jHw8N252UTwKTk945+Am8AaawhHC6DWpFVeTXQO8Gko=
go.elastic.co/apm/module/apmgorilla/v2 v2.3.0/go.mod h1:2LXDBbVhFf9rF65jZecvl78IZMuvSRldQ+9A/fjfIo0=
go.elastic.co/apm/module/apmhttp/v2 v2.3.0 h1:yGZyp26uJXUCfRTwvMmDt1d1jJrHgTBBncZfpYAxR8s=
go.elastic.co/apm/module/apmhttp/v2 v2.3.0/go.mod h1:JCszLIey4ndJGuUUu5FQjNOiTfaln1dqCqXnRcXVxVc=
go.elastic.co/apm/module/apmsql/v2 v2.4.3 h1:wFIibO4FLDNm6B5bQt4YAgt1ZS0X2Rd27HYXXaqVPOo=
go.elastic.co/apm/module/apmsql/v2 v2.4.3/go.mod h1:y8TG3VQepEkAZxMZfyPbb9s3J4B7SP9fWiVnwxmIrJg=
go.elastic.co/apm/v2 v2.3.0/go.mod h1:HdwVuAeoJMmoqAZZBNN2YVzj3UVLebtqoRCCydyCP+Q=
go.elastic.co/apm/v2 v2.4.3 h1:k6mj63O7IIyqqn3S52C2vBXvaSK9M5FHp0aZHpPH/as=
go.elastic.co/apm/v2 v2.4.3/go.mod h1:+CiBUdrrAGnGCL9TNx7tQz3BrfYV23L8Ljvotoc87so=
go.elastic.co/apm/module/apmgorilla/v2 v2.6.2 h1:/myBx0D/JiwTUjFkVFG3zXmDfGPfQjP/cg27qcBbdfU=
go.elastic.co/apm/module/apmgorilla/v2 v2.6.2/go.mod h1:uONZzSIh/cKjQ2rZmINR1VXVOJDq5eWOzKrCY+bu00w=
go.elastic.co/apm/module/apmhttp/v2 v2.6.2 h1:+aYtP1Lnrsm+XtEs87RWG2PAyU6LHDDnYnJl3Lth0Qk=
go.elastic.co/apm/module/apmhttp/v2 v2.6.2/go.mod h1:vlH+vXHaEijKK4pk605LOK+lbLDKwcByhlq4J24PeXw=
go.elastic.co/apm/module/apmsql/v2 v2.6.2 h1:wKCfsGhU9L1w0xM5hVMnukzTb35eIFU3L68gg0v55wU=
go.elastic.co/apm/module/apmsql/v2 v2.6.2/go.mod h1:W2tSac0SXRQwtj4DS+IJTb2oLWffW6fDHQmiw3GKAvk=
go.elastic.co/apm/v2 v2.6.2 h1:VBplAxgbOgTv+Giw/FS91xJpHYw/q8fz/XKPvqC+7/o=
go.elastic.co/apm/v2 v2.6.2/go.mod h1:33rOXgtHwbgZcDgi6I/GtCSMZQqgxkHC0IQT3gudKvo=
go.elastic.co/fastjson v1.1.0 h1:3MrGBWWVIxe/xvsbpghtkFoPciPhOCmjsR/HfwEeQR4=
go.elastic.co/fastjson v1.1.0/go.mod h1:boNGISWMjQsUPy/t6yqt2/1Wx4YNPSe+mZjlyw9vKKI=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@ -1333,7 +1340,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
@ -1387,9 +1393,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
@ -1462,7 +1466,6 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1507,14 +1510,10 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -1625,8 +1624,6 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
@ -1839,7 +1836,6 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=

View file

@ -62,7 +62,8 @@ We track competitors' capabilities and adjacent (or commonly integrated) product
| Ads | <sup><sub>_See [🫧 Demand team](https://fleetdm.com/handbook/demand#team)_</sup></sub>
| Video | <sup><sub>_See [🫧 Digital Marketing Manager](https://fleetdm.com/handbook/demand#team)_</sup></sub>
| Social media | <sup><sub>_See [🫧 Digital Marketing Manager](https://fleetdm.com/handbook/demand#team)_</sup></sub>
| Blog | <sup><sub>_See [🚀 Client Platform Engineer &amp; Community Advocate](https://fleetdm.com/handbook/engineering#team)_</sup></sub>
| Guides | <sup><sub>_See [🌦️ Customer Success &amp; VP of Customer Success](https://fleetdm.com/handbook/customer-success#team)_</sup></sub>
| Release article | <sup><sub>_See [🦢 Head of Product Design](https://fleetdm.com/handbook/product-design#team)_</sup></sub>
| Information technology (IT) | <sup><sub>_See [🚀 Client Platform Engineer &amp; Community Advocate](https://fleetdm.com/handbook/engineering#team)_</sup></sub>
| Payroll, bookkeeping, AR/AP | <sup><sub>_See [💸 Finance Engineer](https://fleetdm.com/handbook/finance#team)_</sup></sub>
| Legal contracts | <sup><sub>_See [🌐 Digital Experience team](https://fleetdm.com/handbook/digital-experience#team)_</sup></sub>
@ -573,7 +574,7 @@ We use `<meta>` tags in Markdown articles to set metadata information about the
- `announcements` - News and announcements about new features and changes to Fleet. Articles in this category are available at fleeetdm.com/announcements
- `guides` - Non-reference documentation and how-to guides. Articles in this category are available at fleetdm.com/guides
- `podcasts` - Episodes of Fleet's podcast. Articles in this category are available at fleetdm.com/podcasts
- `publishedOn`: A ISO 8601 formatted date (YYYY-MM-DD) of the articles publish date. If the article is a guide, this value should be updated whenever a change to the guide is made.
- `publishedOn`: An ISO 8601 formatted date (YYYY-MM-DD) of the articles publish date. If the article is a guide, this value should be updated whenever a change to the guide is made.
- Optional meta tags:
- `articleImageUrl`: A relative link to a cover image for the article. If provided, the image needs to live in the /website/assets/images/articles folder. The image will be added to the card for this article on it's category page, as well as a cover image on the article page. If this value is not provided, the card for the article will display the Fleet logo and the article will have no cover image.
- `description`: A description of the article that will be visible in search results and social share previews. If provided, this value will override the generated meta description for this article. otherwise, the description will default to `[articleTitle] by [authorFullName]`.

View file

@ -109,22 +109,44 @@ In this meeting, the department leader discusses actual week-over-week progress
- Use this meeting to add, remove, or change the definitions or ownership of KPIs. Otherwise, KPI definitions do not change, even if those definitions have problems. For help with KPIs, contact the [Digital Experience department](https://fleetdm.com/handbook/digital-experience#contact-us).
## Adding an advisor
## Hiring
First:
At Fleet, we collaborate with [core team members](#creating-a-new-position), [consultants](#hiring-a-consultant), [advisors](#adding-an-advisor), and [outside contributors](https://github.com/fleetdm/fleet/graphs/contributors) from the community.
Advisor agreements are sent through [DocuSign](https://www.docusign.com/), using the "Advisor Agreement" template.
- Update the ["Advisors" sheet](https://docs.google.com/spreadsheets/d/15knBE2-PrQ1Ad-QcIk0mxCN-xFsATKK9hcifqrm0qFQ/edit#gid=1803674483)
>**_Note:_** *Be sure to mark any columns that haven't been completed yet as "TODO"*
- Update the "Equity plan" sheet (which should have been automatically updated after updating "Advisors" thanks to the global unique IDs next to each row which are used to connect the spreadsheets) to reflect the default number of shares for advisor equity grants.
- Send the advisor agreement [through Docusign](https://apps.docusign.com/send/templates?view=shared&folder=0482b0fd-a752-41be-93a0-185e2fb7ef54) using the CEO's account, pulling the advisor's email address from a recent calendar event on the CEO's calendar.
- Complete the first step of signing, which involves filling in the number of shares.
- Then wait for the advisor to sign. (Fleet's CEO will sign after that.)
> Are you a new fleetie joining the Digital Experience team? For Loom recordings demonstrating how to make offers, hire, onboard, and more please see [this classified Google Doc](https://docs.google.com/document/d/1fimxQguPOtK-2YLAVjWRNCYqs5TszAHJslhtT_23Ly0/edit).
Then, to finalize a new advisor after signing is complete:
- Schedule quarterly recurring 1h meeting between the CEO and the advisor, with 30m of recurring prep scheduled back to back ahead of the meeting.
- Update the status columns in the ["Advisors" sheet](https://docs.google.com/spreadsheets/d/15knBE2-PrQ1Ad-QcIk0mxCN-xFsATKK9hcifqrm0qFQ/edit#gid=1803674483) to show that the agreement has been signed, and ask the new advisor to add us on [LinkedIn](https://www.linkedin.com/company/71111416), [Crunchbase](https://www.crunchbase.com/organization/fleet-device-management), and [Angellist](https://angel.co/company/fleetdm).
- Update "Equity plan" status columns to reflect updated status for this advisor, and to ensure the advisor's equity is queued up for the next quarterly equity grant ritual.
### Consultants
#### Hiring a consultant
## Hiring a consultant
In addition to [core team members](#hiring-a-new-team-member), from time to time Fleet hires consultants who may work for only a handful of hours on short projects.
A consultant is someone who we expect to either:
**Who ISN'T a consultant?**: If a consultant plans to work _more_ than 10 hours per week, or for _longer_ than 6 weeks, they should instead be hired as a [core team member](#hiring-a-new-team-member).
Core team members:
- are hired for an existing [open position](#creating-a-new-position)
- are hired using Fleet's "Hiring" issue template, including receiving a company-issued laptop and Yubikeys
- must be onboarded (complete the entire, unabridged onboarding process in Fleet's "Onboarding" issue template)
- must be offboarded
- get an email address
- have a manager and a formal place in the company [org chart](https://fleetdm.com/handbook/company#org-chart)
- are listed in ["🧑‍🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0) (private Google Doc)
- are paid as part of the standard payroll ritual for the place they work and their employment classification.
Consultants aren't required to do any of those things.
**Who IS a consultant?**: A consultant is someone who we expect to either:
- complete their relationship with the company in less than 6 weeks
- or have a longer-term relationship with the company, but never work more than 10 hours per week.
@ -141,24 +163,7 @@ Consultants [track time using the company's tools](#tracking-hours) and sign [Fl
To hire a consultant, [submit a new consultant onboarding request](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-digital-experience&projects=&template=new-consultant-onboarding.md&title=New+US%2Finternational+consultant) to the Digital Experience team.
#### Who ISN'T a consultant?
If a consultant plans to work _more_ than 10 hours per week, or for _longer_ than 6 weeks, they should instead be hired as a [core team member](#hiring-a-new-team-member).
Core team members:
- are hired for an existing [open position](#creating-a-new-position)
- are hired using Fleet's "Hiring" issue template, including receiving a company-issued laptop and Yubikeys
- must be onboarded (complete the entire, unabridged onboarding process in Fleet's "Onboarding" issue template)
- must be offboarded
- get an email address
- have a manager and a formal place in the company [org chart](https://fleetdm.com/handbook/company#org-chart)
- are listed in ["🧑‍🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0) (private Google Doc)
- are paid as part of the standard payroll ritual for the place they work and their employment classification.
Consultants aren't required to do any of those things.
#### Sending a consulting agreement
### Sending a consulting agreement
To send a consulting agreement, you will need to [submit a new consultant onboarding request](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-digital-experience&projects=&template=new-consultant-onboarding.md&title=New+US%2Finternational+consultant) to the Digital Experience team. They will then peform the steps needed to bring aboard a new consultant.
@ -180,25 +185,36 @@ If the consultant is international, you will also provide:
<img width="384" alt="image" src="https://github.com/fleetdm/fleet/assets/618009/e9d36657-fab9-44c1-9162-7bc640e2f3a0">
### Adding an advisor
## Hiring
First:
At Fleet, we collaborate with [core team members](#creating-a-new-position), [consultants](#hiring-a-consultant), [advisors](#adding-an-advisor), and [outside contributors](https://github.com/fleetdm/fleet/graphs/contributors) from the community.
Advisor agreements are sent through [DocuSign](https://www.docusign.com/), using the "Advisor Agreement" template.
- Update the ["Advisors" sheet](https://docs.google.com/spreadsheets/d/15knBE2-PrQ1Ad-QcIk0mxCN-xFsATKK9hcifqrm0qFQ/edit#gid=1803674483)
>**_Note:_** *Be sure to mark any columns that haven't been completed yet as "TODO"*
- Update the "Equity plan" sheet (which should have been automatically updated after updating "Advisors" thanks to the global unique IDs next to each row which are used to connect the spreadsheets) to reflect the default number of shares for advisor equity grants.
- Send the advisor agreement [through Docusign](https://apps.docusign.com/send/templates?view=shared&folder=0482b0fd-a752-41be-93a0-185e2fb7ef54) using the CEO's account, pulling the advisor's email address from a recent calendar event on the CEO's calendar.
- Complete the first step of signing, which involves filling in the number of shares.
- Then wait for the advisor to sign. (Fleet's CEO will sign after that.)
Then, to finalize a new advisor after signing is complete:
- Schedule quarterly recurring 1h meeting between the CEO and the advisor, with 30m of recurring prep scheduled back to back ahead of the meeting.
- Update the status columns in the ["Advisors" sheet](https://docs.google.com/spreadsheets/d/15knBE2-PrQ1Ad-QcIk0mxCN-xFsATKK9hcifqrm0qFQ/edit#gid=1803674483) to show that the agreement has been signed, and ask the new advisor to add us on [LinkedIn](https://www.linkedin.com/company/71111416), [Crunchbase](https://www.crunchbase.com/organization/fleet-device-management), and [Angellist](https://angel.co/company/fleetdm).
- Update "Equity plan" status columns to reflect updated status for this advisor, and to ensure the advisor's equity is queued up for the next quarterly equity grant ritual.
> Are you a new fleetie joining the Digital Experience team? For Loom recordings demonstrating how to make offers, hire, onboard, and more please see [this classified Google Doc](https://docs.google.com/document/d/1fimxQguPOtK-2YLAVjWRNCYqs5TszAHJslhtT_23Ly0/edit).
### Creating a new position
### Hiring restrictions
**Incompatible former employers**: Fleet maintains a list of companies with whom Fleet has do-not-solicit terms that prevents us from making offers to employees of these companies. The list is in the Do Not Solicit tab of the [Digital Experience spreadsheet](https://docs.google.com/spreadsheets/d/1lp3OugxfPfMjAgQWRi_rbyL_3opILq-duHmlng_pwyo/edit#gid=0).
**Incompatible locations**: Fleet is unable to hire team members in some countries. See [this internal document](https://docs.google.com/document/d/1jHHJqShIyvlVwzx1C-FB9GC74Di_Rfdgmhpai1SPC0g/edit) for the list.
## Interviewing
> TODO: Rewrite this section for the hiring manager as our audience.
We're glad you're interested in joining the team!
Here are some of the things you can anticipate throughout this process:
- We will reply by email within one business day from the time when the application arrives.
- You may receive a rejection email (Bummer, consider applying again in the future).
- You may receive an invitation to "book with us."
If you've been invited to "book with us," you'll have a Zoom meeting with the hiring team to discuss the next steps.
Department-specific interviewing instructions:
- [Engineering](https://fleetdm.com/handbook/engineering#interview-a-developer-candidate)
## Creating a new position
Want to hire? Use these steps to hire a [fleetie, not a consultant](https://fleetdm.com/handbook/company/leadership#who-isnt-a-consultant). Here's how to open up a new position on the core team:
@ -250,7 +266,7 @@ A completed open position entry should look something like this:
4. **Get it approved and merged:** When you submit your proposed job description, the CEO will be automatically tagged for review and get a notification. He will consider where this role fits into Fleet's strategy and decide whether Fleet will open this position at this time. He will review the data carefully to try and catch any simple mistakes, then tentatively budget cash and equity compensation and document this compensation research. He will set a tentative start date (which also indicates this position is no longer just "proposed"; it's now part of the hiring plan.) Then the CEO will start a `#hiring-xxxxx-YYYY` Slack channel, at-mentioning the original proposer and letting them know their position is approved. (Unless it isn't.)
- _**Why bother with approvals?** We avoid cancelling or significantly changing a role after opening it. It hurts candidates too much. Instead, get the position approved first, before you start recruiting and interviewing. This gives you a sounding board and avoids misunderstandings._
- _**Why bother with approvals?** We avoid canceling or significantly changing a role after opening it. It hurts candidates too much. Instead, get the position approved first, before you start recruiting and interviewing. This gives you a sounding board and avoids misunderstandings._
### Approving a new position
@ -277,60 +293,36 @@ When review is requested on a proposal to open a new position, the Apprentice to
> _**Note:** Most columns of the "Equity plan" are updated automatically when "Fleeties" is, based on the unique identifier of each row, like `🧑🚀890`. (Advisors have their own flavor of unique IDs, such as `🦉755`, which are defined in ["Advisors and investors"](https://docs.google.com/spreadsheets/d/15knBE2-PrQ1Ad-QcIk0mxCN-xFsATKK9hcifqrm0qFQ/edit).)_
### Recruiting
## Recruiting
Fleet accepts job applications, but the company does not list positions on general purpose job boards. This prevents us being overwhelmed with candidates so we can fulfill our goal of responding promptly to every applicant. This means that outbound recruiting, 3rd party recruiters, and references from team members are important aspect of the company's hiring strategy. Fleet's CEO is happy to assist with outreach, intros, and recruiting strategy for candidates.
<img width="384" alt="image" src="https://github.com/fleetdm/fleet/assets/618009/ea3d3470-575d-4516-a19e-02abec3e1285">
#### Receiving job applications
### Receiving job applications
Every job description page ends with a "call to action", including a link to the hiring manager's LinkedIn to apply for the job directly with the hiring manager. Fleet replies to all candidates within **1 business day** and always provides either a **rejection** or **decisive next steps**; even if the next step is just a promise. Hiring managers are encouraged to use [email/message templates](https://fleetdm.com/handbook/company/leadership#candidate-correspondence-email-templates) for consistency and efficiency.
#### Candidate correspondence email templates
### Candidate correspondence email templates
Fleet uses [certain email templates](https://docs.google.com/document/d/1VAMWIH8o7_vH7lV9nM1wQCM4Vc3GxBRBDPK-mKO9HNk/edit?usp=sharing) when responding to candidates. This helps us live our value of [🔴 empathy](https://fleetdm.com/handbook/company#empathy) and helps the company meet the aspiration of replying to all applications within one business day.
### Hiring restrictions
#### Incompatible former employers
Fleet maintains a list of companies with whom Fleet has do-not-solicit terms that prevents us from making offers to employees of these companies. The list is in the Do Not Solicit tab of the [Digital Experience spreadsheet](https://docs.google.com/spreadsheets/d/1lp3OugxfPfMjAgQWRi_rbyL_3opILq-duHmlng_pwyo/edit#gid=0).
#### Incompatible locations
Fleet is unable to hire team members in some countries. See [this internal document](https://docs.google.com/document/d/1jHHJqShIyvlVwzx1C-FB9GC74Di_Rfdgmhpai1SPC0g/edit) for the list.
### Interviewing
> TODO: Rewrite this section for the hiring manager as our audience.
We're glad you're interested in joining the team!
Here are some of the things you can anticipate throughout this process:
- We will reply by email within one business day from the time when the application arrives.
- You may receive a rejection email (Bummer, consider applying again in the future).
- You may receive an invitation to "book with us."
If you've been invited to "book with us," you'll have a Zoom meeting with the hiring team to discuss the next steps.
Department specific interviewing instructions:
- [Engineering](https://fleetdm.com/handbook/engineering#interview-a-developer-candidate)
#### Hiring a new team member
## Hiring a new team member
This section is about the hiring process a new core team member, or fleetie.
> **_Note:_** _Employment classification isn't what makes someone a fleetie. Some Fleet team members are contractors and others are employees. The distinction between "contractor" and "employee" varies in different geographies, and the appropriate employment classification and agreement for any given team member and the place where they work is determined by Head of Digital Experience during the process of making an offer._
Here are the steps hiring managers follow to get an offer out to a candidate:
1. **Call references:** Before proceeding, make sure you have 2-5+ references. Ask the candidate for at least 2-5+ references and contact each reference in parallel using the instructions in [Fleet's reference check template](https://docs.google.com/document/d/1LMOUkLJlAohuFykdgxTPL0RjAQxWkypzEYP_AT-bUAw/edit?usp=sharing). Be respectful and keep these calls very short.
2. **Add to team database:** Update the [Fleeties](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0) doc to accurately reflect the candidate's:
1. **Create a copy of the "Why hire?"**: Create a copy of the ["Why hire?" template](https://docs.google.com/document/d/1Hoq7qeZT683JErYeqPYs0v2mIoQCw2lvWOepsZn9IDg/copy?) and complete all TODOs on the "🧑‍🚀 Candidate details" tab.
2. **Call references:** Before proceeding, make sure you have 2-5+ references. Ask the candidate for at least 2-5+ references and contact each reference in parallel using the instructions in [Fleet's reference check template](https://docs.google.com/document/d/1LMOUkLJlAohuFykdgxTPL0RjAQxWkypzEYP_AT-bUAw/edit?usp=sharing). Be respectful and keep these calls very short.
3. **Add to team database:** Update the [Fleeties](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0) doc to accurately reflect the candidate's:
- Start date
> _**Tip:** No need to check with the candidate if you haven't already. Just guess. First Mondays tend to make good start dates. When hiring an international employee, Pilot.co recommends starting the hiring process a month before the new employee's start date._
@ -340,10 +332,9 @@ Here are the steps hiring managers follow to get an offer out to a candidate:
- LinkedIn URL _(If the fleetie does not have a LinkedIn account, enter `N/A`)_
- Location of candidate
3. **Compile feedback into a single doc:** In the "interview packet", include feedback from interviews, reference checks, and challenge submissions. Include any other notes you can think of offhand, and embed links to any supporting documents that were impactful in your final decision-making, such as portfolios or challenge submissions.
- Name the doc with a short, formulaic name that's easy to understand in an instant from just an email subject line (e.g. "_Why hire Jane Doe ("Train Conductor") - 2023-03-21_").
- _Share_ this single document with the [CEO and Head of Digital Experience](https://fleetdm.com/handbook/digital-experience#team).
4. **Request a CEO interview:** Copy the template below, paste it in the hiring Slack channel for the position, and complete all "TODOs" before sending.
4. **Compile feedback**: Include feedback from interviews, and challenge submissions, links to any supporting documents that were impactful in your final decision-making, and any other notes you can think of offhand as tabs in the document. Share the "Why hire?" document with the [CEO and Head of Digital Experience](https://fleetdm.com/handbook/digital-experience#team).
5. **Request a CEO interview:** Copy the template below, paste it in the hiring Slack channel for the position, and complete all "TODOs" before sending.
```
*CEO interview request:*
@ -365,13 +356,16 @@ Here are the steps hiring managers follow to get an offer out to a candidate:
After receiving the interview packet, the Head of Digital Experience uses the following steps to make an offer:
<!-- For future use: some ready-to-go language around rebencharking compensation for cost of living: https://github.com/fleetdm/fleet/pulls/13499 -->
1. **Prepare the "exit scenarios" spreadsheet:** 🌐 Head of Digital Experience [copies the "Exit scenarios (template)"](https://docs.google.com/spreadsheets/d/1k2TzsFYR0QxlD-KGPxuhuvvlJMrCvLPo2z8s8oGChT0/copy) for the candidate, and renames the copy to e.g. "Exit scenarios for Jane Doe".
- _Edit the candidate's copy of the exit scenarios spreadsheet_ to reflect the number of shares in ["🥧 Equity plan"](https://docs.google.com/spreadsheets/d/1_GJlqnWWIQBiZFOoyl9YbTr72bg5qdSSp4O3kuKm1Jc/edit#gid=0), and the spreadsheet will update automatically to reflect their approximate ownership percentage.
> _**Note:** Don't play with numbers in the exit scenarios spreadsheet. The revision history is visible to the candidate, and they might misunderstand._
2. **Prepare offer:** 🌐 Head of Digital Experience [copies "Offer email (template)"](https://docs.google.com/document/d/1zpNN2LWzAj-dVBC8iOg9jLurNlSe7XWKU69j7ntWtbY/copy) and renames to e.g. "Offer email for Jane Doe". Edit the candidate's copy of the offer email template doc and fill in the missing information:
- _Benefits:_ If candidate will work outside the US, [change the "Benefits" bullet](https://docs.google.com/document/d/1zpNN2LWzAj-dVBC8iOg9jLurNlSe7XWKU69j7ntWtbY/edit) to reflect what will be included through Fleet's international payroll provider, depending on the candidate's location.
- _Equity:_ Highlight the number of shares with a link to the candidate's custom "exit scenarios" spreadsheet.
- _Hand off:_ Share the offer email doc with the [Apprentice to the CEO](https://fleetdm.com/handbook/digital-experience#team).
3. **Draft email:** 🦿 Apprentice to the CEO drafts the offer email in the CEO's inbox, reviews one more time, and then brings it to their next daily meeting for CEO's approval:
- To: The candidate's personal email address _(use the email from the CEO interview calendar event)_
- Cc: Head of Digital Experience
@ -380,13 +374,15 @@ After receiving the interview packet, the Head of Digital Experience uses the fo
- _Check all links in offer letter for accuracy (e.g. LinkedIn profile of hiring manager, etc.)_
- _Click the surrounding areas to ensure no "ghost links" are left from previous edits... which has happened before._
- _Re-read the offer email one last time, and especially double-check that the salary, number of shares, and start date match the numbers that are currently in the equity plan._
4. **Send offer:** 🐈‍⬛ CEO reviews and sends the offer to the candidate:
- _Grant the candidate "edit" access_ to their "exit scenarios" spreadsheet.
- _Send_ the email.
5. **Process the offer response** The Head of Digital Experience will process the offer response by either creating a new ["Teammate pre-onboarding" issue](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-digital-experience&projects=&template=pre-onboarding.md&title=Pre-onboarding%3A+______________________) and following the steps if the offer is accepted or notifying the stakeholders that the offer was not accepted and we should continue the search.
#### After an offer is accepted
### After an offer is accepted
The Head of Digital Experience will then follow the steps in the ["Teammate pre-onboarding"](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-digital-experience&projects=&template=pre-onboarding.md&title=Pre-onboarding%3A+______________________) issue, which includes reaching out to the new team member within 1 business day from a separate email thread to get additional information as needed, prepare their agreement, add them to the company's payroll system, and get their new laptop and hardware security keys ordered so that everything is ready for them to start on their first day.

View file

@ -608,7 +608,7 @@
# ║║║╣ ╚╗╔╝║║ ║╣ ╠╦╝║╣ ║║║║╣ ║║║╠═╣ ║ ║║ ║║║║
# ═╩╝╚═╝ ╚╝ ╩╚═╝╚═╝ ╩╚═╚═╝╩ ╩╚═╝═╩╝╩╩ ╩ ╩ ╩╚═╝╝╚╝
- industryName: Device remediation
description: Use Fleet Policies to detect the device state. Automate remediations for issues or allow users to remediate problems in self-service.
description: Use Fleet Policies to detect the device state. Automate remediations for issues or allow users to remediate problems in self-service (Fleet Desktop > My device page).
documentationUrl: https://fleetdm.com/securing/end-user-self-remediation # « NOTE: This link will change when auto-remediation is delivered.
tier: Premium
jamfProHasFeature: yes

View file

@ -350,7 +350,7 @@ The relevant release page on GitHub is updated to indicate that the release cont
When a critical bug is identified, we will then follow the patch release process in [our documentation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Releasing-Fleet.md#patch-releases).
> After a critical bug is fixed, [an incident postmortem](https://fleetdm.com/handbook/engineering#preform-an-incident-postmortem) is scheduled by the EM of the product group that fixed the bug.
> After a critical bug is fixed, [an incident postmortem](https://fleetdm.com/handbook/engineering#perform-an-incident-postmortem) is scheduled by the EM of the product group that fixed the bug.
## Feature fest
@ -373,7 +373,7 @@ To prioritize a new feature, it must meet one of these criteria:
1. Bug
2. Small UX improvement that isn't quite a bug but it's so small that it's worthwhile
3. Contributes to Fleet's [quarterly objectives (OKRs)](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit?gid=1846478041#gid=1846478041&range=A1)
3. Contributes to Fleet's [quarterly key results (KRs)](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit?gid=1846478041#gid=1846478041&range=A1)
4. High priority customer request (customer request, workflow blocking, etc.)
5. Prospect request in an order form

View file

@ -150,7 +150,7 @@
productCategories: [Vulnerability management, Endpoint operations]
-
quote: Fleet has such a huge amount of use cases. My goal was to get telemetry on endpoints, but then our IR team, our TBM team, and multiple other folks in security started heavily utilizing the system in ways I didnt expect. It spread so naturally, even our corporate and infrastructure teams want to run it.
quoteAuthorName: Charles Zaffery
quoteAuthorName: charles zaffery # Note: this name is lowercased here so we can sort the two endpoint-ops-related Charles Zaffery quotes in different spots. (The name will be capitalized via CSS)
quoteLinkUrl: https://www.linkedin.com/in/charleszaffery/
quoteAuthorJobTitle: Principle computer janitor
quoteAuthorProfileImageFilename: testimonial-author-charles-zaffery-48x48@2x.png
@ -195,3 +195,11 @@
quoteAuthorProfileImageFilename: testimonial-author-scott-macvicar-100x100@2x.png
quoteAuthorJobTitle: Head of Developer Infrastructure & Corporate Technology
productCategories: [Device management, Endpoint operations]
-
quote: Fleet helped us determine that Norton antivirus (pre-installed on some machines) was interfering with Crowdstrike's ability to detect downloaded malware.
# quoteImageFilename: social-proof-logo-proofpoint-67x32@2x.png #TODO
quoteLinkUrl: https://www.linkedin.com/in/arsenio-figueroa-81a56198/
quoteAuthorName: Arsenio Figueroa
quoteAuthorProfileImageFilename: testimonial-author-arsenio-figueroa-48x48@2x.png
quoteAuthorJobTitle: Senior Systems Security Engineer
productCategories: [Endpoint operations, Vulnerability management]

View file

@ -242,6 +242,11 @@ Why bother with all that? And why do it in this particular order?
- **It helps us prevent future outages.** By finding outages sooner, we incentivize ourselves to fix the root cause sooner. And by fixing bugs sooner, we prevent them from stacking and bleeding into one another, and we prevent ourselves from implementing future fixes and improvements on top of shaky foundations. This makes contributions less risky and reduces the number of outages.
## Why fix small inconsistencies quickly?
Fixing small inconsistencies quickly is worth it. When we tolerate and overlook little things, we send contributors mixed messages, like "[broken windows](https://en.wikipedia.org/wiki/Broken_windows_theory)". (Are these things actually important?) Since new contributors join the team all the time, we also prevent learning the right way to do things through osmosis, since pattern matching from a bad pattern creates even more inconsistencies. Plus these things add up over time, creating problems for both users and contributors.
## Why make it obvious when stuff breaks?
At Fleet, we detect and fix bugs as quickly as possible.
@ -278,7 +283,7 @@ For example, here is the [philosophy behind Fleet's bug report template](https:/
## Why don't we sell like everyone else?
Many companies encourage salespeople to ["spray and pray"](https://www.linkedin.com/posts/amstech_the-rampant-abuse-of-linkedin-connections-activity-7178412289413246978-Ci0I?utm_source=share&utm_medium=member_ios) email blasts, and to do whatever it takes to close deals. This can sometimes be temporarily effective. But Fleet takes a [🟠longer-term](https://fleetdm.com/handbook/company#ownership) approach:
- **No spam.** Fleet is deliberate and thoughtful in the way we do outreach, whether that's for community-building, education, or [🧊 conversation-starting](https://github.com/fleetdm/confidential/blob/main/cold-outbound-strategy.md).
- **No spam.** Fleet is deliberate and thoughtful in the way we do outreach, whether that's for community-building, education, or [🧊 conversation-starting](https://docs.google.com/document/d/1IbucpsZZ0qbJQRPRtm9e2kMcSBDTXjixAMVOWyTu_pA/edit?tab=t.0).
- **Be a helper.** We focus on [🔴being helpers](https://fleetdm.com/handbook/company#empathy). Always be depositing value. This is how we create a virtuous cycle. (That doesn't mean sharing a random article; it means genuinely hearing, doing whatever it takes to fully understand, and offering only advice or links that we would actually want.) We are genuinely curious and desperate to help, because creating real value for people is the way we win.
- **Engineers first.** We always talk to engineers first, and learn how it's going. Security and IT engineers are the people closest to the work, and the people best positioned to know what their organizations need.
- **Fewer words. Fewer pings.** People are busy. We don't waste their time. Avoid dumping work on prospect's plates at all costs. Light touches, no asks. Every notification from Fleet is a ping they have to deal with. We don't overload people with words and links. We [🟢keep things simple](https://fleetdm.com/handbook/company#results) and [write briefly](http://www.paulgraham.com/writing44.html).

View file

@ -45,6 +45,19 @@ Before a routine customer call, the CSM prepares an agenda including the followi
6. Provide updates to open bug reports
### Invite new customer DRI
Sometimes there is a change in the champion within the customer's organization.
1. Get an introduction to the new DRIs including names, roles, contact information.
2. Make sure they're in the Slack channel.
3. Invite them to the *Success* meetings.
4. In the first meeting understand their proficiency level of osquery.
1. Make sure the meeting time is still convenient for their team.
2. Understand their needs and goals for visibility.
3. Offer training to get them up to speed.
4. Provide a white glove experience.
### Generate an expansion opportunity in Salesforce
[Customer Success Managers (CSMs)](https://fleetdm.com/handbook/customer-success#team) are responsible for developing customer expansion opportunities that are not being worked on in conjunction with an Account Executive (AE). An AE may be assigned by the [Chief Revenue Officer (CRO)](https://fleetdm.com/handbook/sales#team) for large-scale expansion opportunities such as bringing on a new Fleet use case or bringing on a new group of hosts to an existing Fleet use case. CSMs manage expansion opportunities for things like host count increases for customer growth and price increases on renewals. Discuss examples of these scenarios with your manager to learn more. Moving forward, CSM's are responsible for keeping the stage, next steps, and date of next steps fields updated as the opportunity progresses through the sales cycle. Take the steps below when creating an expansion opportunity in Salesforce:

View file

@ -207,6 +207,11 @@ To update the host count on a user's subscription:
7. Let the person who created the request know what actions were taken so they can communicate them to the customer.
### Change customer credit card number
You can help a Premium license dispenser customers change their credit card by directing them to their [account dashboard](https://fleetdm.com/customers/dashboard). On that page, the customer can update their billing card by clicking the pencil icon next to their billing information.
### Cancel a Fleet Premium subscription
Use the following steps to cancel a Fleet Premium subscription:

View file

@ -170,7 +170,7 @@ How to deploy a new release to dogfood:
> Note that this action will not handle down migrations. Always deploy a newer version than is currently deployed.
>
> Note that "fleetdm/fleet:main" is not a image name, instead use the commit hash in place of "main".
> Note that "fleetdm/fleet:main" is not an image name, instead use the commit hash in place of "main".
### Conclude current milestone
@ -205,7 +205,17 @@ Immediately after publishing a new release, we close out the associated GitHub i
The [Fleet releases Google calendar](https://calendar.google.com/calendar/embed?src=c_v7943deqn1uns488a65v2d94bs%40group.calendar.google.com&ctz=America%2FChicago) is kept up-to-date by the [release ritual DRI](https://fleetdm.com/handbook/engineering#rituals). Any change to targeted release dates is reflected on this calendar.
### Handle process exceptions for non-released code
Some of our code does not go through a scheduled release process, but is released immediately via GitHub workflows.
This includes:
- Our [fleetdm/nvd](https://github.com/fleetdm/nvd) repository
- Our [fleetdm/vulnerabilities](https://github.com/fleetdm/vulnerabilities) repository
- Our [website](https://github.com/fleetdm/fleet/tree/main/website) directory
In these cases there are two differences in our process:
- QA is done before merging the code change to the main branch.
- Tickets are not moved to "Ready for release". Bug are closed, and user stories are moved to the product drafting board's "Confirm and celebrate" column.
### Register a domain for Fleet
@ -355,7 +365,7 @@ We use Cloudflare to manage the DNS records of fleetdm.com and our other domains
The on-call developer is responsible for:
- Knowing [the on-call rotation](https://fleetdm.com/handbook/company/product-groups#the-developer-on-call-rotation).
- Preforming the [on-call responsibilities](https://fleetdm.com/handbook/company/product-groups#developer-on-call-responsibilities).
- Performing the [on-call responsibilities](https://fleetdm.com/handbook/company/product-groups#developer-on-call-responsibilities).
- [Escalating community questions and issues](https://fleetdm.com/handbook/company/product-groups#escalations).
- Successfully [transferring the on-call persona to the next developer](https://fleetdm.com/handbook/company/product-groups#changing-of-the-guard).

View file

@ -1,23 +1,28 @@
# Finance
This handbook page details processes specific to working [with](#contact-us) and [within](#responsibilities) this department.
## Team
| Role | Contributor(s) |
|:------------------------------|:-----------------------------------------------------------------------------------------------------------|
| Finance Engineer | [Isabell Reedy](https://www.linkedin.com/in/isabell-reedy-202aa3123/) _([@ireedy](https://github.com/ireedy))_
## Contact us
- To **make a request** of this department, [create an issue](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-finance&projects=&template=custom-request.md) and a team member will get back to you within one business day (If urgent, mention a [team member](#team) in [#g-finance](https://fleetdm.slack.com/archives/C047N5L6EGH).
- Please **use issue comments and GitHub mentions** to communicate follow-ups or answer questions related to your request.
- Any Fleet team member can [view the kanban board](https://app.zenhub.com/workspaces/-g-finance-63f3dc3cc931f6247fcf55a9/board?sprints=none) for this department, including pending tasks and the status of new requests.
## Responsibilities
The Finance department is directly responsible for accounts receivable including invoicing, accounts payable including commision calculations, exspense reporting including Brex memos and maintaining accurate spend projections in "🧮The numbers", sales taxes, payroll taxes, corporate income/franchise taxes, and financial operations including bank accounts and cash flow management.
### Run payroll
Many of these processes are automated, but it's vital to check Gusto and Plane manually for accuracy.
- Salaried fleeties are automated in Gusto and Plane.
@ -76,12 +81,14 @@ To complete payroll for an international contractor, use the following steps:
### Reconcile monthly recurring expenses
Recurring monthly or annual expenses, such as the tools we use throughout Fleet, are tracked as recurring, non-personnel expenses in ["🧮 The Numbers"](https://docs.google.com/spreadsheets/d/1X-brkmUK7_Rgp7aq42drNcUg8ZipzEiS153uKZSabWc/edit#gid=2112277278) _(¶confidential Google Sheet)_, along with their payment source. Reconciliation of recurring expenses happens monthly. <!-- TODO: Merge "🧮 The Numbers" and ["Tools we use" (private Google doc)](https://docs.google.com/spreadsheets/d/170qjzvyGjmbFhwS4Mucotxnw_JvyAjYv4qpwBrS6Gl8/edit?usp=sharing) -->
> Use this spreadsheet as the source of truth. Always make changes to it first before adding or removing a recurring expense. Only track significant expenses. (Other things besides amount can make a payment significant; like it being an individualized expense, for example.)
### Register Fleet as an employer with a new state
Fleet must register as an employer in any state where we hire new teammates. To do this, complete the following steps in Gusto:
1. After a new teammate completes their Gusto profile, the Finance department will be prompted to approve it for payroll. Sign in to your Gusto admin account and begin the approval process.
2. Select "yes" when prompted to file a new hire report and complete the approval process.
@ -93,6 +100,7 @@ Fleet must register as an employer in any state where we hire new teammates. To
### Process an email from a state agency
From time to time, you may get notices via email (or in the mail) from state agencies regarding Fleet's withholding and/or unemployment tax accounts. You can resolve some of these notices on your own by verifying and/or updating the settings in your Gusto account.
If the notice is regarding an upcoming change to your deposit schedule or unemployment tax rate, make the required change in Gusto, such as:
@ -193,6 +201,7 @@ Use the following steps to update the [💸Finance department KPIs](https://docs
### Create an invoice
To create a new invoice for a Fleet customer, follow these steps:
1. Go to the [invoice folder in Google Drive](https://drive.google.com/drive/folders/11limC_KQYNYQPApPoXN0CplHo_5Qgi2b?usp=drive_link).
2. Create a copy of the invoice template, and title the copy `[invoice number] Fleet invoice - [customer name]`.
@ -226,7 +235,17 @@ Thanks,
> Certain vendors require invoices submitted via a payment portal (such as Coupa). Once you've generated the invoice using the steps above, upload it to the relevant payment portal and email the billing contact to let them know you've submitted the invoice.
### Provide payment information to a customer
For customers with large deployments, Fleet accepts payment via wire transfer or electronic debit (ACH/SWIFT).
Payment information for customers within the United States is on Fleet's invoices. Typically, payment information does not need to be sent separately.
For Fleet customers outside of the United States or instances where a customer is requesting payment information prior to invoicing, provide remittance information to customers by exporting ["💸 Paying Fleet"](https://docs.google.com/document/d/1KP_-x9c1x3sS1X9Q8Wlib2H7tq69xRONn1KMA3nVFQc/edit) into a PDF, then sending that to the prospect.
### Communicate the status of customer financial actions
This reporting is performed to update the status of open or upcoming customer actions regarding the financial health of the opportunity. To complete the report:
1. Check [SVB](https://connect.svb.com/#/) and [Brex](https://accounts.brex.com/login) for any recently received payments from customers and record them in SFDC.
2. Go to this [report folder](https://fleetdm.lightning.force.com/lightning/r/Folder/00lUG000000DstpYAC/view?queryScope=userFolders) in SFDC. The three reports will provide the data used in the report.
@ -252,10 +271,17 @@ Thanks,
```
5. If any accounts will become overdue within a week, reply in thread to the slack post, mention the opportunity owner of the account, and ask them to notify their contact that Fleet is still awaiting payment.
6. Review the [billing cycles](https://fleetdm.lightning.force.com/lightning/r/Report/00OUG000000yGjR2AU/view) report in SFDC for customers on multiyear deals. For any customers due for invoicing within the next week, create an issue on the Finance board.
6. If Finance is still awaiting a PO one week after the opportunity has been closed/won, reply to the thread in the Slack post, mention the CSM, and ask them to follow up with their customer contact.
7. Review the [billing cycles](https://fleetdm.lightning.force.com/lightning/r/Report/00OUG000000yGjR2AU/view) report in SFDC for customers on multiyear deals. For any customers due for invoicing within the next week, create an issue on the Finance board.
### Obtain a copy of Fleet's W-9
A recent signed copy of Fleet's W-9 form can be found in [this confidential PDF in Google Drive](https://drive.google.com/file/d/1ugXazEBk1oVm_LqGbYNsIFECcv5jXLA9/view?usp=drivesdk).
### Run US commission payroll
1. Update individual teammates commission calculators (linked from [main commission calculator](https://docs.google.com/spreadsheets/d/1PuqUbfPGos87TfcHWgUd05TRJgQLlBmhyz1euj79m2A/edit?usp=sharing)) with new revenue from any deals that are closed-won (have a subscription agreement signed by both parties) and have a **close date** within the previous month.
- Verify closed-won deal numbers with CRO to ensure any agreed upon exceptions are captured (eg: CRO approves an AE to receive commission on a renewal deal due to cross-sell).
2. In the "Monthly commission payroll party" meeting, present the commission calculations for Fleeties receiving commission for approval.
@ -265,6 +291,7 @@ Thanks,
4. Once commission payroll has been run, update the [main commission calculator](https://docs.google.com/spreadsheets/d/1PuqUbfPGos87TfcHWgUd05TRJgQLlBmhyz1euj79m2A/edit?usp=sharing) to mark the commission as paid.
### Run international commission payroll
1. Follow the steps in [run US commission payroll](https://fleetdm.com/handbook/finance#run-us-commission-payroll) to have the commission amounts approved by the CRO.
2. After the amounts are approved in the "Monthly commission payroll party", navigate to Help > Ask a question in Plane to request a commission payment for the teammate.
3. Send a message using the following template
@ -283,6 +310,7 @@ Thanks,
### Run quarterly or annual employee bonus payroll
1. Update individual teammate bonus calculator (linked from [main commission calculator](https://docs.google.com/spreadsheets/d/1PuqUbfPGos87TfcHWgUd05TRJgQLlBmhyz1euj79m2A/edit?usp=sharing)) with relevant metrics.
- Bonus plans will have details specified on how to measure success, with most drawing from the [KPI spreadsheet](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit?usp=sharing) or from linked SFDC reports. If unsure where to pull achievement metrics from, contact teammate's manager to clarify.
2. In the "Monthly commission payroll party" meeting, present the bonus calculations for Fleeties receiving bonus for approval.
@ -293,6 +321,7 @@ Thanks,
### Process monthly accounting
Create a [new montly accounting issue](https://github.com/fleetdm/confidential/issues/new/choose) for the current month and year named "Closing out YYYY-MM" in GitHub and complete all of the tasks in the issue. (This uses the [monthly accounting issue template](https://github.com/fleetdm/confidential/blob/main/.github/ISSUE_TEMPLATE/5-monthly-accounting.md).
- **SLA:** The monthly accounting issue should be completed and closed before the 7th of the month.
@ -301,6 +330,7 @@ Create a [new montly accounting issue](https://github.com/fleetdm/confidential/i
### Respond to low credit alert
Fleet admins will receive an email alert when the usage of company cards for the month is aproaching the company credit limit. To avoid the limit being exceeded, a Brex admin will follow these steps:
1. Sign in to Fleet's Brex account.
2. On the landing page, use the "Move money" button to "Add funds to your Brex business accounts".
@ -310,6 +340,7 @@ Fleet admins will receive an email alert when the usage of company cards for the
No further action needs to be taken, the amount available for use will increase without disruption to regular processes.
### Check franchise tax status
No later than the second month of every quarter, we check [Delaware divison of corporations](https://icis.corp.delaware.gov) to ensure that Fleet has paid the quarterly franchise tax amounts to remain in good standing with the state of Delaware.
- Go to the [DCIS - eCorp website](https://icis.corp.delaware.gov/ecorp/logintax.aspx?FilingType=FranchiseTax) and use the details in 1Password to look up Fleet's status.
- If no outstanding amounts: the tax has been paid.
@ -317,6 +348,7 @@ No later than the second month of every quarter, we check [Delaware divison of c
### Check finances for quirks
Every quarter, we check Quickbooks Online (QBO) for discrepancies and follow up on quirks.
1. Check to make sure [bookkeeping quirks](https://docs.google.com/spreadsheets/d/1nuUPMZb1z_lrbaQEcgjnxppnYv_GWOTTo4FMqLOlsWg/edit?usp=sharing) are all accounted for and resolved or in progress toward resolution.
2. Check balance sheet and profit and loss statements (P&Ls) in QBO against the latest [monthly workbooks](https://drive.google.com/drive/folders/1ben-xJgL5MlMJhIl2OeQpDjbk-pF6eJM) in Google Drive. Ensure reports are in the "accural" accounting method.
@ -325,6 +357,7 @@ Every quarter, we check Quickbooks Online (QBO) for discrepancies and follow up
### Report quarterly numbers in Chronograph
Follow these steps to perform quarterly reporting for Fleet's investors:
1. Login to Chronograph and upload our profit and loss statement (P&L), balance sheet and cash flow statements for CRV (all in one book saved in [Google Drive](https://drive.google.com/drive/folders/1ben-xJgL5MlMJhIl2OeQpDjbk-pF6eJM).
2. Provide updated metrics for the following items using Fleet's [KPI spreadsheet](https://docs.google.com/spreadsheets/d/1Hso0LxqwrRVINCyW_n436bNHmoqhoLhC8bcbvLPOs9A/edit#gid=0).
@ -345,6 +378,7 @@ Follow these steps to perform quarterly reporting for Fleet's investors:
### Deliver annual report for venture line
Within 60 days of the end of the year, follow these steps:
1. Provide Silicon Valley Bank (SVB) with our balance sheet and profit and loss statement (P&L, sometimes called a cashflow statement) for the past twelve months.
2. Provide SVB with our board-approved annual operating budgets and projections (on a quarterly granularity) for the new year.
@ -352,6 +386,7 @@ Within 60 days of the end of the year, follow these steps:
### Process a new vendor invoice
Fleet pays its vendors in less than 15 business days in most cases. All invoices and tax documents should be submitted to the Finance department using the [appropriate Fleet email address (confidential Google Doc)](https://docs.google.com/document/d/1tE-NpNfw1icmU2MjYuBRib0VWBPVAdmq4NiCrpuI0F0/edit#heading=h.wqalwz1je6rq).
- After making sure the invoice received from a new vendor is valid, add the new vendor to the recurring expenses section of ["The numbers"](https://docs.google.com/spreadsheets/d/1X-brkmUK7_Rgp7aq42drNcUg8ZipzEiS153uKZSabWc/edit#gid=2112277278) before paying the invoice.
- If we have not paid this vendor before, make sure we have received the required W-9 or W-8 form from the vendor. **Accounting cannot process a payment without these tax forms for compliance reasons.**
@ -360,6 +395,7 @@ Fleet pays its vendors in less than 15 business days in most cases. All invoices
### Process a request to cancel a vendor
- Make the cancellation notification in accordance with the contract terms between Fleet and the vendor, typically these notifications are made via email and may have a specific address that notice must be sent to. If the vendor has an autorenew contract with Fleet there will often be a window of time in which Fleet can cancel, if notification is made after this time period Fleet may be obligated to pay for the subsequent year even if we don't use the vendor during the next contract term.
- Once cancelled, update the recurring expenses section of [The Numbers](https://docs.google.com/spreadsheets/d/1X-brkmUK7_Rgp7aq42drNcUg8ZipzEiS153uKZSabWc/edit#gid=2112277278) to reflect the cancellation by changing the projected monthly burn in column G to $0 and adding "CANCELLED" in front of the vendor's name in column C.

View file

@ -28,7 +28,7 @@ The Product Design department is responsible for reviewing and collecting feedba
The Head of Product Design and a former IT admin review the new customer/prospect/community requests in the "Inbox" column the [drafting board](https://github.com/fleetdm/fleet/issues#workspaces/drafting-6192dd66ea2562000faea25c/board) to synthesize why users are making the request (i.e. what problem are they trying to solve).
If a customer/prospect request is missing a Gong snippet or requires additional information to understand the "why", the Head of Product Design will @ mention the relevant Customer Success Manager (CSM) or Account Executive (AE), assign them, and move the request to the "Waiting" column.
If a customer/prospect request is missing a Gong snippet or requires additional information to understand the "why", the Head of Product Design will @ mention the relevant Customer Success Manager (CSM), assign them, and move the request to the [🏹 #g-customer-success](https://github.com/fleetdm/fleet/issues#workspaces/g-customer-success-642c83a53e96760014c978bd/board) board.
### Unpacking the how
@ -102,6 +102,8 @@ The EM is responsible for moving the user story to the "Specified" and "Estimate
Before assigning an EM, double-check that the "Product" section of the user story [checklist](https://github.com/fleetdm/fleet/issues/new?assignees=&labels=story&projects=&template=story.md&title=) is complete (no TODOs).
If the story is tied to a customer feature request, the Head of Product Design (HPD) is responsible for adding the feature request issue to the [🏹 #g-customer-success board](https://github.com/fleetdm/fleet/issues#workspaces/g-customer-success-642c83a53e96760014c978bd/board). This way the Customer Success Manager (CSM) can review the wireframes and provide feedback on whether the proposed changes solve the customer's problem. If the changes don't, it's up to the HPD to decide whether to bring the user story back for more drafting or file a follow up user story (iteration).
Once a bug is approved in design review, The Product Designer is responsible for moving the bug to the appropriate release board.

View file

@ -23,7 +23,7 @@
task: "Sprint kickoff review" # 2024-03-06 TODO: Link to responsibility or corresponding "how to" info e.g. https://fleetdm.com/handbook/company/product-groups#making-changes
startedOn: "2024-03-07"
frequency: "Triweekly"
description: "Review stories that made it into this sprint and stories that didn't make it into this sprint. Ensure stories/bugs have been effectively prioritized across teams. After the call, the Head of Product Designer @ mentions the API design DRI in #help-design about stories that didn't make it into the sprint so that they can update the reference docs release branches."
description: "Review stories that made it into this sprint and stories that didn't make it into this sprint. Ensure stories/bugs have been effectively prioritized across teams. After the call, the Head of Product Design @ mentions the API design DRI in #help-design about stories that didn't make it into the sprint so that they can update the reference docs release branches."
moreInfoUrl:
dri: "noahtalerman"
-

View file

@ -25,11 +25,25 @@ This handbook page details processes specific to working [with](#contact-us) and
The Sales department is directly responsible for attaining the revenue goals of Fleet and helping to deliver upon our customers' objectives.
### Set up a Fleet trial
You can set up a Fleet Managed Cloud environment for a prospect with >300 hosts, or you can help them generate a trial license key to configure on their own self-managed Fleet server.
- **To set up a new Fleet Managed Cloud environment** for a user: First, [create a "New customer environment" issue](https://fleetdm.com/docs/configuration/fleet-server-configuration#license-key). Then, once the environment is set up, you'll get a notification and you can let the user know.
- **To set up only a trial license key** for a user's self-managed Fleet server: Point the user towards fleetdm.com/start, where they can sign up and choose to "Run your own trial with Docker". On that page, they'll see a license key located in the `fleectl preview` CLI instructions, and they can configure this by copying and pasting it as the [`FLEET_LICENSE_KEY`](https://fleetdm.com/docs/configuration/fleet-server-configuration#license-key) environment variable on the server(s) where Fleet is deployed.
### Demo Fleet to a prospect
To run a demo for a prospect, follow the relevant steps in ["Why Fleet?"](https://docs.google.com/document/d/1E0VU4AcB6UTVRd4JKD45Saxh9Gz-mkO3LnGSTBDLEZo/edit#heading=h.vfxwnwufxzzi)
### Introduce Fleet's CEO
To get the CEO's attention and introduce him to an account, follow the relevant steps in ["Why Fleet?"](https://docs.google.com/document/d/1E0VU4AcB6UTVRd4JKD45Saxh9Gz-mkO3LnGSTBDLEZo/edit#heading=h.vfxwnwufxzzi)
### Track an objection
We often hear objections to using Fleet that are important to track, understand, and solve for. To track an objection:
1. Navigate to the ["Understanding objections document" (Confidential Google Doc)](https://docs.google.com/document/d/1UFjHaIBdoSGDiqNqwgxRdwRz9Wn9SqP7h-g2OM8Runk/edit).
2. Copy the template at the top of the page and paste it at the top of the "Objections" section completing all TODOs.
To track an objection you heard from a prospect, follow the relevant steps in ["Why Fleet?"](https://docs.google.com/document/d/1E0VU4AcB6UTVRd4JKD45Saxh9Gz-mkO3LnGSTBDLEZo/edit#heading=h.vfxwnwufxzzi)
### Change a contact's organization in Salesforce
@ -38,10 +52,6 @@ Use the following steps to change a contact's organization in Salesforce:
- If the contact's organization in Salesforce is incorrect but their new organization is unknown, navigate to the contact in Salesforce and change the "Account name" to "?" and save.
- If the contact's organization in Salesforce is incorrect and we know where they're moving to, navigate to the contact in Salesforce, change the "Account name" to the contact's new organization, and save.
### Onboard a new sales team member
Once the standard Fleetie onboarding issue is complete, create a new ["Sales team onboarding"](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-sales&projects=&template=sales-team-onboarding.md&title=Sales%20onboarding%3A_____________) issue and complete it.
### Send a quote
@ -55,18 +65,15 @@ The Fleet owner of the opportunity (usually AE or CSM) will prepare a quote and/
- Before sending to prospect, work with the Finance team to verify if sales tax needs to be charged and, if so, how much.
### Obtain a copy of Fleet's W-9
### Schedule a Solutions Consultant for prospect meeting
A recent signed copy of Fleet's W-9 form can be found in [this confidential PDF in Google Drive](https://drive.google.com/file/d/1ugXazEBk1oVm_LqGbYNsIFECcv5jXLA9/view?usp=drivesdk).
To schedule an [ad hoc meeting](https://www.vocabulary.com/dictionary/ad%20hoc) with a Fleet prospect, the Account Executive (AE) will [open an issue](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-sales%2C%23solutions-consultant%2C%3Adiscovery%2C%3Ademo%2C%3Ascoping%2C%3Atech-eval&projects=&template=custom-request.md&title=prospect+name+-+prep+%28date%29+-+discovery%2Cdemo%2Cscoping+%28date%29).
- Use [this calendly link](https://calendly.com/fleetdm/talk-to-a-solutions-consultant) to obtain SC availability.
- The AE will populate this issue with the appropriate dates for an internal prep meeting as well as the dates for the external prospect meeting.
- Do not assign the issue. The Director of Solutions Consulting will assign the issue.
- Ensure that the product category is defined ("Endpoint ops", "Device management", or "Vulnerability management") in the description of the issue.
### Provide payment information to a prospect
For customers with large deployments, Fleet accepts payment via wire transfer or electronic debit (ACH/SWIFT).
Payment information for customers within the United States is on Fleet's invoices. Typically, payment information does not need to be sent separately.
For Fleet customers outside of the United States or instances where a customer is requesting payment information prior to invoicing, provide remittance information to customers by exporting ["💸 Paying Fleet"](https://docs.google.com/document/d/1KP_-x9c1x3sS1X9Q8Wlib2H7tq69xRONn1KMA3nVFQc/edit) into a PDF, then sending that to the prospect.
<!-- 2024-11-16 We noticed some content in these sections was outdated, so we're using this opportunity to try out a different structure ± altitude level for the content for on this page
### Review rep activity
@ -90,65 +97,6 @@ Every week, AEs will review the status of all qualified opportunities with leade
4. Relay how many meetings they had with attendees from both IT and security this week.
### Validate Salesforce data (RevOps)
In order to maintain a consistent contributor experience in Salesforce, we log in to make sure the structure of Salesforce data continues to look correct based on processes started elsewhere. Then we can look and see that the goals we want to achieve as a business are in line with our view inside Salesforce by conducting the following checkup. Any discrepancies between how information is presented in Salesforce and what should be in there per this ritual should be flagged so that they can be fixed or discussed.
1. Make sure the default tabs for a standard user include a detailed view of contacts, opportunities, accounts, and leads. No other tabs should exist.
2. Click the accounts tab and check for the following:
* The default filter is Customers when you click on the accounts tab. Click on an account to continue.
* Click on a customer and make sure billing address, parent account, LinkedIn company URL, CISO employees (#), employees, and industry appear first at the top of the account.
* Useful links section should appear in the top right section of the account page. It includes links to purchase orders (POs), signed subscription agreements, invoices sent, meeting notes, and signed NDA. Clicking these links should search the appropriate repository for the requested information pertaining to the customer. All meeting notes should be saved in the [Meeting notes](https://drive.google.com/drive/folders/18e-rVadHG0T5w98OKMngM-yv-K9SXaOq) folder in Google Drive with the account name and date in the title. We do not use the notes feature on "accounts" or "opportunities" in Salesforce.
* Additional information section should include fields for account (customer) name first, account rating, LinkedIn sales navigator URL, LinkedIn company URL, and my LinkedIn overlaps. Make sure the LinkedIn links work.
* Accounting section should include the following fields: invoice sent (latest), the payment received on (latest), subscription end date (latest), press approval field, license key, total opportunities (#), deals won (#), close date (first deal), cumulative revenue, payment terms, billing address, and shipping address.
* Opportunities, meeting notes, and activity feed should appear on the right.
3. Click on the opportunities tab and check for the following:
* Default filter should be all opportunities. Open an opportunity to continue.
* Section at the top of the page should include fields for account name, amount, close date, next step, and opportunity owner.
* Opportunity information section should include fields for account name, opportunity name (should have the year on it), amount, next step, next step's due date, close date, and stage.
* The accounting section here should include: up to # of hosts, type, payment terms, billing process, term, reseller, effective date, subscription end date, invoice sent, and the date payment was received.
* Stage history, activity feed, and LinkedIn sales navigator should appear at the right.
4. Click on the contacts tab and check for the following:
* Default filter should be all contacts. Open a contact to continue.
* Top section should have fields for the contact's name, job title, department, account name, LinkedIn, and Orbit feed.
* The second section should have fields for LinkedIn URL, account name, name, title, is champion, and reports to
* Additional information should have fields for email, personal email, Twitter, GitHub, mobile, website, orbit feed, and description.
* Related contacts section should exist at the bottom, activity feed, meeting notes reminder, and manager information should appear on the right.
5. Click on the leads tab and check for the following:
* Default filter should be all leads. Open a lead to continue.
* There should be fields for name, lead source, lead status, and rating.
### Invite new customer DRI
Sometimes there is a change in the champion within the customer's organization.
1. Get an introduction to the new DRIs including names, roles, contact information.
2. Make sure they're in the Slack channel.
3. Invite them to the *Success* meetings.
4. In the first meeting understand their proficiency level of osquery.
1. Make sure the meeting time is still convenient for their team.
2. Understand their needs and goals for visibility.
3. Offer training to get them up to speed.
4. Provide a white glove experience.
### To schedule Solutions Consultant (SC) for a prospect meeting
To schedule an [ad hoc meeting](https://www.vocabulary.com/dictionary/ad%20hoc) with a Fleet prospect, the Account Executive (AE) will [open an issue](https://github.com/fleetdm/confidential/issues/new?assignees=&labels=%23g-sales%2C%23solutions-consultant%2C%3Adiscovery%2C%3Ademo%2C%3Ascoping%2C%3Atech-eval&projects=&template=custom-request.md&title=prospect+name+-+prep+%28date%29+-+discovery%2Cdemo%2Cscoping+%28date%29).
- Use [this calendly link](https://calendly.com/fleetdm/talk-to-a-solutions-consultant) to obtain SC availability.
- The AE will populate this issue with the appropriate dates for an internal prep meeting as well as the dates for the external prospect meeting.
- Do not assign the issue. The Director of Solutions Consulting will assign the issue.
- Ensure that the product category is defined ("Endpoint ops", "Device management", or "Vulnerability management") in the description of the issue.
### Conduct a POV
We use the "tech eval test plan" as a guide when conducting a "POV" (Proof of Value) with a prospect. This planning helps us avoid costly detours that can take a long time, and result in folks getting lost. The tech eval test plan is the main document that will track success criteria for the tech eval.
@ -221,7 +169,7 @@ Temp Transfer to: Temp technical DRI
- phone, try finding their number to text and/or call (as appropriate to the device type: landline vs. cell phone).
- an alternative date and time. Suggest two to three options from which the prospect can choose.
- Confirm that contact information is accurate and that the prospect can receive and access meeting invites.
-->
### Send an NDA to a customer
@ -248,11 +196,6 @@ Temp Transfer to: Temp technical DRI
To close a deal with a new customer (non-self-service), create and complete a GitHub issue using the ["Sale" issue template](https://github.com/fleetdm/confidential/issues/new?assignees=alexmitchelliii&labels=%23g-sales&projects=&template=3-sale.md&title=New+customer%3A+_____________).
### Change customer credit card number
You can help a Premium license dispenser customers change their credit card by directing them to their [account dashboard](https://fleetdm.com/customers/dashboard). On that page, the customer can update their billing card by clicking the pencil icon next to their billing information.
### Process a security questionnaire
- The AE will [use the handbook](https://fleetdm.com/handbook/company/communications#vendor-questionnaires) to answer most of the questions with links to appropriate sections in the handbook. After this first pass has been completed, and if there are outstanding questions, the AE will [assign the issue to Digital Experience (#g-digital-experience)](https://fleetdm.com/handbook/digital-experience#contact-us) with a requested timeline for completion defined.

View file

@ -88,7 +88,7 @@ module "free" {
prefix = local.customer_free
enabled = true
}
idle_timeout = 605
idle_timeout = 905
}
}

View file

@ -214,7 +214,7 @@ module "main" {
prefix = local.customer
enabled = true
}
idle_timeout = 605
idle_timeout = 905
# extra_target_groups = [
# {
# name = module.saml_auth_proxy.name

Some files were not shown because too many files have changed in this diff Show more