diff --git a/.github/ISSUE_TEMPLATE/release-qa.md b/.github/ISSUE_TEMPLATE/release-qa.md
index 19bf5104eec4..5d77f255fc94 100644
--- a/.github/ISSUE_TEMPLATE/release-qa.md
+++ b/.github/ISSUE_TEMPLATE/release-qa.md
@@ -118,6 +118,15 @@ Smoke tests are limited to core functionality and serve as a pre-release final r
7. Verify scripts display correctly in Activity feed.
pass/fail
+
Software
Verify software library and install / download
+
+1. Verify software library upload/download/delete.
+2. From Host details (Windows and macOS) run an install that should PASS, verify.
+3. From My Device (Windows and macOS) software tab should have self-service items available, verify.
+4. Verify UI loading state and statuses for installing software.
+6. Verify software installs display correctly in Activity feed.
+
pass/fail
+
OS settings
Verify OS settings functionality
1. Verify able to configure Disk encryption.
diff --git a/.github/ISSUE_TEMPLATE/story.md b/.github/ISSUE_TEMPLATE/story.md
index cac409d4e394..3f01ffd97920 100644
--- a/.github/ISSUE_TEMPLATE/story.md
+++ b/.github/ISSUE_TEMPLATE/story.md
@@ -32,16 +32,17 @@ What else should contributors [keep in mind](https://fleetdm.com/handbook/compan
## Changes
### Product
+- [ ] Reference documentation changes: TODO
- [ ] UI changes: TODO
-- [ ] CLI usage changes: TODO
+- [ ] CLI (fleetctl) usage changes: TODO
+- [ ] YAML changes: TODO
- [ ] REST API changes: TODO
- [ ] Fleet's agent (fleetd) changes: TODO
- [ ] Permissions changes: TODO
- [ ] Changes to paid features or tiers: TODO
### Engineering
-- [ ] Reference documentation changes: TODO
-- [ ] Usage documentation changes: TODO
+- [ ] Feature guide changes: TODO
- [ ] Database schema migrations: TODO
- [ ] Load testing: TODO
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index d2cc7966af7f..cf6e9c46e2d8 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -5,16 +5,18 @@ 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://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information.
+ See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/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)
- [ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features.
- [ ] Added/updated tests
+- [ ] If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes
- [ ] If database migrations are included, checked table schema to confirm autoupdate
- For database migrations:
- [ ] Checked schema for all modified table for columns that will auto-update timestamps during migration.
- [ ] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects.
- [ ] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`).
- [ ] Manual QA for all new/changed functionality
- - For Orbit and Fleet Desktop changes:
- - [ ] Manual QA must be performed in the three main OSs, macOS, Windows and Linux.
- - [ ] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).
+- For Orbit and Fleet Desktop changes:
+ - [ ] Orbit runs on macOS, Linux and Windows. Check if the orbit feature/bugfix should only apply to one platform (`runtime.GOOS`).
+ - [ ] Manual QA must be performed in the three main OSs, macOS, Windows and Linux.
+ - [ ] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).
diff --git a/.github/workflows/build-and-push-fleetctl-docker.yml b/.github/workflows/build-and-check-fleetctl-docker-and-deps.yml
similarity index 51%
rename from .github/workflows/build-and-push-fleetctl-docker.yml
rename to .github/workflows/build-and-check-fleetctl-docker-and-deps.yml
index 8ae3c7069e2d..ff20260409ac 100644
--- a/.github/workflows/build-and-push-fleetctl-docker.yml
+++ b/.github/workflows/build-and-check-fleetctl-docker-and-deps.yml
@@ -1,13 +1,14 @@
-name: Build and push fleetdm/fleetctl Docker image
+name: Build fleetctl docker dependencies and check vulnerabilities
-# Manually trigger this workflow for now
on:
workflow_dispatch:
inputs:
image_tag:
- description: 'Docker image tag'
+ description: "Docker image tag"
required: true
type: string
+ schedule:
+ - cron: "0 6 * * *"
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
@@ -23,7 +24,7 @@ permissions:
contents: read
jobs:
- docker-push:
+ build-and-check:
runs-on: ubuntu-latest
environment: Docker Hub
permissions:
@@ -46,25 +47,46 @@ jobs:
- name: Set up Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ vars.GO_VERSION }}
+ go-version-file: 'go.mod'
- name: Install Go Dependencies
run: make deps-go
+ - name: Build fleetdm/wix
+ run: make wix-docker
+
+ - name: Build fleetdm/bomutils
+ run: make bomutils-docker
+
- name: Build fleetdm/fleetctl
run: make fleetctl-docker
- - name: Push to Docker
- run: |
- docker tag fleetdm/fleetctl fleetdm/fleetctl:${{ inputs.image_tag }}
- docker push fleetdm/fleetctl:${{ inputs.image_tag }}
+ - name: Run Trivy vulnerability scanner on fleetdm/wix
+ uses: aquasecurity/trivy-action@6e7b7d1fd3e4fef0c5fa8cce1229c54b2c9bd0d8
+ with:
+ image-ref: "fleetdm/wix"
+ format: "table"
+ exit-code: "1"
+ ignore-unfixed: true
+ vuln-type: "os,library"
+ severity: "CRITICAL"
+
+ - name: Run Trivy vulnerability scanner on fleetdm/bomutils
+ uses: aquasecurity/trivy-action@6e7b7d1fd3e4fef0c5fa8cce1229c54b2c9bd0d8
+ with:
+ image-ref: "fleetdm/bomutils"
+ format: "table"
+ exit-code: "1"
+ ignore-unfixed: true
+ vuln-type: "os,library"
+ severity: "CRITICAL"
- - name: Push To quay.io
- id: push-to-quay
- uses: redhat-actions/push-to-registry@9986a6552bc4571882a4a67e016b17361412b4df # v2.7.1
+ - name: Run Trivy vulnerability scanner on fleetdm/fleetctl
+ uses: aquasecurity/trivy-action@6e7b7d1fd3e4fef0c5fa8cce1229c54b2c9bd0d8
with:
- image: fleetdm/fleetctl
- tags: ${{ inputs.image_tag }}
- registry: quay.io/
- username: fleetdm+fleetreleaser
- password: ${{ secrets.QUAY_REGISTRY_PASSWORD }}
+ image-ref: "fleetdm/fleetctl"
+ format: "table"
+ exit-code: "1"
+ ignore-unfixed: true
+ vuln-type: "os,library"
+ severity: "CRITICAL"
diff --git a/.github/workflows/build-binaries.yaml b/.github/workflows/build-binaries.yaml
index ed18437c74a5..278f958b2842 100644
--- a/.github/workflows/build-binaries.yaml
+++ b/.github/workflows/build-binaries.yaml
@@ -29,10 +29,13 @@ jobs:
with:
egress-policy: audit
+ - name: Checkout Code
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
- name: Install Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ vars.GO_VERSION }}
+ go-version-file: 'go.mod'
# Set the Node.js version
- name: Set up Node.js ${{ vars.NODE_VERSION }}
@@ -40,9 +43,6 @@ jobs:
with:
node-version: ${{ vars.NODE_VERSION }}
- - name: Checkout Code
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
-
- name: JS Dependency Cache
id: js-cache
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # v2
diff --git a/.github/workflows/build-orbit.yaml b/.github/workflows/build-orbit.yaml
index 09f296aece9e..002d2657f6ed 100644
--- a/.github/workflows/build-orbit.yaml
+++ b/.github/workflows/build-orbit.yaml
@@ -59,7 +59,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ vars.GO_VERSION }}
+ go-version-file: 'go.mod'
- name: Build, codesign and notarize orbit
run: go run ./orbit/tools/build/build.go
diff --git a/.github/workflows/check-automated-doc.yml b/.github/workflows/check-automated-doc.yml
index c654e7ae4fdc..d289c55318de 100644
--- a/.github/workflows/check-automated-doc.yml
+++ b/.github/workflows/check-automated-doc.yml
@@ -36,15 +36,16 @@ jobs:
with:
egress-policy: audit
- - name: Install Go
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
- with:
- go-version: ${{ vars.GO_VERSION }}
- name: Checkout Code
uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
with:
fetch-depth: 0
+ - name: Install Go
+ uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
+ with:
+ go-version-file: 'go.mod'
+
- name: Verify golang generated documentation is up-to-date
run: |
make generate-doc
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 246c6418a170..c69888f874db 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -56,7 +56,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ vars.GO_VERSION }}
+ go-version-file: 'go.mod'
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
diff --git a/.github/workflows/deploy-bulk-operations-dashboard.yml b/.github/workflows/deploy-bulk-operations-dashboard.yml
new file mode 100644
index 000000000000..090e409fac3d
--- /dev/null
+++ b/.github/workflows/deploy-bulk-operations-dashboard.yml
@@ -0,0 +1,89 @@
+name: Deploy app to bulk operations dashboard pipeline on Heroku.
+
+on:
+ push:
+ branches: [ main ]
+ paths:
+ - 'ee/bulk-operations-dashboard/**'
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ permissions:
+ contents: write # for Git to git push
+ if: ${{ github.repository == 'fleetdm/fleet' }}
+
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [14.x]
+
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
+ with:
+ egress-policy: audit
+
+ - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
+ # Configure our access credentials for the Heroku CLI
+ - uses: akhileshns/heroku-deploy@79ef2ae4ff9b897010907016b268fd0f88561820 # v3.6.8
+ with:
+ heroku_api_key: ${{secrets.HEROKU_API_TOKEN_FOR_BOT_USER}}
+ heroku_app_name: "" # this has to be blank or it doesn't work
+ heroku_email: ${{secrets.HEROKU_EMAIL_FOR_BOT_USER}}
+ justlogin: true
+ - run: heroku auth:whoami
+
+ # Set the Node.js version
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ # Now start building!
+ # > …but first, get a little crazy for a sec and delete the top-level package.json file
+ # > i.e. the one used by the Fleet server. This is because require() in node will go
+ # > hunting in ancestral directories for missing dependencies, and since some of the
+ # > bundled transpiler tasks sniff for package availability using require(), this trips
+ # > up when it encounters another Node universe in the parent directory.
+ - run: rm -rf package.json package-lock.json node_modules/
+ # > Turns out there's a similar issue with how eslint plugins are looked up, so we
+ # > delete the top level .eslintrc file too.
+ - run: rm -f .eslintrc.js
+ # > And, as a change to the top-level fleetdm/fleet .gitignore on May 2, 2022 revealed,
+ # > we also need to delete the top level .gitignore file too, so that its rules don't
+ # > interfere with the committing and force-pushing we're doing as part of our deploy
+ # > script here. For more info, see: https://github.com/fleetdm/fleet/pull/5549
+ - run: rm -f .gitignore
+
+ # Get dependencies (including dev deps)
+ - run: cd ee/bulk-operations-dashboard/ && npm install
+
+ # Run sanity checks
+ - run: cd ee/bulk-operations-dashboard/ && npm test
+
+ # Compile assets
+ - run: cd ee/bulk-operations-dashboard/ && npm run build-for-prod
+
+ # Commit newly-built assets locally so we can push them to Heroku below.
+ # (This commit will never be pushed to GitHub- only to Heroku.)
+ # > The local config flags make this work in GitHub's environment.
+ - run: git add ee/bulk-operations-dashboard/.www
+ - run: git -c "user.name=GitHub" -c "user.email=github@example.com" commit -am 'AUTOMATED COMMIT - Deployed the latest, including modified HTML layouts and .sailsrc file that reference minified assets.'
+
+ # Configure the Heroku app we'll be deploying to
+ - run: heroku git:remote -a bulk-operations-dashboard
+ - run: git remote -v
+
+ # Deploy to Heroku (by pushing)
+ # > Since a shallow clone was grabbed, we have to "unshallow" it before forcepushing.
+ - run: echo "Unshallowing local repository…"
+ - run: git fetch --prune --unshallow
+ - run: echo "Deploying branch '${GITHUB_REF##*/}' to Heroku…"
+ - run: git push heroku +${GITHUB_REF##*/}:master
+ - name: 🌐 The dashboard has been deployed
+ run: echo '' && echo '--' && echo 'OK, done. It should be live momentarily.' && echo '(if you get impatient, check the Heroku dashboard for status)'
diff --git a/.github/workflows/deploy-fleet-website.yml b/.github/workflows/deploy-fleet-website.yml
index 9fc044e13b3f..371a0014f075 100644
--- a/.github/workflows/deploy-fleet-website.yml
+++ b/.github/workflows/deploy-fleet-website.yml
@@ -64,7 +64,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ vars.GO_VERSION }}
+ go-version-file: 'go.mod'
# Download top-level dependencies and build Storybook in the website's assets/ folder
- run: npm install --legacy-peer-deps && npm run build-storybook -- -o ./website/assets/storybook --loglevel verbose
diff --git a/.github/workflows/dogfood-deploy.yml b/.github/workflows/dogfood-deploy.yml
index 39f6983824eb..f17768eec7c5 100644
--- a/.github/workflows/dogfood-deploy.yml
+++ b/.github/workflows/dogfood-deploy.yml
@@ -51,14 +51,17 @@ jobs:
- id: fail-on-main
run: "false"
if: ${{ github.ref == 'main' }}
+
- uses: aws-actions/configure-aws-credentials@67fbcbb121271f7775d2e7715933280b06314838 # v1.7.0
with:
role-to-assume: ${{env.AWS_IAM_ROLE}}
aws-region: ${{ env.AWS_REGION }}
+
- name: Set up Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ vars.GO_VERSION }}
+ go-version-file: 'go.mod'
+
- uses: hashicorp/setup-terraform@633666f66e0061ca3b725c73b2ec20cd13a8fdd1 # v2.0.3
with:
terraform_version: 1.6.3
@@ -77,6 +80,26 @@ jobs:
id: plan
run: terraform plan -no-color
continue-on-error: true
+ - name: Slack Notification
+ if: success()
+ uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0
+ with:
+ payload: |
+ {
+ "text": "${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head.html_url }}",
+ "blocks": [
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": "🚀 🛠️ Dogfood deploy in progress\nhttps://github.com/fleetdm/fleet/actions/runs/${{ github.run_id }}"
+ }
+ }
+ ]
+ }
+ env:
+ SLACK_WEBHOOK_URL: ${{ secrets.SLACK_G_HELP_ENGINEERING_WEBHOOK_URL }}
+ SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
# first we'll scale everything down and create the new task definitions
- name: Terraform Apply
id: apply
diff --git a/.github/workflows/dogfood-gitops.yml b/.github/workflows/dogfood-gitops.yml
index 68fc00cee17d..487cdac1ad11 100644
--- a/.github/workflows/dogfood-gitops.yml
+++ b/.github/workflows/dogfood-gitops.yml
@@ -69,6 +69,7 @@ jobs:
DOGFOOD_GLOBAL_ENROLL_SECRET: ${{ secrets.DOGFOOD_GLOBAL_ENROLL_SECRET }}
DOGFOOD_SSO_ISSUER_URI: ${{ secrets.DOGFOOD_SSO_ISSUER_URI }}
DOGFOOD_SSO_METADATA: ${{ secrets.DOGFOOD_SSO_METADATA }}
+ DOGFOOD_MDM_SSO_METADATA_URL: ${{ secrets.DOGFOOD_MDM_SSO_METADATA_URL }}
DOGFOOD_FAILING_POLICIES_WEBHOOK_URL: ${{ secrets.DOGFOOD_FAILING_POLICIES_WEBHOOK_URL }}
DOGFOOD_VULNERABILITIES_WEBHOOK_URL: ${{ secrets.DOGFOOD_VULNERABILITIES_WEBHOOK_URL }}
DOGFOOD_WORKSTATIONS_ENROLL_SECRET: ${{ secrets.DOGFOOD_WORKSTATIONS_ENROLL_SECRET }}
@@ -78,5 +79,6 @@ jobs:
DOGFOOD_SERVERS_CANARY_ENROLL_SECRET: ${{ secrets.DOGFOOD_SERVERS_CANARY_ENROLL_SECRET }}
DOGFOOD_EXPLORE_DATA_ENROLL_SECRET: ${{ secrets.DOGFOOD_EXPLORE_DATA_ENROLL_SECRET }}
DOGFOOD_CALENDAR_API_KEY: ${{ secrets.DOGFOOD_CALENDAR_API_KEY }}
- DOGFOOD_VIRTUAL_MACHINES_ENROLL_SECRET: ${{ secrets.DOGFOOD_VIRTUAL_MACHINES_ENROLL_SECRET }}
- DOGFOOD_IPHONES_ENROLL_SECRET: ${{ secrets.DOGFOOD_IPHONES_ENROLL_SECRET }}
+ DOGFOOD_COMPLIANCE_EXCLUSIONS_ENROLL_SECRET: ${{ secrets.DOGFOOD_COMPLIANCE_EXCLUSIONS_ENROLL_SECRET }}
+ DOGFOOD_COMPANY_OWNED_IPHONES_ENROLL_SECRET: ${{ secrets.DOGFOOD_COMPANY_OWNED_IPHONES_ENROLL_SECRET }}
+ DOGFOOD_COMPANY_OWNED_IPADS_ENROLL_SECRET: ${{ secrets.DOGFOOD_COMPANY_OWNED_IPADS_ENROLL_SECRET }}
\ No newline at end of file
diff --git a/.github/workflows/fleet-and-orbit.yml b/.github/workflows/fleet-and-orbit.yml
index 6c222b3774fd..f4dfb2780eb7 100644
--- a/.github/workflows/fleet-and-orbit.yml
+++ b/.github/workflows/fleet-and-orbit.yml
@@ -62,8 +62,7 @@ jobs:
timeout-minutes: 60
strategy:
matrix:
- go-version: ["${{ vars.GO_VERSION }}"]
- mysql: ["mysql:5.7"]
+ mysql: ["mysql:8.0.36"]
runs-on: ubuntu-latest
needs: gen
steps:
@@ -72,10 +71,13 @@ jobs:
with:
egress-policy: audit
+ - name: Checkout Code
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
- name: Install Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ matrix.go-version }}
+ go-version-file: 'go.mod'
# Set the Node.js version
- name: Set up Node.js ${{ vars.NODE_VERSION }}
@@ -83,9 +85,6 @@ jobs:
with:
node-version: ${{ vars.NODE_VERSION }}
- - name: Checkout Code
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
-
- name: Start tunnel
env:
CERT_PEM: ${{ secrets.CLOUDFLARE_TUNNEL_FLEETUEM_CERT_B64 }}
@@ -111,7 +110,7 @@ jobs:
done
- name: Start Infra Dependencies
- run: FLEET_MYSQL_IMAGE=${{ matrix.mysql }} docker-compose up -d mysql redis &
+ run: FLEET_MYSQL_IMAGE=${{ matrix.mysql }} docker compose up -d mysql redis &
- name: Install JS Dependencies
run: make deps-js
@@ -175,9 +174,6 @@ jobs:
# This job also makes sure the Fleet server is up and running.
set-enroll-secret:
timeout-minutes: 60
- strategy:
- matrix:
- go-version: ["${{ vars.GO_VERSION }}"]
runs-on: ubuntu-latest
needs: gen
steps:
@@ -186,13 +182,13 @@ jobs:
with:
egress-policy: audit
+ - name: Checkout Code
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
- name: Install Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ matrix.go-version }}
-
- - name: Checkout Code
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+ go-version-file: 'go.mod'
- name: Build Fleetctl
run: make fleetctl
@@ -218,9 +214,6 @@ jobs:
# Here we generate the Fleet Desktop and osqueryd targets for
# macOS which can only be generated from a macOS host.
build-macos-targets:
- strategy:
- matrix:
- go-version: ["${{ vars.GO_VERSION }}"]
# Set macOS version to '12' (current equivalent to macos-latest) for
# building the binary. This ensures compatibility with macOS version 13 and
# later, avoiding runtime errors on systems using macOS 13 or newer.
@@ -234,13 +227,13 @@ jobs:
with:
egress-policy: audit
+ - name: Checkout Code
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
- name: Install Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ matrix.go-version }}
-
- - name: Checkout Code
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+ go-version-file: 'go.mod'
- name: Build desktop.app.tar.gz and osqueryd.app.tar.gz
run: |
@@ -269,9 +262,6 @@ jobs:
# installed, and installing it is time consuming and unreliable.
run-tuf-and-gen-pkgs:
timeout-minutes: 60
- strategy:
- matrix:
- go-version: ["${{ vars.GO_VERSION }}"]
runs-on: ubuntu-latest
needs: [gen, build-macos-targets]
steps:
@@ -280,13 +270,13 @@ jobs:
with:
egress-policy: audit
+ - name: Checkout Code
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
- name: Install Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ matrix.go-version }}
-
- - name: Checkout Code
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+ go-version-file: 'go.mod'
- name: Download macos pre-built apps
id: download
@@ -296,7 +286,7 @@ jobs:
- name: Build Repository and run TUF server
env:
- SYSTEMS: "macos windows linux"
+ SYSTEMS: "macos windows linux linux-arm64"
PKG_FLEET_URL: ${{ needs.gen.outputs.address }}
PKG_TUF_URL: http://localhost:8081
DEB_FLEET_URL: ${{ needs.gen.outputs.address }}
diff --git a/.github/workflows/fleetctl-preview-latest.yml b/.github/workflows/fleetctl-preview-latest.yml
index dda4e0f73c2d..630cfd1dc325 100644
--- a/.github/workflows/fleetctl-preview-latest.yml
+++ b/.github/workflows/fleetctl-preview-latest.yml
@@ -53,7 +53,6 @@ jobs:
# - Unattended installation of Docker on macOS fails. (see
# https://github.com/docker/for-mac/issues/6450)
os: [ubuntu-latest]
- go-version: ['${{ vars.GO_VERSION }}']
runs-on: ${{ matrix.os }}
steps:
@@ -62,13 +61,13 @@ jobs:
with:
egress-policy: audit
+ - name: Checkout Code
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
- name: Install Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ matrix.go-version }}
-
- - name: Checkout Code
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+ go-version-file: 'go.mod'
- name: Build Fleetctl
run: make fleetctl
diff --git a/.github/workflows/fleetd-tuf.yml b/.github/workflows/fleetd-tuf.yml
index ebeca889daa1..7641589f102b 100644
--- a/.github/workflows/fleetd-tuf.yml
+++ b/.github/workflows/fleetd-tuf.yml
@@ -30,16 +30,16 @@ jobs:
with:
egress-policy: audit
- - name: Install Go
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
- with:
- go-version: ${{ vars.GO_VERSION }}
-
- name: Checkout Code
uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
with:
fetch-depth: 0
+ - name: Install Go
+ uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
+ with:
+ go-version-file: 'go.mod'
+
- name: Update orbit/TUF.md
run: |
make fleetd-tuf
diff --git a/.github/workflows/generate-desktop-targets.yml b/.github/workflows/generate-desktop-targets.yml
index 9dff22841a75..d7324c9bf0ba 100644
--- a/.github/workflows/generate-desktop-targets.yml
+++ b/.github/workflows/generate-desktop-targets.yml
@@ -24,7 +24,7 @@ defaults:
shell: bash
env:
- FLEET_DESKTOP_VERSION: 1.27.0
+ FLEET_DESKTOP_VERSION: 1.32.0
permissions:
contents: read
@@ -45,13 +45,13 @@ jobs:
with:
egress-policy: audit
+ - name: Checkout
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
- name: Install Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ vars.GO_VERSION }}
-
- - name: Checkout
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+ go-version-file: 'go.mod'
- name: Import signing keys
env:
@@ -98,13 +98,13 @@ jobs:
with:
egress-policy: audit
+ - name: Checkout
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
- name: Install Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ vars.GO_VERSION }}
-
- - name: Checkout
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+ go-version-file: 'go.mod'
- name: Generate fleet-desktop.exe
run: |
@@ -139,21 +139,49 @@ jobs:
with:
egress-policy: audit
+ - name: Checkout
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
- name: Install Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ vars.GO_VERSION }}
+ go-version-file: 'go.mod'
+
+ - name: Generate desktop.tar.gz
+ run: |
+ FLEET_DESKTOP_VERSION=$FLEET_DESKTOP_VERSION \
+ make desktop-linux
+
+ - name: Upload desktop.tar.gz
+ uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # 4.3.3
+ with:
+ name: desktop.tar.gz
+ path: desktop.tar.gz
+
+ desktop-linux-arm64:
+ runs-on: ubuntu-latest
+ steps:
+
+ - name: Harden Runner
+ uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
+ with:
+ egress-policy: audit
- name: Checkout
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+ - name: Install Go
+ uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
+ with:
+ go-version-file: 'go.mod'
+
- name: Generate desktop.tar.gz
run: |
FLEET_DESKTOP_VERSION=$FLEET_DESKTOP_VERSION \
- make desktop-linux
+ make desktop-linux-arm64
- name: Upload desktop.tar.gz
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # 4.3.3
with:
- name: desktop.tar.gz
+ name: desktop-arm64.tar.gz
path: desktop.tar.gz
diff --git a/.github/workflows/generate-osqueryd-targets.yml b/.github/workflows/generate-osqueryd-targets.yml
index f7f377d17d86..b27e074b6a49 100644
--- a/.github/workflows/generate-osqueryd-targets.yml
+++ b/.github/workflows/generate-osqueryd-targets.yml
@@ -24,7 +24,7 @@ defaults:
shell: bash
env:
- OSQUERY_VERSION: 5.12.2
+ OSQUERY_VERSION: 5.13.1
permissions:
contents: read
@@ -76,6 +76,35 @@ jobs:
name: osqueryd
path: opt/osquery/bin/osqueryd
+ generate-linux-arm64:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
+ with:
+ egress-policy: audit
+
+ - name: Checkout
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
+ - name: Install file
+ run: |
+ sudo apt-get install -y file
+
+ - name: Download and extract osqueryd for linux-arm64
+ run: |
+ curl -L https://github.com/osquery/osquery/releases/download/${{ env.OSQUERY_VERSION }}/osquery_${{ env.OSQUERY_VERSION }}-1.linux_arm64.deb --output osquery.deb
+ ar x osquery.deb
+ tar xf data.tar.gz
+ chmod +x ./opt/osquery/bin/osqueryd
+ file ./opt/osquery/bin/osqueryd | grep aarch64
+
+ - name: Upload osqueryd for linux-arm64
+ uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v2
+ with:
+ name: osqueryd-arm64
+ path: opt/osquery/bin/osqueryd
+
generate-windows:
runs-on: windows-latest
steps:
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index df6b9792b73d..3d3e95ed2c8c 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -38,7 +38,6 @@ jobs:
matrix:
# See #9943, we just need to add windows-latest here once all issues are fixed.
os: [ubuntu-latest, macos-latest]
- go-version: ['${{ vars.GO_VERSION }}']
runs-on: ${{ matrix.os }}
steps:
- name: Harden Runner
@@ -52,7 +51,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ matrix.go-version }}
+ go-version-file: 'go.mod'
- name: Install dependencies (Linux)
if: matrix.os == 'ubuntu-latest'
diff --git a/.github/workflows/goreleaser-fleet.yaml b/.github/workflows/goreleaser-fleet.yaml
index 8de0089b62d7..6ba9aff8f08f 100644
--- a/.github/workflows/goreleaser-fleet.yaml
+++ b/.github/workflows/goreleaser-fleet.yaml
@@ -20,7 +20,7 @@ permissions:
jobs:
goreleaser:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-20.04-4-cores
environment: Docker Hub
permissions:
contents: write
@@ -44,7 +44,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ vars.GO_VERSION }}
+ go-version-file: 'go.mod'
# Set the Node.js version
- name: Set up Node.js ${{ vars.NODE_VERSION }}
diff --git a/.github/workflows/goreleaser-orbit.yaml b/.github/workflows/goreleaser-orbit.yaml
index 1ce2386dd907..54e16752b335 100644
--- a/.github/workflows/goreleaser-orbit.yaml
+++ b/.github/workflows/goreleaser-orbit.yaml
@@ -2,8 +2,8 @@ name: GoReleaser Orbit
on:
push:
- tags:
- - 'orbit-*' # For testing, use a pre-release tag like 'orbit-1.24.0-1'
+ tags:
+ - "orbit-*" # For testing, use a pre-release tag like 'orbit-1.24.0-1'
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
@@ -56,7 +56,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ vars.GO_VERSION }}
+ go-version-file: 'go.mod'
- name: Run GoReleaser
run: go run github.com/goreleaser/goreleaser@56c9d09a1b925e2549631c6d180b0a1c2ebfac82 release --debug --rm-dist --skip-publish -f orbit/goreleaser-macos.yml # v1.20.0
@@ -95,7 +95,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ vars.GO_VERSION }}
+ go-version-file: 'go.mod'
- name: Run GoReleaser
run: go run github.com/goreleaser/goreleaser@56c9d09a1b925e2549631c6d180b0a1c2ebfac82 release --debug --rm-dist --skip-publish -f orbit/goreleaser-linux.yml # v1.20.0
@@ -106,6 +106,39 @@ jobs:
name: orbit-linux
path: dist/orbit_linux_amd64_v1/orbit
+ goreleaser-linux-arm64:
+ runs-on: ubuntu-20.04
+ permissions:
+ contents: read
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
+ with:
+ egress-policy: audit
+
+ - name: Checkout
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
+ # Note that goreleaser does not like the orbit- prefixed flag unless you use the closed-source
+ # paid version. We pay for goreleaser, but using the closed source build would weaken our
+ # supply-chain integrity goals, so we hack around it by replacing the tag.
+ - name: Replace tag
+ run: git tag $(echo ${{ github.ref_name }} | sed -e 's/orbit-//g') && git tag -d ${{ github.ref_name }}
+
+ - name: Set up Go
+ uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
+ with:
+ go-version-file: 'go.mod'
+
+ - name: Run GoReleaser
+ run: go run github.com/goreleaser/goreleaser@56c9d09a1b925e2549631c6d180b0a1c2ebfac82 release --debug --rm-dist --skip-publish -f orbit/goreleaser-linux-arm64.yml # v1.20.0
+
+ - name: Upload
+ uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # 4.3.3
+ with:
+ name: orbit-linux-arm64
+ path: dist/orbit_linux_arm64/orbit
+
goreleaser-windows:
runs-on: windows-2022
permissions:
@@ -128,7 +161,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ vars.GO_VERSION }}
+ go-version-file: 'go.mod'
- name: Run GoReleaser
run: go run github.com/goreleaser/goreleaser@56c9d09a1b925e2549631c6d180b0a1c2ebfac82 release --debug --rm-dist --skip-publish -f orbit/goreleaser-windows.yml # v1.20.0
diff --git a/.github/workflows/goreleaser-snapshot-fleet.yaml b/.github/workflows/goreleaser-snapshot-fleet.yaml
index c204e9ff6918..927cf31be1da 100644
--- a/.github/workflows/goreleaser-snapshot-fleet.yaml
+++ b/.github/workflows/goreleaser-snapshot-fleet.yaml
@@ -4,7 +4,7 @@ on:
push:
branches:
- "main"
- - "prepare-*"
+ - "minor-*"
- "patch-*"
paths-ignore:
- "handbook/**"
@@ -57,7 +57,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ vars.GO_VERSION }}
+ go-version-file: 'go.mod'
# Set the Node.js version
- name: Set up Node.js ${{ vars.NODE_VERSION }}
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 015a464b4b84..98c9cd3a5973 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -264,13 +264,13 @@ jobs:
npm install -g fleetctl
fleetctl config set --address ${{ needs.gen.outputs.address }} --token ${{ needs.login.outputs.token }}
+ - name: Checkout Code
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
- name: Install Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ vars.GO_VERSION }}
-
- - name: Checkout Code
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+ go-version-file: 'go.mod'
- name: Build Fleetctl
run: make fleetctl
diff --git a/.github/workflows/push-osquery-perf-to-ecr.yml b/.github/workflows/push-osquery-perf-to-ecr.yml
deleted file mode 100644
index 0760d03900ad..000000000000
--- a/.github/workflows/push-osquery-perf-to-ecr.yml
+++ /dev/null
@@ -1,64 +0,0 @@
-name: Build docker image and publish to ECR
-
-on:
- workflow_dispatch:
- inputs:
- enroll_secret:
- description: 'Enroll Secret'
- required: true
- url:
- description: 'Fleet server URL'
- required: true
- host_count:
- description: 'Amount of hosts to emulate'
- required: true
- default: 20
- tag:
- description: 'docker image tag'
- required: true
- default: latest
-
-# This allows a subsequently queued workflow run to interrupt previous runs
-concurrency:
- group: ${{ github.workflow }}-${{ github.head_ref || github.run_id}}
- cancel-in-progress: true
-
-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
-
-permissions:
- contents: read
-
-jobs:
- build-docker:
- runs-on: ubuntu-latest
- steps:
- - name: Harden Runner
- uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
- with:
- egress-policy: audit
-
- - name: Checkout Code
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
-
- - name: Configure AWS credentials
- uses: aws-actions/configure-aws-credentials@05b148adc31e091bafbaf404f745055d4d3bc9d2 # v1
- with:
- aws-access-key-id: ${{ secrets.LOADTEST_AWS_ACCESS_KEY_ID }}
- aws-secret-access-key: ${{ secrets.LOADTEST_AWS_SECRET_ACCESS_KEY }}
- aws-region: us-east-2
-
- - name: Login to Amazon ECR
- id: login-ecr
- uses: aws-actions/amazon-ecr-login@2f9f10ea3fa2eed41ac443fee8bfbd059af2d0a4 # v1
-
- - name: Build, tag, and push image to Amazon ECR
- env:
- ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
- ECR_REPOSITORY: osquery-perf
- IMAGE_TAG: ${{ github.event.inputs.tag }}
- run: |
- docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG --build-arg ENROLL_SECRET=${{ github.event.inputs.enroll_secret }} --build-arg HOST_COUNT=${{ github.event.inputs.host_count }} --build-arg SERVER_URL=${{ github.event.inputs.url }} -f Dockerfile.osquery-perf .
- docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
diff --git a/.github/workflows/release-fleetctl-docker-deps.yaml b/.github/workflows/release-fleetctl-docker-deps.yaml
new file mode 100644
index 000000000000..c751655d9353
--- /dev/null
+++ b/.github/workflows/release-fleetctl-docker-deps.yaml
@@ -0,0 +1,84 @@
+# Builds and releases to production the fleetdm/bomutils:latest and fleetdm/wix:latest
+# docker images, which are the docker image dependencies of the fleetctl command.
+#
+# This is separate from Fleet releases because we only release
+# fleetdm/bomutils and fleetdm/wix only if we add new dependencies
+# or for security updates.
+name: Release fleetctl docker dependencies
+
+on:
+ push:
+ tags:
+ - "fleetctl-docker-deps-*"
+
+# This allows a subsequently queued workflow run to interrupt previous runs
+concurrency:
+ group: ${{ github.workflow }}-${{ github.head_ref || github.run_id}}
+ cancel-in-progress: true
+
+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
+
+permissions:
+ contents: read
+
+jobs:
+ push_latest:
+ runs-on: ubuntu-latest
+ environment: Docker Hub
+ permissions:
+ contents: write
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
+ with:
+ egress-policy: audit
+
+ - name: Checkout Code
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
+ - name: Install Go
+ uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
+ with:
+ go-version-file: 'go.mod'
+
+ - name: Login to Docker Hub
+ uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }}
+
+ - name: Build fleetdm/wix
+ run: make wix-docker
+
+ - name: Build fleetdm/bomutils
+ run: make bomutils-docker
+
+ #
+ # After fleetdm/wix and fleetdm/bomutils are built,
+ # let's smoke test pkg/msi generation before pushing.
+ #
+
+ - name: Install Go Dependencies
+ run: make deps-go
+
+ - name: Build fleetctl
+ run: make fleetctl
+
+ - name: Build MSI
+ run: ./build/fleetctl package --type msi --enroll-secret=foo --fleet-url=https://localhost:8080
+
+ - name: Build PKG
+ run: ./build/fleetctl package --type pkg --enroll-secret=foo --fleet-url=https://localhost:8080
+
+ #
+ # Now push to production
+ #
+
+ - name: Push fleetdm/bomutils to docker hub
+ run: docker push fleetdm/bomutils:latest
+
+ - name: Push fleetdm/wix to docker hub
+ run: docker push fleetdm/wix:latest
diff --git a/.github/workflows/release-fleetd-base.yml b/.github/workflows/release-fleetd-base.yml
index d7b02cfcf7fa..99099019641c 100644
--- a/.github/workflows/release-fleetd-base.yml
+++ b/.github/workflows/release-fleetd-base.yml
@@ -51,16 +51,16 @@ jobs:
with:
egress-policy: audit
- - name: Install Go
- uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
- with:
- go-version: ${{ vars.GO_VERSION }}
-
- name: Checkout Code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
fetch-depth: 0
+ - name: Install Go
+ uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
+ with:
+ go-version-file: 'go.mod'
+
- name: Check for fleetd component updates
id: check-for-fleetd-component-updates
run: |
diff --git a/.github/workflows/test-bulk-operations-dashboard-changes.yml b/.github/workflows/test-bulk-operations-dashboard-changes.yml
new file mode 100644
index 000000000000..cfbb26205d87
--- /dev/null
+++ b/.github/workflows/test-bulk-operations-dashboard-changes.yml
@@ -0,0 +1,60 @@
+name: Test bulk operations dashboard changes
+
+on:
+ pull_request:
+ paths:
+ - 'ee/bulk-operations-dashboard/**'
+ - '.github/workflows/test-bulk-operations-dashboard-changes.yml'
+
+# This allows a subsequently queued workflow run to interrupt previous runs
+concurrency:
+ group: ${{ github.workflow }}-${{ github.head_ref || github.run_id}}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ permissions:
+ contents: read
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [16.x]
+
+ steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
+ with:
+ egress-policy: audit
+
+ - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
+ # Set the Node.js version
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
+ with:
+ node-version: ${{ matrix.node-version }}
+
+
+ # Now start building!
+ # > …but first, get a little crazy for a sec and delete the top-level package.json file
+ # > i.e. the one used by the Fleet server. This is because require() in node will go
+ # > hunting in ancestral directories for missing dependencies, and since some of the
+ # > bundled transpiler tasks sniff for package availability using require(), this trips
+ # > up when it encounters another Node universe in the parent directory.
+ - run: rm -rf package.json package-lock.json node_modules/
+ # > Turns out there's a similar issue with how eslint plugins are looked up, so we
+ # > delete the top level .eslintrc file too.
+ - run: rm -f .eslintrc.js
+
+ # Get dependencies (including dev deps)
+ - run: cd ee/bulk-operations-dashboard/ && npm install
+
+ # Run sanity checks
+ - run: cd ee/bulk-operations-dashboard/ && npm test
+
+ # Compile assets
+ - run: cd ee/bulk-operations-dashboard/ && npm run build-for-prod
diff --git a/.github/workflows/test-db-changes.yml b/.github/workflows/test-db-changes.yml
index 301645008ece..2bd89ab82b1e 100644
--- a/.github/workflows/test-db-changes.yml
+++ b/.github/workflows/test-db-changes.yml
@@ -10,7 +10,7 @@ on:
paths:
- '**.go'
- 'server/datastore/mysql/schema.sql'
- - '.github/workflows/test-schema-changes.yml'
+ - '.github/workflows/test-db-changes.yml'
workflow_dispatch: # Manual
# This allows a subsequently queued workflow run to interrupt previous runs
@@ -35,18 +35,28 @@ jobs:
with:
egress-policy: audit
- - name: Install Go
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
- with:
- go-version: ${{ vars.GO_VERSION }}
- name: Checkout Code
uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
with:
fetch-depth: 0
+ - name: Install Go
+ uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
+ with:
+ go-version-file: 'go.mod'
+
- name: Start Infra Dependencies
# Use & to background this
- run: docker-compose up -d mysql_test &
+ run: docker compose up -d mysql_test &
+
+ - name: Wait for mysql
+ run: |
+ echo "waiting for mysql..."
+ until docker compose exec -T mysql_test sh -c "mysql -uroot -p\"\${MYSQL_ROOT_PASSWORD}\" -e \"SELECT 1=1\" fleet" &> /dev/null; do
+ echo "."
+ sleep 1
+ done
+ echo "mysql is ready"
- name: Verify test schema changes
run: |
diff --git a/.github/workflows/test-fleetd-chrome.yml b/.github/workflows/test-fleetd-chrome.yml
index 47ba496ebbb7..8cbb0125f9b8 100644
--- a/.github/workflows/test-fleetd-chrome.yml
+++ b/.github/workflows/test-fleetd-chrome.yml
@@ -66,7 +66,8 @@ jobs:
npm test
- name: Upload to Codecov
- uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # v3.1.1
+ uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
with:
+ token: ${{ secrets.CODECOV_TOKEN }}
directory: ./ee/fleetd-chrome/coverage
flags: fleetd-chrome
diff --git a/.github/workflows/test-go.yaml b/.github/workflows/test-go.yaml
index c288cf8c9b4b..b5f2b8fe943c 100644
--- a/.github/workflows/test-go.yaml
+++ b/.github/workflows/test-go.yaml
@@ -44,8 +44,8 @@ jobs:
matrix:
suite: ["integration", "core"]
os: [ubuntu-latest]
- go-version: ['${{ vars.GO_VERSION }}']
- mysql: ["mysql:5.7.21", "mysql:8.0.28"]
+ mysql: ["mysql:8.0.36", "mysql:8.4.2"]
+ continue-on-error: ${{ matrix.suite == 'integration' }} # Since integration tests have a higher chance of failing, often for unrelated reasons, we don't want to fail the whole job if they fail
runs-on: ${{ matrix.os }}
env:
@@ -58,18 +58,18 @@ jobs:
with:
egress-policy: audit
- - name: Install Go
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
- with:
- go-version: ${{ matrix.go-version }}
-
- name: Checkout Code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+ - name: Install Go
+ uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
+ with:
+ go-version-file: 'go.mod'
+
# Pre-starting dependencies here means they are ready to go when we need them.
- name: Start Infra Dependencies
# Use & to background this
- run: FLEET_MYSQL_IMAGE=${{ matrix.mysql }} docker-compose -f docker-compose.yml -f docker-compose-redis-cluster.yml up -d mysql_test mysql_replica_test redis redis-cluster-1 redis-cluster-2 redis-cluster-3 redis-cluster-4 redis-cluster-5 redis-cluster-6 redis-cluster-setup minio saml_idp mailhog mailpit smtp4dev_test &
+ run: FLEET_MYSQL_IMAGE=${{ matrix.mysql }} docker compose -f docker-compose.yml -f docker-compose-redis-cluster.yml up -d mysql_test mysql_replica_test redis redis-cluster-1 redis-cluster-2 redis-cluster-3 redis-cluster-4 redis-cluster-5 redis-cluster-6 redis-cluster-setup minio saml_idp mailhog mailpit smtp4dev_test &
- name: Add TLS certificate for SMTP Tests
run: |
@@ -97,13 +97,13 @@ jobs:
- name: Wait for mysql
run: |
echo "waiting for mysql..."
- until docker-compose exec -T mysql_test sh -c "mysql -uroot -p\"\${MYSQL_ROOT_PASSWORD}\" -e \"SELECT 1=1\" fleet" &> /dev/null; do
+ until docker compose exec -T mysql_test sh -c "mysql -uroot -p\"\${MYSQL_ROOT_PASSWORD}\" -e \"SELECT 1=1\" fleet" &> /dev/null; do
echo "."
sleep 1
done
echo "mysql is ready"
echo "waiting for mysql replica..."
- until docker-compose exec -T mysql_replica_test sh -c "mysql -uroot -p\"\${MYSQL_ROOT_PASSWORD}\" -e \"SELECT 1=1\" fleet" &> /dev/null; do
+ until docker compose exec -T mysql_replica_test sh -c "mysql -uroot -p\"\${MYSQL_ROOT_PASSWORD}\" -e \"SELECT 1=1\" fleet" &> /dev/null; do
echo "."
sleep 1
done
@@ -118,7 +118,6 @@ jobs:
else
RUN_TESTS_ARG=''
fi
-
GO_TEST_EXTRA_FLAGS="-v -race=$RACE_ENABLED -timeout=$GO_TEST_TIMEOUT $RUN_TESTS_ARG" \
TEST_LOCK_FILE_PATH=$(pwd)/lock \
NETWORK_TEST=1 \
@@ -131,13 +130,17 @@ jobs:
NETWORK_TEST_GITHUB_TOKEN=${{ secrets.FLEET_RELEASE_GITHUB_PAT }} \
make test-go 2>&1 | tee /tmp/gotest.log
- # note: it's fine to upload multiple reports (one per matrix combination)
- # for the same run, see https://docs.codecov.com/docs/merging-reports
- - name: Upload to Codecov
- uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70
+ - name: Create mysql identifier without colon
+ if: always()
+ run: |
+ echo "MATRIX_MYSQL_ID=$(echo ${{ matrix.mysql }} | tr -d ':')" >> $GITHUB_ENV
+
+ - name: Save coverage
+ uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
with:
- files: coverage.txt
- flags: backend
+ name: ${{ matrix.suite }}-${{ env.MATRIX_MYSQL_ID }}-coverage
+ path: ./coverage.txt
+ if-no-files-found: error
- name: Generate summary of errors
if: failure()
@@ -155,10 +158,6 @@ jobs:
fi
GO_FAIL_SUMMARY=$GO_FAIL_SUMMARY envsubst < .github/workflows/config/slack_payload_template.json > ./payload.json
- # TODO: figure out a sane way to combine outputs from different matrix jobs
- # into a single slack notification, instead of sending one per job. This
- # problem already existed but now it's accentuated because we're running 4
- # jobs.
- name: Slack Notification
if: github.event.schedule == '0 4 * * *' && failure()
uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0
@@ -173,15 +172,32 @@ jobs:
- name: Upload test log
if: always()
- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v2
+ uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
with:
- name: test-log
+ name: ${{ matrix.suite }}-${{ env.MATRIX_MYSQL_ID }}-test-log
path: /tmp/gotest.log
if-no-files-found: error
- name: Upload summary test log
if: always()
- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v2
+ uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
with:
- name: summary-test-log
+ name: ${{ matrix.suite }}-${{ env.MATRIX_MYSQL_ID }}-summary-test-log
path: /tmp/summary.txt
+
+ # We upload all backend coverage in one step so that we're less like to end up in a situation with a partial coverage report.
+ upload-coverage:
+ needs: [test-go]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+ - name: Download artifacts
+ uses: actions/download-artifact@9c19ed7fe5d278cd354c7dfd5d3b88589c7e2395 # v4.1.6
+ with:
+ pattern: '*-coverage'
+ - name: Upload to Codecov
+ uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ flags: backend
diff --git a/.github/workflows/test-js.yml b/.github/workflows/test-js.yml
index 9d635237370c..15b4fd05cee5 100644
--- a/.github/workflows/test-js.yml
+++ b/.github/workflows/test-js.yml
@@ -69,8 +69,9 @@ jobs:
yarn test:ci
- name: Upload to Codecov
- uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70
+ uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
with:
+ token: ${{ secrets.CODECOV_TOKEN }}
flags: frontend
lint-js:
diff --git a/.github/workflows/test-native-tooling-packaging.yml b/.github/workflows/test-native-tooling-packaging.yml
index db242cee0c4b..ff0dc4abadf6 100644
--- a/.github/workflows/test-native-tooling-packaging.yml
+++ b/.github/workflows/test-native-tooling-packaging.yml
@@ -1,4 +1,4 @@
-# This workflow tests packaging of Fleet-osquery with the
+# This workflow tests generation of fleetd packages with the
# `fleetdm/fleetctl` Docker image.
name: Test native tooling packaging
@@ -41,7 +41,6 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest]
- go-version: ['${{ vars.GO_VERSION }}']
runs-on: ${{ matrix.os }}
steps:
@@ -50,17 +49,23 @@ jobs:
with:
egress-policy: audit
+ - name: Checkout Code
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
- name: Install Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ matrix.go-version }}
-
- - name: Checkout Code
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+ go-version-file: 'go.mod'
- name: Install Go Dependencies
run: make deps-go
+ - name: Build fleetdm/wix
+ run: make wix-docker
+
+ - name: Build fleetdm/bomutils
+ run: make bomutils-docker
+
- name: Build fleetdm/fleetctl
run: make fleetctl-docker
diff --git a/.github/workflows/test-packaging.yml b/.github/workflows/test-packaging.yml
index 7190314cb9ef..dbe5a96244ea 100644
--- a/.github/workflows/test-packaging.yml
+++ b/.github/workflows/test-packaging.yml
@@ -47,7 +47,6 @@ jobs:
# `macos-latest` uses arm64 by default now, so please be careful when
# updating this version.
os: [ubuntu-latest, macos-13]
- go-version: ['${{ vars.GO_VERSION }}']
runs-on: ${{ matrix.os }}
steps:
@@ -61,25 +60,35 @@ jobs:
# Run in background while other steps complete to speed up the workflow
run: docker pull fleetdm/wix:latest &
+ - name: Pull fleetdm/bomutils
+ # Run in background while other steps complete to speed up the workflow
+ run: docker pull fleetdm/bomutils:latest &
+
- name: Run Colima
if: startsWith(matrix.os, 'macos')
- timeout-minutes: 10
+ timeout-minutes: 15
# notes:
# - docker to install the docker CLI and interact with the Colima
# container runtime
# - colima is pre-installed in macos-12 runners, but not in macos-13 or
# macos-14 runners
run: |
- brew install docker colima
+ brew install docker
+ # The runners come with an old version of python@3.12 that fails to upgrade
+ # when python gets pulled in as a dep through the chain
+ # colima -> lima -> qemu -> glibc -> python@3.12
+ # Force upgrade it for now, remove once the problem is fixed
+ brew install --overwrite python@3.12
+ brew install colima
colima start --mount $TMPDIR:w
+ - name: Checkout Code
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
- name: Install Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ matrix.go-version }}
-
- - name: Checkout Code
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+ go-version-file: 'go.mod'
- name: Install wine and wix
if: startsWith(matrix.os, 'macos')
diff --git a/.github/workflows/test-yml-specs.yml b/.github/workflows/test-yml-specs.yml
index 75e46d6af046..fe8f3ecace97 100644
--- a/.github/workflows/test-yml-specs.yml
+++ b/.github/workflows/test-yml-specs.yml
@@ -33,7 +33,6 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
- go-version: ['${{ vars.GO_VERSION }}']
runs-on: ${{ matrix.os }}
steps:
@@ -42,13 +41,13 @@ jobs:
with:
egress-policy: audit
+ - name: Checkout Code
+ uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
- name: Install Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
with:
- go-version: ${{ matrix.go-version }}
-
- - name: Checkout Code
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+ go-version-file: 'go.mod'
- name: Run apply spec tests
run: |
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 3e3ac95d6acf..885f407fb8e5 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -61,6 +61,21 @@
"--dev_license"
]
},
+ {
+ "name": "Fleet vuln_processing (licensed)",
+ "type": "go",
+ "request": "launch",
+ "mode": "auto",
+ "buildFlags": "-tags='full,fts5'",
+ "cwd": "${workspaceFolder}",
+ "program": "${workspaceFolder}/cmd/fleet",
+ "args": [
+ "vuln_processing",
+ "--dev",
+ "--logging_debug",
+ "--dev_license",
+ ]
+ },
{
"name": "Attach to a running Fleet server",
"type": "go",
diff --git a/16538-preserve-manage-query-automations-modal-state b/16538-preserve-manage-query-automations-modal-state
deleted file mode 100644
index d8ea5ca981cc..000000000000
--- a/16538-preserve-manage-query-automations-modal-state
+++ /dev/null
@@ -1,2 +0,0 @@
-- Fix a bug where the manage query automations modal would lose its state when the user clicks
- "Preview data"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 997118f913b2..24d768a609dc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,20 +1,206 @@
+## Fleet 4.55.2 (Sep 05, 2024)
+
+### Bug fixes
+
+* Removed validation of APNS certificate from server startup. This was no longer necessary because we now allow for APNS certificates to be renewed in the UI.
+* Fixed logic to properly catch and log APNs errors.
+
+## Fleet 4.55.1 (Aug 14, 2024)
+
+### Bug fixes
+
+* Added a disabled overlay to the Other Workflows modal on the policy page.
+* Updated text for "Turn on MDM" banners in UI.
+* Fixed a bug when a cached prepared statement got deleted in the MySQL server itself without Fleet knowing.
+* Continued with an empty CVE description when the NVD CVE feed didn't include description entries (instead of panicking).
+* Scheduled maintenance events are now scheduled over calendar events marked "Free" (not busy) in Google Calendar.
+* Fixed a bug where the wrong API path was used to download a software installer.
+* Improved fleetctl gitops error message when trying to change team name to a team that already exists.
+* Updated ABM (Apple Business Manager) host tooltip copy on the manage host page to clarify when host vitals will be available to view.
+* Added index to query_results DB table to speed up finding the last query timestamp for a given query and host.
+* Displayed the label names in case-insensitive alphabetical order in the fleet UI.
+
+## Fleet 4.55.0 (Aug 8, 2024)
+
+**NOTE:** Beginning with v4.55.0, Fleet no longer supports MySQL 5.7 because it has reached [end of life](https://mattermost.com/blog/mysql-5-7-reached-eol-upgrade-to-mysql-8-x-today/#:~:text=In%20October%202023%2C%20MySQL%205.7,to%20upgrade%20to%20MySQL%208.). The minimum version supported is MySQL 8.0.36.
+
+### Endpoint Operations
+
+- Added support for generating `fleetd` packages for Linux ARM64.
+- Added new `fleetctl package` --arch flag.
+- Updated `fleetctl package` command to remove the `--version` flag. The version of the package can be controlled by `--orbit-channel` flag.
+- Updated maintenance window descriptions to update regularly to match the failing policy description/resolution.
+- Updated maintenance windows using Google Calendar so that calendar events are now recreated within 30 seconds if deleted or moved to the past.
+ - Fleet server watches for potential changes for up to 1 week after original event time. If event is moved forward more than 1 week, then after 1 week Fleet server will check for event changes once every 30 minutes.
+ - **NOTE:** These near real-time updates may add additional load to the Google Calendar API, so it is recommended to use API usage alerts or other monitoring methods.
+
+### Device Management
+
+- Integrated [Escrow Buddy](https://github.com/macadmins/escrow-buddy) to add enforcement of FileVault during the MacOS Setup Assistant process for hosts that are
+enrolled into teams (or no team) with disk encryption turned on. Thank you [homebysix](https://github.com/homebysix) and team!
+- Updated `fleetd` to use [Escrow Buddy](https://github.com/macadmins/escrow-buddy) to rotate FileVault keys. Removed or modified internal API endpoints documented in the API for contributors.
+- Added OS updates support to iOS/iPadOS devices.
+- Added iOS and iPadOS device details refetch triggered with the existing `POST /api/latest/fleet/hosts/:id/refetch` endpoint.
+- Added iOS and iPadOS user-installed apps to Fleet.
+- Added iOS and iPadOS apps to be installed using Apple's VPP (Volume Purchase Program) to Fleet.
+- Added support for VPP to GitOps.
+- Added the `POST /mdm/apple/vpp_token`, `DELETE /mdm/apple/vpp_token` and `GET /vpp` endpoints and related functionality.
+- Added new `GET /software/app_store_apps` and `POST /software/app_store_apps` endpoints and associated functionality.
+- Added the associated VPP apps to the `GET /software/titles` and `GET /software/titles/:id` endpoints.
+- Added the associated VPP apps to the `GET /hosts/:id/software` and `GET /device/:token/software` endpoints.
+- Added support to delete a VPP app from a team in `DELETE /software/titles/:software_title_id/available_for_install`.
+- Added `exclude_software` query parameter to "Get host by identifier" API.
+- Added ability to add/remove/disable apps with VPP in the Fleet UI.
+- Added a warning banner to the UI if the uploaded VPP token is about to expire/has expired.
+- Added UI updates for VPP feature on host software and my device pages.
+- Added global activity support for VPP-related activities.
+- Added UI features for managing VPP apps for iPadOS and iOS hosts.
+- Updated profile activities to include iOS and iPadOS.
+- Updated Fleet UI to show OS version compliance on host details page.
+- Added support for "No teams" on all software pages including adding software installers.
+- Added DB migration to support VPP software features.
+- Added DB migration to migrate older team configurations to the new version that includes both installers and App Store apps.
+- Linux lock/unlock scripts now make use of pam_nologin to keep AD users locked out.
+- Installed software list now includes Linux .deb packages that are 'on hold'.
+- Added a special-case to properly name the Notion .exe Windows installer the same as how it will be reported by osquery post-install.
+- Increased threshold to renew Apple SCEP certificates for MDM enrollments to 180 days.
+
+### Vulnerability Management
+
+- Fixed CVEs identified as 'Rejected' in NVD not matching against software.
+- Fixed false negative vulnerabilities with IntelliJ IDEA CE and PyCharm CE installed via Homebrew.
+
+### Bug fixes and improvements
+
+- Dropped support for MySQL 5.7 and raised minimum required to MySQL 8.0.36.
+- Updated software pre-install to use new GitOps format for query.
+- Updated UI tooltips for pending OS settings.
+- Added a migration to migrate older team configurations to the new version that includes both installers and App Store apps.
+- Fixed a styling issue in the controls > OS settings > disk encryption table.
+- Fixed a bug in `fleetctl preview` that was causing it to fail if Docker was installed without support for the deprecated `docker-compose` CLI.
+- Fixed an issue where the app-wide warning banners were not showing on the initial page load.
+- Fixed a bug where the hosts page would sometimes allow excess pagination.
+- Fixed a bug where software install results could not be retrieved for deleted hosts in the activity feed.
+- Fixed path that was incorrect for the download software installer package endpoint `GET /software/titles/:software_title_id/package`.
+- Fixed a bug that set `last_enrolled_at` during orbit re-enrollment, which caused osquery enroll failures when `FLEET_OSQUERY_ENROLL_COOLDOWN` is set.
+- Fixed the "Available for install" filter in the host's software page so that installers that were requested to be installed on the host (regardless of installation status) also show up in the list.
+- Fixed a styling issue in the Controls > OS Settings > disk encryption table.
+- Fixed a bug where Fleet google calendar events generated by Fleet <= 4.53.0 were not correctly processed by 4.54.0.
+- Fixed a bug in `fleetctl preview` that was causing it to fail if Docker was installed without support for the deprecated `docker-compose` CLI.
+- Fixed a bug where software install results could not be retrieved for deleted hosts in the activity feed.
+- Fixed a bug where a software installer (a package or a VPP app) that has been installed on a host still shows up as "Available for install" and can still be requested to be installed after the host is transferred to a different team without that installer (or after the installer is deleted).
+- Fixed the "Available for install" filter in the host's software page so that installers that were requested to be installed on the host (regardless of installation status) also show up in the list.
+
+## Fleet 4.54.1 (Jul 24, 2024)
+
+### Bug fixes
+
+- Fixed a startup bug by performing an early restart of orbit if an agent options setting has changed.
+- Implemented a small refactor of orbit subsystems.
+- Removed the `--version` flag from the `fleetctl package` command. The version of the package can now be controlled by the `--orbit-channel` flag.
+- Fixed a bug that set `last_enrolled_at` during orbit re-enrollment, which caused osquery enroll failures when `FLEET_OSQUERY_ENROLL_COOLDOWN` is set .
+- In `fleetctl package` command, removed the `--version` flag. The version of the package can be controlled by `--orbit-channel` flag.
+- Fixed a bug where Fleet google calendar events generated by Fleet <= 4.53.0 were not correctly processed by 4.54.0.
+- Re-enabled cached logins after windows Unlock.
+
+## Fleet 4.54.0 (Jul 17, 2024)
+
+### Endpoint Operations
+
+- Updated `fleetctl gitops` to be used to rename teams.
+ - **NOTE:** `fleetctl gitops` needs to have previously run with this Fleet/fleetctl version or later.
+ - The team name is changed if the YAML config is applied from the same filename as before.
+- Updated `fleetctl query --hosts` to work with hostnames, host UUIDs, and/or hardware serial numbers.
+- Added a host's upcoming scheduled maintenance window, if any, on the host details page of the UI and in host responses from the API.
+- Added support to `fleetctl debug connection` to test TLS connection with the embedded certs.pem in
+ the fleetctl executable.
+- Added host's display name to calendar event descriptions.
+- Added .yml and .yaml file type validation and error message to `fleetctl apply`.
+- Added a tooltip to truncated text and not to untruncated values.
+
+### Device Management (MDM)
+
+- Added iOS/iPadOS builtin manual labels.
+ - **NOTE:** Before migrating to this version, make sure to delete any labels with name "iOS" or "iPadOS".
+- Added aggregation of iOS/iPadOS OS versions.
+- Added change to custom profiles for iOS/iPadOS to go from 'pending' straight to 'verified' (skip 'verifying').
+- Added support for renewing SCEP certificates with custom enrollment profiles.
+- Added automatic install of `fleetd` when a host turns on MDM now uses the latest released `fleetd` version.
+- Added support for `END_USER_EMAIL` and `FLEET_DESKTOP` parameters to Windows MSI install package.
+- Added API changes to support the `labels_include_all` and `labels_exclude_any` fields (and accept the deprecated `labels` field as an alias for `labels_include_all`).
+- Added `fleetctl gitops` and `fleetctl apply` support for `labels_include_all` and `labels_exclude_any` to configure a custom setting.
+- Added UI for uploading custom profiles with a target of hosts that include all/exclude any selected labels.
+- Added the database migrations to create the new `exclude` column for labels associated with MDM profiles (and declarations).
+- Updated host script timeouts to be configurable via agent options using `script_execution_timeout`.
+- `fleetctl` now uses a polling mechanism when running `run-script` to accommodate longer script timeout values.
+- Updated the profile reconciliation logic to handle the new "exclude any" labels.
+- Updated so that the `fleetd` cleanup script for macOS that will return completed when run from Fleet.
+- Updated so that the `fleetd` uninstall script will return completed when run from Fleet.
+- Updated script run permissions -- only admins and maintainers can run arbitrary or saved scripts (not observer or observer+).
+- Updated `fleetctl get mdm_commands` to return 20 rows and support `--host` `--type` filters to improve response time.
+- Updated the instructions for manual MDM enrollment on the "My device" page to be clearer and align with Apple updates.
+- Updated UI to allow device users to reinstall self-service software.
+- Updated API to not return a 500 status code if a host sends a command response with an invalid command uuid.
+- Increased the timeout of the upload software installer endpoint to 4 minutes.
+- Disabled credential caching and reboot on Windows lock.
+
+### Vulnerability Management
+
+- Added "Vulnerable" filter to the host details software table.
+- Fixed Microsoft Office June 2024 false negative vulnerabilities and added custom vulnerability matching.
+- Fixed issue where some Windows applications were getting matched against Windows OS vulnerabilities.
+
+### Bug fixes and improvements
+
+- Updated Go version to go1.22.4.
+- Updated to render only one banner on the my device page based on priority order.
+- Updated software updated timestamp tooltip.
+- Removed DB error message from the UI when showing a error response.
+- Updated fleetctl get queries/labels/hosts descriptions.
+- Reinstated ability to sort policies by passing count.
+- Improved the accuracy of the heuristic used to deterimine if a host is connected to Fleet via MDM by using osquery data for hosts that didn't send a Checkout message.
+- Improved the matching of `pkg` installer files to existing software.
+- Improved extraction of application name from `pkg` installers.
+- Clarified various help and error texts around host identifiers.
+- Hid CTA on inherited queries/policies from team level users.
+- Hid query delete checkboxes from team observers.
+- Hid "Self-service" in Fleet Desktop and My device page if there is no self-service software available.
+- Hid the host detail page's "Run script" action from Global and Team Observer/+s.
+- Aligned the "View all hosts" links in the Software titles and versions tables.
+- Fixed counts for hosts with with low disk space in summary page.
+- Fixed allowing Observer and Observer+ roles to download software installers.
+- Fixed crash in `fleetd` installer on Windows if there are registry keys with special characters on the system.
+- Fixed `fleetctl debug connection` to support server TLS certificates with intermediates.
+- Fixed macOS declarations being stuck in "to be removed" state indefinitely.
+- Fixed link to `fleetd` uninstall instructions in "Delete device" modal.
+- Fixed exporting CSVs with fields that contain commas to render properly.
+- Fixed issue where the Fleet UI could not be used to renew the ABM token after the ABM user who created the token was deleted.
+- Fixed styling issues with the target inputs loading spinner on the run live query/policy page.
+- Fixed an issue where special characters in HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall breaks the "installer_utils.ps1 -uninstallOrbit" step in the Windows MSI installer.
+- Fixed a bug causing "No Team" OS versions to display the wrong number.
+- Fixed various UI capitalizations.
+- Fixed UI issue where "Script is already running" tooltip incorrectly displayed when the script is not running.
+- Fixed the script details modal's error message on script timeout to reflect the newly dynamic script timeout limit, if hit.
+- Fixed a discrepancy in the spacing between DataSet labels and values on Firefox relative to other browsers.
+- Fixed bug that set `Added to Fleet` to `Never` after macOS hosts re-enrolled to Fleet via MDM.
+
## Fleet 4.53.1 (Jul 01, 2024)
### Bug fixes
-* Updated fleetctl get queries/labels/hosts descriptions.
-* Fixed exporting CSVs with fields that contain commas to render properly.
-* Fixed link to fleetd uninstall instructions in "Delete device" modal.
-* Rendered only one banner on the my device page based on priority order.
-* Hidden query delete checkboxes from team observers.
-* Fixed issue where the Fleet UI could not be used to renew the ABM token after the ABM user who created the token was deleted.
-* Fixed an issue where special characters in HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall broke the "installer_utils.ps1 -uninstallOrbit" step in the Windows MSI installer.
-* Fixed counts for hosts with low disk space in summary page.
-* Fleet UI fixes: Hide CTA on inherited queries/policies from team level users.
-* Updated software updated timestamp tooltip.
-* Fixed issue where some Windows applications were getting matched against Windows OS vulnerabilities.
-* Fixed crash in `fleetd` installer on Windows if there are registry keys with special characters on the system.
-* Fixed UI capitalizations.
+- Updated fleetctl get queries/labels/hosts descriptions.
+- Fixed exporting CSVs with fields that contain commas to render properly.
+- Fixed link to fleetd uninstall instructions in "Delete device" modal.
+- Rendered only one banner on the my device page based on priority order.
+- Hidden query delete checkboxes from team observers.
+- Fixed issue where the Fleet UI could not be used to renew the ABM token after the ABM user who created the token was deleted.
+- Fixed an issue where special characters in HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall broke the "installer_utils.ps1 -uninstallOrbit" step in the Windows MSI installer.
+- Fixed counts for hosts with low disk space in summary page.
+- Fleet UI fixes: Hide CTA on inherited queries/policies from team level users.
+- Updated software updated timestamp tooltip.
+- Fixed issue where some Windows applications were getting matched against Windows OS vulnerabilities.
+- Fixed crash in `fleetd` installer on Windows if there are registry keys with special characters on the system.
+- Fixed UI capitalizations.
## Fleet 4.53.0 (Jun 25, 2024)
diff --git a/CODEOWNERS b/CODEOWNERS
index 524ca884048f..fae91d00d013 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -39,19 +39,9 @@
go.sum @fleetdm/go
go.mod @fleetdm/go
/cmd/ @fleetdm/go
-/orbit/ @lucasmrod @getvictor @roperzh @gillespi314
/server/ @fleetdm/go
-/server/service/handler.go @lucasmrod @getvictor @roperzh @gillespi314
-/server/mdm/ @roperzh @gillespi314
-/server/worker/ @lucasmrod @getvictor @roperzh @gillespi314
-/server/vulnerabilities/ @lucasmrod @mostlikelee
-/server/cron/ @getvictor @lucasmrod
-/ee/fleetd-chrome @lucasmrod @getvictor
-/ee/vulnerability-dashboard @eashaw
-/ee/cis @sharon-fdm @lucasmrod
-/ee/server/calender @lucasmrod @getvictor
-/ee/server/service @roperzh @gillespi314 @lucasmrod @getvictor
-/scripts/mdm @roperzh @gillespi314
+/ee/server/ @fleetdm/go
+/orbit/ @lucasmrod @roperzh @lukeheath @georgekarrv @sharon-fdm
##############################################################################################
# 🚀 React files and other files related to the core product frontend.
@@ -66,9 +56,9 @@ go.mod @fleetdm/go
# FUTURE: Look for a way to not have this notify every single person in this "github team".
##############################################################################################
-/infrastructure/ @rfairburn @ksatter @lukeheath @edwardsb @pacamaster @georgekarrv
-/charts/ @rfairburn @ksatter @lukeheath @edwardsb @pacamaster @georgekarrv
-/terraform/ @rfairburn @ksatter @lukeheath @edwardsb @pacamaster @georgekarrv
+/infrastructure/ @rfairburn @ksatter @lukeheath @edwardsb @georgekarrv
+/charts/ @rfairburn @ksatter @lukeheath @edwardsb @georgekarrv
+/terraform/ @rfairburn @ksatter @lukeheath @edwardsb @georgekarrv
/it-and-security/ @noahtalerman @lukeheath @spokanemac @getvictor
##############################################################################################
@@ -77,8 +67,8 @@ go.mod @fleetdm/go
# (see website/config/custom.js for DRIs of other paths not listed here)
##############################################################################################
/docs @rachaelshaw
-/docs/Using-Fleet/REST-API.md @rachaelshaw # « REST API reference documentation
-/docs/Contributing/API-for-contributors.md @rachaelshaw @lukeheath # « Advanced / contributors-only API reference documentation
+/docs/REST\ API/rest-api.md @rachaelshaw # « REST API reference documentation
+/docs/Contributing/API-for-contributors.md @lukeheath # « Advanced / contributors-only API reference documentation
/schema @eashaw # « Data tables (osquery/fleetd schema) documentation
/docs/Deploy/_kubernetes/ @dherder # « Kubernetes best practice
##############################################################################################
@@ -137,43 +127,7 @@ go.mod @fleetdm/go
##############################################################################################
# 🚀 GitHub workflows
##############################################################################################
-/.github/workflows/README.md @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/goreleaser-fleet.yaml @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/update-certs.yml @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/codeql-analysis.yml @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/codeql.yml @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/scorecards-analysis.yml @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/integration.yml @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/fleetctl-preview.yml @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/fleetctl-preview-latest.yml @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/goreleaser-orbit.yaml @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/trivy-scan.yml @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/goreleaser-snapshot-fleet.yaml @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/build-and-push-fleetctl-docker.yml @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/fleetd-tuf.yml @lucasmrod @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/generate-desktop-targets.yml @lucasmrod @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/test-yml-specs.yml @lucasmrod @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/build-binaries.yaml @lucasmrod @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/fleet-and-orbit.yml @lucasmrod @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/build-orbit.yaml @lucasmrod @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/generate-osqueryd-targets.yml @lucasmrod @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/test-packaging.yml @lucasmrod @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/release-helm.yaml @rfairburn @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/pr-helm.yaml @rfairburn @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/tfvalidate.yml @rfairburn @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/dogfood-deploy.yml @rfairburn @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/test-db-changes.yml @roperzh @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/test-go.yaml @roperzh @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/golangci-lint.yml @roperzh @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/test-native-tooling-packaging.yml @roperzh @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/check-tuf-timestamps.yml @roperzh @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/test-puppet.yml @roperzh @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/generate-nudge-targets.yml @roperzh @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/test-js.yml @ghernandez345 @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/dogfood-gitops.yml @getvictor @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/test-fleetd-chrome.yml @getvictor @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/release-fleetd-chrome.yml @getvictor @lukeheath @georgekarrv @sharon-fdm
-/.github/workflows/release-fleetd-chrome-beta.yml @getvictor @lukeheath @georgekarrv @sharon-fdm
+/.github/workflows/ @lukeheath @georgekarrv @sharon-fdm
# ℹ️ But wait, there's more!
# See the comments up top to learn where else DRIs and maintainers are configured.
diff --git a/Dockerfile-desktop-linux b/Dockerfile-desktop-linux
index b0ff52c050fd..c17d3894d06e 100644
--- a/Dockerfile-desktop-linux
+++ b/Dockerfile-desktop-linux
@@ -1,4 +1,4 @@
-FROM --platform=linux/amd64 golang:1.22.4-bullseye@sha256:067c5c7fe6d79f900c5ebe8351166356d6e3bbfcc6f807030e89b9a929252273
+FROM --platform=linux/amd64 golang:1.23.1-bullseye@sha256:45b43371f21ec51276118e6806a22cbb0bca087ddd54c491fdc7149be01035d5
LABEL maintainer="Fleet Developers"
RUN mkdir -p /usr/src/fleet
diff --git a/Dockerfile.osquery-perf b/Dockerfile.osquery-perf
deleted file mode 100644
index 89331d931221..000000000000
--- a/Dockerfile.osquery-perf
+++ /dev/null
@@ -1,16 +0,0 @@
-FROM golang:1.22.4-alpine3.20@sha256:ace6cc3fe58d0c7b12303c57afe6d6724851152df55e08057b43990b927ad5e8
-
-ARG ENROLL_SECRET
-ARG HOST_COUNT
-ARG SERVER_URL
-
-ENV ENROLL_SECRET ${ENROLL_SECRET}
-ENV HOST_COUNT ${HOST_COUNT}
-ENV SERVER_URL ${SERVER_URL}
-
-COPY ./cmd/osquery-perf/agent.go ./go.mod ./go.sum ./cmd/osquery-perf/mac10.14.6.tmpl /osquery-perf/
-WORKDIR /osquery-perf/
-RUN go mod download
-RUN go build -o osquery-perf
-
-CMD ./osquery-perf -enroll_secret $ENROLL_SECRET -host_count $HOST_COUNT -server_url $SERVER_URL
diff --git a/Makefile b/Makefile
index d90f615c9ac1..46115cf47903 100644
--- a/Makefile
+++ b/Makefile
@@ -221,6 +221,12 @@ docker-push-release: docker-build-release
fleetctl-docker: xp-fleetctl
docker build -t fleetdm/fleetctl --platform=linux/amd64 -f tools/fleetctl-docker/Dockerfile .
+bomutils-docker:
+ cd tools/bomutils-docker && docker build -t fleetdm/bomutils --platform=linux/amd64 -f Dockerfile .
+
+wix-docker:
+ cd tools/wix-docker && docker build -t fleetdm/wix --platform=linux/amd64 -f Dockerfile .
+
.pre-binary-bundle:
rm -rf build/binary-bundle
mkdir -p build/binary-bundle/linux
@@ -249,13 +255,15 @@ fleetd-tables-windows:
GOOS=windows GOARCH=amd64 go build -o fleetd_tables_windows.exe ./orbit/cmd/fleetd_tables
fleetd-tables-linux:
GOOS=linux GOARCH=amd64 go build -o fleetd_tables_linux.ext ./orbit/cmd/fleetd_tables
+fleetd-tables-linux-arm64:
+ GOOS=linux GOARCH=arm64 go build -o fleetd_tables_linux_arm64.ext ./orbit/cmd/fleetd_tables
fleetd-tables-darwin:
GOOS=darwin GOARCH=amd64 go build -o fleetd_tables_darwin.ext ./orbit/cmd/fleetd_tables
fleetd-tables-darwin_arm:
GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -o fleetd_tables_darwin_arm.ext ./orbit/cmd/fleetd_tables
fleetd-tables-darwin-universal: fleetd-tables-darwin fleetd-tables-darwin_arm
lipo -create fleetd_tables_darwin.ext fleetd_tables_darwin_arm.ext -output fleetd_tables_darwin_universal.ext
-fleetd-tables-all: fleetd-tables-windows fleetd-tables-linux fleetd-tables-darwin-universal
+fleetd-tables-all: fleetd-tables-windows fleetd-tables-linux fleetd-tables-darwin-universal fleetd-tables-linux-arm64
fleetd-tables-clean:
rm -f fleetd_tables_windows.exe fleetd_tables_linux.ext fleetd_tables_darwin.ext fleetd_tables_darwin_arm.ext fleetd_tables_darwin_universal.ext
@@ -279,7 +287,7 @@ binary-arch: .pre-binary-arch .pre-binary-bundle .pre-fleet
# Drop, create, and migrate the e2e test database
e2e-reset-db:
- docker-compose exec -T mysql_test bash -c 'echo "drop database if exists e2e; create database e2e;" | MYSQL_PWD=toor mysql -uroot'
+ docker compose exec -T mysql_test bash -c 'echo "drop database if exists e2e; create database e2e;" | MYSQL_PWD=toor mysql -uroot'
./build/fleet prepare db --mysql_address=localhost:3307 --mysql_username=root --mysql_password=toor --mysql_database=e2e
e2e-setup:
@@ -310,7 +318,7 @@ e2e-serve-premium: e2e-reset-db
# Usage:
# make e2e-set-desktop-token host_id=1 token=foo
e2e-set-desktop-token:
- docker-compose exec -T mysql_test bash -c 'echo "INSERT INTO e2e.host_device_auth (host_id, token) VALUES ($(host_id), \"$(token)\") ON DUPLICATE KEY UPDATE token=VALUES(token)" | MYSQL_PWD=toor mysql -uroot'
+ docker compose exec -T mysql_test bash -c 'echo "INSERT INTO e2e.host_device_auth (host_id, token) VALUES ($(host_id), \"$(token)\") ON DUPLICATE KEY UPDATE token=VALUES(token)" | MYSQL_PWD=toor mysql -uroot'
changelog:
sh -c "find changes -type f | grep -v .keep | xargs -I {} sh -c 'grep \"\S\" {}; echo' > new-CHANGELOG.md"
@@ -345,7 +353,7 @@ fleetd-tuf:
# Reset the development DB
db-reset:
- docker-compose exec -T mysql bash -c 'echo "drop database if exists fleet; create database fleet;" | MYSQL_PWD=toor mysql -uroot'
+ docker compose exec -T mysql bash -c 'echo "drop database if exists fleet; create database fleet;" | MYSQL_PWD=toor mysql -uroot'
./build/fleet prepare db --dev
# Back up the development DB to file
@@ -422,6 +430,14 @@ endif
tar czf $(out-path)/swiftDialog.app.tar.gz -C $(TMP_DIR)/swiftDialog_pkg_payload_expanded/Library/Application\ Support/Dialog/ Dialog.app
rm -rf $(TMP_DIR)
+# Generate escrowBuddy.pkg bundle from the Escrow Buddy repo.
+#
+# Usage:
+# make escrow-buddy-pkg version=1.0.0 out-path=.
+escrow-buddy-pkg:
+ curl -L https://github.com/macadmins/escrow-buddy/releases/download/v$(version)/Escrow.Buddy-$(version).pkg --output $(out-path)/escrowBuddy.pkg
+
+
# Build and generate desktop.app.tar.gz bundle.
#
# Usage:
@@ -465,6 +481,21 @@ desktop-linux:
tar czf desktop.tar.gz fleet-desktop && \
rm -r fleet-desktop"
+# Build desktop executable for Linux ARM.
+#
+# Usage:
+# FLEET_DESKTOP_VERSION=0.0.1 make desktop-linux-arm64
+#
+# Output: desktop.tar.gz
+desktop-linux-arm64:
+ docker build -f Dockerfile-desktop-linux -t desktop-linux-builder .
+ docker run --rm -v $(shell pwd):/output desktop-linux-builder /bin/bash -c "\
+ mkdir /output/fleet-desktop && \
+ GOARCH=arm64 go build -o /output/fleet-desktop/fleet-desktop -ldflags "-X=main.version=$(FLEET_DESKTOP_VERSION)" /usr/src/fleet/orbit/cmd/desktop && \
+ cd /output && \
+ tar czf desktop.tar.gz fleet-desktop && \
+ rm -r fleet-desktop"
+
# Build orbit executable for Windows.
# This generates orbit executable for Windows that includes versioninfo binary properties
# These properties can be displayed when right-click on the binary in Windows Explorer.
diff --git a/README.md b/README.md
index 7e8651a618ef..3c23f067c3b9 100644
--- a/README.md
+++ b/README.md
@@ -43,8 +43,6 @@ Fleet has no ambition to replace all of your other tools. (Though it might repl
Fleet plays well with Munki, Chef, Puppet, and Ansible, as well as with security tools like Crowdstrike and SentinelOne. For example, you can use the free version of Fleet to quickly report on what hosts are _actually_ running your EDR agent.
-While most folks prefer to use one or the other, Fleet can also coexist peacefully with Rapid7 and other agent-based vulnerability scanners. This can be useful during migrations.
-
#### Free as in free
The free version of Fleet will [always be free](https://fleetdm.com/pricing). Fleet is [independently backed](https://linkedin.com/company/fleetdm) and actively maintained with the help of many amazing [contributors](https://github.com/fleetdm/fleet/graphs/contributors).
diff --git a/docs/Using Fleet/Automations.md b/articles/automations.md
similarity index 92%
rename from docs/Using Fleet/Automations.md
rename to articles/automations.md
index e124fc779ea3..478b870556ac 100644
--- a/docs/Using Fleet/Automations.md
+++ b/articles/automations.md
@@ -40,6 +40,9 @@ Host status automations send a webhook request if a configured percentage of hos
Fleet sends these webhook requests once per day by default.
-
+
+
+
+
+
-
diff --git a/articles/certificates-in-fleetd.md b/articles/certificates-in-fleetd.md
index fe149d8ff815..e7ef4a9eef0b 100644
--- a/articles/certificates-in-fleetd.md
+++ b/articles/certificates-in-fleetd.md
@@ -57,6 +57,6 @@ fleetctl debug connection \
-
+
-
\ No newline at end of file
+
diff --git a/docs/Using Fleet/enroll-chromebooks.md b/articles/chrome-os.md
similarity index 85%
rename from docs/Using Fleet/enroll-chromebooks.md
rename to articles/chrome-os.md
index 2c1684cc5f10..d74dfb89c10a 100644
--- a/docs/Using Fleet/enroll-chromebooks.md
+++ b/articles/chrome-os.md
@@ -1,8 +1,6 @@
# ChromeOS
For visibility on ChromeOS hosts, Fleet provides the fleetd Chrome extension which provides similar functionality as osquery on other operating systems.
-## Adding ChromeOS hosts to Fleet
-
To learn how to add ChromeOS hosts to Fleet, visit [here](https://fleetdm.com/docs/using-fleet/adding-hosts#enroll-chromebooks).
> The fleetd Chrome browser extension is supported on ChromeOS operating systems that are managed using [Google Admin](https://admin.google.com). It is not intended for non-ChromeOS hosts with the Chrome browser installed.
@@ -23,6 +21,10 @@ By default, the hostname for a Chromebook host will be blank. The hostname can b
## Debugging ChromeOS
To learn how to debug the Fleetd Chrome extension, visit [here](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Testing-and-local-development.md#fleetd-chrome-extension).
-
-
-
+
+
+
+
+
+
+
diff --git a/docs/Using Fleet/CIS-Benchmarks.md b/articles/cis-benchmarks.md
similarity index 91%
rename from docs/Using Fleet/CIS-Benchmarks.md
rename to articles/cis-benchmarks.md
index 9942eb0e3c20..905d62efbadb 100644
--- a/docs/Using Fleet/CIS-Benchmarks.md
+++ b/articles/cis-benchmarks.md
@@ -11,7 +11,7 @@ Fleet has implemented native support for CIS Benchmarks for the following platfo
- Windows 10 Enterprise
- Windows 11 Enterprise
-[Where possible](#limitations), each CIS Benchmark is implemented with a [policy query](./REST-API.md#policies) in Fleet.
+[Where possible](#limitations), each CIS Benchmark is implemented with a [policy query](https://fleetdm.com/docs/rest-api/rest-api#policies) in Fleet.
These benchmarks are intended to gauge your organization's security posture, rather than the current state of a given host. A host may fail a CIS Benchmark policy despite having the correct settings enabled if there is no configuration profile or Group Policy Object (GPO) in place to enforce the setting. For example, this is the query for **CIS - Ensure FileVault Is Enabled (MDM Required)**:
@@ -95,7 +95,7 @@ Following are the requirements to use the CIS Benchmarks in Fleet:
- Devices must be running [`fleetd`](https://fleetdm.com/docs/using-fleet/orbit), Fleet's lightweight agent.
- Some CIS Benchmarks explicitly involve verifying MDM-based controls, so devices must be enrolled to an MDM solution.
-- On macOS, the orbit component of fleetd must have "Full Disk Access", see [Grant Full Disk Access to Osquery on macOS](./Adding-hosts.md#grant-full-disk-access-to-osquery-on-macos).
+- On macOS, the orbit component of fleetd must have "Full Disk Access", see [Grant Full Disk Access to Osquery on macOS](https://fleetdm.com/guides/enroll-hosts#grant-full-disk-access-to-osquery-on-macos).
## Limitations
@@ -111,7 +111,9 @@ In August 2023, we completed scale testing on 10k Windows hosts and 70k macOS ho
Detailed results are [here](https://docs.google.com/document/d/1OSpyzMkHjVhG_-EIBkLu7X3hj_XfVASGl3IXIYChpck/edit?usp=sharing).
-
-
-
-
+
+
+
+
+
+
diff --git a/articles/config-less-fleetd-agent-deployment.md b/articles/config-less-fleetd-agent-deployment.md
index 6b4258d75146..eb37159fdcf5 100644
--- a/articles/config-less-fleetd-agent-deployment.md
+++ b/articles/config-less-fleetd-agent-deployment.md
@@ -72,6 +72,14 @@ fleetctl package --type=pkg --use-system-configuration --fleet-desktop
msiexec /i fleetd-base.msi FLEET_URL="" FLEET_SECRET=""
```
+Also, you can optionally pass `ENABLE_SCRIPTS`, `END_USER_EMAIL`, and `FLEET_DESKTOP` to the installer.
+
+For example, this command would install fleetd with script execution enabled, custom human-device mapping set, and Fleet Desktop enabled:
+
+```xml
+msiexec /i fleetd-base.msi ENABLE_SCRIPTS=true END_USER_EMAIL="user@example.com" FLEET_DESKTOP=true FLEET_URL="" FLEET_SECRET=""
+```
+
These steps are a flexible alternative to deploying Fleet's agent across macOS and Windows platforms. This method, focused on separating the configuration from the `fleetd` package, empowers you with more control and simplifies the management of your device enrollments.
This approach complements the original packaging method, allowing you to choose the best fit for your organization’s needs. Whether you prioritize streamlined package generation or prefer granular control over configuration distribution, these methods foster an open, flexible environment for deploying Fleet.
diff --git a/docs/Using Fleet/MDM-custom-OS-settings.md b/articles/custom-os-settings.md
similarity index 84%
rename from docs/Using Fleet/MDM-custom-OS-settings.md
rename to articles/custom-os-settings.md
index 5d2b740b0145..aadafc1f844f 100644
--- a/docs/Using Fleet/MDM-custom-OS-settings.md
+++ b/articles/custom-os-settings.md
@@ -1,6 +1,6 @@
# Custom OS settings
-In Fleet you can enforce OS settings on your macOS and Windows hosts using configuration profiles.
+In Fleet you can enforce OS settings like security restrictions, screen lock, Wi-Fi etc., on your your macOS, iOS, iPadOS, and Windows hosts using configuration or device profiles.
## Enforce OS settings
@@ -36,7 +36,9 @@ In the top box, with "Verified," "Verifying," "Pending," and "Failed" statuses,
In the list of hosts, click on an individual host and click the **OS settings** item to see the status for a specific setting.
-
-
+
+
+
+
+
-
diff --git a/articles/debunk-the-cross-platform-myth.md b/articles/debunk-the-cross-platform-myth.md
new file mode 100644
index 000000000000..80d81c919c98
--- /dev/null
+++ b/articles/debunk-the-cross-platform-myth.md
@@ -0,0 +1,59 @@
+# Debunk the cross-platform myth
+
+Conventional wisdom holds that cross-platform device management is a nightmare. It’s no surprise—most solutions out there are cobbled together with bolted-on features that never quite mesh. If you’ve tried managing a mixed fleet of macOS, Windows, and Linux devices, you might have some scars to show for it. But here’s the thing: it doesn’t have to be that way. Fleet is built differently, and it’s time to debunk the myth that cross-platform management has to suck.
+
+## Cross-platform pain points
+
+The skepticism around cross-platform device management is real, and for good reason. Many IT teams have been burned by solutions that promise seamless management across different operating systems but deliver only frustration and complexity. Solutions that often leave a trail of disappointed admins in their wake, often forcing you to manage the tools more than the devices. Fleet flips that script by letting you interact directly with each operating system’s native features. Whether Apple’s macOS, Microsoft’s Windows, or various Linux distributions, Fleet provides a consistent management experience without forcing you to “talk Windows” to your Macs or vice versa.
+
+
+## Managing every OS like it’s your favorite
+
+Fleet introduces familiar concepts like custom attributes and dynamic grouping but adapts them to work with the nuances of each operating system. This means you can manage your macOS, Windows, and Linux devices without juggling multiple management platforms or dealing with convoluted workarounds. Everything is streamlined in one open-source platform, giving you direct access to the data and events from each OS.
+
+By working directly with native operating system features, Fleet ensures you don’t lose low-level control or compromise on capabilities. Instead of managing multiple MDM solutions, you can focus on managing your devices—regardless of OS.
+
+For example:
+
+* **Operating systems**: You can enforce OS updates with Declarative Device Management (DDM), Nudge, and Windows Update from one console.
+* **Automated enrollment**: Drop-ship devices to your end users with Apple Business Manager or Autopilot and let them set up their own accounts. No IT help is needed.
+* **Config management**: Manage settings with configuration profiles for Apple and device profiles for Windows. Use a canary team to test changes before they go live.
+* **App management**: Automatically keep applications and plugins secure and up-to-date. Install the software end users need or let them install it themselves via self-service.
+* **Scripts and events**: Easily manage and version control your custom script library. Execute shell and PowerShell scripts when computers drift from the baseline.
+* **Keep up with Apple**: Fleet's team and community stay current on the latest features and releases from all supported platform vendors, not just Apple.
+
+## Switching platforms is disruptive
+
+It’s understandable to be cautious about adopting a new management solution, especially if you’re concerned about the time and effort involved in switching. However, Fleet is designed with ease of transition in mind. Our platform integrates seamlessly with your existing tools and workflows, minimizing disruption. Plus, with our comprehensive documentation and responsive community support, you’ll have everything you need to get up and running quickly. Fleet’s flexible deployment options let you start small and scale at your pace, ensuring a smooth, controlled migration.
+
+![Migrate to Fleet dialog](../website/assets/images/articles/debunk-the-cross-platform-myth-600x521@2x.png "Migrate to Fleet dialog")
+
+## One platform, many possibilities
+
+Fleet isn’t just about making cross-platform management tolerable—it’s about making it genuinely effective. With Fleet, you can enforce OS updates, automate device enrollment, manage configurations, and keep applications secure, all from one place. You can also deploy Fleet yourself at any time; it’s 100% source-available, meaning you can look at the source code for how any part of it works.
+
+And because Fleet is open-source, it’s designed with flexibility and transparency in mind. You can tailor it to fit your organization’s needs, whether you’re managing a few hundred devices or tens of thousands.
+
+
+
+Mad props to how easy making a deploy pkg of the agent was. I wish everyone made stuff that easy.
+
+
+
+_Wes Whetstone, Staff CPE at Stripe_
+
+
+## The takeaway
+
+Cross-platform management doesn’t have to be the headache it’s been in the past. Fleet is here to simplify how you manage your devices, no matter what mix of operating systems you’re dealing with. It’s time to let go of the myth that managing different platforms means managing different tools. With Fleet, you can have everything you need in one place—without the anxiety.
+
+Ready to get started?
+
+Visit our [start page](https://fleetdm.com/start) to begin your journey.
+
+
+
+
+
+
+
diff --git a/articles/deploy-fleet-on-aws-ecs.md b/articles/deploy-fleet-on-aws-ecs.md
index ac9428865b93..38b802fb9b36 100644
--- a/articles/deploy-fleet-on-aws-ecs.md
+++ b/articles/deploy-fleet-on-aws-ecs.md
@@ -1,6 +1,6 @@
# Deploy Fleet on AWS ECS
-> **This article was archived on May 16, 2024,** and may be outdated. Check out [Deploy Fleet](https://fleetdm.com/docs/deploy/deploy-fleet) for our recommended deployment method.
+> **This article was archived on May 16, 2024.** Check out [Deploy Fleet](https://fleetdm.com/docs/deploy/deploy-fleet) for the most up to date deployment method.
![Deploy Fleet on AWS ECS](../website/assets/images/articles/deploy-fleet-on-aws-ecs-800x450@2x.png)
diff --git a/articles/deploy-fleet-on-aws-with-terraform.md b/articles/deploy-fleet-on-aws-with-terraform.md
index 6853032a50ae..38f8de9b955a 100644
--- a/articles/deploy-fleet-on-aws-with-terraform.md
+++ b/articles/deploy-fleet-on-aws-with-terraform.md
@@ -1,6 +1,6 @@
# Deploy Fleet on AWS with Terraform
-> **This article was archived on May 16, 2024,** and may be outdated. Check out [Deploy Fleet](https://fleetdm.com/docs/deploy/deploy-fleet) for our recommended deployment method.
+> **This article was archived on May 16, 2024.** Check out [Deploy Fleet](https://fleetdm.com/docs/deploy/deploy-fleet) for the most up to date deployment method.
![Deploy Fleet on AWS ECS](../website/assets/images/articles/deploy-fleet-on-aws-with-terraform-800x450@2x.png)
diff --git a/articles/deploy-fleet-on-centos.md b/articles/deploy-fleet-on-centos.md
index b832e0f4f14a..f9439d577374 100644
--- a/articles/deploy-fleet-on-centos.md
+++ b/articles/deploy-fleet-on-centos.md
@@ -1,6 +1,6 @@
# Deploy Fleet on CentOS
-> **This article was archived on May 16, 2024,** and may be outdated. Check out [Deploy Fleet](https://fleetdm.com/docs/deploy/deploy-fleet) for our recommended deployment method.
+> **This article was archived on May 16, 2024.** Check out [Deploy Fleet](https://fleetdm.com/docs/deploy/deploy-fleet) for the most up to date deployment method.
![Deploy Fleet on CentOS](../website/assets/images/articles/deploy-fleet-on-centos-800x450@2x.png)
diff --git a/articles/deploy-fleet-on-cloudgov.md b/articles/deploy-fleet-on-cloudgov.md
index 5e5e2b164455..18f0c238a854 100644
--- a/articles/deploy-fleet-on-cloudgov.md
+++ b/articles/deploy-fleet-on-cloudgov.md
@@ -1,7 +1,5 @@
# Deploy Fleet on Cloud.gov (Cloud Foundry)
-> **This article was archived on May 16, 2024,** and may be outdated. Check out [Deploy Fleet](https://fleetdm.com/docs/deploy/deploy-fleet) for our recommended deployment method.
-
![Deploy Fleet on Cloud.gov](../website/assets/images/articles/deploy-fleet-on-cloudgov-800x450@2x.png)
Cloud.gov is a [FEDRAMP moderate Platform-as-a-Service
diff --git a/articles/deploy-fleet-on-hetzner-cloud.md b/articles/deploy-fleet-on-hetzner-cloud.md
index 82dac9495c50..ec6c7da9a9aa 100644
--- a/articles/deploy-fleet-on-hetzner-cloud.md
+++ b/articles/deploy-fleet-on-hetzner-cloud.md
@@ -1,6 +1,6 @@
# Deploy Fleet on Hetzner Cloud
-> **This article was archived on May 16, 2024,** and may be outdated. Check out [Deploy Fleet](https://fleetdm.com/docs/deploy/deploy-fleet) for our recommended deployment method.
+> **This article was archived on May 16, 2024.** Check out [Deploy Fleet](https://fleetdm.com/docs/deploy/deploy-fleet) for the most up to date deployment method.
![Deploy Fleet on Hetzner Cloud](../website/assets/images/articles/deploy-fleet-on-hetzner-cloud-800x450@2x.png)
@@ -69,14 +69,14 @@ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docke
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
-docker pull mysql@sha256:16e159331007eccc069822f7b731272043ed572a79a196a05ffa2ea127caaf67 # mysql:5.7.38 as of 2022/05/19
+docker pull mysql@sha256:134e2d1c7c517d05e5328a77aa5a165a314dc4c4116503e7e089494f4e398ab1 # mysql:8.0.36 as of 2024/07/04
######################
# MySQL (dockerized) #
######################
-# mysql:5.7.38 as of 2022/05/19
-docker pull mysql@sha256:16e159331007eccc069822f7b731272043ed572a79a196a05ffa2ea127caaf67
+# mysql:8.0.36 as of 2024/07/04
+docker pull mysql@sha256:134e2d1c7c517d05e5328a77aa5a165a314dc4c4116503e7e089494f4e398ab1
# Create the Fleet MySQL data folder
mkdir -p /etc/fleet
@@ -102,14 +102,14 @@ Restart=always
ExecStartPre=-/usr/bin/docker exec %n stop
ExecStartPre=-/usr/bin/docker rm %n
-ExecStartPre=-/usr/bin/docker pull mysql@sha256:16e159331007eccc069822f7b731272043ed572a79a196a05ffa2ea127caaf67
+ExecStartPre=-/usr/bin/docker pull mysql@sha256:134e2d1c7c517d05e5328a77aa5a165a314dc4c4116503e7e089494f4e398ab1
ExecStart=/usr/bin/docker run --rm \
--name %n \
-p 127.0.0.1:3306:3306 \
-v /etc/fleet/mysql:/var/lib/mysql \
--env-file /etc/fleet/mysql.env \
- mysql@sha256:16e159331007eccc069822f7b731272043ed572a79a196a05ffa2ea127caaf67
+ mysql@sha256:134e2d1c7c517d05e5328a77aa5a165a314dc4c4116503e7e089494f4e398ab1
ExecStop=/usr/bin/docker stop %n
@@ -435,7 +435,7 @@ To run MySQL, we’ll have to do the following:
We can pull the [official MySQL docker image](https://hub.docker.com/_/mysql) like so:
```sh
-$ docker pull mysql@sha256:16e159331007eccc069822f7b731272043ed572a79a196a05ffa2ea127caaf67 # mysql:5.7.38 as of 2022/05/19
+$ docker pull mysql@sha256:134e2d1c7c517d05e5328a77aa5a165a314dc4c4116503e7e089494f4e398ab1 # mysql:8.0.36 as of 2024/07/04
```
### Create & enable a systemd unit for MySQL
@@ -472,14 +472,14 @@ Restart=always
ExecStartPre=-/usr/bin/docker exec %n stop
ExecStartPre=-/usr/bin/docker rm %n
-ExecStartPre=-/usr/bin/docker pull mysql@sha256:16e159331007eccc069822f7b731272043ed572a79a196a05ffa2ea127caaf67
+ExecStartPre=-/usr/bin/docker pull mysql@sha256:134e2d1c7c517d05e5328a77aa5a165a314dc4c4116503e7e089494f4e398ab1
ExecStart=/usr/bin/docker run --rm \
--name %n \
-p 127.0.0.1:3306:3306 \
-v /etc/fleet/mysql:/var/lib/mysql \
--env-file /etc/fleet/mysql.env \
- mysql@sha256:16e159331007eccc069822f7b731272043ed572a79a196a05ffa2ea127caaf67
+ mysql@sha256:134e2d1c7c517d05e5328a77aa5a165a314dc4c4116503e7e089494f4e398ab1
ExecStop=/usr/bin/docker stop %n
@@ -712,4 +712,4 @@ Now that you’re ready to use Fleet and have a host installed. Here's some next
-
\ No newline at end of file
+
diff --git a/articles/deploy-fleet-on-kubernetes.md b/articles/deploy-fleet-on-kubernetes.md
index ea85bbb07d2e..5e5144efc526 100644
--- a/articles/deploy-fleet-on-kubernetes.md
+++ b/articles/deploy-fleet-on-kubernetes.md
@@ -1,6 +1,6 @@
# Deploy Fleet on Kubernetes
-> **This article was archived on May 16, 2024,** and may be outdated. Check out [Deploy Fleet](https://fleetdm.com/docs/deploy/deploy-fleet) for our recommended deployment method.
+> **This article was archived on May 16, 2024.** Check out [Deploy Fleet](https://fleetdm.com/docs/deploy/deploy-fleet) for the most up to date deployment method.
![Deploy Fleet on Kubernetes](../website/assets/images/articles/deploy-fleet-on-kubernetes-800x450@2x.png)
diff --git a/articles/deploy-fleet-on-render.md b/articles/deploy-fleet-on-render.md
index 1ec25ae1ed71..703d872cf6f2 100644
--- a/articles/deploy-fleet-on-render.md
+++ b/articles/deploy-fleet-on-render.md
@@ -1,6 +1,6 @@
# Deploy Fleet on Render
-> **This article was archived on May 16, 2024,** and may be outdated. Check out [Deploy Fleet](https://fleetdm.com/docs/deploy/deploy-fleet) for our recommended deployment method.
+> **This article was archived on May 16, 2024.** Check out [Deploy Fleet](https://fleetdm.com/docs/deploy/deploy-fleet) for the most up to date deployment method.
![Deploy Fleet on Render](../website/assets/images/articles/deploy-fleet-on-render-800x450@2x.png)
@@ -31,7 +31,9 @@ The Fleet server and user interface are packaged into a Docker image and hosted
## Setup Fleet and enroll hosts
-The first time you access your Fleet instance you will be prompted with a setup page where you can enter your name, email, and password. Run through those steps to reach the Fleet dashboard.
+The first time you access your Fleet instance, you will be prompted with a setup page where you can enter your name, email, and password. Run through those steps to reach the Fleet dashboard.
+
+> Set a strong and unique password instead of the default password during the setup process.
You’ll find the enroll-secret after clicking “Add hosts”. This is a special secret the host will need to register to your Fleet instance. Once you have the enroll-secret you can use `fleetctl` to generate Fleet's agent (fleetd), which makes installing and updating osquery super simple.
@@ -86,4 +88,4 @@ That’s it! We have successfully deployed and configured a Fleet instance! Rend
-
\ No newline at end of file
+
diff --git a/articles/deploy-security-agents.md b/articles/deploy-security-agents.md
new file mode 100644
index 000000000000..20d6cd28abec
--- /dev/null
+++ b/articles/deploy-security-agents.md
@@ -0,0 +1,97 @@
+# Deploy security agents
+
+![Deploy security agents](../website/assets/images/articles/deploy-security-agents-1600x900@2x.png)
+
+Fleet [v4.50.0](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.50.0) introduced the ability to upload and deploy security agents to your hosts. Beyond a [bootstrap package](https://fleetdm.com/docs/using-fleet/mdm-macos-setup-experience#bootstrap-package) at enrollment, deploying security agents allows you to specify and verify device configuration using a pre-enrollment osquery query and customization of the install and post-install scripts, allowing for key and license deployment and configuration. This guide will walk you through the steps to upload, configure, and install a security agent to hosts in your fleet.
+
+## Prerequisites
+
+* Fleet [v4.50.0](https://github.com/fleetdm/fleet/releases/tag/fleet-v4.50.0).
+* `fleetd` 1.25.0 deployed via MDM or built with the `--scripts-enabled` flag.
+* An S3 bucket [configured](https://fleetdm.com/docs/configuration/fleet-server-configuration#s-3-software-installers-bucket) to store the installers.
+* Increase any load balancer timeouts to at least 5 minutes for the following endpoints:
+ * [Add software](https://fleetdm.com/docs/rest-api/rest-api#add-software).
+ * [Batch-apply software](https://fleetdm.com/docs/rest-api/rest-api#add-software).
+
+## Step-by-step instructions
+
+### Access security agent installers
+
+To access and manage security agents in Fleet:
+
+* **Navigate to the Software page**: Click on the "Software" tab in the main navigation menu.
+* **Select a team**: Click on the dropdown at the top left of the page.
+* **Find your software**: using the filters on the top of the table, you can choose between:
+ * “Available for install” filters software that can be installed on your hosts.
+ * “Self-service” filters software that end users can install from Fleet Desktop.
+* **Select security agent installer**: Click on a software package to view details and access additional actions for the agent installer.
+
+### Add a security agent to a team
+
+* **Navigate to the Software page**: Click on the "Software" tab in the main navigation menu.
+* **Select a team**: Select a team or the "No team" team to add a security agent.
+
+> Security agents cannot be added to "All teams"
+
+* Click the “Add Software” button in the top right corner, and a modal will appear.
+* Choose a file to upload. `.pkg`, `.msi`, `.exe`, or `.deb` files are supported.
+* After selecting a file, a default install script will be pre-filled. If the security agent requires a custom installation process, this script can be edited.
+* To allow users to install the software from Fleet Desktop, check the “Self-service” checkbox.
+* To customize the conditions, click on “Advanced options”:
+ * **Pre-install condition**: A pre-install condition is a valid osquery SQL statement that will be evaluated on the host before installing the software. If provided, the installation will proceed only if the query returns any value.
+ * **Post-install script** A post-install script will run after the installation is complete, allowing you to configure the security agent right after installation. If this script returns a non-zero exit code, the installation will fail, and `fleetd` will attempt to uninstall the software.
+
+### Install a security agent on a host
+
+After an installer is added to a team, it can be installed on hosts via the UI.
+
+* **Navigate to the Hosts page**: Click on the "Hosts" tab in the main navigation menu.
+* **Navigate to the Host details page**: Click the host you want to install the security agent.
+* **Navigate to the Host software tab**: In the host details, search for the tab named “Software”
+* **Find your security agent**: Use the search bar and filters to search for your security agent.
+* **Install the security agent on the host**: In the leftmost row of the table, click on “Actions” > “Install.”
+* **Track installation status**: by either
+ * Checking the “Install status” in the host software table.
+ * Navigate to the “Details” tab on the host details page and check the activity log.
+
+### Edit a security agent
+
+Security agent installers can’t be edited via the UI. To modify an installer, remove it from the UI and add a new one.
+
+### Remove a security agent from a team
+
+* **Navigate to the Software page**: Click on the "Software" tab in the main navigation menu.
+* **Select a team**: Select a team or the "No team" team to add a security agent.
+* **Find your software**: using the filters on the top of the table, you can choose between:
+ * “Available for install” filters software can be installed on your hosts.
+ * “Self-service” filters software that users can install from Fleet Desktop.
+* **Select security agent installer**: Click on a software package to view details.
+* **Remove security agent installer**: From the Actions menu, select "Delete." Click the "Delete" button on the modal.
+
+> Removing a security agent from a team will not uninstall the agent from the existing host(s).
+
+### Manage security agents with the REST API
+
+Fleet also provides a REST API for managing software programmatically. The API allows you to add, update, retrieve, list, and delete software. Detailed documentation on Fleet's [REST API is available](https://fleetdm.com/docs/rest-api/rest-api#software).
+
+### Manage security agents with GitOps
+
+Installers for security agents can be managed via `fleetctl` using [GitOps](https://fleetdm.com/docs/using-fleet/gitops).
+
+Please refer to the documentation specific to [managing software with GitOps](https://fleetdm.com/docs/using-fleet/gitops#software). For a real-world example, [see how we manage software at Fleet](https://github.com/fleetdm/fleet/tree/main/it-and-security/teams).
+
+
+## Conclusion
+
+Deploying security agents with Fleet is straightforward and ensures your hosts are protected with the latest security measures. This guide has shown you how to access, add, and install security agents, as well as manage them using the REST API and `fleetctl`. Following these steps can effectively equip your fleet with the necessary security tools.
+
+See Fleet's [documentation](https://fleetdm.com/docs/using-fleet) and additional [guides](https://fleetdm.com/guides) for more details on advanced setups, software features, and vulnerability detection.
+
+
+
+
+
+
+
+
+
diff --git a/articles/discovering-chrome-ai-using-fleet.md b/articles/discovering-chrome-ai-using-fleet.md
new file mode 100644
index 000000000000..72b73627d608
--- /dev/null
+++ b/articles/discovering-chrome-ai-using-fleet.md
@@ -0,0 +1,67 @@
+# Discovering Chrome AI using Fleet
+
+![Discovering Chrome AI using Fleet](../website/assets/images/articles/discovering-chrome-ai-using-fleet-1600x900@2x.jpg)
+
+# Discovering AI in Chrome with Fleet
+
+Staying ahead of technological innovations is crucial for individuals and organizations. Google Chrome, one of the most widely used web browsers, continually evolves to incorporate new features, including artificial intelligence (AI). This article will guide you through detecting if AI capabilities have been enabled in Chrome using Fleet.
+
+## Introduction to Chrome AI innovations
+
+Google Chrome has integrated AI to enhance user experience by providing intelligent suggestions, improving search results, and offering in-browser assistance. Visit the [Chrome AI Innovations page](https://www.google.com/chrome/ai-innovations/) for more infomration.
+
+## Using Fleet to detect AI features in Chrome
+
+Fleet, a comprehensive device management and security tool, allows you to monitor various aspects of your devices, including software configurations and enabled features. Using Fleet, you can detect whether AI features are enabled in Chrome by querying device settings, specifically in the Chrome "Preferences" JSON file.
+
+### Step 1: Understanding Chrome's preferences JSON file
+
+Chrome stores user settings and configurations in a JSON file at the following path:
+
+```
+/Users//Library/Application Support/Google/Chrome/Default/Preferences
+```
+
+### Step 2: Identifying AI-related settings
+
+AI-related features are stored in the `optimization_guide` section of the preferences. The `tab_organization_setting_state` field will tell you if AI-based tab management features are enabled:
+
+`> jq` is a lightweight and powerful command-line tool for parsing, filtering, and manipulating JSON data. It allows you to extract specific information from JSON files efficiently. In this case, we use `jq` to locate and read the value of the `tab_organization_setting_state` key within Chrome's preference file which will help us understand how to craft our Fleet query for reporting the state of this setting.
+
+- If enabled, the setting will return `1`.
+
+![Chrome settings UI with Chrome AI enabled](../website/assets/images/articles/discovering-chrome-ai-using-fleet-1-1472x370@2x.png)
+
+```
+% jq '.optimization_guide.tab_organization_setting_state' /Users//Library/Application\ Support/Google/Chrome/Default/Preferences
+1
+```
+
+- If disabled, the setting will return `2`.
+
+![Chrome settings UI with Chrome AI disabled](../website/assets/images/articles/discovering-chrome-ai-using-fleet-2-1474x276@2x.png)
+
+```
+% jq '.optimization_guide.tab_organization_setting_state' /Users//Library/Application\ Support/Google/Chrome/Default/Preferences
+2
+```
+
+### Step 3: Query the JSON file with Fleet
+
+To query the JSON file and detect AI features using Fleet, you can use the following SQL query:
+
+```
+SELECT fullkey,path FROM parse_json WHERE path LIKE '/Users/%/Library/Application Support/Google/Chrome/Default/Preferences' AND fullkey='optimization_guide/tab_organization_setting_state';
+```
+
+### Conclusion
+
+Following this guide, you've learned to detect whether AI features are enabled in Google Chrome using Fleet. Fleet's powerful querying abilities allow you to monitor these features across multiple devices, ensuring your organization's preferences and practices align.
+
+
+
+
+
+
+
+
diff --git a/docs/Using Fleet/downgrading-fleet.md b/articles/downgrade-fleet.md
similarity index 87%
rename from docs/Using Fleet/downgrading-fleet.md
rename to articles/downgrade-fleet.md
index 34f8fd04ff9a..0874e27c6603 100644
--- a/docs/Using Fleet/downgrading-fleet.md
+++ b/articles/downgrade-fleet.md
@@ -1,4 +1,4 @@
-# Downgrading from Fleet Premium
+# Downgrade from Fleet Premium
Follow these steps to downgrade your Fleet instance from Fleet Premium.
@@ -34,8 +34,9 @@ Follow these steps to downgrade your Fleet instance from Fleet Premium.
1. Remove your license key from your Fleet configuration. Documentation on where the license key is located in your configuration is [here](https://fleetdm.com/docs/deploying/configuration#license).
2. Restart your Fleet server.
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/Using Fleet/MDM-disk-encryption.md b/articles/enforce-disk-encryption.md
similarity index 76%
rename from docs/Using Fleet/MDM-disk-encryption.md
rename to articles/enforce-disk-encryption.md
index 8d8278763f5b..8dd2b6419ff5 100644
--- a/docs/Using Fleet/MDM-disk-encryption.md
+++ b/articles/enforce-disk-encryption.md
@@ -1,4 +1,4 @@
-# Disk encryption
+# Enforce disk encryption
_Available in Fleet Premium_
@@ -8,7 +8,9 @@ In Fleet, you can enforce disk encryption for your macOS and Windows hosts.
When disk encryption is enforced, hosts’ disk encryption keys will be stored in Fleet.
-For Windows hosts, 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).
## Enforce disk encryption
@@ -54,15 +56,13 @@ How to view the disk encryption key:
## Migrate macOS hosts
-When migrating macOS hosts another MDM solution, in order to complete the process of encrypting the hard drive and escrowing the key in Fleet, your end users must take action.
-
-If the host already had disk encryption turned on, the user will need to input their password.
-
-If the host did not already have disk encryption turned on, the user will need to log out or restart their computer.
+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.
-Share [these guided instructions](./MDM-migration-guide.md#how-to-turn-on-disk-encryption) with your end users.
+Share [these guided instructions](https://fleetdm.com/guides/mdm-migration#how-to-turn-on-disk-encryption) with your end users.
-
-
+
+
+
+
+
-
diff --git a/docs/Using Fleet/MDM-OS-updates.md b/articles/enforce-os-updates.md
similarity index 65%
rename from docs/Using Fleet/MDM-OS-updates.md
rename to articles/enforce-os-updates.md
index 6986593c764e..3db4862a8768 100644
--- a/docs/Using Fleet/MDM-OS-updates.md
+++ b/articles/enforce-os-updates.md
@@ -1,18 +1,14 @@
-# OS updates
+# Enforce OS updates
_Available in Fleet Premium_
-In Fleet you can enforce OS updates on your macOS and Windows hosts remotely.
-
-## Enforce OS updates
-
-You can enforce OS updates using the Fleet UI, Fleet API, or [Fleet's GitOps workflow](https://github.com/fleetdm/fleet-gitops).
+In Fleet you can enforce OS updates on your macOS, Windows, iOS, and iPadOS hosts remotely using the Fleet UI, Fleet API, or [Fleet's GitOps workflow](https://github.com/fleetdm/fleet-gitops).
Fleet UI:
1. Head to the **Controls** > **OS updates** tab.
-2. To enforce OS updates for macOS, select **macOS** and set a **Minimum version** and **Deadline**.
+2. To enforce OS updates for macOS, iOS, or iPadOS, select the platform and set a **Minimum version** and **Deadline**.
3. For Windows, select **Windows** and set a **Deadline** and **Grace period**.
@@ -26,6 +22,10 @@ When a minimum version is enforced, the end users see a native macOS notificatio
If the host was turned off when the deadline passed, the update will be scheduled an hour after it’s turned on.
+For macOS devices that use Automated Device Enrollment (ADE), if the device is below the specified
+minimum version, it will be required to update to the very latest OS version during ADE before
+device setup and enrollment can proceed.
+
### macOS (below version 14.0)
End users are encouraged to update macOS (via [Nudge](https://github.com/macadmins/nudge)).
@@ -38,6 +38,11 @@ End users are encouraged to update macOS (via [Nudge](https://github.com/macadmi
| End user can defer | ✅ | ✅ | ❌ |
| Nudge window is dismissible | ✅ | ✅ | ❌ |
+### iOS and iPadOS (version 17.0 and above)
+
+For iOS and iPadOS devices that use Automated Device Enrollment (ADE), if the device is below the specified
+minimum version, it will be required to update to the very latest OS version during ADE before device setup and enrollment can proceed.
+
### Windows
End users are encouraged to update Windows via the native Windows dialog.
@@ -50,7 +55,13 @@ If an end user was on vacation when the deadline passed, the end user is given a
Fleet enforces OS updates for quality and feature updates. Read more about the types of Windows OS updates in the Microsoft documentation [here](https://learn.microsoft.com/en-us/windows/deployment/update/get-started-updates-channels-tools#types-of-updates).
-
-
-
-
+### iOS and iPadOS
+
+When a minimum version is enforced, end users will see a notification in their Notification Center after the deadline. They can’t use their iPhone or iPad until the OS update is installed.
+
+
+
+
+
+
+
diff --git a/articles/enhancing-k-12-cybersecurity-with-fcc-funds-and-fleet.md b/articles/enhancing-k-12-cybersecurity-with-fcc-funds-and-fleet.md
new file mode 100644
index 000000000000..92a32aa05bab
--- /dev/null
+++ b/articles/enhancing-k-12-cybersecurity-with-fcc-funds-and-fleet.md
@@ -0,0 +1,48 @@
+# Enhancing K-12 cybersecurity with FCC funds and Fleet
+
+![Enhancing K-12 cybersecurity with FCC funds and Fleet](../website/assets/images/articles/enhancing-k-12-cybersecurity-with-fcc-funds-and-fleet-1600x900@2x.png)
+
+The digital landscape in K-12 education is evolving rapidly, and with this transformation comes the critical need to safeguard school networks and data. The new [Federal Communications Commission (FCC) schools and libraries cybersecurity pilot program](https://www.fcc.gov/cybersecurity-pilot-program) offers a significant opportunity for schools to enhance their cybersecurity infrastructure. One of the most effective ways to utilize these funds is by investing in Fleet, an open-source endpoint visibility platform designed to improve endpoint protection, identity protection and authentication, and monitoring, detection, and response.
+
+
+## A real threat
+
+Did you know that [K-12 schools are now the top target for ransomware attacks](https://www.forbes.com/sites/frederickhess/2023/09/20/the-top-target-for-ransomware-its-now-k-12-schools/)? This startling reality is impacting educational institutions nationwide. Imagine a typical school day disrupted by a malicious cyber attack, compromising sensitive student and staff data. This is not just a hypothetical scenario; it’s happening more frequently than ever before.
+
+For instance, in a recent incident covered by [NPR](https://www.npr.org/2024/03/11/1236995412/cybersecurity-hackers-schools-ransomware), a school district in Toledo, Ohio, was paralyzed by a ransomware attack that locked teachers out of their computers and halted online classes for over a week. This real-world example underscores the urgency of bolstering cybersecurity measures. That's why the FCC's new cybersecurity pilot program couldn't have arrived sooner.
+
+
+## Endpoint protection
+
+Endpoint protection is paramount in a school environment where numerous devices connect to the network daily. Fleet provides comprehensive endpoint security by ensuring all devices are accounted for and secure. Schools can deploy Fleet to monitor laptops, desktops, and mobile devices used by students, teachers, and administrative staff. This visibility allows security teams to detect and mitigate threats in real time, preventing potential breaches. Software vulnerabilities are captured and prioritized for remediation using multiple popular scoring mechanisms. Fleet’s MDM (Mobile Device Management) features can remediate the highest-risk vulnerabilities. FCC funds can cover the costs of deploying and maintaining Fleet, ensuring that all endpoints are continuously monitored and protected against malicious activities.
+
+
+## Identity protection and authentication
+
+Identity protection and authentication are crucial in preventing unauthorized access to sensitive information. Fleet integrates seamlessly with existing identity management systems to enhance security measures. By using Fleet, schools can implement zero trust architectures leveraging Fleet’s device health capabilities leverage osquery. This integration ensures that only devices with approved security postures can access the school’s network and resources. With financial support from the FCC program, schools can invest in the necessary infrastructure to support these advanced authentication methods, thereby significantly reducing the risk of unauthorized access.
+
+
+## Monitoring, detection, and response
+
+Effective cybersecurity is about prevention and the ability to monitor, detect, and respond to incidents swiftly. Fleet excels in providing real-time monitoring and detection capabilities. It enables security teams to set up alerts for suspicious activities and promptly investigate potential threats. This proactive approach is essential for minimizing the impact of security incidents. The FCC funds can be allocated to train staff to use Fleet’s features effectively and establish a robust detection platform and incident response plan. This ensures that schools are not only protected but are also prepared to handle cybersecurity incidents efficiently.
+
+
+## Applying
+
+Eligible schools, libraries, and consortia should follow a two-part application process to apply for the FCC Schools and Libraries Cybersecurity Pilot Program, which will be opened by the FCC this Fall, 2024. First, applicants submit general information and details about the proposed cybersecurity project. Second, selected participants provide more detailed cybersecurity information. The FCC will announce selected participants via a public notice, who will then proceed with competitive bidding and reimbursement requests. For detailed instructions, visit the [FCC Cybersecurity Pilot Program page](https://www.fcc.gov/cybersecurity-pilot-program).
+
+
+## Conclusion
+
+The FCC Schools and Libraries Cybersecurity Pilot Program is a game-changer for K-12 schools looking to bolster their cybersecurity defenses. By investing in Fleet, schools can achieve comprehensive endpoint protection, enhance identity protection and authentication, and improve their monitoring, detection, and response capabilities. This strategic use of funds will ensure that schools can provide a safe and secure digital learning environment for all students and staff, safeguarding their data and maintaining the integrity of their educational programs.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/Using Fleet/enroll-hosts.md b/articles/enroll-hosts.md
similarity index 95%
rename from docs/Using Fleet/enroll-hosts.md
rename to articles/enroll-hosts.md
index 952383ec57f0..0635855596ab 100644
--- a/docs/Using Fleet/enroll-hosts.md
+++ b/articles/enroll-hosts.md
@@ -1,7 +1,5 @@
# Enroll hosts
-## Introduction
-
Fleet gathers information from an [osquery](https://github.com/osquery/osquery) agent installed on each of your hosts. The recommended way to install osquery is using fleetd.
You can enroll macOS, Windows or Linux hosts via the [CLI](#cli) or [UI](#ui). To learn how to enroll Chromebooks, see [Enroll Chromebooks](#enroll-chromebooks).
@@ -12,11 +10,11 @@ Fleet supports the [latest version of osquery](https://github.com/osquery/osquer
## CLI
-> You must have `fleetctl` installed. [Learn how to install `fleetctl`](https://fleetdm.com/fleetctl-preview).
+> You must have `fleetctl` installed. [Learn how to install `fleetctl`](https://fleetdm.com/docs/using-fleet/fleetctl-cli#installing-fleetctl).
-The `fleetctl package` command is used to generate Fleet's agent (fleetd).
+The `fleetctl package` command is used to generate Fleet's agent (fleetd) install package..
-The `--type` flag is used to specify the fleetd installer type:
+The `--type` flag is used to specify the fleetd installer type. Note that Windows can only generate an MSI package:
- macOS: .pkg
- Windows: .msi
- Linux: .deb or .rpm
@@ -39,7 +37,7 @@ To generate Fleet's agent (fleetd) in Fleet UI:
1. Go to the **Hosts** page, and select **Add hosts**.
2. Select the tab for your desired platform (e.g. macOS).
-3. A CLI command with all necessary flags will be generated. Copy and run the command with [fleetctl](https://fleetdm.com/docs/using-fleet/fleetctl-cli) installed.
+3. A CLI command with all necessary flags to generate an install package will be generated. Copy and run the command with [fleetctl](https://fleetdm.com/docs/using-fleet/fleetctl-cli) installed.
### Enroll host to a specific team
@@ -54,7 +52,7 @@ You can use your software management tool of choice to distribute Fleet's agent
### Fleet Desktop
-[Fleet Desktop](./Fleet-desktop.md) is a menu bar icon available on macOS, Windows, and Linux that gives your end users visibility into the security posture of their machine.
+[Fleet Desktop](https://fleetdm.com/guides/fleet-desktop) is a menu bar icon available on macOS, Windows, and Linux that gives your end users visibility into the security posture of their machine.
You can include Fleet Desktop in Fleet's agent (fleetd) by including `--fleet-desktop` in the `fleetctl package` command.
@@ -379,6 +377,9 @@ but can result in a large volume of error logs. In fleetd v1.15.1, we added an e
Applying the environmental variable `"FLEETD_SILENCE_ENROLL_ERROR"=1` on a host will silence fleetd enrollment errors if a `--fleet-url` is not present.
This variable is read at launch and will require a restart of the Orbit service if it is not set before installing `fleetd` v1.15.1.
-
+
+
+
+
+
-
diff --git a/articles/filtering-software-by-vulnerability.md b/articles/filtering-software-by-vulnerability.md
new file mode 100644
index 000000000000..8c8326bbc690
--- /dev/null
+++ b/articles/filtering-software-by-vulnerability.md
@@ -0,0 +1,44 @@
+# Filtering software by vulnerability in Fleet
+
+![Filtering software by vulnerability in Fleet](../website/assets/images/articles/discovering-geacon-using-fleet-1600x900@2x.jpg)
+
+## Introduction
+
+Fleet has introduced a powerful new feature that allows you to filter software by its associated vulnerabilities, helping you prioritize patches more effectively. Whether you're managing hundreds or thousands of software titles, this feature makes it easier to identify and address the most critical vulnerabilities in your environment.
+
+This filtering capability is particularly useful in environments where patch management is critical to your security posture. By filtering software based on vulnerability severity and known exploits, you can first ensure that the most critical issues are addressed, enhancing your overall security strategy.
+
+## Prerequisites
+
+* Fleet version 4.56 or later
+* Premium users have access to advanced filters by severity level and known exploited vulnerabilities
+
+### Filtering Software by Vulnerability
+
+1. **Navigate to the Software page**: In your Fleet dashboard, go to the **Software** tab. This will display a list of all the software detected in your environment.
+
+2. **Add filters**: Click on the **Add Filters** button. This will open options for filtering the software list based on specific criteria.
+
+3. **Choose severity level**: From the dropdown menu, select the **Severity level** of vulnerabilities you're interested in. This allows you to focus on software with the highest severity of vulnerabilities, such as "Critical" or "High."
+
+4. **Toggle "Has known exploit"**: You can refine your filter by toggling the **Has known exploit** option. This will filter the software list to show only those with vulnerabilities that have known exploits, enabling you to prioritize these for patching.
+
+5. **Review filtered results**: Once you've applied your filters, the software list will update to show only the software that meets your criteria. This filtered view will help you prioritize which software needs immediate attention in your patching strategy.
+
+### Using the REST API to filter software for vulnerabilities
+
+Fleet provides a REST API to filter software for vulnerabilities, allowing you to integrate this functionality into your automated workflows. Learn more about Fleet's [REST API](https://fleetdm.com/docs/rest-api/rest-api#vulnerabilities).
+
+## Conclusion
+
+The new software filtering feature in Fleet makes it easier than ever to manage vulnerabilities in your software environment. You can better protect your organization from potential threats by prioritizing patches based on severity and known exploits. Explore the API capabilities to integrate this feature into your broader security workflows.
+
+For more tips and detailed guides, don’t forget to check out the Fleet [documentation](https://fleetdm.com/docs/get-started/why-fleet).
+
+
+
+
+
+
+
+
diff --git a/articles/fleet-4.54.0.md b/articles/fleet-4.54.0.md
new file mode 100644
index 000000000000..798090428f69
--- /dev/null
+++ b/articles/fleet-4.54.0.md
@@ -0,0 +1,129 @@
+# 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.
+
+## Highlights
+
+* Target hosts via label exclusion
+* Remote script execution time
+
+### Target hosts via label exclusion
+
+Fleet has enhanced its targeting capabilities by adding support for excluding specific labels when managing and deploying configuration profiles to hosts. This feature allows administrators to precisely control which devices are affected by particular settings or policies by excluding hosts that match specified labels. For instance, if an organization has a group of conference room computers that should not receive a particular configuration, administrators can now easily exclude these devices by applying the relevant label exclusions. This added granularity ensures more accurate and tailored management of devices, reducing the risk of unintended changes and enhancing overall operational efficiency. By allowing the exclusion of any label when targeting hosts, Fleet demonstrates its commitment to providing flexible, user-centric solutions that cater to the nuanced needs of modern IT environments.
+
+### Remote script execution time
+
+Fleet has increased the timeout limit for remote script execution, significantly enhancing its capabilities for deploying software updates and managing complex workflows. This extension allows administrators to run longer, more intricate scripts without interruption, facilitating tasks such as deploying software updates through tools like Homebrew, Installomator, and Chocolatey. Additionally, this update supports extensive log gathering operations and workflows that involve moving data in either direction, making it ideal for comprehensive maintenance and configuration activities. By extending the timeout limit, Fleet ensures that IT teams can execute more demanding tasks efficiently, improving overall operational effectiveness and flexibility. This enhancement reflects Fleet's commitment to providing robust and adaptable solutions that meet the evolving needs of modern IT environments.
+
+## Changes
+
+### Endpoint Operations
+
+- Updated `fleetctl gitops` to be used to rename teams.
+ - **NOTE:** `fleetctl gitops` needs to have previously run with this Fleet/fleetctl version or later.
+ - The team name is changed if the YAML config is applied from the same filename as before.
+- Updated `fleetctl query --hosts` to work with hostnames, host UUIDs, and/or hardware serial numbers.
+- Added a host's upcoming scheduled maintenance window, if any, on the host details page of the UI and in host responses from the API.
+- Added support to `fleetctl debug connection` to test TLS connection with the embedded certs.pem in
+ the fleetctl executable.
+- Added host's display name to calendar event descriptions.
+- Added .yml and .yaml file type validation and error message to `fleetctl apply`.
+- Added a tooltip to truncated text and not to untruncated values.
+
+### Device Management (MDM)
+
+- Added iOS/iPadOS builtin manual labels.
+ - **NOTE:** Before migrating to this version, make sure to delete any labels with name "iOS" or "iPadOS".
+- Added aggregation of iOS/iPadOS OS versions.
+- Added change to custom profiles for iOS/iPadOS to go from 'pending' straight to 'verified' (skip 'verifying').
+- Added support for renewing SCEP certificates with custom enrollment profiles.
+- Added automatic install of `fleetd` when a host turns on MDM now uses the latest released `fleetd` version.
+- Added support for `END_USER_EMAIL` and `FLEET_DESKTOP` parameters to Windows MSI install package.
+- Added API changes to support the `labels_include_all` and `labels_exclude_any` fields (and accept the deprecated `labels` field as an alias for `labels_include_all`).
+- Added `fleetctl gitops` and `fleetctl apply` support for `labels_include_all` and `labels_exclude_any` to configure a custom setting.
+- Added UI for uploading custom profiles with a target of hosts that include all/exclude any selected labels.
+- Added the database migrations to create the new `exclude` column for labels associated with MDM profiles (and declarations).
+- Updated host script timeouts to be configurable via agent options using `script_execution_timeout`.
+- `fleetctl` now uses a polling mechanism when running `run-script` to accommodate longer script timeout values.
+- Updated the profile reconciliation logic to handle the new "exclude any" labels.
+- Updated so that the `fleetd` cleanup script for macOS that will return completed when run from Fleet.
+- Updated so that the `fleetd` uninstall script will return completed when run from Fleet.
+- Updated script run permissions -- only admins and maintainers can run arbitrary or saved scripts (not observer or observer+).
+- Updated `fleetctl get mdm_commands` to return 20 rows and support `--host` `--type` filters to improve response time.
+- Updated the instructions for manual MDM enrollment on the "My device" page to be clearer and align with Apple updates.
+- Updated UI to allow device users to reinstall self-service software.
+- Updated API to not return a 500 status code if a host sends a command response with an invalid command uuid.
+- Increased the timeout of the upload software installer endpoint to 4 minutes.
+- Disabled credential caching and reboot on Windows lock.
+
+### Vulnerability Management
+
+- Added "Vulnerable" filter to the host details software table.
+- Fixed Microsoft Office June 2024 false negative vulnerabilities and added custom vulnerability matching.
+- Fixed issue where some Windows applications were getting matched against Windows OS vulnerabilities.
+
+### Bug fixes and improvements
+
+- Updated Go version to go1.22.4.
+- Updated to render only one banner on the my device page based on priority order.
+- Updated software updated timestamp tooltip.
+- Removed DB error message from the UI when showing a error response.
+- Updated fleetctl get queries/labels/hosts descriptions.
+- Reinstated ability to sort policies by passing count.
+- Improved the accuracy of the heuristic used to deterimine if a host is connected to Fleet via MDM by using osquery data for hosts that didn't send a Checkout message.
+- Improved the matching of `pkg` installer files to existing software.
+- Improved extraction of application name from `pkg` installers.
+- Clarified various help and error texts around host identifiers.
+- Hid CTA on inherited queries/policies from team level users.
+- Hid query delete checkboxes from team observers.
+- Hid "Self-service" in Fleet Desktop and My device page if there is no self-service software available.
+- Hid the host detail page's "Run script" action from Global and Team Observer/+s.
+- Aligned the "View all hosts" links in the Software titles and versions tables.
+- Fixed counts for hosts with with low disk space in summary page.
+- Fixed allowing Observer and Observer+ roles to download software installers.
+- Fixed crash in `fleetd` installer on Windows if there are registry keys with special characters on the system.
+- Fixed `fleetctl debug connection` to support server TLS certificates with intermediates.
+- Fixed macOS declarations being stuck in "to be removed" state indefinitely.
+- Fixed link to `fleetd` uninstall instructions in "Delete device" modal.
+- Fixed exporting CSVs with fields that contain commas to render properly.
+- Fixed issue where the Fleet UI could not be used to renew the ABM token after the ABM user who created the token was deleted.
+- Fixed styling issues with the target inputs loading spinner on the run live query/policy page.
+- Fixed an issue where special characters in HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall breaks the "installer_utils.ps1 -uninstallOrbit" step in the Windows MSI installer.
+- Fixed a bug causing "No Team" OS versions to display the wrong number.
+- Fixed various UI capitalizations.
+- Fixed UI issue where "Script is already running" tooltip incorrectly displayed when the script is not running.
+- Fixed the script details modal's error message on script timeout to reflect the newly dynamic script timeout limit, if hit.
+- Fixed a discrepancy in the spacing between DataSet labels and values on Firefox relative to other browsers.
+- Fixed bug that set `Added to Fleet` to `Never` after macOS hosts re-enrolled to Fleet via MDM.
+
+## Fleet 4.53.1 (Jul 01, 2024)
+
+### Bug fixes
+
+* Updated fleetctl get queries/labels/hosts descriptions.
+* Fixed exporting CSVs with fields that contain commas to render properly.
+* Fixed link to fleetd uninstall instructions in "Delete device" modal.
+* Rendered only one banner on the my device page based on priority order.
+* Hidden query delete checkboxes from team observers.
+* Fixed issue where the Fleet UI could not be used to renew the ABM token after the ABM user who created the token was deleted.
+* Fixed an issue where special characters in HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall broke the "installer_utils.ps1 -uninstallOrbit" step in the Windows MSI installer.
+* Fixed counts for hosts with low disk space in summary page.
+* Fleet UI fixes: Hide CTA on inherited queries/policies from team level users.
+* Updated software updated timestamp tooltip.
+* Fixed issue where some Windows applications were getting matched against Windows OS vulnerabilities.
+* Fixed crash in `fleetd` installer on Windows if there are registry keys with special characters on the system.
+* Fixed UI capitalizations.
+
+## 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.54.0.
+
+
+
+
+
+
+
diff --git a/articles/fleet-4.55.0.md b/articles/fleet-4.55.0.md
new file mode 100644
index 000000000000..24e4a416a535
--- /dev/null
+++ b/articles/fleet-4.55.0.md
@@ -0,0 +1,132 @@
+# 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.
+
+## Highlights
+
+* MySQL 8 support, MySQL 5.7 sunsets
+* FileVault key rotation with Escrow Buddy
+* FileVault enforcement at enrollment
+* Arm64 support
+* VPP app support for macOS
+* "No team" software support
+
+### MySQL 8 support, MySQL 5.7 sunsets
+
+Fleet has updated its database compatibility by adding support for MySQL 8, while simultaneously dropping support for MySQL 5.7. This change aligns Fleet with the latest advancements in database technology, offering enhanced performance, security, and features available in MySQL 8. Organizations using Fleet are encouraged to upgrade their database systems to MySQL 8 to take full advantage of these improvements. By focusing on the latest supported versions, Fleet ensures that its platform remains robust, secure, and well-equipped to handle the demands of modern IT environments while phasing out older versions that may not provide the same level of performance or security.
+
+### FileVault key rotation with Escrow Buddy
+
+Fleet now includes support for FileVault key rotation using [Escrow Buddy](https://github.com/macadmins/escrow-buddy), a tool developed by the Netflix Client Systems Engineering team for the MacAdmins community to securely manage and rotate FileVault recovery keys on macOS devices. This feature allows IT administrators to automate the process of rotating FileVault keys, ensuring that encrypted macOS hosts remain secure while maintaining access control. By integrating with Escrow Buddy, Fleet enables seamless key management, reducing the administrative burden of manually rotating keys and enhancing the overall security posture of macOS environments. This update reflects Fleet's commitment to providing robust security tools that integrate with trusted community resources, ensuring organizations can efficiently manage device encryption and recovery processes.
+
+### FileVault enforcement at enrollment
+
+Fleet now supports enforcing FileVault encryption during the enrollment process for macOS devices, ensuring that all newly enrolled Macs are automatically encrypted. This feature enhances security by mandating that FileVault is enabled as part of the initial device setup, reducing the risk of unencrypted data on managed endpoints. By integrating FileVault enforcement into the enrollment workflow, Fleet helps organizations maintain a consistent security posture across their macOS fleet, ensuring compliance with internal policies and regulatory requirements. This update underscores Fleet's commitment to providing comprehensive security management tools that protect sensitive data and simplify the administration of macOS devices.
+
+### Arm64 support
+
+Fleet now includes support for Linux hosts running on the arm64 architecture. This update enables organizations to integrate a broader range of devices into their Fleet management system, ensuring comprehensive oversight and control across diverse hardware environments. By supporting arm64 Linux hosts, Fleet caters to the growing use of ARM-based systems in various sectors, allowing IT administrators to manage these devices with the same level of detail and efficiency as traditional x86-based hosts. This aligns with Fleet's commitment to providing versatile and inclusive device management solutions, empowering users to maintain a unified and efficient IT infrastructure.
+
+### VPP app support for macOS
+
+Fleet now supports installing Volume Purchase Program (VPP) apps from the Apple App Store on macOS devices. This feature enables IT administrators to deploy and manage apps purchased through Apple's VPP directly to macOS hosts, streamlining the process of distributing essential software across the organization. By integrating VPP app installations into Fleet, organizations can ensure that licensed applications are efficiently deployed to the appropriate devices, improving software management and compliance. This update enhances Fleet's capabilities in managing macOS environments, offering a more seamless and centralized approach to app distribution for enterprise and educational settings.
+
+### "No team" software support
+
+Fleet now supports adding software to the "No team" team, providing greater flexibility in managing software across an organization's devices. This feature allows administrators to deploy and manage software that applies universally without being restricted to specific teams. By adding software to the "No team" team, IT teams can ensure that essential tools and applications are available across all devices, regardless of their team assignment. This update simplifies the management of widely used software and enhances the ability to maintain consistency and compliance across the entire fleet. It reflects Fleet's commitment to offering versatile solutions that cater to diverse organizational needs and streamline device management processes.
+
+## Changes
+
+**NOTE:** Beginning with v4.55.0, Fleet no longer supports MySQL 5.7 because it has reached [end of life](https://mattermost.com/blog/mysql-5-7-reached-eol-upgrade-to-mysql-8-x-today/#:~:text=In%20October%202023%2C%20MySQL%205.7,to%20upgrade%20to%20MySQL%208.). The minimum version supported is MySQL 8.0.
+
+### Endpoint Operations
+
+- Added support for generating `fleetd` packages for Linux ARM64.
+- Added new `fleetctl package` --arch flag.
+- Updated `fleetctl package` command to remove the `--version` flag. The version of the package can be controlled by `--orbit-channel` flag.
+- Updated maintenance window descriptions to update regularly to match the failing policy description/resolution.
+- Updated maintenance windows using Google Calendar so that calendar events are now recreated within 30 seconds if deleted or moved to the past.
+ - Fleet server watches for potential changes for up to 1 week after original event time. If event is moved forward more than 1 week, then after 1 week Fleet server will check for event changes once every 30 minutes.
+ - **NOTE:** These near real-time updates may add additional load to the Google Calendar API, so it is recommended to use API usage alerts or other monitoring methods.
+
+### Device Management
+
+- Integrated [Escrow Buddy](https://github.com/macadmins/escrow-buddy) to add enforcement of FileVault during the MacOS Setup Assistant process for hosts that are
+enrolled into teams (or no team) with disk encryption turned on. Thank you homebysix and team!
+- Added OS updates support to iOS/iPadOS devices.
+- Added iOS and iPadOS device details refetch triggered with the existing `POST /api/latest/fleet/hosts/:id/refetch` endpoint.
+- Added iOS and iPadOS user-installed apps to Fleet.
+- Added iOS and iPadOS apps to be installed using Apple's VPP (Volume Purchase Program) to Fleet.
+- Added support for VPP to GitOps.
+- Added the `POST /mdm/apple/vpp_token`, `DELETE /mdm/apple/vpp_token` and `GET /vpp` endpoints and related functionality.
+- Added new `GET /software/app_store_apps` and `POST /software/app_store_apps` endpoints and associated functionality.
+- Added the associated VPP apps to the `GET /software/titles` and `GET /software/titles/:id` endpoints.
+- Added the associated VPP apps to the `GET /hosts/:id/software` and `GET /device/:token/software` endpoints.
+- Added support to delete a VPP app from a team in `DELETE /software/titles/:software_title_id/available_for_install`.
+- Added `exclude_software` query parameter to "Get host by identifier" API.
+- Added ability to add/remove/disable apps with VPP in the Fleet UI.
+- Added a warning banner to the UI if the uploaded VPP token is about to expire/has expired.
+- Added UI updates for VPP feature on host software and my device pages.
+- Added global activity support for VPP-related activities.
+- Added UI features for managing VPP apps for iPadOS and iOS hosts.
+- Updated profile activities to include iOS and iPadOS.
+- Updated Fleet UI to show OS version compliance on host details page.
+- Added support for "No teams" on all software pages including adding software installers.
+- Added DB migration to support VPP software features.
+- Added DB migration to migrate older team configurations to the new version that includes both installers and App Store apps.
+- Linux lock/unlock scripts now make use of pam_nologin to keep AD users locked out.
+- Installed software list now includes Linux .deb packages that are 'on hold'.
+- Added a special-case to properly name the Notion .exe Windows installer the same as how it will be reported by osquery post-install.
+- Increased threshold to renew Apple SCEP certificates for MDM enrollments to 180 days.
+
+### Vulnerability Management
+
+- Fixed CVEs identified as 'Rejected' in NVD not matching against software.
+- Fixed false negative vulnerabilities with IntelliJ IDEA CE and PyCharm CE installed via Homebrew.
+
+### Bug fixes and improvements
+
+- Dropped support for MySQL 5.7 and raised minimum required to MySQL 8.0.36.
+- Updated software pre-install to use new GitOps format for query.
+- Updated UI tooltips for pending OS settings.
+- Added a migration to migrate older team configurations to the new version that includes both installers and App Store apps.
+- Fixed a styling issue in the controls > OS settings > disk encryption table.
+- Fixed a bug in `fleetctl preview` that was causing it to fail if Docker was installed without support for the deprecated `docker-compose` CLI.
+- Fixed an issue where the app-wide warning banners were not showing on the initial page load.
+- Fixed a bug where the hosts page would sometimes allow excess pagination.
+- Fixed a bug where software install results could not be retrieved for deleted hosts in the activity feed.
+- Fixed path that was incorrect for the download software installer package endpoint `GET /software/titles/:software_title_id/package`.
+- Fixed a bug that set `last_enrolled_at` during orbit re-enrollment, which caused osquery enroll failures when `FLEET_OSQUERY_ENROLL_COOLDOWN` is set.
+- Fixed the "Available for install" filter in the host's software page so that installers that were requested to be installed on the host (regardless of installation status) also show up in the list.
+- Fixed a bug where Fleet google calendar events generated by Fleet <= 4.53.0 were not correctly processed by 4.54.0.
+- Fixed a bug in `fleetctl preview` that was causing it to fail if Docker was installed without support for the deprecated `docker-compose` CLI.
+- Fixed a bug where software install results could not be retrieved for deleted hosts in the activity feed.
+- Fixed a bug where a software installer (a package or a VPP app) that has been installed on a host still shows up as "Available for install" and can still be requested to be installed after the host is transferred to a different team without that installer (or after the installer is deleted).
+- Fixed the "Available for install" filter in the host's software page so that installers that were requested to be installed on the host (regardless of installation status) also show up in the list.
+
+## Fleet 4.54.1 (Jul 24, 2024)
+
+### Bug fixes
+- Fixed a startup bug by performing an early restart of orbit if an agent options setting has changed.
+- Implemented a small refactor of orbit subsystems.
+- Removed the `--version` flag from the `fleetctl package` command. The version of the package can now be controlled by the `--orbit-channel` flag.
+- Fixed a bug that set `last_enrolled_at` during orbit re-enrollment, which caused osquery enroll failures when `FLEET_OSQUERY_ENROLL_COOLDOWN` is set .
+- In `fleetctl package` command, removed the `--version` flag. The version of the package can be controlled by `--orbit-channel` flag.
+- Fixed a bug where Fleet google calendar events generated by Fleet <= 4.53.0 were not correctly processed by 4.54.0.
+- Re-enabled cached logins after windows Unlock.
+
+
+
+## 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.55.0.
+
+
+
+
+
+
+
diff --git a/articles/fleet-4.56.0.md b/articles/fleet-4.56.0.md
new file mode 100644
index 000000000000..158d78746851
--- /dev/null
+++ b/articles/fleet-4.56.0.md
@@ -0,0 +1,153 @@
+# 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.
+
+## Highlights
+* Improved end-user MDM migration
+* Enforce minimum OS version for MDM enrollment
+* Exact match CVE search
+* Software vulnerabilities severity filter
+* Self-service VPP apps
+* Multiple ABM and VPP support
+
+
+### Improved end-user MDM migration
+
+Fleet has improved the end-user MDM migration workflow on macOS by enabling the migration of hosts manually enrolled in a third-party MDM over to Fleet MDM using the Fleet Desktop application. Previously, this capability was limited to hosts enrolled through Apple's Automated Device Enrollment (ADE), but with this update, manually enrolled hosts can now be seamlessly migrated to Fleet MDM. This feature is specifically available for macOS Sonoma devices (macOS 14 or greater). It makes the migration process more flexible and accessible for organizations looking to centralize their MDM management under Fleet. This enhancement simplifies the transition to Fleet MDM for a broader range of macOS devices, ensuring that all hosts can be managed consistently and securely.
+
+
+### Enforce minimum OS version for MDM enrollment
+
+Fleet now enforces a minimum operating system (OS) requirement for macOS devices before they can be enrolled into Fleet's MDM. This feature ensures that only devices running a specified minimum macOS version can be enrolled, helping organizations maintain a consistent security and compliance baseline across their fleet. By setting a minimum OS requirement, Fleet prevents older, potentially less secure macOS versions from being managed under its MDM, thereby reducing vulnerabilities and ensuring all enrolled devices meet the organization's standards. This update enhances Fleet's ability to enforce security policies from the outset, ensuring that all devices in the fleet are up-to-date and capable of supporting the latest security and management features.
+
+
+### Exact match CVE search
+
+Fleet has enhanced its CVE (Common Vulnerabilities and Exposures) search functionality by introducing exact match searching, allowing users to quickly and accurately find specific vulnerabilities across their fleet. This improvement ensures that security teams can pinpoint the exact CVE they are investigating without sifting through irrelevant results, streamlining the vulnerability management process. Additionally, Fleet provides better context in cases where no results are found, helping users understand why a particular CVE might not be present in their environment. This update improves the overall user experience in vulnerability management, making it easier to maintain security and compliance across all managed devices.
+
+
+### Software vulnerabilities severity filter
+
+Fleet has introduced improved filtering capabilities for vulnerable software, allowing users to filter vulnerabilities by severity level. This enhancement enables security teams to prioritize their response efforts by focusing on the most critical vulnerabilities, ensuring that the highest-risk issues are promptly addressed. By providing a straightforward and efficient way to filter vulnerable software based on severity, Fleet helps organizations streamline their vulnerability management processes, reducing the risk of security incidents. This update aligns with Fleet's commitment to providing powerful tools that enhance the efficiency and effectiveness of security operations across all managed devices.
+
+
+### Self-Service Apple App Store apps
+
+Fleet enables organizations to assign and install Apple App Store apps purchased through the Volume Purchase Program (VPP) directly via Self-Service using Fleet Desktop. This new feature allows IT administrators to make VPP-purchased apps available to end users seamlessly and flexibly. By integrating VPP app distribution into the Fleet Desktop Self-Service portal, organizations can streamline the deployment of essential software across their macOS devices, ensuring that users have easy access to the tools they need while maintaining control over software distribution. This update enhances the overall user experience and operational efficiency, empowering end users to install approved applications with minimal IT intervention.
+
+
+### Multiple Apple Business Manager and VPP support
+
+Fleet now enables administrators to add and manage multiple Apple Business Manager (ABM) and Volume Purchase Program (VPP) tokens within a single Fleet instance. This feature is designed for both Managed Service Providers (MSPs) and large enterprises, allowing them to create separate automatic enrollment and App Store app workflows for different clients or divisions, each with their own ABM and VPP tokens. Whether you’re managing devices for multiple customers or supporting large organizations with distinct divisions, this update simplifies the process of handling macOS, iOS, and iPadOS devices. With support for multiple ABM and VPP connections, Fleet streamlines software and device management across varied environments, providing a scalable solution for both MSPs and enterprises looking to centralize control while maintaining flexibility for different user groups.
+
+
+## Changes
+
+**NOTE:** Beginning with Fleet v4.55.0, Fleet no longer supports MySQL 5.7 because it has reached [end of life](https://mattermost.com/blog/mysql-5-7-reached-eol-upgrade-to-mysql-8-x-today/#:~:text=In%20October%202023%2C%20MySQL%205.7,to%20upgrade%20to%20MySQL%208.). The minimum version supported is MySQL 8.0.
+
+## Fleet 4.56.0 (Sep 7, 2024)
+
+### Endpoint operations
+
+- Added index to `query_results` DB table to speed up finding last query timestamp for a given query and host.
+- Added a link in the UI to the error message when a CSR can't be downloaded due to missing private key.
+- Added a disabled overlay to the Other Workflows modal on the policy page.
+- Improved performance of live queries to accommodate for higher volumes when utilizing zero-trust workflows.
+- Improved `fleetctl` gitops error message when trying to change team name to a team that already exists.
+
+### Device management
+
+- Added server support for multiple VPP tokens.
+- Added new endpoints and updated existing endpoints for managing multiple Apple Business Manager tokens.
+- Added support for S3 to store MDM bootstrap packages (uses the same bucket configuration as for software installers).
+- Added support to UI for self service VPP software.
+- Added backend and gitops support for self service VPP.
+- Added ability for MDM migrations if the host is manually enrolled to a 3rd party MDM.
+- Added an offline screen to the macOS MDM migration flow.
+- Added new ABM page to Fleet UI.
+- Added new VPP page to the fleet UI
+- Added support to track the Apple Business Manager "terms expired" API error per token, as well as a global flag that gets set as soon as one token has its terms expired.
+- Updated the instructions on "My device" for MDM migrations on pre-Sonoma macOS hosts.
+- Updated to allow multiple teams to be assigned to the same VPP Token.
+- Updated process so that deleting installed software or VPP app now makes it available for re-installation.
+- Updated to enforce minimum OS version settings during Apple Automated Device Enrollment (ADE).
+- Updated ABM ingestion so that deleted iOS/iPadOS host will continue to report to Fleet as long as host is in Apple Business Manager (ABM).
+- Updated so that refetching an offline iOS/iPadOS host will not add new MDM commands to the queue if previous refetch has not completed yet.
+- Updated UI so that downloading a software installer package now shows the browser's built-in progress bar.
+- Updated relevant documentation to include references to multiple ABM and VPP tokens.
+- Consolidated Automatic Enrollment and VPP settings under the MDM settings integration page.
+- Cleared apps associated with a VPP token if it's moved off of a team.
+
+### Vulnerability management
+
+- Added ALAS bulletins as vulnerability source for Amazon Linux (instead of OVAL for Amazon Linux 2, and adds support for Amazon Linux 1, 2022, and 2023).
+- Added matching rules for July and August Microsoft 365 security updates (https://learn.microsoft.com/en-us/officeupdates/microsoft365-apps-security-updates).
+- Added the following filters to `/software/titles` and `/software/versions` API endpoints: `exploit: bool`, `min_cvss_score: float`, `max_cvss_score: float`.
+- Updated software titles/versions tables to allow for filtering by vulnerabilities including severity and known exploit.
+- Updated to use empty CVE description when the NVD CVE feed doesn't include description entries (instead of panicking).
+- Updated matching software that is not installed by Fleet so that it shows up as 'Available for install' on host details page.
+- Updated base images of `fleetdm/fleetctl`, `fleetdm/bomutils` and `fleetdm/wix` to fix critical vulnerabilities found by Trivy.
+- Updated vulnerability scanning to use `macos` SW target for CPEs of homebrew packages.
+- Updated vulnerability scanning to not ignore software with non-ASCII en dash and em dash characters.
+- Updated `GET /api/v1/fleet/vulnerabilities/{cve}` endpoint to add validation of CVE format, and a 204 response. The 204 response indicates that the vulnerability is known to Fleet but not present on any hosts.
+- Updated the UI to add new empty states for searching vulnerabilities: invalid CVE format searched, a known CVE serached but not present on hosts, not a known CVE searched, exploited vulnerability empty state, operating systems empty state, new icons.
+
+### Bug fixes and improvements
+
+- Added support for MySQL 8.4.2 LTS.
+- Updated Go to go1.22.6.
+- Updated Fleet server to now accept arguments via stdin. This is useful for passing secrets that you don't want to expose as env vars, in the command line, or in the config file.
+- Updated text for "Turn on MDM" banners in UI.
+- Updated ABM host tooltip copy on the manage host page to clarify when host vitals will be available to view.
+- Updated copy on auotmatic enrollment modal on my device page.
+- Updated host details activities tooltip and empty state copy to reflect recently added capabilities.
+- Updated Fleet Free so users see a Premium feature message when clicking to add software.
+- Updated usage reporting to report statistics on new AI features, maintenance window, and `fleetd`.
+- Fixed bug where configuration profile was still showing the old label name after the name was updated.
+- Fixed a bug when a cached prepared statement gets deleted in the MySQL server itself without Fleet knowing.
+- Fixed a bug where the wrong API path was used to download a software installer.
+- Fixed the failing_host_count so it is never 0. This count is normally updated once an hour during cleanups_then_aggregation cron job.
+- Fixed CVE-2024-4030 in Vulncheck feed incorrectly targeting non-Windows hosts.
+- Fixed a bug where the "Self-service" filter for the list of software and the list of host's software did not take App Store apps into account.
+- Fixed a bug where the "My device" page in Fleet Desktop did not show the self-service software tab when App Store apps were available as self-install.
+- Fixed a bug where a software installer (a package or a VPP app) that has been installed on a host still shows up as "Available for install" and can still be requested to be installed after the host is transferred to a different team without that installer (or after the installer is deleted).
+- Fixed the "Available for install" filter in the host's software page so that installers that were requested to be installed on the host (regardless of installation status) also show up in the list.
+- Fixed UI popup messages bleeding off viewport in some cases.
+- Fixed an issue with the scheduling of cron jobs at startup if the job has never run, which caused it to be delayed.
+- Fixed UI to display the label names in case-insensitive alphabetical order.
+
+## Fleet 4.55.2 (Sep 05, 2024)
+
+### Bug fixes
+
+* Removed validation of APNS certificate from server startup. This was no longer necessary because we now allow for APNS certificates to be renewed in the UI.
+* Fixed logic to properly catch and log APNs errors.
+
+## Fleet 4.55.1 (Aug 14, 2024)
+
+### Bug fixes
+
+* Added a disabled overlay to the Other Workflows modal on the policy page.
+* Updated text for "Turn on MDM" banners in UI.
+* Fixed a bug when a cached prepared statement got deleted in the MySQL server itself without Fleet knowing.
+* Continued with an empty CVE description when the NVD CVE feed didn't include description entries (instead of panicking).
+* Scheduled maintenance events are now scheduled over calendar events marked "Free" (not busy) in Google Calendar.
+* Fixed a bug where the wrong API path was used to download a software installer.
+* Improved fleetctl gitops error message when trying to change team name to a team that already exists.
+* Updated ABM (Apple Business Manager) host tooltip copy on the manage host page to clarify when host vitals will be available to view.
+* Added index to query_results DB table to speed up finding the last query timestamp for a given query and host.
+* Displayed the label names in case-insensitive alphabetical order in the fleet UI.
+
+## 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.56.0.
+
+
+
+
+
+
+
diff --git a/docs/Using Fleet/Fleet-desktop.md b/articles/fleet-desktop.md
similarity index 84%
rename from docs/Using Fleet/Fleet-desktop.md
rename to articles/fleet-desktop.md
index b4696943db96..c0b1b0e5744b 100644
--- a/docs/Using Fleet/Fleet-desktop.md
+++ b/articles/fleet-desktop.md
@@ -1,12 +1,7 @@
# Fleet Desktop
-- [Installing Fleet Desktop](#installing-fleet-desktop)
-- [Upgrading Fleet Desktop](#upgrading-fleet-desktop)
-- [Custom Transparency Link](#custom-transparency-link)
-- [Securing Fleet Desktop](#securing-fleet-desktop)
-Fleet Desktop is a menu bar icon available on macOS, Windows, and Linux.
+Fleet Desktop is a menu bar icon available on macOS, Windows, and Linux that gives your end users visibility into the security posture of their machine. This unlocks two key benefits:
-At its core, Fleet Desktop gives your end users visibility into the security posture of their machine. This unlocks two key benefits:
* Self-remediation: end users can see which policies they are failing and resolution steps, reducing the need for IT and security teams to intervene
* Scope transparency: end users can see what the Fleet agent can do on their machines, eliminating ambiguity between end users and their IT and security teams
@@ -16,10 +11,10 @@ At its core, Fleet Desktop gives your end users visibility into the security pos
-## Installing Fleet Desktop
+## Install Fleet Desktop
For information on how to install Fleet Desktop, visit: [Adding Hosts](https://fleetdm.com/docs/using-fleet/adding-hosts#fleet-desktop).
-## Upgrading Fleet Desktop
+## Upgrade Fleet Desktop
Once installed, Fleet Desktop will be automatically updated via Fleetd. To learn more, visit: [Self-managed agent updates](https://fleetdm.com/docs/deploying/fleetctl-agent-updates#self-managed-agent-updates).
## Custom transparency link
@@ -32,7 +27,7 @@ On the settings page, go to "Organization Settings" and select "Fleet Desktop."
For information on how to set the custom transparency link via a YAML configuration file, see the [configuration files](https://fleetdm.com/docs/configuration/fleet-server-configuration#fleet-desktop-settings) documentation.
-## Securing Fleet Desktop
+## Secure Fleet Desktop
Requests sent by Fleet Desktop and the web page that opens when clicking on the "My Device" tray item use a [Random (Version 4) UUID](https://www.rfc-editor.org/rfc/rfc4122.html#section-4.4) token to uniquely identify each host.
@@ -57,7 +52,9 @@ As a consequence, Fleet Desktop will issue a new token if the current token is:
This change is imperceptible to users, as clicking on the "My device" tray item always uses a valid token. If a user visits an address with an expired token, they will get a message instructing them to click on the tray item again.
-
-
+
+
+
+
+
-
diff --git a/articles/fleet-in-your-calendar-introducing-maintenance-windows.md b/articles/fleet-in-your-calendar-introducing-maintenance-windows.md
index 5f23f9bbcec6..79ef934847e0 100644
--- a/articles/fleet-in-your-calendar-introducing-maintenance-windows.md
+++ b/articles/fleet-in-your-calendar-introducing-maintenance-windows.md
@@ -1,5 +1,7 @@
# Fleet in your calendar: introducing maintenance windows
+> Unlike other Fleet features which take advantage of declarative device management (DDM), the approach described in this article still uses traditional MDM commands. (More to come.)
+
![Fleet in your calendar: introducing maintenance windows](../website/assets/images/articles/fleet-in-your-calendar-introducing-maintenance-windows-cover-900x450@2x.png)
Fleet is excited to announce the release of "maintenance windows", a new feature in Fleet v4.48 that helps make sure OS updates occur during times that disrupt your users the least. Now, just like any good colleague, when Fleet needs some of your time, it puts it on your calendar. This approach avoids interrupting your key activities or important meetings, whether in the office, on the road, or working remotely.
@@ -19,7 +21,7 @@ Fleet provides AI-generated explanations directly in the calendar events, detail
## _Maintenance windows_ include:
* **Personalized scheduling:** Updates are timed based on individual calendar events, so interventions happen when they are least intrusive.
-* **Automatic rescheduling:** If a scheduled update becomes impractical—due to changes in your calendar, for example—Fleet automatically finds a new appropriate time.
+* **Rescheduling flexibility:** If a scheduled update becomes impractical for any reason, users have the option to manually move the maintenance window to a more suitable time. We suggest rescheduling within one week to ensure timely updates.
* **Enhanced compliance:** With auto-scheduled maintenance windows, compliance with security protocols is maintained effortlessly, ensuring all devices are up to date without manual intervention.
_Maintenance windows_ is a direct response to common challenges faced in workplace productivity, particularly unplanned disruptions from essential updates. Fleet aims to support smoother, more efficient work environments by incorporating user feedback and addressing these long-standing issues.
diff --git a/docs/Using Fleet/Usage-statistics.md b/articles/fleet-usage-statistics.md
similarity index 84%
rename from docs/Using Fleet/Usage-statistics.md
rename to articles/fleet-usage-statistics.md
index 16e5339e6dac..1db24222dee1 100644
--- a/docs/Using Fleet/Usage-statistics.md
+++ b/articles/fleet-usage-statistics.md
@@ -1,7 +1,9 @@
-# Usage statistics
+# Fleet usage statistics
Fleet Device Management Inc. periodically collects information about your instance.
+> To disable usage statistics, [see here](#disable-usage-statistics).
+
## What is included in usage statistics in Fleet?
Below is the JSON payload that is sent to Fleet Device Management Inc:
@@ -28,6 +30,17 @@ Below is the JSON payload that is sent to Fleet Device Management Inc:
"numWeeklyActiveUsers": 999,
"numWeeklyPolicyViolationDaysActual": 999,
"numWeeklyPolicyViolationDaysPossible": 999,
+ "numSoftwareVersions": 999,
+ "numHostSoftwares": 999,
+ "numSoftwareTitles": 999,
+ "numHostSoftwareInstalledPaths": 999,
+ "numSoftwareCPEs": 999,
+ "numSoftwareCVEs": 999,
+ "numHostsNotResponding": 9,
+ "aiFeaturesDisabled": true,
+ "maintenanceWindowsEnabled": true,
+ "maintenanceWindowsConfigured": true,
+ "numHostsFleetDesktopEnabled": 999,
"hostsEnrolledByOperatingSystem": {
"darwin": [
{
@@ -95,8 +108,7 @@ Below is the JSON payload that is sent to Fleet Device Management Inc:
]
},
...
- ],
- "numHostsNotResponding": 9
+ ]
}
```
@@ -128,6 +140,9 @@ To disable usage statistics:
3. Uncheck the "Enable usage statistics" checkbox and then select "Update settings."
-
+
+
+
+
+
-
diff --git a/docs/Using Fleet/fleetctl-CLI.md b/articles/fleetctl.md
similarity index 92%
rename from docs/Using Fleet/fleetctl-CLI.md
rename to articles/fleetctl.md
index 32b4c6724c63..caa234f845a8 100644
--- a/docs/Using Fleet/fleetctl-CLI.md
+++ b/articles/fleetctl.md
@@ -1,6 +1,6 @@
-# fleetctl CLI
+# fleetctl
-fleetctl (pronounced "Fleet control") is a CLI tool for managing Fleet from the command line. fleetctl enables a GitOps workflow with Fleet.
+fleetctl (pronounced "Fleet control") is a command line interface (CLI) tool for managing Fleet from the command line. fleetctl enables a GitOps workflow with Fleet.
fleetctl also provides a quick way to work with all the data exposed by Fleet without having to use the Fleet UI or work directly with the Fleet API.
@@ -32,6 +32,8 @@ npm install -g fleetctl@latest
Much of the functionality available in the Fleet UI is also available in `fleetctl`. You can run queries, add and remove users, generate Fleet's agent (fleetd) to add new hosts, get information about existing hosts, and more!
+> Note: Unless a logging infrastructure is configured on your Fleet server, osquery-related logs will be stored locally on each device. Read more [here](https://fleetdm.com/guides/log-destinations)
+
To see the available commands you can run:
```sh
@@ -197,6 +199,9 @@ This will generate a `tar.gz` file with:
- A file containing a set of all the errors that happened in the server during the interval of time defined by the [logging_error_retention_period](https://fleetdm.com/docs/deploying/configuration#logging-error-retention-period) configuration.
- Files containing database-specific information.
-
+
+
+
+
+
-
diff --git a/docs/Using Fleet/update-agents.md b/articles/fleetd-updates.md
similarity index 95%
rename from docs/Using Fleet/update-agents.md
rename to articles/fleetd-updates.md
index 93b61c0052c6..d5693eba93e1 100644
--- a/docs/Using Fleet/update-agents.md
+++ b/articles/fleetd-updates.md
@@ -1,7 +1,6 @@
-# Self-managed agent updates
+# Fleetd updates
-The fleetd agent will periodically check the public Fleet update repository and update Orbit, Fleet Desktop, and/or osquery
-if it detects a later version.
+The fleetd agent will periodically check the public Fleet update repository and update Orbit, Fleet Desktop, and/or osquery if it detects a later version.
To override this behavior, users can set a channel for each component or disable updates altogether. Visit [Adding Hosts](https://fleetdm.com/docs/using-fleet/adding-hosts#fleet-desktop) to learn more.
Alternatively, users with a Fleet Premium subscription can self-manage an update server.
@@ -160,6 +159,9 @@ fleetctl updates rotate targets
After the key(s) have been rotated, publish the repository in the same fashion as any other update.
-
+
+
+
+
+
-
diff --git a/articles/install-vpp-apps-on-macos-using-fleet.md b/articles/install-vpp-apps-on-macos-using-fleet.md
new file mode 100644
index 000000000000..387aa00348a5
--- /dev/null
+++ b/articles/install-vpp-apps-on-macos-using-fleet.md
@@ -0,0 +1,114 @@
+# Install App Store apps (VPP) on macOS, iOS, and iPadOS using Fleet
+
+![Install VPP apps on macOS using Fleet](../website/assets/images/articles/install-vpp-apps-on-macos-using-fleet-1600x900@2x.png)
+
+
+Fleet Premium supports the ability to add Apple App Store applications to your software library using the Volume Purchasing Program (VPP) and then install those apps on macOS, iOS, or iPadOS hosts. This guide will walk you through using this feature to add apps from your Apple Business Manager account to Fleet and install those apps on your hosts.
+
+The Volume Purchasing Program is an Apple initiative that allows organizations to purchase and distribute apps and books in bulk. This program is particularly beneficial for organizations that need to deploy multiple apps to many devices. Key benefits of VPP include:
+* **Bulk purchasing**: Purchase multiple licenses for an app in one transaction, often with volume discounts.
+* **Centralized management**: Manage and distribute purchased apps from a central location.
+* **Licensing flexibility**: Reassign app licenses as needed, ensuring efficient use of resources.
+* **Streamlined deployment**: Use Fleet to automate the installation and configuration of purchased apps on enrolled devices.
+* **Self-Service (macOS only)**: Allow users to assign licenses to their own devices as needed.
+
+By integrating VPP with Fleet, organizations can seamlessly add apps to their software library and deploy them across macOS, iOS, and iPadOS hosts, ensuring that all devices have the necessary applications installed efficiently and effectively.
+
+## Prerequisites
+* **MDM features**: to use the VPP integration, you must first enable MDM features in Fleet. See the [MDM setup guide](https://fleetdm.com/docs/using-fleet/mdm-setup) for instructions on enabling MDM features.
+* **Teams**: Apps can only be added to a specific Team. You can manage teams by selecting your avatar in the top navigation and then **Settings > Teams**. (Note: Apps can also be added to the 'No Team' team, which contains hosts not assigned to any other team.) You can control which team uses which VPP token by assigning teams to the VPP token. Each token may have multiple teams assigned to it, but each team may be assigned to only 1 token.
+
+> As of Fleet 4.55.0, there is a [known issue](https://github.com/fleetdm/fleet/issues/20686) that uninstalled or deleted VPP apps will continue to show a status of `installed`.
+
+## Accessing the VPP configuration
+
+1. **Navigate to the MDM integration settings page**: Click your avatar on the far right of the main navigation menu, and then **Settings > Integrations > "Mobile device management (MDM)"**
+
+2. **Add your VPP token**: Scroll to the "Volume Purchasing Program (VPP)" section. Click "Add VPP", and then click "Add VPP" again on the following page. Follow the directions on the modal to get your VPP token from Apple Business Manager, and then click the "Upload" button at the bottom to upload it to Fleet.
+
+3. **Edit the team assignment for the new token**: Find the token in the table of VPP tokens. Click the "Actions" dropdown, and then click "Edit teams". Use the picker to select which team(s) this VPP token should be assigned to.
+
+## Purchasing apps
+
+To add apps to Fleet, you must first purchase them through Apple Business Manager, even if they are free. This ensures that all apps are appropriately licensed and available for distribution via the Volume Purchasing Program (VPP). For detailed instructions on selecting and buying content, please refer to Apple’s documentation on [purchasing apps through Apple Business Manager](https://support.apple.com/guide/apple-business-manager/select-and-buy-content-axmc21817890/web).
+
+## Add an app to Fleet
+
+1. **Navigate to the Software page**: Click on the "Software" tab in the main navigation menu.
+
+2. **Select your team**: Click on the "All teams" dropdown in the top left of the page and select your desired team.
+
+3. **Open the "Add software" modal**: Click on the "Add software" button in the top right of the page.
+
+4. **View your available apps**: Click on the "App Store (VPP)" tab in the "Add software" modal. The modal will list the apps that you have purchased through VPP but still need to add to Fleet.
+
+5. **Add an app**: Select an app from the list. You may optionally check the "Self-Service" box at the bottom left of the modal if you wish for the software to be available for user-initiated installs. Finally, click the "Add software" button in the bottom right of the modal. The app should appear in the software list for the selected team.
+
+## Remove an app from Fleet
+
+1. **Navigate to the Software page**: Click "Software" in the main navigation menu.
+
+2. **Find the app you want to remove**: Search for the app using the search bar in the top right corner of the table.
+
+3. **Access the app's details page**: Click on the app's name in the table.
+
+4. **Remove the app**: Click on the "Actions" dropdown on the right side of the page. Click "Delete," then click "Delete" on the confirmation modal. Deleting an app will not uninstall the app from the hosts on which it was previously installed.
+
+## Installing apps on macOS, iOS, and iPadOS hosts
+
+1. **Add the host to the relevant team.**
+
+2. **Go to the host's detail page**: Click the "Hosts" tab in the main navigation menu. Filter the hosts by the team, and click the host's name to see its details page.
+
+3. **Find the app**: Click the "Software" tab on the host details page. Search for the software you added in the software table's search bar. Instead of searching, you can also filter software by clicking the **All software** dropdown and selecting **Available for install.**
+
+4. **Install the app**: Click the "Actions" dropdown on the far right of the app's entry in the
+ table. Click "Install" to trigger an install. This action will send an MDM command to the host
+ instructing it to install the app. If the host is offline, the upcoming install will show up in
+ the **Details** -> **Activity** -> **Upcoming** tab of this page. After the app is installed and
+ the host details are refetched, the app will show up as **Installed** in the **Software** tab.
+
+## Installing apps on macOS using self-service
+
+1. **Open Fleet from the host**: On the host that will be installing an application through self-service, click on the Fleet Desktop tray icon, then click **My Device**. This will open the browser to the device's page on Fleet.
+
+2. **Navigate to the self-service tab**: Click on the **Self-Service** tab under the device's details.
+
+3. **Locate the app and click install**: Scroll through the list of software to find the app you would like to install, then click the **Install** button underneath it.
+
+## Renewing an expired or expiring VPP token
+
+When one of your uploaded VPP tokens has expired or is within 30 days of expiring, you will see a warning
+banner at the top of page reminding you to renew your token. You can do this with the following steps:
+
+1. **Navigate to the MDM integration settings page**: Click your avatar on the far right of the main navigation menu, and then **Settings > Integrations > "Mobile device management (MDM)"** Scroll to the "Volume Purchasing Program (VPP)" section, and click "Edit".
+
+2. **Renew the token**: Find the VPP token that you want to renew in the table. Token status is indicated in the "Renew date" column: tokens less than 30 days from expiring will have a yellow indicator, and expired tokens will have a red indicator. Click the "Actions" dropdown for the token and then click "Renew". Follow the instructions in the modal to download a new token from Apple Business Manager and then upload the new token to Fleet.
+
+## Deleting a VPP token
+
+To remove VPP tokens from Fleet:
+
+1. **Navigate to the MDM integration settings page**: Click your avatar on the far right of the main navigation menu, and then **Settings > Integrations > "Mobile device management (MDM)"** Scroll to the "Volume Purchasing Program (VPP)" section, and click "Edit".
+
+2. **Delete the token**: Find the VPP token that you want to delete in the table. Click the "Actions" dropdown for that token, and then click "Delete". Click "Delete" in the confirmation modal to finish deleting the token.
+
+## Managing apps with GitOps
+
+To manage App Store apps using Fleet's best practice GitOps, check out the `software` key in the GitOps reference documentation [here](https://fleetdm.com/docs/using-fleet/gitops#software).
+
+## REST API
+
+Fleet also provides a REST API for managing apps programmatically. You can add, install, and delete apps via this API and manage your organization’s VPP tokens. Learn more about Fleet's [REST API](https://fleetdm.com/docs/rest-api/rest-api).
+
+## Conclusion
+
+This feature extends Fleet's capabilities for managing macOS, iOS, and iPadOS hosts. Whether you manage your hosts' software via uploaded installers or via the App Store VPP integration, Fleet provides you with the tools you need to manage your hosts effectively.
+
+
+
+
+
+
+
+
diff --git a/docs/Using Fleet/Log-destinations.md b/articles/log-destinations.md
similarity index 92%
rename from docs/Using Fleet/Log-destinations.md
rename to articles/log-destinations.md
index fb153f258930..ddec0c9b0c61 100644
--- a/docs/Using Fleet/Log-destinations.md
+++ b/articles/log-destinations.md
@@ -1,19 +1,5 @@
# Log destinations
-- [Log destinations](#log-destinations)
- - [Amazon Kinesis Data Firehose](#amazon-kinesis-data-firehose)
- - [Snowflake](#snowflake)
- - [Splunk](#splunk)
- - [Amazon Kinesis Data Streams](#amazon-kinesis-data-streams)
- - [AWS Lambda](#aws-lambda)
- - [Google Cloud Pub/Sub](#google-cloud-pubsub)
- - [Apache Kafka](#apache-kafka)
- - [Stdout](#stdout)
- - [Filesystem](#filesystem)
- - [Sending logs outside of Fleet](#sending-logs-outside-of-fleet)
-
-This document provides a list of the supported log destinations in Fleet.
-
Log destinations can be used in Fleet to log:
- Osquery [status logs](https://osquery.readthedocs.io/en/stable/deployment/logging/#status-logs).
@@ -23,11 +9,27 @@ Log destinations can be used in Fleet to log:
To configure each log destination, you must set the correct logging configuration options in Fleet.
+
Check out the reference documentation for:
- [Osquery status logging configuration options](https://fleetdm.com/docs/deploying/configuration#osquery-status-log-plugin).
- [Osquery result logging configuration options](https://fleetdm.com/docs/deploying/configuration#osquery-result-log-plugin).
- [Activity audit logging configuration options](https://fleetdm.com/docs/deploying/configuration#activity_audit_log_plugin).
+This guide provides a list of the supported log destinations in Fleet.
+
+### In this guide:
+
+- [Amazon Kinesis Data Firehose](#amazon-kinesis-data-firehose)
+- [Snowflake](#snowflake)
+- [Splunk](#splunk)
+- [Amazon Kinesis Data Streams](#amazon-kinesis-data-streams)
+- [AWS Lambda](#aws-lambda)
+- [Google Cloud Pub/Sub](#google-cloud-pubsub)
+- [Apache Kafka](#apache-kafka)
+- [Stdout](#stdout)
+- [Filesystem](#filesystem)
+- [Sending logs outside of Fleet](#sending-logs-outside-of-fleet)
+
## Amazon Kinesis Data Firehose
Logs are written to [Amazon Kinesis Data Firehose (Firehose)](https://aws.amazon.com/kinesis/data-firehose/).
@@ -145,6 +147,9 @@ See the [osquery logging documentation](https://osquery.readthedocs.io/en/stable
If `--logger_plugin=tls` is used with osquery clients, the following configuration can be applied on the Fleet server for handling the incoming logs.
-
+
+
+
+
+
-
diff --git a/articles/macos-mdm-setup.md b/articles/macos-mdm-setup.md
new file mode 100644
index 000000000000..bc91ee6a7206
--- /dev/null
+++ b/articles/macos-mdm-setup.md
@@ -0,0 +1,65 @@
+# macOS MDM setup
+
+To turn on macOS, iOS, and iPadOS MDM features, follow the instructions on this page to connect Fleet to Apple Push Notification service (APNs).
+
+To use automatic enrollment (aka zero-touch) features on macOS, iOS, and iPadOS, follow instructions to connect Fleet with Apple Business Manager (ABM).
+
+To turn on Windows MDM features, head to this [Windows MDM setup article](https://fleetdm.com/guides/windows-mdm-setup).
+
+## Apple Push Notification service (APNs)
+
+Apple uses APNs to authenticate and manage interactions between Fleet and hosts.
+
+To connect Fleet to APNs or renew APNs, head to the **Settings > Integrations > Mobile device management (MDM)** page.
+
+> Apple requires that APNs certificates are renewed annually.
+> - If your certificate expires, you will have to turn MDM off and back on for all macOS hosts.
+> - Be sure to use the same Apple ID from year-to-year. If you don't, you will have to turn MDM off and back on for all macOS hosts.
+
+## Apple Business Manager (ABM)
+
+> Available in Fleet Premium
+
+To connect Fleet to ABM, you have to add an ABM token to Fleet. To add an ABM token:
+
+1. Navigate to the **Settings > Integrations > Mobile device management (MDM)** page.
+2. Under "Automatic enrollment", click "Add ABM", and then click "Add ABM" again on the next page. Follow the instructions in the modal and upload an ABM token to Fleet.
+
+When one of your uploaded ABM tokens has expired or is within 30 days of expiring, you will see a warning
+banner at the top of page reminding you to renew your token.
+
+To renew an ABM token:
+
+1. Navigate to the **Settings > Integrations > Mobile device management (MDM)** page.
+2. Under "Automatic enrollment", click "Edit", and then find the token that you want to renew. Token status is indicated in the "Renew date" column: tokens less than 30 days from expiring will have a yellow indicator, and expired tokens will have a red indicator. Click the "Actions" dropdown for the token and then click "Renew". Follow the instructions in the modal to download a new token from Apple Business Manager and then upload the new token to Fleet.
+
+After connecting Fleet to ABM, set Fleet to be the MDM for all Macs:
+
+1. Log in to [Apple Business Manager](https://business.apple.com)
+2. Click your profile icon in the bottom left
+3. Click **Preferences**
+4. Click **MDM Server Assignment** and click **Edit** next to **Default Server Assignment**.
+5. Switch **Mac**, **iPhone**, and **iPad** to Fleet.
+
+macOS, iOS, and iPadOS hosts listed in ABM and associated to a Fleet instance with MDM enabled will sync to Fleet and appear in the Hosts view with the **MDM status** label set to "Pending".
+
+Hosts that automatically enroll will be assigned to a default team. You can configure the default team for macOS, iOS, and iPadOS hosts by:
+
+1. Navigating to the **Settings > Integrations > Mobile device management (MDM)** page and clicking "Edit" under "Automatic enrollment".
+2. Click on the "Actions" dropdown for the ABM token you want to update, and then click "Edit teams".
+3. Use the dropdowns in the modal to select the default team for each type of host, and click "Save" to save your selections.
+
+If no default team is set for a host platform (macOS, iOS, or iPadOS), then newly enrolled hosts of that platform will be placed in "No team".
+
+> A host can be transferred to a new (not default) team before it enrolls. In the Fleet UI, you can do this under **Settings** > **Teams**.
+
+### Simple Certificate Enrollment Protocol (SCEP)
+
+Fleet uses SCEP certificates (1 year expiry) to authenticate the requests hosts make to Fleet. Fleet renews each host's SCEP certificates automatically every 180 days.
+
+
+
+
+
+
+
diff --git a/docs/Using Fleet/MDM-macOS-setup-experience.md b/articles/macos-setup-experience.md
similarity index 91%
rename from docs/Using Fleet/MDM-macOS-setup-experience.md
rename to articles/macos-setup-experience.md
index 31dac0a131c6..6d7f9cc2ef9a 100644
--- a/docs/Using Fleet/MDM-macOS-setup-experience.md
+++ b/articles/macos-setup-experience.md
@@ -2,7 +2,7 @@
_Available in Fleet Premium_
-In Fleet, you can customize the out-of-the-box macOS setup experience for your end users:
+In Fleet, you can customize the out-of-the-box macOS Setup Assistant with Remote Management and Automated Device Enrollment (ADE) for end users:
* Require end users to authenticate with your identity provider (IdP) and agree to an end user license agreement (EULA) before they can use their new Mac.
@@ -12,7 +12,7 @@ In Fleet, you can customize the out-of-the-box macOS setup experience for your e
In addition to the customization above, Fleet automatically installs the fleetd agent during out-of-the-box macOS setup. This agent is responsible for reporting host vitals to Fleet and presenting Fleet Desktop to the end user.
-macOS setup features require connecting Fleet to Apple Business Manager (ABM). Learn how [here](./mdm-setup.md#apple-business-manager-abm).
+macOS setup features require connecting Fleet to Apple Business Manager (ABM). Learn how [here](https://fleetdm.com/guides/macos-mdm-setup#apple-business-manager-abm).
## End user authentication and EULA
@@ -20,7 +20,7 @@ Using Fleet, you can require end users to authenticate with your identity provid
### End user authentication
-To require end user authentication, first [configure single sign-on (SSO)](../Deploy/single-sign-on-sso.md). Next, enable end user authentication by heading to to **Controls > Setup experience End user authentication** or use [Fleet's GitOps workflow](https://github.com/fleetdm/fleet-gitops).
+To require end user authentication, first [configure single sign-on (SSO)](https://fleetdm.com/docs/deploy/single-sign-on-sso). Next, enable end user authentication by heading to to **Controls > Setup experience End user authentication** or use [Fleet's GitOps workflow](https://github.com/fleetdm/fleet-gitops).
If you've already configured SSO in Fleet, create a new SAML app in your IdP. In your new app, use `https:///api/v1/fleet/mdm/sso/callback` for the SSO URL.
@@ -155,13 +155,15 @@ Testing requires a test Mac that is present in your Apple Business Manager (ABM)
2. In Fleet, navigate to the Hosts page and find your Mac. Make sure that the host's **MDM status** is set to "Pending."
- > New Macs purchased through Apple Business Manager appear in Fleet with MDM status set to "Pending." Learn more about these hosts [here](./mdm-setup.md#pending-hosts).
+ > New Macs purchased through Apple Business Manager appear in Fleet with MDM status set to "Pending." Learn more about these hosts [here](https://fleetdm.com/guides/macos-mdm-setup#apple-business-manager-abm).
3. Transfer this host to the "Workstations (canary)" team by selecting the checkbox to the left of the host and selecting **Transfer** at the top of the table. In the modal, choose the Workstations (canary) team and select **Transfer**.
4. Boot up your test Mac and complete the custom out-of-the-box setup experience.
-
-
+
+
+
+
+
-
diff --git a/articles/managing-labels-in-fleet.md b/articles/managing-labels-in-fleet.md
new file mode 100644
index 000000000000..53e757e1e6df
--- /dev/null
+++ b/articles/managing-labels-in-fleet.md
@@ -0,0 +1,83 @@
+# Managing labels in Fleet
+
+![Managing labels in Fleet](../website/assets/images/articles/managing-labels-in-fleet-1600x900@2x.png)
+
+
+Labels in Fleet provide a powerful way to scope profiles to specific hosts. This guide will walk you through managing labels using the Fleet web UI. Labels can be created dynamically using queries or manually by selecting specific hosts. Dynamic labels are applied to hosts that match the query criteria, while manual labels are assigned to hosts you select.
+
+
+### Accessing labels
+
+To access and manage labels in Fleet:
+
+1. **Navigate to the Hosts page**: Click on the "Hosts" tab in the main navigation menu.
+
+2. **Filter by labels**: In the "Filter by…" drop-down menu, you will see options for "Platforms" (e.g., macOS, Windows, Linux) and "Labels."
+
+You can add a new label, filter existing labels by name, or select a label from the list. Selecting a label filters the view only to show hosts with that label.
+
+
+### Adding a label
+
+To add a new label:
+
+
+
+1. **Navigate to the Hosts page**: Click on the "Hosts" tab in the main navigation menu.
+2. **Access labels**: Click the "Filter by…" drop-down.
+3. **Select "Add Label +"**: This option allows you to create a new label.
+4. **Choose label Type**: You will be prompted to choose between a "Dynamic" or "Manual" label.
+ 1. **Dynamic**: Enter a name and description, then build your query and select the platforms to which this label applies.
+ 2. **Manual**: Enter a name and description, then select the hosts to which you want to apply this label.
+5. **Save the label**: Click the "Save" button to create your label.
+
+
+### Editing a label
+
+To edit an existing label:
+
+
+
+1. **Locate the label**: Find the label you want to edit in the list.
+2. **Click the pencil icon**: A pencil icon will appear next to the label. Clicking this icon allows you to edit the label.
+3. **Edit details**: For manually applied labels, you can change the name, description, and selected hosts. For dynamically applied labels, you can view the query.
+4. **Update restrictions**: To change the query or platforms a dynamic label targets, you must delete the existing label and create a new one. Once set, label queries and platforms are immutable.
+
+
+### Using the REST API for Labels
+
+Fleet also provides a REST API to manage labels programmatically. The API allows you to add, update, retrieve, list, and delete labels. Find detailed documentation on Fleet's [REST API here](https://fleetdm.com/docs/rest-api/rest-api#labels).
+
+
+### Managing labels with `fleetctl`
+
+Fleet's command line tool, `fleetctl` will also allow you to list and manage labels. While managing labels with `fleetctl` is beyond the scope of this guide, you can list all labels using the following command:
+
+```bash
+
+fleetctl get labels
+
+```
+
+> Learn more about [`fleetctl` CLI](https://fleetdm.com/docs/using-fleet/fleetctl-cli).
+
+
+#### Additional Information
+
+
+
+* **Targeting extensions with labels**: Labels can also target extensions to specific hosts. You can find more details on this functionality [here](https://fleetdm.com/docs/configuration/agent-configuration#targeting-extensions-with-labels).
+
+
+### Conclusion
+
+Using labels in Fleet enhances your ability to effectively manage and scope profiles to specific hosts. Whether you prefer to manage labels through the web UI or programmatically via the REST API, Fleet provides the flexibility and control you need. For more information on using Fleet, please refer to the [Fleet documentation](https://fleetdm.com/docs) and [guides](https://fleetdm.com/guides).
+
+
+
+
+
+
+
+
+
diff --git a/docs/Using Fleet/MDM-commands.md b/articles/mdm-commands.md
similarity index 70%
rename from docs/Using Fleet/MDM-commands.md
rename to articles/mdm-commands.md
index c541c7799dac..b1e5918c5eca 100644
--- a/docs/Using Fleet/MDM-commands.md
+++ b/articles/mdm-commands.md
@@ -1,4 +1,4 @@
-# Commands
+# MDM commands
In Fleet you can run MDM commands to take action on your macOS, iOS, iPadOS, and Windows hosts, like restarting the host, remotely.
@@ -83,19 +83,11 @@ You can view a list of the 1,000 latest commands:
1. Run `fleetctl get mdm-commands`
2. View the list of latest commands, most recent first, along with the timestamp, targeted hostname, command type, execution status and command ID.
-The command ID can be used to view command results as documented in [step 4 of the previous section](#step-4-view-the-commands-results).
+The command ID can be used to view command results as documented in [step 4 of the previous section](#step-4-view-the-commands-results).
-The possible statuses for macOS, iOS, and iPadOS hosts are the following:
-
-* Pending: the command has yet to run on the host. The host will run the command the next time it comes online.
-* NotNow: the host responded with "NotNow" status via the MDM protocol: the host received the command, but couldn’t execute it. The host will try to run the command the next time it comes online.
-* Acknowledged: the host responded with "Acknowledged" status via the MDM protocol: the host processed the command successfully.
-* Error: the host responded with "Error" status via the MDM protocol: an error occurred. Run the `fleetctl get mdm-command-results --id=
-
+
+
+
+
+
-
diff --git a/docs/Using Fleet/MDM-migration-guide.md b/articles/mdm-migration.md
similarity index 85%
rename from docs/Using Fleet/MDM-migration-guide.md
rename to articles/mdm-migration.md
index 68e55a7c8138..5eb9e8473d63 100644
--- a/docs/Using Fleet/MDM-migration-guide.md
+++ b/articles/mdm-migration.md
@@ -1,15 +1,19 @@
-# Migration guide
+# MDM migration
-This section provides instructions for migrating your hosts away from your old MDM solution to Fleet.
+This guide provides instructions for migrating devices from your current MDM solution to Fleet.
+
+> For seamless MDM migration, [view this guide](https://fleetdm.com/guides/seamless-mdm-migration).
## Requirements
-1. A [deployed Fleet instance](../Deploying/Introduction.md)
-2. [Fleet connected to Apple](./mdm-setup.md)
+
+- A [deployed Fleet instance](https://fleetdm.com/docs/deploy/deploy-fleet)
+- Fleet is connected to Apple Push Notification service (APNs) and Apple Business Manager (ABM). [See macOS MDM setup](https://fleetdm.com/guides/macos-mdm-setup)
+
## Migrate manually enrolled hosts
-1. [Enroll](./Adding-hosts.md) your hosts to Fleet with [Fleetd and Fleet Desktop](https://fleetdm.com/docs/using-fleet/adding-hosts#including-fleet-desktop)
+1. [Enroll](https://fleetdm.com/guides/enroll-hosts) your hosts to Fleet with [Fleetd and Fleet Desktop](https://fleetdm.com/guides/enroll-hosts#fleet-desktop)
2. Ensure your end users have access to an admin account on their Mac. End users won't be able to migrate on their own if they have a standard account.
3. In your old MDM solution, unenroll the hosts to be migrated. MacOS does not allow multiple MDMs to be installed at once.
4. Send [these guided instructions](#how-to-turn-on-mdm) to your end users to complete the final few steps via Fleet Desktop.
@@ -46,8 +50,8 @@ To migrate automatically enrolled hosts, we will do the following steps:
### Step 1: prepare to migrate hosts
-1. Connect Fleet to Apple Business Manager (ABM). Learn how [here](./mdm-setup.md#apple-business-manager-abm).
-2. [Enroll](./Adding-hosts.md) your hosts to Fleet with [Fleetd and Fleet Desktop](https://fleetdm.com/docs/using-fleet/adding-hosts#including-fleet-desktop)
+1. Connect Fleet to Apple Business Manager (ABM). Learn how [here](https://fleetdm.com/guides/macos-mdm-setup#apple-business-manager-abm).
+2. [Enroll](https://fleetdm.com/guides/enroll-hosts) your hosts to Fleet with [Fleetd and Fleet Desktop](https://fleetdm.com/guides/enroll-hosts#fleet-desktop)
3. Ensure your end users have access to an admin account on their Mac. End users won't be able to migrate on their own if they have a standard account.
4. Migrate your hosts to Fleet in ABM:
1. In ABM, unassign the existing hosts' MDM server from the old MDM solution: In ABM, select **Devices** and then select **All Devices**. Then, select **Edit** next to **Edit MDM Server**, select **Unassign from the current MDM**, and select **Continue**.
@@ -176,21 +180,19 @@ Then, scroll down to the **Mobile device management (MDM)** section.
_Available in Fleet Premium_
-When migrating from a previous MDM, end users need to take action to escrow FileVault keys to Fleet. The **My device** page in Fleet Desktop will present users with instructions to reset their key.
+When migrating from a previous MDM, end users need to restart or logout of their device to escrow FileVault keys to Fleet. The **My device** page in Fleet Desktop will present users with instructions to reset their key.
-To start, enforce FileVault (disk encryption) and escrow in Fleet. Learn how [here](./MDM-disk-encryption.md).
+To start, enforce FileVault (disk encryption) and escrow in Fleet. Learn how [here](https://fleetdm.com/guides/enforce-disk-encryption).
After turning on disk encryption in Fleet, share [these guided instructions](#how-to-turn-on-disk-encryption) with your end users.
-If your old MDM solution did not enforce disk encryption, the end user will need to restart or log out of the host.
-
-If your old MDM solution did enforce disk encryption, the end user will need to reset their disk encryption key by following the prompt on the My device page and inputting their password.
-
-## Activation Lock Bypass codes
+## Activation Lock
In Fleet, the [Activation Lock](https://support.apple.com/en-us/HT208987) feature is disabled by default for automatically enrolled (DEP) hosts.
-If a host under the old MDM solution has Activation Lock enabled, we recommend asking the end user to follow these instructions to disable Activation Lock before migrating this host to Fleet: https://support.apple.com/en-us/HT208987.
+In 2024, Apple added the ability to manage activation lock in Apple Business Manager (ABM). For devices that are owned by the business and available in ABM, you can [turn off activation lock remotely](https://support.apple.com/en-ca/guide/apple-business-manager/axm812df1dd8/web).
+
+If a device is not available in ABM and has Activation Lock enabled, we recommend asking the end user to follow these instructions to disable Activation Lock before migrating the device to Fleet: https://support.apple.com/en-us/HT208987.
This is because if the Activation Lock is enabled, you will need the Activation Lock bypass code to successfully wipe and reuse the Mac.
@@ -208,7 +210,9 @@ However, Activation Lock bypass codes can only be retrieved from the Mac up to 3
-
-
+
+
+
+
+
-
diff --git a/articles/osquery-evented-tables-overview.md b/articles/osquery-evented-tables-overview.md
index 883f0bc8ab0c..f1316a85f1a5 100644
--- a/articles/osquery-evented-tables-overview.md
+++ b/articles/osquery-evented-tables-overview.md
@@ -121,7 +121,7 @@ On macOS, there are two utilities that enable osquery process auditing: [OpenBSM
To use the `es_process_events` tables, use the flag `--disable_endpointsecurity=false`. See the [EndpointSecurity instructions](https://osquery.readthedocs.io/en/latest/deployment/process-auditing/#auditing-processes-with-endpointsecurity) for more information. To use `process_events` and `socket_events` with OpenBSM, see the [OpenBSM instructions](https://osquery.readthedocs.io/en/latest/deployment/process-auditing/#auditing-processes-with-openbsm).
#### Windows
-Currently, osquery does not support process auditing for Windows. To learn more about process auditing on Windows, visit [Microsoft's security auditing overview](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/security-auditing-overview). Fleet is tracking work to build process auditing for Windows in osquery. [Stay up to date on GitHub](https://github.com/fleetdm/fleet/issues/7732).
+Fleet supports auditing process events on Windows via the `process_etw_events` table. To learn more about process auditing on Windows, visit [Microsoft's security auditing overview](https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/security-auditing-overview). Fleet is tracking work to add file auditing for Windows in osquery. [Stay up to date on GitHub](https://github.com/fleetdm/fleet/issues/20946).
### YARA scanning
[YARA](https://virustotal.github.io/yara/) is a malware research and detection tool available on Linux and macOS that allows users to create descriptions of malware families based on patterns of text or binary code. Each potential piece of malware is matched against a YARA rule and triggers if the specified conditions are met.
diff --git a/docs/Using Fleet/Osquery-process.md b/articles/osquery-watchdog.md
similarity index 90%
rename from docs/Using Fleet/Osquery-process.md
rename to articles/osquery-watchdog.md
index 68efcd81da8f..5fb0bd980bfb 100644
--- a/docs/Using Fleet/Osquery-process.md
+++ b/articles/osquery-watchdog.md
@@ -1,4 +1,4 @@
-# Osquery children processes
+# Osquery watchdog
Osquery will run a watcher process to keep track of any child process and any managed extensions. What follows is a description of what happens during the watcher REPL and under what circumstances the child process and/or managed extensions are terminated.
@@ -25,6 +25,9 @@ If the managed extension is `Non-existent` (either because it was `Non-existent`
Lastly, we check the state of the watcher process itself. If it is deemed unhealthy because of resource contention, then the osquery process is shut down.
-
+
+
+
+
+
-
\ No newline at end of file
diff --git a/docs/Using Fleet/Puppet-module.md b/articles/puppet-module.md
similarity index 96%
rename from docs/Using Fleet/Puppet-module.md
rename to articles/puppet-module.md
index 30db545834e7..bf6a442bc14e 100644
--- a/docs/Using Fleet/Puppet-module.md
+++ b/articles/puppet-module.md
@@ -151,7 +151,9 @@ if $err != '' {
The above example includes the XML payload for the `EnableRemoteDesktop` MDM command. Learn more about creating the payload for other custom commands [here](./MDM-commands.md).
-
-
+
+
+
+
+
-
diff --git a/docs/Using Fleet/Fleet-UI.md b/articles/queries.md
similarity index 73%
rename from docs/Using Fleet/Fleet-UI.md
rename to articles/queries.md
index 3ccd10d5e42e..0379bca00434 100644
--- a/docs/Using Fleet/Fleet-UI.md
+++ b/articles/queries.md
@@ -1,18 +1,24 @@
-# Fleet UI
-- [Creating a query](#create-a-query)
-- [Running a query](#run-a-query)
-- [Scheduling a query](#schedule-a-query)
-- [Update agent options](#update-agent-options)
+# Queries
+
+Queries in Fleet allow you to ask questions to help you manage, monitor, and identify threats on your devices. This guide will walk you through how to create, schedule, and run a query.
+
+> Note: Unless a logging infrastructure is configured on your Fleet server, osquery-related logs will be stored locally on each device. Read more [here](https://fleetdm.com/guides/log-destinations)
+
+> New users may find it helpful to start with Fleet's policies. You can find policies and queries from the community in Fleet's [query library](https://fleetdm.com/queries). To learn more about policies, see [What are Fleet policies?](https://fleetdm.com/securing/what-are-fleet-policies) and [Understanding the intricacies of Fleet policies](https://fleetdm.com/guides/understanding-the-intricacies-of-fleet-policies).
+
+### In this guide:
+
+- [Create a query](#create-a-query)
+- [Run a query](#run-a-query)
+- [Schedule a query](#schedule-a-query)
-## Create a query
-Queries in Fleet allow you to ask a multitude of questions to help you manage, monitor, and identify threats on your devices.
-If you're unsure of what to ask, head to Fleet's [query library](https://fleetdm.com/queries). There you'll find common queries that have been tested by members of our community.
+## Create a query
How to create a query:
@@ -63,16 +69,10 @@ By default, queries that run on a schedule will only target platforms compatible
> Note: When viewing a specific [team](https://fleetdm.com/docs/using-fleet/segment-hosts) in Fleet Premium, only queries that belong to the selected team will be listed. When configuring query automations for all hosts, only global queries will be listed.
-## Update agent options
-
-
-
-
-> This content was relocated on 31st August 2023.
-
-See "[Agent configuration](https://fleetdm.com/docs/configuration/agent-configuration)" to learn how to simultaneously update agent options from the Fleet UI or fleetctl command line tool.
-
-
+
+
+
+
+
-
diff --git a/docs/Using Fleet/manage-access.md b/articles/role-based-access.md
similarity index 96%
rename from docs/Using Fleet/manage-access.md
rename to articles/role-based-access.md
index 5f9457988fde..95fc712c5252 100644
--- a/docs/Using Fleet/manage-access.md
+++ b/articles/role-based-access.md
@@ -1,4 +1,4 @@
-# Manage access
+# Role-based access
Users have different abilities depending on the access level they have.
@@ -83,7 +83,7 @@ GitOps is an API-only and write-only role that can be used on CI/CD pipelines.
| View Apple business manager (BM) information | | | | ✅ | |
| Generate Apple mobile device management (MDM) certificate signing request (CSR) | | | | ✅ | |
| View disk encryption key for macOS and Windows hosts | ✅ | ✅ | ✅ | ✅ | |
-| Edit OS updates for macOS and Windows hosts | | | ✅ | ✅ | ✅ |
+| Edit OS updates for macOS, Windows, iOS, and iPadOS hosts | | | ✅ | ✅ | ✅ |
| Create, edit, resend and delete configuration profiles for macOS and Windows hosts | | | ✅ | ✅ | ✅ |
| Execute MDM commands on macOS and Windows hosts\** | | | ✅ | ✅ | |
| View results of MDM commands executed on macOS and Windows hosts\** | ✅ | ✅ | ✅ | ✅ | |
@@ -92,10 +92,9 @@ GitOps is an API-only and write-only role that can be used on CI/CD pipelines.
| View all [MDM settings](https://fleetdm.com/docs/using-fleet/mdm-macos-settings) | | | | ✅ | ✅ |
| Edit setup experience (end user authentication, bootstrap package, Setup Assistant)\* | | | ✅ | ✅ | ✅ |
| Edit end user license agreement (EULA)\* | | | | ✅ | |
-| Run arbitrary scripts on hosts | | | ✅ | ✅ | |
-| View saved scripts | ✅ | ✅ | ✅ | ✅ | |
-| Edit/upload saved scripts | | | ✅ | ✅ | ✅ |
-| Run saved scripts on hosts | ✅ | ✅ | ✅ | ✅ | |
+| Run scripts on hosts | | | ✅ | ✅ | |
+| View saved scripts\* | ✅ | ✅ | ✅ | ✅ | |
+| Edit/upload saved scripts\* | | | ✅ | ✅ | ✅ |
| Lock, unlock, and wipe hosts\* | | | ✅ | ✅ | |
\* Applies only to Fleet Premium
@@ -155,7 +154,7 @@ Users with access to multiple teams can be assigned different roles for each tea
| Edit agent options | | | | ✅ | ✅ |
| Initiate [file carving](https://fleetdm.com/docs/using-fleet/rest-api#file-carving) | | | ✅ | ✅ | |
| View disk encryption key for macOS hosts | ✅ | ✅ | ✅ | ✅ | |
-| Edit OS updates for macOS and Windows hosts | | | ✅ | ✅ | ✅ |
+| Edit OS updates for macOS, Windows, iOS, and iPadOS hosts | | | ✅ | ✅ | ✅ |
| Create, edit, resend and delete configuration profiles for macOS and Windows hosts | | | ✅ | ✅ | ✅ |
| Execute MDM commands on macOS and Windows hosts* | | | ✅ | ✅ | |
| View results of MDM commands executed on macOS and Windows hosts* | ✅ | ✅ | ✅ | ✅ | |
@@ -165,10 +164,9 @@ Users with access to multiple teams can be assigned different roles for each tea
| View metadata of MDM macOS bootstrap packages | | | ✅ | ✅ | |
| Edit/upload MDM macOS bootstrap packages | | | ✅ | ✅ | ✅ |
| Enable/disable MDM macOS setup end user authentication | | | ✅ | ✅ | ✅ |
-| Run arbitrary scripts on hosts | | | ✅ | ✅ | |
+| Run scripts on hosts | | | ✅ | ✅ | |
| View saved scripts | ✅ | ✅ | ✅ | ✅ | |
| Edit/upload saved scripts | | | ✅ | ✅ | |
-| Run saved scripts on hosts | ✅ | ✅ | ✅ | ✅ | |
| View script details by host | ✅ | ✅ | ✅ | ✅ | |
| Lock, unlock, and wipe hosts | | | ✅ | ✅ | |
@@ -177,6 +175,9 @@ Users with access to multiple teams can be assigned different roles for each tea
\** Team-level users only see global query results for hosts on teams where they have access.
-
+
+
+
+
+
-
diff --git a/docs/Using Fleet/Scripts.md b/articles/scripts.md
similarity index 88%
rename from docs/Using Fleet/Scripts.md
rename to articles/scripts.md
index 7adb2c057d68..754fdf4da9d7 100644
--- a/docs/Using Fleet/Scripts.md
+++ b/articles/scripts.md
@@ -19,7 +19,7 @@ If you don't use MDM features, to enable scripts, we'll deploy a fleetd agent wi
2. Deploy fleetd to your hosts. If your hosts already have fleetd installed, you can deploy the new fleetd on-top of the old installation.
-Learn more about generating a fleetd agent and deploying it [here](./enroll-hosts.md).
+Learn more about generating a fleetd agent and deploying it [here](https://fleetdm.com/guides/enroll-hosts).
## Execute a script
@@ -45,7 +45,9 @@ fleetctl CLI:
fleetctl run-script --script-path=/path/to/script --host=hostname
```
-
-
+
+
+
+
+
-
diff --git a/articles/seamless-mdm-migration.md b/articles/seamless-mdm-migration.md
new file mode 100644
index 000000000000..9abf3c516c4f
--- /dev/null
+++ b/articles/seamless-mdm-migration.md
@@ -0,0 +1,133 @@
+# Seamless MDM migrations to Fleet
+
+![Seamless MDM migrations to Fleet](../website/assets/images/articles/seamless-mdm-migration-1600x900@2x.png)
+
+Migrating macOS devices between Mobile Device Management (MDM) solutions is often fraught with challenges, including potential gaps in device management, user disruption, and compliance issues. Traditional MDM migrations typically require end-user interaction and leave devices unmanaged for a period, leading to problems like Wi-Fi disconnections due to certificate profile removal and incomplete migrations. These challenges can force organizations to stay with outdated MDM solutions that no longer meet their needs. But there’s a better way.
+
+Seamless MDM migrations are now possible, allowing organizations to transition their macOS devices to Fleet without any downtime or end-user involvement. By leveraging Fleet, you can ensure that your devices remain fully managed and compliant throughout the migration process. This means no more gaps in management, no user disruptions, and a smoother path to a more modern and effective MDM solution.
+
+This guide will walk you through the entire process of migrating your MDM deployment to Fleet. You’ll start by understanding the specific requirements for a seamless migration, followed by configuring Fleet with the necessary certificates and database records. The guide will then take you through the process of installing Fleet’s agent (`fleetd`) on your devices, updating DNS records to redirect devices to the Fleet server, and finally, decommissioning your old MDM server.
+
+Throughout the guide, you’ll find practical advice and best practices to ensure a smooth transition with minimal risk. By the end, you’ll be equipped with the knowledge and tools to execute a seamless MDM migration to Fleet, ensuring that your organization’s devices are securely managed without the typical headaches associated with a traditional MDM switch.
+
+## Requirements
+
+Note: Deployments that do not meet these seamless migration requirements can still migrate with the [standard MDM migration process](https://fleetdm.com/docs/using-fleet/mdm-migration-guide).
+
+* Customer controls the DNS used in the MDM server enrollment (eg. devices are enrolled to `*.customerowneddomain.com`, not `*.mdmvendor.com`).
+* Customer has access to the Apple Push Notification Service (APNS) certificate/key and SCEP certificate/key, or access to the MDM server database to extract these values.
+
+These requirements are easily met in self-hosted open-source MDM solutions and may be met with commercial solutions when the customer is self-hosting or otherwise controls the DNS.
+
+Seamless migration may still be possible with control of DNS along with a copy of the original Certificate Signing Request (CSR) for the APNS certificate. If you are in this situation, please reach out to the Fleet team.
+
+### Why?
+
+Apple allows changing most values in profiles delivered by MDM, but the `ServerURL`, `CheckinURL`, and `PushTopic` cannot be changed without re-enrollment (and user actions). Control of DNS and the certificates allows the MDM to be swapped out without changing these.
+
+## High-level process
+
+1. Configure Fleet with the APNS & SCEP certificates/keys, path redirects, and SCEP renewal.
+2. Import database records letting Fleet know about the devices to be migrated.
+3. Configure controls (profiles, updates, etc.) in Fleet.
+4. Install `fleetd` on the devices (through the existing MDM).
+5. Update DNS records to point devices to the Fleet server.
+6. Decommission the old server.
+
+It is recommended to follow the entire process on a staging/test MDM instance and devices, then repeat for the production instance and devices.
+
+[![Before migration](https://mermaid.ink/img/pako:eNpVUctuwjAQ_BVrT62URIaEvFRxqNKeSivBrZiDiTeJpdhGxqFQBN9eA23VXvY1o9lZ7RFqIxBKCMOQaSddjyV5xMZYJEq2ljtpNNNXtOnNR91x68jLnOntsPbwpiOK128LUuFO1sg0IUqoupeo3XJWzcitXDGNWjD9i5EwJHMzOBRkfSDV64I8rO2U3HlChHuuNj1GtVH3YTg1vfBTpm95-bSXWyd1Sy7qC7Q7tKu_wufzmTQ9orsY9mn5fIk_TAhAoVVcCn_z8WKXgetQIYPSlwIbPvSOAdMnT-WDM4uDrqF0dsAAho3gDivJ_eUKyob3Wz_dcP1uzL8eyiPsoRzTOBrHySim2SQtaBbAAco4S6NxTmmeZcUoLiZJfArg8ypAo5SOKC3iNM-LNMmTJAAU0hk7u32pNrqRrXdmzdB23xtPX3Gkloc?type=png)](https://mermaid.live/edit#pako:eNpVUctuwjAQ_BVrT62URIaEvFRxqNKeSivBrZiDiTeJpdhGxqFQBN9eA23VXvY1o9lZ7RFqIxBKCMOQaSddjyV5xMZYJEq2ljtpNNNXtOnNR91x68jLnOntsPbwpiOK128LUuFO1sg0IUqoupeo3XJWzcitXDGNWjD9i5EwJHMzOBRkfSDV64I8rO2U3HlChHuuNj1GtVH3YTg1vfBTpm95-bSXWyd1Sy7qC7Q7tKu_wufzmTQ9orsY9mn5fIk_TAhAoVVcCn_z8WKXgetQIYPSlwIbPvSOAdMnT-WDM4uDrqF0dsAAho3gDivJ_eUKyob3Wz_dcP1uzL8eyiPsoRzTOBrHySim2SQtaBbAAco4S6NxTmmeZcUoLiZJfArg8ypAo5SOKC3iNM-LNMmTJAAU0hk7u32pNrqRrXdmzdB23xtPX3Gkloc)
+
+[![After migration](https://mermaid.ink/img/pako:eNpVUcFuwjAM_ZXIu2xSW7XQdaWakCYxTmOT4DayQ0jcNqJJUEgZDMG3L6Vs2g5JbL9n-9k5AjcCoYAwDKl20jVYkKfSoSVKVpY5aTTVF7BszCevmXXkZU71tl15eFMTxfjbgkxwJzlSTYgSijcStVvOJjPSmx9UoxZUm0Z4ePm8l1sndUU6xgLtDq1n_CaS8_lMeurfaBiSuWkdCrI6kMnrgjyu7JjcekKEe6Y2DUbcqLswHJcNousE-2c57e6fLhCAQquYFH7kYyeXgqtRIYXCm42sakch6AHB7Hrmt9NhJWu2eI2vGF9X1rR-okvWzXQ6pUD1yVdnrTOLg-ZQONtiAO1GMIcTyfyyFBR9Gdgw_W7MPx-KI-yhSPI8GgzTJE2T-GGU5UkABygGeRz5k8SDJL8fpGmcnQL4ulSIo8zH49Ewy_NRluZpGgAK6Yyd9R_LjS5l5aV5xVV9bXn6BriRpdY?type=png)](https://mermaid.live/edit#pako:eNpVUcFuwjAM_ZXIu2xSW7XQdaWakCYxTmOT4DayQ0jcNqJJUEgZDMG3L6Vs2g5JbL9n-9k5AjcCoYAwDKl20jVYkKfSoSVKVpY5aTTVF7BszCevmXXkZU71tl15eFMTxfjbgkxwJzlSTYgSijcStVvOJjPSmx9UoxZUm0Z4ePm8l1sndUU6xgLtDq1n_CaS8_lMeurfaBiSuWkdCrI6kMnrgjyu7JjcekKEe6Y2DUbcqLswHJcNousE-2c57e6fLhCAQquYFH7kYyeXgqtRIYXCm42sakch6AHB7Hrmt9NhJWu2eI2vGF9X1rR-okvWzXQ6pUD1yVdnrTOLg-ZQONtiAO1GMIcTyfyyFBR9Gdgw_W7MPx-KI-yhSPI8GgzTJE2T-GGU5UkABygGeRz5k8SDJL8fpGmcnQL4ulSIo8zH49Ewy_NRluZpGgAK6Yyd9R_LjS5l5aV5xVV9bXn6BriRpdY)
+
+### 1. Configure Fleet
+
+The Fleet server must be configured with the APNS & SCEP certificates/keys copied from the existing server. This is done via manual modification of the Fleet database and configurations. The Fleet team will perform this configuration on Fleet Cloud instances and can advise how to do it on self-hosted Fleet instances.
+
+In most cases, the paths (portion of the URL after the domain name) used in the enrollment profile `ServerURL`, `CheckInURL` and SCEP URL will differ from those used by Fleet. The Fleet Server load balancer must be configured to redirect the MDM client via HTTP 3xx redirects.
+
+[Apple's documentation](https://developer.apple.com/documentation/devicemanagement/implementing_device_management/sending_mdm_commands_to_a_device?language=objc) states:
+
+> MDM follows HTTP 3xx redirections without user interaction. However, it doesn’t save the URL given by HTTP 301 (Moved Permanently) redirections. Each transaction begins at the URL the MDM payload specifies.
+
+Therefore, redirects must remain as long as migrated devices are enrolled.
+
+For a typical MicroMDM to Fleet migration, the following redirects are used:
+
+| From (MicroMDM path) | To (Fleet path) |
+| -------------------- | --------------- |
+| /mdm/checkin | /mdm/apple/mdm |
+| /mdm/connect | /mdm/apple/mdm |
+| /scep | /mdm/apple/scep |
+
+SCEP certificate renewals need special handling for migrated devices. This is configured (by, or with guidance from the Fleet team) in the server using the [`FLEET_SILENT_MIGRATION_ENROLLMENT_PROFILE` environment variable](https://github.com/fleetdm/fleet/pull/20063). When configured, migrated devices receive an enrollment profile with matching keys when SCEP renewal comes due (migrated devices reject the typical profile Fleet sends because it includes the new server URL).
+
+### 2. Import database records
+
+The Fleet server is made aware of the devices that will be migrated by inserting records into the database. The Fleet team will perform this operation in Fleet Cloud and can advise for self-hosted instances.
+
+For MicroMDM, a [migration script](https://github.com/fleetdm/fleet/pull/18151) has been made that will generate the necessary SQL statements from the MicroMDM database.
+
+For other MDM solutions, please work with the Fleet team to generate the appropriate records.
+
+### 3. Configure controls
+
+Next, configure the controls that will be applied to migrated devices. Use the Teams features in Fleet Premium to apply different configurations to different devices.
+
+In particular,
+
+* [Configuration profiles](https://fleetdm.com/docs/using-fleet/mdm-custom-os-settings#custom-os-settings)
+* [OS updates](https://fleetdm.com/docs/using-fleet/mdm-os-updates)
+* [Disk encryption](https://fleetdm.com/docs/using-fleet/mdm-disk-encryption)
+
+When the device checks in after migration, Fleet will send the full set of configuration profiles configured for that device's team. Any profiles with identifiers matching existing profiles on the device will be updated in place.
+
+Fleet will not send commands to remove profiles that have not been configured in Fleet. Either remove these profiles before migration in the existing MDM before migration or use `fleetctl` or the Fleet API to send an MDM command to remove any undesired profiles.
+
+OS update configurations will apply automatically after the device is migrated.
+
+As of Fleet 4.55, disk encryption keys will automatically be re-escrowed after migration the next time the user logs into their device.
+
+### 4. Install `fleetd`
+
+Install `fleetd` on the devices to migrate. Devices with `fleetd` installed will begin to show up in the Fleet UI (with profiles in a "Pending" state).
+
+Generate `.pkg` packages following the [standard enrollment documentation](https://fleetdm.com/docs/using-fleet/enroll-hosts). Install the package using the existing MDM or any other management tool.
+
+Devices are automatically assigned to Teams in Fleet based on the package they are provided, so be sure to distribute packages that assign devices to teams with the relevant configurations.
+
+### 5. Update DNS
+
+Devices are now communicating with the Fleet server via the `fleetd` agent. They have not yet migrated MDM servers.
+
+Ensure the Fleet server load balancer can terminate HTTPS using the existing server hostname. This typically involves issuing a certificate [with AWS ACM](https://docs.aws.amazon.com/acm/latest/userguide/gs-acm-request-public.html). In Fleet Cloud, the Fleet team will ask the customer team to update a DNS record for verification so that AWS can issue the certificate.
+
+Now the customer updates DNS to point the existing domain to the Fleet server load balancer. This typically involves setting a `CNAME` record with the hostname of the load balancer (eg. `mdm.example.com -> fleet-cloud-alb-1723349272.us-east-2.elb.amazonaws.com`).
+
+Devices will begin checking in with the Fleet server and receiving new configurations.
+
+### 6. Decommission the old server
+
+At this point, the migration is complete. The old server can be decommissioned.
+
+Keep a database backup of the old server on hand in case it is ever needed for reference or recovery.
+
+## Gradual migration
+
+In the process described, when we update DNS all of the devices are migrated immediately. To minimize risk, it is often desired to gradually migrate devices.
+
+Fleet has created a [migration proxy](https://github.com/fleetdm/fleet/tree/main/tools/mdm/migration/mdmproxy) that can be used to gradually migrate specific devices and/or a percentage of devices. This allows a staged migration with progressively more devices migrated.
+
+## Conclusion
+
+Seamless MDM migrations on macOS are not just possible but are a significant step forward in maintaining a secure and compliant environment without disrupting end users. By following this guide, you can transition from your existing MDM solution to Fleet smoothly, keeping your devices managed and secure throughout the process. If you encounter any challenges, the Fleet team is ready to assist you, ensuring your migration is successful.
+
+For organizations ready to take control of their MDM strategy, this seamless migration process is an opportunity to upgrade to a modern, flexible, and secure management solution. We encourage you to reach out for support or further explore the robust features Fleet offers to enhance your device management capabilities.
+
+
+
+
+
+
+
+
diff --git a/articles/software-self-service.md b/articles/software-self-service.md
new file mode 100644
index 000000000000..f82a27f8c867
--- /dev/null
+++ b/articles/software-self-service.md
@@ -0,0 +1,80 @@
+# Software self-service
+
+![Software self-service](../website/assets/images/articles/software-self-service-1600x900@2x.png)
+
+Fleet’s self-service software feature empowers end users by allowing them to independently install approved software packages from a curated list through the Fleet Desktop “My device” page. This not only reduces the administrative burden on IT teams but also enhances user productivity and satisfaction. In this guide, we will walk you through the process of uploading, editing, and managing self-service software packages in Fleet, enabling seamless software distribution and management.
+
+## Prerequisites
+
+* Fleet Premium is required for software self-service.
+
+> Software packages can be added to a specific team or to the "No team" category. The "No team" category is the default assignment for hosts that are not part of any specific team.
+
+## Step-by-Step Instructions
+
+### Adding a self-service software package
+
+1. **Navigate to the Software page**: Click “Software” in the main navigation menu.
+2. **Select a team**: Click the dropdown in the upper left corner of the page and click on the team to which you want to add the software package.
+3. **Open the “Add software” modal**: Click the “Add software” button in the upper right corner of the page.
+4. **Select a software package to upload**: Click “Choose file” in the “Add software” modal and select a software package from your computer.
+5. **Advanced options**: If desired, click “Advanced options” to add a pre-install condition or post-install script to your software package.
+ * **Pre-install condition**: This is an osquery query that results in true. For example, you might require a specific software title to exist before installing additional extensions.
+ * **Post-install script**: This might be used to apply a license key, perform configuration tasks, or execute cleanup tasks after the software installation.
+6. **Make the software package self-service**: Check the “Self-service” checkbox to mark the software package as self-service.
+7. **Finish the upload**: Click the “Add software” button to finish the upload process.
+
+### Editing a self-service software package
+
+1. **Navigate to the software details page for the software package**: Click “Software” in the main navigation menu.
+2. **Select a team**: Click the dropdown in the upper left corner of the page and click on the team to which you added the software package.
+3. **Filter by self-service**: To make it easier to find your software package, click on the dropdown to the left of the search bar and select “Self-service”. This will filter the results in the table to only show self-service software packages. If you still don’t see your software package, you can page through the results or search for your software package’s name in the search bar.
+4. **Open the details page**: Click on the software package’s name.
+5. **Open the actions dropdown**: Click on the “Actions” dropdown on the far right of the page. From here, you can download the software package, delete the software package, or click “Advanced options” to see the options you configured when adding the software package.
+
+### Downloading a self-service software package
+
+1. **Navigate to the software details page for the software package**: Click “Software” in the main navigation menu.
+2. **Select a team**: Click the dropdown in the upper left corner of the page and click on the team to which you added the software package.
+3. **Filter by self-service**: Click on the dropdown to the left of the search bar and select “Self-service” and page through the results or search for your software package’s name in the search bar.
+4. **Download the software package**:
+* **Option 1**: Click on the down-arrow next to the software package name in the list of self-service software packages to start an immediate download.
+* **Option 2**: Click on the software package’s name to open the details page. Click on the “Actions” dropdown on the far right of the page, and then click on “Download” to download the software package to your computer.
+
+### Deleting a self-service software package
+
+1. **Navigate to the software details page for the software package**: Click “Software” in the main navigation menu.
+2. **Select a team**: Click the dropdown in the upper left corner of the page and click on the team to which you added the software package.
+3. **Filter by self-service**: Click on the dropdown to the left of the search bar and select “Self-service” and page through the results or search for your software package’s name in the search bar.
+4. **Open the details page**: Click on the software package’s name.
+5. **Open the actions dropdown**: Click on the “Actions” dropdown on the far right of the page.
+6. **Delete the software package**: Click on “Delete” to remove the software package from Fleet. Confirm the deletion if prompted.
+
+### Installing self-service software packages
+
+To install the self-service software package on the host:
+
+1. **Navigate to the “Self-service” tab**: Click on the Fleet Desktop icon in the OS menu bar. Click “Self-service”. This will point your default web browser to the list of self-service software packages in the “My device” page.
+2. **Install the self-service software package**: Click the “Install” button for the software package you want to install.
+
+### Using the REST API for self-service software packages
+
+Fleet provides a REST API for managing software packages, including self-service software packages. Learn more about Fleet's [REST API](https://fleetdm.com/docs/rest-api/rest-api#software).
+
+### Managing self-service software packages with GitOps
+
+To manage self-service software packages using Fleet's best practice GitOps, check out the `software` key in the [GitOps reference documentation](https://fleetdm.com/docs/using-fleet/gitops#software).
+
+> Note: with GitOps enabled, software packages uploaded using the web UI will not persist.
+
+## Conclusion
+
+Fleet’s self-service software feature not only simplifies software management for IT administrators but also empowers end users by giving them access to necessary software on demand. This feature ensures that your hosts remain secure while improving overall user experience. For further information and advanced management techniques, refer to Fleet's [REST API](https://fleetdm.com/docs/rest-api/rest-api#software) and [GitOps](https://fleetdm.com/docs/using-fleet/gitops#software) documentation.
+
+
+
+
+
+
+
+
diff --git a/docs/01-Using-Fleet/standard-query-library/README.md b/articles/standard-query-library.md
similarity index 87%
rename from docs/01-Using-Fleet/standard-query-library/README.md
rename to articles/standard-query-library.md
index fc3f3bfdc11c..7bbdd52bf218 100644
--- a/docs/01-Using-Fleet/standard-query-library/README.md
+++ b/articles/standard-query-library.md
@@ -47,4 +47,9 @@ Listed below are great resources that contain additional queries.
- Osquery (https://github.com/osquery/osquery/tree/master/packs)
- Palantir osquery configuration (https://github.com/palantir/osquery-configuration/tree/master/Fleet)
-
+
+
+
+
+
+
diff --git a/docs/Using Fleet/segment-hosts.md b/articles/teams.md
similarity index 51%
rename from docs/Using Fleet/segment-hosts.md
rename to articles/teams.md
index 1047ee4dbbe7..ea688bc08b0c 100644
--- a/docs/Using Fleet/segment-hosts.md
+++ b/articles/teams.md
@@ -1,8 +1,8 @@
-# Segment hosts
+# Teams
_Available in Fleet Premium_
-In Fleet, you can group hosts together in a "team" in Fleet. This way, you can apply queries, policies, scripts, and more that are tailored to the hosts' risk/compliance needs.
+In Fleet, you can group hosts together in a "team" in Fleet. This way, you can apply queries, policies, scripts, and more that are tailored to a host's risk/compliance needs.
A host can only belong to one team.
@@ -13,12 +13,14 @@ You can manage teams by selecting your avatar in the top navigation and then **S
## Best practice
Fleet's best practice teams:
-- `Workstations`: End user's production work computers (macOS, Windows, and Linux)
-- `Workstations (canary)`: IT team's test work computers. Sometimes, for demos or testing, includes end user's work computers. Used for [dogfooding](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) a new workflow or feature that may or may not be rolled out to the "Workstations" team.
-- `Servers`: Security team's production servers.
-- `Servers (canary)`: Security team's test servers.
+- `💻 Workstations`: End users' production work computers (macOS, Windows, and Linux)
+- `💻🐣 Workstations (canary)`: IT team's test work computers. Sometimes, for demos or testing, includes end user's work computers. Used for [dogfooding](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) a new workflow or feature that may or may not be rolled out to the "Workstations" team.
+- `☁️ Servers`: Security team's production servers.
+- `☁️🐣 Servers (canary)`: Security team's test servers.
- `Compliance exclusions`: All contributors' test work computers or virtual machines (VMs). Used for validating workflows for Fleet customers or reproducing bugs in the Fleet product.
-- `iPhones`: All contributors' test iOS hosts. Used to dogfood Fleet's iOS features (coming soon).
+- `📱🏢 Company-owned iPhones`: iPhones purchased by the organization that enroll to Fleet automatically via Apple Business Manager. For example, iPhones used by iOS Engineers.
+- `🔳🏢 Company-owned iPads`: iPads purchased by the organization that enroll to Fleet automatically via Apple Business Manager. For example, conference-room iPads.
+
If some of your hosts don't fall under the above teams, what are these hosts for? The answer determines the the hosts' risk/compliance needs, and thus their security basline, and thus their "team" in Fleet. If the hosts' have a different compliance needs, and thus different security baseline, then it's time to create a new team in Fleet.
@@ -28,10 +30,13 @@ You can add hosts to a new team in Fleet by either enrolling the host with a tea
## Advanced
-You can automatically enroll hosts to a specific team in Fleet by installing a fleetd with a team enroll secret. Learn more [here](./enroll-hosts.md#enroll-host-to-a-specific-team).
+You can automatically enroll hosts to a specific team in Fleet by installing a fleetd with a team enroll secret. Learn more [here](https://fleetdm.com/guides/enroll-hosts#enroll-host-to-a-specific-team).
Changing the host's enroll secret after enrollment will not cause the host to be transferred to a different team.
-
+
+
+
+
+
-
diff --git a/docs/Using Fleet/Vulnerability-Processing.md b/articles/vulnerability-processing.md
similarity index 51%
rename from docs/Using Fleet/Vulnerability-Processing.md
rename to articles/vulnerability-processing.md
index 2217919782f6..610bd51347dc 100644
--- a/docs/Using Fleet/Vulnerability-Processing.md
+++ b/articles/vulnerability-processing.md
@@ -1,7 +1,5 @@
# Vulnerability processing
-## Introduction
-
Vulnerability processing in Fleet detects vulnerabilities (CVEs) for the software installed on your hosts.
To see what software is covered, check out the [Coverage section](#coverage).
@@ -16,16 +14,27 @@ To see what software is covered, check out the [Coverage section](#coverage).
Fleet detects vulnerabilities for these software types:
-| Type | macOS | Windows | Linux |
-| ------------------- | ------------------------------------------ | ------------------------------------------------ | ---------------- |
-| Apps | ✅ | ✅ | ❌ |
-| Browser plugins | Chrome extensions, Firefox extensions | Chrome extensions, Firefox extensions | ❌ |
-| Packages | Python, Homebrew | Python, Atom, Chocolatey | Packages defined in the [OVAL definitions](https://github.com/fleetdm/nvd/blob/master/oval_sources.json), except for vulnerabilities involving configuration files. Supported distributions:
Ubuntu
RHEL based distros (Red Hat, CentOS, Fedora, and Amazon Linux)
|
-| IDE extensions | VS Code extensions | VS Code extensions | VS Code extensions |
+| Type | macOS | Windows | Linux |
+| ------------------- | ------------------------------------------ | ------------------------------------------------ |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Apps | ✅ | ✅ | ❌ |
+| Browser plugins | Chrome extensions, Firefox extensions | Chrome extensions, Firefox extensions | ❌ |
+| Packages | Python, Homebrew | Python, Atom, Chocolatey |
For Ubuntu, Debian, RHEL (including CentOS), and Fedora: packages defined in the [OVAL definitions](https://github.com/fleetdm/nvd/blob/master/oval_sources.json), except for vulnerabilities involving configuration files.
For Amazon Linux, packages maintained by Amazon by checking [ALAS advisories](https://alas.aws.amazon.com/).
|
+| IDE extensions | VS Code extensions | VS Code extensions | VS Code extensions |
As of right now, only app names with all ASCII characters are supported. Apps with names featuring non-ASCII characters, such as Cyrillic, will not generate matches.
-### Advanced configuration
+For Ubuntu Linux, kernel vulnerabilities with known variants (ie. `-generic`) are detected using OVAL. Custom kernels (unknown variants) are detected using NVD.
+
+## Sources
+
+Fleet combines multiple sources to get accurate and up-to-date CVE information:
+- [National Vulnerability Database](https://nvd.nist.gov/developers/vulnerabilities) CVE feeds
+- [VulnCheck](https://vulncheck.com/) CVE feeds
+- [Mac Office release notes](https://learn.microsoft.com/en-us/officeupdates/release-notes-office-for-mac) for Office for Mac
+- [Microsoft MSRC Security Bulletins](https://msrc.microsoft.com/update-guide) for Windows OS vulnerabilities
+- [OVAL definitions](https://github.com/fleetdm/nvd/blob/master/oval_sources.json) for Linux software
+
+## Advanced configuration
Fleet runs vulnerability downloading and processing via internal scheduled cron job. This internal mechanism is very useful
for frictionless deployments and is well suited for most use cases. However, in larger deployments,
@@ -61,6 +70,9 @@ command.
fleet vuln_processing
```
-
+
+
+
+
+
-
diff --git a/articles/windows-mdm-setup.md b/articles/windows-mdm-setup.md
index 1fb36f3bf6a8..87188e11ee11 100644
--- a/articles/windows-mdm-setup.md
+++ b/articles/windows-mdm-setup.md
@@ -10,7 +10,7 @@ To use automatic enrollment (aka zero-touch) features on Windows, follow instruc
### Step 1: Generate your certificate and key
-Fleet uses a certificate and key pair to authenticate and manage interactions between Fleet and Windows host.
+Fleet uses a certificate and key pair to authenticate and manage interactions between the Fleet server and a Windows host.
How to generate a certificate and key:
diff --git a/assets/images/iPadOS-install-profile.png b/assets/images/iPadOS-install-profile.png
new file mode 100644
index 000000000000..c398038517f7
Binary files /dev/null and b/assets/images/iPadOS-install-profile.png differ
diff --git a/assets/images/iPadOS-profile-downloaded.png b/assets/images/iPadOS-profile-downloaded.png
new file mode 100644
index 000000000000..f21b12615f70
Binary files /dev/null and b/assets/images/iPadOS-profile-downloaded.png differ
diff --git a/assets/images/ios-install-profile.png b/assets/images/ios-install-profile.png
new file mode 100644
index 000000000000..8266c55201eb
Binary files /dev/null and b/assets/images/ios-install-profile.png differ
diff --git a/assets/images/ios-profile-downloaded.png b/assets/images/ios-profile-downloaded.png
new file mode 100644
index 000000000000..7941a6581557
Binary files /dev/null and b/assets/images/ios-profile-downloaded.png differ
diff --git a/assets/images/ios-updates-preview.png b/assets/images/ios-updates-preview.png
new file mode 100644
index 000000000000..c5873b8621bb
Binary files /dev/null and b/assets/images/ios-updates-preview.png differ
diff --git a/assets/images/ipados-updates-preview.png b/assets/images/ipados-updates-preview.png
new file mode 100644
index 000000000000..8a08399807c5
Binary files /dev/null and b/assets/images/ipados-updates-preview.png differ
diff --git a/changes/13157-fv-escrow b/changes/13157-fv-escrow
new file mode 100644
index 000000000000..e6804a05ec4b
--- /dev/null
+++ b/changes/13157-fv-escrow
@@ -0,0 +1 @@
+* `fleetd` now uses Escrow Buddy to rotate FileVault keys. Internal API endpoints documented in the API for contributors have been modified and/or removed.
diff --git a/changes/16866-ade-force-filevault b/changes/16866-ade-force-filevault
new file mode 100644
index 000000000000..4486357bf558
--- /dev/null
+++ b/changes/16866-ade-force-filevault
@@ -0,0 +1,2 @@
+- Adds enforcement of FileVault during the MacOS Setup Assistant process for hosts that are enrolled
+into teams (or no team) with disk encryption turned on.
\ No newline at end of file
diff --git a/changes/17249-mysql-8 b/changes/17249-mysql-8
new file mode 100644
index 000000000000..b3948968cf08
--- /dev/null
+++ b/changes/17249-mysql-8
@@ -0,0 +1,2 @@
+* Drop support for MySQL 5.7
+* Minimum requirements raised to MySQL 8.0
diff --git a/changes/17379-live-query-caching b/changes/17379-live-query-caching
new file mode 100644
index 000000000000..949252299593
--- /dev/null
+++ b/changes/17379-live-query-caching
@@ -0,0 +1 @@
+- Increased performance of live queries to accommodate for higher volumes when utilizing zero-trust workflows
\ No newline at end of file
diff --git a/changes/18239-delete-secret-copy b/changes/18239-delete-secret-copy
deleted file mode 100644
index de416369ae91..000000000000
--- a/changes/18239-delete-secret-copy
+++ /dev/null
@@ -1 +0,0 @@
-* Update UI's delete secret link
\ No newline at end of file
diff --git a/changes/1845-linux-arm64 b/changes/1845-linux-arm64
new file mode 100644
index 000000000000..6ebb53ff6380
--- /dev/null
+++ b/changes/1845-linux-arm64
@@ -0,0 +1,2 @@
+* Added support for generating fleetd packages for Linux ARM64
+* fleetctl: New `fleetctl package` --arch flag
diff --git a/changes/18471-gitops-rename-team b/changes/18471-gitops-rename-team
deleted file mode 100644
index 4699cabbfd76..000000000000
--- a/changes/18471-gitops-rename-team
+++ /dev/null
@@ -1,3 +0,0 @@
-- `fleetctl gitops` can now be used to rename teams -- simply change the team name in the YAML file
- - The team name is changed if the YAML config is applied from the same filename as before
- - `fleetctl gitops` needs to have previously run with this Fleet/fleetctl version or later
diff --git a/changes/18554-store-and-show-next-host-maintenance-window b/changes/18554-store-and-show-next-host-maintenance-window
deleted file mode 100644
index 7dea3c952b4b..000000000000
--- a/changes/18554-store-and-show-next-host-maintenance-window
+++ /dev/null
@@ -1,2 +0,0 @@
-- Show a host's upcoming scheduled maintenance window, if any, on the host details page of the UI
- and in host responses from the API.
diff --git a/changes/18815-hide-cta-from-team-roles b/changes/18815-hide-cta-from-team-roles
deleted file mode 100644
index 5b2d9b8ffbd1..000000000000
--- a/changes/18815-hide-cta-from-team-roles
+++ /dev/null
@@ -1 +0,0 @@
-* Fleet UI fixes: Hide CTA on inherited queries/policies from team level users
\ No newline at end of file
diff --git a/changes/18849-config-profiles-exclude-labels b/changes/18849-config-profiles-exclude-labels
deleted file mode 100644
index 4c1d41ecfbf4..000000000000
--- a/changes/18849-config-profiles-exclude-labels
+++ /dev/null
@@ -1,5 +0,0 @@
-* Added the database migrations to create the new `exclude` column for labels associated with MDM profiles (and declarations).
-* Added the API changes to support the `labels_include_all` and `labels_exclude_any` fields (and accept the deprecated `labels` field as an alias for `labels_include_all`).
-* Added `fleetctl gitops` and `fleetctl apply` support for `labels_include_all` and `labels_exclude_any` to configure a custom setting.
-* Updated the profile reconciliation logic to handle the new "exclude any" labels.
-* Fix bug where macOS declarations were stuck in "to be removed" state indefinitely.
diff --git a/changes/18897-shoe-zeroes b/changes/18897-shoe-zeroes
new file mode 100644
index 000000000000..7faddd522dd6
--- /dev/null
+++ b/changes/18897-shoe-zeroes
@@ -0,0 +1 @@
+Added "0 items" description on empty software tables for UI consistency
diff --git a/changes/18913-ignore-rejected-cves b/changes/18913-ignore-rejected-cves
new file mode 100644
index 000000000000..1fabe60f9ffb
--- /dev/null
+++ b/changes/18913-ignore-rejected-cves
@@ -0,0 +1 @@
+CVEs identified as 'Rejected' in NVD will no longer match against software
\ No newline at end of file
diff --git a/changes/19031-maintenance-windows-every-week b/changes/19031-maintenance-windows-every-week
deleted file mode 100644
index c23d6dd53afc..000000000000
--- a/changes/19031-maintenance-windows-every-week
+++ /dev/null
@@ -1 +0,0 @@
-- Maintenance window now scheduled weekly on Tuesdays (previously monthly on the third Tuesday of the month)
diff --git a/changes/19037-support-s3-store-for-bootstrap-packages b/changes/19037-support-s3-store-for-bootstrap-packages
new file mode 100644
index 000000000000..400cc30b3cb3
--- /dev/null
+++ b/changes/19037-support-s3-store-for-bootstrap-packages
@@ -0,0 +1 @@
+* Added support for S3 to store MDM bootstrap packages (uses the same bucket configuration as for software installers).
diff --git a/changes/19055-hide-run-script b/changes/19055-hide-run-script
deleted file mode 100644
index b45a211ea244..000000000000
--- a/changes/19055-hide-run-script
+++ /dev/null
@@ -1 +0,0 @@
-* Hide the host detail page's "Run script" action from Global and Team Observer/+s.
diff --git a/changes/19055-scripts-run-permissions b/changes/19055-scripts-run-permissions
deleted file mode 100644
index 8c5581d89340..000000000000
--- a/changes/19055-scripts-run-permissions
+++ /dev/null
@@ -1 +0,0 @@
-- Updated script run permissions -- only admins and maintainers can run arbitrary or saved scripts (not observer or observer+)
diff --git a/changes/19099-sw-vuln-filters b/changes/19099-sw-vuln-filters
new file mode 100644
index 000000000000..06ef4ac4d2d8
--- /dev/null
+++ b/changes/19099-sw-vuln-filters
@@ -0,0 +1,2 @@
+- adds the following filters to `/software/titles` and `/software/versions` API endpoints: `exploit: bool`, `min_cvss_score: float`, `max_cvss_score: float`
+- Software titles/versions tables allow for filtering by vulnerabilities including severity and known exploit
diff --git a/changes/19127-update-logic-and-copy-around-host-identifiers b/changes/19127-update-logic-and-copy-around-host-identifiers
deleted file mode 100644
index f482ac0fd298..000000000000
--- a/changes/19127-update-logic-and-copy-around-host-identifiers
+++ /dev/null
@@ -1,2 +0,0 @@
-- Update `fleetctl query --hosts` to work with hostnames, host UUIDs, and/or hardware serial numbers.
-- Clarify various help and error texts around host identifiers.
diff --git a/changes/19143-mdm-cmd-filters b/changes/19143-mdm-cmd-filters
deleted file mode 100644
index c17539c7381e..000000000000
--- a/changes/19143-mdm-cmd-filters
+++ /dev/null
@@ -1 +0,0 @@
-- `fleetctl get mdm_commands` now returns 20 rows and supports `--host` `--type` filters to improve response time
\ No newline at end of file
diff --git a/changes/19144-improve-extraction b/changes/19144-improve-extraction
deleted file mode 100644
index cc55af72e65a..000000000000
--- a/changes/19144-improve-extraction
+++ /dev/null
@@ -1 +0,0 @@
-* Improve extraction of application name from `pkg` installers
diff --git a/changes/19144-pkg-matching b/changes/19144-pkg-matching
deleted file mode 100644
index 815f2239fc64..000000000000
--- a/changes/19144-pkg-matching
+++ /dev/null
@@ -1 +0,0 @@
-* Improved the matching of `pkg` installer files to existing software
diff --git a/changes/19176-fleetd-base-mdm b/changes/19176-fleetd-base-mdm
deleted file mode 100644
index c0fbdc929a8b..000000000000
--- a/changes/19176-fleetd-base-mdm
+++ /dev/null
@@ -1 +0,0 @@
-* Automatic install of `fleetd` when a host turns on MDM now uses the latest released `fleetd` version.
diff --git a/changes/19219-fleetd-base-email-desktop b/changes/19219-fleetd-base-email-desktop
deleted file mode 100644
index 85c840ea3939..000000000000
--- a/changes/19219-fleetd-base-email-desktop
+++ /dev/null
@@ -1 +0,0 @@
-- Added support for END_USER_EMAIL and FLEET_DESKTOP parameters to Windows MSI install package
diff --git a/changes/19280-maintenance-window-descriptions b/changes/19280-maintenance-window-descriptions
new file mode 100644
index 000000000000..90848dcffe67
--- /dev/null
+++ b/changes/19280-maintenance-window-descriptions
@@ -0,0 +1 @@
+Maintenance window descriptions are now updated regularly to match the failing policy description/resolution.
diff --git a/changes/19281-add-host-name-to-event-descriptions b/changes/19281-add-host-name-to-event-descriptions
deleted file mode 100644
index 9a8b449fc416..000000000000
--- a/changes/19281-add-host-name-to-event-descriptions
+++ /dev/null
@@ -1 +0,0 @@
-* Add host's display name to calendar event descriptions
diff --git a/changes/19352-calendar-real-time b/changes/19352-calendar-real-time
index dc72e9889922..d96cf1fa1177 100644
--- a/changes/19352-calendar-real-time
+++ b/changes/19352-calendar-real-time
@@ -1 +1,3 @@
- In maintenance windows using Google Calendar, calendar event is now recreated within 30 seconds if deleted or moved to the past.
+ - Fleet server watches for potential changes for up to 1 week after original event time. If event is moved forward more than 1 week, then after 1 week Fleet server will check for event changes once every 30 minutes.
+ - These near real-time updates may add additional load to the Google Calendar API, so it is recommended to use API usage alerts or other monitoring methods.
diff --git a/changes/19447-ios-ipados-software b/changes/19447-ios-ipados-software
new file mode 100644
index 000000000000..26acad51315e
--- /dev/null
+++ b/changes/19447-ios-ipados-software
@@ -0,0 +1,3 @@
+- iOS and iPadOS device details refetch can now be triggered with the existing `POST /api/latest/fleet/hosts/:id/refetch` endpoint.
+- iOS and iPadOS user-installed apps can be viewed in Fleet
+- iOS and iPadOS apps can be installed using Apple's VPP (Volume Purchase Program)
diff --git a/changes/19550-software-no-teams b/changes/19550-software-no-teams
new file mode 100644
index 000000000000..933665cd22c0
--- /dev/null
+++ b/changes/19550-software-no-teams
@@ -0,0 +1 @@
+- adds support for No teams on all software pages including adding software installers
\ No newline at end of file
diff --git a/changes/19551-policy-software-automations b/changes/19551-policy-software-automations
new file mode 100644
index 000000000000..4b88cb4c1fba
--- /dev/null
+++ b/changes/19551-policy-software-automations
@@ -0,0 +1 @@
+* Implement features allowing automatic installation of software on hosts that fail policies.
diff --git a/changes/19557-empty-hover-styles b/changes/19557-empty-hover-styles
deleted file mode 100644
index 20c046c0c7d6..000000000000
--- a/changes/19557-empty-hover-styles
+++ /dev/null
@@ -1 +0,0 @@
-* Update empty state styles in 4 places, clean up
diff --git a/changes/19561-browser-progress-bar b/changes/19561-browser-progress-bar
new file mode 100644
index 000000000000..cfd16443d918
--- /dev/null
+++ b/changes/19561-browser-progress-bar
@@ -0,0 +1 @@
+- In Fleet GUI, downloading a software installer package now shows the browser's built-in progress bar.
diff --git a/changes/19562-python-vuln b/changes/19562-python-vuln
new file mode 100644
index 000000000000..928551bbce0e
--- /dev/null
+++ b/changes/19562-python-vuln
@@ -0,0 +1 @@
+- Fixed CVE-2024-4030 in Vulncheck feed incorrectly targeting non-Windows hosts
\ No newline at end of file
diff --git a/changes/19577-fleetctl-get-desc b/changes/19577-fleetctl-get-desc
deleted file mode 100644
index dc7d737527e8..000000000000
--- a/changes/19577-fleetctl-get-desc
+++ /dev/null
@@ -1 +0,0 @@
-- Updated fleetctl get queries/labels/hosts descriptions.
diff --git a/changes/19586-capitalization-bug b/changes/19586-capitalization-bug
deleted file mode 100644
index 8bd3b20fd2d9..000000000000
--- a/changes/19586-capitalization-bug
+++ /dev/null
@@ -1 +0,0 @@
-* Fix UI capitalizations
\ No newline at end of file
diff --git a/changes/19645-cleanup_macos b/changes/19645-cleanup_macos
deleted file mode 100644
index 106deab08fe0..000000000000
--- a/changes/19645-cleanup_macos
+++ /dev/null
@@ -1 +0,0 @@
-* Fleetd cleanup script for macOS will return completed if run from fleet
diff --git a/changes/19645-uninstall-script-fleetd b/changes/19645-uninstall-script-fleetd
deleted file mode 100644
index b68905ba194e..000000000000
--- a/changes/19645-uninstall-script-fleetd
+++ /dev/null
@@ -1 +0,0 @@
-* Provided fleetd uninstall script will return when run through fleet
diff --git a/changes/19646-ui-profiles-pending-tooltip b/changes/19646-ui-profiles-pending-tooltip
new file mode 100644
index 000000000000..824ba143c903
--- /dev/null
+++ b/changes/19646-ui-profiles-pending-tooltip
@@ -0,0 +1 @@
+- Updated UI tooltips for pending OS settings.
diff --git a/changes/19651-hide-self-service b/changes/19651-hide-self-service
deleted file mode 100644
index e5318d211414..000000000000
--- a/changes/19651-hide-self-service
+++ /dev/null
@@ -1 +0,0 @@
-- Hide "Self-service" in Fleet Desktop and My device page if there is no self-service software available
diff --git a/changes/19674-dep-min-os-version b/changes/19674-dep-min-os-version
new file mode 100644
index 000000000000..b9adefe9ec57
--- /dev/null
+++ b/changes/19674-dep-min-os-version
@@ -0,0 +1 @@
+- Updated MDM features to enforce minimum OS version settings during Apple Automated Device Enrollment (ADE).
diff --git a/changes/19683-csv-comma-bug b/changes/19683-csv-comma-bug
deleted file mode 100644
index d5b3f72667ff..000000000000
--- a/changes/19683-csv-comma-bug
+++ /dev/null
@@ -1 +0,0 @@
-- Fix exporting CSVs with fields that contain commas to render properly
diff --git a/changes/19684-renew-scep-180 b/changes/19684-renew-scep-180
new file mode 100644
index 000000000000..131c08ff5111
--- /dev/null
+++ b/changes/19684-renew-scep-180
@@ -0,0 +1 @@
+* Increase threshold to renew Apple SCEP certificates for MDM enrollments to 180 days.
diff --git a/changes/19693-additional-stats b/changes/19693-additional-stats
new file mode 100644
index 000000000000..5978543420d6
--- /dev/null
+++ b/changes/19693-additional-stats
@@ -0,0 +1 @@
+- Added additional statistics items for AI features, maintenance window, and Fleet Desktop
diff --git a/changes/19694-vul-page-bugs b/changes/19694-vul-page-bugs
deleted file mode 100644
index 433891d9537c..000000000000
--- a/changes/19694-vul-page-bugs
+++ /dev/null
@@ -1 +0,0 @@
-* Fix 3 UI bugs on the Software page
diff --git a/changes/19760-software-update-tooltip b/changes/19760-software-update-tooltip
deleted file mode 100644
index 75c0ae8f4644..000000000000
--- a/changes/19760-software-update-tooltip
+++ /dev/null
@@ -1 +0,0 @@
-* Update software updated timestamp tooltip
\ No newline at end of file
diff --git a/changes/19800-renew-scep-migration b/changes/19800-renew-scep-migration
deleted file mode 100644
index 5ab73deffbfe..000000000000
--- a/changes/19800-renew-scep-migration
+++ /dev/null
@@ -1 +0,0 @@
-* Added support for renewing SCEP certificates with custom enrollment profiles.
diff --git a/changes/19808-prof b/changes/19808-prof
new file mode 100644
index 000000000000..71d19f8c4bc3
--- /dev/null
+++ b/changes/19808-prof
@@ -0,0 +1 @@
+* Fixed bugs on enrollment profiles when the organization name contains invalid XML characters.
diff --git a/changes/19828-hide-query-delete-checkboxes-from-observers b/changes/19828-hide-query-delete-checkboxes-from-observers
deleted file mode 100644
index f4c268d27b9c..000000000000
--- a/changes/19828-hide-query-delete-checkboxes-from-observers
+++ /dev/null
@@ -1 +0,0 @@
-- Hide query delete checkboxes from team observers
diff --git a/changes/19844-update-go b/changes/19844-update-go
deleted file mode 100644
index 7de847f94418..000000000000
--- a/changes/19844-update-go
+++ /dev/null
@@ -1 +0,0 @@
-* Updated Go version to go1.22.4
diff --git a/changes/19853-homebrew-intellij b/changes/19853-homebrew-intellij
new file mode 100644
index 000000000000..713d4ae1423d
--- /dev/null
+++ b/changes/19853-homebrew-intellij
@@ -0,0 +1 @@
+Fixed false negative vulnerabilities with IntelliJ IDEA CE and PyCharm CE installed via Homebrew.
diff --git a/changes/19857-known_vulnerability b/changes/19857-known_vulnerability
new file mode 100644
index 000000000000..b9c95991b3fc
--- /dev/null
+++ b/changes/19857-known_vulnerability
@@ -0,0 +1,2 @@
+For GET /api/v1/fleet/vulnerabilities/{cve} endpoint, added validation of CVE format, and added a 204 response. The 204 response indicates that the vulnerability is known to Fleet but not present on any hosts.
+For the UI, add new empty states for searching vulnerabilities: invalid CVE format searched, a known CVE serached but not present on hosts, not a known CVE searched, exploited vulnerability empty state, operating systems empty state, new icons
diff --git a/changes/19864-vpp-token-crud b/changes/19864-vpp-token-crud
new file mode 100644
index 000000000000..ee4a92e80f25
--- /dev/null
+++ b/changes/19864-vpp-token-crud
@@ -0,0 +1,2 @@
+- Adds the functionality for the `POST /mdm/apple/vpp_token`, `DELETE /mdm/apple/vpp_token` and
+`GET /vpp` endpoints.
\ No newline at end of file
diff --git a/changes/19865-db-schema b/changes/19865-db-schema
new file mode 100644
index 000000000000..ede5f90ed005
--- /dev/null
+++ b/changes/19865-db-schema
@@ -0,0 +1 @@
+- Adds DB updates to support the VPP software feature.
\ No newline at end of file
diff --git a/changes/19867-get-avail-apps b/changes/19867-get-avail-apps
new file mode 100644
index 000000000000..4ace068f95b1
--- /dev/null
+++ b/changes/19867-get-avail-apps
@@ -0,0 +1 @@
+- Adds functionality for the `GET /software/app_store_apps` and `POST /software/app_store_apps` endpoints.
\ No newline at end of file
diff --git a/changes/19868-vpp-install-command b/changes/19868-vpp-install-command
new file mode 100644
index 000000000000..337b5d501004
--- /dev/null
+++ b/changes/19868-vpp-install-command
@@ -0,0 +1 @@
+- Adds functionality for installing App Store apps to the VPP feature.
\ No newline at end of file
diff --git a/changes/19870-vpp-activities-backend b/changes/19870-vpp-activities-backend
new file mode 100644
index 000000000000..115f92e1fd77
--- /dev/null
+++ b/changes/19870-vpp-activities-backend
@@ -0,0 +1 @@
+- Adds global activity support for VPP related activities.
\ No newline at end of file
diff --git a/changes/19871-gitops-vpp-config b/changes/19871-gitops-vpp-config
new file mode 100644
index 000000000000..e9a02e0fa7d2
--- /dev/null
+++ b/changes/19871-gitops-vpp-config
@@ -0,0 +1 @@
+* Add support for VPP to gitops config
diff --git a/changes/19880-include-vpp-apps-in-software-titles-endpoints b/changes/19880-include-vpp-apps-in-software-titles-endpoints
new file mode 100644
index 000000000000..9503cdef99fc
--- /dev/null
+++ b/changes/19880-include-vpp-apps-in-software-titles-endpoints
@@ -0,0 +1,2 @@
+* Added the associated VPP apps to the `GET /software/titles` and `GET /software/titles/:id` endpoints.
+* Added the associated VPP apps to the `GET /hosts/:id/software` and `GET /device/:token/software` endpoints.
diff --git a/changes/19882-ssvpp-backend b/changes/19882-ssvpp-backend
new file mode 100644
index 000000000000..64baa422e854
--- /dev/null
+++ b/changes/19882-ssvpp-backend
@@ -0,0 +1 @@
+- Add backend and gitops support for self service VPP
diff --git a/changes/19883-add-support-for-ui-self-service-vpp b/changes/19883-add-support-for-ui-self-service-vpp
new file mode 100644
index 000000000000..fd5f9fe3c249
--- /dev/null
+++ b/changes/19883-add-support-for-ui-self-service-vpp
@@ -0,0 +1 @@
+- add support to UI for self service VPP software
diff --git a/changes/19910-fix-low_disk_space-counts b/changes/19910-fix-low_disk_space-counts
deleted file mode 100644
index 249945b546c0..000000000000
--- a/changes/19910-fix-low_disk_space-counts
+++ /dev/null
@@ -1 +0,0 @@
-* Fixed counts for hosts with with low disk space in summary page.
diff --git a/changes/19937-fix-anchor-link b/changes/19937-fix-anchor-link
deleted file mode 100644
index 16ef161142e7..000000000000
--- a/changes/19937-fix-anchor-link
+++ /dev/null
@@ -1 +0,0 @@
-* Fix link to fleetd uninstall instructions in "Delete device" modal
diff --git a/changes/19950-changes b/changes/19950-changes
deleted file mode 100644
index 842018494e09..000000000000
--- a/changes/19950-changes
+++ /dev/null
@@ -1 +0,0 @@
-* Fixed crash in `fleetd` installer on Windows if there are registry keys with special characters on the system.
diff --git a/changes/19963-ios-ipados-as-platforms b/changes/19963-ios-ipados-as-platforms
deleted file mode 100644
index a06c8c321a85..000000000000
--- a/changes/19963-ios-ipados-as-platforms
+++ /dev/null
@@ -1,3 +0,0 @@
-- Added iOS/iPadOS builtin manual labels. IMPORTANT: Before migrating to this version, make sure to delete any labels with name "iOS" or "iPadOS".
-- Added aggregation of iOS/iPadOS OS versions.
-- Added change to custom profiles for iOS/iPadOS to go from 'pending' straight to 'verified' (skip 'verifying').
diff --git a/changes/20010-win-false-positives b/changes/20010-win-false-positives
deleted file mode 100644
index de0650b3d674..000000000000
--- a/changes/20010-win-false-positives
+++ /dev/null
@@ -1 +0,0 @@
-- fixed issue where some Windows applications were getting matched against Windows OS vulnerabilities
\ No newline at end of file
diff --git a/changes/20020-mdm-instructions b/changes/20020-mdm-instructions
deleted file mode 100644
index 1efad7712259..000000000000
--- a/changes/20020-mdm-instructions
+++ /dev/null
@@ -1,2 +0,0 @@
-- Updates the instructions for manual MDM enrollment on the "My device" page to be clearer and align
- with Apple updates.
\ No newline at end of file
diff --git a/changes/20042-remove-package-version b/changes/20042-remove-package-version
new file mode 100644
index 000000000000..a4a580141767
--- /dev/null
+++ b/changes/20042-remove-package-version
@@ -0,0 +1 @@
+In `fleetctl package` command, removed the `--version` flag. The version of the package can be controlled by `--orbit-channel` flag.
diff --git a/changes/20050-vuln-software b/changes/20050-vuln-software
deleted file mode 100644
index d6f9fcf54dee..000000000000
--- a/changes/20050-vuln-software
+++ /dev/null
@@ -1 +0,0 @@
-- Adds back the "Vulnerable" filter for the host details software table
\ No newline at end of file
diff --git a/changes/20056-sort-policy-bug b/changes/20056-sort-policy-bug
deleted file mode 100644
index 232756fbc3e5..000000000000
--- a/changes/20056-sort-policy-bug
+++ /dev/null
@@ -1 +0,0 @@
-* UI: Reinstate ability to sort policies by passing count
\ No newline at end of file
diff --git a/changes/20057-connected-tweaks b/changes/20057-connected-tweaks
deleted file mode 100644
index 85d435862445..000000000000
--- a/changes/20057-connected-tweaks
+++ /dev/null
@@ -1 +0,0 @@
-* Improved the accuracy of the heuristic used to deterimine if a host is connected to Fleet via MDM by using osquery data for hosts that didn't send a Checkout message.
diff --git a/changes/20075-fleetctl-apply-validation b/changes/20075-fleetctl-apply-validation
deleted file mode 100644
index 02831f8b5492..000000000000
--- a/changes/20075-fleetctl-apply-validation
+++ /dev/null
@@ -1 +0,0 @@
-- Add .yml and .yaml file type validation and error message to fleetctl apply
diff --git a/changes/20077-align-view-all-hosts-link-sw-page b/changes/20077-align-view-all-hosts-link-sw-page
deleted file mode 100644
index 5dfd1bd2e651..000000000000
--- a/changes/20077-align-view-all-hosts-link-sw-page
+++ /dev/null
@@ -1 +0,0 @@
-- Align the "View all hosts" links in the Software titles and versions tables.
diff --git a/changes/20080-lock-disable-credential-caching b/changes/20080-lock-disable-credential-caching
deleted file mode 100644
index 0a8fbbe46e43..000000000000
--- a/changes/20080-lock-disable-credential-caching
+++ /dev/null
@@ -1 +0,0 @@
-- Disable credential caching and reboot on Windows lock
diff --git a/changes/20100-os-version-compliance b/changes/20100-os-version-compliance
new file mode 100644
index 000000000000..f14334f97f47
--- /dev/null
+++ b/changes/20100-os-version-compliance
@@ -0,0 +1 @@
+- Fleet UI: Show OS version compliance on Host Details page
diff --git a/changes/20143-targets-input-spinner b/changes/20143-targets-input-spinner
deleted file mode 100644
index da5e679348e3..000000000000
--- a/changes/20143-targets-input-spinner
+++ /dev/null
@@ -1 +0,0 @@
-- Fix styling issues with the target inputs loading spinner on the run live query/policy page.
diff --git a/changes/20194-sort-label-names-in-ui b/changes/20194-sort-label-names-in-ui
new file mode 100644
index 000000000000..2f27f77f0b5c
--- /dev/null
+++ b/changes/20194-sort-label-names-in-ui
@@ -0,0 +1 @@
+- display the label names case-insensitive alphabetical order in the fleet UI
diff --git a/changes/20271-deleted-host-software-installs b/changes/20271-deleted-host-software-installs
new file mode 100644
index 000000000000..674b8a823f4b
--- /dev/null
+++ b/changes/20271-deleted-host-software-installs
@@ -0,0 +1 @@
+- Fig bug where software install results could not be retrieved for deleted hosts in the activity feed
diff --git a/changes/20278-vpp-batch-api b/changes/20278-vpp-batch-api
new file mode 100644
index 000000000000..e5cbbf7eca12
--- /dev/null
+++ b/changes/20278-vpp-batch-api
@@ -0,0 +1 @@
+- GitOps supports VPP app associations
diff --git a/changes/20310-update-my-device-copy b/changes/20310-update-my-device-copy
new file mode 100644
index 000000000000..9a91f6432ac4
--- /dev/null
+++ b/changes/20310-update-my-device-copy
@@ -0,0 +1 @@
+- update copy on for automica enrollment modal on my device page.
diff --git a/changes/20311-migrations b/changes/20311-migrations
new file mode 100644
index 000000000000..4cf8dffe0e5f
--- /dev/null
+++ b/changes/20311-migrations
@@ -0,0 +1,3 @@
+- Adds ability for MDM migrations if the host is manually enrolled to a 3rd party MDM.
+- Adds an offline screen to the macOS MDM migration flow.
+- Updates the instructions on "My device" for MDM migrations on pre-Sonoma macOS hosts.
\ No newline at end of file
diff --git a/changes/20370-linux-nologin b/changes/20370-linux-nologin
new file mode 100644
index 000000000000..236418c9636c
--- /dev/null
+++ b/changes/20370-linux-nologin
@@ -0,0 +1 @@
+- Linux lock/unlock scripts now make use of pam_nologin to keep AD users locked out
diff --git a/changes/20395-DE-table-style-fix b/changes/20395-DE-table-style-fix
new file mode 100644
index 000000000000..8907c36986bb
--- /dev/null
+++ b/changes/20395-DE-table-style-fix
@@ -0,0 +1 @@
+* Fix a styling issue in the Controls > OS Settings > disk encryption table
\ No newline at end of file
diff --git a/changes/20397-do-not-set-last_enrolled_at-when-enrolling-orbit b/changes/20397-do-not-set-last_enrolled_at-when-enrolling-orbit
new file mode 100644
index 000000000000..c8f305c4d1a6
--- /dev/null
+++ b/changes/20397-do-not-set-last_enrolled_at-when-enrolling-orbit
@@ -0,0 +1 @@
+* Fixed a bug that set `last_enrolled_at` during orbit re-enrollment, which caused osquery enroll failures when `FLEET_OSQUERY_ENROLL_COOLDOWN` is set .
diff --git a/changes/20409-add-matching-rules-july-and-august-365 b/changes/20409-add-matching-rules-july-and-august-365
new file mode 100644
index 000000000000..7acdea889db8
--- /dev/null
+++ b/changes/20409-add-matching-rules-july-and-august-365
@@ -0,0 +1 @@
+* Added matching rules for July and August Microsoft 365 security updates (https://learn.microsoft.com/en-us/officeupdates/microsoft365-apps-security-updates).
diff --git a/changes/20440-Notion-exe-installer-name b/changes/20440-Notion-exe-installer-name
new file mode 100644
index 000000000000..bc3996cc5dc5
--- /dev/null
+++ b/changes/20440-Notion-exe-installer-name
@@ -0,0 +1 @@
+* Added a special-case to properly name the Notion .exe Windows installer the same as how it will be reported by osquery post-install.
diff --git a/changes/20463-cpe-fixes b/changes/20463-cpe-fixes
new file mode 100644
index 000000000000..d3c9453f37de
--- /dev/null
+++ b/changes/20463-cpe-fixes
@@ -0,0 +1,2 @@
+- During vulnerability scanning, use 'macos' SW target for CPEs of homebrew packages
+- During vulnerability scanning, don't ignore software with non-ASCII en dash and em dash characters
diff --git a/changes/20467-vpp-ipadios-ui b/changes/20467-vpp-ipadios-ui
new file mode 100644
index 000000000000..2cc84e31cdf0
--- /dev/null
+++ b/changes/20467-vpp-ipadios-ui
@@ -0,0 +1 @@
+* Add UI features for managing Apple VPP apps for iPadOS and iOS hosts
\ No newline at end of file
diff --git a/changes/20469-backend-ios-ipados-os-updates b/changes/20469-backend-ios-ipados-os-updates
new file mode 100644
index 000000000000..075cca487621
--- /dev/null
+++ b/changes/20469-backend-ios-ipados-os-updates
@@ -0,0 +1 @@
+* Adding OS updates support to iOS/iPadOS devices.
diff --git a/changes/20515-delete-vpp-app b/changes/20515-delete-vpp-app
new file mode 100644
index 000000000000..49599edf94b2
--- /dev/null
+++ b/changes/20515-delete-vpp-app
@@ -0,0 +1,2 @@
+* Added support to delete a VPP app from a team in `DELETE /software/titles/:software_title_id/available_for_install`.
+* Fixed path that was incorrect for the download software installer package endpoint `GET /software/titles/:software_title_id/package`.
diff --git a/changes/20531-download-CSR-clickable-error b/changes/20531-download-CSR-clickable-error
new file mode 100644
index 000000000000..aeaf1ad18c62
--- /dev/null
+++ b/changes/20531-download-CSR-clickable-error
@@ -0,0 +1,3 @@
+* When a CSR can't be downloaded due to missing private key, make the link clickable in the error
+ message that is flashed.
+
\ No newline at end of file
diff --git a/changes/20535-sw-table-loading b/changes/20535-sw-table-loading
new file mode 100644
index 000000000000..d144ce782cce
--- /dev/null
+++ b/changes/20535-sw-table-loading
@@ -0,0 +1 @@
+* Improve loading state for DataTables when no data is present yet
\ No newline at end of file
diff --git a/changes/20571-update-fleetctl-docker-debian-slim b/changes/20571-update-fleetctl-docker-debian-slim
new file mode 100644
index 000000000000..9f04c02a055b
--- /dev/null
+++ b/changes/20571-update-fleetctl-docker-debian-slim
@@ -0,0 +1 @@
+* Update base images of `fleetdm/fleetctl`, `fleetdm/bomutils` and `fleetdm/wix` to fix CRITICAL vulnerabilities found by trivy.
diff --git a/changes/20575-fix-profile-activities-to-include-ios-ipados b/changes/20575-fix-profile-activities-to-include-ios-ipados
new file mode 100644
index 000000000000..bf089bf48948
--- /dev/null
+++ b/changes/20575-fix-profile-activities-to-include-ios-ipados
@@ -0,0 +1 @@
+- Update profile activities to include iOS and iPadOS
diff --git a/changes/20599-policy-webhook b/changes/20599-policy-webhook
new file mode 100644
index 000000000000..3255a6444eb5
--- /dev/null
+++ b/changes/20599-policy-webhook
@@ -0,0 +1 @@
+- In policy webhook, made sure the failing_host_count is never 0. This count is normally updated once an hour during cleanups_then_aggregation cron job.
diff --git a/changes/20604-hosts-page-pagination b/changes/20604-hosts-page-pagination
new file mode 100644
index 000000000000..c1f68d5f94a4
--- /dev/null
+++ b/changes/20604-hosts-page-pagination
@@ -0,0 +1 @@
+* Fix a bug where hosts page would sometimes allow excess pagination
\ No newline at end of file
diff --git a/changes/20618-nil-tz-not-handled b/changes/20618-nil-tz-not-handled
new file mode 100644
index 000000000000..cbb5d0bd9961
--- /dev/null
+++ b/changes/20618-nil-tz-not-handled
@@ -0,0 +1,2 @@
+* Fix a bug where Fleet google calendar events generated by Fleet <= 4.53.0 were not correctly
+ processed by 4.54.0
\ No newline at end of file
diff --git a/changes/20730-hide-available-for-install-wrong-team b/changes/20730-hide-available-for-install-wrong-team
new file mode 100644
index 000000000000..270fa8e61468
--- /dev/null
+++ b/changes/20730-hide-available-for-install-wrong-team
@@ -0,0 +1 @@
+* Fix a bug where a software installer (a package or a VPP app) that has been installed on a host still shows up as "Available for install" and can still be requested to be installed after the host is transferred to a different team without that installer (or after the installer is deleted).
diff --git a/changes/20747-gitops-software-query b/changes/20747-gitops-software-query
new file mode 100644
index 000000000000..100efc17f3f1
--- /dev/null
+++ b/changes/20747-gitops-software-query
@@ -0,0 +1 @@
+- Use new gitops format for software pre install query
diff --git a/changes/20751-detect-held-linux-packages-as-installed b/changes/20751-detect-held-linux-packages-as-installed
new file mode 100644
index 000000000000..6aa524ce80cd
--- /dev/null
+++ b/changes/20751-detect-held-linux-packages-as-installed
@@ -0,0 +1 @@
+Linux .deb packages 'on hold' are now included in the installed software list.
diff --git a/changes/20757-profiles-batch-activity b/changes/20757-profiles-batch-activity
new file mode 100644
index 000000000000..6b110b87c768
--- /dev/null
+++ b/changes/20757-profiles-batch-activity
@@ -0,0 +1 @@
+API endpoint `/api/v1/fleet/mdm/profiles/batch` will now not log an activity for profile types that did not change in the database (Apple configuration profiles, Windows configuration profiles, or Apple declarations).
diff --git a/changes/20781-cached-statements b/changes/20781-cached-statements
new file mode 100644
index 000000000000..58ae7c8c0d76
--- /dev/null
+++ b/changes/20781-cached-statements
@@ -0,0 +1 @@
+* Fixed a bug when a cached prepared statement gets deleted in the MySQL server itself without Fleet knowing.
diff --git a/changes/20828-better-appid-error b/changes/20828-better-appid-error
new file mode 100644
index 000000000000..540c8fcbfa28
--- /dev/null
+++ b/changes/20828-better-appid-error
@@ -0,0 +1 @@
+- Improve clarity of gitops VPP app ID type errors
diff --git a/changes/20846-vuln-virtual-box b/changes/20846-vuln-virtual-box
new file mode 100644
index 000000000000..225dd0be2274
--- /dev/null
+++ b/changes/20846-vuln-virtual-box
@@ -0,0 +1 @@
+- resolved an issue where virtual box for macOS wasn't matching against the vm_virtualbox NVD product name
\ No newline at end of file
diff --git a/changes/20868-turn-off-mdm b/changes/20868-turn-off-mdm
new file mode 100644
index 000000000000..bfcd35d3150b
--- /dev/null
+++ b/changes/20868-turn-off-mdm
@@ -0,0 +1 @@
+- Improves the UX of turning off MDM on an offline host (endpoint doesn't error anymore)
\ No newline at end of file
diff --git a/changes/20882-ui-update-turn-on-mdm-banner b/changes/20882-ui-update-turn-on-mdm-banner
new file mode 100644
index 000000000000..eca36625ce6e
--- /dev/null
+++ b/changes/20882-ui-update-turn-on-mdm-banner
@@ -0,0 +1 @@
+- Updated text for "Turn on MDM" banners in UI.
\ No newline at end of file
diff --git a/changes/20895-policy-software-install-gitops b/changes/20895-policy-software-install-gitops
new file mode 100644
index 000000000000..774f6a4bfe35
--- /dev/null
+++ b/changes/20895-policy-software-install-gitops
@@ -0,0 +1 @@
+* Added support for configuring policy installers via GitOps.
diff --git a/changes/20897-add-software-premium-message b/changes/20897-add-software-premium-message
new file mode 100644
index 000000000000..82ab23030deb
--- /dev/null
+++ b/changes/20897-add-software-premium-message
@@ -0,0 +1 @@
+- Fleet Free users see a Premium feature message when clicking to add Add software
diff --git a/changes/20900-abm-invalid-ui b/changes/20900-abm-invalid-ui
deleted file mode 100644
index ab64aa6f19b0..000000000000
--- a/changes/20900-abm-invalid-ui
+++ /dev/null
@@ -1,2 +0,0 @@
-- Fixed issue where the Fleet UI could not be used to renew the ABM token after the ABM user who created
- the token was deleted.
diff --git a/changes/20933-disable-overlay-other-workflows-modal b/changes/20933-disable-overlay-other-workflows-modal
new file mode 100644
index 000000000000..e0386552c5cc
--- /dev/null
+++ b/changes/20933-disable-overlay-other-workflows-modal
@@ -0,0 +1 @@
+- add a disabled overlay to the Other Workflows modal on the policy page.
diff --git a/changes/20934-amazon-linux b/changes/20934-amazon-linux
new file mode 100644
index 000000000000..4b174b29ed34
--- /dev/null
+++ b/changes/20934-amazon-linux
@@ -0,0 +1 @@
+Use ALAS bulletins as vulnerability source for Amazon Linux (instead of OVAL for Amazon Linux 2, and adds support for Amazon Linux 1, 2022, and 2023)
diff --git a/changes/20948-fix-flash-bleeding-off-viewport b/changes/20948-fix-flash-bleeding-off-viewport
new file mode 100644
index 000000000000..12a6241da25b
--- /dev/null
+++ b/changes/20948-fix-flash-bleeding-off-viewport
@@ -0,0 +1 @@
+- UI Fix: Flash message no longer bleeds off viewport
diff --git a/changes/20955-host-activity-feed-copy-updates b/changes/20955-host-activity-feed-copy-updates
new file mode 100644
index 000000000000..acfb44dfb01b
--- /dev/null
+++ b/changes/20955-host-activity-feed-copy-updates
@@ -0,0 +1,2 @@
+* Update Host details activities tooltip and empty state copy to reflect recently added
+capabilities.
diff --git a/changes/21006-fleetctl-preview b/changes/21006-fleetctl-preview
new file mode 100644
index 000000000000..9fe2fd3286d8
--- /dev/null
+++ b/changes/21006-fleetctl-preview
@@ -0,0 +1 @@
+* Fixed a bug in `fleetctl preview` that was causing it to fail if Docker was installed without support for the deprecated `docker-compose` CLI
diff --git a/changes/21038-pass-fleet-args-via-stdin b/changes/21038-pass-fleet-args-via-stdin
new file mode 100644
index 000000000000..3112a06649c2
--- /dev/null
+++ b/changes/21038-pass-fleet-args-via-stdin
@@ -0,0 +1 @@
+Fleet server now accepts arguments via stdin. This is useful for passing secrets that you don't want to expose as env vars, in the command line, or in the config file.
diff --git a/changes/21065-update-host-tooltip-copy b/changes/21065-update-host-tooltip-copy
new file mode 100644
index 000000000000..18470a4db730
--- /dev/null
+++ b/changes/21065-update-host-tooltip-copy
@@ -0,0 +1,2 @@
+- update ABM (Apple business manageer) host tooltip copy on the manage host page to clarify when
+ host vitals will be available to view.
diff --git a/changes/21073-deleting-ios-host b/changes/21073-deleting-ios-host
new file mode 100644
index 000000000000..220687fdc9c2
--- /dev/null
+++ b/changes/21073-deleting-ios-host
@@ -0,0 +1,2 @@
+- Deleted iOS/iPadOS host will continue to report to Fleet as long as host is in Apple Business Manager (ABM).
+- Refetching an offline iOS/iPadOS host will not add new MDM commands to the queue if previous refetch has not completed yet.
diff --git a/changes/21082-fix-available-for-install-filter-for-host-software b/changes/21082-fix-available-for-install-filter-for-host-software
new file mode 100644
index 000000000000..9c1b850570d6
--- /dev/null
+++ b/changes/21082-fix-available-for-install-filter-for-host-software
@@ -0,0 +1 @@
+* Fixed the "Available for install" filter in the host's software page so that installers that were requested to be installed on the host (regardless of installation status) also show up in the list.
diff --git a/changes/21104-gitops-team-conflict b/changes/21104-gitops-team-conflict
new file mode 100644
index 000000000000..6749b1b15a2b
--- /dev/null
+++ b/changes/21104-gitops-team-conflict
@@ -0,0 +1 @@
+Improved fleetctl gitops error message when trying to change team name to a team that already exists.
diff --git a/changes/21163-config-profile-label b/changes/21163-config-profile-label
new file mode 100644
index 000000000000..fe23787fbaf5
--- /dev/null
+++ b/changes/21163-config-profile-label
@@ -0,0 +1 @@
+- Fixed bug where configuration profile was still showing the old label name after the name was updated.
diff --git a/changes/21177-abm-crud b/changes/21177-abm-crud
new file mode 100644
index 000000000000..3da1b68e12a2
--- /dev/null
+++ b/changes/21177-abm-crud
@@ -0,0 +1 @@
+- Adds new endpoints and updates existing endpoints for managing multiple Apple Business Manager tokens.
\ No newline at end of file
diff --git a/changes/21178-mabm-vpp-crud b/changes/21178-mabm-vpp-crud
new file mode 100644
index 000000000000..44cd6aee80bf
--- /dev/null
+++ b/changes/21178-mabm-vpp-crud
@@ -0,0 +1 @@
+- Add backend support for multiple VPP tokens
diff --git a/changes/21184-consolidate-all-mdm-settings-under-mdm-section b/changes/21184-consolidate-all-mdm-settings-under-mdm-section
new file mode 100644
index 000000000000..5f0739e727fc
--- /dev/null
+++ b/changes/21184-consolidate-all-mdm-settings-under-mdm-section
@@ -0,0 +1 @@
+- consolidates Automatic Enrollment and VPP settings under the MDM settings integration page.
diff --git a/changes/21185-mabm-guide-updates b/changes/21185-mabm-guide-updates
new file mode 100644
index 000000000000..da209e16ddde
--- /dev/null
+++ b/changes/21185-mabm-guide-updates
@@ -0,0 +1 @@
+- Updated relevant documentation to include references to multiple ABM and VPP tokens.
\ No newline at end of file
diff --git a/changes/21186-new-abm-ui-page b/changes/21186-new-abm-ui-page
new file mode 100644
index 000000000000..c3353457ba8b
--- /dev/null
+++ b/changes/21186-new-abm-ui-page
@@ -0,0 +1 @@
+- add new Apple business manager page to fleet UI
diff --git a/changes/21187-new-vpp-page b/changes/21187-new-vpp-page
new file mode 100644
index 000000000000..2c011411cf0c
--- /dev/null
+++ b/changes/21187-new-vpp-page
@@ -0,0 +1 @@
+- add new vpp page to the fleet UI
diff --git a/changes/21198-update-go b/changes/21198-update-go
new file mode 100644
index 000000000000..bb2eac1add70
--- /dev/null
+++ b/changes/21198-update-go
@@ -0,0 +1 @@
+* Updated go to go1.22.6
diff --git a/changes/21242-nvd-input-validation b/changes/21242-nvd-input-validation
new file mode 100644
index 000000000000..cab3a2e77dec
--- /dev/null
+++ b/changes/21242-nvd-input-validation
@@ -0,0 +1 @@
+Continue with an empty CVE description when the NVD CVE feed doesn't include description entries (instead of panicking)
diff --git a/changes/21251-bugfix-download-software-installer b/changes/21251-bugfix-download-software-installer
new file mode 100644
index 000000000000..fe56429b39d1
--- /dev/null
+++ b/changes/21251-bugfix-download-software-installer
@@ -0,0 +1 @@
+* Fixed a bug where the wrong API path was used to download a software installer.
diff --git a/changes/21264-fix-reserved-team-names b/changes/21264-fix-reserved-team-names
new file mode 100644
index 000000000000..6363b8186977
--- /dev/null
+++ b/changes/21264-fix-reserved-team-names
@@ -0,0 +1,2 @@
+- Prevents teams with the name "All teams" or "No team" from being created (these are reserved team
+ names in Fleet).
\ No newline at end of file
diff --git a/changes/21268-calendar-free b/changes/21268-calendar-free
new file mode 100644
index 000000000000..f51a103ecf8c
--- /dev/null
+++ b/changes/21268-calendar-free
@@ -0,0 +1 @@
+Scheduled maintenance events will now be scheduled over calendar events marked "Free" (not busy) in Google Calendar.
diff --git a/changes/21270-mysql-8.4.2 b/changes/21270-mysql-8.4.2
new file mode 100644
index 000000000000..8d6844f0a23e
--- /dev/null
+++ b/changes/21270-mysql-8.4.2
@@ -0,0 +1 @@
+Added support for MySQL 8.4.2 LTS
diff --git a/changes/21273-handle-abm-terms-expired-flags b/changes/21273-handle-abm-terms-expired-flags
new file mode 100644
index 000000000000..04c8415877e5
--- /dev/null
+++ b/changes/21273-handle-abm-terms-expired-flags
@@ -0,0 +1 @@
+* Added support to track the Apple Business Manager "terms expired" API error per token, as well as a global flag that gets set as soon as one token has its terms expired.
diff --git a/changes/21296-query-results-index b/changes/21296-query-results-index
new file mode 100644
index 000000000000..a317b4747c16
--- /dev/null
+++ b/changes/21296-query-results-index
@@ -0,0 +1 @@
+Added index to query_results DB table to speed up finding last query timestamp for a given query and host.
diff --git a/changes/21315-vpp-premium-license b/changes/21315-vpp-premium-license
new file mode 100644
index 000000000000..2fd081703e47
--- /dev/null
+++ b/changes/21315-vpp-premium-license
@@ -0,0 +1 @@
+- Verify user has premium license before uploading VPP tokens
diff --git a/changes/21404-minio-false-positive b/changes/21404-minio-false-positive
new file mode 100644
index 000000000000..57b4245e45d2
--- /dev/null
+++ b/changes/21404-minio-false-positive
@@ -0,0 +1 @@
+- resolved issue where minio was reporting false positive vulnerabilities due to a mismatch in version strings
\ No newline at end of file
diff --git a/changes/21412-remove-node-key-from-server-logs b/changes/21412-remove-node-key-from-server-logs
new file mode 100644
index 000000000000..c6555bd5bc99
--- /dev/null
+++ b/changes/21412-remove-node-key-from-server-logs
@@ -0,0 +1 @@
+* Removed invalid node keys from server logs.
diff --git a/changes/21423-fix-self-service-vppapps-filter b/changes/21423-fix-self-service-vppapps-filter
new file mode 100644
index 000000000000..d839a5fe23bb
--- /dev/null
+++ b/changes/21423-fix-self-service-vppapps-filter
@@ -0,0 +1,2 @@
+* Fixed a bug where the "Self-service" filter for the list of software and the list of host's software did not take App Store apps into account.
+* Fixed a bug where the "My device" page in Fleet Desktop did not show the self-service software tab when App Store apps were available as self-install.
diff --git a/changes/21427-deleting-software b/changes/21427-deleting-software
new file mode 100644
index 000000000000..aac5bffd6941
--- /dev/null
+++ b/changes/21427-deleting-software
@@ -0,0 +1,2 @@
+- Deleting installed software or VPP app now makes it available for re-installation.
+- Matching software that is not installed by Fleet now shows up as 'Available for install' on host details page.
diff --git a/changes/21428-policy-automatic-install-software b/changes/21428-policy-automatic-install-software
new file mode 100644
index 000000000000..e61dc2a9eadc
--- /dev/null
+++ b/changes/21428-policy-automatic-install-software
@@ -0,0 +1 @@
+* Added automatic installation of software packages using policy automations.
diff --git a/changes/21428-prevent-install-when-already-pending b/changes/21428-prevent-install-when-already-pending
new file mode 100644
index 000000000000..d01006d6f91d
--- /dev/null
+++ b/changes/21428-prevent-install-when-already-pending
@@ -0,0 +1 @@
+* Added validation to `POST /api/_version_/fleet/hosts/{host_id}/software/install/{software_title_id}` to prevent installing on a host that already has a pending installation for that software title.
diff --git a/changes/21439-multiple-teams-vpp-token b/changes/21439-multiple-teams-vpp-token
new file mode 100644
index 000000000000..5ec49a656001
--- /dev/null
+++ b/changes/21439-multiple-teams-vpp-token
@@ -0,0 +1 @@
+- Allow multiple teams to be assigned to the same VPP Token
diff --git a/changes/21557-ota-profile-endpoint b/changes/21557-ota-profile-endpoint
new file mode 100644
index 000000000000..4acf2bbcf5e9
--- /dev/null
+++ b/changes/21557-ota-profile-endpoint
@@ -0,0 +1 @@
+- Adds an endpoint for getting an OTA MDM profile for enrolling iOS and iPadOS hosts.
\ No newline at end of file
diff --git a/changes/21559-add-end-user-enrolment-page b/changes/21559-add-end-user-enrolment-page
new file mode 100644
index 000000000000..427f1c5beb06
--- /dev/null
+++ b/changes/21559-add-end-user-enrolment-page
@@ -0,0 +1 @@
+- add feature for end users to enroll their device into fleet mdm
diff --git a/changes/21683-apns-cert-validation-on-start b/changes/21683-apns-cert-validation-on-start
new file mode 100644
index 000000000000..9f1714359931
--- /dev/null
+++ b/changes/21683-apns-cert-validation-on-start
@@ -0,0 +1,2 @@
+- Removed validation of APNS certificate from server startup. This was no longer necessary because
+ we now allow for APNS certificates to be renewed in the UI.
diff --git a/changes/21757-fix-scheduling-cron-jobs-at-startup b/changes/21757-fix-scheduling-cron-jobs-at-startup
new file mode 100644
index 000000000000..b54ae2c84f53
--- /dev/null
+++ b/changes/21757-fix-scheduling-cron-jobs-at-startup
@@ -0,0 +1 @@
+* Fixed an issue with the scheduling of cron jobs at startup if the job has never run, which caused it to be delayed.
diff --git a/changes/21804-vpp-clear-apps-on-move b/changes/21804-vpp-clear-apps-on-move
new file mode 100644
index 000000000000..3823274aaa12
--- /dev/null
+++ b/changes/21804-vpp-clear-apps-on-move
@@ -0,0 +1 @@
+- Clear apps associated with a VPP token if it's moved off of a team
diff --git a/changes/6085-fleetctl-debug-connection b/changes/6085-fleetctl-debug-connection
deleted file mode 100644
index 240987d39a88..000000000000
--- a/changes/6085-fleetctl-debug-connection
+++ /dev/null
@@ -1,2 +0,0 @@
-* Fixed `fleetctl debug connection` to support server TLS certificates with intermediates.
-* Added support to `fleetctl debug connection` to test TLS connection with the embedded certs.pem in the fleetctl executable (default root CA used to generate fleetd packages). This can help find issues during package generation instead of during package installation.
diff --git a/changes/6979-observer-software b/changes/6979-observer-software
deleted file mode 100644
index 005d4a8eaf13..000000000000
--- a/changes/6979-observer-software
+++ /dev/null
@@ -1 +0,0 @@
-- Bug fix: do not allow Observer and Observer+ roles to download software installers.
\ No newline at end of file
diff --git a/changes/7014-custom-vulnerability-matching b/changes/7014-custom-vulnerability-matching
deleted file mode 100644
index 969e7357a304..000000000000
--- a/changes/7014-custom-vulnerability-matching
+++ /dev/null
@@ -1 +0,0 @@
-- addresses Microsoft Office June 2024 false negative vulnerabilities and adds custom vulnerability matching
\ No newline at end of file
diff --git a/changes/7476-fix-ui-overflow-os-settings-table b/changes/7476-fix-ui-overflow-os-settings-table
new file mode 100644
index 000000000000..6c95925de8f5
--- /dev/null
+++ b/changes/7476-fix-ui-overflow-os-settings-table
@@ -0,0 +1 @@
+- fixes UI overflow issues with OS settings table data
diff --git a/changes/api-get-host-by-identifier-exclude-software b/changes/api-get-host-by-identifier-exclude-software
new file mode 100644
index 000000000000..aa2aa5404af8
--- /dev/null
+++ b/changes/api-get-host-by-identifier-exclude-software
@@ -0,0 +1 @@
+- add exclude_software query paramter to "Get host by identifier" API
\ No newline at end of file
diff --git a/changes/apns-errors b/changes/apns-errors
new file mode 100644
index 000000000000..6de48617a1c1
--- /dev/null
+++ b/changes/apns-errors
@@ -0,0 +1 @@
+* Fixed logic to properly catch and log APNs errors.
diff --git a/changes/fix-software-array-migration b/changes/fix-software-array-migration
new file mode 100644
index 000000000000..27536ccc1ecd
--- /dev/null
+++ b/changes/fix-software-array-migration
@@ -0,0 +1,2 @@
+- Adds a migration to migrate older team configurations to the new version that includes both
+ installers and App Store apps.
\ No newline at end of file
diff --git a/changes/issue-18848-include-all-exclude-any-custom-profile b/changes/issue-18848-include-all-exclude-any-custom-profile
deleted file mode 100644
index ef065303f371..000000000000
--- a/changes/issue-18848-include-all-exclude-any-custom-profile
+++ /dev/null
@@ -1,2 +0,0 @@
-- add UI for uploading custom profiles with a target of hosts that include all/exclude
- any selected labels
diff --git a/changes/issue-19691-add-vpp-token-expiry-banner b/changes/issue-19691-add-vpp-token-expiry-banner
new file mode 100644
index 000000000000..d4f14c98c63b
--- /dev/null
+++ b/changes/issue-19691-add-vpp-token-expiry-banner
@@ -0,0 +1 @@
+- add a warning banner to the UI if the uploaded VPP token is about to expire/has expired.
diff --git a/changes/issue-19731-dont-show-db-error-message b/changes/issue-19731-dont-show-db-error-message
deleted file mode 100644
index d04018535124..000000000000
--- a/changes/issue-19731-dont-show-db-error-message
+++ /dev/null
@@ -1 +0,0 @@
-- remove DB error message from the UI when showing a error response.
diff --git a/changes/issue-19866-add-remove-disable-vpp-in-ui b/changes/issue-19866-add-remove-disable-vpp-in-ui
new file mode 100644
index 000000000000..09000dbff2e0
--- /dev/null
+++ b/changes/issue-19866-add-remove-disable-vpp-in-ui
@@ -0,0 +1 @@
+- add ability to add/remove/disable vpp in the fleet UI.
diff --git a/changes/issue-19869-vpp-ui-on-software-pages b/changes/issue-19869-vpp-ui-on-software-pages
new file mode 100644
index 000000000000..74f71d41c923
--- /dev/null
+++ b/changes/issue-19869-vpp-ui-on-software-pages
@@ -0,0 +1 @@
+- add UI to support the apple vpp feature on the software pages.
diff --git a/changes/issue-19951-render-banners-on-my-device-page-in-priority-order b/changes/issue-19951-render-banners-on-my-device-page-in-priority-order
deleted file mode 100644
index 965c2902abc3..000000000000
--- a/changes/issue-19951-render-banners-on-my-device-page-in-priority-order
+++ /dev/null
@@ -1 +0,0 @@
-- render only one banner on the my device page based on priority order.
diff --git a/changes/issue-20612-ui-updates-host-software-device-user-pages-for-vpp b/changes/issue-20612-ui-updates-host-software-device-user-pages-for-vpp
new file mode 100644
index 000000000000..01e6073b2d91
--- /dev/null
+++ b/changes/issue-20612-ui-updates-host-software-device-user-pages-for-vpp
@@ -0,0 +1 @@
+- add UI updates for VPP feature on host software and my device pages.
diff --git a/changes/issue-20784-fix-app-wide-banner-showing b/changes/issue-20784-fix-app-wide-banner-showing
new file mode 100644
index 000000000000..9720e4b20bec
--- /dev/null
+++ b/changes/issue-20784-fix-app-wide-banner-showing
@@ -0,0 +1 @@
+- fix an issue where the app-wide warning banners were not showing on the initial page load
diff --git a/changes/update-go1.23.1 b/changes/update-go1.23.1
new file mode 100644
index 000000000000..22a59cdc400b
--- /dev/null
+++ b/changes/update-go1.23.1
@@ -0,0 +1 @@
+* Updated Go to go1.23.1
diff --git a/changes/update-installutils-to-allow-for-special-characters-in-registry b/changes/update-installutils-to-allow-for-special-characters-in-registry
deleted file mode 100644
index f3bab1ae9a4e..000000000000
--- a/changes/update-installutils-to-allow-for-special-characters-in-registry
+++ /dev/null
@@ -1 +0,0 @@
-- Fixes an issue where special characters in HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall breaks the "installer_utils.ps1 -uninstallOrbit" step in the Windows MSI installer
\ No newline at end of file
diff --git a/charts/fleet/Chart.yaml b/charts/fleet/Chart.yaml
index 88af956ae514..6921cfc2f0e4 100644
--- a/charts/fleet/Chart.yaml
+++ b/charts/fleet/Chart.yaml
@@ -8,7 +8,7 @@ version: v6.2.0
home: https://github.com/fleetdm/fleet
sources:
- https://github.com/fleetdm/fleet.git
-appVersion: v4.53.1
+appVersion: v4.54.1
dependencies:
- name: mysql
condition: mysql.enabled
diff --git a/charts/fleet/templates/_helpers.tpl b/charts/fleet/templates/_helpers.tpl
index f6c41ef3fb7f..fd0cfffbf93f 100644
--- a/charts/fleet/templates/_helpers.tpl
+++ b/charts/fleet/templates/_helpers.tpl
@@ -23,6 +23,11 @@ If release name contains chart name it will be used as a full name.
{{- end }}
{{- end }}
+{{- define "fleet.servicename" -}}
+{{- $fullName := include "fleet.fullname" . -}}
+{{- printf "%s-service" $fullName }}
+{{- end }}
+
{{/*
Create chart name and version as used by the chart label.
*/}}
diff --git a/charts/fleet/templates/ingress.yaml b/charts/fleet/templates/ingress.yaml
index 0a7326d99512..1a3e758a290a 100644
--- a/charts/fleet/templates/ingress.yaml
+++ b/charts/fleet/templates/ingress.yaml
@@ -1,5 +1,6 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "fleet.fullname" . -}}
+{{- $serviceName := include "fleet.servicename" . -}}
{{- $svcPort := ternary .Values.fleet.listenPort .Values.fleet.servicePort (eq .Values.fleet.servicePort nil) -}}
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
@@ -49,11 +50,11 @@ spec:
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
- name: {{ $fullName }}
+ name: {{ $serviceName }}
port:
number: {{ $svcPort }}
{{- else }}
- serviceName: {{ $fullName }}
+ serviceName: {{ $serviceName }}
servicePort: {{ $svcPort }}
{{- end }}
{{- end }}
diff --git a/charts/fleet/templates/job-migration.yaml b/charts/fleet/templates/job-migration.yaml
index 8226e3d29ab2..135dac9a6328 100644
--- a/charts/fleet/templates/job-migration.yaml
+++ b/charts/fleet/templates/job-migration.yaml
@@ -18,6 +18,7 @@ metadata:
"helm.sh/hook-delete-policy": hook-succeeded
{{- end }}
spec:
+ ttlSecondsAfterFinished: 100
template:
metadata:
{{- with .Values.podAnnotations }}
diff --git a/charts/fleet/templates/service.yaml b/charts/fleet/templates/service.yaml
index 1a22e48fc0d0..fa1c13a94d9a 100644
--- a/charts/fleet/templates/service.yaml
+++ b/charts/fleet/templates/service.yaml
@@ -1,3 +1,4 @@
+{{- $serviceName := include "fleet.servicename" . -}}
apiVersion: v1
kind: Service
metadata:
@@ -6,7 +7,7 @@ metadata:
chart: fleet
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
- name: fleet
+ name: {{ $serviceName }}
namespace: {{ .Release.Namespace }}
spec:
{{- if .Values.gke.ingress.useGKEIngress }}
diff --git a/charts/fleet/values.yaml b/charts/fleet/values.yaml
index 4b7aa1dbb136..7c6376f6cc69 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.53.1 # Version of Fleet to deploy
+imageTag: v4.54.1 # 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:
@@ -212,7 +212,7 @@ environments:
# The following environment variable is required if you are using
# Fleet's macOS MDM features. In a production environment, it is recommended that
# you store this private key in a secret and use envsFrom to reference the secret below.
- # To more information: https://fleetdm.com/docs/using-fleet/fleet-server-configuration#server-private-key
+ # For more information, check out the docs: https://fleetdm.com/docs/configuration/fleet-server-configuration#server-private-key
FLEET_SERVER_PRIVATE_KEY: ""
## Section: Environment Variables from Secrets/CMs
diff --git a/cmd/fleet/cron.go b/cmd/fleet/cron.go
index 9b731db37941..96e7f7998f6f 100644
--- a/cmd/fleet/cron.go
+++ b/cmd/fleet/cron.go
@@ -28,6 +28,7 @@ import (
"github.com/fleetdm/fleet/v4/server/service/externalsvc"
"github.com/fleetdm/fleet/v4/server/service/schedule"
"github.com/fleetdm/fleet/v4/server/vulnerabilities/customcve"
+ "github.com/fleetdm/fleet/v4/server/vulnerabilities/goval_dictionary"
"github.com/fleetdm/fleet/v4/server/vulnerabilities/macoffice"
"github.com/fleetdm/fleet/v4/server/vulnerabilities/msrc"
"github.com/fleetdm/fleet/v4/server/vulnerabilities/nvd"
@@ -37,7 +38,6 @@ import (
"github.com/fleetdm/fleet/v4/server/worker"
kitlog "github.com/go-kit/log"
"github.com/go-kit/log/level"
- "github.com/google/uuid"
"github.com/hashicorp/go-multierror"
)
@@ -159,6 +159,7 @@ func scanVulnerabilities(
nvdVulns := checkNVDVulnerabilities(ctx, ds, logger, vulnPath, config, vulnAutomationEnabled != "")
ovalVulns := checkOvalVulnerabilities(ctx, ds, logger, vulnPath, config, vulnAutomationEnabled != "")
+ govalDictVulns := checkGovalDictionaryVulnerabilities(ctx, ds, logger, vulnPath, config, vulnAutomationEnabled != "")
macOfficeVulns := checkMacOfficeVulnerabilities(ctx, ds, logger, vulnPath, config, vulnAutomationEnabled != "")
customVulns := checkCustomVulnerabilities(ctx, ds, logger, config, vulnAutomationEnabled != "")
@@ -173,6 +174,7 @@ func scanVulnerabilities(
vulns = append(vulns, nvdVulns...)
vulns = append(vulns, ovalVulns...)
vulns = append(vulns, macOfficeVulns...)
+ vulns = append(vulns, govalDictVulns...)
vulns = append(vulns, customVulns...)
meta, err := ds.ListCVEs(ctx, config.RecentVulnerabilityMaxAge)
@@ -345,6 +347,11 @@ func checkOvalVulnerabilities(
for _, version := range versions.OSVersions {
start := time.Now()
r, err := oval.Analyze(ctx, ds, version, vulnPath, collectVulns)
+ if err != nil && errors.Is(err, oval.ErrUnsupportedPlatform) {
+ level.Debug(logger).Log("msg", "oval-analysis-unsupported", "platform", version.Name)
+ continue
+ }
+
elapsed := time.Since(start)
level.Debug(logger).Log(
"msg", "oval-analysis-done",
@@ -360,6 +367,57 @@ func checkOvalVulnerabilities(
return results
}
+func checkGovalDictionaryVulnerabilities(
+ ctx context.Context,
+ ds fleet.Datastore,
+ logger kitlog.Logger,
+ vulnPath string,
+ config *config.VulnerabilitiesConfig,
+ collectVulns bool,
+) []fleet.SoftwareVulnerability {
+ var results []fleet.SoftwareVulnerability
+
+ // Get Platforms
+ versions, err := ds.OSVersions(ctx, nil, nil, nil, nil)
+ if err != nil {
+ errHandler(ctx, logger, "listing platforms for goval_dictionary pulls", err)
+ return nil
+ }
+
+ if !config.DisableDataSync {
+ // Sync on disk goval_dictionary sqlite with current OS Versions.
+ downloaded, err := goval_dictionary.Refresh(versions, vulnPath, logger)
+ if err != nil {
+ errHandler(ctx, logger, "updating goval_dictionary databases", err)
+ }
+ for _, d := range downloaded {
+ level.Debug(logger).Log("goval_dictionary-sync-downloaded", d)
+ }
+ }
+
+ // Analyze all supported os versions using the synced goval_dictionary definitions.
+ for _, version := range versions.OSVersions {
+ start := time.Now()
+ r, err := goval_dictionary.Analyze(ctx, ds, version, vulnPath, collectVulns, logger)
+ if err != nil && errors.Is(err, goval_dictionary.ErrUnsupportedPlatform) {
+ level.Debug(logger).Log("msg", "goval_dictionary-analysis-unsupported", "platform", version.Name)
+ continue
+ }
+ elapsed := time.Since(start)
+ level.Debug(logger).Log(
+ "msg", "goval_dictionary-analysis-done",
+ "platform", version.Name,
+ "elapsed", elapsed,
+ "found new", len(r))
+ results = append(results, r...)
+ if err != nil {
+ errHandler(ctx, logger, "analyzing goval_dictionary definitions", err)
+ }
+ }
+
+ return results
+}
+
func checkNVDVulnerabilities(
ctx context.Context,
ds fleet.Datastore,
@@ -625,7 +683,11 @@ func newWorkerIntegrationsSchedule(
Log: logger,
Commander: commander,
}
- w.Register(jira, zendesk, macosSetupAsst, appleMDM)
+ dbMigrate := &worker.DBMigration{
+ Datastore: ds,
+ Log: logger,
+ }
+ w.Register(jira, zendesk, macosSetupAsst, appleMDM, dbMigrate)
// Read app config a first time before starting, to clear up any failer client
// configuration if we're not on a fleet-owned server. Technically, the ServerURL
@@ -735,6 +797,7 @@ func newCleanupsAndAggregationSchedule(
config *config.FleetConfig,
commander *apple_mdm.MDMAppleCommander,
softwareInstallStore fleet.SoftwareInstallerStore,
+ bootstrapPackageStore fleet.MDMBootstrapPackageStore,
) (*schedule.Schedule, error) {
const (
name = string(fleet.CronCleanupsThenAggregation)
@@ -882,7 +945,19 @@ func newCleanupsAndAggregationSchedule(
return ds.CleanupActivitiesAndAssociatedData(ctx, maxCount, appConfig.ActivityExpirySettings.ActivityExpiryWindow)
}),
schedule.WithJob("cleanup_unused_software_installers", func(ctx context.Context) error {
- return ds.CleanupUnusedSoftwareInstallers(ctx, softwareInstallStore)
+ // remove only those unused created more than a minute ago to avoid a
+ // race where we delete those created after the mysql query to get those
+ // in use.
+ return ds.CleanupUnusedSoftwareInstallers(ctx, softwareInstallStore, time.Now().Add(-time.Minute))
+ }),
+ schedule.WithJob("cleanup_unused_bootstrap_packages", func(ctx context.Context) error {
+ // remove only those unused created more than a minute ago to avoid a
+ // race where we delete those created after the mysql query to get those
+ // in use.
+ return ds.CleanupUnusedBootstrapPackages(ctx, bootstrapPackageStore, time.Now().Add(-time.Minute))
+ }),
+ schedule.WithJob("cleanup_host_mdm_commands", func(ctx context.Context) error {
+ return ds.CleanupHostMDMCommands(ctx)
}),
)
@@ -1050,29 +1125,57 @@ func newAppleMDMDEPProfileAssigner(
) (*schedule.Schedule, error) {
const name = string(fleet.CronAppleMDMDEPProfileAssigner)
logger = kitlog.With(logger, "cron", name, "component", "nanodep-syncer")
- var fleetSyncer *apple_mdm.DEPService
s := schedule.New(
ctx, name, instanceID, periodicity, ds, ds,
schedule.WithLogger(logger),
- schedule.WithJob("dep_syncer", func(ctx context.Context) error {
- appCfg, err := ds.AppConfig(ctx)
- if err != nil {
- return ctxerr.Wrap(ctx, err, "retrieving app config")
- }
+ schedule.WithJob("dep_syncer", appleMDMDEPSyncerJob(ds, depStorage, logger)),
+ )
- if !appCfg.MDM.AppleBMEnabledAndConfigured {
- return nil
- }
+ return s, nil
+}
+
+func appleMDMDEPSyncerJob(
+ ds fleet.Datastore,
+ depStorage *mysql.NanoDEPStorage,
+ logger kitlog.Logger,
+) func(context.Context) error {
+ var fleetSyncer *apple_mdm.DEPService
+ return func(ctx context.Context) error {
+ appCfg, err := ds.AppConfig(ctx)
+ if err != nil {
+ return ctxerr.Wrap(ctx, err, "retrieving app config")
+ }
+
+ if !appCfg.MDM.AppleBMEnabledAndConfigured {
+ return nil
+ }
- if fleetSyncer == nil {
- fleetSyncer = apple_mdm.NewDEPService(ds, depStorage, logger)
+ // As part of the DB migration of the single ABM token to the multi-ABM
+ // token world (where the token was migrated from mdm_config_assets to
+ // abm_tokens), we need to complete migration of the existing token as
+ // during the DB migration we didn't have the organization name, apple id
+ // and renewal date.
+ incompleteToken, err := ds.GetABMTokenByOrgName(ctx, "")
+ if err != nil && !fleet.IsNotFound(err) {
+ return ctxerr.Wrap(ctx, err, "retrieving migrated ABM token")
+ }
+ if incompleteToken != nil {
+ logger.Log("msg", "migrated ABM token found, updating its metadata")
+ if err := apple_mdm.SetABMTokenMetadata(ctx, incompleteToken, depStorage, ds, logger); err != nil {
+ return ctxerr.Wrap(ctx, err, "updating migrated ABM token metadata")
+ }
+ if err := ds.SaveABMToken(ctx, incompleteToken); err != nil {
+ return ctxerr.Wrap(ctx, err, "saving updated migrated ABM token")
}
+ logger.Log("msg", "completed migration of existing ABM token")
+ }
- return fleetSyncer.RunAssigner(ctx)
- }),
- )
+ if fleetSyncer == nil {
+ fleetSyncer = apple_mdm.NewDEPService(ds, depStorage, logger)
+ }
- return s, nil
+ return fleetSyncer.RunAssigner(ctx)
+ }
}
func newMDMProfileManager(
@@ -1108,6 +1211,47 @@ func newMDMProfileManager(
return s, nil
}
+func newMDMAPNsPusher(
+ ctx context.Context,
+ instanceID string,
+ ds fleet.Datastore,
+ commander *apple_mdm.MDMAppleCommander,
+ logger kitlog.Logger,
+) (*schedule.Schedule, error) {
+
+ const name = string(fleet.CronAppleMDMAPNsPusher)
+
+ var interval = 1 * time.Minute
+ if intervalEnv := os.Getenv("FLEET_DEV_CUSTOM_APNS_PUSHER_INTERVAL"); intervalEnv != "" {
+ var err error
+ interval, err = time.ParseDuration(intervalEnv)
+ if err != nil {
+ return nil, ctxerr.Wrap(ctx, err, "invalid duration provided in env var FLEET_DEV_CUSTOM_APNS_PUSHER_INTERVAL")
+ }
+
+ }
+
+ logger = kitlog.With(logger, "cron", name)
+ s := schedule.New(
+ ctx, name, instanceID, interval, ds, ds,
+ schedule.WithLogger(logger),
+ schedule.WithJob("apns_push_to_pending_hosts", func(ctx context.Context) error {
+ appCfg, err := ds.AppConfig(ctx)
+ if err != nil {
+ return ctxerr.Wrap(ctx, err, "retrieving app config")
+ }
+
+ if !appCfg.MDM.EnabledAndConfigured {
+ return nil
+ }
+
+ return service.SendPushesToPendingDevices(ctx, ds, commander, logger)
+ }),
+ )
+
+ return s, nil
+}
+
func cleanupCronStatsOnShutdown(ctx context.Context, ds fleet.Datastore, logger kitlog.Logger, instanceID string) {
if err := ds.UpdateAllCronStatsForInstance(ctx, instanceID, fleet.CronStatsStatusPending, fleet.CronStatsStatusCanceled); err != nil {
logger.Log("err", "cancel pending cron stats for instance", "details", err)
@@ -1244,51 +1388,7 @@ func newIPhoneIPadRefetcher(
ctx, name, instanceID, periodicity, ds, ds,
schedule.WithLogger(logger),
schedule.WithJob("cron_iphone_ipad_refetcher", func(ctx context.Context) error {
- appCfg, err := ds.AppConfig(ctx)
- if err != nil {
- return ctxerr.Wrap(ctx, err, "fetching app config")
- }
-
- if !appCfg.MDM.EnabledAndConfigured {
- level.Debug(logger).Log("msg", "apple mdm is not configured, skipping run")
- return nil
- }
-
- start := time.Now()
- uuids, err := ds.ListIOSAndIPadOSToRefetch(ctx, 1*time.Hour)
- if err != nil {
- return ctxerr.Wrap(ctx, err, "list ios and ipad devices to refetch")
- }
- if len(uuids) == 0 {
- return nil
- }
- logger.Log("msg", "sending commands to refetch", "count", len(uuids), "lookup-duration", time.Since(start))
- commandUUID := fleet.RefetchCommandUUIDPrefix + uuid.NewString()
- if err := commander.EnqueueCommand(ctx, uuids, fmt.Sprintf(`
-
-
-
- Command
-
- Queries
-
- DeviceName
- DeviceCapacity
- AvailableDeviceCapacity
- OSVersion
- WiFiMAC
- ProductName
-
- RequestType
- DeviceInformation
-
- CommandUUID
- %s
-
-`, commandUUID)); err != nil {
- return ctxerr.Wrap(ctx, err, "send DeviceInformation commands to ios and ipados devices")
- }
- return nil
+ return apple_mdm.IOSiPadOSRefetch(ctx, ds, commander, logger)
}),
)
diff --git a/cmd/fleet/cron_test.go b/cmd/fleet/cron_test.go
index dd31dd2bec3c..789b38c40565 100644
--- a/cmd/fleet/cron_test.go
+++ b/cmd/fleet/cron_test.go
@@ -2,13 +2,24 @@ package main
import (
"context"
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
+ "github.com/fleetdm/fleet/v4/server/datastore/mysql"
+ "github.com/fleetdm/fleet/v4/server/fleet"
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
+ nanodep_client "github.com/fleetdm/fleet/v4/server/mdm/nanodep/client"
+ "github.com/fleetdm/fleet/v4/server/mdm/nanodep/godep"
"github.com/fleetdm/fleet/v4/server/mock"
mdmmock "github.com/fleetdm/fleet/v4/server/mock/mdm"
+ "github.com/fleetdm/fleet/v4/server/test"
+ "github.com/go-kit/log"
kitlog "github.com/go-kit/log"
)
@@ -23,3 +34,93 @@ func TestNewMDMProfileManagerWithoutConfig(t *testing.T) {
require.NotNil(t, sch)
require.NoError(t, err)
}
+
+func TestMigrateABMTokenDuringDEPCronJob(t *testing.T) {
+ ctx := context.Background()
+ ds := mysql.CreateMySQLDS(t)
+
+ depStorage, err := ds.NewMDMAppleDEPStorage()
+ require.NoError(t, err)
+ // to avoid issues with syncer, use that constant as org name for now
+ const tokenOrgName = "fleet"
+
+ // insert an ABM token as if it had been migrated by the DB migration script
+ tok := mysql.SetTestABMAssets(t, ds, "")
+ // tok, err := ds.InsertABMToken(ctx, &fleet.ABMToken{EncryptedToken: abmToken, RenewAt: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)})
+ // require.NoError(t, err)
+ require.Empty(t, tok.OrganizationName)
+
+ // start a server that will mock the Apple DEP API
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ encoder := json.NewEncoder(w)
+ switch r.URL.Path {
+ case "/session":
+ _, _ = w.Write([]byte(`{"auth_session_token": "session123"}`))
+ case "/account":
+ _, _ = w.Write([]byte(fmt.Sprintf(`{"admin_id": "admin123", "org_name": "%s"}`, tokenOrgName)))
+ case "/profile":
+ err := encoder.Encode(godep.ProfileResponse{ProfileUUID: "profile123"})
+ require.NoError(t, err)
+ case "/server/devices":
+ err := encoder.Encode(godep.DeviceResponse{Devices: nil})
+ require.NoError(t, err)
+ case "/devices/sync":
+ err := encoder.Encode(godep.DeviceResponse{Devices: nil})
+ require.NoError(t, err)
+ default:
+ t.Errorf("unexpected request to %s", r.URL.Path)
+ }
+ }))
+ t.Cleanup(srv.Close)
+
+ err = depStorage.StoreConfig(ctx, tokenOrgName, &nanodep_client.Config{BaseURL: srv.URL})
+ require.NoError(t, err)
+ err = depStorage.StoreConfig(ctx, apple_mdm.UnsavedABMTokenOrgName, &nanodep_client.Config{BaseURL: srv.URL})
+ require.NoError(t, err)
+
+ logger := log.NewNopLogger()
+ syncFn := appleMDMDEPSyncerJob(ds, depStorage, logger)
+ err = syncFn(ctx)
+ require.NoError(t, err)
+
+ // token has been updated with its org name/apple id
+ tok, err = ds.GetABMTokenByOrgName(ctx, tokenOrgName)
+ require.NoError(t, err)
+ require.Equal(t, tokenOrgName, tok.OrganizationName)
+ require.Equal(t, "admin123", tok.AppleID)
+ require.Nil(t, tok.MacOSDefaultTeamID)
+ require.Nil(t, tok.IOSDefaultTeamID)
+ require.Nil(t, tok.IPadOSDefaultTeamID)
+
+ // empty-name token does not exist anymore
+ _, err = ds.GetABMTokenByOrgName(ctx, "")
+ require.Error(t, err)
+ var nfe fleet.NotFoundError
+ require.ErrorAs(t, err, &nfe)
+
+ // the default profile was created
+ defProf, err := ds.GetMDMAppleEnrollmentProfileByType(ctx, fleet.MDMAppleEnrollmentTypeAutomatic)
+ require.NoError(t, err)
+ require.NotNil(t, defProf)
+ require.NotEmpty(t, defProf.Token)
+
+ // no profile UUID was assigned for no-team (because there are no hosts right now)
+ _, _, err = ds.GetMDMAppleDefaultSetupAssistant(ctx, nil, "")
+ require.Error(t, err)
+ require.ErrorAs(t, err, &nfe)
+
+ // no teams, so no team-specific custom setup assistants
+ teams, err := ds.ListTeams(ctx, fleet.TeamFilter{User: test.UserAdmin}, fleet.ListOptions{})
+ require.NoError(t, err)
+ require.Empty(t, teams)
+
+ // no no-team custom setup assistant
+ _, err = ds.GetMDMAppleSetupAssistant(ctx, nil)
+ require.ErrorIs(t, err, sql.ErrNoRows)
+
+ // no host got created
+ hosts, err := ds.ListHosts(ctx, fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{})
+ require.NoError(t, err)
+ require.Empty(t, hosts)
+}
diff --git a/cmd/fleet/main.go b/cmd/fleet/main.go
index eff64d3562da..b39cbc2ee942 100644
--- a/cmd/fleet/main.go
+++ b/cmd/fleet/main.go
@@ -2,11 +2,14 @@ package main
import (
"fmt"
+ "io"
"math/rand"
"os"
"time"
+ "github.com/briandowns/spinner"
"github.com/fleetdm/fleet/v4/server/config"
+ "github.com/fleetdm/fleet/v4/server/shellquote"
kitlog "github.com/go-kit/log"
"github.com/go-kit/log/level"
_ "github.com/go-sql-driver/mysql"
@@ -29,6 +32,35 @@ func main() {
rootCmd.AddCommand(createConfigDumpCmd(configManager))
rootCmd.AddCommand(createVersionCmd(configManager))
+ // See if the program is being piped data on stdin.
+ fi, err := os.Stdin.Stat()
+ if err != nil {
+ initFatal(err, "getting stdin stats")
+ }
+ if fi.Mode()&os.ModeNamedPipe != 0 {
+ _, _ = fmt.Fprintln(os.Stderr, "Reading additional arguments from stdin...")
+ // See charsets at https://godoc.org/github.com/briandowns/spinner#pkg-variables
+ s := spinner.New(spinner.CharSets[24], 200*time.Millisecond)
+ s.Writer = os.Stderr
+ s.Start()
+
+ data, err := io.ReadAll(os.Stdin)
+ if err != nil {
+ initFatal(err, "reading from stdin")
+ }
+
+ // Split the string into arguments like a shell would.
+ extraArgs, err := shellquote.Split(string(data))
+ if err != nil {
+ initFatal(err, "splitting arguments from stdin")
+ }
+
+ // Add the new args to the existing args
+ os.Args = append(os.Args, extraArgs...)
+
+ s.Stop()
+ }
+
if err := rootCmd.Execute(); err != nil {
initFatal(err, "running root command")
}
diff --git a/cmd/fleet/serve.go b/cmd/fleet/serve.go
index d645f419b871..c8773ba765cd 100644
--- a/cmd/fleet/serve.go
+++ b/cmd/fleet/serve.go
@@ -22,7 +22,6 @@ import (
"github.com/e-dard/netbug"
"github.com/fleetdm/fleet/v4/ee/server/licensing"
eeservice "github.com/fleetdm/fleet/v4/ee/server/service"
- "github.com/fleetdm/fleet/v4/pkg/certificate"
"github.com/fleetdm/fleet/v4/pkg/scripts"
"github.com/fleetdm/fleet/v4/server"
configpkg "github.com/fleetdm/fleet/v4/server/config"
@@ -50,6 +49,7 @@ import (
"github.com/fleetdm/fleet/v4/server/pubsub"
"github.com/fleetdm/fleet/v4/server/service"
"github.com/fleetdm/fleet/v4/server/service/async"
+ "github.com/fleetdm/fleet/v4/server/service/redis_lock"
"github.com/fleetdm/fleet/v4/server/service/redis_policy_set"
"github.com/fleetdm/fleet/v4/server/sso"
"github.com/fleetdm/fleet/v4/server/version"
@@ -74,7 +74,10 @@ import (
var allowedURLPrefixRegexp = regexp.MustCompile("^(?:/[a-zA-Z0-9_.~-]+)+$")
-const softwareInstallerUploadTimeout = 2 * time.Minute
+const (
+ softwareInstallerUploadTimeout = 4 * time.Minute
+ liveQueryMemCacheDuration = 1 * time.Second
+)
type initializer interface {
// Initialize is used to populate a datastore with
@@ -124,6 +127,10 @@ the way that the Fleet server works.
logger := initLogger(config)
+ if dev {
+ createTestBucketForInstallers(&config, logger)
+ }
+
// Init tracing
if config.Logging.TracingEnabled {
ctx := context.Background()
@@ -345,7 +352,7 @@ the way that the Fleet server works.
resultStore := pubsub.NewRedisQueryResults(redisPool, config.Redis.DuplicateResults,
log.With(logger, "component", "query-results"),
)
- liveQueryStore := live_query.NewRedisLiveQuery(redisPool)
+ liveQueryStore := live_query.NewRedisLiveQuery(redisPool, logger, liveQueryMemCacheDuration)
ssoSessionStore := sso.NewSessionStore(redisPool)
// Set common configuration for all logging.
@@ -504,7 +511,7 @@ the way that the Fleet server works.
initFatal(errors.New("inserting APNs and SCEP assets"), "missing required private key. Learn how to configure the private key here: https://fleetdm.com/learn-more-about/fleet-server-private-key")
}
- apnsCert, apnsCertPEM, apnsKeyPEM, err := config.MDM.AppleAPNs()
+ _, apnsCertPEM, apnsKeyPEM, err := config.MDM.AppleAPNs()
if err != nil {
initFatal(err, "validate Apple APNs certificate and key")
}
@@ -514,18 +521,6 @@ the way that the Fleet server works.
initFatal(err, "validate Apple SCEP certificate and key")
}
- const (
- apnsConnectionTimeout = 10 * time.Second
- apnsConnectionURL = "https://api.sandbox.push.apple.com"
- )
-
- // check that the Apple APNs certificate is valid to connect to the API
- ctx, cancel := context.WithTimeout(context.Background(), apnsConnectionTimeout)
- if err := certificate.ValidateClientAuthTLSConnection(ctx, apnsCert, apnsConnectionURL); err != nil {
- initFatal(err, "validate authentication with Apple APNs certificate")
- }
- cancel()
-
err = ds.InsertMDMConfigAssets(context.Background(), []fleet.MDMConfigAsset{
{Name: fleet.MDMAssetAPNSCert, Value: apnsCertPEM},
{Name: fleet.MDMAssetAPNSKey, Value: apnsKeyPEM},
@@ -563,7 +558,6 @@ the way that the Fleet server works.
err = ds.InsertMDMConfigAssets(context.Background(), []fleet.MDMConfigAsset{
{Name: fleet.MDMAssetABMKey, Value: appleBM.KeyPEM},
{Name: fleet.MDMAssetABMCert, Value: appleBM.CertPEM},
- {Name: fleet.MDMAssetABMToken, Value: appleBM.EncryptedToken},
})
if err != nil {
// duplicate key errors mean that we already
@@ -575,6 +569,20 @@ the way that the Fleet server works.
}
level.Warn(logger).Log("msg", "Your server already has stored ABM certificates and token. Fleet will ignore any certificates provided via environment variables when this happens.")
+ } else {
+ // insert the ABM token without any metdata,
+ // it'll be picked by the
+ // apple_mdm_dep_profile_assigner cron and
+ // backfilled
+ tok := &fleet.ABMToken{
+ EncryptedToken: appleBM.EncryptedToken,
+ // 2000-01-01 is our "zero value" for time
+ RenewAt: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC),
+ }
+ _, err = ds.InsertABMToken(context.Background(), tok)
+ if err != nil {
+ initFatal(err, "save ABM token")
+ }
}
}
@@ -607,14 +615,29 @@ the way that the Fleet server works.
initFatal(err, "validating MDM assets from database")
}
- appCfg.MDM.AppleBMEnabledAndConfigured, err = checkMDMAssets([]fleet.MDMAssetName{
+ var appleBMCerts bool
+ appleBMCerts, err = checkMDMAssets([]fleet.MDMAssetName{
fleet.MDMAssetABMCert,
fleet.MDMAssetABMKey,
- fleet.MDMAssetABMToken,
})
if err != nil {
initFatal(err, "validating MDM ABM assets from database")
}
+ if appleBMCerts {
+ // the ABM certs are there, check if a token exists and if so, apple
+ // BM is enabled and configured.
+ count, err := ds.GetABMTokenCount(context.Background())
+ if err != nil {
+ initFatal(err, "validating MDM ABM token from database")
+ }
+ appCfg.MDM.AppleBMEnabledAndConfigured = count > 0
+ }
+ }
+ if appCfg.MDM.EnabledAndConfigured {
+ level.Info(logger).Log("msg", "Apple MDM enabled")
+ }
+ if appCfg.MDM.AppleBMEnabledAndConfigured {
+ level.Info(logger).Log("msg", "Apple Business Manager enabled")
}
// register the Microsoft MDM services
@@ -691,6 +714,8 @@ the way that the Fleet server works.
}
var softwareInstallStore fleet.SoftwareInstallerStore
+ var bootstrapPackageStore fleet.MDMBootstrapPackageStore
+ var distributedLock fleet.Lock
if license.IsPremium() {
profileMatcher := apple_mdm.NewProfileMatcher(redisPool)
if config.S3.SoftwareInstallersBucket != "" {
@@ -703,6 +728,13 @@ the way that the Fleet server works.
}
softwareInstallStore = store
level.Info(logger).Log("msg", "using S3 software installer store", "bucket", config.S3.SoftwareInstallersBucket)
+
+ bstore, err := s3.NewBootstrapPackageStore(config.S3)
+ if err != nil {
+ initFatal(err, "initializing S3 bootstrap package store")
+ }
+ bootstrapPackageStore = bstore
+ level.Info(logger).Log("msg", "using S3 bootstrap package store", "bucket", config.S3.SoftwareInstallersBucket)
} else {
installerDir := os.TempDir()
if dir := os.Getenv("FLEET_SOFTWARE_INSTALLER_STORE_DIR"); dir != "" {
@@ -718,6 +750,7 @@ the way that the Fleet server works.
}
}
+ distributedLock = redis_lock.NewLock(redisPool)
svc, err = eeservice.NewService(
svc,
ds,
@@ -730,6 +763,8 @@ the way that the Fleet server works.
ssoSessionStore,
profileMatcher,
softwareInstallStore,
+ bootstrapPackageStore,
+ distributedLock,
)
if err != nil {
initFatal(err, "initial Fleet Premium service")
@@ -783,7 +818,7 @@ the way that the Fleet server works.
func() (fleet.CronSchedule, error) {
commander := apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPushService)
return newCleanupsAndAggregationSchedule(
- ctx, instanceID, ds, logger, redisWrapperDS, &config, commander, softwareInstallStore,
+ ctx, instanceID, ds, logger, redisWrapperDS, &config, commander, softwareInstallStore, bootstrapPackageStore,
)
},
); err != nil {
@@ -845,6 +880,18 @@ 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 newMDMAPNsPusher(
+ ctx,
+ instanceID,
+ ds,
+ apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPushService),
+ logger,
+ )
+ }); err != nil {
+ initFatal(err, "failed to register APNs pusher schedule")
+ }
+
if license.IsPremium() {
if err := cronSchedules.StartCronSchedule(func() (fleet.CronSchedule, error) {
commander := apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPushService)
@@ -870,7 +917,7 @@ the way that the Fleet server works.
} else {
config.Calendar.Periodicity = 5 * time.Minute
}
- return cron.NewCalendarSchedule(ctx, instanceID, ds, config.Calendar, logger)
+ return cron.NewCalendarSchedule(ctx, instanceID, ds, distributedLock, config.Calendar, logger)
},
); err != nil {
initFatal(err, "failed to register calendar schedule")
@@ -920,7 +967,7 @@ the way that the Fleet server works.
KeyPrefix: "ratelimit::",
}
- var apiHandler, frontendHandler http.Handler
+ var apiHandler, frontendHandler, endUserEnrollOTAHandler http.Handler
{
frontendHandler = service.PrometheusMetricsHandler(
"get_frontend",
@@ -938,8 +985,10 @@ the way that the Fleet server works.
if setupRequired {
apiHandler = service.WithSetup(svc, logger, apiHandler)
frontendHandler = service.RedirectLoginToSetup(svc, logger, frontendHandler, config.Server.URLPrefix)
+ endUserEnrollOTAHandler = service.RedirectLoginToSetup(svc, logger, frontendHandler, config.Server.URLPrefix)
} else {
frontendHandler = service.RedirectSetupToLogin(svc, logger, frontendHandler, config.Server.URLPrefix)
+ endUserEnrollOTAHandler = service.ServeEndUserEnrollOTA(config.Server.URLPrefix, logger)
}
}
@@ -1055,22 +1104,26 @@ the way that the Fleet server works.
// when uploading a software installer, the file might be large so
// the read timeout (to read the full request body) must be extended.
rc := http.NewResponseController(rw)
- // the frontend times out waiting for the upload after 2 minutes, so
+ // the frontend times out waiting for the upload after 4 minutes,
// use that same timeout:
// https://www.figma.com/design/oQl2oQUG0iRkUy0YOxc307/%2314921-Deploy-security-agents-to-macOS%2C-Windows%2C-and-Linux-hosts?node-id=773-18032&t=QjEU6tc73tddNSqn-0
if err := rc.SetReadDeadline(time.Now().Add(softwareInstallerUploadTimeout)); err != nil {
level.Error(logger).Log("msg", "http middleware failed to override endpoint read timeout", "err", err)
}
- // the write timeout should be extended as well to give the server time to
- // write a response body with the right error, otherwise the connection is
- // terminated abruptly.
- if err := rc.SetWriteDeadline(time.Now().Add(softwareInstallerUploadTimeout + 30*time.Second)); err != nil {
+ // the write timeout should be extended to give the server time to
+ // store the installer to S3 (or the configured storage location) and
+ // write a response body, otherwise the connection is terminated
+ // abruptly. Give it twice the read timeout, so that if it takes
+ // 3m59s to upload an installer, we don't fail because of a lack of
+ // time to store to S3.
+ if err := rc.SetWriteDeadline(time.Now().Add(2 * softwareInstallerUploadTimeout)); err != nil {
level.Error(logger).Log("msg", "http middleware failed to override endpoint write timeout", "err", err)
}
req.Body = http.MaxBytesReader(rw, req.Body, service.MaxSoftwareInstallerSize)
}
apiHandler.ServeHTTP(rw, req)
})
+ rootMux.Handle("/enroll", endUserEnrollOTAHandler)
rootMux.Handle("/", frontendHandler)
debugHandler := &debugMux{
@@ -1355,3 +1408,18 @@ var _ push.Pusher = nopPusher{}
func (n nopPusher) Push(context.Context, []string) (map[string]*push.Response, error) {
return nil, nil
}
+
+func createTestBucketForInstallers(config *configpkg.FleetConfig, logger log.Logger) {
+ store, err := s3.NewSoftwareInstallerStore(config.S3)
+ if err != nil {
+ initFatal(err, "initializing S3 software installer store")
+ }
+ if err := store.CreateTestBucket(config.S3.SoftwareInstallersBucket); err != nil {
+ // Don't panic, allow devs to run Fleet without minio/S3 dependency.
+ level.Info(logger).Log(
+ "err", err,
+ "msg", "failed to create test bucket",
+ "name", config.S3.SoftwareInstallersBucket,
+ )
+ }
+}
diff --git a/cmd/fleet/serve_test.go b/cmd/fleet/serve_test.go
index 41e7d3885ad4..675f1242aa76 100644
--- a/cmd/fleet/serve_test.go
+++ b/cmd/fleet/serve_test.go
@@ -116,6 +116,10 @@ func TestMaybeSendStatistics(t *testing.T) {
HostsEnrolledByOsqueryVersion: []fleet.HostsCountByOsqueryVersion{},
StoredErrors: []byte(`[]`),
Organization: "Fleet",
+ AIFeaturesDisabled: true,
+ MaintenanceWindowsEnabled: true,
+ MaintenanceWindowsConfigured: true,
+ NumHostsFleetDesktopEnabled: 1984,
}, true, nil
}
recorded := false
@@ -134,7 +138,7 @@ func TestMaybeSendStatistics(t *testing.T) {
require.NoError(t, err)
assert.True(t, recorded)
require.True(t, cleanedup)
- assert.Equal(t, `{"anonymousIdentifier":"ident","fleetVersion":"1.2.3","licenseTier":"premium","organization":"Fleet","numHostsEnrolled":999,"numUsers":99,"numSoftwareVersions":100,"numHostSoftwares":101,"numSoftwareTitles":102,"numHostSoftwareInstalledPaths":103,"numSoftwareCPEs":104,"numSoftwareCVEs":105,"numTeams":9,"numPolicies":0,"numLabels":3,"softwareInventoryEnabled":true,"vulnDetectionEnabled":true,"systemUsersEnabled":true,"hostsStatusWebHookEnabled":true,"mdmMacOsEnabled":false,"hostExpiryEnabled":false,"mdmWindowsEnabled":false,"liveQueryDisabled":false,"numWeeklyActiveUsers":111,"numWeeklyPolicyViolationDaysActual":0,"numWeeklyPolicyViolationDaysPossible":0,"hostsEnrolledByOperatingSystem":{"linux":[{"version":"1.2.3","numEnrolled":22}]},"hostsEnrolledByOrbitVersion":[],"hostsEnrolledByOsqueryVersion":[],"storedErrors":[],"numHostsNotResponding":0}`, requestBody)
+ assert.Equal(t, `{"anonymousIdentifier":"ident","fleetVersion":"1.2.3","licenseTier":"premium","organization":"Fleet","numHostsEnrolled":999,"numUsers":99,"numSoftwareVersions":100,"numHostSoftwares":101,"numSoftwareTitles":102,"numHostSoftwareInstalledPaths":103,"numSoftwareCPEs":104,"numSoftwareCVEs":105,"numTeams":9,"numPolicies":0,"numLabels":3,"softwareInventoryEnabled":true,"vulnDetectionEnabled":true,"systemUsersEnabled":true,"hostsStatusWebHookEnabled":true,"mdmMacOsEnabled":false,"hostExpiryEnabled":false,"mdmWindowsEnabled":false,"liveQueryDisabled":false,"numWeeklyActiveUsers":111,"numWeeklyPolicyViolationDaysActual":0,"numWeeklyPolicyViolationDaysPossible":0,"hostsEnrolledByOperatingSystem":{"linux":[{"version":"1.2.3","numEnrolled":22}]},"hostsEnrolledByOrbitVersion":[],"hostsEnrolledByOsqueryVersion":[],"storedErrors":[],"numHostsNotResponding":0,"aiFeaturesDisabled":true,"maintenanceWindowsEnabled":true,"maintenanceWindowsConfigured":true,"numHostsFleetDesktopEnabled":1984}`, requestBody)
}
func TestMaybeSendStatisticsSkipsSendingIfNotNeeded(t *testing.T) {
@@ -294,6 +298,7 @@ func TestAutomationsSchedule(t *testing.T) {
}
func TestCronVulnerabilitiesCreatesDatabasesPath(t *testing.T) {
+ t.Parallel()
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
diff --git a/cmd/fleetctl/apply.go b/cmd/fleetctl/apply.go
index 751c74709f18..b201f5dc9474 100644
--- a/cmd/fleetctl/apply.go
+++ b/cmd/fleetctl/apply.go
@@ -90,7 +90,7 @@ func applyCommand() *cli.Command {
opts.TeamForPolicies = policiesTeamName
}
baseDir := filepath.Dir(flFilename)
- _, err = fleetClient.ApplyGroup(c.Context, specs, baseDir, logf, opts)
+ _, _, err = fleetClient.ApplyGroup(c.Context, specs, baseDir, logf, nil, opts)
if err != nil {
return err
}
diff --git a/cmd/fleetctl/apply_test.go b/cmd/fleetctl/apply_test.go
index 5798e26aa87c..1010e8ad1eeb 100644
--- a/cmd/fleetctl/apply_test.go
+++ b/cmd/fleetctl/apply_test.go
@@ -167,12 +167,15 @@ func TestApplyTeamSpecs(t *testing.T) {
return nil
}
- ds.BatchSetMDMProfilesFunc = func(ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile, macDecls []*fleet.MDMAppleDeclaration) error {
- return nil
+ ds.BatchSetMDMProfilesFunc = func(ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile,
+ winProfiles []*fleet.MDMWindowsConfigProfile, macDecls []*fleet.MDMAppleDeclaration,
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
- ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string) error {
- return nil
+ ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string,
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.NewActivityFunc = func(
@@ -182,8 +185,17 @@ func TestApplyTeamSpecs(t *testing.T) {
}
ds.LabelIDsByNameFunc = func(ctx context.Context, labels []string) (map[string]uint, error) {
- require.ElementsMatch(t, labels, []string{fleet.BuiltinLabelMacOS14Plus})
- return map[string]uint{fleet.BuiltinLabelMacOS14Plus: 1}, nil
+ require.Len(t, labels, 1)
+ switch labels[0] {
+ case fleet.BuiltinLabelMacOS14Plus:
+ return map[string]uint{fleet.BuiltinLabelMacOS14Plus: 1}, nil
+ case fleet.BuiltinLabelIOS:
+ return map[string]uint{fleet.BuiltinLabelIOS: 2}, nil
+ case fleet.BuiltinLabelIPadOS:
+ return map[string]uint{fleet.BuiltinLabelIPadOS: 3}, nil
+ default:
+ return nil, ¬FoundError{}
+ }
}
ds.SetOrUpdateMDMAppleDeclarationFunc = func(ctx context.Context, declaration *fleet.MDMAppleDeclaration) (*fleet.MDMAppleDeclaration, error) {
@@ -222,7 +234,7 @@ spec:
newAgentOpts := json.RawMessage(`{"config":{"views":{"foo":"bar"}}}`)
newMDMSettings := fleet.TeamMDM{
- MacOSUpdates: fleet.MacOSUpdates{
+ MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("12.3.1"),
Deadline: optjson.SetString("2011-03-01"),
},
@@ -258,7 +270,7 @@ spec:
`)
require.Equal(t, "[+] applied 1 teams\n", runAppForTest(t, []string{"apply", "-f", filename}))
newMDMSettings = fleet.TeamMDM{
- MacOSUpdates: fleet.MacOSUpdates{
+ MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("12.3.1"),
Deadline: optjson.SetString("2011-03-01"),
},
@@ -286,7 +298,7 @@ spec:
`, mobileCfgPath))
newMDMSettings = fleet.TeamMDM{
- MacOSUpdates: fleet.MacOSUpdates{
+ MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("12.3.1"),
Deadline: optjson.SetString("2011-03-01"),
},
@@ -325,15 +337,29 @@ spec:
macos_updates:
minimum_version: 10.10.10
deadline: 1992-03-01
+ ios_updates:
+ minimum_version: 11.11.11
+ deadline: 1993-04-02
+ ipados_updates:
+ minimum_version: 12.12.12
+ deadline: 1994-05-03
secrets:
- secret: BBB
`)
newMDMSettings = fleet.TeamMDM{
- MacOSUpdates: fleet.MacOSUpdates{
+ MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.10.10"),
Deadline: optjson.SetString("1992-03-01"),
},
+ IOSUpdates: fleet.AppleOSUpdateSettings{
+ MinimumVersion: optjson.SetString("11.11.11"),
+ Deadline: optjson.SetString("1993-04-02"),
+ },
+ IPadOSUpdates: fleet.AppleOSUpdateSettings{
+ MinimumVersion: optjson.SetString("12.12.12"),
+ Deadline: optjson.SetString("1994-05-03"),
+ },
WindowsUpdates: fleet.WindowsUpdates{
DeadlineDays: optjson.SetInt(5),
GracePeriodDays: optjson.SetInt(1),
@@ -398,6 +424,12 @@ spec:
macos_updates:
minimum_version:
deadline:
+ ios_updates:
+ minimum_version:
+ deadline:
+ ipados_updates:
+ minimum_version:
+ deadline:
windows_updates:
deadline_days:
grace_period_days:
@@ -406,7 +438,15 @@ spec:
`)
newMDMSettings = fleet.TeamMDM{
- MacOSUpdates: fleet.MacOSUpdates{
+ MacOSUpdates: fleet.AppleOSUpdateSettings{
+ MinimumVersion: optjson.String{Set: true},
+ Deadline: optjson.String{Set: true},
+ },
+ IOSUpdates: fleet.AppleOSUpdateSettings{
+ MinimumVersion: optjson.String{Set: true},
+ Deadline: optjson.String{Set: true},
+ },
+ IPadOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.String{Set: true},
Deadline: optjson.String{Set: true},
},
@@ -549,6 +589,10 @@ func TestApplyAppConfig(t *testing.T) {
return userRoleSpecList, nil
}
+ ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
+ return nil
+ }
+
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
) error {
@@ -586,8 +630,9 @@ func TestApplyAppConfig(t *testing.T) {
return nil
}
- ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string) error {
- return nil
+ ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string,
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.LabelIDsByNameFunc = func(ctx context.Context, labels []string) (map[string]uint, error) {
@@ -603,6 +648,12 @@ func TestApplyAppConfig(t *testing.T) {
ds.DeleteMDMAppleDeclarationByNameFunc = func(ctx context.Context, teamID *uint, name string) error {
return nil
}
+ ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
+ return []*fleet.ABMToken{{OrganizationName: "Fleet Device Management Inc."}}, nil
+ }
+ ds.TeamsSummaryFunc = func(ctx context.Context) ([]*fleet.TeamSummary, error) {
+ return []*fleet.TeamSummary{{Name: "team1", ID: 1}}, nil
+ }
name := writeTmpYml(t, `---
apiVersion: v1
@@ -622,9 +673,9 @@ spec:
`)
newMDMSettings := fleet.MDM{
- AppleBMDefaultTeam: "team1",
- AppleBMTermsExpired: false,
- MacOSUpdates: fleet.MacOSUpdates{
+ DeprecatedAppleBMDefaultTeam: "team1",
+ AppleBMTermsExpired: false,
+ MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("12.1.1"),
Deadline: optjson.SetString("2011-02-01"),
},
@@ -677,9 +728,9 @@ spec:
`)
newMDMSettings = fleet.MDM{
- AppleBMDefaultTeam: "team1",
- AppleBMTermsExpired: false,
- MacOSUpdates: fleet.MacOSUpdates{
+ DeprecatedAppleBMDefaultTeam: "team1",
+ AppleBMTermsExpired: false,
+ MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("12.1.1"),
Deadline: optjson.SetString("2011-02-01"),
},
@@ -1203,11 +1254,14 @@ func TestApplyAsGitOps(t *testing.T) {
teamEnrollSecrets = secrets
return nil
}
- ds.BatchSetMDMProfilesFunc = func(ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile, macDecls []*fleet.MDMAppleDeclaration) error {
- return nil
+ ds.BatchSetMDMProfilesFunc = func(ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile,
+ winProfiles []*fleet.MDMWindowsConfigProfile, macDecls []*fleet.MDMAppleDeclaration,
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
- ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string) error {
- return nil
+ ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string,
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.GetMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) (*fleet.MDMAppleSetupAssistant, error) {
return nil, ¬FoundError{}
@@ -1221,7 +1275,7 @@ func TestApplyAsGitOps(t *testing.T) {
ds.GetMDMAppleBootstrapPackageMetaFunc = func(ctx context.Context, teamID uint) (*fleet.MDMAppleBootstrapPackage, error) {
return nil, ¬FoundError{}
}
- ds.InsertMDMAppleBootstrapPackageFunc = func(ctx context.Context, bp *fleet.MDMAppleBootstrapPackage) error {
+ ds.InsertMDMAppleBootstrapPackageFunc = func(ctx context.Context, bp *fleet.MDMAppleBootstrapPackage, pkgStore fleet.MDMBootstrapPackageStore) error {
return nil
}
ds.SetOrUpdateMDMWindowsConfigProfileFunc = func(ctx context.Context, cp fleet.MDMWindowsConfigProfile) error {
@@ -1315,7 +1369,7 @@ spec:
MacOSSetupAssistant: optjson.SetString(emptySetupAsst),
EnableReleaseDeviceManually: optjson.SetBool(false),
},
- MacOSUpdates: fleet.MacOSUpdates{
+ MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.10.10"),
Deadline: optjson.SetString("2020-02-02"),
},
@@ -1358,7 +1412,7 @@ spec:
BootstrapPackage: optjson.SetString(bootstrapURL),
EnableReleaseDeviceManually: optjson.SetBool(false),
},
- MacOSUpdates: fleet.MacOSUpdates{
+ MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.10.10"),
Deadline: optjson.SetString("2020-02-02"),
},
@@ -1409,7 +1463,7 @@ spec:
MacOSSettings: fleet.MacOSSettings{
CustomSettings: []fleet.MDMProfileSpec{{Path: mobileConfigPath}},
},
- MacOSUpdates: fleet.MacOSUpdates{
+ MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.10.10"),
Deadline: optjson.SetString("1992-03-01"),
},
@@ -1451,7 +1505,7 @@ spec:
MacOSSettings: fleet.MacOSSettings{
CustomSettings: []fleet.MDMProfileSpec{{Path: mobileConfigPath}},
},
- MacOSUpdates: fleet.MacOSUpdates{
+ MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.10.10"),
Deadline: optjson.SetString("1992-03-01"),
},
@@ -1488,7 +1542,7 @@ spec:
MacOSSettings: fleet.MacOSSettings{
CustomSettings: []fleet.MDMProfileSpec{{Path: mobileConfigPath}},
},
- MacOSUpdates: fleet.MacOSUpdates{
+ MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("10.10.10"),
Deadline: optjson.SetString("1992-03-01"),
},
@@ -1964,7 +2018,7 @@ func TestApplyMacosSetup(t *testing.T) {
}
return nil, ¬FoundError{}
}
- ds.InsertMDMAppleBootstrapPackageFunc = func(ctx context.Context, bp *fleet.MDMAppleBootstrapPackage) error {
+ ds.InsertMDMAppleBootstrapPackageFunc = func(ctx context.Context, bp *fleet.MDMAppleBootstrapPackage, pkgStore fleet.MDMBootstrapPackageStore) error {
return nil
}
ds.DeleteMDMAppleBootstrapPackageFunc = func(ctx context.Context, teamID uint) error {
@@ -2350,7 +2404,7 @@ spec:
t.Run(c.pkgName, func(t *testing.T) {
srv, pkgLen := serveMDMBootstrapPackage(t, filepath.Join("../../server/service/testdata/bootstrap-packages", c.pkgName), c.pkgName)
ds := setupServer(t, true)
- ds.InsertMDMAppleBootstrapPackageFunc = func(ctx context.Context, bp *fleet.MDMAppleBootstrapPackage) error {
+ ds.InsertMDMAppleBootstrapPackageFunc = func(ctx context.Context, bp *fleet.MDMAppleBootstrapPackage, pkgStore fleet.MDMBootstrapPackageStore) error {
require.Equal(t, len(bp.Bytes), pkgLen)
return nil
}
@@ -2409,7 +2463,7 @@ spec:
defer srv.Close()
ds := setupServer(t, true)
- ds.InsertMDMAppleBootstrapPackageFunc = func(ctx context.Context, bp *fleet.MDMAppleBootstrapPackage) error {
+ ds.InsertMDMAppleBootstrapPackageFunc = func(ctx context.Context, bp *fleet.MDMAppleBootstrapPackage, pkgStore fleet.MDMBootstrapPackageStore) error {
mockStore.Lock()
defer mockStore.Unlock()
require.Equal(t, pkgName, bp.Name)
@@ -2562,6 +2616,7 @@ spec:
}
func TestApplySpecs(t *testing.T) {
+ t.Parallel()
// create a macos setup json file (content not important)
macSetupFile := writeTmpJSON(t, map[string]any{})
diff --git a/cmd/fleetctl/get_test.go b/cmd/fleetctl/get_test.go
index 009fbbeb9685..fc9dbe03fbca 100644
--- a/cmd/fleetctl/get_test.go
+++ b/cmd/fleetctl/get_test.go
@@ -166,10 +166,18 @@ func TestGetTeams(t *testing.T) {
HostExpiryWindow: 15,
},
MDM: fleet.TeamMDM{
- MacOSUpdates: fleet.MacOSUpdates{
+ MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("12.3.1"),
Deadline: optjson.SetString("2021-12-14"),
},
+ IOSUpdates: fleet.AppleOSUpdateSettings{
+ MinimumVersion: optjson.SetString("17.5"),
+ Deadline: optjson.SetString("2022-11-15"),
+ },
+ IPadOSUpdates: fleet.AppleOSUpdateSettings{
+ MinimumVersion: optjson.SetString("18.0"),
+ Deadline: optjson.SetString("2023-01-01"),
+ },
WindowsUpdates: fleet.WindowsUpdates{
DeadlineDays: optjson.SetInt(7),
GracePeriodDays: optjson.SetInt(3),
@@ -684,10 +692,10 @@ func TestGetSoftwareTitles(t *testing.T) {
apiVersion: "1"
kind: software_title
spec:
-- hosts_count: 2
+- app_store_app: null
+ hosts_count: 2
id: 0
name: foo
- self_service: false
software_package: null
source: chrome_extensions
versions:
@@ -705,10 +713,10 @@ spec:
vulnerabilities:
- cve-123-456-003
versions_count: 3
-- hosts_count: 0
+- app_store_app: null
+ hosts_count: 0
id: 0
name: bar
- self_service: false
software_package: null
source: deb_packages
versions:
@@ -753,8 +761,8 @@ spec:
]
}
],
- "self_service": false,
- "software_package": null
+ "software_package": null,
+ "app_store_app": null
},
{
"id": 0,
@@ -766,11 +774,11 @@ spec:
{
"id": 0,
"version": "0.0.3",
- "vulnerabilities": null
+ "vulnerabilities": null
}
],
- "self_service": false,
- "software_package": null
+ "software_package": null,
+ "app_store_app": null
}
]
}
@@ -2052,8 +2060,13 @@ func TestGetAppleBM(t *testing.T) {
assert.Contains(t, err.Error(), expected)
})
- t.Run("premium license", func(t *testing.T) {
- runServerWithMockedDS(t, &service.TestServerOpts{License: &fleet.LicenseInfo{Tier: fleet.TierPremium}, DEPStorage: depStorage})
+ t.Run("premium license, single token", func(t *testing.T) {
+ _, ds := runServerWithMockedDS(t, &service.TestServerOpts{License: &fleet.LicenseInfo{Tier: fleet.TierPremium}, DEPStorage: depStorage})
+ ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
+ return []*fleet.ABMToken{
+ {ID: 1},
+ }, nil
+ }
out := runAppForTest(t, []string{"get", "mdm_apple_bm"})
assert.Contains(t, out, "Apple ID:")
@@ -2062,6 +2075,29 @@ func TestGetAppleBM(t *testing.T) {
assert.Contains(t, out, "Renew date:")
assert.Contains(t, out, "Default team:")
})
+
+ t.Run("premium license, no token", func(t *testing.T) {
+ _, ds := runServerWithMockedDS(t, &service.TestServerOpts{License: &fleet.LicenseInfo{Tier: fleet.TierPremium}, DEPStorage: depStorage})
+ ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
+ return nil, nil
+ }
+
+ out := runAppForTest(t, []string{"get", "mdm_apple_bm"})
+ assert.Contains(t, out, "No Apple Business Manager server token found.")
+ })
+
+ t.Run("premium license, multiple tokens", func(t *testing.T) {
+ _, ds := runServerWithMockedDS(t, &service.TestServerOpts{License: &fleet.LicenseInfo{Tier: fleet.TierPremium}, DEPStorage: depStorage})
+ ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
+ return []*fleet.ABMToken{
+ {ID: 1},
+ {ID: 2},
+ }, nil
+ }
+
+ _, err := runAppNoChecks([]string{"get", "mdm_apple_bm"})
+ assert.ErrorContains(t, err, "This API endpoint has been deprecated. Please use the new GET /abm_tokens API endpoint")
+ })
}
func TestGetCarves(t *testing.T) {
@@ -2225,7 +2261,7 @@ func TestGetTeamsYAMLAndApply(t *testing.T) {
AdditionalQueries: &additionalQueries,
},
MDM: fleet.TeamMDM{
- MacOSUpdates: fleet.MacOSUpdates{
+ MacOSUpdates: fleet.AppleOSUpdateSettings{
MinimumVersion: optjson.SetString("12.3.1"),
Deadline: optjson.SetString("2021-12-14"),
},
@@ -2261,11 +2297,14 @@ func TestGetTeamsYAMLAndApply(t *testing.T) {
}
return nil, fmt.Errorf("team not found: %s", name)
}
- ds.BatchSetMDMProfilesFunc = func(ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile, macDecls []*fleet.MDMAppleDeclaration) error {
- return nil
+ ds.BatchSetMDMProfilesFunc = func(ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile,
+ winProfiles []*fleet.MDMWindowsConfigProfile, macDecls []*fleet.MDMAppleDeclaration,
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
- ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, uuids []string) error {
- return nil
+ ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, uuids []string,
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.BatchSetScriptsFunc = func(ctx context.Context, tmID *uint, scripts []*fleet.Script) error {
return nil
@@ -2281,8 +2320,8 @@ func TestGetTeamsYAMLAndApply(t *testing.T) {
declaration.DeclarationUUID = uuid.NewString()
return declaration, nil
}
- ds.BatchSetSoftwareInstallersFunc = func(ctx context.Context, tmID *uint, installers []*fleet.UploadSoftwareInstallerPayload) error {
- return nil
+ ds.BatchSetSoftwareInstallersFunc = func(ctx context.Context, tmID *uint, installers []*fleet.UploadSoftwareInstallerPayload) ([]fleet.SoftwareInstaller, error) {
+ return nil, nil
}
actualYaml := runAppForTest(t, []string{"get", "teams", "--yaml"})
diff --git a/cmd/fleetctl/gitops.go b/cmd/fleetctl/gitops.go
index c56016eca323..b593ebf9299b 100644
--- a/cmd/fleetctl/gitops.go
+++ b/cmd/fleetctl/gitops.go
@@ -78,11 +78,13 @@ func gitopsCommand() *cli.Command {
return errors.New("no license struct found in app config")
}
- var appleBMDefaultTeam string
- var appleBMDefaultTeamFound bool
+ var originalABMConfig []any
+ var originalVPPConfig []any
var teamNames []string
var firstFileMustBeGlobal *bool
var teamDryRunAssumptions *fleet.TeamSpecsDryRunAssumptions
+ var abmTeams, vppTeams []string
+ var hasMissingABMTeam, hasMissingVPPTeam, usesLegacyABMConfig bool
if totalFilenames > 1 {
firstFileMustBeGlobal = ptr.Bool(true)
}
@@ -90,7 +92,7 @@ func gitopsCommand() *cli.Command {
secrets := make(map[string]struct{})
for _, flFilename := range flFilenames.Value() {
baseDir := filepath.Dir(flFilename)
- config, err := spec.GitOpsFromFile(flFilename, baseDir)
+ config, err := spec.GitOpsFromFile(flFilename, baseDir, appConfig)
if err != nil {
return err
}
@@ -106,12 +108,57 @@ func gitopsCommand() *cli.Command {
}
firstFileMustBeGlobal = ptr.Bool(false)
}
+
+ // Special handling for tokens is required because they link to teams (by
+ // name.) Because teams can be created/deleted during the same gitops run, we
+ // grab some information to help us determine allowed/restricted actions and
+ // when to perform the associations.
if isGlobalConfig && totalFilenames > 1 {
- // Check if Apple BM default team already exists
- appleBMDefaultTeam, appleBMDefaultTeamFound, err = checkAppleBMDefaultTeam(config, fleetClient)
+ abmTeams, hasMissingABMTeam, usesLegacyABMConfig, err = checkABMTeamAssignments(config, fleetClient)
+ if err != nil {
+ return err
+ }
+
+ vppTeams, hasMissingVPPTeam, err = checkVPPTeamAssignments(config, fleetClient)
if err != nil {
return err
}
+
+ // if one of the teams assigned to an ABM token doesn't exist yet, we need to
+ // submit the configs without the ABM default team set. We'll set those
+ // separately later when the teams are already created.
+ if hasMissingABMTeam {
+ if mdm, ok := config.OrgSettings["mdm"]; ok {
+ if mdmMap, ok := mdm.(map[string]any); ok {
+ if appleBM, ok := mdmMap["apple_business_manager"]; ok {
+ if bmSettings, ok := appleBM.([]any); ok {
+ originalABMConfig = bmSettings
+ }
+ }
+
+ // If team is not found, we need to remove the AppleBMDefaultTeam from
+ // the global config, and then apply it after teams are processed
+ mdmMap["apple_business_manager"] = nil
+ mdmMap["apple_bm_default_team"] = ""
+ }
+ }
+ }
+
+ if hasMissingVPPTeam {
+ if mdm, ok := config.OrgSettings["mdm"]; ok {
+ if mdmMap, ok := mdm.(map[string]any); ok {
+ if vpp, ok := mdmMap["volume_purchasing_program"]; ok {
+ if vppSettings, ok := vpp.([]any); ok {
+ originalVPPConfig = vppSettings
+ }
+ }
+
+ // If team is not found, we need to remove the VPP config from
+ // the global config, and then apply it after teams are processed
+ mdmMap["volume_purchasing_program"] = nil
+ }
+ }
+ }
}
logf := func(format string, a ...interface{}) {
_, _ = fmt.Fprintf(c.App.Writer, format, a...)
@@ -135,10 +182,15 @@ func gitopsCommand() *cli.Command {
teamDryRunAssumptions = assumptions
}
}
- if appleBMDefaultTeam != "" && !appleBMDefaultTeamFound {
- // If the Apple BM default team did not exist earlier, check again and apply it if needed
- err = applyAppleBMDefaultTeamIfNeeded(c, teamNames, appleBMDefaultTeam, flDryRun, fleetClient)
- if err != nil {
+
+ // if there were assignments to tokens, and some of the teams were missing at that time, submit a separate patch request to set them now.
+ if len(abmTeams) > 0 && hasMissingABMTeam {
+ if err = applyABMTokenAssignmentIfNeeded(c, teamNames, abmTeams, originalABMConfig, usesLegacyABMConfig, flDryRun, fleetClient); err != nil {
+ return err
+ }
+ }
+ if len(vppTeams) > 0 && hasMissingVPPTeam {
+ if err = applyVPPTokenAssignmentIfNeeded(c, teamNames, vppTeams, originalVPPConfig, flDryRun, fleetClient); err != nil {
return err
}
}
@@ -149,8 +201,14 @@ func gitopsCommand() *cli.Command {
}
for _, team := range teams {
if !slices.Contains(teamNames, team.Name) {
- if appleBMDefaultTeam == team.Name {
- return fmt.Errorf("apple_bm_default_team %s cannot be deleted", appleBMDefaultTeam)
+ if slices.Contains(abmTeams, team.Name) {
+ if usesLegacyABMConfig {
+ return fmt.Errorf("apple_bm_default_team %s cannot be deleted", team.Name)
+ }
+ return fmt.Errorf("apple_business_manager team %s cannot be deleted", team.Name)
+ }
+ if slices.Contains(vppTeams, team.Name) {
+ return fmt.Errorf("volume_purchasing_program team %s cannot be deleted", team.Name)
}
if flDryRun {
_, _ = fmt.Fprintf(c.App.Writer, "[!] would delete team %s\n", team.Name)
@@ -174,54 +232,194 @@ func gitopsCommand() *cli.Command {
}
}
-func checkAppleBMDefaultTeam(config *spec.GitOps, fleetClient *service.Client) (
- appleBMDefaultTeam string, appleBMDefaultTeamFound bool, err error,
+// checkABMTeamAssignments validates the spec, and finds if:
+//
+// 1. The user is using the legacy apple_bm_default_team config.
+// 2. All teams assigned to ABM tokens already exist.
+// 3. Performs validations according to the spec for both the new and the
+// deprecated key used for this setting.
+func checkABMTeamAssignments(config *spec.GitOps, fleetClient *service.Client) (
+ abmTeams []string, missingTeam bool, usesLegacyConfig bool, err error,
) {
if mdm, ok := config.OrgSettings["mdm"]; ok {
- if mdmMap, ok := mdm.(map[string]interface{}); ok {
- if appleBMDT, ok := mdmMap["apple_bm_default_team"]; ok {
- if appleBMDefaultTeam, ok = appleBMDT.(string); ok {
- teams, err := fleetClient.ListTeams("")
- if err != nil {
- return "", false, err
- }
- // Normalize AppleBMDefaultTeam for Unicode support
+ if mdmMap, ok := mdm.(map[string]any); ok {
+ appleBMDT, hasLegacyConfig := mdmMap["apple_bm_default_team"]
+ appleBM, hasNewConfig := mdmMap["apple_business_manager"]
+
+ if hasLegacyConfig && hasNewConfig {
+ return nil, false, false, errors.New(fleet.AppleABMDefaultTeamDeprecatedMessage)
+ }
+
+ if !hasLegacyConfig && !hasNewConfig {
+ return nil, false, false, nil
+ }
+
+ teams, err := fleetClient.ListTeams("")
+ if err != nil {
+ return nil, false, false, err
+ }
+ teamNames := map[string]struct{}{}
+ for _, tm := range teams {
+ teamNames[tm.Name] = struct{}{}
+ }
+
+ if hasLegacyConfig {
+ if appleBMDefaultTeam, ok := appleBMDT.(string); ok {
+ // normalize for Unicode support
appleBMDefaultTeam = norm.NFC.String(appleBMDefaultTeam)
- for _, team := range teams {
- if team.Name == appleBMDefaultTeam {
- appleBMDefaultTeamFound = true
- break
+ abmTeams = append(abmTeams, appleBMDefaultTeam)
+ usesLegacyConfig = true
+ if _, ok = teamNames[appleBMDefaultTeam]; !ok {
+ missingTeam = true
+ }
+ }
+ }
+
+ if hasNewConfig {
+ if settingMap, ok := appleBM.([]any); ok {
+ for _, item := range settingMap {
+ if cfg, ok := item.(map[string]any); ok {
+ for _, teamConfigKey := range []string{"macos_team", "ios_team", "ipados_team"} {
+ if team, ok := cfg[teamConfigKey].(string); ok && team != "" {
+ // normalize for Unicode support
+ team = norm.NFC.String(team)
+ abmTeams = append(abmTeams, team)
+ if _, ok := teamNames[team]; !ok {
+ missingTeam = true
+ }
+ }
+ }
}
}
- if !appleBMDefaultTeamFound {
- // If team is not found, we need to remove the AppleBMDefaultTeam from the global config, and then apply it after teams are processed
- mdmMap["apple_bm_default_team"] = ""
+ }
+ }
+ }
+ }
+
+ return abmTeams, missingTeam, usesLegacyConfig, nil
+}
+
+func applyABMTokenAssignmentIfNeeded(
+ ctx *cli.Context,
+ teamNames []string,
+ abmTeamNames []string,
+ originalMDMConfig []any,
+ usesLegacyConfig bool,
+ flDryRun bool,
+ fleetClient *service.Client,
+) error {
+ if usesLegacyConfig && len(abmTeamNames) > 1 {
+ return errors.New(fleet.AppleABMDefaultTeamDeprecatedMessage)
+ }
+
+ if usesLegacyConfig && len(abmTeamNames) == 0 {
+ return errors.New("using legacy config without any ABM teams defined")
+ }
+
+ var appConfigUpdate map[string]map[string]any
+ if usesLegacyConfig {
+ appleBMDefaultTeam := abmTeamNames[0]
+ if !slices.Contains(teamNames, appleBMDefaultTeam) {
+ return fmt.Errorf("apple_bm_default_team %s not found in team configs", appleBMDefaultTeam)
+ }
+ appConfigUpdate = map[string]map[string]any{
+ "mdm": {
+ "apple_bm_default_team": appleBMDefaultTeam,
+ },
+ }
+ } else {
+ for _, abmTeam := range abmTeamNames {
+ if !slices.Contains(teamNames, abmTeam) {
+ return fmt.Errorf("apple_business_manager team %s not found in team configs", abmTeam)
+ }
+ }
+
+ appConfigUpdate = map[string]map[string]any{
+ "mdm": {
+ "apple_business_manager": originalMDMConfig,
+ },
+ }
+ }
+
+ if flDryRun {
+ _, _ = fmt.Fprint(ctx.App.Writer, "[!] would apply ABM teams\n")
+ return nil
+ }
+ _, _ = fmt.Fprintf(ctx.App.Writer, "[+] applying ABM teams\n")
+ if err := fleetClient.ApplyAppConfig(appConfigUpdate, fleet.ApplySpecOptions{}); err != nil {
+ return fmt.Errorf("applying fleet config: %w", err)
+ }
+ return nil
+}
+
+func checkVPPTeamAssignments(config *spec.GitOps, fleetClient *service.Client) (
+ vppTeams []string, missingTeam bool, err error,
+) {
+ if mdm, ok := config.OrgSettings["mdm"]; ok {
+ if mdmMap, ok := mdm.(map[string]any); ok {
+ teams, err := fleetClient.ListTeams("")
+ if err != nil {
+ return nil, false, err
+ }
+ teamNames := map[string]struct{}{}
+ for _, tm := range teams {
+ teamNames[tm.Name] = struct{}{}
+ }
+
+ if vpp, ok := mdmMap["volume_purchasing_program"]; ok {
+ if vppInterfaces, ok := vpp.([]any); ok {
+ for _, item := range vppInterfaces {
+ if itemMap, ok := item.(map[string]any); ok {
+ if teams, ok := itemMap["teams"].([]any); ok {
+ for _, team := range teams {
+ if teamStr, ok := team.(string); ok {
+ // normalize for Unicode support
+ normalizedTeam := norm.NFC.String(teamStr)
+ vppTeams = append(vppTeams, normalizedTeam)
+ if _, ok := teamNames[normalizedTeam]; !ok {
+ missingTeam = true
+ }
+ }
+ }
+ }
+ }
}
}
}
}
}
- return appleBMDefaultTeam, appleBMDefaultTeamFound, nil
+
+ return vppTeams, missingTeam, nil
}
-func applyAppleBMDefaultTeamIfNeeded(
- ctx *cli.Context, teamNames []string, appleBMDefaultTeam string, flDryRun bool, fleetClient *service.Client,
+func applyVPPTokenAssignmentIfNeeded(
+ ctx *cli.Context,
+ teamNames []string,
+ vppTeamNames []string,
+ originalVPPConfig []any,
+ flDryRun bool,
+ fleetClient *service.Client,
) error {
- if !slices.Contains(teamNames, appleBMDefaultTeam) {
- return fmt.Errorf("apple_bm_default_team %s not found in team configs", appleBMDefaultTeam)
+ var appConfigUpdate map[string]map[string]any
+ for _, vppTeam := range vppTeamNames {
+ if !fleet.IsReservedTeamName(vppTeam) && !slices.Contains(teamNames, vppTeam) {
+ return fmt.Errorf("volume_purchasing_program team %s not found in team configs", vppTeam)
+ }
}
- appConfigUpdate := map[string]map[string]interface{}{
+
+ appConfigUpdate = map[string]map[string]any{
"mdm": {
- "apple_bm_default_team": appleBMDefaultTeam,
+ "volume_purchasing_program": originalVPPConfig,
},
}
+
if flDryRun {
- _, _ = fmt.Fprintf(ctx.App.Writer, "[!] would apply apple_bm_default_team %s\n", appleBMDefaultTeam)
- } else {
- _, _ = fmt.Fprintf(ctx.App.Writer, "[+] applying apple_bm_default_team %s\n", appleBMDefaultTeam)
- if err := fleetClient.ApplyAppConfig(appConfigUpdate, fleet.ApplySpecOptions{}); err != nil {
- return fmt.Errorf("applying fleet config: %w", err)
- }
+ _, _ = fmt.Fprint(ctx.App.Writer, "[!] would apply volume_purchasing_program teams\n")
+ return nil
+ }
+ _, _ = fmt.Fprintf(ctx.App.Writer, "[+] applying volume_purchasing_program teams\n")
+ if err := fleetClient.ApplyAppConfig(appConfigUpdate, fleet.ApplySpecOptions{}); err != nil {
+ return fmt.Errorf("applying fleet config for volume_purchasing_program teams: %w", err)
}
return nil
}
diff --git a/cmd/fleetctl/gitops_enterprise_integration_test.go b/cmd/fleetctl/gitops_enterprise_integration_test.go
index ab3f8bf83561..235d89dca82a 100644
--- a/cmd/fleetctl/gitops_enterprise_integration_test.go
+++ b/cmd/fleetctl/gitops_enterprise_integration_test.go
@@ -23,7 +23,7 @@ import (
"github.com/stretchr/testify/suite"
)
-func TestEnterpriseIntegrationsGitops(t *testing.T) {
+func TestIntegrationsEnterpriseGitops(t *testing.T) {
testingSuite := new(enterpriseIntegrationGitopsTestSuite)
testingSuite.suite = &testingSuite.Suite
suite.Run(t, testingSuite)
@@ -176,6 +176,7 @@ contexts:
fmt.Sprintf(
`
controls:
+software:
queries:
policies:
agent_options:
@@ -186,6 +187,9 @@ team_settings:
),
)
require.NoError(t, err)
+
+ test.CreateInsertGlobalVPPToken(t, s.ds)
+
// Apply the team to be deleted
_ = runAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", deletedTeamFile.Name()})
@@ -230,5 +234,4 @@ team_settings:
for _, fileName := range teamFileNames {
_ = runAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", fileName})
}
-
}
diff --git a/cmd/fleetctl/gitops_test.go b/cmd/fleetctl/gitops_test.go
index d59af1bd5a7e..58fb94b1c62a 100644
--- a/cmd/fleetctl/gitops_test.go
+++ b/cmd/fleetctl/gitops_test.go
@@ -2,6 +2,7 @@ package main
import (
"context"
+ "encoding/json"
"fmt"
"net/http"
"net/http/httptest"
@@ -16,11 +17,13 @@ import (
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
"github.com/fleetdm/fleet/v4/server/fleet"
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
+ "github.com/fleetdm/fleet/v4/server/mdm/apple/vpp"
"github.com/fleetdm/fleet/v4/server/mdm/nanodep/tokenpki"
"github.com/fleetdm/fleet/v4/server/mock"
mdmmock "github.com/fleetdm/fleet/v4/server/mock/mdm"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/service"
+ "github.com/fleetdm/fleet/v4/server/test"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -32,13 +35,13 @@ const (
orgName = "GitOps Test"
)
-func TestFilenameValidation(t *testing.T) {
+func TestGitOpsFilenameValidation(t *testing.T) {
filename := strings.Repeat("a", filenameMaxLength+1)
_, err := runAppNoChecks([]string{"gitops", "-f", filename})
assert.ErrorContains(t, err, "file name must be less than")
}
-func TestBasicGlobalGitOps(t *testing.T) {
+func TestGitOpsBasicGlobalFree(t *testing.T) {
// Cannot run t.Parallel() because it sets environment variables
_, ds := runServerWithMockedDS(t)
@@ -46,13 +49,13 @@ func TestBasicGlobalGitOps(t *testing.T) {
ds.BatchSetMDMProfilesFunc = func(
ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile,
macDecls []*fleet.MDMAppleDeclaration,
- ) error {
- return nil
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(
ctx context.Context, hostIDs []uint, teamIDs []uint, profileUUIDs []string, hostUUIDs []string,
- ) error {
- return nil
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.BatchSetScriptsFunc = func(ctx context.Context, tmID *uint, scripts []*fleet.Script) error { return nil }
ds.NewActivityFunc = func(
@@ -150,7 +153,111 @@ org_settings:
assert.Empty(t, enrolledSecrets)
}
-func TestBasicTeamGitOps(t *testing.T) {
+func TestGitOpsBasicGlobalPremium(t *testing.T) {
+ // Cannot run t.Parallel() because it sets environment variables
+
+ license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)}
+ _, ds := runServerWithMockedDS(
+ t, &service.TestServerOpts{
+ License: license,
+ },
+ )
+
+ ds.BatchSetMDMProfilesFunc = func(
+ ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile,
+ macDecls []*fleet.MDMAppleDeclaration,
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
+ }
+ ds.BulkSetPendingMDMHostProfilesFunc = func(
+ ctx context.Context, hostIDs []uint, teamIDs []uint, profileUUIDs []string, hostUUIDs []string,
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
+ }
+ ds.BatchSetScriptsFunc = func(ctx context.Context, tmID *uint, scripts []*fleet.Script) error { return nil }
+ ds.NewActivityFunc = func(
+ ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
+ ) error {
+ return nil
+ }
+ ds.ListGlobalPoliciesFunc = func(ctx context.Context, opts fleet.ListOptions) ([]*fleet.Policy, error) { return nil, nil }
+ ds.ListQueriesFunc = func(ctx context.Context, opts fleet.ListQueryOptions) ([]*fleet.Query, error) { return nil, nil }
+
+ // Mock appConfig
+ savedAppConfig := &fleet.AppConfig{}
+ ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
+ return &fleet.AppConfig{}, nil
+ }
+ ds.SaveAppConfigFunc = func(ctx context.Context, config *fleet.AppConfig) error {
+ savedAppConfig = config
+ return nil
+ }
+ var enrolledSecrets []*fleet.EnrollSecret
+ ds.ApplyEnrollSecretsFunc = func(ctx context.Context, teamID *uint, secrets []*fleet.EnrollSecret) error {
+ enrolledSecrets = secrets
+ return nil
+ }
+ ds.LabelIDsByNameFunc = func(ctx context.Context, labels []string) (map[string]uint, error) {
+ return map[string]uint{labels[0]: 1}, nil
+ }
+ ds.SetOrUpdateMDMAppleDeclarationFunc = func(ctx context.Context, declaration *fleet.MDMAppleDeclaration) (*fleet.MDMAppleDeclaration, error) {
+ return &fleet.MDMAppleDeclaration{}, nil
+ }
+ ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) {
+ return &fleet.Job{}, nil
+ }
+ ds.BatchSetSoftwareInstallersFunc = func(ctx context.Context, teamID *uint, installers []*fleet.UploadSoftwareInstallerPayload) ([]fleet.SoftwareInstaller, error) {
+ return nil, nil
+ }
+
+ tmpFile, err := os.CreateTemp(t.TempDir(), "*.yml")
+ require.NoError(t, err)
+
+ const (
+ fleetServerURL = "https://fleet.example.com"
+ orgName = "GitOps Premium Test"
+ )
+ t.Setenv("FLEET_SERVER_URL", fleetServerURL)
+
+ _, err = tmpFile.WriteString(
+ `
+controls:
+ ios_updates:
+ deadline: "2022-02-02"
+ minimum_version: "17.6"
+ ipados_updates:
+ deadline: "2023-03-03"
+ minimum_version: "18.0"
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets:
+software:
+`,
+ )
+ require.NoError(t, err)
+
+ // Dry run
+ t.Setenv("ORG_NAME", orgName)
+ _ = runAppForTest(t, []string{"gitops", "-f", tmpFile.Name(), "--dry-run"})
+ assert.Equal(t, fleet.AppConfig{}, *savedAppConfig, "AppConfig should be empty")
+
+ // Real run
+ _ = runAppForTest(t, []string{"gitops", "-f", tmpFile.Name()})
+ assert.Equal(t, orgName, savedAppConfig.OrgInfo.OrgName)
+ assert.Equal(t, fleetServerURL, savedAppConfig.ServerSettings.ServerURL)
+ assert.Empty(t, enrolledSecrets)
+}
+
+func TestGitOpsBasicTeam(t *testing.T) {
// Cannot run t.Parallel() because it sets environment variables
license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)}
_, ds := runServerWithMockedDS(
@@ -161,16 +268,22 @@ func TestBasicTeamGitOps(t *testing.T) {
const secret = "TestSecret"
+ ds.SetTeamVPPAppsFunc = func(ctx context.Context, teamID *uint, adamIDs []fleet.VPPAppTeam) error {
+ return nil
+ }
+ ds.BatchInsertVPPAppsFunc = func(ctx context.Context, apps []*fleet.VPPApp) error {
+ return nil
+ }
ds.BatchSetScriptsFunc = func(ctx context.Context, tmID *uint, scripts []*fleet.Script) error { return nil }
ds.BatchSetMDMProfilesFunc = func(
ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile, macDecls []*fleet.MDMAppleDeclaration,
- ) error {
- return nil
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(
ctx context.Context, hostIDs []uint, teamIDs []uint, profileUUIDs []string, hostUUIDs []string,
- ) error {
- return nil
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.NewActivityFunc = func(
ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time,
@@ -222,20 +335,37 @@ func TestBasicTeamGitOps(t *testing.T) {
return team, nil
}
ds.LabelIDsByNameFunc = func(ctx context.Context, labels []string) (map[string]uint, error) {
- require.ElementsMatch(t, labels, []string{fleet.BuiltinLabelMacOS14Plus})
- return map[string]uint{fleet.BuiltinLabelMacOS14Plus: 1}, nil
+ require.Len(t, labels, 1)
+ switch labels[0] {
+ case fleet.BuiltinLabelMacOS14Plus:
+ return map[string]uint{fleet.BuiltinLabelMacOS14Plus: 1}, nil
+ case fleet.BuiltinLabelIOS:
+ return map[string]uint{fleet.BuiltinLabelIOS: 2}, nil
+ case fleet.BuiltinLabelIPadOS:
+ return map[string]uint{fleet.BuiltinLabelIPadOS: 3}, nil
+ default:
+ return nil, ¬FoundError{}
+ }
}
ds.DeleteMDMAppleDeclarationByNameFunc = func(ctx context.Context, teamID *uint, name string) error {
return nil
}
- ds.BatchSetSoftwareInstallersFunc = func(ctx context.Context, teamID *uint, installers []*fleet.UploadSoftwareInstallerPayload) error {
- return nil
+ ds.BatchSetSoftwareInstallersFunc = func(ctx context.Context, teamID *uint, installers []*fleet.UploadSoftwareInstallerPayload) ([]fleet.SoftwareInstaller, error) {
+ return nil, nil
}
-
ds.ApplyEnrollSecretsFunc = func(ctx context.Context, teamID *uint, secrets []*fleet.EnrollSecret) error {
enrolledTeamSecrets = secrets
return nil
}
+ ds.SetOrUpdateMDMAppleDeclarationFunc = func(ctx context.Context, declaration *fleet.MDMAppleDeclaration) (*fleet.MDMAppleDeclaration, error) {
+ return &fleet.MDMAppleDeclaration{}, nil
+ }
+ ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) {
+ return &fleet.Job{}, nil
+ }
+ ds.ListSoftwareTitlesFunc = func(ctx context.Context, opt fleet.SoftwareTitleListOptions, tmFilter fleet.TeamFilter) ([]fleet.SoftwareTitleListResult, int, *fleet.PaginationMetadata, error) {
+ return nil, 0, nil, nil
+ }
tmpFile, err := os.CreateTemp(t.TempDir(), "*.yml")
require.NoError(t, err)
@@ -245,12 +375,19 @@ func TestBasicTeamGitOps(t *testing.T) {
_, err = tmpFile.WriteString(
`
controls:
+ ios_updates:
+ deadline: "2024-10-10"
+ minimum_version: "18.0"
+ ipados_updates:
+ deadline: "2025-11-11"
+ minimum_version: "17.6"
queries:
policies:
agent_options:
name: ${TEST_TEAM_NAME}
team_settings:
secrets: ${TEST_SECRET}
+software:
`,
)
require.NoError(t, err)
@@ -261,6 +398,27 @@ team_settings:
require.Error(t, err)
assert.Contains(t, err.Error(), "'name' is required")
+ // reserved team name; should error in both dry run and real
+ t.Setenv("TEST_TEAM_NAME", "no TEam")
+ _, err = runAppNoChecks([]string{"gitops", "-f", tmpFile.Name(), "--dry-run"})
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), `"No team" is a reserved team name`)
+
+ t.Setenv("TEST_TEAM_NAME", "no TEam")
+ _, err = runAppNoChecks([]string{"gitops", "-f", tmpFile.Name()})
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), `"No team" is a reserved team name`)
+
+ t.Setenv("TEST_TEAM_NAME", "All teams")
+ _, err = runAppNoChecks([]string{"gitops", "-f", tmpFile.Name(), "--dry-run"})
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), `"All teams" is a reserved team name`)
+
+ t.Setenv("TEST_TEAM_NAME", "All TEAMS")
+ _, err = runAppNoChecks([]string{"gitops", "-f", tmpFile.Name()})
+ require.Error(t, err)
+ assert.Contains(t, err.Error(), `"All teams" is a reserved team name`)
+
// Dry run
t.Setenv("TEST_TEAM_NAME", teamName)
_ = runAppForTest(t, []string{"gitops", "-f", tmpFile.Name(), "--dry-run"})
@@ -283,7 +441,7 @@ team_settings:
assert.Equal(t, secret, enrolledTeamSecrets[0].Secret)
}
-func TestFullGlobalGitOps(t *testing.T) {
+func TestGitOpsFullGlobal(t *testing.T) {
// Cannot run t.Parallel() because it sets environment variables
// mdm test configuration must be set so that activating windows MDM works.
testCert, testKey, err := apple_mdm.NewSCEPCACertKey()
@@ -316,13 +474,14 @@ func TestFullGlobalGitOps(t *testing.T) {
var appliedWinProfiles []*fleet.MDMWindowsConfigProfile
ds.BatchSetMDMProfilesFunc = func(
ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile, macDecls []*fleet.MDMAppleDeclaration,
- ) error {
+ ) (updates fleet.MDMProfilesUpdates, err error) {
appliedMacProfiles = macProfiles
appliedWinProfiles = winProfiles
- return nil
+ return fleet.MDMProfilesUpdates{}, nil
}
- ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string) error {
- return nil
+ ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string,
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) {
return job, nil
@@ -407,16 +566,10 @@ func TestFullGlobalGitOps(t *testing.T) {
)
t.Setenv("FLEET_SERVER_URL", fleetServerURL)
t.Setenv("ORG_NAME", orgName)
- t.Setenv("APPLE_BM_DEFAULT_TEAM", teamName)
+ t.Setenv("SOFTWARE_INSTALLER_URL", fleetServerURL)
file := "./testdata/gitops/global_config_no_paths.yml"
- // Dry run should fail because Apple BM Default Team does not exist and premium license is not set
- _, err = runAppNoChecks([]string{"gitops", "-f", file, "--dry-run"})
- require.Error(t, err)
- assert.True(t, strings.Contains(err.Error(), "missing or invalid license"))
-
// Dry run
- t.Setenv("APPLE_BM_DEFAULT_TEAM", "")
_ = runAppForTest(t, []string{"gitops", "-f", file, "--dry-run"})
assert.Equal(t, fleet.AppConfig{}, *savedAppConfig, "AppConfig should be empty")
assert.Len(t, enrolledSecrets, 0)
@@ -449,7 +602,7 @@ func TestFullGlobalGitOps(t *testing.T) {
assert.Equal(t, "https://activities_webhook_url", savedAppConfig.WebhookSettings.ActivitiesWebhook.DestinationURL)
}
-func TestFullTeamGitOps(t *testing.T) {
+func TestGitOpsFullTeam(t *testing.T) {
// Cannot run t.Parallel() because it sets environment variables
license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)}
@@ -497,13 +650,14 @@ func TestFullTeamGitOps(t *testing.T) {
var appliedWinProfiles []*fleet.MDMWindowsConfigProfile
ds.BatchSetMDMProfilesFunc = func(
ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile, macDecls []*fleet.MDMAppleDeclaration,
- ) error {
+ ) (updates fleet.MDMProfilesUpdates, err error) {
appliedMacProfiles = macProfiles
appliedWinProfiles = winProfiles
- return nil
+ return fleet.MDMProfilesUpdates{}, nil
}
- ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string) error {
- return nil
+ ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string,
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) {
return job, nil
@@ -529,6 +683,9 @@ func TestFullTeamGitOps(t *testing.T) {
// Team
var savedTeam *fleet.Team
ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) {
+ if name == "Conflict" {
+ return &fleet.Team{}, nil
+ }
if savedTeam != nil && savedTeam.Name == name {
return savedTeam, nil
}
@@ -624,14 +781,22 @@ func TestFullTeamGitOps(t *testing.T) {
appliedQueries = queries
return nil
}
- ds.BatchSetSoftwareInstallersFunc = func(ctx context.Context, teamID *uint, installers []*fleet.UploadSoftwareInstallerPayload) error {
+ ds.BatchSetSoftwareInstallersFunc = func(ctx context.Context, teamID *uint, installers []*fleet.UploadSoftwareInstallerPayload) ([]fleet.SoftwareInstaller, error) {
+ return nil, nil
+ }
+ ds.SetTeamVPPAppsFunc = func(ctx context.Context, teamID *uint, adamIDs []fleet.VPPAppTeam) error {
+ return nil
+ }
+ ds.BatchInsertVPPAppsFunc = func(ctx context.Context, apps []*fleet.VPPApp) error {
return nil
}
-
ds.ApplyEnrollSecretsFunc = func(ctx context.Context, teamID *uint, secrets []*fleet.EnrollSecret) error {
enrolledSecrets = secrets
return nil
}
+ ds.ListSoftwareTitlesFunc = func(ctx context.Context, opt fleet.SoftwareTitleListOptions, tmFilter fleet.TeamFilter) ([]fleet.SoftwareTitleListResult, int, *fleet.PaginationMetadata, error) {
+ return nil, 0, nil, nil
+ }
startSoftwareInstallerServer(t)
@@ -684,7 +849,15 @@ func TestFullTeamGitOps(t *testing.T) {
assert.Equal(t, newTeamName, savedTeam.Name)
assert.Equal(t, baseFilename, *savedTeam.Filename)
+ // Try to change team name again, but this time the new name conflicts with an existing team
+ t.Setenv("TEST_TEAM_NAME", "Conflict")
+ _, err = runAppNoChecks([]string{"gitops", "-f", file, "--dry-run"})
+ assert.ErrorContains(t, err, "team name already exists")
+ _, err = runAppNoChecks([]string{"gitops", "-f", file})
+ assert.ErrorContains(t, err, "team name already exists")
+
// Now clear the settings
+ t.Setenv("TEST_TEAM_NAME", newTeamName)
tmpFile, err := os.CreateTemp(t.TempDir(), "*.yml")
require.NoError(t, err)
secret := "TestSecret"
@@ -699,6 +872,7 @@ agent_options:
name: ${TEST_TEAM_NAME}
team_settings:
secrets: [{"secret":"${TEST_SECRET}"}]
+software:
`,
)
require.NoError(t, err)
@@ -726,10 +900,9 @@ team_settings:
assert.Empty(t, savedTeam.Config.MDM.MacOSSetup.BootstrapPackage.Value)
assert.False(t, savedTeam.Config.MDM.EnableDiskEncryption)
assert.Equal(t, filepath.Base(tmpFile.Name()), *savedTeam.Filename)
-
}
-func TestBasicGlobalAndTeamGitOps(t *testing.T) {
+func TestGitOpsBasicGlobalAndTeam(t *testing.T) {
// Cannot run t.Parallel() because it sets environment variables
license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)}
_, ds := runServerWithMockedDS(
@@ -748,6 +921,13 @@ func TestBasicGlobalAndTeamGitOps(t *testing.T) {
return nil
}
+ ds.SetTeamVPPAppsFunc = func(ctx context.Context, teamID *uint, adamIDs []fleet.VPPAppTeam) error {
+ return nil
+ }
+ ds.BatchInsertVPPAppsFunc = func(ctx context.Context, apps []*fleet.VPPApp) error {
+ return nil
+ }
+
const (
fleetServerURL = "https://fleet.example.com"
orgName = "GitOps Test"
@@ -776,10 +956,10 @@ func TestBasicGlobalAndTeamGitOps(t *testing.T) {
ds.BatchSetMDMProfilesFunc = func(
ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile,
macDecls []*fleet.MDMAppleDeclaration,
- ) error {
+ ) (updates fleet.MDMProfilesUpdates, err error) {
assert.Empty(t, macProfiles)
assert.Empty(t, winProfiles)
- return nil
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.BatchSetScriptsFunc = func(ctx context.Context, tmID *uint, scripts []*fleet.Script) error {
assert.Empty(t, scripts)
@@ -787,9 +967,9 @@ func TestBasicGlobalAndTeamGitOps(t *testing.T) {
}
ds.BulkSetPendingMDMHostProfilesFunc = func(
ctx context.Context, hostIDs []uint, teamIDs []uint, profileUUIDs []string, hostUUIDs []string,
- ) error {
+ ) (updates fleet.MDMProfilesUpdates, err error) {
assert.Empty(t, profileUUIDs)
- return nil
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.DeleteMDMAppleDeclarationByNameFunc = func(ctx context.Context, teamID *uint, name string) error {
return nil
@@ -845,8 +1025,11 @@ func TestBasicGlobalAndTeamGitOps(t *testing.T) {
savedTeam = team
return team, nil
}
- ds.BatchSetSoftwareInstallersFunc = func(ctx context.Context, teamID *uint, installers []*fleet.UploadSoftwareInstallerPayload) error {
- return nil
+ ds.BatchSetSoftwareInstallersFunc = func(ctx context.Context, teamID *uint, installers []*fleet.UploadSoftwareInstallerPayload) ([]fleet.SoftwareInstaller, error) {
+ return nil, nil
+ }
+ ds.ListSoftwareTitlesFunc = func(ctx context.Context, opt fleet.SoftwareTitleListOptions, tmFilter fleet.TeamFilter) ([]fleet.SoftwareTitleListResult, int, *fleet.PaginationMetadata, error) {
+ return nil, 0, nil, nil
}
globalFile, err := os.CreateTemp(t.TempDir(), "*.yml")
@@ -870,6 +1053,7 @@ org_settings:
org_logo_url_light_background: ""
org_name: ${ORG_NAME}
secrets: [{"secret":"globalSecret"}]
+software:
`,
)
require.NoError(t, err)
@@ -889,6 +1073,7 @@ agent_options:
name: ${TEST_TEAM_NAME}
team_settings:
secrets: [{"secret":"${TEST_SECRET}"}]
+software:
`,
)
require.NoError(t, err)
@@ -904,6 +1089,7 @@ agent_options:
name: ${TEST_TEAM_NAME}
team_settings:
secrets: [{"secret":"${TEST_SECRET}"},{"secret":"globalSecret"}]
+software:
`,
)
require.NoError(t, err)
@@ -978,10 +1164,10 @@ team_settings:
assert.True(t, ds.DeleteTeamFuncInvoked)
}
-func TestFullGlobalAndTeamGitOps(t *testing.T) {
+func TestGitOpsFullGlobalAndTeam(t *testing.T) {
// Cannot run t.Parallel() because it sets environment variables
// mdm test configuration must be set so that activating windows MDM works.
- ds, savedAppConfigPtr, savedTeamPtr := setupFullGitOpsPremiumServer(t)
+ ds, savedAppConfigPtr, savedTeams := setupFullGitOpsPremiumServer(t)
startSoftwareInstallerServer(t)
var enrolledSecrets []*fleet.EnrollSecret
@@ -1009,9 +1195,9 @@ func TestFullGlobalAndTeamGitOps(t *testing.T) {
}
ds.NewTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) {
team.ID = 1
- *savedTeamPtr = team
enrolledTeamSecrets = team.Secrets
- return *savedTeamPtr, nil
+ savedTeams[team.Name] = &team
+ return team, nil
}
apnsCert, apnsKey, err := mysql.GenerateTestCertBytes()
@@ -1030,14 +1216,16 @@ func TestFullGlobalAndTeamGitOps(t *testing.T) {
}, nil
}
+ ds.SetTeamVPPAppsFunc = func(ctx context.Context, teamID *uint, adamIDs []fleet.VPPAppTeam) error {
+ return nil
+ }
+ ds.BatchInsertVPPAppsFunc = func(ctx context.Context, apps []*fleet.VPPApp) error {
+ return nil
+ }
+
globalFile := "./testdata/gitops/global_config_no_paths.yml"
teamFile := "./testdata/gitops/team_config_no_paths.yml"
- // Dry run on global file should fail because Apple BM Default Team does not exist (and has not been provided)
- _, err = runAppNoChecks([]string{"gitops", "-f", globalFile, "--dry-run"})
- require.Error(t, err)
- assert.True(t, strings.Contains(err.Error(), "team name not found"))
-
// Dry run
_ = runAppForTest(t, []string{"gitops", "-f", globalFile, "-f", teamFile, "--dry-run", "--delete-other-teams"})
assert.False(t, ds.SaveAppConfigFuncInvoked)
@@ -1051,12 +1239,12 @@ func TestFullGlobalAndTeamGitOps(t *testing.T) {
assert.Equal(t, orgName, (*savedAppConfigPtr).OrgInfo.OrgName)
assert.Equal(t, fleetServerURL, (*savedAppConfigPtr).ServerSettings.ServerURL)
assert.Len(t, enrolledSecrets, 2)
- require.NotNil(t, *savedTeamPtr)
- assert.Equal(t, teamName, (*savedTeamPtr).Name)
+ require.NotNil(t, *savedTeams[teamName])
+ assert.Equal(t, teamName, (*savedTeams[teamName]).Name)
require.Len(t, enrolledTeamSecrets, 2)
}
-func TestTeamSofwareInstallersGitOps(t *testing.T) {
+func TestGitOpsTeamSofwareInstallers(t *testing.T) {
startSoftwareInstallerServer(t)
cases := []struct {
@@ -1067,12 +1255,14 @@ func TestTeamSofwareInstallersGitOps(t *testing.T) {
{"testdata/gitops/team_software_installer_unsupported.yml", "The file should be .pkg, .msi, .exe or .deb."},
{"testdata/gitops/team_software_installer_too_large.yml", "The maximum file size is 500 MB"},
{"testdata/gitops/team_software_installer_valid.yml", ""},
+ {"testdata/gitops/team_software_installer_valid_apply.yml", ""},
{"testdata/gitops/team_software_installer_pre_condition_multiple_queries.yml", "should have only one query."},
+ {"testdata/gitops/team_software_installer_pre_condition_multiple_queries_apply.yml", "should have only one query."},
{"testdata/gitops/team_software_installer_pre_condition_not_found.yml", "no such file or directory"},
{"testdata/gitops/team_software_installer_install_not_found.yml", "no such file or directory"},
{"testdata/gitops/team_software_installer_post_install_not_found.yml", "no such file or directory"},
{"testdata/gitops/team_software_installer_no_url.yml", "software URL is required"},
- {"testdata/gitops/team_software_installer_invalid_self_service_value.yml", "cannot unmarshal string into Go struct field TeamSpecSoftware.self_service of type bool"},
+ {"testdata/gitops/team_software_installer_invalid_self_service_value.yml", "\"packages.self_service\" must be a bool, found string"},
}
for _, c := range cases {
t.Run(filepath.Base(c.file), func(t *testing.T) {
@@ -1088,7 +1278,150 @@ func TestTeamSofwareInstallersGitOps(t *testing.T) {
}
}
-func TestCustomSettingsGitOps(t *testing.T) {
+func TestGitOpsTeamSoftwareInstallersQueryEnv(t *testing.T) {
+ startSoftwareInstallerServer(t)
+ ds, _, _ := setupFullGitOpsPremiumServer(t)
+
+ t.Setenv("QUERY_VAR", "IT_WORKS")
+
+ ds.BatchSetSoftwareInstallersFunc = func(ctx context.Context, tmID *uint, installers []*fleet.UploadSoftwareInstallerPayload) ([]fleet.SoftwareInstaller, error) {
+ if installers[0].PreInstallQuery != "select IT_WORKS" {
+ return nil, fmt.Errorf("Missing env var, got %s", installers[0].PreInstallQuery)
+ }
+ return nil, nil
+ }
+
+ _, err := runAppNoChecks([]string{"gitops", "-f", "testdata/gitops/team_software_installer_valid_env_query.yml"})
+ require.NoError(t, err)
+}
+
+func TestGitOpsNoTeamSoftwareInstallers(t *testing.T) {
+ startSoftwareInstallerServer(t)
+
+ cases := []struct {
+ file string
+ wantErr string
+ }{
+ {"testdata/gitops/no_team_software_installer_not_found.yml", "Please make sure that URLs are publicy accessible to the internet."},
+ {"testdata/gitops/no_team_software_installer_unsupported.yml", "The file should be .pkg, .msi, .exe or .deb."},
+ {"testdata/gitops/no_team_software_installer_too_large.yml", "The maximum file size is 500 MB"},
+ {"testdata/gitops/no_team_software_installer_valid.yml", ""},
+ {"testdata/gitops/no_team_software_installer_pre_condition_multiple_queries.yml", "should have only one query."},
+ {"testdata/gitops/no_team_software_installer_pre_condition_not_found.yml", "no such file or directory"},
+ {"testdata/gitops/no_team_software_installer_install_not_found.yml", "no such file or directory"},
+ {"testdata/gitops/no_team_software_installer_post_install_not_found.yml", "no such file or directory"},
+ {"testdata/gitops/no_team_software_installer_no_url.yml", "software URL is required"},
+ {"testdata/gitops/no_team_software_installer_invalid_self_service_value.yml", "\"packages.self_service\" must be a bool, found string"},
+ }
+ for _, c := range cases {
+ t.Run(filepath.Base(c.file), func(t *testing.T) {
+ setupFullGitOpsPremiumServer(t)
+
+ t.Setenv("APPLE_BM_DEFAULT_TEAM", "")
+ _, err := runAppNoChecks([]string{"gitops", "-f", c.file})
+ if c.wantErr == "" {
+ require.NoError(t, err)
+ } else {
+ require.ErrorContains(t, err, c.wantErr)
+ }
+ })
+ }
+}
+
+func TestGitOpsTeamVPPApps(t *testing.T) {
+ config := &appleVPPConfigSrvConf{
+ Assets: []vpp.Asset{
+ {
+ AdamID: "1",
+ PricingParam: "STDQ",
+ AvailableCount: 12,
+ },
+ {
+ AdamID: "2",
+ PricingParam: "STDQ",
+ AvailableCount: 3,
+ },
+ },
+ SerialNumbers: []string{"123", "456"},
+ }
+
+ startVPPApplyServer(t, config)
+
+ appleITunesSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // a map of apps we can respond with
+ db := map[string]string{
+ // macos app
+ "1": `{"bundleId": "a-1", "artworkUrl512": "https://example.com/images/1", "version": "1.0.0", "trackName": "App 1", "TrackID": 1}`,
+ // macos, ios, ipados app
+ "2": `{"bundleId": "b-2", "artworkUrl512": "https://example.com/images/2", "version": "2.0.0", "trackName": "App 2", "TrackID": 2,
+ "supportedDevices": ["MacDesktop-MacDesktop", "iPhone5s-iPhone5s", "iPadAir-iPadAir"] }`,
+ // ipados app
+ "3": `{"bundleId": "c-3", "artworkUrl512": "https://example.com/images/3", "version": "3.0.0", "trackName": "App 3", "TrackID": 3,
+ "supportedDevices": ["iPadAir-iPadAir"] }`,
+ }
+
+ adamIDString := r.URL.Query().Get("id")
+ adamIDs := strings.Split(adamIDString, ",")
+
+ var objs []string
+ for _, a := range adamIDs {
+ objs = append(objs, db[a])
+ }
+
+ _, _ = w.Write([]byte(fmt.Sprintf(`{"results": [%s]}`, strings.Join(objs, ","))))
+ }))
+ t.Setenv("FLEET_DEV_ITUNES_URL", appleITunesSrv.URL)
+
+ cases := []struct {
+ file string
+ wantErr string
+ tokenExpiration time.Time
+ }{
+ {"testdata/gitops/team_vpp_valid_app.yml", "", time.Now().Add(24 * time.Hour)},
+ {"testdata/gitops/team_vpp_valid_app_self_service.yml", "", time.Now().Add(24 * time.Hour)},
+ {"testdata/gitops/team_vpp_valid_empty.yml", "", time.Now().Add(24 * time.Hour)},
+ {"testdata/gitops/team_vpp_valid_empty.yml", "", time.Now().Add(-24 * time.Hour)},
+ {"testdata/gitops/team_vpp_valid_app.yml", "VPP token expired", time.Now().Add(-24 * time.Hour)},
+ {"testdata/gitops/team_vpp_invalid_app.yml", "app not available on vpp account", time.Now().Add(24 * time.Hour)},
+ {"testdata/gitops/team_vpp_incorrect_type.yml", "\"app_store_apps.app_store_id\" must be a string, found number", time.Now().Add(24 * time.Hour)},
+ {"testdata/gitops/team_vpp_empty_adamid.yml", "software app store id required", time.Now().Add(24 * time.Hour)},
+ }
+
+ for _, c := range cases {
+ t.Run(filepath.Base(c.file), func(t *testing.T) {
+ ds, _, _ := setupFullGitOpsPremiumServer(t)
+ token, err := test.CreateVPPTokenEncoded(c.tokenExpiration, "fleet", "ca")
+ require.NoError(t, err)
+
+ ds.SetTeamVPPAppsFunc = func(ctx context.Context, teamID *uint, adamIDs []fleet.VPPAppTeam) error {
+ return nil
+ }
+ ds.BatchInsertVPPAppsFunc = func(ctx context.Context, apps []*fleet.VPPApp) error {
+ return nil
+ }
+
+ ds.GetVPPTokenByTeamIDFunc = func(ctx context.Context, teamID *uint) (*fleet.VPPTokenDB, error) {
+ return &fleet.VPPTokenDB{
+ ID: 1,
+ OrgName: "Fleet",
+ Location: "Earth",
+ RenewDate: c.tokenExpiration,
+ Token: string(token),
+ Teams: nil,
+ }, nil
+ }
+
+ _, err = runAppNoChecks([]string{"gitops", "-f", c.file})
+ if c.wantErr == "" {
+ require.NoError(t, err)
+ } else {
+ require.ErrorContains(t, err, c.wantErr)
+ }
+ })
+ }
+}
+
+func TestGitOpsCustomSettings(t *testing.T) {
cases := []struct {
file string
wantErr string
@@ -1124,6 +1457,12 @@ func TestCustomSettingsGitOps(t *testing.T) {
}
return ret, nil
}
+ ds.SetTeamVPPAppsFunc = func(ctx context.Context, teamID *uint, adamIDs []fleet.VPPAppTeam) error {
+ return nil
+ }
+ ds.BatchInsertVPPAppsFunc = func(ctx context.Context, apps []*fleet.VPPApp) error {
+ return nil
+ }
_, err := runAppNoChecks([]string{"gitops", "-f", c.file})
if c.wantErr == "" {
@@ -1169,7 +1508,141 @@ func startSoftwareInstallerServer(t *testing.T) {
t.Setenv("SOFTWARE_INSTALLER_URL", srv.URL)
}
-func setupFullGitOpsPremiumServer(t *testing.T) (*mock.Store, **fleet.AppConfig, **fleet.Team) {
+type appleVPPConfigSrvConf struct {
+ Assets []vpp.Asset
+ SerialNumbers []string
+}
+
+func startVPPApplyServer(t *testing.T, config *appleVPPConfigSrvConf) {
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if strings.Contains(r.URL.Path, "associate") {
+ var associations vpp.AssociateAssetsRequest
+
+ decoder := json.NewDecoder(r.Body)
+ if err := decoder.Decode(&associations); err != nil {
+ http.Error(w, "invalid request", http.StatusBadRequest)
+ return
+ }
+
+ if len(associations.Assets) == 0 {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusBadRequest)
+ res := vpp.ErrorResponse{
+ ErrorNumber: 9718,
+ ErrorMessage: "This request doesn't contain an asset, which is a required argument. Change the request to provide an asset.",
+ }
+ if err := json.NewEncoder(w).Encode(res); err != nil {
+ panic(err)
+ }
+ return
+ }
+
+ if len(associations.SerialNumbers) == 0 {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusBadRequest)
+ res := vpp.ErrorResponse{
+ ErrorNumber: 9719,
+ ErrorMessage: "Either clientUserIds or serialNumbers are required arguments. Change the request to provide assignable users and devices.",
+ }
+ if err := json.NewEncoder(w).Encode(res); err != nil {
+ panic(err)
+ }
+ return
+ }
+
+ var badAssets []vpp.Asset
+ for _, reqAsset := range associations.Assets {
+ var found bool
+ for _, goodAsset := range config.Assets {
+ if reqAsset == goodAsset {
+ found = true
+ }
+ }
+ if !found {
+ badAssets = append(badAssets, reqAsset)
+ }
+ }
+
+ var badSerials []string
+ for _, reqSerial := range associations.SerialNumbers {
+ var found bool
+ for _, goodSerial := range config.SerialNumbers {
+ if reqSerial == goodSerial {
+ found = true
+ }
+ }
+ if !found {
+ badSerials = append(badSerials, reqSerial)
+ }
+ }
+
+ if len(badAssets) != 0 || len(badSerials) != 0 {
+ errMsg := "error associating assets."
+ if len(badAssets) > 0 {
+ var badAdamIds []string
+ for _, asset := range badAssets {
+ badAdamIds = append(badAdamIds, asset.AdamID)
+ }
+ errMsg += fmt.Sprintf(" assets don't exist on account: %s.", strings.Join(badAdamIds, ", "))
+ }
+ if len(badSerials) > 0 {
+ errMsg += fmt.Sprintf(" bad serials: %s.", strings.Join(badSerials, ", "))
+ }
+ res := vpp.ErrorResponse{
+ ErrorInfo: vpp.ResponseErrorInfo{
+ Assets: badAssets,
+ ClientUserIds: []string{"something"},
+ SerialNumbers: badSerials,
+ },
+ // Not sure what error should be returned on each
+ // error type
+ ErrorNumber: 1,
+ ErrorMessage: errMsg,
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusBadRequest)
+ if err := json.NewEncoder(w).Encode(res); err != nil {
+ panic(err)
+ }
+ }
+ return
+ }
+
+ if strings.Contains(r.URL.Path, "assets") {
+ // Then we're responding to GetAssets
+ w.Header().Set("Content-Type", "application/json")
+ encoder := json.NewEncoder(w)
+ err := encoder.Encode(map[string][]vpp.Asset{"assets": config.Assets})
+ if err != nil {
+ panic(err)
+ }
+ return
+ }
+
+ resp := []byte(`{"locationName": "Fleet Location One"}`)
+ if strings.Contains(r.URL.RawQuery, "invalidToken") {
+ // This replicates the response sent back from Apple's VPP endpoints when an invalid
+ // token is passed. For more details see:
+ // https://developer.apple.com/documentation/devicemanagement/app_and_book_management/app_and_book_management_legacy/interpreting_error_codes
+ // https://developer.apple.com/documentation/devicemanagement/client_config
+ // https://developer.apple.com/documentation/devicemanagement/errorresponse
+ // Note that the Apple server returns 200 in this case.
+ resp = []byte(`{"errorNumber": 9622,"errorMessage": "Invalid authentication token"}`)
+ }
+
+ if strings.Contains(r.URL.RawQuery, "serverError") {
+ resp = []byte(`{"errorNumber": 9603,"errorMessage": "Internal server error"}`)
+ w.WriteHeader(http.StatusInternalServerError)
+ }
+
+ _, _ = w.Write(resp)
+ }))
+
+ t.Setenv("FLEET_DEV_VPP_URL", srv.URL)
+ t.Cleanup(srv.Close)
+}
+
+func setupFullGitOpsPremiumServer(t *testing.T) (*mock.Store, **fleet.AppConfig, map[string]**fleet.Team) {
testCert, testKey, err := apple_mdm.NewSCEPCACertKey()
require.NoError(t, err)
testCertPEM := tokenpki.PEMCertificate(testCert.Raw)
@@ -1203,8 +1676,14 @@ func setupFullGitOpsPremiumServer(t *testing.T) (*mock.Store, **fleet.AppConfig,
savedAppConfig = &appConfigCopy
return nil
}
+ ds.SetTeamVPPAppsFunc = func(ctx context.Context, teamID *uint, adamIDs []fleet.VPPAppTeam) error {
+ return nil
+ }
+ ds.BatchInsertVPPAppsFunc = func(ctx context.Context, apps []*fleet.VPPApp) error {
+ return nil
+ }
- var savedTeam *fleet.Team
+ savedTeams := map[string]**fleet.Team{}
ds.ApplyEnrollSecretsFunc = func(ctx context.Context, teamID *uint, secrets []*fleet.EnrollSecret) error {
return nil
@@ -1220,14 +1699,14 @@ func setupFullGitOpsPremiumServer(t *testing.T) (*mock.Store, **fleet.AppConfig,
ds.BatchSetMDMProfilesFunc = func(
ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile,
macDecls []*fleet.MDMAppleDeclaration,
- ) error {
- return nil
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.BatchSetScriptsFunc = func(ctx context.Context, tmID *uint, scripts []*fleet.Script) error { return nil }
ds.BulkSetPendingMDMHostProfilesFunc = func(
ctx context.Context, hostIDs []uint, teamIDs []uint, profileUUIDs []string, hostUUIDs []string,
- ) error {
- return nil
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.DeleteMDMAppleDeclarationByNameFunc = func(ctx context.Context, teamID *uint, name string) error {
return nil
@@ -1246,8 +1725,12 @@ func setupFullGitOpsPremiumServer(t *testing.T) (*mock.Store, **fleet.AppConfig,
return nil, nil, nil
}
ds.ListTeamsFunc = func(ctx context.Context, filter fleet.TeamFilter, opt fleet.ListOptions) ([]*fleet.Team, error) {
- if savedTeam != nil {
- return []*fleet.Team{savedTeam}, nil
+ if savedTeams != nil {
+ var result []*fleet.Team
+ for _, t := range savedTeams {
+ result = append(result, *t)
+ }
+ return result, nil
}
return nil, nil
}
@@ -1265,33 +1748,39 @@ func setupFullGitOpsPremiumServer(t *testing.T) (*mock.Store, **fleet.AppConfig,
return job, nil
}
ds.NewTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) {
- team.ID = 1
- savedTeam = team
- return savedTeam, nil
+ team.ID = uint(len(savedTeams) + 1)
+ savedTeams[team.Name] = &team
+ return team, nil
}
ds.QueryByNameFunc = func(ctx context.Context, teamID *uint, name string) (*fleet.Query, error) {
return nil, ¬FoundError{}
}
ds.TeamFunc = func(ctx context.Context, tid uint) (*fleet.Team, error) {
- if savedTeam != nil && tid == savedTeam.ID {
- return savedTeam, nil
+ for _, tm := range savedTeams {
+ if (*tm).ID == tid {
+ return *tm, nil
+ }
}
return nil, ¬FoundError{}
}
ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) {
- if savedTeam != nil && name == teamName {
- return savedTeam, nil
+ for _, tm := range savedTeams {
+ if (*tm).Name == name {
+ return *tm, nil
+ }
}
return nil, ¬FoundError{}
}
ds.TeamByFilenameFunc = func(ctx context.Context, filename string) (*fleet.Team, error) {
- if savedTeam != nil && *savedTeam.Filename == filename {
- return savedTeam, nil
+ for _, tm := range savedTeams {
+ if *(*tm).Filename == filename {
+ return *tm, nil
+ }
}
return nil, ¬FoundError{}
}
ds.SaveTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) {
- savedTeam = team
+ savedTeams[team.Name] = &team
return team, nil
}
ds.SetOrUpdateMDMAppleDeclarationFunc = func(ctx context.Context, declaration *fleet.MDMAppleDeclaration) (
@@ -1300,8 +1789,30 @@ func setupFullGitOpsPremiumServer(t *testing.T) (*mock.Store, **fleet.AppConfig,
declaration.DeclarationUUID = uuid.NewString()
return declaration, nil
}
- ds.BatchSetSoftwareInstallersFunc = func(ctx context.Context, teamID *uint, installers []*fleet.UploadSoftwareInstallerPayload) error {
- return nil
+ ds.BatchSetSoftwareInstallersFunc = func(ctx context.Context, teamID *uint, installers []*fleet.UploadSoftwareInstallerPayload) ([]fleet.SoftwareInstaller, error) {
+ return nil, nil
+ }
+
+ ds.InsertVPPTokenFunc = func(ctx context.Context, tok *fleet.VPPTokenData) (*fleet.VPPTokenDB, error) {
+ return &fleet.VPPTokenDB{}, nil
+ }
+ ds.GetVPPTokenFunc = func(ctx context.Context, tokenID uint) (*fleet.VPPTokenDB, error) {
+ return &fleet.VPPTokenDB{}, err
+ }
+ ds.GetVPPTokenByTeamIDFunc = func(ctx context.Context, teamID *uint) (*fleet.VPPTokenDB, error) {
+ return &fleet.VPPTokenDB{}, nil
+ }
+ ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
+ return nil, nil
+ }
+ ds.UpdateVPPTokenTeamsFunc = func(ctx context.Context, id uint, teams []uint) (*fleet.VPPTokenDB, error) {
+ return &fleet.VPPTokenDB{}, nil
+ }
+ ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
+ return []*fleet.ABMToken{{OrganizationName: "Fleet Device Management Inc."}}, nil
+ }
+ ds.ListSoftwareTitlesFunc = func(ctx context.Context, opt fleet.SoftwareTitleListOptions, tmFilter fleet.TeamFilter) ([]fleet.SoftwareTitleListResult, int, *fleet.PaginationMetadata, error) {
+ return nil, 0, nil, nil
}
t.Setenv("FLEET_SERVER_URL", fleetServerURL)
@@ -1309,5 +1820,682 @@ func setupFullGitOpsPremiumServer(t *testing.T) (*mock.Store, **fleet.AppConfig,
t.Setenv("TEST_TEAM_NAME", teamName)
t.Setenv("APPLE_BM_DEFAULT_TEAM", teamName)
- return ds, &savedAppConfig, &savedTeam
+ return ds, &savedAppConfig, savedTeams
+}
+
+func TestGitOpsABM(t *testing.T) {
+ global := func(mdm string) string {
+ return fmt.Sprintf(`
+controls:
+queries:
+policies:
+agent_options:
+software:
+org_settings:
+ server_settings:
+ server_url: "https://foo.example.com"
+ org_info:
+ org_name: GitOps Test
+ secrets:
+ - secret: "global"
+ mdm:
+ %s
+ `, mdm)
+ }
+
+ team := func(name string) string {
+ return fmt.Sprintf(`
+name: %s
+team_settings:
+ secrets:
+ - secret: "%s-secret"
+agent_options:
+controls:
+policies:
+queries:
+software:
+`, name, name)
+ }
+
+ workstations := team("💻 Workstations")
+ iosTeam := team("📱🏢 Company-owned iPhones")
+ ipadTeam := team("🔳🏢 Company-owned iPads")
+
+ cases := []struct {
+ name string
+ cfgs []string
+ dryRunAssertion func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error)
+ realRunAssertion func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error)
+ tokens []*fleet.ABMToken
+ }{
+ {
+ name: "backwards compat",
+ cfgs: []string{
+ global("apple_bm_default_team: 💻 Workstations"),
+ workstations,
+ },
+ tokens: []*fleet.ABMToken{{OrganizationName: "Fleet Device Management Inc."}},
+ dryRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.Empty(t, appCfg.MDM.AppleBusinessManager.Value)
+ assert.Empty(t, appCfg.MDM.DeprecatedAppleBMDefaultTeam)
+ assert.Contains(t, out, "[!] gitops dry run succeeded")
+ },
+ realRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.Empty(t, appCfg.MDM.AppleBusinessManager.Value)
+ assert.Equal(t, appCfg.MDM.DeprecatedAppleBMDefaultTeam, "💻 Workstations")
+ assert.Contains(t, out, "[!] gitops succeeded")
+ },
+ },
+ // {
+ // name: "deprecated config with two tokens in the db fails",
+ // cfgs: []string{
+ // global("apple_bm_default_team: 💻 Workstations"),
+ // workstations,
+ // },
+ // tokens: []*fleet.ABMToken{{OrganizationName: "Fleet Device Management Inc."}, {OrganizationName: "Second Token LLC"}},
+ // dryRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ // require.ErrorContains(t, err, "mdm.apple_bm_default_team has been deprecated")
+ // assert.Empty(t, appCfg.MDM.AppleBussinessManager.Value)
+ // assert.Empty(t, appCfg.MDM.DeprecatedAppleBMDefaultTeam)
+ // assert.NotContains(t, out, "[!] gitops dry run succeeded")
+ // },
+ // realRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ // require.ErrorContains(t, err, "mdm.apple_bm_default_team has been deprecated")
+ // assert.Empty(t, appCfg.MDM.AppleBussinessManager.Value)
+ // assert.Empty(t, appCfg.MDM.DeprecatedAppleBMDefaultTeam)
+ // assert.NotContains(t, out, "[!] gitops dry run succeeded")
+ // },
+ // },
+ {
+ name: "new key all valid",
+ cfgs: []string{
+ global(`
+ apple_business_manager:
+ - organization_name: Fleet Device Management Inc.
+ macos_team: "💻 Workstations"
+ ios_team: "📱🏢 Company-owned iPhones"
+ ipados_team: "🔳🏢 Company-owned iPads"`),
+ workstations,
+ iosTeam,
+ ipadTeam,
+ },
+ dryRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.Empty(t, appCfg.MDM.AppleBusinessManager.Value)
+ assert.Empty(t, appCfg.MDM.DeprecatedAppleBMDefaultTeam)
+ assert.Contains(t, out, "[!] gitops dry run succeeded")
+ },
+ realRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.Empty(t, appCfg.MDM.DeprecatedAppleBMDefaultTeam)
+ assert.ElementsMatch(
+ t,
+ appCfg.MDM.AppleBusinessManager.Value,
+ []fleet.MDMAppleABMAssignmentInfo{
+ {
+ OrganizationName: "Fleet Device Management Inc.",
+ MacOSTeam: "💻 Workstations",
+ IOSTeam: "📱🏢 Company-owned iPhones",
+ IpadOSTeam: "🔳🏢 Company-owned iPads",
+ },
+ },
+ )
+ assert.Contains(t, out, "[!] gitops succeeded")
+ },
+ },
+ {
+ name: "new key multiple elements",
+ cfgs: []string{
+ global(`
+ apple_business_manager:
+ - organization_name: Foo Inc.
+ macos_team: "💻 Workstations"
+ ios_team: "📱🏢 Company-owned iPhones"
+ ipados_team: "🔳🏢 Company-owned iPads"
+ - organization_name: Fleet Device Management Inc.
+ macos_team: "💻 Workstations"
+ ios_team: "📱🏢 Company-owned iPhones"
+ ipados_team: "🔳🏢 Company-owned iPads"`),
+ workstations,
+ iosTeam,
+ ipadTeam,
+ },
+ dryRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.Empty(t, appCfg.MDM.AppleBusinessManager.Value)
+ assert.Empty(t, appCfg.MDM.DeprecatedAppleBMDefaultTeam)
+ assert.Contains(t, out, "[!] gitops dry run succeeded")
+ },
+ realRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.Empty(t, appCfg.MDM.DeprecatedAppleBMDefaultTeam)
+ assert.ElementsMatch(
+ t,
+ appCfg.MDM.AppleBusinessManager.Value,
+ []fleet.MDMAppleABMAssignmentInfo{
+ {
+ OrganizationName: "Fleet Device Management Inc.",
+ MacOSTeam: "💻 Workstations",
+ IOSTeam: "📱🏢 Company-owned iPhones",
+ IpadOSTeam: "🔳🏢 Company-owned iPads",
+ },
+ {
+ OrganizationName: "Foo Inc.",
+ MacOSTeam: "💻 Workstations",
+ IOSTeam: "📱🏢 Company-owned iPhones",
+ IpadOSTeam: "🔳🏢 Company-owned iPads",
+ },
+ },
+ )
+ assert.Contains(t, out, "[!] gitops succeeded")
+ },
+ },
+ {
+ name: "both keys errors",
+ cfgs: []string{
+ global(`
+ apple_bm_default_team: "💻 Workstations"
+ apple_business_manager:
+ - organization_name: Fleet Device Management Inc.
+ macos_team: "💻 Workstations"
+ ios_team: "📱🏢 Company-owned iPhones"
+ ipados_team: "🔳🏢 Company-owned iPads"`),
+ workstations,
+ iosTeam,
+ ipadTeam,
+ },
+ dryRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ t.Log(out)
+ require.ErrorContains(t, err, "mdm.apple_bm_default_team has been deprecated")
+ assert.NotContains(t, out, "[!] gitops dry run succeeded")
+ },
+ realRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ require.ErrorContains(t, err, "mdm.apple_bm_default_team has been deprecated")
+ assert.NotContains(t, out, "[!] gitops succeeded")
+ },
+ },
+ {
+ name: "using an undefined team errors",
+ cfgs: []string{
+ global(`
+ apple_business_manager:
+ - organization_name: Fleet Device Management Inc.
+ macos_team: "💻 Workstations"
+ ios_team: "📱🏢 Company-owned iPhones"
+ ipados_team: "🔳🏢 Company-owned iPads"`),
+ workstations,
+ },
+ dryRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.ErrorContains(t, err, "apple_business_manager team 📱🏢 Company-owned iPhones not found in team configs")
+ },
+ realRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.ErrorContains(t, err, "apple_business_manager team 📱🏢 Company-owned iPhones not found in team configs")
+ },
+ },
+ {
+ name: "no team is supported",
+ cfgs: []string{
+ global(`
+ apple_business_manager:
+ - organization_name: Fleet Device Management Inc.
+ macos_team: "No team"
+ ios_team: "No team"
+ ipados_team: "No team"`),
+ },
+ dryRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.Empty(t, appCfg.MDM.AppleBusinessManager.Value)
+ assert.Empty(t, appCfg.MDM.DeprecatedAppleBMDefaultTeam)
+ assert.Contains(t, out, "[!] gitops dry run succeeded")
+ },
+ realRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.Empty(t, appCfg.MDM.DeprecatedAppleBMDefaultTeam)
+ assert.ElementsMatch(
+ t,
+ appCfg.MDM.AppleBusinessManager.Value,
+ []fleet.MDMAppleABMAssignmentInfo{
+ {
+ OrganizationName: "Fleet Device Management Inc.",
+ MacOSTeam: "No team",
+ IOSTeam: "No team",
+ IpadOSTeam: "No team",
+ },
+ },
+ )
+ assert.Contains(t, out, "[!] gitops succeeded")
+ },
+ },
+ {
+ name: "not provided teams defaults to no team",
+ cfgs: []string{
+ global(`
+ apple_business_manager:
+ - organization_name: Fleet Device Management Inc.
+ macos_team: "No team"
+ ios_team: ""`),
+ },
+ dryRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.Empty(t, appCfg.MDM.AppleBusinessManager.Value)
+ assert.Empty(t, appCfg.MDM.DeprecatedAppleBMDefaultTeam)
+ assert.Contains(t, out, "[!] gitops dry run succeeded")
+ },
+ realRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.Empty(t, appCfg.MDM.DeprecatedAppleBMDefaultTeam)
+ assert.ElementsMatch(
+ t,
+ appCfg.MDM.AppleBusinessManager.Value,
+ []fleet.MDMAppleABMAssignmentInfo{
+ {
+ OrganizationName: "Fleet Device Management Inc.",
+ MacOSTeam: "No team",
+ IOSTeam: "",
+ IpadOSTeam: "",
+ },
+ },
+ )
+ assert.Contains(t, out, "[!] gitops succeeded")
+ },
+ },
+ {
+ name: "non existent org name fails",
+ cfgs: []string{
+ global(`
+ apple_business_manager:
+ - organization_name: Does not exist
+ macos_team: "No team"`),
+ },
+ tokens: []*fleet.ABMToken{{OrganizationName: "Fleet Device Management Inc."}},
+ dryRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.ErrorContains(t, err, "token with organization name Does not exist doesn't exist")
+ assert.Empty(t, appCfg.MDM.AppleBusinessManager.Value)
+ assert.Empty(t, appCfg.MDM.DeprecatedAppleBMDefaultTeam)
+ assert.NotContains(t, out, "[!] gitops dry run succeeded")
+ },
+ realRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.ErrorContains(t, err, "token with organization name Does not exist doesn't exist")
+ assert.Empty(t, appCfg.MDM.AppleBusinessManager.Value)
+ assert.Empty(t, appCfg.MDM.DeprecatedAppleBMDefaultTeam)
+ assert.NotContains(t, out, "[!] gitops dry run succeeded")
+ },
+ },
+ }
+
+ for _, tt := range cases {
+ t.Run(tt.name, func(t *testing.T) {
+ ds, savedAppConfigPtr, savedTeams := setupFullGitOpsPremiumServer(t)
+
+ ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
+ if len(tt.tokens) > 0 {
+ return tt.tokens, nil
+ }
+ return []*fleet.ABMToken{{OrganizationName: "Fleet Device Management Inc."}, {OrganizationName: "Foo Inc."}}, nil
+ }
+
+ ds.TeamsSummaryFunc = func(ctx context.Context) ([]*fleet.TeamSummary, error) {
+ var res []*fleet.TeamSummary
+ for _, tm := range savedTeams {
+ res = append(res, &fleet.TeamSummary{Name: (*tm).Name, ID: (*tm).ID})
+ }
+ return res, nil
+ }
+
+ ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
+ return nil
+ }
+
+ args := []string{"gitops"}
+ for _, cfg := range tt.cfgs {
+ if cfg != "" {
+ tmpFile, err := os.CreateTemp(t.TempDir(), "*.yml")
+ require.NoError(t, err)
+ _, err = tmpFile.WriteString(cfg)
+ require.NoError(t, err)
+ args = append(args, "-f", tmpFile.Name())
+ }
+ }
+
+ // Dry run
+ out, err := runAppNoChecks(append(args, "--dry-run"))
+ tt.dryRunAssertion(t, *savedAppConfigPtr, ds, out.String(), err)
+ if t.Failed() {
+ t.FailNow()
+ }
+
+ // Real run
+ out, err = runAppNoChecks(args)
+ tt.realRunAssertion(t, *savedAppConfigPtr, ds, out.String(), err)
+
+ // Second real run, now that all the teams are saved
+ out, err = runAppNoChecks(args)
+ tt.realRunAssertion(t, *savedAppConfigPtr, ds, out.String(), err)
+ })
+ }
+}
+
+func TestGitOpsVPP(t *testing.T) {
+ global := func(mdm string) string {
+ return fmt.Sprintf(`
+controls:
+queries:
+policies:
+agent_options:
+software:
+org_settings:
+ server_settings:
+ server_url: "https://foo.example.com"
+ org_info:
+ org_name: GitOps Test
+ secrets:
+ - secret: "global"
+ mdm:
+ %s
+ `, mdm)
+ }
+
+ team := func(name string) string {
+ return fmt.Sprintf(`
+name: %s
+team_settings:
+ secrets:
+ - secret: "%s-secret"
+agent_options:
+controls:
+policies:
+queries:
+software:
+`, name, name)
+ }
+
+ workstations := team("💻 Workstations")
+ iosTeam := team("📱🏢 Company-owned iPhones")
+ ipadTeam := team("🔳🏢 Company-owned iPads")
+
+ cases := []struct {
+ name string
+ cfgs []string
+ dryRunAssertion func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error)
+ realRunAssertion func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error)
+ }{
+ {
+ name: "new key all valid",
+ cfgs: []string{
+ global(`
+ volume_purchasing_program:
+ - location: Fleet Device Management Inc.
+ teams:
+ - "💻 Workstations"
+ - "📱🏢 Company-owned iPhones"
+ - "🔳🏢 Company-owned iPads"`),
+ workstations,
+ iosTeam,
+ ipadTeam,
+ },
+ dryRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.Empty(t, appCfg.MDM.VolumePurchasingProgram.Value)
+ assert.Contains(t, out, "[!] gitops dry run succeeded")
+ },
+ realRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.ElementsMatch(
+ t,
+ appCfg.MDM.VolumePurchasingProgram.Value,
+ []fleet.MDMAppleVolumePurchasingProgramInfo{
+ {
+ Location: "Fleet Device Management Inc.",
+ Teams: []string{
+ "💻 Workstations",
+ "📱🏢 Company-owned iPhones",
+ "🔳🏢 Company-owned iPads",
+ },
+ },
+ },
+ )
+ assert.Contains(t, out, "[!] gitops succeeded")
+ },
+ },
+ {
+ name: "new key multiple elements",
+ cfgs: []string{
+ global(`
+ volume_purchasing_program:
+ - location: Acme Inc.
+ teams:
+ - "💻 Workstations"
+ - location: Fleet Device Management Inc.
+ teams:
+ - "📱🏢 Company-owned iPhones"
+ - "🔳🏢 Company-owned iPads"`),
+ workstations,
+ iosTeam,
+ ipadTeam,
+ },
+ dryRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.Empty(t, appCfg.MDM.VolumePurchasingProgram.Value)
+ assert.Contains(t, out, "[!] gitops dry run succeeded")
+ },
+ realRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.ElementsMatch(
+ t,
+ appCfg.MDM.VolumePurchasingProgram.Value,
+ []fleet.MDMAppleVolumePurchasingProgramInfo{
+ {
+ Location: "Acme Inc.",
+ Teams: []string{
+ "💻 Workstations",
+ },
+ },
+ {
+ Location: "Fleet Device Management Inc.",
+ Teams: []string{
+ "📱🏢 Company-owned iPhones",
+ "🔳🏢 Company-owned iPads",
+ },
+ },
+ },
+ )
+ assert.Contains(t, out, "[!] gitops succeeded")
+ },
+ },
+ {
+ name: "using an undefined team errors",
+ cfgs: []string{
+ global(`
+ volume_purchasing_program:
+ - location: Fleet Device Management Inc.
+ teams:
+ - "💻 Workstations"
+ - "📱🏢 Company-owned iPhones"
+ - "🔳🏢 Company-owned iPads"`),
+ workstations,
+ ipadTeam,
+ },
+ dryRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.ErrorContains(t, err, "volume_purchasing_program team 📱🏢 Company-owned iPhones not found in team configs")
+ },
+ realRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.ErrorContains(t, err, "volume_purchasing_program team 📱🏢 Company-owned iPhones not found in team configs")
+ },
+ },
+ {
+ name: "no team is supported",
+ cfgs: []string{
+ global(`
+ volume_purchasing_program:
+ - location: Fleet Device Management Inc.
+ teams:
+ - "💻 Workstations"
+ - "📱🏢 Company-owned iPhones"
+ - "No team"`),
+ workstations,
+ iosTeam,
+ },
+ dryRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.Empty(t, appCfg.MDM.VolumePurchasingProgram.Value)
+ assert.Contains(t, out, "[!] gitops dry run succeeded")
+ },
+ realRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.ElementsMatch(
+ t,
+ appCfg.MDM.VolumePurchasingProgram.Value,
+ []fleet.MDMAppleVolumePurchasingProgramInfo{
+ {
+ Location: "Fleet Device Management Inc.",
+ Teams: []string{
+ "💻 Workstations",
+ "📱🏢 Company-owned iPhones",
+ "No team",
+ },
+ },
+ },
+ )
+ assert.Contains(t, out, "[!] gitops succeeded")
+ },
+ },
+ {
+ name: "all teams is supported",
+ cfgs: []string{
+ global(`
+ volume_purchasing_program:
+ - location: Fleet Device Management Inc.
+ teams:
+ - "All teams"`),
+ workstations,
+ iosTeam,
+ },
+ dryRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.Empty(t, appCfg.MDM.VolumePurchasingProgram.Value)
+ assert.Contains(t, out, "[!] gitops dry run succeeded")
+ },
+ realRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.ElementsMatch(
+ t,
+ appCfg.MDM.VolumePurchasingProgram.Value,
+ []fleet.MDMAppleVolumePurchasingProgramInfo{
+ {
+ Location: "Fleet Device Management Inc.",
+ Teams: []string{
+ "All teams",
+ },
+ },
+ },
+ )
+ assert.Contains(t, out, "[!] gitops succeeded")
+ },
+ },
+ {
+ name: "not provided teams defaults to no team",
+ cfgs: []string{
+ global(`
+ volume_purchasing_program:
+ - location: Fleet Device Management Inc.
+ teams:`),
+ workstations,
+ ipadTeam,
+ },
+ dryRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.Empty(t, appCfg.MDM.VolumePurchasingProgram.Value)
+ assert.Contains(t, out, "[!] gitops dry run succeeded")
+ },
+ realRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.NoError(t, err)
+ assert.ElementsMatch(
+ t,
+ appCfg.MDM.VolumePurchasingProgram.Value,
+ []fleet.MDMAppleVolumePurchasingProgramInfo{
+ {
+ Location: "Fleet Device Management Inc.",
+ Teams: nil,
+ },
+ },
+ )
+ assert.Contains(t, out, "[!] gitops succeeded")
+ },
+ },
+ {
+ name: "non existent location fails",
+ cfgs: []string{
+ global(`
+ volume_purchasing_program:
+ - location: Does not exist
+ teams:`),
+ workstations,
+ ipadTeam,
+ },
+ dryRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.ErrorContains(t, err, "token with location Does not exist doesn't exist")
+ assert.Empty(t, appCfg.MDM.VolumePurchasingProgram.Value)
+ assert.NotContains(t, out, "[!] gitops dry run succeeded")
+ },
+ realRunAssertion: func(t *testing.T, appCfg *fleet.AppConfig, ds fleet.Datastore, out string, err error) {
+ assert.ErrorContains(t, err, "token with location Does not exist doesn't exist")
+ assert.Empty(t, appCfg.MDM.VolumePurchasingProgram.Value)
+ assert.NotContains(t, out, "[!] gitops dry run succeeded")
+ },
+ },
+ }
+
+ for _, tt := range cases {
+ t.Run(tt.name, func(t *testing.T) {
+ ds, savedAppConfigPtr, savedTeams := setupFullGitOpsPremiumServer(t)
+
+ ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
+ return []*fleet.VPPTokenDB{{Location: "Fleet Device Management Inc."}, {Location: "Acme Inc."}}, nil
+ }
+
+ ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
+ return []*fleet.ABMToken{{OrganizationName: "Fleet Device Management Inc."}, {OrganizationName: "Foo Inc."}}, nil
+ }
+
+ ds.TeamsSummaryFunc = func(ctx context.Context) ([]*fleet.TeamSummary, error) {
+ var res []*fleet.TeamSummary
+ for _, tm := range savedTeams {
+ res = append(res, &fleet.TeamSummary{Name: (*tm).Name, ID: (*tm).ID})
+ }
+ return res, nil
+ }
+
+ ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
+ return nil
+ }
+
+ args := []string{"gitops"}
+ for _, cfg := range tt.cfgs {
+ if cfg != "" {
+ tmpFile, err := os.CreateTemp(t.TempDir(), "*.yml")
+ require.NoError(t, err)
+ _, err = tmpFile.WriteString(cfg)
+ require.NoError(t, err)
+ args = append(args, "-f", tmpFile.Name())
+ }
+ }
+
+ // Dry run
+ out, err := runAppNoChecks(append(args, "--dry-run"))
+ tt.dryRunAssertion(t, *savedAppConfigPtr, ds, out.String(), err)
+ if t.Failed() {
+ t.FailNow()
+ }
+
+ // Real run
+ out, err = runAppNoChecks(args)
+ tt.realRunAssertion(t, *savedAppConfigPtr, ds, out.String(), err)
+
+ // Second real run, now that all the teams are saved
+ out, err = runAppNoChecks(args)
+ tt.realRunAssertion(t, *savedAppConfigPtr, ds, out.String(), err)
+ })
+ }
}
diff --git a/cmd/fleetctl/hosts_test.go b/cmd/fleetctl/hosts_test.go
index a24fc79cbe3a..6220d6d75c25 100644
--- a/cmd/fleetctl/hosts_test.go
+++ b/cmd/fleetctl/hosts_test.go
@@ -43,8 +43,9 @@ func TestHostsTransferByHosts(t *testing.T) {
return nil
}
- ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, uuids []string) error {
- return nil
+ ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, uuids []string,
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.ListMDMAppleDEPSerialsInHostIDsFunc = func(ctx context.Context, hostIDs []uint) ([]string, error) {
@@ -114,8 +115,9 @@ func TestHostsTransferByLabel(t *testing.T) {
return nil
}
- ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, uuids []string) error {
- return nil
+ ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, uuids []string,
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.ListMDMAppleDEPSerialsInHostIDsFunc = func(ctx context.Context, hostIDs []uint) ([]string, error) {
@@ -184,8 +186,9 @@ func TestHostsTransferByStatus(t *testing.T) {
return nil
}
- ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, uuids []string) error {
- return nil
+ ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, uuids []string,
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.ListMDMAppleDEPSerialsInHostIDsFunc = func(ctx context.Context, hostIDs []uint) ([]string, error) {
@@ -243,8 +246,9 @@ func TestHostsTransferByStatusAndSearchQuery(t *testing.T) {
return nil
}
- ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, uuids []string) error {
- return nil
+ ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, uuids []string,
+ ) (updates fleet.MDMProfilesUpdates, err error) {
+ return fleet.MDMProfilesUpdates{}, nil
}
ds.ListMDMAppleDEPSerialsInHostIDsFunc = func(ctx context.Context, hostIDs []uint) ([]string, error) {
diff --git a/cmd/fleetctl/package.go b/cmd/fleetctl/package.go
index bec5ea3b7d82..1cf3978c72c7 100644
--- a/cmd/fleetctl/package.go
+++ b/cmd/fleetctl/package.go
@@ -38,6 +38,12 @@ func packageCommand() *cli.Command {
Usage: "Type of package to build",
Required: true,
},
+ &cli.StringFlag{
+ Name: "arch",
+ Usage: "Target CPU Architecture for the installer package (Only supported with '--type' deb or rpm)",
+ Destination: &opt.Architecture,
+ Value: "amd64",
+ },
&cli.StringFlag{
Name: "enroll-secret",
Usage: "Enroll secret for authenticating to Fleet server",
@@ -74,11 +80,6 @@ func packageCommand() *cli.Command {
Value: "com.fleetdm.orbit",
Destination: &opt.Identifier,
},
- &cli.StringFlag{
- Name: "version",
- Usage: "Version for package product",
- Destination: &opt.Version,
- },
&cli.BoolFlag{
Name: "insecure",
Usage: "Disable TLS certificate verification",
@@ -336,6 +337,20 @@ func packageCommand() *cli.Command {
return errors.New("--use-system-configuration is only available for pkg installers")
}
+ linuxPackage := false
+ switch c.String("type") {
+ case "deb", "rpm":
+ linuxPackage = true
+ }
+
+ if opt.Architecture != packaging.ArchAmd64 && !linuxPackage {
+ return fmt.Errorf("can't use '--arch' with '--type %s'", c.String("type"))
+ }
+
+ if opt.Architecture != packaging.ArchAmd64 && opt.Architecture != packaging.ArchArm64 {
+ return errors.New("arch must be one of ('amd64', 'arm64')")
+ }
+
var buildFunc func(packaging.Options) (string, error)
switch c.String("type") {
case "pkg":
diff --git a/cmd/fleetctl/preview.go b/cmd/fleetctl/preview.go
index a48734871cff..ef06f3c8cc88 100644
--- a/cmd/fleetctl/preview.go
+++ b/cmd/fleetctl/preview.go
@@ -74,7 +74,7 @@ func (d dockerCompose) Command(arg ...string) *exec.Cmd {
func newDockerCompose() (dockerCompose, error) {
// first, check if `docker compose` is available
- if err := exec.Command("docker compose").Run(); err == nil {
+ if err := exec.Command("docker", "compose").Run(); err == nil {
return dockerCompose{dockerComposeV2}, nil
}
@@ -387,7 +387,7 @@ Use the stop and reset subcommands to manage the server and dependencies once st
}
// this only applies standard queries, the base directory is not used,
// so pass in the current working directory.
- _, err = client.ApplyGroup(c.Context, specs, ".", logf, fleet.ApplyClientSpecOptions{})
+ _, _, err = client.ApplyGroup(c.Context, specs, ".", logf, nil, fleet.ApplyClientSpecOptions{})
if err != nil {
return err
}
diff --git a/cmd/fleetctl/preview_test.go b/cmd/fleetctl/preview_test.go
index 76c68eb19010..c3a75a73e1b0 100644
--- a/cmd/fleetctl/preview_test.go
+++ b/cmd/fleetctl/preview_test.go
@@ -12,7 +12,7 @@ import (
"github.com/stretchr/testify/require"
)
-func TestPreview(t *testing.T) {
+func TestIntegrationsPreview(t *testing.T) {
nettest.Run(t)
t.Setenv("FLEET_SERVER_ADDRESS", "https://localhost:8412")
@@ -74,6 +74,7 @@ func gitRootPath(t *testing.T) string {
}
func TestDockerCompose(t *testing.T) {
+ t.Parallel()
t.Run("returns the right command according to the version", func(t *testing.T) {
v1 := dockerCompose{dockerComposeV1}
cmd1 := v1.Command("up")
diff --git a/cmd/fleetctl/query_test.go b/cmd/fleetctl/query_test.go
index 5a9ca51b9f01..2ee7f8b7c182 100644
--- a/cmd/fleetctl/query_test.go
+++ b/cmd/fleetctl/query_test.go
@@ -49,7 +49,10 @@ func TestSavedLiveQuery(t *testing.T) {
}
ds.HostIDsByIdentifierFunc = func(ctx context.Context, filter fleet.TeamFilter, hostIdentifiers []string) ([]uint, error) {
- return []uint{1234}, nil
+ if len(hostIdentifiers) == 1 && hostIdentifiers[0] == "1234" {
+ return []uint{1234}, nil
+ }
+ return nil, nil
}
ds.LabelIDsByNameFunc = func(ctx context.Context, labels []string) (map[string]uint, error) {
return nil, nil
@@ -70,7 +73,11 @@ func TestSavedLiveQuery(t *testing.T) {
ds.NewDistributedQueryCampaignTargetFunc = func(ctx context.Context, target *fleet.DistributedQueryCampaignTarget) (*fleet.DistributedQueryCampaignTarget, error) {
return target, nil
}
+ noHostsTargeted := false
ds.HostIDsInTargetsFunc = func(ctx context.Context, filter fleet.TeamFilter, targets fleet.HostTargets) ([]uint, error) {
+ if noHostsTargeted {
+ return nil, nil
+ }
return []uint{1}, nil
}
ds.CountHostsInTargetsFunc = func(ctx context.Context, filter fleet.TeamFilter, targets fleet.HostTargets, now time.Time) (fleet.TargetMetrics, error) {
@@ -176,6 +183,12 @@ func TestSavedLiveQuery(t *testing.T) {
)
case <-c: // All good
}
+
+ // Test targeting no hosts (e.g. host does exist)
+ noHostsTargeted = true
+ _, err = runAppNoChecks([]string{"query", "--hosts", "foobar", "--query-name", queryName})
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "No hosts targeted")
}
func TestAdHocLiveQuery(t *testing.T) {
diff --git a/cmd/fleetctl/scripts.go b/cmd/fleetctl/scripts.go
index e1b65f3f264d..81758455f70c 100644
--- a/cmd/fleetctl/scripts.go
+++ b/cmd/fleetctl/scripts.go
@@ -8,8 +8,10 @@ import (
"os"
"path/filepath"
"strings"
+ "time"
"unicode/utf8"
+ "github.com/briandowns/spinner"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/service"
"github.com/urfave/cli/v2"
@@ -27,7 +29,7 @@ func runScriptCommand() *cli.Command {
return &cli.Command{
Name: "run-script",
Aliases: []string{"run_script"},
- Usage: `Run a live script on one host and get results back (5 minute timeout).`,
+ Usage: `Run a script on one host and get results back.`,
UsageText: `fleetctl run-script [options]`,
Flags: []cli.Flag{
&cli.StringFlag{
@@ -159,11 +161,15 @@ func runScriptCommand() *cli.Command {
return nil
}
+ s := spinner.New(spinner.CharSets[24], 200*time.Millisecond)
if !quiet {
- fmt.Println("\nScript is running. Please wait for it to finish...")
+ fmt.Println()
+ s.Suffix = " Script is running or will run when the host comes online..."
+ s.Start()
}
res, err := client.RunHostScriptSync(h.ID, b, name, c.Uint("team"))
+ s.Stop()
if err != nil {
if strings.Contains(err.Error(), `Only one of 'script_contents' or 'team_id' is allowed`) {
return errors.New("Only one of '--script-path' or '--team' is allowed.")
diff --git a/cmd/fleetctl/scripts_test.go b/cmd/fleetctl/scripts_test.go
index abb57585840b..e65a183a2e6a 100644
--- a/cmd/fleetctl/scripts_test.go
+++ b/cmd/fleetctl/scripts_test.go
@@ -9,6 +9,7 @@ import (
"testing"
"time"
+ "github.com/fleetdm/fleet/v4/pkg/scripts"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/service"
@@ -21,6 +22,7 @@ func TestRunScriptCommand(t *testing.T) {
License: &fleet.LicenseInfo{
Tier: fleet.TierPremium,
},
+ NoCacheDatastore: true,
},
&service.TestServerOpts{
HTTPServerConfig: &http.Server{WriteTimeout: 90 * time.Second}, // nolint:gosec
@@ -42,6 +44,9 @@ func TestRunScriptCommand(t *testing.T) {
ds.ListHostBatteriesFunc = func(ctx context.Context, hid uint) ([]*fleet.HostBattery, error) {
return nil, nil
}
+ ds.HostLiteFunc = func(ctx context.Context, hid uint) (*fleet.Host, error) {
+ return &fleet.Host{}, nil
+ }
ds.ListUpcomingHostMaintenanceWindowsFunc = func(ctx context.Context, hid uint) ([]*fleet.HostMaintenanceWindow, error) {
return nil, nil
}
@@ -93,12 +98,6 @@ hello world
}
cases := []testCase{
- {
- name: "host offline",
- scriptPath: generateValidPath,
- expectErrMsg: fleet.RunScriptHostOfflineErrMsg,
- expectOffline: true,
- },
{
name: "host not found",
scriptPath: generateValidPath,
@@ -220,12 +219,6 @@ hello world
scriptPath: func() string { return writeTmpScriptContents(t, "\xff\xfa", ".sh") },
expectErrMsg: `Wrong data format. Only plain text allowed.`,
},
- {
- name: "script already running",
- scriptPath: generateValidPath,
- expectErrMsg: fleet.RunScriptAlreadyRunningErrMsg,
- expectPending: true,
- },
{
name: "script successful",
scriptPath: generateValidPath,
@@ -270,10 +263,10 @@ Output:
scriptResult: &fleet.HostScriptResult{
ExitCode: ptr.Int64(-1),
Output: "Oh no!",
- Message: fleet.RunScriptScriptTimeoutErrMsg,
+ Message: fleet.HostScriptTimeoutMessage(ptr.Int(int(scripts.MaxHostExecutionTime.Seconds()))),
},
expectOutput: `
-Error: Timeout. Fleet stopped the script after 5 minutes to protect host performance.
+Error: Timeout. Fleet stopped the script after 300 seconds to protect host performance.
Output before timeout:
@@ -367,6 +360,7 @@ Fleet records the last 10,000 characters to prevent downtime.
Hostname: "host1",
HostID: req.HostID,
ScriptContents: req.ScriptContents,
+ ExecutionID: "123",
}, nil
}
if c.name == "disabled scripts globally" {
diff --git a/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json b/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json
index c6624c71109f..ece537e7b95f 100644
--- a/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json
+++ b/cmd/fleetctl/testdata/expectedGetConfigAppConfigJson.json
@@ -96,13 +96,22 @@
"apple_bm_terms_expired": false,
"apple_bm_enabled_and_configured": false,
"enabled_and_configured": false,
- "apple_bm_default_team": "",
+ "apple_business_manager": null,
+ "volume_purchasing_program": null,
"windows_enabled_and_configured": false,
"enable_disk_encryption": false,
"macos_updates": {
"minimum_version": null,
"deadline": null
},
+ "ios_updates": {
+ "minimum_version": null,
+ "deadline": null
+ },
+ "ipados_updates": {
+ "minimum_version": null,
+ "deadline": null
+ },
"windows_updates": {
"deadline_days": 7,
"grace_period_days": 3
diff --git a/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml b/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml
index 92254b6052d7..5f19ffcb8ac0 100644
--- a/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml
+++ b/cmd/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml
@@ -21,7 +21,8 @@ spec:
apple_bm_terms_expired: false
apple_bm_enabled_and_configured: false
enabled_and_configured: false
- apple_bm_default_team: ""
+ apple_business_manager: null
+ volume_purchasing_program: null
windows_enabled_and_configured: false
enable_disk_encryption: false
macos_migration:
@@ -31,6 +32,12 @@ spec:
macos_updates:
minimum_version: null
deadline: null
+ ios_updates:
+ minimum_version: null
+ deadline: null
+ ipados_updates:
+ minimum_version: null
+ deadline: null
windows_updates:
deadline_days: 7
grace_period_days: 3
diff --git a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json
index 18d980b320bf..9fa625b676af 100644
--- a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json
+++ b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json
@@ -46,7 +46,8 @@
"enable_software_inventory": false
},
"mdm": {
- "apple_bm_default_team": "",
+ "apple_business_manager": null,
+ "volume_purchasing_program": null,
"apple_bm_terms_expired": false,
"apple_bm_enabled_and_configured": false,
"enabled_and_configured": false,
@@ -56,6 +57,14 @@
"minimum_version": null,
"deadline": null
},
+ "ios_updates": {
+ "minimum_version": null,
+ "deadline": null
+ },
+ "ipados_updates": {
+ "minimum_version": null,
+ "deadline": null
+ },
"windows_updates": {
"deadline_days": 7,
"grace_period_days": 3
diff --git a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml
index 3138a7d349fc..b28ab395b3e9 100644
--- a/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml
+++ b/cmd/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml
@@ -18,7 +18,8 @@ spec:
jira: null
zendesk: null
mdm:
- apple_bm_default_team: ""
+ apple_business_manager: null
+ volume_purchasing_program: null
apple_bm_enabled_and_configured: false
apple_bm_terms_expired: false
enabled_and_configured: false
@@ -31,6 +32,12 @@ spec:
macos_updates:
minimum_version: null
deadline: null
+ ios_updates:
+ minimum_version: null
+ deadline: null
+ ipados_updates:
+ minimum_version: null
+ deadline: null
windows_updates:
deadline_days: 7
grace_period_days: 3
diff --git a/cmd/fleetctl/testdata/expectedGetTeamsJson.json b/cmd/fleetctl/testdata/expectedGetTeamsJson.json
index 1af52bee61b6..de6669ac1259 100644
--- a/cmd/fleetctl/testdata/expectedGetTeamsJson.json
+++ b/cmd/fleetctl/testdata/expectedGetTeamsJson.json
@@ -35,6 +35,14 @@
"minimum_version": null,
"deadline": null
},
+ "ios_updates": {
+ "minimum_version": null,
+ "deadline": null
+ },
+ "ipados_updates": {
+ "minimum_version": null,
+ "deadline": null
+ },
"windows_updates": {
"deadline_days": null,
"grace_period_days": null
@@ -53,7 +61,6 @@
}
},
"scripts": null,
- "software": null,
"user_count": 99,
"host_count": 42
}
@@ -111,6 +118,14 @@
"minimum_version": "12.3.1",
"deadline": "2021-12-14"
},
+ "ios_updates": {
+ "minimum_version": "17.5",
+ "deadline": "2022-11-15"
+ },
+ "ipados_updates": {
+ "minimum_version": "18.0",
+ "deadline": "2023-01-01"
+ },
"windows_updates": {
"deadline_days": 7,
"grace_period_days": 3
@@ -129,7 +144,6 @@
}
},
"scripts": null,
- "software": null,
"user_count": 87,
"host_count": 43
}
diff --git a/cmd/fleetctl/testdata/expectedGetTeamsYaml.yml b/cmd/fleetctl/testdata/expectedGetTeamsYaml.yml
index f10577a3af6f..422456f5b54e 100644
--- a/cmd/fleetctl/testdata/expectedGetTeamsYaml.yml
+++ b/cmd/fleetctl/testdata/expectedGetTeamsYaml.yml
@@ -16,6 +16,12 @@ spec:
macos_updates:
minimum_version: null
deadline: null
+ ios_updates:
+ minimum_version: null
+ deadline: null
+ ipados_updates:
+ minimum_version: null
+ deadline: null
windows_updates:
deadline_days: null
grace_period_days: null
@@ -30,7 +36,6 @@ spec:
macos_setup_assistant:
scripts: null
secrets: null
- software: null
webhook_settings:
host_status_webhook: null
name: team1
@@ -61,6 +66,12 @@ spec:
macos_updates:
minimum_version: "12.3.1"
deadline: "2021-12-14"
+ ios_updates:
+ minimum_version: "17.5"
+ deadline: "2022-11-15"
+ macos_updates:
+ minimum_version: "18.0"
+ deadline: "2023-01-01"
windows_updates:
deadline_days: 7
grace_period_days: 3
@@ -74,7 +85,6 @@ spec:
enable_release_device_manually: false
macos_setup_assistant:
scripts: null
- software: null
webhook_settings:
host_status_webhook: null
name: team2
diff --git a/cmd/fleetctl/testdata/gitops/global_config_no_paths.yml b/cmd/fleetctl/testdata/gitops/global_config_no_paths.yml
index 76936e3ad5af..894841a9338e 100644
--- a/cmd/fleetctl/testdata/gitops/global_config_no_paths.yml
+++ b/cmd/fleetctl/testdata/gitops/global_config_no_paths.yml
@@ -146,7 +146,6 @@ org_settings:
"private_key": "google_calendar_private_key",
}
mdm:
- apple_bm_default_team: $APPLE_BM_DEFAULT_TEAM
end_user_authentication:
entity_id: ""
idp_name: ""
@@ -187,3 +186,4 @@ org_settings:
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_macos_custom_settings_valid_deprecated.yml b/cmd/fleetctl/testdata/gitops/global_macos_custom_settings_valid_deprecated.yml
index 177c1c80cff9..b09844258516 100644
--- a/cmd/fleetctl/testdata/gitops/global_macos_custom_settings_valid_deprecated.yml
+++ b/cmd/fleetctl/testdata/gitops/global_macos_custom_settings_valid_deprecated.yml
@@ -91,3 +91,4 @@ org_settings:
databases_path: ""
secrets:
- secret: ABC
+software:
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/global_macos_windows_custom_settings_valid.yml b/cmd/fleetctl/testdata/gitops/global_macos_windows_custom_settings_valid.yml
index da75847cd59c..e6231bf03098 100644
--- a/cmd/fleetctl/testdata/gitops/global_macos_windows_custom_settings_valid.yml
+++ b/cmd/fleetctl/testdata/gitops/global_macos_windows_custom_settings_valid.yml
@@ -97,3 +97,4 @@ org_settings:
databases_path: ""
secrets:
- secret: ABC
+software:
diff --git a/cmd/fleetctl/testdata/gitops/global_windows_custom_settings_invalid_label_mix.yml b/cmd/fleetctl/testdata/gitops/global_windows_custom_settings_invalid_label_mix.yml
index 9d2ac6e69f62..1a100de6f365 100644
--- a/cmd/fleetctl/testdata/gitops/global_windows_custom_settings_invalid_label_mix.yml
+++ b/cmd/fleetctl/testdata/gitops/global_windows_custom_settings_invalid_label_mix.yml
@@ -93,3 +93,4 @@ org_settings:
databases_path: ""
secrets:
- secret: ABC
+software:
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/global_windows_custom_settings_unknown_label.yml b/cmd/fleetctl/testdata/gitops/global_windows_custom_settings_unknown_label.yml
index ba1d06f7849e..5208ba724863 100644
--- a/cmd/fleetctl/testdata/gitops/global_windows_custom_settings_unknown_label.yml
+++ b/cmd/fleetctl/testdata/gitops/global_windows_custom_settings_unknown_label.yml
@@ -91,3 +91,4 @@ org_settings:
databases_path: ""
secrets:
- secret: ABC
+software:
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/lib/query_multiple.yml b/cmd/fleetctl/testdata/gitops/lib/query_multiple.yml
index c3109b5f71f1..f14ea9ea0e0c 100644
--- a/cmd/fleetctl/testdata/gitops/lib/query_multiple.yml
+++ b/cmd/fleetctl/testdata/gitops/lib/query_multiple.yml
@@ -1,11 +1,4 @@
-apiVersion: v1
-kind: query
-spec:
- name: query_ruby
+- name: query_ruby
query: select 1
----
-apiVersion: v1
-kind: query
-spec:
- name: query_ruby2
+- name: query_ruby2
query: select 2
diff --git a/cmd/fleetctl/testdata/gitops/lib/query_multiple_apply.yml b/cmd/fleetctl/testdata/gitops/lib/query_multiple_apply.yml
new file mode 100644
index 000000000000..c3109b5f71f1
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/lib/query_multiple_apply.yml
@@ -0,0 +1,11 @@
+apiVersion: v1
+kind: query
+spec:
+ name: query_ruby
+ query: select 1
+---
+apiVersion: v1
+kind: query
+spec:
+ name: query_ruby2
+ query: select 2
diff --git a/cmd/fleetctl/testdata/gitops/lib/query_ruby.yml b/cmd/fleetctl/testdata/gitops/lib/query_ruby.yml
index 28714447bf35..bb61c6b32a62 100644
--- a/cmd/fleetctl/testdata/gitops/lib/query_ruby.yml
+++ b/cmd/fleetctl/testdata/gitops/lib/query_ruby.yml
@@ -1,5 +1,2 @@
-apiVersion: v1
-kind: query
-spec:
- name: query_ruby
+- name: query_ruby
query: select 1
diff --git a/cmd/fleetctl/testdata/gitops/lib/query_ruby_apply.yml b/cmd/fleetctl/testdata/gitops/lib/query_ruby_apply.yml
new file mode 100644
index 000000000000..28714447bf35
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/lib/query_ruby_apply.yml
@@ -0,0 +1,5 @@
+apiVersion: v1
+kind: query
+spec:
+ name: query_ruby
+ query: select 1
diff --git a/cmd/fleetctl/testdata/gitops/lib/query_ruby_env.yml b/cmd/fleetctl/testdata/gitops/lib/query_ruby_env.yml
new file mode 100644
index 000000000000..c9b15ff2291f
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/lib/query_ruby_env.yml
@@ -0,0 +1,2 @@
+- name: query_ruby
+ query: select ${QUERY_VAR}
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_install_not_found.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_install_not_found.yml
new file mode 100644
index 000000000000..d3bcada54e82
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_install_not_found.yml
@@ -0,0 +1,19 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/notfound.sh
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_invalid_self_service_value.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_invalid_self_service_value.yml
new file mode 100644
index 000000000000..acee06d683a7
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_invalid_self_service_value.yml
@@ -0,0 +1,18 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/invalidtype.txt
+ self_service: "not a boolean"
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_no_url.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_no_url.yml
new file mode 100644
index 000000000000..6d83a9daed50
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_no_url.yml
@@ -0,0 +1,22 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - install_script:
+ path: lib/install_ruby.sh
+ pre_install_query:
+ path: lib/query_ruby.yml
+ post_install_script:
+ path: lib/post_install_ruby.sh
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_not_found.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_not_found.yml
new file mode 100644
index 000000000000..cd7332f91e56
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_not_found.yml
@@ -0,0 +1,17 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/notfound.deb
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_post_install_not_found.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_post_install_not_found.yml
new file mode 100644
index 000000000000..ac0a436360ca
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_post_install_not_found.yml
@@ -0,0 +1,21 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/install_ruby.sh
+ post_install_script:
+ path: lib/notfound.sh
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_pre_condition_multiple_queries.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_pre_condition_multiple_queries.yml
new file mode 100644
index 000000000000..a2b5419c056a
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_pre_condition_multiple_queries.yml
@@ -0,0 +1,23 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/install_ruby.sh
+ pre_install_query:
+ path: lib/query_multiple.yml
+ post_install_script:
+ path: lib/post_install_ruby.sh
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_pre_condition_not_found.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_pre_condition_not_found.yml
new file mode 100644
index 000000000000..bafde42691b5
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_pre_condition_not_found.yml
@@ -0,0 +1,21 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/install_ruby.sh
+ pre_install_query:
+ path: lib/notfound.yml
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_too_large.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_too_large.yml
new file mode 100644
index 000000000000..db4ffd32113a
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_too_large.yml
@@ -0,0 +1,17 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/toolarge.deb
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_unsupported.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_unsupported.yml
new file mode 100644
index 000000000000..2bc609b931d7
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_unsupported.yml
@@ -0,0 +1,17 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/invalidtype.txt
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/no_team_software_installer_valid.yml b/cmd/fleetctl/testdata/gitops/no_team_software_installer_valid.yml
new file mode 100644
index 000000000000..e0fcaa490ec0
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/no_team_software_installer_valid.yml
@@ -0,0 +1,25 @@
+# Test config
+controls:
+queries:
+policies:
+agent_options:
+org_settings:
+ server_settings:
+ server_url: $FLEET_SERVER_URL
+ org_info:
+ contact_url: https://example.com/contact
+ org_logo_url: ""
+ org_logo_url_light_background: ""
+ org_name: ${ORG_NAME}
+ secrets: [{"secret":"globalSecret"}]
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/install_ruby.sh
+ pre_install_query:
+ path: lib/query_ruby.yml
+ post_install_script:
+ path: lib/post_install_ruby.sh
+ - url: ${SOFTWARE_INSTALLER_URL}/other.deb
+ self_service: true
\ No newline at end of file
diff --git a/cmd/fleetctl/testdata/gitops/team_config_no_paths.yml b/cmd/fleetctl/testdata/gitops/team_config_no_paths.yml
index ee81d27fc4aa..785ba5d21564 100644
--- a/cmd/fleetctl/testdata/gitops/team_config_no_paths.yml
+++ b/cmd/fleetctl/testdata/gitops/team_config_no_paths.yml
@@ -116,12 +116,13 @@ policies:
resolution: There is no resolution for this policy.
query: SELECT 1 FROM osquery_info WHERE start_time < 0;
software:
- - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
- install_script:
- path: lib/install_ruby.sh
- pre_install_query:
- path: lib/query_ruby.yml
- post_install_script:
- path: lib/post_install_ruby.sh
- - url: ${SOFTWARE_INSTALLER_URL}/other.deb
- self_service: true
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/install_ruby.sh
+ pre_install_query:
+ path: lib/query_ruby.yml
+ post_install_script:
+ path: lib/post_install_ruby.sh
+ - url: ${SOFTWARE_INSTALLER_URL}/other.deb
+ self_service: true
diff --git a/cmd/fleetctl/testdata/gitops/team_software_installer_install_not_found.yml b/cmd/fleetctl/testdata/gitops/team_software_installer_install_not_found.yml
index c3837b3a0e5c..8c25d098a142 100644
--- a/cmd/fleetctl/testdata/gitops/team_software_installer_install_not_found.yml
+++ b/cmd/fleetctl/testdata/gitops/team_software_installer_install_not_found.yml
@@ -13,6 +13,7 @@ controls:
policies:
queries:
software:
- - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
- install_script:
- path: lib/notfound.sh
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/notfound.sh
diff --git a/cmd/fleetctl/testdata/gitops/team_software_installer_invalid_self_service_value.yml b/cmd/fleetctl/testdata/gitops/team_software_installer_invalid_self_service_value.yml
index b27a98270316..fe50a971d0f8 100644
--- a/cmd/fleetctl/testdata/gitops/team_software_installer_invalid_self_service_value.yml
+++ b/cmd/fleetctl/testdata/gitops/team_software_installer_invalid_self_service_value.yml
@@ -13,5 +13,6 @@ controls:
policies:
queries:
software:
- - url: ${SOFTWARE_INSTALLER_URL}/invalidtype.txt
- self_service: "not a boolean"
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/invalidtype.txt
+ self_service: "not a boolean"
diff --git a/cmd/fleetctl/testdata/gitops/team_software_installer_no_url.yml b/cmd/fleetctl/testdata/gitops/team_software_installer_no_url.yml
index 43bfa4babf31..dfdce818797b 100644
--- a/cmd/fleetctl/testdata/gitops/team_software_installer_no_url.yml
+++ b/cmd/fleetctl/testdata/gitops/team_software_installer_no_url.yml
@@ -13,9 +13,10 @@ controls:
policies:
queries:
software:
- - install_script:
- path: lib/install_ruby.sh
- pre_install_query:
- path: lib/query_ruby.yml
- post_install_script:
- path: lib/post_install_ruby.sh
+ packages:
+ - install_script:
+ path: lib/install_ruby.sh
+ pre_install_query:
+ path: lib/query_ruby.yml
+ post_install_script:
+ path: lib/post_install_ruby.sh
diff --git a/cmd/fleetctl/testdata/gitops/team_software_installer_not_found.yml b/cmd/fleetctl/testdata/gitops/team_software_installer_not_found.yml
index ca657a5736f9..ff30037bd532 100644
--- a/cmd/fleetctl/testdata/gitops/team_software_installer_not_found.yml
+++ b/cmd/fleetctl/testdata/gitops/team_software_installer_not_found.yml
@@ -13,4 +13,5 @@ controls:
policies:
queries:
software:
- - url: ${SOFTWARE_INSTALLER_URL}/notfound.deb
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/notfound.deb
diff --git a/cmd/fleetctl/testdata/gitops/team_software_installer_post_install_not_found.yml b/cmd/fleetctl/testdata/gitops/team_software_installer_post_install_not_found.yml
index 4cc9fbcef78c..cdd20768bf17 100644
--- a/cmd/fleetctl/testdata/gitops/team_software_installer_post_install_not_found.yml
+++ b/cmd/fleetctl/testdata/gitops/team_software_installer_post_install_not_found.yml
@@ -13,8 +13,9 @@ controls:
policies:
queries:
software:
- - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
- install_script:
- path: lib/install_ruby.sh
- post_install_script:
- path: lib/notfound.sh
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/install_ruby.sh
+ post_install_script:
+ path: lib/notfound.sh
diff --git a/cmd/fleetctl/testdata/gitops/team_software_installer_pre_condition_multiple_queries.yml b/cmd/fleetctl/testdata/gitops/team_software_installer_pre_condition_multiple_queries.yml
index 4b26e63e4eff..a9e96544a22b 100644
--- a/cmd/fleetctl/testdata/gitops/team_software_installer_pre_condition_multiple_queries.yml
+++ b/cmd/fleetctl/testdata/gitops/team_software_installer_pre_condition_multiple_queries.yml
@@ -13,10 +13,11 @@ controls:
policies:
queries:
software:
- - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
- install_script:
- path: lib/install_ruby.sh
- pre_install_query:
- path: lib/query_multiple.yml
- post_install_script:
- path: lib/post_install_ruby.sh
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/install_ruby.sh
+ pre_install_query:
+ path: lib/query_multiple.yml
+ post_install_script:
+ path: lib/post_install_ruby.sh
diff --git a/cmd/fleetctl/testdata/gitops/team_software_installer_pre_condition_multiple_queries_apply.yml b/cmd/fleetctl/testdata/gitops/team_software_installer_pre_condition_multiple_queries_apply.yml
new file mode 100644
index 000000000000..7c88f9963deb
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/team_software_installer_pre_condition_multiple_queries_apply.yml
@@ -0,0 +1,23 @@
+name: "${TEST_TEAM_NAME}"
+team_settings:
+ secrets:
+ - secret: "ABC"
+ features:
+ enable_host_users: true
+ enable_software_inventory: true
+ host_expiry_settings:
+ host_expiry_enabled: true
+ host_expiry_window: 30
+agent_options:
+controls:
+policies:
+queries:
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/install_ruby.sh
+ pre_install_query:
+ path: lib/query_multiple_apply.yml
+ post_install_script:
+ path: lib/post_install_ruby.sh
diff --git a/cmd/fleetctl/testdata/gitops/team_software_installer_pre_condition_not_found.yml b/cmd/fleetctl/testdata/gitops/team_software_installer_pre_condition_not_found.yml
index 681590d04d19..162a9aecb998 100644
--- a/cmd/fleetctl/testdata/gitops/team_software_installer_pre_condition_not_found.yml
+++ b/cmd/fleetctl/testdata/gitops/team_software_installer_pre_condition_not_found.yml
@@ -13,8 +13,9 @@ controls:
policies:
queries:
software:
- - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
- install_script:
- path: lib/install_ruby.sh
- pre_install_query:
- path: lib/notfound.yml
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/install_ruby.sh
+ pre_install_query:
+ path: lib/notfound.yml
diff --git a/cmd/fleetctl/testdata/gitops/team_software_installer_too_large.yml b/cmd/fleetctl/testdata/gitops/team_software_installer_too_large.yml
index 15d16e9e9a9e..6592a5521247 100644
--- a/cmd/fleetctl/testdata/gitops/team_software_installer_too_large.yml
+++ b/cmd/fleetctl/testdata/gitops/team_software_installer_too_large.yml
@@ -13,4 +13,5 @@ controls:
policies:
queries:
software:
- - url: ${SOFTWARE_INSTALLER_URL}/toolarge.deb
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/toolarge.deb
diff --git a/cmd/fleetctl/testdata/gitops/team_software_installer_unsupported.yml b/cmd/fleetctl/testdata/gitops/team_software_installer_unsupported.yml
index 3f58009a03ae..b722970cf8be 100644
--- a/cmd/fleetctl/testdata/gitops/team_software_installer_unsupported.yml
+++ b/cmd/fleetctl/testdata/gitops/team_software_installer_unsupported.yml
@@ -13,4 +13,5 @@ controls:
policies:
queries:
software:
- - url: ${SOFTWARE_INSTALLER_URL}/invalidtype.txt
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/invalidtype.txt
diff --git a/cmd/fleetctl/testdata/gitops/team_software_installer_valid.yml b/cmd/fleetctl/testdata/gitops/team_software_installer_valid.yml
index ceeb1a74151a..e8941122495f 100644
--- a/cmd/fleetctl/testdata/gitops/team_software_installer_valid.yml
+++ b/cmd/fleetctl/testdata/gitops/team_software_installer_valid.yml
@@ -13,12 +13,13 @@ controls:
policies:
queries:
software:
- - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
- install_script:
- path: lib/install_ruby.sh
- pre_install_query:
- path: lib/query_ruby.yml
- post_install_script:
- path: lib/post_install_ruby.sh
- - url: ${SOFTWARE_INSTALLER_URL}/other.deb
- self_service: true
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/install_ruby.sh
+ pre_install_query:
+ path: lib/query_ruby.yml
+ post_install_script:
+ path: lib/post_install_ruby.sh
+ - url: ${SOFTWARE_INSTALLER_URL}/other.deb
+ self_service: true
diff --git a/cmd/fleetctl/testdata/gitops/team_software_installer_valid_apply.yml b/cmd/fleetctl/testdata/gitops/team_software_installer_valid_apply.yml
new file mode 100644
index 000000000000..9fd14c4922cd
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/team_software_installer_valid_apply.yml
@@ -0,0 +1,25 @@
+name: "${TEST_TEAM_NAME}"
+team_settings:
+ secrets:
+ - secret: "ABC"
+ features:
+ enable_host_users: true
+ enable_software_inventory: true
+ host_expiry_settings:
+ host_expiry_enabled: true
+ host_expiry_window: 30
+agent_options:
+controls:
+policies:
+queries:
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/install_ruby.sh
+ pre_install_query:
+ path: lib/query_ruby_apply.yml
+ post_install_script:
+ path: lib/post_install_ruby.sh
+ - url: ${SOFTWARE_INSTALLER_URL}/other.deb
+ self_service: true
diff --git a/cmd/fleetctl/testdata/gitops/team_software_installer_valid_env_query.yml b/cmd/fleetctl/testdata/gitops/team_software_installer_valid_env_query.yml
new file mode 100644
index 000000000000..1c60d3f46964
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/team_software_installer_valid_env_query.yml
@@ -0,0 +1,25 @@
+name: "${TEST_TEAM_NAME}"
+team_settings:
+ secrets:
+ - secret: "ABC"
+ features:
+ enable_host_users: true
+ enable_software_inventory: true
+ host_expiry_settings:
+ host_expiry_enabled: true
+ host_expiry_window: 30
+agent_options:
+controls:
+policies:
+queries:
+software:
+ packages:
+ - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
+ install_script:
+ path: lib/install_ruby.sh
+ pre_install_query:
+ path: lib/query_ruby_env.yml
+ post_install_script:
+ path: lib/post_install_ruby.sh
+ - url: ${SOFTWARE_INSTALLER_URL}/other.deb
+ self_service: true
diff --git a/it-and-security/teams/virtual-machines.yml b/cmd/fleetctl/testdata/gitops/team_vpp_empty_adamid.yml
similarity index 50%
rename from it-and-security/teams/virtual-machines.yml
rename to cmd/fleetctl/testdata/gitops/team_vpp_empty_adamid.yml
index cef82d618e32..675618c60907 100644
--- a/it-and-security/teams/virtual-machines.yml
+++ b/cmd/fleetctl/testdata/gitops/team_vpp_empty_adamid.yml
@@ -1,15 +1,17 @@
-name: "Virtual machines"
+name: "${TEST_TEAM_NAME}"
team_settings:
+ secrets:
+ - secret: "ABC"
features:
enable_host_users: true
enable_software_inventory: true
host_expiry_settings:
- host_expiry_enabled: false
- host_expiry_window: 0
- secrets:
- - secret: $DOGFOOD_VIRTUAL_MACHINES_ENROLL_SECRET
+ host_expiry_enabled: true
+ host_expiry_window: 30
agent_options:
- path: ../lib/agent-options.yml
controls:
policies:
queries:
+software:
+ app_store_apps:
+ - app_store_id:
diff --git a/cmd/fleetctl/testdata/gitops/team_vpp_incorrect_type.yml b/cmd/fleetctl/testdata/gitops/team_vpp_incorrect_type.yml
new file mode 100644
index 000000000000..74e7b178063a
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/team_vpp_incorrect_type.yml
@@ -0,0 +1,17 @@
+name: "${TEST_TEAM_NAME}"
+team_settings:
+ secrets:
+ - secret: "ABC"
+ features:
+ enable_host_users: true
+ enable_software_inventory: true
+ host_expiry_settings:
+ host_expiry_enabled: true
+ host_expiry_window: 30
+agent_options:
+controls:
+policies:
+queries:
+software:
+ app_store_apps:
+ - app_store_id: 1
diff --git a/cmd/fleetctl/testdata/gitops/team_vpp_invalid_app.yml b/cmd/fleetctl/testdata/gitops/team_vpp_invalid_app.yml
new file mode 100644
index 000000000000..f0822f443b06
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/team_vpp_invalid_app.yml
@@ -0,0 +1,17 @@
+name: "${TEST_TEAM_NAME}"
+team_settings:
+ secrets:
+ - secret: "ABC"
+ features:
+ enable_host_users: true
+ enable_software_inventory: true
+ host_expiry_settings:
+ host_expiry_enabled: true
+ host_expiry_window: 30
+agent_options:
+controls:
+policies:
+queries:
+software:
+ app_store_apps:
+ - app_store_id: "999999999"
diff --git a/cmd/fleetctl/testdata/gitops/team_vpp_valid_app.yml b/cmd/fleetctl/testdata/gitops/team_vpp_valid_app.yml
new file mode 100644
index 000000000000..8d588fb12784
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/team_vpp_valid_app.yml
@@ -0,0 +1,17 @@
+name: "${TEST_TEAM_NAME}"
+team_settings:
+ secrets:
+ - secret: "ABC"
+ features:
+ enable_host_users: true
+ enable_software_inventory: true
+ host_expiry_settings:
+ host_expiry_enabled: true
+ host_expiry_window: 30
+agent_options:
+controls:
+policies:
+queries:
+software:
+ app_store_apps:
+ - app_store_id: "1"
diff --git a/cmd/fleetctl/testdata/gitops/team_vpp_valid_app_self_service.yml b/cmd/fleetctl/testdata/gitops/team_vpp_valid_app_self_service.yml
new file mode 100644
index 000000000000..a2daad396efd
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/team_vpp_valid_app_self_service.yml
@@ -0,0 +1,18 @@
+name: "${TEST_TEAM_NAME}"
+team_settings:
+ secrets:
+ - secret: "ABC"
+ features:
+ enable_host_users: true
+ enable_software_inventory: true
+ host_expiry_settings:
+ host_expiry_enabled: true
+ host_expiry_window: 30
+agent_options:
+controls:
+policies:
+queries:
+software:
+ app_store_apps:
+ - app_store_id: "1"
+ self_service: true
diff --git a/cmd/fleetctl/testdata/gitops/team_vpp_valid_empty.yml b/cmd/fleetctl/testdata/gitops/team_vpp_valid_empty.yml
new file mode 100644
index 000000000000..1c98065d599e
--- /dev/null
+++ b/cmd/fleetctl/testdata/gitops/team_vpp_valid_empty.yml
@@ -0,0 +1,16 @@
+name: "${TEST_TEAM_NAME}"
+team_settings:
+ secrets:
+ - secret: "ABC"
+ features:
+ enable_host_users: true
+ enable_software_inventory: true
+ host_expiry_settings:
+ host_expiry_enabled: true
+ host_expiry_window: 30
+agent_options:
+controls:
+policies:
+queries:
+software:
+ app_store_apps:
diff --git a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml
index 67bb96e8c339..02adaad3acfc 100644
--- a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml
+++ b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml
@@ -18,7 +18,8 @@ spec:
jira: null
zendesk: null
mdm:
- apple_bm_default_team: ""
+ apple_business_manager:
+ volume_purchasing_program:
apple_bm_enabled_and_configured: false
apple_bm_terms_expired: false
enabled_and_configured: true
@@ -40,6 +41,12 @@ spec:
macos_updates:
deadline: null
minimum_version: null
+ ios_updates:
+ deadline: null
+ minimum_version: null
+ ipados_updates:
+ deadline: null
+ minimum_version: null
windows_updates:
deadline_days: null
grace_period_days: null
diff --git a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml
index d73894e4a1b5..2dd2f93adf1f 100644
--- a/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml
+++ b/cmd/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml
@@ -18,7 +18,8 @@ spec:
jira: null
zendesk: null
mdm:
- apple_bm_default_team: ""
+ apple_business_manager:
+ volume_purchasing_program:
apple_bm_enabled_and_configured: false
apple_bm_terms_expired: false
enabled_and_configured: true
@@ -40,6 +41,12 @@ spec:
macos_updates:
deadline: null
minimum_version: null
+ ios_updates:
+ deadline: null
+ minimum_version: null
+ ipados_updates:
+ deadline: null
+ minimum_version: null
windows_updates:
deadline_days: null
grace_period_days: null
diff --git a/cmd/fleetctl/testdata/macosSetupExpectedTeam1And2Empty.yml b/cmd/fleetctl/testdata/macosSetupExpectedTeam1And2Empty.yml
index b5a4c03e5cb9..f0aa275b56b3 100644
--- a/cmd/fleetctl/testdata/macosSetupExpectedTeam1And2Empty.yml
+++ b/cmd/fleetctl/testdata/macosSetupExpectedTeam1And2Empty.yml
@@ -25,12 +25,17 @@ spec:
macos_updates:
deadline: null
minimum_version: null
+ ios_updates:
+ deadline: null
+ minimum_version: null
+ ipados_updates:
+ deadline: null
+ minimum_version: null
windows_updates:
deadline_days: null
grace_period_days: null
scripts: null
secrets: null
- software: null
webhook_settings:
host_status_webhook: null
name: tm1
@@ -60,12 +65,17 @@ spec:
macos_updates:
deadline: null
minimum_version: null
+ ios_updates:
+ deadline: null
+ minimum_version: null
+ ipados_updates:
+ deadline: null
+ minimum_version: null
windows_updates:
deadline_days: null
grace_period_days: null
scripts: null
secrets: null
- software: null
webhook_settings:
host_status_webhook: null
name: tm2
diff --git a/cmd/fleetctl/testdata/macosSetupExpectedTeam1And2Set.yml b/cmd/fleetctl/testdata/macosSetupExpectedTeam1And2Set.yml
index a0d15fddd7a7..1d2b07674017 100644
--- a/cmd/fleetctl/testdata/macosSetupExpectedTeam1And2Set.yml
+++ b/cmd/fleetctl/testdata/macosSetupExpectedTeam1And2Set.yml
@@ -25,12 +25,17 @@ spec:
macos_updates:
deadline: null
minimum_version: null
+ ios_updates:
+ deadline: null
+ minimum_version: null
+ ipados_updates:
+ deadline: null
+ minimum_version: null
windows_updates:
deadline_days: null
grace_period_days: null
scripts: null
secrets: null
- software: null
webhook_settings:
host_status_webhook: null
name: tm1
@@ -60,12 +65,17 @@ spec:
macos_updates:
deadline: null
minimum_version: null
+ ios_updates:
+ deadline: null
+ minimum_version: null
+ ipados_updates:
+ deadline: null
+ minimum_version: null
windows_updates:
deadline_days: null
grace_period_days: null
scripts: null
secrets: null
- software: null
webhook_settings:
host_status_webhook: null
name: tm2
diff --git a/cmd/fleetctl/testdata/macosSetupExpectedTeam1Empty.yml b/cmd/fleetctl/testdata/macosSetupExpectedTeam1Empty.yml
index 8a6762468c41..885d25482758 100644
--- a/cmd/fleetctl/testdata/macosSetupExpectedTeam1Empty.yml
+++ b/cmd/fleetctl/testdata/macosSetupExpectedTeam1Empty.yml
@@ -23,6 +23,12 @@ spec:
macos_updates:
deadline: null
minimum_version: null
+ ios_updates:
+ deadline: null
+ minimum_version: null
+ ipados_updates:
+ deadline: null
+ minimum_version: null
windows_updates:
deadline_days: null
grace_period_days: null
@@ -30,8 +36,6 @@ spec:
custom_settings: null
scripts: null
secrets: null
- software: null
webhook_settings:
host_status_webhook: null
name: tm1
-
diff --git a/cmd/fleetctl/testdata/macosSetupExpectedTeam1Set.yml b/cmd/fleetctl/testdata/macosSetupExpectedTeam1Set.yml
index 2aac4b1481d6..76c45b10b2f2 100644
--- a/cmd/fleetctl/testdata/macosSetupExpectedTeam1Set.yml
+++ b/cmd/fleetctl/testdata/macosSetupExpectedTeam1Set.yml
@@ -24,12 +24,17 @@ spec:
macos_updates:
deadline: null
minimum_version: null
+ ios_updates:
+ deadline: null
+ minimum_version: null
+ ipados_updates:
+ deadline: null
+ minimum_version: null
windows_updates:
deadline_days: null
grace_period_days: null
scripts: null
secrets: null
- software: null
webhook_settings:
host_status_webhook: null
name: tm1
diff --git a/cmd/fleetctl/testing_utils.go b/cmd/fleetctl/testing_utils.go
index 7a278ebc3c6b..918a12b94e27 100644
--- a/cmd/fleetctl/testing_utils.go
+++ b/cmd/fleetctl/testing_utils.go
@@ -127,24 +127,24 @@ func runServerWithMockedDS(t *testing.T, opts ...*service.TestServerOpts) (*http
require.NoError(t, err)
ds.GetAllMDMConfigAssetsHashesFunc = func(ctx context.Context, assetNames []fleet.MDMAssetName) (map[fleet.MDMAssetName]string, error) {
return map[fleet.MDMAssetName]string{
- fleet.MDMAssetABMCert: "abmcert",
- fleet.MDMAssetABMKey: "abmkey",
- fleet.MDMAssetABMToken: "abmtoken",
- fleet.MDMAssetAPNSCert: "apnscert",
- fleet.MDMAssetAPNSKey: "apnskey",
- fleet.MDMAssetCACert: "scepcert",
- fleet.MDMAssetCAKey: "scepkey",
+ fleet.MDMAssetABMCert: "abmcert",
+ fleet.MDMAssetABMKey: "abmkey",
+ fleet.MDMAssetABMTokenDeprecated: "abmtoken",
+ fleet.MDMAssetAPNSCert: "apnscert",
+ fleet.MDMAssetAPNSKey: "apnskey",
+ fleet.MDMAssetCACert: "scepcert",
+ fleet.MDMAssetCAKey: "scepkey",
}, nil
}
ds.GetAllMDMConfigAssetsByNameFunc = func(ctx context.Context, assetNames []fleet.MDMAssetName) (map[fleet.MDMAssetName]fleet.MDMConfigAsset, error) {
return map[fleet.MDMAssetName]fleet.MDMConfigAsset{
- fleet.MDMAssetABMCert: {Name: fleet.MDMAssetABMCert, Value: certPEM},
- fleet.MDMAssetABMKey: {Name: fleet.MDMAssetABMKey, Value: keyPEM},
- fleet.MDMAssetABMToken: {Name: fleet.MDMAssetABMToken, Value: tokenBytes},
- fleet.MDMAssetAPNSCert: {Name: fleet.MDMAssetAPNSCert, Value: apnsCert},
- fleet.MDMAssetAPNSKey: {Name: fleet.MDMAssetAPNSKey, Value: apnsKey},
- fleet.MDMAssetCACert: {Name: fleet.MDMAssetCACert, Value: certPEM},
- fleet.MDMAssetCAKey: {Name: fleet.MDMAssetCAKey, Value: keyPEM},
+ fleet.MDMAssetABMCert: {Name: fleet.MDMAssetABMCert, Value: certPEM},
+ fleet.MDMAssetABMKey: {Name: fleet.MDMAssetABMKey, Value: keyPEM},
+ fleet.MDMAssetABMTokenDeprecated: {Name: fleet.MDMAssetABMTokenDeprecated, Value: tokenBytes},
+ fleet.MDMAssetAPNSCert: {Name: fleet.MDMAssetAPNSCert, Value: apnsCert},
+ fleet.MDMAssetAPNSKey: {Name: fleet.MDMAssetAPNSKey, Value: apnsKey},
+ fleet.MDMAssetCACert: {Name: fleet.MDMAssetCACert, Value: certPEM},
+ fleet.MDMAssetCAKey: {Name: fleet.MDMAssetCAKey, Value: keyPEM},
}, nil
}
diff --git a/cmd/fleetctl/vulnerability_data_stream_test.go b/cmd/fleetctl/vulnerability_data_stream_test.go
index 0e61949eea11..d1316a3251d1 100644
--- a/cmd/fleetctl/vulnerability_data_stream_test.go
+++ b/cmd/fleetctl/vulnerability_data_stream_test.go
@@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/require"
)
-func TestVulnerabilityDataStream(t *testing.T) {
+func TestIntegrationsVulnerabilityDataStream(t *testing.T) {
nettest.Run(t)
runAppCheckErr(t, []string{"vulnerability-data-stream"}, "No directory provided")
diff --git a/cmd/osquery-perf/README.md b/cmd/osquery-perf/README.md
index 5ec687f8fd5e..eb179dd13c74 100644
--- a/cmd/osquery-perf/README.md
+++ b/cmd/osquery-perf/README.md
@@ -95,3 +95,27 @@ Run the following command in the shell before running the Fleet server _and_ bef
``` sh
ulimit -n 64000
```
+
+## Running with MDM
+
+Set up MDM on your server. To extract the SCEP challenge, you can use the [MDM asset extractor](https://github.com/fleetdm/fleet/tree/main/tools/mdm/assets).
+
+For your server, disable Apple push notifications since we will be using devices with fake UUIDs:
+
+```
+export FLEET_DEV_MDM_APPLE_DISABLE_PUSH=1
+```
+
+Example of running the agent with MDM. Note that `enroll_secret` is not needed for iPhone/iPad devices:
+
+```
+go run agent.go --os_templates ipad_13.18,iphone_14.6 --host_count 10 --mdm_scep_challenge 0d53306e-6d7a-9d14-a372-f9e53f9d62db
+```
+
+## Installing software
+
+The agent can install software for "macos", "ubuntu", and "windows" OSs when running with orbit agent. The following options control the installation behavior:
+
+- `--software_installer_pre_install_fail_prob`: default 0.05, `select 1` always passes and `select 0` always fails
+- `--software_installer_install_fail_prob`: default 0.05, `exit 0` always passes and `exit 1` always fails
+- `--software_installer_post_install_fail_prob`: default 0.05, `exit 0` always passes and `exit 1` always fails
diff --git a/cmd/osquery-perf/agent.go b/cmd/osquery-perf/agent.go
index 46f6e691832f..10e8ee21b0a8 100644
--- a/cmd/osquery-perf/agent.go
+++ b/cmd/osquery-perf/agent.go
@@ -25,6 +25,8 @@ import (
"text/template"
"time"
+ "github.com/fleetdm/fleet/v4/cmd/osquery-perf/installer_cache"
+ "github.com/fleetdm/fleet/v4/pkg/file"
"github.com/fleetdm/fleet/v4/pkg/mdm/mdmtest"
"github.com/fleetdm/fleet/v4/server/fleet"
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
@@ -54,6 +56,8 @@ var (
vsCodeExtensionsVulnerableSoftware []fleet.Software
windowsSoftware []map[string]string
ubuntuSoftware []map[string]string
+
+ installerMetadataCache installer_cache.Metadata
)
func loadMacOSVulnerableSoftware() {
@@ -424,6 +428,27 @@ func (n *nodeKeyManager) Add(nodekey string) {
}
}
+type mdmAgent struct {
+ agentIndex int
+ MDMCheckInInterval time.Duration
+ model string
+ serverAddress string
+ softwareCount softwareEntityCount
+ stats *Stats
+ strings map[string]string
+}
+
+// stats, model, *serverURL, *mdmSCEPChallenge, *mdmCheckInInterval
+
+func (a *mdmAgent) CachedString(key string) string {
+ if val, ok := a.strings[key]; ok {
+ return val
+ }
+ val := randomString(12)
+ a.strings[key] = val
+ return val
+}
+
type agent struct {
agentIndex int
softwareCount softwareEntityCount
@@ -469,6 +494,11 @@ type agent struct {
softwareQueryFailureProb float64
softwareVSCodeExtensionsFailProb float64
+ softwareInstaller softwareInstaller
+
+ // Software installed on the host via Fleet. Key is the software name + version + bundle identifier.
+ installedSoftware sync.Map
+
//
// The following are exported to be used by the templates.
//
@@ -476,6 +506,7 @@ type agent struct {
EnrollSecret string
UUID string
SerialNumber string
+ defaultSerialProb float64
ConfigInterval time.Duration
LogInterval time.Duration
QueryInterval time.Duration
@@ -493,6 +524,13 @@ type agent struct {
bufferedResults map[resultLog]int
}
+func (a *agent) GetSerialNumber() string {
+ if rand.Float64() <= a.defaultSerialProb {
+ return "-1"
+ }
+ return a.SerialNumber
+}
+
type entityCount struct {
common int
unique int
@@ -515,6 +553,12 @@ type softwareExtraEntityCount struct {
uniqueSoftwareUninstallCount int
uniqueSoftwareUninstallProb float64
}
+type softwareInstaller struct {
+ preInstallFailureProb float64
+ installFailureProb float64
+ postInstallFailureProb float64
+ mu *sync.Mutex
+}
func newAgent(
agentIndex int,
@@ -523,6 +567,7 @@ func newAgent(
configInterval, logInterval, queryInterval, mdmCheckInInterval time.Duration,
softwareQueryFailureProb float64,
softwareVSCodeExtensionsQueryFailureProb float64,
+ softwareInstaller softwareInstaller,
softwareCount softwareEntityCount,
softwareVSCodeExtensionsCount softwareExtraEntityCount,
userCount entityCount,
@@ -530,6 +575,7 @@ func newAgent(
orbitProb float64,
munkiIssueProb float64, munkiIssueCount int,
emptySerialProb float64,
+ defaultSerialProb float64,
mdmProb float64,
mdmSCEPChallenge string,
liveQueryFailProb float64,
@@ -608,9 +654,11 @@ func newAgent(
MDMCheckInInterval: mdmCheckInInterval,
UUID: hostUUID,
SerialNumber: serialNumber,
+ defaultSerialProb: defaultSerialProb,
softwareQueryFailureProb: softwareQueryFailureProb,
softwareVSCodeExtensionsFailProb: softwareVSCodeExtensionsQueryFailureProb,
+ softwareInstaller: softwareInstaller,
macMDMClient: macMDMClient,
winMDMClient: winMDMClient,
@@ -936,6 +984,11 @@ func (a *agent) runOrbitLoop() {
// that will simulate executing them.
go a.execScripts(cfg.Notifications.PendingScriptExecutionIDs, orbitClient)
}
+ if len(cfg.Notifications.PendingSoftwareInstallerIDs) > 0 {
+ // there are pending software installations on this host, start a
+ // goroutine that will download the software
+ go a.installSoftware(cfg.Notifications.PendingSoftwareInstallerIDs, orbitClient)
+ }
if cfg.Notifications.NeedsProgrammaticWindowsMDMEnrollment &&
!a.mdmEnrolled() &&
a.winMDMClient != nil &&
@@ -1211,6 +1264,130 @@ func (a *agent) execScripts(execIDs []string, orbitClient *service.OrbitClient)
}
}
+func (a *agent) installSoftware(installerIDs []string, orbitClient *service.OrbitClient) {
+ // Only allow one software install to happen at a time.
+ if a.softwareInstaller.mu.TryLock() {
+ defer a.softwareInstaller.mu.Unlock()
+ for _, installerID := range installerIDs {
+ a.installSoftwareItem(installerID, orbitClient)
+ }
+ }
+}
+
+func (a *agent) installSoftwareItem(installerID string, orbitClient *service.OrbitClient) {
+ payload := &fleet.HostSoftwareInstallResultPayload{}
+ payload.InstallUUID = installerID
+ installer, err := orbitClient.GetInstallerDetails(installerID)
+ if err != nil {
+ log.Println("get installer details:", err)
+ return
+ }
+ failed := false
+ if installer.PreInstallCondition != "" {
+ time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
+ if installer.PreInstallCondition == "select 1" {
+ // Always pass
+ payload.PreInstallConditionOutput = ptr.String("1")
+ } else if installer.PreInstallCondition == "select 0" ||
+ a.softwareInstaller.preInstallFailureProb > 0.0 && rand.Float64() <= a.softwareInstaller.preInstallFailureProb {
+ // Fail
+ payload.PreInstallConditionOutput = ptr.String("")
+ failed = true
+ } else {
+ payload.PreInstallConditionOutput = ptr.String("1")
+ }
+ }
+
+ var meta *file.InstallerMetadata
+ if !failed {
+ var cacheMiss bool
+ // Download the file if needed to get its metadata
+ meta, cacheMiss, err = installerMetadataCache.Get(installer.InstallerID, orbitClient)
+ if err != nil {
+ return
+ }
+
+ if !cacheMiss {
+ // If we didn't download and analyze the file, we do a download and don't save the result
+ err = orbitClient.DownloadAndDiscardSoftwareInstaller(installer.InstallerID)
+ if err != nil {
+ log.Println("download and discard software installer:", err)
+ return
+ }
+ }
+
+ time.Sleep(time.Duration(rand.Intn(30)) * time.Second)
+ if installer.InstallScript == "exit 0" {
+ // Always pass
+ payload.InstallScriptExitCode = ptr.Int(0)
+ payload.InstallScriptOutput = ptr.String("Installed on osquery-perf (always pass)")
+ } else if installer.InstallScript == "exit 1" {
+ payload.InstallScriptExitCode = ptr.Int(1)
+ payload.InstallScriptOutput = ptr.String("Installed on osquery-perf (always fail)")
+ failed = true
+ } else if a.softwareInstaller.installFailureProb > 0.0 && rand.Float64() <= a.softwareInstaller.installFailureProb {
+ payload.InstallScriptExitCode = ptr.Int(1)
+ payload.InstallScriptOutput = ptr.String("Installed on osquery-perf (fail)")
+ failed = true
+ } else {
+ payload.InstallScriptExitCode = ptr.Int(0)
+ payload.InstallScriptOutput = ptr.String("Installed on osquery-perf (pass)")
+ }
+ }
+ if !failed {
+ if meta.Name == "" {
+ log.Printf("WARNING: installer metadata is missing a name for installer:%d\n", installer.InstallerID)
+ } else {
+ key := meta.Name + "+" + meta.Version + "+" + meta.BundleIdentifier
+ if _, ok := a.installedSoftware.Load(key); !ok {
+ source := ""
+ switch a.os {
+ case "macos":
+ source = "apps"
+ case "windows":
+ source = "programs"
+ case "ubuntu":
+ source = "deb_packages"
+ default:
+ log.Printf("unknown OS to software installer: %s", a.os)
+ return
+ }
+ a.installedSoftware.Store(key, map[string]string{
+ "name": meta.Name,
+ "version": meta.Version,
+ "bundle_identifier": meta.BundleIdentifier,
+ "source": source,
+ "installed_path": os.DevNull,
+ })
+ }
+ }
+
+ if installer.PostInstallScript != "" {
+ time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
+ if installer.PostInstallScript == "exit 0" {
+ // Always pass
+ payload.PostInstallScriptExitCode = ptr.Int(0)
+ payload.PostInstallScriptOutput = ptr.String("PostInstall on osquery-perf (always pass)")
+ } else if installer.PostInstallScript == "exit 1" {
+ payload.PostInstallScriptExitCode = ptr.Int(1)
+ payload.PostInstallScriptOutput = ptr.String("PostInstall on osquery-perf (always fail)")
+ } else if a.softwareInstaller.postInstallFailureProb > 0.0 && rand.Float64() <= a.softwareInstaller.postInstallFailureProb {
+ payload.PostInstallScriptExitCode = ptr.Int(1)
+ payload.PostInstallScriptOutput = ptr.String("PostInstall on osquery-perf (fail)")
+ } else {
+ payload.PostInstallScriptExitCode = ptr.Int(0)
+ payload.PostInstallScriptOutput = ptr.String("PostInstall on osquery-perf (pass)")
+ }
+ }
+ }
+
+ err = orbitClient.SaveInstallerResult(payload)
+ if err != nil {
+ log.Println("save installer result:", err)
+ return
+ }
+}
+
func (a *agent) waitingDo(fn func() *http.Request) *http.Response {
response, err := http.DefaultClient.Do(fn())
for err != nil || response.StatusCode != http.StatusOK {
@@ -1494,12 +1671,63 @@ func (a *agent) softwareMacOS() []map[string]string {
}
software := append(commonSoftware, uniqueSoftware...)
software = append(software, randomVulnerableSoftware...)
+ a.installedSoftware.Range(func(key, value interface{}) bool {
+ software = append(software, value.(map[string]string))
+ return true
+ })
rand.Shuffle(len(software), func(i, j int) {
software[i], software[j] = software[j], software[i]
})
return software
}
+func (a *mdmAgent) softwareIOSandIPadOS(source string) []fleet.Software {
+ commonSoftware := make([]map[string]string, a.softwareCount.common)
+ for i := 0; i < len(commonSoftware); i++ {
+ commonSoftware[i] = map[string]string{
+ "name": fmt.Sprintf("Common_%d", i),
+ "version": "0.0.1",
+ "bundle_identifier": fmt.Sprintf("com.fleetdm.osquery-perf.common_%d", i),
+ "source": source,
+ }
+ }
+ if a.softwareCount.commonSoftwareUninstallProb > 0.0 && rand.Float64() <= a.softwareCount.commonSoftwareUninstallProb {
+ rand.Shuffle(len(commonSoftware), func(i, j int) {
+ commonSoftware[i], commonSoftware[j] = commonSoftware[j], commonSoftware[i]
+ })
+ commonSoftware = commonSoftware[:a.softwareCount.common-a.softwareCount.commonSoftwareUninstallCount]
+ }
+ uniqueSoftware := make([]map[string]string, a.softwareCount.unique)
+ for i := 0; i < len(uniqueSoftware); i++ {
+ uniqueSoftware[i] = map[string]string{
+ "name": fmt.Sprintf("Unique_%s_%d", a.CachedString("hostname"), i),
+ "version": "1.1.1",
+ "bundle_identifier": fmt.Sprintf("com.fleetdm.osquery-perf.unique_%s_%d", a.CachedString("hostname"), i),
+ "source": source,
+ }
+ }
+ if a.softwareCount.uniqueSoftwareUninstallProb > 0.0 && rand.Float64() <= a.softwareCount.uniqueSoftwareUninstallProb {
+ rand.Shuffle(len(uniqueSoftware), func(i, j int) {
+ uniqueSoftware[i], uniqueSoftware[j] = uniqueSoftware[j], uniqueSoftware[i]
+ })
+ uniqueSoftware = uniqueSoftware[:a.softwareCount.unique-a.softwareCount.uniqueSoftwareUninstallCount]
+ }
+ software := append(commonSoftware, uniqueSoftware...)
+ rand.Shuffle(len(software), func(i, j int) {
+ software[i], software[j] = software[j], software[i]
+ })
+ fleetSoftware := make([]fleet.Software, len(software))
+ for i, s := range software {
+ fleetSoftware[i] = fleet.Software{
+ Name: s["name"],
+ Version: s["version"],
+ BundleIdentifier: s["bundle_identifier"],
+ Source: s["source"],
+ }
+ }
+ return fleetSoftware
+}
+
func (a *agent) softwareVSCodeExtensions() []map[string]string {
commonVSCodeExtensionsSoftware := make([]map[string]string, a.softwareVSCodeExtensionsCount.common)
for i := 0; i < len(commonVSCodeExtensionsSoftware); i++ {
@@ -1945,6 +2173,10 @@ func (a *agent) processQuery(name, query string) (
}
if ss == fleet.StatusOK {
results = windowsSoftware
+ a.installedSoftware.Range(func(key, value interface{}) bool {
+ results = append(results, value.(map[string]string))
+ return true
+ })
}
return true, results, &ss, nil, nil
case name == hostDetailQueryPrefix+"software_linux":
@@ -1956,6 +2188,10 @@ func (a *agent) processQuery(name, query string) (
switch a.os {
case "ubuntu":
results = ubuntuSoftware
+ a.installedSoftware.Range(func(key, value interface{}) bool {
+ results = append(results, value.(map[string]string))
+ return true
+ })
}
}
return true, results, &ss, nil, nil
@@ -2150,48 +2386,57 @@ func (a *agent) submitLogs(results []resultLog) error {
return nil
}
-func runAppleIDeviceMDMLoop(i int, stats *Stats, model string, serverURL string, mdmSCEPChallenge string, mdmCheckInInterval time.Duration) {
+func (a *mdmAgent) runAppleIDeviceMDMLoop(mdmSCEPChallenge string) {
udid := mdmtest.RandUDID()
mdmClient := mdmtest.NewTestMDMClientAppleDirect(mdmtest.AppleEnrollInfo{
SCEPChallenge: mdmSCEPChallenge,
- SCEPURL: serverURL + apple_mdm.SCEPPath,
- MDMURL: serverURL + apple_mdm.MDMPath,
- }, model)
+ SCEPURL: a.serverAddress + apple_mdm.SCEPPath,
+ MDMURL: a.serverAddress + apple_mdm.MDMPath,
+ }, a.model)
mdmClient.UUID = udid
mdmClient.SerialNumber = mdmtest.RandSerialNumber()
- deviceName := fmt.Sprintf("%s-%d", model, i)
- productName := model
+ deviceName := fmt.Sprintf("%s-%d", a.model, a.agentIndex)
+ productName := a.model
+ softwareSource := "ios_apps"
+ if strings.HasPrefix(a.model, "iPad") {
+ softwareSource = "ipados_apps"
+ }
if err := mdmClient.Enroll(); err != nil {
- log.Printf("%s MDM enroll failed: %s", model, err)
- stats.IncrementMDMErrors()
+ log.Printf("%s MDM enroll failed: %s", a.model, err)
+ a.stats.IncrementMDMErrors()
return
}
- stats.IncrementMDMEnrollments()
+ a.stats.IncrementMDMEnrollments()
- mdmCheckInTicker := time.Tick(mdmCheckInInterval)
+ mdmCheckInTicker := time.Tick(a.MDMCheckInInterval)
for range mdmCheckInTicker {
mdmCommandPayload, err := mdmClient.Idle()
if err != nil {
- log.Printf("MDM Idle request failed: %s: %s", model, err)
- stats.IncrementMDMErrors()
+ log.Printf("MDM Idle request failed: %s: %s", a.model, err)
+ a.stats.IncrementMDMErrors()
continue
}
- stats.IncrementMDMSessions()
+ a.stats.IncrementMDMSessions()
for mdmCommandPayload != nil {
- stats.IncrementMDMCommandsReceived()
- if mdmCommandPayload.Command.RequestType == "DeviceInformation" {
- mdmCommandPayload, err = mdmClient.AcknowledgeDeviceInformation(udid, mdmCommandPayload.CommandUUID, deviceName, productName)
- } else {
+ a.stats.IncrementMDMCommandsReceived()
+ switch mdmCommandPayload.Command.RequestType {
+ case "DeviceInformation":
+ mdmCommandPayload, err = mdmClient.AcknowledgeDeviceInformation(udid, mdmCommandPayload.CommandUUID, deviceName,
+ productName)
+ case "InstalledApplicationList":
+ software := a.softwareIOSandIPadOS(softwareSource)
+ mdmCommandPayload, err = mdmClient.AcknowledgeInstalledApplicationList(udid, mdmCommandPayload.CommandUUID, software)
+ default:
mdmCommandPayload, err = mdmClient.Acknowledge(mdmCommandPayload.CommandUUID)
}
if err != nil {
- log.Printf("MDM Acknowledge request failed: %s: %s", model, err)
- stats.IncrementMDMErrors()
+ log.Printf("MDM Acknowledge request failed: %s: %s", a.model, err)
+ a.stats.IncrementMDMErrors()
break
}
}
@@ -2264,7 +2509,7 @@ func main() {
// osquery-perf will send log requests with results only if there are scheduled queries configured AND it's their time to run.
logInterval = flag.Duration("logger_tls_period", 10*time.Second, "Interval for scheduled queries log requests")
queryInterval = flag.Duration("query_interval", 10*time.Second, "Interval for distributed query requests")
- mdmCheckInInterval = flag.Duration("mdm_check_in_interval", 10*time.Second, "Interval for performing MDM check-ins (applies to both macOS and Windows)")
+ mdmCheckInInterval = flag.Duration("mdm_check_in_interval", 1*time.Minute, "Interval for performing MDM check-ins (applies to both macOS and Windows)")
onlyAlreadyEnrolled = flag.Bool("only_already_enrolled", false, "Only start agents that are already enrolled")
nodeKeyFile = flag.String("node_key_file", "", "File with node keys to use")
@@ -2274,6 +2519,13 @@ func main() {
softwareQueryFailureProb = flag.Float64("software_query_fail_prob", 0.5, "Probability of the software query failing")
softwareVSCodeExtensionsQueryFailureProb = flag.Float64("software_vscode_extensions_query_fail_prob", 0.0, "Probability of the software vscode_extensions query failing")
+ softwareInstallerPreInstallFailureProb = flag.Float64("software_installer_pre_install_fail_prob", 0.05,
+ "Probability of the pre-install query failing")
+ softwareInstallerInstallFailureProb = flag.Float64("software_installer_install_fail_prob", 0.05,
+ "Probability of the install script failing")
+ softwareInstallerPostInstallFailureProb = flag.Float64("software_installer_post_install_fail_prob", 0.05,
+ "Probability of the post-install script failing")
+
commonSoftwareCount = flag.Int("common_software_count", 10, "Number of common installed applications reported to fleet")
commonVSCodeExtensionsSoftwareCount = flag.Int("common_vscode_extensions_software_count", 5, "Number of common vscode_extensions installed applications reported to fleet")
commonSoftwareUninstallCount = flag.Int("common_software_uninstall_count", 1, "Number of common software to uninstall")
@@ -2299,8 +2551,10 @@ func main() {
munkiIssueCount = flag.Int("munki_issue_count", 10, "Number of munki issues reported by hosts identified to have munki issues")
// E.g. when running with `-host_count=10`, you can set host count for each template the following way:
// `-os_templates=windows_11.tmpl:3,macos_14.1.2.tmpl:4,ubuntu_22.04.tmpl:3`
- osTemplates = flag.String("os_templates", "macos_14.1.2", fmt.Sprintf("Comma-separated list of host OS templates to use and optionally their host count separated by ':' (any of %v, with or without the .tmpl extension)", allowedTemplateNames))
- emptySerialProb = flag.Float64("empty_serial_prob", 0.1, "Probability of a host having no serial number [0, 1]")
+ osTemplates = flag.String("os_templates", "macos_14.1.2", fmt.Sprintf("Comma-separated list of host OS templates to use and optionally their host count separated by ':' (any of %v, with or without the .tmpl extension)", allowedTemplateNames))
+ emptySerialProb = flag.Float64("empty_serial_prob", 0.1, "Probability of a host having no serial number [0, 1]")
+ defaultSerialProb = flag.Float64("default_serial_prob", 0.05,
+ "Probability of osquery returning a default (-1) serial number. See: #19789")
mdmProb = flag.Float64("mdm_prob", 0.0, "Probability of a host enrolling via Fleet MDM (applies for macOS and Windows hosts, implies orbit enrollment on Windows) [0, 1]")
mdmSCEPChallenge = flag.String("mdm_scep_challenge", "", "SCEP challenge to use when running macOS MDM enroll")
@@ -2404,7 +2658,26 @@ func main() {
if tmpl.Name() == "ipad_13.18.tmpl" {
model = "iPad 13,18"
}
- go runAppleIDeviceMDMLoop(i, stats, model, *serverURL, *mdmSCEPChallenge, *mdmCheckInInterval)
+ mobileDevice := mdmAgent{
+ agentIndex: i + 1,
+ MDMCheckInInterval: *mdmCheckInInterval,
+ model: model,
+ serverAddress: *serverURL,
+ softwareCount: softwareEntityCount{
+ entityCount: entityCount{
+ common: *commonSoftwareCount,
+ unique: *uniqueSoftwareCount,
+ },
+ vulnerable: *vulnerableSoftwareCount,
+ commonSoftwareUninstallCount: *commonSoftwareUninstallCount,
+ commonSoftwareUninstallProb: *commonSoftwareUninstallProb,
+ uniqueSoftwareUninstallCount: *uniqueSoftwareUninstallCount,
+ uniqueSoftwareUninstallProb: *uniqueSoftwareUninstallProb,
+ },
+ stats: stats,
+ strings: make(map[string]string),
+ }
+ go mobileDevice.runAppleIDeviceMDMLoop(*mdmSCEPChallenge)
time.Sleep(sleepTime)
continue
}
@@ -2419,6 +2692,12 @@ func main() {
*mdmCheckInInterval,
*softwareQueryFailureProb,
*softwareVSCodeExtensionsQueryFailureProb,
+ softwareInstaller{
+ preInstallFailureProb: *softwareInstallerPreInstallFailureProb,
+ installFailureProb: *softwareInstallerInstallFailureProb,
+ postInstallFailureProb: *softwareInstallerPostInstallFailureProb,
+ mu: new(sync.Mutex),
+ },
softwareEntityCount{
entityCount: entityCount{
common: *commonSoftwareCount,
@@ -2451,6 +2730,7 @@ func main() {
*munkiIssueProb,
*munkiIssueCount,
*emptySerialProb,
+ *defaultSerialProb,
*mdmProb,
*mdmSCEPChallenge,
*liveQueryFailProb,
diff --git a/cmd/osquery-perf/installer_cache/installer-cache.go b/cmd/osquery-perf/installer_cache/installer-cache.go
new file mode 100644
index 000000000000..57994de5f670
--- /dev/null
+++ b/cmd/osquery-perf/installer_cache/installer-cache.go
@@ -0,0 +1,66 @@
+package installer_cache
+
+import (
+ "log"
+ "os"
+ "sync"
+
+ "github.com/fleetdm/fleet/v4/pkg/file"
+ "github.com/fleetdm/fleet/v4/server/service"
+)
+
+// Metadata holds the metadata for software installers.
+// To extract the metadata, we must download the file. Once the file has been downloaded once and analyzed,
+// the other agents can use the cache to get the appropriate metadata.
+type Metadata struct {
+ mu sync.Mutex
+ cache map[uint]*file.InstallerMetadata
+}
+
+func (c *Metadata) Get(key uint, orbitClient *service.OrbitClient) (meta *file.InstallerMetadata,
+ cacheMiss bool, err error) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ if c.cache == nil {
+ c.cache = make(map[uint]*file.InstallerMetadata, 1)
+ }
+
+ meta, ok := c.cache[key]
+ if !ok {
+ var err error
+ meta, err = populateMetadata(orbitClient, key)
+ if err != nil {
+ return nil, false, err
+ }
+ c.cache[key] = meta
+ cacheMiss = true
+ }
+ return meta, cacheMiss, nil
+}
+
+func populateMetadata(orbitClient *service.OrbitClient, installerID uint) (*file.InstallerMetadata, error) {
+ tmpDir, err := os.MkdirTemp("", "")
+ if err != nil {
+ log.Println("create temp dir:", err)
+ return nil, err
+ }
+ defer os.RemoveAll(tmpDir)
+ path, err := orbitClient.DownloadSoftwareInstaller(installerID, tmpDir)
+ if err != nil {
+ log.Println("download software installer:", err)
+ return nil, err
+ }
+ // Figure out what we're actually installing here and add it to software inventory
+ f, err := os.Open(path)
+ if err != nil {
+ log.Println("open installer:", err)
+ return nil, err
+ }
+ defer f.Close()
+ item, err := file.ExtractInstallerMetadata(f)
+ if err != nil {
+ log.Println("extract installer metadata:", err)
+ return nil, err
+ }
+ return item, nil
+}
diff --git a/cmd/osquery-perf/macos_13.6.2.tmpl b/cmd/osquery-perf/macos_13.6.2.tmpl
index 8f59d00e9e85..83ee06cc5f88 100644
--- a/cmd/osquery-perf/macos_13.6.2.tmpl
+++ b/cmd/osquery-perf/macos_13.6.2.tmpl
@@ -173,6 +173,10 @@
{{- end }}
{{- end }}
+{{ define "fleet_detail_query_mdm_config_profiles_darwin" -}}
+[]
+{{- end }}
+
{{/* all hosts */}}
{{ define "fleet_label_query_6" -}}
[
diff --git a/cmd/osquery-perf/macos_14.1.2.tmpl b/cmd/osquery-perf/macos_14.1.2.tmpl
index 8aa29e1c4ac3..4c768d34c1b1 100644
--- a/cmd/osquery-perf/macos_14.1.2.tmpl
+++ b/cmd/osquery-perf/macos_14.1.2.tmpl
@@ -174,6 +174,10 @@
{{- end }}
{{- end }}
+{{ define "fleet_detail_query_mdm_config_profiles_darwin" -}}
+[]
+{{- end }}
+
{{/* all hosts */}}
{{ define "fleet_label_query_6" -}}
[
diff --git a/cmd/osquery-perf/windows_11.tmpl b/cmd/osquery-perf/windows_11.tmpl
index 11c9125b68bd..fcf738cd1cc2 100644
--- a/cmd/osquery-perf/windows_11.tmpl
+++ b/cmd/osquery-perf/windows_11.tmpl
@@ -44,7 +44,7 @@
"cpu_subtype": "-1",
"cpu_type": "x86_64",
"hardware_model": "Surface Laptop 2",
- "hardware_serial": "{{ .SerialNumber }}",
+ "hardware_serial": "{{ .GetSerialNumber }}",
"hardware_vendor": "Microsoft Corporation",
"hardware_version": "-1",
"hostname": "{{ .CachedString "hostname" }}",
@@ -150,7 +150,7 @@
"cpu_subtype": "-1",
"cpu_type": "x86_64",
"hardware_model": "Surface Laptop 2",
- "hardware_serial": "{{ .SerialNumber }}",
+ "hardware_serial": "{{ .GetSerialNumber }}",
"hardware_vendor": "Microsoft Corporation",
"hardware_version": "-1",
"local_hostname": "{{ .CachedString "hostname" }}",
diff --git a/cmd/osquery-perf/windows_11_22H2_2861.tmpl b/cmd/osquery-perf/windows_11_22H2_2861.tmpl
index 0450623681da..b9344795f46b 100644
--- a/cmd/osquery-perf/windows_11_22H2_2861.tmpl
+++ b/cmd/osquery-perf/windows_11_22H2_2861.tmpl
@@ -44,7 +44,7 @@
"cpu_subtype": "-1",
"cpu_type": "x86_64",
"hardware_model": "Surface Laptop 2",
- "hardware_serial": "{{ .SerialNumber }}",
+ "hardware_serial": "{{ .GetSerialNumber }}",
"hardware_vendor": "Microsoft Corporation",
"hardware_version": "-1",
"hostname": "{{ .CachedString "hostname" }}",
@@ -150,7 +150,7 @@
"cpu_subtype": "-1",
"cpu_type": "x86_64",
"hardware_model": "Surface Laptop 2",
- "hardware_serial": "{{ .SerialNumber }}",
+ "hardware_serial": "{{ .GetSerialNumber }}",
"hardware_vendor": "Microsoft Corporation",
"hardware_version": "-1",
"local_hostname": "{{ .CachedString "hostname" }}",
diff --git a/cmd/osquery-perf/windows_11_22H2_3007.tmpl b/cmd/osquery-perf/windows_11_22H2_3007.tmpl
index 135b3dcb4004..f4ac6b1670fc 100644
--- a/cmd/osquery-perf/windows_11_22H2_3007.tmpl
+++ b/cmd/osquery-perf/windows_11_22H2_3007.tmpl
@@ -44,7 +44,7 @@
"cpu_subtype": "-1",
"cpu_type": "x86_64",
"hardware_model": "Surface Laptop 2",
- "hardware_serial": "{{ .SerialNumber }}",
+ "hardware_serial": "{{ .GetSerialNumber }}",
"hardware_vendor": "Microsoft Corporation",
"hardware_version": "-1",
"hostname": "{{ .CachedString "hostname" }}",
@@ -150,7 +150,7 @@
"cpu_subtype": "-1",
"cpu_type": "x86_64",
"hardware_model": "Surface Laptop 2",
- "hardware_serial": "{{ .SerialNumber }}",
+ "hardware_serial": "{{ .GetSerialNumber }}",
"hardware_vendor": "Microsoft Corporation",
"hardware_version": "-1",
"local_hostname": "{{ .CachedString "hostname" }}",
diff --git a/codecov.yml b/codecov.yml
index d0a398457d20..f91f1aae408e 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -22,3 +22,6 @@ flag_management:
- name: frontend
paths:
- frontend/
+
+ignore:
+ - "server/mock"
diff --git a/docker-compose.yml b/docker-compose.yml
index 5b8b7b48eccb..6a393010e035 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,7 +5,7 @@ services:
# officially supported).
# To run in macOS M1, set FLEET_MYSQL_IMAGE=arm64v8/mysql:oracle FLEET_MYSQL_PLATFORM=linux/arm64/v8
mysql:
- image: ${FLEET_MYSQL_IMAGE:-mysql:5.7.21}
+ image: ${FLEET_MYSQL_IMAGE:-mysql:8.0.36}
platform: ${FLEET_MYSQL_PLATFORM:-linux/x86_64}
volumes:
- mysql-persistent-volume:/tmp
@@ -30,7 +30,7 @@ services:
- "3306:3306"
mysql_test:
- image: ${FLEET_MYSQL_IMAGE:-mysql:5.7.21}
+ image: ${FLEET_MYSQL_IMAGE:-mysql:8.0.36}
platform: ${FLEET_MYSQL_PLATFORM:-linux/x86_64}
# innodb-file-per-table=OFF gives ~20% speedup for test runs.
command: [
@@ -56,7 +56,7 @@ services:
- /tmpfs
mysql_replica_test:
- image: ${FLEET_MYSQL_IMAGE:-mysql:5.7.21}
+ image: ${FLEET_MYSQL_IMAGE:-mysql:8.0.36}
platform: ${FLEET_MYSQL_PLATFORM:-linux/x86_64}
# innodb-file-per-table=OFF gives ~20% speedup for test runs.
command: [
@@ -167,12 +167,6 @@ services:
volumes:
- data-minio:/data
- toxiproxy:
- image: shopify/toxiproxy
- ports:
- - "22220:22220"
- - "8474:8474"
-
volumes:
mysql-persistent-volume:
data-minio:
diff --git a/docs/Configuration/agent-configuration.md b/docs/Configuration/agent-configuration.md
index 0c6f851b04a0..525caadcd5c2 100644
--- a/docs/Configuration/agent-configuration.md
+++ b/docs/Configuration/agent-configuration.md
@@ -128,14 +128,11 @@ Just like the other `agent_options` above, remove the dashed lines (`--`) for Fl
Here is an example of using the `command_line_flags` key:
```yaml
-apiVersion: v1
-kind: config
-spec:
- agent_options:
- command_line_flags: # requires Fleet's agent (fleetd)
- verbose: true
- disable_watchdog: false
- logger_path: /path/to/logger
+agent_options:
+ command_line_flags: # requires Fleet's agent (fleetd)
+ verbose: true
+ disable_watchdog: false
+ logger_path: /path/to/logger
```
Note that the `command_line_flags` key does not support the `overrides` key, which is documented below.
@@ -182,20 +179,17 @@ The `extensions` key inside of `agent_options` allows you to remotely manage and
This is best illustrated with an example. Here is an example of using the `extensions` key:
```yaml
-apiVersion: v1
-kind: config
-spec:
- agent_options:
- extensions: # requires Fleet's agent (fleetd)
- hello_world_macos:
- channel: 'stable'
- platform: 'macos'
- hello_world_linux:
- channel: 'stable'
- platform: 'linux'
- hello_world_windows:
- channel: 'stable'
- platform: 'windows'
+agent_options:
+ extensions: # requires Fleet's agent (fleetd)
+ hello_world_macos:
+ channel: 'stable'
+ platform: 'macos'
+ hello_world_linux:
+ channel: 'stable'
+ platform: 'linux'
+ hello_world_windows:
+ channel: 'stable'
+ platform: 'windows'
```
In the above example, we are configuring our `hello_world` extensions for all the supported operating systems. We do this by creating `hello_world_{macos|linux|windows}` subkeys under `extensions`, and then specifying the `channel` and `platform` keys for each extension entry.
@@ -248,25 +242,22 @@ The label names in the list:
Example:
```yaml
-apiVersion: v1
-kind: config
-spec:
- agent_options:
- extensions: # requires Fleet's agent (fleetd)
- hello_world_macos:
- channel: 'stable'
- platform: 'macos'
- labels:
- - Zoom installed
- hello_world_linux:
- channel: 'stable'
- platform: 'linux'
- labels:
- - Ubuntu Linux
- - Zoom installed
- hello_world_windows:
- channel: 'stable'
- platform: 'windows'
+agent_options:
+ extensions: # requires Fleet's agent (fleetd)
+ hello_world_macos:
+ channel: 'stable'
+ platform: 'macos'
+ labels:
+ - Zoom installed
+ hello_world_linux:
+ channel: 'stable'
+ platform: 'linux'
+ labels:
+ - Ubuntu Linux
+ - Zoom installed
+ hello_world_windows:
+ channel: 'stable'
+ platform: 'windows'
```
In the above example:
- the `hello_world_macos` extension is deployed to macOS hosts that are members of the 'Zoom installed' label.
@@ -276,28 +267,22 @@ In the above example:
_Available in Fleet Premium v4.43.0 and fleetd v1.20.0_
-Users can configure fleetd component TUF auto-update channels from Fleet's agent options. The components that can be configured are `orbit`, `osqueryd` and `desktop` (Fleet Desktop). When one of these components is omitted in `update_channels` then `stable` is assumed as the value for such component.
+Users can configure fleetd component TUF auto-update channels from Fleet's agent options. The components that can be configured are `orbit`, `osqueryd` and `desktop` (Fleet Desktop). When one of these components is omitted in `update_channels` then `stable` is assumed as the value for such component. Available options for update channels can be viewed [here](https://fleetdm.com/docs/using-fleet/enroll-hosts#specifying-update-channels).
Examples:
```yaml
-apiVersion: v1
-kind: config
-spec:
- agent_options:
- update_channels: # requires Fleet's agent (fleetd)
- orbit: stable
- osqueryd: '5.10.2'
- desktop: edge
+agent_options:
+ update_channels: # requires Fleet's agent (fleetd)
+ orbit: stable
+ osqueryd: '5.10.2'
+ desktop: edge
```
```yaml
-apiVersion: v1
-kind: config
-spec:
- agent_options:
- update_channels: # requires Fleet's agent (fleetd)
- orbit: edge
- osqueryd: '5.10.2'
- # in this configuration `desktop` is assumed to be "stable"
+agent_options:
+ update_channels: # requires Fleet's agent (fleetd)
+ orbit: edge
+ osqueryd: '5.10.2'
+ # in this configuration `desktop` is assumed to be "stable"
```
- If a configured channel doesn't exist in the TUF repository, then fleetd will log errors on the hosts and will not auto-update the component/s until the channel is changed to a valid value in Fleet's `update_channels` configuration or until the user pushes the component to the channel (which effectively creates the channel).
@@ -331,7 +316,6 @@ agent_options:
options: ~
decorators: ~
yara: ~
- overrides: ~
```
@@ -351,7 +335,6 @@ agent_options:
distributed_tls_max_attempts: 3
logger_tls_endpoint: /api/osquery/log
logger_tls_period: 10
- decorators: ~
```
### Decorators
@@ -380,27 +363,21 @@ You can use Fleet to configure the `yara` and `yara_events` osquery tables. Fore
The following is an example Fleet configuration file with YARA configuration. The values are taken from an example config supplied in the above link to the osquery documentation.
```yaml
----
-apiVersion: v1
-kind: config
-spec:
- agent_options:
- config:
- # ...
- yara:
- file_paths:
- system_binaries:
- - sig_group_1
- tmp:
- - sig_group_1
- - sig_group_2
- signatures:
- sig_group_1:
- - /Users/wxs/sigs/foo.sig
- - /Users/wxs/sigs/bar.sig
- sig_group_2:
- - /Users/wxs/sigs/baz.sig
- overrides: {}
+agent_options:
+ config:
+ yara:
+ file_paths:
+ system_binaries:
+ - sig_group_1
+ tmp:
+ - sig_group_1
+ - sig_group_2
+ signatures:
+ sig_group_1:
+ - /Users/wxs/sigs/foo.sig
+ - /Users/wxs/sigs/bar.sig
+ sig_group_2:
+ - /Users/wxs/sigs/baz.sig
```
## Overrides
@@ -413,11 +390,6 @@ In the example file below, all Darwin and Ubuntu hosts will **only** receive the
```yaml
agent_options:
- config:
- options:
- distributed_interval: 3
- distributed_tls_max_attempts: 3
- logger_tls_period: 10
overrides:
# Note configs in overrides take precedence over the default config defined
# under the config key above. Hosts receive overrides based on the platform
@@ -441,6 +413,20 @@ agent_options:
etc:
- /etc/%%
```
+
+## Script execution timeout
+
+The `script_execution_timeout` allows you to change the default script execution timeout.
+
+- Optional setting (integer)
+- Default value: 300
+- Maximum value: 3600
+- Config file format:
+ ```yaml
+ agent_options:
+ script_execution_timeout: 600
+ ```
+
## Auto table construction
You can use Fleet to query local SQLite databases as tables. For more information on creating ATC configuration from a SQLite database, check out the [Automatic Table Construction section](https://osquery.readthedocs.io/en/stable/deployment/configuration/#automatic-table-construction) of the osquery documentation.
@@ -448,28 +434,25 @@ You can use Fleet to query local SQLite databases as tables. For more informatio
If you already know what your ATC configuration needs to look like, you can add it to an options config file:
```yaml
-apiVersion: v1
-kind: config
-spec:
- agent_options:
- config:
- options:
- # ...
- overrides:
- platforms:
- darwin:
- auto_table_construction:
- tcc_system_entries:
- # This query and columns are restricted for compatability. Open TCC.db with sqlite on
- # your endpoints to expand this out.
- query: "SELECT service, client, last_modified FROM access"
- # Note that TCC.db requires fleetd to have full-disk access, ensure that endpoints have
- # this enabled.
- path: "/Library/Application Support/com.apple.TCC/TCC.db"
- columns:
- - "service"
- - "client"
- - "last_modified"
+agent_options:
+ config:
+ options:
+ # ...
+ overrides:
+ platforms:
+ darwin:
+ auto_table_construction:
+ tcc_system_entries:
+ # This query and columns are restricted for compatability. Open TCC.db with sqlite on
+ # your endpoints to expand this out.
+ query: "SELECT service, client, last_modified FROM access"
+ # Note that TCC.db requires fleetd to have full-disk access, ensure that endpoints have
+ # this enabled.
+ path: "/Library/Application Support/com.apple.TCC/TCC.db"
+ columns:
+ - "service"
+ - "client"
+ - "last_modified"
```
If you're editing this directly from the UI consider copying and pasting the following at the end of your agent configuration block:
diff --git a/docs/Configuration/fleet-server-configuration.md b/docs/Configuration/fleet-server-configuration.md
index 1b5556356f75..145f887ebd38 100644
--- a/docs/Configuration/fleet-server-configuration.md
+++ b/docs/Configuration/fleet-server-configuration.md
@@ -4,6 +4,8 @@ Fleet server configuration options update the internals of the Fleet server (MyS
Only self-managed users and customers can modify this configuration. If you're a managed-cloud customer, please reach out to Fleet about modifying the configuration.
+## Configuration options
+
You can specify configuration options in the following formats:
1. YAML file
@@ -12,58 +14,6 @@ You can specify configuration options in the following formats:
All duration-based settings accept valid time units of `s`, `m`, `h`.
-## YAML file
-
-```sh
-echo '
-
-mysql:
- address: 127.0.0.1:3306
- database: fleet
- username: root
- password: toor
-redis:
- address: 127.0.0.1:6379
-server:
- cert: /tmp/server.cert
- key: /tmp/server.key
-logging:
- json: true
-' > /tmp/fleet.yml
-fleet serve --config /tmp/fleet.yml
-```
-
-## Environment variables
-
-```sh
-FLEET_MYSQL_ADDRESS=127.0.0.1:3306 \
-FLEET_MYSQL_DATABASE=fleet \
-FLEET_MYSQL_USERNAME=root \
-FLEET_MYSQL_PASSWORD=toor \
-FLEET_REDIS_ADDRESS=127.0.0.1:6379 \
-FLEET_SERVER_CERT=/tmp/server.cert \
-FLEET_SERVER_KEY=/tmp/server.key \
-FLEET_LOGGING_JSON=true \
-/usr/bin/fleet serve
-```
-
-## Command-line flags
-
-```sh
-/usr/bin/fleet serve \
---mysql_address=127.0.0.1:3306 \
---mysql_database=fleet \
---mysql_username=root \
---mysql_password=toor \
---redis_address=127.0.0.1:6379 \
---server_cert=/tmp/server.cert \
---server_key=/tmp/server.key \
---logging_json
-```
-
-
-## Configuration options
-
#### MySQL
This section describes the configuration options for the primary. Suppose you also want to set up a read replica. In that case the options are the same, except that the YAML section is `mysql_read_replica`, and the flags have the `mysql_read_replica_` prefix instead of `mysql_` (the corresponding environment variables follow the same transformation). Note that there is no default value for `mysql_read_replica_address`, it must be set explicitly for Fleet to use a read replica, and it is recommended in that case to set a non-zero value for `mysql_read_replica_conn_max_lifetime` as in some environments, the replica's address may dynamically change to point
@@ -236,7 +186,7 @@ The maximum amount of time, in seconds, a connection may be reused.
##### mysql_sql_mode
-Sets the connection `sql_mode`. See [MySQL Reference](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html) for more details.
+Sets the connection `sql_mode`. See [MySQL Reference](https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html) for more details.
This setting should not usually be used.
- Default value: `""`
@@ -1444,7 +1394,7 @@ AWS secret access key to use for Firehose authentication.
firehose:
secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
```
-Optional unique identifier that can be used by the principal assuming the role to assert its identity.
+
##### firehose_sts_assume_role_arn
This flag only has effect if one of the following is true:
@@ -2247,7 +2197,7 @@ The IAM identity used in this context must be allowed to perform the following a
AWS secret access key to use for S3 authentication.
- Default value: none
-- Environment variable: `FLEET_S3_SECRET_ACCESS_KEY`
+- Environment variable: `FLEET_S3_SOFTWARE_INSTALLERS_SECRET_ACCESS_KEY`
- Config file format:
```yaml
s3:
@@ -2891,7 +2841,7 @@ packaging:
region: us-east-1
```
-## Mobile device management (MDM)
+#### Mobile device management (MDM)
> The [`server_private_key` configuration option](#server_private_key) is required for macOS MDM features.
@@ -2962,11 +2912,6 @@ The content of the Windows WSTEP identity key. An RSA private key, PEM-encoded.
-----END RSA PRIVATE KEY-----
```
-
-## Managing osquery configurations
-
-We recommend that you use an infrastructure configuration management tool to manage these osquery configurations consistently across your environment. If you're unsure about what configuration management tools your organization uses, contact your company's system administrators. If you are evaluating new solutions for this problem, the founders of Fleet have successfully managed configurations in large production environments using [Chef](https://www.chef.io/chef/) and [Puppet](https://puppet.com/).
-
Running with systemd
This content was moved to [Systemd](http://fleetdm.com/docs/deploy/system-d) on Sept 6th, 2023.
diff --git a/docs/Using Fleet/GitOps.md b/docs/Configuration/yaml-files.md
similarity index 80%
rename from docs/Using Fleet/GitOps.md
rename to docs/Configuration/yaml-files.md
index 96d336871488..be73c1ff29e5 100644
--- a/docs/Using Fleet/GitOps.md
+++ b/docs/Configuration/yaml-files.md
@@ -1,4 +1,4 @@
-# GitOps
+# YAML files
Use Fleet's best practice GitOps workflow to manage your computers as code.
@@ -20,19 +20,21 @@ The following files are responsible for running the GitHub action. Most users do
The following are the required keys in the `default.yml` and any `teams/team-name.yml` files:
```yaml
-name: # Only teams/team-name.yml
+name: # Only teams/team-name.yml. To edit a team's name, change `name` but don't change the filename.
policies:
queries:
agent_options:
controls:
+software:
org_settings: # Only default.yml
team_settings: # Only teams/team-name.yml
```
- [policies](#policies)
- [queries](#queries)
-- [agent_options](#agent_options)
+- [agent_options](#agent-options)
- [controls](#controls)
+- [software](#software)
- [org_settings and team_settings](#org-settings-and-team-settings)
### policies
@@ -196,9 +198,11 @@ config:
`default.yml` or `teams/team-name.yml`
+> We want `-` for policies and queries because it’s an array. Agent Options we do not use `-` for `path`.
+
```yaml
queries:
- - path: ../lib/agent-options.yml
+ path: ../lib/agent-options.yml
# path is relative to default.yml or teams/team-name.yml
```
@@ -227,12 +231,16 @@ controls:
deadline_days: 5
grace_period_days: 2
macos_settings:
- - path: ../lib/macos-profile1.mobileconfig
- labels:
- - Label name 1
- - path: ../lib/macos-profile2.json
+ custom_settings:
+ - path: ../lib/macos-profile1.mobileconfig
+ labels_exclude_any:
+ - Label 1
+ - path: ../lib/macos-profile2.json
+ labels_include_all:
+ - Label 2
windows_settings:
- - path: ../lib/windows-profile.xml
+ custom_settings
+ - path: ../lib/windows-profile.xml
macos_setup: # Available in Fleet Premium
bootstrap_package: https://example.org/bootstrap_package.pkg
enable_end_user_authentication: true
@@ -261,24 +269,65 @@ controls:
Fleet supports adding [GitHub environment variables](https://docs.github.com/en/actions/learn-github-actions/variables#defining-environment-variables-for-a-single-workflow) in your configuration profiles. Use `$ENV_VARIABLE` format.
-Use `labels` to only apply (scope) profiles to hosts that have all those labels.
+Use `labels_include_all` to only apply (scope) profiles to hosts that have all those labels or `labels_exclude_any` to apply profiles to hosts that don't have any of those labels.
#### macos_setup
-The `macos_setup` section lets you control the [end user migration workflow](https://fleetdm.com/docs/using-fleet/mdm-migration-guide#end-user-workflow) for macOS hosts that automatically enrolled to your old MDM solution.
+The `macos_setup` section lets you control the out-of-the-box macOS [setup experience](https://fleetdm.com/guides/macos-setup-experience) for hosts that use Automated Device Enrollment (ADE).
- `bootstrap_package` is the URL to a bootstap package. Fleet will download the bootstrap package (default: `""`).
- `enable_end_user_authentication` specifies whether or not to require end user authentication when the user first sets up their macOS host.
-- `macos_setup_assistant` is a path to a custom automatic enrollment (DEP) profile (.json).
+- `macos_setup_assistant` is a path to a custom automatic enrollment (ADE) profile (.json).
#### macos_migration
+The `macos_migration` section lets you control the [end user migration workflow](https://fleetdm.com/docs/using-fleet/mdm-migration-guide#end-user-workflow) for macOS hosts that enrolled to your old MDM solution.
+
- `enable` specifies whether or not to enable end user migration workflow (default: `false`)
- `mode` specifies whether the end user initiates migration (`voluntary`) or they're nudged every 15-20 minutes to migrate (`forced`) (default: `""`).
- `webhook_url` is the URL that Fleet sends a webhook to when the end user selects **Start**. Receive this webhook using your automation tool (ex. Tines) to unenroll your end users from your old MDM solution.
Can only be configure for all teams (`default.yml`).
+### software
+
+> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
+
+The `software` section allows you to configure packages and Apple App Store apps that you want to install on your hosts.
+
+- `packages` is a list of software packages (.pkg, .msi, .exe, or .deb) and software specific options.
+- `app_store_apps` is a list of Apple App Store apps.
+
+##### Example
+
+```yaml
+software:
+ packages:
+ - url: https://github.com/organinzation/repository/package-1.pkg
+ install_script:
+ path: /lib/crowdstrike-install.sh
+ pre_install_query:
+ path: /lib/check-crowdstrike-configuration-profile.queries.yml
+ post_install_script:
+ path: /lib/crowdstrike-post-install.sh
+ self_service: true
+ - url: https://github.com/organinzation/repository/package-2.msi
+ app_store_apps:
+ - app_store_id: '1091189122'
+```
+
+#### packages
+
+- `url` specifies the URL at which the software is located. Fleet will download the software and upload it to S3 (default: `""`).
+- `install_script.path` specifies the command Fleet will run on hosts to install software. The [default script](https://github.com/fleetdm/fleet/tree/main/pkg/file/scripts) is dependent on the software type (i.e. .pkg).
+- `pre_install_query.path` is the osquery query Fleet runs before installing the software. Software will be installed only if the [query returns results](https://fleetdm.com/tables/account_policy_data) (default: `""`).
+- `post_install_script.path` is the script Fleet will run on hosts after intalling software (default: `""`).
+- `self_service` specifies whether or not end users can install from **Fleet Desktop > Self-service**.
+
+#### app_store_apps
+
+- `app_store_id` is the ID of the Apple App Store app. You can find this at the end of the app's App Store URL. For example, "Bear - Markdown Notes" URL is "https://apps.apple.com/us/app/bear-markdown-notes/id1016366447" and the `app_store_id` is `1016366447`.
+
### org_settings and team_settings
#### features
@@ -366,6 +415,7 @@ org_settings:
- `enable_analytics` specifies whether or not to enable Fleet's [usage statistics](https://fleetdm.com/docs/using-fleet/usage-statistics) (default: `true`).
- `live_query_disabled` disables the ability to run live queries (ad hoc queries executed via the UI or fleetctl) (default: `false`).
- `query_reports_disabled` disables query reports and deletes existing repors (default: `false`).
+- `query_report_cap` sets the maximum number of results to store per query report before the report is clipped. If increasing this cap, we recommend enabling reports for one query at time and monitoring your infrastructure. (Default: `1000`)
- `scripts_disabled` blocks access to run scripts. Scripts may still be added in the UI and CLI (defaul: `false`).
- `server_url` is the base URL of the Fleet instance (default: provided during Fleet setup)
@@ -537,7 +587,19 @@ org_settings:
Can only be configured for all teams (`org_settings`).
-
+##### end_user_authentication
+
+The `end_user_authentication` section lets you define the identity provider (IdP) settings used for end user authentication during Automated Device Enrollment (ADE). Learn more about end user authentication in Fleet [here](https://fleetdm.com/guides/macos-setup-experience#end-user-authentication-and-eula).
+
+Once the IdP settings are configured, you can use the [`controls.macos_setup.enable_end_user_authentication`](#macos_setup) key to control the end user experience during ADE.
+
+- `idp_name` is the human-friendly name for the identity provider that will provide single sign-on authentication (default: `""`).
+- `entity_id` is the entity ID: a Uniform Resource Identifier (URI) that you use to identify Fleet when configuring the identity provider. It must exactly match the Entity ID field used in identity provider configuration (default: `""`).
+- `metadata` is the metadata (in XML format) provided by the identity provider. (default: `""`)
+- `metadata_url` is the URL that references the identity provider metadata. Only one of `metadata` or `metadata_url` is required (default: `""`).
+
+Can only be configured for all teams (`org_settings`).
+
+
-
diff --git a/docs/Contributing/API-for-contributors.md b/docs/Contributing/API-for-contributors.md
index 6357b4a4d4c5..916a55a17790 100644
--- a/docs/Contributing/API-for-contributors.md
+++ b/docs/Contributing/API-for-contributors.md
@@ -11,9 +11,9 @@
- [Scripts](#scripts)
- [Software](#software)
-This document includes the internal Fleet API routes that are helpful when developing or contributing to Fleet.
+> These endpoints are used by the Fleet UI, Fleet Desktop, and `fleetctl` clients and frequently change to reflect current functionality.
-These endpoints are used by the Fleet UI, Fleet Desktop, and `fleetctl` clients and will frequently change to reflect current functionality.
+This document includes the internal Fleet API routes that are helpful when developing or contributing to Fleet.
If you are interested in gathering information from Fleet in a production environment, please see the [public Fleet REST API documentation](https://fleetdm.com/docs/using-fleet/rest-api).
@@ -540,6 +540,9 @@ The MDM endpoints exist to support the related command-line interface sub-comman
- [Preassign profiles to devices](#preassign-profiles-to-devices)
- [Match preassigned profiles](#match-preassigned-profiles)
- [Get FileVault statistics](#get-filevault-statistics)
+- [Upload VPP content token](#upload-vpp-content-token)
+- [Disable VPP](#disable-vpp)
+
### Generate Apple Business Manager public key (ADE)
@@ -693,7 +696,7 @@ Content-Type: application/octet-stream
| team_id | number | query | _Available in Fleet Premium_ The team ID to apply the custom settings to. Only one of `team_name`/`team_id` can be provided. |
| team_name | string | query | _Available in Fleet Premium_ The name of the team to apply the custom settings to. Only one of `team_name`/`team_id` can be provided. |
| dry_run | bool | query | Validate the provided profiles and return any validation errors, but do not apply the changes. |
-| profiles | json | body | An array of objects, consisting of a `profile` base64-encoded .mobileconfig or JSON for macOS and XML (Windows) file, `labels` array of strings (label names), and `name` display name (for Windows configuration profiles and macOS declaration profiles). |
+| profiles | json | body | An array of objects, consisting of a `profile` base64-encoded .mobileconfig or JSON for macOS and XML (Windows) file, `labels_include_all` or `labels_exclude_any` array of strings (label names), and `name` display name (for Windows configuration profiles and macOS declaration profiles). |
If no team (id or name) is provided, the profiles are applied for all hosts (for _Fleet Free_) or for hosts that are not assigned to any team (for _Fleet Premium_). After the call, the provided list of `profiles` will be the active profiles for that team (or no team) - that is, any existing profile that is not part of that list will be removed, and an existing profile with the same payload identifier (macOS) as a new profile will be edited. If the list of provided `profiles` is empty, all profiles are removed for that team (or no team).
@@ -868,6 +871,55 @@ This endpoint uses the profiles stored by the [Preassign profiles to devices](#p
`Status: 204`
+### Upload VPP content token
+
+`POST /api/v1/fleet/mdm/apple/vpp_token`
+
+#### Parameters
+
+| Name | Type | In | Description |
+| ---- | ---- | -- | ----------- |
+| token | file | form | *Required* The file containing the content token (.vpptoken) from Apple Business Manager |
+
+#### Example
+
+`POST /api/v1/fleet/mdm/apple/vpp_token`
+
+##### Request header
+
+```http
+Content-Length: 850
+Content-Type: multipart/form-data; boundary=------------------------f02md47480und42y
+```
+
+##### Request body
+
+```http
+--------------------------f02md47480und42y
+Content-Disposition: form-data; name="token"; filename="sToken_for_Acme.vpptoken"
+Content-Type: application/octet-stream
+
+--------------------------f02md47480und42y
+```
+
+##### Default response
+
+`Status: 200`
+
+
+### Disable VPP
+
+`DELETE /api/v1/fleet/mdm/apple/vpp_token`
+
+#### Example
+
+`DELETE /api/v1/fleet/mdm/apple/vpp_token`
+
+##### Default response
+
+`Status: 204`
+
+
## Get or apply configuration files
These API routes are used by the `fleetctl` CLI tool. Users can manage Fleet with `fleetctl` and [configuration files in YAML syntax](https://fleetdm.com/docs/using-fleet/configuration-files/).
@@ -1377,11 +1429,13 @@ If the `name` is not already associated with an existing team, this API route cr
| mdm.macos_updates.minimum_version | string | body | The required minimum operating system version. |
| mdm.macos_updates.deadline | string | body | The required installation date for Nudge to enforce the operating system version. |
| mdm.macos_settings | object | body | The macOS-specific MDM settings. |
-| mdm.macos_settings.custom_settings | list | body | The list of objects consists of a `path` to .mobileconfig or JSON file and `labels` list of label names. |
+| mdm.macos_settings.custom_settings | list | body | The list of objects consists of a `path` to .mobileconfig or JSON file and `labels_include_all` or `labels_exclude_any` list of label names. |
| mdm.windows_settings | object | body | The Windows-specific MDM settings. |
-| mdm.windows_settings.custom_settings | list | body | The list of objects consists of a `path` to XML files and `labels` list of label names. |
+| mdm.windows_settings.custom_settings | list | body | The list of objects consists of a `path` to XML files and `labels_include_all` or `labels_exclude_any` list of label names. |
| scripts | list | body | A list of script files to add to this team so they can be executed at a later time. |
-| software | list | body | An array of software objects. Each object consists of:`url`- URL to the software package (PKG, MSI, EXE or DEB),`install_script` - command that Fleet runs to install software, `pre_install_query` - condition query that determines if the install will proceed, `post_install_script` - script that runs after software install, and `self_service` boolean. |
+| software | object | body | The team's software that will be available for install. |
+| software.packages | list | body | An array of objects. Each object consists of:`url`- URL to the software package (PKG, MSI, EXE or DEB),`install_script` - command that Fleet runs to install software, `pre_install_query` - condition query that determines if the install will proceed, `post_install_script` - script that runs after software install, and `self_service` boolean. |
+| software.app_store_apps | list | body | An array objects. Each object consists of `app_store_id` - ID of the App Store app formatted as a string (in quotes) rather than a number. |
| mdm.macos_settings.enable_disk_encryption | bool | body | Whether disk encryption should be enabled for hosts that belong to this team. |
| force | bool | query | Force apply the spec even if there are (ignorable) validation errors. Those are unknown keys and agent options-related validations. |
| dry_run | bool | query | Validate the provided JSON for unknown keys and invalid value types and return any validation errors, but do not apply the changes. |
@@ -1440,32 +1494,43 @@ If the `name` is not already associated with an existing team, this API route cr
"deadline": "2023-12-01"
},
"macos_settings": {
- "custom_settings": {
- "path": "path/to/profile1.mobileconfig"
- "labels": ["Label 1", "Label 2"]
- },
- {
- "path": "path/to/profile2.json"
- "labels": ["Label 3", "Label 4"]
- },
+ "custom_settings": [
+ {
+ "path": "path/to/profile1.mobileconfig"
+ "labels_include_all": ["Label 1", "Label 2"]
+ },
+ {
+ "path": "path/to/profile2.json"
+ "labels_exclude_any": ["Label 3", "Label 4"]
+ },
+ ],
"enable_disk_encryption": true
},
"windows_settings": {
- "custom_settings": {
- "path": "path/to/profile3.xml"
- "labels": ["Label 1", "Label 2"]
- },
+ "custom_settings": [
+ {
+ "path": "path/to/profile3.xml"
+ "labels_include_all": ["Label 1", "Label 2"]
+ }
+ ]
}
},
"scripts": ["path/to/script.sh"],
- "software": [
- {
- "url": "https://cdn.zoom.us/prod/5.16.10.26186/x64/ZoomInstallerFull.msi",
- "pre_install_query": "SELECT 1 FROM macos_profiles WHERE uuid='c9f4f0d5-8426-4eb8-b61b-27c543c9d3db';",
- "post_install_script": "sudo /Applications/Falcon.app/Contents/Resources/falconctl license 0123456789ABCDEFGHIJKLMNOPQRSTUV-WX",
- "self_service": true
- }
- ]
+ "software": {
+ "packages": [
+ {
+ "url": "https://cdn.zoom.us/prod/5.16.10.26186/x64/ZoomInstallerFull.msi",
+ "pre_install_query": "SELECT 1 FROM macos_profiles WHERE uuid='c9f4f0d5-8426-4eb8-b61b-27c543c9d3db';",
+ "post_install_script": "sudo /Applications/Falcon.app/Contents/Resources/falconctl license 0123456789ABCDEFGHIJKLMNOPQRSTUV-WX",
+ "self_service": true,
+ }
+ ],
+ "app_store_apps": [
+ {
+ "app_store_id": "12464567",
+ }
+ ]
+ }
}
]
}
@@ -2477,6 +2542,7 @@ Gets all information required by Fleet Desktop, this includes things like the nu
```json
{
"failing_policies_count": 3,
+ "self_service": true,
"notifications": {
"needs_mdm_migration": true,
"renew_enrollment_profile": false,
@@ -2536,35 +2602,58 @@ Lists the software installed on the current device.
{
"id": 121,
"name": "Google Chrome.app",
+ "software_package": {
+ "name": "GoogleChrome.pkg"
+ "version": "125.12.2"
+ "self_service": true,
+ "last_install": {
+ "install_uuid": "8bbb8ac2-b254-4387-8cba-4d8a0407368b",
+ "installed_at": "2024-05-15T15:23:57Z"
+ },
+ },
+ "app_store_app": null,
"source": "apps",
"status": "failed",
- "last_install": {
- "install_uuid": "8bbb8ac2-b254-4387-8cba-4d8a0407368b",
- "installed_at": "2024-05-15T15:23:57Z"
- },
"installed_versions": [
- {
+ {
"version": "121.0",
"last_opened_at": "2024-04-01T23:03:07Z",
"vulnerabilities": ["CVE-2023-1234","CVE-2023-4321","CVE-2023-7654"],
"installed_paths": ["/Applications/Google Chrome.app"]
}
- ]
+ ],
+ "software_package": {
+ "name": "google-chrome-124-0-6367-207.pkg",
+ "version": "121.0",
+ "self_service": true,
+ "icon_url": null,
+ "last_install": null
+ },
+ "app_store_app": null
},
{
"id": 143,
"name": "Firefox.app",
+ "software_package": null,
+ "app_store_app": null,
"source": "apps",
"status": null,
- "last_install": null,
"installed_versions": [
- {
+ {
"version": "125.6",
"last_opened_at": "2024-04-01T23:03:07Z",
"vulnerabilities": ["CVE-2023-1234","CVE-2023-4321","CVE-2023-7654"],
"installed_paths": ["/Applications/Firefox.app"]
}
- ]
+ ],
+ "software_package": null,
+ "app_store_app": {
+ "app_store_id": "12345",
+ "version": "125.6",
+ "self_service": false,
+ "icon_url": "https://example.com/logo-light.jpg",
+ "last_install": null
+ },
}
],
"meta": {
@@ -2751,27 +2840,6 @@ Signals the Fleet server to send a webbook request with the device UUID and seri
---
-#### Trigger FileVault key escrow
-
-Sends a signal to Fleet Desktop to initiate a FileVault key escrow. This is useful for setting the escrow key initially as well as in scenarios where a token rotation is required. **Requires Fleet Premium license**
-
-`POST /api/v1/fleet/device/{token}/rotate_encryption_key`
-
-##### Parameters
-
-| Name | Type | In | Description |
-| ----- | ------ | ---- | ---------------------------------- |
-| token | string | path | The device's authentication token. |
-
-##### Example
-
-`POST /api/v1/fleet/device/abcdef012456789/rotate_encryption_key`
-
-##### Default response
-
-`Status: 204`
-
-
### Report an agent error
Notifies the server about an agent error, resulting in two outcomes:
@@ -2934,7 +3002,7 @@ If the Fleet instance is provided required parameters to complete setup.
## Scripts
-### Batch-apply scripts
+### Batch-apply scripts
_Available in Fleet Premium_
@@ -2963,7 +3031,7 @@ If both `team_id` and `team_name` parameters are included, this endpoint will re
## Software
-### Batch-apply software
+### Batch-apply software
_Available in Fleet Premium._
@@ -2973,10 +3041,12 @@ _Available in Fleet Premium._
| Name | Type | In | Description |
| --------- | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| team_id | number | query | The ID of the team to add the software package to. Only one team identifier (`team_id` or `team_name`) can be included in the request, omit this parameter if using `team_name`. |
-| team_name | string | query | The name of the team to add the software package to. Only one team identifier (`team_id` or `team_name`) can be included in the request, omit this parameter if using `team_id`. |
+| team_id | number | query | The ID of the team to add the software package to. Only one team identifier (`team_id` or `team_name`) can be included in the request, omit this parameter if using `team_name`. Ommitting these parameters will add software to 'No Team'. |
+| team_name | string | query | The name of the team to add the software package to. Only one team identifier (`team_id` or `team_name`) can be included in the request, omit this parameter if using `team_id`. Ommitting these parameters will add software to 'No Team'. |
| dry_run | bool | query | If `true`, will validate the provided software packages and return any validation errors, but will not apply the changes. |
-| software | list | body | An array of software objects. Each object consists of:`url`- URL to the software package (PKG, MSI, EXE or DEB),`install_script` - command that Fleet runs to install software, `pre_install_query` - condition query that determines if the install will proceed, and `post_install_script` - script that runs after software install. |
+| software | object | body | The team's software that will be available for install. |
+| software.packages | list | body | An array of objects. Each object consists of:`url`- URL to the software package (PKG, MSI, EXE or DEB),`install_script` - command that Fleet runs to install software, `pre_install_query` - condition query that determines if the install will proceed, and `post_install_script` - script that runs after software install. |
+| software.app_store_apps | list | body | An array objects. Each object consists of `app_store_id` - ID of the App Store app. |
If both `team_id` and `team_name` parameters are included, this endpoint will respond with an error. If no `team_name` or `team_id` is provided, the scripts will be applied for **all hosts**.
@@ -2987,3 +3057,102 @@ If both `team_id` and `team_name` parameters are included, this endpoint will re
##### Default response
`Status: 204`
+
+ ### Run live script
+
+Run a live script and get results back (5 minute timeout). Live scripts only runs on the host if it has no other scripts running.
+
+`POST /api/v1/fleet/scripts/run/sync`
+
+#### Parameters
+
+| Name | Type | In | Description |
+| ---- | ------- | ---- | -------------------------------------------- |
+| host_id | integer | body | **Required**. The host ID to run the script on. |
+| script_id | integer | body | The ID of the existing saved script to run. Only one of either `script_id`, `script_name` or `script_contents` can be included in the request; omit this parameter if using `script_contents` or `script_name`. |
+| script_contents | string | body | The contents of the script to run. Only one of either `script_contents`, `script_id` or `script_name` can be included in the request; omit this parameter if using `script_id` or `script_name`. |
+| script_name | string | body | The name of the existing saved script to run. Only one of either `script_name`, `script_id` or `script_contents` can be included in the request; omit this parameter if using `script_contents` or `script_id`. |
+| team_id | integer | body | ID of the team the saved script referenced by `script_name` belongs to. Default: `0` (hosts assigned to "No team") |
+
+
+> Note that if both `script_id` and `script_contents` are included in the request, this endpoint will respond with an error.
+
+#### Example
+
+`POST /api/v1/fleet/scripts/run/sync`
+
+##### Default response
+
+`Status: 200`
+
+```json
+{
+ "host_id": 1227,
+ "execution_id": "e797d6c6-3aae-11ee-be56-0242ac120002",
+ "script_contents": "echo 'hello'",
+ "output": "hello",
+ "message": "",
+ "runtime": 1,
+ "host_timeout": false,
+ "exit_code": 0
+}
+```
+
+### Get token to download package
+
+_Available in Fleet Premium._
+
+`POST /api/v1/fleet/software/titles/:software_title_id/package/token?alt=media`
+
+The returned token is a one-time use token that expires after 10 minutes.
+
+#### Parameters
+
+| Name | Type | In | Description |
+|-------------------|---------|-------|------------------------------------------------------------------|
+| software_title_id | integer | path | **Required**. The ID of the software title for software package. |
+| team_id | integer | query | **Required**. The team ID containing the software package. |
+| alt | integer | query | **Required**. Must be specified and set to "media". |
+
+#### Example
+
+`POST /api/v1/fleet/software/titles/123/package/token?alt=media&team_id=2`
+
+##### Default response
+
+`Status: 200`
+
+```json
+{
+ "token": "e905e33e-07fe-4f82-889c-4848ed7eecb7"
+}
+```
+
+### Download package using a token
+
+_Available in Fleet Premium._
+
+`GET /api/v1/fleet/software/titles/:software_title_id/package/token/:token?alt=media`
+
+#### Parameters
+
+| Name | Type | In | Description |
+|-------------------|---------|------|--------------------------------------------------------------------------|
+| software_title_id | integer | path | **Required**. The ID of the software title to download software package. |
+| token | string | path | **Required**. The token to download the software package. |
+
+#### Example
+
+`GET /api/v1/fleet/software/titles/123/package/token/e905e33e-07fe-4f82-889c-4848ed7eecb7`
+
+##### Default response
+
+`Status: 200`
+
+```http
+Status: 200
+Content-Type: application/octet-stream
+Content-Disposition: attachment
+Content-Length:
+Body:
+```
diff --git a/docs/Using Fleet/Audit-logs.md b/docs/Contributing/Audit-logs.md
similarity index 87%
rename from docs/Using Fleet/Audit-logs.md
rename to docs/Contributing/Audit-logs.md
index ab16e6970d60..b2fe63528bf8 100644
--- a/docs/Using Fleet/Audit-logs.md
+++ b/docs/Contributing/Audit-logs.md
@@ -582,6 +582,48 @@ This activity contains the following fields:
}
```
+## edited_ios_min_version
+
+Generated when the minimum required iOS version or deadline is modified.
+
+This activity contains the following fields:
+- "team_id": The ID of the team that the minimum iOS version applies to, `null` if it applies to devices that are not in a team.
+- "team_name": The name of the team that the minimum iOS version applies to, `null` if it applies to devices that are not in a team.
+- "minimum_version": The minimum iOS version required, empty if the requirement was removed.
+- "deadline": The deadline by which the minimum version requirement must be applied, empty if the requirement was removed.
+
+#### Example
+
+```json
+{
+ "team_id": 3,
+ "team_name": "iPhones",
+ "minimum_version": "17.5.1",
+ "deadline": "2023-06-01"
+}
+```
+
+## edited_ipados_min_version
+
+Generated when the minimum required iPadOS version or deadline is modified.
+
+This activity contains the following fields:
+- "team_id": The ID of the team that the minimum iPadOS version applies to, `null` if it applies to devices that are not in a team.
+- "team_name": The name of the team that the minimum iPadOS version applies to, `null` if it applies to devices that are not in a team.
+- "minimum_version": The minimum iPadOS version required, empty if the requirement was removed.
+- "deadline": The deadline by which the minimum version requirement must be applied, empty if the requirement was removed.
+
+#### Example
+
+```json
+{
+ "team_id": 3,
+ "team_name": "iPads",
+ "minimum_version": "17.5.1",
+ "deadline": "2023-06-01"
+}
+```
+
## edited_windows_updates
Generated when the Windows OS updates deadline or grace period is modified.
@@ -1158,7 +1200,7 @@ Generated when a software installer is deleted from Fleet.
This activity contains the following fields:
- "software_title": Name of the software.
- "software_package": Filename of the installer.
-- "team_name": Name of the team to which this software was added. `null if it was added to no team.
+- "team_name": Name of the team to which this software was added. `null` if it was added to no team.
- "team_id": The ID of the team to which this software was added. `null` if it was added to no team.
- "self_service": Whether the software was available for installation by the end user.
@@ -1174,6 +1216,109 @@ This activity contains the following fields:
}
```
+## enabled_vpp
+
+Generated when VPP features are enabled in Fleet.
+
+This activity contains the following fields:
+- "location": Location associated with the VPP content token for the enabled VPP features.
+
+#### Example
+
+```json
+{
+ "location": "Acme Inc."
+}
+```
+
+## disabled_vpp
+
+Generated when VPP features are disabled in Fleet.
+
+This activity contains the following fields:
+- "location": Location associated with the VPP content token for the disabled VPP features.
+
+#### Example
+
+```json
+{
+ "location": "Acme Inc."
+}
+```
+
+## added_app_store_app
+
+Generated when an App Store app is added to Fleet.
+
+This activity contains the following fields:
+- "software_title": Name of the App Store app.
+- "app_store_id": ID of the app on the Apple App Store.
+- "platform": Platform of the app (`darwin`, `ios`, or `ipados`).
+- "self_service": App installation can be initiated by device owner.
+- "team_name": Name of the team to which this App Store app was added, or `null` if it was added to no team.
+- "team_id": ID of the team to which this App Store app was added, or `null`if it was added to no team.
+
+#### Example
+
+```json
+{
+ "software_title": "Logic Pro",
+ "app_store_id": "1234567",
+ "platform": "darwin",
+ "self_service": false,
+ "team_name": "Workstations",
+ "team_id": 1
+}
+```
+
+## deleted_app_store_app
+
+Generated when an App Store app is deleted from Fleet.
+
+This activity contains the following fields:
+- "software_title": Name of the App Store app.
+- "app_store_id": ID of the app on the Apple App Store.
+- "platform": Platform of the app (`darwin`, `ios`, or `ipados`).
+- "team_name": Name of the team from which this App Store app was deleted, or `null` if it was deleted from no team.
+- "team_id": ID of the team from which this App Store app was deleted, or `null`if it was deleted from no team.
+
+#### Example
+
+```json
+{
+ "software_title": "Logic Pro",
+ "app_store_id": "1234567",
+ "platform": "darwin",
+ "team_name": "Workstations",
+ "team_id": 1
+}
+```
+
+## installed_app_store_app
+
+Generated when an App Store app is installed on a device.
+
+This activity contains the following fields:
+- host_id: ID of the host on which the app was installed.
+- self_service: App installation was initiated by device owner.
+- host_display_name: Display name of the host.
+- software_title: Name of the App Store app.
+- app_store_id: ID of the app on the Apple App Store.
+- command_uuid: UUID of the MDM command used to install the app.
+
+#### Example
+
+```json
+{
+ "host_id": 42,
+ "self_service": true,
+ "host_display_name": "Anna's MacBook Pro",
+ "software_title": "Logic Pro",
+ "app_store_id": "1234567",
+ "command_uuid": "98765432-1234-1234-1234-1234567890ab"
+}
+```
+
diff --git a/docs/Contributing/File-carving.md b/docs/Contributing/File-carving.md
index 57b743f2071f..b283f11f4c8d 100644
--- a/docs/Contributing/File-carving.md
+++ b/docs/Contributing/File-carving.md
@@ -23,8 +23,7 @@ The default flagfile provided in the "Add new host" dialog also includes this co
The `carver_block_size` flag should be configured in osquery.
For the (default) MySQL Backend, the configured value must be less than the value of
-`max_allowed_packet` in the MySQL connection, allowing for some overhead. The default for [MySQL 5.7](https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_max_allowed_packet)
-is 4MB and for [MySQL 8](https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_max_allowed_packet) it is 64MB.
+`max_allowed_packet` in the MySQL connection, allowing for some overhead. The default for [MySQL 8](https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_max_allowed_packet) it is 64MB.
For the S3/Minio backend, this value must be set to at least 5MiB (`5242880`) due to the
[constraints of S3's multipart
@@ -78,7 +77,7 @@ The same is not true if S3 is used as the storage backend. In that scenario, it
### Alternative carving backends
-#### Minio
+#### MinIO
Configure the following:
- `FLEET_S3_ENDPOINT_URL=minio_host:port`
@@ -88,6 +87,11 @@ Configure the following:
- `FLEET_S3_FORCE_S3_PATH_STYLE=true`
- `FLEET_S3_REGION=minio` or any non-empty string otherwise Fleet will attempt to derive the region.
+If you're testing file carving locally with the docker-compose environment, the `--dev` flag on Fleet server will
+automatically point carves to the local MinIO container and write to the `carves-dev` bucket without needing to set
+additional configuration. Note that this bucket is *not* created automatically when bringing MinIO up; you'll need to
+log in via `http://localhost:9001` with credentials `minio` / `minio123!` to create the bucket.
+
### Troubleshooting
#### Check carve status in osquery
diff --git a/docs/Contributing/Simulate-slow-network.md b/docs/Contributing/Simulate-slow-network.md
index 3b342a0f3af4..6fa02d6f8384 100644
--- a/docs/Contributing/Simulate-slow-network.md
+++ b/docs/Contributing/Simulate-slow-network.md
@@ -7,6 +7,17 @@ The guide assumes you'll build and run the Fleet server locally with the `make f
(Has been tested on macOS only.)
+## 0. Edit docker-compose.yml
+
+Add the following service to the main `docker-compose.yml`:
+```yml
+ toxiproxy:
+ image: shopify/toxiproxy
+ ports:
+ - "22220:22220"
+ - "8474:8474"
+```
+
## 1. Start services
```sh
@@ -44,4 +55,4 @@ curl -s -XPOST -d '{"type" : "latency", "attributes" : {"latency" : 1000, "jitte
```
-
\ No newline at end of file
+
diff --git a/docs/Contributing/Testing-and-local-development.md b/docs/Contributing/Testing-and-local-development.md
index a3445e3ccdff..45c5a93fe0e8 100644
--- a/docs/Contributing/Testing-and-local-development.md
+++ b/docs/Contributing/Testing-and-local-development.md
@@ -81,6 +81,14 @@ REDIS_TEST=1 MYSQL_TEST=1 make test
The integration tests in the `server/service` package can generate a lot of logs mixed with the test results output. To make it easier to identify a failing test in this package, you can set the `FLEET_INTEGRATION_TESTS_DISABLE_LOG=1` environment variable so that logging is disabled.
+The MDM integration tests are run with a random selection of software installer storage backends (local filesystem or S3/minio), and similar for the bootstrap packages storage (DB or S3/minio). You can force usage of the S3 backend by setting `FLEET_INTEGRATION_TESTS_SOFTWARE_INSTALLER_STORE=s3`. Note that `MINIO_STORAGE_TEST=1` must also be set for the S3 backend to be used.
+
+When the S3 backend is used, this line will be printed in the tests' output (as this could be relevant to understand and debug the test failure):
+
+```
+ integration_mdm_test.go:196: >>> using S3/minio software installer store
+```
+
Note that on a Linux system, the Redis tests will include running in cluster mode, so the docker Redis Cluster setup must be running. This implies starting the docker dependencies as follows:
```sh
@@ -481,7 +489,9 @@ FLEET_SERVER_SANDBOX_ENABLED=1 FLEET_PACKAGING_GLOBAL_ENROLL_SECRET=xyz ./build
Be sure to replace the `FLEET_PACKAGING_GLOBAL_ENROLL_SECRET` value above with the global enroll
secret from the `fleetctl package` command used to build the installers.
-MinIO also offers a web interface at http://localhost:9001. Credentials are `minio` / `minio123!`.
+MinIO also offers a web interface at http://localhost:9001. Credentials are `minio` / `minio123!`. When starting the
+Fleet server up with `--dev` the server will look for installers in the `software-installers-dev` MinIO bucket. You can
+create this bucket via the MinIO web UI (it is *not* created by default when setting up the docker-compose environment).
## Telemetry
diff --git a/docs/Using Fleet/Understanding-host-vitals.md b/docs/Contributing/Understanding-host-vitals.md
similarity index 99%
rename from docs/Using Fleet/Understanding-host-vitals.md
rename to docs/Contributing/Understanding-host-vitals.md
index 77b9e68ca954..3cdabf2fde06 100644
--- a/docs/Using Fleet/Understanding-host-vitals.md
+++ b/docs/Contributing/Understanding-host-vitals.md
@@ -504,7 +504,7 @@ SELECT
'' AS arch,
'' AS installed_path
FROM deb_packages
-WHERE status = 'install ok installed'
+WHERE status LIKE '% ok installed'
UNION
SELECT
package AS name,
diff --git a/docs/Contributing/Vulnerability-processing.md b/docs/Contributing/Vulnerability-processing.md
index 5c111316fa2c..7a159bc000b2 100644
--- a/docs/Contributing/Vulnerability-processing.md
+++ b/docs/Contributing/Vulnerability-processing.md
@@ -11,7 +11,7 @@ For Fleet Premium users, each CVE includes its Common Vulnerability Scoring Syst
Fleet's strategy for detecting vulnerabilities (CVEs) varies according to the host's platform and
the software in question.
-### Windows/MacOS hosts using the NVD dataset
+### macOS, Windows, and Linux hosts using NVD
First, Fleet retrieves the installed software for each host using osquery queries. Then, Fleet
translates each installed software into [Common Platform Enumeration (CPE)](https://en.wikipedia.org/wiki/Common_Platform_Enumeration) names.
@@ -35,7 +35,7 @@ be able write a query to retrieve all CVEs).
- Fleet combines two sources to get accurate and up-to-date CVE information:
- [National Vulnerability Database](https://nvd.nist.gov/developers/vulnerabilities)'s CVE feeds.
- [VulnCheck](https://vulncheck.com/)
-- To reduce the load and complexity of processing these datasets, Fleet uses two Github repositories (https://github.com/fleetdm/nvd and https://github.com/fleetdm/vulndb) that fetch, pre-process and expose the resulting dataset as Github releases.
+- To reduce the load and complexity of processing these datasets, Fleet uses two Github repositories (https://github.com/fleetdm/nvd and https://github.com/fleetdm/vulnerabilities) that fetch, pre-process and expose the resulting dataset as Github releases.
- The Fleet servers then download these Github releases and run vulnerability processing using the downloaded datasets and the software information fetched from hosts.
```mermaid
@@ -78,19 +78,47 @@ information and what vulnerabilities were patched with the release, we then exam
macOS apps and if an Office app is found we compare its version with the release notes metadata
and report back any vulnerabilities to which the software is susceptible.
-### Linux hosts
+### Linux hosts (via OVAL)
First, we determine what Linux distributions are part of your fleet (keep in mind that there will
-be a small delay between the time a new Linux hosts is added and the time the host is 'detected'). We then
-use
-that information to determine what OVAL definitions need to be downloaded and parsed - you can find
-a list of all the OVAL definitions we use
-[here](https://github.com/fleetdm/nvd/blob/master/oval_sources.json). OVAL definitions will be
+be a small delay between the time a new Linux host is added and the time the host is "detected"). We then
+use that information to determine what OVAL definitions need to be downloaded and parsed - you can find
+a list of all the OVAL definitions we use [here](https://github.com/fleetdm/nvd/blob/master/oval_sources.json). OVAL definitions will be
refreshed on a daily basis.
+*NOTE:* Amazon Linux 2 is included in the OVAL mapping but vulnerabilities are no longer pulled via that file
+as of 4.56.0 due to false positives (Amazon backports fixes and releases updates independent of RHEL).
+
Finally, we look at the software inventory of each host and execute the assertions contained in the
corresponding OVAL file - any match is reported using the same channels as with Windows/Mac OS vulnerabilities
+#### Ubuntu kernel vulnerabilities
+
+For Ubuntu Linux, kernel vulnerabilities are detected against any installed kernel, even if it is not the actively running kernel.
+
+Vulnerabilities are matched against software matching `linux-image.*`
+(ie.`linux-image-5.4.0-163-generic`).
+
+The Canonical OVAL feed is opinionated on the linux
+variant (ie. `-generic`) when matching against vulnerabilities, so when Fleet detects a kernel variant not
+available in the OVAL feed, it uses the NVD feed to look for vulnerabilities matching the following
+CPE pattern:
+`cpe:2.3:o:linux:linux_kernel:*;*:*:*:*:*:*:*:*`
+
+### Amazon Linux hosts (via goval-dictionary)
+
+Amazon Linux uses [ALAS](https://alas.aws.amazon.com/) rather than OVAL files to provide vulnerability info. The
+[goval-dictionary](https://github.com/vulsio/goval-dictionary) tool supports fetching those bulletins into database
+formats including sqlite. The database contains mappings of CVEs to package releases where that CVE was fixed. We run
+this tool for each Amazon Linux version as part of the release process in https://github.com/fleetdm/vulnerabilities.
+
+As part of the (default hourly) vulnerabilities check run, we download the sqlite files for relevant OS versions (keep in
+mind that there will be a small delay between the time a new Linux host is added and the time the host is "detected"),
+then for each host check if each package on that host is mentioned in the database. For each package found, we report
+a vulnerability when the installed version of the package is older than the version where the vulnerability was fixed.
+
+ALAS does *not* use CPE lookups.
+
## Performance
### Windows/Mac OS
@@ -114,9 +142,9 @@ For example, when running a development instance of Fleet on an Apple Macbook Pr
The CPU and memory usages are in burst once every hour (or the configured periodicity) on the
instance that does the processing. RAM spikes are expected to not exceed the 2GBs.
-### Linux
+### Linux (OVAL)
-As with Windows/Mac OS, vulnerability detection for Linux is performed in a single Fleet instance. The
+As with Windows/Mac OS, vulnerability detection for Linux is performed on a single Fleet server. The
files downloaded will vary depending on what distributions are on your fleet. The list of all the
OVAL files we use can be found [here](https://github.com/fleetdm/nvd/blob/master/oval_sources.json).
@@ -138,6 +166,19 @@ The performance will be a function of three variables:
That said, the performance characteristic should be linear (if scanning 200 hosts take
~20 seconds, then scanning 2000 hosts should take ~200 seconds).
+### Linux (goval-dictionary)
+
+As with OVAL, vulnerability detection for Linux via goval-dictionary is performed on a single Fleet server.
+Fleet will download one xz'd sqlite file per supported (see oval_platform.go -> IsGovalDictionarySupported) OS/version
+combination every time vulnerability scanning is run. OS version identifiers are constructed the same way
+as for OVAL, so Amazon Linux 2 becomes `amzn_02` and Amazon Linux 2023 becomes `amzn_2023`. sqlite databases are
+preprocessed as part of our vulnerabilities feed build, so are used as-is after download/extraction; the largest
+database is currently for Amazon Linux 2, at ~10 MiB. Filename format on disk is
+`fleet_goval_dictionary_platform.sqlite3`, corresponding to `platform.sqlite3` in the `vulnerabilities` repo releases.
+
+Like OVAL, execution time currently scales with the size of the database, the number of hosts to scan, and the number
+of packages installed. Scanning 2000 hosts will take about 10x longer than scanning 200 hosts.
+
## Detection pipeline
There are several steps that go into the vulnerability detection process. In this section we'll dive into what they are and how it works.
@@ -171,7 +212,7 @@ The whole pipeline exists to compensate for these differences, and it can be div
process2-->interval
```
- ### Windows/Mac OS
+### Windows/Mac OS
```mermaid
graph TD;
@@ -181,7 +222,7 @@ The whole pipeline exists to compensate for these differences, and it can be div
cveDownload-->cveMap[CVE detection]
```
- ### Linux
+### Linux (OVAL)
```mermaid
graph TD;
@@ -193,6 +234,14 @@ The whole pipeline exists to compensate for these differences, and it can be div
parse --> execute
```
+### Linux (goval-dictionary)
+
+ ```mermaid
+ graph TD;
+ process[Process Linux hosts] --> download(Download vulnerability databases from Fleet)
+ download --> execute(Match vulnerabilities to software older than fixed versions)
+ ```
+
### Ingesting software lists from hosts
The ingestion of software varies per platform. We run a `UNION` of several queries in each:
diff --git a/docs/Contributing/fleetctl-apply.md b/docs/Contributing/fleetctl-apply.md
index 801e48c1cba3..dac4b256302c 100644
--- a/docs/Contributing/fleetctl-apply.md
+++ b/docs/Contributing/fleetctl-apply.md
@@ -2,7 +2,7 @@
The `fleectl apply` command and YAML interface is used for one-off imports and backwards compatibility GitOps.
-To use Fleet's best practice GitOps, check out the GitOps docs [here](https://fleetdm.com/using-fleet/gitops).
+To use Fleet's best practice GitOps, check out the GitOps docs [here](https://fleetdm.com/docs/using-fleet/gitops).
## Queries
@@ -239,19 +239,12 @@ spec:
macos_settings:
custom_settings:
- path: '/path/to/profile1.mobileconfig'
- labels:
- - Label name 1
- path: '/path/to/profile2.mobileconfig'
- path: '/path/to/profile3.mobileconfig'
- labels:
- - Label name 2
- - Label name 3
enable_disk_encryption: true
windows_settings:
custom_settings:
- path: '/path/to/profile4.xml'
- labels:
- - Label name 4
- path: '/path/to/profile5.xml'
scripts:
- path/to/script1.sh
@@ -345,7 +338,8 @@ List of saved scripts that can be run on hosts that are part of the team.
- Default value: none
- Config file format:
- ```yaml
+
+```yaml
apiVersion: v1
kind: team
spec:
@@ -354,7 +348,7 @@ spec:
scripts:
- path/to/script1.sh
- path/to/script2.sh
- ```
+```
## Organization settings
@@ -463,19 +457,12 @@ spec:
macos_settings:
custom_settings:
- path: '/path/to/profile1.mobileconfig'
- labels:
- - Label name 1
- path: '/path/to/profile2.mobileconfig'
- path: '/path/to/profile3.mobileconfig'
- labels:
- - Label name 2
- - Label name 3
enable_disk_encryption: true
windows_settings:
custom_settings:
- path: '/path/to/profile4.xml'
- labels:
- - Label name 4
- path: '/path/to/profile5.xml'
```
diff --git a/docs/Deploy/Reference-Architectures.md b/docs/Deploy/Reference-Architectures.md
index 5241ea3b277b..285eb54d7a95 100644
--- a/docs/Deploy/Reference-Architectures.md
+++ b/docs/Deploy/Reference-Architectures.md
@@ -2,10 +2,10 @@
## The Fleet binary
-The Fleet application contains two single static binaries which provide web based administration, REST API, and CLI interface to Fleet.
+The Fleet application contains two single static binaries which provide web based administration, a REST API, and a [CLI interface](https://fleetdm.com/guides/fleetctl).
The `fleet` binary contains:
-- The Fleet TLS web server (no external webserver is required but it supports a proxy if desired)
+- The [Fleet TLS web server](https://fleetdm.com/docs/configuration/fleet-server-configuration) (no external webserver is required but it supports a proxy if desired)
- The Fleet web interface
- The Fleet application management [REST API](https://fleetdm.com/docs/using-fleet/rest-api)
- The Fleet osquery API endpoints
@@ -24,15 +24,15 @@ Fleet currently has three infrastructure dependencies: MySQL, Redis, and a TLS c
### MySQL
Fleet uses MySQL extensively as its main database. Many cloud providers (such as [AWS](https://aws.amazon.com/rds/mysql/) and [GCP](https://cloud.google.com/sql/)) host reliable MySQL services which you may consider for this purpose. A well-supported MySQL [Docker image](https://hub.docker.com/_/mysql/) also exists if you would rather run MySQL in a container.
-For more information on how to configure the `fleet` binary to use the correct MySQL instance, see the [Configuration](https://fleetdm.com/docs/deploying/configuration) document.
+For more information on how to configure the `fleet` binary to use the correct MySQL instance, see the [MySQL configuration](https://fleetdm.com/docs/configuration/fleet-server-configuration#mysql) documentation.
-Fleet requires at least MySQL version 5.7, and is tested using the InnoDB storage engine.
+Fleet requires at least MySQL version 8.0, and is tested using the InnoDB storage engine.
There are many "drop-in replacements" for MySQL available. If you'd like to experiment with some bleeding-edge technology and use Fleet with one of these alternative database servers, we think that's awesome! Please be aware they are not officially supported and that it is very important to set up a dev environment to thoroughly test new releases.
### Redis
-Fleet uses Redis to ingest and queue the results of distributed queries, cache data, etc. Many cloud providers (such as [AWS](https://aws.amazon.com/elasticache/) and [GCP](https://console.cloud.google.com/launcher/details/click-to-deploy-images/redis)) host reliable Redis services which you may consider for this purpose. A well supported Redis [Docker image](https://hub.docker.com/_/redis/) also exists if you would rather run Redis in a container. For more information on how to configure the `fleet` binary to use the correct Redis instance, see the [Configuration](https://fleetdm.com/docs/deploying/configuration) document.
+Fleet uses Redis to ingest and queue the results of distributed queries, cache data, etc. Many cloud providers (such as [AWS](https://aws.amazon.com/elasticache/) and [GCP](https://console.cloud.google.com/launcher/details/click-to-deploy-images/redis)) host reliable Redis services which you may consider for this purpose. A well supported Redis [Docker image](https://hub.docker.com/_/redis/) also exists if you would rather run Redis in a container. For more information on how to configure the `fleet` binary to use the correct Redis instance, see the [Redis configuration](https://fleetdm.com/docs/configuration/fleet-server-configuration#redis) documentation.
## Systemd
@@ -150,7 +150,7 @@ In some cases adding a read replica can increase database performance for specif
#### Traffic load balancing
Load balancing enables distributing request traffic over many instances of the backend application. Using AWS Application
-Load Balancer can also [offload SSL termination](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html), freeing Fleet to spend the majority of it's allocated compute dedicated
+Load Balancer can also [offload SSL termination](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html), freeing Fleet to spend the majority of its allocated compute dedicated
to its core functionality. More details about ALB can be found [here](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html).
_**Note if using [terraform reference architecture](https://github.com/fleetdm/fleet/tree/main/infrastructure/dogfood/terraform/aws#terraform) all configurations can dynamically scale based on load(cpu/memory) and all configurations
@@ -167,10 +167,11 @@ assume On-Demand pricing (savings are available through Reserved Instances). Cal
| --------------- | ------------- | --- |
| 1 Fargate task | 512 CPU Units | 4GB |
-| Dependencies | Version | Instance type | Nodes |
-| ------------ | ----------------------- | ------------- | ----- |
-| Redis | 6 | t4g.small | 3 |
-| MySQL | 8.0.mysql_aurora.3.04.2 | db.t4g.medium | 2 |
+| Dependencies | Version | Instance type | Nodes |
+| ------------ | ----------------------- | --------------- | ----- |
+| Redis | 6 | cache.t4g.small | 3 |
+| MySQL | 8.0.mysql_aurora.3.04.2 | db.t4g.medium | 2 |
+
###### [Up to 25000 hosts](https://calculator.aws/#/estimate?id=d735758715f059118dbce8dc42f3ff2410adc621)
@@ -178,10 +179,10 @@ assume On-Demand pricing (savings are available through Reserved Instances). Cal
| --------------- | -------------- | --- |
| 10 Fargate task | 1024 CPU Units | 4GB |
-| Dependencies | Version | Instance type | Nodes |
-| ------------ | ----------------------- | ------------- | ----- |
-| Redis | 6 | m6g.large | 3 |
-| MySQL | 8.0.mysql_aurora.3.04.2 | db.r6g.large | 2 |
+| Dependencies | Version | Instance type | Nodes |
+| ------------ | ----------------------- | --------------- | ----- |
+| Redis | 6 | cache.m6g.large | 3 |
+| MySQL | 8.0.mysql_aurora.3.04.2 | db.r6g.large | 2 |
###### [Up to 150000 hosts](https://calculator.aws/#/estimate?id=689fea65efff361ee070b15044a01224b8d26621)
@@ -190,10 +191,11 @@ assume On-Demand pricing (savings are available through Reserved Instances). Cal
| --------------- | -------------- | --- |
| 20 Fargate task | 1024 CPU Units | 4GB |
-| Dependencies | Version | Instance type | Nodes |
-| ------------ | ----------------------- | -------------- | ----- |
-| Redis | 6 | m6g.large | 3 |
-| MySQL | 8.0.mysql_aurora.3.04.2 | db.r6g.4xlarge | 2 |
+| Dependencies | Version | Instance type | Nodes |
+| ------------ | ----------------------- | --------------- | ----- |
+| Redis | 6 | cache.m6g.large | 3 |
+| MySQL | 8.0.mysql_aurora.3.04.2 | db.r6g.4xlarge | 2 |
+
###### [Up to 300000 hosts](https://calculator.aws/#/estimate?id=19b667fde567df0d64d9fae632d4885d7fdc726a)
@@ -203,7 +205,7 @@ assume On-Demand pricing (savings are available through Reserved Instances). Cal
| Dependencies | Version | Instance type | Nodes |
| ------------ | ----------------------- | --------------- | ----- |
-| Redis | 6 | m6g.large | 3 |
+| Redis | 6 | cache.m6g.large | 3 |
| MySQL | 8.0.mysql_aurora.3.04.2 | db.r6g.16xlarge | 2 |
AWS reference architecture can be found [here](https://github.com/fleetdm/fleet/tree/main/terraform/example). This configuration includes:
@@ -277,7 +279,7 @@ The following permissions are the minimum required to apply AWS terraform resour
GCP reference architecture can be found in [the Fleet repository](https://github.com/fleetdm/fleet/tree/main/infrastructure/dogfood/terraform/gcp). This configuration includes:
- Cloud Run (Fleet backend)
-- Cloud SQL MySQL 5.7 (Fleet database)
+- Cloud SQL MySQL 8.0 (Fleet database)
- Memorystore Redis (Fleet cache & live query orchestrator)
GCP support for add/install software and file carve features is coming soon. Get [commmunity support](https://chat.osquery.io/c/fleet).
diff --git a/docs/Deploy/deploy-fleet.md b/docs/Deploy/deploy-fleet.md
index c0e80f4b2e88..ee5299bfc378 100644
--- a/docs/Deploy/deploy-fleet.md
+++ b/docs/Deploy/deploy-fleet.md
@@ -43,7 +43,7 @@ Render is a cloud hosting service that makes it easy to get up and running fast,
-1. Click "Deploy to Render" to open the Fleet Blueprint on Render. You will be prompted to create or log in to your Render account with associated payment information.
+1. Click "Deploy to Render" to open the Fleet Blueprint on Render. Ensure that the Redis instance is manually set to the same region as your other resources. You will be prompted to create or log in to your Render account with associated payment information.
2. Give the Blueprint a unique name like `yourcompany-fleet`.
diff --git a/docs/Get started/FAQ.md b/docs/Get started/FAQ.md
index b6e04cd9699f..f06d7c550230 100644
--- a/docs/Get started/FAQ.md
+++ b/docs/Get started/FAQ.md
@@ -10,11 +10,11 @@ Fleet is simple enough to [spin up for yourself](https://fleetdm.com/docs/deploy
## What is the easiest way to deploy Fleet?
-Fleet provides a standard [Terraform module](https://fleetdm.com/docs/deploy/deploy-on-aws-with-terraform) that deploys Fleet with best practices, along with [cloud cost calculators and reference architectures](https://fleetdm.com/docs/deploy/reference-architectures#cloud-providers) used by some of Fleet’s largest customers with tens and hundreds of thousands of hosts. Fleet Premium customers can also opt for managed hosting provided by Fleet. You can also deploy Fleet anywhere you want.
+Take a look at [Deploying Fleet](https://fleetdm.com/docs/deploy/deploy-fleet) for guides on Render or deploy for scale with AWS Terraform. You can also deploy Fleet anywhere you want.
-You can enroll servers and laptops using a simple installer or automatically deliver Fleet's agent (fleetd) using your existing tools, such as Chef, Terraform, Munki/autopkg, Ansible, Puppet, Jamf, Intune, etc.
+You can enroll servers and laptops using a simple installer or automatically deliver Fleet's agent (fleetd) using your existing tools, such as Chef, Terraform, Munki/autopkg, Ansible, Puppet, Jamf, Intune, etc. As of [Fleet v4.53.0](https://fleetdm.com/releases/fleet-4.53.0), when Fleet's MDM is enabled for MacOS, fleetd is installed as part of the bootstrap.
-By default, Fleet keeps fleetd up to date automatically. For self-managed instances, Fleet provides a [migration runner](https://fleetdm.com/docs/deploy/upgrading-fleet#upgrading-fleet).
+By default, Fleet keeps fleetd up to date automatically. For self-managed instances, Fleet provides a [Simple upgrade process](https://fleetdm.com/docs/deploy/upgrading-fleet#upgrading-fleet).
## What options do I have for access control? What about auditing admin activity?
@@ -45,6 +45,60 @@ When you collect data with Fleet, the [performance impact](https://fleetdm.com/r
You can test changes on a small subset of hosts first, then roll them out to the rest of your organization.
+## What browsers does Fleet supported?
+
+Fleet supports the latest, stable releases of all major browsers and platforms.
+
+We test each browser on Windows whenever possible, because our engineering team primarily uses macOS.
+
+**Note:** This information also applies to [fleetdm.com](https://www.fleetdm.com).
+
+### Desktop
+
+- Chrome
+- Firefox
+- Edge
+- Safari (macOS only)
+
+### Mobile
+
+- Mobile Safari on iOS
+- Mobile Chrome on Android
+
+### Note
+> - Mobile web is not yet supported in the Fleet product.
+> - The Fleet user interface [may not be fully supported](https://github.com/fleetdm/fleet/issues/969) in Google Chrome when the browser is running on ChromeOS.
+
+## What host operating systems does Fleet support?
+
+Fleet supports the following operating system versions on hosts.
+
+| OS | Supported version(s) |
+| :------ | :------------------------------------- |
+| macOS | 13+ (Ventura) |
+| Windows | Pro and Enterprise 10+, Server 2012+ |
+| Linux | CentOS 7.1+, Ubuntu 20.04+, Fedora 38+ |
+| ChromeOS | 112.0.5615.134+ |
+
+While Fleet may still function partially or fully with OS versions older than those above, Fleet does not actively test against unsupported versions and does not pursue bugs on them.
+
+## Some notes on compatibility
+
+### Tables
+Not all osquery tables are available for every OS. Please check out the [osquery schema](https://fleetdm.com/tables) for detailed information.
+
+If a table is not available for your host, Fleet will generally handle things behind the scenes for you.
+
+### Linux
+
+Fleet Desktop is supported on Ubuntu and Fedora.
+
+Fedora requires a [gnome extension](https://extensions.gnome.org/extension/615/appindicator-support/) and Google Chrome for Fleet Desktop.
+
+On Ubuntu, Fleet Desktop currently supports Xorg as X11 server, Wayland is currently not supported. Ubuntu 24.04 comes with Wayland enabled by default. To use X11 instead of Wayland you can set `WaylandEnable=false` in `/etc/gdm3/custom.conf` and reboot.
+
+The `fleetctl package` command is not supported on DISA-STIG distribution.
+
## Is Fleet MIT licensed?
Different portions of the Fleet software are licensed differently, as noted in the [LICENSE](https://github.com/fleetdm/fleet/blob/main/LICENSE) file. The majority of Fleet is MIT licensed. Paid features require a license key.
@@ -71,7 +125,7 @@ Different portions of the Fleet software are licensed differently, as noted in t
## How do I contact Fleet for support?
-A lot of questions can be answered [in the documentation](https://fleetdm.com/docs).
+A lot of questions can be answered [in the documentation](https://fleetdm.com/docs) or [guides](https://fleetdm.com/guides).
To get help from the community, visit https://fleetdm.com/support.
@@ -124,6 +178,8 @@ Run `sudo apt remove fleet-osquery -y`
Run `sudo rpm -e fleet-osquery-X.Y.Z.x86_64`
-| Name | Type | Description |
-| --------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| host_status_webhook | list | See [`webhook_settings.host_status_webhook`](#webhook-settings-host-status-webhook). |
-| failing_policies_webhook | list | See [`webhook_settings.failing_policies_webhook`](#webhook-settings-failing-policies-webhook). |
-| vulnerabilities_webhook | list | See [`webhook_settings.vulnerabilities_webhook`](#webhook-settings-vulnerabilities-webhook). |
-| activities_webhook | list | See [`webhook_settings.activities_webhook`](#webhook-settings-activities-webhook). |
+| Name | Type | Description |
+| --------------------- | ----- | ---------------------------------------------------------------------------------------------- |
+| host_status_webhook | array | See [`webhook_settings.host_status_webhook`](#webhook-settings-host-status-webhook). |
+| failing_policies_webhook | array | See [`webhook_settings.failing_policies_webhook`](#webhook-settings-failing-policies-webhook). |
+| vulnerabilities_webhook | array | See [`webhook_settings.vulnerabilities_webhook`](#webhook-settings-vulnerabilities-webhook). |
+| activities_webhook | array | See [`webhook_settings.activities_webhook`](#webhook-settings-activities-webhook). |
@@ -1513,11 +1530,11 @@ _Available in Fleet Premium._
`webhook_settings.host_status_webhook` is an object with the following structure:
| Name | Type | Description |
-| --------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| enable_host_status_webhook | boolean | Whether or not the host status webhook is enabled. |
-| destination_url | string | The URL to deliver the webhook request to. |
-| host_percentage | integer | The minimum percentage of hosts that must fail to check in to Fleet in order to trigger the webhook request. |
-| days_count | integer | The minimum number of days that the configured `host_percentage` must fail to check in to Fleet in order to trigger the webhook request. |
+| --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
+| enable_host_status_webhook | boolean | Whether or not the host status webhook is enabled. |
+| destination_url | string | The URL to deliver the webhook request to. |
+| host_percentage | integer | The minimum percentage of hosts that must fail to check in to Fleet in order to trigger the webhook request. |
+| days_count | integer | The minimum number of days that the configured `host_percentage` must fail to check in to Fleet in order to trigger the webhook request. |
@@ -1526,9 +1543,9 @@ _Available in Fleet Premium._
`webhook_settings.failing_policies_webhook` is an object with the following structure:
| Name | Type | Description |
-| --------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| enable_failing_policies_webhook | boolean | Whether or not the failing policies webhook is enabled. |
-| destination_url | string | The URL to deliver the webhook requests to. |
+| --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------- |
+| enable_failing_policies_webhook | boolean | Whether or not the failing policies webhook is enabled. |
+| destination_url | string | The URL to deliver the webhook requests to. |
| policy_ids | array | List of policy IDs to enable failing policies webhook. |
| host_batch_size | integer | Maximum number of hosts to batch on failing policy webhook requests. The default, 0, means no batching (all hosts failing a policy are sent on one request). |
@@ -1539,9 +1556,9 @@ _Available in Fleet Premium._
`webhook_settings.vulnerabilities_webhook` is an object with the following structure:
| Name | Type | Description |
-| --------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| enable_vulnerabilities_webhook | boolean | Whether or not the vulnerabilities webhook is enabled. |
-| destination_url | string | The URL to deliver the webhook requests to. |
+| --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| enable_vulnerabilities_webhook | boolean | Whether or not the vulnerabilities webhook is enabled. |
+| destination_url | string | The URL to deliver the webhook requests to. |
| host_batch_size | integer | Maximum number of hosts to batch on vulnerabilities webhook requests. The default, 0, means no batching (all vulnerable hosts are sent on one request). |
@@ -1551,9 +1568,9 @@ _Available in Fleet Premium._
`webhook_settings.activities_webhook` is an object with the following structure:
| Name | Type | Description |
-| --------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| enable_activities_webhook | boolean | Whether or not the activity feed webhook is enabled. |
-| destination_url | string | The URL to deliver the webhook requests to. |
+| --------------------- | ------- | --------------------------------------------------------- |
+| enable_activities_webhook | boolean | Whether or not the activity feed webhook is enabled. |
+| destination_url | string | The URL to deliver the webhook requests to. |
@@ -1595,11 +1612,11 @@ _Available in Fleet Premium._
+ [`integrations.google_calendar`](#integrations-google-calendar)
-->
-| Name | Type | Description |
-| --------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| jira | list | See [`integrations.jira`](#integrations-jira). |
-| zendesk | list | See [`integrations.zendesk`](#integrations-zendesk). |
-| google_calendar | list | See [`integrations.google_calendar`](#integrations-google-calendar). |
+| Name | Type | Description |
+| --------------------- | ----- | -------------------------------------------------------------------- |
+| jira | array | See [`integrations.jira`](#integrations-jira). |
+| zendesk | array | See [`integrations.zendesk`](#integrations-zendesk). |
+| google_calendar | array | See [`integrations.google_calendar`](#integrations-google-calendar). |
> Note that when making changes to the `integrations` object, all integrations must be provided (not just the one being modified). This is because the endpoint will consider missing integrations as deleted.
@@ -1641,8 +1658,8 @@ _Available in Fleet Premium._
`integrations.google_calendar` is an array of objects with the following structure:
| Name | Type | Description |
-| --------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| domain | string | The domain for the Google Workspace service account to be used for this calendar integration. |
+| --------------------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
+| domain | string | The domain for the Google Workspace service account to be used for this calendar integration. |
| api_key_json | object | The private key JSON downloaded when generating the service account API key to be used for this calendar integration. |
@@ -1679,8 +1696,11 @@ _Available in Fleet Premium._
| --------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| apple_bm_default_team | string | _Available in Fleet Premium._ The default team to use with Apple Business Manager. |
| windows_enabled_and_configured | boolean | Enables Windows MDM support. |
-| enable_disk_encryption | boolean | _Available in Fleet Premium._ Hosts that belong to no team and are enrolled into Fleet's MDM will have disk encryption enabled if set to true. |
+| enable_disk_encryption | boolean | _Available in Fleet Premium._ Hosts that belong to no team will have disk encryption enabled if set to true. |
| macos_updates | object | See [`mdm.macos_updates`](#mdm-macos-updates). |
+| ios_updates | object | See [`mdm.ios_updates`](#mdm-ios-updates). |
+| ipados_updates | object | See [`mdm.ipados_updates`](#mdm-ipados-updates). |
+| windows_updates | object | See [`mdm.window_updates`](#mdm-windows-updates). |
| macos_migration | object | See [`mdm.macos_migration`](#mdm-macos-migration). |
| macos_setup | object | See [`mdm.macos_setup`](#mdm-macos-setup). |
| macos_settings | object | See [`mdm.macos_settings`](#mdm-macos-settings). |
@@ -1696,7 +1716,33 @@ _Available in Fleet Premium._
| Name | Type | Description |
| --------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| minimum_version | string | Hosts that belong to no team and are enrolled into Fleet's MDM will be nudged until their macOS is at or above this version. |
+| minimum_version | string | Hosts that belong to no team will be nudged until their macOS is at or above this version. |
+| deadline | string | Hosts that belong to no team won't be able to dismiss the Nudge window once this deadline is past. |
+
+
+
+##### mdm.ios_updates
+
+_Available in Fleet Premium._
+
+`mdm.ios_updates` is an object with the following structure:
+
+| Name | Type | Description |
+| --------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| minimum_version | string | Hosts that belong to no team and are enrolled into Fleet's MDM will be nudged until their iOS is at or above this version. |
+| deadline | string | Hosts that belong to no team and are enrolled into Fleet's MDM won't be able to dismiss the Nudge window once this deadline is past. |
+
+
+
+##### mdm.ipados_updates
+
+_Available in Fleet Premium._
+
+`mdm.ipados_updates` is an object with the following structure:
+
+| Name | Type | Description |
+| --------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| minimum_version | string | Hosts that belong to no team and are enrolled into Fleet's MDM will be nudged until their iPadOS is at or above this version. |
| deadline | string | Hosts that belong to no team and are enrolled into Fleet's MDM won't be able to dismiss the Nudge window once this deadline is past. |
@@ -1709,8 +1755,8 @@ _Available in Fleet Premium._
| Name | Type | Description |
| --------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| deadline_days | integer | Hosts that belong to no team and are enrolled into Fleet's MDM will have this number of days before updates are installed on Windows. |
-| grace_period_days | integer | Hosts that belong to no team and are enrolled into Fleet's MDM will have this number of days before Windows restarts to install updates. |
+| deadline_days | integer | Hosts that belong to no team will have this number of days before updates are installed on Windows. |
+| grace_period_days | integer | Hosts that belong to no team will have this number of days before Windows restarts to install updates. |
@@ -1746,7 +1792,7 @@ _Available in Fleet Premium._
| Name | Type | Description |
| --------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| custom_settings | list | macOS hosts that belong to no team, and are members of specified labels will have custom profiles applied. |
+| custom_settings | array | macOS hosts that belong to no team will have custom profiles applied. |
@@ -1756,7 +1802,7 @@ _Available in Fleet Premium._
| Name | Type | Description |
| --------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| custom_settings | list | Windows hosts that belong to no team, and are members of specified labels will have custom profiles applied. |
+| custom_settings | array | Windows hosts that belong to no team will have custom profiles applied. |
@@ -2053,7 +2099,7 @@ Delete all of a team's existing enroll secrets
| email | string | body | **Required.** The email of the invited user. This email will receive the invitation link. |
| name | string | body | **Required.** The name of the invited user. |
| sso_enabled | boolean | body | **Required.** Whether or not SSO will be enabled for the invited user. |
-| teams | list | body | _Available in Fleet Premium_. A list of the teams the user is a member of. Each item includes the team's ID and the user's role in the specified team. |
+| teams | array | body | _Available in Fleet Premium_. A list of the teams the user is a member of. Each item includes the team's ID and the user's role in the specified team. |
#### Example
@@ -2253,7 +2299,7 @@ Verify the specified invite.
| email | string | body | The email of the invited user. Updates on the email won't resend the invitation. |
| name | string | body | The name of the invited user. |
| sso_enabled | boolean | body | Whether or not SSO will be enabled for the invited user. |
-| teams | list | body | _Available in Fleet Premium_. A list of the teams the user is a member of. Each item includes the team's ID and the user's role in the specified team. |
+| teams | array | body | _Available in Fleet Premium_. A list of the teams the user is a member of. Each item includes the team's ID and the user's role in the specified team. |
#### Example
@@ -2377,7 +2423,6 @@ None.
- [Get host OS version](#get-host-os-version)
- [Get host's scripts](#get-hosts-scripts)
- [Get host's software](#get-hosts-software)
-- [Install software](#install-software)
- [Get hosts report in CSV](#get-hosts-report-in-csv)
- [Get host's disk encryption key](#get-hosts-disk-encryption-key)
- [Lock host](#lock-host)
@@ -2444,6 +2489,7 @@ the `software` table.
| mdm_id | integer | query | The ID of the _mobile device management_ (MDM) solution to filter hosts by (that is, filter hosts that use a specific MDM provider and URL). |
| mdm_name | string | query | The name of the _mobile device management_ (MDM) solution to filter hosts by (that is, filter hosts that use a specific MDM provider). |
| mdm_enrollment_status | string | query | The _mobile device management_ (MDM) enrollment status to filter hosts by. Valid options are 'manual', 'automatic', 'enrolled', 'pending', or 'unenrolled'. |
+| connected_to_fleet | boolean | query | Filter hosts that are talking to this Fleet server for MDM features. In rare cases, hosts can be enrolled to one Fleet server but talk to a different Fleet server for MDM features. In this case, the value would be `false`. Always `false` for Linux hosts. |
| macos_settings | string | query | Filters the hosts by the status of the _mobile device management_ (MDM) profiles applied to 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.** |
| munki_issue_id | integer | query | The ID of the _munki issue_ (a Munki-reported error or warning message) to filter hosts by (that is, filter hosts that are affected by that corresponding error or warning message). |
| low_disk_space | integer | query | _Available in Fleet Premium_. Filters the hosts to only include hosts with less GB of disk space available than this value. Must be a number between 1-100. |
@@ -2790,6 +2836,18 @@ Returns the count of all hosts organized by status. `online_count` includes all
"name": "All Linux",
"description": "All Linux distributions",
"label_type": "builtin"
+ },
+ {
+ "id": 13,
+ "name": "iOS",
+ "description": "All iOS hosts",
+ "label_type": "builtin"
+ },
+ {
+ "id": 14,
+ "name": "iPadOS",
+ "description": "All iPadOS hosts",
+ "label_type": "builtin"
}
],
"platforms": [
@@ -2801,6 +2859,14 @@ Returns the count of all hosts organized by status. `online_count` includes all
"platform": "darwin",
"hosts_count": 1234
},
+ {
+ "platform": "ios",
+ "hosts_count": 1234
+ },
+ {
+ "platform": "ipados",
+ "hosts_count": 1234
+ },
{
"platform": "rhel",
"hosts_count": 1234
@@ -3029,11 +3095,16 @@ Returns the information of the specified host.
"coordinates": [40.6799, -74.0028]
}
},
+ "maintenance_window": {
+ "starts_at": "2024-06-18T13:27:18−04:00",
+ "timezone": "America/New_York"
+ },
"mdm": {
- "encryption_key_available": false,
- "enrollment_status": null,
- "name": "",
- "server_url": null,
+ "encryption_key_available": true,
+ "enrollment_status": "On (manual)",
+ "name": "Fleet",
+ "connected_to_fleet": true,
+ "server_url": "https://acme.com/mdm/apple/mdm",
"device_status": "unlocked",
"pending_action": "",
"macos_settings": {
@@ -3077,18 +3148,18 @@ Returns the information of the specified host.
### Get host by identifier
-Returns the information of the host specified using the `uuid`, `hardware_serial`, `osquery_host_id`, `hostname`, or
-`node_key` as an identifier.
+Returns the information of the host specified using the `hostname`, `uuid`, or `hardware_serial` as an identifier.
-If `hostname` is specified when there is more than one host with the same hostname, the endpoint returns the first matching host. In Fleet, hostnames are fully qualified domain names (FQDNs).
+If `hostname` is specified when there is more than one host with the same hostname, the endpoint returns the first matching host. In Fleet, hostnames are fully qualified domain names (FQDNs). `hostname` (e.g. johns-macbook-air.local) is not the same as `display_name` (e.g. John's MacBook Air).
`GET /api/v1/fleet/hosts/identifier/:identifier`
#### Parameters
-| Name | Type | In | Description |
-| ---------- | ----------------- | ---- | ----------------------------------------------------------------------------- |
-| identifier | integer or string | path | **Required**. The host's `hardware_serial`, `uuid`, `osquery_host_id`, `hostname`, or `node_key` |
+| Name | Type | In | Description |
+| ---------- | ----------------- | ---- | ------------------------------------------------------------------ |
+| identifier | string | path | **Required**. The host's `hostname`, `uuid`, or `hardware_serial`. |
+| exclude_software | boolean | query | If `true`, the response will not include a list of installed software for the host. |
#### Example
@@ -3449,10 +3520,11 @@ This is the API route used by the **My device** page in Fleet desktop to display
}
],
"mdm": {
- "encryption_key_available": false,
- "enrollment_status": null,
- "name": "",
- "server_url": null,
+ "encryption_key_available": true,
+ "enrollment_status": "On (manual)",
+ "name": "Fleet",
+ "connected_to_fleet": true,
+ "server_url": "https://acme.com/mdm/apple/mdm",
"macos_settings": {
"disk_encryption": null,
"action_required": null
@@ -3479,6 +3551,7 @@ This is the API route used by the **My device** page in Fleet desktop to display
]
}
},
+ "self_service": true,
"org_logo_url": "https://example.com/logo.jpg",
"license": {
"tier": "free",
@@ -3626,7 +3699,7 @@ _Available in Fleet Premium_
| Name | Type | In | Description |
| ------- | ------- | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| ids | list | body | A list of the host IDs you'd like to delete. If `ids` is specified, `filters` cannot be specified. |
+| ids | array | body | A list of the host IDs you'd like to delete. If `ids` is specified, `filters` cannot be specified. |
| filters | object | body | Contains any of the following four properties: `query` for search query keywords. Searchable fields include `hostname`, `hardware_serial`, `uuid`, and `ipv4`. `status` to indicate the status of the hosts to return. Can either be `new`, `online`, `offline`, `mia` or `missing`. `label_id` to indicate the selected label. `team_id` to indicate the selected team. If `filters` is specified, `id` cannot be specified. `label_id` and `status` cannot be used at the same time. |
Either ids or filters are required.
@@ -4081,139 +4154,6 @@ Resends a configuration profile for the specified host.
`Status: 202`
-
-### List host OS versions
-
-Retrieves the aggregated host OS versions information.
-
-`GET /api/v1/fleet/os_versions`
-
-#### Parameters
-
-| Name | Type | In | Description |
-| --- | --- | --- | --- |
-| team_id | integer | query | _Available in Fleet Premium_. Filters response data to the specified team. |
-| platform | string | query | Filters the hosts to the specified platform |
-| os_name | string | query | The name of the operating system to filter hosts by. `os_version` must also be specified with `os_name` |
-| os_version | string | query | The version of the operating system to filter hosts by. `os_name` must also be specified with `os_version` |
-| page | integer | query | Page number of the results to fetch. |
-| per_page | integer | query | Results per page. |
-| order_key | string | query | What to order results by. Allowed fields are: `hosts_count`. Default is `hosts_count` (descending). |
-| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include `asc` and `desc`. Default is `asc`. |
-
-
-##### Default response
-
-`Status: 200`
-
-```json
-{
- "count": 1
- "counts_updated_at": "2023-12-06T22:17:30Z",
- "os_versions": [
- {
- "os_version_id": 123,
- "hosts_count": 21,
- "name": "Microsoft Windows 11 Pro 23H2 10.0.22621.1234",
- "name_only": "Microsoft Windows 11 Pro 23H2",
- "version": "10.0.22621.1234",
- "platform": "windows",
- "generated_cpes": [],
- "vulnerabilities": [
- {
- "cve": "CVE-2022-30190",
- "details_link": "https://nvd.nist.gov/vuln/detail/CVE-2022-30190",
- "cvss_score": 7.8,// Available in Fleet Premium
- "epss_probability": 0.9729,// Available in Fleet Premium
- "cisa_known_exploit": false,// Available in Fleet Premium
- "cve_published": "2022-06-01T00:15:00Z",// Available in Fleet Premium
- "cve_description": "Microsoft Windows Support Diagnostic Tool (MSDT) Remote Code Execution Vulnerability.",// Available in Fleet Premium
- "resolved_in_version": ""// Available in Fleet Premium
- }
- ]
- }
- ],
- "meta": {
- "has_next_results": false,
- "has_previous_results": false
- }
-}
-```
-
-OS vulnerability data is currently available for Windows and macOS. For other platforms, `vulnerabilities` will be an empty array:
-
-```json
-{
- "hosts_count": 1,
- "name": "CentOS Linux 7.9.2009",
- "name_only": "CentOS",
- "version": "7.9.2009",
- "platform": "rhel",
- "generated_cpes": [],
- "vulnerabilities": []
-}
-```
-
-### Get host OS version
-
-Retrieves information about the specified OS version.
-
-`GET /api/v1/fleet/os_versions/:id`
-
-#### Parameters
-
-| Name | Type | In | Description |
-| ---- | ---- | -- | ----------- |
-| id | integer | path | **Required.** The OS version's ID. |
-
-##### Default response
-
-`Status: 200`
-
-```json
-{
- "counts_updated_at": "2023-12-06T22:17:30Z",
- "os_version": {
- "id": 123,
- "hosts_count": 21,
- "name": "Microsoft Windows 11 Pro 23H2 10.0.22621.1234",
- "name_only": "Microsoft Windows 11 Pro 23H2",
- "version": "10.0.22621.1234",
- "platform": "windows",
- "generated_cpes": [],
- "vulnerabilities": [
- {
- "cve": "CVE-2022-30190",
- "details_link": "https://nvd.nist.gov/vuln/detail/CVE-2022-30190",
- "created_at": "2024-07-01T00:15:00Z",
- "cvss_score": 7.8,// Available in Fleet Premium
- "epss_probability": 0.9729,// Available in Fleet Premium
- "cisa_known_exploit": false,// Available in Fleet Premium
- "cve_published": "2022-06-01T00:15:00Z",// Available in Fleet Premium
- "cve_description": "Microsoft Windows Support Diagnostic Tool (MSDT) Remote Code Execution Vulnerability.",// Available in Fleet Premium
- "resolved_in_version": ""// Available in Fleet Premium
- }
- ]
- }
-}
-```
-
-OS vulnerability data is currently available for Windows and macOS. For other platforms, `vulnerabilities` will be an empty array:
-
-```json
-{
- "id": 321,
- "hosts_count": 1,
- "name": "CentOS Linux 7.9.2009",
- "name_only": "CentOS",
- "version": "7.9.2009",
- "platform": "rhel",
- "generated_cpes": [],
- "vulnerabilities": []
-}
-```
-
-
### Get host's scripts
`GET /api/v1/fleet/hosts/:id/scripts`
@@ -4235,45 +4175,46 @@ OS vulnerability data is currently available for Windows and macOS. For other pl
`Status: 200`
```json
- "scripts": [
- {
- "script_id": 3,
- "name": "remove-zoom-artifacts.sh",
- "last_execution": {
- "execution_id": "e797d6c6-3aae-11ee-be56-0242ac120002",
- "executed_at": "2021-12-15T15:23:57Z",
- "status": "error"
- }
- },
- {
- "script_id": 5,
- "name": "set-timezone.sh",
- "last_execution": {
- "id": "e797d6c6-3aae-11ee-be56-0242ac120002",
- "executed_at": "2021-12-15T15:23:57Z",
- "status": "pending"
- }
- },
- {
- "script_id": 8,
- "name": "uninstall-zoom.sh",
- "last_execution": {
- "id": "e797d6c6-3aae-11ee-be56-0242ac120002",
- "executed_at": "2021-12-15T15:23:57Z",
- "status": "ran"
- }
+"scripts": [
+ {
+ "script_id": 3,
+ "name": "remove-zoom-artifacts.sh",
+ "last_execution": {
+ "execution_id": "e797d6c6-3aae-11ee-be56-0242ac120002",
+ "executed_at": "2021-12-15T15:23:57Z",
+ "status": "error"
+ }
+ },
+ {
+ "script_id": 5,
+ "name": "set-timezone.sh",
+ "last_execution": {
+ "id": "e797d6c6-3aae-11ee-be56-0242ac120002",
+ "executed_at": "2021-12-15T15:23:57Z",
+ "status": "pending"
+ }
+ },
+ {
+ "script_id": 8,
+ "name": "uninstall-zoom.sh",
+ "last_execution": {
+ "id": "e797d6c6-3aae-11ee-be56-0242ac120002",
+ "executed_at": "2021-12-15T15:23:57Z",
+ "status": "ran"
}
- ],
- "meta": {
- "has_next_results": false,
- "has_previous_results": false
}
+],
+"meta": {
+ "has_next_results": false,
+ "has_previous_results": false
}
```
### Get host's software
+> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
+
`GET /api/v1/fleet/hosts/:id/software`
#### Parameters
@@ -4282,6 +4223,7 @@ OS vulnerability data is currently available for Windows and macOS. For other pl
| ---- | ------- | ---- | ---------------------------- |
| id | integer | path | **Required**. The host's ID. |
| query | string | query | Search query keywords. Searchable fields include `name`. |
+| available_for_install | boolean | query | If `true` or `1`, only list software that is available for install (added by the user). Default is `false`.
| page | integer | query | Page number of the results to fetch.|
| per_page | integer | query | Results per page.|
@@ -4300,16 +4242,20 @@ OS vulnerability data is currently available for Windows and macOS. For other pl
{
"id": 121,
"name": "Google Chrome.app",
- "package_available_for_install": "GoogleChrome.pkg",
- "self_service": true,
+ "software_package": {
+ "name": "GoogleChrome.pkg",
+ "version": "125.12.0.3",
+ "self_service": true,
+ "last_install": {
+ "install_uuid": "8bbb8ac2-b254-4387-8cba-4d8a0407368b",
+ "installed_at": "2024-05-15T15:23:57Z"
+ },
+ },
+ "app_store_app": null
"source": "apps",
"status": "failed",
- "last_install": {
- "install_uuid": "8bbb8ac2-b254-4387-8cba-4d8a0407368b",
- "installed_at": "2024-05-15T15:23:57Z"
- },
"installed_versions": [
- {
+ {
"version": "121.0",
"last_opened_at": "2024-04-01T23:03:07Z",
"vulnerabilities": ["CVE-2023-1234","CVE-2023-4321","CVE-2023-7654"],
@@ -4320,32 +4266,36 @@ OS vulnerability data is currently available for Windows and macOS. For other pl
{
"id": 134,
"name": "Falcon.app",
- "package_available_for_install": "FalconSensor-6.44.pkg",
- "self_service": false,
+ "software_package": {
+ "name": "FalconSensor-6.44.pkg"
+ "self_service": false,
+ "last_install": null
+ },
+ "app_store_app": null
"source": "",
"status": null,
- "last_install": null,
"installed_versions": [],
},
{
"id": 147,
- "name": "Firefox.app",
+ "name": "Logic Pro",
+ "software_package": null
+ "app_store_app": {
+ "app_store_id": "1091189122"
+ "version": "2.04",
+ "last_install": {
+ "command_uuid": "0aa14ae5-58fe-491a-ac9a-e4ee2b3aac40",
+ "installed_at": "2024-05-15T15:23:57Z"
+ },
+ },
"source": "apps",
- "bundle_identifier": "org.mozilla.firefox",
- "status": null,
- "last_install": null,
+ "status": "installed",
"installed_versions": [
{
"version": "118.0",
"last_opened_at": "2024-04-01T23:03:07Z",
"vulnerabilities": ["CVE-2023-1234"],
- "installed_paths": ["/Applications/Firefox.app"]
- },
- {
- "version": "119.0",
- "last_opened_at": "2024-04-01T23:03:07Z",
- "vulnerabilities": ["CVE-2023-4321","CVE-2023-7654"],
- "installed_paths": ["/Downloads/Firefox.app"]
+ "installed_paths": ["/Applications/Logic Pro.app"]
}
]
},
@@ -4357,29 +4307,6 @@ OS vulnerability data is currently available for Windows and macOS. For other pl
}
```
-### Install software
-
-_Available in Fleet Premium._
-
-Install software on a macOS, Windows, or Linux (Ubuntu) host. Software title must have `software_package` added to be installed.
-
-`POST /api/v1/fleet/hosts/:id/software/install/:software_title_id`
-
-#### Parameters
-
-| Name | Type | In | Description |
-| --------- | ---------- | ---- | -------------------------------------------- |
-| id | integer | path | **Required**. The host's ID. |
-| software_title_id | integer | path | **Required**. The software title's ID. |
-
-#### Example
-
-`POST /api/v1/fleet/hosts/123/software/install/3435`
-
-##### Default response
-
-`Status: 202`
-
### Get hosts report in CSV
Returns the list of hosts corresponding to the search criteria in CSV format, ready for download when
@@ -4396,8 +4323,8 @@ requested by a web browser.
| order_key | string | query | What to order results by. Can be any column in the hosts table. |
| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include 'asc' and 'desc'. Default is 'asc'. |
| status | string | query | Indicates the status of the hosts to return. Can either be 'new', 'online', 'offline', 'mia' or 'missing'. |
-| query | string | query | Search query keywords. Searchable fields include `hostname`, `hardware_serial`, `uuid`, `ipv4` and the hosts' email addresses (only searched if the query looks like an email address, i.e. contains an `@`, no space, etc.). |
-| team_id | integer | query | _Available in Fleet Premium_. Filters the hosts to only include hosts in the specified team. |
+| query | string | query | Search query keywords. Searchable fields include `hostname`, `hardware_serial`, `uuid`, `ipv4` and the hosts' email addresses (only searched if the query looks like an email address, i.e. contains an `@`, no space, etc.). |
+| team_id | integer | query | _Available in Fleet Premium_. Filters the hosts to only include hosts in the specified team. |
| policy_id | integer | query | The ID of the policy to filter hosts by. |
| policy_response | string | query | **Requires `policy_id`**. Valid options are 'passing' or 'failing'. **Note: If `policy_id` is specified _without_ including `policy_response`, this will also return hosts where the policy is not configured to run or failed to run.** |
| software_version_id | integer | query | The ID of the software version to filter hosts by. |
@@ -4407,11 +4334,11 @@ requested by a web browser.
| os_version | string | query | The version of the operating system to filter hosts by. `os_name` must also be specified with `os_version` |
| vulnerability | string | query | The cve to filter hosts by (including "cve-" prefix, case-insensitive). |
| mdm_id | integer | query | The ID of the _mobile device management_ (MDM) solution to filter hosts by (that is, filter hosts that use a specific MDM provider and URL). |
-| mdm_name | string | query | The name of the _mobile device management_ (MDM) solution to filter hosts by (that is, filter hosts that use a specific MDM provider). |
-| mdm_enrollment_status | string | query | The _mobile device management_ (MDM) enrollment status to filter hosts by. Valid options are 'manual', 'automatic', 'enrolled', 'pending', or 'unenrolled'. |
+| mdm_name | string | query | The name of the _mobile device management_ (MDM) solution to filter hosts by (that is, filter hosts that use a specific MDM provider). |
+| mdm_enrollment_status | string | query | The _mobile device management_ (MDM) enrollment status to filter hosts by. Valid options are 'manual', 'automatic', 'enrolled', 'pending', or 'unenrolled'. |
| macos_settings | string | query | Filters the hosts by the status of the _mobile device management_ (MDM) profiles applied to 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.** |
| munki_issue_id | integer | query | The ID of the _munki issue_ (a Munki-reported error or warning message) to filter hosts by (that is, filter hosts that are affected by that corresponding error or warning message). |
-| low_disk_space | integer | query | _Available in Fleet Premium_. Filters the hosts to only include hosts with less GB of disk space available than this value. Must be a number between 1-100. |
+| low_disk_space | integer | query | _Available in Fleet Premium_. Filters the hosts to only include hosts with less GB of disk space available than this value. Must be a number between 1-100. |
| label_id | integer | query | A valid label ID. Can only be used in combination with `order_key`, `order_direction`, `status`, `query` and `team_id`. |
| 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'. **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.** |
| disable_failing_policies | boolean | query | If `true`, hosts will return failing policies as 0 (returned as the `issues` column) regardless of whether there are any that failed for the host. This is meant to be used when increased performance is needed in exchange for the extra information. |
@@ -4583,9 +4510,9 @@ To unlock a Windows or Linux host, the host must have [scripts enabled](https://
### Wipe host
-Sends a command to wipe the specified macOS, Windows, or Linux host. The host is wiped once it comes online.
+Sends a command to wipe the specified macOS, iOS, iPadOS, Windows, or Linux host. The host is wiped once it comes online.
-To wipe a macOS or Windows host, the host must have MDM turned on. To lock a Linux host, the host must have [scripts enabled](https://fleetdm.com/docs/using-fleet/scripts).
+To wipe a macOS, iOS, iPadOS, or Windows host, the host must have MDM turned on. To lock a Linux host, the host must have [scripts enabled](https://fleetdm.com/docs/using-fleet/scripts).
`POST /api/v1/fleet/hosts/:id/wipe`
@@ -4689,7 +4616,7 @@ To wipe a macOS or Windows host, the host must have MDM turned on. To lock a Lin
```json
{
- "count": 2,
+ "count": 3,
"activities": [
{
"created_at": "2023-07-27T14:35:08Z",
@@ -4733,15 +4660,15 @@ To wipe a macOS or Windows host, the host must have MDM turned on. To lock a Lin
### Add labels to host
-Adds manual labels to a host.
+Adds manual labels to a host.
`POST /api/v1/fleet/hosts/:id/labels`
#### Parameters
-| Name | Type | In | Description |
-| ---- | ------- | ---- | ---------------------------- |
-| labels | list | body | The list of label names to add to the host. |
+| Name | Type | In | Description |
+| ------ | ------- | ---- | ---------------------------- |
+| labels | array | body | The list of label names to add to the host. |
#### Example
@@ -4768,9 +4695,9 @@ Removes manual labels from a host.
#### Parameters
-| Name | Type | In | Description |
-| ---- | ------- | ---- | ---------------------------- |
-| labels | list | body | The list of label names to delete from the host. |
+| Name | Type | In | Description |
+| ------ | ------- | ---- | ---------------------------- |
+| labels | array | body | The list of label names to delete from the host. |
#### Example
@@ -4802,8 +4729,8 @@ The live query will stop if the targeted host is offline, or if the query times
| Name | Type | In | Description |
|-----------|-------|------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| id | integer | path | **Required**. The target host ID. |
-| query | string | body | **Required**. The query SQL. |
+| id | integer | path | **Required**. The target host ID. |
+| query | string | body | **Required**. The query SQL. |
#### Example
@@ -5365,8 +5292,8 @@ Add a configuration profile to enforce custom settings on macOS and Windows host
| ------------------------- | -------- | ---- | ------------------------------------------------------------------------------------------------------------- |
| profile | file | form | **Required.** The .mobileconfig and JSON for macOS or XML for Windows file containing the profile. |
| team_id | string | form | _Available in Fleet Premium_. The team ID for the profile. If specified, the profile is applied to only hosts that are assigned to the specified team. If not specified, the profile is applied to only to hosts that are not assigned to any team. |
-| labels | array | form | _Available in Fleet Premium_. An array of labels to filter hosts in a team (or no team) that should get a profile. |
-
+| labels_include_all | array | form | _Available in Fleet Premium_. Profile will only be applied to hosts that have all of these labels. Only one of either `labels_include_all` or `labels_exclude_any` can be included in the request. |
+| labels_exclude_any | array | form | _Available in Fleet Premium_. Profile will be applied to hosts that don’t have any of these labels. Only one of either `labels_include_all` or `labels_exclude_any` can be included in the request. |
#### Example
@@ -5391,11 +5318,11 @@ Content-Disposition: form-data; name="team_id"
1
--------------------------f02md47480und42y
-Content-Disposition: form-data; name="labels"
+Content-Disposition: form-data; name="labels_include_all"
Label name 1
--------------------------f02md47480und42y
-Content-Disposition: form-data; name="labels"
+Content-Disposition: form-data; name="labels_include_all"
Label name 2
--------------------------f02md47480und42y
@@ -5481,9 +5408,10 @@ List all configuration profiles for macOS and Windows hosts enrolled to Fleet's
"created_at": "2023-03-31T00:00:00Z",
"updated_at": "2023-03-31T00:00:00Z",
"checksum": "dGVzdAo=",
- "labels": [
+ "labels_exclude_any": [
{
- "name": "Label name 1"
+ "name": "Label name 1",
+ "id": 1
}
]
},
@@ -5495,14 +5423,15 @@ List all configuration profiles for macOS and Windows hosts enrolled to Fleet's
"created_at": "2023-04-31T00:00:00Z",
"updated_at": "2023-04-31T00:00:00Z",
"checksum": "aCLemVr)",
- "labels": [
- {
- "name": "Label name 1",
- "broken": true
- },
- {
- "name": "Label name 2"
- }
+ "labels_include_all": [
+ {
+ "name": "Label name 2",
+ "broken": true,
+ },
+ {
+ "name": "Label name 3",
+ "id": 3
+ }
]
}
],
@@ -5546,13 +5475,15 @@ If one or more assigned labels are deleted the profile is considered broken (`br
"created_at": "2023-03-31T00:00:00Z",
"updated_at": "2023-03-31T00:00:00Z",
"checksum": "dGVzdAo=",
- "labels": [
+ "labels_include_all": [
{
"name": "Label name 1",
+ "id": 1
"broken": true
},
{
"name": "Label name 2",
+ "id": 2
}
]
}
@@ -6186,12 +6117,12 @@ Body:
## Commands
-- [Run custom MDM command](#run-custom-mdm-command)
-- [Get custom MDM command results](#get-custom-mdm-command-results)
-- [List custom MDM commands](#list-custom-mdm-commands)
+- [Run MDM command](#run-mdm-command)
+- [Get MDM command results](#get-mdm-command-results)
+- [List MDM commands](#list-mdm-commands)
-### Run custom MDM command
+### Run MDM command
> `POST /api/v1/fleet/mdm/apple/enqueue` API endpoint is deprecated as of Fleet 4.40. It is maintained for backward compatibility. Please use the new API endpoint below. See old API endpoint docs [here](https://github.com/fleetdm/fleet/blob/fleet-v4.39.0/docs/REST%20API/rest-api.md#run-custom-mdm-command).
@@ -6224,12 +6155,22 @@ Note that the `EraseDevice` and `DeviceLock` commands are _available in Fleet Pr
```
-### Get custom MDM command results
+### Get MDM command results
> `GET /api/v1/fleet/mdm/apple/commandresults` API endpoint is deprecated as of Fleet 4.40. It is maintained for backward compatibility. Please use the new API endpoint below. See old API endpoint docs [here](https://github.com/fleetdm/fleet/blob/fleet-v4.39.0/docs/REST%20API/rest-api.md#get-custom-mdm-command-results).
This endpoint returns the results for a specific custom MDM command.
+In the reponse, the possible `status` values for macOS, iOS, and iPadOS hosts are the following:
+
+* Pending: the command has yet to run on the host. The host will run the command the next time it comes online.
+* NotNow: the host responded with "NotNow" status via the MDM protocol: the host received the command, but couldn’t execute it. The host will try to run the command the next time it comes online.
+* Acknowledged: the host responded with "Acknowledged" status via the MDM protocol: the host processed the command successfully.
+* Error: the host responded with "Error" status via the MDM protocol: an error occurred. Run the `fleetctl get mdm-command-results --id= Note: If the server has not yet received a result for a command, it will return an empty object (`{}`).
-
-### List custom MDM commands
+### List MDM commands
> `GET /api/v1/fleet/mdm/apple/commands` API endpoint is deprecated as of Fleet 4.40. It is maintained for backward compatibility. Please use the new API endpoint below. See old API endpoint docs [here](https://github.com/fleetdm/fleet/blob/fleet-v4.39.0/docs/REST%20API/rest-api.md#list-custom-mdm-commands).
@@ -6282,6 +6222,8 @@ This endpoint returns the list of custom MDM commands that have been executed.
| per_page | integer | query | Results per page. |
| order_key | string | query | What to order results by. Can be any field listed in the `results` array example below. |
| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include `asc` and `desc`. Default is `asc`. |
+| host_identifier | string | query | The host's `hostname`, `uuid`, or `hardware_serial`. |
+| request_type | string | query | The request type to filter commands by. |
#### Example
@@ -6320,6 +6262,7 @@ This endpoint returns the list of custom MDM commands that have been executed.
- [Get Apple Push Notification service (APNs)](#get-apple-push-notification-service-apns)
- [Get Apple Business Manager (ABM)](#get-apple-business-manager-abm)
+- [Get Volume Purchasing Program (VPP)](#get-volume-purchasing-program-vpp)
### Get Apple Push Notification service (APNs)
@@ -6374,6 +6317,30 @@ None.
}
```
+Get Volume Purchasing Program (VPP)
+
+> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
+
+_Available in Fleet Premium_
+
+`GET /api/v1/fleet/vpp`
+
+#### Example
+
+`GET /api/v1/fleet/vpp`
+
+##### Default response
+
+`Status: 200`
+
+```json
+{
+ "org_name": "Acme Inc.",
+ "renew_date": "2023-11-29T00:00:00Z",
+ "location": "Acme Inc. Main Address"
+}
+```
+
---
## Policies
@@ -6594,7 +6561,7 @@ For example, a policy might ask “Is Gatekeeper enabled on macOS devices?“ Th
| Name | Type | In | Description |
| -------- | ------- | ---- | ------------------------------------------------- |
-| ids | list | body | **Required.** The IDs of the policies to delete. |
+| ids | array | body | **Required.** The IDs of the policies to delete. |
#### Example
@@ -6688,8 +6655,8 @@ Triggers [automations](https://fleetdm.com/docs/using-fleet/automations#policy-a
| Name | Type | In | Description |
| ---------- | -------- | ---- | -------------------------------------------------------- |
-| policy_ids | list | body | Filters to only run policy automations for the specified policies. |
-| team_ids | list | body | _Available in Fleet Premium_. Filters to only run policy automations for hosts in the specified teams. |
+| policy_ids | array | body | Filters to only run policy automations for the specified policies. |
+| team_ids | array | body | _Available in Fleet Premium_. Filters to only run policy automations for hosts in the specified teams. |
#### Example
@@ -7029,7 +6996,7 @@ Either `query` or `query_id` must be provided.
| Name | Type | In | Description |
| -------- | ------- | ---- | ------------------------------------------------- |
| team_id | integer | path | **Required.** Defines what team ID to operate on |
-| ids | list | body | **Required.** The IDs of the policies to delete. |
+| ids | array | body | **Required.** The IDs of the policies to delete. |
#### Example
@@ -7325,6 +7292,7 @@ Returns the query report specified by ID.
```json
{
"query_id": 31,
+ "report_clipped": false,
"results": [
{
"host_id": 1,
@@ -7465,14 +7433,14 @@ Creates a global query or team query.
| name | string | body | **Required**. The name of the query. |
| query | string | body | **Required**. The query in SQL syntax. |
| description | string | body | The query's description. |
-| observer_can_run | bool | body | Whether or not users with the `observer` role can run the query. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). This field is only relevant for the `observer` role. The `observer_plus` role can run any query and is not limited by this flag (`observer_plus` role was added in Fleet 4.30.0). |
+| observer_can_run | boolean | body | Whether or not users with the `observer` role can run the query. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). This field is only relevant for the `observer` role. The `observer_plus` role can run any query and is not limited by this flag (`observer_plus` role was added in Fleet 4.30.0). |
| team_id | integer | body | _Available in Fleet Premium_. The parent team to which the new query should be added. If omitted, the query will be global. |
-| interval | integer | body | The amount of time, in seconds, the query waits before running. Can be set to `0` to never run. Default: 0. |
+| interval | integer | body | The amount of time, in seconds, the query waits before running. Can be set to `0` to never run. Default: 0. |
| platform | string | body | The OS platforms where this query will run (other platforms ignored). Comma-separated string. If omitted, runs on all compatible platforms. |
| min_osquery_version | string | body | The minimum required osqueryd version installed on a host. If omitted, all osqueryd versions are acceptable. |
| automations_enabled | boolean | body | Whether to send data to the configured log destination according to the query's `interval`. |
-| logging | string | body | The type of log output for this query. Valid values: `"snapshot"`(default), `"differential"`, or `"differential_ignore_removals"`. |
-| discard_data | bool | body | Whether to skip saving the latest query results for each host. Default: `false`. |
+| logging | string | body | The type of log output for this query. Valid values: `"snapshot"`(default), `"differential"`, or `"differential_ignore_removals"`. |
+| discard_data | boolean | body | Whether to skip saving the latest query results for each host. Default: `false`. |
#### Example
@@ -7539,13 +7507,13 @@ Modifies the query specified by ID.
| name | string | body | The name of the query. |
| query | string | body | The query in SQL syntax. |
| description | string | body | The query's description. |
-| observer_can_run | bool | body | Whether or not users with the `observer` role can run the query. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). This field is only relevant for the `observer` role. The `observer_plus` role can run any query and is not limited by this flag (`observer_plus` role was added in Fleet 4.30.0). |
+| observer_can_run | boolean | body | Whether or not users with the `observer` role can run the query. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). This field is only relevant for the `observer` role. The `observer_plus` role can run any query and is not limited by this flag (`observer_plus` role was added in Fleet 4.30.0). |
| interval | integer | body | The amount of time, in seconds, the query waits before running. Can be set to `0` to never run. Default: 0. |
| platform | string | body | The OS platforms where this query will run (other platforms ignored). Comma-separated string. If set to "", runs on all compatible platforms. |
| min_osquery_version | string | body | The minimum required osqueryd version installed on a host. If omitted, all osqueryd versions are acceptable. |
| automations_enabled | boolean | body | Whether to send data to the configured log destination according to the query's `interval`. |
| logging | string | body | The type of log output for this query. Valid values: `"snapshot"`(default), `"differential"`, or `"differential_ignore_removals"`. |
-| discard_data | bool | body | Whether to skip saving the latest query results for each host. |
+| discard_data | boolean | body | Whether to skip saving the latest query results for each host. |
> Note that any of the following conditions will cause the existing query report to be deleted:
> - Updating the `query` (SQL) field
@@ -7649,9 +7617,9 @@ Deletes the queries specified by ID. Returns the count of queries successfully d
#### Parameters
-| Name | Type | In | Description |
-| ---- | ---- | ---- | ------------------------------------- |
-| ids | list | body | **Required.** The IDs of the queries. |
+| Name | Type | In | Description |
+| ---- | ----- | ---- | ------------------------------------- |
+| ids | array | body | **Required.** The IDs of the queries. |
#### Example
@@ -8209,7 +8177,6 @@ This allows you to easily configure scheduled queries that will impact a whole t
- [Run script](#run-script)
- [Get script result](#get-script-result)
-- [Run live script](#run-live-script)
- [Add script](#add-script)
- [Delete script](#delete-script)
- [List scripts](#list-scripts)
@@ -8285,46 +8252,6 @@ Gets the result of a script that was executed.
> Note: `exit_code` can be `null` if Fleet hasn't heard back from the host yet.
-### Run live script
-
-Run a live script and get results back (5 minute timeout). Live scripts only runs on the host if it has no other scripts running.
-
-`POST /api/v1/fleet/scripts/run/sync`
-
-#### Parameters
-
-| Name | Type | In | Description |
-| ---- | ------- | ---- | -------------------------------------------- |
-| host_id | integer | body | **Required**. The host id to run the script on. |
-| script_id | integer | body | The ID of the existing saved script to run. Only one of either `script_id`, `script_name` or `script_contents` can be included in the request; omit this parameter if using `script_contents` or `script_name`. |
-| script_contents | string | body | The contents of the script to run. Only one of either `script_contents`, `script_id` or `script_name` can be included in the request; omit this parameter if using `script_id` or `script_name`. |
-| script_name | string | body | The name of the existing saved script to run. Only one of either `script_name`, `script_id` or `script_contents` can be included in the request; omit this parameter if using `script_contents` or `script_id`. |
-| team_id | integer | body | ID of the team the saved script referenced by `script_name` belongs to. Default: `0` (hosts assigned to "No team") |
-
-
-> Note that if both `script_id` and `script_contents` are included in the request, this endpoint will respond with an error.
-
-#### Example
-
-`POST /api/v1/fleet/scripts/run/sync`
-
-##### Default response
-
-`Status: 200`
-
-```json
-{
- "host_id": 1227,
- "execution_id": "e797d6c6-3aae-11ee-be56-0242ac120002",
- "script_contents": "echo 'hello'",
- "output": "hello",
- "message": "",
- "runtime": 1,
- "host_timeout": false,
- "exit_code": 0
-}
-```
-
### Add script
Uploads a script, making it available to run on hosts assigned to the specified team (or no team).
@@ -8549,165 +8476,19 @@ Deletes the session specified by ID. When the user associated with the session n
## Software
-- [Add software](#add-software)
-- [Download software](#download-software)
-- [Delete software](#delete-software)
-- [Get installation result](#get-installation-result)
- [List software](#list-software)
- [List software versions](#list-software-versions)
+- [List operating systems](#list-operating-systems)
- [Get software](#get-software)
- [Get software version](#get-software-version)
-
-### Add software
-
-_Available in Fleet Premium._
-
-Add a software package to install on macOS, Windows, and Linux (Ubuntu) hosts.
-
-
-`POST /api/v1/fleet/software/package`
-
-#### Parameters
-
-| Name | Type | In | Description |
-| ---- | ------- | ---- | -------------------------------------------- |
-| software | file | form | **Required**. Installer package file. Supported packages are PKG, MSI, EXE, and DEB. |
-| team_id | integer | form | **Required**. The team ID. Adds a software package to the specified team. |
-| install_script | string | form | Command that Fleet runs to install software. If not specified Fleet runs [default install command](https://github.com/fleetdm/fleet/tree/f71a1f183cc6736205510580c8366153ea083a8d/pkg/file/scripts) for each package type. |
-| pre_install_query | string | form | Query that is pre-install condition. If the query doesn't return any result, Fleet won't proceed to install. |
-| post_install_script | string | form | The contents of the script to run after install. If the specified script fails (exit code non-zero) software install will be marked as failed and rolled back. |
-| self_service | boolean | form | Self-service software is optional and can be installed by the end user. |
-
-#### Example
-
-`POST /api/v1/fleet/software/package`
-
-##### Request header
-
-```http
-Content-Length: 8500
-Content-Type: multipart/form-data; boundary=------------------------d8c247122f594ba0
-```
-
-##### Request body
-
-```http
---------------------------d8c247122f594ba0
-Content-Disposition: form-data; name="team_id"
-1
---------------------------d8c247122f594ba0
-Content-Disposition: form-data; name="self_service"
-true
---------------------------d8c247122f594ba0
-Content-Disposition: form-data; name="install_script"
-sudo installer -pkg /temp/FalconSensor-6.44.pkg -target /
---------------------------d8c247122f594ba0
-Content-Disposition: form-data; name="pre_install_query"
-SELECT 1 FROM macos_profiles WHERE uuid='c9f4f0d5-8426-4eb8-b61b-27c543c9d3db';
---------------------------d8c247122f594ba0
-Content-Disposition: form-data; name="post_install_script"
-sudo /Applications/Falcon.app/Contents/Resources/falconctl license 0123456789ABCDEFGHIJKLMNOPQRSTUV-WX
---------------------------d8c247122f594ba0
-Content-Disposition: form-data; name="software"; filename="FalconSensor-6.44.pkg"
-Content-Type: application/octet-stream
-
---------------------------d8c247122f594ba0
-```
-
-##### Default response
-
-`Status: 200`
-
-
-### Download software
-
-_Available in Fleet Premium._
-
-Download a software package.
-
-`GET /api/v1/fleet/software/titles/:software_title_id/package/?alt=media`
-
-#### Parameters
-
-| Name | Type | In | Description |
-| ---- | ------- | ---- | -------------------------------------------- |
-| software_title_id | integer | path | **Required**. The ID of the software title to download software package.|
-| team_id | integer | query | **Required**. The team ID. Downloads a software package added to the specified team. |
-| alt | integer | query | **Required**. If specified and set to "media", downloads the specified software package. |
-
-#### Example
-
-`GET /api/v1/fleet/software/titles/123/package?alt=media?team_id=2`
-
-##### Default response
-
-`Status: 200`
-
-```http
-Status: 200
-Content-Type: application/octet-stream
-Content-Disposition: attachment
-Content-Length:
-Body:
-```
-
-### Delete software
-
-_Available in Fleet Premium._
-
-Delete a software package.
-
-`DELETE /api/v1/fleet/software/titles/:software_title_id/package`
-
-#### Parameters
-
-| Name | Type | In | Description |
-| ---- | ------- | ---- | -------------------------------------------- |
-| software_title_id | integer | path | **Required**. The ID of the software title for the software package to delete. |
-| team_id | integer | query | **Required**. The team ID. Deletes a software package added to the specified team. |
-
-#### Example
-
-`DELETE /api/v1/fleet/software/titles/24/package?team_id=2`
-
-##### Default response
-
-`Status: 204`
-
-### Get installation results
-
-_Available in Fleet Premium._
-
-`GET /api/v1/fleet/software/install/results/:install_uuid`
-
-Get the results of a software installation.
-
-| Name | Type | In | Description |
-| ---- | ------- | ---- | -------------------------------------------- |
-| install_uuid | string | path | **Required**. The software installation UUID.|
-
-#### Example
-
-`GET /api/v1/fleet/software/install/results/b15ce221-e22e-4c6a-afe7-5b3400a017da`
-
-##### Default response
-
-`Status: 200`
-
-```json
- {
- "install_uuid": "b15ce221-e22e-4c6a-afe7-5b3400a017da",
- "software_title": "Falcon.app",
- "software_title_id": 8353,
- "software_package": "FalconSensor-6.44.pkg",
- "host_id": 123,
- "host_display_name": "Marko's MacBook Pro",
- "status": "failed",
- "output": "Installing software...\nError: The operation can’t be completed because the item “Falcon” is in use.",
- "pre_install_query_output": "Query returned result\nSuccess",
- "post_install_script_output": "Running script...\nExit code: 1 (Failed)\nRolling back software install...\nSuccess"
- }
-```
+- [Get operating system version](#get-operating-system-version)
+- [Add package](#add-package)
+- [List App Store apps](#list-app-store-apps)
+- [Add App Store app](#add-app-store-app)
+- [Install package or App Store app](#install-package-or-app-store-app)
+- [Get package install result](#get-package-install-result)
+- [Download package](#download-package)
+- [Delete package or App Store app](#delete-package-or-app-store-app)
### List software
@@ -8715,6 +8496,8 @@ Get a list of all software.
`GET /api/v1/fleet/software/titles`
+> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
+
#### Parameters
| Name | Type | In | Description |
@@ -8724,10 +8507,10 @@ Get a list of all software.
| order_key | string | query | What to order results by. Allowed fields are `name` and `hosts_count`. Default is `hosts_count` (descending). |
| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include `asc` and `desc`. Default is `asc`. |
| query | string | query | Search query keywords. Searchable fields include `title` and `cve`. |
-| team_id | integer | query | _Available in Fleet Premium_. Filters the software to only include the software installed on the hosts that are assigned to the specified team. |
-| vulnerable | bool | query | If true or 1, only list software that has detected vulnerabilities. Default is `false`. |
-| available_for_install | bool | query | If `true` or `1`, only list software that is available for install (added by the user). Default is `false`. |
-| self_service | bool | query | If `true` or `1`, only lists self-service software. Default is `false`. |
+| team_id | integer | query | _Available in Fleet Premium_. Filters the software to only include the software installed on the hosts that are assigned to the specified team. Use `0` to filter by hosts assigned to "No team". |
+| vulnerable | boolean | query | If true or 1, only list software that has detected vulnerabilities. Default is `false`. |
+| available_for_install | boolean | query | If `true` or `1`, only list software that is available for install (added by the user). Default is `false`. |
+| self_service | boolean | query | If `true` or `1`, only lists self-service software. Default is `false`. |
#### Example
@@ -8745,20 +8528,24 @@ Get a list of all software.
{
"id": 12,
"name": "Firefox.app",
- "software_package": "FirefoxInstall.pkg",
- "self_service": true,
+ "software_package": {
+ "name": "FirefoxInsall.pkg",
+ "version": "125.6",
+ "self_service": true
+ },
+ "app_store_app": null,
"versions_count": 3,
"source": "apps",
"browser": "",
"hosts_count": 48,
- "versions": [
+ "versions": [
{
"id": 123,
"version": "1.12",
"vulnerabilities": ["CVE-2023-1234","CVE-2023-4321","CVE-2023-7654"]
- },
+ },
{
- "id": 124,
+ "id": 124,
"version": "3.4",
"vulnerabilities": ["CVE-2023-1234","CVE-2023-4321","CVE-2023-7654"]
},
@@ -8767,13 +8554,13 @@ Get a list of all software.
"version": "1.13",
"vulnerabilities": ["CVE-2023-1234","CVE-2023-4321","CVE-2023-7654"]
}
- ]
+ ]
},
{
"id": 22,
"name": "Google Chrome.app",
"software_package": null,
- "self_service": false,
+ "app_store_app": null,
"versions_count": 5,
"source": "apps",
"browser": "",
@@ -8805,7 +8592,7 @@ Get a list of all software.
"id": 32,
"name": "1Password – Password Manager",
"software_package": null,
- "self_service": false,
+ "app_store_app": null,
"versions_count": 1,
"source": "chrome_extensions",
"browser": "chrome",
@@ -8841,8 +8628,8 @@ Get a list of all software versions.
| order_key | string | query | What to order results by. Allowed fields are `name`, `hosts_count`, `cve_published`, `cvss_score`, `epss_probability` and `cisa_known_exploit`. Default is `hosts_count` (descending). |
| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include `asc` and `desc`. Default is `asc`. |
| query | string | query | Search query keywords. Searchable fields include `name`, `version`, and `cve`. |
-| team_id | integer | query | _Available in Fleet Premium_. Filters the software to only include the software installed on the hosts that are assigned to the specified team. |
-| vulnerable | bool | query | If true or 1, only list software that has detected vulnerabilities. Default is `false`. |
+| team_id | integer | query | _Available in Fleet Premium_. Filters the software to only include the software installed on the hosts that are assigned to the specified team. Use `0` to filter by hosts assigned to "No team". |
+| vulnerable | boolean | query | If true or 1, only list software that has detected vulnerabilities. Default is `false`. |
#### Example
@@ -8900,22 +8687,25 @@ Get a list of all software versions.
}
```
-### Get software
+### List operating systems
-Returns information about the specified software. By default, `versions` are sorted in descending order by the `hosts_count` field.
+Returns a list of all operating systems.
-`GET /api/v1/fleet/software/titles/:id`
+`GET /api/v1/fleet/os_versions`
#### Parameters
-| Name | Type | In | Description |
-| ---- | ---- | -- | ----------- |
-| id | integer | path | **Required.** The software title's ID. |
-| team_id | integer | query | _Available in Fleet Premium_. Filters response data to the specified team. |
-
-#### Example
+| Name | Type | In | Description |
+| --- | --- | --- | --- |
+| team_id | integer | query | _Available in Fleet Premium_. Filters response data to the specified team. Use `0` to filter by hosts assigned to "No team". |
+| platform | string | query | Filters the hosts to the specified platform |
+| os_name | string | query | The name of the operating system to filter hosts by. `os_version` must also be specified with `os_name` |
+| os_version | string | query | The version of the operating system to filter hosts by. `os_name` must also be specified with `os_version` |
+| page | integer | query | Page number of the results to fetch. |
+| per_page | integer | query | Results per page. |
+| order_key | string | query | What to order results by. Allowed fields are: `hosts_count`. Default is `hosts_count` (descending). |
+| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include `asc` and `desc`. Default is `asc`. |
-`GET /api/v1/fleet/software/titles/12`
##### Default response
@@ -8923,29 +8713,102 @@ Returns information about the specified software. By default, `versions` are sor
```json
{
- "software_title": {
- "id": 12,
- "name": "Firefox.app",
- "software_package": {
- "name": "FalconSensor-6.44.pkg",
- "version": "6.44",
- "installer_id": 23,
- "team_id": 3,
- "uploaded_at": "2024-04-01T14:22:58Z",
- "install_script": "sudo installer -pkg /temp/FalconSensor-6.44.pkg -target /",
- "pre_install_query": "SELECT 1 FROM macos_profiles WHERE uuid='c9f4f0d5-8426-4eb8-b61b-27c543c9d3db';",
- "post_install_script": "sudo /Applications/Falcon.app/Contents/Resources/falconctl license 0123456789ABCDEFGHIJKLMNOPQRSTUV-WX",
- "self_service": true,
- "status": {
- "installed": 3,
- "pending": 1,
- "failed": 2,
- }
- },
- "source": "apps",
- "browser": "",
+ "count": 1
+ "counts_updated_at": "2023-12-06T22:17:30Z",
+ "os_versions": [
+ {
+ "os_version_id": 123,
+ "hosts_count": 21,
+ "name": "Microsoft Windows 11 Pro 23H2 10.0.22621.1234",
+ "name_only": "Microsoft Windows 11 Pro 23H2",
+ "version": "10.0.22621.1234",
+ "platform": "windows",
+ "generated_cpes": [],
+ "vulnerabilities": [
+ {
+ "cve": "CVE-2022-30190",
+ "details_link": "https://nvd.nist.gov/vuln/detail/CVE-2022-30190",
+ "cvss_score": 7.8,// Available in Fleet Premium
+ "epss_probability": 0.9729,// Available in Fleet Premium
+ "cisa_known_exploit": false,// Available in Fleet Premium
+ "cve_published": "2022-06-01T00:15:00Z",// Available in Fleet Premium
+ "cve_description": "Microsoft Windows Support Diagnostic Tool (MSDT) Remote Code Execution Vulnerability.",// Available in Fleet Premium
+ "resolved_in_version": ""// Available in Fleet Premium
+ }
+ ]
+ }
+ ],
+ "meta": {
+ "has_next_results": false,
+ "has_previous_results": false
+ }
+}
+```
+
+OS vulnerability data is currently available for Windows and macOS. For other platforms, `vulnerabilities` will be an empty array:
+
+```json
+{
+ "hosts_count": 1,
+ "name": "CentOS Linux 7.9.2009",
+ "name_only": "CentOS",
+ "version": "7.9.2009",
+ "platform": "rhel",
+ "generated_cpes": [],
+ "vulnerabilities": []
+}
+```
+
+### Get software
+
+> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
+
+Returns information about the specified software. By default, `versions` are sorted in descending order by the `hosts_count` field.
+
+`GET /api/v1/fleet/software/titles/:id`
+
+#### Parameters
+
+| Name | Type | In | Description |
+| ---- | ---- | -- | ----------- |
+| id | integer | path | **Required.** The software title's ID. |
+| team_id | integer | query | _Available in Fleet Premium_. Filters response data to the specified team. Use `0` to filter by hosts assigned to "No team". |
+
+#### Example
+
+`GET /api/v1/fleet/software/titles/12`
+
+##### Default response
+
+`Status: 200`
+
+```json
+{
+ "software_title": {
+ "id": 12,
+ "name": "Firefox.app",
+ "bundle_identifier": "org.mozilla.firefox",
+ "software_package": {
+ "name": "FalconSensor-6.44.pkg",
+ "version": "6.44",
+ "installer_id": 23,
+ "team_id": 3,
+ "uploaded_at": "2024-04-01T14:22:58Z",
+ "install_script": "sudo installer -pkg /temp/FalconSensor-6.44.pkg -target /",
+ "pre_install_query": "SELECT 1 FROM macos_profiles WHERE uuid='c9f4f0d5-8426-4eb8-b61b-27c543c9d3db';",
+ "post_install_script": "sudo /Applications/Falcon.app/Contents/Resources/falconctl license 0123456789ABCDEFGHIJKLMNOPQRSTUV-WX",
+ "self_service": true,
+ "status": {
+ "installed": 3,
+ "pending": 1,
+ "failed": 2,
+ }
+ },
+ "app_store_app": null,
+ "source": "apps",
+ "browser": "",
"hosts_count": 48,
- "versions": [
+ "versions": [
{
"id": 123,
"version": "117.0",
@@ -8964,7 +8827,48 @@ Returns information about the specified software. By default, `versions` are sor
"vulnerabilities": ["CVE-2023-7654"],
"hosts_count": 4
}
- ]
+ ]
+ }
+}
+```
+
+#### Example (App Store app)
+
+`GET /api/v1/fleet/software/titles/15`
+
+##### Default response
+
+`Status: 200`
+
+```json
+{
+ "software_title": {
+ "id": 15,
+ "name": "Logic Pro",
+ "bundle_identifier": "com.apple.logic10",
+ "software_package": null,
+ "app_store_app": {
+ "name": "Logic Pro",
+ "app_store_id": "1091189122",
+ "latest_version": "2.04",
+ "icon_url": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f1/65/1e/a4844ccd-486d-455f-bb31-67336fe46b14/AppIcon-1x_U007emarketing-0-7-0-85-220-0.png/512x512bb.jpg",
+ "status": {
+ "installed": 3,
+ "pending": 1,
+ "failed": 2,
+ }
+ },
+ "source": "apps",
+ "browser": "",
+ "hosts_count": 48,
+ "versions": [
+ {
+ "id": 123,
+ "version": "2.04",
+ "vulnerabilities": [],
+ "hosts_count": 24
+ }
+ ]
}
}
```
@@ -8980,7 +8884,7 @@ Returns information about the specified software version.
| Name | Type | In | Description |
| ---- | ---- | -- | ----------- |
| id | integer | path | **Required.** The software version's ID. |
-| team_id | integer | query | _Available in Fleet Premium_. Filters response data to the specified team. |
+| team_id | integer | query | _Available in Fleet Premium_. Filters response data to the specified team. Use `0` to filter by hosts assigned to "No team". |
#### Example
@@ -9026,8 +8930,338 @@ Returns information about the specified software version.
}
```
-## Vulnerabilities
+### Get operating system version
+
+Retrieves information about the specified operating system (OS) version.
+
+`GET /api/v1/fleet/os_versions/:id`
+
+#### Parameters
+
+| Name | Type | In | Description |
+| ---- | ---- | -- | ----------- |
+| id | integer | path | **Required.** The OS version's ID. |
+| team_id | integer | query | _Available in Fleet Premium_. Filters response data to the specified team. Use `0` to filter by hosts assigned to "No team". |
+
+##### Default response
+
+`Status: 200`
+
+```json
+{
+ "counts_updated_at": "2023-12-06T22:17:30Z",
+ "os_version": {
+ "id": 123,
+ "hosts_count": 21,
+ "name": "Microsoft Windows 11 Pro 23H2 10.0.22621.1234",
+ "name_only": "Microsoft Windows 11 Pro 23H2",
+ "version": "10.0.22621.1234",
+ "platform": "windows",
+ "generated_cpes": [],
+ "vulnerabilities": [
+ {
+ "cve": "CVE-2022-30190",
+ "details_link": "https://nvd.nist.gov/vuln/detail/CVE-2022-30190",
+ "created_at": "2024-07-01T00:15:00Z",
+ "cvss_score": 7.8,// Available in Fleet Premium
+ "epss_probability": 0.9729,// Available in Fleet Premium
+ "cisa_known_exploit": false,// Available in Fleet Premium
+ "cve_published": "2022-06-01T00:15:00Z",// Available in Fleet Premium
+ "cve_description": "Microsoft Windows Support Diagnostic Tool (MSDT) Remote Code Execution Vulnerability.",// Available in Fleet Premium
+ "resolved_in_version": ""// Available in Fleet Premium
+ }
+ ]
+ }
+}
+```
+
+OS vulnerability data is currently available for Windows and macOS. For other platforms, `vulnerabilities` will be an empty array:
+
+```json
+{
+ "id": 321,
+ "hosts_count": 1,
+ "name": "CentOS Linux 7.9.2009",
+ "name_only": "CentOS",
+ "version": "7.9.2009",
+ "platform": "rhel",
+ "generated_cpes": [],
+ "vulnerabilities": []
+}
+```
+
+### Add package
+
+> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
+
+_Available in Fleet Premium._
+
+Add a package (.pkg, .msi, .exe, .deb) to install on macOS, Windows, or Linux (Ubuntu) hosts.
+
+
+`POST /api/v1/fleet/software/package`
+
+#### Parameters
+
+| Name | Type | In | Description |
+| ---- | ------- | ---- | -------------------------------------------- |
+| software | file | form | **Required**. Installer package file. Supported packages are PKG, MSI, EXE, and DEB. |
+| team_id | integer | form | **Required**. The team ID. Adds a software package to the specified team. |
+| install_script | string | form | Command that Fleet runs to install software. If not specified Fleet runs [default install command](https://github.com/fleetdm/fleet/tree/f71a1f183cc6736205510580c8366153ea083a8d/pkg/file/scripts) for each package type. |
+| pre_install_query | string | form | Query that is pre-install condition. If the query doesn't return any result, Fleet won't proceed to install. |
+| post_install_script | string | form | The contents of the script to run after install. If the specified script fails (exit code non-zero) software install will be marked as failed and rolled back. |
+| self_service | boolean | form | Self-service software is optional and can be installed by the end user. |
+
+#### Example
+
+`POST /api/v1/fleet/software/package`
+##### Request header
+
+```http
+Content-Length: 8500
+Content-Type: multipart/form-data; boundary=------------------------d8c247122f594ba0
+```
+
+##### Request body
+
+```http
+--------------------------d8c247122f594ba0
+Content-Disposition: form-data; name="team_id"
+1
+--------------------------d8c247122f594ba0
+Content-Disposition: form-data; name="self_service"
+true
+--------------------------d8c247122f594ba0
+Content-Disposition: form-data; name="install_script"
+sudo installer -pkg /temp/FalconSensor-6.44.pkg -target /
+--------------------------d8c247122f594ba0
+Content-Disposition: form-data; name="pre_install_query"
+SELECT 1 FROM macos_profiles WHERE uuid='c9f4f0d5-8426-4eb8-b61b-27c543c9d3db';
+--------------------------d8c247122f594ba0
+Content-Disposition: form-data; name="post_install_script"
+sudo /Applications/Falcon.app/Contents/Resources/falconctl license 0123456789ABCDEFGHIJKLMNOPQRSTUV-WX
+--------------------------d8c247122f594ba0
+Content-Disposition: form-data; name="software"; filename="FalconSensor-6.44.pkg"
+Content-Type: application/octet-stream
+
+--------------------------d8c247122f594ba0
+```
+
+##### Default response
+
+`Status: 200`
+
+### List App Store apps
+
+> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
+
+Returns the list of Apple App Store (VPP) that can be added to the specified team. If an app is already added to the team, it's excluded from the list.
+
+`GET /api/v1/fleet/software/app_store_apps`
+
+#### Parameters
+
+| Name | Type | In | Description |
+| ------- | ---- | -- | ----------- |
+| team_id | integer | query | **Required**. The team ID. |
+
+#### Example
+
+`GET /api/v1/fleet/software/app_store_apps/?team_id=3`
+
+##### Default response
+
+`Status: 200`
+
+```json
+{
+ "app_store_apps": [
+ {
+ "name": "Xcode",
+ "icon_url": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f1/65/1e/a4844ccd-486d-455f-bb31-67336fe46b14/AppIcon-1x_U007emarketing-0-7-0-85-220-0.png/512x512bb.jpg",
+ "latest_version": "15.4",
+ "app_store_id": "497799835",
+ "platform": "darwin"
+ },
+ {
+ "name": "Logic Pro",
+ "icon_url": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f1/65/1e/a4844ccd-486d-455f-bb31-67336fe46b14/AppIcon-1x_U007emarketing-0-7-0-85-220-0.png/512x512bb.jpg",
+ "latest_version": "2.04",
+ "app_store_id": "634148309",
+ "platform": "ios"
+ },
+ {
+ "name": "Logic Pro",
+ "icon_url": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f1/65/1e/a4844ccd-486d-455f-bb31-67336fe46b14/AppIcon-1x_U007emarketing-0-7-0-85-220-0.png/512x512bb.jpg",
+ "latest_version": "2.04",
+ "app_store_id": "634148309",
+ "platform": "ipados"
+ },
+ ]
+}
+```
+
+### Add App Store app
+
+> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
+
+_Available in Fleet Premium._
+
+Add App Store (VPP) app purchased in Apple Business Manager.
+
+`POST /api/v1/fleet/software/app_store_apps`
+
+#### Parameters
+
+| Name | Type | In | Description |
+| ---- | ---- | -- | ----------- |
+| app_store_id | string | body | **Required.** The ID of App Store app. |
+| team_id | integer | body | **Required**. The team ID. Adds VPP software to the specified team. |
+| platform | string | body | The platform of the app (`darwin`, `ios`, or `ipados`). Default is `darwin`. |
+
+#### Example
+
+`POST /api/v1/fleet/software/app_store_apps?team_id=3`
+
+##### Request body
+
+```json
+{
+ "app_store_id": "497799835",
+ "team_id": 2,
+ "platform": "ipados"
+}
+```
+
+##### Default response
+
+`Status: 200`
+
+### Download package
+
+> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
+
+_Available in Fleet Premium._
+
+`GET /api/v1/fleet/software/titles/:software_title_id/package?alt=media`
+
+#### Parameters
+
+| Name | Type | In | Description |
+| ---- | ------- | ---- | -------------------------------------------- |
+| software_title_id | integer | path | **Required**. The ID of the software title to download software package.|
+| team_id | integer | query | **Required**. The team ID. Downloads a software package added to the specified team. |
+| alt | integer | query | **Required**. If specified and set to "media", downloads the specified software package. |
+
+#### Example
+
+`GET /api/v1/fleet/software/titles/123/package?alt=media?team_id=2`
+
+##### Default response
+
+`Status: 200`
+
+```http
+Status: 200
+Content-Type: application/octet-stream
+Content-Disposition: attachment
+Content-Length:
+Body:
+```
+
+### Install package or App Store app
+
+> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
+
+_Available in Fleet Premium._
+
+Install software (package or App Store app) on a macOS, iOS, iPadOS, Windows, or Linux (Ubuntu) host. Software title must have a `software_package` or `app_store_app` added to be installed.
+
+> Note: Fleet's agent (fleetd) only installs software it has been asked to install, but technically has access to all installer executables.
+
+`POST /api/v1/fleet/hosts/:id/software/install/:software_title_id`
+
+#### Parameters
+
+| Name | Type | In | Description |
+| --------- | ---------- | ---- | -------------------------------------------- |
+| id | integer | path | **Required**. The host's ID. |
+| software_title_id | integer | path | **Required**. The software title's ID. |
+
+#### Example
+
+`POST /api/v1/fleet/hosts/123/software/install/3435`
+
+##### Default response
+
+`Status: 202`
+
+### Get package install result
+
+> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
+
+_Available in Fleet Premium._
+
+`GET /api/v1/fleet/software/install/results/:install_uuid`
+
+Get the results of a software package install.
+
+To get the results of an App Store app install, use the [List MDM commands](#list-mdm-commands) and [Get MDM command results](#get-mdm-command-results) API enpoints. Fleet uses an MDM command to install App Store apps.
+
+| Name | Type | In | Description |
+| ---- | ------- | ---- | -------------------------------------------- |
+| install_uuid | string | path | **Required**. The software installation UUID.|
+
+#### Example
+
+`GET /api/v1/fleet/software/install/results/b15ce221-e22e-4c6a-afe7-5b3400a017da`
+
+##### Default response
+
+`Status: 200`
+
+```json
+ {
+ "install_uuid": "b15ce221-e22e-4c6a-afe7-5b3400a017da",
+ "software_title": "Falcon.app",
+ "software_title_id": 8353,
+ "software_package": "FalconSensor-6.44.pkg",
+ "host_id": 123,
+ "host_display_name": "Marko's MacBook Pro",
+ "status": "failed",
+ "output": "Installing software...\nError: The operation can’t be completed because the item “Falcon” is in use.",
+ "pre_install_query_output": "Query returned result\nSuccess",
+ "post_install_script_output": "Running script...\nExit code: 1 (Failed)\nRolling back software install...\nSuccess"
+ }
+```
+
+### Delete package or App Store app
+
+> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
+
+_Available in Fleet Premium._
+
+Deletes software that's available for install (package or App Store app).
+
+`DELETE /api/v1/fleet/software/titles/:software_title_id/available_for_install`
+
+#### Parameters
+
+| Name | Type | In | Description |
+| ---- | ------- | ---- | -------------------------------------------- |
+| software_title_id | integer | path | **Required**. The ID of the software title to delete software available for install. |
+| team_id | integer | query | **Required**. The team ID. Deletes a software package added to the specified team. |
+
+#### Example
+
+`DELETE /api/v1/fleet/software/titles/24/available_for_install?team_id=2`
+
+##### Default response
+
+`Status: 204`
+
+## Vulnerabilities
- [List vulnerabilities](#list-vulnerabilities)
- [Get vulnerability](#get-vulnerability)
@@ -9042,7 +9276,7 @@ Retrieves a list of all CVEs affecting software and/or OS versions.
| Name | Type | In | Description |
| --- | --- | --- | --- |
-| team_id | integer | query | _Available in Fleet Premium_. Filters only include vulnerabilities affecting the specified team. |
+| team_id | integer | query | _Available in Fleet Premium_. Filters only include vulnerabilities affecting the specified team. Use `0` to filter by hosts assigned to "No team". |
| page | integer | query | Page number of the results to fetch. |
| per_page | integer | query | Results per page. |
| order_key | string | query | What to order results by. Allowed fields are: `cve`, `cvss_score`, `epss_probability`, `cve_published`, `created_at`, and `host_count`. Default is `created_at` (descending). |
@@ -9091,7 +9325,7 @@ Retrieve details about a vulnerability and its affected software and OS versions
| Name | Type | In | Description |
| --- | --- | --- | --- |
| cve | string | path | The cve to get information about (including "cve-" prefix, case-insensitive). |
-| team_id | integer | query | _Available in Fleet Premium_. Filters response data to the specified team. |
+| team_id | integer | query | _Available in Fleet Premium_. Filters response data to the specified team. Use `0` to filter by hosts assigned to "No team". |
`GET /api/v1/fleet/vulnerabilities/:cve`
@@ -9122,7 +9356,7 @@ Retrieve details about a vulnerability and its affected software and OS versions
"name": "macOS 14.1.2",
"name_only": "macOS",
"version": "14.1.2",
- "platform": "darwin",
+
"resolved_in_version": "14.2",
"generated_cpes": [
"cpe:2.3:o:apple:macos:*:*:*:*:*:14.2:*:*",
@@ -9604,8 +9838,8 @@ _Available in Fleet Premium_
| ------------------------------------------------------- | ------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| id | integer | path | **Required.** The desired team's ID. |
| name | string | body | The team's name. |
-| host_ids | list | body | A list of hosts that belong to the team. |
-| user_ids | list | body | A list of users on the team. |
+| host_ids | array | body | A list of hosts that belong to the team. |
+| user_ids | array | body | A list of users on the team. |
| webhook_settings | object | body | Webhook settings contains for the team. |
| failing_policies_webhook | object | body | Failing policies webhook settings. |
| enable_failing_policies_webhook | boolean | body | Whether or not the failing policies webhook is enabled. |
@@ -9630,14 +9864,20 @@ _Available in Fleet Premium_
| macos_updates | object | body | macOS updates settings. |
| minimum_version | string | body | Hosts that belong to this team and are enrolled into Fleet's MDM will be nudged until their macOS is at or above this version. |
| deadline | string | body | Hosts that belong to this team and are enrolled into Fleet's MDM won't be able to dismiss the Nudge window once this deadline is past. |
+| ios_updates | object | body | iOS updates settings. |
+| minimum_version | string | body | Hosts that belong to this team and are enrolled into Fleet's MDM will be nudged until their iOS is at or above this version. |
+| deadline | string | body | Hosts that belong to this team and are enrolled into Fleet's MDM won't be able to dismiss the Nudge window once this deadline is past. |
+| ipados_updates | object | body | iPadOS updates settings. |
+| minimum_version | string | body | Hosts that belong to this team and are enrolled into Fleet's MDM will be nudged until their iPadOS is at or above this version. |
+| deadline | string | body | Hosts that belong to this team and are enrolled into Fleet's MDM won't be able to dismiss the Nudge window once this deadline is past. |
| windows_updates | object | body | Windows updates settings. |
| deadline_days | integer | body | Hosts that belong to this team and are enrolled into Fleet's MDM will have this number of days before updates are installed on Windows. |
| grace_period_days | integer | body | Hosts that belong to this team and are enrolled into Fleet's MDM will have this number of days before Windows restarts to install updates. |
| macos_settings | object | body | macOS-specific settings. |
-| custom_settings | list | body | The list of objects where each object includes .mobileconfig or JSON file (configuration profile) and label name to apply to macOS hosts that belong to this team and are members of the specified label. |
+| custom_settings | array | body | The list of objects where each object includes .mobileconfig or JSON file (configuration profile) and label name to apply to macOS hosts that belong to this team and are members of the specified label. |
| enable_disk_encryption | boolean | body | Hosts that belong to this team and are enrolled into Fleet's MDM will have disk encryption enabled if set to true. |
| windows_settings | object | body | Windows-specific settings. |
-| custom_settings | list | body | The list of objects where each object includes XML file (configuration profile) and label name to apply to Windows hosts that belong to this team and are members of the specified label. |
+| custom_settings | array | body | The list of objects where each object includes XML file (configuration profile) and label name to apply to Windows hosts that belong to this team and are members of the specified label. |
| macos_setup | object | body | Setup for automatic MDM enrollment of macOS hosts. |
| enable_end_user_authentication | boolean | body | If set to true, end user authentication will be required during automatic MDM enrollment of new macOS hosts. Settings for your IdP provider must also be [configured](https://fleetdm.com/docs/using-fleet/mdm-macos-setup-experience#end-user-authentication-and-eula). |
| integrations | object | body | Integration settings for this team. |
@@ -9857,8 +10097,8 @@ _Available in Fleet Premium_
| Name | Type | In | Description |
| --- | --- | --- | --- |
| id | integer | path | **Required.** The desired team's ID. |
-| force | bool | query | Force apply the options even if there are validation errors. |
-| dry_run | bool | query | Validate the options and return any validation errors, but do not apply the changes. |
+| force | boolean | query | Force apply the options even if there are validation errors. |
+| dry_run | boolean | query | Validate the options and return any validation errors, but do not apply the changes. |
| _JSON data_ | object | body | The JSON to use as agent options for this team. See [Agent options](https://fleetdm.com/docs/using-fleet/configuration-files#agent-options) for details. |
#### Example
@@ -9969,9 +10209,9 @@ Transforms a host name into a host id. For example, the Fleet UI use this endpoi
#### Parameters
-| Name | Type | In | Description |
-| ---- | ----- | ---- | ---------------------------------------- |
-| list | array | body | **Required** list of items to translate. |
+| Name | Type | In | Description |
+| ----- | ----- | ---- | ---------------------------------------- |
+| array | array | body | **Required** list of items to translate. |
#### Example
diff --git a/docs/Using Fleet/Learn-how-to-use-Fleet.md b/docs/Using Fleet/Learn-how-to-use-Fleet.md
deleted file mode 100644
index fceea3f7db93..000000000000
--- a/docs/Using Fleet/Learn-how-to-use-Fleet.md
+++ /dev/null
@@ -1,58 +0,0 @@
-# Learn how to use Fleet
-
-- [How to add your device to Fleet](#how-to-add-your-device-to-fleet)
-- [How to ask questions about your device](#how-to-ask-questions-about-your-device)
-
-### Overview
-
-In this guide, we'll cover the following concepts:
-- How to add your device to Fleet
-- How to ask questions about your device
-
-### How to add your device to Fleet
-
-Once you log into Fleet, you are presented with the **Home** page.
-
-To add your device:
-
-1. Select **Add hosts**. In Fleet, devices are referred to as "hosts."
-2. Select your device's platform.
-3. Select **Download** to download Fleet's agent (fleetd). The download may take several seconds.
-4. Open fleetd and follow the installation steps.
-
-> It may take several seconds for Fleet osquery to send your device's data to Fleet.
-
-In the background, Fleet ran several checks to assess the security hygiene of your device.
-
-> In Fleet, these checks are referred to as "policies."
-
-### How to ask questions about your device
-
-With Fleet, you can ask a multitude of questions to help you manage, monitor, and identify threats on your devices, but if you are just starting out, and unsure of what to ask, Fleet comes baked in with a [query library](https://fleetdm.com/queries) of common questions.
-
-So, let's start by asking the following question about your device:
-
-* What operating system is installed on my device and what is its version?
-
-This question can easily be answered by running this simple query: "Get operating system information."
-
-To run this query on your device:
-
-1. Select **Queries** in the top navigation.
-2. Select **Create new query** (or browse your organization's queries for "operating system information" in the search bar).
-3. Type the query you would like to run, `SELECT * FROM os_version;`.
-4. Select **Run query**, then select **All hosts** (your device may be the only host added to Fleet), and finally select **Run** to execute the query.
-
-The query may take several seconds to complete, because Fleet has to wait for the Fleet's agent (fleetd) to respond with results. Only online hosts will respond with results to a live query.
-
-> Fleet's query response time is inherently variable because of osquery's heartbeat response time. This helps prevent performance issues on hosts.
-
-When the query has finished, you should see several columns in the "Results" table:
-
-- The "name" column answers: "What operating system is installed on my device?"
-
-- The "version" column answers: "What version of the installed operating system is on my device?"
-
-
-
-
\ No newline at end of file
diff --git a/docs/Using Fleet/MDM-setup.md b/docs/Using Fleet/MDM-setup.md
deleted file mode 100644
index c56ddeb5d665..000000000000
--- a/docs/Using Fleet/MDM-setup.md
+++ /dev/null
@@ -1,42 +0,0 @@
-# Setup
-
-To turn on macOS, iOS, and iPadOS MDM features, follow the instructions on this page to connect Fleet to Apple Push Notification service (APNs).
-
-To use automatic enrollment (aka zero-touch) features on macOS, iOS, and iPadOS, follow instructions to connect Fleet with Apple Business Manager (ABM).
-
-To turn on Windows MDM features, head to this [Windows MDM setup article](https://fleetdm.com/guides/windows-mdm-setup).
-
-## Apple Push Notification service (APNs)
-
-Apple uses APNs to authenticate and manage interactions between Fleet and hosts.
-
-To connect Fleet to APNs or renew APNs, head to the **Settings > Integrations > Mobile device management (MDM)** page.
-
-> Apple requires that APNs certificates are renewed annually.
-> - If your certificate expires, you will have to turn MDM off and back on for all macOS hosts.
-> - Be sure to use the same Apple ID from year-to-year. If you don't, you will have to turn MDM off and back on for all macOS hosts.
-
-## Apple Business Manager (ABM)
-
-> Available in Fleet Premium
-
-To connect Fleet to ABM or renew ABM, head to the **Settings > Integrations > Automatic enrollment > Apple Business Manager** page.
-
-After connecting Fleet to ABM, set Fleet to be the MDM for all Macs:
-
-1. Log in to [Apple Business Manager](https://business.apple.com)
-2. Click your profile icon in the bottom left
-3. Click **Preferences**
-4. Click **MDM Server Assignment** and click **Edit** next to **Default Server Assignment**.
-5. Switch **Mac**, **iPhone**, and **iPad** to Fleet.
-
-New or wiped macOS, iOS, and iPadOS hosts that are in ABM, before they've been set up, appear in Fleet with **MDM status** set to "Pending".
-
-All macOS hosts that automatically enroll will be assigned to the default team. If no default team is set, then the host will be placed in "No team".
-
-> A host can be transferred to a new (not default) team before it enrolls. In the Fleet UI, you can do this under **Settings** > **Teams**.
-
-
-
-
-
diff --git a/docs/Using Fleet/Supported-browsers.md b/docs/Using Fleet/Supported-browsers.md
deleted file mode 100644
index 0ed5a0dc0e8b..000000000000
--- a/docs/Using Fleet/Supported-browsers.md
+++ /dev/null
@@ -1,28 +0,0 @@
-
-# Supported browsers
-
-Fleet supports the latest, stable releases of all major browsers and platforms.
-
-We test each browser on Windows whenever possible, because our engineering team primarily uses macOS.
-
-**Note:** This information also applies to [fleetdm.com](https://www.fleetdm.com).
-
-### Desktop
-
-- Chrome
-- Firefox
-- Edge
-- Safari (macOS only)
-
-### Mobile
-
-- Mobile Safari on iOS
-- Mobile Chrome on Android
-
-### Note
-> - Mobile web is not yet supported in the Fleet product.
-> - The Fleet user interface [may not be fully supported](https://github.com/fleetdm/fleet/issues/969) in Google Chrome when the browser is running on ChromeOS
-
-
-
-
diff --git a/docs/Using Fleet/Supported-host-operating-systems.md b/docs/Using Fleet/Supported-host-operating-systems.md
deleted file mode 100644
index c7adedff8916..000000000000
--- a/docs/Using Fleet/Supported-host-operating-systems.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# Supported host operating systems
-
-Fleet supports the following operating system versions on hosts.
-
-| OS | Supported version(s) |
-| :------ | :------------------------------------- |
-| macOS | 13+ (Ventura) |
-| Windows | Pro and Enterprise 10+, Server 2012+ |
-| Linux | CentOS 7.1+, Ubuntu 20.04+, Fedora 38+ |
-| ChromeOS | 112.0.5615.134+ |
-
-While Fleet may still function partially or fully with OS versions older than those above, Fleet does not actively test against unsupported versions and does not pursue bugs on them.
-
-## Some notes on compatibility
-
-### Tables
-Not all osquery tables are available for every OS. Please check out the [osquery schema](https://fleetdm.com/tables) for detailed information.
-
-If a table is not available for your host, Fleet will generally handle things behind the scenes for you.
-
-### M1 Macs
-Fleet's agent (fleetd) generated for MacOS by `fleetctl package` does not include native support for M1 Macs. Some values returned may reflect the information returned by Rosetta rather than the system. For example, a CPU will show up as `i486`.
-
-### Linux
-
-> Ubuntu Linux:
-> Fleet Desktop currently supports Xorg as X11 server, Wayland is currently not supported.
-> Ubuntu 24.04 comes with Wayland enabled by default. To use X11 instead of Wayland you can set
-> `WaylandEnable=false` in `/etc/gdm3/custom.conf` and reboot.
-
-> Fedora, CentOS 8 and 9 require a [gnome extension](https://extensions.gnome.org/extension/615/appindicator-support/) and Google Chrome set to the default browser for Fleet Desktop.
-
-> The `fleetctl package` command is not supported on DISA-STIG distribution.
-
-
-
-
diff --git a/docs/files/2024-06-14-fleet-penetration-test.pdf b/docs/files/2024-06-14-fleet-penetration-test.pdf
new file mode 100644
index 000000000000..1be4c4654146
Binary files /dev/null and b/docs/files/2024-06-14-fleet-penetration-test.pdf differ
diff --git a/ee/bulk-operations-dashboard/.editorconfig b/ee/bulk-operations-dashboard/.editorconfig
new file mode 100644
index 000000000000..6d7fa7039c28
--- /dev/null
+++ b/ee/bulk-operations-dashboard/.editorconfig
@@ -0,0 +1,31 @@
+################################################
+# ╔═╗╔╦╗╦╔╦╗╔═╗╦═╗┌─┐┌─┐┌┐┌┌─┐┬┌─┐
+# ║╣ ║║║ ║ ║ ║╠╦╝│ │ ││││├┤ ││ ┬
+# o╚═╝═╩╝╩ ╩ ╚═╝╩╚═└─┘└─┘┘└┘└ ┴└─┘
+#
+# > Formatting conventions for your Sails app.
+#
+# This file (`.editorconfig`) exists to help
+# maintain consistent formatting throughout the
+# files in your Sails app.
+#
+# For the sake of convention, the Sails team's
+# preferred settings are included here out of the
+# box. You can also change this file to fit your
+# team's preferences (for example, if all of the
+# developers on your team have a strong preference
+# for tabs over spaces),
+#
+# To review what each of these options mean, see:
+# http://editorconfig.org/
+#
+################################################
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/ee/bulk-operations-dashboard/.eslintignore b/ee/bulk-operations-dashboard/.eslintignore
new file mode 100644
index 000000000000..f190c2ae4fe6
--- /dev/null
+++ b/ee/bulk-operations-dashboard/.eslintignore
@@ -0,0 +1,3 @@
+assets/dependencies/**/*.js
+views/**/*.ejs
+
diff --git a/ee/bulk-operations-dashboard/.eslintrc b/ee/bulk-operations-dashboard/.eslintrc
new file mode 100644
index 000000000000..37e02b37243e
--- /dev/null
+++ b/ee/bulk-operations-dashboard/.eslintrc
@@ -0,0 +1,92 @@
+{
+ // ╔═╗╔═╗╦ ╦╔╗╔╔╦╗┬─┐┌─┐
+ // ║╣ ╚═╗║ ║║║║ ║ ├┬┘│
+ // o╚═╝╚═╝╩═╝╩╝╚╝ ╩ ┴└─└─┘
+ // A set of basic code conventions designed to encourage quality and consistency
+ // across your Sails app's code base. These rules are checked against
+ // automatically any time you run `npm test`.
+ //
+ // > An additional eslintrc override file is included in the `assets/` folder
+ // > right out of the box. This is specifically to allow for variations in acceptable
+ // > global variables between front-end JavaScript code designed to run in the browser
+ // > vs. backend code designed to run in a Node.js/Sails process.
+ //
+ // > Note: If you're using mocha, you'll want to add an extra override file to your
+ // > `test/` folder so that eslint will tolerate mocha-specific globals like `before`
+ // > and `describe`.
+ // Designed for ESLint v4.
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // For more information about any of the rules below, check out the relevant
+ // reference page on eslint.org. For example, to get details on "no-sequences",
+ // you would visit `http://eslint.org/docs/rules/no-sequences`. If you're unsure
+ // or could use some advice, come by https://sailsjs.com/support.
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ "env": {
+ "node": true
+ },
+
+ "parserOptions": {
+ "ecmaVersion": 2018
+ },
+
+ "globals": {
+ // If "no-undef" is enabled below, be sure to list all global variables that
+ // are used in this app's backend code (including the globalIds of models):
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ "Promise": true,
+ "sails": true,
+ "_": true,
+
+ // Models:
+ "User": true,
+ "UndeployedProfile": true,
+ "UndeployedScript": true
+
+ // …and any others.
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ },
+
+ "rules": {
+ "block-scoped-var": ["error"],
+ "callback-return": ["error", ["done", "proceed", "next", "onwards", "callback", "cb"]],
+ "camelcase": ["warn", {"properties":"always"}],
+ "comma-style": ["warn", "last"],
+ "curly": ["warn"],
+ "eqeqeq": ["error", "always"],
+ "eol-last": ["warn"],
+ "handle-callback-err": ["error"],
+ "indent": ["warn", 2, {
+ "SwitchCase": 1,
+ "MemberExpression": "off",
+ "FunctionDeclaration": {"body":1, "parameters":"off"},
+ "FunctionExpression": {"body":1, "parameters":"off"},
+ "CallExpression": {"arguments":"off"},
+ "ArrayExpression": 1,
+ "ObjectExpression": 1,
+ "ignoredNodes": ["ConditionalExpression"]
+ }],
+ "linebreak-style": ["error", "unix"],
+ "no-dupe-keys": ["error"],
+ "no-duplicate-case": ["error"],
+ "no-extra-semi": ["warn"],
+ "no-labels": ["error"],
+ "no-mixed-spaces-and-tabs": [2, "smart-tabs"],
+ "no-redeclare": ["warn"],
+ "no-return-assign": ["error", "always"],
+ "no-sequences": ["error"],
+ "no-trailing-spaces": ["warn"],
+ "no-undef": ["error"],
+ "no-unexpected-multiline": ["warn"],
+ "no-unreachable": ["warn"],
+ "no-unused-vars": ["warn", {"caughtErrors":"all", "caughtErrorsIgnorePattern": "^unused($|[A-Z].*$)", "argsIgnorePattern": "^unused($|[A-Z].*$)", "varsIgnorePattern": "^unused($|[A-Z].*$)" }],
+ "no-use-before-define": ["error", {"functions":false}],
+ "one-var": ["warn", "never"],
+ "prefer-arrow-callback": ["warn", {"allowNamedFunctions":true}],
+ "quotes": ["warn", "single", {"avoidEscape":false, "allowTemplateLiterals":true}],
+ "semi": ["warn", "always"],
+ "semi-spacing": ["warn", {"before":false, "after":true}],
+ "semi-style": ["warn", "last"]
+ }
+
+}
diff --git a/ee/bulk-operations-dashboard/.gitignore b/ee/bulk-operations-dashboard/.gitignore
new file mode 100644
index 000000000000..a1ddb80e79e8
--- /dev/null
+++ b/ee/bulk-operations-dashboard/.gitignore
@@ -0,0 +1,134 @@
+################################################
+# ┌─┐┬┌┬┐╦╔═╗╔╗╔╔═╗╦═╗╔═╗
+# │ ┬│ │ ║║ ╦║║║║ ║╠╦╝║╣
+# o└─┘┴ ┴ ╩╚═╝╝╚╝╚═╝╩╚═╚═╝
+#
+# > Files to exclude from your app's repo.
+#
+# This file (`.gitignore`) is only relevant if
+# you are using git.
+#
+# It exists to signify to git that certain files
+# and/or directories should be ignored for the
+# purposes of version control.
+#
+# This keeps tmp files and sensitive credentials
+# from being uploaded to your repository. And
+# it allows you to configure your app for your
+# machine without accidentally committing settings
+# which will smash the local settings of other
+# developers on your team.
+#
+# Some reasonable defaults are included below,
+# but, of course, you should modify/extend/prune
+# to fit your needs!
+#
+################################################
+
+
+################################################
+# Local Configuration
+#
+# Explicitly ignore files which contain:
+#
+# 1. Sensitive information you'd rather not push to
+# your git repository.
+# e.g., your personal API keys or passwords.
+#
+# 2. Developer-specific configuration
+# Basically, anything that would be annoying
+# to have to change every time you do a
+# `git pull` on your laptop.
+# e.g. your local development database, or
+# the S3 bucket you're using for file uploads
+# during development.
+#
+################################################
+
+config/local.js
+
+
+################################################
+# Dependencies
+#
+#
+# When releasing a production app, you _could_
+# hypothetically include your node_modules folder
+# in your git repo, but during development, it
+# is always best to exclude it, since different
+# developers may be working on different kernels,
+# where dependencies would need to be recompiled
+# anyway.
+#
+# Most of the time, the node_modules folder can
+# be excluded from your code repository, even
+# in production, thanks to features like the
+# package-lock.json file / NPM shrinkwrap.
+#
+# But no matter what, since this is a Sails app,
+# you should always push up the package-lock.json
+# or shrinkwrap file to your repository, to avoid
+# accidentally pulling in upgraded dependencies
+# and breaking your code.
+#
+# That said, if you are having trouble with
+# dependencies, (particularly when using
+# `npm link`) this can be pretty discouraging.
+# But rather than just adding the lockfile to
+# your .gitignore, try this first:
+# ```
+# rm -rf node_modules
+# rm package-lock.json
+# npm install
+# ```
+#
+# [?] For more tips/advice, come by and say hi
+# over at https://sailsjs.com/support
+#
+################################################
+
+node_modules
+
+
+################################################
+#
+# > Do you use bower?
+# > re: the bower_components dir, see this:
+# > http://addyosmani.com/blog/checking-in-front-end-dependencies/
+# > (credit Addy Osmani, @addyosmani)
+#
+################################################
+
+
+################################################
+# Temporary files generated by Sails/Waterline.
+################################################
+
+.tmp
+
+
+################################################
+# Miscellaneous
+#
+# Common files generated by text editors,
+# operating systems, file systems, dbs, etc.
+################################################
+
+*~
+*#
+.DS_STORE
+.netbeans
+nbproject
+.idea
+*.iml
+.vscode
+.node_history
+dump.rdb
+
+npm-debug.log
+lib-cov
+*.seed
+*.log
+*.out
+*.pid
+
diff --git a/ee/bulk-operations-dashboard/.htmlhintrc b/ee/bulk-operations-dashboard/.htmlhintrc
new file mode 100644
index 000000000000..c9b2ee72b6df
--- /dev/null
+++ b/ee/bulk-operations-dashboard/.htmlhintrc
@@ -0,0 +1,27 @@
+{
+ "alt-require": true,
+ "attr-lowercase": ["viewBox"],
+ "attr-no-duplication": true,
+ "attr-unsafe-chars": true,
+ "attr-value-double-quotes": true,
+ "attr-value-not-empty": false,
+ "csslint": false,
+ "doctype-first": false,
+ "doctype-html5": true,
+ "head-script-disabled": false,
+ "href-abs-or-rel": false,
+ "id-class-ad-disabled": true,
+ "id-class-value": false,
+ "id-unique": true,
+ "inline-script-disabled": true,
+ "inline-style-disabled": false,
+ "jshint": false,
+ "space-tab-mixed-disabled": "space",
+ "spec-char-escape": false,
+ "src-not-empty": true,
+ "style-disabled": false,
+ "tag-pair": true,
+ "tag-self-close": false,
+ "tagname-lowercase": true,
+ "title-require": false
+}
diff --git a/ee/bulk-operations-dashboard/.lesshintrc b/ee/bulk-operations-dashboard/.lesshintrc
new file mode 100644
index 000000000000..6dcb60f2787f
--- /dev/null
+++ b/ee/bulk-operations-dashboard/.lesshintrc
@@ -0,0 +1,46 @@
+{
+ // ╦ ╔═╗╔═╗╔═╗╦ ╦╦╔╗╔╔╦╗┬─┐┌─┐
+ // ║ ║╣ ╚═╗╚═╗╠═╣║║║║ ║ ├┬┘│
+ // o╩═╝╚═╝╚═╝╚═╝╩ ╩╩╝╚╝ ╩ ┴└─└─┘
+ // Configuration designed for the lesshint linter. Describes a loose set of LESS
+ // conventions that help avoid typos, unexpected failed builds, and hard-to-debug
+ // selector and CSS rule issues.
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // For more information about any of the rules below, check out the reference page
+ // of all rules at https://github.com/lesshint/lesshint/blob/v6.3.6/lib/linters/README.md
+ // If you're unsure or could use some advice, come by https://sailsjs.com/support.
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ "singleLinePerSelector": false,
+ "singleLinePerProperty": false,
+ "zeroUnit": false,
+ "idSelector": false,
+ "propertyOrdering": false,
+ "spaceAroundBang": false,
+ "fileExtensions": [".less", ".css"],
+ "excludedFiles": ["vendor.less"],
+ "importPath": false,
+ "borderZero": false,
+ "hexLength": false,
+ "hexNotation": false,
+ "newlineAfterBlock": false,
+ "spaceBeforeBrace": {
+ "style": "one_space"
+ },
+ "spaceAfterPropertyName": false,
+ "spaceAfterPropertyColon": {
+ "enabled": true,
+ "style": "one_space"
+ },
+ "maxCharPerLine": false,
+ "emptyRule": false,
+ "importantRule": true,
+ "qualifyingElement": false
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ // ^^ This last one is only disabled because the lesshint parser seems to have
+ // a hard time distinguishing between things like `div.bar` and `&.bar`.
+ // In this case, the ampersand has a distinct meaning, and it does not refer
+ // to an element. (It's referring to the case where that class is matched at
+ // the parent level, rather than talking about a descendant.)
+ // https://github.com/lesshint/lesshint/blob/v6.3.6/lib/linters/README.md#qualifyingelement
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+}
diff --git a/ee/bulk-operations-dashboard/.npmrc b/ee/bulk-operations-dashboard/.npmrc
new file mode 100644
index 000000000000..43601ce8db37
--- /dev/null
+++ b/ee/bulk-operations-dashboard/.npmrc
@@ -0,0 +1,11 @@
+######################
+# ╔╗╔╔═╗╔╦╗┬─┐┌─┐ #
+# ║║║╠═╝║║║├┬┘│ #
+# o╝╚╝╩ ╩ ╩┴└─└─┘ #
+######################
+
+# Hide NPM log output unless it is related to an error of some kind:
+loglevel=error
+
+# Make "npm audit" an opt-in thing for subsequent installs within this app:
+audit=false
diff --git a/ee/bulk-operations-dashboard/.sailsrc b/ee/bulk-operations-dashboard/.sailsrc
new file mode 100644
index 000000000000..782d995af6ba
--- /dev/null
+++ b/ee/bulk-operations-dashboard/.sailsrc
@@ -0,0 +1,12 @@
+{
+ "hooks": {
+ "sockets": false
+ },
+ "generators": {
+ "modules": {}
+ },
+ "_generatedWith": {
+ "sails": "1.5.11",
+ "sails-generate": "2.0.11"
+ }
+}
diff --git a/ee/bulk-operations-dashboard/Dockerfile b/ee/bulk-operations-dashboard/Dockerfile
new file mode 100644
index 000000000000..c68b6f335a8e
--- /dev/null
+++ b/ee/bulk-operations-dashboard/Dockerfile
@@ -0,0 +1,26 @@
+# Use the official Node.js 14 image as a base
+FROM node:20@sha256:e06aae17c40c7a6b5296ca6f942a02e6737ae61bbbf3e2158624bb0f887991b5
+
+# Set the working directory in the container
+WORKDIR /usr/src/app
+
+# Copy the package.json
+COPY package.json ./
+
+# Install vulnerability dashboard dependencies
+RUN npm install
+
+# Copy the vulnerability dashboard into the container
+COPY . .
+
+# Copy the entrypoint script into the container
+COPY entrypoint.sh /usr/src/app/entrypoint.sh
+
+# Make sure the entrypoint script is executable
+RUN chmod +x /usr/src/app/entrypoint.sh
+
+# Expose the port the vulnerability dashboard runs on
+EXPOSE 1337
+
+# Set the entrypoint script as the entry point
+ENTRYPOINT ["/usr/src/app/entrypoint.sh"]
diff --git a/ee/bulk-operations-dashboard/Gruntfile.js b/ee/bulk-operations-dashboard/Gruntfile.js
new file mode 100644
index 000000000000..e3b284733824
--- /dev/null
+++ b/ee/bulk-operations-dashboard/Gruntfile.js
@@ -0,0 +1,23 @@
+/**
+ * Gruntfile
+ *
+ * This Node script is executed when you run `grunt`-- and also when
+ * you run `sails lift` (provided the grunt hook is installed and
+ * hasn't been disabled).
+ *
+ * WARNING:
+ * Unless you know what you're doing, you shouldn't change this file.
+ * Check out the `tasks/` directory instead.
+ *
+ * For more information see:
+ * https://sailsjs.com/anatomy/Gruntfile.js
+ */
+module.exports = function(grunt) {
+
+ var loadGruntTasks = require('sails-hook-grunt/accessible/load-grunt-tasks');
+
+ // Load Grunt task configurations (from `tasks/config/`) and Grunt
+ // task registrations (from `tasks/register/`).
+ loadGruntTasks(__dirname, grunt);
+
+};
diff --git a/ee/bulk-operations-dashboard/README.md b/ee/bulk-operations-dashboard/README.md
new file mode 100644
index 000000000000..678396b623f3
--- /dev/null
+++ b/ee/bulk-operations-dashboard/README.md
@@ -0,0 +1,49 @@
+# Bulk operations dashboard
+
+
+A dashboard to easily manage profiles and scripts across multiple teams on a Fleet instance.
+
+
+## Dependencies
+
+- A datastore, this app was built using Postgres, but you can use a database of your choice.
+
+- A Redis database - For session storage.
+
+
+## Configuration
+
+This app has two required custom configuration values:
+
+- `sails.config.custom.fleetBaseUrl`: The full URL of your Fleet instance. (e.g., https://fleet.example.com)
+
+- `sails.config.custom.fleetApiToken`: An API token for an API-only user on your Fleet instance.
+
+
+
+## Running the bulk operations dashboard with Docker.
+
+To run a local bulk operations dashboard with docker, you can follow these instructions.
+
+1. Clone this repo
+2. Update the following ENV variables `ee/bulk-operations-dashboard/docker-compose.yml` file:
+
+ 1. `sails_custom__fleetBaseUrl`: The full URL of your Fleet instance. (e.g., https://fleet.example.com)
+
+ 2. `sails_custom__fleetApiToken`: An API token for an API-only user on your Fleet instance.
+
+ >You can read about how to create an API-only user and get it's token [here](https://fleetdm.com/docs/using-fleet/fleetctl-cli#create-api-only-user)
+
+3. Open the `ee/bulk-operations-dashboard/` folder in your terminal.
+
+4. Run `docker compose up --build` to build the bulk operations dashboard's Docker image.
+
+ > The first time the bulk operations dashboard starts it will Initalize the database aby running the `config/bootstrap.js` script before the server starts.
+
+5. Once the container is done building, the bulk operations dashboard will be available at http://localhost:1337
+
+ > You can login with the default admin login:
+ >
+ >- Email address: `admin@example.com`
+ >
+ >- Password: `abc123`
diff --git a/ee/bulk-operations-dashboard/api/controllers/account/logout.js b/ee/bulk-operations-dashboard/api/controllers/account/logout.js
new file mode 100644
index 000000000000..61e50d8c1995
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/account/logout.js
@@ -0,0 +1,55 @@
+module.exports = {
+
+
+ friendlyName: 'Logout',
+
+
+ description: 'Log out of this app.',
+
+
+ extendedDescription:
+`This action deletes the \`req.session.userId\` key from the session of the requesting user agent.
+Actual garbage collection of session data depends on this app's session store, and
+potentially also on the [TTL configuration](https://sailsjs.com/docs/reference/configuration/sails-config-session)
+you provided for it.
+
+Note that this action does not check to see whether or not the requesting user was
+actually logged in. (If they weren't, then this action is just a no-op.)`,
+
+
+ exits: {
+
+ success: {
+ description: 'The requesting user agent has been successfully logged out.'
+ },
+
+ redirect: {
+ description: 'The requesting user agent looks to be a web browser.',
+ extendedDescription: 'After logging out from a web browser, the user is redirected away.',
+ responseType: 'redirect'
+ }
+
+ },
+
+
+ fn: async function () {
+
+ // Clear the `userId` property from this session.
+ delete this.req.session.userId;
+
+ // Broadcast a message that we can display in other open tabs.
+ if (sails.hooks.sockets) {
+ await sails.helpers.broadcastSessionChange(this.req);
+ }
+
+ // Then finish up, sending an appropriate response.
+ // > Under the covers, this persists the now-logged-out session back
+ // > to the underlying session store.
+ if (!this.req.wantsJSON) {
+ throw {redirect: '/login'};
+ }
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/account/update-billing-card.js b/ee/bulk-operations-dashboard/api/controllers/account/update-billing-card.js
new file mode 100644
index 000000000000..7cbeac5bab10
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/account/update-billing-card.js
@@ -0,0 +1,79 @@
+module.exports = {
+
+
+ friendlyName: 'Update billing card',
+
+
+ description: 'Update the credit card for the logged-in user.',
+
+
+ inputs: {
+
+ stripeToken: {
+ type: 'string',
+ example: 'tok_199k3qEXw14QdSnRwmsK99MH',
+ description: 'The single-use Stripe Checkout token identifier representing the user\'s payment source (i.e. credit card.)',
+ extendedDescription: 'Omit this (or use "") to remove this user\'s payment source.',
+ whereToGet: {
+ description: 'This Stripe.js token is provided to the front-end (client-side) code after completing a Stripe Checkout or Stripe Elements flow.'
+ }
+ },
+
+ billingCardLast4: {
+ type: 'string',
+ example: '4242',
+ description: 'Omit if removing card info.',
+ whereToGet: { description: 'Credit card info is provided by Stripe after completing the checkout flow.' }
+ },
+
+ billingCardBrand: {
+ type: 'string',
+ example: 'visa',
+ description: 'Omit if removing card info.',
+ whereToGet: { description: 'Credit card info is provided by Stripe after completing the checkout flow.' }
+ },
+
+ billingCardExpMonth: {
+ type: 'string',
+ example: '08',
+ description: 'Omit if removing card info.',
+ whereToGet: { description: 'Credit card info is provided by Stripe after completing the checkout flow.' }
+ },
+
+ billingCardExpYear: {
+ type: 'string',
+ example: '2023',
+ description: 'Omit if removing card info.',
+ whereToGet: { description: 'Credit card info is provided by Stripe after completing the checkout flow.' }
+ },
+
+ },
+
+
+ fn: async function ({stripeToken, billingCardLast4, billingCardBrand, billingCardExpMonth, billingCardExpYear}) {
+
+ // Add, update, or remove the default payment source for the logged-in user's
+ // customer entry in Stripe.
+ var stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({
+ stripeCustomerId: this.req.me.stripeCustomerId,
+ token: stripeToken || '',
+ }).timeout(5000).retry();
+
+ // Update (or clear) the card info we have stored for this user in our database.
+ // > Remember, never store complete card numbers-- only the last 4 digits + expiration!
+ // > Storing (or even receiving) complete, unencrypted card numbers would require PCI
+ // > compliance in the U.S.
+ await User.updateOne({ id: this.req.me.id })
+ .set({
+ stripeCustomerId,
+ hasBillingCard: stripeToken ? true : false,
+ billingCardBrand: stripeToken ? billingCardBrand : '',
+ billingCardLast4: stripeToken ? billingCardLast4 : '',
+ billingCardExpMonth: stripeToken ? billingCardExpMonth : '',
+ billingCardExpYear: stripeToken ? billingCardExpYear : ''
+ });
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/account/update-password.js b/ee/bulk-operations-dashboard/api/controllers/account/update-password.js
new file mode 100644
index 000000000000..00a53a38d676
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/account/update-password.js
@@ -0,0 +1,35 @@
+module.exports = {
+
+
+ friendlyName: 'Update password',
+
+
+ description: 'Update the password for the logged-in user.',
+
+
+ inputs: {
+
+ password: {
+ description: 'The new, unencrypted password.',
+ example: 'abc123v2',
+ required: true
+ }
+
+ },
+
+
+ fn: async function ({password}) {
+
+ // Hash the new password.
+ var hashed = await sails.helpers.passwords.hashPassword(password);
+
+ // Update the record for the logged-in user.
+ await User.updateOne({ id: this.req.me.id })
+ .set({
+ password: hashed
+ });
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/account/update-profile.js b/ee/bulk-operations-dashboard/api/controllers/account/update-profile.js
new file mode 100644
index 000000000000..afc5fd1b5477
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/account/update-profile.js
@@ -0,0 +1,160 @@
+module.exports = {
+
+
+ friendlyName: 'Update profile',
+
+
+ description: 'Update the profile for the logged-in user.',
+
+
+ inputs: {
+
+ fullName: {
+ type: 'string'
+ },
+
+ emailAddress: {
+ type: 'string'
+ },
+
+ },
+
+
+ exits: {
+
+ emailAlreadyInUse: {
+ statusCode: 409,
+ description: 'The provided email address is already in use.',
+ },
+
+ },
+
+
+ fn: async function ({fullName, emailAddress}) {
+
+ var newEmailAddress = emailAddress;
+ if (newEmailAddress !== undefined) {
+ newEmailAddress = newEmailAddress.toLowerCase();
+ }
+
+ // Determine if this request wants to change the current user's email address,
+ // revert her pending email address change, modify her pending email address
+ // change, or if the email address won't be affected at all.
+ var desiredEmailEffect;// ('change-immediately', 'begin-change', 'cancel-pending-change', 'modify-pending-change', or '')
+ if (
+ newEmailAddress === undefined ||
+ (this.req.me.emailStatus !== 'change-requested' && newEmailAddress === this.req.me.emailAddress) ||
+ (this.req.me.emailStatus === 'change-requested' && newEmailAddress === this.req.me.emailChangeCandidate)
+ ) {
+ desiredEmailEffect = '';
+ } else if (this.req.me.emailStatus === 'change-requested' && newEmailAddress === this.req.me.emailAddress) {
+ desiredEmailEffect = 'cancel-pending-change';
+ } else if (this.req.me.emailStatus === 'change-requested' && newEmailAddress !== this.req.me.emailAddress) {
+ desiredEmailEffect = 'modify-pending-change';
+ } else if (!sails.config.custom.verifyEmailAddresses || this.req.me.emailStatus === 'unconfirmed') {
+ desiredEmailEffect = 'change-immediately';
+ } else {
+ desiredEmailEffect = 'begin-change';
+ }
+
+
+ // If the email address is changing, make sure it is not already being used.
+ if (_.contains(['begin-change', 'change-immediately', 'modify-pending-change'], desiredEmailEffect)) {
+ let conflictingUser = await User.findOne({
+ or: [
+ { emailAddress: newEmailAddress },
+ { emailChangeCandidate: newEmailAddress }
+ ]
+ });
+ if (conflictingUser) {
+ throw 'emailAlreadyInUse';
+ }
+ }
+
+
+ // Start building the values to set in the db.
+ // (We always set the fullName if provided.)
+ var valuesToSet = {
+ fullName,
+ };
+
+ switch (desiredEmailEffect) {
+
+ // Change now
+ case 'change-immediately':
+ _.extend(valuesToSet, {
+ emailAddress: newEmailAddress,
+ emailChangeCandidate: '',
+ emailProofToken: '',
+ emailProofTokenExpiresAt: 0,
+ emailStatus: this.req.me.emailStatus === 'unconfirmed' ? 'unconfirmed' : 'confirmed'
+ });
+ break;
+
+ // Begin new email change, or modify a pending email change
+ case 'begin-change':
+ case 'modify-pending-change':
+ _.extend(valuesToSet, {
+ emailChangeCandidate: newEmailAddress,
+ emailProofToken: await sails.helpers.strings.random('url-friendly'),
+ emailProofTokenExpiresAt: Date.now() + sails.config.custom.emailProofTokenTTL,
+ emailStatus: 'change-requested'
+ });
+ break;
+
+ // Cancel pending email change
+ case 'cancel-pending-change':
+ _.extend(valuesToSet, {
+ emailChangeCandidate: '',
+ emailProofToken: '',
+ emailProofTokenExpiresAt: 0,
+ emailStatus: 'confirmed'
+ });
+ break;
+
+ // Otherwise, do nothing re: email
+ }
+
+ // Save to the db
+ await User.updateOne({id: this.req.me.id })
+ .set(valuesToSet);
+
+ // If this is an immediate change, and billing features are enabled,
+ // then also update the billing email for this user's linked customer entry
+ // in the Stripe API to make sure they receive email receipts.
+ // > Note: If there was not already a Stripe customer entry for this user,
+ // > then one will be set up implicitly, so we'll need to persist it to our
+ // > database. (This could happen if Stripe credentials were not configured
+ // > at the time this user was originally created.)
+ if(desiredEmailEffect === 'change-immediately' && sails.config.custom.enableBillingFeatures) {
+ let didNotAlreadyHaveCustomerId = (! this.req.me.stripeCustomerId);
+ let stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({
+ stripeCustomerId: this.req.me.stripeCustomerId,
+ emailAddress: newEmailAddress
+ }).timeout(5000).retry();
+ if (didNotAlreadyHaveCustomerId){
+ await User.updateOne({ id: this.req.me.id })
+ .set({
+ stripeCustomerId
+ });
+ }
+ }
+
+ // If an email address change was requested, and re-confirmation is required,
+ // send the "confirm account" email.
+ if (desiredEmailEffect === 'begin-change' || desiredEmailEffect === 'modify-pending-change') {
+ await sails.helpers.sendTemplateEmail.with({
+ to: newEmailAddress,
+ subject: 'Your account has been updated',
+ template: 'email-verify-new-email',
+ templateData: {
+ fullName: fullName||this.req.me.fullName,
+ token: valuesToSet.emailProofToken
+ }
+ });
+ }
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/account/view-account-overview.js b/ee/bulk-operations-dashboard/api/controllers/account/view-account-overview.js
new file mode 100644
index 000000000000..f5841f99813d
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/account/view-account-overview.js
@@ -0,0 +1,30 @@
+module.exports = {
+
+
+ friendlyName: 'View account overview',
+
+
+ description: 'Display "Account Overview" page.',
+
+
+ exits: {
+
+ success: {
+ viewTemplatePath: 'pages/account/account-overview',
+ }
+
+ },
+
+
+ fn: async function () {
+
+ // If billing features are enabled, include our configured Stripe.js
+ // public key in the view locals. Otherwise, leave it as undefined.
+ return {
+ stripePublishableKey: sails.config.custom.enableBillingFeatures? sails.config.custom.stripePublishableKey : undefined,
+ };
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/account/view-edit-password.js b/ee/bulk-operations-dashboard/api/controllers/account/view-edit-password.js
new file mode 100644
index 000000000000..208a1d6a3fbc
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/account/view-edit-password.js
@@ -0,0 +1,26 @@
+module.exports = {
+
+
+ friendlyName: 'View edit password',
+
+
+ description: 'Display "Edit password" page.',
+
+
+ exits: {
+
+ success: {
+ viewTemplatePath: 'pages/account/edit-password'
+ }
+
+ },
+
+
+ fn: async function () {
+
+ return {};
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/account/view-edit-profile.js b/ee/bulk-operations-dashboard/api/controllers/account/view-edit-profile.js
new file mode 100644
index 000000000000..baea0f7c206d
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/account/view-edit-profile.js
@@ -0,0 +1,26 @@
+module.exports = {
+
+
+ friendlyName: 'View edit profile',
+
+
+ description: 'Display "Edit profile" page.',
+
+
+ exits: {
+
+ success: {
+ viewTemplatePath: 'pages/account/edit-profile',
+ }
+
+ },
+
+
+ fn: async function () {
+
+ return {};
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/entrance/confirm-email.js b/ee/bulk-operations-dashboard/api/controllers/entrance/confirm-email.js
new file mode 100644
index 000000000000..01afe04cd968
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/entrance/confirm-email.js
@@ -0,0 +1,160 @@
+module.exports = {
+
+
+ friendlyName: 'Confirm email',
+
+
+ description:
+`Confirm a new user's email address, or an existing user's request for an email address change,
+then redirect to either a special landing page (for newly-signed up users), or the account page
+(for existing users who just changed their email address).`,
+
+
+ inputs: {
+
+ token: {
+ description: 'The confirmation token from the email.',
+ example: '4-32fad81jdaf$329'
+ }
+
+ },
+
+
+ exits: {
+
+ success: {
+ description: 'Email address confirmed and requesting user logged in.'
+ },
+
+ redirect: {
+ description: 'Email address confirmed and requesting user logged in. Since this looks like a browser, redirecting...',
+ responseType: 'redirect'
+ },
+
+ invalidOrExpiredToken: {
+ responseType: 'expired',
+ description: 'The provided token is expired, invalid, or already used up.',
+ },
+
+ emailAddressNoLongerAvailable: {
+ statusCode: 409,
+ viewTemplatePath: '500',
+ description: 'The email address is no longer available.',
+ extendedDescription: 'This is an edge case that is not always anticipated by websites and APIs. Since it is pretty rare, the 500 server error page is used as a simple catch-all. If this becomes important in the future, this could easily be expanded into a custom error page or resolution flow. But for context: this behavior of showing the 500 server error page mimics how popular apps like Slack behave under the same circumstances.',
+ }
+
+ },
+
+
+ fn: async function ({token}) {
+
+ // If no token was provided, this is automatically invalid.
+ if (!token) {
+ throw 'invalidOrExpiredToken';
+ }
+
+ // Get the user with the matching email token.
+ var user = await User.findOne({ emailProofToken: token });
+
+ // If no such user exists, or their token is expired, bail.
+ if (!user || user.emailProofTokenExpiresAt <= Date.now()) {
+ throw 'invalidOrExpiredToken';
+ }
+
+ if (user.emailStatus === 'unconfirmed') {
+ // ┌─┐┌─┐┌┐┌┌─┐┬┬─┐┌┬┐┬┌┐┌┌─┐ ╔═╗╦╦═╗╔═╗╔╦╗ ╔╦╗╦╔╦╗╔═╗ ╦ ╦╔═╗╔═╗╦═╗ ┌─┐┌┬┐┌─┐┬┬
+ // │ │ ││││├┤ │├┬┘││││││││ ┬ ╠╣ ║╠╦╝╚═╗ ║───║ ║║║║║╣ ║ ║╚═╗║╣ ╠╦╝ ├┤ │││├─┤││
+ // └─┘└─┘┘└┘└ ┴┴└─┴ ┴┴┘└┘└─┘ ╚ ╩╩╚═╚═╝ ╩ ╩ ╩╩ ╩╚═╝ ╚═╝╚═╝╚═╝╩╚═ └─┘┴ ┴┴ ┴┴┴─┘
+ // If this is a new user confirming their email for the first time,
+ // then just update the state of their user record in the database,
+ // store their user id in the session (just in case they aren't logged
+ // in already), and then redirect them to the "email confirmed" page.
+ await User.updateOne({ id: user.id }).set({
+ emailStatus: 'confirmed',
+ emailProofToken: '',
+ emailProofTokenExpiresAt: 0
+ });
+ this.req.session.userId = user.id;
+
+ // In case there was an existing session, broadcast a message that we can
+ // display in other open tabs.
+ if (sails.hooks.sockets) {
+ await sails.helpers.broadcastSessionChange(this.req);
+ }
+
+ if (this.req.wantsJSON) {
+ return;
+ } else {
+ throw { redirect: '/email/confirmed' };
+ }
+
+ } else if (user.emailStatus === 'change-requested') {
+ // ┌─┐┌─┐┌┐┌┌─┐┬┬─┐┌┬┐┬┌┐┌┌─┐ ╔═╗╦ ╦╔═╗╔╗╔╔═╗╔═╗╔╦╗ ┌─┐┌┬┐┌─┐┬┬
+ // │ │ ││││├┤ │├┬┘││││││││ ┬ ║ ╠═╣╠═╣║║║║ ╦║╣ ║║ ├┤ │││├─┤││
+ // └─┘└─┘┘└┘└ ┴┴└─┴ ┴┴┘└┘└─┘ ╚═╝╩ ╩╩ ╩╝╚╝╚═╝╚═╝═╩╝ └─┘┴ ┴┴ ┴┴┴─┘
+ if (!user.emailChangeCandidate){
+ throw new Error(`Consistency violation: Could not update Stripe customer because this user record's emailChangeCandidate ("${user.emailChangeCandidate}") is missing. (This should never happen.)`);
+ }
+
+ // Last line of defense: since email change candidates are not protected
+ // by a uniqueness constraint in the database, it's important that we make
+ // sure no one else managed to grab this email in the mean time since we
+ // last checked its availability. (This is a relatively rare edge case--
+ // see exit description.)
+ if (await User.count({ emailAddress: user.emailChangeCandidate }) > 0) {
+ throw 'emailAddressNoLongerAvailable';
+ }
+
+ // If billing features are enabled, also update the billing email for this
+ // user's linked customer entry in the Stripe API to make sure they receive
+ // email receipts.
+ // > Note: If there was not already a Stripe customer entry for this user,
+ // > then one will be set up implicitly, so we'll need to persist it to our
+ // > database. (This could happen if Stripe credentials were not configured
+ // > at the time this user was originally created.)
+ if(sails.config.custom.enableBillingFeatures) {
+ let didNotAlreadyHaveCustomerId = (! user.stripeCustomerId);
+ let stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({
+ stripeCustomerId: user.stripeCustomerId,
+ emailAddress: user.emailChangeCandidate
+ }).timeout(5000).retry();
+ if (didNotAlreadyHaveCustomerId){
+ await User.updateOne({ id: user.id }).set({
+ stripeCustomerId
+ });
+ }
+ }
+
+ // Finally update the user in the database, store their id in the session
+ // (just in case they aren't logged in already), then redirect them to
+ // their "my account" page so they can see their updated email address.
+ await User.updateOne({ id: user.id })
+ .set({
+ emailStatus: 'confirmed',
+ emailProofToken: '',
+ emailProofTokenExpiresAt: 0,
+ emailAddress: user.emailChangeCandidate,
+ emailChangeCandidate: '',
+ });
+ this.req.session.userId = user.id;
+
+ // In case there was an existing session, broadcast a message that we can
+ // display in other open tabs.
+ if (sails.hooks.sockets) {
+ await sails.helpers.broadcastSessionChange(this.req);
+ }
+
+ if (this.req.wantsJSON) {
+ return;
+ } else {
+ throw { redirect: '/account' };
+ }
+
+ } else {
+ throw new Error(`Consistency violation: User ${user.id} has an email proof token, but somehow also has an emailStatus of "${user.emailStatus}"! (This should never happen.)`);
+ }
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/entrance/login.js b/ee/bulk-operations-dashboard/api/controllers/entrance/login.js
new file mode 100644
index 000000000000..a95aabf84c79
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/entrance/login.js
@@ -0,0 +1,119 @@
+module.exports = {
+
+
+ friendlyName: 'Login',
+
+
+ description: 'Log in using the provided email and password combination.',
+
+
+ extendedDescription:
+`This action attempts to look up the user record in the database with the
+specified email address. Then, if such a user exists, it uses
+bcrypt to compare the hashed password from the database with the provided
+password attempt.`,
+
+
+ inputs: {
+
+ emailAddress: {
+ description: 'The email to try in this attempt, e.g. "irl@example.com".',
+ type: 'string',
+ required: true
+ },
+
+ password: {
+ description: 'The unencrypted password to try in this attempt, e.g. "passwordlol".',
+ type: 'string',
+ required: true
+ },
+
+ rememberMe: {
+ description: 'Whether to extend the lifetime of the user\'s session.',
+ extendedDescription:
+`Note that this is NOT SUPPORTED when using virtual requests (e.g. sending
+requests over WebSockets instead of HTTP).`,
+ type: 'boolean'
+ }
+
+ },
+
+
+ exits: {
+
+ success: {
+ description: 'The requesting user agent has been successfully logged in.',
+ extendedDescription:
+`Under the covers, this stores the id of the logged-in user in the session
+as the \`userId\` key. The next time this user agent sends a request, assuming
+it includes a cookie (like a web browser), Sails will automatically make this
+user id available as req.session.userId in the corresponding action. (Also note
+that, thanks to the included "custom" hook, when a relevant request is received
+from a logged-in user, that user's entire record from the database will be fetched
+and exposed as \`req.me\`.)`
+ },
+
+ badCombo: {
+ description: `The provided email and password combination does not
+ match any user in the database.`,
+ responseType: 'unauthorized'
+ // ^This uses the custom `unauthorized` response located in `api/responses/unauthorized.js`.
+ // To customize the generic "unauthorized" response across this entire app, change that file
+ // (see api/responses/unauthorized).
+ //
+ // To customize the response for _only this_ action, replace `responseType` with
+ // something else. For example, you might set `statusCode: 498` and change the
+ // implementation below accordingly (see http://sailsjs.com/docs/concepts/controllers).
+ }
+
+ },
+
+
+ fn: async function ({emailAddress, password, rememberMe}) {
+
+ // Look up by the email address.
+ // (note that we lowercase it to ensure the lookup is always case-insensitive,
+ // regardless of which database we're using)
+ var userRecord = await User.findOne({
+ emailAddress: emailAddress.toLowerCase(),
+ });
+
+ // If there was no matching user, respond thru the "badCombo" exit.
+ if(!userRecord) {
+ throw 'badCombo';
+ }
+
+ // If the password doesn't match, then also exit thru "badCombo".
+ await sails.helpers.passwords.checkPassword(password, userRecord.password)
+ .intercept('incorrect', 'badCombo');
+
+ // If "Remember Me" was enabled, then keep the session alive for
+ // a longer amount of time. (This causes an updated "Set Cookie"
+ // response header to be sent as the result of this request -- thus
+ // we must be dealing with a traditional HTTP request in order for
+ // this to work.)
+ if (rememberMe) {
+ if (this.req.isSocket) {
+ sails.log.warn(
+ 'Received `rememberMe: true` from a virtual request, but it was ignored\n'+
+ 'because a browser\'s session cookie cannot be reset over sockets.\n'+
+ 'Please use a traditional HTTP request instead.'
+ );
+ } else {
+ this.req.session.cookie.maxAge = sails.config.custom.rememberMeCookieMaxAge;
+ }
+ }//fi
+
+ // Modify the active session instance.
+ // (This will be persisted when the response is sent.)
+ this.req.session.userId = userRecord.id;
+
+ // In case there was an existing session (e.g. if we allow users to go to the login page
+ // when they're already logged in), broadcast a message that we can display in other open tabs.
+ if (sails.hooks.sockets) {
+ await sails.helpers.broadcastSessionChange(this.req);
+ }
+
+ }
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/entrance/send-password-recovery-email.js b/ee/bulk-operations-dashboard/api/controllers/entrance/send-password-recovery-email.js
new file mode 100644
index 000000000000..21d8f0efa287
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/entrance/send-password-recovery-email.js
@@ -0,0 +1,66 @@
+module.exports = {
+
+
+ friendlyName: 'Send password recovery email',
+
+
+ description: 'Send a password recovery notification to the user with the specified email address.',
+
+
+ inputs: {
+
+ emailAddress: {
+ description: 'The email address of the alleged user who wants to recover their password.',
+ example: 'rydahl@example.com',
+ type: 'string',
+ required: true
+ }
+
+ },
+
+
+ exits: {
+
+ success: {
+ description: 'The email address might have matched a user in the database. (If so, a recovery email was sent.)'
+ },
+
+ },
+
+
+ fn: async function ({emailAddress}) {
+
+ // Find the record for this user.
+ // (Even if no such user exists, pretend it worked to discourage sniffing.)
+ var userRecord = await User.findOne({ emailAddress });
+ if (!userRecord) {
+ return;
+ }//•
+
+ // Come up with a pseudorandom, probabilistically-unique token for use
+ // in our password recovery email.
+ var token = await sails.helpers.strings.random('url-friendly');
+
+ // Store the token on the user record
+ // (This allows us to look up the user when the link from the email is clicked.)
+ await User.updateOne({ id: userRecord.id })
+ .set({
+ passwordResetToken: token,
+ passwordResetTokenExpiresAt: Date.now() + sails.config.custom.passwordResetTokenTTL,
+ });
+
+ // Send recovery email
+ await sails.helpers.sendTemplateEmail.with({
+ to: emailAddress,
+ subject: 'Password reset instructions',
+ template: 'email-reset-password',
+ templateData: {
+ fullName: userRecord.fullName,
+ token: token
+ }
+ });
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/entrance/signup.js b/ee/bulk-operations-dashboard/api/controllers/entrance/signup.js
new file mode 100644
index 000000000000..e1fd133c593a
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/entrance/signup.js
@@ -0,0 +1,127 @@
+module.exports = {
+
+
+ friendlyName: 'Signup',
+
+
+ description: 'Sign up for a new user account.',
+
+
+ extendedDescription:
+`This creates a new user record in the database, signs in the requesting user agent
+by modifying its [session](https://sailsjs.com/documentation/concepts/sessions), and
+(if emailing with Mailgun is enabled) sends an account verification email.
+
+If a verification email is sent, the new user's account is put in an "unconfirmed" state
+until they confirm they are using a legitimate email address (by clicking the link in
+the account verification message.)`,
+
+
+ inputs: {
+
+ emailAddress: {
+ required: true,
+ type: 'string',
+ isEmail: true,
+ description: 'The email address for the new account, e.g. m@example.com.',
+ extendedDescription: 'Must be a valid email address.',
+ },
+
+ password: {
+ required: true,
+ type: 'string',
+ maxLength: 200,
+ example: 'passwordlol',
+ description: 'The unencrypted password to use for the new account.'
+ },
+
+ fullName: {
+ required: true,
+ type: 'string',
+ example: 'Frida Kahlo de Rivera',
+ description: 'The user\'s full name.',
+ }
+
+ },
+
+
+ exits: {
+
+ success: {
+ description: 'New user account was created successfully.'
+ },
+
+ invalid: {
+ responseType: 'badRequest',
+ description: 'The provided fullName, password and/or email address are invalid.',
+ extendedDescription: 'If this request was sent from a graphical user interface, the request '+
+ 'parameters should have been validated/coerced _before_ they were sent.'
+ },
+
+ emailAlreadyInUse: {
+ statusCode: 409,
+ description: 'The provided email address is already in use.',
+ },
+
+ },
+
+
+ fn: async function ({emailAddress, password, fullName}) {
+
+ var newEmailAddress = emailAddress.toLowerCase();
+
+ // Build up data for the new user record and save it to the database.
+ // (Also use `fetch` to retrieve the new ID so that we can use it below.)
+ var newUserRecord = await User.create(_.extend({
+ fullName,
+ emailAddress: newEmailAddress,
+ password: await sails.helpers.passwords.hashPassword(password),
+ tosAcceptedByIp: this.req.ip
+ }, sails.config.custom.verifyEmailAddresses? {
+ emailProofToken: await sails.helpers.strings.random('url-friendly'),
+ emailProofTokenExpiresAt: Date.now() + sails.config.custom.emailProofTokenTTL,
+ emailStatus: 'unconfirmed'
+ }:{}))
+ .intercept('E_UNIQUE', 'emailAlreadyInUse')
+ .intercept({name: 'UsageError'}, 'invalid')
+ .fetch();
+
+ // If billing feaures are enabled, save a new customer entry in the Stripe API.
+ // Then persist the Stripe customer id in the database.
+ if (sails.config.custom.enableBillingFeatures) {
+ let stripeCustomerId = await sails.helpers.stripe.saveBillingInfo.with({
+ emailAddress: newEmailAddress
+ }).timeout(5000).retry();
+ await User.updateOne({id: newUserRecord.id})
+ .set({
+ stripeCustomerId
+ });
+ }
+
+ // Store the user's new id in their session.
+ this.req.session.userId = newUserRecord.id;
+
+ // In case there was an existing session (e.g. if we allow users to go to the signup page
+ // when they're already logged in), broadcast a message that we can display in other open tabs.
+ if (sails.hooks.sockets) {
+ await sails.helpers.broadcastSessionChange(this.req);
+ }
+
+ if (sails.config.custom.verifyEmailAddresses) {
+ // Send "confirm account" email
+ await sails.helpers.sendTemplateEmail.with({
+ to: newEmailAddress,
+ subject: 'Please confirm your account',
+ template: 'email-verify-account',
+ templateData: {
+ fullName,
+ token: newUserRecord.emailProofToken
+ }
+ });
+ } else {
+ sails.log.info('Skipping new account email verification... (since `verifyEmailAddresses` is disabled)');
+ }
+
+ }
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/entrance/update-password-and-login.js b/ee/bulk-operations-dashboard/api/controllers/entrance/update-password-and-login.js
new file mode 100644
index 000000000000..51a75b8746d6
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/entrance/update-password-and-login.js
@@ -0,0 +1,80 @@
+module.exports = {
+
+
+ friendlyName: 'Update password and login',
+
+
+ description: 'Finish the password recovery flow by setting the new password and '+
+ 'logging in the requesting user, based on the authenticity of their token.',
+
+
+ inputs: {
+
+ password: {
+ description: 'The new, unencrypted password.',
+ example: 'abc123v2',
+ required: true
+ },
+
+ token: {
+ description: 'The password token that was generated by the `sendPasswordRecoveryEmail` endpoint.',
+ example: 'gwa8gs8hgw9h2g9hg29hgwh9asdgh9q34$$$$$asdgasdggds',
+ required: true
+ }
+
+ },
+
+
+ exits: {
+
+ success: {
+ description: 'Password successfully updated, and requesting user agent is now logged in.'
+ },
+
+ invalidToken: {
+ description: 'The provided password token is invalid, expired, or has already been used.',
+ responseType: 'expired'
+ }
+
+ },
+
+
+ fn: async function ({password, token}) {
+
+ if(!token) {
+ throw 'invalidToken';
+ }
+
+ // Look up the user with this reset token.
+ var userRecord = await User.findOne({ passwordResetToken: token });
+
+ // If no such user exists, or their token is expired, bail.
+ if (!userRecord || userRecord.passwordResetTokenExpiresAt <= Date.now()) {
+ throw 'invalidToken';
+ }
+
+ // Hash the new password.
+ var hashed = await sails.helpers.passwords.hashPassword(password);
+
+ // Store the user's new password and clear their reset token so it can't be used again.
+ await User.updateOne({ id: userRecord.id })
+ .set({
+ password: hashed,
+ passwordResetToken: '',
+ passwordResetTokenExpiresAt: 0
+ });
+
+ // Log the user in.
+ // (This will be persisted when the response is sent.)
+ this.req.session.userId = userRecord.id;
+
+ // In case there was an existing session, broadcast a message that we can
+ // display in other open tabs.
+ if (sails.hooks.sockets) {
+ await sails.helpers.broadcastSessionChange(this.req);
+ }
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/entrance/view-confirmed-email.js b/ee/bulk-operations-dashboard/api/controllers/entrance/view-confirmed-email.js
new file mode 100644
index 000000000000..0602f10e9936
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/entrance/view-confirmed-email.js
@@ -0,0 +1,27 @@
+module.exports = {
+
+
+ friendlyName: 'View confirmed email',
+
+
+ description: 'Display "Confirmed email" page.',
+
+
+ exits: {
+
+ success: {
+ viewTemplatePath: 'pages/entrance/confirmed-email'
+ }
+
+ },
+
+
+ fn: async function () {
+
+ // Respond with view.
+ return {};
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/entrance/view-forgot-password.js b/ee/bulk-operations-dashboard/api/controllers/entrance/view-forgot-password.js
new file mode 100644
index 000000000000..e6b5404c9f3d
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/entrance/view-forgot-password.js
@@ -0,0 +1,36 @@
+module.exports = {
+
+
+ friendlyName: 'View forgot password',
+
+
+ description: 'Display "Forgot password" page.',
+
+
+ exits: {
+
+ success: {
+ viewTemplatePath: 'pages/entrance/forgot-password',
+ },
+
+ redirect: {
+ description: 'The requesting user is already logged in.',
+ extendedDescription: 'Logged-in users should change their password in "Account settings."',
+ responseType: 'redirect',
+ }
+
+ },
+
+
+ fn: async function () {
+
+ if (this.req.me) {
+ throw {redirect: '/'};
+ }
+
+ return {};
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/entrance/view-login.js b/ee/bulk-operations-dashboard/api/controllers/entrance/view-login.js
new file mode 100644
index 000000000000..1d1c590b53bc
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/entrance/view-login.js
@@ -0,0 +1,35 @@
+module.exports = {
+
+
+ friendlyName: 'View login',
+
+
+ description: 'Display "Login" page.',
+
+
+ exits: {
+
+ success: {
+ viewTemplatePath: 'pages/entrance/login',
+ },
+
+ redirect: {
+ description: 'The requesting user is already logged in.',
+ responseType: 'redirect'
+ }
+
+ },
+
+
+ fn: async function () {
+
+ if (this.req.me) {
+ throw {redirect: '/'};
+ }
+
+ return {};
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/entrance/view-new-password.js b/ee/bulk-operations-dashboard/api/controllers/entrance/view-new-password.js
new file mode 100644
index 000000000000..4532fa5feff0
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/entrance/view-new-password.js
@@ -0,0 +1,57 @@
+module.exports = {
+
+
+ friendlyName: 'View new password',
+
+
+ description: 'Display "New password" page.',
+
+
+ inputs: {
+
+ token: {
+ description: 'The password reset token from the email.',
+ example: '4-32fad81jdaf$329'
+ }
+
+ },
+
+
+ exits: {
+
+ success: {
+ viewTemplatePath: 'pages/entrance/new-password'
+ },
+
+ invalidOrExpiredToken: {
+ responseType: 'expired',
+ description: 'The provided token is expired, invalid, or has already been used.',
+ }
+
+ },
+
+
+ fn: async function ({token}) {
+
+ // If password reset token is missing, display an error page explaining that the link is bad.
+ if (!token) {
+ sails.log.warn('Attempting to view new password (recovery) page, but no reset password token included in request! Displaying error page...');
+ throw 'invalidOrExpiredToken';
+ }//•
+
+ // Look up the user with this reset token.
+ var userRecord = await User.findOne({ passwordResetToken: token });
+ // If no such user exists, or their token is expired, display an error page explaining that the link is bad.
+ if (!userRecord || userRecord.passwordResetTokenExpiresAt <= Date.now()) {
+ throw 'invalidOrExpiredToken';
+ }
+
+ // Grab token and include it in view locals
+ return {
+ token,
+ };
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/entrance/view-signup.js b/ee/bulk-operations-dashboard/api/controllers/entrance/view-signup.js
new file mode 100644
index 000000000000..be43753770d4
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/entrance/view-signup.js
@@ -0,0 +1,35 @@
+module.exports = {
+
+
+ friendlyName: 'View signup',
+
+
+ description: 'Display "Signup" page.',
+
+
+ exits: {
+
+ success: {
+ viewTemplatePath: 'pages/entrance/signup',
+ },
+
+ redirect: {
+ description: 'The requesting user is already logged in.',
+ responseType: 'redirect'
+ }
+
+ },
+
+
+ fn: async function () {
+
+ if (this.req.me) {
+ throw {redirect: '/'};
+ }
+
+ return {};
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/get-profiles.js b/ee/bulk-operations-dashboard/api/controllers/get-profiles.js
new file mode 100644
index 000000000000..7326fea9aef3
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/get-profiles.js
@@ -0,0 +1,113 @@
+module.exports = {
+
+
+ friendlyName: 'Get profiles',
+
+
+ description: 'Builds and returns an array of deployed configuration profiles on the Fleet instance and undeployed profiles stored in the dashboard\'s datastore.',
+
+ exits: {
+ success: {
+ outputType: [{}],
+ }
+ },
+
+
+ fn: async function () {
+ // Get all teams on the Fleet instance.
+ let teamsResponseData = await sails.helpers.http.get.with({
+ url: '/api/v1/fleet/teams',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ })
+ .timeout(120000)
+ .retry(['requestFailed', {name: 'TimeoutError'}]);
+
+ let allTeams = teamsResponseData.teams;
+
+ let teams = [];
+ for(let team of allTeams) {
+ teams.push({
+ fleetApid: team.id,
+ teamName: team.name,
+ });
+ }
+ // Add the "team" for hosts with no team
+ teams.push({
+ fleetApid: 0,
+ teamName: 'No team',
+ });
+
+
+ let allProfiles = [];
+ let teamApids = _.pluck(allTeams, 'id');
+ // Get all of the configuration profiles on the Fleet instance.
+ for(let teamApid of teamApids){
+ let configurationProfilesResponseData = await sails.helpers.http.get.with({
+ url: `/api/v1/fleet/configuration_profiles?team_id=${teamApid}`,
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ })
+ .timeout(120000)
+ .retry(['requestFailed', {name: 'TimeoutError'}]);
+ let profilesForThisTeam = configurationProfilesResponseData.profiles;
+ allProfiles = allProfiles.concat(profilesForThisTeam);
+ }
+
+ // Add the configurations profiles that are assigned to the "no team" team.
+ let noTeamConfigurationProfilesResponseData = await sails.helpers.http.get.with({
+ url: '/api/v1/fleet/configuration_profiles',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ })
+ .timeout(120000)
+ .retry(['requestFailed', {name: 'TimeoutError'}]);
+ let profilesForThisTeam = noTeamConfigurationProfilesResponseData.profiles;
+ allProfiles = allProfiles.concat(profilesForThisTeam);
+
+ // console.log(allProfiles);
+
+ let profilesOnThisFleetInstance = [];
+ // Group configuration profiles by their identifier.
+ let allProfilesByIdentifier = _.groupBy(allProfiles, 'identifier');
+ for(let profileIdentifier in allProfilesByIdentifier) {
+ // Iterate through the arrays of profiles with the same unique identifier.
+ let teamsForThisProfile = [];
+ // Add the profile's UUID and information about the team this profile is assigned to the teams array for profiles.
+ for(let profile of allProfilesByIdentifier[profileIdentifier]){
+ let informationAboutThisProfile = {
+ uuid: profile.profile_uuid,
+ fleetApid: profile.team_id,
+ teamName: _.find(teams, {fleetApid: profile.team_id}).teamName,
+ };
+ teamsForThisProfile.push(informationAboutThisProfile);
+ }
+ let profile = allProfilesByIdentifier[profileIdentifier][0];// Grab the first profile returned in the api repsonse to build our profile configuration.
+ let profileInformation = {
+ name: profile.name,
+ identifier: profileIdentifier,
+ platform: profile.platform,
+ createdAt: new Date(profile.created_at).getTime(),
+ teams: teamsForThisProfile
+ };
+ profilesOnThisFleetInstance.push(profileInformation);
+ }
+ // Get the undeployed profiles from the app's database.
+ let undeployedProfiles = await UndeployedProfile.find();
+ profilesOnThisFleetInstance = _.union(profilesOnThisFleetInstance, undeployedProfiles);
+
+ // Sort profiles by their name.
+ profilesOnThisFleetInstance = _.sortByOrder(profilesOnThisFleetInstance, 'name', 'asc');
+
+ return profilesOnThisFleetInstance;
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/get-scripts.js b/ee/bulk-operations-dashboard/api/controllers/get-scripts.js
new file mode 100644
index 000000000000..1900b9b700dd
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/get-scripts.js
@@ -0,0 +1,120 @@
+module.exports = {
+
+
+ friendlyName: 'Get scripts',
+
+
+ description: 'Builds and returns an array of deployed scripts on the Fleet instance and undeployed scripts stored in the dashboard\'s datastore',
+
+
+ exits: {
+ success: {
+ outputType: [{}],
+ }
+ },
+
+
+ fn: async function () {
+ // Get all teams on the Fleet instance.
+ let teamsResponseData = await sails.helpers.http.get.with({
+ url: '/api/v1/fleet/teams',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ })
+ .timeout(120000)
+ .retry(['requestFailed', {name: 'TimeoutError'}]);
+
+ let allTeams = teamsResponseData.teams;
+
+ let teamApids = _.pluck(allTeams, 'id');
+ let teams = [];
+ for(let team of allTeams) {
+ teams.push({
+ fleetApid: team.id,
+ teamName: team.name,
+ });
+ }
+ // Add the "team" for hosts with no team
+ teams.push({
+ fleetApid: 0,
+ teamName: 'No team',
+ });
+
+
+ let allScripts = [];
+ // Get all of the scripts on a Fleet instance.
+ for(let teamApid of teamApids){
+ let scriptsResponseData = await sails.helpers.http.get.with({
+ url: `/api/v1/fleet/scripts?team_id=${teamApid}`,
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ })
+ .timeout(120000)
+ .retry(['requestFailed', {name: 'TimeoutError'}]);
+ let scriptsForThisTeam = scriptsResponseData.scripts;
+ if(scriptsForThisTeam !== null) {
+ allScripts = allScripts.concat(scriptsForThisTeam);
+ }
+ }
+
+ // Grab all of the configuration scripts on the Fleet instance.
+ let noTeamConfigurationScriptsResponseData = await sails.helpers.http.get.with({
+ url: '/api/v1/fleet/scripts',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ })
+ .timeout(120000)
+ .retry(['requestFailed', {name: 'TimeoutError'}]);
+ let scriptsForThisTeam = noTeamConfigurationScriptsResponseData.scripts;
+
+ if(scriptsForThisTeam !== null){
+ allScripts = allScripts.concat(scriptsForThisTeam);
+ }
+
+ // If there are no scripts on the Fleet instance, return an empty array and the teams information.
+ if(allScripts === [ null ]){
+ return {scripts: [], teams};
+ }
+ let scriptsOnThisFleetInstance = [];
+
+ let allScriptsByIdentifier = _.groupBy(allScripts, 'name');
+ for(let scriptIdentifier in allScriptsByIdentifier) {
+ if(scriptIdentifier === null){
+ continue;
+ }
+ let teamsForThisProfile = [];
+ for(let script of allScriptsByIdentifier[scriptIdentifier]){
+ let informationAboutThisScript = {
+ scriptFleetApid: script.id,
+ fleetApid: script.team_id ? script.team_id : 0,
+ teamName: script.team_id ? _.find(teams, {fleetApid: script.team_id}).teamName : 'No team',
+ };
+ teamsForThisProfile.push(informationAboutThisScript);
+ }
+ let script = allScriptsByIdentifier[scriptIdentifier][0];// Grab the first script returned in the api repsonse to build our script configuration.
+ let scriptInformation = {
+ name: script.name,
+ identifier: scriptIdentifier,
+ platform: _.endsWith(script.name, 'sh') ? 'macOS & Linux' : 'Windows',
+ createdAt: new Date(script.created_at).getTime(),
+ teams: teamsForThisProfile
+ };
+ scriptsOnThisFleetInstance.push(scriptInformation);
+ }
+ // Get the undeployed scripts from the app's database.
+ let undeployedScripts = await UndeployedScript.find();
+ scriptsOnThisFleetInstance = _.union(scriptsOnThisFleetInstance, undeployedScripts);
+ // Sort scripts by their name.
+ scriptsOnThisFleetInstance = _.sortByOrder(scriptsOnThisFleetInstance, 'name', 'asc');
+ return scriptsOnThisFleetInstance;
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/legal/view-privacy.js b/ee/bulk-operations-dashboard/api/controllers/legal/view-privacy.js
new file mode 100644
index 000000000000..6960be873f16
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/legal/view-privacy.js
@@ -0,0 +1,27 @@
+module.exports = {
+
+
+ friendlyName: 'View privacy',
+
+
+ description: 'Display "Privacy policy" page.',
+
+
+ exits: {
+
+ success: {
+ viewTemplatePath: 'pages/legal/privacy'
+ }
+
+ },
+
+
+ fn: async function () {
+
+ // All done.
+ return;
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/legal/view-terms.js b/ee/bulk-operations-dashboard/api/controllers/legal/view-terms.js
new file mode 100644
index 000000000000..643f04408afd
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/legal/view-terms.js
@@ -0,0 +1,27 @@
+module.exports = {
+
+
+ friendlyName: 'View terms',
+
+
+ description: 'Display "Legal terms" page.',
+
+
+ exits: {
+
+ success: {
+ viewTemplatePath: 'pages/legal/terms'
+ }
+
+ },
+
+
+ fn: async function () {
+
+ // All done.
+ return;
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/profiles/delete-profile.js b/ee/bulk-operations-dashboard/api/controllers/profiles/delete-profile.js
new file mode 100644
index 000000000000..3a974058695a
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/profiles/delete-profile.js
@@ -0,0 +1,46 @@
+module.exports = {
+
+
+ friendlyName: 'Delete profile',
+
+
+ description: '',
+
+
+ inputs: {
+ profile: {
+ type: {},
+ description: 'The configuration profile that will be deleted.',
+ required: true,
+ }
+ },
+
+
+ exits: {
+
+ },
+
+
+ fn: async function ({profile}) {
+ // If the provided profile does not have a teams array and has an ID, it is an undeployed profile that will be deleted.
+ if(profile.id && !profile.teams){
+ await UndeployedProfile.destroy({id: profile.id});
+ } else {// Otherwise, this is a deployed profile, and we'll use information from the teams array to remove the profile.
+ for(let team of profile.teams){
+ await sails.helpers.http.sendHttpRequest.with({
+ method: 'DELETE',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ url: `/api/v1/fleet/configuration_profiles/${team.uuid}`,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`,
+ }
+ });
+ }
+ }
+ // All done.
+ return;
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/profiles/download-profile.js b/ee/bulk-operations-dashboard/api/controllers/profiles/download-profile.js
new file mode 100644
index 000000000000..ebbf569d52e7
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/profiles/download-profile.js
@@ -0,0 +1,78 @@
+module.exports = {
+
+
+ friendlyName: 'Download profile',
+
+
+ description: 'Download profile file (returning a stream).',
+
+
+ inputs: {
+ id: {
+ type: 'number',
+ description: 'The database ID of the undeployed profile to download.'
+ },
+ uuid: {
+ type: 'string',
+ description: 'The uuid of a profile on a team.'
+ },
+ },
+
+
+ exits: {
+ success: {
+ outputFriendlyName: 'File',
+ outputDescription: 'The streaming bytes of the file.',
+ outputType: 'ref'
+ },
+
+ notFound: {
+ description: 'No profile exists with the specified ID or UUID.',
+ responseType: 'notFound'
+ },
+ },
+
+
+ fn: async function ({id, uuid}) {
+ if(!uuid && !id){
+ return this.res.badRequest();
+ }
+ let datePrefix = new Date().toISOString();
+ datePrefix = datePrefix.split('T')[0] +'_';
+ let profileContents;
+ let filename;
+ let download;
+ if(id){
+ let profileToDownload = await UndeployedProfile.findOne({id: id});
+
+ filename = datePrefix + profileToDownload.name + profileToDownload.profileType;
+ profileContents = profileToDownload.profileContents;
+ if(profileToDownload.profileType === '.mobileconfig'){
+ this.res.type('application/x-apple-aspen-config');
+ } else {
+ this.res.type('application/octet-stream');
+ }
+ download = profileContents;
+ } else {
+ let profileDownloadResponse = await sails.helpers.http.sendHttpRequest.with({
+ method: 'GET',
+ url: `${sails.config.custom.fleetBaseUrl}/api/v1/fleet/configuration_profiles/${uuid}?alt=media`,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ });
+ let contentDispositionHeader = profileDownloadResponse.headers['content-disposition'];
+ let filenameMatch = contentDispositionHeader.replace(/^attachment;filename="(.+?)"$/, '$1');
+ filename = filenameMatch;
+ let contentType = profileDownloadResponse.headers['content-type'];
+ download = profileDownloadResponse.body;
+ this.res.type(contentType);
+ }
+ this.res.attachment(filename);
+ // All done.
+ return download;
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/profiles/edit-profile.js b/ee/bulk-operations-dashboard/api/controllers/profiles/edit-profile.js
new file mode 100644
index 000000000000..bede01b44f79
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/profiles/edit-profile.js
@@ -0,0 +1,204 @@
+module.exports = {
+
+
+ friendlyName: 'Edit profile',
+
+
+ description: 'Edits the teams a profile is assigned to and/or replaces the file on the Fleet instance if the new file\'s profile identifier matches',
+
+ files: ['newProfile'],
+
+ inputs: {
+ profile: {
+ type: {},
+ description: 'The configuration profile that is being editted',
+ required: true,
+ },
+ newTeamIds: {
+ type: ['string'],
+ description: 'An array of teams that this profile will be deployed on or Undefined if the profile is being removed from a team.'
+ },
+ newProfile: {
+ type: 'ref',
+ description: 'A file that will be replacing the profile.'
+ },
+ },
+
+
+ exits: {
+ payloadIdentifierDoesNotMatch: {
+ statusCode: 409,
+ description: 'The new profiles bundle indentifer does not match the existing profile',
+ }
+ },
+
+
+
+ fn: async function ({profile, newTeamIds, newProfile}) {
+ if(newProfile.isNoop){
+ newProfile.noMoreFiles();
+ newProfile = undefined;
+ }
+ // ╔═╗╔═╗╔╦╗ ╔═╗╦═╗╔═╗╔═╗╦╦ ╔═╗
+ // ║ ╦║╣ ║ ╠═╝╠╦╝║ ║╠╣ ║║ ║╣
+ // ╚═╝╚═╝ ╩ ╩ ╩╚═╚═╝╚ ╩╩═╝╚═╝
+ let profileContents; // The raw text contents of a profile file.
+ let filename;
+ let extension;
+ // If there is not a new profile, and the profile is deployed (has teams array === deployed), download the profile to be able to add it to other teams.
+ if(!newProfile && profile.teams){
+ // console.log('Existing deployed profile!');
+ let profileUuid = profile.teams[0].uuid;
+ let profileDownloadResponse = await sails.helpers.http.sendHttpRequest.with({
+ method: 'GET',
+ url: `${sails.config.custom.fleetBaseUrl}/api/v1/fleet/configuration_profiles/${profileUuid}?alt=media`,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ });
+ let contentDispositionHeader = profileDownloadResponse.headers['content-disposition'];
+ let filenameMatch = contentDispositionHeader.match(/filename="(.+?)"/);
+ filename = filenameMatch[1];
+ extension = '.'+filename.split('.').pop();
+ profileContents = profileDownloadResponse.body;
+ } else if(newProfile) {// Otherwise, if there is a new profile file uploaded, check that the payload identifier maches the existing profile on the Fleet instance.
+ // console.log('Replacing an existing(/undeployed) profile!');
+ let file = await sails.reservoir(newProfile);
+ profileContents = file[0].contentBytes;
+ let profileFileName = file[0].name;
+ filename = profileFileName.replace(/^\d{4}-\d{2}-\d{2}_/, '').replace(/\.[^/.]+$/, '');
+ extension = '.'+profileFileName.split('.').pop();
+ let profilePlatform = 'darwin';
+ if(_.endsWith(profileFileName, '.xml')) {
+ profilePlatform = 'windows';
+ }
+ if(newTeamIds && profile.teams && profilePlatform === 'darwin'){
+ let existingProfileInfo = await sails.helpers.http.get.with({
+ url: `${sails.config.custom.fleetBaseUrl}/api/v1/fleet/configuration_profiles/${profile.teams[0].uuid}?alt=media`,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ });
+ let newProfileBundleIdentifier = profileContents.match(/PayloadIdentifier<\/key>\s*(.*?)<\/string>/)[1];
+ let existingProfileBundleIdentifier = existingProfileInfo.match(/PayloadIdentifier<\/key>\s*(.*?)<\/string>/)[1];
+ // Note: We're using the _.startsWith method to check that the identifier is the same. The identifiers returned by the Fleet instance are
+ if(existingProfileBundleIdentifier !== newProfileBundleIdentifier){
+ throw 'payloadIdentifierDoesNotMatch';
+ }
+ }
+ } else if (!newProfile && !profile.teams){// Undeployed profiles are stored in the app's database.
+ // console.log('editing an undeployed profile!');
+ profileContents = profile.profileContents;
+ filename = profile.name;
+ extension = profile.profileType;
+ }
+ // ╔═╗╔═╗╔═╗╦╔═╗╔╗╔ ╔═╗╦═╗╔═╗╔═╗╦╦ ╔═╗
+ // ╠═╣╚═╗╚═╗║║ ╦║║║ ╠═╝╠╦╝║ ║╠╣ ║║ ║╣
+ // ╩ ╩╚═╝╚═╝╩╚═╝╝╚╝ ╩ ╩╚═╚═╝╚ ╩╩═╝╚═╝
+ if(!newProfile){
+ // If we're changing the teams for an existing profile, we'll remove this profile from any team not included in the newTeamIds array.
+ let currentProfileTeamIds = _.pluck(profile.teams, 'fleetApid');
+ let addedTeams = _.difference(newTeamIds, currentProfileTeamIds);
+ let removedTeams = _.difference(currentProfileTeamIds, newTeamIds);
+ let removedTeamsInfo = _.filter(profile.teams, (team)=>{
+ return removedTeams.includes(team.fleetApid);
+ });
+ for(let team of removedTeamsInfo){
+ // console.log(`removing ${profile.name} from team id ${team.teamName}`);
+ await sails.helpers.http.sendHttpRequest.with({
+ method: 'DELETE',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ url: `/api/v1/fleet/configuration_profiles/${team.uuid}`,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`,
+ }
+ });
+ }
+ for(let teamApid of addedTeams){
+ // console.log(`Adding ${profile.name} to team id ${teamApid}`);
+ await sails.helpers.http.sendHttpRequest.with({
+ method: 'POST',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ url: `/api/v1/fleet/configuration_profiles?team_id=${teamApid}`,
+ enctype: 'multipart/form-data',
+ body: {
+ team_id: teamApid,// eslint-disable-line camelcase
+ profile: {
+ options: {
+ filename: filename + extension,
+ contentType: 'application/octet-stream'
+ },
+ value: profileContents,
+ }
+ },
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`,
+ },
+ });
+ }// After every added team
+ } else {
+ if(profile.teams) {
+ // If there is a new profile uploaded, we will need to delete the old profiles, and add the new profile.
+ for(let team of profile.teams) {
+ // console.log(`removing ${profile.name} from team id ${team.teamName}`);
+ await sails.helpers.http.sendHttpRequest.with({
+ method: 'DELETE',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ url: `/api/v1/fleet/configuration_profiles/${team.uuid}`,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`,
+ }
+ });
+ }
+ }
+ for(let teamApid of newTeamIds){
+ // console.log(`Adding ${profile.name} to team id ${teamApid}`);
+ await sails.helpers.http.sendHttpRequest.with({
+ method: 'POST',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ url: `/api/v1/fleet/configuration_profiles?team_id=${teamApid}`,
+ enctype: 'multipart/form-data',
+ body: {
+ team_id: teamApid,// eslint-disable-line camelcase
+ profile: {
+ options: {
+ filename: filename + extension,
+ contentType: 'application/octet-stream'
+ },
+ value: profileContents,
+ }
+ },
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`,
+ },
+ });
+ }// After every added team
+
+ }
+ // If this profile has an ID, then it is a database record, and we will delete it if it has been deployed to a team.
+ if(profile.id && newTeamIds.length > 0){
+ // console.log('Undeployed profile has been deployed. deleting DB record!');
+ await UndeployedProfile.destroy({id: profile.id});
+ } else if(!profile.id && newTeamIds.length === 0){
+ // If this is not a database record of a profile, and the profile is being undeployed from all teams, we'll create a databse record for it.
+ // console.log('Creating database record for a (now) undeployed profile!');
+ await UndeployedProfile.create({
+ name: profile.name,
+ platform: extension === '.xml' ? 'windows' : 'darwin',
+ profileContents,
+ profileType: extension,
+ });
+ } else if(profile.id && newProfile){
+ // If there is a new profile that is replacing a database record, update the profileContents in the database.
+ // console.log('Updating existing undeployed profile!');
+ await UndeployedProfile.updateOne({id: profile.id}).set({
+ profileContents,
+ });
+ }
+ // All done.
+ return;
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/profiles/upload-profile.js b/ee/bulk-operations-dashboard/api/controllers/profiles/upload-profile.js
new file mode 100644
index 000000000000..19c847bf62d5
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/profiles/upload-profile.js
@@ -0,0 +1,120 @@
+module.exports = {
+
+
+ friendlyName: 'Upload profile',
+
+
+ description: '',
+
+ files: ['newProfile'],
+
+ inputs: {
+ newProfile: {
+ type: 'ref',
+ description: 'An Upstream with an incoming file upload.',
+ required: true,
+ },
+ teams: {
+ type: ['string'],
+ description: 'An array of team IDs that this profile will be added to'
+ }
+ },
+
+
+ exits: {
+ success: {
+ outputDescription: 'The new profile has been uploaded',
+ outputType: {},
+ },
+
+ noFileAttached: {
+ description: 'No file was attached.',
+ responseType: 'badRequest'
+ },
+
+ tooBig: {
+ description: 'The file is too big.',
+ responseType: 'badRequest'
+ },
+
+ },
+
+
+ fn: async function ({newProfile, teams}) {
+ let util = require('util');
+ let profile = await sails.reservoir(newProfile)
+ .intercept('E_EXCEEDS_UPLOAD_LIMIT', 'tooBig')
+ .intercept((err)=>new Error('The configuration profile upload failed. '+util.inspect(err)));
+ if(!profile) {
+ throw 'noFileAttached';
+ }
+ let profileContents = profile[0].contentBytes;
+ let profileFileName = profile[0].name;
+ let datelessExtensionlessFilename = profileFileName.replace(/^\d{4}-\d{2}-\d{2}_/, '').replace(/\.[^/.]+$/, '');
+ let extension = '.'+profileFileName.split('.').pop();
+ let profilePlatform = 'darwin';
+ if(_.endsWith(profileFileName, '.xml')) {
+ profilePlatform = 'windows';
+ }
+
+ let profileToReturn;
+ let newProfileInfo = {
+ name: datelessExtensionlessFilename,
+ platform: profilePlatform,
+ profileType: extension,
+ createdAt: Date.now(),
+ };
+ if(!teams) {
+ newProfileInfo.profileContents = profileContents;
+ profileToReturn = await UndeployedProfile.create(newProfileInfo).fetch();
+ } else {
+ let newTeams = [];
+ for(let teamApid of teams){
+ let newProfileResponse = await sails.helpers.http.sendHttpRequest.with({
+ method: 'POST',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ url: `/api/v1/fleet/configuration_profiles?team_id=${teamApid}`,
+ enctype: 'multipart/form-data',
+ body: {
+ team_id: teamApid,// eslint-disable-line camelcase
+ profile: {
+ options: {
+ filename: profileFileName,
+ contentType: 'application/octet-stream'
+ },
+ value: profileContents,
+ }
+ },
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`,
+ },
+ });
+ let parsedJsonResponse = JSON.parse(newProfileResponse.body);
+ let uuidForThisProfile = parsedJsonResponse.profile_uuid;
+ // send a request to the Fleet instance to get the bundleId of the new profile.
+ await sails.helpers.http.get.with({
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ url: `/api/v1/fleet/configuration_profiles/${uuidForThisProfile}`,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`,
+ }
+ });
+ newTeams.push({
+ fleetApid: teamApid,
+ uuid: JSON.parse(newProfileResponse.body).profile_uuid
+ });
+ }
+ newProfileInfo.teams = newTeams;
+ profileToReturn = newProfileInfo;
+ }
+
+
+
+
+ // All done.
+ return profileToReturn;
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/profiles/view-profiles.js b/ee/bulk-operations-dashboard/api/controllers/profiles/view-profiles.js
new file mode 100644
index 000000000000..9ebe8079322f
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/profiles/view-profiles.js
@@ -0,0 +1,114 @@
+module.exports = {
+
+
+ friendlyName: 'View profiles',
+
+
+ description: 'Display "Profiles" page.',
+
+
+ exits: {
+
+ success: {
+ viewTemplatePath: 'pages/profiles'
+ }
+
+ },
+
+
+ fn: async function () {
+
+ // Get all teams on the Fleet instance.
+ let teamsResponseData = await sails.helpers.http.get.with({
+ url: '/api/v1/fleet/teams',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ })
+ .timeout(120000)
+ .retry(['requestFailed', {name: 'TimeoutError'}]);
+
+ let allTeams = teamsResponseData.teams;
+
+ let teams = [];
+ for(let team of allTeams) {
+ teams.push({
+ fleetApid: team.id,
+ teamName: team.name,
+ });
+ }
+ // Add the "team" for hosts with no team
+ teams.push({
+ fleetApid: 0,
+ teamName: 'No team',
+ });
+
+
+ let allProfiles = [];
+ let teamApids = _.pluck(allTeams, 'id');
+ // Get all of the configuration profiles on the Fleet instance.
+ for(let teamApid of teamApids){
+ let configurationProfilesResponseData = await sails.helpers.http.get.with({
+ url: `/api/v1/fleet/configuration_profiles?team_id=${teamApid}`,
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ })
+ .timeout(120000)
+ .retry(['requestFailed', {name: 'TimeoutError'}]);
+ let profilesForThisTeam = configurationProfilesResponseData.profiles;
+ allProfiles = allProfiles.concat(profilesForThisTeam);
+ }
+ // Add the configurations profiles that are assigned to the "no team" team.
+ let noTeamConfigurationProfilesResponseData = await sails.helpers.http.get.with({
+ url: '/api/v1/fleet/configuration_profiles',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ })
+ .timeout(120000)
+ .retry(['requestFailed', {name: 'TimeoutError'}]);
+ let profilesForThisTeam = noTeamConfigurationProfilesResponseData.profiles;
+ allProfiles = allProfiles.concat(profilesForThisTeam);
+ let profilesInformation = [];
+ // Group configuration profiles by their identifier.
+ let allProfilesByIdentifier = _.groupBy(allProfiles, 'identifier');
+ for(let profileIdentifier in allProfilesByIdentifier) {
+ // Iterate through the arrays of profiles with the same unique identifier.
+ let teamsForThisProfile = [];
+ // Add the profile's UUID and information about the team this profile is assigned to the teams array for profiles.
+ for(let profile of allProfilesByIdentifier[profileIdentifier]) {
+ let informationAboutThisProfile = {
+ uuid: profile.profile_uuid,
+ fleetApid: profile.team_id,
+ teamName: _.find(teams, {fleetApid: profile.team_id}).teamName,
+ };
+ teamsForThisProfile.push(informationAboutThisProfile);
+ }
+ let profile = allProfilesByIdentifier[profileIdentifier][0];// Grab the first profile returned in the api repsonse to build our profile configuration.
+ let profileInformation = {
+ name: profile.name,
+ identifier: profileIdentifier,
+ platform: profile.platform,
+ createdAt: new Date(profile.created_at).getTime(),
+ teams: teamsForThisProfile
+ };
+ profilesInformation.push(profileInformation);
+ }
+ // Get the undeployed profiles from the app's database.
+ let undeployedProfiles = await UndeployedProfile.find();
+ profilesInformation = _.union(profilesInformation, undeployedProfiles);
+
+ // Sort profiles by their name.
+ profilesInformation = _.sortByOrder(profilesInformation, 'name', 'asc');
+
+ // Respond with view.
+ return {profiles: profilesInformation, teams};
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/scripts/delete-script.js b/ee/bulk-operations-dashboard/api/controllers/scripts/delete-script.js
new file mode 100644
index 000000000000..f7c38c1664c1
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/scripts/delete-script.js
@@ -0,0 +1,46 @@
+module.exports = {
+
+
+ friendlyName: 'Delete script',
+
+
+ description: '',
+
+
+ inputs: {
+ script: {
+ type: {},
+ description: 'The script that will be deleted.',
+ required: true,
+ }
+ },
+
+
+ exits: {
+
+ },
+
+
+ fn: async function ({script}) {
+ // If the provided script does not have a teams array and has an ID, it is an undeployed script that will be deleted.
+ if(script.id && !script.teams){
+ await UndeployedScript.destroy({id: script.id});
+ } else {
+ for(let teamScript of script.teams){
+ await sails.helpers.http.sendHttpRequest.with({
+ method: 'DELETE',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ url: `/api/v1/fleet/scripts/${teamScript.scriptFleetApid}`,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`,
+ }
+ });
+ }
+ }
+ // All done.
+ return;
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/scripts/download-script.js b/ee/bulk-operations-dashboard/api/controllers/scripts/download-script.js
new file mode 100644
index 000000000000..4c9b7bc809b8
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/scripts/download-script.js
@@ -0,0 +1,76 @@
+module.exports = {
+
+
+ friendlyName: 'Download script',
+
+
+ description: 'Download script file (returning a stream).',
+
+
+ inputs: {
+ fleetApid: {
+ type: 'number',
+ description: 'The Fleet API ID of the script to download.',
+ },
+ id: {
+ type: 'number',
+ description: 'The database ID of the undeployed script to download'
+ }
+ },
+
+
+ exits: {
+ success: {
+ outputFriendlyName: 'File',
+ outputDescription: 'The streaming bytes of the file.',
+ outputType: 'ref'
+ },
+
+ notFound: {
+ description: 'No script exists with the specified ID or UUID.',
+ responseType: 'notFound'
+ },
+ },
+
+
+ fn: async function ({fleetApid, id}) {
+ if(!fleetApid && !id){
+ return this.res.badRequest();
+ }
+ let filename;
+ let download;
+ if(id){
+ let datePrefix = new Date().toISOString();
+ datePrefix = datePrefix.split('T')[0] +'_';
+ let scriptToDownload = await UndeployedScript.findOne({id: id});
+ filename = datePrefix + scriptToDownload.name;
+ let scriptContents = scriptToDownload.scriptContents;
+ if(scriptToDownload.scriptType === '.sh'){
+ this.res.type('application/x-apple-aspen-config');
+ } else {
+ this.res.type('application/octet-stream');
+ }
+ download = scriptContents;
+ } else {
+ let scriptDownloadResponse = await sails.helpers.http.sendHttpRequest.with({
+ method: 'GET',
+ url: `${sails.config.custom.fleetBaseUrl}/api/v1/fleet/scripts/${fleetApid}?alt=media`,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ });
+ let contentDispositionHeader = scriptDownloadResponse.headers['content-disposition'];
+ let filenameMatch = contentDispositionHeader.replace(/^attachment;filename="(.+?)"$/, '$1');
+ filename = filenameMatch;
+ let contentType = scriptDownloadResponse.headers['content-type'];
+ download = scriptDownloadResponse.body;
+ this.res.type(contentType);
+ }
+ this.res.attachment(filename);
+ // All done.
+ return download;
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/scripts/edit-script.js b/ee/bulk-operations-dashboard/api/controllers/scripts/edit-script.js
new file mode 100644
index 000000000000..ec8d30f182e9
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/scripts/edit-script.js
@@ -0,0 +1,197 @@
+module.exports = {
+
+
+ friendlyName: 'Edit script',
+
+
+ description: '',
+
+ files: ['newScript'],
+
+ inputs: {
+ script: {
+ type: {},
+ description: 'The script that is being editted',
+ required: true,
+ },
+ newTeamIds: {
+ type: ['ref'],
+ description: 'An array of teams that this script will be added to.'
+ },
+ newScript: {
+ type: 'ref',
+ description: 'A file that will be replacing the script.'
+ },
+ },
+
+
+ exits: {
+ scriptNameDoesNotMatch: {
+ description: 'The provided replacement script\'s filename does not match the name of the script on the Fleet instance.',
+ statusCode: 400,
+ },
+ },
+
+
+ fn: async function ({script, newTeamIds, newScript}) {
+ if(newScript.isNoop){
+ newScript.noMoreFiles();
+ newScript = undefined;
+ }
+ let scriptContents; // The raw text contents of a script file.
+ let filename;
+ let extension;
+ // If there is not a new script, and the script is deployed (has teams array === deployed), download the script to be able to add it to other teams.
+ if(!newScript && script.teams){
+ let scriptFleetApid = script.teams[0].scriptFleetApid;
+ let scriptDownloadResponse = await sails.helpers.http.sendHttpRequest.with({
+ method: 'GET',
+ url: `${sails.config.custom.fleetBaseUrl}/api/v1/fleet/scripts/${scriptFleetApid}?alt=media`,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ });
+ let contentDispositionHeader = scriptDownloadResponse.headers['content-disposition'];
+ let filenameMatch = contentDispositionHeader.match(/filename="(.+?)"/);
+ filename = filenameMatch[1];
+ extension = '.'+filename.split('.').pop();
+ filename = filename.replace(/^\d{4}-\d{2}-\d{2}[_|\s]?/, '');
+ scriptContents = scriptDownloadResponse.body;
+ } else if(newScript) {
+ let file = await sails.reservoir(newScript);
+ scriptContents = file[0].contentBytes;
+ let scriptFilename = file[0].name;
+ filename = scriptFilename.replace(/^\d{4}-\d{2}-\d{2}[_|\s]?/, '').replace(/\.[^/.]+$/, '');
+ extension = '.'+scriptFilename.split('.').pop();
+ if(script.name !== filename+extension){
+ throw 'scriptNameDoesNotMatch';
+ }
+ } else if (!newScript && !script.teams){// Undeployed profiles are stored in the app's database.
+ // console.log('editing an undeployed profile!');
+ scriptContents = script.scriptContents;
+ filename = script.name;
+ extension = script.scriptType;
+ }
+
+ if(!newScript){
+ let currentScriptTeamIds = _.pluck(script.teams, 'fleetApid');
+ let addedTeams = _.difference(newTeamIds, currentScriptTeamIds);
+ let removedTeams = _.difference(currentScriptTeamIds, newTeamIds);
+ let removedTeamsInfo = _.filter(script.teams, (team)=>{
+ return removedTeams.includes(team.fleetApid);
+ });
+ for(let script of removedTeamsInfo){
+ await sails.helpers.http.sendHttpRequest.with({
+ method: 'DELETE',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ url: `/api/v1/fleet/scripts/${script.scriptFleetApid}`,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`,
+ }
+ });
+ }
+ for(let teamApid of addedTeams){
+ // Build a request body for the team.
+ let requestBodyForThisTeam = {
+ script: {
+ options: {
+ filename: filename,
+ contentType: 'application/octet-stream'
+ },
+ value: scriptContents,
+ }
+ };
+ let addScriptUrl;
+ // If the script is being added to the "no team" team, then we need to include the team ID of the no team team in the request URL
+ if(Number(teamApid) === 0){
+ addScriptUrl = `/api/v1/fleet/scripts?team_id=${teamApid}`;
+ } else {
+ // Otherwise, the team_id needs to be included in the request's formData.
+ addScriptUrl = `/api/v1/fleet/scripts`;
+ requestBodyForThisTeam.team_id = Number(teamApid);// eslint-disable-line camelcase
+ }
+ await sails.helpers.http.sendHttpRequest.with({
+ method: 'POST',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ url: addScriptUrl,
+ enctype: 'multipart/form-data',
+ body: requestBodyForThisTeam,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`,
+ },
+ });
+ }
+ } else {
+ if(script.teams) {
+ for(let scriptId of script.teams){
+ await sails.helpers.http.sendHttpRequest.with({
+ method: 'DELETE',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ url: `/api/v1/fleet/scripts/${scriptId.scriptFleetApid}`,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`,
+ }
+ });
+ }
+ }
+ for(let teamApid of newTeamIds){
+ // Build a request body for the team.
+ let requestBodyForThisTeam = {
+ script: {
+ options: {
+ filename: filename,
+ contentType: 'application/octet-stream'
+ },
+ value: scriptContents,
+ }
+ };
+ let addScriptUrl;
+ // If the script is being added to the "no team" team, then we need to include the team ID of the no team team in the request URL
+ if(Number(teamApid) === 0){
+ addScriptUrl = `/api/v1/fleet/scripts?team_id=${teamApid}`;
+ } else {
+ // Otherwise, the team_id needs to be included in the request's formData.
+ addScriptUrl = `/api/v1/fleet/scripts`;
+ requestBodyForThisTeam.team_id = Number(teamApid);// eslint-disable-line camelcase
+ }
+ await sails.helpers.http.sendHttpRequest.with({
+ method: 'POST',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ url: addScriptUrl,
+ enctype: 'multipart/form-data',
+ body: requestBodyForThisTeam,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`,
+ },
+ });
+ }
+ }
+
+ // If this profile has an ID, then it is a database record, and we will delete it if it has been deployed to a team.
+ if(script.id && newTeamIds.length > 0){
+ // console.log('Undeployed script has been deployed. deleting DB record!');
+ await UndeployedScript.destroy({id: script.id});
+ } else if(!script.id && newTeamIds.length === 0){
+ // If this is not a database record of a script, and the script is being undeployed from all teams, we'll create a databse record for it.
+ // console.log('Creating database record for a (now) undeployed script!');
+ await UndeployedScript.create({
+ name: script.name,
+ platform: extension === '.ps1' ? 'Windows' : 'macOS & Linux',
+ scriptContents,
+ scriptType: extension,
+ });
+ } else if(script.id && newScript){
+ // If there is a new script that is replacing a database record, update the scriptContents in the database.
+ // console.log('Updating existing undeployed script!');
+ await UndeployedScript.updateOne({id: script.id}).set({
+ scriptContents,
+ });
+ }
+
+ // All done.
+ return;
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/scripts/upload-script.js b/ee/bulk-operations-dashboard/api/controllers/scripts/upload-script.js
new file mode 100644
index 000000000000..55265f2fa23a
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/scripts/upload-script.js
@@ -0,0 +1,118 @@
+module.exports = {
+
+
+ friendlyName: 'Upload script',
+
+
+ description: 'Uploads a script to the connected Fleet instance',
+
+ files: ['newScript'],
+
+ inputs: {
+
+ newScript: {
+ type: 'ref',
+ description: 'An Upstream with an incoming file upload.',
+ required: true,
+ },
+
+ teams: {
+ type: ['string'],
+ description: 'An array of team IDs that this profile will be added to'
+ }
+ },
+
+
+ exits: {
+ success: {
+ outputDescription: 'The new script has been uploaded',
+ outputType: {},
+ },
+
+ scriptWithThisNameAlreadyExists: {
+ description: 'A script with this name already exists on the Fleet Instance',
+ statusCode: 409,
+ },
+
+ noFileAttached: {
+ description: 'No file was attached.',
+ responseType: 'badRequest'
+ },
+
+ tooBig: {
+ description: 'The file is too big.',
+ responseType: 'badRequest'
+ },
+ },
+
+
+ fn: async function ({newScript, teams}) {
+
+ let util = require('util');
+ let script = await sails.reservoir(newScript)
+ .intercept('E_EXCEEDS_UPLOAD_LIMIT', 'tooBig')
+ .intercept((err)=>new Error('The script upload failed. '+util.inspect(err)));
+ if(!script) {
+ throw 'noFileAttached';
+ }
+ // Get the file contents and filename.
+ let scriptContents = script[0].contentBytes;
+ let scriptFilename = script[0].name;
+ // Strip out any automatically added date prefixes from uploaded scripts.
+ let datelessExtensionlessFilename = scriptFilename.replace(/^\d{4}-\d{2}-\d{2}\s/, '').replace(/\.[^/.]+$/, '');
+ let extension = '.'+scriptFilename.split('.').pop();
+ // Build a dictonary of information about this script to return to the scripts page.
+ let newScriptInfo = {
+ name: datelessExtensionlessFilename,
+ platform: _.endsWith(scriptFilename, '.ps1') ? 'Windows' : 'macOS & Linux',
+ scriptType: extension,
+ createdAt: Date.now()
+ };
+ if(!teams) {
+ newScriptInfo.scriptContents = scriptContents;
+ await UndeployedScript.create(newScriptInfo).fetch();
+ } else {
+ // Send a request to add the script for every team ID in the array of teams.
+ for(let teamApid of teams){
+ // Build a request body for the team.
+ let requestBodyForThisTeam = {
+ script: {
+ options: {
+ filename: datelessExtensionlessFilename + extension,
+ contentType: 'application/octet-stream'
+ },
+ value: scriptContents,
+ }
+ };
+ let addScriptUrl;
+ // If the script is being added to the "no team" team, then we need to include the team ID of the no team team in the request URL
+ if(Number(teamApid) === 0){
+ addScriptUrl = `/api/v1/fleet/scripts?team_id=${teamApid}`;
+ } else {
+ // Otherwise, the team_id needs to be included in the request's formData.
+ addScriptUrl = `/api/v1/fleet/scripts`;
+ requestBodyForThisTeam.team_id = Number(teamApid);// eslint-disable-line camelcase
+ }
+ // Send a PSOT request to add the script.
+ await sails.helpers.http.sendHttpRequest.with({
+ method: 'POST',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ url:addScriptUrl,
+ enctype: 'multipart/form-data',
+ body: requestBodyForThisTeam,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`,
+ },
+ })
+ .intercept({raw: {statusCode: 409}}, ()=>{
+ return 'scriptWithThisNameAlreadyExists';
+ });
+ }
+ }
+ // All done.
+ return newScriptInfo;
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/controllers/scripts/view-scripts.js b/ee/bulk-operations-dashboard/api/controllers/scripts/view-scripts.js
new file mode 100644
index 000000000000..02fabebdd243
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/controllers/scripts/view-scripts.js
@@ -0,0 +1,124 @@
+module.exports = {
+
+
+ friendlyName: 'View scripts',
+
+
+ description: 'Display "Scripts" page.',
+
+
+ exits: {
+
+ success: {
+ viewTemplatePath: 'pages/scripts'
+ }
+
+ },
+
+
+ fn: async function () {
+ let teamsResponseData = await sails.helpers.http.get.with({
+ url: '/api/v1/fleet/teams',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ })
+ .timeout(120000)
+ .retry(['requestFailed', {name: 'TimeoutError'}]);
+
+ let allTeams = teamsResponseData.teams;
+
+ let teamApids = _.pluck(allTeams, 'id');
+ let teams = [];
+ for(let team of allTeams) {
+ teams.push({
+ fleetApid: team.id,
+ teamName: team.name,
+ });
+ }
+ // Add the "team" for hosts with no team
+ teams.push({
+ fleetApid: 0,
+ teamName: 'No team',
+ });
+
+ let allScripts = [];
+
+ for(let teamApid of teamApids){
+ let scriptsResponseData = await sails.helpers.http.get.with({
+ url: `/api/v1/fleet/scripts?team_id=${teamApid}`,
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ })
+ .timeout(120000)
+ .retry(['requestFailed', {name: 'TimeoutError'}]);
+ let scriptsForThisTeam = scriptsResponseData.scripts;
+ if(scriptsForThisTeam !== null) {
+ allScripts = allScripts.concat(scriptsForThisTeam);
+ }
+ }
+
+ // Grab all of the configuration scripts on the Fleet instance.
+ let noTeamConfigurationScriptsResponseData = await sails.helpers.http.get.with({
+ url: '/api/v1/fleet/scripts',
+ baseUrl: sails.config.custom.fleetBaseUrl,
+ headers: {
+ Authorization: `Bearer ${sails.config.custom.fleetApiToken}`
+ }
+ })
+ .timeout(120000)
+ .retry(['requestFailed', {name: 'TimeoutError'}]);
+ let scriptsForThisTeam = noTeamConfigurationScriptsResponseData.scripts;
+
+ if(scriptsForThisTeam !== null){
+ allScripts = allScripts.concat(scriptsForThisTeam);
+ }
+
+ if(allScripts === [ null ]){
+ return {scripts: [], teams};
+ }
+ let scriptsOnThisFleetInstance = [];
+
+ let allScriptsByIdentifier = _.groupBy(allScripts, 'name');
+ for(let scriptIdentifier in allScriptsByIdentifier) {
+ if(scriptIdentifier === null){
+ continue;
+ }
+ let teamsForThisProfile = [];
+ // console.log(teamsForThisProfile);
+ // let platforms = _.uniq(_.pluck(allScriptsByIdentifier[scriptIdentifier], 'platform'));
+ for(let script of allScriptsByIdentifier[scriptIdentifier]){
+ let informationAboutThisScript = {
+ scriptFleetApid: script.id,
+ fleetApid: script.team_id ? script.team_id : 0,
+ teamName: script.team_id ? _.find(teams, {fleetApid: script.team_id}).teamName : 'No team',
+ };
+ teamsForThisProfile.push(informationAboutThisScript);
+ }
+ let script = allScriptsByIdentifier[scriptIdentifier][0];// Grab the first script returned in the api repsonse to build our script configuration.
+ let scriptInformation = {
+ name: script.name,
+ identifier: scriptIdentifier,
+ platform: _.endsWith(script.name, 'sh') ? 'macOS & Linux' : 'Windows',
+ createdAt: new Date(script.created_at).getTime(),
+ teams: teamsForThisProfile
+ };
+ scriptsOnThisFleetInstance.push(scriptInformation);
+ }
+ // Get the undeployed scripts from the app's database.
+ let undeployedScripts = await UndeployedScript.find();
+ scriptsOnThisFleetInstance = _.union(scriptsOnThisFleetInstance, undeployedScripts);
+
+ // Sort the scripts by name.
+ scriptsOnThisFleetInstance = _.sortByOrder(scriptsOnThisFleetInstance, 'name', 'asc');
+ // Respond with view.
+ return {scripts: scriptsOnThisFleetInstance, teams};
+
+
+ }
+
+
+};
diff --git a/ee/bulk-operations-dashboard/api/helpers/broadcast-session-change.js b/ee/bulk-operations-dashboard/api/helpers/broadcast-session-change.js
new file mode 100644
index 000000000000..f4aa836b3cf1
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/helpers/broadcast-session-change.js
@@ -0,0 +1,45 @@
+module.exports = {
+
+
+ friendlyName: 'Broadcast session change',
+
+
+ description: 'Broadcast a socket notification indicating a change in login status.',
+
+
+ inputs: {
+
+ req: {
+ type: 'ref',
+ required: true,
+ },
+
+ },
+
+
+ exits: {
+
+ success: {
+ description: 'All done.',
+ },
+
+ },
+
+
+ fn: async function ({ req }) {
+
+ // If there's no sessionID, we don't need to broadcase a message about the old session.
+ if(!req.sessionID) {
+ return;
+ }
+
+ let roomName = `session${_.deburr(req.sessionID)}`;
+ let messageText = `You have signed out or signed into a different session in another tab or window. Reload the page to refresh your session.`;
+ sails.sockets.broadcast(roomName, 'session', { notificationText: messageText }, req);
+
+
+ }
+
+
+};
+
diff --git a/ee/bulk-operations-dashboard/api/helpers/redact-user.js b/ee/bulk-operations-dashboard/api/helpers/redact-user.js
new file mode 100644
index 000000000000..28d9a7c44f72
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/helpers/redact-user.js
@@ -0,0 +1,33 @@
+module.exports = {
+
+
+ friendlyName: 'Redact user',
+
+
+ description: 'Destructively remove properties from the provided User record to prepare it for publication.',
+
+
+ sync: true,
+
+
+ inputs: {
+
+ user: {
+ type: 'ref',
+ readOnly: false
+ }
+
+ },
+
+
+ fn: function ({ user }) {
+ for (let [attrName, attrDef] of Object.entries(User.attributes)) {
+ if (attrDef.protect) {
+ delete user[attrName];
+ }//fi
+ }//∞
+ }
+
+
+};
+
diff --git a/ee/bulk-operations-dashboard/api/helpers/send-template-email.js b/ee/bulk-operations-dashboard/api/helpers/send-template-email.js
new file mode 100644
index 000000000000..f03b44746281
--- /dev/null
+++ b/ee/bulk-operations-dashboard/api/helpers/send-template-email.js
@@ -0,0 +1,282 @@
+module.exports = {
+
+
+ friendlyName: 'Send template email',
+
+
+ description: 'Send an email using a template.',
+
+
+ extendedDescription: 'To ease testing and development, if the provided "to" email address ends in "@example.com", '+
+ 'then the email message will be written to the terminal instead of actually being sent.'+
+ '(Thanks [@simonratner](https://github.com/simonratner)!)',
+
+
+ inputs: {
+
+
+ template: {
+ description: 'The relative path to an EJS template within our `views/emails/` folder -- WITHOUT the file extension.',
+ extendedDescription: 'Use strings like "foo" or "foo/bar", but NEVER "foo/bar.ejs" or "/foo/bar". For example, '+
+ '"internal/email-contact-form" would send an email using the "views/emails/internal/email-contact-form.ejs" template.',
+ example: 'email-reset-password',
+ type: 'string',
+ required: true
+ },
+
+ templateData: {
+ description: 'A dictionary of data which will be accessible in the EJS template.',
+ extendedDescription: 'Each key will be a local variable accessible in the template. For instance, if you supply '+
+ 'a dictionary with a \`friends\` key, and \`friends\` is an array like \`[{name:"Chandra"}, {name:"Mary"}]\`),'+
+ 'then you will be able to access \`friends\` from the template:\n'+
+ '\`\`\`\n'+
+ '