diff --git a/.github/workflows/generate-desktop-targets.yml b/.github/workflows/generate-desktop-targets.yml index 5633467a88..8f28f858fe 100644 --- a/.github/workflows/generate-desktop-targets.yml +++ b/.github/workflows/generate-desktop-targets.yml @@ -19,20 +19,17 @@ defaults: shell: bash env: - FLEET_DESKTOP_VERSION: 1.35.0 + FLEET_DESKTOP_VERSION: 1.36.0 permissions: contents: read jobs: desktop-macos: - # Set macOS version to '12' (current equivalent to macos-latest) for + # Set macOS version to '13' (previously was macos-12, and it was deprecated) for # building the binary. This ensures compatibility with macOS version 13 and # later, avoiding runtime errors on systems using macOS 13 or newer. - # - # Note: Update this version to '13' once GitHub marks macOS 13 as stable - # or if we revise our minimum supported macOS version. - runs-on: macos-12 + runs-on: macos-13 steps: - name: Harden Runner diff --git a/.github/workflows/tuf-update-timestamp.yaml b/.github/workflows/tuf-update-timestamp.yaml new file mode 100644 index 0000000000..692872aadd --- /dev/null +++ b/.github/workflows/tuf-update-timestamp.yaml @@ -0,0 +1,59 @@ +# This workflow update the timestamp of the TUF repository at https://tuf.fleetctl.com +name: Update TUF timestamp + +on: + schedule: + - cron: "0 14 * * TUE" # Every Tuesday at 2 PM UTC + workflow_dispatch: # Manual + +defaults: + run: + # fail-fast using bash -eo pipefail. See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference + shell: bash + +env: + AWS_REGION: us-east-1 + AWS_IAM_ROLE: arn:aws:iam::142412512209:role/github-actions-role + +permissions: + id-token: write # This is required for aws-actions/configure-aws-credentials + +jobs: + tuf-update-timestamp: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + with: + egress-policy: audit + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@67fbcbb121271f7775d2e7715933280b06314838 # v1.7.0 + with: + role-to-assume: ${{ env.AWS_IAM_ROLE }} + aws-region: ${{ env.AWS_REGION }} + + - name: Install fleetctl + run: npm install -g fleetctl + + - name: Pull metadata files + run: | + mkdir -p keys repository staged + aws s3 cp s3://fleet-tuf-repo/timestamp.json ./repository/timestamp.json + aws s3 cp s3://fleet-tuf-repo/snapshot.json ./repository/snapshot.json + aws s3 cp s3://fleet-tuf-repo/targets.json ./repository/targets.json + aws s3 cp s3://fleet-tuf-repo/root.json ./repository/root.json + cat ./repository/timestamp.json + + - name: Update timestamp + env: + BASE64_ENCRYPTED_TIMESTAMP_KEY_CONTENTS: ${{ secrets.BASE64_ENCRYPTED_TIMESTAMP_KEY }} + FLEET_TIMESTAMP_PASSPHRASE: ${{ secrets.TUF_TIMESTAMP_PASSPHRASE }} + run: | + echo "$BASE64_ENCRYPTED_TIMESTAMP_KEY_CONTENTS" | base64 -d > ./keys/timestamp.json + fleetctl updates timestamp --path . + + - name: Push timestamp.json + run: | + cat ./repository/timestamp.json + aws s3 cp ./repository/timestamp.json s3://fleet-tuf-repo/timestamp.json diff --git a/CHANGELOG.md b/CHANGELOG.md index ec554cea35..bf3831e84a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,52 @@ +## Fleet 4.60.0 (Nov 27, 2024) + +### Endpoint operations +- Added support for labels_include_any to gitops. +- Added major improvements to keyboard accessibility throughout app (e.g. checkboxes, dropdowns, table navigation). +- Added activity item for `fleetd` enrollment with host serial and display name. +- Added capability for Fleet to serve YARA rules to agents over HTTPS authenticated via node key (requires osquery 5.14+). +- Added a query to allow users to turn on/off automations while being transparent of the current log destination. +- Updated UI to allow users to view scripts (from both the scripts page and host details page) without downloading them. +- Updated activity feed to generate an activity when activity automations are enabled, edited, or disabled. +- Cancelled pending script executions when a script is edited or deleted. + +### Device management (MDM) +- Added better handling of timeout and insufficient permissions errors in NDES SCEP proxy. +- Added info banner for cloud customers to help with their windows autoenrollment setup. +- Added DB support for "include any" label profile deployment. +- Added support for "include any" label/profile relationships to the profile reconciliation machinery. +- Added `team_identifier` signature information to Apple macOS applications to the `/api/latest/fleet/hosts/:id/software` API endpoint. +- Added indicator of how fresh a software title's host and version counts are on the title's details page. +- Added UI for allowing users to install custom profiles on hosts that include any of the defined labels. +- Added UI features supporting disk encryption for Ubuntu and Fedora Linux. +- Added support for deb packages compressed with zstd. + +### Vulnerability management +- Allowed skipping computationally heavy population of vulnerability details when populating host software on hosts list endpoint (`GET /api/latest/fleet/hosts`) when using Fleet Premium (`populate_software=without_vulnerability_descriptions`). + +### Bug fixes and improvements +- Improved memory usage of the Fleet server when uploading a large software installer file. Note that the installer will now use (temporary) disk space and sufficient storage space is required. +- Improved performance of adding and removing profiles to large teams by an order of magnitude. +- Disabled accessibility via keyboard for forms that are disabled via a slider. +- Updated software batch endpoint status code from 200 (OK) to 202 (Accepted). +- Updated a package used for testing (msw) to improve security. +- Updated to reboot linux machine on unlock to work around GDM bug on Ubuntu 24.04. +- Updated GitOps to return an error if the deprecated `apple_bm_default_team` key is used and there are more than 1 ABM tokens in Fleet. +- Dismissed error flash on the my device page when navigating to another URL. +- Modified the Fleet setup experience feature to not run if there is no software or script configured for the setup experience. +- Set a more accurate minimum height for the Add hosts > ChromeOS > Policy for extension field, avoiding a scrollbar. +- Added UI prompt for user to reenter the password if SCEP/NDES url or username has changed. +- Updated ABM public key to download as as PEM format instead of CRT. +- Fixed issue with uploading macOS software packages that do not have a top level `Distribution.xml`, but do have a top level `PackageInfo.xml`. For example, Okta Verify.app. +- Fixed some cases where Fleet Maintained Apps generated incorrect uninstall scripts. +- Fixed a bug where a device that was removed from ABM and then added back wouldn't properly re-enroll in Fleet MDM. +- Fixed name/version parsing issue with PE (EXE) installer self-extracting archives such as Opera. +- Fixed a bug where the create and update label endpoints could return outdated information in a deployment using a mysql replica. +- Fixed the MDM configuration profiles deployment when based on excluded labels. +- Fixed gitops path resolution for installer queries and scripts to always be relative to where the query file or script is referenced. This change breaks existing YAML files that had to account for previous inconsistent behavior (e.g. installers in a subdirectory referencing scripts elsewhere). +- Fixed issue where minimum OS version enforcement was not being applied during Apple ADE if MDM IdP integration was enabled. +- Fixed a bug where users would be allowed to attempt an install of an App Store app on a host that was not MDM enrolled. + ## Fleet 4.59.1 (Nov 18, 2024) ### Bug fixes diff --git a/CODEOWNERS b/CODEOWNERS index 68cb118802..bce1e230f6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -65,11 +65,10 @@ go.mod @fleetdm/go # # (see website/config/custom.js for DRIs of other paths not listed here) ############################################################################################## -/docs @rachaelshaw -/docs/REST\ API/rest-api.md @rachaelshaw # « REST API reference documentation -/docs/Contributing/API-for-contributors.md @rachaelshaw # « Advanced / contributors-only API reference documentation +/docs @rachaelshaw @noahtalerman +/docs/REST\ API/rest-api.md @rachaelshaw @noahtalerman # « REST API reference documentation +/docs/Contributing/API-for-contributors.md @rachaelshaw @noahtalerman # « Advanced / contributors-only API reference documentation /schema @eashaw # « Data tables (osquery/fleetd schema) documentation -/docs/Deploy/_kubernetes/ @dherder # « Kubernetes best practice /render.yaml @edwardsb ############################################################################################## @@ -89,15 +88,15 @@ go.mod @fleetdm/go /handbook/README.md @mikermcneil /handbook/company/open-positions.yml @sampfluger88 #/handbook/company/product-groups.md 🤡 Covered in custom.js -/handbook/finance/README.md @sampfluger88 -/handbook/finance/finance.rituals.yml @sampfluger88 +/handbook/finance/README.md @sampfluger88 +/handbook/finance/finance.rituals.yml @sampfluger88 /handbook/digital-experience/security.md @sampfluger88 -/handbook/digital-experience @sampfluger88 -/handbook/customer-success @sampfluger88 +/handbook/digital-experience @sampfluger88 +/handbook/customer-success @sampfluger88 /handbook/demand @sampfluger88 #/handbook/engineering 🤡 Covered in custom.js /handbook/sales @sampfluger88 -#/handbook/product-design 🤡 Covered in custom.js +#/handbook/product-design 🤡 Covered in custom.js ############################################################################################## # 🌐 GitHub issue templates diff --git a/articles/enforce-disk-encryption.md b/articles/enforce-disk-encryption.md index 8dd2b6419f..5500d45286 100644 --- a/articles/enforce-disk-encryption.md +++ b/articles/enforce-disk-encryption.md @@ -2,21 +2,19 @@ _Available in Fleet Premium_ -In Fleet, you can enforce disk encryption for your macOS and Windows hosts. +In Fleet, you can enforce disk encryption for your macOS and Windows hosts, and verify disk encryption for Ubuntu Linux and Fedora Linux hosts. -> Apple calls this [FileVault](https://support.apple.com/en-us/HT204837) and Microsoft calls this [BitLocker](https://learn.microsoft.com/en-us/windows/security/operating-system-security/data-protection/bitlocker/). +> Apple calls this [FileVault](https://support.apple.com/en-us/HT204837), Microsoft calls this [BitLocker](https://learn.microsoft.com/en-us/windows/security/operating-system-security/data-protection/bitlocker/), and Linux typically uses [LUKS](https://en.wikipedia.org/wiki/Linux_Unified_Key_Setup) (Linux Unified Key Setup). -When disk encryption is enforced, hosts’ disk encryption keys will be stored in Fleet. +When disk encryption is enforced, hosts' disk encryption keys will be stored in Fleet. -For macOS hosts that automatically enroll, disk encryption is enforced during Setup Assistant. - -For Windows, disk encryption is enforced on the C: volume (default system/OS drive). +For macOS hosts that automatically enroll, disk encryption is enforced during Setup Assistant. For Windows, disk encryption is enforced on the C: volume (default system/OS drive). On Linux, encryption requires user interaction to encrypt the device with LUKS. ## Enforce disk encryption You can enforce disk encryption using the Fleet UI, Fleet API, or [Fleet's GitOps workflow](https://github.com/fleetdm/fleet-gitops). -Fleet UI: +#### Fleet UI: 1. In Fleet, head to the **Controls > OS settings > Disk encryption** page. @@ -24,7 +22,9 @@ Fleet UI: 3. Check the box next to **Turn on** and select **Save**. -Fleet API: API documentation is [here](https://fleetdm.com/docs/rest-api/rest-api#update-disk-encryption-enforcement). +#### Fleet API: + +API documentation is [here](https://fleetdm.com/docs/rest-api/rest-api#update-disk-encryption-enforcement). ### Disk encryption status @@ -42,10 +42,28 @@ In the Fleet UI, head to the **Controls > OS settings > Disk encryption** tab. Y * Removing enforcement (pending): the host will receive the MDM command to remove the disk encryption profile when the host comes online. -* Failed: hosts that are failed to enforce disk encryption. +* Failed: hosts that failed to enforce disk encryption. You can click each status to view the list of hosts for that status. +## Enforce disk encryption on Linux + +To enforce disk encryption on Ubuntu Linux and Fedora Linux devices, Fleet supports Linux Unified Key Setup (LUKS) for encrypting volumes. Support for Ubuntu 20.04 is coming soon. + +1. Share [this step-by-step guide](https://fleetdm.com/learn-more-about/encrypt-linux-device) with end users setting up a work computer running Ubuntu Linux or Fedora Linux. + +> Note that full disk encryption can only enabled during operating system setup. If the operating system has already been installed, the end user will be required to re-install the OS to enable disk encryption. + +2. Once the user encrypts the disk, Fleet will initiate a key escrow process through Fleet Desktop: + * Fleet Desktop prompts the user to enter their current encryption passphrase. + * A new encryption passphrase is generated and added as a LUKS keyslot for the encrypted volume. + * The new passphrase is securely stored in Fleet. + +3. Fleet verifies that the encryption is complete, and the key has been escrowed. Once successful, the host's status will be updated to "Verified" in the disk encryption status table. + +> Note: LUKS allows multiple passphrases for decrypting the volume. The original passphrase remains active along with the escrowed passphrase created by Fleet. + + ## View disk encryption key How to view the disk encryption key: @@ -54,6 +72,8 @@ How to view the disk encryption key: 2. On the **Host details** page, select **Actions > Show disk encryption key**. +> This action is logged in the activity log for security auditing purposes. + ## Migrate macOS hosts When migrating macOS hosts from another MDM solution, in order to complete the process of encrypting the hard drive and escrowing the key in Fleet, your end users must log out or restart their device. @@ -65,4 +85,4 @@ Share [these guided instructions](https://fleetdm.com/guides/mdm-migration#how-t - + diff --git a/articles/fleet-4.40.0.md b/articles/fleet-4.40.0.md index a532533165..6200a56635 100644 --- a/articles/fleet-4.40.0.md +++ b/articles/fleet-4.40.0.md @@ -1,6 +1,8 @@ # Fleet 4.40.0 | More Data, Rapid Security Response, CIS Benchmark updates. -![Fleet 4.40.0](../website/assets/images/articles/fleet-4.40.0-1600x900@2x.png) +
+ +
Fleet 4.40.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.40.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. diff --git a/articles/fleet-4.47.0.md b/articles/fleet-4.47.0.md index 01a2fb3e62..69f42984e3 100644 --- a/articles/fleet-4.47.0.md +++ b/articles/fleet-4.47.0.md @@ -1,6 +1,8 @@ # Fleet 4.47.0 | Cross-platform remote wipe, vulnerabilities page, and scripting improvements. -![Fleet 4.47.0](../website/assets/images/articles/fleet-4.47.0-1600x900@2x.png) +
+ +
Fleet 4.47.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.47.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. diff --git a/articles/fleet-4.48.0.md b/articles/fleet-4.48.0.md index 4bfdcb5527..16867fb73f 100644 --- a/articles/fleet-4.48.0.md +++ b/articles/fleet-4.48.0.md @@ -1,6 +1,8 @@ # Fleet 4.48.0 | IdP local account creation, VS Code extensions. -![Fleet 4.48.0](../website/assets/images/articles/fleet-4.48.0-1600x900@2x.png) +
+ +
Fleet 4.48.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.48.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. diff --git a/articles/fleet-4.49.0.md b/articles/fleet-4.49.0.md index bda46c13e2..3694b32f6d 100644 --- a/articles/fleet-4.49.0.md +++ b/articles/fleet-4.49.0.md @@ -1,6 +1,8 @@ # Fleet 4.49.0 | VulnCheck's NVD++, device health API, `fleetd` data parsing. -![Fleet 4.49.0](../website/assets/images/articles/fleet-4.49.0-1600x900@2x.png) +
+ +
Fleet 4.49.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.49.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. diff --git a/articles/fleet-4.50.0.md b/articles/fleet-4.50.0.md index 661b08cc38..f331194749 100644 --- a/articles/fleet-4.50.0.md +++ b/articles/fleet-4.50.0.md @@ -1,6 +1,8 @@ # Fleet 4.50.0 | Security agent deployment, AI descriptions, and Mac Admins SOFA support. -![Fleet 4.50.0](../website/assets/images/articles/fleet-4.50.0-1600x900@2x.png) +
+ +
Fleet 4.50.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.50.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. diff --git a/articles/fleet-4.51.0.md b/articles/fleet-4.51.0.md index 7f43b9f66b..4d7e046ae4 100644 --- a/articles/fleet-4.51.0.md +++ b/articles/fleet-4.51.0.md @@ -1,6 +1,8 @@ # Fleet 4.51.0 | Global activity webhook, macOS TCC table, and software self-service. -![Fleet 4.51.0](../website/assets/images/articles/fleet-4.51.0-1600x900@2x.png) +
+ +
Fleet 4.51.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.51.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. diff --git a/articles/fleet-4.53.0.md b/articles/fleet-4.53.0.md index 3a238a69aa..88c2cf6158 100644 --- a/articles/fleet-4.53.0.md +++ b/articles/fleet-4.53.0.md @@ -1,6 +1,8 @@ # Fleet 4.53.0 | Better vuln matching, multi-issue hosts, & `fleetd` logs as tables. -![Fleet 4.53.0](../website/assets/images/articles/fleet-4.53.0-1600x900@2x.png) +
+ +
Fleet 4.53.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.53.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. diff --git a/articles/fleet-4.54.0.md b/articles/fleet-4.54.0.md index 798090428f..4f2f2b003a 100644 --- a/articles/fleet-4.54.0.md +++ b/articles/fleet-4.54.0.md @@ -1,6 +1,8 @@ # Fleet 4.54.0 | Target hosts via label exclusion, arm64 support, script execution time. -![Fleet 4.54.0](../website/assets/images/articles/fleet-4.54.0-1600x900@2x.png) +
+ +
Fleet 4.54.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.54.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. diff --git a/articles/fleet-4.55.0.md b/articles/fleet-4.55.0.md index 309e0e70df..8f76ef5772 100644 --- a/articles/fleet-4.55.0.md +++ b/articles/fleet-4.55.0.md @@ -1,6 +1,8 @@ # Fleet 4.55.0 | MySQL 8, arm64 support, FileVault improvements, VPP support. -![Fleet 4.55.0](../website/assets/images/articles/fleet-4.55.0-1600x900@2x.png) +
+ +
Fleet 4.55.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.55.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. diff --git a/articles/fleet-4.56.0.md b/articles/fleet-4.56.0.md index 14089ad9d6..152347f621 100644 --- a/articles/fleet-4.56.0.md +++ b/articles/fleet-4.56.0.md @@ -1,6 +1,8 @@ # Fleet 4.56.0 | Enhanced MDM migration, Exact CVE Search, and Self-Service VPP Apps. -![Fleet 4.56.0](../website/assets/images/articles/fleet-4.56.0-1600x900@2x.png) +
+ +
Fleet 4.56.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.56.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. diff --git a/articles/fleet-4.57.0.md b/articles/fleet-4.57.0.md index 4c9f959f9a..8fa3366ae7 100644 --- a/articles/fleet-4.57.0.md +++ b/articles/fleet-4.57.0.md @@ -1,6 +1,8 @@ # Fleet 4.57.0 | Software improvements, policy automation, GitLab support. -![Fleet 4.57.0](../website/assets/images/articles/fleet-4.57.0-1600x900@2x.png) +
+ +
Fleet 4.57.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.57.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. diff --git a/articles/fleet-4.58.0.md b/articles/fleet-4.58.0.md index cfd2b7f9f6..05efdcc691 100644 --- a/articles/fleet-4.58.0.md +++ b/articles/fleet-4.58.0.md @@ -1,6 +1,8 @@ # Fleet 4.58.0 | Run script on policy failure, Fleet-maintained apps, Sequoia firewall status. -![Fleet 4.58.0](../website/assets/images/articles/fleet-4.58.0-1600x900@2x.png) +
+ +
Fleet 4.58.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.58.0) or continue reading to get the highlights. For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. diff --git a/articles/fleet-4.59.0.md b/articles/fleet-4.59.0.md index 749c981136..49549ed374 100644 --- a/articles/fleet-4.59.0.md +++ b/articles/fleet-4.59.0.md @@ -96,7 +96,7 @@ SET i.software_title_name = COALESCE(a.details->>"$.software_title", i.software_ ## Ready to upgrade? -Visit our [Upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs for instructions on updating to Fleet 4.58.0. +Visit our [Upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs for instructions on updating to Fleet 4.59.0. diff --git a/articles/fleet-4.60.0.md b/articles/fleet-4.60.0.md new file mode 100644 index 0000000000..a54b3dca62 --- /dev/null +++ b/articles/fleet-4.60.0.md @@ -0,0 +1,83 @@ +# Fleet 4.60.0 | Escrow Linux disk encryption keys, custom targets for OS settings, scripts preview + +![Fleet 4.60.0](../website/assets/images/articles/fleet-4.60.0-1600x900@2x.png) + +Fleet 4.60.0 is live. Check out the full [changelog](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.60.0) or continue reading to get the highlights. +For upgrade instructions, see our [upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs. + +## Highlights +- Escrow Linux disk encryption keys +- Custom targets for OS settings +- Preview scripts before run + +### Escrow Linux disk encryption keys + +Fleet now supports escrowing the disk encryption keys for Linux (Ubuntu and Fedora) workstations. This means teams can access encrypted data without needing the local password when an employee leaves, simplifying handoffs and ensuring critical data remains accessible while protected. Learn more in the guide [here](https://fleetdm.com/guides/enforce-disk-encryption). + +### Custom targets for OS settings + +With Fleet, you can now use a new "include any" label option to target OS settings (configuration profiles) to specific hosts within a team. This added flexibility allows for finer control over which OS settings apply to which hosts, making it easier to tweak configurations without disrupting broader baselines (Fleet [teams](https://fleetdm.com/guides/teams)). + +### Preview scripts before run + +Fleet now provides the ability to preview scripts directly on the **Host details** or **Scripts** page. This quick-view feature reduces the risk of errors by letting you verify the script is correct before running it, saving time and ensuring smoother operations. + +## Changes + +### Endpoint operations +- Added support for `labels_include_any` to gitops. +- Added major improvements to keyboard accessibility throughout app (e.g. checkboxes, dropdowns, table navigation). +- Added activity item for `fleetd` enrollment with host serial and display name. +- Added capability for Fleet to serve YARA rules to agents over HTTPS authenticated via node key (requires osquery 5.14+). +- Added a query to allow users to turn on/off automations while being transparent of the current log destination. +- Updated UI to allow users to view scripts (from both the scripts page and host details page) without downloading them. +- Updated activity feed to generate an activity when activity automations are enabled, edited, or disabled. +- Cancelled pending script executions when a script is edited or deleted. + +### Device management (MDM) +- Added better handling of timeout and insufficient permissions errors in NDES SCEP proxy. +- Added info banner for cloud customers to help with their windows autoenrollment setup. +- Added DB support for "include any" label profile deployment. +- Added support for "include any" label/profile relationships to the profile reconciliation machinery. +- Added `team_identifier` signature information to Apple macOS applications to the `/api/latest/fleet/hosts/:id/software` API endpoint. +- Added indicator of how fresh a software title's host and version counts are on the title's details page. +- Added UI for allowing users to install custom profiles on hosts that include any of the defined labels. +- Added UI features supporting disk encryption for Ubuntu and Fedora Linux. +- Added support for deb packages compressed with zstd. + +### Vulnerability management +- Allowed skipping computationally heavy population of vulnerability details when populating host software on hosts list endpoint (`GET /api/latest/fleet/hosts`) when using Fleet Premium (`populate_software=without_vulnerability_descriptions`). + +### Bug fixes and improvements +- Improved memory usage of the Fleet server when uploading a large software installer file. Note that the installer will now use (temporary) disk space and sufficient storage space is required. +- Improved performance of adding and removing profiles to large teams by an order of magnitude. +- Disabled accessibility via keyboard for forms that are disabled via a slider. +- Updated software batch endpoint status code from 200 (OK) to 202 (Accepted). +- Updated a package used for testing (msw) to improve security. +- Updated to reboot linux machine on unlock to work around GDM bug on Ubuntu 24.04. +- Updated GitOps to return an error if the deprecated `apple_bm_default_team` key is used and there are more than 1 ABM tokens in Fleet. +- Dismissed error flash on the my device page when navigating to another URL. +- Modified the Fleet setup experience feature to not run if there is no software or script configured for the setup experience. +- Set a more accurate minimum height for the Add hosts > ChromeOS > Policy for extension field, avoiding a scrollbar. +- Added UI prompt for user to reenter the password if SCEP/NDES url or username has changed. +- Updated ABM public key to download as as PEM format instead of CRT. +- Fixed issue with uploading macOS software packages that do not have a top level `Distribution.xml`, but do have a top level `PackageInfo.xml`. For example, Okta Verify.app. +- Fixed some cases where Fleet Maintained Apps generated incorrect uninstall scripts. +- Fixed a bug where a device that was removed from ABM and then added back wouldn't properly re-enroll in Fleet MDM. +- Fixed name/version parsing issue with PE (EXE) installer self-extracting archives such as Opera. +- Fixed a bug where the create and update label endpoints could return outdated information in a deployment using a mysql replica. +- Fixed the MDM configuration profiles deployment when based on excluded labels. +- Fixed gitops path resolution for installer queries and scripts to always be relative to where the query file or script is referenced. This change breaks existing YAML files that had to account for previous inconsistent behavior (e.g. installers in a subdirectory referencing scripts elsewhere). +- Fixed issue where minimum OS version enforcement was not being applied during Apple ADE if MDM IdP integration was enabled. +- Fixed a bug where users would be allowed to attempt an install of an App Store app on a host that was not MDM enrolled. + +## Ready to upgrade? + +Visit our [Upgrade guide](https://fleetdm.com/docs/deploying/upgrading-fleet) in the Fleet docs for instructions on updating to Fleet 4.60.0. + + + + + + + \ No newline at end of file diff --git a/articles/linux-disk-encryption-end-user.md b/articles/linux-disk-encryption-end-user.md new file mode 100644 index 0000000000..0fd8ffb913 --- /dev/null +++ b/articles/linux-disk-encryption-end-user.md @@ -0,0 +1,56 @@ +# Encrypt your Fleet-managed Linux device + +> This guide is intended for new device setup. If the operating system has already been installed without enabling disk encryption, you will need to re-install in order to turn on full disk encryption. + + +LUKS (Linux Unified Key Setup) is a standard tool for encrypting Linux disks. It uses a "volume key" to encrypt your data, and this key is protected by passphrases. LUKS supports multiple passphrases, allowing you to securely share access or recover encrypted data. Fleet uses LUKS to ensure that only authorized users can access the data on your work computer. + +Fleet securely stores a passphrase to ensure that the data on your work computer is always recoverable. To get your computer set up for key escrow, you will first need to enable disk encryption on your end, then provide your encryption passphrase to Fleet. + +Follow the steps below to get set up. + + +## 1. Enable encryption during installation + + #### Ubuntu Linux + + - When installing Ubuntu, choose the option to "Use LVM with encryption." + - Set a strong passphrase when prompted. This passphrase will be used to encrypt your disk and is separate from your login password. + + ![Ubuntu setup "How do you want to install Ubuntu?" screen](../website/assets/images/articles/ubuntu-1-1200x675@2x.png) + + ![Ubuntu setup: Advanced features > Use LVM and encryption](../website/assets/images/articles/ubuntu-2-1200x675@2x.png) + + #### Fedora Linux + + - During Fedora installation, under **Installation destination** > **Encryption** select the "Encrypt my data" checkbox. + - Enter a secure passphrase when prompted. + + ![Fedora setup "Installation summary" screen](../website/assets/images/articles/fedora-1-1200x675@2x.png) + ![Fedora setup: Installation destination > Encryption > Encrypt my data](../website/assets/images/articles/fedora-2-1200x675@2x.png) + +## 2. Verify encryption + + - Once installation is complete, verify that your disk is encrypted by running: + ```bash + lsblk -o NAME,MOUNTPOINT,TYPE,SIZE,FSUSED,FSTYPE,ENCRYPTED + ``` + - **Ubuntu Linux**: Look for the root (`/`) partition, and confirm it is marked as encrypted. + - **Fedora Linux**: Ensure the `/` (root) and `/home` partitions are encrypted. + +## 3. Escrow your key with Fleet + + - Open Fleet Desktop. If your device is encrypted, you'll see a banner prompting you to escrow the key. + - Click **Create key**. Enter your existing encryption passphrase when prompted. + - Fleet will generate and securely store a new passphrase for recovery. This may take several minutes. A popup will appear when Fleet is done. + +Now, your encryption status will update to "verified" in Fleet Desktop, meaning that your recovery key has been successfully stored. + + + + + + + + + \ No newline at end of file diff --git a/changes/14899-yara-rules b/changes/14899-yara-rules deleted file mode 100644 index 2c92188cfc..0000000000 --- a/changes/14899-yara-rules +++ /dev/null @@ -1 +0,0 @@ -* Added capability for Fleet to serve yara rules to agents over HTTPS authenticated via node key (requires osquery 5.14+). \ No newline at end of file diff --git a/changes/20595-improve-memory-usage-software-installers b/changes/20595-improve-memory-usage-software-installers deleted file mode 100644 index 7e15f3b935..0000000000 --- a/changes/20595-improve-memory-usage-software-installers +++ /dev/null @@ -1 +0,0 @@ -* Improved memory usage of the Fleet server when uploading a large software installer file. Note that the installer will now use (temporary) disk space and sufficient storage space is required. diff --git a/changes/21338-scope-profile-pending-rebuild b/changes/21338-scope-profile-pending-rebuild deleted file mode 100644 index 59e4883955..0000000000 --- a/changes/21338-scope-profile-pending-rebuild +++ /dev/null @@ -1 +0,0 @@ -- Speed up adding and removing profiles to large teams by an order of magnitude diff --git a/changes/21633-windows-auto-enrollment-info-banner b/changes/21633-windows-auto-enrollment-info-banner deleted file mode 100644 index 86cdfafdaf..0000000000 --- a/changes/21633-windows-auto-enrollment-info-banner +++ /dev/null @@ -1 +0,0 @@ -- add info banner for cloud customers to help with their windows autoenrollment setup diff --git a/changes/21709-activities-automation-activity b/changes/21709-activities-automation-activity deleted file mode 100644 index bc47a6e273..0000000000 --- a/changes/21709-activities-automation-activity +++ /dev/null @@ -1 +0,0 @@ -* Generate an activity when activity automations are enabled, edited, or disabled. diff --git a/changes/21795-resend-config-profile-api b/changes/21795-resend-config-profile-api new file mode 100644 index 0000000000..0612554c37 --- /dev/null +++ b/changes/21795-resend-config-profile-api @@ -0,0 +1 @@ +* Update resend config profile API from hosts/[hostid}/configuration_profiles/resend/{uuid} to hosts/{hostid}/configuration_profiles/{uuid}/resend \ No newline at end of file diff --git a/changes/21888-dequeue-pending-scripts b/changes/21888-dequeue-pending-scripts deleted file mode 100644 index 3852ee09c3..0000000000 --- a/changes/21888-dequeue-pending-scripts +++ /dev/null @@ -1 +0,0 @@ -* Cancelled pending script executions when a script is edited or deleted. diff --git a/changes/21908-replace-mozilla-pkcs7 b/changes/21908-replace-mozilla-pkcs7 new file mode 100644 index 0000000000..65fc8d79bf --- /dev/null +++ b/changes/21908-replace-mozilla-pkcs7 @@ -0,0 +1 @@ +* Replaced the internal use of the deprecated `go.mozilla.org/pkcs7` package with the maintained fork `github.com/smallstep/pkcs7`. diff --git a/changes/22162-exclude-labels-fix-default-behavior b/changes/22162-exclude-labels-fix-default-behavior deleted file mode 100644 index 41524c8c03..0000000000 --- a/changes/22162-exclude-labels-fix-default-behavior +++ /dev/null @@ -1 +0,0 @@ -* Fixed the MDM configuration profiles deployment when based on excluded labels - prior to this fix, hosts were considered "not a member" of the label by default, even if they had not yet returned results for the excluded labels. The fix checks the label's creation time vs the host's last reported label results timestamp to prevent deploying a configuration profile if it does not yet know if the host is a member or not of those labels. diff --git a/changes/22187-gitops-software-relative-paths b/changes/22187-gitops-software-relative-paths deleted file mode 100644 index 8f1ce8f480..0000000000 --- a/changes/22187-gitops-software-relative-paths +++ /dev/null @@ -1 +0,0 @@ -* GitOps: Fixed path resolution for installer queries and scripts to always be relative to where the query file or script is referenced. This change breaks existing YAML files that had to account for previous inconsistent behavior (e.g. installers in a subdirectory referencing scripts elsewhere). \ No newline at end of file diff --git a/changes/22224-query-log-destinations b/changes/22224-query-log-destinations deleted file mode 100644 index b6172a331b..0000000000 --- a/changes/22224-query-log-destinations +++ /dev/null @@ -1 +0,0 @@ -- Creating a query allow users to turn on/off automations while being transparent of the current log destination diff --git a/changes/22269-software-title-updated-at b/changes/22269-software-title-updated-at deleted file mode 100644 index dfc3f12769..0000000000 --- a/changes/22269-software-title-updated-at +++ /dev/null @@ -1 +0,0 @@ -* Added indicator of how fresh a software title's host and version counts are on the title's details page diff --git a/changes/22359-gitops-mult-abm b/changes/22359-gitops-mult-abm deleted file mode 100644 index b7a7801edb..0000000000 --- a/changes/22359-gitops-mult-abm +++ /dev/null @@ -1,2 +0,0 @@ -- Updates GitOps to return an error if the deprecated `apple_bm_default_team` key is used and there - are more than 1 ABM tokens in Fleet. \ No newline at end of file diff --git a/changes/22361-os-update-ade-sso b/changes/22361-os-update-ade-sso deleted file mode 100644 index 40221866fb..0000000000 --- a/changes/22361-os-update-ade-sso +++ /dev/null @@ -1,2 +0,0 @@ -- Fixed issue where minimum OS version enforcement was not being applied during Apple ADE if MDM - IdP integration was enabled. diff --git a/changes/22437-linux-lock-black-screen b/changes/22437-linux-lock-black-screen deleted file mode 100644 index edfd4dc8d4..0000000000 --- a/changes/22437-linux-lock-black-screen +++ /dev/null @@ -1 +0,0 @@ -- Reboot linux machine on unlock to work around GDM bug on Ubuntu 24.04 diff --git a/changes/22446-scripts-modal b/changes/22446-scripts-modal deleted file mode 100644 index 1e06aea931..0000000000 --- a/changes/22446-scripts-modal +++ /dev/null @@ -1 +0,0 @@ -- Users can view scripts in the UI (from both the scripts page and host details page) without downloading them diff --git a/changes/22527-policy-automation-ui-improvements b/changes/22527-policy-automation-ui-improvements new file mode 100644 index 0000000000..6d56f7efa6 --- /dev/null +++ b/changes/22527-policy-automation-ui-improvements @@ -0,0 +1 @@ +- Update help text for policy automation Install software and Run script modals diff --git a/changes/22575-ui-for-include-any-labels b/changes/22575-ui-for-include-any-labels deleted file mode 100644 index 5f66f8396b..0000000000 --- a/changes/22575-ui-for-include-any-labels +++ /dev/null @@ -1,2 +0,0 @@ -- add UI for allowing users to install custom profiles on hosts that include any of the defined -labels diff --git a/changes/22576-labels-include-any-gitops b/changes/22576-labels-include-any-gitops deleted file mode 100644 index 228171c7d1..0000000000 --- a/changes/22576-labels-include-any-gitops +++ /dev/null @@ -1 +0,0 @@ -- Add support for labels_include_any to gitops diff --git a/changes/22578-db-schema b/changes/22578-db-schema deleted file mode 100644 index 281c14a6b9..0000000000 --- a/changes/22578-db-schema +++ /dev/null @@ -1 +0,0 @@ -- Adds DB support for "include any" label profile deployment \ No newline at end of file diff --git a/changes/22581-cron-updates b/changes/22581-cron-updates deleted file mode 100644 index f228460a04..0000000000 --- a/changes/22581-cron-updates +++ /dev/null @@ -1 +0,0 @@ -- Adds support for "include any" label/profile relationships to the profile reconciliation machinery. \ No newline at end of file diff --git a/changes/22606-keyboard-accessiblity b/changes/22606-keyboard-accessiblity deleted file mode 100644 index 6f863e248a..0000000000 --- a/changes/22606-keyboard-accessiblity +++ /dev/null @@ -1 +0,0 @@ -- Fleet UI: Major improvements to keyboard accessibility throughout app (e.g. checkboxes, dropdowns, table navigation) \ No newline at end of file diff --git a/changes/22702-linux-encryption-frontend b/changes/22702-linux-encryption-frontend deleted file mode 100644 index a35d242375..0000000000 --- a/changes/22702-linux-encryption-frontend +++ /dev/null @@ -1 +0,0 @@ -- Added UI features supporting disk encryption for Ubuntu and Fedora Linux. diff --git a/changes/22773-fma-uninstall-fix b/changes/22773-fma-uninstall-fix deleted file mode 100644 index 74c4390533..0000000000 --- a/changes/22773-fma-uninstall-fix +++ /dev/null @@ -1 +0,0 @@ -- Fix some cases where Fleet Maintained Apps generated incorrect uninstall scripts diff --git a/changes/22810-fleetd-enroll-activity b/changes/22810-fleetd-enroll-activity deleted file mode 100644 index b9b9380a05..0000000000 --- a/changes/22810-fleetd-enroll-activity +++ /dev/null @@ -1 +0,0 @@ -Added activity item for fleetd enrollment with host serial and display name. diff --git a/changes/22891-zstd-deb-packages b/changes/22891-zstd-deb-packages deleted file mode 100644 index f523dd6272..0000000000 --- a/changes/22891-zstd-deb-packages +++ /dev/null @@ -1 +0,0 @@ -- Add support for deb packages compressed with zstd diff --git a/changes/22896-ui-windows-automatic-migration b/changes/22896-ui-windows-automatic-migration new file mode 100644 index 0000000000..ae0234123b --- /dev/null +++ b/changes/22896-ui-windows-automatic-migration @@ -0,0 +1 @@ +- add UI changes for windows mdm page and allow for automatic migration for windows hosts. diff --git a/changes/22897-add-windows-migration-enabled-setting b/changes/22897-add-windows-migration-enabled-setting new file mode 100644 index 0000000000..15866a98c7 --- /dev/null +++ b/changes/22897-add-windows-migration-enabled-setting @@ -0,0 +1 @@ +* Added support for the new `windows_migration_enabled` setting (can be set via `fleetctl`, the `PATCH /api/latest/fleet/config` API endpoint and the UI). Requires a premium license. diff --git a/changes/22985-disable-forms-keyboard-access b/changes/22985-disable-forms-keyboard-access deleted file mode 100644 index 2e90b69dc5..0000000000 --- a/changes/22985-disable-forms-keyboard-access +++ /dev/null @@ -1 +0,0 @@ -- Fleet UI: Disable accessibility via keyboard for forms that are disabled via a slider diff --git a/changes/23016-add-chrome-host-text-area-height b/changes/23016-add-chrome-host-text-area-height deleted file mode 100644 index 7616f4bfa0..0000000000 --- a/changes/23016-add-chrome-host-text-area-height +++ /dev/null @@ -1,2 +0,0 @@ -* Set a more elegant minimum height for the Add hosts > ChromeOS > Policy for extension field, -avoiding a scrollbar. diff --git a/changes/23021-abm-cert-pem b/changes/23021-abm-cert-pem deleted file mode 100644 index c1890e07bb..0000000000 --- a/changes/23021-abm-cert-pem +++ /dev/null @@ -1 +0,0 @@ -- Download ABM public key as PEM format instead of CRT diff --git a/changes/23027-settings-empty-states b/changes/23027-settings-empty-states new file mode 100644 index 0000000000..ecc6736d05 --- /dev/null +++ b/changes/23027-settings-empty-states @@ -0,0 +1 @@ +* Improve side nav empty state UI under `/settings` \ No newline at end of file diff --git a/changes/23078-allow-skipping-vuln-details b/changes/23078-allow-skipping-vuln-details deleted file mode 100644 index 7a29933976..0000000000 --- a/changes/23078-allow-skipping-vuln-details +++ /dev/null @@ -1 +0,0 @@ -* Allowed skipping computationally heavy population of vulnerability details when populating host software on hosts list endpoint (`GET /api/latest/fleet/hosts`) when using Fleet Premium (`populate_software=without_vulnerability_descriptions`) \ No newline at end of file diff --git a/changes/23128-update-mock-service-worker-package-for-secutiy b/changes/23128-update-mock-service-worker-package-for-secutiy deleted file mode 100644 index aa9a3e47af..0000000000 --- a/changes/23128-update-mock-service-worker-package-for-secutiy +++ /dev/null @@ -1 +0,0 @@ -- update a package used for testing (msw) to improve security diff --git a/changes/23200-ade-enroll b/changes/23200-ade-enroll deleted file mode 100644 index 6a6c597bf4..0000000000 --- a/changes/23200-ade-enroll +++ /dev/null @@ -1,2 +0,0 @@ -- Fixes a bug where a device that was removed from ABM and then added back wouldn't properly - re-enroll in Fleet MDM \ No newline at end of file diff --git a/changes/23213-okta-verify b/changes/23213-okta-verify deleted file mode 100644 index 6fd38a9e47..0000000000 --- a/changes/23213-okta-verify +++ /dev/null @@ -1 +0,0 @@ -Fixed issue with uploading macOS software packages that do not have a top level Distribution.xml, but do have a top level PackageInfo.xml. For example, Okta Verify.app diff --git a/changes/23247-vpp-app-install b/changes/23247-vpp-app-install deleted file mode 100644 index 97a62eb9df..0000000000 --- a/changes/23247-vpp-app-install +++ /dev/null @@ -1,2 +0,0 @@ -- Fixes a bug where users would be allowed to attempt an install of an App Store app on a host that - was not MDM enrolled. \ No newline at end of file diff --git a/changes/23462-show-windows-mdm-wstep-options b/changes/23462-show-windows-mdm-wstep-options new file mode 100644 index 0000000000..8df6b93139 --- /dev/null +++ b/changes/23462-show-windows-mdm-wstep-options @@ -0,0 +1 @@ +- Display Windows MDM WSTEP flags in `fleet --help`. diff --git a/changes/23492-software-batch-status-code b/changes/23492-software-batch-status-code deleted file mode 100644 index 9ab51770d9..0000000000 --- a/changes/23492-software-batch-status-code +++ /dev/null @@ -1 +0,0 @@ -* Updated software batch endpoint status code from 200 (OK) to 202 (Accepted) \ No newline at end of file diff --git a/changes/23525-ndes-errors b/changes/23525-ndes-errors deleted file mode 100644 index 409723e809..0000000000 --- a/changes/23525-ndes-errors +++ /dev/null @@ -1 +0,0 @@ -Added better handling of timeout and insufficient permissions errors in NDES SCEP proxy. diff --git a/changes/23540-pe-sfx b/changes/23540-pe-sfx deleted file mode 100644 index 63c241a8be..0000000000 --- a/changes/23540-pe-sfx +++ /dev/null @@ -1 +0,0 @@ -Fixed name/version parsing issue with PE (EXE) installer self-extracting archives such as Opera. diff --git a/changes/23597-fix-create-update-label-returns-outdated-info b/changes/23597-fix-create-update-label-returns-outdated-info deleted file mode 100644 index 3a5e26e5aa..0000000000 --- a/changes/23597-fix-create-update-label-returns-outdated-info +++ /dev/null @@ -1 +0,0 @@ -* Fixed a bug where the create and update label endpoints could return outdated information in a deployment using a mysql replica. diff --git a/changes/23621-unlock-text b/changes/23621-unlock-text new file mode 100644 index 0000000000..6715062fdf --- /dev/null +++ b/changes/23621-unlock-text @@ -0,0 +1 @@ +- Fixes an issue with the copy for the activity generated by viewing a locked macOS host's PIN. \ No newline at end of file diff --git a/changes/23651-reenter-password b/changes/23651-reenter-password deleted file mode 100644 index b3fc7df44d..0000000000 --- a/changes/23651-reenter-password +++ /dev/null @@ -1 +0,0 @@ -- Fleet UI: Prompt user to reenter the password if SCEP/NDES url or username has changed diff --git a/changes/23669-dismiss-error-flash-on-url-change-dup b/changes/23669-dismiss-error-flash-on-url-change-dup deleted file mode 100644 index 125774f81f..0000000000 --- a/changes/23669-dismiss-error-flash-on-url-change-dup +++ /dev/null @@ -1 +0,0 @@ -* Dismiss error flash on the my device page when navigating to another URL. \ No newline at end of file diff --git a/changes/23686-update-zoom b/changes/23686-update-zoom new file mode 100644 index 0000000000..5a4dace779 --- /dev/null +++ b/changes/23686-update-zoom @@ -0,0 +1,2 @@ +- Replaces Zoom Fleet-maintained app with Zoom for IT, which does not open any windows during + installation. \ No newline at end of file diff --git a/changes/23733-apple-app-store-icons b/changes/23733-apple-app-store-icons new file mode 100644 index 0000000000..f9b062ff82 --- /dev/null +++ b/changes/23733-apple-app-store-icons @@ -0,0 +1 @@ +- Fleet UI: Remove image borders that are included in Apple's app store icons diff --git a/changes/24009-gh-translation b/changes/24009-gh-translation new file mode 100644 index 0000000000..103bd7b6eb --- /dev/null +++ b/changes/24009-gh-translation @@ -0,0 +1 @@ +* Fixed an issue where the github cli software name was not matching against the cpe vulnerability name \ No newline at end of file diff --git a/changes/24024-bypass-setup-experience-if-empty b/changes/24024-bypass-setup-experience-if-empty new file mode 100644 index 0000000000..319df88c1c --- /dev/null +++ b/changes/24024-bypass-setup-experience-if-empty @@ -0,0 +1,2 @@ +* Bypass the setup experience UI if there is no setup experience item to process (no software to install, no script to execute), so that releasing the device is done without going through that window. +* Fixed releasing a DEP-enrolled macOS device if mTLS is configured for `fleetd`. diff --git a/changes/24024-no-setup-exp b/changes/24024-no-setup-exp deleted file mode 100644 index 44ab42bcf0..0000000000 --- a/changes/24024-no-setup-exp +++ /dev/null @@ -1,2 +0,0 @@ -- Modifies the Fleet setup experience feature to not run if there is no software or script - configured for the setup experience. \ No newline at end of file diff --git a/changes/24093-clear-policy-automation b/changes/24093-clear-policy-automation new file mode 100644 index 0000000000..4d77791615 --- /dev/null +++ b/changes/24093-clear-policy-automation @@ -0,0 +1 @@ +- Fleet UI: Fix ability to clear policy automation that empties webhook URL diff --git a/changes/24109-drop-duplicate-indexes b/changes/24109-drop-duplicate-indexes new file mode 100644 index 0000000000..df813981a4 --- /dev/null +++ b/changes/24109-drop-duplicate-indexes @@ -0,0 +1 @@ +Removed duplicate indexes from the database schema. diff --git a/changes/24248-host-details-encryption-banner b/changes/24248-host-details-encryption-banner new file mode 100644 index 0000000000..7de5934177 --- /dev/null +++ b/changes/24248-host-details-encryption-banner @@ -0,0 +1,2 @@ +* Only show the "follow instructions on My device" banner for Linux hosts whose disks are encrypted +but for which Fleet hasn't escrowed a valid key. diff --git a/changes/24288-mdm-gitops-role b/changes/24288-mdm-gitops-role new file mode 100644 index 0000000000..2d04811311 --- /dev/null +++ b/changes/24288-mdm-gitops-role @@ -0,0 +1 @@ +Fixed breaking with gitops user role running `fleetctl gitops` command when MDM is enabled. diff --git a/changes/jve-fix-typo b/changes/jve-fix-typo new file mode 100644 index 0000000000..79379dadc5 --- /dev/null +++ b/changes/jve-fix-typo @@ -0,0 +1 @@ +- Fixes a typo in the loading modal when adding a Fleet-maintained app. \ No newline at end of file diff --git a/charts/fleet/Chart.yaml b/charts/fleet/Chart.yaml index f9b80ed1e3..aeb5a838e0 100644 --- a/charts/fleet/Chart.yaml +++ b/charts/fleet/Chart.yaml @@ -4,11 +4,11 @@ name: fleet keywords: - fleet - osquery -version: v6.2.2 +version: v6.2.3 home: https://github.com/fleetdm/fleet sources: - https://github.com/fleetdm/fleet.git -appVersion: v4.59.1 +appVersion: v4.60.0 dependencies: - name: mysql condition: mysql.enabled diff --git a/charts/fleet/values.yaml b/charts/fleet/values.yaml index 7e1c7f7916..231c8bb22b 100644 --- a/charts/fleet/values.yaml +++ b/charts/fleet/values.yaml @@ -3,7 +3,7 @@ hostName: fleet.localhost replicas: 3 # The number of Fleet instances to deploy imageRepository: fleetdm/fleet -imageTag: v4.59.1 # Version of Fleet to deploy +imageTag: v4.60.0 # Version of Fleet to deploy podAnnotations: {} # Additional annotations to add to the Fleet pod serviceAccountAnnotations: {} # Additional annotations to add to the Fleet service account resources: diff --git a/cmd/cpe/generate.go b/cmd/cpe/generate.go index b2ed44ced7..5018f02003 100644 --- a/cmd/cpe/generate.go +++ b/cmd/cpe/generate.go @@ -22,10 +22,10 @@ import ( ) const ( - httpClientTimeout = 2 * time.Minute + httpClientTimeout = 3 * time.Minute waitTimeBetweenRequests = 6 * time.Second - waitTimeForRetry = 30 * time.Second - maxRetryAttempts = 10 + waitTimeForRetry = 10 * time.Second + maxRetryAttempts = 20 apiKeyEnvVar = "NVD_API_KEY" //nolint:gosec ) diff --git a/cmd/fleet/cron.go b/cmd/fleet/cron.go index 90dc3fffa9..75d6f3ba83 100644 --- a/cmd/fleet/cron.go +++ b/cmd/fleet/cron.go @@ -1182,7 +1182,7 @@ func appleMDMDEPSyncerJob( } } -func newMDMProfileManager( +func newAppleMDMProfileManagerSchedule( ctx context.Context, instanceID string, ds fleet.Datastore, @@ -1207,6 +1207,29 @@ func newMDMProfileManager( schedule.WithJob("manage_apple_declarations", func(ctx context.Context) error { return service.ReconcileAppleDeclarations(ctx, ds, commander, logger) }), + ) + + return s, nil +} + +func newWindowsMDMProfileManagerSchedule( + ctx context.Context, + instanceID string, + ds fleet.Datastore, + logger kitlog.Logger, +) (*schedule.Schedule, error) { + const ( + name = string(fleet.CronMDMWindowsProfileManager) + // Note: per a request from #g-product we are running this cron + // every 30 seconds, we should re-evaluate how we handle the + // cron interval as we scale to more hosts. + defaultInterval = 30 * time.Second + ) + + logger = kitlog.With(logger, "cron", name) + s := schedule.New( + ctx, name, instanceID, defaultInterval, ds, ds, + schedule.WithLogger(logger), schedule.WithJob("manage_windows_profiles", func(ctx context.Context) error { return service.ReconcileWindowsProfiles(ctx, ds, logger) }), diff --git a/cmd/fleet/cron_test.go b/cmd/fleet/cron_test.go index 789b38c405..2f051d9f17 100644 --- a/cmd/fleet/cron_test.go +++ b/cmd/fleet/cron_test.go @@ -23,14 +23,24 @@ import ( kitlog "github.com/go-kit/log" ) -func TestNewMDMProfileManagerWithoutConfig(t *testing.T) { +func TestNewAppleMDMProfileManagerWithoutConfig(t *testing.T) { ctx := context.Background() mdmStorage := &mdmmock.MDMAppleStore{} ds := new(mock.Store) cmdr := apple_mdm.NewMDMAppleCommander(mdmStorage, nil) logger := kitlog.NewNopLogger() - sch, err := newMDMProfileManager(ctx, "foo", ds, cmdr, logger) + sch, err := newAppleMDMProfileManagerSchedule(ctx, "foo", ds, cmdr, logger) + require.NotNil(t, sch) + require.NoError(t, err) +} + +func TestNewWindowsMDMProfileManagerWithoutConfig(t *testing.T) { + ctx := context.Background() + ds := new(mock.Store) + logger := kitlog.NewNopLogger() + + sch, err := newWindowsMDMProfileManagerSchedule(ctx, "foo", ds, logger) require.NotNil(t, sch) require.NoError(t, err) } diff --git a/cmd/fleet/serve.go b/cmd/fleet/serve.go index a523a91bac..0b770ca43a 100644 --- a/cmd/fleet/serve.go +++ b/cmd/fleet/serve.go @@ -924,7 +924,7 @@ the way that the Fleet server works. } if err := cronSchedules.StartCronSchedule(func() (fleet.CronSchedule, error) { - return newMDMProfileManager( + return newAppleMDMProfileManagerSchedule( ctx, instanceID, ds, @@ -935,6 +935,17 @@ the way that the Fleet server works. initFatal(err, "failed to register mdm_apple_profile_manager schedule") } + if err := cronSchedules.StartCronSchedule(func() (fleet.CronSchedule, error) { + return newWindowsMDMProfileManagerSchedule( + ctx, + instanceID, + ds, + logger, + ) + }); err != nil { + initFatal(err, "failed to register mdm_windows_profile_manager schedule") + } + if err := cronSchedules.StartCronSchedule(func() (fleet.CronSchedule, error) { return newMDMAPNsPusher( ctx, diff --git a/cmd/fleetctl/gitops.go b/cmd/fleetctl/gitops.go index 30efe0a513..5389065913 100644 --- a/cmd/fleetctl/gitops.go +++ b/cmd/fleetctl/gitops.go @@ -299,12 +299,12 @@ func checkABMTeamAssignments(config *spec.GitOps, fleetClient *service.Client) ( return nil, false, false, errors.New(fleet.AppleABMDefaultTeamDeprecatedMessage) } - abmToks, err := fleetClient.ListABMTokens() + abmToks, err := fleetClient.CountABMTokens() if err != nil { return nil, false, false, err } - if hasLegacyConfig && len(abmToks) > 1 { + if hasLegacyConfig && abmToks > 1 { return nil, false, false, errors.New(fleet.AppleABMDefaultTeamDeprecatedMessage) } diff --git a/cmd/fleetctl/gitops_test.go b/cmd/fleetctl/gitops_test.go index 407351c154..ad11066b2a 100644 --- a/cmd/fleetctl/gitops_test.go +++ b/cmd/fleetctl/gitops_test.go @@ -1217,6 +1217,9 @@ func TestGitOpsBasicGlobalAndTeam(t *testing.T) { ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { return []*fleet.ABMToken{}, nil } + ds.GetABMTokenCountFunc = func(ctx context.Context) (int, error) { + return 0, nil + } ds.DeleteSetupExperienceScriptFunc = func(ctx context.Context, teamID *uint) error { return nil } @@ -1815,6 +1818,9 @@ func TestGitOpsFullGlobalAndTeam(t *testing.T) { ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { return []*fleet.ABMToken{}, nil } + ds.GetABMTokenCountFunc = func(ctx context.Context) (int, error) { + return 0, nil + } apnsCert, apnsKey, err := mysql.GenerateTestCertBytes() require.NoError(t, err) @@ -2854,6 +2860,9 @@ software: } return []*fleet.ABMToken{{OrganizationName: "Fleet Device Management Inc."}, {OrganizationName: "Foo Inc."}}, nil } + ds.GetABMTokenCountFunc = func(ctx context.Context) (int, error) { + return len(tt.tokens), nil + } ds.TeamsSummaryFunc = func(ctx context.Context) ([]*fleet.TeamSummary, error) { var res []*fleet.TeamSummary @@ -3177,6 +3186,9 @@ software: ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { return []*fleet.ABMToken{{OrganizationName: "Fleet Device Management Inc."}, {OrganizationName: "Foo Inc."}}, nil } + ds.GetABMTokenCountFunc = func(ctx context.Context) (int, error) { + return 1, nil + } ds.TeamsSummaryFunc = func(ctx context.Context) ([]*fleet.TeamSummary, error) { var res []*fleet.TeamSummary @@ -3219,6 +3231,31 @@ software: } } +func TestGitOpsWindowsMigration(t *testing.T) { + cases := []struct { + file string + wantErr string + }{ + // booleans are Windows MDM enabled and Windows migration enabled + {"testdata/gitops/global_config_windows_migration_true_true.yml", ""}, + {"testdata/gitops/global_config_windows_migration_false_true.yml", "Windows MDM is not enabled"}, + {"testdata/gitops/global_config_windows_migration_true_false.yml", ""}, + {"testdata/gitops/global_config_windows_migration_false_false.yml", ""}, + } + for _, c := range cases { + t.Run(filepath.Base(c.file), func(t *testing.T) { + setupFullGitOpsPremiumServer(t) + + _, err := runAppNoChecks([]string{"gitops", "-f", c.file}) + if c.wantErr == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, c.wantErr) + } + }) + } +} + type memKeyValueStore struct { m sync.Map } diff --git a/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json b/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json index c377964177..1cbede5abf 100644 --- a/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json +++ b/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json @@ -118,6 +118,7 @@ "deadline_days": 7, "grace_period_days": 3 }, + "windows_migration_enabled": false, "macos_migration": { "enable": false, "mode": "", diff --git a/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml b/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml index ff6fbaa22e..042be51191 100644 --- a/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml +++ b/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml @@ -27,6 +27,7 @@ spec: volume_purchasing_program: null windows_enabled_and_configured: false enable_disk_encryption: false + windows_migration_enabled: false macos_migration: enable: false mode: "" diff --git a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json index dea76a995b..f8c421065b 100644 --- a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json +++ b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json @@ -70,6 +70,7 @@ "deadline_days": 7, "grace_period_days": 3 }, + "windows_migration_enabled": false, "macos_migration": { "enable": false, "mode": "", diff --git a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml index f6b8136407..5f6e877f16 100644 --- a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml +++ b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml @@ -27,6 +27,7 @@ spec: enabled_and_configured: false windows_enabled_and_configured: false enable_disk_encryption: false + windows_migration_enabled: false macos_migration: enable: false mode: "" diff --git a/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_false.yml b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_false.yml new file mode 100644 index 0000000000..645be877d3 --- /dev/null +++ b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_false.yml @@ -0,0 +1,75 @@ +controls: + macos_settings: + windows_settings: + scripts: + enable_disk_encryption: false + macos_migration: + enable: false + mode: "" + webhook_url: "" + macos_setup: + bootstrap_package: null + enable_end_user_authentication: false + macos_setup_assistant: null + macos_updates: + deadline: null + minimum_version: null + windows_enabled_and_configured: false + windows_migration_enabled: false + windows_updates: + deadline_days: null + grace_period_days: null +queries: +policies: +agent_options: + command_line_flags: + distributed_denylist_duration: 0 + config: + decorators: + load: + - SELECT uuid AS host_uuid FROM system_info; + - SELECT hostname AS hostname FROM system_info; + options: + disable_distributed: false + distributed_interval: 10 + distributed_plugin: tls + distributed_tls_max_attempts: 3 + logger_tls_endpoint: /api/v1/osquery/log + pack_delimiter: / +org_settings: + server_settings: + deferred_save_host: false + enable_analytics: true + live_query_disabled: false + query_report_cap: 2000 + query_reports_disabled: false + scripts_disabled: false + server_url: $FLEET_SERVER_URL + ai_features_disabled: true + org_info: + contact_url: https://fleetdm.com/company/contact + org_logo_url: "" + org_logo_url_light_background: "" + org_name: $ORG_NAME + smtp_settings: + sso_settings: + integrations: + mdm: + end_user_authentication: + webhook_settings: + fleet_desktop: # Applies to Fleet Premium only + transparency_url: https://fleetdm.com/transparency + host_expiry_settings: # Applies to all teams + host_expiry_enabled: false + activity_expiry_settings: + activity_expiry_enabled: true + activity_expiry_window: 60 + features: # Features added to all teams + enable_host_users: true + enable_software_inventory: true + vulnerability_settings: + databases_path: "" + secrets: # These secrets are used to enroll hosts to the "All teams" team + - secret: SampleSecret123 + - secret: ABC +software: diff --git a/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_true.yml b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_true.yml new file mode 100644 index 0000000000..99cdf07c39 --- /dev/null +++ b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_false_true.yml @@ -0,0 +1,75 @@ +controls: + macos_settings: + windows_settings: + scripts: + enable_disk_encryption: false + macos_migration: + enable: false + mode: "" + webhook_url: "" + macos_setup: + bootstrap_package: null + enable_end_user_authentication: false + macos_setup_assistant: null + macos_updates: + deadline: null + minimum_version: null + windows_enabled_and_configured: false + windows_migration_enabled: true + windows_updates: + deadline_days: null + grace_period_days: null +queries: +policies: +agent_options: + command_line_flags: + distributed_denylist_duration: 0 + config: + decorators: + load: + - SELECT uuid AS host_uuid FROM system_info; + - SELECT hostname AS hostname FROM system_info; + options: + disable_distributed: false + distributed_interval: 10 + distributed_plugin: tls + distributed_tls_max_attempts: 3 + logger_tls_endpoint: /api/v1/osquery/log + pack_delimiter: / +org_settings: + server_settings: + deferred_save_host: false + enable_analytics: true + live_query_disabled: false + query_report_cap: 2000 + query_reports_disabled: false + scripts_disabled: false + server_url: $FLEET_SERVER_URL + ai_features_disabled: true + org_info: + contact_url: https://fleetdm.com/company/contact + org_logo_url: "" + org_logo_url_light_background: "" + org_name: $ORG_NAME + smtp_settings: + sso_settings: + integrations: + mdm: + end_user_authentication: + webhook_settings: + fleet_desktop: # Applies to Fleet Premium only + transparency_url: https://fleetdm.com/transparency + host_expiry_settings: # Applies to all teams + host_expiry_enabled: false + activity_expiry_settings: + activity_expiry_enabled: true + activity_expiry_window: 60 + features: # Features added to all teams + enable_host_users: true + enable_software_inventory: true + vulnerability_settings: + databases_path: "" + secrets: # These secrets are used to enroll hosts to the "All teams" team + - secret: SampleSecret123 + - secret: ABC +software: diff --git a/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_false.yml b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_false.yml new file mode 100644 index 0000000000..c934e124a2 --- /dev/null +++ b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_false.yml @@ -0,0 +1,75 @@ +controls: + macos_settings: + windows_settings: + scripts: + enable_disk_encryption: false + macos_migration: + enable: false + mode: "" + webhook_url: "" + macos_setup: + bootstrap_package: null + enable_end_user_authentication: false + macos_setup_assistant: null + macos_updates: + deadline: null + minimum_version: null + windows_enabled_and_configured: true + windows_migration_enabled: false + windows_updates: + deadline_days: null + grace_period_days: null +queries: +policies: +agent_options: + command_line_flags: + distributed_denylist_duration: 0 + config: + decorators: + load: + - SELECT uuid AS host_uuid FROM system_info; + - SELECT hostname AS hostname FROM system_info; + options: + disable_distributed: false + distributed_interval: 10 + distributed_plugin: tls + distributed_tls_max_attempts: 3 + logger_tls_endpoint: /api/v1/osquery/log + pack_delimiter: / +org_settings: + server_settings: + deferred_save_host: false + enable_analytics: true + live_query_disabled: false + query_report_cap: 2000 + query_reports_disabled: false + scripts_disabled: false + server_url: $FLEET_SERVER_URL + ai_features_disabled: true + org_info: + contact_url: https://fleetdm.com/company/contact + org_logo_url: "" + org_logo_url_light_background: "" + org_name: $ORG_NAME + smtp_settings: + sso_settings: + integrations: + mdm: + end_user_authentication: + webhook_settings: + fleet_desktop: # Applies to Fleet Premium only + transparency_url: https://fleetdm.com/transparency + host_expiry_settings: # Applies to all teams + host_expiry_enabled: false + activity_expiry_settings: + activity_expiry_enabled: true + activity_expiry_window: 60 + features: # Features added to all teams + enable_host_users: true + enable_software_inventory: true + vulnerability_settings: + databases_path: "" + secrets: # These secrets are used to enroll hosts to the "All teams" team + - secret: SampleSecret123 + - secret: ABC +software: diff --git a/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_true.yml b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_true.yml new file mode 100644 index 0000000000..f6ab917ecf --- /dev/null +++ b/cmd/fleetctl/testdata/gitops/global_config_windows_migration_true_true.yml @@ -0,0 +1,75 @@ +controls: + macos_settings: + windows_settings: + scripts: + enable_disk_encryption: false + macos_migration: + enable: false + mode: "" + webhook_url: "" + macos_setup: + bootstrap_package: null + enable_end_user_authentication: false + macos_setup_assistant: null + macos_updates: + deadline: null + minimum_version: null + windows_enabled_and_configured: true + windows_migration_enabled: true + windows_updates: + deadline_days: null + grace_period_days: null +queries: +policies: +agent_options: + command_line_flags: + distributed_denylist_duration: 0 + config: + decorators: + load: + - SELECT uuid AS host_uuid FROM system_info; + - SELECT hostname AS hostname FROM system_info; + options: + disable_distributed: false + distributed_interval: 10 + distributed_plugin: tls + distributed_tls_max_attempts: 3 + logger_tls_endpoint: /api/v1/osquery/log + pack_delimiter: / +org_settings: + server_settings: + deferred_save_host: false + enable_analytics: true + live_query_disabled: false + query_report_cap: 2000 + query_reports_disabled: false + scripts_disabled: false + server_url: $FLEET_SERVER_URL + ai_features_disabled: true + org_info: + contact_url: https://fleetdm.com/company/contact + org_logo_url: "" + org_logo_url_light_background: "" + org_name: $ORG_NAME + smtp_settings: + sso_settings: + integrations: + mdm: + end_user_authentication: + webhook_settings: + fleet_desktop: # Applies to Fleet Premium only + transparency_url: https://fleetdm.com/transparency + host_expiry_settings: # Applies to all teams + host_expiry_enabled: false + activity_expiry_settings: + activity_expiry_enabled: true + activity_expiry_window: 60 + features: # Features added to all teams + enable_host_users: true + enable_software_inventory: true + vulnerability_settings: + databases_path: "" + secrets: # These secrets are used to enroll hosts to the "All teams" team + - secret: SampleSecret123 + - secret: ABC +software: diff --git a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml index 49c129df69..50250bc34e 100644 --- a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml +++ b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml @@ -27,6 +27,7 @@ spec: enabled_and_configured: true windows_enabled_and_configured: false enable_disk_encryption: false + windows_migration_enabled: false macos_migration: enable: false mode: "" diff --git a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml index 27e6a2a545..b74e3c2d8f 100644 --- a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml +++ b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml @@ -27,6 +27,7 @@ spec: enabled_and_configured: true windows_enabled_and_configured: false enable_disk_encryption: false + windows_migration_enabled: false macos_migration: enable: false mode: "" diff --git a/docs/Contributing/Audit-logs.md b/docs/Contributing/Audit-logs.md index 26c8a02f3d..0650b61798 100644 --- a/docs/Contributing/Audit-logs.md +++ b/docs/Contributing/Audit-logs.md @@ -894,6 +894,18 @@ Generated when a user turns off MDM features for all Windows hosts. This activity does not contain any detail fields. +## enabled_windows_mdm_migration + +Generated when a user enables automatic MDM migration for Windows hosts, if Windows MDM is turned on. + +This activity does not contain any detail fields. + +## disabled_windows_mdm_migration + +Generated when a user disables automatic MDM migration for Windows hosts, if Windows MDM is turned on. + +This activity does not contain any detail fields. + ## ran_script Generated when a script is sent to be run for a host. diff --git a/docs/REST API/rest-api.md b/docs/REST API/rest-api.md index 579dd9772d..0e25be5561 100644 --- a/docs/REST API/rest-api.md +++ b/docs/REST API/rest-api.md @@ -500,6 +500,19 @@ for pagination. For a comprehensive list of activity types and detailed informat "status": "failed_install" } }, + { + "created_at": "2021-07-29T14:40:27Z", + "id": 21, + "actor_full_name": "name", + "actor_id": 1, + "actor_gravatar": "", + "actor_email": "name@example.com", + "type": "created_team", + "details": { + "team_id": 2, + "team_name": "Apples" + } + }, { "created_at": "2021-07-30T13:41:07Z", "id": 24, @@ -541,80 +554,6 @@ for pagination. For a comprehensive list of activity types and detailed informat "team_name": "Oranges" } }, - { - "created_at": "2021-07-29T14:40:27Z", - "id": 21, - "actor_full_name": "name", - "actor_id": 1, - "actor_gravatar": "", - "actor_email": "name@example.com", - "type": "created_team", - "details": { - "team_id": 2, - "team_name": "Apples" - } - }, - { - "created_at": "2021-07-27T14:35:08Z", - "id": 20, - "actor_full_name": "name", - "actor_id": 1, - "actor_gravatar": "", - "actor_email": "name@example.com", - "type": "created_pack", - "details": { - "pack_id": 2, - "pack_name": "New pack" - } - }, - { - "created_at": "2021-07-27T13:25:21Z", - "id": 19, - "actor_full_name": "name", - "actor_id": 1, - "actor_gravatar": "", - "actor_email": "name@example.com", - "type": "live_query", - "details": { - "targets_count": 14 - } - }, - { - "created_at": "2021-07-27T13:25:14Z", - "id": 18, - "actor_full_name": "name", - "actor_id": 1, - "actor_gravatar": "", - "actor_email": "name@example.com", - "type": "live_query", - "details": { - "targets_count": 14 - } - }, - { - "created_at": "2021-07-26T19:28:24Z", - "id": 17, - "actor_full_name": "name", - "actor_id": 1, - "actor_gravatar": "", - "actor_email": "name@example.com", - "type": "live_query", - "details": { - "target_counts": 1 - } - }, - { - "created_at": "2021-07-26T17:27:37Z", - "id": 16, - "actor_full_name": "name", - "actor_id": 1, - "actor_gravatar": "", - "actor_email": "name@example.com", - "type": "live_query", - "details": { - "target_counts": 14 - } - }, { "created_at": "2021-07-26T17:27:08Z", "id": 15, @@ -2543,11 +2482,13 @@ the `software` table. | bootstrap_package | string | query | _Available in Fleet Premium_. Filters the hosts by the status of the MDM bootstrap package on the host. Valid options are 'installed', 'pending', or 'failed'. | | os_settings | string | query | Filters the hosts by the status of the operating system settings applied to the hosts. Valid options are 'verified', 'verifying', 'pending', or 'failed'. **Note: If this filter is used in Fleet Premium without a team ID filter, the results include only hosts that are not assigned to any team.** | | os_settings_disk_encryption | string | query | Filters the hosts by the status of the disk encryption setting applied to the hosts. Valid options are 'verified', 'verifying', 'action_required', 'enforcing', 'failed', or 'removing_enforcement'. **Note: If this filter is used in Fleet Premium without a team ID filter, the results include only hosts that are not assigned to any team.** | -| populate_software | boolean | query | If `true`, the response will include a list of installed software for each host, including vulnerability data. (Note that software lists can be large, so this may cause significant CPU and RAM usage depending on page size and request concurrency.) | +| populate_software | string | query | If `false` (or omitted), omits installed software details for each host. If `"without_vulnerability_details"`, include a list of installed software for each host, including which CVEs apply to the installed software versions. `true` adds vulnerability description, CVSS score, and other details when using Fleet Premium. See notes below on performance. | | populate_policies | boolean | query | If `true`, the response will include policy data for each host. | > `software_id` is deprecated as of Fleet 4.42. It is maintained for backwards compatibility. Please use the `software_version_id` instead. +> `populate_software` returns a lot of data per host when set, and drastically more data when set to `true` on Fleet Premium. If you need vulnerability details for a large number of hosts, consider setting `populate_software` to `without_vulnerability_details` and pulling vulnerability details from the [Get vulnerability](#get-vulnerability) endpoint, as this returns details once per vulnerability rather than once per vulnerability per host. + If `software_title_id` is specified, an additional top-level key `"software_title"` is returned with the software title object corresponding to the `software_title_id`. See [List software](#list-software) response payload for details about this object. If `software_version_id` is specified, an additional top-level key `"software"` is returned with the software object corresponding to the `software_version_id`. See [List software versions](#list-software-versions) response payload for details about this object. @@ -5729,12 +5670,12 @@ Get aggregate disk encryption status counts of macOS and Windows hosts enrolled ```json { - "verified": {"macos": 123, "windows": 123}, - "verifying": {"macos": 123, "windows": 0}, - "action_required": {"macos": 123, "windows": 0}, - "enforcing": {"macos": 123, "windows": 123}, - "failed": {"macos": 123, "windows": 123}, - "removing_enforcement": {"macos": 123, "windows": 0}, + "verified": {"macos": 123, "windows": 123, "linux": 13}, + "verifying": {"macos": 123, "windows": 0, "linux": 0}, + "action_required": {"macos": 123, "windows": 0, "linux": 37}, + "enforcing": {"macos": 123, "windows": 123, "linux": 0}, + "failed": {"macos": 123, "windows": 123, "linux": 0}, + "removing_enforcement": {"macos": 123, "windows": 0, "linux": 0} } ``` @@ -5838,6 +5779,8 @@ Sets the custom MDM setup enrollment profile for a team or no team. } ``` +> NOTE: The `ConfigurationWebURL` and `URL` values in the custom MDM setup enrollment profile are automatically populated. Attempting to populate them with custom values may generate server response errors. + ### Get custom MDM setup enrollment profile _Available in Fleet Premium_ @@ -7656,6 +7599,9 @@ Returns a list of global queries or team queries. | team_id | integer | query | _Available in Fleet Premium_. The ID of the parent team for the queries to be listed. When omitted, returns global queries. | | query | string | query | Search query keywords. Searchable fields include `name`. | | merge_inherited | boolean | query | _Available in Fleet Premium_. If `true`, will include global queries in addition to team queries when filtering by `team_id`. (If no `team_id` is provided, this parameter is ignored.) | +| compatible_platform | string | query | Return queries that only reference tables compatible with this platform (not a strict compatibility check). One of: `"macos"`, `"windows"`, `"linux"`, `"chrome"` (case-insensitive). | +| page | integer | query | Page number of the results to fetch. | +| per_page | integer | query | Results per page. | #### Example @@ -7744,7 +7690,12 @@ Returns a list of global queries or team queries. "total_executions": null } } - ] + ], + "meta": { + "has_next_results": true, + "has_previous_results": false + }, + "count": 200 } ``` @@ -9367,6 +9318,7 @@ Returns information about the specified software. By default, `versions` are sor } }, "app_store_app": null, + "counts_updated_at": "2024-11-03T22:39:36Z", "source": "apps", "browser": "", "hosts_count": 48, diff --git a/ee/bulk-operations-dashboard/api/controllers/software/delete-software.js b/ee/bulk-operations-dashboard/api/controllers/software/delete-software.js index c60f71e7ec..539d8c4676 100644 --- a/ee/bulk-operations-dashboard/api/controllers/software/delete-software.js +++ b/ee/bulk-operations-dashboard/api/controllers/software/delete-software.js @@ -16,7 +16,10 @@ module.exports = { exits: { - + softwareDeletionFailed: { + description: 'The specified software could not be deleted from the Fleet instance.', + statusCode: 409, + } }, @@ -34,6 +37,11 @@ module.exports = { headers: { Authorization: `Bearer ${sails.config.custom.fleetApiToken}`, } + }) + .intercept({raw:{statusCode: 409}}, (error)=>{ + // If the Fleet instance's returns a 409 response, then the software is configured to be installed as + // part of the macOS setup experience, and must be removed before it can be deleted via API requests. + return {softwareDeletionFailed: error}; }); } } diff --git a/ee/bulk-operations-dashboard/api/controllers/software/edit-software.js b/ee/bulk-operations-dashboard/api/controllers/software/edit-software.js index abe14930ea..d97b0366a8 100644 --- a/ee/bulk-operations-dashboard/api/controllers/software/edit-software.js +++ b/ee/bulk-operations-dashboard/api/controllers/software/edit-software.js @@ -46,9 +46,23 @@ module.exports = { description: 'The provided replacement software\'s has the wrong extension.', statusCode: 400, }, + softwareUploadFailed: { description: 'The software upload failed' - } + }, + + softwareAlreadyExistsOnThisTeam: { + description: 'A software installer with this name already exists on the Fleet Instance', + }, + + couldNotReadVersion: { + description:'Fleet could not read version information from the provided software installer.' + }, + + softwareDeletionFailed: { + description: 'The specified software could not be deleted from the Fleet instance.', + statusCode: 409, + }, }, @@ -84,6 +98,9 @@ module.exports = { headers: { Authorization: `Bearer ${sails.config.custom.fleetApiToken}`, } + }) + .intercept('non200Response', (error)=>{ + return new Error(`When attempting to transfer the installer for ${software.name} to a new team on the Fleet instance, the Fleet isntance returned a non-200 response when a request was sent to get a download stream of the installer on team_id ${teamIdToGetInstallerFrom}. Full Error: ${require('util').inspect(error, {depth: 1})}`); }); let tempUploadedSoftware = await sails.uploadOne(softwareStream, {bucket: sails.config.uploads.bucketWithPostfix}); softwareFd = tempUploadedSoftware.fd; @@ -164,7 +181,35 @@ module.exports = { } }; }, - }) + } + ) + .intercept({response: {status: 409}}, async (error)=>{// handles errors related to duplicate software items. + if(!software.id) {// If the software does not have an ID, it not stored in the app's database/s3 bucket, so we can safely delete the file in s3. + await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd); + } + return {'softwareAlreadyExistsOnThisTeam': error}; + }) + .intercept({name: 'AxiosError', response: {status: 400}}, async (error)=>{// Handles errors related to malformed installer packages + if(!software.id) {// If the software does not have an ID, it not stored in the app's database/s3 bucket, so we can safely delete the file in s3. + await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd); + } + let axiosError = error; + if(axiosError.response.data) { + if(axiosError.response.data.errors && _.isArray(axiosError.response.data.errors)){ + if(axiosError.response.data.errors[0] && axiosError.response.data.errors[0].reason) { + let errorMessageFromFleetInstance = axiosError.response.data.errors[0].reason; + if(_.startsWith(errorMessageFromFleetInstance, `Couldn't add. Fleet couldn't read the version`)){ + return 'couldNotReadVersion'; + } else { + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API. Error returned from Fleet API: ${errorMessageFromFleetInstance}`); + return {'softwareUploadFailed': error}; + } + } + } + } + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 3})}`); + return {'softwareUploadFailed': error}; + }) .intercept(async (error)=>{ // Note: with this current behavior, all errors from this upload are currently swallowed and a softwareUploadFailed response is returned. // FUTURE: Test to make sure that uploading duplicate software to a team results in a 409 response. @@ -173,7 +218,7 @@ module.exports = { await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd); } // Log a warning containing an error - sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 0})}`); + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, Full error: ${require('util').inspect(error, {depth: 2})}`); return {'softwareUploadFailed': error}; }); // console.timeEnd(`transfering ${software.name} to fleet instance for team id ${team}`); @@ -183,15 +228,6 @@ module.exports = { // If a new installer package was provided, send patch requests to update the installer package on teams that it is already deployed to. await sails.helpers.flow.simultaneouslyForEach(unchangedTeamIds, async (teamApid)=>{ // console.log(`Adding new version of ${softwareName} to teamId ${teamApid}`); - await sails.helpers.http.sendHttpRequest.with({ - method: 'DELETE', - baseUrl: sails.config.custom.fleetBaseUrl, - url: `/api/v1/fleet/software/titles/${software.fleetApid}/available_for_install?team_id=${teamApid}`, - headers: { - Authorization: `Bearer ${sails.config.custom.fleetApiToken}`, - } - }); - // console.log(`transfering the changed installer ${software.name} to fleet instance for team id ${teamApid}`); // console.time(`transfering ${software.name} to fleet instance for team id ${teamApid}`); await sails.cp(softwareFd, {bucket: sails.config.uploads.bucketWithPostfix}, { @@ -220,7 +256,7 @@ module.exports = { contentType: 'application/octet-stream' }); (async ()=>{ - await axios.post(`${sails.config.custom.fleetBaseUrl}/api/v1/fleet/software/package`, form, { + await axios.patch(`${sails.config.custom.fleetBaseUrl}/api/v1/fleet/software/titles/${software.fleetApid}/package`, form, { headers: { Authorization: `Bearer ${sails.config.custom.fleetApiToken}`, ...form.getHeaders() @@ -240,6 +276,33 @@ module.exports = { }; }, }) + .intercept({response: {status: 409}}, async (error)=>{// handles errors related to duplicate software items. + if(!software.id) {// If the software does not have an ID, it not stored in the app's database/s3 bucket, so we can safely delete the file in s3. + await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd); + } + return {'softwareAlreadyExistsOnThisTeam': error}; + }) + .intercept({name: 'AxiosError', response: {status: 400}}, async (error)=>{// Handles errors related to malformed installer packages + if(!software.id) {// If the software does not have an ID, it not stored in the app's database/s3 bucket, so we can safely delete the file in s3. + await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd); + } + let axiosError = error; + if(axiosError.response.data) { + if(axiosError.response.data.errors && _.isArray(axiosError.response.data.errors)){ + if(axiosError.response.data.errors[0] && axiosError.response.data.errors[0].reason) { + let errorMessageFromFleetInstance = axiosError.response.data.errors[0].reason; + if(_.startsWith(errorMessageFromFleetInstance, `Couldn't add. Fleet couldn't read the version`)){ + return 'couldNotReadVersion'; + } else { + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API. Error returned from Fleet API: ${errorMessageFromFleetInstance}`); + return {'softwareUploadFailed': error}; + } + } + } + } + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 3})}`); + return {'softwareUploadFailed': error}; + }) .intercept(async (error)=>{ // Note: with this current behavior, all errors from this upload are currently swallowed and a softwareUploadFailed response is returned. // FUTURE: Test to make sure that uploading duplicate software to a team results in a 409 response. @@ -248,7 +311,7 @@ module.exports = { await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd); } // Log a warning containing an error - sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 0})}`); + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 2})}`); return {'softwareUploadFailed': error}; }); // console.timeEnd(`transfering ${software.name} to fleet instance for team id ${teamApid}`); @@ -285,6 +348,11 @@ module.exports = { headers: { Authorization: `Bearer ${sails.config.custom.fleetApiToken}`, } + }) + .intercept({raw:{statusCode: 409}}, (error)=>{ + // If the Fleet instance's returns a 409 response, then the software is configured to be installed as + // part of the macOS setup experience, and must be removed before it can be deleted via API requests. + return {softwareDeletionFailed: error}; }); } // If the software had been previously undeployed, delete the installer in s3 and the db record. @@ -295,9 +363,23 @@ module.exports = { } else if(software.teams && newTeamIds.length === 0) { // If this is a deployed software that is being unassigned, save information about the uploaded file in our s3 bucket. + for(let team of software.teams) { + // Now delete the software on the Fleet instance. + await sails.helpers.http.sendHttpRequest.with({ + method: 'DELETE', + baseUrl: sails.config.custom.fleetBaseUrl, + url: `/api/v1/fleet/software/titles/${software.fleetApid}/available_for_install?team_id=${team.fleetApid}`, + headers: { + Authorization: `Bearer ${sails.config.custom.fleetApiToken}`, + } + }) + .intercept({raw:{statusCode: 409}}, (error)=>{ + // If the Fleet instance's returns a 409 response, then the software is configured to be installed as + // part of the macOS setup experience, and must be removed before it can be deleted via API requests. + return {softwareDeletionFailed: error}; + }); + } if(newSoftware) { - // remove the old copy. - // console.log('Removing old package for ',softwareName); await UndeployedSoftware.create({ uploadFd: softwareFd, uploadMime: softwareMime, @@ -321,17 +403,6 @@ module.exports = { uninstallScript, }); } - // Now delete the software on the Fleet instance. - for(let team of software.teams) { - await sails.helpers.http.sendHttpRequest.with({ - method: 'DELETE', - baseUrl: sails.config.custom.fleetBaseUrl, - url: `/api/v1/fleet/software/titles/${software.fleetApid}/available_for_install?team_id=${team.fleetApid}`, - headers: { - Authorization: `Bearer ${sails.config.custom.fleetApiToken}`, - } - }); - } } else { // console.log('updating existing db record!'); diff --git a/ee/bulk-operations-dashboard/api/controllers/software/upload-software.js b/ee/bulk-operations-dashboard/api/controllers/software/upload-software.js index 29b4817b3b..987bfe8792 100644 --- a/ee/bulk-operations-dashboard/api/controllers/software/upload-software.js +++ b/ee/bulk-operations-dashboard/api/controllers/software/upload-software.js @@ -35,6 +35,10 @@ module.exports = { softwareUploadFailed: { description:'An unexpected error occurred communicating with the Fleet API' + }, + + couldNotReadVersion: { + description:'Fleet could not read version information from the provided software installer.' } }, @@ -100,13 +104,32 @@ module.exports = { }; } }) - .intercept({response: {status: 409}}, async (error)=>{ + .intercept({response: {status: 409}}, async (error)=>{// handles errors related to duplicate software items. await sails.rm(sails.config.uploads.prefixForFileDeletion+uploadedSoftware.fd); return {'softwareAlreadyExistsOnThisTeam': error}; }) - .intercept({name: 'AxiosError'}, async (error)=>{ + .intercept({name: 'AxiosError', response: {status: 400}}, async (error)=>{// Handles errors related to malformed installer packages await sails.rm(sails.config.uploads.prefixForFileDeletion+uploadedSoftware.fd); - sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 0})}`); + let axiosError = error; + if(axiosError.response.data) { + if(axiosError.response.data.errors && _.isArray(axiosError.response.data.errors)){ + if(axiosError.response.data.errors[0] && axiosError.response.data.errors[0].reason) { + let errorMessageFromFleetInstance = axiosError.response.data.errors[0].reason; + if(_.startsWith(errorMessageFromFleetInstance, `Couldn't add. Fleet couldn't read the version`)){ + return 'couldNotReadVersion'; + } else { + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API. Error returned from Fleet API: ${errorMessageFromFleetInstance}`); + return {'softwareUploadFailed': error}; + } + } + } + } + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 3})}`); + return {'softwareUploadFailed': error}; + }) + .intercept({name: 'AxiosError'}, async (error)=>{// Handles any other error. + await sails.rm(sails.config.uploads.prefixForFileDeletion+uploadedSoftware.fd); + sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 3})}`); return {'softwareUploadFailed': error}; }); } diff --git a/ee/bulk-operations-dashboard/api/controllers/software/view-software.js b/ee/bulk-operations-dashboard/api/controllers/software/view-software.js index 8a8a8ccb5e..6b06dabeb2 100644 --- a/ee/bulk-operations-dashboard/api/controllers/software/view-software.js +++ b/ee/bulk-operations-dashboard/api/controllers/software/view-software.js @@ -104,7 +104,7 @@ module.exports = { let undeployedSoftware = await UndeployedSoftware.find(); allSoftware = allSoftware.concat(undeployedSoftware); - return {software: allSoftware, teams}; + return {software: allSoftware, teams, fleetBaseUrl: sails.config.custom.fleetBaseUrl}; } diff --git a/ee/bulk-operations-dashboard/views/pages/software/software.ejs b/ee/bulk-operations-dashboard/views/pages/software/software.ejs index 0467e7f606..dcc8b823aa 100644 --- a/ee/bulk-operations-dashboard/views/pages/software/software.ejs +++ b/ee/bulk-operations-dashboard/views/pages/software/software.ejs @@ -145,8 +145,11 @@

Teams

- {{cloudError.responseInfo.body}} - + {{cloudError.responseInfo.body}} + The Fleet instance could not read version information from the provided software installer. + This software has been configured to be installed as part of the macOS setup experience and cannot be removed from a team. Please remove this software from any teams you want to remove this from in the "Setup experience" tab of the Controls page on your Fleet instance and try again + An error occured when transfering this software to a new team. A software installer with the same name as this software already exists on one or more of the selected teams. +
Save
@@ -163,7 +166,8 @@

{{formData.software.name}} will be removed from your library.

- + This software has been configured to be installed as part of the macOS setup experience and cannot be deleted. Please remove this software from all teams the "Setup experience" tab of the Controls page on your Fleet instance and try again +
Cancel Delete @@ -188,6 +192,7 @@
Please select the teams you want to deploy this software to.
A software with the same name as the uploaded software already exists on one or more of the selected teams. + The Fleet instance could not read version information from the provided software installer.
Cancel diff --git a/ee/server/service/devices.go b/ee/server/service/devices.go index 77a6c7ce46..b0752ef0ec 100644 --- a/ee/server/service/devices.go +++ b/ee/server/service/devices.go @@ -17,8 +17,6 @@ func (svc *Service) ListDevicePolicies(ctx context.Context, host *fleet.Host) ([ return svc.ds.ListPoliciesForHost(ctx, host) } -const refetchMDMUnenrollCriticalQueryDuration = 3 * time.Minute - // TriggerMigrateMDMDevice triggers the webhook associated with the MDM // migration to Fleet configuration. It is located in the ee package instead of // the server/webhooks one because it is a Fleet Premium only feature and for @@ -88,7 +86,7 @@ func (svc *Service) TriggerMigrateMDMDevice(ctx context.Context, host *fleet.Hos // if the webhook was successfully triggered, we update the host to // constantly run the query to check if it has been unenrolled from its // existing third-party MDM. - refetchUntil := svc.clock.Now().Add(refetchMDMUnenrollCriticalQueryDuration) + refetchUntil := svc.clock.Now().Add(fleet.RefetchMDMUnenrollCriticalQueryDuration) host.RefetchCriticalQueriesUntil = &refetchUntil if err := svc.ds.UpdateHostRefetchCriticalQueriesUntil(ctx, host.ID, &refetchUntil); err != nil { return ctxerr.Wrap(ctx, err, "save host with refetch critical queries timestamp") diff --git a/ee/server/service/mdm.go b/ee/server/service/mdm.go index 674329d9e4..2de82b7080 100644 --- a/ee/server/service/mdm.go +++ b/ee/server/service/mdm.go @@ -1274,6 +1274,22 @@ func (svc *Service) ListABMTokens(ctx context.Context) ([]*fleet.ABMToken, error return tokens, nil } +func (svc *Service) CountABMTokens(ctx context.Context) (int, error) { + // Authorizing using the more general AppConfig object because: + // - this service method returns a count, which is not sensitive information + // - gitops role, which needs this info, is not authorized for AppleBM access (as of 2024/12/02) + if err := svc.authz.Authorize(ctx, &fleet.AppConfig{}, fleet.ActionRead); err != nil { + return 0, err + } + + tokens, err := svc.ds.GetABMTokenCount(ctx) + if err != nil { + return 0, ctxerr.Wrap(ctx, err, "count ABM tokens") + } + + return tokens, nil +} + func (svc *Service) UpdateABMTokenTeams(ctx context.Context, tokenID uint, macOSTeamID, iOSTeamID, iPadOSTeamID *uint) (*fleet.ABMToken, error) { if err := svc.authz.Authorize(ctx, &fleet.AppleBM{}, fleet.ActionWrite); err != nil { return nil, err diff --git a/ee/server/service/mdm_test.go b/ee/server/service/mdm_test.go index 21cc3d21d9..5162f20f4d 100644 --- a/ee/server/service/mdm_test.go +++ b/ee/server/service/mdm_test.go @@ -6,12 +6,15 @@ import ( "strings" "testing" + "github.com/fleetdm/fleet/v4/server/authz" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/mdm" "github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig" "github.com/fleetdm/fleet/v4/server/mock" "github.com/fleetdm/fleet/v4/server/ptr" + "github.com/fleetdm/fleet/v4/server/test" "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -148,3 +151,46 @@ b1xn1jGQd/o0xFf9ojpDNy6vNojidQGHh6E3h0GYvxbnQmVNq5U= // prevent static analysis tools from raising issues due to detection of // private key in code. func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") } + +func TestCountABMTokensAuth(t *testing.T) { + t.Parallel() + ds := new(mock.Store) + ctx := context.Background() + authorizer, err := authz.NewAuthorizer() + require.NoError(t, err) + svc := Service{ds: ds, authz: authorizer} + + ds.GetABMTokenCountFunc = func(ctx context.Context) (int, error) { + return 5, nil + } + + t.Run("CountABMTokens", func(t *testing.T) { + cases := []struct { + desc string + user *fleet.User + shoudFailWithAuth bool + }{ + {"no role", test.UserNoRoles, true}, + {"gitops can read", test.UserGitOps, false}, + {"maintainer can read", test.UserMaintainer, false}, + {"observer can read", test.UserObserver, false}, + {"observer+ can read", test.UserObserverPlus, false}, + {"admin can read", test.UserAdmin, false}, + {"tm1 gitops cannot read", test.UserTeamGitOpsTeam1, true}, + {"tm1 maintainer can read", test.UserTeamMaintainerTeam1, false}, + {"tm1 observer can read", test.UserTeamObserverTeam1, false}, + {"tm1 observer+ can read", test.UserTeamObserverPlusTeam1, false}, + {"tm1 admin can read", test.UserTeamAdminTeam1, false}, + } + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + ctx = test.UserContext(ctx, c.user) + count, err := svc.CountABMTokens(ctx) + checkAuthErr(t, c.shoudFailWithAuth, err) + if !c.shoudFailWithAuth { + assert.EqualValues(t, 5, count) + } + }) + } + }) +} diff --git a/frontend/__mocks__/configMock.ts b/frontend/__mocks__/configMock.ts index c589f05386..d1f0a644cd 100644 --- a/frontend/__mocks__/configMock.ts +++ b/frontend/__mocks__/configMock.ts @@ -39,6 +39,7 @@ const DEFAULT_CONFIG_MDM_MOCK: IMdmConfig = { deadline_days: null, grace_period_days: null, }, + windows_migration_enabled: false, end_user_authentication: { entity_id: "", issuer_uri: "", diff --git a/frontend/components/Modal/Modal.tsx b/frontend/components/Modal/Modal.tsx index d3947370a2..8049b47441 100644 --- a/frontend/components/Modal/Modal.tsx +++ b/frontend/components/Modal/Modal.tsx @@ -37,7 +37,6 @@ export interface IModalProps { * */ disableClosingModal?: boolean; className?: string; - actionsFooter?: JSX.Element; } const Modal = ({ @@ -51,7 +50,6 @@ const Modal = ({ isContentDisabled = false, disableClosingModal = false, className, - actionsFooter, }: IModalProps): JSX.Element => { useEffect(() => { const closeWithEscapeKey = (e: KeyboardEvent) => { @@ -127,9 +125,6 @@ const Modal = ({ )}
{children}
- {actionsFooter && ( -
{actionsFooter}
- )} ); diff --git a/frontend/components/Modal/_styles.scss b/frontend/components/Modal/_styles.scss index 433e2e886d..747f6e58f2 100644 --- a/frontend/components/Modal/_styles.scss +++ b/frontend/components/Modal/_styles.scss @@ -25,17 +25,9 @@ &__content-wrapper { margin-top: $pad-large; font-size: $x-small; - max-height: 800px; - overflow: visible; .input-field { width: 100%; - - &::placeholder { - font-size: $x-small; - font-style: italic; - line-height: 24px; - } } form .modal-cta-wrap, @@ -142,6 +134,11 @@ } } +.modal-scrollable-content { + overflow-y: auto; + max-height: 705px; +} + .modal-cta-wrap { align-self: flex-end; display: flex; diff --git a/frontend/components/ModalFooter/ModalFooter.stories.tsx b/frontend/components/ModalFooter/ModalFooter.stories.tsx new file mode 100644 index 0000000000..802f76f9f0 --- /dev/null +++ b/frontend/components/ModalFooter/ModalFooter.stories.tsx @@ -0,0 +1,63 @@ +/* eslint-disable no-alert */ +import React from "react"; +import { Meta, Story } from "@storybook/react"; + +import Button from "components/buttons/Button"; +import Icon from "components/Icon"; +import ActionsDropdown from "components/ActionsDropdown"; +import ModalFooter from "./ModalFooter"; + +export default { + title: "Components/ModalFooter", + component: ModalFooter, +} as Meta; + +const Template: Story = (args) => ( + } {...args} /> +); + +export const Default = Template.bind({}); +Default.args = { + primaryButtons: ( + <> + alert(`Selected action: ${value}`)} + placeholder="More actions" + isSearchable={false} + options={[ + { value: "action1", label: "Action 1" }, + { value: "action2", label: "Action 2" }, + ]} + menuPlacement="top" + /> + + + ), + secondaryButtons: ( + <> + + + + ), + isTopScrolling: false, +}; + +export const WithTopScrolling = Template.bind({}); +WithTopScrolling.args = { + ...Default.args, + isTopScrolling: true, +}; + +export const WithoutSecondaryButtons = Template.bind({}); +WithoutSecondaryButtons.args = { + primaryButtons: Default.args.primaryButtons, + secondaryButtons: undefined, + isTopScrolling: false, +}; diff --git a/frontend/components/ModalFooter/ModalFooter.tsx b/frontend/components/ModalFooter/ModalFooter.tsx new file mode 100644 index 0000000000..4b24cc6db0 --- /dev/null +++ b/frontend/components/ModalFooter/ModalFooter.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import classnames from "classnames"; +import { COLORS } from "styles/var/colors"; + +const baseClass = "modal-footer"; + +interface IModalFooterProps { + primaryButtons: JSX.Element; + secondaryButtons?: JSX.Element; + className?: string; + /** Renders a line above action buttons to indicate scrollability */ + isTopScrolling?: boolean; +} + +const ModalFooter = ({ + primaryButtons, + secondaryButtons, + className, + isTopScrolling = false, +}: IModalFooterProps): JSX.Element => { + const classes = classnames(className, `${baseClass}__content-wrapper`); + + return ( +
+
+ {primaryButtons} +
+ {secondaryButtons && ( +
+ {secondaryButtons} +
+ )} +
+ ); +}; + +export default ModalFooter; diff --git a/frontend/components/ModalFooter/_styles.scss b/frontend/components/ModalFooter/_styles.scss new file mode 100644 index 0000000000..679551e1d8 --- /dev/null +++ b/frontend/components/ModalFooter/_styles.scss @@ -0,0 +1,27 @@ +.modal-footer { + &__content-wrapper { + align-self: flex-end; + display: flex; + flex-direction: row-reverse; + padding-top: $pad-medium; + justify-content: space-between; + } + + // Styles both primary-actions and secondary-actions + &__primary-buttons-wrapper, + &__secondary-buttons_wrapper { + display: flex; + justify-content: space-between; + gap: $pad-medium; + align-items: center; + } + + // Align primary actions right if no secondary actions + > :last-child { + margin-left: auto; + } + + .button__text-icon { + padding: 11px; + } +} diff --git a/frontend/components/ModalFooter/index.ts b/frontend/components/ModalFooter/index.ts new file mode 100644 index 0000000000..6a36137070 --- /dev/null +++ b/frontend/components/ModalFooter/index.ts @@ -0,0 +1 @@ +export { default } from "./ModalFooter"; diff --git a/frontend/components/StatusIndicator/StatusIndicator.tests.tsx b/frontend/components/StatusIndicator/StatusIndicator.tests.tsx index 73e4b665e4..418b6ce96d 100644 --- a/frontend/components/StatusIndicator/StatusIndicator.tests.tsx +++ b/frontend/components/StatusIndicator/StatusIndicator.tests.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { render, screen, fireEvent, act } from "@testing-library/react"; +import { render, screen, fireEvent } from "@testing-library/react"; import StatusIndicator from "./StatusIndicator"; @@ -16,9 +16,7 @@ describe("Status indicator", () => { ); - await act(async () => { - fireEvent.mouseEnter(screen.getByText("Online")); - }); + await fireEvent.mouseEnter(screen.getByText("Online")); expect(screen.getByText(TOOLTIP_TEXT)).toBeInTheDocument(); }); diff --git a/frontend/components/TeamsDropdown/TeamsDropdown.tsx b/frontend/components/TeamsDropdown/TeamsDropdown.tsx index e38b1c1b13..bc59c6fb0c 100644 --- a/frontend/components/TeamsDropdown/TeamsDropdown.tsx +++ b/frontend/components/TeamsDropdown/TeamsDropdown.tsx @@ -18,7 +18,7 @@ import { import Icon from "components/Icon"; -interface INumberDropdownOption extends Omit { +export interface INumberDropdownOption extends Omit { value: number; // Redefine the value property to be just number } diff --git a/frontend/components/forms/fields/Checkbox/_styles.scss b/frontend/components/forms/fields/Checkbox/_styles.scss index 8e8d023422..6d311ead47 100644 --- a/frontend/components/forms/fields/Checkbox/_styles.scss +++ b/frontend/components/forms/fields/Checkbox/_styles.scss @@ -68,6 +68,7 @@ } .checkbox-unchecked-state { stroke: $ui-fleet-black-25; + fill: $ui-fleet-black-25; } } } diff --git a/frontend/components/forms/fields/Dropdown/DropdownOptionTooltipWrapper/_styles.scss b/frontend/components/forms/fields/Dropdown/DropdownOptionTooltipWrapper/_styles.scss index 7fcd24ad33..23bf82f070 100644 --- a/frontend/components/forms/fields/Dropdown/DropdownOptionTooltipWrapper/_styles.scss +++ b/frontend/components/forms/fields/Dropdown/DropdownOptionTooltipWrapper/_styles.scss @@ -1,7 +1,8 @@ // Used with old react-select dropdown and -// New react-select-5 ActionsDropdown.tsx +// New react-select-5: ActionsDropdown.tsx, DropdownWrapper.tsx .Select > .Select-menu-outer, -.actions-dropdown { +.actions-dropdown, +.react-select__option { .is-disabled * { color: $ui-fleet-black-50; } diff --git a/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.stories.tsx b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.stories.tsx new file mode 100644 index 0000000000..ee0d223c58 --- /dev/null +++ b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.stories.tsx @@ -0,0 +1,62 @@ +// stories/DropdownWrapper.stories.tsx + +import React from "react"; +import { Meta, Story } from "@storybook/react"; +import DropdownWrapper, { + IDropdownWrapper, + CustomOptionType, +} from "./DropdownWrapper"; + +// Define metadata for the story +export default { + title: "Components/DropdownWrapper", + component: DropdownWrapper, + argTypes: { + onChange: { action: "changed" }, + }, +} as Meta; + +// Define a template for the stories +const Template: Story = (args) => ( + +); + +// Sample options to be used in the dropdown +const sampleOptions: CustomOptionType[] = [ + { label: "Option 1", value: "option1", helpText: "Help text for option 1" }, + { + label: "Option 2", + value: "option2", + tooltipContent: "Tooltip for option 2", + }, + { label: "Option 3", value: "option3", isDisabled: true }, +]; + +// Default story +export const Default = Template.bind({}); +Default.args = { + options: sampleOptions, + name: "dropdown-example", + label: "Select an option", +}; + +// Disabled story +export const Disabled = Template.bind({}); +Disabled.args = { + ...Default.args, + isDisabled: true, +}; + +// With Help Text story +export const WithHelpText = Template.bind({}); +WithHelpText.args = { + ...Default.args, + helpText: "This is some help text for the dropdown", +}; + +// With Error story +export const WithError = Template.bind({}); +WithError.args = { + ...Default.args, + error: "This is an error message", +}; diff --git a/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tests.tsx b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tests.tsx new file mode 100644 index 0000000000..2715876f8b --- /dev/null +++ b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tests.tsx @@ -0,0 +1,102 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import DropdownWrapper, { CustomOptionType } from "./DropdownWrapper"; + +const sampleOptions: CustomOptionType[] = [ + { + label: "Option 1", + value: "option1", + tooltipContent: "Tooltip 1", + helpText: "Help text 1", + }, + { + label: "Option 2", + value: "option2", + tooltipContent: "Tooltip 2", + helpText: "Help text 2", + }, +]; + +describe("DropdownWrapper Component", () => { + const mockOnChange = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + test("renders with help text", () => { + render( + + ); + + expect(screen.getByText(/test dropdown/i)).toBeInTheDocument(); + expect(screen.getByText(/this is a help text/i)).toBeInTheDocument(); + }); + + test("calls onChange when an option is selected", async () => { + render( + + ); + + // Open the dropdown + await userEvent.click(screen.getByText(/option 1/i)); + + // Select Option 2 + await userEvent.click(screen.getByText(/option 2/i)); + + expect(mockOnChange).toHaveBeenCalledWith({ + helpText: "Help text 2", + label: "Option 2", + tooltipContent: "Tooltip 2", + value: "option2", + }); + }); + + test("renders error message when provided", () => { + render( + + ); + + expect(screen.getByText(/this is an error message/i)).toBeInTheDocument(); + }); + + test("displays no options message when no options are available", async () => { + render( + + ); + + // Open dropdown + await userEvent.click(screen.getByText(/choose option/i)); + + expect(screen.getByText(/no results found/i)).toBeInTheDocument(); + }); +}); diff --git a/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tsx b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tsx new file mode 100644 index 0000000000..51c5c3cd69 --- /dev/null +++ b/frontend/components/forms/fields/DropdownWrapper/DropdownWrapper.tsx @@ -0,0 +1,340 @@ +/** + * This is a new component built off react-select 5.4 + * meant to replace Dropdown.jsx built off react-select 1.3 + * + * See storybook component for current functionality + * + * Prototyped on UserForm.tsx but added and tested the following: + * Options: text, disabled, option helptext, option tooltip + * Other: label text, dropdown help text, dropdown error + */ + +import classnames from "classnames"; +import React from "react"; +import Select, { + StylesConfig, + DropdownIndicatorProps, + OptionProps, + components, + PropsValue, + SingleValue, +} from "react-select-5"; + +import { COLORS } from "styles/var/colors"; +import { PADDING } from "styles/var/padding"; + +import FormField from "components/forms/FormField"; +import DropdownOptionTooltipWrapper from "components/forms/fields/Dropdown/DropdownOptionTooltipWrapper"; +import Icon from "components/Icon"; + +const getOptionBackgroundColor = (state: any) => { + return state.isSelected || state.isFocused + ? COLORS["ui-vibrant-blue-10"] + : "transparent"; +}; + +export interface CustomOptionType { + label: string; + value: string; + tooltipContent?: string; + helpText?: string; + isDisabled?: boolean; +} + +export interface IDropdownWrapper { + options: CustomOptionType[]; + value?: PropsValue | string; + onChange: (newValue: SingleValue) => void; + name: string; + className?: string; + labelClassname?: string; + error?: string; + label?: JSX.Element | string; + helpText?: JSX.Element | string; + isSearchable?: boolean; + isDisabled?: boolean; + placeholder?: string; + menuPortalTarget?: HTMLElement | null; +} + +const baseClass = "dropdown-wrapper"; + +const DropdownWrapper = ({ + options, + value, + onChange, + name, + className, + labelClassname, + error, + label, + helpText, + isSearchable, + isDisabled = false, + placeholder, + menuPortalTarget, +}: IDropdownWrapper) => { + const wrapperClassNames = classnames(baseClass, className); + + const handleChange = (newValue: SingleValue) => { + onChange(newValue); + }; + + // Ability to handle value of type string or CustomOptionType + const getCurrentValue = () => { + if (typeof value === "string") { + return options.find((option) => option.value === value) || null; + } + return value; + }; + + interface CustomOptionProps + extends Omit, "data"> { + data: CustomOptionType; + } + + const CustomOption = (props: CustomOptionProps) => { + const { data, ...rest } = props; + + const optionContent = ( +
+ {data.label} + {data.helpText && ( + {data.helpText} + )} +
+ ); + + return ( + + {data.tooltipContent ? ( + + {optionContent} + + ) : ( + optionContent + )} + + ); + }; + + const CustomDropdownIndicator = ( + props: DropdownIndicatorProps + ) => { + const { isFocused, selectProps } = props; + const color = + isFocused || selectProps.menuIsOpen + ? "core-fleet-blue" + : "core-fleet-black"; + + return ( + + + + ); + }; + + const customStyles: StylesConfig = { + container: (provided) => ({ + ...provided, + width: "100%", + height: "40px", + }), + control: (provided, state) => ({ + ...provided, + display: "flex", + flexDirection: "row", + width: "100%", + backgroundColor: COLORS["ui-off-white"], + paddingLeft: "8px", // TODO: Update to match styleguide of (16px) when updating rest of UI (8px) + paddingRight: "8px", + cursor: "pointer", + boxShadow: "none", + borderRadius: "4px", + borderColor: state.isFocused + ? COLORS["core-fleet-blue"] + : COLORS["ui-fleet-black-10"], + "&:hover": { + boxShadow: "none", + borderColor: COLORS["core-fleet-blue"], + ".dropdown-wrapper__single-value": { + color: COLORS["core-vibrant-blue-over"], + }, + ".dropdown-wrapper__indicator path": { + stroke: COLORS["core-vibrant-blue-over"], + }, + }, + // When tabbing + // Relies on --is-focused for styling as &:focus-visible cannot be applied + "&.dropdown-wrapper__control--is-focused": { + ".dropdown-wrapper__single-value": { + color: COLORS["core-vibrant-blue-over"], + }, + ".dropdown-wrapper__indicator path": { + stroke: COLORS["core-vibrant-blue-over"], + }, + }, + ...(state.isDisabled && { + ".dropdown-wrapper__single-value": { + color: COLORS["ui-fleet-black-50"], + }, + ".dropdown-wrapper__indicator path": { + stroke: COLORS["ui-fleet-black-50"], + }, + }), + "&:active": { + ".dropdown-wrapper__single-value": { + color: COLORS["core-vibrant-blue-down"], + }, + ".dropdown-wrapper__indicator path": { + stroke: COLORS["core-vibrant-blue-down"], + }, + }, + ...(state.menuIsOpen && { + ".dropdown-wrapper__indicator svg": { + transform: "rotate(180deg)", + transition: "transform 0.25s ease", + }, + }), + }), + singleValue: (provided) => ({ + ...provided, + fontSize: "16px", + margin: 0, + padding: 0, + }), + dropdownIndicator: (provided) => ({ + ...provided, + display: "flex", + padding: "2px", + svg: { + transition: "transform 0.25s ease", + }, + }), + menu: (provided) => ({ + ...provided, + boxShadow: "0 2px 6px rgba(0, 0, 0, 0.1)", + borderRadius: "4px", + zIndex: 6, + overflow: "hidden", + border: 0, + marginTop: 0, + maxHeight: "none", + position: "absolute", + left: "0", + animation: "fade-in 150ms ease-out", + }), + menuList: (provided) => ({ + ...provided, + padding: PADDING["pad-small"], + }), + valueContainer: (provided) => ({ + ...provided, + padding: 0, + }), + option: (provided, state) => ({ + ...provided, + padding: "10px 8px", + fontSize: "14px", + backgroundColor: getOptionBackgroundColor(state), + color: COLORS["core-fleet-black"], + "&:hover": { + backgroundColor: state.isDisabled + ? "transparent" + : COLORS["ui-vibrant-blue-10"], + }, + "&:active": { + backgroundColor: state.isDisabled + ? "transparent" + : COLORS["ui-vibrant-blue-10"], + }, + ...(state.isDisabled && { + color: COLORS["ui-fleet-black-50"], + fontStyle: "italic", + cursor: "not-allowed", + pointerEvents: "none", + }), + // Styles for custom option + ".dropdown-wrapper__option": { + display: "flex", + flexDirection: "column", + gap: "8px", + width: "100%", + }, + ".dropdown-wrapper__help-text": { + fontSize: "12px", + whiteSpace: "normal", + color: COLORS["ui-fleet-black-50"], + fontStyle: "italic", + }, + }), + menuPortal: (base) => ({ ...base, zIndex: 999 }), // Not hidden beneath scrollable sections + noOptionsMessage: (provided) => ({ + ...provided, + textAlign: "left", + fontSize: "14px", + padding: "10px 8px", + }), + }; + + const renderLabel = () => { + const labelWrapperClasses = classnames( + `${baseClass}__label`, + labelClassname, + { + [`${baseClass}__label--error`]: !!error, + [`${baseClass}__label--disabled`]: isDisabled, + } + ); + + if (!label) { + return ""; + } + + return ( + + ); + }; + + return ( + + + classNamePrefix="react-select" + isSearchable={isSearchable} + styles={customStyles} + options={options} + components={{ + Option: CustomOption, + DropdownIndicator: CustomDropdownIndicator, + IndicatorSeparator: () => null, + }} + value={getCurrentValue()} + onChange={handleChange} + isDisabled={isDisabled} + menuPortalTarget={ + menuPortalTarget === undefined ? document.body : menuPortalTarget + } + noOptionsMessage={() => "No results found"} + tabIndex={isDisabled ? -1 : 0} // Ensures disabled dropdown has no keyboard accessibility + placeholder={placeholder} + /> + + ); +}; + +export default DropdownWrapper; diff --git a/frontend/components/forms/fields/DropdownWrapper/_styles.scss b/frontend/components/forms/fields/DropdownWrapper/_styles.scss new file mode 100644 index 0000000000..2a92f5ced6 --- /dev/null +++ b/frontend/components/forms/fields/DropdownWrapper/_styles.scss @@ -0,0 +1,14 @@ +// react-select's