mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 08:58:41 +00:00
Merge branch 'main' into 14415
This commit is contained in:
commit
5677d734de
77 changed files with 1771 additions and 905 deletions
|
|
@ -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`
|
||||
|
|
|
|||
1
changes/12037-fix-dep-reenroll-activity
Normal file
1
changes/12037-fix-dep-reenroll-activity
Normal file
|
|
@ -0,0 +1 @@
|
|||
* Fix `installed_from_dep` in `mdm_enrolled` activity when a DEP device unenrolls and re-enrolls.
|
||||
1
changes/15168-scheduled-outside-fleet
Normal file
1
changes/15168-scheduled-outside-fleet
Normal 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`.
|
||||
1
changes/15362-windows-mdm-query
Normal file
1
changes/15362-windows-mdm-query
Normal file
|
|
@ -0,0 +1 @@
|
|||
* Improved the query used to get MDM details for Windows hosts to account for multiple registry entries.
|
||||
1
changes/issue-15274-unify-profiles-uuid
Normal file
1
changes/issue-15274-unify-profiles-uuid
Normal 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".
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.">
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)",
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ const ProfileStatusAggregate = ({
|
|||
|
||||
return (
|
||||
<ProfileStatusCount
|
||||
key={value}
|
||||
statusIcon={iconName}
|
||||
statusValue={value}
|
||||
teamId={teamId}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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}`;
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ const Scripts = ({ router, currentPage, teamIdForApi }: IScriptsProps) => {
|
|||
return (
|
||||
<>
|
||||
<UploadList
|
||||
keyAttribute="id"
|
||||
listItems={scripts || []}
|
||||
HeadingComponent={ScriptListHeading}
|
||||
ListItemComponent={({ listItem }) => (
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const UploadedPackageView = ({
|
|||
/>
|
||||
</p>
|
||||
<UploadList
|
||||
keyAttribute="name"
|
||||
listItems={[bootstrapPackage]}
|
||||
ListItemComponent={({ listItem }) => (
|
||||
<BootstrapPackageListItem
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ const UploadedEulaView = ({
|
|||
/>
|
||||
</p>
|
||||
<UploadList
|
||||
keyAttribute="name"
|
||||
listItems={[eulaMetadata]}
|
||||
ListItemComponent={({ listItem }) => (
|
||||
<EulaListItem eulaData={listItem} onDelete={onDelete} />
|
||||
|
|
|
|||
|
|
@ -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)",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 (?, ?, ?, ?);`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue