Merge branch 'main' into feat-mdm-wipe-host

This commit is contained in:
Gabriel Hernandez 2024-02-29 11:22:31 +00:00
commit 4ee65ce184
48 changed files with 4224 additions and 269 deletions

55
.github/workflows/dogfood-gitops.yml vendored Normal file
View File

@ -0,0 +1,55 @@
name: 'Apply latest configuration to dogfood with gitops'
on:
push:
branches:
- main
paths:
- 'it-and-security/**'
- 'mdm_profiles/**'
- '.github/workflows/dogfood-gitops.yml'
workflow_dispatch: # allows manual triggering
defaults:
run:
shell: bash
# Limit permissions of GITHUB_TOKEN.
permissions:
contents: read
jobs:
fleet-gitops:
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- name: Checkout our repository
uses: actions/checkout@v4
- name: Checkout GitOps repository
uses: actions/checkout@v4
with:
repository: fleetdm/fleet-gitops
ref: main
path: fleet-gitops
- name: Apply latest configuration to Fleet
uses: ./fleet-gitops/.github/gitops-action
with:
working-directory: ${{ github.workspace }}/fleet-gitops
env:
FLEET_GITOPS_DIR: ${{ github.workspace }}/it-and-security
FLEET_URL: https://dogfood.fleetdm.com
FLEET_API_TOKEN: ${{ secrets.DOGFOOD_API_TOKEN }}
DOGFOOD_APPLE_BM_DEFAULT_TEAM: Workstations
DOGFOOD_MACOS_MIGRATION_WEBHOOK_URL: ${{ secrets.DOGFOOD_MACOS_MIGRATION_WEBHOOK_URL }}
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_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 }}
DOGFOOD_WORKSTATIONS_CANARY_ENROLL_SECRET: ${{ secrets.DOGFOOD_WORKSTATIONS_CANARY_ENROLL_SECRET }}
DOGFOOD_SERVERS_ENROLL_SECRET: ${{ secrets.DOGFOOD_SERVERS_ENROLL_SECRET }}
DOGFOOD_SERVERS_CANARY_ENROLL_SECRET: ${{ secrets.DOGFOOD_SERVERS_CANARY_ENROLL_SECRET }}
DOGFOOD_EXPLORE_DATA_ENROLL_SECRET: ${{ secrets.DOGFOOD_EXPLORE_DATA_ENROLL_SECRET }}

View File

@ -1,44 +0,0 @@
# This workflow applies the latest configuration profiles (macOS settings) and macOS updates minimum version and deadline to the provided team.
name: Apply latest configuration profiles (example)
on:
push:
branches:
- main
paths:
- "path/to/**.mobileconfig"
workflow_dispatch: # Manual
# 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
env:
FLEET_API_TOKEN: ${{ secrets.DOGFOOD_API_TOKEN }}
FLEET_URL: ${{ secrets.DOGFOOD_URL }}
TOKEN_USED_BY_PROFILE: ${{ secrets.TOKEN_USED_BY_PROFILE }}
jobs:
apply-profiles:
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- name: Apply configuration profiles and updates
uses: fleetdm/fleet-mdm-gitops@15072f2739ef92c6357414ddd86e89b6bf302a2b
with:
FLEET_API_TOKEN: $FLEET_API_TOKEN
FLEET_URL: $FLEET_URL
FLEET_TEAM_NAME: 💻🐣 Workstations (canary)
MDM_CONFIG_REPO: fleetdm/fleet
MDM_CONFIG_DIRECTORY: mdm_profiles
MAC_OS_MIN_VERSION: 13.3.2
MAC_OS_VERSION_DEADLINE: 2023-06-15
MAC_OS_ENABLE_DISK_ENCRYPTION: true

View File

@ -1,49 +0,0 @@
# This workflow applies the latest configuration profiles (macOS settings) and macOS updates minimum version and deadline to the workstations (canary) team.
# It uses a fleet instance also built and executed from source.
#
# It runs automatically when a file is changed in /mdm_profiles.
name: Apply latest configuration profiles and macOS updates (Canary)
on:
push:
branches:
- main
paths:
- "mdm_profiles/**.mobileconfig"
- ".github/workflows/fleetctl-workstations-canary.yml"
workflow_dispatch: # Manual
# 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
env:
DOGFOOD_API_TOKEN: ${{ secrets.DOGFOOD_API_TOKEN }}
DOGFOOD_URL: ${{ secrets.DOGFOOD_URL }}
CLOUD_MANAGEMENT_ENROLLMENT_TOKEN: ${{ secrets.CLOUD_MANAGEMENT_ENROLLMENT_TOKEN }}
jobs:
apply-profiles:
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- name: Apply configuration profiles and updates
uses: fleetdm/fleet-mdm-gitops@15072f2739ef92c6357414ddd86e89b6bf302a2b # v1.1.0
with:
FLEET_API_TOKEN: $DOGFOOD_API_TOKEN
FLEET_URL: $DOGFOOD_URL
FLEET_TEAM_NAME: 💻🐣 Workstations (canary)
MDM_CONFIG_REPO: fleetdm/fleet
MDM_CONFIG_DIRECTORY: mdm_profiles
MAC_OS_MIN_VERSION: "14.2"
MAC_OS_VERSION_DEADLINE: 2023-12-15
MAC_OS_ENABLE_DISK_ENCRYPTION: true

View File

@ -1,49 +0,0 @@
# This workflow applies the latest configuration profiles (macOS settings) and macOS updates minimum version and deadline to the workstations team.
# It uses a Fleet instance also built and executed from source.
#
# It runs when the GitHub action is triggered manually
name: Apply latest configuration profiles and macOS updates
on:
push:
branches:
- main
paths:
- "mdm_profiles/**.mobileconfig"
- ".github/workflows/fleetctl-workstations.yml"
workflow_dispatch: # Manual
# 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
env:
DOGFOOD_API_TOKEN: ${{ secrets.DOGFOOD_API_TOKEN }}
DOGFOOD_URL: ${{ secrets.DOGFOOD_URL }}
CLOUD_MANAGEMENT_ENROLLMENT_TOKEN: ${{ secrets.CLOUD_MANAGEMENT_ENROLLMENT_TOKEN }}
jobs:
apply-profiles:
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- name: Apply configuration profiles and updates
uses: fleetdm/fleet-mdm-gitops@15072f2739ef92c6357414ddd86e89b6bf302a2b # v1.1.0
with:
FLEET_API_TOKEN: $DOGFOOD_API_TOKEN
FLEET_URL: $DOGFOOD_URL
FLEET_TEAM_NAME: 💻 Workstations
MDM_CONFIG_REPO: fleetdm/fleet
MDM_CONFIG_DIRECTORY: mdm_profiles
MAC_OS_MIN_VERSION: "14.2"
MAC_OS_VERSION_DEADLINE: 2023-12-19
MAC_OS_ENABLE_DISK_ENCRYPTION: true

View File

@ -7,7 +7,7 @@ on:
- patch-*
pull_request:
paths:
- 'ee/tools/puppet/fleetdm/*.*'
- 'ee/tools/puppet/fleetdm/**'
- '.github/workflows/test-puppet.yml'
workflow_dispatch: # Manual

View File

@ -0,0 +1 @@
Added --server_frequent_cleanups_enabled (FLEET_SERVER_FREQUENT_CLEANUPS_ENABLED) flag to enable cron job to clean up stale data running every 15 minutes. Currently disabled by default.

1
changes/jve-16335 Normal file
View File

@ -0,0 +1 @@
- Enables usage of `<Add>` nodes in Windows MDM profiles.

View File

@ -705,7 +705,6 @@ func newCleanupsAndAggregationSchedule(
ctx context.Context,
instanceID string,
ds fleet.Datastore,
lq fleet.LiveQueryStore,
logger kitlog.Logger,
enrollHostLimiter fleet.EnrollHostLimiter,
config *config.FleetConfig,
@ -721,6 +720,13 @@ func newCleanupsAndAggregationSchedule(
schedule.WithAltLockID("leader"),
schedule.WithLogger(kitlog.With(logger, "cron", name)),
// Run cleanup jobs first.
schedule.WithJob(
"distributed_query_campaigns",
func(ctx context.Context) error {
_, err := ds.CleanupDistributedQueryCampaigns(ctx, time.Now().UTC())
return err
},
),
schedule.WithJob(
"incoming_hosts",
func(ctx context.Context) error {
@ -846,16 +852,16 @@ func newFrequentCleanupsSchedule(
s := schedule.New(
ctx, name, instanceID, defaultInterval, ds, ds,
// Using leader for the lock to be backwards compatilibity with old deployments.
schedule.WithAltLockID("leader"),
schedule.WithAltLockID("leader_frequent_cleanups"),
schedule.WithLogger(kitlog.With(logger, "cron", name)),
// Run cleanup jobs first.
schedule.WithJob(
"distributed_query_campaigns",
"redis_live_queries",
func(ctx context.Context) error {
_, err := ds.CleanupDistributedQueryCampaigns(ctx, time.Now().UTC())
if err != nil {
return err
}
// It's necessary to avoid lingering live queries in case of:
// - (Unknown) bug in the implementation, or,
// - Redis is so overloaded already that the lq.StopQuery in svc.CompleteCampaign fails to execute, or,
// - MySQL is so overloaded that ds.SaveDistributedQueryCampaign in svc.CompleteCampaign fails to execute.
names, err := lq.LoadActiveQueryNames()
if err != nil {
return err
@ -865,10 +871,8 @@ func newFrequentCleanupsSchedule(
if err != nil {
return err
}
if err := lq.CleanupInactiveQueries(ctx, completed); err != nil {
return err
}
return nil
err = lq.CleanupInactiveQueries(ctx, completed)
return err
},
),
)

View File

@ -680,10 +680,14 @@ the way that the Fleet server works.
}
}()
if err := cronSchedules.StartCronSchedule(func() (fleet.CronSchedule, error) {
return newFrequentCleanupsSchedule(ctx, instanceID, ds, liveQueryStore, logger)
}); err != nil {
initFatal(err, "failed to register frequent_cleanups schedule")
if config.Server.FrequentCleanupsEnabled {
if err := cronSchedules.StartCronSchedule(
func() (fleet.CronSchedule, error) {
return newFrequentCleanupsSchedule(ctx, instanceID, ds, liveQueryStore, logger)
},
); err != nil {
initFatal(err, "failed to register frequent_cleanups schedule")
}
}
if err := cronSchedules.StartCronSchedule(
@ -693,7 +697,7 @@ the way that the Fleet server works.
commander = apple_mdm.NewMDMAppleCommander(mdmStorage, mdmPushService)
}
return newCleanupsAndAggregationSchedule(
ctx, instanceID, ds, liveQueryStore, logger, redisWrapperDS, &config, commander,
ctx, instanceID, ds, logger, redisWrapperDS, &config, commander,
)
},
); err != nil {

View File

@ -2155,9 +2155,9 @@ Returns the count of all hosts organized by status. `online_count` includes all
| Name | Type | In | Description |
| --------------- | ------- | ---- | ------------------------------------------------------------------------------- |
| team_id | integer | query | The ID of the team whose host counts should be included. Defaults to all teams. |
| team_id | integer | query | _Available in Fleet Premium_. The ID of the team whose host counts should be included. Defaults to all teams. |
| platform | string | query | Platform to filter by when counting. Defaults to all platforms. |
| low_disk_space | integer | query | _Available in Fleet Premium_ Returns the count of 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_. Returns the count of hosts with less GB of disk space available than this value. Must be a number between 1-100. |
#### Example
@ -3464,11 +3464,10 @@ Retrieves the aggregated host OS versions information.
| Name | Type | In | Description |
| --- | --- | --- | --- |
| team_id | integer | query | _Available in Fleet Premium_ Filters the hosts to only include hosts in the specified team. If not provided, all hosts are included. |
| team_id | integer | query | _Available in Fleet Premium_. Filters to only include OS versions for hosts on the specified team. If not provided, OS versions for all hosts are included. |
| 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` |
| team_id | integer | query | _Available in Fleet Premium_. Filters to only include OS versions for the specified 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: `hosts_count`. Default is `hosts_count` (descending). |
@ -6268,7 +6267,7 @@ Returns a list of global queries or team queries.
| --------------- | ------- | ----- | ----------------------------------------------------------------------------------------------------------------------------- |
| order_key | string | query | What to order results by. Can be any column in the queries 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`. |
| team_id | integer | query | The ID of the parent team for the queries to be listed. When omitted, returns global queries. |
| team_id | integer | query | _Available in Fleet Premium_. The ID of the parent team for the queries to be listed. When omitted, returns global queries. |
| query | string | query | Search query keywords. Searchable fields include `name`. |
@ -6589,7 +6588,7 @@ Creates a global query or team 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). |
| team_id | integer | body | The parent team to which the new query should be added. If omitted, the query will be global. |
| 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. |
| 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. |
@ -6732,7 +6731,7 @@ Deletes the query specified by name.
| Name | Type | In | Description |
| ---- | ---------- | ---- | ------------------------------------ |
| name | string | path | **Required.** The name of the query. |
| team_id | integer | body | The ID of the parent team of the query to be deleted. If omitted, Fleet will search among queries in the global context. |
| team_id | integer | body | _Available in Fleet Premium_. The ID of the parent team of the query to be deleted. If omitted, Fleet will search among queries in the global context. |
#### Example
@ -7456,7 +7455,7 @@ Uploads a script, making it available to run on hosts assigned to the specified
| Name | Type | In | Description |
| ---- | ------- | ---- | -------------------------------------------- |
| script | file | form | **Required**. The file containing the script. |
| team_id | integer | form | The team ID. If specified, the script will only be available to hosts assigned to this team. If not specified, the script will only be available to hosts on **no team**. |
| team_id | integer | form | _Available in Fleet Premium_. The team ID. If specified, the script will only be available to hosts assigned to this team. If not specified, the script will only be available to hosts on **no team**. |
#### Example

View File

@ -8,15 +8,21 @@ Puppet::Reports.register_report(:fleetdm) do
def process
return if noop
client = Puppet::Util::FleetClient.instance
node_name = Puppet[:node_name_value]
if resource_statuses.any? { |r| r.include?('error pre-setting fleetdm::profile') }
Puppet.err("Some resources failed to be assigned, not matching profiles for #{node_name}")
return
end
client = Puppet::Util::FleetClient.instance
run_identifier = "#{catalog_uuid}-#{node_name}"
response = client.match_profiles(run_identifier, environment)
if response['error'].empty?
Puppet.info("Successfully matched #{node_name} with a team containing configuration profiles")
else
Puppet.err("Error matching node #{node_name} with a team containing configuration profiles: #{response['error']}")
return
end
Puppet.err("Error matching node #{node_name} with a team containing configuration profiles: #{response['error']}")
end
end

View File

@ -47,13 +47,13 @@ define fleetdm::profile (
$changed = $response['resource_changed']
if $err != '' {
notify { "error pre-setting profile ${name} as ${ensure}: ${err}":
notify { "error pre-setting fleetdm::profile ${name} as ${ensure}: ${err}":
loglevel => 'err',
}
} elsif $changed {
# NOTE: sending a notification also marks the
# 'fleetdm::profile' as changed in the reports.
notify { "successfully pre-set profile ${name} as ${ensure}": }
notify { "successfully pre-set fleetdm::profile ${name} as ${ensure}": }
}
}
}

View File

@ -0,0 +1,64 @@
# frozen_string_literal: true
require 'spec_helper'
require 'puppet/reports'
require_relative '../../../lib/puppet/reports/fleetdm.rb'
describe 'Puppet::Reports::Fleetdm' do
let(:fleet_client_mock) { instance_double('Puppet::Util::FleetClient') }
let(:catalog_uuid) { '827a74c8-cf98-44da-9ff7-18c5e4bee41e' }
let(:node_name) { Puppet[:node_name_value] }
let(:report) do
report = Puppet::Transaction::Report.new('apply')
report.extend(Puppet::Reports.report(:fleetdm))
report
end
before(:each) do
Puppet[:reports] = 'fleetdm'
Puppet::Util::Log.level = :warning
Puppet::Util::Log.newdestination(:console)
fleet_client_class = class_spy('Puppet::Util::FleetClient')
stub_const('Puppet::Util::FleetClient', fleet_client_class)
allow(fleet_client_class).to receive(:instance) { fleet_client_mock }
allow(SecureRandom).to receive(:uuid).and_return(catalog_uuid)
end
it 'does not process in noop mode' do
allow(report).to receive(:noop).and_return(true)
expect(fleet_client_mock).not_to receive(:match_profiles)
report.process
end
it 'logs an error if resources failed to be assigned' do
allow(report).to receive(:resource_statuses).and_return({ 'myresource' => 'error pre-setting fleetdm::profile' })
expect(Puppet).to receive(:err).with(%r{Some resources failed to be assigned})
expect(fleet_client_mock).not_to receive(:match_profiles)
report.process
end
it 'successfully matches profiles when there are no errors' do
allow(report).to receive(:noop).and_return(false)
allow(report).to receive(:resource_statuses).and_return({})
allow(fleet_client_mock).to receive(:match_profiles).and_return({ 'error' => '' })
allow(report).to receive(:catalog_uuid).and_return(catalog_uuid)
expect(fleet_client_mock).to receive(:match_profiles).with("#{catalog_uuid}-#{node_name}", anything)
expect(Puppet).to receive(:info).with("Successfully matched #{node_name} with a team containing configuration profiles")
report.process
end
it 'logs an error when matching profiles fails' do
allow(report).to receive(:noop).and_return(false)
allow(report).to receive(:resource_statuses).and_return({})
allow(fleet_client_mock).to receive(:match_profiles).and_return({ 'error' => 'Some error' })
allow(report).to receive(:catalog_uuid).and_return(catalog_uuid)
expect(fleet_client_mock).to receive(:match_profiles).with("#{catalog_uuid}-#{node_name}", anything)
expect(Puppet).to receive(:err).with("Error matching node #{node_name} with a team containing configuration profiles: Some error")
report.process
end
end

View File

@ -147,6 +147,37 @@ When a Fleetie, consultant or advisor requests an update to their personnel deta
- If required, BizOps also makes changes to other core systems (e.g: creating a new email alias in google workspace; updating details in Carta; etc).
- The change is now actioned, notify the team member and close the issue.
### Change a Fleetie's job title
When BizOps receives notification of a Fleetie's job title changing, follow these steps to ensure accurate recording of the change across our systems.
- Update the ["🧑‍🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0) spreadsheet:
- Search the spreadsheet for the Fleetie in need of a job title change.
- Input the new job title in the Fleetie's row in the "Job title" cell.
- Navigate to the "Org chart" tab of the spreadsheet, and verify that the Fleetie's title appears correctly in the org chart.
- Update the relevant HRIS system.
- For updating Gusto (US-based Fleeties):
- Login to Gusto and navigate to "People > Team members".
- Find the Fleetie and select them to see their profile page.
- Under the "Compensation" heading, select edit and update the "Job title" and input the specific date the change happened. Save the changes.
- For updating Plane (non-US Fleeties):
- Login to Plane and navigate to "People > Team".
- Find the Fleetie and select them to see their profile page.
- Use the "Help" function, or email support@plane.com to notify Plane of the need to change the job title for the Fleetie. Include the Fleetie's name, current title, new title, and effective date.
- Take any relevant steps as directed by Plane in order to make the required changes to the Fleetie's profile.
### Change a Fleetie's manager
When BizOps receives notification of a Fleetie's manager changing, follow these steps to ensure correct recording in our systems.
- Update the [Fleeties](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0).
- Search for the Fleetie's new manager, and copy the new manager's unique ID from the far left "Unique ID" column.
- Search for the Fleetie who's manager is changing, and paste (without formatting) their new manager's unique ID in the "Reports to: (manager unique ID)" cell in the Fleetie's row.
- Verify that the "Reports to (auto: manager name and job title)" cell in the Fleetie's row reflects the new manager's details.
- Verify that in the new manager's row, the "# direct reports" cell reflect the correct number.
- Navigate to the "Org chart" tab in the spreadsheet, and verify that the Fleetie now appears in the correct place in the org chart.
> **Note:** The Fleeties spreadsheet is the source of truth for this information, and any other systems reflecting reporting lines should be disregarded.
### Prepare salary benchmarking information
- Use the relevant template text in the README section of the [¶¶ 💌 Compensation decisions document](https://docs.google.com/document/d/1NQ-IjcOTbyFluCWqsFLMfP4SvnopoXDcX0civ-STS5c/edit?usp=sharing) for a current Fleetie, a new role, a prospective hire, or other benchmarking use case.
- Copy the template text and paste at the end of the document.

View File

@ -253,6 +253,7 @@ Labels with a `#g-` prefix refer to a kanban board. Since it is best practice to
> - `bug-` Defect category. For example, `bug-enrollment`, `bug-profiles-sync`, `bug-policies`. This allows us to track the areas of the product producing the most bugs.
> - `story` A user story.
> - `prospect-` A customer prospect.
> - `P-` A [priority level](https://fleetdm.com/handbook/company/product-groups#high-priority-user-stories-and-bugs).
> - `Epic` Do not use. _(TODO: ZenHub automatically recreates this label when we group sub-tasks inside of a user story. Find a way to remove this. It is an artifact from Zenhub and not something we actually want to exist or use, as it is confusing.)_
### Process new requests

View File

@ -141,17 +141,6 @@ User stories are small and independently valuable.
- Is it small enough? Will this task be likely to fit in 1 sprint when estimated?
- Is it valuable enough? Will this task drive business value when released, independent of other tasks?
#### Engineering-initiated stories
<!-- TODO: Move steps to "Create an Engineering-initiated story" to handbook/engineering#responsibilities -->
Engineering-initiated stories are types of user stories created by engineers to make technical changes to Fleet. Technical changes should improve the user experience or contributor experience. For example, optimizing SQL that improves the response time of an API endpoint improves user experience by reducing latency. A script that generates common boilerplate, or automated tests to cover important business logic, improves the quality of life for contributors, making them happier and more productive, resulting in faster delivery of features to our customers.
It is important to frame engineering-initiated user stories the same way we frame all user stories. Stay focused on how this technical change will drive value for our users.
To [create an engineering-initiated user story](https://fleetdm.com/handbook/engineering#creating-an-engineering-initiated-story), follow the [user story drafting process](https://fleetdm.com/handbook/company/development-groups#drafting). Once your user story is created using the [new story template](https://github.com/fleetdm/fleet/issues/new?assignees=&labels=story,~engineering-initiated&projects=&template=story.md&title=), add the `~engineering-initiated` label, assign it to yourself, and bring to your EM to be considered for future prioritization into a sprint. The engineering output and architecture DRI is responsible for prioritizing engineering-initiated stories.
> We prefer the term engineering-initiated stories over technical debt because the user story format helps keep us focused on our users.
#### Defining "done"
To successfully deliver a user story, the people working on it need to know what "done" means.
@ -428,6 +417,28 @@ Fleet [always prioritizes bugs](https://fleetdm.com/handbook/product#prioritizin
#### Awaiting QA
Bugs will be verified as fixed by QA when they are placed in the "Awaiting QA" column of the relevant product group's sprint board. If the bug is verified as fixed, it is moved to the "Ready for release" column of the sprint board. Otherwise, the remaining issues are noted in a comment, and it is moved back to the "In progress" column of the sprint board.
## High priority user stories and bugs
All issues are treated as standard priority by default. Some issues are assigned a priority label to indicate urgency for the business.
1. Emergency: `P0`
- Examples: Customer outage, confirmed security vulnerability ([critical bug](https://fleetdm.com/handbook/company/product-groups#release-testing)), a new feature is needed to address an immediate business emergency.
- Response: Immediately stop other work to swarm the issue. Work 24/7 in shifts until resolved.
- Impact: Significant impact. May void current sprint.
2. Critical: `P1`
- Examples: A supported workflow is broken ([critical bug](https://fleetdm.com/handbook/company/product-groups#release-testing)), a potential security vulnerability, a new feature is required to address an immediate critical business need.
- Response: Issue brought to next standup for estimation and immediately brought into the sprint. Necessary team members are assigned as their top priority.
- Impact: High impact. Does not void sprint, but reduces overall velocity and requires deprioritizing other work.
3. Urgent: `P2`
- Examples: A supported workflow is not functioning as intended, a newly drafted feature has an associated urgent business need.
- Response: Issue is prioritized at the top of the next sprint. If opporunity cost of waiting for the next sprint is too high, it may be considered for current sprint.
- Impact: Low to medium impact. If prioritized into current sprint, may reduce overall velocity and require deprioritizing other work.
Add as much context as possible to the issue description and assign labels to help the team understand the problem and what is driving the urgency. All issues with a `P0`, `P1`, or `P2` label should be assigned to the [DRI for what goes in a release](https://fleetdm.com/handbook/company/communications#directly-responsible-individuals-dris). For immediate action, follow up on Slack or by phone.
Once the release DRI is aware of the issue, they will adjust the labels as needed and assign to the PM and EM of the appropriate product group. If they disagree with the priority label applied to the issue, they will contact the requestor to discuss further.
## How to reach the developer on-call
Oncall engineers do not need to actively monitor Slack channels, except when called in by the Community or Customer teams. Members of those teams are instructed to `@oncall` in `#help-engineering` to get the attention of the on-call engineer to continue discussing any issues that come up. In some cases, the Community or Customer representative will continue to communicate with the requestor. In others, the on-call engineer will communicate directly (team members should use their judgment and discuss on a case-by-case basis how to best communicate with community members and customers).
@ -464,13 +475,11 @@ The on-call developer is encouraged to attend some of the customer success meeti
This has a dual purpose of providing more context for how our customers use Fleet. The developer should actively participate and provide input where appropriate (if not sure, please ask your manager or organizer of the call).
- **Documentation for contributors**
Fleet's documentation for contributors can be found in the [Fleet GitHub repo](https://github.com/fleetdm/fleet/tree/main/docs/Contributing).
The on-call developer is asked to read, understand, test, correct, and improve at least one doc page per week. Our goal is to 1, ensure accuracy and verify that our deployment guides and tutorials are up to date and work as expected. And 2, improve the readability, consistency, and simplicity of our documentation with empathy towards first-time users. See [Writing documentation](https://fleetdm.com/handbook/marketing#writing-documentation) for writing guidelines, and don't hesitate to reach out to [#g-digital-experience](https://fleetdm.slack.com/archives/C01GQUZ91TN) on Slack for writing support. A backlog of documentation improvement needs is kept [here](https://github.com/fleetdm/fleet/issues?q=is%3Aopen+is%3Aissue+label%3A%22%3Aimprove+documentation%22).
### Escalations
When the on-call developer is unsure of the answer, they should follow this process for escalation.
@ -482,7 +491,6 @@ How to escalate:
2. Create a new thread in the [#help-engineering channel](https://fleetdm.slack.com/archives/C019WG4GH0A), tagging `@zwass` and provide the information turned up in your research. Please include possibly relevant links (even if you didn't find what you were looking for there). Zach will work with you to craft an appropriate answer or find another team member who can help.
### Changing of the guard
The on-call developer changes each week on Wednesday.
@ -509,8 +517,7 @@ In the Slack reminder thread, the on-call developer includes their retrospective
## Wireframes
- Showing these principles and ideas, to help remember the pros and cons and conceptualize the above visually.
- Figma: [⚗️ Fleet product project](https://www.figma.com/files/project/17318630/%E2%9A%97%EF%B8%8F-Fleet-product?fuid=1234929285759903870)
- Figma: [⚗️ Fleet product project](https://www.figma.com/files/project/17318630/%E2%9A%97%EF%B8%8F-Fleet-product?fuid=1234929285759903870)
We have certain design conventions that we include in Fleet. We will document more of these over time.
@ -581,22 +588,6 @@ OPTIONS
## Meetings
<!-- TODO: Find out what to do with this stuff. Delete?
### Eng Together
This meeting is to disseminate engineering-wide announcements, promote cohesion across groups within the engineering team, and connect with engineers (and the "engineering-curious") in other departments. Held monthly for one hour.
**Participants:** Everyone at the company is welcome to attend. All engineers are asked to attend. The subject matter is focused on engineering.
**Agenda:**
- Announcements
- Engineering KPIs review
- “Tech talks”
- At least one engineer from each product group demos or discusses a technical aspect of their recent work.
- Everyone is welcome to present on a technical topic. Add your name and tech talk subject in the agenda doc included in the Eng Together calendar event.
- Social
- Structured and/or unstructured social activities
### User story discovery
User story discovery meetings are scheduled as needed to align on large or complicated user stories. Before a discovery meeting is scheduled, the user story must be prioritized for product drafting and go through the design and specification process. When the user story is ready to be estimated, a user story discovery meeting may be scheduled to provide more dedicated, synchronous time for the team to discuss the user story than is available during weekly estimation sessions.
@ -617,18 +608,6 @@ All participants are expected to review the user story and associated designs an
- Software Engineers: Clarifying questions and implementation details
- Product Quality Specialist: Testing plan
### Group weeklies
A chance for deeper, synchronous discussion on topics relevant across product groups like “Frontend weekly”, “Backend weekly”, etc.
**Participants:** Anyone who wishes to participate.
**Sample agenda (Frontend weekly)**
- Discuss common patterns and conventions in the codebase
- Review difficult frontend bugs
- Write engineering-initiated stories
-->
### Design consultation
Design consultations are scheduled as needed with the relevant participants, typically product designers and frontend engineers. It is an opportunity to collaborate and discuss design, implementation, and story requirements. The meeting is scheduled as needed by the product designer or frontend engineer when a user story is in the "Prioritized" column on the [drafting board](https://app.zenhub.com/workspaces/-drafting-ships-in-6-weeks-6192dd66ea2562000faea25c/board).
@ -662,6 +641,30 @@ QA has weekly check-in with product to go over the inbox items. QA is responsibl
QA may also propose that a reported bug is not actually a bug. A bug is defined as “behavior that is not according to spec or implied by spec.” If agreed that it is not a bug, then it's assigned to the relevant product manager to determine its priority.
### Group weeklies
A chance for deeper, synchronous discussion on topics relevant across product groups like “Frontend weekly”, “Backend weekly”, etc.
**Participants:** Anyone who wishes to participate.
**Sample agenda from frontend weekly**
- Discuss common patterns and conventions in the codebase
- Review difficult frontend bugs
- Write engineering-initiated stories
### Eng Together
This meeting is to disseminate engineering-wide announcements, promote cohesion across groups within the engineering team, and connect with engineers (and the "engineering-curious") in other departments. Held monthly for one hour.
**Participants:** Everyone at the company is welcome to attend. All engineers are asked to attend. The subject matter is focused on engineering.
**Agenda:**
- Announcements
- Engineering KPIs review
- “Tech talks”
- At least one member from each product group demos or discusses a technical subject relevant to engineering at Fleet.
- Everyone is welcome to present on a technical topic. Add your name and tech talk subject in the agenda doc included in the Eng Together calendar event.
- Social
- Structured and/or unstructured social activities
## Development best practices
- Remember the user. What would you do if you saw that error message? [🔴](https://fleetdm.com/handbook/company#empathy)
- Communicate any blockers ASAP in your group Slack channel or standup. [🟠](https://fleetdm.com/handbook/company#ownership)

View File

@ -27,6 +27,15 @@ The metrics are:
Each week these are tracked and shared in the weekly KPI sheet by Luke Heath.
#### Create an engineering-initiated story
Engineering-initiated stories are types of user stories created by engineers to make technical changes to Fleet. Technical changes should improve the user experience or contributor experience. For example, optimizing SQL that improves the response time of an API endpoint improves user experience by reducing latency. A script that generates common boilerplate, or automated tests to cover important business logic, improves the quality of life for contributors, making them happier and more productive, resulting in faster delivery of features to our customers.
It is important to frame engineering-initiated user stories the same way we frame all user stories. Stay focused on how this technical change will drive value for our users.
To [create an engineering-initiated user story](https://fleetdm.com/handbook/engineering#creating-an-engineering-initiated-story), follow the [user story drafting process](https://fleetdm.com/handbook/company/development-groups#drafting). Once your user story is created using the [new story template](https://github.com/fleetdm/fleet/issues/new?assignees=&labels=story,~engineering-initiated&projects=&template=story.md&title=), add the `~engineering-initiated` label, assign it to yourself, and bring to your EM to be considered for future prioritization into a sprint. The engineering output and architecture DRI is responsible for prioritizing engineering-initiated stories.
> We prefer the term engineering-initiated stories over technical debt because the user story format helps keep us focused on our users and contributors.
### Begin a merge freeze
To ensure release quality, Fleet has a freeze period for testing beginning the Tuesday before the release at 9:00 AM Pacific. Effective at the start of the freeze period, new feature work will not be merged into `main`.
@ -180,7 +189,6 @@ When merging a pull request from a community contributor:
- Thank and congratulate the contributor.
- Share the merged PR with the team in the #help-promote channel of Fleet Slack to be publicized on social media. Those who contribute to Fleet and are recognized for their contributions often become great champions for the project.
### Schedule developer on-call workload
Engineering managers are asked to be aware of the [on-call rotation](https://docs.google.com/document/d/1FNQdu23wc1S9Yo6x5k04uxT2RwT77CIMzLLeEI2U7JA/edit#) and schedule a light workload for engineers while they are on-call. While it varies week to week considerably, the on-call responsibilities can sometimes take up a substantial portion of the engineer's time.
@ -334,40 +342,40 @@ Please see [handbook/engineering#notify-community-members-about-a-critical-bug](
Please see [handbook/engineering#run-fleet-locally-for-qa-purposes](https://fleetdm.com/handbook/engineering#run-fleet-localy-for-qa-purposes)
##### Scrum at Fleet
Please see [handbook/company/product-groups#engineering-initiated-stories](https://fleetdm.com/handbook/company/product-groups#scrum-at-fleet)
Please see [handbook/company/engineering#create-an-engineering-initiated-story](https://fleetdm.com/handbook/company/product-groups#scrum-at-fleet)
##### Scrum items
Please see [handbook/company/product-groups#engineering-initiated-stories](https://fleetdm.com/handbook/company/product-groups#scrum-items)
Please see [handbook/company/engineering#create-an-engineering-initiated-story](https://fleetdm.com/handbook/company/product-groups#scrum-items)
##### Sprint ceremonies
Please see [handbook/company/product-groups#engineering-initiated-stories](https://fleetdm.com/handbook/company/product-groups#sprint-ceremonies)
Please see [handbook/company/engineering#create-an-engineering-initiated-story](https://fleetdm.com/handbook/company/product-groups#sprint-ceremonies)
##### Meetings
Please see [handbook/company/product-groups#engineering-initiated-stories](https://fleetdm.com/handbook/company/product-groups#meetings)
Please see [handbook/company/engineering#create-an-engineering-initiated-story](https://fleetdm.com/handbook/company/product-groups#meetings)
##### Principles
Please see [handbook/company/product-groups#engineering-initiated-stories](https://fleetdm.com/handbook/company/product-groups#principles)
Please see [handbook/company/engineering#create-an-engineering-initiated-story](https://fleetdm.com/handbook/company/product-groups#principles)
Please see [handbook/company/product-groups#engineering-initiated-stories](https://fleetdm.com/handbook/company/product-groups#eng-together) for **below**
Please see [handbook/company/engineering#create-an-engineering-initiated-story](https://fleetdm.com/handbook/company/product-groups#eng-together) for **below**
##### Eng Together
##### Participants
##### Agenda
Please see [handbook/company/product-groups#engineering-initiated-stories](https://fleetdm.com/handbook/company/product-groups#eng-together) for **above**
Please see [handbook/company/engineering#create-an-engineering-initiated-story](https://fleetdm.com/handbook/company/product-groups#eng-together) for **above**
Please see [handbook/company/product-groups#engineering-initiated-stories](https://fleetdm.com/handbook/company/product-groups#group-weeklies) for **below**
Please see [handbook/company/engineering#create-an-engineering-initiated-story](https://fleetdm.com/handbook/company/product-groups#group-weeklies) for **below**
##### User story discovery
##### Participants
##### Agenda
Please see [handbook/company/product-groups#engineering-initiated-stories](https://fleetdm.com/handbook/company/product-groups#group-weeklies) for **above**
Please see [handbook/company/engineering#create-an-engineering-initiated-story](https://fleetdm.com/handbook/company/product-groups#group-weeklies) for **above**
Please see [handbook/company/product-groups#engineering-initiated-stories](https://fleetdm.com/handbook/company/product-groups#group-weeklies) for **below**
Please see [handbook/company/engineering#create-an-engineering-initiated-story](https://fleetdm.com/handbook/company/product-groups#group-weeklies) for **below**
##### Group weeklies
##### Participants
##### Sample agenda (Frontend weekly)
Please see [handbook/company/product-groups#engineering-initiated-stories](https://fleetdm.com/handbook/company/product-groups#group-weeklies) for **above**
Please see [handbook/company/engineering#create-an-engineering-initiated-story](https://fleetdm.com/handbook/company/product-groups#group-weeklies) for **above**
##### Engineering-initiated stories
Please see [handbook/company/product-groups#engineering-initiated-stories](https://fleetdm.com/handbook/company/product-groups#engineering-initiated-stories)
Please see [handbook/company/engineering#create-an-engineering-initiated-story](https://fleetdm.com/handbook/company/engineering#create-an-engineering-initiated-story)
##### Creating an engineering-initiated story
Please see [handbook/engineering#create-an-engineering-initiated-user-story](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-user-story)

View File

@ -0,0 +1,86 @@
agent_options:
path: ./lib/agent-options.yml
controls:
enable_disk_encryption: true
macos_migration:
enable: true
mode: voluntary
webhook_url: $DOGFOOD_MACOS_MIGRATION_WEBHOOK_URL
macos_settings:
custom_settings: null
macos_setup:
bootstrap_package: ""
enable_end_user_authentication: false
macos_setup_assistant: null
macos_updates:
deadline: "2023-06-13"
minimum_version: 13.4.1
windows_enabled_and_configured: true
windows_settings:
custom_settings: []
windows_updates:
deadline_days: 3
grace_period_days: 2
scripts: []
org_settings:
features:
enable_host_users: true
enable_software_inventory: true
fleet_desktop:
transparency_url: https://fleetdm.com/transparency
host_expiry_settings:
host_expiry_enabled: false
host_expiry_window: 7
integrations:
jira: [ ]
zendesk: [ ]
mdm:
apple_bm_default_team: $DOGFOOD_APPLE_BM_DEFAULT_TEAM
org_info:
contact_url: https://fleetdm.com/company/contact
org_logo_url: ""
org_logo_url_light_background: ""
org_name: Fleet Device Management
secrets:
- secret: $DOGFOOD_GLOBAL_ENROLL_SECRET
server_settings:
debug_host_ids:
- 1
- 3
deferred_save_host: false
enable_analytics: true
live_query_disabled: false
query_reports_disabled: false
scripts_disabled: false
server_url: https://dogfood.fleetdm.com
sso_settings:
enable_jit_provisioning: true
enable_jit_role_sync: false
enable_sso: true
enable_sso_idp_login: false
entity_id: dogfood.fleetdm.com
idp_image_url: ""
idp_name: Google
issuer_uri: $DOGFOOD_SSO_ISSUER_URI
metadata: |-
$DOGFOOD_SSO_METADATA
metadata_url: ""
webhook_settings:
failing_policies_webhook:
destination_url: $DOGFOOD_FAILING_POLICIES_WEBHOOK_URL
enable_failing_policies_webhook: true
host_batch_size: 0
policy_ids: []
host_status_webhook:
days_count: 1
destination_url: ""
enable_host_status_webhook: false
host_percentage: 25
interval: 1m0s
vulnerabilities_webhook:
destination_url: $DOGFOOD_VULNERABILITIES_WEBHOOK_URL
enable_vulnerabilities_webhook: true
host_batch_size: 0
policies:
queries:
- path: ./lib/collect-fleetd-update-channels.queries.yml

View File

@ -0,0 +1,13 @@
config:
decorators:
load:
- SELECT uuid AS host_uuid FROM system_info;
- SELECT hostname AS hostname FROM system_info;
options:
disable_distributed: false
distributed_interval: 10
distributed_plugin: tls
distributed_tls_max_attempts: 3
logger_tls_endpoint: /api/osquery/log
logger_tls_period: 10
pack_delimiter: /

View File

@ -0,0 +1,14 @@
- name: Collect failed login attempts
automations_enabled: true
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.
discard_data: false
interval: 300
logging: snapshot
min_osquery_version: ""
observer_can_run: false
platform: ""
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;

View File

@ -0,0 +1,7 @@
cp /var/log/orbit/orbit.stderr.log ~/Library/Logs/Fleet/fleet-desktop.log /Users/Shared
echo "Successfully copied fleetd logs to the /Users/Shared folder."
echo "To retrieve logs, ask the end user to open Finder and in the menu bar select Go > Go to Folder."
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."

View File

@ -0,0 +1,7 @@
- name: Collect fleetd update channels
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."
query: SELECT desktop_channel, orbit_channel, osqueryd_channel FROM orbit_info;
interval: 300 # 5 minutes
observer_can_run: true
automations_enabled: false
platform: darwin,linux,windows

View File

@ -0,0 +1,10 @@
- name: Collect USB devices
automations_enabled: false
description: Collects the USB devices that are currently connected to macOS and Linux hosts.
discard_data: false
interval: 300
logging: snapshot
min_osquery_version: ""
observer_can_run: true
platform: ""
query: SELECT model, vendor FROM usb_devices;

View File

@ -0,0 +1,12 @@
- name: Collect Visual Studio (VS) Code extensions
automations_enabled: false
description: Collects the name, publisher, and version of the VS Code extensions
installed on hosts.
discard_data: false
interval: 3600
logging: snapshot
min_osquery_version: ""
observer_can_run: false
platform: ""
query: SELECT extension.name, extension.publisher, extension.version FROM users
JOIN vscode_extensions extension USING (uid);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
- name: Linux - Enable disk encryption
query: SELECT 1 FROM disk_encryption WHERE encrypted=1 AND name LIKE '/dev/dm-1';
critical: false
description: This policy checks if disk encryption is enabled.
resolution: As an IT admin, deploy an image that includes disk encryption.
platform: linux

View File

@ -0,0 +1,55 @@
- name: macOS - Enable FileVault
query: SELECT 1 FROM filevault_status WHERE status = 'FileVault is On.';
critical: false
description: This policy checks if FileVault (disk encryption) is enabled.
resolution: As an IT admin, turn on disk encryption in Fleet.
platform: darwin
- name: macOS - Enable Firewall
query: SELECT 1 FROM managed_policies WHERE domain='com.apple.security.firewall' AND username = '' AND name='EnableFirewall' AND CAST(value AS INT) = 1;
critical: false
description: This policy checks if Firewall is enabled.
resolution: An an IT admin, deploy a macOS, Firewall profile with the EnableFirewall option set to true.
platform: darwin
- name: macOS - Disable guest account
query: SELECT 1 FROM managed_policies WHERE domain='com.apple.loginwindow' AND username = '' AND name='DisableGuestAccount' AND CAST(value AS INT) = 1;
critical: false
description: This policy checks if the guest account is disabled.
resolution: An an IT admin, deploy a macOS, login window profile with the DisableGuestAccount option set to true.
platform: darwin
- name: macOS - Require 10 character password
query: SELECT 1 WHERE
EXISTS (
SELECT 1 FROM managed_policies WHERE
domain='com.apple.screensaver' AND
name='askForPassword' AND
CAST(value AS INT)
)
AND EXISTS (
SELECT 1 FROM managed_policies WHERE
domain='com.apple.screensaver' AND
name='minLength' AND
CAST(value AS INT) <= 10
);
critical: false
description: This policy checks if the end user is required to enter a password, with at least 10 characters, to unlock the host.
resolution: An an IT admin, deploy a macOS, screensaver profile with the askForPassword option set to true and minLength option set to 10.
platform: darwin
- name: macOS - Enable screen saver after 20 minutes
query: SELECT 1 WHERE
EXISTS (
SELECT 1 FROM managed_policies WHERE
domain='com.apple.screensaver' AND
name='idleTime' AND
CAST(value AS INT) <= 1200 AND
username = ''
)
AND NOT EXISTS (
SELECT 1 FROM managed_policies WHERE
domain='com.apple.screensaver' AND
name='idleTime' AND
CAST(value AS INT) > 1200
);
critical: false
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.
resolution: An an IT admin, deploy a macOS, screen saver profile with the maxInactivity option set to 20 minutes.
platform: darwin

View File

@ -0,0 +1 @@
profiles show -type enrollment

View File

@ -0,0 +1,13 @@
config:
decorators:
load:
- SELECT uuid AS host_uuid FROM system_info;
- SELECT hostname AS hostname FROM system_info;
options:
disable_distributed: false
distributed_interval: 10
distributed_plugin: tls
distributed_tls_max_attempts: 3
logger_tls_endpoint: /api/osquery/log
logger_tls_period: 10
pack_delimiter: /

View File

@ -0,0 +1,24 @@
- name: Windows - Enable screen saver after 20 minutes
query: SELECT 1 FROM mdm_bridge where mdm_command_input = "<SyncBody><Get><CmdID>1</CmdID><Item><Target><LocURI>./Device/Vendor/MSFT/Policy/Result/DeviceLock/MaxInactivityTimeDeviceLock</LocURI></Target></Item></Get></SyncBody>" and CAST(mdm_command_output AS INT) <= 20;
critical: false
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.
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"
platform: windows
- name: Windows - Enable BitLocker
query: SELECT * FROM bitlocker_info WHERE drive_letter='C:' AND protection_status = 1;
critical: false
description: As an IT admin, turn on disk encryption in Fleet.
resolution: Ask your system administrator to turn on disk encryption in Fleet
platform: windows
- name: Windows - Disable guest account
query: SELECT 1 FROM mdm_bridge where mdm_command_input = "<SyncBody><Get><CmdID>1</CmdID><Item><Target><LocURI>./Device/Vendor/MSFT/Policy/Result/LocalPoliciesSecurityOptions/Accounts_EnableGuestAccountStatus</LocURI></Target></Item></Get></SyncBody>" and CAST(mdm_command_output AS INT) = 0;
critical: false
description: This policy checks if the guest account is disabled. The Guest account allows unauthenticated network users to gain access to the system.
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"
platform: windows
- name: Windows - Require 10 character password
query: SELECT 1 FROM mdm_bridge where mdm_command_input = "<SyncBody><Get><CmdID>1</CmdID><Item><Target><LocURI>./Device/Vendor/MSFT/Policy/Result/DeviceLock/DevicePasswordEnabled</LocURI></Target></Item></Get></SyncBody>" and CAST(mdm_command_output AS INT) = 0;
critical: false
description: This policy checks if the end user is required to enter a password, with at least 10 characters, to unlock the host.
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"
platform: windows

View File

@ -0,0 +1,110 @@
function Test-Administrator
{
[OutputType([bool])]
param()
process {
[Security.Principal.WindowsPrincipal]$user = [Security.Principal.WindowsIdentity]::GetCurrent();
return $user.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator);
}
}
# borrowed from Jeffrey Snover http://blogs.msdn.com/powershell/archive/2006/12/07/resolve-error.aspx
function Resolve-Error-Detailed($ErrorRecord = $Error[0]) {
$error_message = "========== ErrorRecord:{0}ErrorRecord.InvocationInfo:{1}Exception:{2}"
$formatted_errorRecord = $ErrorRecord | format-list * -force | out-string
$formatted_invocationInfo = $ErrorRecord.InvocationInfo | format-list * -force | out-string
$formatted_exception = ""
$Exception = $ErrorRecord.Exception
for ($i = 0; $Exception; $i++, ($Exception = $Exception.InnerException)) {
$formatted_exception += ("$i" * 70) + "-----"
$formatted_exception += $Exception | format-list * -force | out-string
$formatted_exception += "-----"
}
return $error_message -f $formatted_errorRecord, $formatted_invocationInfo, $formatted_exception
}
#Stops Orbit service and related processes
function Stop-Orbit {
# Stop Service
Stop-Service -Name "Fleet osquery" -ErrorAction "Continue"
Start-Sleep -Milliseconds 1000
# Ensure that no process left running
Get-Process -Name "orbit" -ErrorAction "SilentlyContinue" | Stop-Process -Force
Get-Process -Name "osqueryd" -ErrorAction "SilentlyContinue" | Stop-Process -Force
Get-Process -Name "fleet-desktop" -ErrorAction "SilentlyContinue" | Stop-Process -Force
Start-Sleep -Milliseconds 1000
}
#Remove Orbit footprint from registry and disk
function Force-Remove-Orbit {
try {
#Stoping Orbit
Stop-Orbit
#Remove Service
$service = Get-WmiObject -Class Win32_Service -Filter "Name='Fleet osquery'"
if ($service) {
$service.delete() | Out-Null
}
#Removing Program files entries
$targetPath = $Env:Programfiles + "\\Orbit"
Remove-Item -LiteralPath $targetPath -Force -Recurse -ErrorAction "Continue"
#Remove HKLM registry entries
Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" -Recurse -ErrorAction "SilentlyContinue" | Where-Object {($_.ValueCount -gt 0)} | ForEach-Object {
# Filter for osquery entries
$properties = Get-ItemProperty $_.PSPath -ErrorAction "SilentlyContinue" | Where-Object {($_.DisplayName -eq "Fleet osquery")}
if ($properties) {
#Remove Registry Entries
$regKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" + $_.PSChildName
Get-Item $regKey -ErrorAction "SilentlyContinue" | Remove-Item -Force -ErrorAction "SilentlyContinue"
return
}
}
}
catch {
Write-Host "There was a problem running Force-Remove-Orbit"
Write-Host "$(Resolve-Error-Detailed)"
return $false
}
return $true
}
function Main {
try {
# Is Administrator check
if (-not (Test-Administrator)) {
Write-Host "Please run this script with adming privileges."
Exit -1
}
Write-Host "About to uninstall fleetd..."
if (Force-Remove-Orbit) {
Write-Host "fleetd was uninstalled."
Exit 0
} else {
Write-Host "There was a problem uninstalling fleetd."
Exit -1
}
} catch {
Write-Host "Errorr: Entry point"
Write-Host "$(Resolve-Error-Detailed)"
Exit -1
}
}
$null = Main

View File

@ -0,0 +1,27 @@
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class MdmRegistration
{
[DllImport("mdmregistration.dll", SetLastError = true)]
public static extern int UnregisterDeviceWithManagement(IntPtr pDeviceID);
public static int UnregisterDevice()
{
return UnregisterDeviceWithManagement(IntPtr.Zero);
}
}
"@ -Language CSharp
try {
$result = [MdmRegistration]::UnregisterDevice()
if ($result -ne 0) {
throw "UnregisterDeviceWithManagement failed with error code: $result"
}
Write-Host "Device unregistration called successfully."
} catch {
Write-Error "Error calling UnregisterDeviceWithManagement: $_"
}

View File

@ -0,0 +1,43 @@
name: "Explore data (fleetdm.com) [DO NOT DELETE]"
team_settings:
features:
enable_host_users: true
enable_software_inventory: true
host_expiry_settings:
host_expiry_enabled: false
host_expiry_window: 0
secrets:
- secret: $DOGFOOD_EXPLORE_DATA_ENROLL_SECRET
agent_options:
config:
decorators:
load:
- SELECT uuid AS host_uuid FROM system_info;
- SELECT hostname AS hostname FROM system_info;
options:
disable_distributed: false
distributed_interval: 5
distributed_plugin: tls
distributed_tls_max_attempts: 3
logger_tls_endpoint: /api/v1/osquery/log
pack_delimiter: /
controls:
enable_disk_encryption: false
macos_settings:
custom_settings:
macos_setup:
bootstrap_package: null
enable_end_user_authentication: false
macos_setup_assistant: null
macos_updates:
deadline: null
minimum_version: null
windows_settings:
custom_settings: null
windows_updates:
deadline_days: null
grace_period_days: null
scripts:
policies:
queries:
- path: ../lib/explore-data.queries.yml

View File

@ -0,0 +1,31 @@
name: "Servers (canary)"
team_settings:
features:
enable_host_users: false
enable_software_inventory: false
host_expiry_settings:
host_expiry_enabled: false
host_expiry_window: 0
secrets:
- secret: $DOGFOOD_SERVERS_CANARY_ENROLL_SECRET
agent_options:
path: ../lib/servers.agent-options.yml
controls:
enable_disk_encryption: false
macos_settings:
custom_settings:
macos_setup:
bootstrap_package: null
enable_end_user_authentication: false
macos_setup_assistant: null
macos_updates:
deadline: null
minimum_version: null
windows_settings:
custom_settings: null
windows_updates:
deadline_days: null
grace_period_days: null
scripts:
policies:
queries:

View File

@ -0,0 +1,31 @@
name: "Servers"
team_settings:
features:
enable_host_users: true
enable_software_inventory: true
host_expiry_settings:
host_expiry_enabled: false
host_expiry_window: 0
secrets:
- secret: $DOGFOOD_SERVERS_ENROLL_SECRET
agent_options:
path: ../lib/servers.agent-options.yml
controls:
enable_disk_encryption: false
macos_settings:
custom_settings:
macos_setup:
bootstrap_package: null
enable_end_user_authentication: false
macos_setup_assistant: null
macos_updates:
deadline: null
minimum_version: null
windows_settings:
custom_settings: null
windows_updates:
deadline_days: null
grace_period_days: null
scripts:
policies:
queries:

View File

@ -0,0 +1,68 @@
name: "Workstations (canary)"
team_settings:
features:
enable_host_users: true
enable_software_inventory: true
host_expiry_settings:
host_expiry_enabled: false
host_expiry_window: 0
secrets:
- secret: $DOGFOOD_WORKSTATIONS_CANARY_ENROLL_SECRET
agent_options:
path: ../lib/agent-options.yml
controls:
enable_disk_encryption: true
macos_settings:
custom_settings:
- path: ../../mdm_profiles/automatic_updates.mobileconfig
- path: ../../mdm_profiles/chrome_enrollment.mobileconfig
- path: ../../mdm_profiles/disable_bluetooth_file_sharing.mobileconfig
- path: ../../mdm_profiles/disable_content_caching.mobileconfig
- path: ../../mdm_profiles/disable_guest_account.mobileconfig
- path: ../../mdm_profiles/disable_guest_shares.mobileconfig
- path: ../../mdm_profiles/disable_internet_sharing.mobileconfig
- path: ../../mdm_profiles/disable_media_sharing.mobileconfig
- path: ../../mdm_profiles/disable_safari_safefiles.mobileconfig
- path: ../../mdm_profiles/enable_doh.mobileconfig
- path: ../../mdm_profiles/enable_firewall_logging.mobileconfig
- path: ../../mdm_profiles/enable_gatekeeper.mobileconfig
- path: ../../mdm_profiles/enforce_library_validation.mobileconfig
- path: ../../mdm_profiles/firewall.mobileconfig
- path: ../../mdm_profiles/full_disk_access_for_orbit.mobileconfig
- path: ../../mdm_profiles/limit_ad_tracking.mobileconfig
- path: ../../mdm_profiles/misc.mobileconfig
- path: ../../mdm_profiles/password_policy.mobileconfig
- path: ../../mdm_profiles/prevent_autologon.mobileconfig
- path: ../../mdm_profiles/secure_terminal_keyboard.mobileconfig
- path: ../../mdm_profiles/time_and_date.mobileconfig
macos_setup:
bootstrap_package: ""
enable_end_user_authentication: true
macos_setup_assistant: null
macos_updates:
deadline: "2023-12-15"
minimum_version: "14.2"
windows_settings:
custom_settings: null
windows_updates:
deadline_days: 7
grace_period_days: 2
scripts:
- path: ../lib/collect-fleetd-logs.sh
- path: ../lib/macos-see-automatic-enrollment-profile.sh
- path: ../lib/windows-remove-fleetd.ps1
- path: ../lib/windows-turn-off-mdm.ps1
policies:
- path: ../lib/macos-device-health.policies.yml
- path: ../lib/windows-device-health.policies.yml
- path: ../lib/linux-device-health.policies.yml
- name: chromeOS/macOS - Screenlock enabled
query: SELECT 1 FROM screenlock WHERE enabled = 1;
critical: false
description: ""
resolution: ""
platform: darwin,chrome
queries:
- path: ../lib/collect-failed-login-attempts.queries.yml
- path: ../lib/collect-usb-devices.queries.yml
- path: ../lib/collect-vs-code-extensions.queries.yml

View File

@ -0,0 +1,62 @@
name: "Workstations"
team_settings:
features:
enable_host_users: true
enable_software_inventory: true
host_expiry_settings:
host_expiry_enabled: false
host_expiry_window: 0
secrets:
- secret: $DOGFOOD_WORKSTATIONS_ENROLL_SECRET
agent_options:
path: ../lib/agent-options.yml
controls:
enable_disk_encryption: true
macos_settings:
custom_settings:
- path: ../../mdm_profiles/automatic_updates.mobileconfig
- path: ../../mdm_profiles/chrome_enrollment.mobileconfig
- path: ../../mdm_profiles/disable_bluetooth_file_sharing.mobileconfig
- path: ../../mdm_profiles/disable_content_caching.mobileconfig
- path: ../../mdm_profiles/disable_guest_account.mobileconfig
- path: ../../mdm_profiles/disable_guest_shares.mobileconfig
- path: ../../mdm_profiles/disable_internet_sharing.mobileconfig
- path: ../../mdm_profiles/disable_media_sharing.mobileconfig
- path: ../../mdm_profiles/disable_safari_safefiles.mobileconfig
- path: ../../mdm_profiles/enable_doh.mobileconfig
- path: ../../mdm_profiles/enable_firewall_logging.mobileconfig
- path: ../../mdm_profiles/enable_gatekeeper.mobileconfig
- path: ../../mdm_profiles/enforce_library_validation.mobileconfig
- path: ../../mdm_profiles/firewall.mobileconfig
- path: ../../mdm_profiles/full_disk_access_for_orbit.mobileconfig
- path: ../../mdm_profiles/limit_ad_tracking.mobileconfig
- path: ../../mdm_profiles/misc.mobileconfig
- path: ../../mdm_profiles/password_policy.mobileconfig
- path: ../../mdm_profiles/prevent_autologon.mobileconfig
- path: ../../mdm_profiles/secure_terminal_keyboard.mobileconfig
- path: ../../mdm_profiles/time_and_date.mobileconfig
macos_setup:
bootstrap_package: ""
enable_end_user_authentication: true
macos_setup_assistant: null
macos_updates:
deadline: "2023-12-19"
minimum_version: "14.2"
windows_settings:
custom_settings: null
windows_updates:
deadline_days: 7
grace_period_days: 2
scripts:
- path: ../lib/collect-fleetd-logs.sh
- path: ../lib/macos-see-automatic-enrollment-profile.sh
- path: ../lib/windows-remove-fleetd.ps1
- path: ../lib/windows-turn-off-mdm.ps1
policies:
- path: ../lib/macos-device-health.policies.yml
- path: ../lib/windows-device-health.policies.yml
- path: ../lib/linux-device-health.policies.yml
queries:
- path: ../lib/collect-failed-login-attempts.queries.yml
- path: ../lib/collect-usb-devices.queries.yml
- path: ../lib/collect-vs-code-extensions.queries.yml

View File

@ -94,6 +94,7 @@ type ServerConfig struct {
Keepalive bool `yaml:"keepalive"`
SandboxEnabled bool `yaml:"sandbox_enabled"`
WebsocketsAllowUnsafeOrigin bool `yaml:"websockets_allow_unsafe_origin"`
FrequentCleanupsEnabled bool `yaml:"frequent_cleanups_enabled"`
}
func (s *ServerConfig) DefaultHTTPServer(ctx context.Context, handler http.Handler) *http.Server {
@ -841,6 +842,7 @@ func (man Manager) addConfigs() {
man.addConfigBool("server.sandbox_enabled", false,
"When enabled, Fleet limits some features for the Sandbox")
man.addConfigBool("server.websockets_allow_unsafe_origin", false, "Disable checking the origin header on websocket connections, this is sometimes necessary when proxies rewrite origin headers between the client and the Fleet webserver")
man.addConfigBool("server.frequent_cleanups_enabled", false, "Enable frequent cleanups of expired data (15 minute interval)")
// Hide the sandbox flag as we don't want it to be discoverable for users for now
sandboxFlag := man.command.PersistentFlags().Lookup(flagNameFromConfigKey("server.sandbox_enabled"))
@ -1191,6 +1193,7 @@ func (man Manager) LoadConfig() FleetConfig {
Keepalive: man.getConfigBool("server.keepalive"),
SandboxEnabled: man.getConfigBool("server.sandbox_enabled"),
WebsocketsAllowUnsafeOrigin: man.getConfigBool("server.websockets_allow_unsafe_origin"),
FrequentCleanupsEnabled: man.getConfigBool("server.frequent_cleanups_enabled"),
},
Auth: AuthConfig{
BcryptCost: man.getConfigInt("auth.bcrypt_cost"),

View File

@ -404,6 +404,7 @@ func saltAndHashPassword(keySize int, plaintext string, cost int) (hashed []byte
return nil, "", err
}
salt = salt[:keySize]
withSalt := []byte(fmt.Sprintf("%s%s", plaintext, salt))
hashed, err = bcrypt.GenerateFromPassword(withSalt, cost)
if err != nil {

View File

@ -164,11 +164,11 @@ func TestUserPasswordRequirements(t *testing.T) {
}
func TestSaltAndHashPassword(t *testing.T) {
passwordTests := []string{"foobar!!", "bazbing!!"}
goodTests := []string{"foobar!!", "bazbing!!", "foobarbaz!!!foobarbaz!!!foobarbaz!!!foobarbaz!!", "foobarbaz!!!foobarbaz!!!foobarbaz!!!foobarbaz!!!"}
keySize := 24
cost := 10
for _, pwd := range passwordTests {
for _, pwd := range goodTests {
hashed, salt, err := saltAndHashPassword(keySize, pwd, cost)
require.NoError(t, err)
@ -178,6 +178,14 @@ func TestSaltAndHashPassword(t *testing.T) {
err = bcrypt.CompareHashAndPassword(hashed, []byte(fmt.Sprint("invalidpassword", salt)))
require.Error(t, err)
// too long
badTests := []string{"foobarbaz!!!foobarbaz!!!foobarbaz!!!foobarbaz!!!!"}
for _, pwd := range badTests {
_, _, err := saltAndHashPassword(keySize, pwd, cost)
require.Error(t, err)
}
}
}

View File

@ -75,7 +75,7 @@ func (m *MDMWindowsConfigProfile) ValidateUserProvided() error {
// NOTE: since we're only checking for well-formedness
// we don't need to validate the required nesting
// structure (Target>Item>LocURI) so we don't need to track all the tags.
var inReplace bool
var inValidNode bool
var inLocURI bool
for {
@ -96,24 +96,24 @@ func (m *MDMWindowsConfigProfile) ValidateUserProvided() error {
case xml.StartElement:
switch t.Name.Local {
case "Replace":
inReplace = true
case "Replace", "Add":
inValidNode = true
case "LocURI":
if !inReplace {
return errors.New("Only <Replace> supported as a top level element. Make sure you don't have other top level elements.")
if !inValidNode {
return errors.New("Windows configuration profiles can only have <Replace> or <Add> top level elements.")
}
inLocURI = true
default:
if !inReplace {
return errors.New("Only <Replace> supported as a top level element. Make sure you don't have other top level elements.")
if !inValidNode {
return errors.New("Windows configuration profiles can only have <Replace> or <Add> top level elements.")
}
}
case xml.EndElement:
switch t.Name.Local {
case "Replace":
inReplace = false
case "Replace", "Add":
inValidNode = false
case "LocURI":
inLocURI = false
}

View File

@ -39,10 +39,10 @@ func TestValidateUserProvided(t *testing.T) {
</SyncML>
`),
},
wantErr: "Only <Replace> supported as a top level element. Make sure you don't have other top level elements",
wantErr: "Windows configuration profiles can only have <Replace> or <Add> top level elements.",
},
{
name: "Invalid top level element",
name: "Add top level element",
profile: MDMWindowsConfigProfile{
SyncML: []byte(`
<Add>
@ -52,7 +52,7 @@ func TestValidateUserProvided(t *testing.T) {
</Add>
`),
},
wantErr: "Only <Replace> supported as a top level element. Make sure you don't have other top level elements.",
wantErr: "",
},
{
name: "Reserved LocURI",
@ -139,7 +139,7 @@ func TestValidateUserProvided(t *testing.T) {
</Add>
`),
},
wantErr: "Only <Replace> supported as a top level element. Make sure you don't have other top level elements",
wantErr: "",
},
{
name: "XML with Replace and Alert",
@ -157,7 +157,7 @@ func TestValidateUserProvided(t *testing.T) {
</Alert>
`),
},
wantErr: "Only <Replace> supported as a top level element. Make sure you don't have other top level elements",
wantErr: "Windows configuration profiles can only have <Replace> or <Add> top level elements.",
},
{
name: "XML with Replace and Atomic",
@ -175,7 +175,7 @@ func TestValidateUserProvided(t *testing.T) {
</Atomic>
`),
},
wantErr: "Only <Replace> supported as a top level element. Make sure you don't have other top level elements",
wantErr: "Windows configuration profiles can only have <Replace> or <Add> top level elements.",
},
{
name: "XML with Replace and Delete",
@ -193,7 +193,7 @@ func TestValidateUserProvided(t *testing.T) {
</Delete>
`),
},
wantErr: "Only <Replace> supported as a top level element. Make sure you don't have other top level elements",
wantErr: "Windows configuration profiles can only have <Replace> or <Add> top level elements.",
},
{
name: "XML with Replace and Exec",
@ -211,7 +211,7 @@ func TestValidateUserProvided(t *testing.T) {
</Exec>
`),
},
wantErr: "Only <Replace> supported as a top level element. Make sure you don't have other top level elements",
wantErr: "Windows configuration profiles can only have <Replace> or <Add> top level elements.",
},
{
name: "XML with Replace and Get",
@ -229,7 +229,7 @@ func TestValidateUserProvided(t *testing.T) {
</Get>
`),
},
wantErr: "Only <Replace> supported as a top level element. Make sure you don't have other top level elements",
wantErr: "Windows configuration profiles can only have <Replace> or <Add> top level elements.",
},
{
name: "XML with Replace and Results",
@ -247,7 +247,7 @@ func TestValidateUserProvided(t *testing.T) {
</Results>
`),
},
wantErr: "Only <Replace> supported as a top level element. Make sure you don't have other top level elements",
wantErr: "Windows configuration profiles can only have <Replace> or <Add> top level elements.",
},
{
name: "XML with Replace and Status",
@ -265,7 +265,7 @@ func TestValidateUserProvided(t *testing.T) {
</Status>
`),
},
wantErr: "Only <Replace> supported as a top level element. Make sure you don't have other top level elements",
wantErr: "Windows configuration profiles can only have <Replace> or <Add> top level elements.",
},
{
name: "XML with elements not defined in the protocol",
@ -283,7 +283,7 @@ func TestValidateUserProvided(t *testing.T) {
</Foo>
`),
},
wantErr: "Only <Replace> supported as a top level element. Make sure you don't have other top level elements",
wantErr: "Windows configuration profiles can only have <Replace> or <Add> top level elements.",
},
{
name: "invalid XML with mismatched tags",
@ -359,7 +359,8 @@ func TestValidateUserProvided(t *testing.T) {
<Data>Invalid & Data</Data>
</Item>
</Replace>
`)},
`),
},
wantErr: "The file should include valid XML",
},
{

View File

@ -35,8 +35,7 @@ func DecryptBase64CMS(p7Base64 string, cert *x509.Certificate, key crypto.Privat
//
// - Returns "darwin" if the profile starts with "<?xml", typical of Darwin
// platform profiles.
// - Returns "windows" if the profile begins with "<replace", as we only accept
// replaces directives for profiles.
// - Returns "windows" if the profile begins with "<replace" or "<add",
// - Returns an empty string for profiles that are either unrecognized or
// empty.
func GetRawProfilePlatform(profile []byte) string {
@ -46,13 +45,16 @@ func GetRawProfilePlatform(profile []byte) string {
return ""
}
darwinPrefix := []byte("<?xml")
if len(trimmedProfile) >= len(darwinPrefix) && bytes.EqualFold(darwinPrefix, trimmedProfile[:len(darwinPrefix)]) {
prefixMatches := func(prefix []byte) bool {
return len(trimmedProfile) >= len(prefix) &&
bytes.EqualFold(prefix, trimmedProfile[:len(prefix)])
}
if prefixMatches([]byte("<?xml")) {
return "darwin"
}
windowsPrefix := []byte("<replace")
if len(trimmedProfile) >= len(windowsPrefix) && bytes.EqualFold(windowsPrefix, trimmedProfile[:len(windowsPrefix)]) {
if prefixMatches([]byte("<replace")) || prefixMatches([]byte("<add")) {
return "windows"
}

View File

@ -144,6 +144,16 @@ func TestGetRawProfilePlatform(t *testing.T) {
input: []byte("<REPLACE this=\"that\">"),
expected: "windows",
},
{
name: "Windows case insensitive add ",
input: []byte("<ADD this=\"that\">"),
expected: "windows",
},
{
name: "Windows case sensitive add",
input: []byte("<Add this=\"that\">"),
expected: "windows",
},
{
name: "Whitespace before prefix",
input: []byte(" <?xml version=\"1.0\"?>"),

View File

@ -8078,10 +8078,36 @@ func (s *integrationMDMTestSuite) TestWindowsMDM() {
err = s.ds.MDMWindowsInsertCommandForHosts(context.Background(), []string{orbitHost.UUID}, commandThree)
require.NoError(t, err)
cmdFourUUID := uuid.New().String()
commandFour := &fleet.MDMWindowsCommand{
CommandUUID: cmdFourUUID,
RawCommand: []byte(fmt.Sprintf(`
<Add>
<CmdID>%s</CmdID>
<Item>
<Target>
<LocURI>./Vendor/MSFT/WiFi/Profile/MyNetwork/WlanXml</LocURI>
</Target>
<Meta>
<Type xmlns="syncml:metinf">text/plain</Type>
<Format xmlns="syncml:metinf">chr</Format>
</Meta>
<Data>
&lt;?xml version=&quot;1.0&quot;?&gt;&lt;WLANProfile
xmlns=&quot;http://contoso.com/provisioning/EapHostConfig&quot;&gt;&lt;EapMethod&gt;&lt;Type
</Data>
</Item>
</Add>
`, cmdFourUUID)),
TargetLocURI: "./Vendor/MSFT/WiFi/Profile/MyNetwork/WlanXml",
}
err = s.ds.MDMWindowsInsertCommandForHosts(context.Background(), []string{orbitHost.UUID}, commandFour)
require.NoError(t, err)
cmds, err = d.StartManagementSession()
require.NoError(t, err)
// two status + the two commands we enqueued
require.Len(t, cmds, 4)
// two status + the three commands we enqueued
require.Len(t, cmds, 5)
receivedCmdTwo := cmds[cmdTwoUUID]
require.NotNil(t, receivedCmdTwo)
require.Equal(t, receivedCmdTwo.Verb, fleet.CmdGet)
@ -8094,6 +8120,12 @@ func (s *integrationMDMTestSuite) TestWindowsMDM() {
require.Len(t, receivedCmdThree.Cmd.Items, 1)
require.EqualValues(t, "./Device/Vendor/MSFT/DMClient/Provider/DEMO%20MDM/SignedEntDMID", *receivedCmdThree.Cmd.Items[0].Target)
receivedCmdFour := cmds[cmdFourUUID]
require.NotNil(t, receivedCmdFour)
require.Equal(t, receivedCmdFour.Verb, fleet.CmdAdd)
require.Len(t, receivedCmdFour.Cmd.Items, 1)
require.EqualValues(t, "./Vendor/MSFT/WiFi/Profile/MyNetwork/WlanXml", *receivedCmdFour.Cmd.Items[0].Target)
// status 200 for command Two (Get)
d.AppendResponse(fleet.SyncMLCmd{
XMLName: xml.Name{Local: mdm_types.CmdStatus},
@ -8130,8 +8162,19 @@ func (s *integrationMDMTestSuite) TestWindowsMDM() {
Items: nil,
CmdID: fleet.CmdID{Value: uuid.NewString()},
})
// status 200 for command Four (Add)
d.AppendResponse(fleet.SyncMLCmd{
XMLName: xml.Name{Local: mdm_types.CmdStatus},
MsgRef: &msgID,
CmdRef: &cmdFourUUID,
Cmd: ptr.String("Add"),
Data: ptr.String("200"),
Items: nil,
CmdID: fleet.CmdID{Value: uuid.NewString()},
})
cmds, err = d.SendResponse()
require.NoError(t, err)
// the ack of the message should be the only returned command
require.Len(t, cmds, 1)
@ -8192,6 +8235,20 @@ func (s *integrationMDMTestSuite) TestWindowsMDM() {
Hostname: "TestIntegrationsMDM/TestWindowsMDMh1.local",
Payload: commandThree.RawCommand,
}, getMDMCmdResp.Results[0])
s.DoJSON("GET", "/api/latest/fleet/mdm/commandresults", nil, http.StatusOK, &getMDMCmdResp, "command_uuid", cmdFourUUID)
require.Len(t, getMDMCmdResp.Results, 1)
require.NotZero(t, getMDMCmdResp.Results[0].UpdatedAt)
getMDMCmdResp.Results[0].UpdatedAt = time.Time{}
require.Equal(t, &fleet.MDMCommandResult{
HostUUID: orbitHost.UUID,
CommandUUID: cmdFourUUID,
Status: "200",
RequestType: "./Vendor/MSFT/WiFi/Profile/MyNetwork/WlanXml",
Result: getCommandFullResult(cmdFourUUID),
Hostname: "TestIntegrationsMDM/TestWindowsMDMh1.local",
Payload: commandFour.RawCommand,
}, getMDMCmdResp.Results[0])
}
func (s *integrationMDMTestSuite) TestWindowsAutomaticEnrollmentCommands() {
@ -8730,7 +8787,7 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
body, headers := generateNewProfileMultipartRequest(
t,
filename,
[]byte(fmt.Sprintf(`<Replace><Item><Target><LocURI>%s</LocURI></Target></Item></Replace>`, locURI)),
[]byte(fmt.Sprintf(`<Add><Item><Target><LocURI>%s</LocURI></Target></Item></Add><Replace><Item><Target><LocURI>%s</LocURI></Target></Item></Replace>`, locURI, locURI)),
s.token,
fields,
)
@ -9011,7 +9068,7 @@ func (s *integrationMDMTestSuite) TestListMDMConfigProfiles() {
tm2ProfG, err := s.ds.NewMDMWindowsConfigProfile(ctx, fleet.MDMWindowsConfigProfile{
Name: "tG",
TeamID: &tm2.ID,
SyncML: []byte(`<Replace></Replace>`),
SyncML: []byte(`<Add></Add>`),
Labels: []mdm_types.ConfigurationProfileLabel{
{LabelID: lblFoo.ID, LabelName: lblFoo.Name},
{LabelID: lblBar.ID, LabelName: lblBar.Name},

View File

@ -1028,7 +1028,7 @@ func TestUploadWindowsMDMConfigProfileValidations(t *testing.T) {
{"mdm not enabled", 0, `<Replace></Replace>`, false, "Windows MDM isn't turned on."},
{"duplicate profile name", 0, `<Replace>duplicate</Replace>`, true, "configuration profile with this name already exists."},
{"multiple Replace", 0, `<Replace>a</Replace><Replace>b</Replace>`, true, ""},
{"Replace and non-Replace", 0, `<Replace>a</Replace><Get>b</Get>`, true, "Only <Replace> supported as a top level element."},
{"Replace and non-Replace", 0, `<Replace>a</Replace><Get>b</Get>`, true, "Windows configuration profiles can only have <Replace> or <Add> top level elements."},
{"BitLocker profile", 0, `<Replace><Item><Target><LocURI>./Device/Vendor/MSFT/BitLocker/AllowStandardUserEncryption</LocURI></Target></Item></Replace>`, true, "Custom configuration profiles can't include BitLocker settings."},
{"Windows updates profile", 0, `<Replace><Item><Target><LocURI> ./Device/Vendor/MSFT/Policy/Config/Update/ConfigureDeadlineNoAutoRebootForFeatureUpdates </LocURI></Target></Item></Replace>`, true, "Custom configuration profiles can't include Windows updates settings."},
@ -1039,7 +1039,7 @@ func TestUploadWindowsMDMConfigProfileValidations(t *testing.T) {
{"team mdm not enabled", 1, `<Replace></Replace>`, false, "Windows MDM isn't turned on."},
{"team duplicate profile name", 1, `<Replace>duplicate</Replace>`, true, "configuration profile with this name already exists."},
{"team multiple Replace", 1, `<Replace>a</Replace><Replace>b</Replace>`, true, ""},
{"team Replace and non-Replace", 1, `<Replace>a</Replace><Get>b</Get>`, true, "Only <Replace> supported as a top level element."},
{"team Replace and non-Replace", 1, `<Replace>a</Replace><Get>b</Get>`, true, "Windows configuration profiles can only have <Replace> or <Add> top level elements."},
{"team BitLocker profile", 1, `<Replace><Item><Target><LocURI>./Device/Vendor/MSFT/BitLocker/AllowStandardUserEncryption</LocURI></Target></Item></Replace>`, true, "Custom configuration profiles can't include BitLocker settings."},
{"team Windows updates profile", 1, `<Replace><Item><Target><LocURI> ./Device/Vendor/MSFT/Policy/Config/Update/ConfigureDeadlineNoAutoRebootForFeatureUpdates </LocURI></Target></Item></Replace>`, true, "Custom configuration profiles can't include Windows updates settings."},

View File

@ -407,11 +407,18 @@ func TestBuildCommandFromProfileBytes(t *testing.T) {
func syncMLForTest(locURI string) []byte {
return []byte(fmt.Sprintf(`
<Add>
<Item>
<Target>
<LocURI>%s</LocURI>
</Target>
</Item>
</Add>
<Replace>
<Item>
<Target>
<LocURI>%s</LocURI>
</Target>
</Item>
</Replace>`, locURI))
</Replace>`, locURI, locURI))
}

View File

@ -157,11 +157,12 @@
<div purpose="page-section">
<div purpose="feature" class="d-flex flex-md-row flex-column-reverse justify-content-between mx-auto align-items-center">
<div purpose="feature-text" class="d-flex flex-column">
<h2>Absolute certainty</h2>
<p>Reduce time wasted hunting down whether a change happened. Actually verify that settings are applied using real data pulled from your users' devices.</p>
<h2>Shorten the feedback loop</h2>
<p>Spend less time debugging whether changes actually happened. Auto-verify using real data pulled from your users' devices.</p>
<div purpose="checklist" class="flex-column d-flex">
<p>Use a git repo as the source of truth to reduce errors (submitting the wrong patch, configuration setting etc)</p>
<p>Every change to a policy or security control is tracked and auditable in Fleets history, or via the repo commit log</p>
<p>Instantly reveal failed patches and broken settings with osquery to shorten the feedback loop and avoid tickets.</p>
</div>
</div>
<div purpose="feature-image" class="right">