diff --git a/articles/deploy-software-packages.md b/articles/deploy-software-packages.md index b8dfeaf12ed4..b40c4bde5c08 100644 --- a/articles/deploy-software-packages.md +++ b/articles/deploy-software-packages.md @@ -35,6 +35,8 @@ Learn more about automatically installing software in a separate guide [here](ht > - [.deb extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/deb.go#:~:text=func%20ExtractDebMetadata) > - [.rpm extractor code](https://github.com/fleetdm/fleet/blob/main/pkg/file/rpm.go#:~:text=func%20ExtractRPMMetadata) +* Select the hosts that you want to target with this software, under "Target". Select "All hosts" if you want the software to be available to all your hosts. Select "Custom" to scope the software to specific groups of hosts based on label membership. You can select "Include any", which will scope the software to hosts that have any of the labels you select, or "Exclude any", which will scope the software to hosts that do _not_ have the selected labels. + * To allow users to install the software from Fleet Desktop, check the “Self-service” checkbox. * To customize installer behavior, click on “Advanced options.” diff --git a/articles/install-fleet-maintained-apps-on-macos-hosts.md b/articles/install-fleet-maintained-apps-on-macos-hosts.md index 8dc8eecccd1d..511f9a125f63 100644 --- a/articles/install-fleet-maintained-apps-on-macos-hosts.md +++ b/articles/install-fleet-maintained-apps-on-macos-hosts.md @@ -12,8 +12,10 @@ Fleet maintains these [celebrity apps](https://github.com/fleetdm/fleet/blob/mai 1. Head to the **Software** page and click **Add software**. 2. From the **Add software** page, navigate to the **Fleet-maintained** tab. -3. You’ll see a list of popular apps, such as Chrome, Visual Studio Code, and Notion. Click on a row in the table to select the desired app. -4. You will be taken to the app details page after selecting the app. Here, you can set the app as a self-service app, allowing hosts to install it on demand. You can also expand the **Advanced options**, which will enable you to edit the following: +3. You’ll see a list of popular apps, such as Chrome, Visual Studio Code, and Notion. Click on a row in the table to select the desired app and go to its details page. +4. Select the hosts that you want to target with this software, under "Target". Select "All hosts" if you want the software to be available to all your hosts. Select "Custom" to scope the software to specific groups of hosts based on label membership. You can select "Include any", which will scope the software to hosts that have any of the labels you select, or "Exclude any", which will scope the software to hosts that do _not_ have the selected labels. +5. Choose whether you want the app to be self-service. [Self-service apps](https://fleetdm.com/guides/software-self-service) are available for install on demand by end users on the "My device" page, which they can access via Fleet Desktop. +6. You can also expand the **Advanced options**, which will enable you to edit the following: - Pre-install query - Installation script diff --git a/articles/software-self-service.md b/articles/software-self-service.md index f82a27f8c867..07ad2502a1ad 100644 --- a/articles/software-self-service.md +++ b/articles/software-self-service.md @@ -18,11 +18,12 @@ Fleet’s self-service software feature empowers end users by allowing them to i 2. **Select a team**: Click the dropdown in the upper left corner of the page and click on the team to which you want to add the software package. 3. **Open the “Add software” modal**: Click the “Add software” button in the upper right corner of the page. 4. **Select a software package to upload**: Click “Choose file” in the “Add software” modal and select a software package from your computer. -5. **Advanced options**: If desired, click “Advanced options” to add a pre-install condition or post-install script to your software package. +5. **Select the hosts that you want to target**: Select "All hosts" if you want the software to be available to all your hosts. Select "Custom" to scope the software to specific groups of hosts based on label membership. You can select "Include any", which will scope the software to hosts that have any of the labels you select, or "Exclude any", which will scope the software to hosts that do _not_ have the selected labels. +6. **Advanced options**: If desired, click “Advanced options” to add a pre-install condition or post-install script to your software package. * **Pre-install condition**: This is an osquery query that results in true. For example, you might require a specific software title to exist before installing additional extensions. * **Post-install script**: This might be used to apply a license key, perform configuration tasks, or execute cleanup tasks after the software installation. -6. **Make the software package self-service**: Check the “Self-service” checkbox to mark the software package as self-service. -7. **Finish the upload**: Click the “Add software” button to finish the upload process. +7. **Make the software package self-service**: Check the “Self-service” checkbox to mark the software package as self-service. +8. **Finish the upload**: Click the “Add software” button to finish the upload process. ### Editing a self-service software package diff --git a/articles/using-bioutil-cmd.md b/articles/using-bioutil-cmd.md new file mode 100644 index 000000000000..a81264ef6231 --- /dev/null +++ b/articles/using-bioutil-cmd.md @@ -0,0 +1,109 @@ +# Using bioutil to verify Touch ID/biometric utilization + +![Apple TouchID](../website/assets/images/articles/bioutil@2x.png) + +## Intro + +The `bioutil` command-line utility was introduced in macOS Sierra to manage Touch ID configurations and enrolled fingerprints. This handy tool can provide admins with the flexibility to understand a snapshot of what TouchID configurations exist in their fleet. It is important to note that this command is for verifying Apple services in use and confirming general Touch ID settings. Fingerprint metadata is stored on the secure enclave inside the device and this data is not on Apple servers or iCloud, and is not accessible by Apple or any third parties. + +Let's take a look at a few examples of `bioutil`. + +`bioutil -r` - useful for reading the current users biometrics settings + +``` +User Touch ID configuration: + Biometrics for unlock: 1 + Biometrics for ApplePay: 1 + Effective biometrics for unlock: 1 + Effective biometrics for ApplePay: 1 +``` + +`sudo bioutil -s -d 712` - would be used to delete the biometric settings for UID 712 + +## Creating a script + +Our organization wants to better understand the number of fingerprints that the users of a computer has enrolled, and pull this data into Fleet to use in a policy. + +First, we create a basic shell script: + +``` +#!/bin/sh + +# vars +OUTPUT_FILE="/opt/orbit/biometric_config.json" +CURRENT_USER=$(stat -f%Su /dev/console) + +# bioutil command as the currently logged-in user +BIOMETRIC_OUTPUT=$(su -l "$CURRENT_USER" -c "bioutil -c") + +# user and number of fingerprints from the command output +USER_ID=$(echo "$BIOMETRIC_OUTPUT" | grep -oE 'User [0-9]+' | awk '{print $2}') +FINGERPRINT_COUNT=$(echo "$BIOMETRIC_OUTPUT" | grep -oE '[0-9]+ biometric template' | awk '{print $1}') + +# Create JSON +JSON_OUTPUT=$(cat < "$OUTPUT_FILE" + +# output status to fleet ui +echo "JSON written to $OUTPUT_FILE." + +``` + +> This script is printing output to `/opt/orbit`, where other Fleet configs live, but can be placed anywhere on the filesystem. + +## Deploying the script via Fleet + +In this example, we just want to check a 'snapshot' of our fleet's configuration, but this script could also be added to a crobtab to generate refreshed data on a set interval. + +From the Fleet UI, select **Controls** > **Scripts** and upload the script from the previous step. + +Now, navigate to **Policies** > **Add policy**. In this example, we want to use [policy automation](https://fleetdm.com/guides/policy-automation-run-script) to look for the existance of the config file - `biometric_config.json` and if this doesn't exist on the host, run the aformentioned script to generate the output. + +Use the following query to build your policy: + +`SELECT 1 FROM file WHERE path = '/opt/orbit/biometric_config.json` + +Save this policy and select **Manage automations** > **Run script** to bring up the modal where we will tie a failure of this policy (i.e. file does not exist) to script execution. + +## Reading the config data + +At this point, we know that the file we want to read, `biometric_config.json` exists on all our hosts so now we can write a query, using the powerful `parse_json` table. + +`SELECT * FROM parse_json WHERE path = '/opt/orbit/biometric_config.json'` + +![bioutil example query](../website/assets/images/articles/bioutil-command.png) + +If you just wanted to return the number of enrolled fingerprints, use a query like such: + +`SELECT value FROM parse_json WHERE path = '/opt/orbit/biometric_config.json' AND key = 'fingerprint_count'` + +## Writing a policy + +Our infosec team wants to know any devices that have more than 1 fingerprint enabled on them, potentially indicating another user having access to the device. We can answer this question easily with a policy. + +`SELECT 1 FROM parse_json WHERE path = '/opt/orbit/biometric_config.json' AND key = 'fingerprint_count' AND value = 1` + +Any device, where the value of `fingerprint_count` is greater than 1 will result in a failed policy. Your infosec team can quickly export the information on the hosts, or even use the same script execution automation to remediate this with the `bioutil -p` command, for example. + +## Conclusion + +Using `bioutil` alongside Fleet provides a powerful method for managing and monitoring biometric configurations across your macOS fleet. By automating script deployment and leveraging Fleet's policy capabilities, organizations can gain valuable insights into Touch ID usage, identify potential security risks, and enforce compliance with minimal manual intervention. + +For more tips and detailed guides, don’t forget to check out the Fleet +[documentation.](https://fleetdm.com/docs/get-started/why-fleet) + + + + + + + + diff --git a/changes/25038-fix-profile-status-aggregate-tooltips-spacing b/changes/25038-fix-profile-status-aggregate-tooltips-spacing new file mode 100644 index 000000000000..79504e60eca0 --- /dev/null +++ b/changes/25038-fix-profile-status-aggregate-tooltips-spacing @@ -0,0 +1 @@ +* Add offset to the tooltips on hover of the profile aggregate status indicators. diff --git a/frontend/components/StatusIndicatorWithIcon/StatusIndicatorWithIcon.tsx b/frontend/components/StatusIndicatorWithIcon/StatusIndicatorWithIcon.tsx index e0ae3cd20d7c..3ef8a5c8f767 100644 --- a/frontend/components/StatusIndicatorWithIcon/StatusIndicatorWithIcon.tsx +++ b/frontend/components/StatusIndicatorWithIcon/StatusIndicatorWithIcon.tsx @@ -1,11 +1,9 @@ import React from "react"; -import ReactTooltip from "react-tooltip"; -import { uniqueId } from "lodash"; import classnames from "classnames"; import { IconNames } from "components/icons"; import Icon from "components/Icon"; -import { COLORS } from "styles/var/colors"; +import TooltipWrapper from "components/TooltipWrapper"; const baseClass = "status-indicator-with-icon"; @@ -46,7 +44,6 @@ const StatusIndicatorWithIcon = ({ valueClassName, }: IStatusIndicatorWithIconProps) => { const classNames = classnames(baseClass, className); - const id = `status-${uniqueId()}`; const valueClasses = classnames(`${baseClass}__value`, valueClassName, { [`${baseClass}__value-vertical`]: layout === "vertical", @@ -59,21 +56,18 @@ const StatusIndicatorWithIcon = ({ ); const indicatorContent = tooltip ? ( - <> - - {valueContent} - - - {tooltip.tooltipText} - - + + {valueContent} + ) : ( {valueContent} ); diff --git a/frontend/components/StatusIndicatorWithIcon/_styles.scss b/frontend/components/StatusIndicatorWithIcon/_styles.scss index c5589de2d7c1..35009c7d9576 100644 --- a/frontend/components/StatusIndicatorWithIcon/_styles.scss +++ b/frontend/components/StatusIndicatorWithIcon/_styles.scss @@ -19,4 +19,8 @@ flex-direction: column; gap: $pad-xsmall; } + + .indicator-tip-text { + text-align: center; + } } diff --git a/frontend/pages/ManageControlsPage/OSSettings/ProfileStatusAggregate/_styles.scss b/frontend/pages/ManageControlsPage/OSSettings/ProfileStatusAggregate/_styles.scss index c37c7cf654bd..c945f7519150 100644 --- a/frontend/pages/ManageControlsPage/OSSettings/ProfileStatusAggregate/_styles.scss +++ b/frontend/pages/ManageControlsPage/OSSettings/ProfileStatusAggregate/_styles.scss @@ -42,4 +42,8 @@ &__status-indicator-value { font-weight: $bold; } + + .icon { + margin-right: initial; + } } diff --git a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/SoftwareCustomPackage.tsx b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/SoftwareCustomPackage.tsx index f2b648744af3..c12ad876e7f9 100644 --- a/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/SoftwareCustomPackage.tsx +++ b/frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareCustomPackage/SoftwareCustomPackage.tsx @@ -1,13 +1,9 @@ import React, { useContext, useEffect } from "react"; import { InjectedRouter } from "react-router"; import { useQuery } from "react-query"; -import { isAxiosError } from "axios"; import PATHS from "router/paths"; -import { - DEFAULT_USE_QUERY_OPTIONS, - LEARN_MORE_ABOUT_BASE_LINK, -} from "utilities/constants"; +import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants"; import { getFileDetails, IFileDetails } from "utilities/file/fileUtils"; import { buildQueryStringFromParams, QueryParams } from "utilities/url"; import softwareAPI, { @@ -18,10 +14,8 @@ import labelsAPI, { getCustomLabels } from "services/entities/labels"; import { NotificationContext } from "context/notification"; import { AppContext } from "context/app"; -import { getErrorReason } from "interfaces/errors"; import { ILabelSummary } from "interfaces/label"; -import CustomLink from "components/CustomLink"; import FileProgressModal from "components/FileProgressModal"; import PremiumFeatureMessage from "components/PremiumFeatureMessage"; import Spinner from "components/Spinner"; diff --git a/frontend/pages/SoftwarePage/components/PackageForm/PackageForm.tsx b/frontend/pages/SoftwarePage/components/PackageForm/PackageForm.tsx index 0408ab150bbc..8da22a54071f 100644 --- a/frontend/pages/SoftwarePage/components/PackageForm/PackageForm.tsx +++ b/frontend/pages/SoftwarePage/components/PackageForm/PackageForm.tsx @@ -1,5 +1,5 @@ // Used in AddPackageModal.tsx and EditSoftwareModal.tsx -import React, { useContext, useState, useEffect } from "react"; +import React, { useContext, useState, useEffect, useCallback } from "react"; import classnames from "classnames"; import { NotificationContext } from "context/notification"; @@ -24,6 +24,7 @@ import PackageAdvancedOptions from "../PackageAdvancedOptions"; import { CUSTOM_TARGET_OPTIONS, generateFormValidation, + generateHelpText, generateSelectedLabels, getCustomTarget, getTargetType, @@ -172,11 +173,14 @@ const PackageForm = ({ setFormValidation(generateFormValidation(newData)); }; - const onChangeInstallType = (value: string) => { - const installType = value as InstallType; - const newData = { ...formData, installType }; - setFormData(newData); - }; + const onChangeInstallType = useCallback( + (value: string) => { + const installType = value as InstallType; + const newData = { ...formData, installType }; + setFormData(newData); + }, + [formData] + ); const onToggleSelfServiceCheckbox = (value: boolean) => { const newData = { ...formData, selfService: value }; @@ -219,7 +223,7 @@ const PackageForm = ({ if (isExePackage && formData.installType === "automatic") { onChangeInstallType("manual"); } - }, [isExePackage]); + }, [formData.installType, isExePackage, onChangeInstallType]); return (
@@ -253,6 +257,10 @@ const PackageForm = ({ onSelectCustomTarget={onSelectCustomTarget} onSelectLabel={onSelectLabel} labels={labels || []} + dropdownHelpText={ + formData.targetType === "Custom" && + generateHelpText(formData.installType, formData.customTarget) + } /> { ) ?? {} ); }; + +export const generateHelpText = (installType: string, customTarget: string) => { + if (customTarget === "labelsIncludeAny") { + return installType === "manual" ? ( + <> + Software will only be available for install on hosts that{" "} + have any of these labels: + + ) : ( + <> + Software will only be installed on hosts that have any of these + labels: + + ); + } + + // this is the case for labelsExcludeAny + return installType === "manual" ? ( + <> + Software will only be available for install on hosts that{" "} + don't have any of these labels: + + ) : ( + <> + Software will only be installed on hosts that don't have any{" "} + of these labels:{" "} + + ); +}; diff --git a/frontend/pages/queries/ManageQueriesPage/ManageQueriesPage.tsx b/frontend/pages/queries/ManageQueriesPage/ManageQueriesPage.tsx index 2c988659814f..1f5e1b22744b 100644 --- a/frontend/pages/queries/ManageQueriesPage/ManageQueriesPage.tsx +++ b/frontend/pages/queries/ManageQueriesPage/ManageQueriesPage.tsx @@ -273,9 +273,6 @@ const ManageQueriesPage = ({ }; const renderQueriesTable = () => { - if (isLoadingQueries) { - return ; - } if (queriesError) { return ; } @@ -285,7 +282,7 @@ const ManageQueriesPage = ({ totalQueriesCount={queriesResponse?.count} hasNextResults={!!queriesResponse?.meta.has_next_results} onlyInheritedQueries={onlyInheritedQueries} - isLoading={isFetchingQueries} + isLoading={isLoadingQueries || isFetchingQueries} onCreateQueryClick={onCreateQueryClick} onDeleteQueryClick={onDeleteQueryClick} isOnlyObserver={isOnlyObserver} diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go index 4b2d211f51a0..302bde1f79fa 100644 --- a/server/service/apple_mdm.go +++ b/server/service/apple_mdm.go @@ -513,8 +513,7 @@ func (svc *Service) NewMDMAppleDeclaration(ctx context.Context, teamID uint, r i return nil, err } - dataWithSecrets, secretsUpdatedAt, err := svc.ds.ExpandEmbeddedSecretsAndUpdatedAt(ctx, string(data)) - if err != nil { + if err := svc.ds.ValidateEmbeddedSecrets(ctx, []string{string(data)}); err != nil { return nil, fleet.NewInvalidArgumentError("profile", err.Error()) } diff --git a/website/assets/images/articles/bioutil-command.png b/website/assets/images/articles/bioutil-command.png new file mode 100644 index 000000000000..0c4187bc7a20 Binary files /dev/null and b/website/assets/images/articles/bioutil-command.png differ diff --git a/website/assets/images/articles/bioutil@2x.png b/website/assets/images/articles/bioutil@2x.png new file mode 100644 index 000000000000..5a987d91ae36 Binary files /dev/null and b/website/assets/images/articles/bioutil@2x.png differ