Merge branch 'main' into feat-setup-experience

This commit is contained in:
Gabriel Hernandez 2024-10-02 10:07:59 +01:00
commit 05e271a449
75 changed files with 1196 additions and 257 deletions

View file

@ -37,11 +37,10 @@ jobs:
with:
go-version-file: 'go.mod'
# Set the Node.js version
- name: Set up Node.js ${{ vars.NODE_VERSION }}
- name: Set up Node.js
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: ${{ vars.NODE_VERSION }}
node-version-file: package.json
- name: JS Dependency Cache
id: js-cache
@ -51,7 +50,7 @@ jobs:
**/node_modules
# Use a separate cache for this from other JS jobs since we run the
# webpack steps and will have more to cache.
key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }}-node_version-${{ vars.NODE_VERSION }}
key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node_modules-

View file

@ -79,11 +79,10 @@ jobs:
with:
go-version-file: 'go.mod'
# Set the Node.js version
- name: Set up Node.js ${{ vars.NODE_VERSION }}
- name: Set up Node.js
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: ${{ vars.NODE_VERSION }}
node-version-file: package.json
- name: Start tunnel
env:

View file

@ -46,11 +46,10 @@ jobs:
with:
go-version-file: 'go.mod'
# Set the Node.js version
- name: Set up Node.js ${{ vars.NODE_VERSION }}
- name: Set up Node.js
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: ${{ vars.NODE_VERSION }}
node-version-file: package.json
- name: Install JS Dependencies
run: make deps-js

View file

@ -60,10 +60,10 @@ jobs:
go-version-file: 'go.mod'
# Set the Node.js version
- name: Set up Node.js ${{ vars.NODE_VERSION }}
- name: Set up Node.js
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: ${{ vars.NODE_VERSION }}
node-version-file: package.json
- name: Install Dependencies
run: make deps

View file

@ -42,14 +42,14 @@ jobs:
with:
egress-policy: audit
- name: Set up Node.js ${{ vars.NODE_VERSION }}
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: ${{ vars.NODE_VERSION }}
- name: Checkout Code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Set up Node.js
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version-file: package.json
- name: JS Dependency Cache
id: js-cache
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # v2
@ -87,14 +87,14 @@ jobs:
with:
egress-policy: audit
- name: Set up Node.js ${{ vars.NODE_VERSION }}
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: ${{ vars.NODE_VERSION }}
- name: Checkout Code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Set up Node.js
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version-file: package.json
- name: JS Dependency Cache
id: js-cache
uses: actions/cache@69d9d449aced6a2ede0bc19182fadc3a0a42d2b0 # v2

View file

@ -1,3 +1,12 @@
## Fleet 4.57.1 (Oct 01, 2024)
### Bug fixes
* Improved performance of SQL queries used to determine MDM profile status for Apple hosts.
* Ensured request timeouts for software installer edits were just as high as for initial software installer uploads.
* Fixed an issue with the migration that added support for multiple VPP tokens, which would happen if a token was removed prior to upgrading Fleet.
* Fixed a "no rows" error when adding a software installer that matched an existing title's name and source but not its bundle ID.
## Fleet 4.57.0 (Sep 23, 2024)
**Endpoint Operations**

View file

@ -4,7 +4,7 @@
Deploying Fleet's agent across a diverse range of devices often involves the crucial step of enrolling each device. Traditionally, this involves [packaging](https://fleetdm.com/docs/using-fleet/fleetd#packaging) `fleetd` with configuration including the enroll secret and server URL. While effective, an alternative offers more flexibility in your deployment process. This guide introduces a different approach for deploying Fleet's agent without embedding configuration settings directly into `fleetd`. Ideal for IT administrators who prefer to generate a single package and maintain greater control over the distribution of enrollment secrets and server URLs, this method simplifies the enrollment process across macOS and Windows hosts.
Emphasizing adaptability and convenience, this approach allows for a more efficient way to manage device enrollments. Lets dive into how to deploy Fleet's agent using this alternative method, ensuring a more open and flexible deployment process.
This approach emphasizes adaptability and convenience and allows for a more efficient way to manage device enrollments. Lets explore how to deploy Fleet's agent using this alternative method, ensuring a more open and flexible deployment process.
## For macOS:
@ -44,6 +44,18 @@ fleetctl package --type=pkg --use-system-configuration --fleet-desktop
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
<dict>
<key>EndUserEmail</key>
<string>END_USER_EMAIL_HERE</string>
<key>PayloadIdentifier</key>
<string>com.fleetdm.fleet.mdm.apple.mdm</string>
<key>PayloadType</key>
<string>com.apple.mdm</string>
<key>PayloadUUID</key>
<string>29713130-1602-4D27-90C9-B822A295E44E</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>Fleetd configuration</string>
@ -56,11 +68,38 @@ fleetctl package --type=pkg --use-system-configuration --fleet-desktop
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadDescription</key>
<string>Default configuration for the fleetd agent.</string>
<string>Configuration for the fleetd agent.</string>
</dict>
</plist>
```
You can optionally specify the `END_USER_EMAIL` that will be added to the host's [human-device mapping](https://fleetdm.com/docs/rest-api/rest-api#get-human-device-mapping):
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
...
<dict>
<key>EndUserEmail</key>
<string>END_USER_EMAIL</string>
<key>PayloadIdentifier</key>
<string>com.fleetdm.fleet.mdm.apple.mdm</string>
<key>PayloadType</key>
<string>com.apple.mdm</string>
<key>PayloadUUID</key>
<string>29713130-1602-4D27-90C9-B822A295E44E</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
...
</dict>
</plist>
```
## For Windows:

View file

@ -0,0 +1 @@
* Added support for uploading RPM packages.

2
changes/22198-defaults Normal file
View file

@ -0,0 +1,2 @@
- Fixes a bug where removing a VPP or ABM token from a GitOps YAML file would leave the team
assignments unchanged.

View file

@ -8,7 +8,7 @@ version: v6.2.0
home: https://github.com/fleetdm/fleet
sources:
- https://github.com/fleetdm/fleet.git
appVersion: v4.57.0
appVersion: v4.57.1
dependencies:
- name: mysql
condition: mysql.enabled

View file

@ -3,7 +3,7 @@
hostName: fleet.localhost
replicas: 3 # The number of Fleet instances to deploy
imageRepository: fleetdm/fleet
imageTag: v4.57.0 # Version of Fleet to deploy
imageTag: v4.57.1 # Version of Fleet to deploy
podAnnotations: {} # Additional annotations to add to the Fleet pod
serviceAccountAnnotations: {} # Additional annotations to add to the Fleet service account
resources:

View file

@ -657,6 +657,18 @@ func TestApplyAppConfig(t *testing.T) {
return []*fleet.TeamSummary{{Name: "team1", ID: 1}}, nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{{OrganizationName: t.Name()}}, nil
}
name := writeTmpYml(t, `---
apiVersion: v1
kind: config
@ -782,6 +794,18 @@ func TestApplyAppConfigDryRunIssue(t *testing.T) {
return nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
// first, set the default app config's agent options as set after fleetctl setup
name := writeTmpYml(t, `---
apiVersion: v1
@ -914,6 +938,18 @@ func TestApplyAppConfigDeprecatedFields(t *testing.T) {
return nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
name := writeTmpYml(t, `---
apiVersion: v1
kind: config
@ -1316,6 +1352,14 @@ func TestApplyAsGitOps(t *testing.T) {
return []*fleet.ABMToken{{ID: 1}}, nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
// Apply global config.
name := writeTmpYml(t, `---
apiVersion: v1
@ -1873,6 +1917,18 @@ func TestCanApplyIntervalsInNanoseconds(t *testing.T) {
return nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
name := writeTmpYml(t, `---
apiVersion: v1
kind: config
@ -1908,6 +1964,18 @@ func TestCanApplyIntervalsUsingDurations(t *testing.T) {
return nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
name := writeTmpYml(t, `---
apiVersion: v1
kind: config
@ -2091,6 +2159,18 @@ func TestApplyMacosSetup(t *testing.T) {
return []*fleet.ABMToken{{ID: 1}}, nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
return ds
}
@ -2764,6 +2844,17 @@ func TestApplySpecs(t *testing.T) {
ds.DeleteMDMWindowsConfigProfileByTeamAndNameFunc = func(ctx context.Context, teamID *uint, profileName string) error {
return nil
}
// VPP/AMB
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
}
cases := []struct {

View file

@ -83,6 +83,18 @@ func TestGitOpsBasicGlobalFree(t *testing.T) {
return nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
tmpFile, err := os.CreateTemp(t.TempDir(), "*.yml")
require.NoError(t, err)
@ -238,6 +250,18 @@ func TestGitOpsBasicGlobalPremium(t *testing.T) {
return nil, nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
tmpFile, err := os.CreateTemp(t.TempDir(), "*.yml")
require.NoError(t, err)
@ -591,6 +615,17 @@ func TestGitOpsFullGlobal(t *testing.T) {
return nil
}
// Needed for checking tokens
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
const (
fleetServerURL = "https://fleet.example.com"
orgName = "GitOps Test"
@ -1079,6 +1114,18 @@ func TestGitOpsBasicGlobalAndTeam(t *testing.T) {
return nil, 0, nil, nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
globalFile, err := os.CreateTemp(t.TempDir(), "*.yml")
require.NoError(t, err)
@ -1345,6 +1392,18 @@ func TestGitOpsBasicGlobalAndNoTeam(t *testing.T) {
return nil, 0, nil, nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
globalFileBasic, err := os.CreateTemp(t.TempDir(), "*.yml")
require.NoError(t, err)
@ -1604,6 +1663,18 @@ func TestGitOpsFullGlobalAndTeam(t *testing.T) {
return team, nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
apnsCert, apnsKey, err := mysql.GenerateTestCertBytes()
require.NoError(t, err)
crt, key, err := apple_mdm.NewSCEPCACertKey()
@ -1656,7 +1727,7 @@ func TestGitOpsTeamSofwareInstallers(t *testing.T) {
wantErr string
}{
{"testdata/gitops/team_software_installer_not_found.yml", "Please make sure that URLs are reachable from your Fleet server."},
{"testdata/gitops/team_software_installer_unsupported.yml", "The file should be .pkg, .msi, .exe or .deb."},
{"testdata/gitops/team_software_installer_unsupported.yml", "The file should be .pkg, .msi, .exe, .deb or .rpm."},
{"testdata/gitops/team_software_installer_too_large.yml", "The maximum file size is 500 MiB"},
{"testdata/gitops/team_software_installer_valid.yml", ""},
{"testdata/gitops/team_software_installer_valid_apply.yml", ""},
@ -1711,7 +1782,7 @@ func TestGitOpsNoTeamSoftwareInstallers(t *testing.T) {
wantErr string
}{
{"testdata/gitops/no_team_software_installer_not_found.yml", "Please make sure that URLs are reachable from your Fleet server."},
{"testdata/gitops/no_team_software_installer_unsupported.yml", "The file should be .pkg, .msi, .exe or .deb."},
{"testdata/gitops/no_team_software_installer_unsupported.yml", "The file should be .pkg, .msi, .exe, .deb or .rpm."},
{"testdata/gitops/no_team_software_installer_too_large.yml", "The maximum file size is 500 MiB"},
{"testdata/gitops/no_team_software_installer_valid.yml", ""},
{"testdata/gitops/no_team_software_installer_pre_condition_multiple_queries.yml", "should have only one query."},
@ -2234,6 +2305,15 @@ func setupFullGitOpsPremiumServer(t *testing.T) (*mock.Store, **fleet.AppConfig,
ds.ListSoftwareTitlesFunc = func(ctx context.Context, opt fleet.SoftwareTitleListOptions, tmFilter fleet.TeamFilter) ([]fleet.SoftwareTitleListResult, int, *fleet.PaginationMetadata, error) {
return nil, 0, nil, nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
t.Setenv("FLEET_SERVER_URL", fleetServerURL)
t.Setenv("ORG_NAME", orgName)

View file

@ -354,7 +354,9 @@ software:
- `app_store_id` is the ID of the Apple App Store app. You can find this at the end of the app's App Store URL. For example, "Bear - Markdown Notes" URL is "https://apps.apple.com/us/app/bear-markdown-notes/id1016366447" and the `app_store_id` is `1016366447`.
> Make sure to include only the ID itself, and not the `id` prefix shown in the URL. The ID must be wrapped in quotes as shown in the example so that it is processed as a string.
> Make sure to include only the ID itself, and not the `id` prefix shown in the URL. The ID must be wrapped in quotes as shown in the example so that it is processed as a string.
`self_service` only applies to macOS, and is ignored for other platforms. For example, if the app is supported on macOS, iOS, and iPadOS, and `self_service` is set to `true`, it will be self-service on macOS workstations but not iPhones or iPads.
##### Separate file

View file

@ -541,6 +541,10 @@ The MDM endpoints exist to support the related command-line interface sub-comman
- [Renew VPP token](#renew-vpp-token)
- [Delete VPP token](#delete-vpp-token)
- [Batch-apply MDM custom settings](#batch-apply-mdm-custom-settings)
- [Batch-apply packages](#batch-apply-packages)
- [Batch-apply App Store apps](#batch-apply-app-store-apps)
- [Get token to download package](#get-token-to-download-package)
- [Download package using a token](#download-package-using-a-token)
- [Initiate SSO during DEP enrollment](#initiate-sso-during-dep-enrollment)
- [Complete SSO during DEP enrollment](#complete-sso-during-dep-enrollment)
- [Over the air enrollment](#over-the-air-enrollment)
@ -549,7 +553,6 @@ The MDM endpoints exist to support the related command-line interface sub-comman
- [Get FileVault statistics](#get-filevault-statistics)
- [Upload VPP content token](#upload-vpp-content-token)
- [Disable VPP](#disable-vpp)
- [Get an over the air (OTA) enrollment profile](#get-an-over-the-air-ota-enrollment-profile)
### Generate Apple Business Manager public key (ADE)
@ -1744,7 +1747,7 @@ If the `name` is not already associated with an existing team, this API route cr
| scripts | list | body | A list of script files to add to this team so they can be executed at a later time. |
| software | object | body | The team's software that will be available for install. |
| software.packages | list | body | An array of objects. Each object consists of:`url`- URL to the software package (PKG, MSI, EXE or DEB),`install_script` - command that Fleet runs to install software, `pre_install_query` - condition query that determines if the install will proceed, `post_install_script` - script that runs after software install, and `self_service` boolean. |
| software.app_store_apps | list | body | An array objects. Each object consists of `app_store_id` - ID of the App Store app formatted as a string (in quotes) rather than a number. |
| software.app_store_apps | list | body | An array of objects. Each object consists of `app_store_id` - ID of the App Store app and `self_service` boolean. |
| mdm.macos_settings.enable_disk_encryption | bool | body | Whether disk encryption should be enabled for hosts that belong to this team. |
| force | bool | query | Force apply the spec even if there are (ignorable) validation errors. Those are unknown keys and agent options-related validations. |
| dry_run | bool | query | Validate the provided JSON for unknown keys and invalid value types and return any validation errors, but do not apply the changes. |
@ -1837,6 +1840,7 @@ If the `name` is not already associated with an existing team, this API route cr
"app_store_apps": [
{
"app_store_id": "12464567",
"self_service": true
}
]
}
@ -3377,3 +3381,187 @@ Run a live script and get results back (5 minute timeout). Live scripts only run
"exit_code": 0
}
```
## Software
### Batch-apply software
_Available in Fleet Premium._
`POST /api/v1/fleet/software/batch`
This endpoint is asynchronous, meaning it will start a background process to download and apply the software and return a `request_uuid` in the JSON response that can be used to query the status of the batch-apply (using the `GET /api/v1/fleet/software/batch/:request_uuid` endpoint defined below).
#### Parameters
| Name | Type | In | Description |
| --------- | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| team_name | string | query | The name of the team to add the software package to. Ommitting these parameters will add software to 'No Team'. |
| dry_run | bool | query | If `true`, will validate the provided software packages and return any validation errors, but will not apply the changes. |
| software | object | body | The team's software that will be available for install. |
| software.packages | list | body | An array of objects. Each object consists of:`url`- URL to the software package (PKG, MSI, EXE or DEB),`install_script` - command that Fleet runs to install software, `pre_install_query` - condition query that determines if the install will proceed, `post_install_script` - script that runs after software install, and `uninstall_script` - command that Fleet runs to uninstall software. |
#### Example
`POST /api/v1/fleet/software/batch`
##### Default response
`Status: 200`
```json
{
"request_uuid": "ec23c7b6-c336-4109-b89d-6afd859659b4",
}
```
### Get status of software batch-apply request
_Available in Fleet Premium._
`GET /api/v1/fleet/software/batch/:request_uuid`
This endpoint allows querying the status of a batch-apply software request (`POST /api/v1/fleet/software/batch`).
Returns `"status"` field that can be one of `"processing"`, `"complete"` or `"failed"`.
If `"status"` is `"completed"` then the `"packages"` field contains the applied packages.
If `"status"` is `"processing"` then the operation is ongoing and the request should be retried.
If `"status"` is `"failed"` then the `"message"` field contains the error message.
#### Parameters
| Name | Type | In | Description |
| ------------ | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| request_uuid | string | query | The request_uuid returned by the `POST /api/v1/fleet/software/batch` endpoint. |
| team_name | string | query | The name of the team to add the software package to. Ommitting these parameters will add software to 'No Team'. |
| dry_run | bool | query | If `true`, will validate the provided software packages and return any validation errors, but will not apply the changes. |
##### Default responses
`Status: 200`
```json
{
"status": "processing",
"message": "",
"packages": null
}
```
`Status: 200`
```json
{
"status": "completed",
"message": "",
"packages": [
{
"team_id": 1,
"title_id": 2751,
"url": "https://ftp.mozilla.org/pub/firefox/releases/129.0.2/win64/en-US/Firefox%20Setup%20129.0.2.msi"
}
]
}
```
`Status: 200`
```json
{
"status": "failed",
"message": "validation failed: software.url Couldn't edit software. URL (\"https://foobar.does.not.exist.com\") returned \"Not Found\". Please make sure that URLs are reachable from your Fleet server.",
"packages": null
}
```
### Batch-apply App Store apps
_Available in Fleet Premium._
`POST /api/latest/fleet/software/app_store_apps/batch`
#### Parameters
| Name | Type | In | Description |
| --------- | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| team_name | string | query | The name of the team to add the software package to. Ommitting this parameter will add software to "No team". |
| dry_run | bool | query | If `true`, will validate the provided VPP apps and return any validation errors, but will not apply the changes. |
| app_store_apps | list | body | An array of objects. Each object contains `app_store_id` and `self_service`. |
| app_store_apps.app_store_id | string | body | ID of the App Store app. |
| app_store_apps.self_service | boolean | body | Whether the VPP app is "Self-service" or not. |
#### Example
`POST /api/latest/fleet/software/app_store_apps/batch`
```json
{
"team_name": "Foobar",
"app_store_apps": {
{
"app_store_id": "597799333",
"self_service": false,
},
{
"app_store_id": "497799835",
"self_service": true,
}
}
}
```
##### Default response
`Status: 204`
### Get token to download package
_Available in Fleet Premium._
`POST /api/v1/fleet/software/titles/:software_title_id/package/token?alt=media`
The returned token is a one-time use token that expires after 10 minutes.
#### Parameters
| Name | Type | In | Description |
|-------------------|---------|-------|------------------------------------------------------------------|
| software_title_id | integer | path | **Required**. The ID of the software title for software package. |
| team_id | integer | query | **Required**. The team ID containing the software package. |
| alt | integer | query | **Required**. Must be specified and set to "media". |
#### Example
`POST /api/v1/fleet/software/titles/123/package/token?alt=media&team_id=2`
##### Default response
`Status: 200`
```json
{
"token": "e905e33e-07fe-4f82-889c-4848ed7eecb7"
}
```
### Download package using a token
_Available in Fleet Premium._
`GET /api/v1/fleet/software/titles/:software_title_id/package/token/:token?alt=media`
#### Parameters
| Name | Type | In | Description |
|-------------------|---------|------|--------------------------------------------------------------------------|
| software_title_id | integer | path | **Required**. The ID of the software title to download software package. |
| token | string | path | **Required**. The token to download the software package. |
#### Example
`GET /api/v1/fleet/software/titles/123/package/token/e905e33e-07fe-4f82-889c-4848ed7eecb7`
##### Default response
`Status: 200`
```http
Status: 200
Content-Type: application/octet-stream
Content-Disposition: attachment
Content-Length: <length>
Body: <blob>
```

View file

@ -4,9 +4,9 @@ Fleet is an open-source device management platform for Linux, macOS, Windows, Ch
## What's it for?
Managing computers today is getting harder. You have to juggle a mix of operating systems and devices, with a whole bunch of middleman vendors in between.
Managing computers today is getting harder. You have to juggle a mix of operating systems and devices, with a bunch of middleman vendors in between.
Fleet makes things easier by giving you a single system to manage and secure all your computing devices. You can do MDM, patch stuff, and verify anything—all from one dashboard. It's like having a universal remote control for all your organization's computers.
Fleet makes things easier by giving you a single system to secure and maintain all your computing devices over the air. You can do MDM, patch stuff, and verify anything—all from one system. It's like having a universal remote control for all your organization's computers.
Fleet is open source, and free features will always be free.
@ -15,7 +15,7 @@ Fleet is open source, and free features will always be free.
Fleet is used in production by IT and security teams with thousands of laptops and servers. Many deployments support tens of thousands of hosts, and a few large organizations manage deployments as large as 400,000+ hosts.
- **Get what you need:** Fleet lets you work directly with [data](https://fleetdm.com/integrations) and events from the native operating system. It lets you go all the way down to the bare metal. Its also modular. (You can turn off features you are not using.)
- **Out of the box:** Ready-to-use integrations exist for the [most common tools](https://fleetdm.com/integrations). You can also build custom workflows with the REST API, webhook events, and the fleetctl command line tool. Or go all in and manage computers [with GitOps](https://fleetdm.com/handbook/company#history).
- **Out of the box:** Ready-to-use integrations exist for the [most common tools](https://fleetdm.com/integrations). You can also build custom workflows with the REST API, webhook events, and the fleetctl command line tool. Or go all in and govern computers [with GitOps](https://github.com/fleetdm/fleet-gitops).
- **Good neighbors:** We think tools should be as easy as possible for everyone to understand. We helped [create osquery](https://fleetdm.com/handbook/company#history), and we are committed to improving it.
- **Free as in free:** The free version of Fleet will [always be free](https://fleetdm.com/pricing). Fleet is independently backed and actively maintained with the help of many amazing contributors.

View file

@ -4295,8 +4295,10 @@ Resends a configuration profile for the specified host.
"name": "Logic Pro",
"software_package": null
"app_store_app": {
"app_store_id": "1091189122"
"app_store_id": "1091189122",
"icon_url": "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/f4/25/1f/f4251f60-e27a-6f05-daa7-9f3a63aac929/AppIcon-0-0-85-220-0-0-4-0-0-2x-0-0-0-0-0.png/512x512bb.png"
"version": "2.04",
"self_service": false,
"last_install": {
"command_uuid": "0aa14ae5-58fe-491a-ac9a-e4ee2b3aac40",
"installed_at": "2024-05-15T15:23:57Z"
@ -5573,6 +5575,7 @@ solely on the response status code returned by this endpoint.
```
###### Example response body
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@ -5720,6 +5723,7 @@ Get aggregate status counts of profiles for to macOS and Windows hosts that are
- [Set custom MDM setup enrollment profile](#set-custom-mdm-setup-enrollment-profile)
- [Get custom MDM setup enrollment profile](#get-custom-mdm-setup-enrollment-profile)
- [Delete custom MDM setup enrollment profile](#delete-custom-mdm-setup-enrollment-profile)
- [Get Over-the-Air (OTA) enrollment profile](#get-over-the-air-ota-enrollment-profile)
- [Get manual enrollment profile](#get-manual-enrollment-profile)
- [Upload a bootstrap package](#upload-a-bootstrap-package)
- [Get metadata about a bootstrap package](#get-metadata-about-a-bootstrap-package)
@ -5827,10 +5831,83 @@ Deletes the custom MDM setup enrollment profile assigned to a team or no team.
`Status: 204`
### Get Over-the-Air (OTA) enrollment profile
`GET /api/v1/fleet/enrollment_profiles/ota`
The returned value is a signed `.mobileconfig` OTA enrollment profile. Install this profile on macOS, iOS, or iPadOS hosts to enroll them to a specific team in Fleet and turn on MDM features.
To enroll macOS hosts, turn on MDM features, and add [human-device mapping](#get-human-device-mapping), install the [manual enrollment profile](#get-manual-enrollment-profile) instead.
Learn more about OTA profiles [here](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/iPhoneOTAConfiguration/OTASecurity/OTASecurity.html).
#### Parameters
| Name | Type | In | Description |
|-------------------|---------|-------|----------------------------------------------------------------------------------|
| enroll_secret | string | query | **Required**. The enroll secret of the team this host will be assigned to. |
#### Example
`GET /api/v1/fleet/enrollment_profiles/ota?enroll_secret=foobar`
##### Default response
`Status: 200`
> **Note:** To confirm success, it is important for clients to match content length with the response header (this is done automatically by most clients, including the browser) rather than relying solely on the response status code returned by this endpoint.
##### Example response headers
```http
Content-Length: 542
Content-Type: application/x-apple-aspen-config; charset=urf-8
Content-Disposition: attachment;filename="fleet-mdm-enrollment-profile.mobileconfig"
X-Content-Type-Options: nosniff
```
###### Example response body
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Inc//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<dict>
<key>URL</key>
<string>https://foo.example.com/api/fleet/ota_enrollment?enroll_secret=foobar</string>
<key>DeviceAttributes</key>
<array>
<string>UDID</string>
<string>VERSION</string>
<string>PRODUCT</string>
<string>SERIAL</string>
</array>
</dict>
<key>PayloadOrganization</key>
<string>Acme Inc.</string>
<key>PayloadDisplayName</key>
<string>Acme Inc. enrollment</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadUUID</key>
<string>fdb376e5-b5bb-4d8c-829e-e90865f990c9</string>
<key>PayloadIdentifier</key>
<string>com.fleetdm.fleet.mdm.apple.ota</string>
<key>PayloadType</key>
<string>Profile Service</string>
</dict>
</plist>
```
### Get manual enrollment profile
Retrieves an unsigned manual enrollment profile for macOS hosts. Install this profile on macOS hosts to turn on MDM features manually.
To add [human-device mapping](#get-human-device-mapping), add the end user's email to the enrollment profle. Learn how [here](https://fleetdm.com/guides/config-less-fleetd-agent-deployment#basic-article).
`GET /api/v1/fleet/enrollment_profiles/manual`
##### Example
@ -6442,7 +6519,8 @@ None.
]
```
Get Volume Purchasing Program (VPP)
### Get Volume Purchasing Program (VPP)
> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
@ -8652,10 +8730,6 @@ Deletes the session specified by ID. When the user associated with the session n
- [Get package install result](#get-package-install-result)
- [Download package](#download-package)
- [Delete package or App Store app](#delete-package-or-app-store-app)
- [Batch-apply software](#batch-apply-software)
- [Batch-apply app store apps](#batch-apply-app-store-apps)
- [Get token to download package](#get-token-to-download-package)
- [Download package using a token](#download-package-using-a-token)
### List software
@ -9026,9 +9100,10 @@ Returns information about the specified software. By default, `versions` are sor
"software_package": null,
"app_store_app": {
"name": "Logic Pro",
"app_store_id": "1091189122",
"app_store_id": 1091189122,
"latest_version": "2.04",
"icon_url": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/f1/65/1e/a4844ccd-486d-455f-bb31-67336fe46b14/AppIcon-1x_U007emarketing-0-7-0-85-220-0.png/512x512bb.jpg",
"self_service": true,
"status": {
"installed": 3,
"pending": 1,
@ -9107,6 +9182,7 @@ Returns information about the specified software version.
}
```
### Get operating system version
Retrieves information about the specified operating system (OS) version.
@ -9379,6 +9455,7 @@ Add App Store (VPP) app purchased in Apple Business Manager.
| app_store_id | string | body | **Required.** The ID of App Store app. |
| team_id | integer | body | **Required**. The team ID. Adds VPP software to the specified team. |
| platform | string | body | The platform of the app (`darwin`, `ios`, or `ipados`). Default is `darwin`. |
| self_service | boolean | body | Self-service software is optional and can be installed by the end user. |
#### Example
@ -9391,6 +9468,7 @@ Add App Store (VPP) app purchased in Apple Business Manager.
"app_store_id": "497799835",
"team_id": 2,
"platform": "ipados"
"self_service": true
}
```
@ -9398,38 +9476,6 @@ Add App Store (VPP) app purchased in Apple Business Manager.
`Status: 200`
### Download package
> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
_Available in Fleet Premium._
`GET /api/v1/fleet/software/titles/:software_title_id/package?alt=media`
#### Parameters
| Name | Type | In | Description |
| ---- | ------- | ---- | -------------------------------------------- |
| software_title_id | integer | path | **Required**. The ID of the software title to download software package.|
| team_id | integer | query | **Required**. The team ID. Downloads a software package added to the specified team. |
| alt | integer | query | **Required**. If specified and set to "media", downloads the specified software package. |
#### Example
`GET /api/v1/fleet/software/titles/123/package?alt=media?team_id=2`
##### Default response
`Status: 200`
```http
Status: 200
Content-Type: application/octet-stream
Content-Disposition: attachment
Content-Length: <length>
Body: <blob>
```
### Install package or App Store app
> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
@ -9487,7 +9533,7 @@ _Available in Fleet Premium._
`GET /api/v1/fleet/software/install/:install_uuid/results`
Get the results of a software package install.
Get the results of a software package install.
To get the results of an App Store app install, use the [List MDM commands](#list-mdm-commands) and [Get MDM command results](#get-mdm-command-results) API enpoints. Fleet uses an MDM command to install App Store apps.
@ -9518,6 +9564,38 @@ To get the results of an App Store app install, use the [List MDM commands](#lis
}
```
### Download package
> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
_Available in Fleet Premium._
`GET /api/v1/fleet/software/titles/:software_title_id/package?alt=media`
#### Parameters
| Name | Type | In | Description |
| ---- | ------- | ---- | -------------------------------------------- |
| software_title_id | integer | path | **Required**. The ID of the software title to download software package.|
| team_id | integer | query | **Required**. The team ID. Downloads a software package added to the specified team. |
| alt | integer | query | **Required**. If specified and set to "media", downloads the specified software package. |
#### Example
`GET /api/v1/fleet/software/titles/123/package?alt=media?team_id=2`
##### Default response
`Status: 200`
```http
Status: 200
Content-Type: application/octet-stream
Content-Disposition: attachment
Content-Length: <length>
Body: <blob>
```
### Delete package or App Store app
> **Experimental feature**. This feature is undergoing rapid improvement, which may result in breaking changes to the API or configuration surface. It is not recommended for use in automated workflows.
@ -9543,117 +9621,6 @@ Deletes software that's available for install (package or App Store app).
`Status: 204`
### Batch-apply software
_Available in Fleet Premium._
`POST /api/v1/fleet/software/batch`
#### Parameters
| Name | Type | In | Description |
| --------- | ------ | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| team_id | number | query | The ID of the team to add the software package to. Only one team identifier (`team_id` or `team_name`) can be included in the request; omit this parameter if using `team_name`. Omitting these parameters will add software to "No Team". |
| team_name | string | query | The name of the team to add the software package to. Only one team identifier (`team_id` or `team_name`) can be included in the request; omit this parameter if using `team_id`. Omitting these parameters will add software to "No Team". |
| dry_run | bool | query | If `true`, will validate the provided software packages and return any validation errors, but will not apply the changes. |
| software | object | body | The team's software that will be available for install. |
| software.packages | list | body | An array of objects. Each object consists of:`url`- URL to the software package (PKG, MSI, EXE or DEB),`install_script` - command that Fleet runs to install software, `pre_install_query` - condition query that determines if the install will proceed, `post_install_script` - script that runs after software install, and `uninstall_script` - command that Fleet runs to uninstall software. |
| software.app_store_apps | list | body | An array objects. Each object consists of `app_store_id` - ID of the App Store app. |
If both `team_id` and `team_name` parameters are included, this endpoint will respond with an error. If no `team_name` or `team_id` is provided, the scripts will be applied for **all hosts**.
#### Example
`POST /api/v1/fleet/software/batch`
##### Default response
`Status: 204`
### Batch-apply app store apps
_Available in Fleet Premium._
`POST /api/v1/fleet/software/app_store_apps/batch`
#### Parameters
| Name | Type | In | Description |
|-----------------|---------|-------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| team_name | integer | query | **Required**. The name of the team to add the app to. |
| dry_run | bool | query | If `true`, will validate the provided apps and return any validation errors, but will not apply the changes. |
| apps_store_apps | list | body | The list of objects containing `app_store_id`: a string representation of the app's App ID, `self_service`: a bool indicating if the app's installation can be initiated by end users. |
> Note that this endpoint replaces all apps associated with a team.
#### Example
`POST /api/v1/fleet/software/app_store_apps/batch`
#### Default response
`Status: 204`
### Get token to download package
_Available in Fleet Premium._
`POST /api/v1/fleet/software/titles/:software_title_id/package/token?alt=media`
The returned token is a one-time use token that expires after 10 minutes.
#### Parameters
| Name | Type | In | Description |
|-------------------|---------|-------|------------------------------------------------------------------|
| software_title_id | integer | path | **Required**. The ID of the software title for software package. |
| team_id | integer | query | **Required**. The team ID containing the software package. |
| alt | integer | query | **Required**. Must be specified and set to "media". |
#### Example
`POST /api/v1/fleet/software/titles/123/package/token?alt=media&team_id=2`
##### Default response
`Status: 200`
```json
{
"token": "e905e33e-07fe-4f82-889c-4848ed7eecb7"
}
```
### Download package using a token
_Available in Fleet Premium._
`GET /api/v1/fleet/software/titles/:software_title_id/package/token/:token?alt=media`
#### Parameters
| Name | Type | In | Description |
|-------------------|---------|------|--------------------------------------------------------------------------|
| software_title_id | integer | path | **Required**. The ID of the software title to download software package. |
| token | string | path | **Required**. The token to download the software package. |
#### Example
`GET /api/v1/fleet/software/titles/123/package/token/e905e33e-07fe-4f82-889c-4848ed7eecb7`
##### Default response
`Status: 200`
```http
Status: 200
Content-Type: application/octet-stream
Content-Disposition: attachment
Content-Length: <length>
Body: <blob>
```
## Vulnerabilities
- [List vulnerabilities](#list-vulnerabilities)

View file

@ -1054,7 +1054,7 @@ func (svc *Service) addMetadataToSoftwarePayload(ctx context.Context, payload *f
if err != nil {
if errors.Is(err, file.ErrUnsupportedType) {
return "", &fleet.BadRequestError{
Message: "Couldn't edit software. File type not supported. The file should be .pkg, .msi, .exe or .deb.",
Message: "Couldn't edit software. File type not supported. The file should be .pkg, .msi, .exe, .deb or .rpm.",
InternalErr: ctxerr.Wrap(ctx, err, "extracting metadata from installer"),
}
}
@ -1517,7 +1517,7 @@ func packageExtensionToPlatform(ext string) string {
requiredPlatform = "windows"
case ".pkg":
requiredPlatform = "darwin"
case ".deb":
case ".deb", ".rpm":
requiredPlatform = "linux"
default:
return ""

View file

@ -1,4 +1,4 @@
const unixPackageTypes = ["pkg", "deb"] as const;
const unixPackageTypes = ["pkg", "deb", "rpm"] as const;
const windowsPackageTypes = ["msi", "exe"] as const;
export const packageTypes = [
...unixPackageTypes,

View file

@ -270,7 +270,7 @@ export interface ISoftwareInstallResults {
// ISoftwareInstallerType defines the supported installer types for
// software uploaded by the IT admin.
export type ISoftwareInstallerType = "pkg" | "msi" | "deb" | "exe";
export type ISoftwareInstallerType = "pkg" | "msi" | "deb" | "rpm" | "exe";
export interface ISoftwareLastInstall {
install_uuid: string;

View file

@ -23,6 +23,7 @@ const getSupportedScriptTypeText = (pkgType: PackageType) => {
const PKG_TYPE_TO_ID_TEXT = {
pkg: "package IDs",
deb: "package name",
rpm: "package name",
msi: "product code",
exe: "software name",
} as const;

View file

@ -58,7 +58,7 @@ interface IPackageFormProps {
defaultSelfService?: boolean;
}
const ACCEPTED_EXTENSIONS = ".pkg,.msi,.exe,.deb";
const ACCEPTED_EXTENSIONS = ".pkg,.msi,.exe,.deb,.rpm";
const PackageForm = ({
isUploading,
@ -173,7 +173,7 @@ const PackageForm = ({
canEdit={isEditingSoftware}
graphicName={"file-pkg"}
accept={ACCEPTED_EXTENSIONS}
message=".pkg, .msi, .exe, or .deb"
message=".pkg, .msi, .exe, .deb, or .rpm"
onFileUpload={onFileSelect}
buttonMessage="Choose file"
buttonType="link"

View file

@ -17,7 +17,7 @@ const generateCell = (teams: ITokenTeam[] | null) => {
}
if (teams.length === 0) {
return <TextCell value="All Teams" />;
return <TextCell value="All teams" />;
}
let text = "";
@ -83,7 +83,7 @@ const TeamsCell = ({ teams, className }: ITeamsCellProps) => {
}
if (teams.length === 0) {
return <TextCell value="All Teams" />;
return <TextCell value="All teams" />;
}
if (teams.length === 1) {

View file

@ -28,6 +28,7 @@ const getPlatformDisplayFromPackageSuffix = (packageName: string) => {
case "pkg":
return "macOS";
case "deb":
case "rpm":
return "Linux";
case "exe":
return "Windows";

View file

@ -2,15 +2,22 @@ import { getPlatformDisplayName } from "./fileUtils";
describe("fileUtils", () => {
describe("getPlatformDisplayName", () => {
it("should return the correct platform display name depending on the file extension", () => {
const file = new File([""], "test.pkg");
expect(getPlatformDisplayName(file)).toEqual("macOS");
const testCases = [
{ extension: "pkg", platform: "macOS" },
{ extension: "json", platform: "macOS" },
{ extension: "mobileconfig", platform: "macOS" },
{ extension: "exe", platform: "Windows" },
{ extension: "msi", platform: "Windows" },
{ extension: "xml", platform: "Windows" },
{ extension: "deb", platform: "Linux" },
{ extension: "rpm", platform: "Linux" },
];
const file2 = new File([""], "test.exe");
expect(getPlatformDisplayName(file2)).toEqual("Windows");
const file3 = new File([""], "test.deb");
expect(getPlatformDisplayName(file3)).toEqual("linux");
testCases.forEach(({ extension, platform }) => {
it(`should return ${platform} for .${extension} files`, () => {
const file = new File([""], `test.${extension}`);
expect(getPlatformDisplayName(file)).toEqual(platform);
});
});
});
});

View file

@ -1,4 +1,4 @@
type IPlatformDisplayName = "macOS" | "Windows" | "linux";
type IPlatformDisplayName = "macOS" | "Windows" | "Linux";
const getFileExtension = (file: File) => {
const nameParts = file.name.split(".");
@ -15,7 +15,8 @@ export const FILE_EXTENSIONS_TO_PLATFORM_DISPLAY_NAME: Record<
exe: "Windows",
msi: "Windows",
xml: "Windows",
deb: "linux",
deb: "Linux",
rpm: "Linux",
};
/**

View file

@ -6,6 +6,8 @@ import installMsi from "../../pkg/file/scripts/install_msi.ps1";
import installExe from "../../pkg/file/scripts/install_exe.ps1";
// @ts-ignore
import installDeb from "../../pkg/file/scripts/install_deb.sh";
// @ts-ignore
import installRPM from "../../pkg/file/scripts/install_rpm.sh";
/*
* getInstallScript returns a string with a script to install the
@ -20,6 +22,8 @@ const getDefaultInstallScript = (fileName: string): string => {
return installMsi;
case "deb":
return installDeb;
case "rpm":
return installRPM;
case "exe":
return installExe;
default:

View file

@ -6,6 +6,8 @@ import uninstallMsi from "../../pkg/file/scripts/uninstall_msi.ps1";
import uninstallExe from "../../pkg/file/scripts/uninstall_exe.ps1";
// @ts-ignore
import uninstallDeb from "../../pkg/file/scripts/uninstall_deb.sh";
// @ts-ignore
import uninstallRPM from "../../pkg/file/scripts/uninstall_rpm.sh";
/*
* getUninstallScript returns a string with a script to uninstall the
@ -20,6 +22,8 @@ const getDefaultUninstallScript = (fileName: string): string => {
return uninstallMsi;
case "deb":
return uninstallDeb;
case "rpm":
return uninstallRPM;
case "exe":
return uninstallExe;
default:

2
go.mod
View file

@ -199,6 +199,8 @@ require (
github.com/caarlos0/env/v6 v6.7.0 // indirect
github.com/caarlos0/go-shellwords v1.0.12 // indirect
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e // indirect
github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/cavaliergopher/rpm v1.2.0 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cloudflare/circl v1.3.8 // indirect

4
go.sum
View file

@ -320,6 +320,10 @@ github.com/caarlos0/testfs v0.4.3 h1:q1zEM5hgsssqWanAfevJYYa0So60DdK6wlJeTc/yfUE
github.com/caarlos0/testfs v0.4.3/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e h1:hHg27A0RSSp2Om9lubZpiMgVbvn39bsUmW9U5h0twqc=
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
github.com/cavaliergopher/rpm v1.2.0 h1:s0h+QeVK252QFTolkhGiMeQ1f+tMeIMhGl8B1HUmGUc=
github.com/cavaliergopher/rpm v1.2.0/go.mod h1:R0q3vTqa7RUvPofAZYrnjJ63hh2vngjFfphuXiExVos=
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=

View file

@ -415,7 +415,11 @@ Although it's sad to see someone go, Fleet understands that not everything is me
## Changing someone's position
From time to time, someone's job title changes. To do this, reach out to [Digital Experience](https://fleetdm.com/handbook/digital-experience).
From time to time, someone's job title changes. Use the following steps to change someone's position:
1. Create Slack channel: Create a private "#YYYY-change-title-for-xxxxxx" Slack channel (where "xxxxxx" is the Fleetie's name and YYYY is the current year) for discussion and invite the CEO and Head of Digital Experience.
2. At-mention the Head of Digital Experience in the new channel with any context regarding the title change. Share any related documents with the Head of Digital Experience and the CEO.
3. After getting approval from the [Head of People](https://fleetdm.com/handbook/digital-experience#team), the Digital Experience team will take the necessary steps to [change the fleetie's job title](https://fleetdm.com/handbook/digital-experience#change-a-fleeties-job-title).
<img width="384" alt="image" src="https://github.com/fleetdm/fleet/assets/618009/1e59ac8f-26d8-4f2f-9ff4-2e303753c910">

View file

@ -91,7 +91,8 @@ When a Fleetie, consultant or advisor requests an update to their personnel deta
### Change a Fleetie's job title
When Digital Experience receives notification of a Fleetie's job title changing, follow these steps to ensure accurate recording of the change across our systems.
1. Update ["🧑‍🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0):
1. The Head of Digital Experience will bring the proposed title change to the next Roundup meeting with the CEO for approval. If the proposed change is rejected, the Head of Digital Experience will inform the requesting manager as to why.
1. If approved, update ["🧑‍🚀 Fleeties"](https://docs.google.com/spreadsheets/d/1OSLn-ZCbGSjPusHPiR5dwQhheH1K8-xqyZdsOe9y7qc/edit#gid=0):
- Search the spreadsheet for the Fleetie in need of a job title change.
- Input the new job title in the Fleetie's row in the "Job title" cell.
- Navigate to the "Org chart" tab of the spreadsheet, and verify that the Fleetie's title appears correctly in the org chart.

View file

@ -137,6 +137,8 @@ Next, the API design DRI reviews all user stories and bugs with the release mile
To signal that the reference docs branch is ready for release, the API design DRI opens a PR to `main`, adds the DRI for what goes in a release as the reviewer, and adds the release milestone.
> Anytime there is a missing or incorrect configuration option or REST API endpoint in the docs, it is treated as a released bug to be filed and fixed ASAP.
### Interview a Product Designer candidate
Ensure the interview process follows these steps in order. This process must follow [creating a new position](https://fleetdm.com/handbook/company/leadership#creating-a-new-position) through [receiving job applications](https://fleetdm.com/handbook/company/leadership#receiving-job-applications).

View file

@ -56,7 +56,7 @@ variable "database_name" {
variable "fleet_image" {
description = "the name of the container image to run"
default = "fleetdm/fleet:v4.57.0"
default = "fleetdm/fleet:v4.57.1"
}
variable "software_inventory" {

View file

@ -68,7 +68,7 @@ variable "redis_mem" {
}
variable "image" {
default = "fleetdm/fleet:v4.57.0"
default = "fleetdm/fleet:v4.57.1"
}
variable "software_installers_bucket_name" {

View file

@ -2,7 +2,7 @@
"Type": "com.apple.configuration.passcode.settings",
"Identifier": "com.fleetdm.config.passcode.settings",
"Payload": {
"RequireComplexPasscode": true,
"RequireAlphanumericPasscode": true,
"MinimumLength": 10,
"MinimumComplexCharacters": 1,
"MaximumFailedAttempts": 11,

View file

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

View file

@ -22,8 +22,10 @@ import (
"github.com/rs/zerolog/log"
)
type QueryResponse = osquery_gen.ExtensionResponse
type QueryResponseStatus = osquery_gen.ExtensionStatus
type (
QueryResponse = osquery_gen.ExtensionResponse
QueryResponseStatus = osquery_gen.ExtensionStatus
)
// Client defines the methods required for the API requests to the server. The
// fleet.OrbitClient type satisfies this interface.
@ -202,7 +204,7 @@ func (r *Runner) installSoftware(ctx context.Context, installID string) (*fleet.
return payload, fmt.Errorf("creating temporary directory: %w", err)
}
log.Debug().Msgf("about to download software installer")
log.Debug().Str("install_id", installID).Msgf("about to download software installer")
installerPath, err := r.OrbitClient.DownloadSoftwareInstaller(installer.InstallerID, tmpDir)
if err != nil {
return payload, err
@ -233,7 +235,7 @@ func (r *Runner) installSoftware(ctx context.Context, installID string) (*fleet.
}
if installer.PostInstallScript != "" {
log.Debug().Msgf("about to run post-install script")
log.Debug().Msgf("about to run post-install script for %s", installerPath)
postOutput, postExitCode, postErr := r.runInstallerScript(ctx, installer.PostInstallScript, installerPath, "post-install-script"+scriptExtension)
payload.PostInstallScriptOutput = &postOutput
payload.PostInstallScriptExitCode = &postExitCode

View file

@ -42,6 +42,8 @@ func ExtractInstallerMetadata(r io.Reader) (*InstallerMetadata, error) {
switch extension {
case "deb":
meta, err = ExtractDebMetadata(br)
case "rpm":
meta, err = ExtractRPMMetadata(br)
case "exe":
meta, err = ExtractPEMetadata(br)
case "pkg":
@ -59,12 +61,16 @@ func ExtractInstallerMetadata(r io.Reader) (*InstallerMetadata, error) {
return meta, err
}
// typeFromBytes deduces the type from the magic bytes.
// See https://en.wikipedia.org/wiki/List_of_file_signatures.
func typeFromBytes(br *bufio.Reader) (string, error) {
switch {
case hasPrefix(br, []byte{0x78, 0x61, 0x72, 0x21}):
return "pkg", nil
case hasPrefix(br, []byte("!<arch>\ndebian")):
return "deb", nil
case hasPrefix(br, []byte{0xed, 0xab, 0xee, 0xdb}):
return "rpm", nil
case hasPrefix(br, []byte{0xd0, 0xcf}):
return "msi", nil
case hasPrefix(br, []byte("MZ")):

View file

@ -16,6 +16,9 @@ var installExeScript string
//go:embed scripts/install_deb.sh
var installDebScript string
//go:embed scripts/install_rpm.sh
var installRPMScript string
// GetInstallScript returns a script that can be used to install the given extension
func GetInstallScript(extension string) string {
switch extension {
@ -23,6 +26,8 @@ func GetInstallScript(extension string) string {
return installMsiScript
case "deb":
return installDebScript
case "rpm":
return installRPMScript
case "pkg":
return installPkgScript
case "exe":
@ -44,6 +49,9 @@ var removeMsiScript string
//go:embed scripts/remove_deb.sh
var removeDebScript string
//go:embed scripts/remove_rpm.sh
var removeRPMScript string
// GetRemoveScript returns a script that can be used to remove an
// installer with the given extension.
func GetRemoveScript(extension string) string {
@ -52,6 +60,8 @@ func GetRemoveScript(extension string) string {
return removeMsiScript
case "deb":
return removeDebScript
case "rpm":
return removeRPMScript
case "pkg":
return removePkgScript
case "exe":
@ -73,6 +83,9 @@ var uninstallMsiScript string
//go:embed scripts/uninstall_deb.sh
var uninstallDebScript string
//go:embed scripts/uninstall_rpm.sh
var uninstallRPMScript string
// GetUninstallScript returns a script that can be used to uninstall a
// software item with the given extension.
func GetUninstallScript(extension string) string {
@ -81,6 +94,8 @@ func GetUninstallScript(extension string) string {
return uninstallMsiScript
case "deb":
return uninstallDebScript
case "rpm":
return uninstallRPMScript
case "pkg":
return uninstallPkgScript
case "exe":

View file

@ -11,9 +11,7 @@ import (
"github.com/stretchr/testify/require"
)
var (
update = flag.Bool("update", false, "update the golden files of this test")
)
var update = flag.Bool("update", false, "update the golden files of this test")
func TestMain(m *testing.M) {
flag.Parse()
@ -41,6 +39,11 @@ func TestGetInstallAndRemoveScript(t *testing.T) {
"remove": "./scripts/remove_deb.sh",
"uninstall": "./scripts/uninstall_deb.sh",
},
"rpm": {
"install": "./scripts/install_rpm.sh",
"remove": "./scripts/remove_rpm.sh",
"uninstall": "./scripts/uninstall_rpm.sh",
},
"exe": {
"install": "./scripts/install_exe.ps1",
"remove": "./scripts/remove_exe.ps1",
@ -64,7 +67,7 @@ func assertGoldenMatches(t *testing.T, goldenFile string, actual string, update
t.Helper()
goldenPath := filepath.Join("testdata", goldenFile+".golden")
f, err := os.OpenFile(goldenPath, os.O_RDWR|os.O_CREATE, 0644)
f, err := os.OpenFile(goldenPath, os.O_RDWR|os.O_CREATE, 0o644)
require.NoError(t, err)
defer f.Close()

33
pkg/file/rpm.go Normal file
View file

@ -0,0 +1,33 @@
package file
import (
"crypto/sha256"
"fmt"
"io"
"github.com/cavaliergopher/rpm"
)
func ExtractRPMMetadata(r io.Reader) (*InstallerMetadata, error) {
h := sha256.New()
r = io.TeeReader(r, h)
// Read the package headers
pkg, err := rpm.Read(r)
if err != nil {
return nil, fmt.Errorf("read headers: %w", err)
}
// r is now positioned at the RPM payload.
// Ensure the whole file is read to get the correct hash
if _, err := io.Copy(io.Discard, r); err != nil {
return nil, fmt.Errorf("read all RPM content: %w", err)
}
return &InstallerMetadata{
Name: pkg.Name(),
Version: pkg.Version(),
SHASum: h.Sum(nil),
PackageIDs: []string{pkg.Name()},
}, nil
}

94
pkg/file/rpm_test.go Normal file
View file

@ -0,0 +1,94 @@
package file
import (
"crypto/sha256"
"io"
"os"
"path/filepath"
"testing"
"github.com/fleetdm/fleet/v4/orbit/pkg/constant"
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"
"github.com/goreleaser/nfpm/v2/rpm"
"github.com/stretchr/testify/require"
)
func TestExtractRPMMetadata(t *testing.T) {
//
// Build an RPM package on the fly with nfpm.
//
tmpDir := t.TempDir()
err := os.WriteFile(filepath.Join(tmpDir, "foo.sh"), []byte("#!/bin/sh\n\necho \"Foo!\"\n"), constant.DefaultFileMode)
require.NoError(t, err)
contents := files.Contents{
&files.Content{
Source: filepath.Join(tmpDir, "**"),
Destination: "/",
},
}
postInstallPath := filepath.Join(t.TempDir(), "postinstall.sh")
err = os.WriteFile(postInstallPath, []byte("#!/bin/sh\n\necho \"Hello world!\"\n"), constant.DefaultFileMode)
require.NoError(t, err)
info := &nfpm.Info{
Name: "foobar",
Version: "1.2.3",
Description: "Foo bar",
Arch: "x86_64",
Maintainer: "Fleet Device Management",
Vendor: "Fleet Device Management",
License: "LICENSE",
Homepage: "https://example.com",
Overridables: nfpm.Overridables{
Contents: contents,
Scripts: nfpm.Scripts{
PostInstall: postInstallPath,
},
},
}
rpmPath := filepath.Join(t.TempDir(), "foobar.rpm")
out, err := os.OpenFile(rpmPath, os.O_CREATE|os.O_RDWR, constant.DefaultFileMode)
require.NoError(t, err)
t.Cleanup(func() {
out.Close()
})
err = rpm.Default.Package(info, out)
require.NoError(t, err)
err = out.Close()
require.NoError(t, err)
//
// Test ExtractRPMMetadata with the generated package.
// Using ExtractInstallerMetadata for broader testing (for a file
// with rpm extension it will call ExtractRPMMetadata).
//
f, err := os.Open(rpmPath)
require.NoError(t, err)
t.Cleanup(func() {
f.Close()
})
m, err := ExtractInstallerMetadata(f)
require.NoError(t, err)
err = f.Close()
require.NoError(t, err)
require.Empty(t, m.BundleIdentifier)
require.Equal(t, "rpm", m.Extension)
require.Equal(t, "foobar", m.Name)
require.Equal(t, []string{"foobar"}, m.PackageIDs)
require.Equal(t, sha256FilePath(t, rpmPath), m.SHASum)
require.Equal(t, "1.2.3", m.Version)
}
func sha256FilePath(t *testing.T, path string) []byte {
f, err := os.Open(path)
require.NoError(t, err)
t.Cleanup(func() {
f.Close()
})
h := sha256.New()
_, err = io.Copy(h, f)
require.NoError(t, err)
err = f.Close()
require.NoError(t, err)
return h.Sum(nil)
}

View file

@ -3,10 +3,15 @@
This folder contains scripts to install/remove software for different types of installers.
Scripts are stored on their own files for two reasons:
1. Some of them are read and displayed in the UI.
2. It's helpful to have good syntax highlighting and easy ways to run them.
#### Scripts
- `install_*.*`: Default installer scripts for each platform.
- `uninstall_*.*`: Default uinstaller scripts for each platform.
- `remove_*.*`: Uninstaller scripts used when the uninstall script is not set (for packages added before the uninstall feature was released) or empty uninstaller scripts.
#### Variables
The scripts in this folder accept variables like `$VAR_NAME` that will be replaced/populated by `fleetd` when they run.
@ -14,4 +19,3 @@ The scripts in this folder accept variables like `$VAR_NAME` that will be replac
Supported variables are:
- `$INSTALLER_PATH` path to the installer file.

View file

@ -0,0 +1,3 @@
#!/bin/sh
dnf install --assumeyes "$INSTALLER_PATH"

View file

@ -0,0 +1,6 @@
#!/bin/sh
package_name=$PACKAGE_ID
# Fleet uninstalls app using product name that's extracted on upload
dnf remove --assumeyes "$package_name"

View file

@ -0,0 +1,4 @@
package_name=$PACKAGE_ID
# Fleet uninstalls app using product name that's extracted on upload
dnf remove --assumeyes "$package_name"

View file

@ -0,0 +1,3 @@
#!/bin/sh
dnf install --assumeyes "$INSTALLER_PATH"

View file

@ -0,0 +1,6 @@
#!/bin/sh
package_name=$PACKAGE_ID
# Fleet uninstalls app using product name that's extracted on upload
dnf remove --assumeyes "$package_name"

View file

@ -0,0 +1,4 @@
package_name=$PACKAGE_ID
# Fleet uninstalls app using product name that's extracted on upload
dnf remove --assumeyes "$package_name"

View file

@ -291,7 +291,9 @@ WHERE
}
func (ds *Datastore) DeleteMDMAppleConfigProfileByDeprecatedID(ctx context.Context, profileID uint) error {
return ds.deleteMDMAppleConfigProfileByIDOrUUID(ctx, profileID, "")
return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
return deleteMDMAppleConfigProfileByIDOrUUID(ctx, tx, profileID, "")
})
}
func (ds *Datastore) DeleteMDMAppleConfigProfile(ctx context.Context, profileUUID string) error {
@ -299,10 +301,20 @@ func (ds *Datastore) DeleteMDMAppleConfigProfile(ctx context.Context, profileUUI
if strings.HasPrefix(profileUUID, fleet.MDMAppleDeclarationUUIDPrefix) {
return ds.deleteMDMAppleDeclaration(ctx, profileUUID)
}
return ds.deleteMDMAppleConfigProfileByIDOrUUID(ctx, 0, profileUUID)
return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
if err := deleteMDMAppleConfigProfileByIDOrUUID(ctx, tx, 0, profileUUID); err != nil {
return err
}
if err := deleteUnsentAppleHostMDMProfile(ctx, tx, profileUUID); err != nil {
return err
}
return nil
})
}
func (ds *Datastore) deleteMDMAppleConfigProfileByIDOrUUID(ctx context.Context, id uint, uuid string) error {
func deleteMDMAppleConfigProfileByIDOrUUID(ctx context.Context, tx sqlx.ExtContext, id uint, uuid string) error {
var arg any
stmt := `DELETE FROM mdm_apple_configuration_profiles WHERE `
if uuid != "" {
@ -312,7 +324,7 @@ func (ds *Datastore) deleteMDMAppleConfigProfileByIDOrUUID(ctx context.Context,
arg = id
stmt += `profile_id = ?`
}
res, err := ds.writer(ctx).ExecContext(ctx, stmt, arg)
res, err := tx.ExecContext(ctx, stmt, arg)
if err != nil {
return ctxerr.Wrap(ctx, err)
}
@ -328,6 +340,15 @@ func (ds *Datastore) deleteMDMAppleConfigProfileByIDOrUUID(ctx context.Context,
return nil
}
func deleteUnsentAppleHostMDMProfile(ctx context.Context, tx sqlx.ExtContext, uuid string) error {
const stmt = `DELETE FROM host_mdm_apple_profiles WHERE profile_uuid = ? AND status IS NULL AND operation_type = ? AND command_uuid = ''`
if _, err := tx.ExecContext(ctx, stmt, uuid, fleet.MDMOperationTypeInstall); err != nil {
return ctxerr.Wrap(ctx, err, "deleting host profile that has not been sent to host")
}
return nil
}
func (ds *Datastore) DeleteMDMAppleDeclarationByName(ctx context.Context, teamID *uint, name string) error {
const stmt = `DELETE FROM mdm_apple_declarations WHERE team_id = ? AND name = ?`

View file

@ -557,6 +557,7 @@ SELECT
hsi.self_service,
hsi.host_deleted_at,
hsi.created_at as created_at,
hsi.updated_at as updated_at,
si.user_id AS software_installer_user_id,
si.user_name AS software_installer_user_name,
si.user_email AS software_installer_user_email

View file

@ -511,8 +511,18 @@ func testGetSoftwareInstallResult(t *testing.T, ds *Datastore) {
})
require.NoError(t, err)
beforeInstallRequest := time.Now()
installUUID, err := ds.InsertSoftwareInstallRequest(ctx, host.ID, installerID, false)
require.NoError(t, err)
res, err := ds.GetSoftwareInstallResults(ctx, installUUID)
require.NoError(t, err)
require.NotNil(t, res.UpdatedAt)
require.Less(t, beforeInstallRequest, res.CreatedAt)
createdAt := res.CreatedAt
require.Less(t, beforeInstallRequest, *res.UpdatedAt)
beforeInstallResult := time.Now()
err = ds.SetHostSoftwareInstallResult(ctx, &fleet.HostSoftwareInstallResultPayload{
HostID: host.ID,
InstallUUID: installUUID,
@ -524,7 +534,7 @@ func testGetSoftwareInstallResult(t *testing.T, ds *Datastore) {
})
require.NoError(t, err)
res, err := ds.GetSoftwareInstallResults(ctx, installUUID)
res, err = ds.GetSoftwareInstallResults(ctx, installUUID)
require.NoError(t, err)
require.Equal(t, installUUID, res.InstallUUID)
@ -534,6 +544,10 @@ func testGetSoftwareInstallResult(t *testing.T, ds *Datastore) {
require.Equal(t, tc.preInstallQueryOutput, res.PreInstallQueryOutput)
require.Equal(t, tc.postInstallScriptOutput, res.PostInstallScriptOutput)
require.Equal(t, tc.installScriptOutput, res.Output)
require.NotNil(t, res.CreatedAt)
require.Equal(t, createdAt, res.CreatedAt)
require.NotNil(t, res.UpdatedAt)
require.Less(t, beforeInstallResult, *res.UpdatedAt)
})
}
}

View file

@ -363,6 +363,8 @@ func SofwareInstallerSourceFromExtensionAndName(ext, name string) (string, error
switch ext {
case "deb":
return "deb_packages", nil
case "rpm":
return "rpm_packages", nil
case "exe", "msi":
return "programs", nil
case "pkg":
@ -378,7 +380,7 @@ func SofwareInstallerSourceFromExtensionAndName(ext, name string) (string, error
func SofwareInstallerPlatformFromExtension(ext string) (string, error) {
ext = strings.TrimPrefix(ext, ".")
switch ext {
case "deb":
case "deb", "rpm":
return "linux", nil
case "exe", "msi":
return "windows", nil

View file

@ -545,15 +545,55 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
}
}
if (appConfig.MDM.AppleBusinessManager.Set && appConfig.MDM.AppleBusinessManager.Valid) || appConfig.MDM.DeprecatedAppleBMDefaultTeam != "" {
for _, tok := range abmAssignments {
fmt.Println(tok.EncryptedToken)
// Reset teams for ABM tokens that exist in Fleet but aren't present in the config being passed
tokensInCfg := make(map[string]struct{})
for _, t := range newAppConfig.MDM.AppleBusinessManager.Value {
tokensInCfg[t.OrganizationName] = struct{}{}
}
toks, err := svc.ds.ListABMTokens(ctx)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "listing ABM tokens")
}
for _, tok := range toks {
if _, ok := tokensInCfg[tok.OrganizationName]; !ok {
tok.MacOSDefaultTeamID = nil
tok.IOSDefaultTeamID = nil
tok.IPadOSDefaultTeamID = nil
if err := svc.ds.SaveABMToken(ctx, tok); err != nil {
return nil, ctxerr.Wrap(ctx, err, "saving ABM token assignments")
}
}
}
if (appConfig.MDM.AppleBusinessManager.Set && appConfig.MDM.AppleBusinessManager.Valid) || appConfig.MDM.DeprecatedAppleBMDefaultTeam != "" {
for _, tok := range abmAssignments {
if err := svc.ds.SaveABMToken(ctx, tok); err != nil {
return nil, ctxerr.Wrap(ctx, err, "saving ABM token assignments")
}
}
}
// Reset teams for VPP tokens that exist in Fleet but aren't present in the config being passed
clear(tokensInCfg)
for _, t := range newAppConfig.MDM.VolumePurchasingProgram.Value {
tokensInCfg[t.Location] = struct{}{}
}
vppToks, err := svc.ds.ListVPPTokens(ctx)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "listing VPP tokens")
}
for _, tok := range vppToks {
if _, ok := tokensInCfg[tok.Location]; !ok {
tok.Teams = nil
if _, err := svc.ds.UpdateVPPTokenTeams(ctx, tok.ID, nil); err != nil {
return nil, ctxerr.Wrap(ctx, err, "saving VPP token teams")
}
}
}
if appConfig.MDM.VolumePurchasingProgram.Set && appConfig.MDM.VolumePurchasingProgram.Valid {
for tokenID, tokenTeams := range vppAssignments {
if _, err := svc.ds.UpdateVPPTokenTeams(ctx, tokenID, tokenTeams); err != nil {

View file

@ -51,6 +51,18 @@ func TestAppConfigAuth(t *testing.T) {
return nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
testCases := []struct {
name string
user *fleet.User
@ -647,6 +659,18 @@ func TestModifyAppConfigSMTPConfigured(t *testing.T) {
return nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
// Disable SMTP.
newAppConfig := fleet.AppConfig{
SMTPSettings: &fleet.SMTPSettings{
@ -751,6 +775,18 @@ func TestTransparencyURL(t *testing.T) {
return nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
ac, err := svc.AppConfigObfuscated(ctx)
require.NoError(t, err)
require.Equal(t, tt.initialURL, ac.FleetDesktop.TransparencyURL)
@ -800,6 +836,18 @@ func TestTransparencyURLDowngradeLicense(t *testing.T) {
return nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
ac, err := svc.AppConfigObfuscated(ctx)
require.NoError(t, err)
require.Equal(t, "https://example.com/transparency", ac.FleetDesktop.TransparencyURL)
@ -1090,6 +1138,15 @@ func TestMDMAppleConfig(t *testing.T) {
depStorage.StoreAssignerProfileFunc = func(ctx context.Context, name string, profileUUID string) error {
return nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{{OrganizationName: t.Name()}}, nil
}
ac, err := svc.AppConfigObfuscated(ctx)
require.NoError(t, err)
@ -1168,6 +1225,15 @@ func TestModifyAppConfigSMTPSSOAgentOptions(t *testing.T) {
) error {
return nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
// Not sending smtp_settings, sso_settings or agent_settings will do nothing.
b := []byte(`{}`)
@ -1297,6 +1363,18 @@ func TestModifyEnableAnalytics(t *testing.T) {
return nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
ac, err := svc.AppConfigObfuscated(ctx)
require.NoError(t, err)
require.Equal(t, tt.initialEnabled, ac.ServerSettings.EnableAnalytics)

View file

@ -770,9 +770,12 @@ func (svc *Service) DeleteMDMAppleConfigProfile(ctx context.Context, profileUUID
}
}
// This call will also delete host_mdm_apple_profiles references IFF the profile has not been sent to
// the host yet.
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); err != nil {
return ctxerr.Wrap(ctx, err, "bulk set pending host profiles")

View file

@ -14404,3 +14404,74 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallersWithoutBundleIden
}
s.uploadSoftwareInstaller(payload, http.StatusOK, "")
}
func (s *integrationEnterpriseTestSuite) TestSoftwareUploadRPM() {
ctx := context.Background()
t := s.T()
// Fedora and RHEL have hosts.platform = 'rhel'.
host := createOrbitEnrolledHost(t, "rhel", "", s.ds)
// Upload an RPM package.
payload := &fleet.UploadSoftwareInstallerPayload{
InstallScript: "install script",
PreInstallQuery: "pre install query",
PostInstallScript: "post install script",
Filename: "ruby.rpm",
Title: "ruby",
}
s.uploadSoftwareInstaller(payload, http.StatusOK, "")
titleID := getSoftwareTitleID(t, s.ds, payload.Title, "rpm_packages")
latestInstallUUID := func() string {
var id string
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
return sqlx.GetContext(ctx, q, &id, `SELECT execution_id FROM host_software_installs ORDER BY id DESC LIMIT 1`)
})
return id
}
// Send a request to the host to install the RPM package.
var installSoftwareResp installSoftwareResponse
beforeInstallRequest := time.Now()
s.DoJSON("POST", fmt.Sprintf("/api/v1/fleet/hosts/%d/software/%d/install", host.ID, titleID), nil, http.StatusAccepted, &installSoftwareResp)
installUUID := latestInstallUUID()
// Simulate host installing the RPM package.
beforeInstallResult := time.Now()
s.Do("POST", "/api/fleet/orbit/software_install/result",
json.RawMessage(fmt.Sprintf(`{
"orbit_node_key": %q,
"install_uuid": %q,
"pre_install_condition_output": "1",
"install_script_exit_code": 1,
"install_script_output": "failed"
}`, *host.OrbitNodeKey, installUUID)),
http.StatusNoContent,
)
var resp getSoftwareInstallResultsResponse
s.DoJSON("GET", fmt.Sprintf("/api/v1/fleet/software/install/%s/results", installUUID), nil, http.StatusOK, &resp)
assert.Equal(t, host.ID, resp.Results.HostID)
assert.Equal(t, installUUID, resp.Results.InstallUUID)
assert.Equal(t, fleet.SoftwareInstallFailed, resp.Results.Status)
assert.NotNil(t, resp.Results.PreInstallQueryOutput)
assert.Equal(t, fleet.SoftwareInstallerQuerySuccessCopy, *resp.Results.PreInstallQueryOutput)
assert.NotNil(t, resp.Results.Output)
assert.Equal(t, fmt.Sprintf(fleet.SoftwareInstallerInstallFailCopy, "failed"), *resp.Results.Output)
assert.Empty(t, resp.Results.PostInstallScriptOutput)
assert.Less(t, beforeInstallRequest, resp.Results.CreatedAt)
assert.Greater(t, time.Now(), resp.Results.CreatedAt)
assert.NotNil(t, resp.Results.UpdatedAt)
assert.Less(t, beforeInstallResult, *resp.Results.UpdatedAt)
wantAct := fleet.ActivityTypeInstalledSoftware{
HostID: host.ID,
HostDisplayName: host.DisplayName(),
SoftwareTitle: payload.Title,
SoftwarePackage: payload.Filename,
InstallUUID: installUUID,
Status: string(fleet.SoftwareInstallFailed),
}
s.lastActivityMatches(wantAct.ActivityName(), string(jsonMustMarshal(t, wantAct)), 0)
}

View file

@ -4359,6 +4359,9 @@ func (s *integrationMDMTestSuite) TestMDMAppleConfigProfileCRUD() {
testTeam, err := s.ds.NewTeam(ctx, &fleet.Team{Name: "TestTeam"})
require.NoError(t, err)
teamDelete, err := s.ds.NewTeam(ctx, &fleet.Team{Name: "TeamDelete"})
require.NoError(t, err)
testProfiles := make(map[string]fleet.MDMAppleConfigProfile)
generateTestProfile := func(name string, identifier string) {
i := identifier
@ -4402,6 +4405,12 @@ func (s *integrationMDMTestSuite) TestMDMAppleConfigProfileCRUD() {
require.Equal(t, expected.Identifier, actual.Identifier)
}
host, _ := createHostThenEnrollMDM(s.ds, s.server.URL, t)
s.Do("POST", "/api/latest/fleet/hosts/transfer", addHostsToTeamRequest{
TeamID: &teamDelete.ID,
HostIDs: []uint{host.ID},
}, http.StatusOK)
// create new profile (no team)
generateTestProfile("TestNoTeam", "")
body, headers := generateNewReq("TestNoTeam", nil)
@ -4421,9 +4430,42 @@ func (s *integrationMDMTestSuite) TestMDMAppleConfigProfileCRUD() {
require.NotEmpty(t, newCP.ProfileID)
setTestProfileID("TestWithTeamID", newCP.ProfileID)
// Create a profile that we're going to remove immediately
generateTestProfile("TestImmediateDelete", "")
body, headers = generateNewReq("TestImmediateDelete", &teamDelete.ID)
newResp = s.DoRawWithHeaders("POST", "/api/latest/fleet/mdm/apple/profiles", body.Bytes(), http.StatusOK, headers)
newCP = fleet.MDMAppleConfigProfile{}
err = json.NewDecoder(newResp.Body).Decode(&newCP)
require.NoError(t, err)
require.NotEmpty(t, newCP.ProfileID)
setTestProfileID("TestImmediateDelete", newCP.ProfileID)
// check that host_mdm_apple_profiles entry was created
var hostResp getHostResponse
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", host.ID), nil, http.StatusOK, &hostResp)
require.NotNil(t, hostResp.Host.MDM.Profiles)
require.Len(t, *hostResp.Host.MDM.Profiles, 1)
require.Equal(t, (*hostResp.Host.MDM.Profiles)[0].Name, "TestImmediateDelete")
// now delete the profile before it's sent, we should see the host_mdm_apple_profiles entry go
// away
deletedCP := testProfiles["TestImmediateDelete"]
deletePath := fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", deletedCP.ProfileID)
var deleteResp deleteMDMAppleConfigProfileResponse
s.DoJSON("DELETE", deletePath, nil, http.StatusOK, &deleteResp)
// confirm deleted
var listResp listMDMAppleConfigProfilesResponse
s.DoJSON("GET", "/api/latest/fleet/mdm/apple/profiles", listMDMAppleConfigProfilesRequest{TeamID: teamDelete.ID}, http.StatusOK, &listResp)
require.Len(t, listResp.ConfigProfiles, 0)
getPath := fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", deletedCP.ProfileID)
_ = s.DoRawWithHeaders("GET", getPath, nil, http.StatusNotFound, map[string]string{"Authorization": fmt.Sprintf("Bearer %s", s.token)})
// confirm no host profiles
hostResp = getHostResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", host.ID), nil, http.StatusOK, &hostResp)
require.Nil(t, hostResp.Host.MDM.Profiles)
// list profiles (no team)
expectedCP := testProfiles["TestNoTeam"]
var listResp listMDMAppleConfigProfilesResponse
s.DoJSON("GET", "/api/latest/fleet/mdm/apple/profiles", nil, http.StatusOK, &listResp)
require.Len(t, listResp.ConfigProfiles, 1)
respCP := listResp.ConfigProfiles[0]
@ -4445,7 +4487,7 @@ func (s *integrationMDMTestSuite) TestMDMAppleConfigProfileCRUD() {
// get profile (no team)
expectedCP = testProfiles["TestNoTeam"]
getPath := fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", expectedCP.ProfileID)
getPath = fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", expectedCP.ProfileID)
getResp := s.DoRawWithHeaders("GET", getPath, nil, http.StatusOK, map[string]string{"Authorization": fmt.Sprintf("Bearer %s", s.token)})
checkGetResponse(getResp, expectedCP)
@ -4456,9 +4498,8 @@ func (s *integrationMDMTestSuite) TestMDMAppleConfigProfileCRUD() {
checkGetResponse(getResp, expectedCP)
// delete profile (no team)
deletedCP := testProfiles["TestNoTeam"]
deletePath := fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", deletedCP.ProfileID)
var deleteResp deleteMDMAppleConfigProfileResponse
deletedCP = testProfiles["TestNoTeam"]
deletePath = fmt.Sprintf("/api/latest/fleet/mdm/apple/profiles/%d", deletedCP.ProfileID)
s.DoJSON("DELETE", deletePath, nil, http.StatusOK, &deleteResp)
// confirm deleted
listResp = listMDMAppleConfigProfilesResponse{}

View file

@ -879,6 +879,26 @@ func (s *integrationMDMTestSuite) TestAppleGetAppleMDM() {
require.Equal(t, tm.Name, tok.MacOSTeam.Name)
require.Equal(t, tm.Name, tok.IOSTeam.Name)
require.Equal(t, tm.Name, tok.IPadOSTeam.Name)
// Reset the teams via app config
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"mdm": {
"apple_business_manager": []
}
}`), http.StatusOK, &acResp)
tokensResp = listABMTokensResponse{}
s.DoJSON("GET", "/api/latest/fleet/abm_tokens", nil, http.StatusOK, &tokensResp)
tok = s.getABMTokenByName(tmOrgName, tokensResp.Tokens)
require.NotNil(t, tok)
require.False(t, tok.TermsExpired)
require.Equal(t, "abc", tok.AppleID)
require.Equal(t, tmOrgName, tok.OrganizationName)
require.Equal(t, s.server.URL+"/mdm/apple/mdm", tok.MDMServerURL)
require.Equal(t, fleet.TeamNameNoTeam, tok.MacOSTeam.Name)
require.Equal(t, fleet.TeamNameNoTeam, tok.IOSTeam.Name)
require.Equal(t, fleet.TeamNameNoTeam, tok.IPadOSTeam.Name)
}
func (s *integrationMDMTestSuite) getABMTokenByName(orgName string, tokens []*fleet.ABMToken) *fleet.ABMToken {
@ -10532,6 +10552,25 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
var resPatchVPP patchVPPTokensTeamsResponse
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/vpp_tokens/%d/teams", resp.Tokens[0].ID), patchVPPTokensTeamsRequest{TeamIDs: []uint{}}, http.StatusOK, &resPatchVPP)
// Reset the token's teams by omitting the token from app config
acResp := appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"mdm": { "volume_purchasing_program": null }
}`), http.StatusOK, &acResp)
resp = getVPPTokensResponse{}
s.DoJSON("GET", "/api/latest/fleet/vpp_tokens", &getVPPTokensRequest{}, http.StatusOK, &resp)
require.NoError(t, resp.Err)
require.Len(t, resp.Tokens, 1)
require.Equal(t, orgName, resp.Tokens[0].OrgName)
require.Equal(t, location, resp.Tokens[0].Location)
require.Equal(t, expTime, resp.Tokens[0].RenewDate)
require.Empty(t, resp.Tokens[0].Teams)
// Add the team back
resPatchVPP = patchVPPTokensTeamsResponse{}
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/vpp_tokens/%d/teams", resp.Tokens[0].ID), patchVPPTokensTeamsRequest{TeamIDs: []uint{}}, http.StatusOK, &resPatchVPP)
// Get list of VPP apps from "Apple"
// We're passing team 1 here, but we haven't added any app store apps to that team, so we get
// back all available apps in our VPP location.
@ -10801,14 +10840,6 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/vpp_tokens/%d/teams", vppRes.Token.ID), patchVPPTokensTeamsRequest{TeamIDs: []uint{team.ID}}, http.StatusOK, &resPatchVPP)
// mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
// _, err := q.ExecContext(context.Background(), "UPDATE vpp_tokens SET renew_at = ? WHERE organization_name = ?", time.Now().Add(-1*time.Hour), "badtoken")
// return err
// })
// r := s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/install/%d", mdmHost.ID, errTitleID), &installSoftwareRequest{}, http.StatusUnprocessableEntity)
// require.Contains(t, extractServerErrorText(r.Body), "VPP token expired")
// Disable the token
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/vpp_tokens/%d/teams", vppRes.Token.ID), patchVPPTokensTeamsRequest{}, http.StatusOK, &resPatchVPP)

View file

@ -86,6 +86,18 @@ func TestMailService(t *testing.T) {
return invite, nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
ctx = test.UserContext(ctx, test.UserAdmin)
// (1) Modifying the app config `sender_address` field to trigger a test e-mail send.

View file

@ -373,6 +373,18 @@ func TestModifyAppConfigPatches(t *testing.T) {
return nil
}
ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error {
return nil
}
ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) {
return []*fleet.VPPTokenDB{}, nil
}
ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) {
return []*fleet.ABMToken{}, nil
}
configJSON := []byte(`{"org_info": { "org_name": "Acme", "org_logo_url": "somelogo.jpg" }}`)
ctx = test.UserContext(ctx, test.UserAdmin)

View file

@ -1,3 +1,4 @@
# testdata
- `fleet-osquery.msi` is a dummy MSI installer created by `packaging.BuildMSI` with a fake `orbit.exe` that just has `hello world` in it. Its software title is `Fleet osquery` and its version is `1.0.0`.
- `fleet-osquery.msi` is a dummy MSI installer created by `packaging.BuildMSI` with a fake `orbit.exe` that just has `hello world` in it. Its software title is `Fleet osquery` and its version is `1.0.0`.
- `ruby.rpm` was downloaded from https://rpmfind.net/linux/fedora/linux/development/rawhide/Everything/x86_64/os/Packages/r/ruby-3.3.5-15.fc42.x86_64.rpm.

Binary file not shown.

View file

@ -24,7 +24,7 @@ variable "fleet_config" {
vuln_processing_cpu = optional(number, 2048)
vuln_data_stream_mem = optional(number, 1024)
vuln_data_stream_cpu = optional(number, 512)
image = optional(string, "fleetdm/fleet:v4.57.0")
image = optional(string, "fleetdm/fleet:v4.57.1")
family = optional(string, "fleet-vuln-processing")
sidecars = optional(list(any), [])
extra_environment_variables = optional(map(string), {})
@ -82,7 +82,7 @@ variable "fleet_config" {
vuln_processing_cpu = 2048
vuln_data_stream_mem = 1024
vuln_data_stream_cpu = 512
image = "fleetdm/fleet:v4.57.0"
image = "fleetdm/fleet:v4.57.1"
family = "fleet-vuln-processing"
sidecars = []
extra_environment_variables = {}

View file

@ -16,7 +16,7 @@ variable "fleet_config" {
mem = optional(number, 4096)
cpu = optional(number, 512)
pid_mode = optional(string, null)
image = optional(string, "fleetdm/fleet:v4.57.0")
image = optional(string, "fleetdm/fleet:v4.57.1")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
@ -119,7 +119,7 @@ variable "fleet_config" {
mem = 512
cpu = 256
pid_mode = null
image = "fleetdm/fleet:v4.57.0"
image = "fleetdm/fleet:v4.57.1"
family = "fleet"
sidecars = []
depends_on = []

View file

@ -77,7 +77,7 @@ variable "fleet_config" {
mem = optional(number, 4096)
cpu = optional(number, 512)
pid_mode = optional(string, null)
image = optional(string, "fleetdm/fleet:v4.57.0")
image = optional(string, "fleetdm/fleet:v4.57.1")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
@ -205,7 +205,7 @@ variable "fleet_config" {
mem = 512
cpu = 256
pid_mode = null
image = "fleetdm/fleet:v4.57.0"
image = "fleetdm/fleet:v4.57.1"
family = "fleet"
sidecars = []
depends_on = []

View file

@ -17,7 +17,7 @@ provider "aws" {
}
locals {
fleet_image = "fleetdm/fleet:v4.57.0"
fleet_image = "fleetdm/fleet:v4.57.1"
domain_name = "example.com"
}

View file

@ -170,7 +170,7 @@ variable "fleet_config" {
mem = optional(number, 4096)
cpu = optional(number, 512)
pid_mode = optional(string, null)
image = optional(string, "fleetdm/fleet:v4.57.0")
image = optional(string, "fleetdm/fleet:v4.57.1")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
@ -298,7 +298,7 @@ variable "fleet_config" {
mem = 512
cpu = 256
pid_mode = null
image = "fleetdm/fleet:v4.57.0"
image = "fleetdm/fleet:v4.57.1"
family = "fleet"
sidecars = []
depends_on = []

View file

@ -63,8 +63,8 @@ module "fleet" {
fleet_config = {
# To avoid pull-rate limiting from dockerhub, consider using our quay.io mirror
# for the Fleet image. e.g. "quay.io/fleetdm/fleet:v4.57.0"
image = "fleetdm/fleet:v4.57.0" # override default to deploy the image you desire
# for the Fleet image. e.g. "quay.io/fleetdm/fleet:v4.57.1"
image = "fleetdm/fleet:v4.57.1" # override default to deploy the image you desire
# See https://fleetdm.com/docs/deploy/reference-architectures#aws for appropriate scaling
# memory and cpu.
autoscaling = {

View file

@ -218,7 +218,7 @@ variable "fleet_config" {
mem = optional(number, 4096)
cpu = optional(number, 512)
pid_mode = optional(string, null)
image = optional(string, "fleetdm/fleet:v4.57.0")
image = optional(string, "fleetdm/fleet:v4.57.1")
family = optional(string, "fleet")
sidecars = optional(list(any), [])
depends_on = optional(list(any), [])
@ -346,7 +346,7 @@ variable "fleet_config" {
mem = 512
cpu = 256
pid_mode = null
image = "fleetdm/fleet:v4.57.0"
image = "fleetdm/fleet:v4.57.1"
family = "fleet"
sidecars = []
depends_on = []

View file

@ -1,6 +1,6 @@
{
"name": "fleetctl",
"version": "v4.57.0",
"version": "v4.57.1",
"description": "Installer for the fleetctl CLI tool",
"bin": {
"fleetctl": "./run.js"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -39,6 +39,27 @@
strong {
color: @core-fleet-black;
}
[purpose='jnuc-banner'] {
height: 44px;
background-color: #0587FF;
p {
color: #FFF;
font-size: 14px;
font-weight: 700;
margin-bottom: 0px;
}
a {
color: #FFF;
text-decoration-line: underline;
text-underline-offset: 2px;
}
img {
display: inline;
height: 20px;
margin-right: 10px;
}
}
[purpose='page-container'] {
padding-left: 64px;
padding-right: 64px;

View file

@ -1,4 +1,5 @@
<div id="device-management-page" v-cloak>
<div purpose="jnuc-banner" class="d-flex flex-row align-items-center justify-content-center"><img src="/images/icon-hand-wave-20x20@2x.png" alt="👋"><p>Attending JNUC? <a href="https://calendly.com/d/ckwb-wcp-8fz/meet-with-fleet-ceo" target="_blank">Chat with our CEO</a>.</p></div>
<div purpose="page-container">
<div purpose="page-content" class="mx-auto">
<div purpose="hero">