mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
**Related issue:** Resolves #32084 This PR modifies `isValidAppFilePath` to allow subdirectors in `Applications/`, like in this case `Applications/Cisco/Cisco Secure Client.app`. This also changes the metadata extraction from packageinfo to trim `.app` from the name in all cases. # Checklist for submitter If some of the following don't apply, delete the relevant line. - [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. - [ ] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [ ] If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes ## Testing - [x] Added/updated automated tests - [ ] Where appropriate, [automated tests simulate multiple hosts and test for host isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing) (updates to one hosts's records do not affect another) - [x] QA'd all new/changed functionality manually ### Test plan: --- I ran this on my local environment and it seemed fine - Have environment with the bug recreated, it has two software titles for "Cisco Secure Client", and the one with the bundle id `com.cisco.pkg.anyconnect.vpn` is used by the installer. - URL to pkg: https://fndtnfleetmsp.blob.core.windows.net/fndtnpkgs/cisco-secure-client-macos-5.1.3.62-core-vpn-webdeploy-k9.pkg - Cisco Secure Client doesn't show as installed in UI even after installing. - Run the new migration. - Cisco Secure Client shows as installed now in ui, software title with bundle id `com.cisco.pkg.anyconnect.vpn` is gone from the database, and the software installer references the correct title (`com.cisco.secureclient.gui`). - Check that deleting and reuploading the installer doesn't recreate the bad software title. ### QA Note: --- There are some problems with the install script, but that is probably a different scope than this ticket. `Reinstall` wont work, it says Cisco Secure Client is already installed. Uninstalling through Fleet then Installing again works fine though.
349 lines
11 KiB
Go
349 lines
11 KiB
Go
package file
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestCheckPKGSignature(t *testing.T) {
|
|
read := func(name string) []byte {
|
|
b, err := os.ReadFile(name)
|
|
require.NoError(t, err)
|
|
return b
|
|
}
|
|
testCases := []struct {
|
|
in []byte
|
|
out error
|
|
}{
|
|
{in: []byte{}, out: io.EOF},
|
|
{
|
|
in: read("./testdata/invalid.tar.gz"),
|
|
out: ErrInvalidType,
|
|
},
|
|
{
|
|
in: read("./testdata/unsigned.pkg"),
|
|
out: ErrNotSigned,
|
|
},
|
|
{
|
|
in: read("./testdata/signed.pkg"),
|
|
out: nil,
|
|
},
|
|
{
|
|
out: errors.New("decompressing TOC: unexpected EOF"),
|
|
in: read("./testdata/wrong-toc.pkg"),
|
|
},
|
|
}
|
|
|
|
for _, c := range testCases {
|
|
r := bytes.NewReader(c.in)
|
|
err := CheckPKGSignature(r)
|
|
if c.out != nil {
|
|
require.ErrorContains(t, err, c.out.Error())
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseRealDistributionFiles(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
file string
|
|
expectedName string
|
|
expectedVersion string
|
|
expectedBundleID string
|
|
expectedPackageIDs []string
|
|
}{
|
|
{
|
|
file: "distribution-1password.xml",
|
|
expectedName: "1Password",
|
|
expectedVersion: "8.10.34",
|
|
expectedBundleID: "com.1password.1password",
|
|
expectedPackageIDs: []string{"com.1password.1password"},
|
|
},
|
|
{
|
|
file: "distribution-chrome.xml",
|
|
expectedName: "Google Chrome",
|
|
expectedVersion: "126.0.6478.62",
|
|
expectedBundleID: "com.google.Chrome",
|
|
expectedPackageIDs: []string{"com.google.Chrome"},
|
|
},
|
|
{
|
|
file: "distribution-edge.xml",
|
|
expectedName: "Microsoft Edge",
|
|
expectedVersion: "126.0.2592.56",
|
|
expectedBundleID: "com.microsoft.edgemac",
|
|
expectedPackageIDs: []string{"com.microsoft.edgemac"},
|
|
},
|
|
{
|
|
file: "distribution-firefox.xml",
|
|
expectedName: "Firefox",
|
|
expectedVersion: "99.0",
|
|
expectedBundleID: "org.mozilla.firefox",
|
|
expectedPackageIDs: []string{"org.mozilla.firefox"},
|
|
},
|
|
{
|
|
file: "distribution-fleet.xml",
|
|
expectedName: "Fleet osquery",
|
|
expectedVersion: "42.0.0",
|
|
expectedBundleID: "com.fleetdm.orbit",
|
|
expectedPackageIDs: []string{"com.fleetdm.orbit.base.pkg"},
|
|
},
|
|
{
|
|
file: "distribution-go.xml",
|
|
expectedName: "Go",
|
|
expectedVersion: "go1.22.4",
|
|
expectedBundleID: "org.golang.go",
|
|
expectedPackageIDs: []string{"org.golang.go"},
|
|
},
|
|
{
|
|
file: "distribution-microsoft-teams.xml",
|
|
expectedName: "Microsoft Teams",
|
|
expectedVersion: "24124.1412.2911.3341",
|
|
expectedBundleID: "com.microsoft.teams2",
|
|
expectedPackageIDs: []string{
|
|
"com.microsoft.MSTeamsAudioDevice", "com.microsoft.package.Microsoft_AutoUpdate.app",
|
|
"com.microsoft.teams2",
|
|
},
|
|
},
|
|
{
|
|
file: "distribution-zoom.xml",
|
|
expectedName: "zoom.us",
|
|
expectedVersion: "6.0.11.35001",
|
|
expectedBundleID: "us.zoom.xos",
|
|
expectedPackageIDs: []string{"us.zoom.pkg.videomeeting"},
|
|
},
|
|
{
|
|
file: "distribution-acrobatreader.xml",
|
|
expectedName: "Adobe Acrobat Reader",
|
|
expectedVersion: "24.002.20857",
|
|
expectedBundleID: "com.adobe.Reader",
|
|
expectedPackageIDs: []string{
|
|
"com.adobe.acrobat.DC.reader.app.pkg.MUI", "com.adobe.acrobat.DC.reader.appsupport.pkg.MUI",
|
|
"com.adobe.acrobat.reader.DC.reader.app.pkg.MUI", "com.adobe.armdc.app.pkg",
|
|
},
|
|
},
|
|
{
|
|
file: "distribution-airtame.xml",
|
|
expectedName: "Airtame",
|
|
expectedVersion: "4.10.1",
|
|
expectedBundleID: "com.airtame.airtame-application",
|
|
expectedPackageIDs: []string{"com.airtame.airtame-application"},
|
|
},
|
|
{
|
|
file: "distribution-boxdrive.xml",
|
|
expectedName: "Box",
|
|
expectedVersion: "2.38.173",
|
|
expectedBundleID: "com.box.desktop",
|
|
expectedPackageIDs: []string{
|
|
"com.box.desktop.installer.autoupdater", "com.box.desktop.installer.desktop",
|
|
"com.box.desktop.installer.local.appsupport", "com.box.desktop.installer.osxfuse",
|
|
},
|
|
},
|
|
{
|
|
file: "distribution-iriunwebcam.xml",
|
|
expectedName: "IriunWebcam",
|
|
expectedVersion: "2.8.8",
|
|
expectedBundleID: "com.iriun.macwebcam",
|
|
// Note: "com.iriun.pkg.multicam" is part of the installer package, but it is not actually installed by default.
|
|
// We can't reliably determine which packages are installed by the installer, so we just list all of them.
|
|
expectedPackageIDs: []string{"com.iriun.pkg.multicam", "com.iriun.pkg.webcam.tmp"},
|
|
},
|
|
{
|
|
file: "distribution-microsoftexcel.xml",
|
|
expectedName: "Microsoft Excel",
|
|
expectedVersion: "16.86",
|
|
expectedBundleID: "com.microsoft.Excel",
|
|
expectedPackageIDs: []string{
|
|
"com.microsoft.package.Microsoft_AutoUpdate.app", "com.microsoft.package.Microsoft_Excel.app",
|
|
"com.microsoft.pkg.licensing",
|
|
},
|
|
},
|
|
{
|
|
file: "distribution-microsoftword.xml",
|
|
expectedName: "Microsoft Word",
|
|
expectedVersion: "16.86",
|
|
expectedBundleID: "com.microsoft.Word",
|
|
expectedPackageIDs: []string{
|
|
"com.microsoft.package.Microsoft_AutoUpdate.app", "com.microsoft.package.Microsoft_Word.app",
|
|
"com.microsoft.pkg.licensing",
|
|
},
|
|
},
|
|
{
|
|
file: "distribution-miscrosoftpowerpoint.xml",
|
|
expectedName: "Microsoft PowerPoint",
|
|
expectedVersion: "16.86",
|
|
expectedBundleID: "com.microsoft.Powerpoint",
|
|
expectedPackageIDs: []string{
|
|
"com.microsoft.package.Microsoft_AutoUpdate.app", "com.microsoft.package.Microsoft_PowerPoint.app",
|
|
"com.microsoft.pkg.licensing",
|
|
},
|
|
},
|
|
{
|
|
file: "distribution-ringcentral.xml",
|
|
expectedName: "RingCentral",
|
|
expectedVersion: "24.1.32.9774",
|
|
expectedBundleID: "com.ringcentral.glip",
|
|
expectedPackageIDs: []string{"com.ringcentral.glip"},
|
|
},
|
|
{
|
|
file: "distribution-zoom-full.xml",
|
|
expectedName: "Zoom Workplace",
|
|
expectedVersion: "6.1.1.36333",
|
|
expectedBundleID: "us.zoom.xos",
|
|
expectedPackageIDs: []string{"us.zoom.pkg.videomeeting"},
|
|
},
|
|
{
|
|
file: "test-zero-installkbytes.xml",
|
|
expectedName: "ZeroInstallSize",
|
|
expectedVersion: "1.2.3",
|
|
expectedBundleID: "com.bozo.zeroinstallsize",
|
|
expectedPackageIDs: []string{"com.bozo.zeroinstallsize.app"},
|
|
},
|
|
{
|
|
file: "distribution-sentinelone.xml",
|
|
expectedName: "SentinelOne",
|
|
expectedVersion: "24.3.2.7753",
|
|
expectedBundleID: "com.sentinelone.SentinelAgent",
|
|
expectedPackageIDs: []string{"com.sentinelone.pkg.sentinel-agent", "com.sentinelone.sentinel-agent"},
|
|
},
|
|
{
|
|
file: "distribution-cold-turkey.xml",
|
|
expectedName: "Cold Turkey Blocker",
|
|
expectedVersion: "4.5",
|
|
expectedBundleID: "com.getcoldturkey.coldturkeyblocker",
|
|
expectedPackageIDs: []string{
|
|
"com.getcoldturkey.blocker-chrome-ext", "com.getcoldturkey.blocker-edge-ext",
|
|
"com.getcoldturkey.blocker-firefox-ext", "com.getcoldturkey.coldturkeyblocker",
|
|
},
|
|
},
|
|
{
|
|
file: "distribution-privileges.xml",
|
|
expectedName: "Privileges",
|
|
expectedVersion: "2.4.0",
|
|
expectedBundleID: "corp.sap.privileges",
|
|
expectedPackageIDs: []string{"corp.sap.privileges.pkg"},
|
|
},
|
|
{
|
|
file: "distribution-cisco-secure-client.xml",
|
|
expectedName: "Cisco Secure Client",
|
|
expectedVersion: "5.1.3.62",
|
|
expectedBundleID: "com.cisco.secureclient.gui",
|
|
expectedPackageIDs: []string{"com.cisco.pkg.anyconnect.vpn"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.file, func(t *testing.T) {
|
|
rawXML, err := os.ReadFile(filepath.Join("testdata", "distribution", tt.file))
|
|
require.NoError(t, err)
|
|
metadata, err := parseDistributionFile(rawXML)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.expectedPackageIDs, metadata.PackageIDs)
|
|
require.Equal(t, tt.expectedName, metadata.Name)
|
|
require.Equal(t, tt.expectedVersion, metadata.Version)
|
|
require.Equal(t, tt.expectedBundleID, metadata.BundleIdentifier)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParsePackageInfoFiles(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
file string
|
|
expectedName string
|
|
expectedVersion string
|
|
expectedBundleID string
|
|
expectedPackageIDs []string
|
|
}{
|
|
{
|
|
file: "packageInfo-oktaVerify.xml",
|
|
expectedName: "Okta Verify",
|
|
expectedVersion: "9.27.0",
|
|
expectedBundleID: "com.okta.mobile",
|
|
expectedPackageIDs: []string{"com.okta.mobile"},
|
|
},
|
|
{
|
|
file: "packageInfo-iriunWebcam.xml",
|
|
expectedName: "IriunWebcam",
|
|
expectedVersion: "2.8.10",
|
|
expectedBundleID: "com.iriun.macwebcam",
|
|
expectedPackageIDs: []string{
|
|
"com.iriun.macwebcam", "com.iriun.macwebcam.extension",
|
|
"com.iriun.macwebcam.extension4", "com.iriun.mic",
|
|
},
|
|
},
|
|
{
|
|
file: "packageinfo-subEthaEdit-modded.xml",
|
|
expectedName: "SubEthaEdit",
|
|
expectedVersion: "5.2.4",
|
|
expectedBundleID: "de.codingmonkeys.SubEthaEdit.MacFULL",
|
|
expectedPackageIDs: []string{"de.codingmonkeys.SubEthaEdit.MacFULL"},
|
|
},
|
|
{
|
|
file: "packageInfo-scriptOnly.xml",
|
|
expectedName: "HelloWorld",
|
|
expectedVersion: "1.2.3",
|
|
expectedBundleID: "com.mygreatcompany.pkg.HelloWorld",
|
|
expectedPackageIDs: []string{"com.mygreatcompany.pkg.HelloWorld"},
|
|
},
|
|
{
|
|
file: "packageInfo-empty.xml",
|
|
expectedName: "",
|
|
expectedVersion: "",
|
|
expectedBundleID: "",
|
|
expectedPackageIDs: []string(nil),
|
|
},
|
|
{
|
|
file: "packageInfo-versionOnly.xml",
|
|
expectedName: "",
|
|
expectedVersion: "test-version",
|
|
expectedBundleID: "",
|
|
expectedPackageIDs: []string(nil),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.file, func(t *testing.T) {
|
|
rawXML, err := os.ReadFile(filepath.Join("testdata", "packageInfo", tt.file))
|
|
require.NoError(t, err)
|
|
metadata, err := parsePackageInfoFile(rawXML)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.expectedPackageIDs, metadata.PackageIDs)
|
|
assert.Equal(t, tt.expectedName, metadata.Name)
|
|
assert.Equal(t, tt.expectedVersion, metadata.Version)
|
|
assert.Equal(t, tt.expectedBundleID, metadata.BundleIdentifier)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsValidAppFilePath(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expected bool
|
|
}{
|
|
{"baz.app", true},
|
|
{"foo/bar/baz.app", false},
|
|
{"Applications/baz.app", true},
|
|
{"Applications/foo/baz.app", true},
|
|
{"foo/baz.app", false},
|
|
{"baz.txt", true},
|
|
{"Applications/baz.txt", false},
|
|
{"Applications/Cisco/Cisco Secure Client.app", true},
|
|
{"Applications/Foo with spaces.app", true},
|
|
{"Applications/foo", false},
|
|
{"foo", true},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
_, ok := isValidAppFilePath(test.input)
|
|
require.Equal(t, test.expected, ok, test.input)
|
|
}
|
|
}
|