diff --git a/CODEOWNERS b/CODEOWNERS index 52f1680b77..eba3b36a49 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -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 diff --git a/changes/16593-disk-encryption-verifying b/changes/16593-disk-encryption-verifying new file mode 100644 index 0000000000..44e531026d --- /dev/null +++ b/changes/16593-disk-encryption-verifying @@ -0,0 +1 @@ +* Display disk encryption status in macOS as "verifying" while Fleet verifies if the escrowed key can be decrypted. diff --git a/changes/16608-search-target-icon b/changes/16608-search-target-icon new file mode 100644 index 0000000000..50ca4e4179 --- /dev/null +++ b/changes/16608-search-target-icon @@ -0,0 +1 @@ +- Fix position of live query/poilcy host search icon diff --git a/frontend/components/LiveQuery/TargetsInput/TargetsInput.tsx b/frontend/components/LiveQuery/TargetsInput/TargetsInput.tsx index 80f7b89b38..c56bdd1e33 100644 --- a/frontend/components/LiveQuery/TargetsInput/TargetsInput.tsx +++ b/frontend/components/LiveQuery/TargetsInput/TargetsInput.tsx @@ -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 (
- {this.props.label && this.renderHeading()} - { - this.input = r; - }} - tabIndex={tabIndex} - type={type} - value={value} - disabled={disabled} - {...inputOptions} - data-1p-ignore={ignore1Password} - /> - {iconSvg && } - {iconName && } +
+ { + this.input = r; + }} + tabIndex={tabIndex} + type={type} + value={value} + disabled={disabled} + {...inputOptions} + data-1p-ignore={ignore1Password} + /> + {iconSvg && } + {iconName && } +
{renderHelpText()}
); diff --git a/frontend/components/forms/fields/InputFieldWithIcon/_styles.scss b/frontend/components/forms/fields/InputFieldWithIcon/_styles.scss index fed3ee5e7a..13ff7bbb2a 100644 --- a/frontend/components/forms/fields/InputFieldWithIcon/_styles.scss +++ b/frontend/components/forms/fields/InputFieldWithIcon/_styles.scss @@ -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; diff --git a/server/datastore/mysql/apple_mdm.go b/server/datastore/mysql/apple_mdm.go index d4564b05c5..f7792bbfe0 100644 --- a/server/datastore/mysql/apple_mdm.go +++ b/server/datastore/mysql/apple_mdm.go @@ -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 } diff --git a/server/datastore/mysql/apple_mdm_test.go b/server/datastore/mysql/apple_mdm_test.go index bf8debbcd4..77da42fc92 100644 --- a/server/datastore/mysql/apple_mdm_test.go +++ b/server/datastore/mysql/apple_mdm_test.go @@ -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) diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go index d2b118bb9c..7895228cdd 100644 --- a/server/datastore/mysql/hosts.go +++ b/server/datastore/mysql/hosts.go @@ -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 + ")" diff --git a/server/datastore/mysql/labels.go b/server/datastore/mysql/labels.go index 7cc3fbb58c..c2075b3a5f 100644 --- a/server/datastore/mysql/labels.go +++ b/server/datastore/mysql/labels.go @@ -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 { diff --git a/server/fleet/hosts.go b/server/fleet/hosts.go index 8028ce5a35..e483cd555f 100644 --- a/server/fleet/hosts.go +++ b/server/fleet/hosts.go @@ -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: diff --git a/server/service/campaigns.go b/server/service/campaigns.go index efa82fbf89..cba57bd04e 100644 --- a/server/service/campaigns.go +++ b/server/service/campaigns.go @@ -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 } diff --git a/server/service/hosts_test.go b/server/service/hosts_test.go index 7ef2c05b23..1d3a5ac4c5 100644 --- a/server/service/hosts_test.go +++ b/server/service/hosts_test.go @@ -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", diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index b92172bdac..ba09cefaab 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -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 diff --git a/server/service/live_queries.go b/server/service/live_queries.go index 7bbe6a79b1..11c46457b5 100644 --- a/server/service/live_queries.go +++ b/server/service/live_queries.go @@ -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() { diff --git a/website/api/helpers/github-automations/get-is-pr-preapproved.js b/website/api/helpers/github-automations/get-is-pr-preapproved.js index 8cb8cd281a..1c90da1f16 100644 --- a/website/api/helpers/github-automations/get-is-pr-preapproved.js +++ b/website/api/helpers/github-automations/get-is-pr-preapproved.js @@ -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; } diff --git a/website/config/custom.js b/website/config/custom.js index 2937ef2590..83ae5b768f 100644 --- a/website/config/custom.js +++ b/website/config/custom.js @@ -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'], diff --git a/website/views/pages/vulnerability-management.ejs b/website/views/pages/vulnerability-management.ejs index 50b542445f..2d525f3cc2 100644 --- a/website/views/pages/vulnerability-management.ejs +++ b/website/views/pages/vulnerability-management.ejs @@ -81,11 +81,11 @@
- Consolidate your security stack + Untangle your security stack
-

Consolidate your security stack

-

Consolidate your point vulnerability solution with your cybersecurity asset management and log capture tools.

+

Untangle your security stack

+

Use open data and APIs to connect your point vulnerability solution with your cybersecurity asset management and log capture tools.

Prevent duplicated, inaccurate CMDBs to reduce tool sprawl and wasted budget

Normalize asset management data and software inventories from multiple tools and operating systems