**Related issue:**
Ref #34797
Ref #42675
## Problem
When a software installer spec has no `hash_sha256`, Fleet re-downloads
the package, re-extracts metadata, and re-upserts the DB on every GitOps
run, even if the upstream file hasn't changed. For deployments with 50+
URL-only packages across multiple teams, this wastes bandwidth and
processing time on every run.
## Solution
By default, use etags to avoid unnecessary downloads:
1. First run: Fleet downloads the package normally and stores the
server's ETag header
2. Subsequent runs: Fleet sends a conditional GET with `If-None-Match`.
If the server returns 304 Not Modified, Fleet skips the download,
metadata extraction, S3 upload, and DB upsert entirely
Opt-out with `always_download:true`, meaning packages continue to be
downloaded and re-processed on every run, same as today. No UI changes
needed.
```yaml
url: https://nvidia.gpcloudservice.com/global-protect/getmsi.esp?version=64&platform=windows
always_download: true
install_script:
path: install.ps1
```
### Why conditional GET instead of HEAD
Fleet team [analysis of 276 maintained
apps](https://github.com/fleetdm/fleet/pull/42216#issuecomment-4105430061)
showed 7 apps where HEAD requests fail (405, 403, timeout) but GET works
for all. Conditional GET eliminates that failure class: if the server
doesn't support conditional requests, it returns 200 with the full body,
same as today.
### Why opt-in
5 of 276 apps (1.8%) have stale ETags (content changes but ETag stays
the same), caused by CDN caching artifacts (CloudFront, Cloudflare,
nginx inode-based ETags). The `cache` key lets users opt in per package
for URLs where they've verified ETag behavior is correct.
Validation rejects `always_download: true` when hash_sha256` is set
## Changes
- New YAML field: `cache` (bool, package-level)
- New migration: `http_etag` VARCHAR(512) column (explicit
`utf8mb4_unicode_ci` collation) + composite index `(global_or_team_id,
url(255))` on `software_installers`
- New datastore method: `GetInstallerByTeamAndURL`
- `downloadURLFn` accepts optional `If-None-Match` header, returns 304
as `(resp, nil, nil)` with `http.NoBody`
- ETag validated per RFC 7232 (ASCII printable only, no control chars,
max 512 bytes) at both write and read time
- Cache skipped for `.ipa` packages (multi-platform extraInstallers)
- TempFileReader and HTTP response leak prevention on download retry
- Docs updated in `yaml-files.md`
## What doesn't change
- Packages with `hash_sha256`: existing hash-based skip, untouched
- FMA packages: FMA version cache, untouched
- Packages with `always_download: true`: identical to current behavior
- Fleet UI: no changes
## Test plan
Automated testing:
- [x] 16 unit tests for `validETag`
- [x] 8 unit tests for conditional GET behavior (304, 200, 403, 500,
weak ETag, S3 multipart, no ETag)
- [x] MySQL integration test for `GetInstallerByTeamAndURL`
- [x] All 23 existing `TestSoftwareInstallers` datastore tests pass
- [x] All existing service tests pass
Manual testing:
- [x] E2E: 86 packages across 6 CDN patterns, second apply shows 51
conditional hits (304)
- [x] @sgress454 used a local fileserver tool to test w/ a new instance
and dummy packages
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* ETag-based conditional downloads to skip unchanged remote installer
files.
* New always_download flag to force full re-downloads.
* **Tests**
* Added integration and unit tests covering conditional GETs, ETag
validation, retries, edge cases, and payload behavior.
* **Chores**
* Persist HTTP ETag and related metadata; DB migration and index to
speed installer lookups.
* Added installer lookup by team+URL to support conditional download
flow.
* **Bug Fix**
* Rejects using always_download together with an explicit SHA256 in
uploads.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Scott Gress <scott@fleetdm.com>
Co-authored-by: Scott Gress <scott@pigandcow.com>
Co-authored-by: Ian Littman <iansltx@gmail.com>
Fixes#40785
## Summary
When a GitOps run includes a `volume_purchasing_program` config that
references a team that doesn't exist yet, the code temporarily removes
the entire VPP config from the global AppConfig, clearing ALL VPP
token-to-team assignments on the server. However, the code only deferred
`app_store_apps` for the missing teams, not for existing teams that also
lost their VPP assignments. Those existing teams then failed with "No
available VPP Token" when their `app_store_apps` were applied.
The fix widens the deferral scope to match the clearing scope. When VPP
assignments are temporarily cleared, `app_store_apps` are now deferred
for all teams in the VPP config, not just the missing ones.
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#42180
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Enhanced GitOps exception handling for labels, secrets, and software
with clearer enforcement and omission semantics.
* Server-side prefetch of team software so omitted team software can
preserve existing installers during validation.
* Presence flags track whether top-level keys (labels, secrets,
software) were provided versus omitted.
* **Behavior Changes**
* Omitted vs empty sections are now distinguished: omission can mean
“no-op” or “delete-all” depending on exception settings.
* GitOps YAML can define and manage labels directly; validations now
reject YAML that includes keys marked as excepted.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [X] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
## Testing
- [X] Added/updated automated tests
- [X] QA'd all new/changed functionality manually
* **Labels**
- [ ] Validated that with label exceptions off, omitting `labels:` key
from default.yml clears all global labels
- [ ] Validated that with label exceptions off, omitting `labels:` key
from a fleet .yml clears all labels for that fleet
- [ ] Validated that with label exceptions off, setting empty `labels:`
key from default.yml clears all global labels
- [ ] Validated that with label exceptions off, setting empty `labels:`
key from a fleet .yml clears all labels for that fleet
- [ ] Validated that with label exceptions on, omitting `labels:` key
from default .yml leaves existing global labels as-is
- [ ] Validated that with label exceptions on, omitting `labels:` key
from a fleet .yml leaves existing labels as-is
- [ ] Validated that with label exceptions on, setting `labels:` key on
default .yml generates an error
- [ ] Validated that with label exceptions on, setting `labels:` key on
a fleet .yml generates an error
- [ ] Validated that with label exceptions on, a policy using
`labels_include_any` referencing an existing label succeeds without
`labels:` key
- [ ] Validated that with label exceptions on, a query using
`labels_include_any` referencing an existing label succeeds without
`labels:` key
- [ ] Validated that with label exceptions on, an MDM profile using
`labels_include_any` referencing an existing label succeeds without
`labels:` key
- [ ] Validated that with label exceptions on, a software package using
`labels_include_any` referencing an existing label succeeds without
`labels:` key (requires software exceptions off)
- [ ] Validated that with label exceptions on, an app store app using
`labels_include_any` referencing an existing label succeeds without
`labels:` key (requires software exceptions off)
- [ ] Validated that with label exceptions on, a fleet maintained app
using `labels_include_any` referencing an existing label succeeds
without `labels:` key (requires software exceptions off)
* **Secrets**
- [ ] Validated that with secrets exceptions off, omitting `secrets:`
key from default.yml clears all global secrets
- [ ] Validated that with secrets exceptions off, omitting `secrets:`
key from a fleet .yml clears all secrets for that fleet
- [ ] Validated that with secrets exceptions on, omitting `secrets:` key
from default .yml leaves existing global secrets as-is
- [ ] Validated that with secrets exceptions on, omitting `secrets:` key
from a fleet .yml leaves existing secrets as-is
- [ ] Validated that with secrets exceptions on, setting `secrets:` key
on default .yml generates an error
- [ ] Validated that with secrets exceptions on, setting `secrets:` key
on a fleet .yml generates an error
* **Software**
- [ ] Validated that with software exceptions off, omitting `software:`
key from no-team.yml/unassigned.yml clears all software for "no team"
- [ ] Validated that with software exceptions off, omitting `software:`
key from a fleet .yml clears all software for that fleet
- [ ] Validated that with software exceptions off, setting empty
`software:` key on a fleet .yml clears all software for that fleet
- [ ] Validated that with software exceptions off, setting empty
`software:` key on no-team.yml/unassigned.yml clears all software for
"no team
- [ ] Validated that with software exceptions on, omitting `software:`
key from a fleet .yml leaves existing software as-is
- [ ] Validated that with software exceptions on, setting `software:`
key on a fleet .yml generates an error
- [ ] Validated that with software exceptions on, omitting `software:`
key from no-team.yml/unassigned.yml leaves existing software as-is for
"no team"
- [ ] Validated that with software exceptions on, setting `software:`
key on no-team.yml/unassigned.yml generates an error
- [ ] Validated that with software exceptions on, a policy using
`install_software.hash_sha256` referencing an existing package succeeds
without `software:` key
- [ ] Validated that with software exceptions on, a policy using
`install_software.app_store_id` referencing an existing VPP app succeeds
without `software:` key
- [ ] Validated that with software exceptions on, a patch policy using
`fleet_maintained_app_slug` referencing an existing FMA succeeds without
`software:` key
- [ ] Validated that with software exceptions on,
`setup_experience.software` referencing existing software succeeds
without `software:` key (server-side validation fallback)
- [ ] Validated that with software exceptions on, omitting `software:`
from no-team.yml/unassigned.yml preserves existing no-team software
- [ ] Validated that with software exceptions on, a policy in
no-team.yml/unassigned.yml using `install_software.hash_sha256`
referencing existing no-team software succeeds without `software:` key
For unreleased bug fixes in a release candidate, one of:
- [X] Confirmed that the fix is not expected to adversely impact load
test results
I don't think so. There is a bit of overhead when this feature is used
since we have to fetch software from the server, but it would be done in
a specific test, so even if there is an impact it should affect existing
load testing, only new, specific tests.
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#40721
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects
## Testing
- [x] Added/updated automated tests
- [x] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)
- [ ] QA'd all new/changed functionality manually
I (Martin) did test `labels_include_all` for FMA, custom installer, IPA
and VPP apps, and it seemed to all work great for gitops apply and
gitops generate, **except for VPP apps** which seem to have 2 important
pre-existing bugs, see
https://github.com/fleetdm/fleet/issues/40723#issuecomment-4041780707
## New Fleet configuration settings
- [ ] Verified that the setting is exported via `fleetctl
generate-gitops`
- [ ] Verified the setting is documented in a separate PR to [the GitOps
documentation](https://github.com/fleetdm/fleet/blob/main/docs/Configuration/yaml-files.md#L485)
- [ ] Verified that the setting is cleared on the server if it is not
supplied in a YAML file (or that it is documented as being optional)
- [ ] Verified that any relevant UI is disabled when GitOps mode is
enabled
---------
Co-authored-by: Jahziel Villasana-Espinoza <jahziel@fleetdm.com>
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#40621
# Details
This PR updates the output from `fleetctl gitops` and `fleetctl apply`
to use the correct terminology:
* "fleet" instead of "team"
* "report" instead of "query" (where appropriate)
* "for unassigned hosts" in place of "No Team" where possible, and "for
fleet Unassigned" otherwise.
All changes other than tests are in `client.go` and are text-only; no
functional code is changed (and no code relies on the strings besides
tests).
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [ ] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
n/a
## Testing
- [X] Added/updated automated tests
- [ ] QA'd all new/changed functionality manually
I did a `fleetctl generate-gitops` and a `fleetctl gitops` run and saw
the updated logs:
```
[+] would've applied EULA
[+] would've applied certificate authorities
[+] would've applied fleet config
[+] would've applied MDM profiles
[+] would've applied enroll secrets
[+] would've applied 1 report
[+] would've applied 1 software package for fleet 'Unassigned'
[+] would've applied 0 app store apps for fleet 'Unassigned'
[+] would've applied webhook settings for unassigned hosts
[+] would've applied 1 policy
[!] gitops dry run succeeded
```
but I did not go through and try and replicate every log or error
message. I think the best we can do on this one is eyeball the code
changes for mistakes.
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#38431
Attempts to get the ManualAgentInstall config from `ds.TeamLite` in the
case of software installers. In the VPP case, it attempts to get it from
either `ds.TeamByName` or from global `ds.AppConfig` if no team name is
provided.
I had to add some mock functions for TeamLite that were missing, or
missing for the team 0 case. Also added
FLEET_INTEGRATION_TESTS_DISABLE_LOG to disable logs in gitops
integration tests.
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
- [ ] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [ ] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes
## Testing
- [x] Added/updated automated tests
- [ ] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)
- [x] QA'd all new/changed functionality manually
Resolves#37464.
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
## Testing
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
## New Fleet configuration settings
- [x] Setting(s) is/are explicitly excluded from GitOps
Resolves#32481 for Fleet server-side work.
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
## Testing
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#35554
- Setup experience is generated to and can be set in the GitOps yaml
- No changes to policy creation, setup experience apps are still added
as `PREINSTALLED`
- API change: `GET /fleet/setup_experience/software` modified to be able
to take a comma separated list of platforms, like `GET
/fleet/setup_experience/software` does. Documentation update will be in
another PR.
- Modified `SetTeamVPPApps` to return if setup experience changed so the
function that calls it can create a "setup experience changed" activity.
# Checklist for submitter
## Testing
- [x] Added/updated automated tests
- [ ] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)
- [x] QA'd all new/changed functionality manually
- Used generate-gitops to create a yaml file, edited setup experience
apps with it to test that it applies and creates activities correctly.
- Re-enrolled an Android phone after editing setup experience with
GitOps, all setup experience apps were installed.
Resolves#37104
## Testing
- [X] Added/updated automated tests
- [X] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)
- [ ] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Label validation now enforces team-context constraints for policies,
queries, and MDM profiles.
* Global policies now verify label validity before creation.
* **Bug Fixes**
* Improved label association verification in team-specific
configurations.
* **Tests**
* Added comprehensive test coverage for team label associations,
including label scoping validation and team deletion scenarios.
<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Ian Littman <iansltx@gmail.com>
### Summary
This PR adds support for `.zip` files as Windows Fleet Managed Apps
(FMAs). Zip files on Windows are treated similarly to `.exe` files and
require custom install/uninstall scripts, typically used for AppX/MSIX
packages that are distributed as zip archives.
### Changes
**Backend:**
- Added `.zip` as a supported Windows package type in
`SoftwareInstallerPlatformFromExtension`
- Updated validation to require install/uninstall scripts for zip files
(similar to `.exe` files)
- Added `addZipPackageMetadata` function to handle zip file metadata
extraction
- Updated error messages to include `.zip` in the list of supported file
types
- Enhanced `appExists` validation to detect provisioned AppX packages
(packages that are provisioned for all users but don't show up in the
programs table until a user logs in)
- Added version normalization logic to handle version string differences
(e.g., "11.2.1495.0" vs "11.2.1495")
- Updated winget ingester to recognize zip files and default to user
scope for MSIX/zip installers
**Frontend:**
- Added `.zip` to `windowsPackageTypes` in `package_type.ts`
- Updated `PackageAdvancedOptions` component to require
install/uninstall scripts for zip files
- Added validation rules for zip files in `PackageForm/helpers.tsx`
- Updated help text and tooltips to indicate script requirements for zip
packages
- Updated `software_install_scripts.ts` to include zip in default script
handling
**Maintained Apps:**
- Added Microsoft Company Portal as a maintained app example using zip
files
- Created install/uninstall PowerShell scripts that:
- Extract zip files containing AppX packages
- Provision packages for all future users (works in headless
environments)
- Install packages for the current user
- Handle both provisioned and installed package detection
**Tests:**
- Updated integration tests to include `.zip` in supported file type
error messages
- Updated unit tests for `SoftwareInstallerPlatformFromExtension` and
`SofwareInstallerSourceFromExtensionAndName`
### Use Case
This enables distribution of Windows AppX/MSIX packages that are
packaged as zip files (common for Microsoft Store apps like Company
Portal). The implementation handles both provisioned packages (for all
users) and user-installed packages, ensuring detection works in both
headless and interactive environments.
### Testing
- Updated existing tests to include zip file support
- Added Microsoft Company Portal as a maintained app example with full
install/uninstall scripts
---
**Note:** This PR follows the same pattern as `.exe` file support,
requiring custom install/uninstall scripts since zip files can contain
various content types that need custom handling.
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves#36700
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
## Testing
- [x] Added/updated automated tests
- [x] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)
- [x] QA'd all new/changed functionality manually
For unreleased bug fixes in a release candidate, one of:
- [x] Confirmed that the fix is not expected to adversely impact load
test results
## New Fleet configuration settings
- [x] Verified that the setting is exported via `fleetctl
generate-gitops`
- [x] Verified that the setting is cleared on the server if it is not
supplied in a YAML file (or that it is documented as being optional)
<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** For #35460
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
## Testing
- [X] Added/updated automated tests
- [X] QA'd all new/changed functionality manually
For #30849.
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
## Testing
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
## New Fleet configuration settings
- [n/a] Verified that the setting is exported via `fleetctl
generate-gitops`
- [x] Verified the setting is documented in a separate PR to [the GitOps
documentation](https://github.com/fleetdm/fleet/blob/main/docs/Configuration/yaml-files.md#L485)
- [x] Verified that the setting is cleared on the server if it is not
supplied in a YAML file (or that it is documented as being optional)
- [x] Verified that any relevant UI is disabled when GitOps mode is
enabled
For #30095.
#32482 is additional cleanup. Merging this to unblock orchestration
Linux setup experience work. Code has already been reviewed prior to
merging into the feature branch.
---------
Co-authored-by: Konstantin Sykulev <konst@sykulev.com>
Co-authored-by: Anthony Maxwell <133805840+Illbjorn@users.noreply.github.com>
Fixes#30853
Install and uninstall scripts that contain fleet secrets do not need to
be validated in the `batchSetSoftwareInstallersEndpoint` during gitops
dry runs. These secrets are already validated on the gitops side.
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
## Testing
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually