mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 17:08:53 +00:00
Merge branch 'main' into mna-17401-puppet-related-integration-tests
This commit is contained in:
commit
66f90ccd93
190 changed files with 4588 additions and 6020 deletions
6
.github/workflows/check-tuf-timestamps.yml
vendored
6
.github/workflows/check-tuf-timestamps.yml
vendored
|
|
@ -38,11 +38,11 @@ jobs:
|
|||
run: |
|
||||
expires=$(curl -s http://tuf.fleetctl.com/timestamp.json | jq -r '.signed.expires' | cut -c 1-10)
|
||||
today=$(date "+%Y-%m-%d")
|
||||
tomorrow=$(date -d "$today + 1 day" "+%Y-%m-%d")
|
||||
warning_at=$(date -d "$today + 2 day" "+%Y-%m-%d")
|
||||
expires_sec=$(date -d "$expires" "+%s")
|
||||
tomorrow_sec=$(date -d "$tomorrow" "+%s")
|
||||
warning_at_sec=$(date -d "$warning_at" "+%s")
|
||||
|
||||
if [ "$expires_sec" -le "$tomorrow_sec" ]; then
|
||||
if [ "$expires_sec" -le "$warning_at_sec" ]; then
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ defaults:
|
|||
shell: bash
|
||||
|
||||
env:
|
||||
OSQUERY_VERSION: 5.12.0
|
||||
OSQUERY_VERSION: 5.12.1
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
|
|
|||
2
.github/workflows/test-packaging.yml
vendored
2
.github/workflows/test-packaging.yml
vendored
|
|
@ -85,7 +85,7 @@ jobs:
|
|||
- name: Install wine and wix
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: |
|
||||
./scripts/macos-install-wine.sh
|
||||
./scripts/macos-install-wine.sh -n
|
||||
wget https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip -nv -O wix.zip
|
||||
mkdir wix
|
||||
unzip wix.zip -d wix
|
||||
|
|
|
|||
|
|
@ -12,3 +12,8 @@ CVE-2020-7753
|
|||
# We feel like the risk of DoS using this technique, which requires being logged in, is low probability and low impact, as such we will not update glob-parent only for this CVE
|
||||
|
||||
CVE-2020-28469
|
||||
|
||||
# 2024/04/04 (github.com/goreleaser/nfpm/v2 should be updated)
|
||||
# When packaging linux files, we do not use global permissions. Manually verified that packed fleet-osquery files do not have group/global write permissions.
|
||||
|
||||
CVE-2023-32698
|
||||
|
|
|
|||
44
CHANGELOG.md
44
CHANGELOG.md
|
|
@ -1,3 +1,47 @@
|
|||
## Fleet 4.48.0 (Apr 03, 2024)
|
||||
|
||||
### Endpoint operations
|
||||
- Added integration with Google Calendar.
|
||||
* Fleet admins can enable Google Calendar integration by using a Google service account with domain-wide delegation.
|
||||
* Calendar integration is enabled at the team level for specific team policies.
|
||||
* If the policy is failing, a calendar event will be put on the host user's calendar for the 3rd Tuesday of the month.
|
||||
* During the event, Fleet will fire a webhook. IT admins should use this webhook to trigger a script or MDM command that will remediate the issue.
|
||||
- Reduced the number of 'Deadlock found' errors seen by the server when multiple hosts share the same UUID.
|
||||
- Removed outdated tooltips from UI.
|
||||
- Added hover states to clickable elements.
|
||||
- Added cross-platform check for duplicate MDM profiles names in batch set MDM profiles API.
|
||||
|
||||
### Device management (MDM)
|
||||
- Added Windows MDM support to the `osquery-perf` host-simulation command.
|
||||
- Added a missing database index to the MDM Windows enrollments table that will improve performance at scale.
|
||||
- Migrate MDM-related endpoints to new paths, deprecating (but still supporting indefinitely) the old endpoints.
|
||||
- Adds API functionality for creating DDM declarations, both individually and as a batch.
|
||||
- Added DDM activities to the fleet UI.
|
||||
- Added the `enable_release_device_manually` configuration setting for a team and no team. **Note** that the macOS automatic enrollment profile cannot set the `await_device_configured` option anymore, this setting is controlled by Fleet via the new `enable_release_device_manually` option.
|
||||
- Automatically release a macOS DEP-enrolled device after enrollment commands and profiles have been delivered, unless `enable_release_device_manually` is set to `true`.
|
||||
|
||||
### Vulnerability management
|
||||
- Added Visual Studio extensions to Fleet's software inventory.
|
||||
|
||||
### Bug fixes
|
||||
- Fixed a bug where valid MDM enrollments would show up as unmanaged (EnrollmentState 3).
|
||||
- Fixed flash message from closing when a modal closes.
|
||||
- Fixed a bug where OS version information would not get detected on Windows Server 2019.
|
||||
- Fixed issue where getting host details failed when attempting to read the host's BitLocker status from the datastore.
|
||||
- Fixed false negative vulnerabilities on macOS Homebrew python packages.
|
||||
- Fixed styling of live query disabled warning.
|
||||
- Fixed issue where Windows MDM profile processing was skipping `<Add>` commands.
|
||||
- Fixed UI's ability to bulk delete hosts when "All teams" is selected.
|
||||
- Fixed error state rendering on the global Host status expiry settings page, fix error state alignment for tooltip-wrapper field labels across organization settings.
|
||||
- Fixed `GET fleet/os_versions` and `GET fleet/os_versions/[id]` so team users no longer have access to os versions on hosts from other teams.
|
||||
- `fleetctl gitops` now batch processes queries and policies.
|
||||
- Fixed UI bug to render the query platform correctly for queries imported from the standard query library.
|
||||
- Fixed issue where Microsoft Edge was not reporting vulnerabilities.
|
||||
- Fixed a bug where all Windows MDM enrollments were detected as automatic.
|
||||
- Fixed a bug where `null` or excluded `smtp_settings` caused a UI 500.
|
||||
- Fixed query reports so they reset when there is a change to the selected platform or selected minimum osquery version.
|
||||
- Fixed live query sort of SQL result sort for both string and numerical columns.
|
||||
|
||||
## Fleet 4.47.3 (Mar 26, 2024)
|
||||
|
||||
### Bug fixes
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ go.mod @fleetdm/go
|
|||
#
|
||||
# (see website/config/custom.js for DRIs of other paths not listed here)
|
||||
##############################################################################################
|
||||
/handbook/company/pricing-features-table.yml @mikermcneil # « CEO is current DRI for features table
|
||||
/handbook/company/pricing-features-table.yml @noahtalerman # « Head of Product Design is current DRI for features table
|
||||
|
||||
##############################################################################################
|
||||
# 🦿 Repo automation and change control settings
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ The Fleet community is full of [kind and helpful people](https://fleetdm.com/han
|
|||
|
||||
The landscape of cybersecurity and IT is too complex. Let's open it up.
|
||||
|
||||
Contributions are welcome, whether you answer questions on [Slack](#chat) / [GitHub](https://github.com/fleetdm/fleet/issues) / [StackOverflow](https://stackoverflow.com/search?q=osquery) / [LinkedIn](https://linkedin.com/company/fleetdm) / [Twitter](https://twitter.com/fleetctl), improve the documentation or [website](./website), write a tutorial, give a talk at a conference or local meetup, give an [interview on a podcast](https://fleetdm.com/podcasts), troubleshoot reported issues, or [submit a patch](https://fleetdm.com/docs/contributing/contributing). The Fleet code of conduct is [on GitHub](https://github.com/fleetdm/fleet/blob/main/CODE_OF_CONDUCT.md).
|
||||
Contributions are welcome, whether you answer questions on [Slack](https://fleetdm.com/slack) / [GitHub](https://github.com/fleetdm/fleet/issues) / [StackOverflow](https://stackoverflow.com/search?q=osquery) / [LinkedIn](https://linkedin.com/company/fleetdm) / [Twitter](https://twitter.com/fleetctl), improve the documentation or [website](./website), write a tutorial, give a talk at a conference or local meetup, give an [interview on a podcast](https://fleetdm.com/podcasts), troubleshoot reported issues, or [submit a patch](https://fleetdm.com/docs/contributing/contributing). The Fleet code of conduct is [on GitHub](https://github.com/fleetdm/fleet/blob/main/CODE_OF_CONDUCT.md).
|
||||
|
||||
<!-- - Great contributions are motivated by real-world use cases or learning.
|
||||
- Some of the most valuable contributions might not touch any code at all.
|
||||
|
|
@ -81,7 +81,7 @@ Contributions are welcome, whether you answer questions on [Slack](#chat) / [Git
|
|||
To see what Fleet can do, head over to [fleetdm.com](https://fleetdm.com) and try it out for yourself, grab time with one of the maintainers to discuss, or visit the docs and roll it out to your organization.
|
||||
|
||||
#### Production deployment
|
||||
Fleet is simple enough to [spin up for yourself](https://fleetdm.com/docs/using-fleet/learn-how-to-use-fleet). Or you can have us [host it for you](https://fleetdm.com/pricing). Premium features are [available](https://fleetdm.com/pricing) either way.
|
||||
Fleet is simple enough to [spin up for yourself](https://fleetdm.com/docs/get-started/tutorials-and-guides). Or you can have us [host it for you](https://fleetdm.com/pricing). Premium features are [available](https://fleetdm.com/pricing) either way.
|
||||
|
||||
#### Documentation
|
||||
Complete documentation for Fleet can be found at [https://fleetdm.com/docs](https://fleetdm.com/docs).
|
||||
|
|
|
|||
105
articles/fleet-4.48.0.md
Normal file
105
articles/fleet-4.48.0.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# Fleet 4.48.0 | IdP local account creation, VS Code extensions.
|
||||
|
||||

|
||||
|
||||
Fleet 4.48.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.48.0) or continue reading to get the highlights.
|
||||
For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs.
|
||||
|
||||
## Highlights
|
||||
|
||||
* IdP local account creation
|
||||
* Software inventory includes VS Code extensions
|
||||
|
||||
|
||||
|
||||
### IdP local account creation
|
||||
|
||||
Local account creation with an Identity Provider (IdP) now prefills and locks local account creation with details sourced from the IdP. This feature streamlines the initial setup experience during the macOS out-of-the-box setup process by ensuring that the full name and account name fields are automatically populated with the user's IdP username. Additionally, it enforces password policies to be applied before the account creation is finalized, adding an extra layer of security and compliance to the enrollment process. This update simplifies the onboarding experience for end-users, allowing them to log into their Mac with their IdP credentials seamlessly. It reflects Fleet's commitment to enhancing device management efficiency and security. Fleet empowers IT administrators to ensure a consistent and secure user experience across their macOS fleet by focusing on automating and securing the setup process.
|
||||
|
||||
|
||||
### VS Code extensions
|
||||
|
||||
In addressing the need for comprehensive software inventory management, Fleet has expanded its inventory capabilities to include Visual Studio Code (VS Code) extensions. This addition enables IT and security teams to gain visibility into the VS Code extensions installed across their device fleet, offering a clearer view of their environments' development tools and resources. By surfacing VS Code extensions in the software inventory, Fleet allows for a more detailed assessment of the software landscape, facilitating better compliance, security assessments, and software management practices. This feature aligns with Fleet's commitment to providing detailed and actionable insights into the software ecosystem, supporting informed decision-making and proactive management of digital assets.
|
||||
|
||||
|
||||
|
||||
|
||||
## Changes
|
||||
|
||||
|
||||
### Endpoint operations
|
||||
- Added integration with Google Calendar.
|
||||
* Fleet admins can enable Google Calendar integration by using a Google service account with domain-wide delegation.
|
||||
* Calendar integration is enabled at the team level for specific team policies.
|
||||
* If the policy is failing, a calendar event will be put on the host user's calendar for the 3rd Tuesday of the month.
|
||||
* During the event, Fleet will fire a webhook. IT admins should use this webhook to trigger a script or MDM command that will remediate the issue.
|
||||
- Reduced the number of 'Deadlock found' errors seen by the server when multiple hosts share the same UUID.
|
||||
- Removed outdated tooltips from UI.
|
||||
- Added hover states to clickable elements.
|
||||
- Added cross-platform check for duplicate MDM profile names in batch set MDM profiles API.
|
||||
|
||||
### Device management (MDM)
|
||||
- Added Windows MDM support to the `osquery-perf` host-simulation command.
|
||||
- Added a missing database index to the MDM Windows enrollments table that will improve performance at scale.
|
||||
- Migrate MDM-related endpoints to new paths, deprecating (but still supporting indefinitely) the old endpoints.
|
||||
- Adds API functionality for creating DDM declarations, both individually and as a batch.
|
||||
- Added DDM activities to the fleet UI.
|
||||
- Added the `enable_release_device_manually` configuration setting for a team and no team. **Note** that the macOS automatic enrollment profile cannot set the `await_device_configured` option anymore, this setting is controlled by Fleet via the new `enable_release_device_manually` option.
|
||||
- Automatically release a macOS DEP-enrolled device after enrollment commands and profiles have been delivered, unless `enable_release_device_manually` is set to `true`.
|
||||
|
||||
### Vulnerability management
|
||||
- Added Visual Studio extensions to Fleet's software inventory.
|
||||
|
||||
### Bug fixes
|
||||
- Fixed a bug where valid MDM enrollments would show up as unmanaged (EnrollmentState 3).
|
||||
- Fixed flash message from closing when a modal closes.
|
||||
- Fixed a bug where OS version information would not get detected on Windows Server 2019.
|
||||
- Fixed issue where getting host details failed when attempting to read the host's BitLocker status from the datastore.
|
||||
- Fixed false negative vulnerabilities on macOS Homebrew python packages.
|
||||
- Fixed styling of live query disabled warning.
|
||||
- Fixed issue where Windows MDM profile processing was skipping `<Add>` commands.
|
||||
- Fixed UI's ability to bulk delete hosts when "All teams" is selected.
|
||||
- Fixed error state rendering on the global Host status expiry settings page, fix error state alignment for tooltip-wrapper field labels across organization settings.
|
||||
- Fixed `GET fleet/os_versions` and `GET fleet/os_versions/[id]` so team users no longer have access to os versions on hosts from other teams.
|
||||
- `fleetctl gitops` now batch processes queries and policies.
|
||||
- Fixed UI bug to render the query platform correctly for queries imported from the standard query library.
|
||||
- Fixed issue where Microsoft Edge was not reporting vulnerabilities.
|
||||
- Fixed a bug where all Windows MDM enrollments were detected as automatic.
|
||||
- Fixed a bug where `null` or excluded `smtp_settings` caused a UI 500.
|
||||
- Fixed query reports so they reset when there is a change to the selected platform or selected minimum osquery version.
|
||||
- Fixed live query sort of SQL result sort for both string and numerical columns.
|
||||
|
||||
## Fleet 4.47.3 (Mar 26, 2024)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed a bug where valid Windows MDM enrollments would show up as unmanaged (EnrollmentState 3).
|
||||
|
||||
## Fleet 4.47.2 (Mar 22, 2024)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed false negative vulnerabilities on macOS Homebrew Python packages.
|
||||
* Fixed policies to check "disable guest user".
|
||||
* Resolved the issue where Microsoft Edge was not reporting vulnerabilities.
|
||||
|
||||
## Fleet 4.47.1 (Mar 18, 2024)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Removed outdated tooltips from UI.
|
||||
* Fixed an issue with Windows MDM profile processing where `<Add>` commands were being skipped.
|
||||
* Team users no longer have access to OS versions on hosts from other teams for GET fleet/os_versions and GET fleet/os_versions/[id].
|
||||
* Reduced the number of 'Deadlock found' errors seen by the server when multiple hosts share the same UUID.
|
||||
|
||||
|
||||
## Ready to upgrade?
|
||||
|
||||
Visit our [Upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs for instructions on updating to Fleet 4.48.0.
|
||||
|
||||
<meta name="category" value="releases">
|
||||
<meta name="authorFullName" value="JD Strong">
|
||||
<meta name="authorGitHubUsername" value="spokanemac">
|
||||
<meta name="publishedOn" value="2024-04-03">
|
||||
<meta name="articleTitle" value="Fleet 4.48.0 | IdP local account creation, VS Code extensions.">
|
||||
<meta name="articleImageUrl" value="../website/assets/images/articles/fleet-4.48.0-1600x900@2x.png">
|
||||
62
articles/sysadmin-diaries-passcode-profiles.md
Normal file
62
articles/sysadmin-diaries-passcode-profiles.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# Sysadmin diaries: passcode profiles
|
||||
|
||||

|
||||
|
||||
Passcode MDM profiles do not work the way we might think they should. We recently onboarded a new Fleetie, which is always an opportunity to _eat our own dog food_ when using Fleet for device management.
|
||||
|
||||
This is the first in a series of things we encounter when managing our own devices (aka hosts). Fleet is an open-source and open-core company. Our [handbook](https://fleetdm.com/handbook) is public for everyone to view (and improve!). The [configuration policies](https://github.com/fleetdm/fleet-gitops) we apply to our devices reside in our public git repo. Today, we are looking at Fleet's password policy for macOS devices, which utilizes the [passcode policy payload](https://developer.apple.com/documentation/devicemanagement/passcode).
|
||||
|
||||
The user set up their new computer, created an account, and used a passcode that does not meet Fleet's passcode policy, namely a length of 10 characters. Sometime after that, the MDM profiles were delivered to the host. The expected behavior would be that the user would be prompted to enter a compliant passcode upon the next login.
|
||||
|
||||
What happens instead with this policy is that after login, the user is prompted with a "Password Policy Updated" notification.
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
This notification comes with the ability just to ignore it: Change Later or just dismiss the dialog.
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
A quick search of the [Mac Admins Slack](https://www.macadmins.org/) confirmed my suspicions. The non-compliant passcode will remain indefinitely, and the profile requirements are only enforced on the next reset or new account creation.
|
||||
|
||||
|
||||
### Why did this happen, and how do we solve it?
|
||||
|
||||
We discovered that the policy was not applied because Fleet needed to lock out account creation before all the policies had been successfully applied to the host. We have corrected this in [Fleet 4.48.0](https://fleetdm.com/releases/fleet-4.48.0), but how do we resolve this issue with an existing enrolled device and a change in the organization's password policy?
|
||||
|
||||
|
||||
Do we add the `changeAtNextAuth` key? A read of Apple's documentation means every user with this policy must reset their password on the next authentication. That could be highly disruptive. And, if the policy is redeployed for any reason, could institute a password reset to every host in that team.
|
||||
|
||||
|
||||
<blockquote purpose="large-quote">
|
||||
<code>changeAtNextAuth</code> (boolean)
|
||||
|
||||
If true, the system causes a password reset to occur the next time the user tries to authenticate. If this key is set in a device profile, the setting takes effect for all users, and admin authentications may fail until the admin user password is also reset. Available in macOS 10.13 and later.
|
||||
</blockquote>
|
||||
|
||||
Another solution is to use Fleet's remote script execution capability to trigger a one-off password reset on the host.
|
||||
|
||||
```
|
||||
pwpolicy -u "501" -setpolicy "newPasswordRequired=1"
|
||||
```
|
||||
|
||||
This will require the user to reset their password upon the next login to the host. This is likely the best solution in this situation, as it can be applied on an individual host basis.
|
||||
|
||||
In wrapping up this exploration into the intricacies of passcode profiles and their challenges, Fleet's open-source nature allows us to share these experiences and collectively seek solutions that enhance our understanding and implementation of device management policies. Let's continue the conversation. [Join us on Slack](https://fleetdm.com/support) and let us know how you might solve this issue and what device management problems you want to solve.
|
||||
|
||||
|
||||
|
||||
|
||||
<meta name="articleTitle" value="Sysadmin diaries: passcode profiles">
|
||||
<meta name="authorFullName" value="JD Strong">
|
||||
<meta name="authorGitHubUsername" value="spokanemac">
|
||||
<meta name="category" value="guides">
|
||||
<meta name="publishedOn" value="2024-04-01">
|
||||
<meta name="articleImageUrl" value="../website/assets/images/articles/sysadmin-diaries-1600x900@2x.png">
|
||||
<meta name="description" value="In this sysadmin diary, we explore a missapplied passcode policy.">
|
||||
1
changes/16951-improve-carve-request-timeout-error-code
Normal file
1
changes/16951-improve-carve-request-timeout-error-code
Normal file
|
|
@ -0,0 +1 @@
|
|||
* Made block_id mismatch errors more informative as 400s instead of 500s.
|
||||
1
changes/17194-fix-edge-cases-team-id-lost
Normal file
1
changes/17194-fix-edge-cases-team-id-lost
Normal file
|
|
@ -0,0 +1 @@
|
|||
Fleet UI: Fix edge cases of team ID being lost in various flows
|
||||
1
changes/17361-host-details-updates
Normal file
1
changes/17361-host-details-updates
Normal file
|
|
@ -0,0 +1 @@
|
|||
- UI: Surface fleet desktop and orbit version to the host details page
|
||||
1
changes/18044-calendar-webhook-retry
Normal file
1
changes/18044-calendar-webhook-retry
Normal file
|
|
@ -0,0 +1 @@
|
|||
Calendar webhook will retry if it receives response 429 Too Many Requests. Webhook request will retry for 30 minutes with a 1 minute max delay between retries.
|
||||
1
changes/18065-calendar-config-panic
Normal file
1
changes/18065-calendar-config-panic
Normal file
|
|
@ -0,0 +1 @@
|
|||
Fixing potential server panic when events are created with calendar integration, but then global calendar integration is disabled.
|
||||
1
changes/18083-no-values-in-host-details-query-reports
Normal file
1
changes/18083-no-values-in-host-details-query-reports
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Fix a bug where values were not being rendered in host-specific query reports.
|
||||
|
|
@ -8,7 +8,7 @@ version: v6.0.2
|
|||
home: https://github.com/fleetdm/fleet
|
||||
sources:
|
||||
- https://github.com/fleetdm/fleet.git
|
||||
appVersion: v4.47.3
|
||||
appVersion: v4.48.0
|
||||
dependencies:
|
||||
- name: mysql
|
||||
condition: mysql.enabled
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# All settings related to how Fleet is deployed in Kubernetes
|
||||
hostName: fleet.localhost
|
||||
replicas: 3 # The number of Fleet instances to deploy
|
||||
imageTag: v4.47.3 # Version of Fleet to deploy
|
||||
imageTag: v4.48.0 # 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:
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
configpkg "github.com/fleetdm/fleet/v4/server/config"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
licensectx "github.com/fleetdm/fleet/v4/server/contexts/license"
|
||||
"github.com/fleetdm/fleet/v4/server/cron"
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/cached_mysql"
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/mysqlredis"
|
||||
|
|
@ -773,9 +774,7 @@ the way that the Fleet server works.
|
|||
if license.IsPremium() {
|
||||
if err := cronSchedules.StartCronSchedule(
|
||||
func() (fleet.CronSchedule, error) {
|
||||
return newCalendarSchedule(
|
||||
ctx, instanceID, ds, logger,
|
||||
)
|
||||
return cron.NewCalendarSchedule(ctx, instanceID, ds, 5*time.Minute, logger)
|
||||
},
|
||||
); err != nil {
|
||||
initFatal(err, "failed to register calendar schedule")
|
||||
|
|
|
|||
|
|
@ -1457,9 +1457,9 @@ func TestGetQuery(t *testing.T) {
|
|||
Platform: "linux",
|
||||
Logging: "differential",
|
||||
}, nil
|
||||
} else {
|
||||
return nil, ¬FoundError{}
|
||||
}
|
||||
|
||||
return nil, ¬FoundError{}
|
||||
}
|
||||
|
||||
expectedYaml := `---
|
||||
|
|
|
|||
|
|
@ -879,9 +879,9 @@ spec:
|
|||
apiVersion: v1
|
||||
kind: policy
|
||||
spec:
|
||||
name: No 1Password emergency kit stored on desktop or in downloads (macOS)
|
||||
query: SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM file WHERE filename LIKE '%Emergency Kit%.pdf' AND (path LIKE '/Users/%%/Desktop/%%' OR path LIKE '/Users/%%/Documents/%%' OR path LIKE '/Users/%%/Downloads/%%' OR path LIKE '/Users/Shared'));
|
||||
description: "Looks for PDF files with file names typically used by 1Password for emergency recovery kits."
|
||||
name: No 1Password emergency kit stored in desktop, documents, or downloads folders (macOS)
|
||||
query: SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM file WHERE filename LIKE '%Emergency Kit%.pdf' AND (path LIKE '/Users/%/Desktop/%' OR path LIKE '/Users/%/Documents/%' OR path LIKE '/Users/%/Downloads/%' OR path LIKE '/Users/Shared/%'));
|
||||
description: "Looks for PDF files with file names typically used by 1Password for emergency recovery kits. To protect the performance of your devices, the search is one level deep and limited to the Desktop, Documents, Downloads, and Shared folders."
|
||||
resolution: "Delete 1Password emergency kits from your computer, and empty the trash. 1Password emergency kits should only be printed and stored in a physically secure location."
|
||||
platform: darwin
|
||||
tags: compliance, built-in
|
||||
|
|
|
|||
|
|
@ -906,7 +906,8 @@ None.
|
|||
"macos_setup": {
|
||||
"bootstrap_package": "",
|
||||
"enable_end_user_authentication": false,
|
||||
"macos_setup_assistant": "path/to/config.json"
|
||||
"macos_setup_assistant": "path/to/config.json",
|
||||
"enable_release_device_manually": true
|
||||
}
|
||||
},
|
||||
"agent_options": {
|
||||
|
|
@ -1509,7 +1510,7 @@ Delete all of a team's existing enroll secrets
|
|||
| email | string | body | **Required.** The email of the invited user. This email will receive the invitation link. |
|
||||
| name | string | body | **Required.** The name of the invited user. |
|
||||
| sso_enabled | boolean | body | **Required.** Whether or not SSO will be enabled for the invited user. |
|
||||
| teams | list | body | _Available in Fleet Premium_ A list of the teams the user is a member of. Each item includes the team's ID and the user's role in the specified team. |
|
||||
| teams | list | body | _Available in Fleet Premium_. A list of the teams the user is a member of. Each item includes the team's ID and the user's role in the specified team. |
|
||||
|
||||
#### Example
|
||||
|
||||
|
|
@ -1709,7 +1710,7 @@ Verify the specified invite.
|
|||
| email | string | body | The email of the invited user. Updates on the email won't resend the invitation. |
|
||||
| name | string | body | The name of the invited user. |
|
||||
| sso_enabled | boolean | body | Whether or not SSO will be enabled for the invited user. |
|
||||
| teams | list | body | _Available in Fleet Premium_ A list of the teams the user is a member of. Each item includes the team's ID and the user's role in the specified team. |
|
||||
| teams | list | body | _Available in Fleet Premium_. A list of the teams the user is a member of. Each item includes the team's ID and the user's role in the specified team. |
|
||||
|
||||
#### Example
|
||||
|
||||
|
|
@ -1896,10 +1897,10 @@ the `software` table.
|
|||
| mdm_enrollment_status | string | query | The _mobile device management_ (MDM) enrollment status to filter hosts by. Valid options are 'manual', 'automatic', 'enrolled', 'pending', or 'unenrolled'. |
|
||||
| macos_settings | string | query | Filters the hosts by the status of the _mobile device management_ (MDM) profiles applied to hosts. Valid options are 'verified', 'verifying', 'pending', or 'failed'. **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.** |
|
||||
| munki_issue_id | integer | query | The ID of the _munki issue_ (a Munki-reported error or warning message) to filter hosts by (that is, filter hosts that are affected by that corresponding error or warning message). |
|
||||
| low_disk_space | integer | query | _Available in Fleet Premium_ Filters the hosts to only include hosts with less GB of disk space available than this value. Must be a number between 1-100. |
|
||||
| low_disk_space | integer | query | _Available in Fleet Premium_. Filters the hosts to only include hosts with less GB of disk space available than this value. Must be a number between 1-100. |
|
||||
| disable_failing_policies| boolean | query | If `true`, hosts will return failing policies as 0 regardless of whether there are any that failed for the host. This is meant to be used when increased performance is needed in exchange for the extra information. |
|
||||
| macos_settings_disk_encryption | string | query | Filters the hosts by the status of the macOS disk encryption MDM profile on the host. Valid options are 'verified', 'verifying', 'action_required', 'enforcing', 'failed', or 'removing_enforcement'. |
|
||||
| bootstrap_package | string | query | _Available in Fleet Premium_ Filters the hosts by the status of the MDM bootstrap package on the host. Valid options are 'installed', 'pending', or 'failed'. |
|
||||
| bootstrap_package | string | query | _Available in Fleet Premium_. Filters the hosts by the status of the MDM bootstrap package on the host. Valid options are 'installed', 'pending', or 'failed'. |
|
||||
| os_settings | string | query | Filters the hosts by the status of the operating system settings applied to the hosts. Valid options are 'verified', 'verifying', 'pending', or 'failed'. **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.** |
|
||||
| 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. |
|
||||
|
|
@ -2115,7 +2116,7 @@ Response payload with the `munki_issue_id` filter provided:
|
|||
| after | string | query | The value to get results after. This needs `order_key` defined, as that's the column that would be used. |
|
||||
| status | string | query | Indicates the status of the hosts to return. Can either be 'new', 'online', 'offline', 'mia' or 'missing'. |
|
||||
| query | string | query | Search query keywords. Searchable fields include `hostname`, `hardware_serial`, `uuid`, `ipv4` and the hosts' email addresses (only searched if the query looks like an email address, i.e. contains an '@', no space, etc.). |
|
||||
| team_id | integer | query | _Available in Fleet Premium_ Filters the hosts to only include hosts in the specified team. |
|
||||
| team_id | integer | query | _Available in Fleet Premium_. Filters the hosts to only include hosts in the specified team. |
|
||||
| policy_id | integer | query | The ID of the policy to filter hosts by. |
|
||||
| policy_response | string | query | **Requires `policy_id`**. Valid options are 'passing' or 'failing'. |
|
||||
| software_version_id | integer | query | The ID of the software version to filter hosts by. |
|
||||
|
|
@ -2130,9 +2131,9 @@ Response payload with the `munki_issue_id` filter provided:
|
|||
| mdm_enrollment_status | string | query | The _mobile device management_ (MDM) enrollment status to filter hosts by. Valid options are 'manual', 'automatic', 'enrolled', 'pending', or 'unenrolled'. |
|
||||
| macos_settings | string | query | Filters the hosts by the status of the _mobile device management_ (MDM) profiles applied to hosts. Valid options are 'verified', 'verifying', 'pending', or 'failed'. **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.** |
|
||||
| munki_issue_id | integer | query | The ID of the _munki issue_ (a Munki-reported error or warning message) to filter hosts by (that is, filter hosts that are affected by that corresponding error or warning message). |
|
||||
| low_disk_space | integer | query | _Available in Fleet Premium_ Filters the hosts to only include hosts with less GB of disk space available than this value. Must be a number between 1-100. |
|
||||
| low_disk_space | integer | query | _Available in Fleet Premium_. Filters the hosts to only include hosts with less GB of disk space available than this value. Must be a number between 1-100. |
|
||||
| macos_settings_disk_encryption | string | query | Filters the hosts by the status of the macOS disk encryption MDM profile on the host. Valid options are 'verified', 'verifying', 'action_required', 'enforcing', 'failed', or 'removing_enforcement'. |
|
||||
| bootstrap_package | string | query | _Available in Fleet Premium_ Filters the hosts by the status of the MDM bootstrap package on the host. Valid options are 'installed', 'pending', or 'failed'. **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.** |
|
||||
| bootstrap_package | string | query | _Available in Fleet Premium_. Filters the hosts by the status of the MDM bootstrap package on the host. Valid options are 'installed', 'pending', or 'failed'. **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.** |
|
||||
| os_settings | string | query | Filters the hosts by the status of the operating system settings applied to the hosts. Valid options are 'verified', 'verifying', 'pending', or 'failed'. **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.** |
|
||||
| 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.** |
|
||||
|
||||
|
|
@ -3289,7 +3290,7 @@ Retrieves MDM enrollment summary. Windows servers are excluded from the aggregat
|
|||
|
||||
| Name | Type | In | Description |
|
||||
| -------- | ------- | ----- | -------------------------------------------------------------------------------- |
|
||||
| team_id | integer | query | _Available in Fleet Premium_ Filter by team |
|
||||
| team_id | integer | query | _Available in Fleet Premium_. Filter by team |
|
||||
| platform | string | query | Filter by platform ("windows" or "darwin") |
|
||||
|
||||
A `team_id` of `0` returns the statistics for hosts that are not part of any team. A `null` or missing `team_id` returns statistics for all hosts regardless of the team.
|
||||
|
|
@ -3399,7 +3400,7 @@ Retrieves aggregated host's MDM enrollment status and Munki versions.
|
|||
|
||||
| Name | Type | In | Description |
|
||||
| ------- | ------- | ----- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| team_id | integer | query | _Available in Fleet Premium_ Filters the aggregate host information to only include hosts in the specified team. | |
|
||||
| team_id | integer | query | _Available in Fleet Premium_. Filters the aggregate host information to only include hosts in the specified team. | |
|
||||
|
||||
A `team_id` of `0` returns the statistics for hosts that are not part of any team. A `null` or missing `team_id` returns statistics for all hosts regardless of the team.
|
||||
|
||||
|
|
@ -3680,7 +3681,7 @@ requested by a web browser.
|
|||
| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include 'asc' and 'desc'. Default is 'asc'. |
|
||||
| status | string | query | Indicates the status of the hosts to return. Can either be 'new', 'online', 'offline', 'mia' or 'missing'. |
|
||||
| query | string | query | Search query keywords. Searchable fields include `hostname`, `hardware_serial`, `uuid`, `ipv4` and the hosts' email addresses (only searched if the query looks like an email address, i.e. contains an `@`, no space, etc.). |
|
||||
| team_id | integer | query | _Available in Fleet Premium_ Filters the hosts to only include hosts in the specified team. |
|
||||
| team_id | integer | query | _Available in Fleet Premium_. Filters the hosts to only include hosts in the specified team. |
|
||||
| policy_id | integer | query | The ID of the policy to filter hosts by. |
|
||||
| policy_response | string | query | **Requires `policy_id`**. Valid options are 'passing' or 'failing'. **Note: If `policy_id` is specified _without_ including `policy_response`, this will also return hosts where the policy is not configured to run or failed to run.** |
|
||||
| software_version_id | integer | query | The ID of the software version to filter hosts by. |
|
||||
|
|
@ -3694,9 +3695,9 @@ requested by a web browser.
|
|||
| mdm_enrollment_status | string | query | The _mobile device management_ (MDM) enrollment status to filter hosts by. Valid options are 'manual', 'automatic', 'enrolled', 'pending', or 'unenrolled'. |
|
||||
| macos_settings | string | query | Filters the hosts by the status of the _mobile device management_ (MDM) profiles applied to hosts. Valid options are 'verified', 'verifying', 'pending', or 'failed'. **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.** |
|
||||
| munki_issue_id | integer | query | The ID of the _munki issue_ (a Munki-reported error or warning message) to filter hosts by (that is, filter hosts that are affected by that corresponding error or warning message). |
|
||||
| low_disk_space | integer | query | _Available in Fleet Premium_ Filters the hosts to only include hosts with less GB of disk space available than this value. Must be a number between 1-100. |
|
||||
| low_disk_space | integer | query | _Available in Fleet Premium_. Filters the hosts to only include hosts with less GB of disk space available than this value. Must be a number between 1-100. |
|
||||
| label_id | integer | query | A valid label ID. Can only be used in combination with `order_key`, `order_direction`, `status`, `query` and `team_id`. |
|
||||
| bootstrap_package | string | query | _Available in Fleet Premium_ Filters the hosts by the status of the MDM bootstrap package on the host. Valid options are 'installed', 'pending', or 'failed'. **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.** |
|
||||
| bootstrap_package | string | query | _Available in Fleet Premium_. Filters the hosts by the status of the MDM bootstrap package on the host. Valid options are 'installed', 'pending', or 'failed'. **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.** |
|
||||
| disable_failing_policies | boolean | query | If `true`, hosts will return failing policies as 0 (returned as the `issues` column) regardless of whether there are any that failed for the host. This is meant to be used when increased performance is needed in exchange for the extra information. |
|
||||
|
||||
If `mdm_id`, `mdm_name` or `mdm_enrollment_status` is specified, then Windows Servers are excluded from the results.
|
||||
|
|
@ -4424,15 +4425,15 @@ Returns a list of the hosts that belong to the specified label.
|
|||
| after | string | query | The value to get results after. This needs `order_key` defined, as that's the column that would be used. |
|
||||
| status | string | query | Indicates the status of the hosts to return. Can either be 'new', 'online', 'offline', 'mia' or 'missing'. |
|
||||
| query | string | query | Search query keywords. Searchable fields include `hostname`, `hardware_serial`, `uuid`, and `ipv4`. |
|
||||
| team_id | integer | query | _Available in Fleet Premium_ Filters the hosts to only include hosts in the specified team. |
|
||||
| team_id | integer | query | _Available in Fleet Premium_. Filters the hosts to only include hosts in the specified team. |
|
||||
| disable_failing_policies | boolean | query | If "true", hosts will return failing policies as 0 regardless of whether there are any that failed for the host. This is meant to be used when increased performance is needed in exchange for the extra information. |
|
||||
| mdm_id | integer | query | The ID of the _mobile device management_ (MDM) solution to filter hosts by (that is, filter hosts that use a specific MDM provider and URL). |
|
||||
| mdm_name | string | query | The name of the _mobile device management_ (MDM) solution to filter hosts by (that is, filter hosts that use a specific MDM provider). |
|
||||
| mdm_enrollment_status | string | query | The _mobile device management_ (MDM) enrollment status to filter hosts by. Valid options are 'manual', 'automatic', 'enrolled', 'pending', or 'unenrolled'. |
|
||||
| macos_settings | string | query | Filters the hosts by the status of the _mobile device management_ (MDM) profiles applied to hosts. Valid options are 'verified', 'verifying', 'pending', or 'failed'. **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.** |
|
||||
| low_disk_space | integer | query | _Available in Fleet Premium_ Filters the hosts to only include hosts with less GB of disk space available than this value. Must be a number between 1-100. |
|
||||
| low_disk_space | integer | query | _Available in Fleet Premium_. Filters the hosts to only include hosts with less GB of disk space available than this value. Must be a number between 1-100. |
|
||||
| macos_settings_disk_encryption | string | query | Filters the hosts by the status of the macOS disk encryption MDM profile on the host. Valid options are 'verified', 'verifying', 'action_required', 'enforcing', 'failed', or 'removing_enforcement'. |
|
||||
| bootstrap_package | string | query | _Available in Fleet Premium_ Filters the hosts by the status of the MDM bootstrap package on the host. Valid options are 'installed', 'pending', or 'failed'. **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.** |
|
||||
| bootstrap_package | string | query | _Available in Fleet Premium_. Filters the hosts by the status of the MDM bootstrap package on the host. Valid options are 'installed', 'pending', or 'failed'. **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.** |
|
||||
| os_settings | string | query | Filters the hosts by the status of the operating system settings applied to the hosts. Valid options are 'verified', 'verifying', 'pending', or 'failed'. **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.** |
|
||||
| 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.** |
|
||||
|
||||
|
|
@ -4589,8 +4590,8 @@ Add a configuration profile to enforce custom settings on macOS and Windows host
|
|||
| Name | Type | In | Description |
|
||||
| ------------------------- | -------- | ---- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| profile | file | form | **Required.** The .mobileconfig (macOS) or XML (Windows) file containing the profile. |
|
||||
| team_id | string | form | _Available in Fleet Premium_ The team ID for the profile. If specified, the profile is applied to only hosts that are assigned to the specified team. If not specified, the profile is applied to only to hosts that are not assigned to any team. |
|
||||
| labels | array | form | _Available in Fleet Premium_ An array of labels to filter hosts in a team (or no team) that should get a profile. |
|
||||
| team_id | string | form | _Available in Fleet Premium_. The team ID for the profile. If specified, the profile is applied to only hosts that are assigned to the specified team. If not specified, the profile is applied to only to hosts that are not assigned to any team. |
|
||||
| labels | array | form | _Available in Fleet Premium_. An array of labels to filter hosts in a team (or no team) that should get a profile. |
|
||||
|
||||
#### Example
|
||||
|
||||
|
|
@ -4700,7 +4701,7 @@ results (i.e., only profiles that are associated with "No team" are listed).
|
|||
|
||||
| Name | Type | In | Description |
|
||||
| ------------------------- | ------ | ----- | ------------------------------------------------------------------------- |
|
||||
| team_id | string | query | _Available in Fleet Premium_ The team id to filter profiles. |
|
||||
| team_id | string | query | _Available in Fleet Premium_. The team id to filter profiles. |
|
||||
| page | integer | query | Page number of the results to fetch. |
|
||||
| per_page | integer | query | Results per page. |
|
||||
|
||||
|
|
@ -4900,7 +4901,7 @@ The summary can optionally be filtered by team ID.
|
|||
|
||||
| Name | Type | In | Description |
|
||||
| ------------------------- | ------ | ----- | ------------------------------------------------------------------------- |
|
||||
| team_id | string | query | _Available in Fleet Premium_ The team ID to filter the summary. |
|
||||
| team_id | string | query | _Available in Fleet Premium_. The team ID to filter the summary. |
|
||||
|
||||
#### Example
|
||||
|
||||
|
|
@ -4938,7 +4939,7 @@ optionally be filtered by `team_id`. If no `team_id` is specified, team profiles
|
|||
|
||||
| Name | Type | In | Description |
|
||||
| ------------------------- | ------ | ----- | ------------------------------------------------------------------------- |
|
||||
| team_id | string | query | _Available in Fleet Premium_ The team ID to filter profiles. |
|
||||
| team_id | string | query | _Available in Fleet Premium_. The team ID to filter profiles. |
|
||||
|
||||
#### Example
|
||||
|
||||
|
|
@ -5874,7 +5875,7 @@ Where `query_id` references an existing `query`.
|
|||
| description | string | body | The query's description. |
|
||||
| resolution | string | body | The resolution steps for the policy. |
|
||||
| platform | string | body | Comma-separated target platforms, currently supported values are "windows", "linux", "darwin". The default, an empty string means target all platforms. |
|
||||
| critical | boolean | body | _Available in Fleet Premium_ Mark policy as critical/high impact. |
|
||||
| critical | boolean | body | _Available in Fleet Premium_. Mark policy as critical/high impact. |
|
||||
|
||||
#### Example
|
||||
|
||||
|
|
@ -6137,7 +6138,7 @@ The semantics for creating a team policy are the same as for global policies, se
|
|||
| resolution | string | body | The resolution steps for the policy. |
|
||||
| query_id | integer | body | An existing query's ID (legacy). |
|
||||
| platform | string | body | Comma-separated target platforms, currently supported values are "windows", "linux", "darwin". The default, an empty string means target all platforms. |
|
||||
| critical | boolean | body | _Available in Fleet Premium_ Mark policy as critical/high impact. |
|
||||
| critical | boolean | body | _Available in Fleet Premium_. Mark policy as critical/high impact. |
|
||||
|
||||
Either `query` or `query_id` must be provided.
|
||||
|
||||
|
|
@ -6233,7 +6234,7 @@ Either `query` or `query_id` must be provided.
|
|||
| description | string | body | The query's description. |
|
||||
| resolution | string | body | The resolution steps for the policy. |
|
||||
| platform | string | body | Comma-separated target platforms, currently supported values are "windows", "linux", "darwin". The default, an empty string means target all platforms. |
|
||||
| critical | boolean | body | _Available in Fleet Premium_ Mark policy as critical/high impact. |
|
||||
| critical | boolean | body | _Available in Fleet Premium_. Mark policy as critical/high impact. |
|
||||
|
||||
#### Example
|
||||
|
||||
|
|
@ -7732,7 +7733,7 @@ Get a list of all software.
|
|||
| order_key | string | query | What to order results by. Allowed fields are `name` and `hosts_count`. Default is `hosts_count` (descending). |
|
||||
| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include `asc` and `desc`. Default is `asc`. |
|
||||
| query | string | query | Search query keywords. Searchable fields include `title` and `cve`. |
|
||||
| team_id | integer | query | _Available in Fleet Premium_ Filters the software to only include the software installed on the hosts that are assigned to the specified team. |
|
||||
| team_id | integer | query | _Available in Fleet Premium_. Filters the software to only include the software installed on the hosts that are assigned to the specified team. |
|
||||
| vulnerable | bool | query | If true or 1, only list software that has detected vulnerabilities. Default is `false`. |
|
||||
|
||||
#### Example
|
||||
|
|
@ -7841,7 +7842,7 @@ Get a list of all software versions.
|
|||
| order_key | string | query | What to order results by. Allowed fields are `name`, `hosts_count`, `cve_published`, `cvss_score`, `epss_probability` and `cisa_known_exploit`. Default is `hosts_count` (descending). |
|
||||
| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include `asc` and `desc`. Default is `asc`. |
|
||||
| query | string | query | Search query keywords. Searchable fields include `name`, `version`, and `cve`. |
|
||||
| team_id | integer | query | _Available in Fleet Premium_ Filters the software to only include the software installed on the hosts that are assigned to the specified team. |
|
||||
| team_id | integer | query | _Available in Fleet Premium_. Filters the software to only include the software installed on the hosts that are assigned to the specified team. |
|
||||
| vulnerable | bool | query | If true or 1, only list software that has detected vulnerabilities. Default is `false`. |
|
||||
|
||||
#### Example
|
||||
|
|
@ -8024,7 +8025,7 @@ Retrieves a list of all CVEs affecting software and/or OS versions.
|
|||
|
||||
| Name | Type | In | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| team_id | integer | query | _Available in Fleet Premium_ Filters only include vulnerabilities affecting the specified team. |
|
||||
| team_id | integer | query | _Available in Fleet Premium_. Filters only include vulnerabilities affecting the specified team. |
|
||||
| page | integer | query | Page number of the results to fetch. |
|
||||
| per_page | integer | query | Results per page. |
|
||||
| order_key | string | query | What to order results by. Allowed fields are: `cve`, `cvss_score`, `epss_probability`, `cve_published`, `created_at`, and `host_count`. Default is `created_at` (descending). |
|
||||
|
|
@ -9027,7 +9028,7 @@ Returns a list of all enabled users
|
|||
| page | integer | query | Page number of the results to fetch. |
|
||||
| query | string | query | Search query keywords. Searchable fields include `name` and `email`. |
|
||||
| per_page | integer | query | Results per page. |
|
||||
| team_id | integer | query | _Available in Fleet Premium_ Filters the users to only include users in the specified team. |
|
||||
| team_id | integer | query | _Available in Fleet Premium_. Filters the users to only include users in the specified team. |
|
||||
|
||||
#### Example
|
||||
|
||||
|
|
@ -9101,7 +9102,7 @@ Creates a user account after an invited user provides registration information a
|
|||
| password | string | body | The password chosen by the user (if not SSO user). |
|
||||
| password_confirmation | string | body | Confirmation of the password chosen by the user. |
|
||||
| global_role | string | body | The role assigned to the user. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). In Fleet 4.30.0 and 4.31.0, the `observer_plus` and `gitops` roles were introduced respectively. If `global_role` is specified, `teams` cannot be specified. For more information, see [manage access](https://fleetdm.com/docs/using-fleet/manage-access). |
|
||||
| teams | array | body | _Available in Fleet Premium_ The teams and respective roles assigned to the user. Should contain an array of objects in which each object includes the team's `id` and the user's `role` on each team. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). In Fleet 4.30.0 and 4.31.0, the `observer_plus` and `gitops` roles were introduced respectively. If `teams` is specified, `global_role` cannot be specified. For more information, see [manage access](https://fleetdm.com/docs/using-fleet/manage-access). |
|
||||
| teams | array | body | _Available in Fleet Premium_. The teams and respective roles assigned to the user. Should contain an array of objects in which each object includes the team's `id` and the user's `role` on each team. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). In Fleet 4.30.0 and 4.31.0, the `observer_plus` and `gitops` roles were introduced respectively. If `teams` is specified, `global_role` cannot be specified. For more information, see [manage access](https://fleetdm.com/docs/using-fleet/manage-access). |
|
||||
|
||||
#### Example
|
||||
|
||||
|
|
@ -9219,7 +9220,7 @@ By default, the user will be forced to reset its password upon first login.
|
|||
| api_only | boolean | body | User is an "API-only" user (cannot use web UI) if true. |
|
||||
| global_role | string | body | The role assigned to the user. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). In Fleet 4.30.0 and 4.31.0, the `observer_plus` and `gitops` roles were introduced respectively. If `global_role` is specified, `teams` cannot be specified. For more information, see [manage access](https://fleetdm.com/docs/using-fleet/manage-access). |
|
||||
| admin_forced_password_reset | boolean | body | Sets whether the user will be forced to reset its password upon first login (default=true) |
|
||||
| teams | array | body | _Available in Fleet Premium_ The teams and respective roles assigned to the user. Should contain an array of objects in which each object includes the team's `id` and the user's `role` on each team. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). In Fleet 4.30.0 and 4.31.0, the `observer_plus` and `gitops` roles were introduced respectively. If `teams` is specified, `global_role` cannot be specified. For more information, see [manage access](https://fleetdm.com/docs/using-fleet/manage-access). |
|
||||
| teams | array | body | _Available in Fleet Premium_. The teams and respective roles assigned to the user. Should contain an array of objects in which each object includes the team's `id` and the user's `role` on each team. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). In Fleet 4.30.0 and 4.31.0, the `observer_plus` and `gitops` roles were introduced respectively. If `teams` is specified, `global_role` cannot be specified. For more information, see [manage access](https://fleetdm.com/docs/using-fleet/manage-access). |
|
||||
|
||||
#### Example
|
||||
|
||||
|
|
@ -9372,7 +9373,7 @@ Returns all information about a specific user.
|
|||
| password | string | body | The user's current password, required to change the user's own email or password (not required for an admin to modify another user). |
|
||||
| new_password| string | body | The user's new password. |
|
||||
| global_role | string | body | The role assigned to the user. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). If `global_role` is specified, `teams` cannot be specified. |
|
||||
| teams | array | body | _Available in Fleet Premium_ The teams and respective roles assigned to the user. Should contain an array of objects in which each object includes the team's `id` and the user's `role` on each team. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). If `teams` is specified, `global_role` cannot be specified. |
|
||||
| teams | array | body | _Available in Fleet Premium_. The teams and respective roles assigned to the user. Should contain an array of objects in which each object includes the team's `id` and the user's `role` on each team. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). If `teams` is specified, `global_role` cannot be specified. |
|
||||
|
||||
#### Example
|
||||
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ so:
|
|||
```
|
||||
If the provided path doesn't contain all 3 binaries, the command will fail.
|
||||
|
||||
>**Note:** Creating a fleetd agent for Windows (.msi) on macOS also requires Wine. To install Wine see the script [here](https://github.com/fleetdm/fleet/blob/fleet-v4.44.0/scripts/macos-install-wine.sh).
|
||||
>**Note:** Creating a fleetd agent for Windows (.msi) on macOS also requires Wine. To install Wine see the script [here](https://fleetdm.com/install-wine).
|
||||
|
||||
### Experimental features
|
||||
|
||||
|
|
|
|||
|
|
@ -764,8 +764,8 @@ func (p *passphraseHandler) checkPassphrase(store tuf.LocalStore, role string) e
|
|||
continue
|
||||
} else if len(keys) == 0 {
|
||||
return fmt.Errorf("%s key not found", role)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,3 +93,14 @@ func ClearMockEvents() {
|
|||
defer mu.Unlock()
|
||||
mockEvents = make(map[string]*calendar.Event)
|
||||
}
|
||||
|
||||
func SetMockEventsToNow() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
for _, mockEvent := range mockEvents {
|
||||
mockEvent.Start = &calendar.EventDateTime{DateTime: now.Format(time.RFC3339)}
|
||||
mockEvent.End = &calendar.EventDateTime{DateTime: now.Add(30 * time.Minute).Format(time.RFC3339)}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ const (
|
|||
// These are just example keys generated locally with openssl. They are intentionally published
|
||||
// and should never be used in production.
|
||||
exampleKeyPassphrase = "password"
|
||||
exampleKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
|
||||
// #nosec G101
|
||||
exampleKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,5F4D77F29A9E2675
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { IHost } from "interfaces/host";
|
||||
import { IHost, IHostResponse } from "interfaces/host";
|
||||
import { IHostMdmProfile } from "interfaces/mdm";
|
||||
|
||||
const DEFAULT_HOST_PROFILE_MOCK: IHostMdmProfile = {
|
||||
|
|
@ -34,6 +34,8 @@ const DEFAULT_HOST_MOCK: IHost = {
|
|||
uuid: "09b244f8-0000-0000-b5cc-791a15f11073",
|
||||
platform: "ubuntu",
|
||||
osquery_version: "4.9.0",
|
||||
orbit_version: "1.22.0",
|
||||
fleet_desktop_version: "1.22.0",
|
||||
os_version: "Ubuntu 18.4.0",
|
||||
build: "",
|
||||
platform_like: "debian",
|
||||
|
|
@ -101,4 +103,6 @@ const createMockHost = (overrides?: Partial<IHost>): IHost => {
|
|||
return { ...DEFAULT_HOST_MOCK, ...overrides };
|
||||
};
|
||||
|
||||
export const createMockHostResponse = { host: createMockHost() };
|
||||
|
||||
export default createMockHost;
|
||||
|
|
|
|||
|
|
@ -138,6 +138,24 @@ We tend to use explicit assignment of prop values, instead of object spread synt
|
|||
```
|
||||
<ExampleComponent prop1={pop1Val} prop2={prop2Val} prop3={prop3Val} />
|
||||
```
|
||||
|
||||
### Naming handlers
|
||||
When defining component props for handlers, we prefer naming with a more general `onAction`. When
|
||||
naming the handler passed into that prop or used in the same component it's defined, we prefer
|
||||
either the same `onAction` or, if useful, a more specific `onMoreSpecifiedAction`. E.g.:
|
||||
|
||||
```tsx
|
||||
<BigSecretComponent
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
```
|
||||
or
|
||||
```tsx
|
||||
<BigSecretComponent
|
||||
onSubmit={onUpdateBigSecret}
|
||||
/>
|
||||
```
|
||||
|
||||
### Page component pattern
|
||||
|
||||
When creating a **top level page** (e.g. dashboard page, hosts page, policies page)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ export default PropTypes.shape({
|
|||
uuid: PropTypes.string,
|
||||
platform: PropTypes.string,
|
||||
osquery_version: PropTypes.string,
|
||||
orbit_version: PropTypes.string,
|
||||
fleet_desktop_version: PropTypes.string,
|
||||
os_version: PropTypes.string,
|
||||
build: PropTypes.string,
|
||||
platform_like: PropTypes.string,
|
||||
|
|
@ -267,6 +269,8 @@ export interface IHost {
|
|||
uuid: string;
|
||||
platform: string;
|
||||
osquery_version: string;
|
||||
orbit_version?: string;
|
||||
fleet_desktop_version?: string;
|
||||
os_version: string;
|
||||
build: string;
|
||||
platform_like: string; // TODO: replace with more specific union type
|
||||
|
|
|
|||
|
|
@ -137,6 +137,9 @@ const ManageHostsPage = ({
|
|||
isFreeTier,
|
||||
isSandboxMode,
|
||||
setFilteredHostsPath,
|
||||
setFilteredPoliciesPath,
|
||||
setFilteredQueriesPath,
|
||||
setFilteredSoftwarePath,
|
||||
} = useContext(AppContext);
|
||||
const { renderFlash } = useContext(NotificationContext);
|
||||
|
||||
|
|
@ -886,6 +889,11 @@ const ManageHostsPage = ({
|
|||
// tableQueryData)
|
||||
handleTeamChange(teamId);
|
||||
handleResetPageIndex();
|
||||
// Must clear other page paths or the team might accidentally switch
|
||||
// When navigating from host details
|
||||
setFilteredSoftwarePath("");
|
||||
setFilteredQueriesPath("");
|
||||
setFilteredPoliciesPath("");
|
||||
},
|
||||
[handleTeamChange]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ import {
|
|||
HOST_ABOUT_DATA,
|
||||
HOST_OSQUERY_DATA,
|
||||
} from "utilities/constants";
|
||||
import { createMockHostMdmProfile } from "__mocks__/hostMock";
|
||||
|
||||
import Spinner from "components/Spinner";
|
||||
import TabsWrapper from "components/TabsWrapper";
|
||||
|
|
@ -147,6 +146,7 @@ const HostDetailsPage = ({
|
|||
isSandboxMode,
|
||||
isOnlyObserver,
|
||||
filteredHostsPath,
|
||||
currentTeam,
|
||||
} = useContext(AppContext);
|
||||
const { setSelectedQueryTargetsByType } = useContext(QueryContext);
|
||||
const { renderFlash } = useContext(NotificationContext);
|
||||
|
|
@ -567,7 +567,8 @@ const HostDetailsPage = ({
|
|||
const onQueryHostCustom = () => {
|
||||
setSelectedQueryTargetsByType(DEFAULT_TARGETS_BY_TYPE);
|
||||
router.push(
|
||||
PATHS.NEW_QUERY() + TAGGED_TEMPLATES.queryByHostRoute(host?.id)
|
||||
PATHS.NEW_QUERY() +
|
||||
TAGGED_TEMPLATES.queryByHostRoute(host?.id, currentTeam?.id)
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -575,7 +576,7 @@ const HostDetailsPage = ({
|
|||
setSelectedQueryTargetsByType(DEFAULT_TARGETS_BY_TYPE);
|
||||
router.push(
|
||||
PATHS.EDIT_QUERY(selectedQuery.id) +
|
||||
TAGGED_TEMPLATES.queryByHostRoute(host?.id)
|
||||
TAGGED_TEMPLATES.queryByHostRoute(host?.id, currentTeam?.id)
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -141,8 +141,8 @@ const RunScriptModal = ({
|
|||
{!isLoading && isError && <DataError />}
|
||||
{!isLoading && !isError && (!tableData || tableData.length === 0) && (
|
||||
<EmptyTable
|
||||
header="No scripts are available for this host"
|
||||
info="Expecting to see scripts? Try selecting “Refetch” to ask the host to report new vitals."
|
||||
header="No scripts available for this host"
|
||||
info="Expecting to see scripts? Close this modal and try again."
|
||||
/>
|
||||
)}
|
||||
{!isLoading && !isError && tableData && tableData.length > 0 && (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
import React from "react";
|
||||
|
||||
import { render, screen } from "@testing-library/react";
|
||||
|
||||
import HQRTable, { IHQRTable } from "./HQRTable";
|
||||
|
||||
describe("HQRTable component", () => {
|
||||
it("Renders results normally when they are present", () => {
|
||||
const testData: IHQRTable[] = [
|
||||
{
|
||||
queryName: "testQuery0",
|
||||
queryDescription: "testDescription0",
|
||||
hostName: "testHost0",
|
||||
rows: [
|
||||
{
|
||||
build_distro: "10.14",
|
||||
build_platform: "darwin",
|
||||
config_hash: "111111111111111111111111",
|
||||
config_valid: "1",
|
||||
extensions: "active",
|
||||
instance_id: "2f7a7b8e-8f35-4fa8-9e8b-1111111111111111",
|
||||
pid: "575",
|
||||
platform_mask: "21",
|
||||
start_time: "1711512878",
|
||||
uuid: "gggggg-4568-5BD9-9F1C-6D2E701FAB5C",
|
||||
version: "5.11.0",
|
||||
watcher: "574",
|
||||
},
|
||||
],
|
||||
reportClipped: false,
|
||||
lastFetched: "2021-09-01T00:00:00Z",
|
||||
onShowQuery: jest.fn(),
|
||||
isLoading: false,
|
||||
},
|
||||
];
|
||||
|
||||
testData.forEach((tableProps) => {
|
||||
render(<HQRTable {...tableProps} />);
|
||||
expect(screen.getByText("1 result")).toBeInTheDocument();
|
||||
expect(screen.getByText("Last fetched")).toBeInTheDocument();
|
||||
tableProps.rows.forEach((row) => {
|
||||
Object.entries(row).forEach(([col, val]) => {
|
||||
expect(screen.getByText(col)).toBeInTheDocument();
|
||||
expect(screen.getByText(val)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("Renders the 'collecting results' empty state when results have never been collected.", () => {
|
||||
const testData: IHQRTable[] = [
|
||||
{
|
||||
queryName: "testQuery0",
|
||||
queryDescription: "testDescription0",
|
||||
hostName: "testHost0",
|
||||
rows: [],
|
||||
reportClipped: false,
|
||||
lastFetched: null,
|
||||
onShowQuery: jest.fn(),
|
||||
isLoading: false,
|
||||
},
|
||||
];
|
||||
|
||||
testData.forEach((tableProps) => {
|
||||
render(<HQRTable {...tableProps} />);
|
||||
expect(screen.queryByText("Last fetched")).toBeNull();
|
||||
expect(screen.getByText("Collecting results...")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("Renders the 'report clipped' empty state when reporting for this query has been paused and there are no existing results.", () => {
|
||||
const testData: IHQRTable[] = [
|
||||
{
|
||||
queryName: "testQuery0",
|
||||
queryDescription: "testDescription0",
|
||||
hostName: "testHost0",
|
||||
rows: [],
|
||||
reportClipped: true,
|
||||
lastFetched: "2021-09-01T00:00:00Z",
|
||||
onShowQuery: jest.fn(),
|
||||
isLoading: false,
|
||||
},
|
||||
];
|
||||
|
||||
testData.forEach((tableProps) => {
|
||||
render(<HQRTable {...tableProps} />);
|
||||
expect(screen.queryByText("Last fetched")).toBeNull();
|
||||
expect(screen.getByText("Report clipped")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("Renders the 'nothing to report' empty state when the query has run and there are no results.", () => {
|
||||
const testData: IHQRTable[] = [
|
||||
{
|
||||
queryName: "testQuery0",
|
||||
queryDescription: "testDescription0",
|
||||
hostName: "testHost0",
|
||||
rows: [],
|
||||
reportClipped: false,
|
||||
lastFetched: "2021-09-01T00:00:00Z",
|
||||
onShowQuery: jest.fn(),
|
||||
isLoading: false,
|
||||
},
|
||||
];
|
||||
|
||||
testData.forEach((tableProps) => {
|
||||
render(<HQRTable {...tableProps} />);
|
||||
expect(screen.queryByText("Last fetched")).toBeNull();
|
||||
expect(screen.getByText("Nothing to report")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -15,7 +15,7 @@ import generateColumnConfigs from "./HQRTableConfig";
|
|||
|
||||
const baseClass = "hqr-table";
|
||||
|
||||
interface IHQRTable {
|
||||
export interface IHQRTable {
|
||||
queryName?: string;
|
||||
queryDescription?: string;
|
||||
hostName?: string;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
import DefaultColumnFilter from "components/TableContainer/DataTable/DefaultColumnFilter";
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
|
||||
import {
|
||||
IHeaderProps,
|
||||
IStringCellProps,
|
||||
IWebSocketData,
|
||||
} from "interfaces/datatable_config";
|
||||
import { IHeaderProps, IWebSocketData } from "interfaces/datatable_config";
|
||||
import React from "react";
|
||||
|
||||
import { CellProps, Column } from "react-table";
|
||||
|
|
@ -47,7 +43,7 @@ const generateColumnConfigs = (rows: IWebSocketData[]): IHQRTTableColumn[] =>
|
|||
const val = cellProps?.cell?.value;
|
||||
return !!val?.length && val.length > 300
|
||||
? internallyTruncateText(val)
|
||||
: <>val</> ?? null;
|
||||
: <>{val}</> ?? null;
|
||||
},
|
||||
Filter: DefaultColumnFilter, // Component hides filter for last_fetched
|
||||
filterType: "text",
|
||||
|
|
|
|||
|
|
@ -317,6 +317,39 @@ const HostSummary = ({
|
|||
);
|
||||
};
|
||||
|
||||
const renderAgentSummary = () => {
|
||||
if (platform === "chrome") {
|
||||
return <DataSet title="Agent" value={summaryData.osquery_version} />;
|
||||
}
|
||||
if (summaryData.orbit_version) {
|
||||
return (
|
||||
<DataSet
|
||||
title="Agent"
|
||||
value={
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<>
|
||||
osquery: {summaryData.osquery_version}
|
||||
<br />
|
||||
Orbit: {summaryData.orbit_version}
|
||||
{summaryData.fleet_desktop_version && (
|
||||
<>
|
||||
<br />
|
||||
Fleet Desktop: {summaryData.fleet_desktop_version}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
{summaryData.orbit_version}
|
||||
</TooltipWrapper>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <DataSet title="Osquery" value={summaryData.osquery_version} />;
|
||||
};
|
||||
|
||||
const renderSummary = () => {
|
||||
// for windows hosts we have to manually add a profile for disk encryption
|
||||
// as this is not currently included in the `profiles` value from the API
|
||||
|
|
@ -401,7 +434,8 @@ const HostSummary = ({
|
|||
/>
|
||||
<DataSet title="Processor type" value={summaryData.cpu_type} />
|
||||
<DataSet title="Operating system" value={summaryData.os_version} />
|
||||
<DataSet title="Osquery" value={summaryData.osquery_version} />
|
||||
|
||||
{renderAgentSummary()}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ import {
|
|||
isTeamObserver,
|
||||
} from "utilities/permissions/permissions";
|
||||
import { DOCUMENT_TITLE_SUFFIX } from "utilities/constants";
|
||||
import { buildQueryStringFromParams } from "utilities/url";
|
||||
import useTeamIdParam from "hooks/useTeamIdParam";
|
||||
|
||||
import Spinner from "components/Spinner/Spinner";
|
||||
import Button from "components/buttons/Button";
|
||||
|
|
@ -65,9 +67,13 @@ const QueryDetailsPage = ({
|
|||
router.push(PATHS.MANAGE_QUERIES);
|
||||
}
|
||||
const queryParams = location.query;
|
||||
const teamId = location.query.team_id
|
||||
? parseInt(location.query.team_id, 10)
|
||||
: undefined;
|
||||
|
||||
const { currentTeamId } = useTeamIdParam({
|
||||
location,
|
||||
router,
|
||||
includeAllTeams: true,
|
||||
includeNoTeam: false,
|
||||
});
|
||||
|
||||
// Functions to avoid race conditions
|
||||
const serverSortBy: ISortOption[] = (() => {
|
||||
|
|
@ -203,7 +209,12 @@ const QueryDetailsPage = ({
|
|||
|
||||
// Function instead of constant eliminates race condition with filteredQueriesPath
|
||||
const backToQueriesPath = () => {
|
||||
return filteredQueriesPath || PATHS.MANAGE_QUERIES;
|
||||
return (
|
||||
filteredQueriesPath ||
|
||||
`${PATHS.MANAGE_QUERIES}?${buildQueryStringFromParams({
|
||||
team_id: currentTeamId,
|
||||
})}`
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -233,7 +244,8 @@ const QueryDetailsPage = ({
|
|||
{canEditQuery && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
queryId && router.push(PATHS.EDIT_QUERY(queryId, teamId));
|
||||
queryId &&
|
||||
router.push(PATHS.EDIT_QUERY(queryId, currentTeamId));
|
||||
}}
|
||||
className={`${baseClass}__manage-automations button`}
|
||||
variant="brand"
|
||||
|
|
@ -332,7 +344,7 @@ const QueryDetailsPage = ({
|
|||
// Exclude below message for global and team observers/observer+s
|
||||
!(
|
||||
(currentUser && isGlobalObserver(currentUser)) ||
|
||||
isTeamObserver(currentUser, teamId ?? null)
|
||||
isTeamObserver(currentUser, currentTeamId ?? null)
|
||||
) &&
|
||||
" You can still use query automations to complete this report in your log destination."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import { NotificationContext } from "context/notification";
|
|||
import PATHS from "router/paths";
|
||||
import debounce from "utilities/debounce";
|
||||
import deepDifference from "utilities/deep_difference";
|
||||
import { buildQueryStringFromParams } from "utilities/url";
|
||||
|
||||
import EditQueryForm from "./components/EditQueryForm";
|
||||
|
||||
|
|
@ -38,7 +39,7 @@ interface IEditQueryPageProps {
|
|||
params: Params;
|
||||
location: {
|
||||
pathname: string;
|
||||
query: { host_ids: string; team_id?: string };
|
||||
query: { host_id: string; team_id?: string };
|
||||
search: string;
|
||||
};
|
||||
}
|
||||
|
|
@ -51,9 +52,11 @@ const EditQueryPage = ({
|
|||
location,
|
||||
}: IEditQueryPageProps): JSX.Element => {
|
||||
const queryId = paramsQueryId ? parseInt(paramsQueryId, 10) : null;
|
||||
|
||||
const {
|
||||
currentTeamName: teamNameForQuery,
|
||||
teamIdForApi: apiTeamIdForQuery,
|
||||
currentTeamId,
|
||||
} = useTeamIdParam({
|
||||
location,
|
||||
router,
|
||||
|
|
@ -70,6 +73,7 @@ const EditQueryPage = ({
|
|||
isObserverPlus,
|
||||
isAnyTeamObserverPlus,
|
||||
config,
|
||||
filteredQueriesPath,
|
||||
} = useContext(AppContext);
|
||||
const {
|
||||
editingExistingQuery,
|
||||
|
|
@ -319,7 +323,15 @@ const EditQueryPage = ({
|
|||
|
||||
// Function instead of constant eliminates race condition
|
||||
const backToQueriesPath = () => {
|
||||
return queryId ? PATHS.QUERY_DETAILS(queryId) : PATHS.MANAGE_QUERIES;
|
||||
const manageQueryPage =
|
||||
filteredQueriesPath ||
|
||||
`${PATHS.MANAGE_QUERIES}?${buildQueryStringFromParams({
|
||||
team_id: currentTeamId,
|
||||
})}`;
|
||||
|
||||
return queryId
|
||||
? PATHS.QUERY_DETAILS(queryId, currentTeamId)
|
||||
: manageQueryPage;
|
||||
};
|
||||
|
||||
const showSidebar =
|
||||
|
|
@ -348,6 +360,7 @@ const EditQueryPage = ({
|
|||
storedQuery={storedQuery}
|
||||
queryIdForEdit={queryId}
|
||||
apiTeamIdForQuery={apiTeamIdForQuery}
|
||||
currentTeamId={currentTeamId}
|
||||
teamNameForQuery={teamNameForQuery}
|
||||
isStoredQueryLoading={isStoredQueryLoading}
|
||||
showOpenSchemaActionText={showOpenSchemaActionText}
|
||||
|
|
@ -356,7 +369,7 @@ const EditQueryPage = ({
|
|||
backendValidators={backendValidators}
|
||||
isQuerySaving={isQuerySaving}
|
||||
isQueryUpdating={isQueryUpdating}
|
||||
hostId={parseInt(location.query.host_ids as string, 10)}
|
||||
hostId={parseInt(location.query.host_id as string, 10)}
|
||||
queryReportsDisabled={
|
||||
appConfig?.server_settings.query_reports_disabled
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ interface IEditQueryFormProps {
|
|||
router: InjectedRouter;
|
||||
queryIdForEdit: number | null;
|
||||
apiTeamIdForQuery?: number;
|
||||
currentTeamId?: number;
|
||||
teamNameForQuery?: string;
|
||||
showOpenSchemaActionText: boolean;
|
||||
storedQuery: ISchedulableQuery | undefined;
|
||||
|
|
@ -97,6 +98,7 @@ const EditQueryForm = ({
|
|||
router,
|
||||
queryIdForEdit,
|
||||
apiTeamIdForQuery,
|
||||
currentTeamId,
|
||||
teamNameForQuery,
|
||||
showOpenSchemaActionText,
|
||||
storedQuery,
|
||||
|
|
@ -601,7 +603,7 @@ const EditQueryForm = ({
|
|||
onClick={() => {
|
||||
router.push(
|
||||
PATHS.LIVE_QUERY(queryIdForEdit) +
|
||||
TAGGED_TEMPLATES.queryByHostRoute(hostId)
|
||||
TAGGED_TEMPLATES.queryByHostRoute(hostId, apiTeamIdForQuery)
|
||||
);
|
||||
}}
|
||||
disabled={disabledLiveQuery}
|
||||
|
|
@ -680,7 +682,6 @@ const EditQueryForm = ({
|
|||
const disableSaveFormErrors =
|
||||
(lastEditedQueryName === "" && !!lastEditedQueryId) || !!size(errors);
|
||||
|
||||
console.log("lastEditedQueryPlatforms", lastEditedQueryPlatforms);
|
||||
return (
|
||||
<>
|
||||
<form className={`${baseClass}`} autoComplete="off">
|
||||
|
|
@ -846,7 +847,7 @@ const EditQueryForm = ({
|
|||
setEditingExistingQuery(true); // Persists edited query data through live query flow
|
||||
router.push(
|
||||
PATHS.LIVE_QUERY(queryIdForEdit) +
|
||||
TAGGED_TEMPLATES.queryByHostRoute(hostId)
|
||||
TAGGED_TEMPLATES.queryByHostRoute(hostId, currentTeamId)
|
||||
);
|
||||
}}
|
||||
disabled={disabledLiveQuery}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ interface IRunQueryPageProps {
|
|||
params: Params;
|
||||
location: {
|
||||
pathname: string;
|
||||
query: { host_ids: string; team_id?: string };
|
||||
query: { host_id: string; team_id?: string };
|
||||
search: string;
|
||||
};
|
||||
}
|
||||
|
|
@ -109,9 +109,9 @@ const RunQueryPage = ({
|
|||
useQuery<IHostResponse, Error, IHost>(
|
||||
"hostFromURL",
|
||||
() =>
|
||||
hostAPI.loadHostDetails(parseInt(location.query.host_ids as string, 10)),
|
||||
hostAPI.loadHostDetails(parseInt(location.query.host_id as string, 10)),
|
||||
{
|
||||
enabled: !!location.query.host_ids && !queryParamHostsAdded,
|
||||
enabled: !!location.query.host_id && !queryParamHostsAdded,
|
||||
select: (data: IHostResponse) => data.host,
|
||||
onSuccess: (host) => {
|
||||
setTargetedHosts((prevHosts) =>
|
||||
|
|
|
|||
|
|
@ -341,6 +341,8 @@ export const HOST_SUMMARY_DATA = [
|
|||
"platform",
|
||||
"os_version",
|
||||
"osquery_version",
|
||||
"orbit_version",
|
||||
"fleet_desktop_version",
|
||||
"enroll_secret_name",
|
||||
"detail_updated_at",
|
||||
"percent_disk_space_available",
|
||||
|
|
|
|||
|
|
@ -853,8 +853,13 @@ export const getSoftwareBundleTooltipJSX = (bundle: string) => (
|
|||
);
|
||||
|
||||
export const TAGGED_TEMPLATES = {
|
||||
queryByHostRoute: (hostId: number | undefined | null) => {
|
||||
return `${hostId ? `?host_ids=${hostId}` : ""}`;
|
||||
queryByHostRoute: (hostId?: number | null, teamId?: number | null) => {
|
||||
const queryString = buildQueryStringFromParams({
|
||||
host_id: hostId || undefined,
|
||||
team_id: teamId,
|
||||
});
|
||||
|
||||
return queryString && `?${queryString}`;
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
10
go.mod
10
go.mod
|
|
@ -110,16 +110,17 @@ require (
|
|||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0
|
||||
go.opentelemetry.io/otel/sdk v1.19.0
|
||||
golang.org/x/crypto v0.17.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/exp v0.0.0-20230105202349-8879d0199aa3
|
||||
golang.org/x/image v0.10.0
|
||||
golang.org/x/mod v0.12.0
|
||||
golang.org/x/net v0.19.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/oauth2 v0.12.0
|
||||
golang.org/x/sync v0.3.0
|
||||
golang.org/x/sys v0.15.0
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/tools v0.13.0
|
||||
google.golang.org/api v0.128.0
|
||||
google.golang.org/grpc v1.58.3
|
||||
gopkg.in/guregu/null.v3 v3.5.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
|
|
@ -304,10 +305,9 @@ require (
|
|||
go.opentelemetry.io/otel/trace v1.19.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||
gocloud.dev v0.24.0 // indirect
|
||||
golang.org/x/term v0.15.0 // indirect
|
||||
golang.org/x/term v0.19.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/api v0.128.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a // indirect
|
||||
|
|
|
|||
16
go.sum
16
go.sum
|
|
@ -1313,8 +1313,8 @@ golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0
|
|||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
|
@ -1425,8 +1425,8 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
|||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
|
@ -1576,8 +1576,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
|
@ -1587,8 +1587,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
|||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ This handbook page details processes specific to working [with](#what-we-do) and
|
|||
|
||||
|
||||
## Responsibilities
|
||||
The Business Operations department is directly responsible for the functions of all Finance, People, Legal, IT, and Revenue Operations (RevOps).
|
||||
The Business Operations department is directly responsible for finance + invoicing, people operations, legal + deal desk, and corporate information technology (IT).
|
||||
|
||||
### Run payroll
|
||||
Many of these processes are automated, but it's vital to check Gusto and Plane manually for accuracy.
|
||||
|
|
@ -24,10 +24,10 @@ Many of these processes are automated, but it's vital to check Gusto and Plane m
|
|||
|
||||
| Payroll type | What to use | DRI |
|
||||
|:-----------------------------|:-----------------------------|:-----------------------------|
|
||||
| [Commissions and ramp](https://fleetdm.com/handbook/business-operations#run-us-commission-payroll) | "Off-cycle" payroll | Head of Revenue Operations
|
||||
| [Commissions and ramp](https://fleetdm.com/handbook/business-operations#run-us-commission-payroll) | "Off-cycle" payroll | Head of Business Operations
|
||||
| Sign-on bonus | "Bonus" payroll | Head of Business Operations
|
||||
| Performance bonus | "Bonus" payroll | Head of Business Operations
|
||||
| Accelerations (quarterly) | "Off-cycle" payroll | Head of Revenue Operations
|
||||
| Accelerations (quarterly) | "Off-cycle" payroll | Head of Business Operations
|
||||
| [US contractor payroll](https://fleetdm.com/handbook/business-operations#run-us-contractor-payroll) | "Off-cycle" payroll | Head of Business Operations
|
||||
|
||||
### Reconcile monthly recurring expenses
|
||||
|
|
@ -109,7 +109,7 @@ For Fleet's US contractors, running payroll is a manual process:
|
|||
- Sync hours and run contractor payroll.
|
||||
|
||||
|
||||
### Grant role-specific license to a team member (RevOps)
|
||||
### Grant role-specific license to a team member
|
||||
Certain new team members, especially in go-to-market (GTM) roles, will need paid access to paid tools like Salesforce and LinkedIn Sales Navigator immediately on their first day with the company. Gong licenses that other departments need may [request them from BizOps](https://fleetdm.com/handbook/business-operations#contact-us) and we will make sure there is no license redundancy in that department. The table below can be used to determine which paid licenses they will need, based on their role:
|
||||
|
||||
| Role | Salesforce CRM | Salesforce "Inbox" | LinkedIn _(paid)_ | Gong _(paid)_ | Zoom _(paid)_|
|
||||
|
|
@ -125,6 +125,18 @@ Certain new team members, especially in go-to-market (GTM) roles, will need paid
|
|||
> **Warning:** Do NOT buy LinkedIn Recruiter. AEs and SDRs should use their personal Brex card to purchase the monthly [Core Sales Navigator](https://business.linkedin.com/sales-solutions/compare-plans) plan. Fleet does not use a company wide Sales Navigator account. The goal of Sales Navigator is to access to profile views and data, not InMail. Fleet does not send InMail.
|
||||
|
||||
|
||||
### 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:
|
||||
- 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.
|
||||
- Copy the template below and paste it into the [#g-sales slack channel](https://fleetdm.slack.com/archives/C030A767HQV) and complete all "todos" using the data from Salesforce before sending.
|
||||
```
|
||||
Weekly revenue report - [@`todo: CRO` and @`todo: CEO`]
|
||||
- Number accounts with outstanding balances = `todo`
|
||||
- Number of customers awaiting invoices = `todo`
|
||||
- Number of past-due renewals = `todo`
|
||||
```
|
||||
|
||||
|
||||
### Add a seat to Salesforce
|
||||
Here are the steps we take to grant appropriate Salesforce licenses to a new hire:
|
||||
- Go to ["My Account"](https://fleetdm.lightning.force.com/lightning/n/standard-OnlineSalesHome).
|
||||
|
|
@ -170,6 +182,7 @@ When a Fleetie, consultant or advisor requests an update to their personnel deta
|
|||
- If required, BizOps also makes changes to other core systems (e.g: creating a new email alias in google workspace; updating details in Carta; etc).
|
||||
- The change is now actioned, notify the team member and close the issue.
|
||||
|
||||
> Note: if the Fleetie is US based and has a qualifying life event that impacts benefit coverage, they can [follow the Gusto steps](https://support.gusto.com/article/100895878100000/Change-your-benefits-with-a-qualifying-life-event) to update their coverage elections.
|
||||
|
||||
### Change a Fleetie's job title
|
||||
When BizOps receives notification of a Fleetie's job title changing, follow these steps to ensure accurate recording of the change across our systems.
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ Please also see [privacy](https://fleetdm.com/legal/privacy)
|
|||
## Sub-processors
|
||||
| Question | Answer |
|
||||
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Does Fleet possess an APEC PRP certification issued by a certification body (or Accountability Agent)? If not, is Fleet able to provide any evidence that the PRP requirements are being met as it relates to the Scoped Services provided to its customers? | Fleet has not undergone APEC PRP certification but has undergone an external security audit that included pen testing. |
|
||||
| Does Fleet possess an APEC PRP certification issued by a certification body (or Accountability Agent)? If not, is Fleet able to provide any evidence that the PRP requirements are being met as it relates to the Scoped Services provided to its customers? | Fleet has not undergone APEC PRP certification but has undergone an external security audit that included pen testing. For a complete list of subprocessors, please refer to https://trust.fleetdm.com/subprocessors |
|
||||
|
||||
<meta name="maintainedBy" value="dherder">
|
||||
<meta name="title" value="📃 Vendor questionnaires">
|
||||
|
|
|
|||
|
|
@ -214,15 +214,15 @@ If you need to track content from a Slack channel (ie. #g-sales), you can automa
|
|||
|
||||
**Fleet Free:**
|
||||
|
||||
| Impact Level | Definition | Preferred Contact | Response Time |
|
||||
| Impact level | Definition | Preferred contact | Response time |
|
||||
|:---|:---|:---|:---|
|
||||
| All Inquiries | Any request regardless of impact level or severity | Osquery #fleet Slack channel | No guaranteed resolution |
|
||||
| All inquiries | Any request regardless of impact level or severity | Osquery #fleet Slack channel | No guaranteed resolution |
|
||||
|
||||
> **Note:** If you're using Fleet Free, you can also access community support by [opening a bug](https://github.com/fleetdm/fleet/issues/new?assignees=&labels=bug%2C%3Areproduce&projects=&template=bug-report.md&title=) in the [Fleet GitHub](https://github.com/fleetdm/fleet/) repository.
|
||||
|
||||
**Fleet Premium:**
|
||||
|
||||
| Impact Level | Definition | Preferred Contact | Response Time |
|
||||
| Impact level | Definition | Preferred contact | Response time |
|
||||
|:-----|:----|:----|:-----|
|
||||
| Emergency (P0) | Your production instance of Fleet is unavailable or completely unusable. For example, if Fleet is showing 502 errors for all users. | Expedited phone/chat/email support during business hours. </br></br>Email the contact address provided in your Fleet contract or chat with us via your dedicated private Slack channel | **≤4 hours** |
|
||||
| High (P1) | Fleet is highly degraded with significant business impact. | Expedited phone/chat/email support during business hours. </br></br>Email the contact address provided in your Fleet contract or chat with us via your dedicated private Slack channel | **≤4 business hours** |
|
||||
|
|
@ -493,7 +493,7 @@ You can learn more about how Fleet approaches security in the [security handbook
|
|||
|
||||
|
||||
## Vendor questionnaires
|
||||
In responding to security questionnaires, Fleet endeavors to provide full transparency via our [security policies](https://fleetdm.com/handbook/security/security-policies#security-policies) and [application security](https://fleetdm.com/handbook/business-operations/application-security) documentation. In addition to this documentation, please refer to [the vendor questionnaires page](https://fleetdm.com/handbook/business-operations/vendor-questionnaires)
|
||||
In responding to security questionnaires, Fleet endeavors to provide full transparency via our [security policies](https://fleetdm.com/handbook/security/security-policies#security-policies), [trust](https://trust.fleetdm.com/), and [application security](https://fleetdm.com/handbook/business-operations/application-security) documentation. In addition to this documentation, please refer to [the vendor questionnaires page](https://fleetdm.com/handbook/business-operations/vendor-questionnaires). [Contact the Sales department](https://fleetdm.com/handbook/sales#contact-us) to address any pending questionnaires.
|
||||
|
||||
## Getting a contract signed
|
||||
If a contract is ready for signature and requires no review or revision, the requestor logins into DocuSign using hello@ from the 1Password vault and routes the agreement to the CEO for signature.
|
||||
|
|
|
|||
|
|
@ -184,13 +184,7 @@ This section is about creating a core team member role, and the hiring process f
|
|||
|
||||
#### Creating a new position
|
||||
|
||||
Want to hire? Here's how to open up a new position on the core team:
|
||||
|
||||
> Use these steps to hire a [fleetie, not a consultant](https://fleetdm.com/handbook/business-operations#who-isnt-a-consultant).
|
||||
|
||||
<!--
|
||||
> If you think this job posting may need to stay temporarily classified (¶¶) and not shared company-wide or publicly yet, for any reason, then stop here and send a Slack DM with your proposal to the CEO instead of modifying ["🧑🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit) (which is visible company-wide) or submitting a draft pull request to "Open positions" (which is public).
|
||||
-->
|
||||
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:
|
||||
|
||||
1. **Propose headcount:** Add the proposed position to ["🧑🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0) in an empty row (but using one of the existing IDs. Unsure? Ask for help.) Be sure to include job title, manager, and department. Set the start date to the first Monday of the next month (This position is still only proposed (not approved), but would make it easier for the approver to have the date set).
|
||||
2. **Propose job description:** Copy, personalize, and publish the job description:
|
||||
|
|
|
|||
|
|
@ -61,3 +61,32 @@
|
|||
- 🛠️ Technical: You understand the software development processes.
|
||||
- 🟣 Openness: You are flexible and open to new ideas and ways of working
|
||||
- ➕ Bonus: Cybersecurity or IT background
|
||||
- jobTitle: 🐋 Customer Success Engineer
|
||||
department: Customers
|
||||
hiringManagerName: Jason Lewis
|
||||
hiringManagerGithubUsername: Patagonia121
|
||||
hiringManagerLinkedInUrl: https://www.linkedin.com/in/jlewis0451/
|
||||
responsibilities: |
|
||||
- 🎯 Strong attention to detail and can act as an encyclopedia of knowledge about how Fleet works - our customers represent a wide range of needs across many different use cases. Be adaptable to learning new things quickly and then share this knowledge with others.
|
||||
- 📣 Manage multiple customer deployments and escalations simultaneously with the ability to stay organized.
|
||||
- 🚀 Deploy Fleet on your own to have a better understanding of the customer experience and how the product works.
|
||||
- 🪴 Promote product adoption, referencability, and customer advocacy with key customer stakeholders.
|
||||
- 🥇 Be the first line of defense in customer Slack channels for any reported problems, how-to questions, feature request intake, and bug report filling.
|
||||
- 🚀 Work collaboratively with product and engineering teams to facilitate bug resolution and feature development based on customer asks.
|
||||
- ⏫ Work hand-in-hand with the customer success team by participating in ad-hoc calls with customers to discuss any support issues they may have.
|
||||
- 💡 Excellent communication and collaboration skills, with the ability to work closely with customer success, engineering, and product teams.
|
||||
experience: |
|
||||
- 💭 Cybersecurity or IT background, experience with cloud environments like AWS and Azure or device management solutions like Fleet, Intune, Jamf Pro, Workspace One, etc.
|
||||
- 💖 You know how to manage your time and priorities between customer support engagements, customer escalations, and other day-to-day responsibilities.
|
||||
- 🧬 An excellent understanding of macOS, Windows, Linux and core services like Autopilot, ABM/ASM, MDM, ADE, APNs, syslog, etc.
|
||||
- 🤝 Collaboration: You work best in a team-based environment. You are decisive with the ability to shift gears between thinking and doing.
|
||||
- 👥 A customer-centric mindset, focusing on delivering value and a positive user experience.
|
||||
- 🦉 2-3 years of work experience providing technical support to enterprise customers in the cybersecurity or device management space. Experience with executing and tracking results tied to customer escalations.
|
||||
- 🛠️ You are personable, enjoy being customer facing, and have a passion for problem solving while assisting external and internal stakeholders.
|
||||
- 🧪 Extensive experience with Slack, Google Suite, and GitHub.
|
||||
- ✍️ Familiarity with shell scripting, Python, Powershell, and using Terminal to execute commands or run scripts, and other line of business applications.
|
||||
- 🟣 Openness: Speak freely. Interrupt and be interrupted. Give pointed and respectful feedback, even when you disagree.
|
||||
- 🔴 Empathy: You should demonstrate empathy by keenly understanding and addressing customer concerns with genuine compassion.
|
||||
- ➕ Bonus: Familiarity with osquery, MYSql, GitOps workflows, Terraform, Tines/Torq and open source projects. Experience working with IT, SRE, CPE, or SecOps teams.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ This page details processes specific to working [with](#contact-us) and [within]
|
|||
| Head of Design | [Mike Thomas](https://www.linkedin.com/in/mike-thomas-52277938) _([@mike-j-thomas](https://github.com/mike-j-thomas))_
|
||||
| Software Engineer | [Eric Shaw](https://www.linkedin.com/in/eric-shaw-1423831a9/) _([@eashaw](https://github.com/eashaw))_
|
||||
| Head of Revenue Operations | [Taylor Hughes](https://www.linkedin.com/in/taylorhughes834/) _([@hughestaylor](https://github.com/hughestaylor))_
|
||||
| Apprentice | [Award Malisi](https://www.linkedin.com/in/award-malisi/) _([@Unearthlyglow](https://github.com/Unearthlyglow))_
|
||||
|
||||
|
||||
## Contact us
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
task: "Check browser compatibility for fleetdm.com"
|
||||
startedOn: "2024-03-06"
|
||||
frequency: "Monthly"
|
||||
description: "Run `npm audit --only=prod` to check for vulnerabilities on the production dependencies of fleetdm.com."
|
||||
moreInfoUrl: "https://fleetdm.com/handbook/digital-experience#check-production-dependencies-of-fleetdm-com"
|
||||
description: "Use Browserstack to manually QA pages on fleetdm.com in each of the earliest supported browser versions"
|
||||
moreInfoUrl: "https://fleetdm.com/handbook/digital-experience#check-browser-compatibility-for-fleetdm-com"
|
||||
dri: "eashaw"
|
||||
autoIssue: # Enables automation of GitHub issues
|
||||
labels: [ "#g-digital-experience" ] # label to be applied to issue
|
||||
|
|
|
|||
|
|
@ -143,6 +143,12 @@ To close a deal with a new customer (non-self-service), create and complete a Gi
|
|||
### 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 Business Operations (#g-business-operations)](https://fleetdm.com/handbook/business-operations#contact-us) with a requested timeline for completion defined.
|
||||
- BizOps consults the handbook to validate that nothing was missed by the AE. After the second pass has been completed, and if there are outstanding questions, BizOps will [reassign the issue to Sales (#g-sales)](https://fleetdm.com/handbook/sales#contact-us) for intake.
|
||||
- The issue will be assigned to the Solutions Consultant (SC) associated to the opportunity in order to complete any unanswered questions.
|
||||
- The SC will search for unanswered questions and confirm again that nothing was missed from the handbook. Content missing from the handbook will need to be added via PR by the SC. Any unanswered questions after this pass has been completed by the SC will need to be [escalated to the Infrastructure team (#g-customer-success)](https://fleetdm.com/handbook/customer-success#contact-us) with the requested timeline for completion defined in the issue. Once complete, the infra team will assign the issue back to the #g-sales board.
|
||||
- Any questions answered by the infra team will be added to the handbook by the SC.
|
||||
|
||||
## Rituals
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ variable "database_name" {
|
|||
|
||||
variable "fleet_image" {
|
||||
description = "the name of the container image to run"
|
||||
default = "fleetdm/fleet:v4.47.3"
|
||||
default = "fleetdm/fleet:v4.48.0"
|
||||
}
|
||||
|
||||
variable "software_inventory" {
|
||||
|
|
|
|||
|
|
@ -68,5 +68,5 @@ variable "redis_mem" {
|
|||
}
|
||||
|
||||
variable "image" {
|
||||
default = "fleet:v4.47.3"
|
||||
default = "fleet:v4.48.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,11 +38,11 @@ require (
|
|||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/term v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
|
|
|
|||
|
|
@ -381,6 +381,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
|
@ -453,6 +455,7 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
|
@ -461,6 +464,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
@ -472,6 +476,7 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
|
|
|||
|
|
@ -53,9 +53,9 @@
|
|||
description: This policy checks if maximum amount of time (in minutes) the device is allowed to sit idle before the screen is locked. End users can select any value less than the specified maximum.
|
||||
resolution: An an IT admin, deploy a macOS, screen saver profile with the maxInactivity option set to 20 minutes.
|
||||
platform: darwin
|
||||
- name: macOS - No 1Password emergency kit stored on desktop or in downloads
|
||||
query: SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM file WHERE filename LIKE '%Emergency Kit%.pdf' AND (path LIKE '/Users/%%/Desktop/%%' OR path LIKE '/Users/%%/Documents/%%' OR path LIKE '/Users/%%/Downloads/%%' OR path LIKE '/Users/Shared'));
|
||||
- name: macOS - No 1Password emergency kit stored in desktop, documents, or downloads folders
|
||||
query: SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM file WHERE filename LIKE '%Emergency Kit%.pdf' AND (path LIKE '/Users/%/Desktop/%' OR path LIKE '/Users/%/Documents/%' OR path LIKE '/Users/%/Downloads/%' OR path LIKE '/Users/Shared/%'));
|
||||
critical: false
|
||||
description: "Looks for PDF files with file names typically used by 1Password for emergency recovery kits."
|
||||
description: "Looks for PDF files with file names typically used by 1Password for emergency recovery kits. To protect the performance of your devices, the search is one level deep and limited to the Desktop, Documents, Downloads, and Shared folders."
|
||||
resolution: "Delete 1Password emergency kits from your computer, and empty the trash. 1Password emergency kits should only be printed and stored in a physically secure location."
|
||||
platform: darwin
|
||||
|
|
|
|||
16
it-and-security/lib/servers-canary.agent-options.yml
Normal file
16
it-and-security/lib/servers-canary.agent-options.yml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
config:
|
||||
decorators:
|
||||
load:
|
||||
- SELECT uuid AS host_uuid FROM system_info;
|
||||
- SELECT hostname AS hostname FROM system_info;
|
||||
options:
|
||||
disable_distributed: false
|
||||
distributed_interval: 10
|
||||
distributed_plugin: tls
|
||||
distributed_tls_max_attempts: 3
|
||||
logger_tls_endpoint: /api/osquery/log
|
||||
logger_tls_period: 10
|
||||
pack_delimiter: /
|
||||
update_channels:
|
||||
# We want to use these hosts to smoke test osquery releases.
|
||||
osqueryd: edge
|
||||
|
|
@ -9,7 +9,7 @@ team_settings:
|
|||
secrets:
|
||||
- secret: $DOGFOOD_SERVERS_CANARY_ENROLL_SECRET
|
||||
agent_options:
|
||||
path: ../lib/servers.agent-options.yml
|
||||
path: ../lib/servers-canary.agent-options.yml
|
||||
controls:
|
||||
enable_disk_encryption: false
|
||||
macos_settings:
|
||||
|
|
|
|||
|
|
@ -100,12 +100,6 @@ policies:
|
|||
- path: ../lib/macos-device-health.policies.yml
|
||||
- path: ../lib/windows-device-health.policies.yml
|
||||
- path: ../lib/linux-device-health.policies.yml
|
||||
- name: chromeOS/macOS - Screenlock enabled
|
||||
query: SELECT 1 FROM screenlock WHERE enabled = 1;
|
||||
critical: false
|
||||
description: ""
|
||||
resolution: ""
|
||||
platform: darwin,chrome
|
||||
queries:
|
||||
- path: ../lib/collect-failed-login-attempts.queries.yml
|
||||
- path: ../lib/collect-usb-devices.queries.yml
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ Following are the currently deployed versions of fleetd components on the `stabl
|
|||
|--------------|--------|--------|---------|
|
||||
| orbit | 1.22.0 | 1.22.0 | 1.22.0 |
|
||||
| desktop | 1.22.0 | 1.22.0 | 1.22.0 |
|
||||
| osqueryd | 5.12.0 | 5.12.0 | 5.12.0 |
|
||||
| osqueryd | 5.12.1 | 5.12.1 | 5.12.1 |
|
||||
| nudge | - | - | - |
|
||||
| swiftDialog | - | - | - |
|
||||
|
|
|
|||
1
orbit/changes/dataflatten-tables
Normal file
1
orbit/changes/dataflatten-tables
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Add `parse_json`, `parse_jsonl`, `parse_xml`, and `parse_ini` tables.
|
||||
|
|
@ -385,11 +385,20 @@ func createVersionInfo(vParts []string, manifestPath string) (*goversioninfo.Ver
|
|||
// SanitizeVersion returns the version parts (Major, Minor, Patch and Build), filling the Build part
|
||||
// with '0' if missing. Will error out if the version string is missing the Major, Minor or
|
||||
// Patch part(s).
|
||||
// It supports the version with a pre-release part (e.g. 1.2.3-1) and returns it as the Build number.
|
||||
func SanitizeVersion(version string) ([]string, error) {
|
||||
vParts := strings.Split(version, ".")
|
||||
if len(vParts) < 3 {
|
||||
return nil, errors.New("invalid version string")
|
||||
}
|
||||
if len(vParts) == 3 && strings.Contains(vParts[2], "-") {
|
||||
parts := strings.SplitN(vParts[2], "-", 2)
|
||||
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||||
return nil, fmt.Errorf("invalid patch and pre-release version: %s", vParts[2])
|
||||
}
|
||||
patch, preRelease := parts[0], parts[1]
|
||||
vParts = []string{vParts[0], vParts[1], patch, preRelease}
|
||||
}
|
||||
|
||||
if len(vParts) < 4 {
|
||||
vParts = append(vParts, "0")
|
||||
|
|
@ -465,7 +474,7 @@ func downloadAndExtractZip(client *http.Client, urlPath string, destPath string)
|
|||
}
|
||||
defer zipReader.Close()
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(destPath), 0755)
|
||||
err = os.MkdirAll(filepath.Dir(destPath), 0o755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create directory %s: %w", filepath.Dir(destPath), err)
|
||||
}
|
||||
|
|
@ -479,7 +488,6 @@ func downloadAndExtractZip(client *http.Client, urlPath string, destPath string)
|
|||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func extractZipFile(archiveReader *zip.File, destPath string) error {
|
||||
|
|
@ -506,13 +514,13 @@ func extractZipFile(archiveReader *zip.File, destPath string) error {
|
|||
|
||||
// Check if the file to extract is just a directory
|
||||
if archiveReader.FileInfo().IsDir() {
|
||||
err = os.MkdirAll(finalPath, 0755)
|
||||
err = os.MkdirAll(finalPath, 0o755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create directory %s: %w", finalPath, err)
|
||||
}
|
||||
} else {
|
||||
// Create all needed directories
|
||||
if os.MkdirAll(filepath.Dir(finalPath), 0755) != nil {
|
||||
if os.MkdirAll(filepath.Dir(finalPath), 0o755) != nil {
|
||||
return fmt.Errorf("could not create directory %s: %w", filepath.Dir(finalPath), err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,14 @@ func TestSanitizeVersion(t *testing.T) {
|
|||
}{
|
||||
{Version: "4.13.0", Parts: []string{"4", "13", "0", "0"}},
|
||||
{Version: "4.13.0.1", Parts: []string{"4", "13", "0", "1"}},
|
||||
|
||||
// We need to support this form of semantic versioning (with pre-releases)
|
||||
// to comply with semantic versioning required by goreleaser to allow building
|
||||
// orbit pre-releases.
|
||||
{Version: "4.13.0-1", Parts: []string{"4", "13", "0", "1"}},
|
||||
{Version: "4.13.0-alpha", Parts: []string{"4", "13", "0", "alpha"}},
|
||||
{Version: "4.13.0-", ErrorsOut: true},
|
||||
|
||||
{Version: "4.13.0.1.2", Parts: []string{"4", "13", "0", "1"}},
|
||||
{Version: "4", ErrorsOut: true},
|
||||
{Version: "4.13", ErrorsOut: true},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/table/cryptoinfotable"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/table/dataflattentable"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/table/firefox_preferences"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/table/sntp_request"
|
||||
"github.com/macadmins/osquery-extension/tables/chromeuserprofiles"
|
||||
|
|
@ -134,6 +135,13 @@ func OrbitDefaultTables() []osquery.OsqueryPlugin {
|
|||
|
||||
firefox_preferences.TablePlugin(osqueryLogger),
|
||||
cryptoinfotable.TablePlugin(osqueryLogger),
|
||||
|
||||
// Additional data format tables
|
||||
dataflattentable.TablePlugin(osqueryLogger, dataflattentable.JsonType), // table name is "parse_json"
|
||||
dataflattentable.TablePlugin(osqueryLogger, dataflattentable.JsonlType), // table name is "parse_jsonl"
|
||||
dataflattentable.TablePlugin(osqueryLogger, dataflattentable.XmlType), // table name is "parse_xml"
|
||||
dataflattentable.TablePlugin(osqueryLogger, dataflattentable.IniType), // table name is "parse_ini"
|
||||
|
||||
}
|
||||
return plugins
|
||||
}
|
||||
|
|
|
|||
10
osv-scanner.toml
Normal file
10
osv-scanner.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Configure OSV-Scanner
|
||||
# https://google.github.io/osv-scanner/configuration/
|
||||
|
||||
[[IgnoredVulns]]
|
||||
id = "GO-2022-0646"
|
||||
reason = "2024/04/02 - This project does not use github.com/aws/aws-sdk-go/service/s3/s3crypto. Reference: https://osv.dev/vulnerability/GO-2022-0646"
|
||||
|
||||
[[IgnoredVulns]]
|
||||
id = "GO-2023-1788"
|
||||
reason = "2024/04/02 - When packaging linux files, we do not use global permissions. Manually verified that packed fleet-osquery files do not have group/global write permissions. Reference: https://osv.dev/vulnerability/GO-2023-1788"
|
||||
13
package.json
13
package.json
|
|
@ -117,7 +117,7 @@
|
|||
"@types/uuid": "8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "5.58.0",
|
||||
"@typescript-eslint/parser": "5.58.0",
|
||||
"autoprefixer": "9.8.8",
|
||||
"autoprefixer": "10.4.19",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-eslint": "9.0.0",
|
||||
"babel-jest": "29.2.0",
|
||||
|
|
@ -150,10 +150,9 @@
|
|||
"json-loader": "0.5.7",
|
||||
"mini-css-extract-plugin": "2.7.5",
|
||||
"msw": "0.47.4",
|
||||
"nock": "13.2.4",
|
||||
"node-bourbon": "4.2.8",
|
||||
"node-sass-glob-importer": "5.3.2",
|
||||
"postcss-loader": "3.0.0",
|
||||
"node-sass-glob-importer": "5.3.3",
|
||||
"postcss-loader": "4.3.0",
|
||||
"prettier": "2.2.1",
|
||||
"react-docgen-typescript-plugin": "1.0.5",
|
||||
"regenerator-runtime": "0.13.9",
|
||||
|
|
@ -169,6 +168,12 @@
|
|||
"webpack-cli": "5.0.1",
|
||||
"webpack-notifier": "1.12.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"**/css-node-extract": "~3.0.4",
|
||||
"**/css-node-extract/postcss": "^8.4.31",
|
||||
"**/css-selector-extract": "~4.0.1",
|
||||
"**/wait-on/axios": "^0.28.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1715,8 +1715,8 @@
|
|||
],
|
||||
"evented": false,
|
||||
"cacheable": false,
|
||||
"notes": "",
|
||||
"examples": "List the SSH keys allowed to connect to this host.\n```\nSELECT key FROM authorized_keys;\n```",
|
||||
"notes": "Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)",
|
||||
"examples": "```\nSELECT * FROM users CROSS JOIN authorized_keys USING (uid);\n```",
|
||||
"columns": [
|
||||
{
|
||||
"name": "uid",
|
||||
|
|
@ -1725,8 +1725,7 @@
|
|||
"notes": "",
|
||||
"hidden": false,
|
||||
"required": false,
|
||||
"index": false,
|
||||
"requires_user_context": true
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "algorithm",
|
||||
|
|
@ -2838,8 +2837,8 @@
|
|||
],
|
||||
"evented": false,
|
||||
"cacheable": false,
|
||||
"notes": "",
|
||||
"examples": "See classic browser plugins (C/NPAPI) installed by users. These plugins have been deprecated for a long time, so this query will usually not return anything.\n```\nSELECT bp.name, bp.identifier, bp.version FROM browser_plugins bp JOIN users u on bp.uid = u.uid ;\n```",
|
||||
"notes": "Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)",
|
||||
"examples": "See classic browser plugins (C/NPAPI) installed by users. These plugins have been deprecated for a long time, so this query will usually not return anything.\n```\nSELECT * FROM users CROSS JOIN browser_plugins USING (uid);\n```",
|
||||
"columns": [
|
||||
{
|
||||
"name": "uid",
|
||||
|
|
@ -3691,8 +3690,8 @@
|
|||
],
|
||||
"evented": false,
|
||||
"cacheable": false,
|
||||
"notes": "",
|
||||
"examples": "```\nSELECT chrome_extension_content_scripts.* FROM users JOIN chrome_extension_content_scripts USING (uid) GROUP BY identifier, match\n```",
|
||||
"notes": "Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)",
|
||||
"examples": "```\nSELECT * FROM users CROSS JOIN chrome_extension_content_scripts USING (uid);\n```",
|
||||
"columns": [
|
||||
{
|
||||
"name": "browser_type",
|
||||
|
|
@ -3710,8 +3709,7 @@
|
|||
"notes": "",
|
||||
"hidden": false,
|
||||
"required": false,
|
||||
"index": true,
|
||||
"requires_user_context": true
|
||||
"index": true
|
||||
},
|
||||
{
|
||||
"name": "identifier",
|
||||
|
|
@ -3791,8 +3789,8 @@
|
|||
],
|
||||
"evented": false,
|
||||
"cacheable": false,
|
||||
"notes": "- On ChromeOS, this table requires the [fleetd Chrome extension](https://fleetdm.com/docs/using-fleet/chromeos).\n",
|
||||
"examples": "List Chrome extensions by user and profile which have full access to HTTPS browsing.\n```\nSELECT u.username, ce.name, ce.description, ce.version, ce.profile, ce.permissions FROM users u CROSS JOIN chrome_extensions ce USING (uid) WHERE ce.permissions LIKE '%%https://*/*%%';\n```",
|
||||
"notes": "Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)\n\nOn ChromeOS, this table requires the [fleetd Chrome extension](https://fleetdm.com/docs/using-fleet/chromeos).\n",
|
||||
"examples": "```\nSELECT * FROM users CROSS JOIN chrome_extensions USING (uid);\n```\nList Chrome extensions by user and profile which have full access to HTTPS browsing.\n```\nSELECT u.username, ce.name, ce.description, ce.version, ce.profile, ce.permissions FROM users u CROSS JOIN chrome_extensions ce USING (uid) WHERE ce.permissions LIKE '%%https://*/*%%';\n```",
|
||||
"columns": [
|
||||
{
|
||||
"name": "browser_type",
|
||||
|
|
@ -3815,8 +3813,7 @@
|
|||
"macOS",
|
||||
"Windows",
|
||||
"Linux"
|
||||
],
|
||||
"requires_user_context": true
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
|
|
@ -5067,8 +5064,8 @@
|
|||
],
|
||||
"evented": false,
|
||||
"cacheable": false,
|
||||
"notes": "",
|
||||
"examples": "See software responsible for crashes. This can be useful to detect what the most problematic software in your environment is.\n```\nSELECT crash_path, identifier, responsible, exception_type FROM crashes;\n```",
|
||||
"notes": "Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)",
|
||||
"examples": "```\nSELECT * FROM users CROSS JOIN crashes USING (uid);\n```",
|
||||
"columns": [
|
||||
{
|
||||
"name": "type",
|
||||
|
|
@ -5149,8 +5146,7 @@
|
|||
"notes": "",
|
||||
"hidden": false,
|
||||
"required": false,
|
||||
"index": true,
|
||||
"requires_user_context": true
|
||||
"index": true
|
||||
},
|
||||
{
|
||||
"name": "datetime",
|
||||
|
|
@ -10781,8 +10777,8 @@
|
|||
],
|
||||
"evented": false,
|
||||
"cacheable": false,
|
||||
"notes": "",
|
||||
"examples": "See Firefox extensions by user as well as information about their creator and automatic update status.\n```\nSELECT u.username, f.identifier, f.creator, f.description, f.version, f.autoupdate FROM users u CROSS JOIN firefox_addons f USING (uid) WHERE f.active='1';\n```",
|
||||
"notes": "Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)",
|
||||
"examples": "```\nSELECT * FROM users CROSS JOIN firefox_addons USING (uid);\n```\nSee Firefox extensions by user as well as information about their creator and automatic update status.\n```\nSELECT u.username, f.identifier, f.creator, f.description, f.version, f.autoupdate FROM users u CROSS JOIN firefox_addons f USING (uid) WHERE f.active='1';\n```",
|
||||
"columns": [
|
||||
{
|
||||
"name": "uid",
|
||||
|
|
@ -10791,8 +10787,7 @@
|
|||
"notes": "",
|
||||
"hidden": false,
|
||||
"required": false,
|
||||
"index": false,
|
||||
"requires_user_context": true
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
|
|
@ -13306,8 +13301,8 @@
|
|||
],
|
||||
"evented": false,
|
||||
"cacheable": false,
|
||||
"notes": "",
|
||||
"examples": "```\nselect * from users join known_hosts using (uid)\n```",
|
||||
"notes": "- Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)",
|
||||
"examples": "```\nSELECT * FROM users CROSS JOIN known_hosts USING (uid);\n```",
|
||||
"columns": [
|
||||
{
|
||||
"name": "uid",
|
||||
|
|
@ -13316,8 +13311,7 @@
|
|||
"notes": "",
|
||||
"hidden": false,
|
||||
"required": false,
|
||||
"index": true,
|
||||
"requires_user_context": true
|
||||
"index": true
|
||||
},
|
||||
{
|
||||
"name": "key",
|
||||
|
|
@ -18786,6 +18780,186 @@
|
|||
],
|
||||
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/package_receipts.yml"
|
||||
},
|
||||
{
|
||||
"name": "parse_ini",
|
||||
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).",
|
||||
"description": "Parse a file as INI configuration.",
|
||||
"platforms": [
|
||||
"darwin",
|
||||
"windows",
|
||||
"linux"
|
||||
],
|
||||
"evented": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "path",
|
||||
"description": "Path of the file to read.",
|
||||
"required": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "fullkey",
|
||||
"description": "Key including any parent keys.",
|
||||
"type": "text",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "parent",
|
||||
"description": "Parent key when keys are nested in the document.",
|
||||
"required": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "key",
|
||||
"description": "JSON key or array index.",
|
||||
"required": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"description": "JSON value",
|
||||
"required": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"url": "https://fleetdm.com/tables/parse_ini",
|
||||
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/parse_ini.yml"
|
||||
},
|
||||
{
|
||||
"name": "parse_json",
|
||||
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).",
|
||||
"description": "Parses an entire file as JSON. See `parse_jsonl` where multiple JSON documents are supported.",
|
||||
"platforms": [
|
||||
"darwin",
|
||||
"windows",
|
||||
"linux"
|
||||
],
|
||||
"evented": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "path",
|
||||
"description": "Path of the file to read.",
|
||||
"required": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "fullkey",
|
||||
"description": "Same as `key` in this table. See `parse_jsonl` where multiple JSON documents are supported.",
|
||||
"required": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "parent",
|
||||
"description": "Parent key when keys are nested in the document.",
|
||||
"required": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "key",
|
||||
"description": "JSON key or array index.",
|
||||
"required": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"description": "JSON value",
|
||||
"required": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"url": "https://fleetdm.com/tables/parse_json",
|
||||
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/parse_json.yml"
|
||||
},
|
||||
{
|
||||
"name": "parse_jsonl",
|
||||
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).",
|
||||
"description": "Parses each line of a file as a separate JSON document. See `parse_json` to treat an entire file as a single JSON document.",
|
||||
"platforms": [
|
||||
"darwin",
|
||||
"windows",
|
||||
"linux"
|
||||
],
|
||||
"evented": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "path",
|
||||
"description": "Path of the file to read.",
|
||||
"required": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "fullkey",
|
||||
"description": "Key including any parent keys or document indices.",
|
||||
"required": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "parent",
|
||||
"description": "Parent key when keys are nested in the document.",
|
||||
"required": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "key",
|
||||
"description": "INI key",
|
||||
"required": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"description": "INI value",
|
||||
"required": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"url": "https://fleetdm.com/tables/parse_jsonl",
|
||||
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/parse_jsonl.yml"
|
||||
},
|
||||
{
|
||||
"name": "parse_xml",
|
||||
"notes": "This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).",
|
||||
"description": "Parses a file as an XML document.",
|
||||
"platforms": [
|
||||
"darwin",
|
||||
"windows",
|
||||
"linux"
|
||||
],
|
||||
"evented": false,
|
||||
"columns": [
|
||||
{
|
||||
"name": "path",
|
||||
"description": "Path of the file to read.",
|
||||
"required": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "fullkey",
|
||||
"description": "Key including any parent keys.",
|
||||
"required": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "parent",
|
||||
"description": "Parent key when keys are nested in the document.",
|
||||
"required": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "key",
|
||||
"description": "XML key",
|
||||
"required": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"description": "XML value",
|
||||
"required": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"url": "https://fleetdm.com/tables/parse_xml",
|
||||
"fleetRepoUrl": "https://github.com/fleetdm/fleet/blob/main/schema/tables/parse_xml.yml"
|
||||
},
|
||||
{
|
||||
"name": "password_policy",
|
||||
"description": "Password Policies for macOS.",
|
||||
|
|
@ -19795,8 +19969,8 @@
|
|||
],
|
||||
"evented": false,
|
||||
"cacheable": false,
|
||||
"notes": "- The `value` column will be empty for keys that contain binary data.",
|
||||
"examples": "This table reads a huge amount of preferences, including on third-party apps. This query will show how many users are enrolled to TouchID.\n```\nSELECT * FROM preferences WHERE subkey='dailyEvents/2/enrolledUserCount';\n```",
|
||||
"notes": "- Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)\n- The `value` column will be empty for keys that contain binary data.",
|
||||
"examples": "This table reads a huge amount of preferences, including on third-party apps.\n```\nSELECT * FROM users CROSS JOIN preferences USING (username);\n```",
|
||||
"columns": [
|
||||
{
|
||||
"name": "domain",
|
||||
|
|
@ -19850,8 +20024,7 @@
|
|||
"notes": "",
|
||||
"hidden": false,
|
||||
"required": false,
|
||||
"index": false,
|
||||
"requires_user_context": true
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "host",
|
||||
|
|
@ -22842,8 +23015,8 @@
|
|||
],
|
||||
"evented": false,
|
||||
"cacheable": false,
|
||||
"notes": "- Includes installed extensions for all system users.",
|
||||
"examples": "```\nselect count(*) from users JOIN safari_extensions using (uid)\n```",
|
||||
"notes": "- Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table) - Includes installed extensions for all system users.",
|
||||
"examples": "```\nSELECT * FROM users CROSS JOIN safari_extensions USING (uid);\n```",
|
||||
"columns": [
|
||||
{
|
||||
"name": "uid",
|
||||
|
|
@ -22852,8 +23025,7 @@
|
|||
"notes": "",
|
||||
"hidden": false,
|
||||
"required": false,
|
||||
"index": true,
|
||||
"requires_user_context": true
|
||||
"index": true
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
|
|
@ -24342,8 +24514,8 @@
|
|||
],
|
||||
"evented": false,
|
||||
"cacheable": false,
|
||||
"notes": "",
|
||||
"examples": "See command line executions and related timestamps. Useful for threat hunting when a device is suspected of being compromised.\n```\nSELECT u.username, s.command, s.time FROM users u CROSS JOIN shell_history s USING (uid);\n```",
|
||||
"notes": "- Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)",
|
||||
"examples": "```\nSELECT * FROM users CROSS JOIN shell_history USING (uid);\n```\nSee command line executions and related timestamps. Useful for threat hunting when a device is suspected of being compromised.\n```\nSELECT u.username, s.command, s.time FROM users u CROSS JOIN shell_history s USING (uid);\n```",
|
||||
"columns": [
|
||||
{
|
||||
"name": "uid",
|
||||
|
|
@ -24352,8 +24524,7 @@
|
|||
"notes": "",
|
||||
"hidden": false,
|
||||
"required": false,
|
||||
"index": false,
|
||||
"requires_user_context": true
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "time",
|
||||
|
|
@ -25024,8 +25195,8 @@
|
|||
],
|
||||
"evented": false,
|
||||
"cacheable": false,
|
||||
"notes": "",
|
||||
"examples": "Identify SSH clients configured to send their locales to the server.\n```\nSELECT * FROM ssh_configs WHERE option='sendenv lang lc_*'; \n```",
|
||||
"notes": "Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)",
|
||||
"examples": "```\nSELECT * FROM users CROSS JOIN ssh_configs USING (uid);\n```\nIdentify SSH clients configured to send their locales to the server.\n```\nSELECT * FROM ssh_configs WHERE option='sendenv lang lc_*'; \n```",
|
||||
"columns": [
|
||||
{
|
||||
"name": "uid",
|
||||
|
|
@ -25034,8 +25205,7 @@
|
|||
"notes": "",
|
||||
"hidden": false,
|
||||
"required": false,
|
||||
"index": false,
|
||||
"requires_user_context": true
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "block",
|
||||
|
|
@ -26918,8 +27088,8 @@
|
|||
],
|
||||
"evented": false,
|
||||
"cacheable": false,
|
||||
"notes": "",
|
||||
"examples": "Identify SSH keys stored in clear text in user directories\n```\nSELECT * FROM users JOIN user_ssh_keys USING (uid) WHERE encrypted = 0;\n```",
|
||||
"notes": "Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)",
|
||||
"examples": "```\nSELECT * FROM users CROSS JOIN user_ssh_keys USING (uid);\n```\nIdentify SSH keys stored in clear text in user directories\n```\nSELECT * FROM users JOIN user_ssh_keys USING (uid) WHERE encrypted = 0;\n```",
|
||||
"columns": [
|
||||
{
|
||||
"name": "uid",
|
||||
|
|
@ -26928,8 +27098,7 @@
|
|||
"notes": "",
|
||||
"hidden": false,
|
||||
"required": false,
|
||||
"index": false,
|
||||
"requires_user_context": true
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "path",
|
||||
|
|
@ -27509,8 +27678,8 @@
|
|||
],
|
||||
"evented": false,
|
||||
"cacheable": false,
|
||||
"notes": "Querying this table requires joining against the `users` table.",
|
||||
"examples": "List the name, publisher, and version of the Visual Studio (VS) Code extensions installed on hosts.\n```\nSELECT extension.name, extension.publisher, extension.version FROM users JOIN vscode_extensions extension USING (uid);\n```",
|
||||
"notes": "Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)",
|
||||
"examples": "```\nSELECT * FROM users CROSS JOIN vscode_extensions USING (uid);\n```\n\nList the name, publisher, and version of the Visual Studio (VS) Code extensions installed on hosts.\n```\nSELECT extension.name, extension.publisher, extension.version FROM users JOIN vscode_extensions extension USING (uid);\n```",
|
||||
"columns": [
|
||||
{
|
||||
"name": "name",
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
name: authorized_keys
|
||||
examples: >-
|
||||
List the SSH keys allowed to connect to this host.
|
||||
|
||||
```
|
||||
|
||||
SELECT key FROM authorized_keys;
|
||||
|
||||
|
||||
SELECT * FROM users CROSS JOIN authorized_keys USING (uid);
|
||||
|
||||
```
|
||||
columns:
|
||||
- name: pid_with_namespace
|
||||
platforms:
|
||||
- linux
|
||||
- name: uid
|
||||
requires_user_context: true
|
||||
notes: Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ examples: >-
|
|||
|
||||
```
|
||||
|
||||
SELECT bp.name, bp.identifier, bp.version FROM browser_plugins bp JOIN users u on bp.uid = u.uid ;
|
||||
SELECT * FROM users CROSS JOIN browser_plugins USING (uid);
|
||||
|
||||
```
|
||||
|
||||
notes: Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
name: chrome_extension_content_scripts
|
||||
columns:
|
||||
- name: uid
|
||||
requires_user_context: true
|
||||
examples: >-
|
||||
```
|
||||
|
||||
SELECT * FROM users CROSS JOIN chrome_extension_content_scripts USING (uid);
|
||||
|
||||
```
|
||||
notes: Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@ platforms:
|
|||
- chrome
|
||||
description: Installed extensions (plugins) for [Chromium-based](https://en.wikipedia.org/wiki/Chromium_(web_browser)) browsers, including [Google Chrome](https://en.wikipedia.org/wiki/Google_Chrome), [Edge](https://en.wikipedia.org/wiki/Microsoft_Edge), [Brave](https://en.wikipedia.org/wiki/Brave_(web_browser)), [Opera](https://en.wikipedia.org/wiki/Opera_(web_browser)), and [Yandex](https://en.wikipedia.org/wiki/Yandex_Browser).
|
||||
examples: >-
|
||||
```
|
||||
|
||||
SELECT * FROM users CROSS JOIN chrome_extensions USING (uid);
|
||||
|
||||
```
|
||||
|
||||
List Chrome extensions by user and profile which have full access to HTTPS
|
||||
browsing.
|
||||
|
||||
|
|
@ -14,9 +20,12 @@ examples: >-
|
|||
SELECT u.username, ce.name, ce.description, ce.version, ce.profile, ce.permissions FROM users u CROSS JOIN chrome_extensions ce USING (uid) WHERE ce.permissions LIKE '%%https://*/*%%';
|
||||
|
||||
```
|
||||
notes: |
|
||||
Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)
|
||||
|
||||
On ChromeOS, this table requires the [fleetd Chrome extension](https://fleetdm.com/docs/using-fleet/chromeos).
|
||||
columns:
|
||||
- name: uid
|
||||
requires_user_context: true
|
||||
platforms:
|
||||
- darwin
|
||||
- windows
|
||||
|
|
@ -106,5 +115,3 @@ columns:
|
|||
- darwin
|
||||
- windows
|
||||
- linux
|
||||
notes: |
|
||||
- On ChromeOS, this table requires the [fleetd Chrome extension](https://fleetdm.com/docs/using-fleet/chromeos).
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
name: crashes
|
||||
examples: >-
|
||||
See software responsible for crashes. This can be useful to detect what the
|
||||
most problematic software in your environment is.
|
||||
|
||||
```
|
||||
|
||||
SELECT crash_path, identifier, responsible, exception_type FROM crashes;
|
||||
SELECT * FROM users CROSS JOIN crashes USING (uid);
|
||||
|
||||
```
|
||||
notes: Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)
|
||||
columns:
|
||||
- name: uid
|
||||
requires_user_context: true
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
name: firefox_addons
|
||||
description: Firefox browser [add-ons](https://addons.mozilla.org/en-US/firefox/) (plugins).
|
||||
examples: >-
|
||||
```
|
||||
|
||||
SELECT * FROM users CROSS JOIN firefox_addons USING (uid);
|
||||
|
||||
```
|
||||
|
||||
See Firefox extensions by user as well as information about their creator and
|
||||
automatic update status.
|
||||
|
||||
|
|
@ -9,6 +15,6 @@ examples: >-
|
|||
SELECT u.username, f.identifier, f.creator, f.description, f.version, f.autoupdate FROM users u CROSS JOIN firefox_addons f USING (uid) WHERE f.active='1';
|
||||
|
||||
```
|
||||
notes: Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)
|
||||
columns:
|
||||
- name: uid
|
||||
requires_user_context: true
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
name: known_hosts
|
||||
columns:
|
||||
- name: uid
|
||||
requires_user_context: true
|
||||
examples: >-
|
||||
```
|
||||
|
||||
SELECT * FROM users CROSS JOIN known_hosts USING (uid);
|
||||
|
||||
```
|
||||
|
||||
notes: >-
|
||||
- Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)
|
||||
|
|
|
|||
29
schema/tables/parse_ini.yml
Normal file
29
schema/tables/parse_ini.yml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
name: parse_ini
|
||||
notes: This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).
|
||||
description: Parse a file as INI configuration.
|
||||
platforms:
|
||||
- darwin
|
||||
- windows
|
||||
- linux
|
||||
evented: false
|
||||
columns:
|
||||
- name: path
|
||||
description: Path of the file to read.
|
||||
required: true
|
||||
type: text
|
||||
- name: fullkey
|
||||
description: Key including any parent keys.
|
||||
type: text
|
||||
required: false
|
||||
- name: parent
|
||||
description: Parent key when keys are nested in the document.
|
||||
required: false
|
||||
type: text
|
||||
- name: key
|
||||
description: JSON key or array index.
|
||||
required: false
|
||||
type: text
|
||||
- name: value
|
||||
description: JSON value
|
||||
required: false
|
||||
type: text
|
||||
29
schema/tables/parse_json.yml
Normal file
29
schema/tables/parse_json.yml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
name: parse_json
|
||||
notes: This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).
|
||||
description: Parses an entire file as JSON. See `parse_jsonl` where multiple JSON documents are supported.
|
||||
platforms:
|
||||
- darwin
|
||||
- windows
|
||||
- linux
|
||||
evented: false
|
||||
columns:
|
||||
- name: path
|
||||
description: Path of the file to read.
|
||||
required: true
|
||||
type: text
|
||||
- name: fullkey
|
||||
description: Same as `key` in this table. See `parse_jsonl` where multiple JSON documents are supported.
|
||||
required: false
|
||||
type: text
|
||||
- name: parent
|
||||
description: Parent key when keys are nested in the document.
|
||||
required: false
|
||||
type: text
|
||||
- name: key
|
||||
description: JSON key or array index.
|
||||
required: false
|
||||
type: text
|
||||
- name: value
|
||||
description: JSON value
|
||||
required: false
|
||||
type: text
|
||||
29
schema/tables/parse_jsonl.yml
Normal file
29
schema/tables/parse_jsonl.yml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
name: parse_jsonl
|
||||
notes: This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).
|
||||
description: Parses each line of a file as a separate JSON document. See `parse_json` to treat an entire file as a single JSON document.
|
||||
platforms:
|
||||
- darwin
|
||||
- windows
|
||||
- linux
|
||||
evented: false
|
||||
columns:
|
||||
- name: path
|
||||
description: Path of the file to read.
|
||||
required: true
|
||||
type: text
|
||||
- name: fullkey
|
||||
description: Key including any parent keys or document indices.
|
||||
required: false
|
||||
type: text
|
||||
- name: parent
|
||||
description: Parent key when keys are nested in the document.
|
||||
required: false
|
||||
type: text
|
||||
- name: key
|
||||
description: INI key
|
||||
required: false
|
||||
type: text
|
||||
- name: value
|
||||
description: INI value
|
||||
required: false
|
||||
type: text
|
||||
29
schema/tables/parse_xml.yml
Normal file
29
schema/tables/parse_xml.yml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
name: parse_xml
|
||||
notes: This table is not a core osquery table. It is included as part of [Fleetd](https://fleetdm.com/docs/using-fleet/orbit), the osquery manager from Fleet. Fleetd can be built with [fleetctl](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).
|
||||
description: Parses a file as an XML document.
|
||||
platforms:
|
||||
- darwin
|
||||
- windows
|
||||
- linux
|
||||
evented: false
|
||||
columns:
|
||||
- name: path
|
||||
description: Path of the file to read.
|
||||
required: true
|
||||
type: text
|
||||
- name: fullkey
|
||||
description: Key including any parent keys.
|
||||
required: false
|
||||
type: text
|
||||
- name: parent
|
||||
description: Parent key when keys are nested in the document.
|
||||
required: false
|
||||
type: text
|
||||
- name: key
|
||||
description: XML key
|
||||
required: false
|
||||
type: text
|
||||
- name: value
|
||||
description: XML value
|
||||
required: false
|
||||
type: text
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
name: preferences
|
||||
examples: >-
|
||||
This table reads a huge amount of preferences, including on third-party apps.
|
||||
This query will show how many users are enrolled to TouchID.
|
||||
|
||||
|
||||
```
|
||||
|
||||
SELECT * FROM preferences WHERE subkey='dailyEvents/2/enrolledUserCount';
|
||||
SELECT * FROM users CROSS JOIN preferences USING (username);
|
||||
|
||||
```
|
||||
notes: >-
|
||||
- Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)
|
||||
|
||||
- The `value` column will be empty for keys that contain binary data.
|
||||
columns:
|
||||
- name: username
|
||||
requires_user_context: true
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@ name: safari_extensions
|
|||
description: Installed Safari browser extensions (plugins).
|
||||
columns:
|
||||
- name: uid
|
||||
requires_user_context: true
|
||||
examples: >-
|
||||
```
|
||||
|
||||
SELECT * FROM users CROSS JOIN safari_extensions USING (uid);
|
||||
|
||||
```
|
||||
notes: >-
|
||||
- Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)
|
||||
- Includes installed extensions for all system users.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
name: shell_history
|
||||
examples: >-
|
||||
```
|
||||
|
||||
SELECT * FROM users CROSS JOIN shell_history USING (uid);
|
||||
|
||||
```
|
||||
|
||||
See command line executions and related timestamps. Useful for threat hunting
|
||||
when a device is suspected of being compromised.
|
||||
|
||||
|
|
@ -10,4 +16,7 @@ examples: >-
|
|||
```
|
||||
columns:
|
||||
- name: uid
|
||||
requires_user_context: true
|
||||
|
||||
|
||||
notes: >-
|
||||
- Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
name: ssh_configs
|
||||
examples: >-
|
||||
```
|
||||
|
||||
SELECT * FROM users CROSS JOIN ssh_configs USING (uid);
|
||||
|
||||
```
|
||||
|
||||
Identify SSH clients configured to send their locales to the server.
|
||||
|
||||
```
|
||||
|
|
@ -9,4 +15,4 @@ examples: >-
|
|||
```
|
||||
columns:
|
||||
- name: uid
|
||||
requires_user_context: true
|
||||
notes: Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)
|
||||
|
|
@ -1,5 +1,11 @@
|
|||
name: user_ssh_keys
|
||||
examples: >-
|
||||
```
|
||||
|
||||
SELECT * FROM users CROSS JOIN user_ssh_keys USING (uid);
|
||||
|
||||
```
|
||||
|
||||
Identify SSH keys stored in clear text in user directories
|
||||
|
||||
```
|
||||
|
|
@ -12,4 +18,4 @@ columns:
|
|||
platforms:
|
||||
- linux
|
||||
- name: uid
|
||||
requires_user_context: true
|
||||
notes: Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
name: vscode_extensions
|
||||
description: Installed extensions for [Visual Studio (VS) Code](https://code.visualstudio.com/).
|
||||
examples: >-
|
||||
```
|
||||
|
||||
SELECT * FROM users CROSS JOIN vscode_extensions USING (uid);
|
||||
|
||||
```
|
||||
|
||||
|
||||
List the name, publisher, and version of the Visual Studio (VS) Code extensions installed on hosts.
|
||||
|
||||
```
|
||||
|
|
@ -8,7 +15,7 @@ examples: >-
|
|||
SELECT extension.name, extension.publisher, extension.version FROM users JOIN vscode_extensions extension USING (uid);
|
||||
|
||||
```
|
||||
notes: Querying this table requires joining against the `users` table.
|
||||
notes: Querying this table requires joining against the `users` table. [Learn more](https://fleetdm.com/guides/osquery-consider-joining-against-the-users-table)
|
||||
columns:
|
||||
- name: name
|
||||
description: Extension Name
|
||||
|
|
|
|||
|
|
@ -1,16 +1,59 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
# Run this script in user context (not root).
|
||||
# Reference: https://wiki.winehq.org/MacOS
|
||||
# Wine can be installed without brew via a distribution such as https://github.com/Gcenx/macOS_Wine_builds/releases/tag/9.0, or by building from source.
|
||||
|
||||
# Check if brew is installed
|
||||
if ! command -v brew >/dev/null 2>&1 ; then
|
||||
echo "Homebrew is not installed. Please install Homebrew first. For instructions, see https://brew.sh/"
|
||||
exit 1
|
||||
brew_wine(){
|
||||
# Wine reference: https://wiki.winehq.org/MacOS
|
||||
# Wine can be installed without brew via a distribution such as https://github.com/Gcenx/macOS_Wine_builds/releases/tag/9.0 or by building from source.
|
||||
brew install --cask --no-quarantine https://raw.githubusercontent.com/Homebrew/homebrew-cask/1ecfe82f84e0f3c3c6b741d3ddc19a164c2cb18d/Casks/w/wine-stable.rb; exit 0
|
||||
}
|
||||
|
||||
|
||||
warn_wine(){
|
||||
printf "\nWARNING: The Wine app developer has an Apple Developer certificate but the\napp bundle post-installation will not be code-signed or notarized.\n\nDo you wish to proceed?\n\n"
|
||||
while true
|
||||
do
|
||||
read -r -p "install> " install
|
||||
case "$install" in
|
||||
y|yes|Y|YES) brew_wine ;;
|
||||
n|no|N|NO) printf "\nExiting...\n\n"; exit 1 ;;
|
||||
*) printf "\nPlease enter yes or no at the prompt...\n\n" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
# option to execute script in non-interactive mode
|
||||
while getopts 'n' option
|
||||
do
|
||||
case "$option" in
|
||||
n) mode=auto ;;
|
||||
*) : ;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
# prevent root execution
|
||||
if [ "$EUID" = 0 ]
|
||||
then
|
||||
printf "\nTo prevent unnecessary privilege elevation do not execute this script as the root user.\nExiting...\n\n"; exit 1
|
||||
fi
|
||||
|
||||
|
||||
# check if Homebrew is installed
|
||||
if ! command -v brew > /dev/null 2>&1
|
||||
then
|
||||
printf "\nHomebrew is not installed.\nPlease install Homebrew.\nFor instructions, see https://brew.sh/\n\n"; exit 1
|
||||
fi
|
||||
|
||||
|
||||
# install Wine
|
||||
if [ "$mode" = 'auto' ]
|
||||
then
|
||||
printf "\n%s executed in non-interactive mode.\n\n" "$0"; brew_wine
|
||||
else
|
||||
warn_wine
|
||||
fi
|
||||
|
||||
# Install wine via brew
|
||||
brew install --cask --no-quarantine https://raw.githubusercontent.com/Homebrew/homebrew-cask/1ecfe82f84e0f3c3c6b741d3ddc19a164c2cb18d/Casks/w/wine-stable.rb
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package cron
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -19,19 +19,19 @@ import (
|
|||
|
||||
const calendarConsumers = 18
|
||||
|
||||
func newCalendarSchedule(
|
||||
func NewCalendarSchedule(
|
||||
ctx context.Context,
|
||||
instanceID string,
|
||||
ds fleet.Datastore,
|
||||
interval time.Duration,
|
||||
logger kitlog.Logger,
|
||||
) (*schedule.Schedule, error) {
|
||||
const (
|
||||
name = string(fleet.CronCalendar)
|
||||
defaultInterval = 5 * time.Minute
|
||||
name = string(fleet.CronCalendar)
|
||||
)
|
||||
logger = kitlog.With(logger, "cron", name)
|
||||
s := schedule.New(
|
||||
ctx, name, instanceID, defaultInterval, ds, ds,
|
||||
ctx, name, instanceID, interval, ds, ds,
|
||||
schedule.WithAltLockID("calendar"),
|
||||
schedule.WithLogger(logger),
|
||||
schedule.WithJob(
|
||||
|
|
@ -222,7 +222,7 @@ func processCalendarFailingHosts(
|
|||
// thus we skip this entry.
|
||||
continue // continue with next host
|
||||
}
|
||||
if hostCalendarEvent.WebhookStatus == fleet.CalendarWebhookStatusPending {
|
||||
if hostCalendarEvent.WebhookStatus == fleet.CalendarWebhookStatusPending || hostCalendarEvent.WebhookStatus == fleet.CalendarWebhookStatusRetry {
|
||||
// This can happen if the host went offline (and never returned results)
|
||||
// after setting the webhook as pending.
|
||||
continue // continue with next host
|
||||
|
|
@ -318,9 +318,6 @@ func processFailingHostExistingCalendarEvent(
|
|||
}
|
||||
// Even if fields haven't changed we want to update the calendar_events.updated_at below.
|
||||
updated = true
|
||||
//
|
||||
// TODO(lucas): Check changing updatedEvent to UTC before consuming.
|
||||
//
|
||||
}
|
||||
|
||||
if updated {
|
||||
|
|
@ -367,8 +364,6 @@ func processFailingHostExistingCalendarEvent(
|
|||
return fmt.Errorf("update host calendar webhook status: %w", err)
|
||||
}
|
||||
|
||||
// TODO(lucas): If this doesn't work at scale, then implement a special refetch
|
||||
// for policies only.
|
||||
if err := ds.UpdateHostRefetchRequested(ctx, host.HostID, true); err != nil {
|
||||
return fmt.Errorf("refetch host: %w", err)
|
||||
}
|
||||
|
|
@ -676,7 +671,10 @@ func deleteCalendarEventsInParallel(
|
|||
go func() {
|
||||
defer wg.Done()
|
||||
for calEvent := range calendarEventCh {
|
||||
userCalendar := createUserCalendarFromConfig(ctx, calendarConfig, logger)
|
||||
var userCalendar fleet.UserCalendar
|
||||
if calendarConfig != nil {
|
||||
userCalendar = createUserCalendarFromConfig(ctx, calendarConfig, logger)
|
||||
}
|
||||
if err := deleteCalendarEvent(ctx, ds, userCalendar, calEvent); err != nil {
|
||||
level.Error(logger).Log("msg", "delete user calendar event", "err", err)
|
||||
continue
|
||||
|
|
@ -1,11 +1,8 @@
|
|||
package main
|
||||
package cron
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -17,7 +14,6 @@ import (
|
|||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/mock"
|
||||
kitlog "github.com/go-kit/log"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
@ -207,16 +203,19 @@ func TestCalendarEventsMultipleHosts(t *testing.T) {
|
|||
calendar.ClearMockEvents()
|
||||
})
|
||||
|
||||
// TODO(lucas): Test!
|
||||
webhookServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, "POST", r.Method)
|
||||
requestBodyBytes, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
t.Logf("webhook request: %s\n", requestBodyBytes)
|
||||
}))
|
||||
t.Cleanup(func() {
|
||||
webhookServer.Close()
|
||||
})
|
||||
//
|
||||
// Test setup
|
||||
//
|
||||
// team1:
|
||||
//
|
||||
// policyID1 (calendar)
|
||||
// policyID2 (calendar)
|
||||
//
|
||||
// hostID1 has user1@example.com not passing policies.
|
||||
// hostID2 has user2@example.com passing policies.
|
||||
// hostID3 does not have example.com email and is not passing policies.
|
||||
// hostID4 does not have example.com email and is passing policies.
|
||||
//
|
||||
|
||||
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||||
return &fleet.AppConfig{
|
||||
|
|
@ -242,7 +241,7 @@ func TestCalendarEventsMultipleHosts(t *testing.T) {
|
|||
Integrations: fleet.TeamIntegrations{
|
||||
GoogleCalendar: &fleet.TeamGoogleCalendarIntegration{
|
||||
Enable: true,
|
||||
WebhookURL: webhookServer.URL,
|
||||
WebhookURL: "https://foo.example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -268,12 +267,13 @@ func TestCalendarEventsMultipleHosts(t *testing.T) {
|
|||
|
||||
hostID1, userEmail1 := uint(100), "user1@example.com"
|
||||
hostID2, userEmail2 := uint(101), "user2@example.com"
|
||||
hostID3, userEmail3 := uint(102), "user3@other.com"
|
||||
hostID4, userEmail4 := uint(103), "user4@other.com"
|
||||
hostID3 := uint(102)
|
||||
hostID4 := uint(103)
|
||||
|
||||
ds.GetTeamHostsPolicyMembershipsFunc = func(
|
||||
ctx context.Context, domain string, teamID uint, policyIDs []uint,
|
||||
) ([]fleet.HostPolicyMembershipData, error) {
|
||||
require.Equal(t, "example.com", domain)
|
||||
require.Equal(t, teamID1, teamID)
|
||||
require.Equal(t, []uint{policyID1, policyID2}, policyIDs)
|
||||
return []fleet.HostPolicyMembershipData{
|
||||
|
|
@ -289,12 +289,12 @@ func TestCalendarEventsMultipleHosts(t *testing.T) {
|
|||
},
|
||||
{
|
||||
HostID: hostID3,
|
||||
Email: userEmail3,
|
||||
Email: "", // because it does not belong to example.com
|
||||
Passing: false,
|
||||
},
|
||||
{
|
||||
HostID: hostID4,
|
||||
Email: userEmail4,
|
||||
Email: "", // because it does not belong to example.com
|
||||
Passing: true,
|
||||
},
|
||||
}, nil
|
||||
|
|
@ -304,6 +304,10 @@ func TestCalendarEventsMultipleHosts(t *testing.T) {
|
|||
return nil, nil, notFoundErr{}
|
||||
}
|
||||
|
||||
var eventsMu sync.Mutex
|
||||
calendarEvents := make(map[string]*fleet.CalendarEvent)
|
||||
hostCalendarEvents := make(map[uint]*fleet.HostCalendarEvent)
|
||||
|
||||
ds.CreateOrUpdateCalendarEventFunc = func(ctx context.Context,
|
||||
email string,
|
||||
startTime, endTime time.Time,
|
||||
|
|
@ -311,26 +315,43 @@ func TestCalendarEventsMultipleHosts(t *testing.T) {
|
|||
hostID uint,
|
||||
webhookStatus fleet.CalendarWebhookStatus,
|
||||
) (*fleet.CalendarEvent, error) {
|
||||
switch email {
|
||||
case userEmail1:
|
||||
require.Equal(t, hostID1, hostID)
|
||||
case userEmail2:
|
||||
require.Equal(t, hostID2, hostID)
|
||||
case userEmail3:
|
||||
require.Equal(t, hostID3, hostID)
|
||||
case userEmail4:
|
||||
require.Equal(t, hostID4, hostID)
|
||||
}
|
||||
require.Equal(t, hostID1, hostID)
|
||||
require.Equal(t, userEmail1, email)
|
||||
require.Equal(t, fleet.CalendarWebhookStatusNone, webhookStatus)
|
||||
require.NotEmpty(t, data)
|
||||
require.NotZero(t, startTime)
|
||||
require.NotZero(t, endTime)
|
||||
// Currently, the returned calendar event is unused.
|
||||
|
||||
eventsMu.Lock()
|
||||
calendarEventID := uint(len(calendarEvents) + 1)
|
||||
calendarEvents[email] = &fleet.CalendarEvent{
|
||||
ID: calendarEventID,
|
||||
Email: email,
|
||||
StartTime: startTime,
|
||||
EndTime: endTime,
|
||||
Data: data,
|
||||
}
|
||||
hostCalendarEventID := uint(len(hostCalendarEvents) + 1)
|
||||
hostCalendarEvents[hostID] = &fleet.HostCalendarEvent{
|
||||
ID: hostCalendarEventID,
|
||||
HostID: hostID,
|
||||
CalendarEventID: calendarEventID,
|
||||
WebhookStatus: webhookStatus,
|
||||
}
|
||||
eventsMu.Unlock()
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err := cronCalendarEvents(ctx, ds, logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
eventsMu.Lock()
|
||||
require.Len(t, calendarEvents, 1)
|
||||
require.Len(t, hostCalendarEvents, 1)
|
||||
eventsMu.Unlock()
|
||||
|
||||
createdCalendarEvents := calendar.ListGoogleMockEvents()
|
||||
require.Len(t, createdCalendarEvents, 1)
|
||||
}
|
||||
|
||||
type notFoundErr struct{}
|
||||
|
|
@ -356,17 +377,6 @@ func TestCalendarEvents1KHosts(t *testing.T) {
|
|||
calendar.ClearMockEvents()
|
||||
})
|
||||
|
||||
// TODO(lucas): Use for the test.
|
||||
webhookServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, "POST", r.Method)
|
||||
requestBodyBytes, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
t.Logf("webhook request: %s\n", requestBodyBytes)
|
||||
}))
|
||||
t.Cleanup(func() {
|
||||
webhookServer.Close()
|
||||
})
|
||||
|
||||
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||||
return &fleet.AppConfig{
|
||||
Integrations: fleet.Integrations{
|
||||
|
|
@ -395,7 +405,7 @@ func TestCalendarEvents1KHosts(t *testing.T) {
|
|||
Integrations: fleet.TeamIntegrations{
|
||||
GoogleCalendar: &fleet.TeamGoogleCalendarIntegration{
|
||||
Enable: true,
|
||||
WebhookURL: webhookServer.URL,
|
||||
WebhookURL: "https://foo.example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -406,7 +416,7 @@ func TestCalendarEvents1KHosts(t *testing.T) {
|
|||
Integrations: fleet.TeamIntegrations{
|
||||
GoogleCalendar: &fleet.TeamGoogleCalendarIntegration{
|
||||
Enable: true,
|
||||
WebhookURL: webhookServer.URL,
|
||||
WebhookURL: "https://foo.example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -417,7 +427,7 @@ func TestCalendarEvents1KHosts(t *testing.T) {
|
|||
Integrations: fleet.TeamIntegrations{
|
||||
GoogleCalendar: &fleet.TeamGoogleCalendarIntegration{
|
||||
Enable: true,
|
||||
WebhookURL: webhookServer.URL,
|
||||
WebhookURL: "https://foo.example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -428,7 +438,7 @@ func TestCalendarEvents1KHosts(t *testing.T) {
|
|||
Integrations: fleet.TeamIntegrations{
|
||||
GoogleCalendar: &fleet.TeamGoogleCalendarIntegration{
|
||||
Enable: true,
|
||||
WebhookURL: webhookServer.URL,
|
||||
WebhookURL: "https://foo.example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -439,7 +449,7 @@ func TestCalendarEvents1KHosts(t *testing.T) {
|
|||
Integrations: fleet.TeamIntegrations{
|
||||
GoogleCalendar: &fleet.TeamGoogleCalendarIntegration{
|
||||
Enable: true,
|
||||
WebhookURL: webhookServer.URL,
|
||||
WebhookURL: "https://foo.example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -52,7 +52,7 @@ func (ds *Datastore) CreateOrUpdateCalendarEvent(
|
|||
} else {
|
||||
stmt := `SELECT id FROM calendar_events WHERE email = ?`
|
||||
if err := sqlx.GetContext(ctx, tx, &id, stmt, email); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "query mdm solution id")
|
||||
return ctxerr.Wrap(ctx, err, "calendar event id")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1171,7 +1171,6 @@ func (ds *Datastore) GetCalendarPolicies(ctx context.Context, teamID uint) ([]fl
|
|||
return policies, nil
|
||||
}
|
||||
|
||||
// TODO(lucas): Must be tested at scale.
|
||||
func (ds *Datastore) GetTeamHostsPolicyMemberships(
|
||||
ctx context.Context,
|
||||
domain string,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ const (
|
|||
CalendarWebhookStatusNone CalendarWebhookStatus = iota
|
||||
CalendarWebhookStatusPending
|
||||
CalendarWebhookStatusSent
|
||||
CalendarWebhookStatusError
|
||||
CalendarWebhookStatusRetry
|
||||
)
|
||||
|
||||
type HostCalendarEvent struct {
|
||||
|
|
|
|||
|
|
@ -594,6 +594,11 @@ type Datastore interface {
|
|||
|
||||
PolicyQueriesForHost(ctx context.Context, host *Host) (map[string]string, error)
|
||||
|
||||
// GetTeamHostsPolicyMembmerships returns the hosts that belong to the given team and their pass/fail statuses
|
||||
// around the provided policyIDs.
|
||||
// - Returns hosts of the team that are failing one or more of the provided policies.
|
||||
// - Returns hosts of the team that are passing all the policies (or are not running any of the provided policies)
|
||||
// and have a calendar event scheduled.
|
||||
GetTeamHostsPolicyMemberships(ctx context.Context, domain string, teamID uint, policyIDs []uint) ([]HostPolicyMembershipData, error)
|
||||
GetCalendarPolicies(ctx context.Context, teamID uint) ([]PolicyCalendarData, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"crypto/x509"
|
||||
"encoding/binary"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
|
|
@ -36,18 +35,6 @@ func EncodeCertPEM(cert *x509.Certificate) []byte {
|
|||
return pem.EncodeToMemory(&block)
|
||||
}
|
||||
|
||||
func DecodeCertPEM(encoded []byte) (*x509.Certificate, error) {
|
||||
block, _ := pem.Decode(encoded)
|
||||
if block == nil {
|
||||
return nil, errors.New("no PEM-encoded data found")
|
||||
}
|
||||
if block.Type != "CERTIFICATE" {
|
||||
return nil, fmt.Errorf("unexpected block type %s", block.Type)
|
||||
}
|
||||
|
||||
return x509.ParseCertificate(block.Bytes)
|
||||
}
|
||||
|
||||
func EncodeCertRequestPEM(cert *x509.CertificateRequest) []byte {
|
||||
pemBlock := &pem.Block{
|
||||
Type: "CERTIFICATE REQUEST",
|
||||
|
|
@ -67,19 +54,6 @@ func EncodePrivateKeyPEM(key *rsa.PrivateKey) []byte {
|
|||
return pem.EncodeToMemory(&block)
|
||||
}
|
||||
|
||||
// DecodePrivateKeyPEM decodes PEM-encoded private key data.
|
||||
func DecodePrivateKeyPEM(encoded []byte) (*rsa.PrivateKey, error) {
|
||||
block, _ := pem.Decode(encoded)
|
||||
if block == nil {
|
||||
return nil, errors.New("no PEM-encoded data found")
|
||||
}
|
||||
if block.Type != "RSA PRIVATE KEY" {
|
||||
return nil, fmt.Errorf("unexpected block type %s", block.Type)
|
||||
}
|
||||
|
||||
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
}
|
||||
|
||||
// GenerateRandomPin generates a `lenght`-digit PIN number that takes into
|
||||
// account the current time as described in rfc4226 (for one time passwords)
|
||||
//
|
||||
|
|
|
|||
67
server/mdm/internal/commonmdm/commonmdm_test.go
Normal file
67
server/mdm/internal/commonmdm/commonmdm_test.go
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package commonmdm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestResolveURL(t *testing.T) {
|
||||
type testCase struct {
|
||||
serverURL string
|
||||
relPath string
|
||||
cleanQuery bool
|
||||
expected string
|
||||
expectErr bool
|
||||
}
|
||||
|
||||
testCases := []testCase{
|
||||
{
|
||||
serverURL: "http://example.com",
|
||||
relPath: "path/to/resource",
|
||||
cleanQuery: false,
|
||||
expected: "http://example.com/path/to/resource",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
serverURL: "http://example.com?query=string",
|
||||
relPath: "path",
|
||||
cleanQuery: true,
|
||||
expected: "http://example.com/path",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
serverURL: "http://example.com/base/",
|
||||
relPath: "/path",
|
||||
cleanQuery: false,
|
||||
expected: "http://example.com/base/path",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
serverURL: "http://example.com",
|
||||
relPath: "path/to/resource",
|
||||
cleanQuery: true,
|
||||
expected: "http://example.com/path/to/resource",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
serverURL: ":invalidurl",
|
||||
relPath: "path",
|
||||
cleanQuery: false,
|
||||
expected: "",
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.serverURL+"_"+tc.relPath, func(t *testing.T) {
|
||||
result, err := ResolveURL(tc.serverURL, tc.relPath, tc.cleanQuery)
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -81,10 +81,6 @@ func ResolveWindowsMDMEnroll(serverURL string) (string, error) {
|
|||
return commonmdm.ResolveURL(serverURL, MDE2EnrollPath, false)
|
||||
}
|
||||
|
||||
func ResolveWindowsMDMAuth(serverURL string) (string, error) {
|
||||
return commonmdm.ResolveURL(serverURL, MDE2AuthPath, false)
|
||||
}
|
||||
|
||||
func ResolveWindowsMDMManagement(serverURL string) (string, error) {
|
||||
return commonmdm.ResolveURL(serverURL, MDE2ManagementPath, false)
|
||||
}
|
||||
|
|
|
|||
201
server/mdm/microsoft/wstep_csr_test.go
Normal file
201
server/mdm/microsoft/wstep_csr_test.go
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
package microsoft_mdm
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetPublicKeyAlgorithmFromOID(t *testing.T) {
|
||||
testCases := []struct {
|
||||
oid asn1.ObjectIdentifier
|
||||
expected x509.PublicKeyAlgorithm
|
||||
}{
|
||||
{oidPublicKeyRSA, x509.RSA},
|
||||
{oidPublicKeyDSA, x509.DSA},
|
||||
{oidPublicKeyECDSA, x509.ECDSA},
|
||||
{oidPublicKeyEd25519, x509.Ed25519},
|
||||
{asn1.ObjectIdentifier{0, 0}, x509.UnknownPublicKeyAlgorithm},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.oid.String(), func(t *testing.T) {
|
||||
result := getPublicKeyAlgorithmFromOID(tc.oid)
|
||||
require.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The following tests were taken from the Go standard library (since the wstep
|
||||
// code was taken from there as well)
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
|
||||
var pemPrivateKey = testingKey(`
|
||||
-----BEGIN RSA TESTING KEY-----
|
||||
MIICXAIBAAKBgQCxoeCUW5KJxNPxMp+KmCxKLc1Zv9Ny+4CFqcUXVUYH69L3mQ7v
|
||||
IWrJ9GBfcaA7BPQqUlWxWM+OCEQZH1EZNIuqRMNQVuIGCbz5UQ8w6tS0gcgdeGX7
|
||||
J7jgCQ4RK3F/PuCM38QBLaHx988qG8NMc6VKErBjctCXFHQt14lerd5KpQIDAQAB
|
||||
AoGAYrf6Hbk+mT5AI33k2Jt1kcweodBP7UkExkPxeuQzRVe0KVJw0EkcFhywKpr1
|
||||
V5eLMrILWcJnpyHE5slWwtFHBG6a5fLaNtsBBtcAIfqTQ0Vfj5c6SzVaJv0Z5rOd
|
||||
7gQF6isy3t3w9IF3We9wXQKzT6q5ypPGdm6fciKQ8RnzREkCQQDZwppKATqQ41/R
|
||||
vhSj90fFifrGE6aVKC1hgSpxGQa4oIdsYYHwMzyhBmWW9Xv/R+fPyr8ZwPxp2c12
|
||||
33QwOLPLAkEA0NNUb+z4ebVVHyvSwF5jhfJxigim+s49KuzJ1+A2RaSApGyBZiwS
|
||||
rWvWkB471POAKUYt5ykIWVZ83zcceQiNTwJBAMJUFQZX5GDqWFc/zwGoKkeR49Yi
|
||||
MTXIvf7Wmv6E++eFcnT461FlGAUHRV+bQQXGsItR/opIG7mGogIkVXa3E1MCQARX
|
||||
AAA7eoZ9AEHflUeuLn9QJI/r0hyQQLEtrpwv6rDT1GCWaLII5HJ6NUFVf4TTcqxo
|
||||
6vdM4QGKTJoO+SaCyP0CQFdpcxSAuzpFcKv0IlJ8XzS/cy+mweCMwyJ1PFEc4FX6
|
||||
wg/HcAJWY60xZTJDFN+Qfx8ZQvBEin6c2/h+zZi5IVY=
|
||||
-----END RSA TESTING KEY-----
|
||||
`)
|
||||
|
||||
var testPrivateKey *rsa.PrivateKey
|
||||
|
||||
func init() {
|
||||
block, _ := pem.Decode([]byte(pemPrivateKey))
|
||||
|
||||
var err error
|
||||
if testPrivateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
|
||||
panic("Failed to parse private key: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateCertificateRequest(t *testing.T) {
|
||||
random := rand.Reader
|
||||
|
||||
ecdsa256Priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate ECDSA key: %s", err)
|
||||
}
|
||||
|
||||
ecdsa384Priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate ECDSA key: %s", err)
|
||||
}
|
||||
|
||||
ecdsa521Priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate ECDSA key: %s", err)
|
||||
}
|
||||
|
||||
_, ed25519Priv, err := ed25519.GenerateKey(random)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate Ed25519 key: %s", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
priv interface{}
|
||||
sigAlgo x509.SignatureAlgorithm
|
||||
}{
|
||||
{"RSA", testPrivateKey, x509.SHA1WithRSA},
|
||||
{"ECDSA-256", ecdsa256Priv, x509.ECDSAWithSHA1},
|
||||
{"ECDSA-384", ecdsa384Priv, x509.ECDSAWithSHA1},
|
||||
{"ECDSA-521", ecdsa521Priv, x509.ECDSAWithSHA1},
|
||||
{"Ed25519", ed25519Priv, x509.PureEd25519},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
template := x509.CertificateRequest{
|
||||
Subject: pkix.Name{
|
||||
CommonName: "test.example.com",
|
||||
Organization: []string{"Σ Acme Co"},
|
||||
},
|
||||
SignatureAlgorithm: test.sigAlgo,
|
||||
DNSNames: []string{"test.example.com"},
|
||||
EmailAddresses: []string{"gopher@golang.org"},
|
||||
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1).To4(), net.ParseIP("2001:4860:0:2001::68")},
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificateRequest(random, &template, test.priv)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create certificate request: %s", test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
out, err := ParseCertificateRequestFromWindowsDevice(derBytes)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create certificate request: %s", test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = out.CheckSignature()
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to check certificate request signature: %s", test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if out.Subject.CommonName != template.Subject.CommonName {
|
||||
t.Errorf("%s: output subject common name and template subject common name don't match", test.name)
|
||||
} else if len(out.Subject.Organization) != len(template.Subject.Organization) {
|
||||
t.Errorf("%s: output subject organisation and template subject organisation don't match", test.name)
|
||||
} else if len(out.DNSNames) != len(template.DNSNames) {
|
||||
t.Errorf("%s: output DNS names and template DNS names don't match", test.name)
|
||||
} else if len(out.EmailAddresses) != len(template.EmailAddresses) {
|
||||
t.Errorf("%s: output email addresses and template email addresses don't match", test.name)
|
||||
} else if len(out.IPAddresses) != len(template.IPAddresses) {
|
||||
t.Errorf("%s: output IP addresses and template IP addresses names don't match", test.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fromBase64(in string) []byte {
|
||||
out := make([]byte, base64.StdEncoding.DecodedLen(len(in)))
|
||||
n, err := base64.StdEncoding.Decode(out, []byte(in))
|
||||
if err != nil {
|
||||
panic("failed to base64 decode")
|
||||
}
|
||||
return out[:n]
|
||||
}
|
||||
|
||||
func TestParseCertificateRequestFromWindowsDevice(t *testing.T) {
|
||||
for _, csrBase64 := range csrBase64Array {
|
||||
csrBytes := fromBase64(csrBase64)
|
||||
csr, err := ParseCertificateRequestFromWindowsDevice(csrBytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse CSR: %s", err)
|
||||
}
|
||||
|
||||
if len(csr.EmailAddresses) != 1 || csr.EmailAddresses[0] != "gopher@golang.org" {
|
||||
t.Errorf("incorrect email addresses found: %v", csr.EmailAddresses)
|
||||
}
|
||||
|
||||
if len(csr.DNSNames) != 1 || csr.DNSNames[0] != "test.example.com" {
|
||||
t.Errorf("incorrect DNS names found: %v", csr.DNSNames)
|
||||
}
|
||||
|
||||
if len(csr.Subject.Country) != 1 || csr.Subject.Country[0] != "AU" {
|
||||
t.Errorf("incorrect Subject name: %v", csr.Subject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These CSR was generated with OpenSSL:
|
||||
//
|
||||
// openssl req -out CSR.csr -new -sha256 -nodes -keyout privateKey.key -config openssl.cnf
|
||||
//
|
||||
// With openssl.cnf containing the following sections:
|
||||
//
|
||||
// [ v3_req ]
|
||||
// basicConstraints = CA:FALSE
|
||||
// keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
// subjectAltName = email:gopher@golang.org,DNS:test.example.com
|
||||
// [ req_attributes ]
|
||||
// challengePassword = ignored challenge
|
||||
// unstructuredName = ignored unstructured name
|
||||
var csrBase64Array = [...]string{
|
||||
// Just [ v3_req ]
|
||||
"MIIDHDCCAgQCAQAwfjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAwwLQ29tbW9uIE5hbWUxITAfBgkqhkiG9w0BCQEWEnRlc3RAZW1haWwuYWRkcmVzczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK1GY4YFx2ujlZEOJxQVYmsjUnLsd5nFVnNpLE4cV+77sgv9NPNlB8uhn3MXt5leD34rm/2BisCHOifPucYlSrszo2beuKhvwn4+2FxDmWtBEMu/QA16L5IvoOfYZm/gJTsPwKDqvaR0tTU67a9OtxwNTBMI56YKtmwd/o8d3hYv9cg+9ZGAZ/gKONcg/OWYx/XRh6bd0g8DMbCikpWgXKDsvvK1Nk+VtkDO1JxuBaj4Lz/p/MifTfnHoqHxWOWl4EaTs4Ychxsv34/rSj1KD1tJqorIv5Xv2aqv4sjxfbrYzX4kvS5SC1goIovLnhj5UjmQ3Qy8u65eow/LLWw+YFcCAwEAAaBZMFcGCSqGSIb3DQEJDjFKMEgwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwLgYDVR0RBCcwJYERZ29waGVyQGdvbGFuZy5vcmeCEHRlc3QuZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAB6VPMRrchvNW61Tokyq3ZvO6/NoGIbuwUn54q6l5VZW0Ep5Nq8juhegSSnaJ0jrovmUgKDN9vEo2KxuAtwG6udS6Ami3zP+hRd4k9Q8djJPb78nrjzWiindLK5Fps9U5mMoi1ER8ViveyAOTfnZt/jsKUaRsscY2FzE9t9/o5moE6LTcHUS4Ap1eheR+J72WOnQYn3cifYaemsA9MJuLko+kQ6xseqttbh9zjqd9fiCSh/LNkzos9c+mg2yMADitaZinAh+HZi50ooEbjaT3erNq9O6RqwJlgD00g6MQdoz9bTAryCUhCQfkIaepmQ7BxS0pqWNW3MMwfDwx/Snz6g=",
|
||||
// Both [ v3_req ] and [ req_attributes ]
|
||||
"MIIDaTCCAlECAQAwfjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAwwLQ29tbW9uIE5hbWUxITAfBgkqhkiG9w0BCQEWEnRlc3RAZW1haWwuYWRkcmVzczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK1GY4YFx2ujlZEOJxQVYmsjUnLsd5nFVnNpLE4cV+77sgv9NPNlB8uhn3MXt5leD34rm/2BisCHOifPucYlSrszo2beuKhvwn4+2FxDmWtBEMu/QA16L5IvoOfYZm/gJTsPwKDqvaR0tTU67a9OtxwNTBMI56YKtmwd/o8d3hYv9cg+9ZGAZ/gKONcg/OWYx/XRh6bd0g8DMbCikpWgXKDsvvK1Nk+VtkDO1JxuBaj4Lz/p/MifTfnHoqHxWOWl4EaTs4Ychxsv34/rSj1KD1tJqorIv5Xv2aqv4sjxfbrYzX4kvS5SC1goIovLnhj5UjmQ3Qy8u65eow/LLWw+YFcCAwEAAaCBpTAgBgkqhkiG9w0BCQcxEwwRaWdub3JlZCBjaGFsbGVuZ2UwKAYJKoZIhvcNAQkCMRsMGWlnbm9yZWQgdW5zdHJ1Y3R1cmVkIG5hbWUwVwYJKoZIhvcNAQkOMUowSDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAuBgNVHREEJzAlgRFnb3BoZXJAZ29sYW5nLm9yZ4IQdGVzdC5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAgxe2N5O48EMsYE7o0rZBB0wi3Ov5/yYfnmmVI22Y3sP6VXbLDW0+UWIeSccOhzUCcZ/G4qcrfhhx6gTZTeA01nP7TdTJURvWAH5iFqj9sQ0qnLq6nEcVHij3sG6M5+BxAIVClQBk6lTCzgphc835Fjj6qSLuJ20XHdL5UfUbiJxx299CHgyBRL+hBUIPfz8p+ZgamyAuDLfnj54zzcRVyLlrmMLNPZNll1Q70RxoU6uWvLH8wB8vQe3Q/guSGubLyLRTUQVPh+dw1L4t8MKFWfX/48jwRM4gIRHFHPeAAE9D9YAoqdIvj/iFm/eQ++7DP8MDwOZWsXeB6jjwHuLmkQ==",
|
||||
}
|
||||
|
|
@ -297,7 +297,7 @@ func (svc *Service) CarveBlock(ctx context.Context, payload fleet.CarveBlockPayl
|
|||
logging.WithExtras(ctx, "validate_carve_error", errRecord, "carve_id", carve.ID)
|
||||
}
|
||||
|
||||
return ctxerr.Wrap(ctx, err, "validate carve block")
|
||||
return ctxerr.Wrap(ctx, badRequest("validate carve block"), err.Error())
|
||||
}
|
||||
|
||||
if err := svc.carveStore.NewBlock(ctx, carve, payload.BlockId, payload.Data); err != nil {
|
||||
|
|
|
|||
|
|
@ -477,7 +477,7 @@ func TestCarveCarveBlockBlockCountExceedError(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "block_id exceeds expected max")
|
||||
}
|
||||
|
||||
func TestCarveCarveBlockBlockCountMatchError(t *testing.T) {
|
||||
func TestCarveBlockCountMatchError(t *testing.T) {
|
||||
sessionId := "foobar"
|
||||
metadata := &fleet.CarveMetadata{
|
||||
ID: 2,
|
||||
|
|
@ -509,7 +509,8 @@ func TestCarveCarveBlockBlockCountMatchError(t *testing.T) {
|
|||
}
|
||||
|
||||
err := svc.CarveBlock(context.Background(), payload)
|
||||
require.Error(t, err)
|
||||
var be *fleet.BadRequestError
|
||||
require.ErrorAs(t, err, &be)
|
||||
assert.Contains(t, err.Error(), "block_id does not match")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5367,7 +5367,6 @@ func (s *integrationTestSuite) TestGoogleCalendarIntegrations() {
|
|||
}`, domain,
|
||||
)), http.StatusUnprocessableEntity,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
func (s *integrationTestSuite) TestQueriesBadRequests() {
|
||||
|
|
@ -6866,7 +6865,7 @@ func (s *integrationTestSuite) TestCarve() {
|
|||
SessionId: sid,
|
||||
RequestId: "r1",
|
||||
Data: []byte("p1."),
|
||||
}, http.StatusInternalServerError, &blockResp) // TODO: should be 400, see #4406
|
||||
}, http.StatusBadRequest, &blockResp)
|
||||
checkCarveError(1, "block_id does not match expected block (0): 1")
|
||||
|
||||
// sending a block with valid payload, block 0
|
||||
|
|
@ -6895,7 +6894,7 @@ func (s *integrationTestSuite) TestCarve() {
|
|||
SessionId: sid,
|
||||
RequestId: "r1",
|
||||
Data: []byte("p2."),
|
||||
}, http.StatusInternalServerError, &blockResp) // TODO: should be 400, see #4406
|
||||
}, http.StatusBadRequest, &blockResp)
|
||||
checkCarveError(1, "block_id does not match expected block (2): 1")
|
||||
|
||||
// sending final block with too many bytes
|
||||
|
|
@ -6905,7 +6904,7 @@ func (s *integrationTestSuite) TestCarve() {
|
|||
SessionId: sid,
|
||||
RequestId: "r1",
|
||||
Data: []byte("p3extra"),
|
||||
}, http.StatusInternalServerError, &blockResp) // TODO: should be 400, see #4406
|
||||
}, http.StatusBadRequest, &blockResp)
|
||||
checkCarveError(1, "exceeded declared block size 3: 7")
|
||||
|
||||
// sending actual final block
|
||||
|
|
@ -6925,7 +6924,7 @@ func (s *integrationTestSuite) TestCarve() {
|
|||
SessionId: sid,
|
||||
RequestId: "r1",
|
||||
Data: []byte("p4."),
|
||||
}, http.StatusInternalServerError, &blockResp) // TODO: should be 400, see #4406
|
||||
}, http.StatusBadRequest, &blockResp)
|
||||
checkCarveError(1, "block_id exceeds expected max (2): 3")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,18 +16,21 @@ import (
|
|||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/pubsub"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/ee/server/calendar"
|
||||
"github.com/fleetdm/fleet/v4/pkg/optjson"
|
||||
"github.com/fleetdm/fleet/v4/server/cron"
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/redis/redistest"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/live_query/live_query_mock"
|
||||
"github.com/fleetdm/fleet/v4/server/mdm"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
"github.com/fleetdm/fleet/v4/server/pubsub"
|
||||
"github.com/fleetdm/fleet/v4/server/service/schedule"
|
||||
"github.com/fleetdm/fleet/v4/server/test"
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/log"
|
||||
|
|
@ -48,7 +51,8 @@ func TestIntegrationsEnterprise(t *testing.T) {
|
|||
type integrationEnterpriseTestSuite struct {
|
||||
withServer
|
||||
suite.Suite
|
||||
redisPool fleet.RedisPool
|
||||
redisPool fleet.RedisPool
|
||||
calendarSchedule *schedule.Schedule
|
||||
|
||||
lq *live_query_mock.MockLiveQuery
|
||||
}
|
||||
|
|
@ -58,6 +62,7 @@ func (s *integrationEnterpriseTestSuite) SetupSuite() {
|
|||
|
||||
s.redisPool = redistest.SetupRedis(s.T(), "integration_enterprise", false, false, false)
|
||||
s.lq = live_query_mock.New(s.T())
|
||||
var calendarSchedule *schedule.Schedule
|
||||
config := TestServerOpts{
|
||||
License: &fleet.LicenseInfo{
|
||||
Tier: fleet.TierPremium,
|
||||
|
|
@ -67,6 +72,16 @@ func (s *integrationEnterpriseTestSuite) SetupSuite() {
|
|||
Lq: s.lq,
|
||||
Logger: log.NewLogfmtLogger(os.Stdout),
|
||||
EnableCachedDS: true,
|
||||
StartCronSchedules: []TestNewScheduleFunc{
|
||||
func(ctx context.Context, ds fleet.Datastore) fleet.NewCronScheduleFunc {
|
||||
return func() (fleet.CronSchedule, error) {
|
||||
// We set 24-hour interval so that it only runs when triggered.
|
||||
var err error
|
||||
calendarSchedule, err = cron.NewCalendarSchedule(ctx, s.T().Name(), s.ds, 24*time.Hour, log.NewJSONLogger(os.Stdout))
|
||||
return calendarSchedule, err
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
if os.Getenv("FLEET_INTEGRATION_TESTS_DISABLE_LOG") != "" {
|
||||
config.Logger = kitlog.NewNopLogger()
|
||||
|
|
@ -76,6 +91,7 @@ func (s *integrationEnterpriseTestSuite) SetupSuite() {
|
|||
s.users = users
|
||||
s.token = s.getTestAdminToken()
|
||||
s.cachedTokens = make(map[string]string)
|
||||
s.calendarSchedule = calendarSchedule
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TearDownTest() {
|
||||
|
|
@ -3605,7 +3621,6 @@ func (s *integrationEnterpriseTestSuite) TestOSVersions() {
|
|||
"GET", fmt.Sprintf("/api/latest/fleet/os_versions/%d", osinfo.OSVersionID), nil, http.StatusForbidden, &osVersionResp, "team_id",
|
||||
"99999",
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestMDMNotConfiguredEndpoints() {
|
||||
|
|
@ -7336,7 +7351,8 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareAuth() {
|
|||
Description: "desc team1",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.ds.AddHostsToTeam(ctx, &team1.ID, []uint{tmHost.ID}))
|
||||
err = s.ds.AddHostsToTeam(ctx, &team1.ID, []uint{tmHost.ID})
|
||||
require.NoError(t, err)
|
||||
team2, err := s.ds.NewTeam(ctx, &fleet.Team{
|
||||
ID: 43,
|
||||
Name: "team2",
|
||||
|
|
@ -7653,3 +7669,653 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareAuth() {
|
|||
// set the admin token again to avoid breaking other tests
|
||||
s.token = s.getTestAdminToken()
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestCalendarEvents() {
|
||||
ctx := context.Background()
|
||||
t := s.T()
|
||||
t.Cleanup(func() {
|
||||
calendar.ClearMockEvents()
|
||||
})
|
||||
currentAppCfg, err := s.ds.AppConfig(ctx)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err = s.ds.SaveAppConfig(ctx, currentAppCfg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
team1, err := s.ds.NewTeam(ctx, &fleet.Team{
|
||||
Name: "team1",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
team2, err := s.ds.NewTeam(ctx, &fleet.Team{
|
||||
Name: "team2",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
newHost := func(name string, teamID *uint) *fleet.Host {
|
||||
h, err := s.ds.NewHost(ctx, &fleet.Host{
|
||||
DetailUpdatedAt: time.Now(),
|
||||
LabelUpdatedAt: time.Now(),
|
||||
PolicyUpdatedAt: time.Now(),
|
||||
SeenTime: time.Now().Add(-1 * time.Minute),
|
||||
OsqueryHostID: ptr.String(t.Name() + name),
|
||||
NodeKey: ptr.String(t.Name() + name),
|
||||
UUID: uuid.New().String(),
|
||||
Hostname: fmt.Sprintf("%s.%s.local", name, t.Name()),
|
||||
Platform: "darwin",
|
||||
TeamID: teamID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return h
|
||||
}
|
||||
|
||||
host1Team1 := newHost("host1", &team1.ID)
|
||||
host2Team1 := newHost("host2", &team1.ID)
|
||||
host3Team2 := newHost("host3", &team2.ID)
|
||||
host4Team2 := newHost("host4", &team2.ID)
|
||||
_ = newHost("host5", nil) // global host
|
||||
|
||||
team1Policy1Calendar, err := s.ds.NewTeamPolicy(
|
||||
ctx, team1.ID, nil, fleet.PolicyPayload{
|
||||
Name: "team1Policy1Calendar",
|
||||
Query: "SELECT 1;",
|
||||
CalendarEventsEnabled: true,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
team1Policy2, err := s.ds.NewTeamPolicy(
|
||||
ctx, team1.ID, nil, fleet.PolicyPayload{
|
||||
Name: "team1Policy2",
|
||||
Query: "SELECT 2;",
|
||||
CalendarEventsEnabled: true,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
team2Policy1Calendar, err := s.ds.NewTeamPolicy(
|
||||
ctx, team1.ID, nil, fleet.PolicyPayload{
|
||||
Name: "team2Policy1Calendar",
|
||||
Query: "SELECT 3;",
|
||||
CalendarEventsEnabled: true,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
team2Policy2, err := s.ds.NewTeamPolicy(
|
||||
ctx, team1.ID, nil, fleet.PolicyPayload{
|
||||
Name: "team2Policy2",
|
||||
Query: "SELECT 4;",
|
||||
CalendarEventsEnabled: false,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
globalPolicy, err := s.ds.NewGlobalPolicy(
|
||||
ctx, nil, fleet.PolicyPayload{
|
||||
Name: "globalPolicy",
|
||||
Query: "SELECT 5;",
|
||||
CalendarEventsEnabled: false,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
genDistributedReqWithPolicyResults := func(host *fleet.Host, policyResults map[uint]*bool) submitDistributedQueryResultsRequestShim {
|
||||
var (
|
||||
results = make(map[string]json.RawMessage)
|
||||
statuses = make(map[string]interface{})
|
||||
messages = make(map[string]string)
|
||||
)
|
||||
for policyID, policyResult := range policyResults {
|
||||
distributedQueryName := hostPolicyQueryPrefix + fmt.Sprint(policyID)
|
||||
switch {
|
||||
case policyResult == nil:
|
||||
results[distributedQueryName] = json.RawMessage(`[]`)
|
||||
statuses[distributedQueryName] = 1
|
||||
messages[distributedQueryName] = "policy failed execution"
|
||||
case *policyResult:
|
||||
results[distributedQueryName] = json.RawMessage(`[{"1": "1"}]`)
|
||||
statuses[distributedQueryName] = 0
|
||||
case !*policyResult:
|
||||
results[distributedQueryName] = json.RawMessage(`[]`)
|
||||
statuses[distributedQueryName] = 0
|
||||
}
|
||||
}
|
||||
return submitDistributedQueryResultsRequestShim{
|
||||
NodeKey: *host.NodeKey,
|
||||
Results: results,
|
||||
Statuses: statuses,
|
||||
Messages: messages,
|
||||
Stats: map[string]*fleet.Stats{},
|
||||
}
|
||||
}
|
||||
|
||||
// host1Team1 is failing a calendar policy and not a non-calendar policy (no results for global).
|
||||
distributedResp := submitDistributedQueryResultsResponse{}
|
||||
s.DoJSON("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host1Team1,
|
||||
map[uint]*bool{
|
||||
team1Policy1Calendar.ID: ptr.Bool(false),
|
||||
team1Policy2.ID: ptr.Bool(true),
|
||||
globalPolicy.ID: nil,
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
|
||||
// host2Team1 is passing the calendar policy but not the non-calendar policy (no results for global).
|
||||
s.DoJSON("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host2Team1,
|
||||
map[uint]*bool{
|
||||
team2Policy1Calendar.ID: ptr.Bool(true),
|
||||
team2Policy2.ID: ptr.Bool(false),
|
||||
globalPolicy.ID: nil,
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
|
||||
// host3Team2 is passing team2Policy1Calendar and failing the global policy
|
||||
// (not results for team2Policy2).
|
||||
s.DoJSON("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host3Team2,
|
||||
map[uint]*bool{
|
||||
team2Policy1Calendar.ID: ptr.Bool(true),
|
||||
team2Policy2.ID: nil,
|
||||
globalPolicy.ID: ptr.Bool(false),
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
|
||||
// host4Team2 is not returning results for the calendar policy, failing the non-calendar
|
||||
// policy and passing the global policy.
|
||||
s.DoJSON("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host4Team2,
|
||||
map[uint]*bool{
|
||||
team2Policy1Calendar.ID: nil,
|
||||
team2Policy2.ID: ptr.Bool(false),
|
||||
globalPolicy.ID: ptr.Bool(true),
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
|
||||
// Trigger the calendar cron with the global feature is disabled.
|
||||
triggerAndWait(ctx, t, s.ds, s.calendarSchedule, 5*time.Second)
|
||||
|
||||
// No calendar events were created.
|
||||
allCalendarEvents, err := s.ds.ListCalendarEvents(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, allCalendarEvents)
|
||||
|
||||
// Set global configuration for the calendar feature.
|
||||
appCfg, err := s.ds.AppConfig(ctx)
|
||||
require.NoError(t, err)
|
||||
appCfg.Integrations.GoogleCalendar = []*fleet.GoogleCalendarIntegration{
|
||||
{
|
||||
Domain: "example.com",
|
||||
ApiKey: map[string]string{
|
||||
fleet.GoogleCalendarEmail: "calendar-mock@example.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
err = s.ds.SaveAppConfig(ctx, appCfg)
|
||||
require.NoError(t, err)
|
||||
time.Sleep(2 * time.Second) // Wait 2 seconds for the app config cache to clear.
|
||||
|
||||
triggerAndWait(ctx, t, s.ds, s.calendarSchedule, 5*time.Second)
|
||||
|
||||
// No calendar events were created because we are missing enabling it on the teams.
|
||||
allCalendarEvents, err = s.ds.ListCalendarEvents(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, allCalendarEvents)
|
||||
|
||||
// Run distributed/write for host4Team2 again, it should not attempt to trigger the webhook because
|
||||
// it's disabled for the teams.
|
||||
s.DoJSON("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host4Team2,
|
||||
map[uint]*bool{
|
||||
team2Policy1Calendar.ID: nil,
|
||||
team2Policy2.ID: ptr.Bool(false),
|
||||
globalPolicy.ID: ptr.Bool(true),
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
|
||||
var (
|
||||
team1Fired int
|
||||
team1FiredMu sync.Mutex
|
||||
)
|
||||
|
||||
team1WebhookFired := make(chan struct{})
|
||||
team1WebhookServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, "POST", r.Method)
|
||||
requestBodyBytes, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
t.Logf("team1 webhook request: %s\n", requestBodyBytes)
|
||||
team1FiredMu.Lock()
|
||||
team1Fired++
|
||||
team1WebhookFired <- struct{}{}
|
||||
team1FiredMu.Unlock()
|
||||
}))
|
||||
t.Cleanup(func() {
|
||||
team1WebhookServer.Close()
|
||||
})
|
||||
|
||||
team1.Config.Integrations.GoogleCalendar = &fleet.TeamGoogleCalendarIntegration{
|
||||
Enable: true,
|
||||
WebhookURL: team1WebhookServer.URL,
|
||||
}
|
||||
team1, err = s.ds.SaveTeam(ctx, team1)
|
||||
require.NoError(t, err)
|
||||
|
||||
var (
|
||||
team2Fired int
|
||||
team2FiredMu sync.Mutex
|
||||
)
|
||||
|
||||
team2WebhookServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, "POST", r.Method)
|
||||
requestBodyBytes, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
t.Logf("team2 webhook request: %s\n", requestBodyBytes)
|
||||
team2FiredMu.Lock()
|
||||
team2Fired++
|
||||
team2FiredMu.Unlock()
|
||||
}))
|
||||
t.Cleanup(func() {
|
||||
team2WebhookServer.Close()
|
||||
})
|
||||
|
||||
team2.Config.Integrations.GoogleCalendar = &fleet.TeamGoogleCalendarIntegration{
|
||||
Enable: true,
|
||||
WebhookURL: team2WebhookServer.URL,
|
||||
}
|
||||
team2, err = s.ds.SaveTeam(ctx, team2)
|
||||
require.NoError(t, err)
|
||||
|
||||
//
|
||||
// Same distributed/write as before but they should not fire yet.
|
||||
//
|
||||
|
||||
// host1Team1 is failing a calendar policy and not a non-calendar policy (no results for global).
|
||||
s.DoJSON("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host1Team1,
|
||||
map[uint]*bool{
|
||||
team1Policy1Calendar.ID: ptr.Bool(false),
|
||||
team1Policy2.ID: ptr.Bool(true),
|
||||
globalPolicy.ID: nil,
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
|
||||
// host2Team1 is passing the calendar policy but not the non-calendar policy (no results for global).
|
||||
s.DoJSON("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host2Team1,
|
||||
map[uint]*bool{
|
||||
team2Policy1Calendar.ID: ptr.Bool(true),
|
||||
team2Policy2.ID: ptr.Bool(false),
|
||||
globalPolicy.ID: nil,
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
|
||||
// host3Team2 is passing team2Policy1Calendar and failing the global policy
|
||||
// (not results for team2Policy2).
|
||||
s.DoJSON("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host3Team2,
|
||||
map[uint]*bool{
|
||||
team2Policy1Calendar.ID: ptr.Bool(true),
|
||||
team2Policy2.ID: nil,
|
||||
globalPolicy.ID: ptr.Bool(false),
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
|
||||
// host4Team2 is not returning results for the calendar policy, failing the non-calendar
|
||||
// policy and passing the global policy.
|
||||
s.DoJSON("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host4Team2,
|
||||
map[uint]*bool{
|
||||
team2Policy1Calendar.ID: nil,
|
||||
team2Policy2.ID: ptr.Bool(false),
|
||||
globalPolicy.ID: ptr.Bool(true),
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
|
||||
team1FiredMu.Lock()
|
||||
require.Zero(t, team1Fired)
|
||||
team1FiredMu.Unlock()
|
||||
|
||||
team2FiredMu.Lock()
|
||||
require.Zero(t, team2Fired)
|
||||
team2FiredMu.Unlock()
|
||||
|
||||
// Trigger the calendar cron, global feature enabled, team1 enabled, team2 not yet enabled
|
||||
// and hosts do not have an associated email yet.
|
||||
triggerAndWait(ctx, t, s.ds, s.calendarSchedule, 5*time.Second)
|
||||
|
||||
team1CalendarEvents, err := s.ds.ListCalendarEvents(ctx, &team1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, team1CalendarEvents)
|
||||
|
||||
// Add an email but of another domain.
|
||||
err = s.ds.ReplaceHostDeviceMapping(ctx, host1Team1.ID, []*fleet.HostDeviceMapping{
|
||||
{
|
||||
HostID: host1Team1.ID,
|
||||
Email: "user@other.com",
|
||||
Source: "google_chrome_profiles",
|
||||
},
|
||||
}, "google_chrome_profiles")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Trigger the calendar cron, global feature enabled, team1 enabled, team2 not yet enabled
|
||||
// and hosts do not have an associated email for the domain yet.
|
||||
triggerAndWait(ctx, t, s.ds, s.calendarSchedule, 5*time.Second)
|
||||
|
||||
team1CalendarEvents, err = s.ds.ListCalendarEvents(ctx, &team1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, team1CalendarEvents)
|
||||
|
||||
err = s.ds.ReplaceHostDeviceMapping(ctx, host1Team1.ID, []*fleet.HostDeviceMapping{
|
||||
{
|
||||
HostID: host1Team1.ID,
|
||||
Email: "user1@example.com",
|
||||
Source: "google_chrome_profiles",
|
||||
},
|
||||
}, "google_chrome_profiles")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Trigger the calendar cron, global feature enabled, team1 enabled, team2 not yet enabled
|
||||
// and host1Team1 has a domain email associated.
|
||||
triggerAndWait(ctx, t, s.ds, s.calendarSchedule, 5*time.Second)
|
||||
|
||||
// An event should be generated for host1Team1
|
||||
team1CalendarEvents, err = s.ds.ListCalendarEvents(ctx, &team1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, team1CalendarEvents, 1)
|
||||
require.NotZero(t, team1CalendarEvents[0].ID)
|
||||
require.Equal(t, "user1@example.com", team1CalendarEvents[0].Email)
|
||||
require.NotZero(t, team1CalendarEvents[0].StartTime)
|
||||
require.NotZero(t, team1CalendarEvents[0].EndTime)
|
||||
|
||||
calendar.SetMockEventsToNow()
|
||||
|
||||
mysql.ExecAdhocSQL(t, s.ds, func(db sqlx.ExtContext) error {
|
||||
// Update updated_at so the event gets updated (the event is updated every 30 minutes)
|
||||
_, err := db.ExecContext(ctx,
|
||||
`UPDATE calendar_events SET updated_at = DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 1 HOUR) WHERE id = ?`, team1CalendarEvents[0].ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Set host1Team1 as online.
|
||||
if _, err := db.ExecContext(ctx,
|
||||
`UPDATE host_seen_times SET seen_time = CURRENT_TIMESTAMP WHERE host_id = ?`, host1Team1.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// Trigger the calendar cron, global feature enabled, team1 enabled, team2 not yet enabled
|
||||
// and host1Team1 has a domain email associated.
|
||||
triggerAndWait(ctx, t, s.ds, s.calendarSchedule, 5*time.Second)
|
||||
|
||||
// Check that refetch on the host was set.
|
||||
host, err := s.ds.Host(ctx, host1Team1.ID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, host.RefetchRequested)
|
||||
|
||||
// host1Team1 is failing a calendar policy and not a non-calendar policy (no results for global).
|
||||
s.DoJSON("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host1Team1,
|
||||
map[uint]*bool{
|
||||
team1Policy1Calendar.ID: ptr.Bool(false),
|
||||
team1Policy2.ID: ptr.Bool(true),
|
||||
globalPolicy.ID: nil,
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
|
||||
// host2Team1 is passing the calendar policy but not the non-calendar policy (no results for global).
|
||||
s.DoJSON("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host2Team1,
|
||||
map[uint]*bool{
|
||||
team2Policy1Calendar.ID: ptr.Bool(true),
|
||||
team2Policy2.ID: ptr.Bool(false),
|
||||
globalPolicy.ID: nil,
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
|
||||
select {
|
||||
case <-team1WebhookFired:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Error("timeout waiting for team1 webhook to fire")
|
||||
}
|
||||
|
||||
// Trigger again, nothing should fire as webhook has already fired.
|
||||
triggerAndWait(ctx, t, s.ds, s.calendarSchedule, 5*time.Second)
|
||||
|
||||
team1FiredMu.Lock()
|
||||
require.Equal(t, 1, team1Fired)
|
||||
team1FiredMu.Unlock()
|
||||
team2FiredMu.Lock()
|
||||
require.Equal(t, 0, team2Fired)
|
||||
team2FiredMu.Unlock()
|
||||
|
||||
// Make host1Team1 pass all policies.
|
||||
s.DoJSON("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host1Team1,
|
||||
map[uint]*bool{
|
||||
team1Policy1Calendar.ID: ptr.Bool(true),
|
||||
team1Policy2.ID: ptr.Bool(true),
|
||||
globalPolicy.ID: nil,
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
|
||||
// Trigger calendar should cleanup the events.
|
||||
triggerAndWait(ctx, t, s.ds, s.calendarSchedule, 5*time.Second)
|
||||
|
||||
// Events in the user calendar should not be cleaned up because they are not in the future.
|
||||
mockEvents := calendar.ListGoogleMockEvents()
|
||||
require.NotEmpty(t, mockEvents)
|
||||
|
||||
// Event should be cleaned up from our database.
|
||||
team1CalendarEvents, err = s.ds.ListCalendarEvents(ctx, &team1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, team1CalendarEvents)
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestCalendarEventsTransferringHosts() {
|
||||
ctx := context.Background()
|
||||
t := s.T()
|
||||
t.Cleanup(func() {
|
||||
calendar.ClearMockEvents()
|
||||
})
|
||||
currentAppCfg, err := s.ds.AppConfig(ctx)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err = s.ds.SaveAppConfig(ctx, currentAppCfg)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Set global configuration for the calendar feature.
|
||||
appCfg, err := s.ds.AppConfig(ctx)
|
||||
require.NoError(t, err)
|
||||
appCfg.Integrations.GoogleCalendar = []*fleet.GoogleCalendarIntegration{
|
||||
{
|
||||
Domain: "example.com",
|
||||
ApiKey: map[string]string{
|
||||
fleet.GoogleCalendarEmail: "calendar-mock@example.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
err = s.ds.SaveAppConfig(ctx, appCfg)
|
||||
require.NoError(t, err)
|
||||
time.Sleep(2 * time.Second) // Wait 2 seconds for the app config cache to clear.
|
||||
|
||||
team1, err := s.ds.NewTeam(ctx, &fleet.Team{
|
||||
Name: "team1",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
team2, err := s.ds.NewTeam(ctx, &fleet.Team{
|
||||
Name: "team2",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
team1.Config.Integrations.GoogleCalendar = &fleet.TeamGoogleCalendarIntegration{
|
||||
Enable: true,
|
||||
WebhookURL: "https://foo.example.com",
|
||||
}
|
||||
team1, err = s.ds.SaveTeam(ctx, team1)
|
||||
require.NoError(t, err)
|
||||
team2.Config.Integrations.GoogleCalendar = &fleet.TeamGoogleCalendarIntegration{
|
||||
Enable: true,
|
||||
WebhookURL: "https://foo.example.com",
|
||||
}
|
||||
team2, err = s.ds.SaveTeam(ctx, team2)
|
||||
require.NoError(t, err)
|
||||
|
||||
newHost := func(name string, teamID *uint) *fleet.Host {
|
||||
h, err := s.ds.NewHost(ctx, &fleet.Host{
|
||||
DetailUpdatedAt: time.Now(),
|
||||
LabelUpdatedAt: time.Now(),
|
||||
PolicyUpdatedAt: time.Now(),
|
||||
SeenTime: time.Now().Add(-1 * time.Minute),
|
||||
OsqueryHostID: ptr.String(t.Name() + name),
|
||||
NodeKey: ptr.String(t.Name() + name),
|
||||
UUID: uuid.New().String(),
|
||||
Hostname: fmt.Sprintf("%s.%s.local", name, t.Name()),
|
||||
Platform: "darwin",
|
||||
TeamID: teamID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return h
|
||||
}
|
||||
|
||||
host1 := newHost("host1", &team1.ID)
|
||||
err = s.ds.ReplaceHostDeviceMapping(ctx, host1.ID, []*fleet.HostDeviceMapping{
|
||||
{
|
||||
HostID: host1.ID,
|
||||
Email: "user1@example.com",
|
||||
Source: "google_chrome_profiles",
|
||||
},
|
||||
}, "google_chrome_profiles")
|
||||
require.NoError(t, err)
|
||||
|
||||
team1Policy1, err := s.ds.NewTeamPolicy(
|
||||
ctx, team1.ID, nil, fleet.PolicyPayload{
|
||||
Name: "team1Policy1",
|
||||
Query: "SELECT 1;",
|
||||
CalendarEventsEnabled: true,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
team2Policy1, err := s.ds.NewTeamPolicy(
|
||||
ctx, team2.ID, nil, fleet.PolicyPayload{
|
||||
Name: "team2Policy1",
|
||||
Query: "SELECT 2;",
|
||||
CalendarEventsEnabled: true,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
distributedResp := submitDistributedQueryResultsResponse{}
|
||||
s.DoJSON("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host1,
|
||||
map[uint]*bool{
|
||||
team1Policy1.ID: ptr.Bool(false),
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
|
||||
triggerAndWait(ctx, t, s.ds, s.calendarSchedule, 5*time.Second)
|
||||
|
||||
team1CalendarEvents, err := s.ds.ListCalendarEvents(ctx, &team1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, team1CalendarEvents, 1)
|
||||
|
||||
// Check the calendar was created on the DB.
|
||||
hostCalendarEvent, calendarEvent, err := s.ds.GetHostCalendarEventByEmail(ctx, "user1@example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Transfer host to team2.
|
||||
err = s.ds.AddHostsToTeam(ctx, &team2.ID, []uint{host1.ID})
|
||||
require.NoError(t, err)
|
||||
|
||||
// host1 is failing team2's policy too.
|
||||
s.DoJSON("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host1,
|
||||
map[uint]*bool{
|
||||
team2Policy1.ID: ptr.Bool(false),
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
|
||||
triggerAndWait(ctx, t, s.ds, s.calendarSchedule, 5*time.Second)
|
||||
|
||||
// Check the calendar event entry was reused.
|
||||
hostCalendarEvent2, calendarEvent2, err := s.ds.GetHostCalendarEventByEmail(ctx, "user1@example.com")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, calendarEvent2.ID, calendarEvent.ID)
|
||||
require.Equal(t, hostCalendarEvent2.CalendarEventID, hostCalendarEvent.CalendarEventID)
|
||||
|
||||
// Transfer host to global.
|
||||
err = s.ds.AddHostsToTeam(ctx, nil, []uint{host1.ID})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Move event to two days ago (to clean up the calendar event)
|
||||
mysql.ExecAdhocSQL(t, s.ds, func(db sqlx.ExtContext) error {
|
||||
_, err := db.ExecContext(ctx,
|
||||
`UPDATE calendar_events SET updated_at = DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 49 HOUR) WHERE id = ?`, team1CalendarEvents[0].ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
triggerAndWait(ctx, t, s.ds, s.calendarSchedule, 5*time.Second)
|
||||
|
||||
// Calendar event is cleaned up.
|
||||
_, _, err = s.ds.GetHostCalendarEventByEmail(ctx, "user1@example.com")
|
||||
require.True(t, fleet.IsNotFound(err))
|
||||
}
|
||||
|
||||
func genDistributedReqWithPolicyResults(host *fleet.Host, policyResults map[uint]*bool) submitDistributedQueryResultsRequestShim {
|
||||
var (
|
||||
results = make(map[string]json.RawMessage)
|
||||
statuses = make(map[string]interface{})
|
||||
messages = make(map[string]string)
|
||||
)
|
||||
for policyID, policyResult := range policyResults {
|
||||
distributedQueryName := hostPolicyQueryPrefix + fmt.Sprint(policyID)
|
||||
switch {
|
||||
case policyResult == nil:
|
||||
results[distributedQueryName] = json.RawMessage(`[]`)
|
||||
statuses[distributedQueryName] = 1
|
||||
messages[distributedQueryName] = "policy failed execution"
|
||||
case *policyResult:
|
||||
results[distributedQueryName] = json.RawMessage(`[{"1": "1"}]`)
|
||||
statuses[distributedQueryName] = 0
|
||||
case !*policyResult:
|
||||
results[distributedQueryName] = json.RawMessage(`[]`)
|
||||
statuses[distributedQueryName] = 0
|
||||
}
|
||||
}
|
||||
return submitDistributedQueryResultsRequestShim{
|
||||
NodeKey: *host.NodeKey,
|
||||
Results: results,
|
||||
Statuses: statuses,
|
||||
Messages: messages,
|
||||
Stats: map[string]*fleet.Stats{},
|
||||
}
|
||||
}
|
||||
|
||||
func triggerAndWait(ctx context.Context, t *testing.T, ds fleet.Datastore, s *schedule.Schedule, timeout time.Duration) {
|
||||
// Following code assumes (for simplicity) only triggered runs.
|
||||
stats, err := ds.GetLatestCronStats(ctx, s.Name())
|
||||
require.NoError(t, err)
|
||||
var previousRunID int
|
||||
if len(stats) > 0 {
|
||||
previousRunID = stats[0].ID
|
||||
}
|
||||
|
||||
_, err = s.Trigger()
|
||||
require.NoError(t, err)
|
||||
|
||||
timeoutCh := time.After(timeout)
|
||||
for {
|
||||
stats, err := ds.GetLatestCronStats(ctx, s.Name())
|
||||
require.NoError(t, err)
|
||||
if len(stats) > 0 && stats[0].ID > previousRunID && stats[0].Status == fleet.CronStatsStatusCompleted {
|
||||
t.Logf("cron %s:%d done", s.Name(), stats[0].ID)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-timeoutCh:
|
||||
t.Fatalf("timeout waiting for schedule %s to complete", s.Name())
|
||||
case <-time.After(250 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue