├── lib ├── macos │ ├── scripts │ │ ├── uninstall-santa.sh │ │ ├── set-timezone.script.sh │ │ ├── remove-zoom-artifacts.script.sh │ │ ├── install-santa.sh │ │ ├── collect-fleetd-logs.sh │ │ └── macos-password.mobileconfig │ ├── software │ │ └── santa.yml │ ├── configuration-profiles │ │ └── passcode-settings-ddm.json │ ├── enrollment-profiles │ │ └── automatic-enrollment.dep.json │ └── policies │ │ └── macos-device-health.policies.yml ├── windows │ ├── software │ │ └── slack.yml │ ├── scripts │ │ ├── uninstall-slack.ps1 │ │ ├── default-exe-install-script.ps1 │ │ └── windows-screenlock.xml │ ├── configuration-profiles │ │ └── passcode-settings-ddm.json │ └── policies │ │ └── windows-device-health.policies.yml ├── all │ └── queries │ │ ├── collect-usb-devices.queries.yml │ │ ├── collect-fleetd-update-channels.queries.yml │ │ └── collect-failed-login-attempts.queries.yml ├── linux │ └── policies │ │ └── linux-device-health.policies.yml ├── agent-options.yml └── README.md ├── teams ├── no-team.yml ├── workstations.yml └── workstations-canary.yml ├── default.yml ├── .gitlab-ci.yml ├── LICENSE ├── CODEOWNERS ├── .github ├── workflows │ └── workflow.yml └── gitops-action │ └── action.yml ├── gitops.sh └── README.md /lib/macos/scripts/uninstall-santa.sh: -------------------------------------------------------------------------------- 1 | # This will be a script that uninstalls Santa from macOS hosts. -------------------------------------------------------------------------------- /lib/macos/scripts/set-timezone.script.sh: -------------------------------------------------------------------------------- 1 | # This will be a script that sets the timezone on macOS hosts. 2 | -------------------------------------------------------------------------------- /lib/macos/scripts/remove-zoom-artifacts.script.sh: -------------------------------------------------------------------------------- 1 | # This will be a script that removes Zoom artifacts from macOS hosts. 2 | -------------------------------------------------------------------------------- /lib/macos/scripts/install-santa.sh: -------------------------------------------------------------------------------- 1 | # This will be a script that installs Santa onto macOS hosts. 2 | # Documentation: https://fleetdm.com/docs/configuration/yaml-files#packages -------------------------------------------------------------------------------- /lib/macos/software/santa.yml: -------------------------------------------------------------------------------- 1 | # This will be the configuration for a custom package on macOS hosts. 2 | # Documentation: https://fleetdm.com/docs/configuration/yaml-files#packages 3 | -------------------------------------------------------------------------------- /lib/windows/software/slack.yml: -------------------------------------------------------------------------------- 1 | # This will be the configuration for a custom package on Windows hosts. 2 | # Documentation: https://fleetdm.com/docs/configuration/yaml-files#packages 3 | -------------------------------------------------------------------------------- /lib/windows/scripts/uninstall-slack.ps1: -------------------------------------------------------------------------------- 1 | # This will be a script that uninstalls Slack from Windows hosts. 2 | # Documentation: https://fleetdm.com/docs/configuration/yaml-files#packages 3 | -------------------------------------------------------------------------------- /lib/windows/scripts/default-exe-install-script.ps1: -------------------------------------------------------------------------------- 1 | # This will be a default script that can install packages on Windows hosts. 2 | # Documentation: https://fleetdm.com/docs/configuration/yaml-files#packages 3 | -------------------------------------------------------------------------------- /lib/all/queries/collect-usb-devices.queries.yml: -------------------------------------------------------------------------------- 1 | - name: Collect USB devices 2 | description: Collects the USB devices that are currently connected to macOS and Linux hosts. 3 | query: SELECT model, vendor FROM usb_devices; 4 | interval: 360 # 6 minutes 5 | observer_can_run: true 6 | automations_enabled: false 7 | platform: darwin,linux 8 | -------------------------------------------------------------------------------- /lib/linux/policies/linux-device-health.policies.yml: -------------------------------------------------------------------------------- 1 | - name: Linux - Enable disk encryption 2 | platform: linux 3 | description: This policy checks if disk encryption is enabled. 4 | resolution: As an IT admin, deploy an image that includes disk encryption. 5 | query: SELECT 1 FROM disk_encryption WHERE encrypted=1 AND name LIKE '/dev/dm-1'; 6 | -------------------------------------------------------------------------------- /teams/no-team.yml: -------------------------------------------------------------------------------- 1 | # Teams are available in Fleet Premium. 2 | 3 | # This file updates policies, controls, and software for hosts assigned to "No team." 4 | 5 | # To update queries and agent options for hosts assigned to "No team," use the default.yml file. 6 | 7 | name: No team 8 | policies: 9 | controls: # This cannot be set here and in default.yml 10 | software: 11 | -------------------------------------------------------------------------------- /lib/macos/configuration-profiles/passcode-settings-ddm.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "com.apple.configuration.passcode.settings", 3 | "Identifier": "956e0d14-6019-479b-a6f9-a69ef77668c5", 4 | "Payload": { 5 | "MaximumFailedAttempts": 10, 6 | "MaximumInactivityInMinutes": 5, 7 | "MinimumLength": 12, 8 | "MinimumComplexCharacters": 1 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/windows/configuration-profiles/passcode-settings-ddm.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type": "com.apple.configuration.passcode.settings", 3 | "Identifier": "956e0d14-6019-479b-a6f9-a69ef77668c5", 4 | "Payload": { 5 | "MaximumFailedAttempts": 10, 6 | "MaximumInactivityInMinutes": 5, 7 | "MinimumLength": 12, 8 | "MinimumComplexCharacters": 1 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/agent-options.yml: -------------------------------------------------------------------------------- 1 | command_line_flags: 2 | config: 3 | decorators: 4 | load: 5 | - SELECT uuid AS host_uuid FROM system_info; 6 | - SELECT hostname AS hostname FROM system_info; 7 | options: 8 | disable_distributed: false 9 | distributed_interval: 10 10 | distributed_plugin: tls 11 | distributed_tls_max_attempts: 3 12 | logger_tls_endpoint: /api/v1/osquery/log 13 | pack_delimiter: / 14 | -------------------------------------------------------------------------------- /lib/macos/scripts/collect-fleetd-logs.sh: -------------------------------------------------------------------------------- 1 | cp /var/log/orbit/orbit.stderr.log ~/Library/Logs/Fleet/fleet-desktop.log /Users/Shared 2 | 3 | echo "Successfully copied fleetd logs to the /Users/Shared folder." 4 | 5 | echo "To retrieve logs, ask the end user to open Finder and in the menu bar select Go > Go to Folder." 6 | 7 | echo "Then, ask the end user to type in /Users/Shared, press Return, and locate orbit.stderr.log (Orbit logs) and fleet-desktop.log (Fleet Desktop logs) files." -------------------------------------------------------------------------------- /lib/all/queries/collect-fleetd-update-channels.queries.yml: -------------------------------------------------------------------------------- 1 | - name: Collect fleetd update channels 2 | description: "Collects the update channels for all fleetd components: osquery, Orbit, and Fleet Desktop. To see which version number each channel is on, ask in #help-engineering." 3 | query: SELECT desktop_channel, orbit_channel, osqueryd_channel FROM orbit_info; 4 | interval: 300 # 5 minutes 5 | observer_can_run: true 6 | automations_enabled: false 7 | platform: darwin,linux,windows 8 | -------------------------------------------------------------------------------- /lib/all/queries/collect-failed-login-attempts.queries.yml: -------------------------------------------------------------------------------- 1 | - name: Collect failed login attempts 2 | description: Lists the users at least one failed login attempt and timestamp of failed login. Number of failed login attempts reset to zero after a user successfully logs in. 3 | query: SELECT users.username, account_policy_data.failed_login_count, account_policy_data.failed_login_timestamp FROM users INNER JOIN account_policy_data using (uid) WHERE account_policy_data.failed_login_count > 0; 4 | interval: 300 # 5 minutes 5 | observer_can_run: false 6 | automations_enabled: false 7 | platform: darwin,linux,windows 8 | -------------------------------------------------------------------------------- /lib/macos/enrollment-profiles/automatic-enrollment.dep.json: -------------------------------------------------------------------------------- 1 | { 2 | "profile_name": "Fleet's example automatic enrollment profile", 3 | "allow_pairing": true, 4 | "is_mdm_removable": true, 5 | "org_magic": "1", 6 | "language": "en", 7 | "region": "US", 8 | "skip_setup_items": [ 9 | "Accessibility", 10 | "Appearance", 11 | "AppleID", 12 | "AppStore", 13 | "Biometric", 14 | "Diagnostics", 15 | "FileVault", 16 | "iCloudDiagnostics", 17 | "iCloudStorage", 18 | "Location", 19 | "Payment", 20 | "Privacy", 21 | "Restore", 22 | "ScreenTime", 23 | "Siri", 24 | "TermsOfAddress", 25 | "TOS", 26 | "UnlockWithWatch" 27 | ] 28 | } -------------------------------------------------------------------------------- /default.yml: -------------------------------------------------------------------------------- 1 | # For Fleet Free: 2 | # - This file updates policies, queries, agent_options, and controls for all hosts. 3 | 4 | # For Fleet Premium: 5 | # - This file updates policies and queries that run on all hosts ("All teams"). 6 | # - Remove "controls" and add this to your YAML files in teams/ instead. 7 | 8 | policies: 9 | queries: 10 | agent_options: 11 | path: ./lib/agent-options.yml 12 | controls: # This cannot be set here and in no-team.yml 13 | org_settings: 14 | server_settings: 15 | server_url: $FLEET_URL 16 | org_info: 17 | org_name: Fleet 18 | secrets: 19 | - secret: "$FLEET_GLOBAL_ENROLL_SECRET" 20 | features: 21 | enable_host_users: true 22 | enable_software_inventory: true 23 | -------------------------------------------------------------------------------- /lib/README.md: -------------------------------------------------------------------------------- 1 | # `lib/` 2 | 3 | This folder is for files referenced by `path` in Fleet config YAML. 4 | 5 | This can reduce duplication for policies, scripts, and other config that is the same across multiple teams in Fleet Premium. 6 | 7 | ### Examples 8 | 9 | ##### Policies 10 | 11 | ```yaml 12 | # default.yml 13 | policies: 14 | - path: ./lib/macos/policies/macos-device-health.policies.yml 15 | ``` 16 | 17 | ##### Queries 18 | 19 | ```yaml 20 | # default.yml 21 | queries: 22 | - path: ./lib/all/queries/collect-usb-devices.queries.yml 23 | ``` 24 | 25 | ##### Scripts 26 | 27 | ```yaml 28 | # default.yml 29 | controls: 30 | scripts: 31 | - path: ./lib/macos/scripts/remove-zoom-artifacts.script.sh 32 | ``` 33 | 34 | ##### Agent options 35 | 36 | ```yaml 37 | # default.yml 38 | agent_options: 39 | path: ./lib/agent-options.yml 40 | ``` 41 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | fleet-gitops: 2 | image: node:22 3 | variables: 4 | FLEET_DRY_RUN_ONLY: true 5 | rules: 6 | - if: $CI_PIPELINE_SOURCE == 'merge_request_event' 7 | - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH 8 | variables: 9 | FLEET_DRY_RUN_ONLY: false 10 | - if: $CI_PIPELINE_SOURCE == 'schedule' && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH 11 | variables: 12 | FLEET_DRY_RUN_ONLY: false 13 | before_script: 14 | - apt-get -qq update 15 | - apt-get install -y 'jq=1.6-2.1*' 16 | script: 17 | - > 18 | FLEET_VERSION="$(curl "$FLEET_URL/api/v1/fleet/version" --header "Authorization: Bearer $FLEET_API_TOKEN" --fail --silent | jq --raw-output '.version')" 19 | - > 20 | if [[ -n "$FLEET_VERSION" ]] ; then 21 | npm install -g "fleetctl@$FLEET_VERSION" || npm install -g fleetctl 22 | else 23 | echo "Failed to get Fleet version from $FLEET_URL, installing latest version of fleetctl" 24 | npm install -g fleetctl 25 | fi 26 | - fleetctl config set --address $FLEET_URL --token $FLEET_API_TOKEN 27 | - ./gitops.sh 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-present Fleet Device Management Inc 2 | 3 | This software is available under the "MIT Expat" license as defined below. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /teams/workstations.yml: -------------------------------------------------------------------------------- 1 | # Teams are available in Fleet Premium. 2 | 3 | # This file updates policies, queries, agent options, controls, and software for hosts assigned to the "Workstations" team. 4 | 5 | # To add another team, create a new file in the teams/ directory and copy and paste the contents from this file. 6 | # Update the secret in the new file, then create the corresponding secret in GitHub Actions secrets. 7 | # Then add that secret to .github/workflows/workflow.yml as an env variable. 8 | # The secret name in the YAML file must match the secret name in GitHub Actions secrets. 9 | 10 | name: Workstations 11 | policies: 12 | - path: ../lib/macos/policies/macos-device-health.policies.yml 13 | - path: ../lib/windows/policies/windows-device-health.policies.yml 14 | - path: ../lib/linux/policies/linux-device-health.policies.yml 15 | queries: 16 | - path: ../lib/all/queries/collect-usb-devices.queries.yml 17 | - path: ../lib/all/queries/collect-failed-login-attempts.queries.yml 18 | agent_options: 19 | path: ../lib/agent-options.yml 20 | controls: 21 | scripts: 22 | - path: ../lib/macos/scripts/remove-zoom-artifacts.script.sh 23 | - path: ../lib/macos/scripts/set-timezone.script.sh 24 | team_settings: 25 | secrets: 26 | - secret: "$FLEET_WORKSTATIONS_ENROLL_SECRET" 27 | features: 28 | enable_host_users: true 29 | enable_software_inventory: true 30 | software: 31 | -------------------------------------------------------------------------------- /teams/workstations-canary.yml: -------------------------------------------------------------------------------- 1 | # Teams are available in Fleet Premium. 2 | 3 | # This file updates policies, queries, agent options, controls, and software for hosts assigned to the "Workstations (canary)" team. 4 | 5 | # To add another team, create a new file in the teams/ directory and copy and paste the contents from this file. 6 | # Update the secret in the new file, then create the corresponding secret in GitHub Actions secrets. 7 | # Then add that secret to .github/workflows/workflow.yml as an env variable. 8 | # The secret name in the YAML file must match the secret name in GitHub Actions secrets. 9 | 10 | name: Workstations (canary) 11 | policies: 12 | - path: ../lib/macos/policies/macos-device-health.policies.yml 13 | - path: ../lib/windows/policies/windows-device-health.policies.yml 14 | - path: ../lib/linux/policies/linux-device-health.policies.yml 15 | queries: 16 | - path: ../lib/all/queries/collect-usb-devices.queries.yml 17 | - path: ../lib/all/queries/collect-failed-login-attempts.queries.yml 18 | agent_options: 19 | path: ../lib/agent-options.yml 20 | controls: 21 | scripts: 22 | - path: ../lib/macos/scripts/remove-zoom-artifacts.script.sh 23 | - path: ../lib/macos/scripts/set-timezone.script.sh 24 | team_settings: 25 | secrets: 26 | - secret: "$FLEET_WORKSTATIONS_CANARY_ENROLL_SECRET" 27 | features: 28 | enable_host_users: true 29 | enable_software_inventory: true 30 | software: 31 | 32 | -------------------------------------------------------------------------------- /lib/windows/scripts/windows-screenlock.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | int 6 | 7 | 8 | ./Device/Vendor/MSFT/Policy/Config/DeviceLock/DevicePasswordEnabled 9 | 10 | 0 11 | 12 | 13 | 14 | 15 | 16 | 17 | int 18 | 19 | 20 | ./Device/Vendor/MSFT/Policy/Config/DeviceLock/MaxInactivityTimeDeviceLock 21 | 22 | 15 23 | 24 | 25 | 26 | 27 | 28 | 29 | int 30 | 31 | 32 | ./Device/Vendor/MSFT/Policy/Config/DeviceLock/MinDevicePasswordLength 33 | 34 | 10 35 | 36 | 37 | 38 | 39 | 40 | 41 | int 42 | 43 | 44 | ./Device/Vendor/MSFT/Policy/Config/DeviceLock/MinDevicePasswordComplexCharacters 45 | 46 | 2 47 | 48 | 49 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | ############################################################################################## 2 | # ██████╗ ██████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗███╗ ██╗███████╗██████╗ ███████╗ 3 | # ██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔═══██╗██║ ██║████╗ ██║██╔════╝██╔══██╗██╔════╝ 4 | # ██║ ██║ ██║██║ ██║█████╗ ██║ ██║██║ █╗ ██║██╔██╗ ██║█████╗ ██████╔╝███████╗ 5 | # ██║ ██║ ██║██║ ██║██╔══╝ ██║ ██║██║███╗██║██║╚██╗██║██╔══╝ ██╔══██╗╚════██║ 6 | # ╚██████╗╚██████╔╝██████╔╝███████╗╚██████╔╝╚███╔███╔╝██║ ╚████║███████╗██║ ██║███████║ 7 | # ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚══╝╚══╝ ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝╚══════╝ 8 | ############################################################################################## 9 | # ⛔ This file indicates REQUIRED reviewers for changes in this repo. 10 | # 11 | # For more information on how this works, see: 12 | # - What is a DRI and how is this configured? https://fleetdm.com/handbook/company/why-this-way#why-direct-responsibility 13 | # - Historical context: https://github.com/fleetdm/fleet/pull/12786 14 | ############################################################################################## 15 | 16 | # Best practice file structure 17 | /lib @harrisonravazzolo 18 | /teams @harrisonravazzolo 19 | default.yml @harrisonravazzolo 20 | 21 | # GitHub Action and GitLab CI/CD 22 | .github @getvictor @allenhouchins 23 | .gitlab-ci.yml @getvictor 24 | gitops.sh @getvictor 25 | 26 | # Everything else 27 | * @getvictor 28 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: 'Apply latest configuration to Fleet' 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | workflow_dispatch: # allows manual triggering 9 | schedule: 10 | - cron: '0 6 * * *' # Nightly 6AM UTC 11 | 12 | # Prevent concurrent runs of this workflow. 13 | concurrency: 14 | group: ${{ github.workflow }} 15 | cancel-in-progress: false 16 | 17 | defaults: 18 | run: 19 | shell: bash 20 | 21 | # Limit permissions of GITHUB_TOKEN. 22 | permissions: 23 | contents: read 24 | 25 | jobs: 26 | fleet-gitops: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout GitOps repository 30 | uses: actions/checkout@v4 31 | 32 | - name: Apply latest configuration to Fleet 33 | uses: ./.github/gitops-action 34 | with: 35 | # Run GitOps in dry-run mode for pull requests. 36 | dry-run-only: ${{ github.event_name == 'pull_request' && 'true' || 'false' }} 37 | # Add FLEET_URL and FLEET_API_TOKEN to the repository secrets. 38 | # In addition, specify or add secrets for all the environment variables that are mentioned in the global/team YAML files. 39 | env: 40 | FLEET_URL: ${{ secrets.FLEET_URL }} 41 | FLEET_API_TOKEN: ${{ secrets.FLEET_API_TOKEN }} 42 | FLEET_GLOBAL_ENROLL_SECRET: ${{ secrets.FLEET_GLOBAL_ENROLL_SECRET }} 43 | FLEET_WORKSTATIONS_ENROLL_SECRET: ${{ secrets.FLEET_WORKSTATIONS_ENROLL_SECRET }} 44 | FLEET_WORKSTATIONS_CANARY_ENROLL_SECRET: ${{ secrets.FLEET_WORKSTATIONS_CANARY_ENROLL_SECRET }} 45 | -------------------------------------------------------------------------------- /gitops.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # -e: Immediately exit if any command has a non-zero exit status. 4 | # -x: Print all executed commands to the terminal. 5 | # -u: Exit if an undefined variable is used. 6 | # -o pipefail: Exit if any command in a pipeline fails. 7 | set -exuo pipefail 8 | 9 | FLEET_GITOPS_DIR="${FLEET_GITOPS_DIR:-.}" 10 | FLEET_GLOBAL_FILE="${FLEET_GLOBAL_FILE:-$FLEET_GITOPS_DIR/default.yml}" 11 | FLEETCTL="${FLEETCTL:-fleetctl}" 12 | FLEET_DRY_RUN_ONLY="${FLEET_DRY_RUN_ONLY:-false}" 13 | FLEET_DELETE_OTHER_TEAMS="${FLEET_DELETE_OTHER_TEAMS:-true}" 14 | 15 | # Validate that global file contains org_settings 16 | grep -Exq "^org_settings:.*" "$FLEET_GLOBAL_FILE" 17 | 18 | # Copy/pasting raw SSO metadata into GitHub secrets will result in malformed yaml. 19 | # Adds spaces to all but the first line of metadata keeps the multiline string in bounds. 20 | # See README for more information 21 | 22 | # FLEET_SSO_METADATA=$( sed '2,$s/^/ /' <<< "${FLEET_MDM_SSO_METADATA}") 23 | # FLEET_MDM_SSO_METADATA=$( sed '2,$s/^/ /' <<< "${FLEET_MDM_SSO_METADATA}") 24 | 25 | if compgen -G "$FLEET_GITOPS_DIR"/teams/*.yml > /dev/null; then 26 | # Validate that every team has a unique name. 27 | # This is a limited check that assumes all team files contain the phrase: `name: ` 28 | ! perl -nle 'print $1 if /^name:\s*(.+)$/' "$FLEET_GITOPS_DIR"/teams/*.yml | sort | uniq -d | grep . -cq 29 | fi 30 | 31 | args=(-f "$FLEET_GLOBAL_FILE") 32 | for team_file in "$FLEET_GITOPS_DIR"/teams/*.yml; do 33 | if [ -f "$team_file" ]; then 34 | args+=(-f "$team_file") 35 | fi 36 | done 37 | if [ "$FLEET_DELETE_OTHER_TEAMS" = true ]; then 38 | args+=(--delete-other-teams) 39 | fi 40 | 41 | # Dry run 42 | $FLEETCTL gitops "${args[@]}" --dry-run 43 | if [ "$FLEET_DRY_RUN_ONLY" = true ]; then 44 | exit 0 45 | fi 46 | 47 | # Real run 48 | $FLEETCTL gitops "${args[@]}" 49 | -------------------------------------------------------------------------------- /lib/macos/scripts/macos-password.mobileconfig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PayloadContent 6 | 7 | 8 | PayloadDescription 9 | Configures Passcode settings 10 | PayloadDisplayName 11 | Passcode 12 | PayloadIdentifier 13 | com.github.erikberglund.ProfileCreator.F7CF282E-D91B-44E9-922F-A719634F9C8E.com.apple.mobiledevice.passwordpolicy.231DFC90-D5A7-41B8-9246-564056048AC5 14 | PayloadOrganization 15 | 16 | PayloadType 17 | com.apple.mobiledevice.passwordpolicy 18 | PayloadUUID 19 | 231DFC90-D5A7-41B8-9246-564056048AC5 20 | PayloadVersion 21 | 1 22 | allowSimple 23 | 24 | forcePIN 25 | 26 | maxFailedAttempts 27 | 11 28 | maxGracePeriod 29 | 1 30 | maxInactivity 31 | 15 32 | minLength 33 | 10 34 | requireAlphanumeric 35 | 36 | 37 | 38 | PayloadDescription 39 | Configures our Macs to require passwords that are 10 character long 40 | PayloadDisplayName 41 | Password policy - require 10 characters 42 | PayloadIdentifier 43 | com.github.erikberglund.ProfileCreator.F7CF282E-D91B-44E9-922F-A719634F9C8E 44 | PayloadOrganization 45 | FleetDM 46 | PayloadScope 47 | System 48 | PayloadType 49 | Configuration 50 | PayloadUUID 51 | F7CF282E-D91B-44E9-922F-A719634F9C8E 52 | PayloadVersion 53 | 1 54 | 55 | -------------------------------------------------------------------------------- /.github/gitops-action/action.yml: -------------------------------------------------------------------------------- 1 | name: fleetctl-gitops 2 | description: Runs fleetctl gitops to apply configuration to Fleet 3 | 4 | inputs: 5 | working-directory: 6 | description: 'The working directory, which should be the root of the fleet-gitops repository.' 7 | default: './' 8 | dry-run-only: 9 | description: 'Whether to only run the fleetctl gitops commands in dry-run mode.' 10 | default: 'false' 11 | delete-other-teams: 12 | description: 'Whether to delete other teams in Fleet which are not part of the gitops config.' 13 | default: 'true' 14 | 15 | runs: 16 | using: "composite" 17 | steps: 18 | - name: Install fleetctl 19 | shell: bash 20 | working-directory: ${{ inputs.working-directory }} 21 | run: | 22 | FLEET_VERSION="$(curl "$FLEET_URL/api/v1/fleet/version" --header "Authorization: Bearer $FLEET_API_TOKEN" --fail --silent | jq --raw-output '.version')" 23 | DEFAULT_FLEETCTL_VERSION="4.77" 24 | 25 | # Decide which fleetctl version to install: 26 | # If the server returns a clean version (e.g. 4.74.0), use that. 27 | # If the server returns a snapshot (e.g. 0.0.0-SNAPSHOT-xxxxx) or is empty, pin to DEFAULT_FLEETCTL_VERSION. 28 | if [[ -z "$FLEET_VERSION" ]]; then 29 | INSTALL_VERSION="$DEFAULT_FLEETCTL_VERSION" 30 | elif [[ "$FLEET_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then 31 | INSTALL_VERSION="$FLEET_VERSION" 32 | else 33 | INSTALL_VERSION="$DEFAULT_FLEETCTL_VERSION" 34 | fi 35 | 36 | echo "Installing fleetctl v$INSTALL_VERSION..." 37 | npm install -g "fleetctl@$INSTALL_VERSION" || npm install -g fleetctl@latest 38 | 39 | - name: Configure fleetctl 40 | shell: bash 41 | working-directory: ${{ inputs.working-directory }} 42 | run: fleetctl config set --address ${{ env.FLEET_URL }} --token ${{ env.FLEET_API_TOKEN }} 43 | 44 | - name: Run fleetctl gitops commands 45 | shell: bash 46 | working-directory: ${{ inputs.working-directory }} 47 | env: 48 | FLEET_DRY_RUN_ONLY: ${{ inputs.dry-run-only }} 49 | FLEET_DELETE_OTHER_TEAMS: ${{ inputs.delete-other-teams }} 50 | run: ./gitops.sh 51 | -------------------------------------------------------------------------------- /lib/windows/policies/windows-device-health.policies.yml: -------------------------------------------------------------------------------- 1 | - name: Windows - Enable BitLocker 2 | platform: windows 3 | description: "This policy checks if BitLocker (disk encryption) is enabled on the C: volume." 4 | resolution: As an IT admin, turn on disk encryption in Fleet. 5 | query: SELECT * FROM bitlocker_info WHERE drive_letter='C:' AND protection_status = 1; 6 | - name: Windows - Disable guest account 7 | platform: windows 8 | description: This policy checks if the guest account is disabled. The Guest account allows unauthenticated network users to gain access to the system. 9 | resolution: "As an IT admin, deploy a Windows profile with the Accounts_EnableGuestAccountStatus option documented here: https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-localpoliciessecurityoptions#accounts_enableguestaccountstatus" 10 | query: SELECT 1 FROM mdm_bridge where mdm_command_input = "1./Device/Vendor/MSFT/Policy/Result/LocalPoliciesSecurityOptions/Accounts_EnableGuestAccountStatus" and CAST(mdm_command_output AS INT) = 0; 11 | - name: Windows - Require 10 character password 12 | platform: windows 13 | description: This policy checks if the end user is required to enter a password, with at least 10 characters, to unlock the host. 14 | resolution: "As an IT admin, deploy a Windows profile with the DevicePasswordEnabled and MinDevicePasswordLength option documented here: https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-devicelock" 15 | query: SELECT 1 FROM mdm_bridge where mdm_command_input = "1./Device/Vendor/MSFT/Policy/Result/DeviceLock/DevicePasswordEnabled" and CAST(mdm_command_output AS INT) = 0; 16 | - name: Windows - Enable screen saver after 20 minutes 17 | platform: windows 18 | description: This policy checks if maximum amount of time (in minutes) the device is allowed to sit idle before the screen is locked. End users can select any value less than the specified maximum. 19 | resolution: "As an IT admin, to deploy a Windows profile with the MaxInactivityTimeDeviceLock option documented here: https://learn.microsoft.com/en-us/windows/client-management/mdm/policy-csp-devicelock#maxinactivitytimedevicelock" 20 | query: SELECT 1 FROM mdm_bridge where mdm_command_input = "1./Device/Vendor/MSFT/Policy/Result/DeviceLock/MaxInactivityTimeDeviceLock" and CAST(mdm_command_output AS INT) <= 20; 21 | -------------------------------------------------------------------------------- /lib/macos/policies/macos-device-health.policies.yml: -------------------------------------------------------------------------------- 1 | - name: macOS - Enable FileVault 2 | platform: darwin 3 | description: This policy checks if FileVault (disk encryption) is enabled. 4 | resolution: As an IT admin, turn on disk encryption in Fleet. 5 | query: SELECT 1 FROM filevault_status WHERE status = 'FileVault is On.'; 6 | - name: macOS - Disable guest account 7 | platform: darwin 8 | description: This policy checks if the guest account is disabled. 9 | resolution: An an IT admin, deploy a macOS, login window profile with the DisableGuestAccount option set to true. 10 | query: SELECT 1 FROM managed_policies WHERE domain='com.apple.loginwindow' AND username = '' AND name='DisableGuestAccount' AND CAST(value AS INT) = 1; 11 | - name: macOS - Enable Firewall 12 | platform: darwin 13 | description: This policy checks if Firewall is enabled. 14 | resolution: An an IT admin, deploy a macOS, Firewall profile with the EnableFirewall option set to true. 15 | query: SELECT 1 FROM managed_policies WHERE domain='com.apple.security.firewall' AND username = '' AND name='EnableFirewall' AND CAST(value AS INT) = 1; 16 | - name: macOS - Require 10 character password 17 | platform: darwin 18 | description: This policy checks if the end user is required to enter a password, with at least 10 characters, to unlock the host. 19 | resolution: An an IT admin, deploy a macOS, screensaver profile with the askForPassword option set to true and minLength option set to 10. 20 | query: | 21 | SELECT 1 WHERE 22 | EXISTS ( 23 | SELECT 1 FROM managed_policies WHERE 24 | domain='com.apple.screensaver' AND 25 | name='askForPassword' AND 26 | CAST(value AS INT) 27 | ) 28 | AND EXISTS ( 29 | SELECT 1 FROM managed_policies WHERE 30 | domain='com.apple.screensaver' AND 31 | name='minLength' AND 32 | CAST(value AS INT) <= 10 33 | ); 34 | - name: macOS - Enable screen saver after 20 minutes 35 | platform: darwin 36 | description: This policy checks if maximum amount of time (in minutes) the device is allowed to sit idle before the screen is locked. End users can select any value less than the specified maximum. 37 | resolution: An an IT admin, deploy a macOS, screen saver profile with the maxInactivity option set to 20 minutes. 38 | query: | 39 | SELECT 1 WHERE 40 | EXISTS ( 41 | SELECT 1 FROM managed_policies WHERE 42 | domain='com.apple.screensaver' AND 43 | name='idleTime' AND 44 | CAST(value AS INT) <= 1200 AND 45 | username = '' 46 | ) 47 | AND NOT EXISTS ( 48 | SELECT 1 FROM managed_policies WHERE 49 | domain='com.apple.screensaver' AND 50 | name='idleTime' AND 51 | CAST(value AS INT) > 1200 52 | ); 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fleet GitOps 2 | 3 | This is the starter repository for using [Fleet](https://fleetdm.com) with a GitOps workflow. 4 | 5 | [Why use GitOps?](https://fleetdm.com/guides/sysadmin-diaries-gitops-a-strategic-advantage#basic-article) 6 | 7 | ## GitHub setup 8 | 9 | 1. Clone the [GitHub repository](https://github.com/fleetdm/fleet-gitops), create your own GitHub repository, and push your clone to your new repo. Note that a workflow will run once and fail because the required variables haven't been added (step 2 and 3). 10 | 11 | 2. Add `FLEET_URL` and `FLEET_API_TOKEN` secrets to your new repository's secrets. Learn how [here](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository). Set `FLEET_URL` to your Fleet instance's URL (ex. https://organization.fleet.com). [Create an API-only user](https://fleetdm.com/docs/using-fleet/fleetctl-cli#create-api-only-user) with the "GitOps" role and set `FLEET_API_TOKEN` to your user's API token. If you're using Fleet Free, set the API-only user's role to global admin. 12 | 13 | 3. Add `FLEET_GLOBAL_ENROLL_SECRET` secret to your new repository's secrets. The enroll secret must be an alphanumeric string of at least 32 and at most 255 characters. 14 | - If you have a Premium Fleet license, also add `FLEET_WORKSTATIONS_ENROLL_SECRET` and `FLEET_WORKSTATIONS_CANARY_ENROLL_SECRET`. 15 | - If you do not have a Premium Fleet license, delete the `teams` directory. 16 | 17 | 4. If you are using secrets to manage SSO metadata for Fleet SSO login or MDM SSO login, uncomment lines 22 and 23 in `gitops.sh`. 18 | - If you are using different variable names for your secrets, edit the appropriate line to reflect the correct variable name. 19 | 20 | 5. In GitHub, enable the `Apply latest configuration to Fleet` GitHub Actions workflow, and run workflow manually. Now, when anyone pushes a new commit to the default branch, the action will run and update Fleet. For pull requests, the workflow will do a dry run only. 21 | 22 | ## GitLab setup 23 | 24 | 1. Clone the [GitLab repository](https://gitlab.com/fleetdm/fleet-gitops), create your own GitLab repository, and push your clone to your new repo. Note that a pipeline will run once and fail because the required variables haven't been added (step 2 and 3). 25 | 26 | 2. Add `FLEET_URL` and `FLEET_API_TOKEN` as masked CI/CD variables. Learn how [here](https://docs.gitlab.com/ee/ci/variables/#define-a-cicd-variable-in-the-ui). Set `FLEET_URL` to your Fleet instance's URL (ex. https://organization.fleet.com). Set `FLEET_API_TOKEN` to an API token for an API-only user in Fleet. Learn how [here](https://fleetdm.com/docs/using-fleet/fleetctl-cli#create-api-only-user), then, grant it the `GitOps` role via the **Settings** > **Users** page so it can make changes. 27 | 28 | 3. Add `FLEET_GLOBAL_ENROLL_SECRET` secret as a masked CI/CD variable. The enroll secret must be an alphanumeric string of at least 32 and at most 255 characters. 29 | - If you have a Premium Fleet license, also add `FLEET_WORKSTATIONS_ENROLL_SECRET` and `FLEET_WORKSTATIONS_CANARY_ENROLL_SECRET`. 30 | - If you do not have a Premium Fleet license, delete the `teams` directory. 31 | 32 | 4. If you are using secrets to manage SSO metadata for Fleet SSO login or MDM SSO login, uncomment lines 22 and 23 in `gitops.sh`. 33 | - If you are using different variable names for your secrets, edit the appropriate line to reflect the correct variable name. 34 | 35 | 5. Now, when anyone pushes a new commit to the default branch, the pipeline will run and update Fleet. For merge requests, the pipeline will do a dry run only. 36 | 37 | 6. (Optional) To ensure your Fleet configuration stays up to date even when there are no new commits, set up a scheduled pipeline: 38 | - In your GitLab project, go to the left sidebar and navigate to **Build > Pipeline schedules**. (In some GitLab versions, this may appear as **CI/CD > Schedules**.) 39 | - Click **Create a new pipeline schedule** (or **Schedule a new pipeline**). 40 | - Fill in the form: 41 | - **Description**: e.g., `Daily GitOps sync` 42 | - **Cron timezone**: e.g., `[UTC 0] UTC` 43 | - **Interval pattern**: e.g., Custom: `0 6 * * *` (runs nightly at 6AM UTC) 44 | - **Target branch or tag**: your default branch (e.g., `main`) 45 | - Click **Create pipeline schedule**. 46 | 47 | ## Configuration options 48 | 49 | For all configuration options, go to the [YAML files reference](https://fleetdm.com/docs/using-fleet/gitops) in the Fleet docs. 50 | 51 | ## Fleet UI 52 | 53 | Once you're set up with GitOps in Fleet, you can optionally put the UI in GitOps mode. This prevents you from making changes in the UI that would be overridden by GitOps workflows. 54 | 55 | An admin can enable GitOps mode in **Settings** > **Integrations** > **Change management**. 56 | 57 | Note that this is a UI-only setting. API permissions are restricted based on user role. 58 | 59 | --------------------------------------------------------------------------------