mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 17:08:53 +00:00
Back-end fixes for FMA (#22742)
for https://github.com/fleetdm/fleet/issues/22733, https://github.com/fleetdm/fleet/issues/22734 and https://github.com/fleetdm/fleet/issues/22735 # Checklist for submitter If some of the following don't apply, delete the relevant line. <!-- Note that API documentation changes are now addressed by the product design team. --> - [x] Added/updated tests - [x] Manual QA for all new/changed functionality
This commit is contained in:
parent
2b651a9e01
commit
c4c8efb5b1
3 changed files with 103 additions and 15 deletions
|
|
@ -5,10 +5,8 @@ import (
|
|||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/pkg/file"
|
||||
|
|
@ -19,6 +17,9 @@ import (
|
|||
"github.com/fleetdm/fleet/v4/server/mdm/maintainedapps"
|
||||
)
|
||||
|
||||
// noCheckHash is used by homebrew to signal that a hash shouldn't be checked.
|
||||
const noCheckHash = "no_check"
|
||||
|
||||
func (svc *Service) AddFleetMaintainedApp(
|
||||
ctx context.Context,
|
||||
teamID *uint,
|
||||
|
|
@ -52,16 +53,25 @@ func (svc *Service) AddFleetMaintainedApp(
|
|||
return ctxerr.Wrap(ctx, err, "downloading app installer")
|
||||
}
|
||||
|
||||
// Validate the bytes we got are what we expected
|
||||
h := sha256.New()
|
||||
_, err = h.Write(installerBytes)
|
||||
extension, err := maintainedapps.ExtensionForBundleIdentifier(app.BundleIdentifier)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "generating SHA256 of maintained app installer")
|
||||
return ctxerr.Errorf(ctx, "getting extension from bundle identifier %q", app.BundleIdentifier)
|
||||
}
|
||||
gotHash := hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
if gotHash != app.SHA256 {
|
||||
return ctxerr.New(ctx, "mismatch in maintained app SHA256 hash")
|
||||
// Validate the bytes we got are what we expected, if homebrew supports
|
||||
// it, the string "no_check" is a special token used to signal that the
|
||||
// hash shouldn't be checked.
|
||||
if app.SHA256 != noCheckHash {
|
||||
h := sha256.New()
|
||||
_, err = h.Write(installerBytes)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "generating SHA256 of maintained app installer")
|
||||
}
|
||||
gotHash := hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
if gotHash != app.SHA256 {
|
||||
return ctxerr.New(ctx, "mismatch in maintained app SHA256 hash")
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to the filename if we weren't able to extract a filename from the installer response
|
||||
|
|
@ -69,6 +79,12 @@ func (svc *Service) AddFleetMaintainedApp(
|
|||
filename = app.Name
|
||||
}
|
||||
|
||||
// The UI requires all filenames to have extensions. If we couldn't get
|
||||
// one, use the extension we extracted prior
|
||||
if filepath.Ext(filename) == "" {
|
||||
filename = filename + "." + extension
|
||||
}
|
||||
|
||||
installScript = file.Dos2UnixNewlines(installScript)
|
||||
if installScript == "" {
|
||||
installScript = app.InstallScript
|
||||
|
|
@ -79,11 +95,6 @@ func (svc *Service) AddFleetMaintainedApp(
|
|||
uninstallScript = app.UninstallScript
|
||||
}
|
||||
|
||||
installerURL, err := url.Parse(app.InstallerURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
installerReader := bytes.NewReader(installerBytes)
|
||||
payload := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallerFile: installerReader,
|
||||
|
|
@ -94,7 +105,7 @@ func (svc *Service) AddFleetMaintainedApp(
|
|||
Filename: filename,
|
||||
Platform: string(app.Platform),
|
||||
Source: "apps",
|
||||
Extension: strings.TrimPrefix(filepath.Ext(installerURL.Path), "."),
|
||||
Extension: extension,
|
||||
BundleIdentifier: app.BundleIdentifier,
|
||||
StorageID: app.SHA256,
|
||||
FleetLibraryAppID: &app.ID,
|
||||
|
|
|
|||
|
|
@ -54,6 +54,29 @@ func Refresh(ctx context.Context, ds fleet.Datastore, logger kitlog.Logger) erro
|
|||
return i.ingest(ctx, apps)
|
||||
}
|
||||
|
||||
// ExtensionForBundleIdentifier returns an extension for the given FMA
|
||||
// identifier. If one can't be found it returns an empty string.
|
||||
//
|
||||
// This function is used because we can't always extract the extension based on
|
||||
// the installer URL.
|
||||
func ExtensionForBundleIdentifier(identifier string) (string, error) {
|
||||
var apps []maintainedApp
|
||||
if err := json.Unmarshal(appsJSON, &apps); err != nil {
|
||||
return "", fmt.Errorf("unmarshal embedded apps.json: %w", err)
|
||||
}
|
||||
|
||||
for _, app := range apps {
|
||||
if app.BundleIdentifier == identifier {
|
||||
formats := strings.Split(app.InstallerFormat, ":")
|
||||
if len(formats) > 0 {
|
||||
return formats[0], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
type ingester struct {
|
||||
baseURL string
|
||||
ds fleet.Datastore
|
||||
|
|
|
|||
|
|
@ -166,3 +166,57 @@ func TestIngestValidations(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtensionForBundleIdentifier(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
identifier string
|
||||
expected string
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "Valid identifier with zip format",
|
||||
identifier: "com.1password.1password",
|
||||
expected: "zip",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "Valid identifier with dmg format",
|
||||
identifier: "com.adobe.Reader",
|
||||
expected: "dmg",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "Valid identifier with pkg format",
|
||||
identifier: "com.box.desktop",
|
||||
expected: "pkg",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "Non-existent identifier",
|
||||
identifier: "com.nonexistent.app",
|
||||
expected: "",
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "Empty identifier",
|
||||
identifier: "",
|
||||
expected: "",
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
extension, err := ExtensionForBundleIdentifier(tc.identifier)
|
||||
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
require.Equal(t, tc.expected, extension)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue