<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #48102 Changes: - Renames `ExtractIPAMetadata` to `ExtractZIPMetadata` because the magic bytes for zip based installers (.ipa, .msix, .zip, etc) are the same so any zip file reaches it. If the zip does not contain an `Info.plist` file it will now fail with `ErrInvalidType`. - Did **NOT** make typeFromBytes return "zip" instead of "ipa" because meta.Extension is set from that which has downstream effects. - Added test files The actual error message is still just "invalid file type". # 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), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. - [ ] Timeouts are implemented and retries are limited to avoid infinite loops - [ ] 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 - Tested adding a valid `.ipa`, a macos FMA that uses a .zip file (alt-tab/darwin), and a windows FMA that uses a .zip file (vnc-server/windows). - Tested an msix file (renamed or not) cannot be uploaded or edited for an existing msi installer - Also tested the same things via GitOps <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved installer type detection so ZIP-based packages are less likely to be misidentified. * Fixed an error message that incorrectly referred to the wrong file type when detection fails. * MSIX packages are now reported more accurately when they don’t match IPA parsing rules. * **Refactor** * Cleaned up installer metadata handling for ZIP-based archives. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
81 lines
2.0 KiB
Go
81 lines
2.0 KiB
Go
package file
|
|
|
|
import (
|
|
"archive/zip"
|
|
"crypto/sha256"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"howett.net/plist"
|
|
)
|
|
|
|
// ExtractZIPMetadata extracts the metadata from a zip file for an Apple app
|
|
func ExtractZIPMetadata(tfr *fleet.TempFileReader) (*InstallerMetadata, error) {
|
|
h := sha256.New()
|
|
_, _ = io.Copy(h, tfr) // writes to a hash cannot fail
|
|
if err := tfr.Rewind(); err != nil {
|
|
return nil, fmt.Errorf("rewind reader: %w", err)
|
|
}
|
|
|
|
r, err := zip.OpenReader(tfr.Name())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var plistData struct {
|
|
BundleID string `plist:"CFBundleIdentifier"`
|
|
Name string `plist:"CFBundleName"`
|
|
Version string `plist:"CFBundleShortVersionString"`
|
|
RequiresIPhoneOS bool `plist:"LSRequiresIPhoneOS"`
|
|
}
|
|
var hasInfoPlist, isIPA bool
|
|
|
|
for _, f := range r.File {
|
|
// Matches any Info.plist and the last wins, so a nested framework or
|
|
// extension plist can override the app's own plist.
|
|
if strings.Contains(f.Name, "Info.plist") {
|
|
// Get data from plist file
|
|
archiveFile, err := f.Open()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not open archive %s: %w", f.Name, err)
|
|
}
|
|
defer archiveFile.Close()
|
|
|
|
rawData, err := io.ReadAll(archiveFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, err = plist.Unmarshal(rawData, &plistData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hasInfoPlist = true
|
|
// LSRequiresIPhoneOS is set on iOS/iPadOS apps and never on macOS
|
|
// apps, so it is probably an .ipa
|
|
if plistData.RequiresIPhoneOS {
|
|
isIPA = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if !hasInfoPlist || !isIPA {
|
|
// non Apple file formats based on zip are not supported (msix)
|
|
return nil, ErrInvalidType
|
|
}
|
|
if plistData.BundleID == "" {
|
|
return nil, errors.New("couldn't find bundle identifier for in-house app")
|
|
}
|
|
|
|
return &InstallerMetadata{
|
|
BundleIdentifier: plistData.BundleID,
|
|
SHASum: h.Sum(nil),
|
|
PackageIDs: []string{plistData.BundleID},
|
|
Name: plistData.Name,
|
|
Version: plistData.Version,
|
|
}, nil
|
|
}
|