merge main into feat-mdm-wipe-host

This commit is contained in:
Gabriel Hernandez 2024-02-29 18:26:29 +00:00
commit 40892c9adb
20 changed files with 343 additions and 288 deletions

View file

@ -84,15 +84,16 @@ go.mod @fleetdm/go
#
# (see website/config/custom.js for DRIs of other paths not listed here)
##############################################################################################
/handbook/company @mikermcneil
/handbook/README.md @mikermcneil
/handbook/business-operations @sampfluger88
/handbook/digital-experience @sampfluger88
/handbook/customer-success @sampfluger88
/handbook/demand @sampfluger88
/handbook/engineering @sampfluger88
/handbook/sales @sampfluger88
/handbook/product-design @sampfluger88
/handbook/company @mikermcneil
/handbook/README.md @mikermcneil
/handbook/business-operations @sampfluger88
/handbook/digital-experience @sampfluger88
/handbook/customer-success @sampfluger88
/handbook/demand @sampfluger88
/handbook/engineering @sampfluger88 @lukeheath
/handbook/sales @sampfluger88
/handbook/product-design @sampfluger88
/handbook/company/product-groups @sampfluger88 @lukeheath
##############################################################################################
# 🦿 GitHub issue templates

View file

@ -0,0 +1 @@
* Display disk encryption status in macOS as "verifying" while Fleet verifies if the escrowed key can be decrypted.

View file

@ -0,0 +1 @@
- Fix position of live query/poilcy host search icon

View file

@ -7,7 +7,7 @@ import { HOSTS_SEARCH_BOX_PLACEHOLDER } from "utilities/constants";
import DataError from "components/DataError";
// @ts-ignore
import Input from "components/forms/fields/InputFieldWithIcon";
import InputFieldWithIcon from "components/forms/fields/InputFieldWithIcon/InputFieldWithIcon";
import TableContainer from "components/TableContainer";
import { generateTableHeaders } from "./TargetsInputHostsTableConfig";
@ -47,7 +47,7 @@ const TargetsInput = ({
return (
<div>
<div className={baseClass}>
<Input
<InputFieldWithIcon
autofocus
type="search"
iconSvg="search"

View file

@ -94,7 +94,4 @@
overflow: auto;
}
}
.input-icon-field__icon {
top: 34px; // Override styling to include label header
}
}

View file

@ -1,4 +1,8 @@
.filter-cell {
.input-icon-field__input-wrapper {
margin-top: $pad-xsmall;
}
input {
height: 40px;
width: 100%;
@ -8,7 +12,6 @@
border-radius: 4px;
padding: 4px;
padding-left: 32px;
margin-top: $pad-xsmall;
}
.search-field__input-wrapper {
@ -18,9 +21,6 @@
}
.icon {
left: 10px;
top: 17px;
path {
fill: $ui-fleet-black-33; // Override input icon color
}

View file

@ -114,25 +114,27 @@ class InputFieldWithIcon extends InputField {
return (
<div className={wrapperClasses}>
{this.props.label && this.renderHeading()}
<input
id={name}
name={name}
onChange={onInputChange}
onClick={onClick}
className={inputClasses}
placeholder={placeholder}
ref={(r) => {
this.input = r;
}}
tabIndex={tabIndex}
type={type}
value={value}
disabled={disabled}
{...inputOptions}
data-1p-ignore={ignore1Password}
/>
{iconSvg && <Icon name={iconSvg} className={iconClasses} />}
{iconName && <FleetIcon name={iconName} className={iconClasses} />}
<div className={`${baseClass}__input-wrapper`}>
<input
id={name}
name={name}
onChange={onInputChange}
onClick={onClick}
className={inputClasses}
placeholder={placeholder}
ref={(r) => {
this.input = r;
}}
tabIndex={tabIndex}
type={type}
value={value}
disabled={disabled}
{...inputOptions}
data-1p-ignore={ignore1Password}
/>
{iconSvg && <Icon name={iconSvg} className={iconClasses} />}
{iconName && <FleetIcon name={iconName} className={iconClasses} />}
</div>
{renderHelpText()}
</div>
);

View file

@ -18,6 +18,14 @@
}
}
// Relative input wrapper with absolute icon corrects icon alignment on all browsers
&__input-wrapper {
position: relative;
height: 40px;
display: flex;
align-items: center;
}
// Refactor to include svg icons
&--icon-start {
margin-top: 0;
@ -25,10 +33,11 @@
.input-icon-field__icon {
position: absolute;
left: 12px;
top: 13px;
top: 0;
height: 40px;
width: 16px;
font-size: $x-small;
color: $core-fleet-blue;
flex-wrap: wrap;
align-content: center;
z-index: 1;
}
}
@ -51,20 +60,6 @@
color: $core-fleet-blue;
}
&:hover,
&:focus {
border: 1px solid $core-vibrant-blue;
// Icon color matches border color on focus and on hover
+ .input-icon-field__icon {
svg {
path {
fill: $core-vibrant-blue;
}
}
}
}
&:focus {
outline: none;
}
@ -82,6 +77,33 @@
}
}
&__input-wrapper:hover {
.input-icon-field__input {
border: 1px solid $core-vibrant-blue;
}
// Icon color matches border color on focus and on hover
.input-icon-field__icon {
svg {
path {
fill: $core-vibrant-blue;
}
}
}
}
.input-icon-field__input:focus {
border: 1px solid $core-vibrant-blue;
// Icon color matches border color on focus and on hover
+ .input-icon-field__icon {
svg {
path {
fill: $core-vibrant-blue;
}
}
}
}
&__label {
display: block;
font-size: $x-small;

View file

@ -1965,186 +1965,179 @@ func (ds *Datastore) UpdateOrDeleteHostMDMAppleProfile(ctx context.Context, prof
return err
}
func subqueryHostsMacOSSettingsStatusFailed() (string, []interface{}) {
sql := `
SELECT
1 FROM host_mdm_apple_profiles hmap
WHERE
h.uuid = hmap.host_uuid
AND hmap.status = ?`
args := []interface{}{fleet.MDMDeliveryFailed}
const (
appleMDMFailedProfilesStmt = `
h.uuid = hmap.host_uuid AND
hmap.status = :failed`
return sql, args
}
appleMDMPendingProfilesStmt = `
h.uuid = hmap.host_uuid AND
(
hmap.status IS NULL OR
hmap.status = :pending OR
-- 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)
)
)
)`
func subqueryHostsMacOSSettingsStatusPending() (string, []interface{}) {
sql := `
SELECT
1 FROM host_mdm_apple_profiles hmap
WHERE
h.uuid = hmap.host_uuid
AND (hmap.status IS NULL
OR hmap.status = ?
OR(hmap.profile_identifier = ?
AND hmap.status IN (?, ?)
AND hmap.operation_type = ?
AND NOT EXISTS (
SELECT
1 FROM host_disk_encryption_keys hdek
WHERE
h.id = hdek.host_id
AND hdek.decryptable = 1)))
AND NOT EXISTS (
SELECT
1 FROM host_mdm_apple_profiles hmap2
WHERE
h.uuid = hmap2.host_uuid
AND hmap2.status = ?)`
args := []interface{}{
fleet.MDMDeliveryPending,
mobileconfig.FleetFileVaultPayloadIdentifier,
fleet.MDMDeliveryVerifying,
fleet.MDMDeliveryVerified,
fleet.MDMOperationTypeInstall,
fleet.MDMDeliveryFailed,
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)
}
return sql, args
}
func subqueryHostsMacOSSetttingsStatusVerifying() (string, []interface{}) {
sql := `
SELECT
1 FROM host_mdm_apple_profiles hmap
WHERE
h.uuid = hmap.host_uuid
AND hmap.operation_type = ?
AND hmap.status = ?
AND(hmap.profile_identifier != ?
OR EXISTS (
SELECT
1 FROM host_disk_encryption_keys hdek
WHERE
h.id = hdek.host_id
AND hdek.decryptable = 1))
AND NOT EXISTS (
SELECT
1 FROM host_mdm_apple_profiles hmap2
WHERE (h.uuid = hmap2.host_uuid
AND hmap2.operation_type = ?
AND(hmap2.status IS NULL
OR hmap2.status NOT IN(?, ?)
OR(hmap2.profile_identifier = ?
AND hmap2.status IN(?, ?)
AND NOT EXISTS (
SELECT
1 FROM host_disk_encryption_keys hdek
WHERE
h.id = hdek.host_id
AND hdek.decryptable = 1))))
OR(h.uuid = hmap2.host_uuid
AND hmap2.operation_type = ?
AND(hmap2.status IS NULL
OR hmap2.status NOT IN(?, ?))))`
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)
args := []interface{}{
fleet.MDMOperationTypeInstall,
fleet.MDMDeliveryVerifying,
mobileconfig.FleetFileVaultPayloadIdentifier,
fleet.MDMOperationTypeInstall,
fleet.MDMDeliveryVerifying,
fleet.MDMDeliveryVerified,
mobileconfig.FleetFileVaultPayloadIdentifier,
fleet.MDMDeliveryVerifying,
fleet.MDMDeliveryVerified,
fleet.MDMOperationTypeRemove,
fleet.MDMDeliveryVerifying,
fleet.MDMDeliveryVerified,
arg := map[string]any{
"install": fleet.MDMOperationTypeInstall,
"remove": fleet.MDMOperationTypeRemove,
"verifying": fleet.MDMDeliveryVerifying,
"failed": fleet.MDMDeliveryFailed,
"verified": fleet.MDMDeliveryVerified,
"pending": fleet.MDMDeliveryPending,
"filevault": mobileconfig.FleetFileVaultPayloadIdentifier,
}
return sql, args
}
func subqueryHostsMacOSSetttingsStatusVerified() (string, []interface{}) {
sql := `
SELECT
1 FROM host_mdm_apple_profiles hmap
WHERE
h.uuid = hmap.host_uuid
AND hmap.operation_type = ?
AND hmap.status = ?
AND(hmap.profile_identifier != ?
OR EXISTS (
SELECT
1 FROM host_disk_encryption_keys hdek
WHERE
h.id = hdek.host_id
AND hdek.decryptable = 1))
AND NOT EXISTS (
SELECT
1 FROM host_mdm_apple_profiles hmap2
WHERE (h.uuid = hmap2.host_uuid
AND hmap2.operation_type = ?
AND (hmap2.status IS NULL
OR hmap2.status != ?
OR(hmap2.profile_identifier = ?
AND hmap2.status = ?
AND NOT EXISTS (
SELECT
1 FROM host_disk_encryption_keys hdek
WHERE
h.id = hdek.host_id
AND hdek.decryptable = 1))))
OR(h.uuid = hmap2.host_uuid
AND hmap2.operation_type = ?
AND (hmap2.status IS NULL
OR hmap2.status NOT IN(?, ?))))`
args := []interface{}{
fleet.MDMOperationTypeInstall,
fleet.MDMDeliveryVerified,
mobileconfig.FleetFileVaultPayloadIdentifier,
fleet.MDMOperationTypeInstall,
fleet.MDMDeliveryVerified,
mobileconfig.FleetFileVaultPayloadIdentifier,
fleet.MDMDeliveryVerified,
fleet.MDMOperationTypeRemove,
fleet.MDMDeliveryVerifying,
fleet.MDMDeliveryVerified,
query, args, err := sqlx.Named(sql, arg)
if err != nil {
return "", nil, fmt.Errorf("subqueryAppleProfileStatus %s: %w", status, err)
}
return sql, args
return query, args, nil
}
func (ds *Datastore) GetMDMAppleProfilesSummary(ctx context.Context, teamID *uint) (*fleet.MDMProfilesSummary, error) {
var args []interface{}
subqueryFailed, subqueryFailedArgs := subqueryHostsMacOSSettingsStatusFailed()
subqueryFailed, subqueryFailedArgs, err := subqueryAppleProfileStatus(fleet.MDMDeliveryFailed)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "building failed subquery")
}
args = append(args, subqueryFailedArgs...)
subqueryPending, subqueryPendingArgs := subqueryHostsMacOSSettingsStatusPending()
subqueryPending, subqueryPendingArgs, err := subqueryAppleProfileStatus(fleet.MDMDeliveryPending)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "building pending subquery")
}
args = append(args, subqueryPendingArgs...)
subqueryVerifying, subqueryVeryingingArgs := subqueryHostsMacOSSetttingsStatusVerifying()
args = append(args, subqueryVeryingingArgs...)
subqueryVerified, subqueryVerifiedArgs := subqueryHostsMacOSSetttingsStatusVerified()
subqueryVerifying, subqueryVerifyingArgs, err := subqueryAppleProfileStatus(fleet.MDMDeliveryVerifying)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "building verifying subquery")
}
args = append(args, subqueryVerifyingArgs...)
subqueryVerified, subqueryVerifiedArgs, err := subqueryAppleProfileStatus(fleet.MDMDeliveryVerified)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "building verified subquery")
}
args = append(args, subqueryVerifiedArgs...)
sqlFmt := `
SELECT
COUNT(
CASE WHEN EXISTS (%s)
THEN 1
END) AS failed,
COUNT(
CASE WHEN EXISTS (%s)
THEN 1
END) AS pending,
COUNT(
CASE WHEN EXISTS (%s)
THEN 1
END) AS verifying,
COUNT(
CASE WHEN EXISTS (%s)
THEN 1
END) AS verified
FROM
hosts h
WHERE
h.platform = 'darwin' AND %s`
SELECT
COUNT(CASE WHEN EXISTS (%s) THEN 1 END) AS failed,
COUNT(CASE WHEN EXISTS (%s) THEN 1 END) AS pending,
COUNT(CASE WHEN EXISTS (%s) THEN 1 END) AS verifying,
COUNT(CASE WHEN EXISTS (%s) THEN 1 END) AS verified
FROM
hosts h
WHERE
h.platform = 'darwin' AND %s`
teamFilter := "h.team_id IS NULL"
if teamID != nil && *teamID > 0 {
@ -2153,9 +2146,8 @@ WHERE
}
stmt := fmt.Sprintf(sqlFmt, subqueryFailed, subqueryPending, subqueryVerifying, subqueryVerified, teamFilter)
var res fleet.MDMProfilesSummary
err := sqlx.GetContext(ctx, ds.reader(ctx), &res, stmt, args...)
err = sqlx.GetContext(ctx, ds.reader(ctx), &res, stmt, args...)
if err != nil {
return nil, err
}
@ -2209,14 +2201,18 @@ func subqueryFileVaultVerifying() (string, []interface{}) {
1 FROM host_mdm_apple_profiles hmap
WHERE
h.uuid = hmap.host_uuid
AND hdek.decryptable = 1
AND hmap.profile_identifier = ?
AND hmap.status = ?
AND hmap.operation_type = ?`
AND hmap.operation_type = ?
AND (
(hmap.status = ? AND hdek.decryptable IS NULL)
OR
(hmap.status = ? AND hdek.decryptable = 1)
)`
args := []interface{}{
mobileconfig.FleetFileVaultPayloadIdentifier,
fleet.MDMDeliveryVerifying,
fleet.MDMOperationTypeInstall,
fleet.MDMDeliveryVerified,
fleet.MDMDeliveryVerifying,
}
return sql, args
}
@ -2268,23 +2264,11 @@ func subqueryFileVaultEnforcing() (string, []interface{}) {
AND hmap.profile_identifier = ?
AND (hmap.status IS NULL OR hmap.status = ?)
AND hmap.operation_type = ?
UNION SELECT
1 FROM host_mdm_apple_profiles hmap
WHERE
h.uuid = hmap.host_uuid
AND hmap.profile_identifier = ?
AND (hmap.status IS NOT NULL AND (hmap.status = ? OR hmap.status = ?))
AND hmap.operation_type = ?
AND hdek.decryptable IS NULL
AND hdek.host_id IS NOT NULL`
`
args := []interface{}{
mobileconfig.FleetFileVaultPayloadIdentifier,
fleet.MDMDeliveryPending,
fleet.MDMOperationTypeInstall,
mobileconfig.FleetFileVaultPayloadIdentifier,
fleet.MDMDeliveryVerifying,
fleet.MDMDeliveryVerified,
fleet.MDMOperationTypeInstall,
}
return sql, args
}

View file

@ -1769,9 +1769,11 @@ func testAggregateMacOSSettingsStatusWithFileVault(t *testing.T, ds *Datastore)
res, err = ds.GetMDMAppleProfilesSummary(ctx, nil)
require.NoError(t, err)
require.NotNil(t, res)
require.Equal(t, uint(len(hosts)), res.Pending) // still pending because disk encryption key decryptable is not set
// hosts still pending because disk encryption key decryptable is not set
require.Equal(t, uint(len(hosts)-1), res.Pending)
require.Equal(t, uint(0), res.Failed)
require.Equal(t, uint(0), res.Verifying)
// one host is verifying because the disk is encrypted and we're verifying the key
require.Equal(t, uint(1), res.Verifying)
require.Equal(t, uint(0), res.Verified)
err = ds.SetHostsDiskEncryptionKeyStatus(ctx, []uint{hosts[0].ID}, false, time.Now().Add(1*time.Hour))
@ -2434,7 +2436,13 @@ func TestMDMAppleFileVaultSummary(t *testing.T) {
// verifying status
verifyingHost := hosts[0]
upsertHostCPs([]*fleet.Host{verifyingHost}, []*fleet.MDMAppleConfigProfile{noTeamFVProfile}, fleet.MDMOperationTypeInstall, &fleet.MDMDeliveryVerifying, ctx, ds, t)
upsertHostCPs(
[]*fleet.Host{verifyingHost},
[]*fleet.MDMAppleConfigProfile{noTeamFVProfile},
fleet.MDMOperationTypeInstall,
&fleet.MDMDeliveryVerifying,
ctx, ds, t,
)
oneMinuteAfterThreshold := time.Now().Add(+1 * time.Minute)
createDiskEncryptionRecord(ctx, ds, t, verifyingHost.ID, "key-1", true, oneMinuteAfterThreshold)
@ -2643,7 +2651,13 @@ func TestMDMAppleFileVaultSummary(t *testing.T) {
require.Equal(t, uint(0), allProfilesSummary.Verified)
// verified status
upsertHostCPs([]*fleet.Host{verifyingTeam1Host}, []*fleet.MDMAppleConfigProfile{team1FVProfile}, fleet.MDMOperationTypeInstall, &fleet.MDMDeliveryVerified, ctx, ds, t)
upsertHostCPs(
[]*fleet.Host{verifyingTeam1Host},
[]*fleet.MDMAppleConfigProfile{team1FVProfile},
fleet.MDMOperationTypeInstall,
&fleet.MDMDeliveryVerified,
ctx, ds, t,
)
fvProfileSummary, err = ds.GetMDMAppleFileVaultSummary(ctx, &tm.ID)
require.NoError(t, err)
require.NotNil(t, fvProfileSummary)

View file

@ -1074,7 +1074,11 @@ func (ds *Datastore) applyHostFilters(
sqlStmt, params = filterHostsByTeam(sqlStmt, opt, params)
sqlStmt, params = filterHostsByPolicy(sqlStmt, opt, params)
sqlStmt, params = filterHostsByMDM(sqlStmt, opt, params)
sqlStmt, params = filterHostsByMacOSSettingsStatus(sqlStmt, opt, params)
var err error
sqlStmt, params, err = filterHostsByMacOSSettingsStatus(sqlStmt, opt, params)
if err != nil {
return "", nil, ctxerr.Wrap(ctx, err, "building query to filter macOS settings status")
}
sqlStmt, params = filterHostsByMacOSDiskEncryptionStatus(sqlStmt, opt, params)
if enableDiskEncryption, err := ds.getConfigEnableDiskEncryption(ctx, opt.TeamFilter); err != nil {
if errors.Is(err, sql.ErrNoRows) {
@ -1195,9 +1199,9 @@ func filterHostsByStatus(now time.Time, sql string, opt fleet.HostListOptions, p
return sql, params
}
func filterHostsByMacOSSettingsStatus(sql string, opt fleet.HostListOptions, params []interface{}) (string, []interface{}) {
func filterHostsByMacOSSettingsStatus(sql string, opt fleet.HostListOptions, params []any) (string, []any, error) {
if !opt.MacOSSettingsFilter.IsValid() {
return sql, params
return sql, params, nil
}
newSQL := ""
@ -1208,22 +1212,26 @@ func filterHostsByMacOSSettingsStatus(sql string, opt fleet.HostListOptions, par
}
var subquery string
var subqueryParams []interface{}
var subqueryParams []any
var err error
switch opt.MacOSSettingsFilter {
case fleet.OSSettingsFailed:
subquery, subqueryParams = subqueryHostsMacOSSettingsStatusFailed()
subquery, subqueryParams, err = subqueryAppleProfileStatus(fleet.MDMDeliveryFailed)
case fleet.OSSettingsPending:
subquery, subqueryParams = subqueryHostsMacOSSettingsStatusPending()
subquery, subqueryParams, err = subqueryAppleProfileStatus(fleet.MDMDeliveryPending)
case fleet.OSSettingsVerifying:
subquery, subqueryParams = subqueryHostsMacOSSetttingsStatusVerifying()
subquery, subqueryParams, err = subqueryAppleProfileStatus(fleet.MDMDeliveryVerifying)
case fleet.OSSettingsVerified:
subquery, subqueryParams = subqueryHostsMacOSSetttingsStatusVerified()
subquery, subqueryParams, err = subqueryAppleProfileStatus(fleet.MDMDeliveryVerified)
}
if err != nil {
return "", nil, fmt.Errorf("building subquery for %s filter: %w", opt.MacOSSettingsFilter, err)
}
if subquery != "" {
newSQL += fmt.Sprintf(` AND EXISTS (%s)`, subquery)
}
return sql + newSQL, append(params, subqueryParams...)
return sql + newSQL, append(params, subqueryParams...), nil
}
func filterHostsByMacOSDiskEncryptionStatus(sql string, opt fleet.HostListOptions, params []interface{}) (string, []interface{}) {
@ -1276,15 +1284,19 @@ func (ds *Datastore) filterHostsByOSSettingsStatus(sql string, opt fleet.HostLis
// construct the WHERE for macOS
var subqueryMacOS string
var paramsMacOS []interface{}
var err error
switch opt.OSSettingsFilter {
case fleet.OSSettingsFailed:
subqueryMacOS, paramsMacOS = subqueryHostsMacOSSettingsStatusFailed()
subqueryMacOS, paramsMacOS, err = subqueryAppleProfileStatus(fleet.MDMDeliveryFailed)
case fleet.OSSettingsPending:
subqueryMacOS, paramsMacOS = subqueryHostsMacOSSettingsStatusPending()
subqueryMacOS, paramsMacOS, err = subqueryAppleProfileStatus(fleet.MDMDeliveryPending)
case fleet.OSSettingsVerifying:
subqueryMacOS, paramsMacOS = subqueryHostsMacOSSetttingsStatusVerifying()
subqueryMacOS, paramsMacOS, err = subqueryAppleProfileStatus(fleet.MDMDeliveryVerifying)
case fleet.OSSettingsVerified:
subqueryMacOS, paramsMacOS = subqueryHostsMacOSSetttingsStatusVerified()
subqueryMacOS, paramsMacOS, err = subqueryAppleProfileStatus(fleet.MDMDeliveryVerified)
}
if err != nil {
return "", nil, fmt.Errorf("building subquery for %s filter: %w", opt.OSSettingsFilter, err)
}
if subqueryMacOS != "" {
whereMacOS = "EXISTS (" + subqueryMacOS + ")"

View file

@ -584,10 +584,14 @@ func (ds *Datastore) applyHostLabelFilters(ctx context.Context, filter fleet.Tea
params = append(params, *opt.LowDiskSpaceFilter)
}
var err error
query, params = filterHostsByStatus(ds.clock.Now(), query, opt, params)
query, params = filterHostsByTeam(query, opt, params)
query, params = filterHostsByMDM(query, opt, params)
query, params = filterHostsByMacOSSettingsStatus(query, opt, params)
query, params, err = filterHostsByMacOSSettingsStatus(query, opt, params)
if err != nil {
return "", nil, ctxerr.Wrap(ctx, err, "building macOS settings status filter")
}
query, params = filterHostsByMacOSDiskEncryptionStatus(query, opt, params)
query, params = filterHostsByMDMBootstrapPackageStatus(query, opt, params)
if enableDiskEncryption, err := ds.getConfigEnableDiskEncryption(ctx, opt.TeamFilter); err != nil {

View file

@ -505,10 +505,10 @@ func (d *MDMHostData) PopulateOSSettingsAndMacOSSettings(profiles []HostMDMApple
if d.rawDecryptable != nil && *d.rawDecryptable == 1 {
// if a FileVault profile has been successfully installed on the host
// AND we have fetched and are able to decrypt the key
switch {
case *fvprof.Status == MDMDeliveryVerifying:
switch *fvprof.Status {
case MDMDeliveryVerifying:
settings.DiskEncryption = DiskEncryptionVerifying.addrOf()
case *fvprof.Status == MDMDeliveryVerified:
case MDMDeliveryVerified:
settings.DiskEncryption = DiskEncryptionVerified.addrOf()
}
} else if d.rawDecryptable != nil {
@ -525,7 +525,12 @@ func (d *MDMHostData) PopulateOSSettingsAndMacOSSettings(profiles []HostMDMApple
// if [a FileVault profile is pending to be installed or] the
// matching row in host_disk_encryption_keys has a field decryptable
// = NULL
settings.DiskEncryption = DiskEncryptionEnforcing.addrOf()
switch *fvprof.Status {
case MDMDeliveryVerifying, MDMDeliveryVerified:
settings.DiskEncryption = DiskEncryptionVerifying.addrOf()
case MDMDeliveryPending:
settings.DiskEncryption = DiskEncryptionEnforcing.addrOf()
}
}
case fvprof.Status != nil && *fvprof.Status == MDMDeliveryFailed:

View file

@ -156,17 +156,17 @@ func (svc *Service) NewDistributedQueryCampaign(ctx context.Context, queryString
}
}
err = svc.liveQueryStore.RunQuery(strconv.Itoa(int(campaign.ID)), queryString, hostIDs)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "run query")
}
// Metrics are used for total hosts targeted for the activity feed.
campaign.Metrics, err = svc.ds.CountHostsInTargets(ctx, filter, targets, time.Now())
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "counting hosts")
}
err = svc.liveQueryStore.RunQuery(strconv.Itoa(int(campaign.ID)), queryString, hostIDs)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "run query")
}
return campaign, nil
}

View file

@ -144,9 +144,9 @@ func TestHostDetailsMDMAppleDiskEncryption(t *testing.T) {
Status: &fleet.MDMDeliveryVerifying,
OperationType: fleet.MDMOperationTypeInstall,
},
fleet.DiskEncryptionEnforcing,
fleet.DiskEncryptionVerifying,
"",
&fleet.MDMDeliveryPending,
&fleet.MDMDeliveryVerifying,
},
{
"installed profile, not decryptable",

View file

@ -2963,15 +2963,15 @@ func (s *integrationMDMTestSuite) TestMDMAppleHostDiskEncryption() {
require.NoError(t, err)
// get that host - it has an encryption key with unknown decryptability, so
// it should report "enforcing" disk encryption.
// it should report "verifying" disk encryption.
getHostResp = getHostResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", host.ID), nil, http.StatusOK, &getHostResp)
require.NotNil(t, getHostResp.Host.MDM.MacOSSettings.DiskEncryption)
require.Equal(t, fleet.DiskEncryptionEnforcing, *getHostResp.Host.MDM.MacOSSettings.DiskEncryption)
require.Equal(t, fleet.DiskEncryptionVerifying, *getHostResp.Host.MDM.MacOSSettings.DiskEncryption)
require.Nil(t, getHostResp.Host.MDM.MacOSSettings.ActionRequired)
require.NotNil(t, getHostResp.Host.MDM.OSSettings)
require.NotNil(t, getHostResp.Host.MDM.OSSettings.DiskEncryption.Status)
require.Equal(t, fleet.DiskEncryptionEnforcing, *getHostResp.Host.MDM.OSSettings.DiskEncryption.Status)
require.Equal(t, fleet.DiskEncryptionVerifying, *getHostResp.Host.MDM.OSSettings.DiskEncryption.Status)
require.Equal(t, "", getHostResp.Host.MDM.OSSettings.DiskEncryption.Detail)
// request with no token

View file

@ -263,27 +263,30 @@ func (svc *Service) RunLiveQueryDeadline(
queryIDPtr = nil
queryString = query
}
campaign, err := svc.NewDistributedQueryCampaign(ctx, queryString, queryIDPtr, fleet.HostTargets{HostIDs: hostIDs})
if err != nil {
level.Error(svc.logger).Log(
"msg", "new distributed query campaign",
"queryString", queryString,
"queryID", queryID,
"err", err,
)
resultsCh <- fleet.QueryCampaignResult{QueryID: queryID, Error: ptr.String(err.Error()), Err: err}
return
}
queryID = campaign.QueryID
readChan, cancelFunc, err := svc.GetCampaignReader(ctx, campaign)
if err != nil {
resultsCh <- fleet.QueryCampaignResult{QueryID: queryID, Error: ptr.String(err.Error()), Err: err}
return
}
defer cancelFunc()
// We do not want to use the outer `ctx` directly because we want to cleanup the campaign
// even if the outer `ctx` is canceled (e.g. a client terminating the connection).
// Also, we make sure stats and activity DB operations don't get killed after we return results.
ctxWithoutCancel := context.WithoutCancel(ctx)
defer func() {
// We do not want to use the outer `ctx` directly because we want to cleanup the campaign
// even if the outer `ctx` is canceled (e.g. a client terminating the connection).
ctx := context.WithoutCancel(ctx)
err := svc.CompleteCampaign(ctx, campaign)
err := svc.CompleteCampaign(ctxWithoutCancel, campaign)
if err != nil {
level.Error(svc.logger).Log("msg", "completing campaign (sync)", "query.id", campaign.QueryID, "err", err)
level.Error(svc.logger).Log(
"msg", "completing campaign (sync)", "query.id", campaign.QueryID, "campaign.id", campaign.ID, "err", err,
)
resultsCh <- fleet.QueryCampaignResult{
QueryID: queryID,
Error: ptr.String(err.Error()),
@ -292,6 +295,16 @@ func (svc *Service) RunLiveQueryDeadline(
}
}()
readChan, cancelFunc, err := svc.GetCampaignReader(ctx, campaign)
if err != nil {
level.Error(svc.logger).Log(
"msg", "get campaign reader", "query.id", campaign.QueryID, "campaign.id", campaign.ID, "err", err,
)
resultsCh <- fleet.QueryCampaignResult{QueryID: queryID, Error: ptr.String(err.Error()), Err: err}
return
}
defer cancelFunc()
var results []fleet.QueryResult
timeout := time.After(deadline)
@ -305,8 +318,6 @@ func (svc *Service) RunLiveQueryDeadline(
level.Error(svc.logger).Log("msg", "error checking saved query", "query.id", campaign.QueryID, "err", err)
perfStatsTracker.saveStats = false
}
// to make sure stats and activity DB operations don't get killed after we return results.
ctxWithoutCancel := context.WithoutCancel(ctx)
totalHosts := campaign.Metrics.TotalHosts
// We update aggregated stats and activity at the end asynchronously.
defer func() {

View file

@ -39,7 +39,7 @@ module.exports = {
MAINTAINERS_BY_PATH = sails.config.custom.confidentialGithubRepoMaintainersByPath;
}
if (repo === 'fleet-mdm-gitops') {
if (repo === 'fleet-gitops') {
MAINTAINERS_BY_PATH = sails.config.custom.fleetMdmGitopsGithubRepoMaintainersByPath;
}

View file

@ -221,9 +221,10 @@ module.exports.custom = {
// Handbook
'handbook/README.md': 'mikermcneil', // See https://github.com/fleetdm/fleet/pull/13195
'handbook/company': 'mikermcneil',
'handbook/company/product-groups': ['lukeheath', 'sampfluger88','mikermcneil'],
'handbook/digital-experience': ['sampfluger88','mikermcneil'],
'handbook/business-operations': ['sampfluger88','mikermcneil'],
'handbook/engineering': ['sampfluger88','mikermcneil'],
'handbook/engineering': ['sampfluger88','mikermcneil', 'lukeheath'],
'handbook/product-design': ['sampfluger88','mikermcneil'],
'handbook/sales': ['sampfluger88','mikermcneil'],
'handbook/demand': ['sampfluger88','mikermcneil'],

View file

@ -81,11 +81,11 @@
<div purpose="feature" class="d-flex flex-md-row flex-column justify-content-between mx-auto align-items-center">
<div purpose="feature-image" class="right">
<img alt="Consolidate your security stack" src="/images/vuln-management-feature-image-3-380x320@2x.png">
<img alt="Untangle your security stack" src="/images/vuln-management-feature-image-3-380x320@2x.png">
</div>
<div purpose="feature-text" class="d-flex flex-column">
<h3>Consolidate your security stack</h3>
<p>Consolidate your point vulnerability solution with your cybersecurity asset management and log capture tools.</p>
<h3>Untangle your security stack</h3>
<p>Use open data and APIs to connect your point vulnerability solution with your cybersecurity asset management and log capture tools.</p>
<div purpose="checklist" class="flex-column d-flex">
<p>Prevent duplicated, inaccurate CMDBs to reduce tool sprawl and wasted budget</p>
<p>Normalize asset management data and software inventories from multiple tools and operating systems</p>