From cc687d9d1eb961e145475939472af71b19d79b86 Mon Sep 17 00:00:00 2001 From: Zach Wasserman Date: Mon, 21 Mar 2022 15:01:50 -0700 Subject: [PATCH] Add Notarization for Fleet Desktop (#4720) --- .../workflows/generate-desktop-app-tar-gz.yml | 1 + orbit/pkg/packaging/macos.go | 2 +- orbit/pkg/packaging/macos_notarize.go | 28 ++++++++++++++--- tools/desktop/desktop.go | 31 +++++++++++++++++-- 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/.github/workflows/generate-desktop-app-tar-gz.yml b/.github/workflows/generate-desktop-app-tar-gz.yml index a129f0ad0..a0380c8de 100644 --- a/.github/workflows/generate-desktop-app-tar-gz.yml +++ b/.github/workflows/generate-desktop-app-tar-gz.yml @@ -54,6 +54,7 @@ jobs: AC_USERNAME=$AC_USERNAME \ AC_PASSWORD=$AC_PASSWORD \ FLEET_DESKTOP_APPLE_AUTHORITY=$CODESIGN_IDENTITY \ + FLEET_DESKTOP_NOTARIZE=true \ FLEET_DESKTOP_VERSION=$FLEET_DESKTOP_VERSION \ make desktop-app-tar-gz - name: Upload desktop.app.tar.gz diff --git a/orbit/pkg/packaging/macos.go b/orbit/pkg/packaging/macos.go index 65da1ff44..3d53b0c54 100644 --- a/orbit/pkg/packaging/macos.go +++ b/orbit/pkg/packaging/macos.go @@ -117,7 +117,7 @@ func BuildPkg(opt Options) (string, error) { } if opt.Notarize { - if err := notarizePkg(generatedPath); err != nil { + if err := NotarizeStaple(generatedPath, "com.fleetdm.orbit"); err != nil { return "", err } } diff --git a/orbit/pkg/packaging/macos_notarize.go b/orbit/pkg/packaging/macos_notarize.go index f847b767a..7fab97b32 100644 --- a/orbit/pkg/packaging/macos_notarize.go +++ b/orbit/pkg/packaging/macos_notarize.go @@ -13,7 +13,8 @@ import ( "github.com/rs/zerolog/log" ) -func notarizePkg(pkgPath string) error { +// Notarize will do the Notarization step. Note that the provided path must be a .zip or a .dmg. +func Notarize(path, bundleIdentifier string) error { username, ok := os.LookupEnv("AC_USERNAME") if !ok { return errors.New("AC_USERNAME must be set in environment") @@ -27,8 +28,8 @@ func notarizePkg(pkgPath string) error { info, err := notarize.Notarize( context.Background(), ¬arize.Options{ - File: pkgPath, - BundleId: "com.fleetdm.orbit", + File: path, + BundleId: bundleIdentifier, Username: username, Password: password, Status: &statusHuman{ @@ -42,13 +43,32 @@ func notarizePkg(pkgPath string) error { log.Info().Str("logs", info.LogFileURL).Msg("notarization completed") - if err := staple.Staple(context.Background(), &staple.Options{File: pkgPath}); err != nil { + return nil +} + +// Staple will do the "stapling" step of Notarization. Note that this only works on .app and .dmg +// (not .zip or plain binaries). +func Staple(path string) error { + if err := staple.Staple(context.Background(), &staple.Options{File: path}); err != nil { return fmt.Errorf("staple notarization: %w", err) } return nil } +// NotarizeStaple will notarize and staple a macOS artifact. +func NotarizeStaple(path, bundleIdentifier string) error { + if err := Notarize(path, bundleIdentifier); err != nil { + return err + } + + if err := Staple(path); err != nil { + return err + } + + return nil +} + // This status plugin copied from // https://github.com/mitchellh/gon/blob/v0.2.3/cmd/gon/status_human.go since it // is not exposed from the gon library. diff --git a/tools/desktop/desktop.go b/tools/desktop/desktop.go index ffaa84f03..cbbe22bc1 100644 --- a/tools/desktop/desktop.go +++ b/tools/desktop/desktop.go @@ -3,6 +3,7 @@ package main import ( "archive/tar" "compress/gzip" + "context" "errors" "fmt" "io" @@ -13,8 +14,10 @@ import ( "runtime" "github.com/fleetdm/fleet/v4/orbit/pkg/constant" + "github.com/fleetdm/fleet/v4/orbit/pkg/packaging" "github.com/fleetdm/fleet/v4/pkg/secure" "github.com/kolide/kit/version" + "github.com/mitchellh/gon/package/zip" "github.com/rs/zerolog" zlog "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" @@ -75,17 +78,22 @@ func macos() *cli.Command { Usage: "Authority to use on the codesign invocation (if not set, app is not signed)", EnvVars: []string{"FLEET_DESKTOP_APPLE_AUTHORITY"}, }, + &cli.BoolFlag{ + Name: "notarize", + Usage: "If true, the generated application will be notarized and stapled. Requires the `AC_USERNAME` and `AC_PASSWORD` to be set in the environment", + EnvVars: []string{"FLEET_DESKTOP_NOTARIZE"}, + }, }, Action: func(c *cli.Context) error { if !c.Bool("verbose") { zlog.Logger = zerolog.Nop() } - return createMacOSApp(c.String("version"), c.String("authority")) + return createMacOSApp(c.String("version"), c.String("authority"), c.Bool("notarize")) }, } } -func createMacOSApp(version, authority string) error { +func createMacOSApp(version, authority string, notarize bool) error { const ( appDir = "Fleet Desktop.app" bundleIdentifier = "com.fleetdm.desktop" @@ -162,6 +170,25 @@ func createMacOSApp(version, authority string) error { } } + if notarize { + const notarizationZip = "desktop.zip" + // Note that the app needs to be zipped in order to upload to Apple for Notarization, but + // the Stapling has to happen on just the app (not zipped). Apple is a bit inconsistent here. + if err := zip.Zip(context.Background(), &zip.Options{Files: []string{appDir}, OutputPath: notarizationZip}); err != nil { + return fmt.Errorf("zip app for notarization: %w", err) + } + defer os.Remove(notarizationZip) + + if err := packaging.Notarize(notarizationZip, "com.fleetdm.desktop"); err != nil { + return err + } + + if err := packaging.Staple(appDir); err != nil { + return err + } + + } + const tarGzName = "desktop.app.tar.gz" if err := compressDir(tarGzName, appDir); err != nil { return fmt.Errorf("compress app: %w", err)