mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 08:58:41 +00:00
Merge branch 'main' into feat-fleet-app-library
This commit is contained in:
commit
825cec3dbd
27 changed files with 322 additions and 424 deletions
80
articles/automatic-software-install-in-fleet.md
Normal file
80
articles/automatic-software-install-in-fleet.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# Automatic policy-based installation of software on hosts
|
||||
|
||||

|
||||
|
||||
Fleet [v4.57.0](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.57.0) introduces the ability to automatically and remotely install software on hosts based on predefined policy failures. This guide will walk you through the process of configuring fleet for automatic installation of software on hosts using uploaded installation images and based on programmed policies. You'll learn how to configure and use this feature, as well as understand how the underlying mechanism works.
|
||||
|
||||
Fleet allows its users to upload trusted software installation files to be installed and used on hosts. This installation could be conditioned on a failure of a specific Fleet Policy.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* Fleet premium with Admin permissions.
|
||||
* Fleet [v4.57.0](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.57.0) or greater.
|
||||
|
||||
## Step-by-step instructions
|
||||
|
||||
1. **Adding software**: Add any software to be available for installation. Follow the [deploying software](https://fleetdm.com/guides/deploy-security-agents) document with instructions how to do it. Note that all installation steps (pre-install query, install script, and post-install script) will be executed as configured, regardless of the policy that triggers the installation.
|
||||
|
||||
|
||||

|
||||
|
||||
Current supported software deployment formats:
|
||||
- macOS: .pkg
|
||||
- Windows: .msi, .exe
|
||||
- Linux: .deb
|
||||
|
||||
Coming soon:
|
||||
- VPP for iOS and iPadOS
|
||||
|
||||
2. **Add a policy**: In Fleet, add a policy that failure to pass will trigger the required installation. Go to Policies tab --> Press the top right "Add policy" button. --> Click "create your own policy" --> Enter your policy SQL --> Save --> Fill in details in the Save modal and Save.
|
||||
|
||||
```
|
||||
SELECT 1 FROM apps WHERE name = 'Adobe Acrobat Reader.app' AND version_compare(bundle_short_version, '23.001.20687') >= 0;
|
||||
```
|
||||
|
||||
Note: In order to know the exact application name to put in the query (e.g. "Adobe Acrobat Reader.app" in the query above) you can manually install it on a canary/test host and then query SELECT * from apps;
|
||||
|
||||
|
||||
3. **Manage automation**: Open Manage Automations: Policies Tab --> top right "Manage automations" --> "Install software".
|
||||
|
||||

|
||||
|
||||
4. **Select policy**: Select (click the check box of) your newly created policy. To the right of it select from the
|
||||
drop-down list the software you would like to be installed upon failure of this policy.
|
||||
|
||||

|
||||
|
||||
Upon failure of the selected policy, the selected software installation will be triggered.
|
||||
|
||||
## How does it work?
|
||||
|
||||
* After configuring Fleet to auto-install a specific software the rest will be done automatically.
|
||||
* The policy check mechanism runs on a typical 1 hour cadence on all online hosts.
|
||||
* Fleet will send install requests to the hosts on the first policy failure (first "No" result for the host) or if a policy goes from "Yes" to "No". On this iteration it will not send a install request if a policy is already failing and continues to fail ("No" -> "No"). See the following flowchart for details.
|
||||
|
||||

|
||||
*Detailed flowchart*
|
||||
|
||||
## Using the REST API for self-service software packages
|
||||
|
||||
Fleet provides a REST API for managing software packages, including self-service software packages. Learn more about Fleet's [REST API](https://fleetdm.com/docs/rest-api/rest-api#add-team-policy).
|
||||
|
||||
## Managing self-service software packages with GitOps
|
||||
|
||||
To manage self-service software packages using Fleet's best practice GitOps, check out the `software` key in the [GitOps reference documentation](https://fleetdm.com/docs/configuration/yaml-files#policies).
|
||||
|
||||
## Conclusion
|
||||
|
||||
Software deployment can be time-consuming and risky. This guide presents Fleet's ability to mass deploy software to your fleet in a simple and safe way. Starting with uploading a trusted installer and ending with deploying it to the proper set of machines answering the exact policy defined by you.
|
||||
|
||||
Leveraging Fleet’s ability to install and upgrade software on your hosts, you can streamline the process of controlling your hosts, replacing old versions of software and having the up-to-date info on what's installed on your fleet.
|
||||
|
||||
By automating software deployment, you can gain greater control over what's installed on your machines and have better oversight of version upgrades, ensuring old software with known issues is replaced.
|
||||
|
||||
<meta name="articleTitle" value="Automatic installation of software on hosts">
|
||||
<meta name="authorFullName" value="Sharon Katz">
|
||||
<meta name="authorGitHubUsername" value="sharon-fdm">
|
||||
<meta name="category" value="guides">
|
||||
<meta name="publishedOn" value="2024-09-23">
|
||||
<meta name="articleImageUrl" value="../website/assets/images/articles/automatic-software-install-in-fleet-731x738@2x.png">
|
||||
<meta name="description" value="A guide to workflows using automatic software installation in Fleet.">
|
||||
|
|
@ -79,9 +79,10 @@ GitOps is an API-only and write-only role that can be used on CI/CD pipelines.
|
|||
| Edit agent options for hosts assigned to teams\* | | | | ✅ | ✅ |
|
||||
| Initiate [file carving](https://fleetdm.com/docs/using-fleet/rest-api#file-carving) | | | ✅ | ✅ | |
|
||||
| Retrieve contents from file carving | | | | ✅ | |
|
||||
| View Apple mobile device management (MDM) certificate information | | | | ✅ | |
|
||||
| View Apple business manager (BM) information | | | | ✅ | |
|
||||
| Generate Apple mobile device management (MDM) certificate signing request (CSR) | | | | ✅ | |
|
||||
| Create Apple Push Certificates service (APNs) certificate signing request (CSR) | | | | ✅ | |
|
||||
| View, edit, and delete APNs certificate | | | | ✅ | |
|
||||
| View, edit, and delete Apple Business Manager (ABM) connections | | | | ✅ | |
|
||||
| View, edit, and delete Volume Purchasing Program (VPP) connections | | | | ✅ | |
|
||||
| View disk encryption key for macOS and Windows hosts | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Edit OS updates for macOS, Windows, iOS, and iPadOS hosts | | | ✅ | ✅ | ✅ |
|
||||
| Create, edit, resend and delete configuration profiles for macOS and Windows hosts | | | ✅ | ✅ | ✅ |
|
||||
|
|
|
|||
1
changes/21343-hide-redundant-built-in-label-pills
Normal file
1
changes/21343-hide-redundant-built-in-label-pills
Normal file
|
|
@ -0,0 +1 @@
|
|||
- UI: Remove redundant built in label filter pills
|
||||
1
changes/22122-mdm-apple-status-queries
Normal file
1
changes/22122-mdm-apple-status-queries
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Improved performance of SQL queries used to determine MDM profile status for Apple hosts.
|
||||
|
|
@ -5,12 +5,13 @@ import { APP_CONTEXT_NO_TEAM_ID } from "interfaces/team";
|
|||
import { NotificationContext } from "context/notification";
|
||||
import configAPI from "services/entities/config";
|
||||
import teamsAPI from "services/entities/teams";
|
||||
import { ApplePlatform } from "interfaces/platform";
|
||||
|
||||
// @ts-ignore
|
||||
import InputField from "components/forms/fields/InputField";
|
||||
import Button from "components/buttons/Button";
|
||||
import validatePresence from "components/forms/validators/validate_presence";
|
||||
import { ApplePlatform } from "interfaces/platform";
|
||||
import CustomLink from "components/CustomLink";
|
||||
|
||||
const baseClass = "apple-os-target-form";
|
||||
|
||||
|
|
@ -197,7 +198,16 @@ const AppleOSTargetForm = ({
|
|||
<InputField
|
||||
label="Minimum version"
|
||||
tooltip={getMinimumVersionTooltip()}
|
||||
helpText="Version number only (e.g., “13.0.1” not “Ventura 13” or “13.0.1 (22A400)”)"
|
||||
helpText={
|
||||
<>
|
||||
Use only versions available from Apple.{" "}
|
||||
<CustomLink
|
||||
text="Learn more"
|
||||
newTab
|
||||
url="https://fleetdm.com/learn-more-about/available-os-update-versions"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
value={minOsVersion}
|
||||
error={minOsVersionError}
|
||||
onChange={handleMinVersionChange}
|
||||
|
|
|
|||
|
|
@ -1430,16 +1430,11 @@ const ManageHostsPage = ({
|
|||
? selectedLabel
|
||||
: undefined;
|
||||
|
||||
const statusDropdownClassnames = classNames(
|
||||
`${baseClass}__status_dropdown`,
|
||||
{ [`${baseClass}__status-dropdown-sandbox`]: isSandboxMode }
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`${baseClass}__filter-dropdowns`}>
|
||||
<Dropdown
|
||||
value={status || ""}
|
||||
className={statusDropdownClassnames}
|
||||
className={`${baseClass}__status_dropdown`}
|
||||
options={getHostSelectStatuses(isSandboxMode)}
|
||||
searchable={false}
|
||||
onChange={handleStatusDropdownChange}
|
||||
|
|
|
|||
|
|
@ -121,13 +121,6 @@
|
|||
line-height: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
.manage-hosts__status-dropdown-sandbox {
|
||||
width: auto;
|
||||
.Select-control {
|
||||
width: 182px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -183,12 +176,6 @@
|
|||
.manage-hosts__status_dropdown {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.manage-hosts__status-dropdown-sandbox {
|
||||
.Select-control {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -246,7 +233,7 @@
|
|||
|
||||
.Select-menu-outer {
|
||||
width: 364px;
|
||||
max-height: 325px;
|
||||
max-height: min-content;
|
||||
|
||||
.Select-menu {
|
||||
max-height: none;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {
|
|||
|
||||
import {
|
||||
PLATFORM_LABEL_DISPLAY_NAMES,
|
||||
PLATFORM_TYPE_ICONS,
|
||||
isPlatformLabelNameFromAPI,
|
||||
PolicyResponse,
|
||||
} from "utilities/constants";
|
||||
|
|
@ -41,6 +42,8 @@ import BootstrapPackageStatusFilter from "../BootstrapPackageStatusFilter/Bootst
|
|||
|
||||
const baseClass = "hosts-filter-block";
|
||||
|
||||
type PlatformLabelNameFromAPI = keyof typeof PLATFORM_TYPE_ICONS;
|
||||
|
||||
interface IHostsFilterBlockProps {
|
||||
/**
|
||||
* An object of params the the HostFilterBlock uses to render the correct
|
||||
|
|
@ -145,6 +148,16 @@ const HostsFilterBlock = ({
|
|||
PLATFORM_LABEL_DISPLAY_NAMES[display_text]) ||
|
||||
display_text;
|
||||
|
||||
// Hide built-in labels supported in label dropdown
|
||||
if (
|
||||
label_type === "builtin" &&
|
||||
Object.keys(PLATFORM_TYPE_ICONS).includes(
|
||||
display_text as PlatformLabelNameFromAPI
|
||||
)
|
||||
) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<FilterPill
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@
|
|||
min-width: 220px;
|
||||
|
||||
&__single-value {
|
||||
max-width: 135px !important; // Must override default styling of .css-qc6sy-singleValue
|
||||
max-width: 210px !important; // Must override default styling of .css-qc6sy-singleValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
&__item-topline {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 64px;
|
||||
height: 66px;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
overflow: hidden;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
.Select {
|
||||
.Select-menu-outer {
|
||||
width: 364px;
|
||||
max-height: 310px;
|
||||
max-height: min-content;
|
||||
|
||||
.Select-menu {
|
||||
max-height: none;
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
&__platform-dropdown {
|
||||
.Select-menu-outer {
|
||||
width: 364px;
|
||||
max-height: 380px;
|
||||
max-height: min-content;
|
||||
|
||||
.Select-menu {
|
||||
max-height: none;
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ This handbook page details processes specific to working [with](#contact-us) and
|
|||
| Chief Revenue Officer (CRO) | [Alex Mitchell](https://www.linkedin.com/in/alexandercmitchell/) _([@alexmitchelliii](https://github.com/alexmitchelliii))_
|
||||
| Solutions Consulting (SC) | [Dave Herder](https://www.linkedin.com/in/daveherder/) _([@dherder](https://github.com/dherder))_ <br> [Zach Wasserman](https://www.linkedin.com/in/zacharywasserman/) _([@zwass](https://github.com/zwass))_ <br> [Allen Houchins](https://www.linkedin.com/in/allenhouchins/) _([@allenhouchins](https://github.com/allenhouchins))_ <br> [Harrison Ravazzolo](https://www.linkedin.com/in/harrison-ravazzolo/) _([@harrisonravazzolo](https://github.com/harrisonravazzolo))_
|
||||
| Channel Sales | [Tom Ostertag](https://www.linkedin.com/in/tom-ostertag-77212791/) _([@tomostertag](https://github.com/TomOstertag))_
|
||||
| Sr. Account Executive | [Kendra McKeever](https://www.linkedin.com/in/kendramckeever/) _([@KendraAtFleet](https://github.com/KendraAtFleet))_
|
||||
| Account Executive (AE) | [Patricia Ambrus](https://www.linkedin.com/in/pambrus/) _([@ambrusps](https://github.com/ambrusps))_ <br> [Anthony Snyder](https://www.linkedin.com/in/anthonysnyder8/) _([@anthonysnyder8](https://github.com/AnthonySnyder8))_ <br> [Paul Tardif](https://www.linkedin.com/in/paul-t-750833/) _([@phtardif1](https://github.com/phtardif1))_
|
||||
| Account Executive (AE) | [Patricia Ambrus](https://www.linkedin.com/in/pambrus/) _([@ambrusps](https://github.com/ambrusps))_ <br> [Anthony Snyder](https://www.linkedin.com/in/anthonysnyder8/) _([@anthonysnyder8](https://github.com/AnthonySnyder8))_ <br> [Paul Tardif](https://www.linkedin.com/in/paul-t-750833/) _([@phtardif1](https://github.com/phtardif1))_ <br> [Kendra McKeever](https://www.linkedin.com/in/kendramckeever/) _([@KendraAtFleet](https://github.com/KendraAtFleet))_
|
||||
|
||||
|
||||
## Contact us
|
||||
|
|
|
|||
|
|
@ -1,27 +1,5 @@
|
|||
agent_options:
|
||||
path: ./lib/agent-options.yml
|
||||
controls:
|
||||
enable_disk_encryption: true
|
||||
macos_migration:
|
||||
enable: true
|
||||
mode: voluntary
|
||||
webhook_url: $DOGFOOD_MACOS_MIGRATION_WEBHOOK_URL
|
||||
macos_settings:
|
||||
custom_settings: null
|
||||
macos_setup:
|
||||
bootstrap_package: ""
|
||||
enable_end_user_authentication: false
|
||||
macos_setup_assistant: null
|
||||
macos_updates:
|
||||
deadline: "2023-06-13"
|
||||
minimum_version: 13.4.1
|
||||
windows_enabled_and_configured: true
|
||||
windows_settings:
|
||||
custom_settings: []
|
||||
windows_updates:
|
||||
deadline_days: 3
|
||||
grace_period_days: 2
|
||||
scripts: []
|
||||
org_settings:
|
||||
features:
|
||||
enable_host_users: true
|
||||
|
|
@ -90,4 +68,3 @@ org_settings:
|
|||
policies:
|
||||
queries:
|
||||
- path: ./lib/collect-fleetd-update-channels.queries.yml
|
||||
software:
|
||||
|
|
|
|||
25
it-and-security/teams/no-team.yml
Normal file
25
it-and-security/teams/no-team.yml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
name: No team
|
||||
policies:
|
||||
controls:
|
||||
enable_disk_encryption: true
|
||||
macos_migration:
|
||||
enable: true
|
||||
mode: voluntary
|
||||
webhook_url: $DOGFOOD_MACOS_MIGRATION_WEBHOOK_URL
|
||||
macos_settings:
|
||||
custom_settings: null
|
||||
macos_setup:
|
||||
bootstrap_package: ""
|
||||
enable_end_user_authentication: false
|
||||
macos_setup_assistant: null
|
||||
macos_updates:
|
||||
deadline: "2023-06-13"
|
||||
minimum_version: 13.4.1
|
||||
windows_enabled_and_configured: true
|
||||
windows_settings:
|
||||
custom_settings: []
|
||||
windows_updates:
|
||||
deadline_days: 3
|
||||
grace_period_days: 2
|
||||
scripts: []
|
||||
software:
|
||||
|
|
@ -5,8 +5,9 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
)
|
||||
|
||||
// EscrowBuddyRunner sets up [Escrow Buddy][1] to rotate FileVault keys on
|
||||
|
|
@ -86,6 +87,13 @@ func (e *EscrowBuddyRunner) Run(cfg *fleet.OrbitConfig) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Some macOS updates and upgrades reset the authorization database to its default state
|
||||
// which will deactivate Escrow Buddy and prevent FileVault key generation upon next login.
|
||||
log.Debug().Msg("EscrowBuddyRunner: re-enable Escrow Buddy in the authorization database")
|
||||
if err := e.setAuthDBSetup(); err != nil {
|
||||
return fmt.Errorf("failed to re-enable Escrow Buddy in the authorization database, err: %w", err)
|
||||
}
|
||||
|
||||
log.Debug().Msg("EscrowBuddyRunner: enabling disk encryption rotation")
|
||||
if err := e.setGenerateNewKeyTo(true); err != nil {
|
||||
return fmt.Errorf("enabling disk encryption rotation: %w", err)
|
||||
|
|
@ -118,3 +126,13 @@ func (e *EscrowBuddyRunner) setGenerateNewKeyTo(enabled bool) error {
|
|||
}
|
||||
return fn("sh", "-c", cmd)
|
||||
}
|
||||
|
||||
func (e *EscrowBuddyRunner) setAuthDBSetup() error {
|
||||
log.Debug().Msg("ready to re-enable Escrow Buddy in the authorization database")
|
||||
cmd := "/Library/Security/SecurityAgentPlugins/Escrow\\ Buddy.bundle/Contents/Resources/AuthDBSetup.sh"
|
||||
fn := e.runCmdFunc
|
||||
if fn == nil {
|
||||
fn = runCmdCollectErr
|
||||
}
|
||||
return fn("sh", "-c", cmd)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,9 +65,11 @@ func (s *escrowBuddyTestSuite) TestEscrowBuddyRotatesKey() {
|
|||
|
||||
err = r.Run(cfg)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, cmdCalls, 1)
|
||||
require.Len(t, cmdCalls, 2)
|
||||
require.Equal(t, cmdCalls[0]["cmd"], "sh")
|
||||
require.Equal(t, cmdCalls[0]["args"], []string{"-c", "defaults write /Library/Preferences/com.netflix.Escrow-Buddy.plist GenerateNewKey -bool true"})
|
||||
require.Equal(t, cmdCalls[0]["args"], []string{"-c", "/Library/Security/SecurityAgentPlugins/Escrow\\ Buddy.bundle/Contents/Resources/AuthDBSetup.sh"})
|
||||
require.Equal(t, cmdCalls[1]["cmd"], "sh")
|
||||
require.Equal(t, cmdCalls[1]["args"], []string{"-c", "defaults write /Library/Preferences/com.netflix.Escrow-Buddy.plist GenerateNewKey -bool true"})
|
||||
|
||||
targets = runner.updater.opt.Targets
|
||||
require.Len(t, targets, 1)
|
||||
|
|
@ -77,10 +79,12 @@ func (s *escrowBuddyTestSuite) TestEscrowBuddyRotatesKey() {
|
|||
|
||||
time.Sleep(3 * time.Millisecond)
|
||||
cfg.Notifications.RotateDiskEncryptionKey = false
|
||||
cmdCalls = []map[string]any{}
|
||||
err = r.Run(cfg)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, cmdCalls, 2)
|
||||
require.Equal(t, cmdCalls[1]["cmd"], "sh")
|
||||
require.Equal(t, cmdCalls[1]["args"], []string{"-c", "defaults write /Library/Preferences/com.netflix.Escrow-Buddy.plist GenerateNewKey -bool false"})
|
||||
// only one call to set the GenerateNewKey to false
|
||||
require.Len(t, cmdCalls, 1)
|
||||
require.Equal(t, cmdCalls[0]["cmd"], "sh")
|
||||
require.Equal(t, cmdCalls[0]["args"], []string{"-c", "defaults write /Library/Preferences/com.netflix.Escrow-Buddy.plist GenerateNewKey -bool false"})
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2535,377 +2535,150 @@ func (ds *Datastore) UpdateOrDeleteHostMDMAppleProfile(ctx context.Context, prof
|
|||
return err
|
||||
}
|
||||
|
||||
const (
|
||||
appleMDMFailedProfilesStmt = `
|
||||
h.uuid = hmap.host_uuid AND
|
||||
hmap.status = :failed`
|
||||
|
||||
appleMDMPendingProfilesStmt = `
|
||||
h.uuid = hmap.host_uuid AND
|
||||
(
|
||||
hmap.status IS NULL OR
|
||||
hmap.status = :pending OR
|
||||
// sqlCaseMDMAppleStatus returns a SQL snippet that can be used to determine the status of a host
|
||||
// based on the status of its profiles and declarations and filevault status. It should be used in
|
||||
// conjunction with sqlJoinMDMAppleProfilesStatus and sqlJoinMDMAppleDeclarationsStatus. It assumes the
|
||||
// hosts table to be aliased as 'h' and the host_disk_encryption_keys table to be aliased as 'hdek'.
|
||||
func sqlCaseMDMAppleStatus() string {
|
||||
// NOTE: To make this snippet reusable, we're not using sqlx.Named here because it would
|
||||
// complicate usage in other queries (e.g., list hosts).
|
||||
var (
|
||||
failed = fmt.Sprintf("'%s'", string(fleet.MDMDeliveryFailed))
|
||||
pending = fmt.Sprintf("'%s'", string(fleet.MDMDeliveryPending))
|
||||
verifying = fmt.Sprintf("'%s'", string(fleet.MDMDeliveryVerifying))
|
||||
verified = fmt.Sprintf("'%s'", string(fleet.MDMDeliveryVerified))
|
||||
)
|
||||
return `
|
||||
CASE WHEN (prof_failed
|
||||
OR decl_failed
|
||||
OR fv_failed) THEN
|
||||
` + failed + `
|
||||
WHEN (prof_pending
|
||||
OR decl_pending
|
||||
-- special case for filevault, it's pending if the profile is
|
||||
-- pending OR the profile is verified or verifying but we still
|
||||
-- don't have an encryption key.
|
||||
(
|
||||
hmap.profile_identifier = :filevault AND
|
||||
hmap.status IN (:verifying, :verified) AND
|
||||
hmap.operation_type = :install AND
|
||||
NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM host_disk_encryption_keys hdek
|
||||
WHERE h.id = hdek.host_id AND
|
||||
(hdek.decryptable = 1 OR hdek.decryptable IS NULL)
|
||||
)
|
||||
)
|
||||
)`
|
||||
|
||||
appleMDMVerifyingProfilesStmt = `
|
||||
h.uuid = hmap.host_uuid AND
|
||||
hmap.operation_type = :install AND
|
||||
(
|
||||
-- all profiles except filevault that are 'verifying'
|
||||
(
|
||||
hmap.profile_identifier != :filevault AND
|
||||
hmap.status = :verifying
|
||||
)
|
||||
OR
|
||||
-- special cases for filevault
|
||||
(
|
||||
hmap.profile_identifier = :filevault AND
|
||||
(
|
||||
-- filevault profile is verified, but we didn't verify the encryption key
|
||||
(
|
||||
hmap.status = :verified AND
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM host_disk_encryption_keys AS hdek
|
||||
WHERE h.id = hdek.host_id AND
|
||||
hdek.decryptable IS NULL
|
||||
)
|
||||
)
|
||||
OR
|
||||
-- filevault profile is verifying, and we already have an encryption key, in any state
|
||||
(
|
||||
hmap.status = :verifying AND
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM host_disk_encryption_keys AS hdek
|
||||
WHERE h.id = hdek.host_id AND
|
||||
hdek.decryptable = 1 OR hdek.decryptable IS NULL
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)`
|
||||
|
||||
appleVerifiedProfilesStmt = `
|
||||
h.uuid = hmap.host_uuid AND
|
||||
hmap.operation_type = :install AND
|
||||
hmap.status = :verified AND
|
||||
(
|
||||
hmap.profile_identifier != :filevault OR
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM host_disk_encryption_keys hdek
|
||||
WHERE h.id = hdek.host_id AND
|
||||
hdek.decryptable = 1
|
||||
)
|
||||
)`
|
||||
)
|
||||
|
||||
// subqueryAppleProfileStatus builds the right subquery that can be used to
|
||||
// filter hosts based on their profile status.
|
||||
//
|
||||
// The subquery mechanism works by finding profiles for hosts that:
|
||||
// - match with the provided status
|
||||
// - match any status that supercedes the provided status (eg: failed supercedes verifying)
|
||||
//
|
||||
// Hosts will be considered to be in the given status only if the profiles
|
||||
// match the given status and zero profiles match any superceding status.
|
||||
func subqueryAppleProfileStatus(status fleet.MDMDeliveryStatus) (string, []any, error) {
|
||||
var condition string
|
||||
var excludeConditions string
|
||||
switch status {
|
||||
case fleet.MDMDeliveryFailed:
|
||||
condition = appleMDMFailedProfilesStmt
|
||||
excludeConditions = "FALSE"
|
||||
case fleet.MDMDeliveryPending:
|
||||
condition = appleMDMPendingProfilesStmt
|
||||
excludeConditions = appleMDMFailedProfilesStmt
|
||||
case fleet.MDMDeliveryVerifying:
|
||||
condition = appleMDMVerifyingProfilesStmt
|
||||
excludeConditions = fmt.Sprintf("(%s) OR (%s)", appleMDMPendingProfilesStmt, appleMDMFailedProfilesStmt)
|
||||
case fleet.MDMDeliveryVerified:
|
||||
condition = appleVerifiedProfilesStmt
|
||||
excludeConditions = fmt.Sprintf("(%s) OR (%s) OR (%s)", appleMDMPendingProfilesStmt, appleMDMFailedProfilesStmt, appleMDMVerifyingProfilesStmt)
|
||||
default:
|
||||
return "", nil, fmt.Errorf("invalid status: %s", status)
|
||||
}
|
||||
|
||||
sql := fmt.Sprintf(`
|
||||
SELECT 1
|
||||
FROM host_mdm_apple_profiles hmap
|
||||
WHERE %s AND
|
||||
NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM host_mdm_apple_profiles hmap
|
||||
WHERE %s
|
||||
)`, condition, excludeConditions)
|
||||
|
||||
arg := map[string]any{
|
||||
"install": fleet.MDMOperationTypeInstall,
|
||||
"verifying": fleet.MDMDeliveryVerifying,
|
||||
"failed": fleet.MDMDeliveryFailed,
|
||||
"verified": fleet.MDMDeliveryVerified,
|
||||
"pending": fleet.MDMDeliveryPending,
|
||||
"filevault": mobileconfig.FleetFileVaultPayloadIdentifier,
|
||||
}
|
||||
query, args, err := sqlx.Named(sql, arg)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("subqueryAppleProfileStatus %s: %w", status, err)
|
||||
}
|
||||
|
||||
return query, args, nil
|
||||
OR(fv_pending
|
||||
OR((fv_verifying
|
||||
OR fv_verified)
|
||||
AND (hdek.base64_encrypted IS NULL OR (hdek.decryptable IS NOT NULL AND hdek.decryptable != 1))))) THEN
|
||||
` + pending + `
|
||||
WHEN (prof_verifying
|
||||
OR decl_verifying
|
||||
-- special case when fv profile is verifying, and we already have an encryption key, in any state, we treat as verifying
|
||||
OR(fv_verifying
|
||||
AND hdek.base64_encrypted IS NOT NULL AND (hdek.decryptable IS NULL OR hdek.decryptable = 1))
|
||||
-- special case when fv profile is verified, but we didn't verify the encryption key, we treat as verifying
|
||||
OR(fv_verified
|
||||
AND hdek.base64_encrypted IS NOT NULL AND hdek.decryptable IS NULL)) THEN
|
||||
` + verifying + `
|
||||
WHEN (prof_verified
|
||||
OR decl_verified
|
||||
OR(fv_verified
|
||||
AND hdek.base64_encrypted IS NOT NULL AND hdek.decryptable = 1)) THEN
|
||||
` + verified + `
|
||||
END
|
||||
`
|
||||
}
|
||||
|
||||
// subqueryAppleDeclarationStatus builds out the subquery for declaration status
|
||||
func subqueryAppleDeclarationStatus() (string, []any, error) {
|
||||
const declNamedStmt = `
|
||||
CASE WHEN EXISTS (
|
||||
SELECT
|
||||
1
|
||||
FROM
|
||||
host_mdm_apple_declarations d1
|
||||
WHERE
|
||||
h.uuid = d1.host_uuid
|
||||
AND d1.operation_type = :install
|
||||
AND d1.status = :failed
|
||||
AND d1.declaration_name NOT IN (:reserved_names)) THEN
|
||||
'declarations_failed'
|
||||
WHEN EXISTS (
|
||||
SELECT
|
||||
1
|
||||
FROM
|
||||
host_mdm_apple_declarations d2
|
||||
WHERE
|
||||
h.uuid = d2.host_uuid
|
||||
AND d2.operation_type = :install
|
||||
AND(d2.status IS NULL
|
||||
OR d2.status = :pending)
|
||||
AND d2.declaration_name NOT IN (:reserved_names)
|
||||
AND NOT EXISTS (
|
||||
SELECT
|
||||
1
|
||||
FROM
|
||||
host_mdm_apple_declarations d3
|
||||
WHERE
|
||||
h.uuid = d3.host_uuid
|
||||
AND d3.operation_type = :install
|
||||
AND d3.status = :failed
|
||||
AND d3.declaration_name NOT IN (:reserved_names))) THEN
|
||||
'declarations_pending'
|
||||
WHEN EXISTS (
|
||||
SELECT
|
||||
1
|
||||
FROM
|
||||
host_mdm_apple_declarations d4
|
||||
WHERE
|
||||
h.uuid = d4.host_uuid
|
||||
AND d4.operation_type = :install
|
||||
AND d4.status = :verifying
|
||||
AND d4.declaration_name NOT IN (:reserved_names)
|
||||
AND NOT EXISTS (
|
||||
SELECT
|
||||
1
|
||||
FROM
|
||||
host_mdm_apple_declarations d5
|
||||
WHERE (h.uuid = d5.host_uuid
|
||||
AND d5.operation_type = :install
|
||||
AND d5.declaration_name NOT IN (:reserved_names)
|
||||
AND(d5.status IS NULL
|
||||
OR d5.status IN(:pending, :failed))))) THEN
|
||||
'declarations_verifying'
|
||||
WHEN EXISTS (
|
||||
SELECT
|
||||
1
|
||||
FROM
|
||||
host_mdm_apple_declarations d6
|
||||
WHERE
|
||||
h.uuid = d6.host_uuid
|
||||
AND d6.operation_type = :install
|
||||
AND d6.status = :verified
|
||||
AND d6.declaration_name NOT IN (:reserved_names)
|
||||
AND NOT EXISTS (
|
||||
SELECT
|
||||
1
|
||||
FROM
|
||||
host_mdm_apple_declarations d7
|
||||
WHERE (h.uuid = d7.host_uuid
|
||||
AND d7.operation_type = :install
|
||||
AND d7.declaration_name NOT IN (:reserved_names)
|
||||
AND(d7.status IS NULL
|
||||
OR d7.status IN(:pending, :failed, :verifying))))) THEN
|
||||
'declarations_verified'
|
||||
ELSE
|
||||
''
|
||||
END`
|
||||
|
||||
arg := map[string]any{
|
||||
"install": fleet.MDMOperationTypeInstall,
|
||||
"verifying": fleet.MDMDeliveryVerifying,
|
||||
"failed": fleet.MDMDeliveryFailed,
|
||||
"verified": fleet.MDMDeliveryVerified,
|
||||
"pending": fleet.MDMDeliveryPending,
|
||||
"reserved_names": fleetmdm.ListFleetReservedMacOSDeclarationNames(),
|
||||
}
|
||||
query, args, err := sqlx.Named(declNamedStmt, arg)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("subqueryAppleDeclarationStatus: %w", err)
|
||||
}
|
||||
query, args, err = sqlx.In(query, args...)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("subqueryAppleDeclarationStatus resolve IN: %w", err)
|
||||
}
|
||||
|
||||
return query, args, nil
|
||||
}
|
||||
|
||||
func subqueryOSSettingsStatusMac() (string, []any, error) {
|
||||
var profArgs []any
|
||||
profFailed, profFailedArgs, err := subqueryAppleProfileStatus(fleet.MDMDeliveryFailed)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
profArgs = append(profArgs, profFailedArgs...)
|
||||
|
||||
profPending, profPendingArgs, err := subqueryAppleProfileStatus(fleet.MDMDeliveryPending)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
profArgs = append(profArgs, profPendingArgs...)
|
||||
|
||||
profVerifying, profVerifyingArgs, err := subqueryAppleProfileStatus(fleet.MDMDeliveryVerifying)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
profArgs = append(profArgs, profVerifyingArgs...)
|
||||
|
||||
profVerified, profVerifiedArgs, err := subqueryAppleProfileStatus(fleet.MDMDeliveryVerified)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
profArgs = append(profArgs, profVerifiedArgs...)
|
||||
|
||||
profStmt := fmt.Sprintf(`
|
||||
CASE WHEN EXISTS (%s) THEN
|
||||
'profiles_failed'
|
||||
WHEN EXISTS (%s) THEN
|
||||
'profiles_pending'
|
||||
WHEN EXISTS (%s) THEN
|
||||
'profiles_verifying'
|
||||
WHEN EXISTS (%s) THEN
|
||||
'profiles_verified'
|
||||
ELSE
|
||||
''
|
||||
END`,
|
||||
profFailed,
|
||||
profPending,
|
||||
profVerifying,
|
||||
profVerified,
|
||||
// sqlJoinMDMAppleProfilesStatus returns a SQL snippet that can be used to join a table derived from
|
||||
// host_mdm_apple_profiles (grouped by host_uuid and status) and the hosts table. For each host_uuid,
|
||||
// it derives a boolean value for each status category. The value will be 1 if the host has any
|
||||
// profile in the given status category. Separate columns are used for status of the filevault profile
|
||||
// vs. all other profiles. The snippet assumes the hosts table to be aliased as 'h'.
|
||||
func sqlJoinMDMAppleProfilesStatus() string {
|
||||
// NOTE: To make this snippet reusable, we're not using sqlx.Named here because it would
|
||||
// complicate usage in other queries (e.g., list hosts).
|
||||
var (
|
||||
failed = fmt.Sprintf("'%s'", string(fleet.MDMDeliveryFailed))
|
||||
pending = fmt.Sprintf("'%s'", string(fleet.MDMDeliveryPending))
|
||||
verifying = fmt.Sprintf("'%s'", string(fleet.MDMDeliveryVerifying))
|
||||
verified = fmt.Sprintf("'%s'", string(fleet.MDMDeliveryVerified))
|
||||
install = fmt.Sprintf("'%s'", string(fleet.MDMOperationTypeInstall))
|
||||
filevault = fmt.Sprintf("'%s'", mobileconfig.FleetFileVaultPayloadIdentifier)
|
||||
)
|
||||
return `
|
||||
LEFT JOIN (
|
||||
-- profile statuses grouped by host uuid, boolean value will be 1 if host has any profile with the given status
|
||||
-- filevault profiles are treated separately
|
||||
SELECT
|
||||
host_uuid,
|
||||
MAX( IF((status IS NULL OR status = ` + pending + `) AND profile_identifier != ` + filevault + `, 1, 0)) AS prof_pending,
|
||||
MAX( IF(status = ` + failed + ` AND profile_identifier != ` + filevault + `, 1, 0)) AS prof_failed,
|
||||
MAX( IF(status = ` + verifying + ` AND profile_identifier != ` + filevault + ` AND operation_type = ` + install + `, 1, 0)) AS prof_verifying,
|
||||
MAX( IF(status = ` + verified + ` AND profile_identifier != ` + filevault + ` AND operation_type = ` + install + `, 1, 0)) AS prof_verified,
|
||||
MAX( IF((status IS NULL OR status = ` + pending + `) AND profile_identifier = ` + filevault + `, 1, 0)) AS fv_pending,
|
||||
MAX( IF(status = ` + failed + ` AND profile_identifier = ` + filevault + `, 1, 0)) AS fv_failed,
|
||||
MAX( IF(status = ` + verifying + ` AND profile_identifier = ` + filevault + ` AND operation_type = ` + install + `, 1, 0)) AS fv_verifying,
|
||||
MAX( IF(status = ` + verified + ` AND profile_identifier = ` + filevault + ` AND operation_type = ` + install + `, 1, 0)) AS fv_verified
|
||||
FROM
|
||||
host_mdm_apple_profiles
|
||||
GROUP BY
|
||||
host_uuid) hmap ON h.uuid = hmap.host_uuid
|
||||
`
|
||||
}
|
||||
|
||||
declStmt, declArgs, err := subqueryAppleDeclarationStatus()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
stmt := fmt.Sprintf(`
|
||||
CASE (%s)
|
||||
WHEN 'profiles_failed' THEN
|
||||
'failed'
|
||||
WHEN 'profiles_pending' THEN (
|
||||
CASE (%s)
|
||||
WHEN 'declarations_failed' THEN
|
||||
'failed'
|
||||
ELSE
|
||||
'pending'
|
||||
END)
|
||||
WHEN 'profiles_verifying' THEN (
|
||||
CASE (%s)
|
||||
WHEN 'declarations_failed' THEN
|
||||
'failed'
|
||||
WHEN 'declarations_pending' THEN
|
||||
'pending'
|
||||
ELSE
|
||||
'verifying'
|
||||
END)
|
||||
WHEN 'profiles_verified' THEN (
|
||||
CASE (%s)
|
||||
WHEN 'declarations_failed' THEN
|
||||
'failed'
|
||||
WHEN 'declarations_pending' THEN
|
||||
'pending'
|
||||
WHEN 'declarations_verifying' THEN
|
||||
'verifying'
|
||||
ELSE
|
||||
'verified'
|
||||
END)
|
||||
ELSE
|
||||
REPLACE((%s), 'declarations_', '')
|
||||
END`, profStmt, declStmt, declStmt, declStmt, declStmt)
|
||||
|
||||
args := append(profArgs, declArgs...)
|
||||
args = append(args, declArgs...)
|
||||
args = append(args, declArgs...)
|
||||
args = append(args, declArgs...)
|
||||
|
||||
// FIXME(roberto): we found issues in MySQL 5.7.17 (only that version,
|
||||
// which we must support for now) with prepared statements on this
|
||||
// query. The results returned by the DB were always different what
|
||||
// expected unless the arguments are inlined in the query.
|
||||
//
|
||||
// We decided to do this given:
|
||||
//
|
||||
// - The time constraints we were given to develop DDM
|
||||
// - The fact that all the variables in this query are really strings managed by us
|
||||
// - The imminent deprecation of MySQL 5.7
|
||||
return fmt.Sprintf(strings.Replace(stmt, "?", "'%s'", -1), args...), []any{}, nil
|
||||
// sqlJoinMDMAppleDeclarationsStatus returns a SQL snippet that can be used to join a table derived from
|
||||
// host_mdm_apple_declarations (grouped by host_uuid and status) and the hosts table. For each host_uuid,
|
||||
// it derives a boolean value for each status category. The value will be 1 if the host has any
|
||||
// declaration in the given status category. The snippet assumes the hosts table to be aliased as 'h'.
|
||||
func sqlJoinMDMAppleDeclarationsStatus() string {
|
||||
// NOTE: To make this snippet reusable, we're not using sqlx.Named here because it would
|
||||
// complicate usage in other queries (e.g., list hosts).
|
||||
var (
|
||||
failed = fmt.Sprintf("'%s'", string(fleet.MDMDeliveryFailed))
|
||||
pending = fmt.Sprintf("'%s'", string(fleet.MDMDeliveryPending))
|
||||
verifying = fmt.Sprintf("'%s'", string(fleet.MDMDeliveryVerifying))
|
||||
verified = fmt.Sprintf("'%s'", string(fleet.MDMDeliveryVerified))
|
||||
install = fmt.Sprintf("'%s'", string(fleet.MDMOperationTypeInstall))
|
||||
reservedDeclNames = fmt.Sprintf("'%s', '%s', '%s'", fleetmdm.FleetMacOSUpdatesProfileName, fleetmdm.FleetIOSUpdatesProfileName, fleetmdm.FleetIPadOSUpdatesProfileName)
|
||||
)
|
||||
return `
|
||||
LEFT JOIN (
|
||||
-- declaration statuses grouped by host uuid, boolean value will be 1 if host has any declaration with the given status
|
||||
SELECT
|
||||
host_uuid,
|
||||
MAX( IF((status IS NULL OR status = ` + pending + `), 1, 0)) AS decl_pending,
|
||||
MAX( IF(status = ` + failed + `, 1, 0)) AS decl_failed,
|
||||
MAX( IF(status = ` + verifying + ` , 1, 0)) AS decl_verifying,
|
||||
MAX( IF(status = ` + verified + ` , 1, 0)) AS decl_verified
|
||||
FROM
|
||||
host_mdm_apple_declarations
|
||||
WHERE
|
||||
operation_type = ` + install + ` AND declaration_name NOT IN(` + reservedDeclNames + `)
|
||||
GROUP BY
|
||||
host_uuid) hmad ON h.uuid = hmad.host_uuid
|
||||
`
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetMDMAppleProfilesSummary(ctx context.Context, teamID *uint) (*fleet.MDMProfilesSummary, error) {
|
||||
subquery, args, err := subqueryOSSettingsStatusMac()
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "building os settings subquery")
|
||||
}
|
||||
|
||||
sqlFmt := `
|
||||
stmt := `
|
||||
SELECT
|
||||
%s as status,
|
||||
COUNT(id) as count
|
||||
COUNT(id) AS count,
|
||||
%s AS status
|
||||
FROM
|
||||
hosts h
|
||||
WHERE platform = 'darwin' OR platform = 'ios' OR platform = 'ipados'
|
||||
GROUP BY status, team_id HAVING status IN (?, ?, ?, ?) AND %s`
|
||||
|
||||
args = append(args, fleet.MDMDeliveryFailed, fleet.MDMDeliveryPending, fleet.MDMDeliveryVerifying, fleet.MDMDeliveryVerified)
|
||||
hosts h
|
||||
%s
|
||||
%s
|
||||
LEFT JOIN host_disk_encryption_keys hdek ON h.id = hdek.host_id
|
||||
WHERE
|
||||
platform IN('darwin', 'ios', 'ipad_os') AND %s
|
||||
GROUP BY
|
||||
status HAVING status IS NOT NULL`
|
||||
|
||||
teamFilter := "team_id IS NULL"
|
||||
if teamID != nil && *teamID > 0 {
|
||||
teamFilter = "team_id = ?"
|
||||
args = append(args, *teamID)
|
||||
teamFilter = fmt.Sprintf("team_id = %d", *teamID)
|
||||
}
|
||||
|
||||
stmt := fmt.Sprintf(sqlFmt, subquery, teamFilter)
|
||||
stmt = fmt.Sprintf(stmt, sqlCaseMDMAppleStatus(), sqlJoinMDMAppleProfilesStatus(), sqlJoinMDMAppleDeclarationsStatus(), teamFilter)
|
||||
|
||||
var dest []struct {
|
||||
Count uint `db:"count"`
|
||||
Status string `db:"status"`
|
||||
}
|
||||
|
||||
err = sqlx.SelectContext(ctx, ds.reader(ctx), &dest, stmt, args...)
|
||||
if err != nil {
|
||||
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &dest, stmt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1114,6 +1114,14 @@ func (ds *Datastore) applyHostFilters(
|
|||
whereParams = append(whereParams, microsoft_mdm.MDMDeviceStateEnrolled)
|
||||
}
|
||||
|
||||
mdmAppleProfilesStatusJoin := ""
|
||||
mdmAppleDeclarationsStatusJoin := ""
|
||||
if opt.OSSettingsFilter.IsValid() ||
|
||||
opt.MacOSSettingsFilter.IsValid() {
|
||||
mdmAppleProfilesStatusJoin = sqlJoinMDMAppleProfilesStatus()
|
||||
mdmAppleDeclarationsStatusJoin = sqlJoinMDMAppleDeclarationsStatus()
|
||||
}
|
||||
|
||||
sqlStmt += fmt.Sprintf(
|
||||
`FROM hosts h
|
||||
LEFT JOIN host_seen_times hst ON (h.id = hst.host_id)
|
||||
|
|
@ -1128,6 +1136,8 @@ func (ds *Datastore) applyHostFilters(
|
|||
%s
|
||||
%s
|
||||
%s
|
||||
%s
|
||||
%s
|
||||
%s
|
||||
WHERE TRUE AND %s AND %s AND %s AND %s
|
||||
`,
|
||||
|
|
@ -1142,6 +1152,8 @@ func (ds *Datastore) applyHostFilters(
|
|||
munkiJoin,
|
||||
displayNameJoin,
|
||||
connectedToFleetJoin,
|
||||
mdmAppleProfilesStatusJoin,
|
||||
mdmAppleDeclarationsStatusJoin,
|
||||
|
||||
// Conditions
|
||||
ds.whereFilterHostsByTeams(filter, "h"),
|
||||
|
|
@ -1304,15 +1316,9 @@ func filterHostsByMacOSSettingsStatus(sql string, opt fleet.HostListOptions, par
|
|||
whereStatus += ` AND h.team_id IS NULL`
|
||||
}
|
||||
|
||||
subqueryStatus, paramsStatus, err := subqueryOSSettingsStatusMac()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
whereStatus += fmt.Sprintf(` AND %s = ?`, sqlCaseMDMAppleStatus())
|
||||
|
||||
whereStatus += fmt.Sprintf(` AND %s = ?`, subqueryStatus)
|
||||
paramsStatus = append(paramsStatus, opt.MacOSSettingsFilter)
|
||||
|
||||
return sql + whereStatus, append(params, paramsStatus...), nil
|
||||
return sql + whereStatus, append(params, opt.MacOSSettingsFilter), nil
|
||||
}
|
||||
|
||||
func filterHostsByMacOSDiskEncryptionStatus(sql string, opt fleet.HostListOptions, params []interface{}) (string, []interface{}) {
|
||||
|
|
@ -1364,13 +1370,9 @@ func (ds *Datastore) filterHostsByOSSettingsStatus(sql string, opt fleet.HostLis
|
|||
AND ((h.platform = 'windows' AND (%s))
|
||||
OR ((h.platform = 'darwin' OR h.platform = 'ios' OR h.platform = 'ipados') AND (%s)))`
|
||||
|
||||
whereMacOS, paramsMacOS, err := subqueryOSSettingsStatusMac()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
whereMacOS += ` = ?`
|
||||
// ensure the host has MDM turned on
|
||||
paramsMacOS = append(paramsMacOS, opt.OSSettingsFilter)
|
||||
// construct the WHERE for macOS
|
||||
whereMacOS = fmt.Sprintf(`(%s) = ?`, sqlCaseMDMAppleStatus())
|
||||
paramsMacOS := []any{opt.OSSettingsFilter}
|
||||
|
||||
// construct the WHERE for windows
|
||||
whereWindows = `hmdm.is_server = 0`
|
||||
|
|
|
|||
|
|
@ -638,6 +638,12 @@ func (ds *Datastore) applyHostLabelFilters(ctx context.Context, filter fleet.Tea
|
|||
joinParams = append(joinParams, microsoft_mdm.MDMDeviceStateEnrolled)
|
||||
}
|
||||
|
||||
if opt.OSSettingsFilter.IsValid() ||
|
||||
opt.MacOSSettingsFilter.IsValid() {
|
||||
query += sqlJoinMDMAppleProfilesStatus()
|
||||
query += sqlJoinMDMAppleDeclarationsStatus()
|
||||
}
|
||||
|
||||
query += fmt.Sprintf(` WHERE lm.label_id = ? AND %s `, ds.whereFilterHostsByTeams(filter, "h"))
|
||||
whereParams = append(whereParams, lid)
|
||||
|
||||
|
|
|
|||
|
|
@ -343,7 +343,7 @@ func TestTranslateCPEToCVE(t *testing.T) {
|
|||
},
|
||||
"cpe:2.3:a:python:python:3.9.6:*:*:*:*:windows:*:*": {
|
||||
includedCVEs: []cve{
|
||||
{ID: "CVE-2024-4030", resolvedInVersion: "3.12.4"},
|
||||
{ID: "CVE-2024-4030", resolvedInVersion: "3.9.20"},
|
||||
},
|
||||
continuesToUpdate: true,
|
||||
},
|
||||
|
|
|
|||
BIN
website/assets/images/articles/automatic-software-install-add-software.png
vendored
Normal file
BIN
website/assets/images/articles/automatic-software-install-add-software.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
BIN
website/assets/images/articles/automatic-software-install-install-software.png
vendored
Normal file
BIN
website/assets/images/articles/automatic-software-install-install-software.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
website/assets/images/articles/automatic-software-install-policies-manage.png
vendored
Normal file
BIN
website/assets/images/articles/automatic-software-install-policies-manage.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
BIN
website/assets/images/articles/automatic-software-install-top-image.png
vendored
Normal file
BIN
website/assets/images/articles/automatic-software-install-top-image.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
BIN
website/assets/images/articles/automatic-software-install-workflow.png
vendored
Normal file
BIN
website/assets/images/articles/automatic-software-install-workflow.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
6
website/config/routes.js
vendored
6
website/config/routes.js
vendored
|
|
@ -353,6 +353,11 @@ module.exports.routes = {
|
|||
'GET /device-management/fleet-user-stories-wayfair': '/success-stories/fleet-user-stories-wayfair',
|
||||
'GET /handbook/security': '/handbook/digital-experience/security',
|
||||
'GET /handbook/security/security-policies':'/handbook/digital-experience/security-policies#information-security-policy-and-acceptable-use-policy',// « reasoning: https://github.com/fleetdm/fleet/pull/9624
|
||||
'GET /handbook/business-operations/security-policies':'/handbook/digital-experience/security-policies',
|
||||
'GET /handbook/business-operations/application-security': '/handbook/digital-experience/application-security',
|
||||
'GET /handbook/business-operations/security-audits': '/handbook/digital-experience/security-audits',
|
||||
'GET /handbook/business-operations/security': '/handbook/digital-experience/security',
|
||||
'GET /handbook/business-operations/vendor-questionnaires': '/handbook/digital-experience/vendor-questionnaires',
|
||||
'GET /handbook/handbook': '/handbook/company/handbook',
|
||||
'GET /handbook/company/development-groups': '/handbook/company/product-groups',
|
||||
'GET /docs/using-fleet/mdm-macos-settings': '/docs/using-fleet/mdm-custom-macos-settings',
|
||||
|
|
@ -565,6 +570,7 @@ module.exports.routes = {
|
|||
'GET /learn-more-about/apple-business-manager-teams-api': 'https://github.com/fleetdm/fleet/blob/main/docs/Contributing/API-for-contributors.md#update-abm-tokens-teams',
|
||||
'GET /learn-more-about/apple-business-manager-gitops': '/docs/using-fleet/gitops#apple-business-manager',
|
||||
'GET /learn-more-about/s3-bootstrap-package': '/docs/configuration/fleet-server-configuration#s-3-software-installers-bucket',
|
||||
'GET /learn-more-about/policy-automation-install-software': '/guides/automatic-software-install-in-fleet',
|
||||
'GET /learn-more-about/exe-install-scripts': '/guides/exe-install-scripts',
|
||||
'GET /learn-more-about/install-scripts': '/guides/deploy-software-packages#install-script',
|
||||
'GET /learn-more-about/uninstall-scripts': '/guides/deploy-software-packages#uninstall-script',
|
||||
|
|
|
|||
Loading…
Reference in a new issue