Skip to content
18 changes: 12 additions & 6 deletions ee/server/service/software_installers.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ func (svc *Service) UploadSoftwareInstaller(ctx context.Context, payload *fleet.
payload.PostInstallScript = file.Dos2UnixNewlines(payload.PostInstallScript)
payload.UninstallScript = file.Dos2UnixNewlines(payload.UninstallScript)

if _, err := svc.addMetadataToSoftwarePayload(ctx, payload, true); err != nil {
failOnBlankScript := !strings.HasSuffix(payload.Filename, ".ipa")

if _, err := svc.addMetadataToSoftwarePayload(ctx, payload, failOnBlankScript); err != nil {
return nil, ctxerr.Wrap(ctx, err, "adding metadata to payload")
}

Expand Down Expand Up @@ -151,6 +153,15 @@ func (svc *Service) UploadSoftwareInstaller(ctx context.Context, payload *fleet.
if payload.TeamID != nil {
tmID = *payload.TeamID
}

if payload.Extension == "ipa" {
addedInstaller, err := svc.ds.GetInHouseAppMetadataByTeamAndTitleID(ctx, &tmID, titleID)
if err != nil {
return nil, err
}
return addedInstaller, nil
}

addedInstaller, err := svc.ds.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, &tmID, titleID, true)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "getting added software installer")
Expand Down Expand Up @@ -1573,11 +1584,6 @@ func (svc *Service) addMetadataToSoftwarePayload(ctx context.Context, payload *f
payload.Extension = meta.Extension
payload.UpgradeCode = meta.UpgradeCode

if payload.Extension == "ipa" {
fmt.Println("processing IPA upload")
return meta.Extension, nil
}

// reset the reader (it was consumed to extract metadata)
if err := payload.InstallerFile.Rewind(); err != nil {
return "", ctxerr.Wrap(ctx, err, "resetting installer file reader")
Expand Down
26 changes: 2 additions & 24 deletions pkg/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package file

import (
"archive/tar"
"archive/zip"
"bufio"
"bytes"
"compress/gzip"
Expand Down Expand Up @@ -40,26 +39,6 @@ type InstallerMetadata struct {
UpgradeCode string
}

func ExtractIPAMetadata(tfr *fleet.TempFileReader) (*InstallerMetadata, error) {
// TODO(JVE): fill me in! needs to unzip the file, then use the binary plist reader we have to get the metadata
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.NewReader(tfr, 1000)
if err != nil {
return nil, err
}

for _, f := range r.File {
fmt.Printf("f.Name: %v\n", f.Name)
}

return &InstallerMetadata{SHASum: h.Sum(nil), PackageIDs: []string{"com.foo.bar"}}, nil
}

// ExtractInstallerMetadata extracts the software name and version from the
// installer file and returns them along with the sha256 hash of the bytes. The
// format of the installer is determined based on the magic bytes of the content.
Expand Down Expand Up @@ -90,9 +69,8 @@ func ExtractInstallerMetadata(tfr *fleet.TempFileReader) (*InstallerMetadata, er
if err != nil {
err = errors.Join(ErrInvalidTarball, err)
}
// TODO: implement this
// case "ipa":
// meta, err = ExtractIPAMetadata(tfr)
case "ipa":
meta, err = ExtractIPAMetadata(tfr)
default:
return nil, ErrUnsupportedType
}
Expand Down
65 changes: 65 additions & 0 deletions pkg/file/ipa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package file

import (
"archive/zip"
"crypto/sha256"
"errors"
"fmt"
"io"
"strings"

"github.com/fleetdm/fleet/v4/server/fleet"
"howett.net/plist"
)

func ExtractIPAMetadata(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)
}

fmt.Printf("tfr.Name(): %v\n", tfr.Name())

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"`
}
for _, f := range r.File {
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
}
}
}

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
}
9 changes: 5 additions & 4 deletions server/datastore/mysql/hosts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8194,10 +8194,11 @@ func testHostsDeleteHosts(t *testing.T, ds *Datastore) {
`, certSerial, host.ID, "test-host", time.Now().Add(-1*time.Hour), time.Now().Add(24*time.Hour), "-----BEGIN CERTIFICATE-----", []byte{0x04})
require.NoError(t, err)

err = ds.InsertInHouseApp(ctx, &fleet.InHouseAppPayload{
Name: "test",
StorageID: uuid.NewString(),
Platform: string(fleet.MacOSPlatform),
_, _, err = ds.insertInHouseApp(ctx, &fleet.InHouseAppPayload{
Name: "test",
StorageID: uuid.NewString(),
Platform: string(fleet.MacOSPlatform),
ValidatedLabels: &fleet.LabelIdentsWithScope{},
})
require.NoError(t, err)
var inHouseID uint
Expand Down
36 changes: 29 additions & 7 deletions server/datastore/mysql/in_house_apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ import (
"github.com/jmoiron/sqlx"
)

func (ds *Datastore) InsertInHouseApp(ctx context.Context, payload *fleet.InHouseAppPayload) error {
func (ds *Datastore) insertInHouseApp(ctx context.Context, payload *fleet.InHouseAppPayload) (uint, uint, error) {

stmt := `
INSERT INTO in_house_apps (
team_id,
title_id,
global_or_team_id,
name,
storage_id,
platform
)
VALUES (?, ?, ?, ?, ?)
`
VALUES (?, ?, ?, ?, ?, ?)`

var tid *uint
var globalOrTeamID uint
Expand All @@ -31,26 +31,48 @@ func (ds *Datastore) InsertInHouseApp(ctx context.Context, payload *fleet.InHous
}
}

err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
titleID, err := ds.getOrGenerateSoftwareInstallerTitleID(ctx, &fleet.UploadSoftwareInstallerPayload{
TeamID: tid,
Title: payload.Name,
BundleIdentifier: payload.BundleID,
Source: "ios_apps"}, // TODO: what about iPad apps
)
if err != nil {
return 0, 0, ctxerr.Wrap(ctx, err, "insertInHouseApp")
}

var installerID uint
err = ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
args := []any{
tid,
titleID,
globalOrTeamID,
payload.Name,
payload.StorageID,
payload.Platform,
}

_, err := tx.ExecContext(ctx, stmt, args...)
res, err := tx.ExecContext(ctx, stmt, args...)
if err != nil {
if IsDuplicate(err) {
// already exists for this team/no team
err = alreadyExists("InHouseApp", payload.Name)
}
return err
return ctxerr.Wrap(ctx, err, "insertInHouseApp")
}

id64, err := res.LastInsertId()
installerID = uint(id64) //nolint:gosec // dismiss G115
if err != nil {
return ctxerr.Wrap(ctx, err, "insertInHouseApp")
}

if err := setOrUpdateSoftwareInstallerLabelsDB(ctx, tx, installerID, *payload.ValidatedLabels, softwareTypeInHouseApp); err != nil {
return ctxerr.Wrap(ctx, err, "upsert in house app labels")
}

return nil
})

return ctxerr.Wrap(ctx, err, "insert in house app")
return installerID, titleID, ctxerr.Wrap(ctx, err, "insertInHouseApp")
}
28 changes: 23 additions & 5 deletions server/datastore/mysql/in_house_apps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,29 @@ func TestInHouseApps(t *testing.T) {

func testInHouseAppsCrud(t *testing.T, ds *Datastore) {
ctx := context.Background()
err := ds.InsertInHouseApp(ctx, &fleet.InHouseAppPayload{
Name: "foo",
StorageID: "testingtesting123",
Platform: "ios",
})

team, err := ds.NewTeam(ctx, &fleet.Team{Name: "team 1"})
require.NoError(t, err)

payload := fleet.UploadSoftwareInstallerPayload{
TeamID: &team.ID,
Title: "foo",
BundleIdentifier: "com.foo",
StorageID: "testingtesting123",
Platform: "ios",
Extension: "ipa",
}
// TODO(JK): test with svc.UploadSoftwareInstaller
_, _, err = ds.MatchOrCreateSoftwareInstaller(ctx, &payload)
require.Error(t, err, "ValidatedLabels must not be nil")

payload.ValidatedLabels = &fleet.LabelIdentsWithScope{}
installerID, titleID, err := ds.MatchOrCreateSoftwareInstaller(ctx, &payload)
require.NoError(t, err)
require.NotZero(t, installerID)
require.NotZero(t, titleID)

installer, err := ds.GetInHouseAppMetadataByTeamAndTitleID(ctx, &team.ID, titleID)
require.NoError(t, err)
require.Equal(t, payload.Title, installer.SoftwareTitle)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ CREATE TABLE in_house_apps (
title_id int unsigned DEFAULT NULL,
team_id int unsigned DEFAULT NULL,
global_or_team_id int unsigned NOT NULL DEFAULT '0',
name varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
name VARCHAR(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
version VARCHAR(255) NOT NULL DEFAULT '',
storage_id VARCHAR(64) COLLATE utf8mb4_unicode_ci NOT NULL,
created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
Expand Down
1 change: 1 addition & 0 deletions server/datastore/mysql/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1126,6 +1126,7 @@ CREATE TABLE `in_house_apps` (
`team_id` int unsigned DEFAULT NULL,
`global_or_team_id` int unsigned NOT NULL DEFAULT '0',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`version` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`storage_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
Expand Down
Loading
Loading