Merge branch 'main' into 14415

This commit is contained in:
mostlikelee 2023-12-04 14:38:28 -07:00
commit 5677d734de
77 changed files with 1771 additions and 905 deletions

View file

@ -30,7 +30,6 @@ Our query runs as expected when `osqueryi` is run as a normal user, but returns
This same issue manifests on many tables that include a `uid` column:
- `atom_packages`
- `authorized_keys`
- `chrome_extension_content_scripts`
- `chrome_extensions`

View file

@ -0,0 +1 @@
* Fix `installed_from_dep` in `mdm_enrolled` activity when a DEP device unenrolls and re-enrolls.

View file

@ -0,0 +1 @@
* Fixed logging of results for scheduled queries configured outside of Fleet, when `server_settings.query_reports_disabled` is set to `true`.

View file

@ -0,0 +1 @@
* Improved the query used to get MDM details for Windows hosts to account for multiple registry entries.

View file

@ -0,0 +1 @@
* Changed the Apple profiles ID to be a prefixed UUID as is the case for Windows profiles. The Apple profiles are now a UUID prefixed with "a" and the Windows ones are prefixed with "w".

View file

@ -71,6 +71,12 @@ func newVulnerabilitiesSchedule(
return ds.SyncHostsSoftware(ctx, time.Now())
},
),
schedule.WithJob(
"cron_reconcile_software_titles",
func(ctx context.Context) error {
return ds.ReconcileSoftwareTitles(ctx)
},
),
)
return s, nil

View file

@ -164,7 +164,7 @@ func TestApplyTeamSpecs(t *testing.T) {
return nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs, profileIDs []uint, profileUUIDs, hostUUIDs []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string) error {
return nil
}
@ -991,7 +991,7 @@ func TestApplyAsGitOps(t *testing.T) {
ds.BatchSetMDMProfilesFunc = func(ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile) error {
return nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs, profileIDs []uint, profileUUIDs, hostUUIDs []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string) error {
return nil
}
ds.GetMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) (*fleet.MDMAppleSetupAssistant, error) {

View file

@ -2036,7 +2036,7 @@ func TestGetTeamsYAMLAndApply(t *testing.T) {
ds.BatchSetMDMProfilesFunc = func(ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile) error {
return nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs, profileIDs []uint, profileUUIDs, uuids []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, uuids []string) error {
return nil
}
ds.BatchSetScriptsFunc = func(ctx context.Context, tmID *uint, scripts []*fleet.Script) error {

View file

@ -42,7 +42,7 @@ func TestHostsTransferByHosts(t *testing.T) {
return nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs, profileIDs []uint, profileUUIDs, uuids []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, uuids []string) error {
return nil
}
@ -98,7 +98,7 @@ func TestHostsTransferByLabel(t *testing.T) {
return nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs, profileIDs []uint, profileUUIDs, uuids []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, uuids []string) error {
return nil
}
@ -153,7 +153,7 @@ func TestHostsTransferByStatus(t *testing.T) {
return nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs, profileIDs []uint, profileUUIDs, uuids []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, uuids []string) error {
return nil
}
@ -210,7 +210,7 @@ func TestHostsTransferByStatusAndSearchQuery(t *testing.T) {
return nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs, profileIDs []uint, profileUUIDs, uuids []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, uuids []string) error {
return nil
}

View file

@ -61,7 +61,7 @@ spec:
name: Get installed Linux software
platform: linux
description: Get all software installed on a Linux computer, including browser plugins and installed packages. Note that this does not include other running processes in the processes table.
query: SELECT name AS name, version AS version, 'Package (APT)' AS type, 'apt_sources' AS source FROM apt_sources UNION SELECT name AS name, version AS version, 'Package (deb)' AS type, 'deb_packages' AS source FROM deb_packages UNION SELECT package AS name, version AS version, 'Package (Portage)' AS type, 'portage_packages' AS source FROM portage_packages UNION SELECT name AS name, version AS version, 'Package (RPM)' AS type, 'rpm_packages' AS source FROM rpm_packages UNION SELECT name AS name, '' AS version, 'Package (YUM)' AS type, 'yum_sources' AS source FROM yum_sources UNION SELECT name AS name, version AS version, 'Package (NPM)' AS type, 'npm_packages' AS source FROM npm_packages UNION SELECT name AS name, version AS version, 'Package (Atom)' AS type, 'atom_packages' AS source FROM atom_packages UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages;
query: SELECT name AS name, version AS version, 'Package (APT)' AS type, 'apt_sources' AS source FROM apt_sources UNION SELECT name AS name, version AS version, 'Package (deb)' AS type, 'deb_packages' AS source FROM deb_packages UNION SELECT package AS name, version AS version, 'Package (Portage)' AS type, 'portage_packages' AS source FROM portage_packages UNION SELECT name AS name, version AS version, 'Package (RPM)' AS type, 'rpm_packages' AS source FROM rpm_packages UNION SELECT name AS name, '' AS version, 'Package (YUM)' AS type, 'yum_sources' AS source FROM yum_sources UNION SELECT name AS name, version AS version, 'Package (NPM)' AS type, 'npm_packages' AS source FROM npm_packages UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages;
purpose: Informational
tags: inventory, built-in
contributors: zwass
@ -94,7 +94,7 @@ spec:
name: Get installed Windows software
platform: windows
description: Get all software installed on a Windows computer, including programs, browser plugins, and installed packages. Note that this does not include other running processes in the processes table.
query: SELECT name AS name, version AS version, 'Program (Windows)' AS type, 'programs' AS source FROM programs UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages UNION SELECT name AS name, version AS version, 'Browser plugin (IE)' AS type, 'ie_extensions' AS source FROM ie_extensions UNION SELECT name AS name, version AS version, 'Browser plugin (Chrome)' AS type, 'chrome_extensions' AS source FROM chrome_extensions UNION SELECT name AS name, version AS version, 'Browser plugin (Firefox)' AS type, 'firefox_addons' AS source FROM firefox_addons UNION SELECT name AS name, version AS version, 'Package (Chocolatey)' AS type, 'chocolatey_packages' AS source FROM chocolatey_packages UNION SELECT name AS name, version AS version, 'Package (Atom)' AS type, 'atom_packages' AS source FROM atom_packages;
query: SELECT name AS name, version AS version, 'Program (Windows)' AS type, 'programs' AS source FROM programs UNION SELECT name AS name, version AS version, 'Package (Python)' AS type, 'python_packages' AS source FROM python_packages UNION SELECT name AS name, version AS version, 'Browser plugin (IE)' AS type, 'ie_extensions' AS source FROM ie_extensions UNION SELECT name AS name, version AS version, 'Browser plugin (Chrome)' AS type, 'chrome_extensions' AS source FROM chrome_extensions UNION SELECT name AS name, version AS version, 'Browser plugin (Firefox)' AS type, 'firefox_addons' AS source FROM firefox_addons UNION SELECT name AS name, version AS version, 'Package (Chocolatey)' AS type, 'chocolatey_packages' AS source FROM chocolatey_packages;
purpose: Informational
tags: inventory, built-in
contributors: zwass

View file

@ -249,6 +249,10 @@ spec:
- path/to/profile1.mobileconfig
- path/to/profile2.mobileconfig
enable_disk_encryption: true
windows_settings:
custom_settings:
- path/to/profile3.xml
- path/to/profile4.xml
scripts:
- path/to/script1.sh
- path/to/script2.sh
@ -460,6 +464,10 @@ spec:
- path/to/profile1.mobileconfig
- path/to/profile2.mobileconfig
enable_disk_encryption: true
windows_settings:
custom_settings:
- path/to/profile3.xml
- path/to/profile4.xml
```
### Settings
@ -1187,6 +1195,28 @@ If you're using Fleet Premium, this enforces disk encryption on all hosts assign
enable_disk_encryption: true
```
##### mdm.windows_settings
The following settings are Windows-specific settings for Fleet's MDM solution.
##### mdm.windows_settings.custom_settings
List of configuration profile files to apply to all hosts.
If you're using Fleet Premium, these profiles apply to all hosts assigned to no team.
> If you want to add profiles to all Windows hosts on a specific team in Fleet, use the `team` YAML document. Learn how to create one [here](#teams).
- Default value: none
- Config file format:
```yaml
mdm:
windows_settings:
custom_settings:
- path/to/profile1.xml
- path/to/profile2.xml
```
#### Scripts
List of saved scripts that can be run on all hosts.

View file

@ -1283,6 +1283,8 @@ If the `name` is not already associated with an existing team, this API route cr
| mdm.macos_settings.custom_settings | list | body | The list of .mobileconfig files to apply to hosts that belong to this team. |
| scripts | list | body | A list of script files to add to this team so they can be executed at a later time. |
| mdm.macos_settings.enable_disk_encryption | bool | body | Whether disk encryption should be enabled for hosts that belong to this team. |
| mdm.windows_settings | object | body | The Windows-specific MDM settings. |
| mdm.windows_settings.custom_settings | list | body | The list of .xml files to apply to hosts that belong to this team. |
| force | bool | query | Force apply the spec even if there are (ignorable) validation errors. Those are unknown keys and agent options-related validations. |
| dry_run | bool | query | Validate the provided JSON for unknown keys and invalid value types and return any validation errors, but do not apply the changes. |
@ -1342,6 +1344,9 @@ If the `name` is not already associated with an existing team, this API route cr
"macos_settings": {
"custom_settings": ["path/to/profile1.mobileconfig"],
"enable_disk_encryption": true
},
"windows_settings": {
"custom_settings": ["path/to/profile2.xml"],
}
},
"scripts": ["path/to/script.sh"],

View file

@ -879,10 +879,17 @@ None.
"minimum_version": "12.3.1",
"deadline": "2022-01-01"
},
"windows_updates": {
"deadline_days": 5,
"grace_period_days": 1
},
"macos_settings": {
"custom_settings": ["path/to/profile1.mobileconfig"],
"enable_disk_encryption": true
},
"windows_settings": {
"custom_settings": ["path/to/profile2.xml"],
},
"scripts": ["path/to/script.sh"],
"end_user_authentication": {
"entity_id": "",
@ -1079,11 +1086,14 @@ Modifies the Fleet's configuration with the supplied information.
| windows_enabled_and_configured | boolean | body | _mdm settings_. Enables Windows MDM support. |
| minimum_version | string | body | _mdm.macos_updates settings_. Hosts that belong to no team and are enrolled into Fleet's MDM will be nudged until their macOS is at or above this version. **Requires Fleet Premium license** |
| deadline | string | body | _mdm.macos_updates settings_. Hosts that belong to no team and are enrolled into Fleet's MDM won't be able to dismiss the Nudge window once this deadline is past. **Requires Fleet Premium license** |
| deadline_days | integer | body | _mdm.windows_updates settings_. Hosts that belong to no team and are enrolled into Fleet's MDM will have this number of days before updates are installed on Windows. **Requires Fleet Premium license** |
| grace_period_days | integer | body | _mdm.windows_updates settings_. Hosts that belong to no team and are enrolled into Fleet's MDM will have this number of days before Windows restarts to install updates. **Requires Fleet Premium license** |
| enable | boolean | body | _mdm.macos_migration settings_. Whether to enable the end user migration workflow for devices migrating from your old MDM solution. **Requires Fleet Premium license** |
| mode | string | body | _mdm.macos_migration settings_. The end user migration workflow mode for devices migrating from your old MDM solution. Options are `"voluntary"` or `"forced"`. **Requires Fleet Premium license** |
| webhook_url | string | body | _mdm.macos_migration settings_. The webhook url configured to receive requests to unenroll devices migrating from your old MDM solution. **Requires Fleet Premium license** |
| custom_settings | list | body | _mdm.macos_settings settings_. Hosts that belong to no team and are enrolled into Fleet's MDM will have those custom profiles applied. |
| enable_disk_encryption | boolean | body | _mdm.macos_settings settings_. Hosts that belong to no team and are enrolled into Fleet's MDM will have disk encryption enabled if set to true. **Requires Fleet Premium license** |
| custom_settings | list | body | _mdm.windows_settings settings_. Hosts that belong to no team and are enrolled into Fleet's MDM will have those custom profiles applied. |
| scripts | list | body | A list of script files to add so they can be executed at a later time. |
| enable_end_user_authentication | boolean | body | _mdm.macos_setup settings_. If set to true, end user authentication will be required during automatic MDM enrollment of new macOS devices. Settings for your IdP provider must also be [configured](https://fleetdm.com/docs/using-fleet/mdm-macos-setup-experience#end-user-authentication-and-eula). **Requires Fleet Premium license** |
| additional_queries | boolean | body | Whether or not additional queries are enabled on hosts. |
@ -1174,10 +1184,17 @@ Note that when making changes to the `integrations` object, all integrations mus
"minimum_version": "12.3.1",
"deadline": "2022-01-01"
},
"windows_updates": {
"deadline_days": 5,
"grace_period_days": 1
},
"macos_settings": {
"custom_settings": ["path/to/profile1.mobileconfig"],
"enable_disk_encryption": true
},
"windows_settings": {
"custom_settings": ["path/to/profile2.xml"],
},
"end_user_authentication": {
"entity_id": "",
"issuer_uri": "",
@ -7378,10 +7395,17 @@ _Available in Fleet Premium_
"minimum_version": "12.3.1",
"deadline": "2022-01-01"
},
"windows_updates": {
"deadline_days": 5,
"grace_period_days": 1
},
"macos_settings": {
"custom_settings": ["path/to/profile1.mobileconfig"],
"enable_disk_encryption": false
},
"windows_settings": {
"custom_settings": ["path/to/profile2.xml"],
},
"macos_setup": {
"bootstrap_package": "",
"enable_end_user_authentication": false,
@ -7495,9 +7519,14 @@ _Available in Fleet Premium_
|   macos_updates | object | body | macOS updates settings. |
|     minimum_version | string | body | Hosts that belong to this team and are enrolled into Fleet's MDM will be nudged until their macOS is at or above this version. |
|     deadline | string | body | Hosts that belong to this team and are enrolled into Fleet's MDM won't be able to dismiss the Nudge window once this deadline is past. |
|   windows_updates | object | body | Windows updates settings. |
|     deadline_days | integer | body | Hosts that belong to this team and are enrolled into Fleet's MDM will have this number of days before updates are installed on Windows. |
|     grace_period_days | integer | body | Hosts that belong to this team and are enrolled into Fleet's MDM will have this number of days before Windows restarts to install updates. |
|   macos_settings | object | body | macOS-specific settings. |
|     custom_settings | list | body | The list of .mobileconfig files to apply to macOS hosts that belong to this team. |
|     enable_disk_encryption | boolean | body | Hosts that belong to this team and are enrolled into Fleet's MDM will have disk encryption enabled if set to true. |
|   windows_settings | object | body | Windows-specific settings. |
|     custom_settings | list | body | The list of XML files to apply to Windows hosts that belong to this team. |
|   macos_setup | object | body | Setup for automatic MDM enrollment of macOS hosts. |
|     enable_end_user_authentication | boolean | body | If set to true, end user authentication will be required during automatic MDM enrollment of new macOS hosts. Settings for your IdP provider must also be [configured](https://fleetdm.com/docs/using-fleet/mdm-macos-setup-experience#end-user-authentication-and-eula). |
@ -7559,10 +7588,17 @@ _Available in Fleet Premium_
"minimum_version": "12.3.1",
"deadline": "2022-01-01"
},
"windows_updates": {
"deadline_days": 5,
"grace_period_days": 1
},
"macos_settings": {
"custom_settings": ["path/to/profile1.mobileconfig"],
"enable_disk_encryption": false
},
"windows_settings": {
"custom_settings": ["path/to/profile2.xml"],
},
"macos_setup": {
"bootstrap_package": "",
"enable_end_user_authentication": false,

View file

@ -610,6 +610,27 @@ This activity contains the following fields:
}
```
## edited_windows_updates
Generated when the Windows OS updates deadline or grace period is modified.
This activity contains the following fields:
- "team_id": The ID of the team that the Windows OS updates settings applies to, `null` if it applies to devices that are not in a team.
- "team_name": The name of the team that the Windows OS updates settings applies to, `null` if it applies to devices that are not in a team.
- "deadline_days": The number of days before updates are installed, `null` if the requirement was removed.
- "grace_period_days": The number of days after the deadline before the host is forced to restart, `null` if the requirement was removed.
#### Example
```json
{
"team_id": 3,
"team_name": "Workstations",
"deadline_days": 5,
"grace_period_days": 2
}
```
## read_host_disk_encryption_key
Generated when a user reads the disk encryption key for a host.
@ -918,7 +939,7 @@ This activity contains the following fields:
}
```
### Type `created_windows_profile`
## created_windows_profile
Generated when a user adds a new Windows profile to a team (or no team).
@ -937,7 +958,7 @@ This activity contains the following fields:
}
```
### Type `deleted_windows_profile`
## deleted_windows_profile
Generated when a user deletes a Windows profile from a team (or no team).
@ -956,7 +977,7 @@ This activity contains the following fields:
}
```
### Type `edited_windows_profile`
## edited_windows_profile
Generated when a user edits the Windows profiles of a team (or no team) via the fleetctl CLI.

View file

@ -20,7 +20,7 @@ Install [Fleet's Puppet module](https://forge.puppet.com/modules/fleetdm/fleetdm
### Step 2: configure Puppet to talk to Fleet using Heira
1. Create an API-only user with the GitOps role in Fleet. Instructions for creating an API-only user in Fleet are [here](./fleetctl-CLI.md#create-an-api-only-user).
1. In Fleet, create an API-only user with the global admin role. Instructions for creating an API-only user are [here](./fleetctl-CLI.md#create-an-api-only-user).
2. Get the API token for your new API-only user. Learn how [here](./fleetctl-CLI.md#get-the-api-token-of-an-api-only-user).

View file

@ -544,17 +544,6 @@ SELECT
path AS installed_path
FROM cached_users CROSS JOIN firefox_addons USING (uid)
UNION
SELECT
name AS name,
version AS version,
'Package (Atom)' AS type,
'atom_packages' AS source,
'' AS release,
'' AS vendor,
'' AS arch,
path AS installed_path
FROM cached_users CROSS JOIN atom_packages USING (uid)
UNION
SELECT
name AS name,
version AS version,
@ -628,16 +617,6 @@ SELECT
path AS installed_path
FROM cached_users CROSS JOIN safari_extensions USING (uid)
UNION
SELECT
name AS name,
version AS version,
'Package (Atom)' AS type,
'' AS bundle_identifier,
'atom_packages' AS source,
0 AS last_opened_at,
path AS installed_path
FROM cached_users CROSS JOIN atom_packages USING (uid)
UNION
SELECT
name AS name,
version AS version,
@ -713,15 +692,6 @@ SELECT
'' AS vendor,
path AS installed_path
FROM chocolatey_packages
UNION
SELECT
name AS name,
version AS version,
'Package (Atom)' AS type,
'atom_packages' AS source,
'' AS vendor,
path AS installed_path
FROM cached_users CROSS JOIN atom_packages USING (uid);
```
## system_info

View file

@ -6,14 +6,10 @@ Fleet gathers information from an [osquery](https://github.com/osquery/osquery)
You can enroll macOS, Windows or Linux hosts via the [CLI](#cli) or [UI](#ui). To learn how to enroll Chromebooks, see [Enroll Chromebooks](#enroll-chromebooks).
### Supported osquery versions
Fleet supports the [latest version of osquery](https://github.com/osquery/osquery/tags).
## CLI
> You must have `fleetctl` installed. [Learn how to install `fleetctl`](https://fleetdm.com/fleetctl-preview).
@ -35,6 +31,8 @@ Generate macOS installer (.pkg)
fleetctl package --type pkg --fleet-url=example.fleetinstance.com --enroll-secret=85O6XRG8'!l~P&zWt_'f&$QK(sM8_D4x
```
Tip: To see all options for `fleetctl package` command, run `fleetctl package -h` in your Terminal.
## UI
To generate an installer in Fleet UI:
@ -48,10 +46,6 @@ With hosts segmented into teams, you can apply unique queries and give users acc
To generate an installer that enrolls to a specific team: from the **Hosts** page, select the desired team from the menu at the top of the screen, then follow the instructions above for generating an installer. The team's enroll secret will be included in the generated command.
### Enroll multiple hosts
If you're managing an enterprise environment with multiple hosts, you likely have an enterprise deployment tool like [Munki](https://www.munki.org/munki/), [Jamf Pro](https://www.jamf.com/products/jamf-pro/), [Chef](https://www.chef.io/), [Ansible](https://www.ansible.com/), or [Puppet](https://puppet.com/) to deliver software to your hosts.
@ -108,7 +102,15 @@ In the Google Admin console:
5. Enter the **Extension ID** and **Installation URL** using the data provided in the modal.
6. Under **Installation Policy**, select **Block**.
## Grant full disk access to osquery on macOS
## Advanced
- [Grant full disk access to osquery on macOS](#grant-full-disk-access-to-osquery-on-macos)
- [Signing fleetd installer](#signing-fleetd-installer)
- [Generating Windows installers using local WiX toolset](#generating-windows-installers-using-local-wix-toolset)
- [fleetd configuration options](#fleetd-configuration-options)
- [Enroll hosts with plain osquery](#enroll-hosts-with-plain-osquery)
### Grant full disk access to osquery on macOS
macOS does not allow applications to access all system files by default. If you are using MDM, which
is required to deploy these profiles, you
@ -117,8 +119,9 @@ access. This is necessary to query for files located in protected paths as well
tables that require access to the [EndpointSecurity
API](https://developer.apple.com/documentation/endpointsecurity#overview), such as *es_process_events*.
### Creating the configuration profile
#### Obtaining identifiers
#### Creating the configuration profile
##### Obtaining identifiers
If you use plain osquery, instructions are [available here](https://osquery.readthedocs.io/en/stable/deployment/process-auditing/).
On a system with osquery installed via the Fleet osquery installer (fleetd), obtain the
@ -142,7 +145,7 @@ Note down the **executable path** and the entire **identifier**.
Osqueryd will inherit the privileges from Orbit and does not need explicit permissions.
#### Creating the profile
##### Creating the profile
Depending on your MDM, this might be possible in the UI or require a custom profile. If your MDM has a feature to configure *Policy Preferences*, follow these steps:
1. Configure the identifier type to “path.”
@ -154,7 +157,7 @@ If your MDM does not have built-in support for privacy preferences profiles, you
[PPPC-Utility](https://github.com/jamf/PPPC-Utility) to create a profile with those values, then upload it to
your MDM as a custom profile.
#### Test the profile
##### Test the profile
Link the profile to a test group that contains at least one Mac.
Once the computer has received the profile, which you can verify by looking at *Profiles* in *System
Preferences*, run this query from Fleet:
@ -176,13 +179,6 @@ See the last hour of logs related to TCC permissions with this command:
You can then look for `orbit` or `osquery` to narrow down results.
## Advanced
- [Signing fleetd installer](#signing-fleetd-installer)
- [Generating Windows installers using local WiX toolset](#generating-windows-installers-using-local-wix-toolset)
- [fleetd configuration options](#fleetd-configuration-options)
- [Enroll hosts with plain osquery](#enroll-hosts-with-plain-osquery)
### Signing fleetd installers
>**Note:** Currently, the `fleetctl package` command does not support signing Windows fleetd installers. Windows installers can be signed after building.
@ -218,45 +214,6 @@ so:
```
If the provided path doesn't contain all 3 binaries, the command will fail.
### Fleetd configuration options
The following command-line flags to `fleetctl package` allow you to configure an osquery installer further to communicate with a specific Fleet instance.
| Flag | Options |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| --type | **Required** - Type of package to build.<br> Options: `pkg`(macOS),`msi`(Windows), `deb`(Debian based Linux), `rpm`(RHEL, CentOS, etc.) |
| --fleet-desktop | Include Fleet Desktop. |
| --enroll-secret | Enroll secret for authenticating to Fleet server |
| --fleet-url | URL (`host:port`) of Fleet server |
| --fleet-certificate | Path to server certificate bundle |
| --identifier | Identifier for package product (default: `com.fleetdm.orbit`) |
| --version | Version for package product (default: `0.0.3`) |
| --insecure | Disable TLS certificate verification (default: `false`) |
| --service | Install osquery with a persistence service (launchd, systemd, etc.) (default: `true`) |
| --sign-identity | Identity to use for macOS codesigning |
| --notarize | Whether to notarize macOS packages (default: `false`) |
| --disable-updates | Disable auto updates on the generated package (default: false) |
| --osqueryd-channel | Update channel of osqueryd to use (default: `stable`) |
| --orbit-channel | Update channel of Orbit to use (default: `stable`) |
| --desktop-channel | Update channel of desktop to use (default: `stable`) |
| --update-url | URL for update server (default: `https://tuf.fleetctl.com`) |
| --update-roots | Root key JSON metadata for update server (from fleetctl updates roots) |
| --use-system-configuration | Try to read --fleet-url and --enroll-secret using configuration in the host (currently only macOS profiles are supported) |
| --enable-scripts | Enable script execution (default: `false`) |
| --debug | Enable debug logging (default: `false`) |
| --verbose | Log detailed information when building the package (default: false) |
| --local-wix-dir | Use local installations of the 3 WiX v3 binaries this command uses (`heat.exe`, `candle.exe`, and `light.exe`) instead of installations in a pre-configered Docker Hub (only available on Windows w/ WiX v3) |
| --help, -h | show help (default: `false`) |
Fleet supports other methods for adding your hosts to Fleet, such as the plain osquery
binaries or [Kolide Osquery
Launcher](https://github.com/kolide/launcher/blob/master/docs/launcher.md#connecting-to-fleet).
### Enroll hosts with plain osquery
Osquery's [TLS API](http://osquery.readthedocs.io/en/stable/deployment/remote/) plugin lets you use
the native osqueryd binaries to connect to Fleet. Learn more [here](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Enroll-hosts-with-plain-osquery.md).
<meta name="pageOrderInSection" value="500">
<meta name="description" value="Learn how to generate installers and enroll hosts in your Fleet instance using fleetd or osquery.">

View file

@ -513,7 +513,7 @@ func (svc *Service) DeleteTeam(ctx context.Context, teamID uint) error {
}
if len(hostIDs) > 0 {
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, hostIDs, nil, nil, nil, nil); err != nil {
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, hostIDs, nil, nil, nil); err != nil {
return ctxerr.Wrap(ctx, err, "bulk set pending host profiles")
}

View file

@ -2,9 +2,10 @@ import { IHost } from "interfaces/host";
import { IHostMdmProfile } from "interfaces/mdm";
const DEFAULT_HOST_PROFILE_MOCK: IHostMdmProfile = {
profile_id: 1,
profile_uuid: "123-abc",
name: "Test Profile",
operation_type: "install",
platform: "darwin",
status: "verified",
detail: "This is verified",
};

View file

@ -15,7 +15,7 @@ export const createMockMdmSolution = (
};
const DEFAULT_MDM_PROFILE_DATA: IMdmProfile = {
profile_id: 1,
profile_uuid: "123-abc",
team_id: 0,
name: "Test Profile",
platform: "darwin",

View file

@ -58,7 +58,7 @@ export interface IMdmSummaryResponse {
type ProfilePlatform = "darwin" | "windows";
export interface IMdmProfile {
profile_id: number | string; // string for windows profiles
profile_uuid: string;
team_id: number;
name: string;
platform: ProfilePlatform;
@ -73,9 +73,10 @@ export type MdmProfileStatus = "verified" | "verifying" | "pending" | "failed";
export type ProfileOperationType = "remove" | "install";
export interface IHostMdmProfile {
profile_id: number;
profile_uuid: string;
name: string;
operation_type: ProfileOperationType | null;
platform: ProfilePlatform;
status: MdmProfileStatus;
detail: string;
}

View file

@ -43,7 +43,7 @@ export const TYPE_CONVERSION: Record<string, string> = {
rpm_packages: "Package (RPM)",
yum_sources: "Package (YUM)",
npm_packages: "Package (NPM)",
atom_packages: "Package (Atom)",
atom_packages: "Package (Atom)", // Atom packages were removed from software inventory. Mapping is maintained for backwards compatibility. (2023-12-04)
python_packages: "Package (Python)",
apps: "Application (macOS)",
chrome_extensions: "Browser plugin (Chrome)",

View file

@ -80,6 +80,7 @@ const ProfileStatusAggregate = ({
return (
<ProfileStatusCount
key={value}
statusIcon={iconName}
statusValue={value}
teamId={teamId}

View file

@ -81,7 +81,7 @@ const CustomSettings = ({
setShowDeleteProfileModal(false);
};
const onDeleteProfile = async (profileId: number | string) => {
const onDeleteProfile = async (profileId: string) => {
try {
await mdmAPI.deleteProfile(profileId);
refetchProfiles();
@ -134,6 +134,7 @@ const CustomSettings = ({
return (
<>
<UploadList
keyAttribute="profile_uuid"
listItems={profiles}
HeadingComponent={ProfileListHeading}
ListItemComponent={({ listItem }) => (
@ -170,7 +171,7 @@ const CustomSettings = ({
{showDeleteProfileModal && selectedProfile.current && (
<DeleteProfileModal
profileName={selectedProfile.current?.name}
profileId={selectedProfile.current?.profile_id}
profileId={selectedProfile.current?.profile_uuid}
onCancel={onCancelDelete}
onDelete={onDeleteProfile}
/>

View file

@ -7,9 +7,9 @@ import Button from "components/buttons/Button";
interface DeleteProfileModalProps {
profileName: string;
profileId: number | string;
profileId: string;
onCancel: () => void;
onDelete: (profileId: number | string) => void;
onDelete: (profileId: string) => void;
}
const baseClass = "delete-profile-modal";

View file

@ -39,7 +39,7 @@ interface IProfileListItemProps {
const ProfileListItem = ({ profile, onDelete }: IProfileListItemProps) => {
const onClickDownload = async () => {
const fileContent = await mdmAPI.downloadProfile(profile.profile_id);
const fileContent = await mdmAPI.downloadProfile(profile.profile_uuid);
const formatDate = format(new Date(), "yyyy-MM-dd");
const extension = profile.platform === "darwin" ? "mobileconfig" : "xml";
const filename = `${formatDate}_${profile.name}.${extension}`;

View file

@ -120,6 +120,7 @@ const Scripts = ({ router, currentPage, teamIdForApi }: IScriptsProps) => {
return (
<>
<UploadList
keyAttribute="id"
listItems={scripts || []}
HeadingComponent={ScriptListHeading}
ListItemComponent={({ listItem }) => (

View file

@ -34,6 +34,7 @@ const UploadedPackageView = ({
/>
</p>
<UploadList
keyAttribute="name"
listItems={[bootstrapPackage]}
ListItemComponent={({ listItem }) => (
<BootstrapPackageListItem

View file

@ -3,6 +3,8 @@ import React from "react";
const baseClass = "upload-list";
interface IUploadListProps {
/** The attribute name that is used for the react key for each list item */
keyAttribute: string;
listItems: any[]; // TODO: typings
HeadingComponent?: (props: any) => JSX.Element; // TODO: Typings
ListItemComponent: (props: { listItem: any }) => JSX.Element; // TODO: types
@ -10,6 +12,7 @@ interface IUploadListProps {
}
const UploadList = ({
keyAttribute,
listItems,
HeadingComponent,
ListItemComponent,
@ -17,7 +20,10 @@ const UploadList = ({
}: IUploadListProps) => {
const items = listItems.map((listItem) => {
return (
<li key={`${listItem.id}`} className={`${baseClass}__list-item`}>
<li
key={`${listItem[keyAttribute]}`}
className={`${baseClass}__list-item`}
>
<ListItemComponent listItem={listItem} />
</li>
);

View file

@ -29,6 +29,7 @@ const UploadedEulaView = ({
/>
</p>
<UploadList
keyAttribute="name"
listItems={[eulaMetadata]}
ListItemComponent={({ listItem }) => (
<EulaListItem eulaData={listItem} onDelete={onDelete} />

View file

@ -75,7 +75,7 @@ const TYPE_CONVERSION: Record<string, string> = {
rpm_packages: "Package (RPM)",
yum_sources: "Package (YUM)",
npm_packages: "Package (NPM)",
atom_packages: "Package (Atom)",
atom_packages: "Package (Atom)", // Atom packages were removed from software inventory. Mapping is maintained for backwards compatibility. (2023-12-04)
python_packages: "Package (Python)",
apps: "Application (macOS)",
chrome_extensions: "Browser plugin (Chrome)",

View file

@ -25,7 +25,8 @@ export const generateWinDiskEncryptionProfile = (
detail: string
): IHostMdmProfile => {
return {
profile_id: 0, // This s the only type of profile that can have this number
profile_uuid: "0", // This s the only type of profile that can have this value
platform: "windows",
name: "Disk Encryption",
status: convertWinDiskEncryptionStatusToProfileStatus(diskEncryptionStatus),
detail,

View file

@ -92,7 +92,7 @@ const mdmService = {
return sendRequest("POST", MDM_PROFILES, formData);
},
downloadProfile: (profileId: number | string) => {
downloadProfile: (profileId: string) => {
const { MDM_PROFILE } = endpoints;
const path = `${MDM_PROFILE(profileId)}?${buildQueryStringFromParams({
alt: "media",
@ -100,7 +100,7 @@ const mdmService = {
return sendRequest("GET", path);
},
deleteProfile: (profileId: number | string) => {
deleteProfile: (profileId: string) => {
const { MDM_PROFILE } = endpoints;
return sendRequest("DELETE", MDM_PROFILE(profileId));
},

View file

@ -50,8 +50,7 @@ export default {
// MDM profile endpoints
MDM_PROFILES: `/${API_VERSION}/fleet/mdm/profiles`,
MDM_PROFILE: (id: number | string) =>
`/${API_VERSION}/fleet/mdm/profiles/${id}`,
MDM_PROFILE: (id: string) => `/${API_VERSION}/fleet/mdm/profiles/${id}`,
MDM_UPDATE_APPLE_SETTINGS: `/${API_VERSION}/fleet/mdm/apple/settings`,
MDM_PROFILES_STATUS_SUMMARY: `/${API_VERSION}/fleet/mdm/profiles/summary`,

View file

@ -992,16 +992,6 @@
}
]
},
{
"name": "atom_packages",
"examples": "List installed Atom packages and their version.\n```\nSELECT name, version, description FROM atom_packages;\n```",
"columns": [
{
"name": "uid",
"requires_user_context": true
}
]
},
{
"name": "chrome_extension_content_scripts",
"columns": [

View file

@ -1,12 +1,13 @@
name: atom_packages
examples: >-
List installed Atom packages and their version.
```
SELECT name, version, description FROM atom_packages;
```
columns:
- name: uid
requires_user_context: true
hidden: true
# examples: >-
# List installed Atom packages and their version.
#
# ```
#
# SELECT name, version, description FROM atom_packages;
#
# ```
# columns:
# - name: uid
# requires_user_context: true

View file

@ -23,10 +23,11 @@ import (
)
func (ds *Datastore) NewMDMAppleConfigProfile(ctx context.Context, cp fleet.MDMAppleConfigProfile) (*fleet.MDMAppleConfigProfile, error) {
profUUID := "a" + uuid.New().String()
stmt := `
INSERT INTO
mdm_apple_configuration_profiles (team_id, identifier, name, mobileconfig, checksum)
(SELECT ?, ?, ?, ?, UNHEX(MD5(?)) FROM DUAL WHERE
mdm_apple_configuration_profiles (profile_uuid, team_id, identifier, name, mobileconfig, checksum)
(SELECT ?, ?, ?, ?, ?, UNHEX(MD5(?)) FROM DUAL WHERE
NOT EXISTS (
SELECT 1 FROM mdm_windows_configuration_profiles WHERE name = ? AND team_id = ?
)
@ -37,7 +38,8 @@ INSERT INTO
teamID = *cp.TeamID
}
res, err := ds.writer(ctx).ExecContext(ctx, stmt, teamID, cp.Identifier, cp.Name, cp.Mobileconfig, cp.Mobileconfig, cp.Name, teamID)
res, err := ds.writer(ctx).ExecContext(ctx, stmt,
profUUID, teamID, cp.Identifier, cp.Name, cp.Mobileconfig, cp.Mobileconfig, cp.Name, teamID)
if err != nil {
switch {
case isDuplicate(err):
@ -59,6 +61,7 @@ INSERT INTO
id, _ := res.LastInsertId()
return &fleet.MDMAppleConfigProfile{
ProfileUUID: profUUID,
ProfileID: uint(id),
Identifier: cp.Identifier,
Name: cp.Name,
@ -89,6 +92,7 @@ func formatErrorDuplicateConfigProfile(err error, cp *fleet.MDMAppleConfigProfil
func (ds *Datastore) ListMDMAppleConfigProfiles(ctx context.Context, teamID *uint) ([]*fleet.MDMAppleConfigProfile, error) {
stmt := `
SELECT
profile_uuid,
profile_id,
team_id,
name,
@ -123,9 +127,18 @@ ORDER BY name`
return res, nil
}
func (ds *Datastore) GetMDMAppleConfigProfile(ctx context.Context, profileID uint) (*fleet.MDMAppleConfigProfile, error) {
func (ds *Datastore) GetMDMAppleConfigProfileByDeprecatedID(ctx context.Context, profileID uint) (*fleet.MDMAppleConfigProfile, error) {
return ds.getMDMAppleConfigProfileByIDOrUUID(ctx, profileID, "")
}
func (ds *Datastore) GetMDMAppleConfigProfile(ctx context.Context, profileUUID string) (*fleet.MDMAppleConfigProfile, error) {
return ds.getMDMAppleConfigProfileByIDOrUUID(ctx, 0, profileUUID)
}
func (ds *Datastore) getMDMAppleConfigProfileByIDOrUUID(ctx context.Context, id uint, uuid string) (*fleet.MDMAppleConfigProfile, error) {
stmt := `
SELECT
profile_uuid,
profile_id,
team_id,
name,
@ -137,13 +150,24 @@ SELECT
FROM
mdm_apple_configuration_profiles
WHERE
profile_id=?`
`
var arg any
if uuid != "" {
arg = uuid
stmt += `profile_uuid = ?`
} else {
arg = id
stmt += `profile_id = ?`
}
var res fleet.MDMAppleConfigProfile
err := sqlx.GetContext(ctx, ds.reader(ctx), &res, stmt, profileID)
err := sqlx.GetContext(ctx, ds.reader(ctx), &res, stmt, arg)
if err != nil {
if err == sql.ErrNoRows {
return nil, ctxerr.Wrap(ctx, notFound("MDMAppleConfigProfile").WithID(profileID))
if uuid != "" {
return nil, ctxerr.Wrap(ctx, notFound("MDMAppleConfigProfile").WithName(uuid))
}
return nil, ctxerr.Wrap(ctx, notFound("MDMAppleConfigProfile").WithID(id))
}
return nil, ctxerr.Wrap(ctx, err, "get mdm apple config profile")
}
@ -151,18 +175,35 @@ WHERE
return &res, nil
}
func (ds *Datastore) DeleteMDMAppleConfigProfile(ctx context.Context, profileID uint) error {
res, err := ds.writer(ctx).ExecContext(ctx, `DELETE FROM mdm_apple_configuration_profiles WHERE profile_id=?`, profileID)
func (ds *Datastore) DeleteMDMAppleConfigProfileByDeprecatedID(ctx context.Context, profileID uint) error {
return ds.deleteMDMAppleConfigProfileByIDOrUUID(ctx, profileID, "")
}
func (ds *Datastore) DeleteMDMAppleConfigProfile(ctx context.Context, profileUUID string) error {
return ds.deleteMDMAppleConfigProfileByIDOrUUID(ctx, 0, profileUUID)
}
func (ds *Datastore) deleteMDMAppleConfigProfileByIDOrUUID(ctx context.Context, id uint, uuid string) error {
var arg any
stmt := `DELETE FROM mdm_apple_configuration_profiles WHERE `
if uuid != "" {
arg = uuid
stmt += `profile_uuid = ?`
} else {
arg = id
stmt += `profile_id = ?`
}
res, err := ds.writer(ctx).ExecContext(ctx, stmt, arg)
if err != nil {
return ctxerr.Wrap(ctx, err)
}
deleted, err := res.RowsAffected()
if err != nil {
return ctxerr.Wrap(ctx, err, "fetching delete mdm config profile query rows affected")
}
deleted, _ := res.RowsAffected()
if deleted != 1 {
return ctxerr.Wrap(ctx, notFound("MDMAppleConfigProfile").WithID(profileID))
if uuid != "" {
return ctxerr.Wrap(ctx, notFound("MDMAppleConfigProfile").WithName(uuid))
}
return ctxerr.Wrap(ctx, notFound("MDMAppleConfigProfile").WithID(id))
}
return nil
@ -189,7 +230,7 @@ func (ds *Datastore) DeleteMDMAppleConfigProfileByTeamAndIdentifier(ctx context.
func (ds *Datastore) GetHostMDMAppleProfiles(ctx context.Context, hostUUID string) ([]fleet.HostMDMAppleProfile, error) {
stmt := fmt.Sprintf(`
SELECT
profile_id,
profile_uuid,
profile_name AS name,
profile_identifier AS identifier,
-- internally, a NULL status implies that the cron needs to pick up
@ -943,7 +984,7 @@ func (ds *Datastore) UpdateHostTablesOnMDMUnenroll(ctx context.Context, uuid str
return ctxerr.Wrap(ctx, err, "getting host id from UUID")
}
// NOTE: set installed_from_dep = 0 so DEP host will not be counted as pending after it unrolls
// NOTE: set installed_from_dep = 0 so DEP host will not be counted as pending after it unenrolls.
_, err = tx.ExecContext(ctx, `
UPDATE host_mdm SET enrolled = 0, installed_from_dep = 0, server_url = '', mdm_id = NULL WHERE host_id = ?`, hostID)
if err != nil {
@ -1128,10 +1169,11 @@ WHERE
const insertNewOrEditedProfile = `
INSERT INTO
mdm_apple_configuration_profiles (
team_id, identifier, name, mobileconfig, checksum
profile_uuid, team_id, identifier, name, mobileconfig, checksum
)
VALUES
( ?, ?, ?, ?, UNHEX(MD5(mobileconfig)) )
-- see https://stackoverflow.com/a/51393124/1094941
( CONCAT('a', CONVERT(uuid() USING utf8mb4)), ?, ?, ?, ?, UNHEX(MD5(mobileconfig)) )
ON DUPLICATE KEY UPDATE
name = VALUES(name),
mobileconfig = VALUES(mobileconfig),
@ -1276,14 +1318,14 @@ func (ds *Datastore) bulkSetPendingMDMAppleHostProfilesDB(
const desiredStateStmt = `
SELECT
ds.profile_id as profile_id,
ds.profile_uuid as profile_uuid,
ds.host_uuid as host_uuid,
ds.profile_identifier as profile_identifier,
ds.profile_name as profile_name,
ds.checksum as checksum
FROM (
SELECT
macp.profile_id,
macp.profile_uuid,
h.uuid as host_uuid,
macp.identifier as profile_identifier,
macp.name as profile_name,
@ -1294,12 +1336,12 @@ func (ds *Datastore) bulkSetPendingMDMAppleHostProfilesDB(
WHERE h.platform = 'darwin' AND ne.enabled = 1 AND ne.type = 'Device' AND h.uuid IN (?)
) as ds
LEFT JOIN host_mdm_apple_profiles hmap
ON hmap.profile_id = ds.profile_id AND hmap.host_uuid = ds.host_uuid
ON hmap.profile_uuid = ds.profile_uuid AND hmap.host_uuid = ds.host_uuid
WHERE
-- profile has been updated
( hmap.checksum != ds.checksum ) OR
-- profiles in A but not in B
( hmap.profile_id IS NULL AND hmap.host_uuid IS NULL ) OR
( hmap.profile_uuid IS NULL AND hmap.host_uuid IS NULL ) OR
-- profiles in A and B but with operation type "remove"
( hmap.host_uuid IS NOT NULL AND ( hmap.operation_type = ? OR hmap.operation_type IS NULL ) )`
@ -1319,7 +1361,7 @@ func (ds *Datastore) bulkSetPendingMDMAppleHostProfilesDB(
const currentStateStmt = `
SELECT
hmap.profile_id as profile_id,
hmap.profile_uuid as profile_uuid,
hmap.host_uuid as host_uuid,
hmap.profile_identifier as profile_identifier,
hmap.profile_name as profile_name,
@ -1330,18 +1372,18 @@ func (ds *Datastore) bulkSetPendingMDMAppleHostProfilesDB(
hmap.command_uuid as command_uuid
FROM (
SELECT
h.uuid, macp.profile_id
h.uuid, macp.profile_uuid
FROM mdm_apple_configuration_profiles macp
JOIN hosts h ON h.team_id = macp.team_id OR (h.team_id IS NULL AND macp.team_id = 0)
JOIN nano_enrollments ne ON ne.device_id = h.uuid
WHERE h.platform = 'darwin' AND ne.enabled = 1 AND ne.type = 'Device' AND h.uuid IN (?)
) as ds
RIGHT JOIN host_mdm_apple_profiles hmap
ON hmap.profile_id = ds.profile_id AND hmap.host_uuid = ds.uuid
ON hmap.profile_uuid = ds.profile_uuid AND hmap.host_uuid = ds.uuid
WHERE
hmap.host_uuid IN (?)
-- profiles that are in B but not in A
AND ds.profile_id IS NULL AND ds.uuid IS NULL
AND ds.profile_uuid IS NULL AND ds.uuid IS NULL
-- except "remove" operations in any state
AND ( hmap.operation_type IS NULL OR hmap.operation_type != ? )
`
@ -1395,7 +1437,7 @@ func (ds *Datastore) bulkSetPendingMDMAppleHostProfilesDB(
executeUpsertBatch := func(valuePart string, args []any) error {
baseStmt := fmt.Sprintf(`
INSERT INTO host_mdm_apple_profiles (
profile_id,
profile_uuid,
host_uuid,
profile_identifier,
profile_name,
@ -1439,7 +1481,7 @@ func (ds *Datastore) bulkSetPendingMDMAppleHostProfilesDB(
for _, p := range wantedProfiles {
if pp, ok := profileIntersection.GetMatchingProfileInCurrentState(p); ok {
if pp.Status != &fleet.MDMDeliveryFailed && bytes.Equal(pp.Checksum, p.Checksum) {
pargs = append(pargs, p.ProfileID, p.HostUUID, p.ProfileIdentifier, p.ProfileName, p.Checksum,
pargs = append(pargs, p.ProfileUUID, p.HostUUID, p.ProfileIdentifier, p.ProfileName, p.Checksum,
pp.OperationType, pp.Status, pp.CommandUUID, pp.Detail)
psb.WriteString("(?, ?, ?, ?, ?, ?, ?, ?, ?),")
batchCount++
@ -1454,7 +1496,7 @@ func (ds *Datastore) bulkSetPendingMDMAppleHostProfilesDB(
}
}
pargs = append(pargs, p.ProfileID, p.HostUUID, p.ProfileIdentifier, p.ProfileName, p.Checksum,
pargs = append(pargs, p.ProfileUUID, p.HostUUID, p.ProfileIdentifier, p.ProfileName, p.Checksum,
fleet.MDMOperationTypeInstall, nil, "", "")
psb.WriteString("(?, ?, ?, ?, ?, ?, ?, ?, ?),")
batchCount++
@ -1471,7 +1513,7 @@ func (ds *Datastore) bulkSetPendingMDMAppleHostProfilesDB(
if _, ok := profileIntersection.GetMatchingProfileInDesiredState(p); ok {
continue
}
pargs = append(pargs, p.ProfileID, p.HostUUID, p.ProfileIdentifier, p.ProfileName, p.Checksum,
pargs = append(pargs, p.ProfileUUID, p.HostUUID, p.ProfileIdentifier, p.ProfileName, p.Checksum,
fleet.MDMOperationTypeRemove, nil, "", "")
psb.WriteString("(?, ?, ?, ?, ?, ?, ?, ?, ?),")
batchCount++
@ -1524,30 +1566,30 @@ func (ds *Datastore) ListMDMAppleProfilesToInstall(ctx context.Context) ([]*flee
// be marked as status NULL so that it gets re-installed.
query := `
SELECT
ds.profile_id,
ds.host_uuid,
ds.profile_identifier,
ds.profile_name,
ds.checksum
ds.profile_uuid,
ds.host_uuid,
ds.profile_identifier,
ds.profile_name,
ds.checksum
FROM (
SELECT
macp.profile_id,
macp.profile_uuid,
h.uuid as host_uuid,
macp.identifier as profile_identifier,
macp.name as profile_name,
macp.checksum as checksum
macp.checksum as checksum
FROM mdm_apple_configuration_profiles macp
JOIN hosts h ON h.team_id = macp.team_id OR (h.team_id IS NULL AND macp.team_id = 0)
JOIN nano_enrollments ne ON ne.device_id = h.uuid
WHERE h.platform = 'darwin' AND ne.enabled = 1 AND ne.type = 'Device'
) as ds
LEFT JOIN host_mdm_apple_profiles hmap
ON hmap.profile_id = ds.profile_id AND hmap.host_uuid = ds.host_uuid
ON hmap.profile_uuid = ds.profile_uuid AND hmap.host_uuid = ds.host_uuid
WHERE
-- profile has been updated
( hmap.checksum != ds.checksum ) OR
-- profile has been updated
( hmap.checksum != ds.checksum ) OR
-- profiles in A but not in B
( hmap.profile_id IS NULL AND hmap.host_uuid IS NULL ) OR
( hmap.profile_uuid IS NULL AND hmap.host_uuid IS NULL ) OR
-- profiles in A and B but with operation type "remove"
( hmap.host_uuid IS NOT NULL AND ( hmap.operation_type = ? OR hmap.operation_type IS NULL ) ) OR
-- profiles in A and B with operation type "install" and NULL status
@ -1579,26 +1621,26 @@ func (ds *Datastore) ListMDMAppleProfilesToRemove(ctx context.Context) ([]*fleet
// both, their desired state is necessarily to be installed).
query := `
SELECT
hmap.profile_id,
hmap.profile_identifier,
hmap.profile_name,
hmap.host_uuid,
hmap.checksum,
hmap.operation_type,
COALESCE(hmap.detail, '') as detail,
hmap.status,
hmap.command_uuid
hmap.profile_uuid,
hmap.profile_identifier,
hmap.profile_name,
hmap.host_uuid,
hmap.checksum,
hmap.operation_type,
COALESCE(hmap.detail, '') as detail,
hmap.status,
hmap.command_uuid
FROM (
SELECT h.uuid, macp.profile_id
SELECT h.uuid, macp.profile_uuid
FROM mdm_apple_configuration_profiles macp
JOIN hosts h ON h.team_id = macp.team_id OR (h.team_id IS NULL AND macp.team_id = 0)
JOIN nano_enrollments ne ON ne.device_id = h.uuid
WHERE h.platform = 'darwin' AND ne.enabled = 1 AND ne.type = 'Device'
) as ds
RIGHT JOIN host_mdm_apple_profiles hmap
ON hmap.profile_id = ds.profile_id AND hmap.host_uuid = ds.uuid
ON hmap.profile_uuid = ds.profile_uuid AND hmap.host_uuid = ds.uuid
-- profiles that are in B but not in A
WHERE ds.profile_id IS NULL AND ds.uuid IS NULL
WHERE ds.profile_uuid IS NULL AND ds.uuid IS NULL
-- except "remove" operations in a terminal state or already pending
AND ( hmap.operation_type IS NULL OR hmap.operation_type != ? OR hmap.status IS NULL )
`
@ -1608,16 +1650,16 @@ func (ds *Datastore) ListMDMAppleProfilesToRemove(ctx context.Context) ([]*fleet
return profiles, err
}
func (ds *Datastore) GetMDMAppleProfilesContents(ctx context.Context, ids []uint) (map[uint]mobileconfig.Mobileconfig, error) {
if len(ids) == 0 {
func (ds *Datastore) GetMDMAppleProfilesContents(ctx context.Context, uuids []string) (map[string]mobileconfig.Mobileconfig, error) {
if len(uuids) == 0 {
return nil, nil
}
stmt := `
SELECT profile_id, mobileconfig as mobileconfig
FROM mdm_apple_configuration_profiles WHERE profile_id IN (?)
SELECT profile_uuid, mobileconfig as mobileconfig
FROM mdm_apple_configuration_profiles WHERE profile_uuid IN (?)
`
query, args, err := sqlx.In(stmt, ids)
query, args, err := sqlx.In(stmt, uuids)
if err != nil {
return nil, err
}
@ -1626,14 +1668,14 @@ func (ds *Datastore) GetMDMAppleProfilesContents(ctx context.Context, ids []uint
return nil, err
}
defer rows.Close()
results := make(map[uint]mobileconfig.Mobileconfig)
results := make(map[string]mobileconfig.Mobileconfig)
for rows.Next() {
var id uint
var uid string
var mobileconfig mobileconfig.Mobileconfig
if err := rows.Scan(&id, &mobileconfig); err != nil {
if err := rows.Scan(&uid, &mobileconfig); err != nil {
return nil, err
}
results[id] = mobileconfig
results[uid] = mobileconfig
}
return results, nil
}
@ -1646,7 +1688,7 @@ func (ds *Datastore) BulkUpsertMDMAppleHostProfiles(ctx context.Context, payload
executeUpsertBatch := func(valuePart string, args []any) error {
stmt := fmt.Sprintf(`
INSERT INTO host_mdm_apple_profiles (
profile_id,
profile_uuid,
profile_identifier,
profile_name,
host_uuid,
@ -1654,10 +1696,10 @@ func (ds *Datastore) BulkUpsertMDMAppleHostProfiles(ctx context.Context, payload
operation_type,
detail,
command_uuid,
checksum
checksum
)
VALUES %s
ON DUPLICATE KEY UPDATE
ON DUPLICATE KEY UPDATE
status = VALUES(status),
operation_type = VALUES(operation_type),
detail = VALUES(detail),
@ -1691,7 +1733,7 @@ func (ds *Datastore) BulkUpsertMDMAppleHostProfiles(ctx context.Context, payload
}
for _, p := range payload {
args = append(args, p.ProfileID, p.ProfileIdentifier, p.ProfileName, p.HostUUID, p.Status, p.OperationType, p.Detail, p.CommandUUID, p.Checksum)
args = append(args, p.ProfileUUID, p.ProfileIdentifier, p.ProfileName, p.HostUUID, p.Status, p.OperationType, p.Detail, p.CommandUUID, p.Checksum)
sb.WriteString("(?, ?, ?, ?, ?, ?, ?, ?, ?),")
batchCount++
@ -2157,12 +2199,13 @@ func (ds *Datastore) BulkUpsertMDMAppleConfigProfiles(ctx context.Context, paylo
}
args = append(args, teamID, cp.Identifier, cp.Name, cp.Mobileconfig)
sb.WriteString("(?, ?, ?, ?, UNHEX(MD5(mobileconfig))),")
// see https://stackoverflow.com/a/51393124/1094941
sb.WriteString("(CONCAT('a', CONVERT(uuid() USING utf8mb4)), ?, ?, ?, ?, UNHEX(MD5(mobileconfig))),")
}
stmt := fmt.Sprintf(`
INSERT INTO
mdm_apple_configuration_profiles (team_id, identifier, name, mobileconfig, checksum)
mdm_apple_configuration_profiles (profile_uuid, team_id, identifier, name, mobileconfig, checksum)
VALUES %s
ON DUPLICATE KEY UPDATE
mobileconfig = VALUES(mobileconfig),
@ -2396,6 +2439,7 @@ func getMDMAppleConfigProfileByTeamAndIdentifierDB(ctx context.Context, tx sqlx.
stmt := `
SELECT
profile_uuid,
profile_id,
team_id,
name,

View file

@ -85,18 +85,25 @@ func testNewMDMAppleConfigProfileDuplicateName(t *testing.T, ds *Datastore) {
profA, err := ds.NewMDMAppleConfigProfile(ctx, *generateCP("a", "a", 0))
require.NoError(t, err)
require.NotZero(t, profA.ProfileID)
require.NotEmpty(t, profA.ProfileUUID)
require.Equal(t, "a", string(profA.ProfileUUID[0]))
profB, err := ds.NewMDMAppleConfigProfile(ctx, *generateCP("b", "b", 0))
require.NoError(t, err)
require.NotZero(t, profB.ProfileID)
require.NotEmpty(t, profB.ProfileUUID)
require.Equal(t, "a", string(profB.ProfileUUID[0]))
// create a Windows profile for no-team
profC, err := ds.NewMDMWindowsConfigProfile(ctx, fleet.MDMWindowsConfigProfile{Name: "c", TeamID: nil, SyncML: []byte("<Replace></Replace>")})
require.NoError(t, err)
require.NotEmpty(t, profC.ProfileUUID)
require.Equal(t, "w", string(profC.ProfileUUID[0]))
// create the same name for team 1 as Apple profile
profATm, err := ds.NewMDMAppleConfigProfile(ctx, *generateCP("a", "a", 1))
require.NoError(t, err)
require.NotZero(t, profATm.ProfileID)
require.NotEmpty(t, profATm.ProfileUUID)
require.Equal(t, "a", string(profATm.ProfileUUID[0]))
require.NotNil(t, profATm.TeamID)
require.Equal(t, uint(1), *profATm.TeamID)
// create the same B profile for team 1 as Windows profile
@ -151,7 +158,12 @@ func testNewMDMAppleConfigProfileDuplicateIdentifier(t *testing.T, ds *Datastore
newCP, err := ds.NewMDMAppleConfigProfile(ctx, duplicateCP)
require.NoError(t, err)
checkConfigProfile(t, duplicateCP, *newCP)
storedCP, err := ds.GetMDMAppleConfigProfile(ctx, newCP.ProfileID)
// get it back from both the deprecated ID and the uuid methods
storedCP, err := ds.GetMDMAppleConfigProfileByDeprecatedID(ctx, newCP.ProfileID)
require.NoError(t, err)
checkConfigProfile(t, *newCP, *storedCP)
storedCP, err = ds.GetMDMAppleConfigProfile(ctx, newCP.ProfileUUID)
require.NoError(t, err)
checkConfigProfile(t, *newCP, *storedCP)
}
@ -226,15 +238,25 @@ func testListMDMAppleConfigProfiles(t *testing.T, ds *Datastore) {
func testDeleteMDMAppleConfigProfile(t *testing.T, ds *Datastore) {
ctx := context.Background()
// first via the deprecated ID
initialCP := storeDummyConfigProfileForTest(t, ds)
err := ds.DeleteMDMAppleConfigProfile(ctx, initialCP.ProfileID)
err := ds.DeleteMDMAppleConfigProfileByDeprecatedID(ctx, initialCP.ProfileID)
require.NoError(t, err)
_, err = ds.GetMDMAppleConfigProfile(ctx, initialCP.ProfileID)
_, err = ds.GetMDMAppleConfigProfileByDeprecatedID(ctx, initialCP.ProfileID)
require.ErrorIs(t, err, sql.ErrNoRows)
err = ds.DeleteMDMAppleConfigProfile(ctx, initialCP.ProfileID)
err = ds.DeleteMDMAppleConfigProfileByDeprecatedID(ctx, initialCP.ProfileID)
require.ErrorIs(t, err, sql.ErrNoRows)
// next via the uuid
initialCP = storeDummyConfigProfileForTest(t, ds)
err = ds.DeleteMDMAppleConfigProfile(ctx, initialCP.ProfileUUID)
require.NoError(t, err)
_, err = ds.GetMDMAppleConfigProfile(ctx, initialCP.ProfileUUID)
require.ErrorIs(t, err, sql.ErrNoRows)
err = ds.DeleteMDMAppleConfigProfile(ctx, initialCP.ProfileUUID)
require.ErrorIs(t, err, sql.ErrNoRows)
}
@ -245,7 +267,7 @@ func testDeleteMDMAppleConfigProfileByTeamAndIdentifier(t *testing.T, ds *Datast
err := ds.DeleteMDMAppleConfigProfileByTeamAndIdentifier(ctx, initialCP.TeamID, initialCP.Identifier)
require.NoError(t, err)
_, err = ds.GetMDMAppleConfigProfile(ctx, initialCP.ProfileID)
_, err = ds.GetMDMAppleConfigProfile(ctx, initialCP.ProfileUUID)
require.ErrorIs(t, err, sql.ErrNoRows)
err = ds.DeleteMDMAppleConfigProfileByTeamAndIdentifier(ctx, initialCP.TeamID, initialCP.Identifier)
@ -266,7 +288,7 @@ func storeDummyConfigProfileForTest(t *testing.T, ds *Datastore) *fleet.MDMApple
newCP, err := ds.NewMDMAppleConfigProfile(ctx, dummyCP)
require.NoError(t, err)
checkConfigProfile(t, dummyCP, *newCP)
storedCP, err := ds.GetMDMAppleConfigProfile(ctx, newCP.ProfileID)
storedCP, err := ds.GetMDMAppleConfigProfile(ctx, newCP.ProfileUUID)
require.NoError(t, err)
checkConfigProfile(t, *newCP, *storedCP)
@ -338,30 +360,30 @@ func testHostDetailsMDMProfiles(t *testing.T, ds *Datastore) {
require.NoError(t, err)
require.Nil(t, gotProfs)
expectedProfiles0 := map[uint]fleet.HostMDMAppleProfile{
p0.ProfileID: {HostUUID: h0.UUID, Name: p0.Name, ProfileID: p0.ProfileID, CommandUUID: "cmd0-uuid", Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall, Detail: ""},
p1.ProfileID: {HostUUID: h0.UUID, Name: p1.Name, ProfileID: p1.ProfileID, CommandUUID: "cmd1-uuid", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall, Detail: ""},
p2.ProfileID: {HostUUID: h0.UUID, Name: p2.Name, ProfileID: p2.ProfileID, CommandUUID: "cmd2-uuid", Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove, Detail: "Error removing profile"},
expectedProfiles0 := map[string]fleet.HostMDMAppleProfile{
p0.ProfileUUID: {HostUUID: h0.UUID, Name: p0.Name, ProfileUUID: p0.ProfileUUID, CommandUUID: "cmd0-uuid", Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall, Detail: ""},
p1.ProfileUUID: {HostUUID: h0.UUID, Name: p1.Name, ProfileUUID: p1.ProfileUUID, CommandUUID: "cmd1-uuid", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall, Detail: ""},
p2.ProfileUUID: {HostUUID: h0.UUID, Name: p2.Name, ProfileUUID: p2.ProfileUUID, CommandUUID: "cmd2-uuid", Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove, Detail: "Error removing profile"},
}
expectedProfiles1 := map[uint]fleet.HostMDMAppleProfile{
p0.ProfileID: {HostUUID: h1.UUID, Name: p0.Name, ProfileID: p0.ProfileID, CommandUUID: "cmd0-uuid", Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeInstall, Detail: "Error installing profile"},
p1.ProfileID: {HostUUID: h1.UUID, Name: p1.Name, ProfileID: p1.ProfileID, CommandUUID: "cmd1-uuid", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall, Detail: ""},
p2.ProfileID: {HostUUID: h1.UUID, Name: p2.Name, ProfileID: p2.ProfileID, CommandUUID: "cmd2-uuid", Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove, Detail: "Error removing profile"},
expectedProfiles1 := map[string]fleet.HostMDMAppleProfile{
p0.ProfileUUID: {HostUUID: h1.UUID, Name: p0.Name, ProfileUUID: p0.ProfileUUID, CommandUUID: "cmd0-uuid", Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeInstall, Detail: "Error installing profile"},
p1.ProfileUUID: {HostUUID: h1.UUID, Name: p1.Name, ProfileUUID: p1.ProfileUUID, CommandUUID: "cmd1-uuid", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall, Detail: ""},
p2.ProfileUUID: {HostUUID: h1.UUID, Name: p2.Name, ProfileUUID: p2.ProfileUUID, CommandUUID: "cmd2-uuid", Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove, Detail: "Error removing profile"},
}
var args []interface{}
for _, p := range expectedProfiles0 {
args = append(args, p.HostUUID, p.ProfileID, p.CommandUUID, *p.Status, p.OperationType, p.Detail, p.Name)
args = append(args, p.HostUUID, p.ProfileUUID, p.CommandUUID, *p.Status, p.OperationType, p.Detail, p.Name)
}
for _, p := range expectedProfiles1 {
args = append(args, p.HostUUID, p.ProfileID, p.CommandUUID, *p.Status, p.OperationType, p.Detail, p.Name)
args = append(args, p.HostUUID, p.ProfileUUID, p.CommandUUID, *p.Status, p.OperationType, p.Detail, p.Name)
}
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
_, err := q.ExecContext(ctx, `
INSERT INTO host_mdm_apple_profiles (
host_uuid, profile_id, command_uuid, status, operation_type, detail, profile_name)
host_uuid, profile_uuid, command_uuid, status, operation_type, detail, profile_name)
VALUES (?,?,?,?,?,?,?),(?,?,?,?,?,?,?),(?,?,?,?,?,?,?),(?,?,?,?,?,?,?),(?,?,?,?,?,?,?),(?,?,?,?,?,?,?)
`, args...,
)
@ -379,7 +401,7 @@ func testHostDetailsMDMProfiles(t *testing.T, ds *Datastore) {
require.NoError(t, err)
require.Len(t, gotProfs, 3)
for _, gp := range gotProfs {
ep, ok := expectedProfiles0[gp.ProfileID]
ep, ok := expectedProfiles0[gp.ProfileUUID]
require.True(t, ok)
require.Equal(t, ep.Name, gp.Name)
require.Equal(t, *ep.Status, *gp.Status)
@ -395,7 +417,7 @@ func testHostDetailsMDMProfiles(t *testing.T, ds *Datastore) {
require.NoError(t, err)
require.Len(t, gotProfs, 3)
for _, gp := range gotProfs {
ep, ok := expectedProfiles1[gp.ProfileID]
ep, ok := expectedProfiles1[gp.ProfileUUID]
require.True(t, ok)
require.Equal(t, ep.Name, gp.Name)
require.Equal(t, *ep.Status, *gp.Status)
@ -404,11 +426,11 @@ func testHostDetailsMDMProfiles(t *testing.T, ds *Datastore) {
}
// mark h1's install+failed profile as install+pending
h1InstallFailed := expectedProfiles1[p0.ProfileID]
h1InstallFailed := expectedProfiles1[p0.ProfileUUID]
err = ds.UpdateOrDeleteHostMDMAppleProfile(ctx, &fleet.HostMDMAppleProfile{
HostUUID: h1InstallFailed.HostUUID,
CommandUUID: h1InstallFailed.CommandUUID,
ProfileID: h1InstallFailed.ProfileID,
ProfileUUID: h1InstallFailed.ProfileUUID,
Name: h1InstallFailed.Name,
Status: &fleet.MDMDeliveryPending,
OperationType: fleet.MDMOperationTypeInstall,
@ -417,11 +439,11 @@ func testHostDetailsMDMProfiles(t *testing.T, ds *Datastore) {
require.NoError(t, err)
// mark h1's remove+failed profile as remove+verifying, deletes the host profile row
h1RemoveFailed := expectedProfiles1[p2.ProfileID]
h1RemoveFailed := expectedProfiles1[p2.ProfileUUID]
err = ds.UpdateOrDeleteHostMDMAppleProfile(ctx, &fleet.HostMDMAppleProfile{
HostUUID: h1RemoveFailed.HostUUID,
CommandUUID: h1RemoveFailed.CommandUUID,
ProfileID: h1RemoveFailed.ProfileID,
ProfileUUID: h1RemoveFailed.ProfileUUID,
Name: h1RemoveFailed.Name,
Status: &fleet.MDMDeliveryVerifying,
OperationType: fleet.MDMOperationTypeRemove,
@ -436,10 +458,10 @@ func testHostDetailsMDMProfiles(t *testing.T, ds *Datastore) {
h1InstallPending := h1InstallFailed
h1InstallPending.Status = &fleet.MDMDeliveryPending
h1InstallPending.Detail = ""
expectedProfiles1[p0.ProfileID] = h1InstallPending
delete(expectedProfiles1, p2.ProfileID)
expectedProfiles1[p0.ProfileUUID] = h1InstallPending
delete(expectedProfiles1, p2.ProfileUUID)
for _, gp := range gotProfs {
ep, ok := expectedProfiles1[gp.ProfileID]
ep, ok := expectedProfiles1[gp.ProfileUUID]
require.True(t, ok)
require.Equal(t, ep.Name, gp.Name)
require.Equal(t, *ep.Status, *gp.Status)
@ -791,7 +813,7 @@ func testUpdateHostTablesOnMDMUnenroll(t *testing.T, ds *Datastore) {
err = ds.BulkUpsertMDMAppleHostProfiles(ctx, []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{
ProfileID: profiles[0].ProfileID,
ProfileUUID: profiles[0].ProfileUUID,
ProfileIdentifier: profiles[0].Identifier,
ProfileName: profiles[0].Name,
HostUUID: testUUID,
@ -844,7 +866,7 @@ func expectAppleProfiles(
ds *Datastore,
tmID *uint,
want []*fleet.MDMAppleConfigProfile,
) map[string]uint {
) map[string]string {
if tmID == nil {
tmID = ptr.Uint(0)
}
@ -857,14 +879,15 @@ func expectAppleProfiles(
})
// compare only the fields we care about, and build the resulting map of
// profile identifier as key to profile ID as value
m := make(map[string]uint)
// profile identifier as key to profile UUID as value
m := make(map[string]string)
for _, gotp := range got {
m[gotp.Identifier] = gotp.ProfileID
m[gotp.Identifier] = gotp.ProfileUUID
if gotp.TeamID != nil && *gotp.TeamID == 0 {
gotp.TeamID = nil
}
gotp.ProfileID = 0
gotp.ProfileUUID = ""
gotp.CreatedAt = time.Time{}
gotp.UpdatedAt = time.Time{}
}
@ -874,7 +897,7 @@ func expectAppleProfiles(
}
func testBatchSetMDMAppleProfiles(t *testing.T, ds *Datastore) {
applyAndExpect := func(newSet []*fleet.MDMAppleConfigProfile, tmID *uint, want []*fleet.MDMAppleConfigProfile) map[string]uint {
applyAndExpect := func(newSet []*fleet.MDMAppleConfigProfile, tmID *uint, want []*fleet.MDMAppleConfigProfile) map[string]string {
ctx := context.Background()
err := ds.BatchSetMDMAppleProfiles(ctx, tmID, newSet)
require.NoError(t, err)
@ -1112,9 +1135,9 @@ func testMDMAppleProfileManagement(t *testing.T, ds *Datastore) {
profiles, err = ds.ListMDMAppleProfilesToInstall(ctx)
require.NoError(t, err)
matchProfiles([]*fleet.MDMAppleProfilePayload{
{ProfileID: globalPfs[0].ProfileID, ProfileIdentifier: globalPfs[0].Identifier, ProfileName: globalPfs[0].Name, HostUUID: "test-uuid-1"},
{ProfileID: globalPfs[1].ProfileID, ProfileIdentifier: globalPfs[1].Identifier, ProfileName: globalPfs[1].Name, HostUUID: "test-uuid-1"},
{ProfileID: globalPfs[2].ProfileID, ProfileIdentifier: globalPfs[2].Identifier, ProfileName: globalPfs[2].Name, HostUUID: "test-uuid-1"},
{ProfileUUID: globalPfs[0].ProfileUUID, ProfileIdentifier: globalPfs[0].Identifier, ProfileName: globalPfs[0].Name, HostUUID: "test-uuid-1"},
{ProfileUUID: globalPfs[1].ProfileUUID, ProfileIdentifier: globalPfs[1].Identifier, ProfileName: globalPfs[1].Name, HostUUID: "test-uuid-1"},
{ProfileUUID: globalPfs[2].ProfileUUID, ProfileIdentifier: globalPfs[2].Identifier, ProfileName: globalPfs[2].Name, HostUUID: "test-uuid-1"},
}, profiles)
// add another host, it belongs to a team
@ -1135,9 +1158,9 @@ func testMDMAppleProfileManagement(t *testing.T, ds *Datastore) {
profiles, err = ds.ListMDMAppleProfilesToInstall(ctx)
require.NoError(t, err)
matchProfiles([]*fleet.MDMAppleProfilePayload{
{ProfileID: globalPfs[0].ProfileID, ProfileIdentifier: globalPfs[0].Identifier, ProfileName: globalPfs[0].Name, HostUUID: "test-uuid-1"},
{ProfileID: globalPfs[1].ProfileID, ProfileIdentifier: globalPfs[1].Identifier, ProfileName: globalPfs[1].Name, HostUUID: "test-uuid-1"},
{ProfileID: globalPfs[2].ProfileID, ProfileIdentifier: globalPfs[2].Identifier, ProfileName: globalPfs[2].Name, HostUUID: "test-uuid-1"},
{ProfileUUID: globalPfs[0].ProfileUUID, ProfileIdentifier: globalPfs[0].Identifier, ProfileName: globalPfs[0].Name, HostUUID: "test-uuid-1"},
{ProfileUUID: globalPfs[1].ProfileUUID, ProfileIdentifier: globalPfs[1].Identifier, ProfileName: globalPfs[1].Name, HostUUID: "test-uuid-1"},
{ProfileUUID: globalPfs[2].ProfileUUID, ProfileIdentifier: globalPfs[2].Identifier, ProfileName: globalPfs[2].Name, HostUUID: "test-uuid-1"},
}, profiles)
// assign profiles to team 1
@ -1159,11 +1182,11 @@ func testMDMAppleProfileManagement(t *testing.T, ds *Datastore) {
profiles, err = ds.ListMDMAppleProfilesToInstall(ctx)
require.NoError(t, err)
matchProfiles([]*fleet.MDMAppleProfilePayload{
{ProfileID: globalPfs[0].ProfileID, ProfileIdentifier: globalPfs[0].Identifier, ProfileName: globalPfs[0].Name, HostUUID: "test-uuid-1"},
{ProfileID: globalPfs[1].ProfileID, ProfileIdentifier: globalPfs[1].Identifier, ProfileName: globalPfs[1].Name, HostUUID: "test-uuid-1"},
{ProfileID: globalPfs[2].ProfileID, ProfileIdentifier: globalPfs[2].Identifier, ProfileName: globalPfs[2].Name, HostUUID: "test-uuid-1"},
{ProfileID: teamPfs[0].ProfileID, ProfileIdentifier: teamPfs[0].Identifier, ProfileName: teamPfs[0].Name, HostUUID: "test-uuid-2"},
{ProfileID: teamPfs[1].ProfileID, ProfileIdentifier: teamPfs[1].Identifier, ProfileName: teamPfs[1].Name, HostUUID: "test-uuid-2"},
{ProfileUUID: globalPfs[0].ProfileUUID, ProfileIdentifier: globalPfs[0].Identifier, ProfileName: globalPfs[0].Name, HostUUID: "test-uuid-1"},
{ProfileUUID: globalPfs[1].ProfileUUID, ProfileIdentifier: globalPfs[1].Identifier, ProfileName: globalPfs[1].Name, HostUUID: "test-uuid-1"},
{ProfileUUID: globalPfs[2].ProfileUUID, ProfileIdentifier: globalPfs[2].Identifier, ProfileName: globalPfs[2].Name, HostUUID: "test-uuid-1"},
{ProfileUUID: teamPfs[0].ProfileUUID, ProfileIdentifier: teamPfs[0].Identifier, ProfileName: teamPfs[0].Name, HostUUID: "test-uuid-2"},
{ProfileUUID: teamPfs[1].ProfileUUID, ProfileIdentifier: teamPfs[1].Identifier, ProfileName: teamPfs[1].Name, HostUUID: "test-uuid-2"},
}, profiles)
// add another global host
@ -1182,21 +1205,21 @@ func testMDMAppleProfileManagement(t *testing.T, ds *Datastore) {
profiles, err = ds.ListMDMAppleProfilesToInstall(ctx)
require.NoError(t, err)
matchProfiles([]*fleet.MDMAppleProfilePayload{
{ProfileID: globalPfs[0].ProfileID, ProfileIdentifier: globalPfs[0].Identifier, ProfileName: globalPfs[0].Name, HostUUID: "test-uuid-1"},
{ProfileID: globalPfs[1].ProfileID, ProfileIdentifier: globalPfs[1].Identifier, ProfileName: globalPfs[1].Name, HostUUID: "test-uuid-1"},
{ProfileID: globalPfs[2].ProfileID, ProfileIdentifier: globalPfs[2].Identifier, ProfileName: globalPfs[2].Name, HostUUID: "test-uuid-1"},
{ProfileID: teamPfs[0].ProfileID, ProfileIdentifier: teamPfs[0].Identifier, ProfileName: teamPfs[0].Name, HostUUID: "test-uuid-2"},
{ProfileID: teamPfs[1].ProfileID, ProfileIdentifier: teamPfs[1].Identifier, ProfileName: teamPfs[1].Name, HostUUID: "test-uuid-2"},
{ProfileID: globalPfs[0].ProfileID, ProfileIdentifier: globalPfs[0].Identifier, ProfileName: globalPfs[0].Name, HostUUID: "test-uuid-3"},
{ProfileID: globalPfs[1].ProfileID, ProfileIdentifier: globalPfs[1].Identifier, ProfileName: globalPfs[1].Name, HostUUID: "test-uuid-3"},
{ProfileID: globalPfs[2].ProfileID, ProfileIdentifier: globalPfs[2].Identifier, ProfileName: globalPfs[2].Name, HostUUID: "test-uuid-3"},
{ProfileUUID: globalPfs[0].ProfileUUID, ProfileIdentifier: globalPfs[0].Identifier, ProfileName: globalPfs[0].Name, HostUUID: "test-uuid-1"},
{ProfileUUID: globalPfs[1].ProfileUUID, ProfileIdentifier: globalPfs[1].Identifier, ProfileName: globalPfs[1].Name, HostUUID: "test-uuid-1"},
{ProfileUUID: globalPfs[2].ProfileUUID, ProfileIdentifier: globalPfs[2].Identifier, ProfileName: globalPfs[2].Name, HostUUID: "test-uuid-1"},
{ProfileUUID: teamPfs[0].ProfileUUID, ProfileIdentifier: teamPfs[0].Identifier, ProfileName: teamPfs[0].Name, HostUUID: "test-uuid-2"},
{ProfileUUID: teamPfs[1].ProfileUUID, ProfileIdentifier: teamPfs[1].Identifier, ProfileName: teamPfs[1].Name, HostUUID: "test-uuid-2"},
{ProfileUUID: globalPfs[0].ProfileUUID, ProfileIdentifier: globalPfs[0].Identifier, ProfileName: globalPfs[0].Name, HostUUID: "test-uuid-3"},
{ProfileUUID: globalPfs[1].ProfileUUID, ProfileIdentifier: globalPfs[1].Identifier, ProfileName: globalPfs[1].Name, HostUUID: "test-uuid-3"},
{ProfileUUID: globalPfs[2].ProfileUUID, ProfileIdentifier: globalPfs[2].Identifier, ProfileName: globalPfs[2].Name, HostUUID: "test-uuid-3"},
}, profiles)
// cron runs and updates the status
err = ds.BulkUpsertMDMAppleHostProfiles(
ctx, []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{
ProfileID: globalPfs[0].ProfileID,
ProfileUUID: globalPfs[0].ProfileUUID,
ProfileIdentifier: globalPfs[0].Identifier,
ProfileName: globalPfs[0].Name,
Checksum: globalProfiles[0].Checksum,
@ -1206,7 +1229,7 @@ func testMDMAppleProfileManagement(t *testing.T, ds *Datastore) {
CommandUUID: "command-uuid",
},
{
ProfileID: globalPfs[0].ProfileID,
ProfileUUID: globalPfs[0].ProfileUUID,
ProfileIdentifier: globalPfs[0].Identifier,
ProfileName: globalPfs[0].Name,
Checksum: globalProfiles[0].Checksum,
@ -1216,7 +1239,7 @@ func testMDMAppleProfileManagement(t *testing.T, ds *Datastore) {
CommandUUID: "command-uuid",
},
{
ProfileID: globalPfs[1].ProfileID,
ProfileUUID: globalPfs[1].ProfileUUID,
ProfileIdentifier: globalPfs[1].Identifier,
ProfileName: globalPfs[1].Name,
Checksum: globalProfiles[1].Checksum,
@ -1226,7 +1249,7 @@ func testMDMAppleProfileManagement(t *testing.T, ds *Datastore) {
CommandUUID: "command-uuid",
},
{
ProfileID: globalPfs[1].ProfileID,
ProfileUUID: globalPfs[1].ProfileUUID,
ProfileIdentifier: globalPfs[1].Identifier,
ProfileName: globalPfs[1].Name,
Checksum: globalProfiles[1].Checksum,
@ -1236,7 +1259,7 @@ func testMDMAppleProfileManagement(t *testing.T, ds *Datastore) {
CommandUUID: "command-uuid",
},
{
ProfileID: globalPfs[2].ProfileID,
ProfileUUID: globalPfs[2].ProfileUUID,
ProfileIdentifier: globalPfs[2].Identifier,
ProfileName: globalPfs[2].Name,
Checksum: globalProfiles[2].Checksum,
@ -1246,7 +1269,7 @@ func testMDMAppleProfileManagement(t *testing.T, ds *Datastore) {
CommandUUID: "command-uuid",
},
{
ProfileID: globalPfs[2].ProfileID,
ProfileUUID: globalPfs[2].ProfileUUID,
ProfileIdentifier: globalPfs[2].Identifier,
ProfileName: globalPfs[2].Name,
Checksum: globalProfiles[2].Checksum,
@ -1256,7 +1279,7 @@ func testMDMAppleProfileManagement(t *testing.T, ds *Datastore) {
CommandUUID: "command-uuid",
},
{
ProfileID: teamPfs[0].ProfileID,
ProfileUUID: teamPfs[0].ProfileUUID,
ProfileIdentifier: teamPfs[0].Identifier,
ProfileName: teamPfs[0].Name,
Checksum: teamProfiles[0].Checksum,
@ -1266,7 +1289,7 @@ func testMDMAppleProfileManagement(t *testing.T, ds *Datastore) {
CommandUUID: "command-uuid",
},
{
ProfileID: teamPfs[1].ProfileID,
ProfileUUID: teamPfs[1].ProfileUUID,
ProfileIdentifier: teamPfs[1].Identifier,
ProfileName: teamPfs[1].Name,
Checksum: teamProfiles[1].Checksum,
@ -1316,8 +1339,8 @@ func testMDMAppleProfileManagement(t *testing.T, ds *Datastore) {
profiles, err = ds.ListMDMAppleProfilesToInstall(ctx)
require.NoError(t, err)
matchProfiles([]*fleet.MDMAppleProfilePayload{
{ProfileID: teamPfs[0].ProfileID, ProfileIdentifier: teamPfs[0].Identifier, ProfileName: teamPfs[0].Name, HostUUID: "test-uuid-1"},
{ProfileID: teamPfs[1].ProfileID, ProfileIdentifier: teamPfs[1].Identifier, ProfileName: teamPfs[1].Name, HostUUID: "test-uuid-1"},
{ProfileUUID: teamPfs[0].ProfileUUID, ProfileIdentifier: teamPfs[0].Identifier, ProfileName: teamPfs[0].Name, HostUUID: "test-uuid-1"},
{ProfileUUID: teamPfs[1].ProfileUUID, ProfileIdentifier: teamPfs[1].Identifier, ProfileName: teamPfs[1].Name, HostUUID: "test-uuid-1"},
}, profiles)
// profiles to be removed includes host1's old profiles
@ -1325,7 +1348,7 @@ func testMDMAppleProfileManagement(t *testing.T, ds *Datastore) {
require.NoError(t, err)
matchProfiles([]*fleet.MDMAppleProfilePayload{
{
ProfileID: globalPfs[0].ProfileID,
ProfileUUID: globalPfs[0].ProfileUUID,
ProfileIdentifier: globalPfs[0].Identifier,
ProfileName: globalPfs[0].Name,
Status: &fleet.MDMDeliveryVerified,
@ -1334,7 +1357,7 @@ func testMDMAppleProfileManagement(t *testing.T, ds *Datastore) {
CommandUUID: "command-uuid",
},
{
ProfileID: globalPfs[1].ProfileID,
ProfileUUID: globalPfs[1].ProfileUUID,
ProfileIdentifier: globalPfs[1].Identifier,
ProfileName: globalPfs[1].Name,
OperationType: fleet.MDMOperationTypeInstall,
@ -1343,7 +1366,7 @@ func testMDMAppleProfileManagement(t *testing.T, ds *Datastore) {
CommandUUID: "command-uuid",
},
{
ProfileID: globalPfs[2].ProfileID,
ProfileUUID: globalPfs[2].ProfileUUID,
ProfileIdentifier: globalPfs[2].Identifier,
ProfileName: globalPfs[2].Name,
OperationType: fleet.MDMOperationTypeInstall,
@ -1404,24 +1427,24 @@ func testGetMDMAppleProfilesContents(t *testing.T, ds *Datastore) {
require.NoError(t, err)
cases := []struct {
ids []uint
want map[uint]mobileconfig.Mobileconfig
uuids []string
want map[string]mobileconfig.Mobileconfig
}{
{[]uint{}, nil},
{[]string{}, nil},
{nil, nil},
{[]uint{profiles[0].ProfileID}, map[uint]mobileconfig.Mobileconfig{profiles[0].ProfileID: profiles[0].Mobileconfig}},
{[]string{profiles[0].ProfileUUID}, map[string]mobileconfig.Mobileconfig{profiles[0].ProfileUUID: profiles[0].Mobileconfig}},
{
[]uint{profiles[0].ProfileID, profiles[1].ProfileID, profiles[2].ProfileID},
map[uint]mobileconfig.Mobileconfig{
profiles[0].ProfileID: profiles[0].Mobileconfig,
profiles[1].ProfileID: profiles[1].Mobileconfig,
profiles[2].ProfileID: profiles[2].Mobileconfig,
[]string{profiles[0].ProfileUUID, profiles[1].ProfileUUID, profiles[2].ProfileUUID},
map[string]mobileconfig.Mobileconfig{
profiles[0].ProfileUUID: profiles[0].Mobileconfig,
profiles[1].ProfileUUID: profiles[1].Mobileconfig,
profiles[2].ProfileUUID: profiles[2].Mobileconfig,
},
},
}
for _, c := range cases {
out, err := ds.GetMDMAppleProfilesContents(ctx, c.ids)
out, err := ds.GetMDMAppleProfilesContents(ctx, c.uuids)
require.NoError(t, err)
require.Equal(t, c.want, out)
}
@ -1507,7 +1530,7 @@ func upsertHostCPs(
csum = cp.Checksum
}
payload := fleet.MDMAppleBulkUpsertHostProfilePayload{
ProfileID: cp.ProfileID,
ProfileUUID: cp.ProfileUUID,
ProfileIdentifier: cp.Identifier,
ProfileName: cp.Name,
HostUUID: h.UUID,
@ -2143,7 +2166,7 @@ func testIgnoreMDMClientError(t *testing.T, ds *Datastore) {
// create new record for remove pending
require.NoError(t, ds.BulkUpsertMDMAppleHostProfiles(ctx, []*fleet.MDMAppleBulkUpsertHostProfilePayload{{
ProfileID: uint(1),
ProfileUUID: "a" + uuid.NewString(),
ProfileIdentifier: "p1",
ProfileName: "name1",
HostUUID: "h1",
@ -2174,7 +2197,7 @@ func testIgnoreMDMClientError(t *testing.T, ds *Datastore) {
// create another new record
require.NoError(t, ds.BulkUpsertMDMAppleHostProfiles(ctx, []*fleet.MDMAppleBulkUpsertHostProfilePayload{{
ProfileID: uint(2),
ProfileUUID: "a" + uuid.NewString(),
ProfileIdentifier: "p2",
ProfileName: "name2",
HostUUID: "h2",
@ -2224,7 +2247,7 @@ func testDeleteMDMAppleProfilesForHost(t *testing.T, ds *Datastore) {
require.NoError(t, err)
require.NoError(t, ds.BulkUpsertMDMAppleHostProfiles(ctx, []*fleet.MDMAppleBulkUpsertHostProfilePayload{{
ProfileID: uint(1),
ProfileUUID: "a" + uuid.NewString(),
ProfileIdentifier: "p1",
ProfileName: "name1",
HostUUID: h.UUID,
@ -3580,9 +3603,9 @@ func testSetVerifiedMacOSProfiles(t *testing.T, ds *Datastore) {
// simulate expired grace period by setting updated_at timestamp of profiles back by 24 hours
ExecAdhocSQL(t, ds, func(tx sqlx.ExtContext) error {
_, err := tx.ExecContext(ctx,
`UPDATE mdm_apple_configuration_profiles SET updated_at = ? WHERE profile_id IN(?, ?, ?)`,
`UPDATE mdm_apple_configuration_profiles SET updated_at = ? WHERE profile_uuid IN(?, ?, ?)`,
time.Now().Add(-24*time.Hour),
cp1.ProfileID, cp2.ProfileID, cp3.ProfileID,
cp1.ProfileUUID, cp2.ProfileUUID, cp3.ProfileUUID,
)
return err
})
@ -4091,7 +4114,7 @@ func testMDMAppleConfigProfileHash(t *testing.T, ds *Datastore) {
require.NoError(t, err)
t.Cleanup(func() {
err := ds.DeleteMDMAppleConfigProfile(ctx, prof.ProfileID)
err := ds.DeleteMDMAppleConfigProfile(ctx, prof.ProfileUUID)
require.NoError(t, err)
})
@ -4099,11 +4122,11 @@ func testMDMAppleConfigProfileHash(t *testing.T, ds *Datastore) {
goHash := goProf.HexMD5Hash()
require.NotEmpty(t, goHash)
var id uint
var uid string
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
return sqlx.GetContext(ctx, q, &id, `SELECT profile_id FROM mdm_apple_configuration_profiles WHERE checksum = UNHEX(?)`, goHash)
return sqlx.GetContext(ctx, q, &uid, `SELECT profile_uuid FROM mdm_apple_configuration_profiles WHERE checksum = UNHEX(?)`, goHash)
})
require.Equal(t, prof.ProfileID, id)
require.Equal(t, prof.ProfileUUID, uid)
})
}
}
@ -4269,7 +4292,7 @@ func TestMDMAppleProfileVerification(t *testing.T) {
setProfileUpdatedAt := func(t *testing.T, cp *fleet.MDMAppleConfigProfile, ua time.Time) {
ExecAdhocSQL(t, ds, func(tx sqlx.ExtContext) error {
_, err := tx.ExecContext(ctx, `UPDATE mdm_apple_configuration_profiles SET updated_at = ? WHERE profile_id = ?`, ua, cp.ProfileID)
_, err := tx.ExecContext(ctx, `UPDATE mdm_apple_configuration_profiles SET updated_at = ? WHERE profile_uuid = ?`, ua, cp.ProfileUUID)
return err
})
}

View file

@ -759,9 +759,10 @@ func testHostListOptionsTeamFilter(t *testing.T, ds *Datastore) {
listHostsCheckCount(t, ds, userFilter, fleet.HostListOptions{TeamFilter: &team2.ID}, 4)
// test team filter in combination with macos settings filter
profUUID := "a" + uuid.NewString()
require.NoError(t, ds.BulkUpsertMDMAppleHostProfiles(context.Background(), []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{
ProfileID: 1,
ProfileUUID: profUUID,
ProfileIdentifier: "identifier",
HostUUID: hosts[0].UUID, // hosts[0] is assgined to team 1
CommandUUID: "command-uuid-1",
@ -779,7 +780,7 @@ func testHostListOptionsTeamFilter(t *testing.T, ds *Datastore) {
require.NoError(t, ds.BulkUpsertMDMAppleHostProfiles(context.Background(), []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{
ProfileID: 1,
ProfileUUID: profUUID,
ProfileIdentifier: "identifier",
HostUUID: hosts[9].UUID, // hosts[9] is assgined to no team
CommandUUID: "command-uuid-2",
@ -806,7 +807,7 @@ func testHostListOptionsTeamFilter(t *testing.T, ds *Datastore) {
// test team filter in combination with os settings disk encryptionfilter
require.NoError(t, ds.BulkUpsertMDMAppleHostProfiles(context.Background(), []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{
ProfileID: 1,
ProfileUUID: profUUID,
ProfileIdentifier: mobileconfig.FleetFileVaultPayloadIdentifier,
HostUUID: hosts[8].UUID, // hosts[8] is assgined to no team
CommandUUID: "command-uuid-3",
@ -5834,7 +5835,7 @@ func testHostsDeleteHosts(t *testing.T, ds *Datastore) {
prof, err := ds.NewMDMAppleConfigProfile(context.Background(), *configProfileForTest(t, "N1", "I1", "U1"))
require.NoError(t, err)
err = ds.BulkUpsertMDMAppleHostProfiles(context.Background(), []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{ProfileID: prof.ProfileID, ProfileIdentifier: prof.Identifier, ProfileName: prof.Name, HostUUID: host.UUID, OperationType: fleet.MDMOperationTypeInstall, Checksum: []byte("csum")},
{ProfileUUID: prof.ProfileUUID, ProfileIdentifier: prof.Identifier, ProfileName: prof.Name, HostUUID: host.UUID, OperationType: fleet.MDMOperationTypeInstall, Checksum: []byte("csum")},
})
require.NoError(t, err)

View file

@ -13,6 +13,7 @@ import (
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/test"
"github.com/google/go-cmp/cmp"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -490,7 +491,7 @@ func testLabelsListHostsInLabelAndTeamFilter(deferred bool, t *testing.T, db *Da
// test team filter in combination with macos settings filter
require.NoError(t, db.BulkUpsertMDMAppleHostProfiles(context.Background(), []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{
ProfileID: 1,
ProfileUUID: "a" + uuid.NewString(),
ProfileIdentifier: "identifier",
HostUUID: h1.UUID, // hosts[0] is assgined to team 1
CommandUUID: "command-uuid-1",
@ -508,7 +509,7 @@ func testLabelsListHostsInLabelAndTeamFilter(deferred bool, t *testing.T, db *Da
require.NoError(t, db.BulkUpsertMDMAppleHostProfiles(context.Background(), []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{
ProfileID: 1,
ProfileUUID: "a" + uuid.NewString(),
ProfileIdentifier: "identifier",
HostUUID: h2.UUID, // hosts[9] is assgined to no team
CommandUUID: "command-uuid-2",

View file

@ -5,6 +5,7 @@ import (
"database/sql"
"errors"
"fmt"
"strings"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
@ -108,7 +109,7 @@ func (ds *Datastore) ListMDMConfigProfiles(ctx context.Context, teamID *uint, op
const selectStmt = `
SELECT
profile_id,
profile_uuid,
team_id,
name,
platform,
@ -118,7 +119,7 @@ SELECT
updated_at
FROM (
SELECT
CONVERT(profile_id, CHAR) as profile_id,
profile_uuid,
team_id,
name,
'darwin' as platform,
@ -135,7 +136,7 @@ FROM (
UNION
SELECT
profile_uuid as profile_id,
profile_uuid,
team_id,
name,
'windows' as platform,
@ -195,31 +196,45 @@ FROM (
// slice arguments can have values.
func (ds *Datastore) BulkSetPendingMDMHostProfiles(
ctx context.Context,
hostIDs, teamIDs, profileIDs []uint,
hostIDs, teamIDs []uint,
profileUUIDs, hostUUIDs []string,
) error {
var countArgs int
var (
countArgs int
macProfUUIDs []string
winProfUUIDs []string
)
if len(hostIDs) > 0 {
countArgs++
}
if len(teamIDs) > 0 {
countArgs++
}
if len(profileIDs) > 0 {
countArgs++
}
if len(profileUUIDs) > 0 {
countArgs++
// split into mac and win profiles
for _, puid := range profileUUIDs {
if strings.HasPrefix(puid, "a") {
macProfUUIDs = append(macProfUUIDs, puid)
} else {
winProfUUIDs = append(winProfUUIDs, puid)
}
}
}
if len(hostUUIDs) > 0 {
countArgs++
}
if countArgs > 1 {
return errors.New("only one of hostIDs, teamIDs, profileIDs, profileUUIDs or hostUUIDs can be provided")
return errors.New("only one of hostIDs, teamIDs, profileUUIDs or hostUUIDs can be provided")
}
if countArgs == 0 {
return nil
}
if len(macProfUUIDs) > 0 && len(winProfUUIDs) > 0 {
return errors.New("profile uuids must all be Apple or Windows profiles")
}
var (
hosts []fleet.Host
@ -257,8 +272,8 @@ func (ds *Datastore) BulkSetPendingMDMHostProfiles(
}
}
case len(profileIDs) > 0:
// TODO: if a very large number (~65K) of profile IDs was provided, could
case len(macProfUUIDs) > 0:
// TODO: if a very large number (~65K) of profile UUIDs was provided, could
// result in too many placeholders (not an immediate concern).
uuidStmt = `
SELECT DISTINCT h.uuid, h.platform
@ -266,10 +281,10 @@ FROM hosts h
JOIN mdm_apple_configuration_profiles macp
ON h.team_id = macp.team_id OR (h.team_id IS NULL AND macp.team_id = 0)
WHERE
macp.profile_id IN (?) AND h.platform = 'darwin'`
args = append(args, profileIDs)
macp.profile_uuid IN (?) AND h.platform = 'darwin'`
args = append(args, macProfUUIDs)
case len(profileUUIDs) > 0:
case len(winProfUUIDs) > 0:
// TODO: if a very large number (~65K) of profile IDs was provided, could
// result in too many placeholders (not an immediate concern).
uuidStmt = `
@ -279,7 +294,7 @@ JOIN mdm_windows_configuration_profiles mawp
ON h.team_id = mawp.team_id OR (h.team_id IS NULL AND mawp.team_id = 0)
WHERE
mawp.profile_uuid IN (?) AND h.platform = 'windows'`
args = append(args, profileUUIDs)
args = append(args, winProfUUIDs)
}

View file

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"sort"
"strconv"
"testing"
"github.com/fleetdm/fleet/v4/server/fleet"
@ -468,7 +467,7 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
}
type anyProfile struct {
ProfileID string `db:"profile_uuid"`
ProfileUUID string `db:"profile_uuid"`
Status *fleet.MDMDeliveryStatus `db:"status"`
OperationType fleet.MDMOperationType `db:"operation_type"`
IdentifierOrName string `db:"profile_name"`
@ -499,7 +498,7 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
require.Equal(t, len(wantProfs), len(profs), "host uuid: %s", h.UUID)
for _, p := range profs {
gotProfs = append(gotProfs, anyProfile{
ProfileID: fmt.Sprint(p.ProfileID),
ProfileUUID: p.ProfileUUID,
Status: p.Status,
OperationType: p.OperationType,
IdentifierOrName: p.Identifier,
@ -510,7 +509,7 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
sortProfs := func(profs []anyProfile) []anyProfile {
sort.Slice(profs, func(i, j int) bool {
l, r := profs[i], profs[j]
if l.ProfileID == r.ProfileID {
if l.ProfileUUID == r.ProfileUUID {
return l.OperationType < r.OperationType
}
@ -524,7 +523,7 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
wantProfs = sortProfs(wantProfs)
for i, wp := range wantProfs {
gp := gotProfs[i]
require.Equal(t, wp.ProfileID, gp.ProfileID, "host uuid: %s, prof id or name: %s", h.UUID, gp.IdentifierOrName)
require.Equal(t, wp.ProfileUUID, gp.ProfileUUID, "host uuid: %s, prof id or name: %s", h.UUID, gp.IdentifierOrName)
require.Equal(t, wp.Status, gp.Status, "host uuid: %s, prof id or name: %s", h.UUID, gp.IdentifierOrName)
require.Equal(t, wp.OperationType, gp.OperationType, "host uuid: %s, prof id or name: %s", h.UUID, gp.IdentifierOrName)
}
@ -603,16 +602,16 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
}
// bulk set for no target ids, does nothing
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, nil, nil, nil)
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, nil, nil)
require.NoError(t, err)
// bulk set for combination of target ids, not allowed
err = ds.BulkSetPendingMDMHostProfiles(ctx, []uint{1}, []uint{2}, nil, nil, nil)
err = ds.BulkSetPendingMDMHostProfiles(ctx, []uint{1}, []uint{2}, nil, nil)
require.Error(t, err)
// bulk set for all created hosts, no profiles yet so nothing changed
allHosts := append(darwinHosts, unenrolledHost, linuxHost)
allHosts = append(allHosts, windowsHosts...)
err = ds.BulkSetPendingMDMHostProfiles(ctx, hostIDsFromHosts(allHosts...), nil, nil, nil, nil)
err = ds.BulkSetPendingMDMHostProfiles(ctx, hostIDsFromHosts(allHosts...), nil, nil, nil)
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {},
@ -662,40 +661,40 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
require.Len(t, toRemoveWindows, 0)
// bulk set for all created hosts, enrolled hosts get the no-team profiles
err = ds.BulkSetPendingMDMHostProfiles(ctx, hostIDsFromHosts(allHosts...), nil, nil, nil, nil)
err = ds.BulkSetPendingMDMHostProfiles(ctx, hostIDsFromHosts(allHosts...), nil, nil, nil)
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
darwinHosts[1]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
darwinHosts[2]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
{ProfileID: globalProfiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
windowsHosts[1]: {
{ProfileID: globalProfiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
windowsHosts[2]: {
{ProfileID: globalProfiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
})
@ -724,37 +723,37 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
require.Len(t, toRemoveWindows, 3)
// update status of the moved host (team has no profiles)
err = ds.BulkSetPendingMDMHostProfiles(ctx, hostIDsFromHosts(darwinHosts[0], windowsHosts[0]), nil, nil, nil, nil)
err = ds.BulkSetPendingMDMHostProfiles(ctx, hostIDsFromHosts(darwinHosts[0], windowsHosts[0]), nil, nil, nil)
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
},
darwinHosts[1]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
darwinHosts[2]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
unenrolledHost: {},
linuxHost: {},
// windows profiles are directly deleted without a pending state
windowsHosts[0]: {},
windowsHosts[1]: {
{ProfileID: globalProfiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
windowsHosts[2]: {
{ProfileID: globalProfiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
})
@ -784,23 +783,23 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
require.Len(t, toRemoveWindows, 3)
// update status of the moved host via its uuid (team has no profiles)
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, nil, nil, []string{darwinHosts[1].UUID, windowsHosts[1].UUID})
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, nil, []string{darwinHosts[1].UUID, windowsHosts[1].UUID})
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
},
darwinHosts[1]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
},
darwinHosts[2]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
unenrolledHost: {},
linuxHost: {},
@ -808,9 +807,9 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
// windows profiles are directly deleted without a pending state
windowsHosts[1]: {},
windowsHosts[2]: {
{ProfileID: globalProfiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
})
@ -847,37 +846,37 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
require.Len(t, toRemoveWindows, 0)
// update status of the affected team
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{team1.ID}, nil, nil, nil)
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{team1.ID}, nil, nil)
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: tm1Profiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: tm1Profiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: tm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: tm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
darwinHosts[1]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
},
darwinHosts[2]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
{ProfileID: tm1Profiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: tm1Profiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: tm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: tm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
windowsHosts[1]: {},
windowsHosts[2]: {
{ProfileID: globalProfiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
})
@ -894,15 +893,15 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
// all rows in this test since we don't have command uuids.
err = ds.BulkUpsertMDMAppleHostProfiles(ctx, []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{
HostUUID: darwinHosts[0].UUID, ProfileID: darwinGlobalProfiles[0].ProfileID,
HostUUID: darwinHosts[0].UUID, ProfileUUID: darwinGlobalProfiles[0].ProfileUUID,
Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeRemove, Checksum: []byte("csum"),
},
{
HostUUID: darwinHosts[0].UUID, ProfileID: darwinGlobalProfiles[1].ProfileID,
HostUUID: darwinHosts[0].UUID, ProfileUUID: darwinGlobalProfiles[1].ProfileUUID,
Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeRemove, Checksum: []byte("csum"),
},
{
HostUUID: darwinHosts[0].UUID, ProfileID: darwinGlobalProfiles[2].ProfileID,
HostUUID: darwinHosts[0].UUID, ProfileUUID: darwinGlobalProfiles[2].ProfileUUID,
Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove, Checksum: []byte("csum"),
},
})
@ -925,37 +924,37 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
require.Len(t, newTm1Profiles, 4)
// update status of the affected team
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{team1.ID}, nil, nil, nil)
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{team1.ID}, nil, nil)
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: tm1Profiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: newTm1Profiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: tm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
darwinHosts[1]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
},
darwinHosts[2]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
{ProfileID: newTm1Profiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
windowsHosts[1]: {},
windowsHosts[2]: {
{ProfileID: globalProfiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
})
@ -980,38 +979,38 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
require.Len(t, newTm1Profiles, 6)
// update status of the affected team
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{team1.ID}, nil, nil, nil)
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{team1.ID}, nil, nil)
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: newTm1Profiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
darwinHosts[1]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
},
darwinHosts[2]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
{ProfileID: newTm1Profiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
windowsHosts[1]: {},
windowsHosts[2]: {
{ProfileID: globalProfiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: globalProfiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
})
@ -1035,39 +1034,39 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
require.Len(t, newGlobalProfiles, 6)
// update status of the affected "no-team"
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{0}, nil, nil, nil)
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{0}, nil, nil)
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: newTm1Profiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
darwinHosts[1]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
},
darwinHosts[2]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: newGlobalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
{ProfileID: newTm1Profiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
windowsHosts[1]: {},
windowsHosts[2]: {
{ProfileID: newGlobalProfiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
})
@ -1092,80 +1091,80 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
newGlobalProfiles = getProfs(nil)
require.Len(t, newGlobalProfiles, 8)
newDarwinProfileID, err := strconv.ParseUint(newGlobalProfiles[3].ProfileID, 10, 64)
newDarwinProfileUUID := newGlobalProfiles[3].ProfileUUID
require.NoError(t, err)
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, []uint{uint(newDarwinProfileID)}, []string{}, nil)
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, []string{newDarwinProfileUUID}, nil)
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: newTm1Profiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
darwinHosts[1]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
},
darwinHosts[2]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: newGlobalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
{ProfileID: newTm1Profiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
windowsHosts[1]: {},
windowsHosts[2]: {
{ProfileID: newGlobalProfiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[6].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
})
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, []uint{}, []string{newGlobalProfiles[7].ProfileID}, nil)
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, []string{newGlobalProfiles[7].ProfileUUID}, nil)
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: newTm1Profiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
darwinHosts[1]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
},
darwinHosts[2]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: newGlobalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
{ProfileID: newTm1Profiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
windowsHosts[1]: {},
windowsHosts[2]: {
{ProfileID: newGlobalProfiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[6].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[7].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
})
@ -1185,50 +1184,50 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
require.Len(t, tm2Profiles, 2)
// update status via tm2 id and the global 0 id to test that custom sql statement
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{team2.ID, 0}, nil, nil, nil)
err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{team2.ID, 0}, nil, nil)
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: newTm1Profiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
darwinHosts[1]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: tm2Profiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: tm2Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
darwinHosts[2]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: newGlobalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
{ProfileID: newTm1Profiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
windowsHosts[1]: {
{ProfileID: tm2Profiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: tm2Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
windowsHosts[2]: {
{ProfileID: newGlobalProfiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[6].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[7].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
})
// simulate an entry with some values set to NULL
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
_, err := q.ExecContext(ctx, `UPDATE host_mdm_apple_profiles SET detail = NULL WHERE profile_id = ?`, globalProfiles[2].ProfileID)
_, err := q.ExecContext(ctx, `UPDATE host_mdm_apple_profiles SET detail = NULL WHERE profile_uuid = ?`, globalProfiles[2].ProfileUUID)
if err != nil {
return err
}
@ -1236,44 +1235,44 @@ func testBulkSetPendingMDMHostProfiles(t *testing.T, ds *Datastore) {
})
// do a final sync of all hosts, should not change anything
err = ds.BulkSetPendingMDMHostProfiles(ctx, hostIDsFromHosts(append(darwinHosts, unenrolledHost, linuxHost)...), nil, nil, nil, nil)
err = ds.BulkSetPendingMDMHostProfiles(ctx, hostIDsFromHosts(append(darwinHosts, unenrolledHost, linuxHost)...), nil, nil, nil)
require.NoError(t, err)
assertHostProfiles(map[*fleet.Host][]anyProfile{
darwinHosts[0]: {
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: newTm1Profiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: newTm1Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
darwinHosts[1]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: globalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: tm2Profiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: globalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: tm2Profiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
darwinHosts[2]: {
{ProfileID: globalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileID: newGlobalProfiles[0].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[2].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: globalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
{ProfileUUID: newGlobalProfiles[0].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[2].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
unenrolledHost: {},
linuxHost: {},
windowsHosts[0]: {
{ProfileID: newTm1Profiles[3].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newTm1Profiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[3].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newTm1Profiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
windowsHosts[1]: {
{ProfileID: tm2Profiles[1].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: tm2Profiles[1].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
windowsHosts[2]: {
{ProfileID: newGlobalProfiles[4].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[5].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[6].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileID: newGlobalProfiles[7].ProfileID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[4].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[5].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[6].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
{ProfileUUID: newGlobalProfiles[7].ProfileUUID, Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall},
},
})
}

View file

@ -1341,7 +1341,7 @@ func (ds *Datastore) bulkDeleteMDMWindowsHostsConfigProfilesDB(
}
func (ds *Datastore) NewMDMWindowsConfigProfile(ctx context.Context, cp fleet.MDMWindowsConfigProfile) (*fleet.MDMWindowsConfigProfile, error) {
profileUUID := uuid.New().String()
profileUUID := "w" + uuid.New().String()
stmt := `
INSERT INTO
mdm_windows_configuration_profiles (profile_uuid, team_id, name, syncml)
@ -1470,7 +1470,8 @@ INSERT INTO
profile_uuid, team_id, name, syncml
)
VALUES
( UUID(), ?, ?, ? )
-- see https://stackoverflow.com/a/51393124/1094941
( CONCAT('w', CONVERT(UUID() USING utf8mb4)), ?, ?, ? )
ON DUPLICATE KEY UPDATE
name = VALUES(name),
syncml = VALUES(syncml)

View file

@ -526,7 +526,7 @@ func testMDMWindowsDiskEncryption(t *testing.T, ds *Datastore) {
HostUUID: hosts[5].UUID,
ProfileIdentifier: mobileconfig.FleetFileVaultPayloadIdentifier,
ProfileName: "Disk encryption",
ProfileID: 1,
ProfileUUID: "a" + uuid.NewString(),
CommandUUID: uuid.New().String(),
OperationType: fleet.MDMOperationTypeInstall,
Status: &fleet.MDMDeliveryFailed,
@ -1741,6 +1741,7 @@ func testMDMWindowsConfigProfiles(t *testing.T, ds *Datastore) {
profC, err := ds.NewMDMAppleConfigProfile(ctx, *generateCP("c", "c", 0))
require.NoError(t, err)
require.NotZero(t, profC.ProfileID)
require.NotEmpty(t, profC.ProfileUUID)
// create the same name for team 1 as Windows profile
profATm, err := ds.NewMDMWindowsConfigProfile(ctx, fleet.MDMWindowsConfigProfile{Name: "a", TeamID: ptr.Uint(1), SyncML: []byte("<Replace></Replace>")})
@ -1752,6 +1753,7 @@ func testMDMWindowsConfigProfiles(t *testing.T, ds *Datastore) {
profBTm, err := ds.NewMDMAppleConfigProfile(ctx, *generateCP("b", "b", 1))
require.NoError(t, err)
require.NotZero(t, profBTm.ProfileID)
require.NotEmpty(t, profBTm.ProfileUUID)
var existsErr *existsError
// create a duplicate of Windows for no-team

View file

@ -0,0 +1,130 @@
package tables
import (
"database/sql"
"fmt"
)
func init() {
MigrationClient.AddMigration(Up_20231204155427, Down_20231204155427)
}
func Up_20231204155427(tx *sql.Tx) error {
// update the windows profiles tables to use a 37-char uuid column for
// the 'w' prefix.
_, err := tx.Exec(`
ALTER TABLE host_mdm_windows_profiles
CHANGE COLUMN profile_uuid profile_uuid VARCHAR(37) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT ''
`)
if err != nil {
return fmt.Errorf("failed to alter host_mdm_windows_profiles table: %w", err)
}
_, err = tx.Exec(`
ALTER TABLE mdm_windows_configuration_profiles
CHANGE COLUMN profile_uuid profile_uuid VARCHAR(37) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT ''
`)
if err != nil {
return fmt.Errorf("failed to alter mdm_windows_configuration_profiles table: %w", err)
}
// add the 'w' prefix to the windows profiles table
_, err = tx.Exec(`
UPDATE
mdm_windows_configuration_profiles
SET
profile_uuid = CONCAT('w', profile_uuid)
`)
if err != nil {
return fmt.Errorf("failed to update mdm_windows_configuration_profiles table: %w", err)
}
_, err = tx.Exec(`
UPDATE
host_mdm_windows_profiles
SET
profile_uuid = CONCAT('w', profile_uuid)
`)
if err != nil {
return fmt.Errorf("failed to update host_mdm_windows_profiles table: %w", err)
}
// update the apple profiles table to add the profile_uuid column and
// temporarily drop the primary key until we fill those uuids.
_, err = tx.Exec(`
ALTER TABLE mdm_apple_configuration_profiles
-- 37 and not 36 because the UUID will be prefixed with 'a' to indicate
-- that it's an Apple profile.
ADD COLUMN profile_uuid VARCHAR(37) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
-- auto-increment column must have an index, so we create one before
-- dropping the primary key to make it profile_uuid later.
ADD UNIQUE KEY idx_mdm_apple_config_prof_id (profile_id),
DROP PRIMARY KEY
`)
if err != nil {
return fmt.Errorf("failed to alter mdm_apple_configuration_profiles table: %w", err)
}
// generate the uuids for the apple profiles table
_, err = tx.Exec(`
UPDATE
mdm_apple_configuration_profiles
SET
-- see https://stackoverflow.com/a/51393124/1094941
profile_uuid = CONCAT('a', CONVERT(uuid() USING utf8mb4))
`)
if err != nil {
return fmt.Errorf("failed to update mdm_apple_configuration_profiles table: %w", err)
}
// set the profile uuid as the new primary key
_, err = tx.Exec(`
ALTER TABLE mdm_apple_configuration_profiles
ADD PRIMARY KEY (profile_uuid)`)
if err != nil {
return fmt.Errorf("failed to set primary key of mdm_apple_configuration_profiles table: %w", err)
}
// add the profile_uuid column to the host apple profiles table, keeping the
// old id for now. Cannot be set as primary key yet as it may have duplicates
// until we generate the uuids.
_, err = tx.Exec(`
ALTER TABLE host_mdm_apple_profiles
DROP PRIMARY KEY,
ADD COLUMN profile_uuid VARCHAR(37) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT ''
`)
if err != nil {
return fmt.Errorf("failed to alter host_mdm_apple_profiles table: %w", err)
}
// update the apple host profiles table's profile_uuid based on its profile_id
_, err = tx.Exec(`
UPDATE
host_mdm_apple_profiles
SET
profile_uuid = COALESCE((
SELECT
macp.profile_uuid
FROM
mdm_apple_configuration_profiles macp
WHERE
host_mdm_apple_profiles.profile_id = macp.profile_id
-- see https://stackoverflow.com/a/51393124/1094941
), CONCAT('a', CONVERT(uuid() USING utf8mb4)))
`)
if err != nil {
return fmt.Errorf("failed to update host_mdm_apple_profiles table: %w", err)
}
// drop the now unused profile_id column from the host apple profiles table
_, err = tx.Exec(`ALTER TABLE host_mdm_apple_profiles
ADD PRIMARY KEY (host_uuid, profile_uuid),
DROP COLUMN profile_id`)
if err != nil {
return fmt.Errorf("failed to drop column from host_mdm_apple_profiles table: %w", err)
}
return nil
}
func Down_20231204155427(tx *sql.Tx) error {
return nil
}

View file

@ -0,0 +1,142 @@
package tables
import (
"testing"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/require"
)
func TestUp_20231204155427(t *testing.T) {
db := applyUpToPrev(t)
// create some Windows profiles
idwA, idwB, idwC := uuid.New().String(), uuid.New().String(), uuid.New().String()
execNoErr(t, db, `INSERT INTO mdm_windows_configuration_profiles (profile_uuid, team_id, name, syncml) VALUES (?, 0, 'A', '<Replace>A</Replace>')`, idwA)
execNoErr(t, db, `INSERT INTO mdm_windows_configuration_profiles (profile_uuid, team_id, name, syncml) VALUES (?, 1, 'B', '<Replace>B</Replace>')`, idwB)
execNoErr(t, db, `INSERT INTO mdm_windows_configuration_profiles (profile_uuid, team_id, name, syncml) VALUES (?, 0, 'C', '<Replace>C</Replace>')`, idwC)
nonExistingWID := uuid.New().String()
// create some Windows hosts profiles with one not related to an existing profile
execNoErr(t, db, `INSERT INTO host_mdm_windows_profiles (host_uuid, profile_uuid, command_uuid) VALUES ('h1', ?, 'c1')`, idwA)
execNoErr(t, db, `INSERT INTO host_mdm_windows_profiles (host_uuid, profile_uuid, command_uuid) VALUES ('h2', ?, 'c2')`, idwB)
execNoErr(t, db, `INSERT INTO host_mdm_windows_profiles (host_uuid, profile_uuid, command_uuid) VALUES ('h2', ?, 'c3')`, nonExistingWID)
execNoErr(t, db, `INSERT INTO host_mdm_windows_profiles (host_uuid, profile_uuid, command_uuid) VALUES ('h2', ?, 'c4')`, idwA)
// create some Apple profiles
idaA := execNoErrLastID(t, db, `INSERT INTO mdm_apple_configuration_profiles (team_id, identifier, name, mobileconfig, checksum) VALUES (0, 'IA', 'NA', '<plist></plist>', '')`)
idaB := execNoErrLastID(t, db, `INSERT INTO mdm_apple_configuration_profiles (team_id, identifier, name, mobileconfig, checksum) VALUES (1, 'IB', 'NB', '<plist></plist>', '')`)
idaC := execNoErrLastID(t, db, `INSERT INTO mdm_apple_configuration_profiles (team_id, identifier, name, mobileconfig, checksum) VALUES (0, 'IC', 'NC', '<plist></plist>', '')`)
nonExistingAID := idaC + 1000
// create some Apple hosts profiles with one not related to an existing profile
execNoErr(t, db, `INSERT INTO host_mdm_apple_profiles (host_uuid, profile_id, command_uuid, profile_identifier, checksum) VALUES ('h1', ?, 'c1', 'IA', '')`, idaA)
execNoErr(t, db, `INSERT INTO host_mdm_apple_profiles (host_uuid, profile_id, command_uuid, profile_identifier, checksum) VALUES ('h2', ?, 'c2', 'IB', '')`, idaB)
execNoErr(t, db, `INSERT INTO host_mdm_apple_profiles (host_uuid, profile_id, command_uuid, profile_identifier, checksum) VALUES ('h2', ?, 'c3', 'IZ', '')`, nonExistingAID)
execNoErr(t, db, `INSERT INTO host_mdm_apple_profiles (host_uuid, profile_id, command_uuid, profile_identifier, checksum) VALUES ('h2', ?, 'c4', 'IA', '')`, idaA)
// Apply current migration.
applyNext(t, db)
// Windows profile uuids were updated with the prefix
var wprofUUIDs []string
err := sqlx.Select(db, &wprofUUIDs, `SELECT profile_uuid FROM mdm_windows_configuration_profiles ORDER BY name`)
require.NoError(t, err)
require.Len(t, wprofUUIDs, 3)
require.Equal(t, "w"+idwA, wprofUUIDs[0])
require.Equal(t, "w"+idwB, wprofUUIDs[1])
require.Equal(t, "w"+idwC, wprofUUIDs[2])
// Apple profiles were assigned uuids in addition to identifier
var aprofUUIDs []string
err = sqlx.Select(db, &aprofUUIDs, `SELECT profile_uuid FROM mdm_apple_configuration_profiles ORDER BY name`)
require.NoError(t, err)
require.Len(t, aprofUUIDs, 3)
require.Len(t, aprofUUIDs[0], 37)
require.Len(t, aprofUUIDs[1], 37)
require.Len(t, aprofUUIDs[2], 37)
require.Equal(t, "a", string(aprofUUIDs[0][0]))
require.Equal(t, "a", string(aprofUUIDs[1][0]))
require.Equal(t, "a", string(aprofUUIDs[2][0]))
var hostUUIDs []string
// get Windows hosts with profile A
err = sqlx.Select(db, &hostUUIDs, `SELECT host_uuid FROM host_mdm_windows_profiles WHERE profile_uuid = ? ORDER BY host_uuid`, wprofUUIDs[0])
require.NoError(t, err)
require.Equal(t, []string{"h1", "h2"}, hostUUIDs)
// get Windows hosts with profile B
hostUUIDs = hostUUIDs[:0]
err = sqlx.Select(db, &hostUUIDs, `SELECT host_uuid FROM host_mdm_windows_profiles WHERE profile_uuid = ? ORDER BY host_uuid`, wprofUUIDs[1])
require.NoError(t, err)
require.Equal(t, []string{"h2"}, hostUUIDs)
// get Windows hosts with profile C
hostUUIDs = hostUUIDs[:0]
err = sqlx.Select(db, &hostUUIDs, `SELECT host_uuid FROM host_mdm_windows_profiles WHERE profile_uuid = ? ORDER BY host_uuid`, wprofUUIDs[2])
require.NoError(t, err)
require.Empty(t, hostUUIDs)
// get Windows hosts with unknown profile
hostUUIDs = hostUUIDs[:0]
err = sqlx.Select(db, &hostUUIDs, `SELECT host_uuid FROM host_mdm_windows_profiles WHERE profile_uuid = ? ORDER BY host_uuid`, "w"+nonExistingWID)
require.NoError(t, err)
require.Equal(t, []string{"h2"}, hostUUIDs)
// get profile uuid of non-existing profile
var nonExistingProfUUIDs []string
err = sqlx.Select(db, &nonExistingProfUUIDs, `SELECT profile_uuid FROM host_mdm_windows_profiles WHERE command_uuid = 'c3' ORDER BY profile_uuid`)
require.NoError(t, err)
require.Len(t, nonExistingProfUUIDs, 1)
require.Len(t, nonExistingProfUUIDs[0], 37)
require.Equal(t, "w", string(nonExistingProfUUIDs[0][0]))
// get Apple hosts with profile NA
hostUUIDs = hostUUIDs[:0]
err = sqlx.Select(db, &hostUUIDs, `SELECT host_uuid FROM host_mdm_apple_profiles WHERE profile_uuid = ? ORDER BY host_uuid`, aprofUUIDs[0])
require.NoError(t, err)
require.Equal(t, []string{"h1", "h2"}, hostUUIDs)
// get Apple hosts with profile NB
hostUUIDs = hostUUIDs[:0]
err = sqlx.Select(db, &hostUUIDs, `SELECT host_uuid FROM host_mdm_apple_profiles WHERE profile_uuid = ? ORDER BY host_uuid`, aprofUUIDs[1])
require.NoError(t, err)
require.Equal(t, []string{"h2"}, hostUUIDs)
// get Apple hosts with profile C
hostUUIDs = hostUUIDs[:0]
err = sqlx.Select(db, &hostUUIDs, `SELECT host_uuid FROM host_mdm_apple_profiles WHERE profile_uuid = ? ORDER BY host_uuid`, aprofUUIDs[2])
require.NoError(t, err)
require.Empty(t, hostUUIDs)
// get Apple hosts with unknown profile, it was assigned an apple uuid
hostUUIDs = hostUUIDs[:0]
err = sqlx.Select(db, &hostUUIDs, `SELECT host_uuid FROM host_mdm_apple_profiles WHERE profile_identifier = 'IZ' ORDER BY host_uuid`)
require.NoError(t, err)
require.Equal(t, []string{"h2"}, hostUUIDs)
nonExistingProfUUIDs = nonExistingProfUUIDs[:0]
err = sqlx.Select(db, &nonExistingProfUUIDs, `SELECT profile_uuid FROM host_mdm_apple_profiles WHERE profile_identifier = 'IZ' ORDER BY host_uuid`)
require.NoError(t, err)
require.Len(t, nonExistingProfUUIDs, 1)
require.Len(t, nonExistingProfUUIDs[0], 37)
require.Equal(t, "a", string(nonExistingProfUUIDs[0][0]))
// creating a new Apple profile still generates a unique numerical id
idaD := execNoErrLastID(t, db, `INSERT INTO mdm_apple_configuration_profiles (profile_uuid, team_id, identifier, name, mobileconfig, checksum) VALUES (CONCAT('a', CONVERT(uuid() USING utf8mb4)), 0, 'ID', 'ND', '<plist></plist>', '')`)
require.NotZero(t, idaD)
require.Greater(t, idaD, idaC)
// batch-creating new Apple profiles also generates unique numerical ids
execNoErr(t, db, `INSERT INTO mdm_apple_configuration_profiles
(profile_uuid, team_id, identifier, name, mobileconfig, checksum)
VALUES
(CONCAT('a', CONVERT(uuid() USING utf8mb4)), 0, 'IE', 'NE', '<plist></plist>', ''),
(CONCAT('a', CONVERT(uuid() USING utf8mb4)), 0, 'IF', 'NF', '<plist></plist>', ''),
(CONCAT('a', CONVERT(uuid() USING utf8mb4)), 0, 'IG', 'NG', '<plist></plist>', '')
`)
var profIDs []int64
err = sqlx.Select(db, &profIDs, `SELECT profile_id FROM mdm_apple_configuration_profiles ORDER BY name`)
require.NoError(t, err)
require.Equal(t, []int64{idaA, idaB, idaC, idaD, idaD + 1, idaD + 2, idaD + 3}, profIDs)
}

File diff suppressed because one or more lines are too long

View file

@ -13,6 +13,7 @@ import (
_ "github.com/doug-martin/goqu/v9/dialect/mysql"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/go-kit/kit/log/level"
"github.com/jmoiron/sqlx"
)
@ -1286,6 +1287,65 @@ func (ds *Datastore) SyncHostsSoftware(ctx context.Context, updatedAt time.Time)
return nil
}
func (ds *Datastore) ReconcileSoftwareTitles(ctx context.Context) error {
// TODO: consider if we should batch writes to software or software_titles table
// ensure all software titles are in the software_titles table
upsertTitlesStmt := `
INSERT INTO software_titles (name, source)
SELECT DISTINCT
name,
source
FROM
software s
WHERE
NOT EXISTS (SELECT 1 FROM software_titles st WHERE (s.name, s.source) = (st.name, st.source))
ON DUPLICATE KEY UPDATE software_titles.id = software_titles.id`
// TODO: consider the impact of on duplicate key update vs. risk of insert ignore
// or performing a select first to see if the title exists and only inserting
// new titles
res, err := ds.writer(ctx).ExecContext(ctx, upsertTitlesStmt)
if err != nil {
return ctxerr.Wrap(ctx, err, "upsert software titles")
}
n, _ := res.RowsAffected()
level.Debug(ds.logger).Log("msg", "upsert software titles", "rows_affected", n)
// update title ids for software table entries
updateSoftwareStmt := `
UPDATE
software s,
software_titles st
SET
s.title_id = st.id
WHERE
(s.name, s.source) = (st.name, st.source)
AND (s.title_id IS NULL OR s.title_id != st.id)`
res, err = ds.writer(ctx).ExecContext(ctx, updateSoftwareStmt)
if err != nil {
return ctxerr.Wrap(ctx, err, "update software titles")
}
n, _ = res.RowsAffected()
level.Debug(ds.logger).Log("msg", "update software titles", "rows_affected", n)
// clean up orphaned software titles
cleanupStmt := `
DELETE st FROM software_titles st
LEFT JOIN software s ON s.title_id = st.id
WHERE s.title_id IS NULL`
res, err = ds.writer(ctx).ExecContext(ctx, cleanupStmt)
if err != nil {
return ctxerr.Wrap(ctx, err, "cleanup orphaned software titles")
}
n, _ = res.RowsAffected()
level.Debug(ds.logger).Log("msg", "cleanup orphaned software titles", "rows_affected", n)
return nil
}
func (ds *Datastore) HostVulnSummariesBySoftwareIDs(ctx context.Context, softwareIDs []uint) ([]fleet.HostVulnerabilitySummary, error) {
stmt := `
SELECT DISTINCT

View file

@ -2578,3 +2578,203 @@ func testInsertHostSoftwareInstalledPaths(t *testing.T, ds *Datastore) {
require.ElementsMatch(t, actual, toInsert)
}
func TestReconcileSoftwareTitles(t *testing.T) {
ds := CreateMySQLDS(t)
ctx := context.Background()
host1 := test.NewHost(t, ds, "host1", "", "host1key", "host1uuid", time.Now())
host2 := test.NewHost(t, ds, "host2", "", "host2key", "host2uuid", time.Now())
host3 := test.NewHost(t, ds, "host3", "", "host3key", "host3uuid", time.Now())
expectedSoftware := []fleet.Software{
{Name: "foo", Version: "0.0.1", Source: "chrome_extensions"},
{Name: "foo", Version: "v0.0.2", Source: "chrome_extensions"},
{Name: "foo", Version: "0.0.3", Source: "chrome_extensions"},
{Name: "bar", Version: "0.0.3", Source: "deb_packages"},
{Name: "baz", Version: "0.0.1", Source: "deb_packages"},
}
software1 := []fleet.Software{expectedSoftware[0], expectedSoftware[2]}
software2 := []fleet.Software{expectedSoftware[1], expectedSoftware[2], expectedSoftware[3]}
software3 := []fleet.Software{expectedSoftware[4]}
_, err := ds.UpdateHostSoftware(ctx, host1.ID, software1)
require.NoError(t, err)
_, err = ds.UpdateHostSoftware(ctx, host2.ID, software2)
require.NoError(t, err)
_, err = ds.UpdateHostSoftware(ctx, host3.ID, software3)
require.NoError(t, err)
getSoftware := func() ([]fleet.Software, error) {
var sw []fleet.Software
err := ds.writer(ctx).SelectContext(ctx, &sw, `SELECT * FROM software ORDER BY name, version`)
if err != nil {
return nil, err
}
return sw, nil
}
getTitles := func() ([]fleet.SoftwareTitle, error) {
var swt []fleet.SoftwareTitle
err := ds.writer(ctx).SelectContext(ctx, &swt, `SELECT * FROM software_titles ORDER BY name, source`)
if err != nil {
return nil, err
}
return swt, nil
}
expectedTitlesByNS := map[string]fleet.SoftwareTitle{}
assertSoftware := func(t *testing.T, wantSoftware []fleet.Software, wantNilTitleID []fleet.Software) {
gotSoftware, err := getSoftware()
require.NoError(t, err)
require.Len(t, gotSoftware, len(wantSoftware))
byNSV := map[string]fleet.Software{}
for _, s := range wantSoftware {
byNSV[s.Name+s.Source+s.Version] = s
}
for _, r := range gotSoftware {
_, ok := byNSV[r.Name+r.Source+r.Version]
require.True(t, ok)
if r.TitleID == nil {
var found bool
for _, s := range wantNilTitleID {
if s.Name == r.Name && s.Source == r.Source && s.Version == r.Version {
found = true
break
}
}
require.True(t, found)
} else {
require.NotNil(t, r.TitleID)
swt, ok := expectedTitlesByNS[r.Name+r.Source]
require.True(t, ok)
require.NotNil(t, r.TitleID)
require.Equal(t, swt.ID, *r.TitleID)
require.Equal(t, swt.Name, r.Name)
require.Equal(t, swt.Source, r.Source)
}
}
}
assertTitles := func(t *testing.T, gotTitles []fleet.SoftwareTitle, expectMissing []string) {
for _, r := range gotTitles {
if len(expectMissing) > 0 {
require.NotContains(t, expectMissing, r.Name)
}
e, ok := expectedTitlesByNS[r.Name+r.Source]
require.True(t, ok)
require.Equal(t, e.ID, r.ID)
require.Equal(t, e.Name, r.Name)
require.Equal(t, e.Source, r.Source)
}
}
// title_id is initially nil for all software entries
assertSoftware(t, expectedSoftware, expectedSoftware)
// reconcile software titles
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
swt, err := getTitles()
require.NoError(t, err)
require.Len(t, swt, 3)
require.Equal(t, swt[0].Name, "bar")
require.Equal(t, swt[0].Source, "deb_packages")
expectedTitlesByNS[swt[0].Name+swt[0].Source] = swt[0]
require.Equal(t, swt[1].Name, "baz")
require.Equal(t, swt[1].Source, "deb_packages")
expectedTitlesByNS[swt[1].Name+swt[1].Source] = swt[1]
require.Equal(t, swt[2].Name, "foo")
require.Equal(t, swt[2].Source, "chrome_extensions")
expectedTitlesByNS[swt[2].Name+swt[2].Source] = swt[2]
// title_id is now populated for all software entries
assertSoftware(t, expectedSoftware, nil)
// remove the bar software title from host 2
_, err = ds.UpdateHostSoftware(context.Background(), host2.ID, software2[:2])
require.NoError(t, err)
assertSoftware(t, []fleet.Software{expectedSoftware[0], expectedSoftware[1], expectedSoftware[2], expectedSoftware[4]}, nil)
// bar is no longer associated with any host so the title should be deleted
require.NoError(t, ds.ReconcileSoftwareTitles(context.Background()))
gotTitles, err := getTitles()
require.NoError(t, err)
require.Len(t, gotTitles, 2)
assertTitles(t, gotTitles, []string{"bar"})
// add bar to host 3
_, err = ds.UpdateHostSoftware(context.Background(), host3.ID, []fleet.Software{expectedSoftware[3], expectedSoftware[4]})
require.NoError(t, err)
require.NoError(t, ds.SyncHostsSoftware(context.Background(), time.Now()))
// title_id is initially nil for new software entries
assertSoftware(t, expectedSoftware, []fleet.Software{expectedSoftware[3]})
// bar isn't added back to software titles until we reconcile software titles
gotTitles, err = getTitles()
require.NoError(t, err)
require.Len(t, gotTitles, 2)
assertTitles(t, gotTitles, []string{"bar"})
// reconcile software titles
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
gotTitles, err = getTitles()
require.NoError(t, err)
require.Len(t, gotTitles, 3)
// bar was added back to software titles with a new ID
require.Equal(t, gotTitles[0].Name, "bar")
require.Equal(t, gotTitles[0].Source, "deb_packages")
require.NotEqual(t, expectedTitlesByNS[gotTitles[0].Name+gotTitles[0].Source], gotTitles[0].ID)
expectedTitlesByNS[gotTitles[0].Name+gotTitles[0].Source] = gotTitles[0]
assertTitles(t, gotTitles, nil)
// title_id is now populated for bar
assertSoftware(t, expectedSoftware, nil)
// add a new version of foo to host 3
expectedSoftware = append(expectedSoftware, fleet.Software{Name: "foo", Version: "0.0.4", Source: "chrome_extensions"})
_, err = ds.UpdateHostSoftware(ctx, host3.ID, expectedSoftware[3:])
require.NoError(t, err)
// title_id is initially nil for new software entries
assertSoftware(t, expectedSoftware, []fleet.Software{expectedSoftware[5]})
// new version of foo doesn't result in a new software title entry
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
gotTitles, err = getTitles()
require.NoError(t, err)
require.Len(t, gotTitles, 3)
assertTitles(t, gotTitles, nil)
// title_id is now populated for new version of foo
assertSoftware(t, expectedSoftware, nil)
// add a new source of foo to host 3
expectedSoftware = append(expectedSoftware, fleet.Software{Name: "foo", Version: "0.0.4", Source: "rpm_packages"})
_, err = ds.UpdateHostSoftware(ctx, host3.ID, expectedSoftware[3:])
require.NoError(t, err)
// title_id is initially nil for new software entries
assertSoftware(t, expectedSoftware, []fleet.Software{expectedSoftware[6]})
// new source of foo results in a new software title entry
require.NoError(t, ds.ReconcileSoftwareTitles(ctx))
gotTitles, err = getTitles()
require.NoError(t, err)
require.Len(t, gotTitles, 4)
require.Equal(t, gotTitles[3].Name, "foo")
require.Equal(t, gotTitles[3].Source, "rpm_packages")
expectedTitlesByNS[gotTitles[3].Name+gotTitles[3].Source] = gotTitles[3]
assertTitles(t, gotTitles, nil)
// title_id is now populated for new source of foo
assertSoftware(t, expectedSoftware, nil)
}

View file

@ -86,12 +86,12 @@ func testTeamsGetSetDelete(t *testing.T, ds *Datastore) {
cp, err := ds.NewMDMAppleConfigProfile(context.Background(), dummyCP)
require.NoError(t, err)
// TODO: once the datastore methods are implemented, use them in tests.
ExecAdhocSQL(t, ds, func(tx sqlx.ExtContext) error {
_, err := tx.ExecContext(context.Background(),
`INSERT INTO mdm_windows_configuration_profiles (profile_uuid, name, team_id, syncml) VALUES (uuid(), 'abc', ?, ?)`, team.ID, "<SyncML></SyncML>")
return err
wcp, err := ds.NewMDMWindowsConfigProfile(context.Background(), fleet.MDMWindowsConfigProfile{
Name: "abc",
TeamID: &team.ID,
SyncML: []byte(`<Replace></Replace>`),
})
require.NoError(t, err)
err = ds.DeleteTeam(context.Background(), team.ID)
require.NoError(t, err)
@ -103,17 +103,12 @@ func testTeamsGetSetDelete(t *testing.T, ds *Datastore) {
_, err = ds.TeamByName(context.Background(), tt.name)
require.Error(t, err)
_, err = ds.GetMDMAppleConfigProfile(context.Background(), cp.ProfileID)
_, err = ds.GetMDMAppleConfigProfile(context.Background(), cp.ProfileUUID)
var nfe fleet.NotFoundError
require.ErrorAs(t, err, &nfe)
// TODO: once the datastore methods are implemented, use them in tests.
var count int
ExecAdhocSQL(t, ds, func(tx sqlx.ExtContext) error {
return sqlx.GetContext(context.Background(), tx, &count,
`SELECT count(*) FROM mdm_windows_configuration_profiles WHERE team_id = ?`, team.ID)
})
require.Zero(t, count)
_, err = ds.GetMDMWindowsConfigProfile(context.Background(), wcp.ProfileUUID)
require.ErrorAs(t, err, &nfe)
require.NoError(t, ds.DeletePack(context.Background(), newP.Name))
})

View file

@ -427,7 +427,7 @@ func generateDummyWindowsProfile(uuid string) []byte {
// TODO(roberto): update when we have datastore functions and API methods for this
func InsertWindowsProfileForTest(t *testing.T, ds *Datastore, teamID uint) string {
profUUID := uuid.NewString()
profUUID := "w" + uuid.NewString()
prof := generateDummyWindowsProfile(profUUID)
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
stmt := `INSERT INTO mdm_windows_configuration_profiles (profile_uuid, team_id, name, syncml) VALUES (?, ?, ?, ?);`

View file

@ -175,7 +175,12 @@ func (e MDMAppleCommandTimeoutError) StatusCode() int {
// Configuration profiles are used to configure Apple devices .
// See also https://developer.apple.com/documentation/devicemanagement/configuring_multiple_devices_using_profiles.
type MDMAppleConfigProfile struct {
// ProfileID is the unique id of the configuration profile in Fleet
// ProfileUUID is the unique identifier of the configuration profile in
// Fleet. For Apple profiles, it is the letter "a" followed by a uuid.
ProfileUUID string `db:"profile_uuid" json:"profile_uuid"`
// Deprecated: ProfileID is the old unique id of the configuration profile in
// Fleet. It is still maintained and generated for new profiles, but only
// used in legacy API endpoints.
ProfileID uint `db:"profile_id" json:"profile_id"`
// TeamID is the id of the team with which the configuration is associated. A nil team id
// represents a configuration profile that is not associated with any team.
@ -227,7 +232,7 @@ func (cp MDMAppleConfigProfile) ValidateUserProvided() error {
type HostMDMAppleProfile struct {
HostUUID string `db:"host_uuid" json:"-"`
CommandUUID string `db:"command_uuid" json:"-"`
ProfileID uint `db:"profile_id" json:"profile_id"`
ProfileUUID string `db:"profile_uuid" json:"profile_uuid"`
Name string `db:"name" json:"name"`
Identifier string `db:"identifier" json:"-"`
Status *MDMDeliveryStatus `db:"status" json:"status"`
@ -239,7 +244,7 @@ type HostMDMAppleProfile struct {
func (p HostMDMAppleProfile) ToHostMDMProfile() HostMDMProfile {
return HostMDMProfile{
HostUUID: p.HostUUID,
ProfileID: p.ProfileID,
ProfileUUID: p.ProfileUUID,
Name: p.Name,
Identifier: p.Identifier,
Status: p.Status,
@ -280,7 +285,7 @@ func (d HostMDMProfileDetail) Message() string {
}
type MDMAppleProfilePayload struct {
ProfileID uint `db:"profile_id"`
ProfileUUID string `db:"profile_uuid"`
ProfileIdentifier string `db:"profile_identifier"`
ProfileName string `db:"profile_name"`
HostUUID string `db:"host_uuid"`
@ -292,7 +297,7 @@ type MDMAppleProfilePayload struct {
}
type MDMAppleBulkUpsertHostProfilePayload struct {
ProfileID uint
ProfileUUID string
ProfileIdentifier string
ProfileName string
HostUUID string

View file

@ -461,6 +461,13 @@ type Datastore interface {
// After aggregation, it cleans up unused software (e.g. software installed
// on removed hosts, software uninstalled on hosts, etc.)
SyncHostsSoftware(ctx context.Context, updatedAt time.Time) error
// ReconcileSoftwareTitles ensures the software_titles and software tables are in sync.
// It inserts new software titles and updates the software table with the title_id.
// It also cleans up any software titles that are no longer associated with any software.
// It is intended to be run after SyncHostsSoftware.
ReconcileSoftwareTitles(ctx context.Context) error
// HostVulnSummariesBySoftwareIDs returns a list of all hosts that have at least one of the
// specified Software installed. Includes the path were the software was installed.
HostVulnSummariesBySoftwareIDs(ctx context.Context, softwareIDs []uint) ([]HostVulnerabilitySummary, error)
@ -803,17 +810,25 @@ type Datastore interface {
// this is mainly aimed to internal usage within the Fleet server.
BulkUpsertMDMAppleConfigProfiles(ctx context.Context, payload []*MDMAppleConfigProfile) error
// GetMDMAppleConfigProfileByDeprecatedID returns the mdm config profile
// corresponding to the specified numeric profile id. This is deprecated and
// should not be used for new endpoints.
GetMDMAppleConfigProfileByDeprecatedID(ctx context.Context, profileID uint) (*MDMAppleConfigProfile, error)
// GetMDMAppleConfigProfile returns the mdm config profile corresponding to the specified
// profile id.
GetMDMAppleConfigProfile(ctx context.Context, profileID uint) (*MDMAppleConfigProfile, error)
// profile uuid.
GetMDMAppleConfigProfile(ctx context.Context, profileUUID string) (*MDMAppleConfigProfile, error)
// ListMDMAppleConfigProfiles lists mdm config profiles associated with the specified team id.
// For global config profiles, specify nil as the team id.
ListMDMAppleConfigProfiles(ctx context.Context, teamID *uint) ([]*MDMAppleConfigProfile, error)
// DeleteMDMAppleConfigProfileByDeprecatedID deletes the mdm config profile
// corresponding to the specified numeric profile id. This is deprecated and
// should not be used for new endpoints.
DeleteMDMAppleConfigProfileByDeprecatedID(ctx context.Context, profileID uint) error
// DeleteMDMAppleConfigProfile deletes the mdm config profile corresponding
// to the specified profile id.
DeleteMDMAppleConfigProfile(ctx context.Context, profileID uint) error
// to the specified profile uuid.
DeleteMDMAppleConfigProfile(ctx context.Context, profileUUID string) error
BulkDeleteMDMAppleHostsConfigProfiles(ctx context.Context, payload []*MDMAppleProfilePayload) error
@ -929,15 +944,15 @@ type Datastore interface {
// status of a profile in a host.
BulkUpsertMDMAppleHostProfiles(ctx context.Context, payload []*MDMAppleBulkUpsertHostProfilePayload) error
// BulkSetPendingMDMHostProfiles sets the status of profiles to install
// or to remove for each affected host to pending for the provided criteria,
// which may be either a list of hostIDs, teamIDs, profileIDs or hostUUIDs
// (only one of those ID types can be provided).
BulkSetPendingMDMHostProfiles(ctx context.Context, hostIDs, teamIDs, profileIDs []uint, profileUUIDs, hostUUIDs []string) error
// BulkSetPendingMDMHostProfiles sets the status of profiles to install or to
// remove for each affected host to pending for the provided criteria, which
// may be either a list of hostIDs, teamIDs, profileUUIDs or hostUUIDs (only
// one of those ID types can be provided).
BulkSetPendingMDMHostProfiles(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string) error
// GetMDMAppleProfilesContents retrieves the XML contents of the
// profiles requested.
GetMDMAppleProfilesContents(ctx context.Context, profileIDs []uint) (map[uint]mobileconfig.Mobileconfig, error)
GetMDMAppleProfilesContents(ctx context.Context, profileUUIDs []string) (map[string]mobileconfig.Mobileconfig, error)
// UpdateOrDeleteHostMDMAppleProfile updates information about a single
// profile status. It deletes the row if the profile operation is "remove"
@ -1123,7 +1138,7 @@ type Datastore interface {
// ListMDMWindowsProfilesToRemove returns all the profiles that should
// be removed based on diffing the ideal state vs the state we have
// registered in `host_mdm_apple_profiles`
// registered in `host_mdm_windows_profiles`
ListMDMWindowsProfilesToRemove(ctx context.Context) ([]*MDMWindowsProfilePayload, error)
// BulkUpsertMDMWindowsHostProfiles bulk-adds/updates records to track the

View file

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"net/url"
"strconv"
"time"
)
@ -268,11 +267,11 @@ type MDMProfilesSummary struct {
}
// HostMDMProfile is the status of an MDM profile on a host. It can be used to represent either
// a Windows or macOS profile. The ProfileID field is a string for Windows and an integer for macOS.
// a Windows or macOS profile.
type HostMDMProfile struct {
HostUUID string `db:"-" json:"-"`
CommandUUID string `db:"-" json:"-"`
ProfileID interface{} `db:"-" json:"profile_id"`
ProfileUUID string `db:"-" json:"profile_uuid"`
Name string `db:"-" json:"name"`
Identifier string `db:"-" json:"-"`
Status *MDMDeliveryStatus `db:"-" json:"status"`
@ -352,14 +351,14 @@ func (m MDMConfigProfileAuthz) AuthzType() string {
// MDMConfigProfilePayload is the platform-agnostic struct returned by
// endpoints that return MDM configuration profiles (get/list profiles).
type MDMConfigProfilePayload struct {
ProfileID string `json:"profile_id" db:"profile_id"` // is a uuid string for Windows
TeamID *uint `json:"team_id" db:"team_id"` // null for no-team
Name string `json:"name" db:"name"`
Platform string `json:"platform" db:"platform"` // "windows" or "darwin"
Identifier string `json:"identifier,omitempty" db:"identifier"` // only set for macOS
Checksum []byte `json:"checksum,omitempty" db:"checksum"` // only set for macOS
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
ProfileUUID string `json:"profile_uuid" db:"profile_uuid"`
TeamID *uint `json:"team_id" db:"team_id"` // null for no-team
Name string `json:"name" db:"name"`
Platform string `json:"platform" db:"platform"` // "windows" or "darwin"
Identifier string `json:"identifier,omitempty" db:"identifier"` // only set for macOS
Checksum []byte `json:"checksum,omitempty" db:"checksum"` // only set for macOS
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
func NewMDMConfigProfilePayloadFromWindows(cp *MDMWindowsConfigProfile) *MDMConfigProfilePayload {
@ -368,12 +367,12 @@ func NewMDMConfigProfilePayloadFromWindows(cp *MDMWindowsConfigProfile) *MDMConf
tid = cp.TeamID
}
return &MDMConfigProfilePayload{
ProfileID: cp.ProfileUUID,
TeamID: tid,
Name: cp.Name,
Platform: "windows",
CreatedAt: cp.CreatedAt,
UpdatedAt: cp.UpdatedAt,
ProfileUUID: cp.ProfileUUID,
TeamID: tid,
Name: cp.Name,
Platform: "windows",
CreatedAt: cp.CreatedAt,
UpdatedAt: cp.UpdatedAt,
}
}
@ -383,13 +382,13 @@ func NewMDMConfigProfilePayloadFromApple(cp *MDMAppleConfigProfile) *MDMConfigPr
tid = cp.TeamID
}
return &MDMConfigProfilePayload{
ProfileID: strconv.FormatUint(uint64(cp.ProfileID), 10),
TeamID: tid,
Name: cp.Name,
Identifier: cp.Identifier,
Platform: "darwin",
Checksum: cp.Checksum,
CreatedAt: cp.CreatedAt,
UpdatedAt: cp.UpdatedAt,
ProfileUUID: cp.ProfileUUID,
TeamID: tid,
Name: cp.Name,
Identifier: cp.Identifier,
Platform: "darwin",
Checksum: cp.Checksum,
CreatedAt: cp.CreatedAt,
UpdatedAt: cp.UpdatedAt,
}
}

View file

@ -1394,7 +1394,7 @@ type HostMDMWindowsProfile struct {
func (p HostMDMWindowsProfile) ToHostMDMProfile() HostMDMProfile {
return HostMDMProfile{
HostUUID: p.HostUUID,
ProfileID: p.ProfileUUID,
ProfileUUID: p.ProfileUUID,
Name: p.Name,
Identifier: "",
Status: p.Status,

View file

@ -600,10 +600,18 @@ type Service interface {
// NewMDMAppleConfigProfile creates a new configuration profile for the specified team.
NewMDMAppleConfigProfile(ctx context.Context, teamID uint, r io.Reader) (*MDMAppleConfigProfile, error)
// GetMDMAppleConfigProfileByDeprecatedID retrieves the specified Apple
// configuration profile via its numeric ID. This method is deprecated and
// should not be used for new endpoints.
GetMDMAppleConfigProfileByDeprecatedID(ctx context.Context, profileID uint) (*MDMAppleConfigProfile, error)
// GetMDMAppleConfigProfile retrieves the specified configuration profile.
GetMDMAppleConfigProfile(ctx context.Context, profileID uint) (*MDMAppleConfigProfile, error)
GetMDMAppleConfigProfile(ctx context.Context, profileUUID string) (*MDMAppleConfigProfile, error)
// DeleteMDMAppleConfigProfileByDeprecatedID deletes the specified Apple
// configuration profile via its numeric ID. This method is deprecated and
// should not be used for new endpoints.
DeleteMDMAppleConfigProfileByDeprecatedID(ctx context.Context, profileID uint) error
// DeleteMDMAppleConfigProfile deletes the specified configuration profile.
DeleteMDMAppleConfigProfile(ctx context.Context, profileID uint) error
DeleteMDMAppleConfigProfile(ctx context.Context, profileUUID string) error
// ListMDMAppleConfigProfiles returns the list of all the configuration profiles for the
// specified team.
ListMDMAppleConfigProfiles(ctx context.Context, teamID uint) ([]*MDMAppleConfigProfile, error)

View file

@ -102,6 +102,14 @@ func (s Software) ToUniqueStr() string {
return strings.Join(ss, SoftwareFieldSeparator)
}
type SoftwareTitle struct {
ID uint `json:"id" db:"id"`
// Name is the name reported by osquery.
Name string `json:"name" db:"name"`
// Source is the source reported by osquery.
Source string `json:"source" db:"source"`
}
// AuthzSoftwareInventory is used for access controls on software inventory.
type AuthzSoftwareInventory struct {
// TeamID is the ID of the team. A value of nil means global scope.

View file

@ -29,6 +29,8 @@ type MDMWindowsBitLockerSummary struct {
// MDMWindowsConfigProfile represents a Windows MDM profile in Fleet.
type MDMWindowsConfigProfile struct {
// ProfileUUID is the unique identifier of the configuration profile in
// Fleet. For Windows profiles, it is the letter "w" followed by a uuid.
ProfileUUID string `db:"profile_uuid" json:"profile_uuid"`
TeamID *uint `db:"team_id" json:"team_id"`
Name string `db:"name" json:"name"`

View file

@ -346,6 +346,8 @@ type ListSoftwareByHostIDShortFunc func(ctx context.Context, hostID uint) ([]fle
type SyncHostsSoftwareFunc func(ctx context.Context, updatedAt time.Time) error
type ReconcileSoftwareTitlesFunc func(ctx context.Context) error
type HostVulnSummariesBySoftwareIDsFunc func(ctx context.Context, softwareIDs []uint) ([]fleet.HostVulnerabilitySummary, error)
type HostsByCVEFunc func(ctx context.Context, cve string) ([]fleet.HostVulnerabilitySummary, error)
@ -548,11 +550,15 @@ type NewMDMAppleConfigProfileFunc func(ctx context.Context, p fleet.MDMAppleConf
type BulkUpsertMDMAppleConfigProfilesFunc func(ctx context.Context, payload []*fleet.MDMAppleConfigProfile) error
type GetMDMAppleConfigProfileFunc func(ctx context.Context, profileID uint) (*fleet.MDMAppleConfigProfile, error)
type GetMDMAppleConfigProfileByDeprecatedIDFunc func(ctx context.Context, profileID uint) (*fleet.MDMAppleConfigProfile, error)
type GetMDMAppleConfigProfileFunc func(ctx context.Context, profileUUID string) (*fleet.MDMAppleConfigProfile, error)
type ListMDMAppleConfigProfilesFunc func(ctx context.Context, teamID *uint) ([]*fleet.MDMAppleConfigProfile, error)
type DeleteMDMAppleConfigProfileFunc func(ctx context.Context, profileID uint) error
type DeleteMDMAppleConfigProfileByDeprecatedIDFunc func(ctx context.Context, profileID uint) error
type DeleteMDMAppleConfigProfileFunc func(ctx context.Context, profileUUID string) error
type BulkDeleteMDMAppleHostsConfigProfilesFunc func(ctx context.Context, payload []*fleet.MDMAppleProfilePayload) error
@ -618,9 +624,9 @@ type ListMDMAppleProfilesToRemoveFunc func(ctx context.Context) ([]*fleet.MDMApp
type BulkUpsertMDMAppleHostProfilesFunc func(ctx context.Context, payload []*fleet.MDMAppleBulkUpsertHostProfilePayload) error
type BulkSetPendingMDMHostProfilesFunc func(ctx context.Context, hostIDs []uint, teamIDs []uint, profileIDs []uint, profileUUIDs []string, hostUUIDs []string) error
type BulkSetPendingMDMHostProfilesFunc func(ctx context.Context, hostIDs []uint, teamIDs []uint, profileUUIDs []string, hostUUIDs []string) error
type GetMDMAppleProfilesContentsFunc func(ctx context.Context, profileIDs []uint) (map[uint]mobileconfig.Mobileconfig, error)
type GetMDMAppleProfilesContentsFunc func(ctx context.Context, profileUUIDs []string) (map[string]mobileconfig.Mobileconfig, error)
type UpdateOrDeleteHostMDMAppleProfileFunc func(ctx context.Context, profile *fleet.HostMDMAppleProfile) error
@ -1251,6 +1257,9 @@ type DataStore struct {
SyncHostsSoftwareFunc SyncHostsSoftwareFunc
SyncHostsSoftwareFuncInvoked bool
ReconcileSoftwareTitlesFunc ReconcileSoftwareTitlesFunc
ReconcileSoftwareTitlesFuncInvoked bool
HostVulnSummariesBySoftwareIDsFunc HostVulnSummariesBySoftwareIDsFunc
HostVulnSummariesBySoftwareIDsFuncInvoked bool
@ -1554,12 +1563,18 @@ type DataStore struct {
BulkUpsertMDMAppleConfigProfilesFunc BulkUpsertMDMAppleConfigProfilesFunc
BulkUpsertMDMAppleConfigProfilesFuncInvoked bool
GetMDMAppleConfigProfileByDeprecatedIDFunc GetMDMAppleConfigProfileByDeprecatedIDFunc
GetMDMAppleConfigProfileByDeprecatedIDFuncInvoked bool
GetMDMAppleConfigProfileFunc GetMDMAppleConfigProfileFunc
GetMDMAppleConfigProfileFuncInvoked bool
ListMDMAppleConfigProfilesFunc ListMDMAppleConfigProfilesFunc
ListMDMAppleConfigProfilesFuncInvoked bool
DeleteMDMAppleConfigProfileByDeprecatedIDFunc DeleteMDMAppleConfigProfileByDeprecatedIDFunc
DeleteMDMAppleConfigProfileByDeprecatedIDFuncInvoked bool
DeleteMDMAppleConfigProfileFunc DeleteMDMAppleConfigProfileFunc
DeleteMDMAppleConfigProfileFuncInvoked bool
@ -3020,6 +3035,13 @@ func (s *DataStore) SyncHostsSoftware(ctx context.Context, updatedAt time.Time)
return s.SyncHostsSoftwareFunc(ctx, updatedAt)
}
func (s *DataStore) ReconcileSoftwareTitles(ctx context.Context) error {
s.mu.Lock()
s.ReconcileSoftwareTitlesFuncInvoked = true
s.mu.Unlock()
return s.ReconcileSoftwareTitlesFunc(ctx)
}
func (s *DataStore) HostVulnSummariesBySoftwareIDs(ctx context.Context, softwareIDs []uint) ([]fleet.HostVulnerabilitySummary, error) {
s.mu.Lock()
s.HostVulnSummariesBySoftwareIDsFuncInvoked = true
@ -3727,11 +3749,18 @@ func (s *DataStore) BulkUpsertMDMAppleConfigProfiles(ctx context.Context, payloa
return s.BulkUpsertMDMAppleConfigProfilesFunc(ctx, payload)
}
func (s *DataStore) GetMDMAppleConfigProfile(ctx context.Context, profileID uint) (*fleet.MDMAppleConfigProfile, error) {
func (s *DataStore) GetMDMAppleConfigProfileByDeprecatedID(ctx context.Context, profileID uint) (*fleet.MDMAppleConfigProfile, error) {
s.mu.Lock()
s.GetMDMAppleConfigProfileByDeprecatedIDFuncInvoked = true
s.mu.Unlock()
return s.GetMDMAppleConfigProfileByDeprecatedIDFunc(ctx, profileID)
}
func (s *DataStore) GetMDMAppleConfigProfile(ctx context.Context, profileUUID string) (*fleet.MDMAppleConfigProfile, error) {
s.mu.Lock()
s.GetMDMAppleConfigProfileFuncInvoked = true
s.mu.Unlock()
return s.GetMDMAppleConfigProfileFunc(ctx, profileID)
return s.GetMDMAppleConfigProfileFunc(ctx, profileUUID)
}
func (s *DataStore) ListMDMAppleConfigProfiles(ctx context.Context, teamID *uint) ([]*fleet.MDMAppleConfigProfile, error) {
@ -3741,11 +3770,18 @@ func (s *DataStore) ListMDMAppleConfigProfiles(ctx context.Context, teamID *uint
return s.ListMDMAppleConfigProfilesFunc(ctx, teamID)
}
func (s *DataStore) DeleteMDMAppleConfigProfile(ctx context.Context, profileID uint) error {
func (s *DataStore) DeleteMDMAppleConfigProfileByDeprecatedID(ctx context.Context, profileID uint) error {
s.mu.Lock()
s.DeleteMDMAppleConfigProfileByDeprecatedIDFuncInvoked = true
s.mu.Unlock()
return s.DeleteMDMAppleConfigProfileByDeprecatedIDFunc(ctx, profileID)
}
func (s *DataStore) DeleteMDMAppleConfigProfile(ctx context.Context, profileUUID string) error {
s.mu.Lock()
s.DeleteMDMAppleConfigProfileFuncInvoked = true
s.mu.Unlock()
return s.DeleteMDMAppleConfigProfileFunc(ctx, profileID)
return s.DeleteMDMAppleConfigProfileFunc(ctx, profileUUID)
}
func (s *DataStore) BulkDeleteMDMAppleHostsConfigProfiles(ctx context.Context, payload []*fleet.MDMAppleProfilePayload) error {
@ -3972,18 +4008,18 @@ func (s *DataStore) BulkUpsertMDMAppleHostProfiles(ctx context.Context, payload
return s.BulkUpsertMDMAppleHostProfilesFunc(ctx, payload)
}
func (s *DataStore) BulkSetPendingMDMHostProfiles(ctx context.Context, hostIDs []uint, teamIDs []uint, profileIDs []uint, profileUUIDs []string, hostUUIDs []string) error {
func (s *DataStore) BulkSetPendingMDMHostProfiles(ctx context.Context, hostIDs []uint, teamIDs []uint, profileUUIDs []string, hostUUIDs []string) error {
s.mu.Lock()
s.BulkSetPendingMDMHostProfilesFuncInvoked = true
s.mu.Unlock()
return s.BulkSetPendingMDMHostProfilesFunc(ctx, hostIDs, teamIDs, profileIDs, profileUUIDs, hostUUIDs)
return s.BulkSetPendingMDMHostProfilesFunc(ctx, hostIDs, teamIDs, profileUUIDs, hostUUIDs)
}
func (s *DataStore) GetMDMAppleProfilesContents(ctx context.Context, profileIDs []uint) (map[uint]mobileconfig.Mobileconfig, error) {
func (s *DataStore) GetMDMAppleProfilesContents(ctx context.Context, profileUUIDs []string) (map[string]mobileconfig.Mobileconfig, error) {
s.mu.Lock()
s.GetMDMAppleProfilesContentsFuncInvoked = true
s.mu.Unlock()
return s.GetMDMAppleProfilesContentsFunc(ctx, profileIDs)
return s.GetMDMAppleProfilesContentsFunc(ctx, profileUUIDs)
}
func (s *DataStore) UpdateOrDeleteHostMDMAppleProfile(ctx context.Context, profile *fleet.HostMDMAppleProfile) error {

View file

@ -356,7 +356,7 @@ func (svc *Service) NewMDMAppleConfigProfile(ctx context.Context, teamID uint, r
}
return nil, ctxerr.Wrap(ctx, err)
}
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, []uint{newCP.ProfileID}, nil, nil); err != nil {
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, []string{newCP.ProfileUUID}, nil); err != nil {
return nil, ctxerr.Wrap(ctx, err, "bulk set pending host profiles")
}
@ -461,7 +461,7 @@ func (r getMDMAppleConfigProfileResponse) hijackRender(ctx context.Context, w ht
func getMDMAppleConfigProfileEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
req := request.(*getMDMAppleConfigProfileRequest)
cp, err := svc.GetMDMAppleConfigProfile(ctx, req.ProfileID)
cp, err := svc.GetMDMAppleConfigProfileByDeprecatedID(ctx, req.ProfileID)
if err != nil {
return getMDMAppleConfigProfileResponse{Err: err}, nil
}
@ -471,13 +471,31 @@ func getMDMAppleConfigProfileEndpoint(ctx context.Context, request interface{},
return getMDMAppleConfigProfileResponse{fileReader: io.NopCloser(reader), fileLength: reader.Size(), fileName: fileName}, nil
}
func (svc *Service) GetMDMAppleConfigProfile(ctx context.Context, profileID uint) (*fleet.MDMAppleConfigProfile, error) {
func (svc *Service) GetMDMAppleConfigProfileByDeprecatedID(ctx context.Context, profileID uint) (*fleet.MDMAppleConfigProfile, error) {
// first we perform a perform basic authz check
if err := svc.authz.Authorize(ctx, &fleet.Team{}, fleet.ActionRead); err != nil {
return nil, err
}
cp, err := svc.ds.GetMDMAppleConfigProfile(ctx, profileID)
cp, err := svc.ds.GetMDMAppleConfigProfileByDeprecatedID(ctx, profileID)
if err != nil {
if fleet.IsNotFound(err) {
// call the standard service method with a profile UUID that will not be
// found, just to ensure the same sequence of validations are applied.
return svc.GetMDMAppleConfigProfile(ctx, "-")
}
return nil, ctxerr.Wrap(ctx, err)
}
return svc.GetMDMAppleConfigProfile(ctx, cp.ProfileUUID)
}
func (svc *Service) GetMDMAppleConfigProfile(ctx context.Context, profileUUID string) (*fleet.MDMAppleConfigProfile, error) {
// first we perform a perform basic authz check
if err := svc.authz.Authorize(ctx, &fleet.Team{}, fleet.ActionRead); err != nil {
return nil, err
}
cp, err := svc.ds.GetMDMAppleConfigProfile(ctx, profileUUID)
if err != nil {
return nil, ctxerr.Wrap(ctx, err)
}
@ -503,14 +521,33 @@ func (r deleteMDMAppleConfigProfileResponse) error() error { return r.Err }
func deleteMDMAppleConfigProfileEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
req := request.(*deleteMDMAppleConfigProfileRequest)
if err := svc.DeleteMDMAppleConfigProfile(ctx, req.ProfileID); err != nil {
if err := svc.DeleteMDMAppleConfigProfileByDeprecatedID(ctx, req.ProfileID); err != nil {
return &deleteMDMAppleConfigProfileResponse{Err: err}, nil
}
return &deleteMDMAppleConfigProfileResponse{}, nil
}
func (svc *Service) DeleteMDMAppleConfigProfile(ctx context.Context, profileID uint) error {
func (svc *Service) DeleteMDMAppleConfigProfileByDeprecatedID(ctx context.Context, profileID uint) error {
// first we perform a perform basic authz check
if err := svc.authz.Authorize(ctx, &fleet.Team{}, fleet.ActionRead); err != nil {
return ctxerr.Wrap(ctx, err)
}
// get the profile by ID and call the standard delete function
cp, err := svc.ds.GetMDMAppleConfigProfileByDeprecatedID(ctx, profileID)
if err != nil {
if fleet.IsNotFound(err) {
// call the standard service method with a profile UUID that will not be
// found, just to ensure the same sequence of validations are applied.
return svc.DeleteMDMAppleConfigProfile(ctx, "-")
}
return ctxerr.Wrap(ctx, err)
}
return svc.DeleteMDMAppleConfigProfile(ctx, cp.ProfileUUID)
}
func (svc *Service) DeleteMDMAppleConfigProfile(ctx context.Context, profileUUID string) error {
// first we perform a perform basic authz check
if err := svc.authz.Authorize(ctx, &fleet.Team{}, fleet.ActionRead); err != nil {
return ctxerr.Wrap(ctx, err)
@ -519,11 +556,11 @@ func (svc *Service) DeleteMDMAppleConfigProfile(ctx context.Context, profileID u
// check that Apple MDM is enabled - the middleware of that endpoint checks
// only that any MDM is enabled, maybe it's just Windows
if err := svc.VerifyMDMAppleConfigured(ctx); err != nil {
err := fleet.NewInvalidArgumentError("profile_id", fleet.AppleMDMNotConfiguredMessage).WithStatus(http.StatusBadRequest)
err := fleet.NewInvalidArgumentError("profile_uuid", fleet.AppleMDMNotConfiguredMessage).WithStatus(http.StatusBadRequest)
return ctxerr.Wrap(ctx, err, "check macOS MDM enabled")
}
cp, err := svc.ds.GetMDMAppleConfigProfile(ctx, profileID)
cp, err := svc.ds.GetMDMAppleConfigProfile(ctx, profileUUID)
if err != nil {
return ctxerr.Wrap(ctx, err)
}
@ -551,11 +588,11 @@ func (svc *Service) DeleteMDMAppleConfigProfile(ctx context.Context, profileID u
}
}
if err := svc.ds.DeleteMDMAppleConfigProfile(ctx, profileID); err != nil {
if err := svc.ds.DeleteMDMAppleConfigProfile(ctx, profileUUID); err != nil {
return ctxerr.Wrap(ctx, err)
}
// cannot use the profile ID as it is now deleted
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{teamID}, nil, nil, nil); err != nil {
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{teamID}, nil, nil); err != nil {
return ctxerr.Wrap(ctx, err, "bulk set pending host profiles")
}
@ -1495,7 +1532,7 @@ func (svc *Service) BatchSetMDMAppleProfiles(ctx context.Context, tmID *uint, tm
}
if !skipBulkPending {
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{bulkTeamID}, nil, nil, nil); err != nil {
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{bulkTeamID}, nil, nil); err != nil {
return ctxerr.Wrap(ctx, err, "bulk set pending host profiles")
}
}
@ -2169,7 +2206,7 @@ func (svc *MDMAppleCheckinAndCommandService) Authenticate(r *mdm.Request, m *mdm
return svc.ds.NewActivity(r.Context, nil, &fleet.ActivityTypeMDMEnrolled{
HostSerial: info.HardwareSerial,
HostDisplayName: info.DisplayName,
InstalledFromDEP: info.InstalledFromDEP,
InstalledFromDEP: info.DEPAssignedToFleet,
MDMPlatform: fleet.MDMPlatformApple,
})
}
@ -2187,7 +2224,7 @@ func (svc *MDMAppleCheckinAndCommandService) TokenUpdate(r *mdm.Request, m *mdm.
if nanoEnroll != nil && nanoEnroll.Enabled &&
nanoEnroll.Type == "Device" && nanoEnroll.TokenUpdateTally == 1 {
// device is enrolled for the first time, not a token update
if err := svc.ds.BulkSetPendingMDMHostProfiles(r.Context, nil, nil, nil, nil, []string{r.ID}); err != nil {
if err := svc.ds.BulkSetPendingMDMHostProfiles(r.Context, nil, nil, nil, []string{r.ID}); err != nil {
return err
}
@ -2442,11 +2479,11 @@ func ReconcileAppleProfiles(
// Perform aggregations to support all the operations we need to do
// toGetContents contains the IDs of all the profiles from which we
// toGetContents contains the UUIDs of all the profiles from which we
// need to retrieve contents. Since the previous query returns one row
// per host, it would be too expensive to retrieve the profile contents
// there, so we make another request. Using a map to deduplicate.
toGetContents := make(map[uint]bool)
toGetContents := make(map[string]bool)
// hostProfiles tracks each host_mdm_apple_profile we need to upsert
// with the new status, operation_type, etc.
@ -2468,7 +2505,7 @@ func ReconcileAppleProfiles(
// command.
hostProfilesToCleanup := []*fleet.MDMAppleProfilePayload{}
// install/removeTargets are maps from profileID -> command uuid and host
// install/removeTargets are maps from profileUUID -> command uuid and host
// UUIDs as the underlying MDM services are optimized to send one command to
// multiple hosts at the same time. Note that the same command uuid is used
// for all hosts in a given install/remove target operation.
@ -2477,7 +2514,7 @@ func ReconcileAppleProfiles(
profIdent string
hostUUIDs []string
}
installTargets, removeTargets := make(map[uint]*cmdTarget), make(map[uint]*cmdTarget)
installTargets, removeTargets := make(map[string]*cmdTarget), make(map[string]*cmdTarget)
for _, p := range toInstall {
if pp, ok := profileIntersection.GetMatchingProfileInCurrentState(p); ok {
// if the profile was in any other status than `failed`
@ -2486,7 +2523,7 @@ func ReconcileAppleProfiles(
// command.
if pp.Status != &fleet.MDMDeliveryFailed && bytes.Equal(pp.Checksum, p.Checksum) {
hostProfiles = append(hostProfiles, &fleet.MDMAppleBulkUpsertHostProfilePayload{
ProfileID: p.ProfileID,
ProfileUUID: p.ProfileUUID,
HostUUID: p.HostUUID,
ProfileIdentifier: p.ProfileIdentifier,
ProfileName: p.ProfileName,
@ -2499,20 +2536,20 @@ func ReconcileAppleProfiles(
continue
}
}
toGetContents[p.ProfileID] = true
toGetContents[p.ProfileUUID] = true
target := installTargets[p.ProfileID]
target := installTargets[p.ProfileUUID]
if target == nil {
target = &cmdTarget{
cmdUUID: uuid.New().String(),
profIdent: p.ProfileIdentifier,
}
installTargets[p.ProfileID] = target
installTargets[p.ProfileUUID] = target
}
target.hostUUIDs = append(target.hostUUIDs, p.HostUUID)
hostProfiles = append(hostProfiles, &fleet.MDMAppleBulkUpsertHostProfilePayload{
ProfileID: p.ProfileID,
ProfileUUID: p.ProfileUUID,
HostUUID: p.HostUUID,
OperationType: fleet.MDMOperationTypeInstall,
Status: &fleet.MDMDeliveryPending,
@ -2529,18 +2566,18 @@ func ReconcileAppleProfiles(
continue
}
target := removeTargets[p.ProfileID]
target := removeTargets[p.ProfileUUID]
if target == nil {
target = &cmdTarget{
cmdUUID: uuid.New().String(),
profIdent: p.ProfileIdentifier,
}
removeTargets[p.ProfileID] = target
removeTargets[p.ProfileUUID] = target
}
target.hostUUIDs = append(target.hostUUIDs, p.HostUUID)
hostProfiles = append(hostProfiles, &fleet.MDMAppleBulkUpsertHostProfilePayload{
ProfileID: p.ProfileID,
ProfileUUID: p.ProfileUUID,
HostUUID: p.HostUUID,
OperationType: fleet.MDMOperationTypeRemove,
Status: &fleet.MDMDeliveryPending,
@ -2571,11 +2608,11 @@ func ReconcileAppleProfiles(
}
// Grab the contents of all the profiles we need to install
profileIDs := make([]uint, 0, len(toGetContents))
for pid := range toGetContents {
profileIDs = append(profileIDs, pid)
profileUUIDs := make([]string, 0, len(toGetContents))
for pUUID := range toGetContents {
profileUUIDs = append(profileUUIDs, pUUID)
}
profileContents, err := ds.GetMDMAppleProfilesContents(ctx, profileIDs)
profileContents, err := ds.GetMDMAppleProfilesContents(ctx, profileUUIDs)
if err != nil {
return ctxerr.Wrap(ctx, err, "get profile contents")
}
@ -2589,13 +2626,13 @@ func ReconcileAppleProfiles(
var wgProd, wgCons sync.WaitGroup
ch := make(chan remoteResult)
execCmd := func(profID uint, target *cmdTarget, op fleet.MDMOperationType) {
execCmd := func(profUUID string, target *cmdTarget, op fleet.MDMOperationType) {
defer wgProd.Done()
var err error
switch op {
case fleet.MDMOperationTypeInstall:
err = commander.InstallProfile(ctx, target.hostUUIDs, profileContents[profID], target.cmdUUID)
err = commander.InstallProfile(ctx, target.hostUUIDs, profileContents[profUUID], target.cmdUUID)
case fleet.MDMOperationTypeRemove:
err = commander.RemoveProfile(ctx, target.hostUUIDs, target.profIdent, target.cmdUUID)
}
@ -2609,13 +2646,13 @@ func ReconcileAppleProfiles(
ch <- remoteResult{err, target.cmdUUID}
}
}
for profID, target := range installTargets {
for profUUID, target := range installTargets {
wgProd.Add(1)
go execCmd(profID, target, fleet.MDMOperationTypeInstall)
go execCmd(profUUID, target, fleet.MDMOperationTypeInstall)
}
for profID, target := range removeTargets {
for profUUID, target := range removeTargets {
wgProd.Add(1)
go execCmd(profID, target, fleet.MDMOperationTypeRemove)
go execCmd(profUUID, target, fleet.MDMOperationTypeRemove)
}
// index the host profiles by cmdUUID, for ease of error processing in the

View file

@ -467,6 +467,7 @@ func TestAppleMDMAuthorization(t *testing.T) {
func TestMDMAppleConfigProfileAuthz(t *testing.T) {
svc, ctx, ds := setupAppleMDMService(t, &fleet.LicenseInfo{Tier: fleet.TierPremium})
profUUID := "a" + uuid.NewString()
testCases := []struct {
name string
user *fleet.User
@ -547,18 +548,18 @@ func TestMDMAppleConfigProfileAuthz(t *testing.T) {
ds.GetMDMAppleProfilesSummaryFunc = func(context.Context, *uint) (*fleet.MDMProfilesSummary, error) {
return nil, nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids, pids []uint, puuids, uuids []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids []uint, puuids, uuids []string) error {
return nil
}
mockGetFuncWithTeamID := func(teamID uint) mock.GetMDMAppleConfigProfileFunc {
return func(ctx context.Context, profileID uint) (*fleet.MDMAppleConfigProfile, error) {
require.Equal(t, uint(42), profileID)
return func(ctx context.Context, puid string) (*fleet.MDMAppleConfigProfile, error) {
require.Equal(t, profUUID, puid)
return &fleet.MDMAppleConfigProfile{TeamID: &teamID}, nil
}
}
mockDeleteFuncWithTeamID := func(teamID uint) mock.DeleteMDMAppleConfigProfileFunc {
return func(ctx context.Context, profileID uint) error {
require.Equal(t, uint(42), profileID)
return func(ctx context.Context, puid string) error {
require.Equal(t, profUUID, puid)
return nil
}
}
@ -609,22 +610,22 @@ func TestMDMAppleConfigProfileAuthz(t *testing.T) {
// test authz get config profile (no team)
ds.GetMDMAppleConfigProfileFunc = mockGetFuncWithTeamID(0)
_, err = svc.GetMDMAppleConfigProfile(ctx, 42)
_, err = svc.GetMDMAppleConfigProfile(ctx, profUUID)
checkShouldFail(err, tt.shouldFailGlobal)
// test authz delete config profile (no team)
ds.DeleteMDMAppleConfigProfileFunc = mockDeleteFuncWithTeamID(0)
err = svc.DeleteMDMAppleConfigProfile(ctx, 42)
err = svc.DeleteMDMAppleConfigProfile(ctx, profUUID)
checkShouldFail(err, tt.shouldFailGlobal)
// test authz get config profile (team 1)
ds.GetMDMAppleConfigProfileFunc = mockGetFuncWithTeamID(1)
_, err = svc.GetMDMAppleConfigProfile(ctx, 42)
_, err = svc.GetMDMAppleConfigProfile(ctx, profUUID)
checkShouldFail(err, tt.shouldFailTeam)
// test authz delete config profile (team 1)
ds.DeleteMDMAppleConfigProfileFunc = mockDeleteFuncWithTeamID(1)
err = svc.DeleteMDMAppleConfigProfile(ctx, 42)
err = svc.DeleteMDMAppleConfigProfile(ctx, profUUID)
checkShouldFail(err, tt.shouldFailTeam)
// test authz get profiles summary (no team)
@ -649,13 +650,12 @@ func TestNewMDMAppleConfigProfile(t *testing.T) {
require.Equal(t, "Foo", cp.Name)
require.Equal(t, "Bar", cp.Identifier)
require.Equal(t, mcBytes, []byte(cp.Mobileconfig))
cp.ProfileID = 1
return &cp, nil
}
ds.NewActivityFunc = func(context.Context, *fleet.User, fleet.ActivityDetails) error {
return nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids, pids []uint, puuids, uuids []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids []uint, puuids, uuids []string) error {
return nil
}
@ -693,13 +693,9 @@ func TestHostDetailsMDMProfiles(t *testing.T) {
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}})
expected := []fleet.HostMDMAppleProfile{
{HostUUID: "H057-UU1D-1337", Name: "NAME-5", ProfileID: uint(5), CommandUUID: "CMD-UU1D-5", Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall, Detail: ""},
{HostUUID: "H057-UU1D-1337", Name: "NAME-9", ProfileID: uint(8), CommandUUID: "CMD-UU1D-8", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall, Detail: ""},
{HostUUID: "H057-UU1D-1337", Name: "NAME-13", ProfileID: uint(13), CommandUUID: "CMD-UU1D-13", Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove, Detail: "Error removing profile"},
}
expectedByProfileID := make(map[uint]fleet.HostMDMAppleProfile)
for _, ep := range expected {
expectedByProfileID[ep.ProfileID] = ep
{HostUUID: "H057-UU1D-1337", Name: "NAME-5", ProfileUUID: "a" + uuid.NewString(), CommandUUID: "CMD-UU1D-5", Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeInstall, Detail: ""},
{HostUUID: "H057-UU1D-1337", Name: "NAME-9", ProfileUUID: "a" + uuid.NewString(), CommandUUID: "CMD-UU1D-8", Status: &fleet.MDMDeliveryVerifying, OperationType: fleet.MDMOperationTypeInstall, Detail: ""},
{HostUUID: "H057-UU1D-1337", Name: "NAME-13", ProfileUUID: "a" + uuid.NewString(), CommandUUID: "CMD-UU1D-13", Status: &fleet.MDMDeliveryFailed, OperationType: fleet.MDMOperationTypeRemove, Detail: "Error removing profile"},
}
ds.GetHostMDMAppleProfilesFunc = func(ctx context.Context, hostUUID string) ([]fleet.HostMDMAppleProfile, error) {
@ -1057,7 +1053,7 @@ func TestMDMTokenUpdate(t *testing.T) {
}, nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids, pids []uint, puuids, uuids []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids []uint, puuids, uuids []string) error {
return nil
}
@ -1300,7 +1296,7 @@ func TestMDMBatchSetAppleProfiles(t *testing.T) {
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
return nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids, pids []uint, puuids, uuids []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids []uint, puuids, uuids []string) error {
return nil
}
@ -1611,7 +1607,7 @@ func TestMDMBatchSetAppleProfilesBoolArgs(t *testing.T) {
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
return nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids, pids []uint, profileUUIDs, uuids []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids []uint, profileUUIDs, uuids []string) error {
return nil
}
@ -2051,29 +2047,30 @@ func TestMDMAppleReconcileAppleProfiles(t *testing.T) {
contents4 := []byte("test-content-4")
contents4Base64 := base64.StdEncoding.EncodeToString(contents4)
p1, p2, p3, p4 := "a"+uuid.NewString(), "a"+uuid.NewString(), "a"+uuid.NewString(), "a"+uuid.NewString()
ds.ListMDMAppleProfilesToInstallFunc = func(ctx context.Context) ([]*fleet.MDMAppleProfilePayload, error) {
return []*fleet.MDMAppleProfilePayload{
{ProfileID: 1, ProfileIdentifier: "com.add.profile", HostUUID: hostUUID},
{ProfileID: 2, ProfileIdentifier: "com.add.profile.two", HostUUID: hostUUID},
{ProfileID: 2, ProfileIdentifier: "com.add.profile.two", HostUUID: hostUUID2},
{ProfileID: 4, ProfileIdentifier: "com.add.profile.four", HostUUID: hostUUID2},
{ProfileUUID: p1, ProfileIdentifier: "com.add.profile", HostUUID: hostUUID},
{ProfileUUID: p2, ProfileIdentifier: "com.add.profile.two", HostUUID: hostUUID},
{ProfileUUID: p2, ProfileIdentifier: "com.add.profile.two", HostUUID: hostUUID2},
{ProfileUUID: p4, ProfileIdentifier: "com.add.profile.four", HostUUID: hostUUID2},
}, nil
}
ds.ListMDMAppleProfilesToRemoveFunc = func(ctx context.Context) ([]*fleet.MDMAppleProfilePayload, error) {
return []*fleet.MDMAppleProfilePayload{
{ProfileID: 3, ProfileIdentifier: "com.remove.profile", HostUUID: hostUUID},
{ProfileID: 3, ProfileIdentifier: "com.remove.profile", HostUUID: hostUUID2},
{ProfileUUID: p3, ProfileIdentifier: "com.remove.profile", HostUUID: hostUUID},
{ProfileUUID: p3, ProfileIdentifier: "com.remove.profile", HostUUID: hostUUID2},
}, nil
}
ds.GetMDMAppleProfilesContentsFunc = func(ctx context.Context, profileIDs []uint) (map[uint]mobileconfig.Mobileconfig, error) {
require.ElementsMatch(t, []uint{1, 2, 4}, profileIDs)
ds.GetMDMAppleProfilesContentsFunc = func(ctx context.Context, profileUUIDs []string) (map[string]mobileconfig.Mobileconfig, error) {
require.ElementsMatch(t, []string{p1, p2, p4}, profileUUIDs)
// only those profiles that are to be installed
return map[uint]mobileconfig.Mobileconfig{
1: contents1,
2: contents2,
4: contents4,
return map[string]mobileconfig.Mobileconfig{
p1: contents1,
p2: contents2,
p4: contents4,
}, nil
}
@ -2146,24 +2143,24 @@ func TestMDMAppleReconcileAppleProfiles(t *testing.T) {
// first time it is called, it is to set the status to pending and all
// host profiles have a command uuid
cmdUUIDByProfileIDInstall := make(map[uint]string)
cmdUUIDByProfileIDRemove := make(map[uint]string)
cmdUUIDByProfileUUIDInstall := make(map[string]string)
cmdUUIDByProfileUUIDRemove := make(map[string]string)
copies := make([]*fleet.MDMAppleBulkUpsertHostProfilePayload, len(payload))
for i, p := range payload {
if p.OperationType == fleet.MDMOperationTypeInstall {
existing, ok := cmdUUIDByProfileIDInstall[p.ProfileID]
existing, ok := cmdUUIDByProfileUUIDInstall[p.ProfileUUID]
if ok {
require.Equal(t, existing, p.CommandUUID)
} else {
cmdUUIDByProfileIDInstall[p.ProfileID] = p.CommandUUID
cmdUUIDByProfileUUIDInstall[p.ProfileUUID] = p.CommandUUID
}
} else {
require.Equal(t, fleet.MDMOperationTypeRemove, p.OperationType)
existing, ok := cmdUUIDByProfileIDRemove[p.ProfileID]
existing, ok := cmdUUIDByProfileUUIDRemove[p.ProfileUUID]
if ok {
require.Equal(t, existing, p.CommandUUID)
} else {
cmdUUIDByProfileIDRemove[p.ProfileID] = p.CommandUUID
cmdUUIDByProfileUUIDRemove[p.ProfileUUID] = p.CommandUUID
}
}
@ -2176,42 +2173,42 @@ func TestMDMAppleReconcileAppleProfiles(t *testing.T) {
require.ElementsMatch(t, []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{
ProfileID: 1,
ProfileUUID: p1,
ProfileIdentifier: "com.add.profile",
HostUUID: hostUUID,
OperationType: fleet.MDMOperationTypeInstall,
Status: &fleet.MDMDeliveryPending,
},
{
ProfileID: 2,
ProfileUUID: p2,
ProfileIdentifier: "com.add.profile.two",
HostUUID: hostUUID,
OperationType: fleet.MDMOperationTypeInstall,
Status: &fleet.MDMDeliveryPending,
},
{
ProfileID: 2,
ProfileUUID: p2,
ProfileIdentifier: "com.add.profile.two",
HostUUID: hostUUID2,
OperationType: fleet.MDMOperationTypeInstall,
Status: &fleet.MDMDeliveryPending,
},
{
ProfileID: 3,
ProfileUUID: p3,
ProfileIdentifier: "com.remove.profile",
HostUUID: hostUUID,
OperationType: fleet.MDMOperationTypeRemove,
Status: &fleet.MDMDeliveryPending,
},
{
ProfileID: 3,
ProfileUUID: p3,
ProfileIdentifier: "com.remove.profile",
HostUUID: hostUUID2,
OperationType: fleet.MDMOperationTypeRemove,
Status: &fleet.MDMDeliveryPending,
},
{
ProfileID: 4,
ProfileUUID: p4,
ProfileIdentifier: "com.add.profile.four",
HostUUID: hostUUID2,
OperationType: fleet.MDMOperationTypeInstall,
@ -2269,7 +2266,7 @@ func TestMDMAppleReconcileAppleProfiles(t *testing.T) {
require.Len(t, payload, 2) // the 2 remove ops
require.ElementsMatch(t, []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{
ProfileID: 3,
ProfileUUID: p3,
ProfileIdentifier: "com.remove.profile",
HostUUID: hostUUID,
OperationType: fleet.MDMOperationTypeRemove,
@ -2277,7 +2274,7 @@ func TestMDMAppleReconcileAppleProfiles(t *testing.T) {
CommandUUID: "",
},
{
ProfileID: 3,
ProfileUUID: p3,
ProfileIdentifier: "com.remove.profile",
HostUUID: hostUUID2,
OperationType: fleet.MDMOperationTypeRemove,
@ -2306,7 +2303,7 @@ func TestMDMAppleReconcileAppleProfiles(t *testing.T) {
require.Len(t, payload, 4) // the 4 install ops
require.ElementsMatch(t, []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{
ProfileID: 1,
ProfileUUID: p1,
ProfileIdentifier: "com.add.profile",
HostUUID: hostUUID,
OperationType: fleet.MDMOperationTypeInstall,
@ -2314,7 +2311,7 @@ func TestMDMAppleReconcileAppleProfiles(t *testing.T) {
CommandUUID: "",
},
{
ProfileID: 2,
ProfileUUID: p2,
ProfileIdentifier: "com.add.profile.two",
HostUUID: hostUUID,
OperationType: fleet.MDMOperationTypeInstall,
@ -2322,7 +2319,7 @@ func TestMDMAppleReconcileAppleProfiles(t *testing.T) {
CommandUUID: "",
},
{
ProfileID: 2,
ProfileUUID: p2,
ProfileIdentifier: "com.add.profile.two",
HostUUID: hostUUID2,
OperationType: fleet.MDMOperationTypeInstall,
@ -2330,7 +2327,7 @@ func TestMDMAppleReconcileAppleProfiles(t *testing.T) {
CommandUUID: "",
},
{
ProfileID: 4,
ProfileUUID: p4,
ProfileIdentifier: "com.add.profile.four",
HostUUID: hostUUID2,
OperationType: fleet.MDMOperationTypeInstall,

View file

@ -14,6 +14,9 @@ import (
"github.com/fleetdm/fleet/v4/server/fleet"
)
// TODO(mna): those methods are unused except for an internal tool, remove or
// migrate to new endpoints (those apple-specific endpoints are deprecated)?
func (c *Client) DeleteProfile(profileID uint) error {
verb, path := "DELETE", "/api/latest/fleet/mdm/apple/profiles/"+strconv.FormatUint(uint64(profileID), 10)
var responseBody deleteMDMAppleConfigProfileResponse

View file

@ -539,8 +539,8 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
mdmAnyMW.GET("/api/_version_/fleet/mdm/profiles/summary", getMDMProfilesSummaryEndpoint, getMDMProfilesSummaryRequest{})
mdmAnyMW.POST("/api/_version_/fleet/mdm/profiles", newMDMConfigProfileEndpoint, newMDMConfigProfileRequest{})
mdmAnyMW.GET("/api/_version_/fleet/mdm/profiles/{profile_id_or_uuid}", getMDMConfigProfileEndpoint, getMDMConfigProfileRequest{})
mdmAnyMW.DELETE("/api/_version_/fleet/mdm/profiles/{profile_id_or_uuid}", deleteMDMConfigProfileEndpoint, deleteMDMConfigProfileRequest{})
mdmAnyMW.GET("/api/_version_/fleet/mdm/profiles/{profile_uuid}", getMDMConfigProfileEndpoint, getMDMConfigProfileRequest{})
mdmAnyMW.DELETE("/api/_version_/fleet/mdm/profiles/{profile_uuid}", deleteMDMConfigProfileEndpoint, deleteMDMConfigProfileRequest{})
mdmAnyMW.GET("/api/_version_/fleet/mdm/profiles", listMDMConfigProfilesEndpoint, listMDMConfigProfilesRequest{})
// the following set of mdm endpoints must always be accessible (even

View file

@ -706,7 +706,7 @@ func (svc *Service) AddHostsToTeam(ctx context.Context, teamID *uint, hostIDs []
return err
}
if !skipBulkPending {
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, hostIDs, nil, nil, nil, nil); err != nil {
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, hostIDs, nil, nil, nil); err != nil {
return ctxerr.Wrap(ctx, err, "bulk set pending host profiles")
}
}
@ -843,7 +843,7 @@ func (svc *Service) AddHostsToTeamByFilter(ctx context.Context, teamID *uint, op
if err := svc.ds.AddHostsToTeam(ctx, teamID, hostIDs); err != nil {
return err
}
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, hostIDs, nil, nil, nil, nil); err != nil {
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, hostIDs, nil, nil, nil); err != nil {
return ctxerr.Wrap(ctx, err, "bulk set pending host profiles")
}
serials, err := svc.ds.ListMDMAppleDEPSerialsInHostIDs(ctx, hostIDs)

View file

@ -567,7 +567,7 @@ func TestHostAuth(t *testing.T) {
}
return nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids, pids []uint, puuids, uuids []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids []uint, puuids, uuids []string) error {
return nil
}
ds.ListMDMAppleDEPSerialsInHostIDsFunc = func(ctx context.Context, hids []uint) ([]string, error) {
@ -790,7 +790,7 @@ func TestAddHostsToTeamByFilter(t *testing.T) {
assert.Equal(t, expectedHostIDs, hostIDs)
return nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids, pids []uint, puuids, uuids []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids []uint, puuids, uuids []string) error {
return nil
}
ds.ListMDMAppleDEPSerialsInHostIDsFunc = func(ctx context.Context, hids []uint) ([]string, error) {
@ -825,7 +825,7 @@ func TestAddHostsToTeamByFilterLabel(t *testing.T) {
assert.Equal(t, expectedHostIDs, hostIDs)
return nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids, pids []uint, puuids, uuids []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids []uint, puuids, uuids []string) error {
return nil
}
ds.ListMDMAppleDEPSerialsInHostIDsFunc = func(ctx context.Context, hids []uint) ([]string, error) {
@ -853,7 +853,7 @@ func TestAddHostsToTeamByFilterEmptyHosts(t *testing.T) {
ds.AddHostsToTeamFunc = func(ctx context.Context, teamID *uint, hostIDs []uint) error {
return nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids, pids []uint, puuids, uuids []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids []uint, puuids, uuids []string) error {
return nil
}

View file

@ -2196,6 +2196,31 @@ func (s *integrationMDMTestSuite) TestDEPProfileAssignment() {
require.NoError(t, mdmDevice.Enroll())
checkPostEnrollmentCommands(mdmDevice, true)
// The user unenrolls from Fleet (e.g. was DEP enrolled but with `is_mdm_removable: true`
// so the user removes the enrollment profile).
err = mdmDevice.Checkout()
require.NoError(t, err)
// Simulate a refetch where we clean up the MDM data since the host is not enrolled anymore
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
_, err := q.ExecContext(ctx, `DELETE FROM host_mdm WHERE host_id = ?`, mdmDeviceID)
return err
})
// Simulate fleetd re-enrolling automatically.
err = mdmDevice.Enroll()
require.NoError(t, err)
// The last activity should have `installed_from_dep=true`.
s.lastActivityMatches(
"mdm_enrolled",
fmt.Sprintf(
`{"host_serial": "%s", "host_display_name": "%s (%s)", "installed_from_dep": true, "mdm_platform": "apple"}`,
mdmDevice.SerialNumber, mdmDevice.Model, mdmDevice.SerialNumber,
),
0,
)
// enroll a host into Fleet
eHost, err := s.ds.NewHost(context.Background(), &fleet.Host{
ID: 1,
@ -2825,7 +2850,7 @@ func (s *integrationMDMTestSuite) TestMDMAppleHostDiskEncryption() {
hostCmdUUID := uuid.New().String()
err = s.ds.BulkUpsertMDMAppleHostProfiles(ctx, []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{
ProfileID: fileVaultProf.ProfileID,
ProfileUUID: fileVaultProf.ProfileUUID,
ProfileIdentifier: fileVaultProf.Identifier,
HostUUID: host.UUID,
CommandUUID: hostCmdUUID,
@ -2840,13 +2865,13 @@ func (s *integrationMDMTestSuite) TestMDMAppleHostDiskEncryption() {
err := s.ds.UpdateOrDeleteHostMDMAppleProfile(ctx, &fleet.HostMDMAppleProfile{
HostUUID: host.UUID,
CommandUUID: hostCmdUUID,
ProfileID: fileVaultProf.ProfileID,
ProfileUUID: fileVaultProf.ProfileUUID,
Status: &fleet.MDMDeliveryVerifying,
OperationType: fleet.MDMOperationTypeRemove,
})
require.NoError(t, err)
// not an error if the profile does not exist
_ = s.ds.DeleteMDMAppleConfigProfile(ctx, fileVaultProf.ProfileID)
_ = s.ds.DeleteMDMAppleConfigProfile(ctx, fileVaultProf.ProfileUUID)
})
// get that host - it should
@ -2865,7 +2890,7 @@ func (s *integrationMDMTestSuite) TestMDMAppleHostDiskEncryption() {
err = s.ds.UpdateOrDeleteHostMDMAppleProfile(ctx, &fleet.HostMDMAppleProfile{
HostUUID: host.UUID,
CommandUUID: hostCmdUUID,
ProfileID: fileVaultProf.ProfileID,
ProfileUUID: fileVaultProf.ProfileUUID,
Status: &fleet.MDMDeliveryFailed,
OperationType: fleet.MDMOperationTypeInstall,
Detail: "test error",
@ -2887,7 +2912,7 @@ func (s *integrationMDMTestSuite) TestMDMAppleHostDiskEncryption() {
err = s.ds.UpdateOrDeleteHostMDMAppleProfile(ctx, &fleet.HostMDMAppleProfile{
HostUUID: host.UUID,
CommandUUID: hostCmdUUID,
ProfileID: fileVaultProf.ProfileID,
ProfileUUID: fileVaultProf.ProfileUUID,
Status: &fleet.MDMDeliveryVerified,
OperationType: fleet.MDMOperationTypeInstall,
Detail: "",
@ -3615,7 +3640,7 @@ func (s *integrationMDMTestSuite) TestMDMAppleDiskEncryptionAggregate() {
hostCmdUUID := uuid.New().String()
err := s.ds.BulkUpsertMDMAppleHostProfiles(ctx, []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{
ProfileID: prof.ProfileID,
ProfileUUID: prof.ProfileUUID,
ProfileIdentifier: prof.Identifier,
HostUUID: host.UUID,
CommandUUID: hostCmdUUID,
@ -8338,11 +8363,12 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
var resp newMDMConfigProfileResponse
err := json.NewDecoder(res.Body).Decode(&resp)
require.NoError(t, err)
require.NotEmpty(t, resp.ProfileID)
return resp.ProfileID
require.NotEmpty(t, resp.ProfileUUID)
require.Equal(t, "a", string(resp.ProfileUUID[0]))
return resp.ProfileUUID
}
createAppleProfile := func(name, ident string, teamID uint) string {
id := assertAppleProfile(name+".mobileconfig", name, ident, teamID, http.StatusOK, "")
uid := assertAppleProfile(name+".mobileconfig", name, ident, teamID, http.StatusOK, "")
var wantJSON string
if teamID == 0 {
@ -8352,7 +8378,7 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
}
s.lastActivityOfTypeMatches(fleet.ActivityTypeCreatedMacosProfile{}.ActivityName(), wantJSON, 0)
return id
return uid
}
assertWindowsProfile := func(filename, locURI string, teamID uint, wantStatus int, wantErrMsg string) string {
@ -8373,11 +8399,12 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
var resp newMDMConfigProfileResponse
err := json.NewDecoder(res.Body).Decode(&resp)
require.NoError(t, err)
require.NotEmpty(t, resp.ProfileID)
return resp.ProfileID
require.NotEmpty(t, resp.ProfileUUID)
require.Equal(t, "w", string(resp.ProfileUUID[0]))
return resp.ProfileUUID
}
createWindowsProfile := func(name string, teamID uint) string {
id := assertWindowsProfile(name+".xml", "./Test", teamID, http.StatusOK, "")
uid := assertWindowsProfile(name+".xml", "./Test", teamID, http.StatusOK, "")
var wantJSON string
if teamID == 0 {
@ -8387,15 +8414,15 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
}
s.lastActivityOfTypeMatches(fleet.ActivityTypeCreatedWindowsProfile{}.ActivityName(), wantJSON, 0)
return id
return uid
}
// create a couple Apple profiles for no-team and team
noTeamAppleProfID := createAppleProfile("apple-global-profile", "test-global-ident", 0)
teamAppleProfID := createAppleProfile("apple-team-profile", "test-team-ident", testTeam.ID)
noTeamAppleProfUUID := createAppleProfile("apple-global-profile", "test-global-ident", 0)
teamAppleProfUUID := createAppleProfile("apple-team-profile", "test-team-ident", testTeam.ID)
// create a couple Windows profiles for no-team and team
noTeamWinProfID := createWindowsProfile("win-global-profile", 0)
teamWinProfID := createWindowsProfile("win-team-profile", testTeam.ID)
noTeamWinProfUUID := createWindowsProfile("win-global-profile", 0)
teamWinProfUUID := createWindowsProfile("win-team-profile", testTeam.ID)
// Windows profile name conflicts with Apple's for no team
assertWindowsProfile("apple-global-profile.xml", "./Test", 0, http.StatusConflict, "Couldn't upload. A configuration profile with this name already exists.")
@ -8443,14 +8470,14 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
// get the existing profiles work
expectedProfiles := []fleet.MDMConfigProfilePayload{
{ProfileID: fmt.Sprint(noTeamAppleProfID), Platform: "darwin", Name: "apple-global-profile", Identifier: "test-global-ident", TeamID: nil},
{ProfileID: fmt.Sprint(teamAppleProfID), Platform: "darwin", Name: "apple-team-profile", Identifier: "test-team-ident", TeamID: &testTeam.ID},
{ProfileID: noTeamWinProfID, Platform: "windows", Name: "win-global-profile", TeamID: nil},
{ProfileID: teamWinProfID, Platform: "windows", Name: "win-team-profile", TeamID: &testTeam.ID},
{ProfileUUID: noTeamAppleProfUUID, Platform: "darwin", Name: "apple-global-profile", Identifier: "test-global-ident", TeamID: nil},
{ProfileUUID: teamAppleProfUUID, Platform: "darwin", Name: "apple-team-profile", Identifier: "test-team-ident", TeamID: &testTeam.ID},
{ProfileUUID: noTeamWinProfUUID, Platform: "windows", Name: "win-global-profile", TeamID: nil},
{ProfileUUID: teamWinProfUUID, Platform: "windows", Name: "win-team-profile", TeamID: &testTeam.ID},
}
for _, prof := range expectedProfiles {
var getResp getMDMConfigProfileResponse
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", prof.ProfileID), nil, http.StatusOK, &getResp)
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", prof.ProfileUUID), nil, http.StatusOK, &getResp)
require.NotZero(t, getResp.CreatedAt)
require.NotZero(t, getResp.UpdatedAt)
if getResp.Platform == "darwin" {
@ -8462,7 +8489,7 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
getResp.Checksum = nil
require.Equal(t, prof, *getResp.MDMConfigProfilePayload)
resp := s.Do("GET", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", prof.ProfileID), nil, http.StatusOK, "alt", "media")
resp := s.Do("GET", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", prof.ProfileUUID), nil, http.StatusOK, "alt", "media")
require.NotZero(t, resp.ContentLength)
require.Contains(t, resp.Header.Get("Content-Disposition"), "attachment;")
if getResp.Platform == "darwin" {
@ -8479,49 +8506,45 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
var getResp getMDMConfigProfileResponse
// get an unknown Apple profile
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%d", 99999), nil, http.StatusNotFound, &getResp)
s.Do("GET", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%d", 99999), nil, http.StatusNotFound, "alt", "media")
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", "ano-such-profile"), nil, http.StatusNotFound, &getResp)
s.Do("GET", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", "ano-such-profile"), nil, http.StatusNotFound, "alt", "media")
// get an unknown Windows profile
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", "no-such-profile"), nil, http.StatusNotFound, &getResp)
s.Do("GET", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", "no-such-profile"), nil, http.StatusNotFound, "alt", "media")
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", "wno-such-profile"), nil, http.StatusNotFound, &getResp)
s.Do("GET", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", "wno-such-profile"), nil, http.StatusNotFound, "alt", "media")
var deleteResp deleteMDMConfigProfileResponse
// delete existing Apple profiles
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", noTeamAppleProfID), nil, http.StatusOK, &deleteResp)
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", teamAppleProfID), nil, http.StatusOK, &deleteResp)
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", noTeamAppleProfUUID), nil, http.StatusOK, &deleteResp)
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", teamAppleProfUUID), nil, http.StatusOK, &deleteResp)
// delete non-existing Apple profile
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%d", 99999), nil, http.StatusNotFound, &deleteResp)
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", "ano-such-profile"), nil, http.StatusNotFound, &deleteResp)
// delete existing Windows profiles
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", noTeamWinProfID), nil, http.StatusOK, &deleteResp)
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", teamWinProfID), nil, http.StatusOK, &deleteResp)
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", noTeamWinProfUUID), nil, http.StatusOK, &deleteResp)
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", teamWinProfUUID), nil, http.StatusOK, &deleteResp)
// delete non-existing Windows profile
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", "no-such-profile"), nil, http.StatusNotFound, &deleteResp)
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", "wno-such-profile"), nil, http.StatusNotFound, &deleteResp)
// trying to create/delete profiles managed by Fleet fails
for p := range mobileconfig.FleetPayloadIdentifiers() {
assertAppleProfile("foo.mobileconfig", p, p, 0, http.StatusBadRequest, fmt.Sprintf("payload identifier %s is not allowed", p))
// create it directly in the DB to test deletion
var id int64
uid := "a" + uuid.NewString()
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
mc := mcBytesForTest(p, p, uuid.New().String())
res, err := q.ExecContext(ctx,
"INSERT INTO mdm_apple_configuration_profiles (identifier, name, mobileconfig, checksum, team_id) VALUES (?, ?, ?, ?, ?)",
p, p, mc, "1234", 0)
if err != nil {
return err
}
id, _ = res.LastInsertId()
return nil
_, err := q.ExecContext(ctx,
"INSERT INTO mdm_apple_configuration_profiles (profile_uuid, identifier, name, mobileconfig, checksum, team_id) VALUES (?, ?, ?, ?, ?, ?)",
uid, p, p, mc, "1234", 0)
return err
})
var deleteResp deleteMDMConfigProfileResponse
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%d", id), nil, http.StatusBadRequest, &deleteResp)
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", uid), nil, http.StatusBadRequest, &deleteResp)
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
_, err := q.ExecContext(ctx,
"DELETE FROM mdm_apple_configuration_profiles WHERE profile_id = ?",
id)
"DELETE FROM mdm_apple_configuration_profiles WHERE profile_uuid = ?",
uid)
return err
})
}
@ -8535,7 +8558,7 @@ func (s *integrationMDMTestSuite) TestMDMConfigProfileCRUD() {
profile := s.assertConfigProfilesByIdentifier(nil, mobileconfig.FleetFileVaultPayloadIdentifier, true)
// try to delete the profile
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%d", profile.ProfileID), nil, http.StatusBadRequest, &deleteResp)
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/mdm/profiles/%s", profile.ProfileUUID), nil, http.StatusBadRequest, &deleteResp)
// make fleet add a Windows OS Updates profile
acResp = appConfigResponse{}
@ -8607,18 +8630,18 @@ func (s *integrationMDMTestSuite) TestListMDMConfigProfiles() {
listResp.Profiles[0].CreatedAt, listResp.Profiles[0].UpdatedAt = time.Time{}, time.Time{}
listResp.Profiles[1].CreatedAt, listResp.Profiles[1].UpdatedAt = time.Time{}, time.Time{}
require.Equal(t, &fleet.MDMConfigProfilePayload{
ProfileID: fmt.Sprint(tm2ProfF.ProfileID),
TeamID: tm2ProfF.TeamID,
Name: tm2ProfF.Name,
Platform: "darwin",
Identifier: tm2ProfF.Identifier,
Checksum: tm2ProfF.Checksum,
ProfileUUID: tm2ProfF.ProfileUUID,
TeamID: tm2ProfF.TeamID,
Name: tm2ProfF.Name,
Platform: "darwin",
Identifier: tm2ProfF.Identifier,
Checksum: tm2ProfF.Checksum,
}, listResp.Profiles[0])
require.Equal(t, &fleet.MDMConfigProfilePayload{
ProfileID: tm2ProfG.ProfileUUID,
TeamID: tm2ProfG.TeamID,
Name: tm2ProfG.Name,
Platform: "windows",
ProfileUUID: tm2ProfG.ProfileUUID,
TeamID: tm2ProfG.TeamID,
Name: tm2ProfG.Name,
Platform: "windows",
}, listResp.Profiles[1])
// list for a non-existing team returns 404

View file

@ -978,12 +978,12 @@ func (svc *Service) authorizeAllHostsTeams(ctx context.Context, hostUUIDs []stri
}
////////////////////////////////////////////////////////////////////////////////
// GET /mdm/profiles/{id_or_uuid}
// GET /mdm/profiles/{uuid}
////////////////////////////////////////////////////////////////////////////////
type getMDMConfigProfileRequest struct {
ProfileIDOrUUID string `url:"profile_id_or_uuid"`
Alt string `query:"alt,optional"`
ProfileUUID string `url:"profile_uuid"`
Alt string `query:"alt,optional"`
}
type getMDMConfigProfileResponse struct {
@ -997,11 +997,10 @@ func getMDMConfigProfileEndpoint(ctx context.Context, request interface{}, svc f
req := request.(*getMDMConfigProfileRequest)
downloadRequested := req.Alt == "media"
appleID, isApple := isAppleProfileID(req.ProfileIDOrUUID)
var err error
if isApple {
if isAppleProfileUUID(req.ProfileUUID) {
// Apple config profile
cp, err := svc.GetMDMAppleConfigProfile(ctx, appleID)
cp, err := svc.GetMDMAppleConfigProfile(ctx, req.ProfileUUID)
if err != nil {
return &getMDMConfigProfileResponse{Err: err}, nil
}
@ -1019,7 +1018,7 @@ func getMDMConfigProfileEndpoint(ctx context.Context, request interface{}, svc f
}
// Windows config profile
cp, err := svc.GetMDMWindowsConfigProfile(ctx, req.ProfileIDOrUUID)
cp, err := svc.GetMDMWindowsConfigProfile(ctx, req.ProfileUUID)
if err != nil {
return &getMDMConfigProfileResponse{Err: err}, nil
}
@ -1057,11 +1056,11 @@ func (svc *Service) GetMDMWindowsConfigProfile(ctx context.Context, profileUUID
}
////////////////////////////////////////////////////////////////////////////////
// DELETE /mdm/profiles/{id_or_uuid}
// DELETE /mdm/profiles/{uuid}
////////////////////////////////////////////////////////////////////////////////
type deleteMDMConfigProfileRequest struct {
ProfileIDOrUUID string `url:"profile_id_or_uuid"`
ProfileUUID string `url:"profile_uuid"`
}
type deleteMDMConfigProfileResponse struct {
@ -1073,12 +1072,11 @@ func (r deleteMDMConfigProfileResponse) error() error { return r.Err }
func deleteMDMConfigProfileEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
req := request.(*deleteMDMConfigProfileRequest)
appleID, isApple := isAppleProfileID(req.ProfileIDOrUUID)
var err error
if isApple {
err = svc.DeleteMDMAppleConfigProfile(ctx, appleID)
if isAppleProfileUUID(req.ProfileUUID) {
err = svc.DeleteMDMAppleConfigProfile(ctx, req.ProfileUUID)
} else {
err = svc.DeleteMDMWindowsConfigProfile(ctx, req.ProfileIDOrUUID)
err = svc.DeleteMDMWindowsConfigProfile(ctx, req.ProfileUUID)
}
return &deleteMDMConfigProfileResponse{Err: err}, nil
}
@ -1092,7 +1090,7 @@ func (svc *Service) DeleteMDMWindowsConfigProfile(ctx context.Context, profileUU
// check that Windows MDM is enabled - the middleware of that endpoint checks
// only that any MDM is enabled, maybe it's just macOS
if err := svc.VerifyMDMWindowsConfigured(ctx); err != nil {
err := fleet.NewInvalidArgumentError("profile_id", fleet.WindowsMDMNotConfiguredMessage).WithStatus(http.StatusBadRequest)
err := fleet.NewInvalidArgumentError("profile_uuid", fleet.WindowsMDMNotConfiguredMessage).WithStatus(http.StatusBadRequest)
return ctxerr.Wrap(ctx, err, "check windows MDM enabled")
}
@ -1128,7 +1126,7 @@ func (svc *Service) DeleteMDMWindowsConfigProfile(ctx context.Context, profileUU
}
// cannot use the profile ID as it is now deleted
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{teamID}, nil, nil, nil); err != nil {
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, []uint{teamID}, nil, nil); err != nil {
return ctxerr.Wrap(ctx, err, "bulk set pending host profiles")
}
@ -1153,14 +1151,11 @@ func (svc *Service) DeleteMDMWindowsConfigProfile(ctx context.Context, profileUU
// returns the numeric Apple profile ID and true if it is an Apple identifier,
// or 0 and false otherwise.
func isAppleProfileID(profileIDOrUUID string) (uint, bool) {
// parsing as 32 bits as that's the maximum value of the DB column (and can
// be safely converted to uint).
id, err := strconv.ParseUint(profileIDOrUUID, 10, 32)
if err != nil {
return 0, false
func isAppleProfileUUID(profileUUID string) bool {
if strings.HasPrefix(profileUUID, "a") {
return true
}
return uint(id), true
return false
}
////////////////////////////////////////////////////////////////////////////////
@ -1205,8 +1200,8 @@ func (newMDMConfigProfileRequest) DecodeRequest(ctx context.Context, r *http.Req
}
type newMDMConfigProfileResponse struct {
ProfileID string `json:"profile_id"`
Err error `json:"error,omitempty"`
ProfileUUID string `json:"profile_uuid"`
Err error `json:"error,omitempty"`
}
func (r newMDMConfigProfileResponse) error() error { return r.Err }
@ -1227,7 +1222,7 @@ func newMDMConfigProfileEndpoint(ctx context.Context, request interface{}, svc f
return &newMDMConfigProfileResponse{Err: err}, nil
}
return &newMDMConfigProfileResponse{
ProfileID: fmt.Sprint(cp.ProfileID),
ProfileUUID: cp.ProfileUUID,
}, nil
}
@ -1238,7 +1233,7 @@ func newMDMConfigProfileEndpoint(ctx context.Context, request interface{}, svc f
return &newMDMConfigProfileResponse{Err: err}, nil
}
return &newMDMConfigProfileResponse{
ProfileID: cp.ProfileUUID,
ProfileUUID: cp.ProfileUUID,
}, nil
}
@ -1312,7 +1307,7 @@ func (svc *Service) NewMDMWindowsConfigProfile(ctx context.Context, teamID uint,
return nil, ctxerr.Wrap(ctx, err)
}
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, nil, []string{newCP.ProfileUUID}, nil); err != nil {
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, []string{newCP.ProfileUUID}, nil); err != nil {
return nil, ctxerr.Wrap(ctx, err, "bulk set pending host profiles")
}
@ -1400,16 +1395,16 @@ func (svc *Service) BatchSetMDMProfiles(ctx context.Context, tmID *uint, tmName
for _, p := range windowsProfiles {
winProfUUIDs = append(winProfUUIDs, p.ProfileUUID)
}
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, nil, winProfUUIDs, nil); err != nil {
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, winProfUUIDs, nil); err != nil {
return ctxerr.Wrap(ctx, err, "bulk set pending windows host profiles")
}
// set pending status for apple profiles
appleProfIDs := []uint{}
appleProfUUIDs := []string{}
for _, p := range appleProfiles {
appleProfIDs = append(appleProfIDs, p.ProfileID)
appleProfUUIDs = append(appleProfUUIDs, p.ProfileUUID)
}
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, appleProfIDs, nil, nil); err != nil {
if err := svc.ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, appleProfUUIDs, nil); err != nil {
return ctxerr.Wrap(ctx, err, "bulk set pending apple host profiles")
}

View file

@ -936,7 +936,7 @@ func TestMDMWindowsConfigProfileAuthz(t *testing.T) {
ds.ListMDMConfigProfilesFunc = func(ctx context.Context, teamID *uint, opt fleet.ListOptions) ([]*fleet.MDMConfigProfilePayload, *fleet.PaginationMetadata, error) {
return nil, nil, nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs []uint, teamIDs []uint, profileIDs []uint, profileUUIDs []string, hostUUIDs []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs []uint, teamIDs []uint, profileUUIDs []string, hostUUIDs []string) error {
return nil
}
@ -1010,7 +1010,7 @@ func TestUploadWindowsMDMConfigProfileValidations(t *testing.T) {
cp.ProfileUUID = uuid.New().String()
return &cp, nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs []uint, teamIDs []uint, profileIDs []uint, profileUUIDs []string, hostUUIDs []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs []uint, teamIDs []uint, profileUUIDs []string, hostUUIDs []string) error {
return nil
}
@ -1099,7 +1099,7 @@ func TestMDMBatchSetProfiles(t *testing.T) {
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
return nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs []uint, teamIDs []uint, profileIDs []uint, profileUUIDs []string, hostUUIDs []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs []uint, teamIDs []uint, profileUUIDs []string, hostUUIDs []string) error {
return nil
}

View file

@ -2098,7 +2098,7 @@ func ReconcileWindowsProfiles(ctx context.Context, ds fleet.Datastore, logger ki
// with the new status, operation_type, etc.
hostProfiles := make([]*fleet.MDMWindowsBulkUpsertHostProfilePayload, 0, len(toInstall))
// install are maps from profileID -> command uuid and host
// install are maps from profileUUID -> command uuid and host
// UUIDs as the underlying MDM services are optimized to send one command to
// multiple hosts at the same time. Note that the same command uuid is used
// for all hosts in a given install/remove target operation.
@ -2129,7 +2129,7 @@ func ReconcileWindowsProfiles(ctx context.Context, ds fleet.Datastore, logger ki
OperationType: fleet.MDMOperationTypeInstall,
Status: &fleet.MDMDeliveryPending,
})
level.Debug(logger).Log("msg", "installing profile", "profile_id", p.ProfileUUID, "host_id", p.HostUUID, "name", p.ProfileName)
level.Debug(logger).Log("msg", "installing profile", "profile_uuid", p.ProfileUUID, "host_id", p.HostUUID, "name", p.ProfileName)
}
// Grab the contents of all the profiles we need to install
@ -2142,16 +2142,16 @@ func ReconcileWindowsProfiles(ctx context.Context, ds fleet.Datastore, logger ki
return ctxerr.Wrap(ctx, err, "get profile contents")
}
for profID, target := range installTargets {
p, ok := profileContents[profID]
for profUUID, target := range installTargets {
p, ok := profileContents[profUUID]
if !ok {
// this should never happen
return ctxerr.Wrap(ctx, err, "inserting commands for hosts")
return ctxerr.Wrapf(ctx, err, "missing profile content for profile %s", profUUID)
}
command, err := buildCommandFromProfileBytes(p, target.cmdUUID)
if err != nil {
level.Info(logger).Log("err", err, "profile_id", profID)
level.Info(logger).Log("err", err, "profile_uuid", profUUID)
continue
}
if err := ds.MDMWindowsInsertCommandForHosts(ctx, target.hostUUIDs, command); err != nil {

View file

@ -1402,15 +1402,44 @@ func submitLogsEndpoint(ctx context.Context, request interface{}, svc fleet.Serv
return submitLogsResponse{Err: err}, nil
}
func (svc *Service) preProcessOsqueryResults(ctx context.Context, osqueryResults []json.RawMessage, queryReportsDisabled bool) (unmarshaledResults []*fleet.ScheduledQueryResult, queriesDBData map[string]*fleet.Query) {
// preProcessOsqueryResults will attempt to unmarshal `osqueryResults` and will return:
// - `unmarshaledResults` with each result unmarshaled to `fleet.ScheduledQueryResult`s, where if an item is `nil` it means the corresponding
// `osqueryResults` item could not be unmarshaled.
// - queriesDBData has the corresponding DB query to each unmarshalled result in `osqueryResults`.
//
// If queryReportsDisabled is true then it returns only t he `unmarshaledResults` without querying the DB.
func (svc *Service) preProcessOsqueryResults(
ctx context.Context,
osqueryResults []json.RawMessage,
queryReportsDisabled bool,
) (unmarshaledResults []*fleet.ScheduledQueryResult, queriesDBData map[string]*fleet.Query) {
// skipauth: Authorization is currently for user endpoints only.
svc.authz.SkipAuthorization(ctx)
lograw := func(raw json.RawMessage) string {
logr := raw
if len(raw) >= 64 {
logr = raw[:64]
}
return string(logr)
}
for _, raw := range osqueryResults {
var result *fleet.ScheduledQueryResult
if err := json.Unmarshal(raw, &result); err != nil {
level.Error(svc.logger).Log("msg", "unmarshalling result", "err", err)
// Note we store a nil item if the result could not be unmarshaled.
level.Debug(svc.logger).Log("msg", "unmarshalling result", "err", err, "result", lograw(raw))
// Note that if err != nil we have two scenarios:
// - result == nil: which means the result could not be unmarshalled, e.g. not JSON.
// - result != nil: which means that the result was (partially) unmarshalled but some specific
// field could not be unmarshalled.
//
// In both scenarios we want to add `result` to `unmarshaledResults`.
} else {
// If the unmarshaled result doesn't have a "name" field then we ignore the result.
if result != nil && result.QueryName == "" {
level.Debug(svc.logger).Log("msg", "missing name field", "result", lograw(raw))
result = nil
}
}
unmarshaledResults = append(unmarshaledResults, result)
}
@ -1427,7 +1456,7 @@ func (svc *Service) preProcessOsqueryResults(ctx context.Context, osqueryResults
}
teamID, queryName, err := getQueryNameAndTeamIDFromResult(queryResult.QueryName)
if err != nil {
level.Error(svc.logger).Log("msg", "querying name and team ID from result", "err", err)
level.Debug(svc.logger).Log("msg", "querying name and team ID from result", "err", err)
continue
}
if _, ok := queriesDBData[queryResult.QueryName]; ok {
@ -1463,6 +1492,9 @@ func (svc *Service) SubmitResultLogs(ctx context.Context, logs []json.RawMessage
// otherwise the results will never clear from its local DB and
// will keep retrying forever.
//
// We do return errors if we fail to write to the external logging destination,
// so that the logs are not lost and osquery retries on its next log interval.
//
var queryReportsDisabled bool
appConfig, err := svc.ds.AppConfig(ctx)
@ -1486,32 +1518,35 @@ func (svc *Service) SubmitResultLogs(ctx context.Context, logs []json.RawMessage
// Ignore results that could not be unmarshaled.
continue
}
if queryReportsDisabled {
// If query_reports_disabled=true we write the logs
// to the logging destination without any extra processing.
// If query_reports_disabled=true we write the logs to the logging destination without any extra processing.
//
// If a query has automations_enabled = 0 we may still write the results for it here.
// Eventually the query will be removed from the host schedule and thus Fleet
// won't receive any further results anymore (/api/v1/osquery/config).
// If a query was recently configured with automations_enabled = 0 we may still write
// the results for it here. Eventually the query will be removed from the host schedule
// and thus Fleet won't receive any further results anymore.
filteredLogs = append(filteredLogs, logs[i])
continue
}
//
// If query_reports_disabled=false then we need to filter
// the queries that have automations_enabled=false because
// the query might have been scheduled because of discard_data=false.
//
dbQuery, ok := queriesDBData[unmarshaledResult.QueryName]
if !ok {
// Ignore results for unknown queries.
// If Fleet doesn't know of the query we write the logs to the logging destination
// without any extra processing. This is to support osquery nodes that load their
// config from elsewhere (e.g. using `--config_plugin=filesystem`).
//
// If a query was configured from Fleet but was recently removed, we may still write
// the results for it here. Eventually the query will be removed from the host schedule
// and thus Fleet won't receive any further results anymore.
filteredLogs = append(filteredLogs, logs[i])
continue
}
if !dbQuery.AutomationsEnabled {
// Ignore results for queries that have automations disabled.
continue
}
filteredLogs = append(filteredLogs, logs[i])
}
@ -1653,7 +1688,7 @@ func getQueryNameAndTeamIDFromResult(path string) (*uint, string, error) {
if strings.HasPrefix(path, "pack/team-") {
parts := strings.SplitN(path, "/", 3)
if len(parts) != 3 {
return nil, "", fmt.Errorf("unknown format: %s", path)
return nil, "", fmt.Errorf("unknown format: %q", path)
}
teamNumberStr := strings.TrimPrefix(parts[1], "team-")
@ -1670,11 +1705,11 @@ func getQueryNameAndTeamIDFromResult(path string) (*uint, string, error) {
if strings.HasPrefix(path, "pack/") {
parts := strings.SplitN(path, "/", 3)
if len(parts) != 3 {
return nil, "", fmt.Errorf("unknown format: %s", path)
return nil, "", fmt.Errorf("unknown format: %q", path)
}
return nil, parts[2], nil
}
// If none of the above patterns match, return error
return nil, "", fmt.Errorf("unknown format: %s", path)
return nil, "", fmt.Errorf("unknown format: %q", path)
}

View file

@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"io"
"os"
"reflect"
"sort"
"strconv"
@ -533,7 +534,7 @@ func TestSubmitStatusLogs(t *testing.T) {
func TestSubmitResultLogsToLogDestination(t *testing.T) {
ds := new(mock.Store)
svc, ctx := newTestService(t, ds, nil, nil)
svc, ctx := newTestService(t, ds, nil, nil, &TestServerOpts{Logger: log.NewJSONLogger(os.Stdout)})
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return &fleet.AppConfig{}, nil
@ -614,60 +615,65 @@ func TestSubmitResultLogsToLogDestination(t *testing.T) {
serv.osqueryLogWriter = &OsqueryLogger{Result: testLogger}
validLogResults := []string{
`{"name":"pack/Global/system_info","hostIdentifier":"some_uuid","calendarTime":"Fri Sep 30 17:55:15 2016 UTC","unixTime":"1475258115","decorations":{"host_uuid":"some_uuid","username":"zwass"},"columns":{"cpu_brand":"Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz","hostname":"hostimus","physical_memory":"17179869184"},"action":"added"}`,
`{"name":"pack/Global/system_info","hostIdentifier":"some_uuid","calendarTime":"Fri Sep 30 17:55:15 2016 UTC","unixTime":1475258115,"decorations":{"host_uuid":"some_uuid","username":"zwass"},"columns":{"cpu_brand":"Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz","hostname":"hostimus","physical_memory":"17179869184"},"action":"added"}`,
`{"name":"pack/SomePack/encrypted","hostIdentifier":"some_uuid","calendarTime":"Fri Sep 30 21:19:15 2016 UTC","unixTime":"1475270355","decorations":{"host_uuid":"4740D59F-699E-5B29-960B-979AAF9BBEEB","username":"zwass"},"columns":{"encrypted":"1","name":"\/dev\/disk1","type":"AES-XTS","uid":"","user_uuid":"","uuid":"some_uuid"},"action":"added"}`,
`{"name":"pack/SomePack/encrypted","hostIdentifier":"some_uuid","calendarTime":"Fri Sep 30 21:19:14 2016 UTC","unixTime":"1475270354","decorations":{"host_uuid":"4740D59F-699E-5B29-960B-979AAF9BBEEB","username":"zwass"},"columns":{"encrypted":"1","name":"\/dev\/disk1","type":"AES-XTS","uid":"","user_uuid":"","uuid":"some_uuid"},"action":"added"}`,
`{"name":"pack/SomePack/encrypted","hostIdentifier":"some_uuid","calendarTime":"Fri Sep 30 21:19:15 2016 UTC","unixTime":1475270355,"decorations":{"host_uuid":"4740D59F-699E-5B29-960B-979AAF9BBEEB","username":"zwass"},"columns":{"encrypted":"1","name":"\/dev\/disk1","type":"AES-XTS","uid":"","user_uuid":"","uuid":"some_uuid"},"action":"added"}`,
`{"name":"pack/SomePack/encrypted","hostIdentifier":"some_uuid","calendarTime":"Fri Sep 30 21:19:14 2016 UTC","unixTime":1475270354,"decorations":{"host_uuid":"4740D59F-699E-5B29-960B-979AAF9BBEEB","username":"zwass"},"columns":{"encrypted":"1","name":"\/dev\/disk1","type":"AES-XTS","uid":"","user_uuid":"","uuid":"some_uuid"},"action":"added"}`,
// These results belong to the same query but have 1 second difference.
`{"snapshot":[{"hour":"20","minutes":"8"}],"action":"snapshot","name":"pack/Global/time","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:51 2017 UTC","unixTime":1484078931,"decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`,
`{"snapshot":[{"hour":"20","minutes":"8"}],"action":"snapshot","name":"pack/Global/time","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:50 2017 UTC","unixTime":1484078930,"decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`,
`{"snapshot":[{"hour":"20","minutes":"8"}],"action":"snapshot","name":"pack/Global/time","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:52 2017 UTC","unixTime":1484078932,"decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`,
`{"diffResults":{"removed":[{"address":"127.0.0.1","hostnames":"kl.groob.io"}],"added":""},"name":"pack\/team-1/hosts","hostIdentifier":"FA01680E-98CA-5557-8F59-7716ECFEE964","calendarTime":"Sun Nov 19 00:02:08 2017 UTC","unixTime":"1511049728","epoch":"0","counter":"10","decorations":{"host_uuid":"FA01680E-98CA-5557-8F59-7716ECFEE964","hostname":"kl.groob.io"}}`,
`{"diffResults":{"removed":[{"address":"127.0.0.1","hostnames":"kl.groob.io"}],"added":""},"name":"pack\/team-1/hosts","hostIdentifier":"FA01680E-98CA-5557-8F59-7716ECFEE964","calendarTime":"Sun Nov 19 00:02:08 2017 UTC","unixTime":1511049728,"epoch":"0","counter":"10","decorations":{"host_uuid":"FA01680E-98CA-5557-8F59-7716ECFEE964","hostname":"kl.groob.io"}}`,
`{"snapshot":[{"hour":"20","minutes":"8"}],"action":"snapshot","name":"pack/Global/query_should_be_saved_and_submitted","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:51 2017 UTC","unixTime":1484078931,"decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`,
//`{"snapshot":[],"action":"snapshot","name":"pack/Global/query_no_rows","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:51 2017 UTC","unixTime":1484078931,"decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`,
// Fleet doesn't know of this query, so this result should be streamed as is (This is to support streaming results for osquery nodes that are configured outside of Fleet, e.g. `--config_plugin=filesystem`).
`{"snapshot":[{"hour":"20","minutes":"8"}],"action":"snapshot","name":"pack/Global/doesntexist","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:51 2017 UTC","unixTime":1484078931,"decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`,
// The "name" field has invalid format, so this result will be streamed as is (This is to support streaming results for osquery nodes that are configured outside of Fleet, e.g. `--config_plugin=filesystem`).
`{"snapshot":[{"hour":"20","minutes":"8"}],"action":"snapshot","name":"some_name","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:51 2017 UTC","unixTime":1484078931,"decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`,
`{"snapshot":[{"hour":"20","minutes":"8"}],"action":"snapshot","name":"pack/team-foo/bar","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:51 2017 UTC","unixTime":1484078931,"decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`,
`{"snapshot":[{"hour":"20","minutes":"8"}],"action":"snapshot","name":"pack/team-","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:51 2017 UTC","unixTime":1484078931,"decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`,
`{"snapshot":[{"hour":"20","minutes":"8"}],"action":"snapshot","name":"pack/PackName","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:51 2017 UTC","unixTime":1484078931,"decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`,
}
logJSON := fmt.Sprintf("[%s]", strings.Join(validLogResults, ","))
resultWithInvalidJSON := []byte("foobar:\n\t123")
resultWithInvalidJSONLong := []byte("foobar:\n\t1233333333333333333333333333333333333333333333333333333333")
// The "name" field will be empty, so this result will be ignored.
resultWithoutName := []byte(`{"unknown":{"foo": [] }}`)
// The "name" field has invalid format, so this result will be ignored.
resultWithInvalidNameFmt1 := []byte(`{"snapshot":[{"hour":"20","minutes":"8"}],"action":"snapshot","name":"pack/team-foo/bar","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:51 2017 UTC","unixTime":"1484078931","decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`)
resultWithInvalidNameFmt2 := []byte(`{"snapshot":[{"hour":"20","minutes":"8"}],"action":"snapshot","name":"pack/team-","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:51 2017 UTC","unixTime":"1484078931","decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`)
resultWithInvalidNameFmt3 := []byte(`{"snapshot":[{"hour":"20","minutes":"8"}],"action":"snapshot","name":"pack/PackName","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:51 2017 UTC","unixTime":"1484078931","decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`)
// The query doesn't exist, so this result will be ignored.
resultWithQueryDoesNotExist := []byte(`{"snapshot":[{"hour":"20","minutes":"8"}],"action":"snapshot","name":"pack/Global/doesntexist","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:51 2017 UTC","unixTime":"1484078931","decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`)
// The query was configured with automations disabled, so this result will be ignored.
resultWithQueryNotAutomated := []byte(`{"snapshot":[{"hour":"20","minutes":"8"}],"action":"snapshot","name":"pack/Global/query_not_automated","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:51 2017 UTC","unixTime":"1484078931","decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`)
resultWithQueryNotAutomated := []byte(`{"snapshot":[{"hour":"20","minutes":"8"}],"action":"snapshot","name":"pack/Global/query_not_automated","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:51 2017 UTC","unixTime":1484078931,"decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`)
// The query is supposed to be saved but with automations disabled (and has two columns).
resultWithQuerySavedNotAutomated := []byte(`{"snapshot":[{"hour":"20","minutes":"8"},{"hour":"21","minutes":"9"}],"action":"snapshot","name":"pack/Global/query_should_be_saved_but_not_submitted","hostIdentifier":"1379f59d98f4","calendarTime":"Tue Jan 10 20:08:51 2017 UTC","unixTime":1484078931,"decorations":{"host_uuid":"EB714C9D-C1F8-A436-B6DA-3F853C5502EA"}}`)
var results []json.RawMessage
err := json.Unmarshal([]byte(logJSON), &results)
var validResults []json.RawMessage
err := json.Unmarshal([]byte(logJSON), &validResults)
require.NoError(t, err)
host := fleet.Host{
ID: 999,
}
ctx = hostctx.NewContext(ctx, &host)
// Submit valid and invalid log results mixed.
err = serv.SubmitResultLogs(ctx, append(append(results[:3],
resultWithInvalidJSON,
resultWithoutName,
resultWithInvalidNameFmt1,
resultWithInvalidNameFmt2,
resultWithInvalidNameFmt3,
resultWithQueryDoesNotExist,
resultWithQueryNotAutomated,
resultWithQuerySavedNotAutomated,
), results[3:]...))
// Submit valid, invalid and to-be-ignored log results mixed.
validAndInvalidResults := make([]json.RawMessage, 0, len(validResults)+5)
for i, result := range validResults {
validAndInvalidResults = append(validAndInvalidResults, result)
if i == 2 {
validAndInvalidResults = append(validAndInvalidResults,
resultWithInvalidJSON, resultWithInvalidJSONLong,
resultWithoutName, resultWithQueryNotAutomated,
resultWithQuerySavedNotAutomated,
)
}
}
err = serv.SubmitResultLogs(ctx, validAndInvalidResults)
require.NoError(t, err)
assert.Equal(t, results, testLogger.logs)
assert.Equal(t, validResults, testLogger.logs)
}
func TestSaveResultLogsToQueryReports(t *testing.T) {

View file

@ -438,31 +438,48 @@ var extraDetailQueries = map[string]DetailQuery{
Discovery: discoveryTable("mdm"),
},
"mdm_windows": {
// we get most of the MDM information for Windows from the
// `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments\%%`
// registry keys. A computer might many different folders under
// that path, for different enrollments, so we need to group by
// enrollment (key in this case) and try to grab the most
// likely candiate to be an MDM solution.
//
// The best way I have found, is to filter by groups of entries
// with an UPN value, and pick the first one.
//
// An example of a host having more than one entry: when
// the `mdm_bridge` table is used, the `mdmlocalmanagement.dll`
// registers an MDM with ProviderID = `Local_Management`
//
// For more information, refer to issue #15362
Query: `
SELECT * FROM (
SELECT "provider_id" AS "key", data as "value" FROM registry
WHERE path LIKE 'HKEY_LOCAL_MACHINE\Software\Microsoft\Enrollments\%\ProviderID'
LIMIT 1
WITH registry_keys AS (
SELECT *
FROM registry
WHERE path LIKE 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments\%%'
),
enrollment_info AS (
SELECT
MAX(CASE WHEN name = 'UPN' THEN data END) AS upn,
MAX(CASE WHEN name = 'IsFederated' THEN data END) AS is_federated,
MAX(CASE WHEN name = 'DiscoveryServiceFullURL' THEN data END) AS discovery_service_url,
MAX(CASE WHEN name = 'ProviderID' THEN data END) AS provider_id
FROM registry_keys
GROUP BY key
)
UNION ALL
SELECT * FROM (
SELECT "discovery_service_url" AS "key", data as "value" FROM registry
WHERE path LIKE 'HKEY_LOCAL_MACHINE\Software\Microsoft\Enrollments\%\DiscoveryServiceFullURL'
LIMIT 1
)
UNION ALL
SELECT * FROM (
SELECT "is_federated" AS "key", data as "value" FROM registry
WHERE path LIKE 'HKEY_LOCAL_MACHINE\Software\Microsoft\Enrollments\%\IsFederated'
LIMIT 1
)
UNION ALL
SELECT * FROM (
SELECT "installation_type" AS "key", data as "value" FROM registry
WHERE path = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\InstallationType'
LIMIT 1
)
;
SELECT
e.is_federated,
e.discovery_service_url,
e.provider_id,
(
SELECT data
FROM registry
WHERE path = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\InstallationType'
) AS installation_type
FROM enrollment_info e
WHERE e.upn IS NOT NULL
LIMIT 1;
`,
DirectIngestFunc: directIngestMDMWindows,
Platforms: []string{"windows"},
@ -1458,10 +1475,19 @@ func deduceMDMNameWindows(data map[string]string) string {
}
func directIngestMDMWindows(ctx context.Context, logger log.Logger, host *fleet.Host, ds fleet.Datastore, rows []map[string]string) error {
data := make(map[string]string, len(rows))
for _, r := range rows {
data[r["key"]] = r["value"]
if len(rows) != 1 {
logger.Log("component", "service", "method", "directIngestMDMWindows", "warn",
fmt.Sprintf("mdm expected single result got %d", len(rows)))
// assume the extension is not there
return nil
}
if len(rows) > 1 {
logger.Log("component", "service", "method", "directIngestMDMWindows", "warn",
fmt.Sprintf("mdm expected single result got %d", len(rows)))
}
data := rows[0]
var enrolled bool
var automatic bool
serverURL := data["discovery_service_url"]

View file

@ -588,10 +588,12 @@ func TestDirectIngestMDMWindows(t *testing.T) {
{
name: "off empty server URL",
data: []map[string]string{
{"key": "discovery_service_url", "value": ""},
{"key": "is_federated", "value": "1"},
{"key": "provider_id", "value": "Some_ID"},
{"key": "installation_type", "value": "Client"},
{
"discovery_service_url": "",
"is_federated": "1",
"provider_id": "Some_ID",
"installation_type": "Client",
},
},
wantEnrolled: false,
wantInstalledFromDep: false,
@ -601,8 +603,10 @@ func TestDirectIngestMDMWindows(t *testing.T) {
{
name: "off missing is_federated and server url",
data: []map[string]string{
{"key": "provider_id", "value": "Some_ID"},
{"key": "installation_type", "value": "Client"},
{
"provider_id": "Some_ID",
"installation_type": "Client",
},
},
wantEnrolled: false,
wantInstalledFromDep: false,
@ -612,10 +616,12 @@ func TestDirectIngestMDMWindows(t *testing.T) {
{
name: "on automatic",
data: []map[string]string{
{"key": "discovery_service_url", "value": "https://example.com"},
{"key": "is_federated", "value": "1"},
{"key": "provider_id", "value": "Some_ID"},
{"key": "installation_type", "value": "Client"},
{
"discovery_service_url": "https://example.com",
"is_federated": "1",
"provider_id": "Some_ID",
"installation_type": "Client",
},
},
wantEnrolled: true,
wantInstalledFromDep: true,
@ -625,10 +631,12 @@ func TestDirectIngestMDMWindows(t *testing.T) {
{
name: "on manual",
data: []map[string]string{
{"key": "discovery_service_url", "value": "https://example.com"},
{"key": "is_federated", "value": "0"},
{"key": "provider_id", "value": "Local_Management"},
{"key": "installation_type", "value": "Client"},
{
"discovery_service_url": "https://example.com",
"is_federated": "0",
"provider_id": "Local_Management",
"installation_type": "Client",
},
},
wantEnrolled: true,
wantInstalledFromDep: false,
@ -638,9 +646,11 @@ func TestDirectIngestMDMWindows(t *testing.T) {
{
name: "on manual missing is_federated",
data: []map[string]string{
{"key": "discovery_service_url", "value": "https://example.com"},
{"key": "provider_id", "value": "Some_ID"},
{"key": "installation_type", "value": "Client"},
{
"discovery_service_url": "https://example.com",
"provider_id": "Some_ID",
"installation_type": "Client",
},
},
wantEnrolled: true,
wantInstalledFromDep: false,
@ -650,10 +660,11 @@ func TestDirectIngestMDMWindows(t *testing.T) {
{
name: "is_server",
data: []map[string]string{
{"key": "discovery_service_url", "value": "https://example.com"},
{"key": "is_federated", "value": "1"},
{"key": "provider_id", "value": "Some_ID"},
{"key": "installation_type", "value": "Windows SeRvEr 99.9"},
{
"discovery_service_url": "https://example.com",
"is_federated": "1",
"provider_id": "Some_ID",
"installation_type": "Windows SeRvEr 99.9"},
},
wantEnrolled: true,
wantInstalledFromDep: true,

View file

@ -50,7 +50,7 @@ func TestTeamAuth(t *testing.T) {
ds.ApplyEnrollSecretsFunc = func(ctx context.Context, teamID *uint, secrets []*fleet.EnrollSecret) error {
return nil
}
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids, pids []uint, puuids, uuids []string) error {
ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hids, tids []uint, puuids, uuids []string) error {
return nil
}
ds.ListHostsFunc = func(ctx context.Context, filter fleet.TeamFilter, opt fleet.HostListOptions) ([]*fleet.Host, error) {

View file

@ -10,7 +10,6 @@ app_schemes SELECT * FROM app_schemes
apps SELECT * FROM apps
arp_cache SELECT * FROM arp_cache
asl SELECT * FROM asl
atom_packages SELECT * FROM atom_packages
augeas SELECT * from augeas where path = '/etc/hosts'
authorization_mechanisms SELECT * FROM authorization_mechanisms
authorizations SELECT * FROM authorizations

View file

@ -108,11 +108,18 @@ parasails.registerPage('basic-documentation', {
// Handle hashes in urls when coming from an external page.
if(window.location.hash){
let possibleHashToScrollTo = _.trimLeft(window.location.hash, '#');
let hashToScrollTo = document.getElementById(possibleHashToScrollTo);
// If a hash was provided, we'll remove the # and any query parameters from it. (e.g., #create-an-api-only-user?utm_medium=fleetui&utm_campaign=get-api-token » create-an-api-only-user)
// Note: Hash links for headings in markdown content will never have a '?' beacause they are removed when convereted to kebab-case, so we can safely strip everything after one if a url contains a query parameter.
let possibleHashToScrollTo = _.trimLeft(window.location.hash.split('?')[0], '#');
let elementWithMatchingId = document.getElementById(possibleHashToScrollTo);
// If the hash matches a header's ID, we'll scroll to that section.
if(hashToScrollTo){
hashToScrollTo.scrollIntoView();
if(elementWithMatchingId){
// Get the distance of the specified element, and reduce it by 90 so the section is not hidden by the page header.
let amountToScroll = elementWithMatchingId.offsetTop - 90;
window.scrollTo({
top: amountToScroll,
left: 0,
});
}
}

View file

@ -52,11 +52,18 @@ parasails.registerPage('basic-handbook', {
// Handle hashes in urls when coming from an external page.
if(window.location.hash){
let possibleHashToScrollTo = _.trimLeft(window.location.hash, '#');
let hashToScrollTo = document.getElementById(possibleHashToScrollTo);
// If a hash was provided, we'll remove the # and any query parameters from it. (e.g., #create-an-api-only-user?utm_medium=fleetui&utm_campaign=get-api-token » create-an-api-only-user)
// Note: Hash links for headings in markdown content will never have a '?' beacause they are removed when convereted to kebab-case, so we can safely strip everything after one if a url contains a query parameter.
let possibleHashToScrollTo = _.trimLeft(window.location.hash.split('?')[0], '#');
let elementWithMatchingId = document.getElementById(possibleHashToScrollTo);
// If the hash matches a header's ID, we'll scroll to that section.
if(hashToScrollTo){
hashToScrollTo.scrollIntoView();
if(elementWithMatchingId){
// Get the distance of the specified element, and reduce it by 90 so the section is not hidden by the page header.
let amountToScroll = elementWithMatchingId.offsetTop - 90;
window.scrollTo({
top: amountToScroll,
left: 0,
});
}
}