From ae27d42a3721fe684c8d8974e5c30dfd3037fc5b Mon Sep 17 00:00:00 2001 From: Lucas Manuel Rodriguez Date: Mon, 18 Jul 2022 14:30:17 -0300 Subject: [PATCH] Add blueprint for Fleet Packager service (#6229) * Add blueprint for fleet packager service * Fix formatting * Add rate-limit comments and remove API versioning * Add review comments and notarization alternative * Other optimizations * Add storing of state and fix typo * Add error case for /create * Add installers for Sandbox document * Remove already done optimization * Add S3 alternative to package storage * Move to proposals directory * Last amends to specs * Add fleetctl proposal * Add comment from Guillaume --- orbit/pkg/packaging/macos_notarize.go | 4 +- proposals/.keep | 0 proposals/Fleet-Installers-4-Sandbox.md | 50 +++ proposals/Fleet-Installers.md | 469 ++++++++++++++++++++++++ proposals/Fleetctl-Docker-Image.md | 51 +++ 5 files changed, 572 insertions(+), 2 deletions(-) create mode 100644 proposals/.keep create mode 100644 proposals/Fleet-Installers-4-Sandbox.md create mode 100644 proposals/Fleet-Installers.md create mode 100644 proposals/Fleetctl-Docker-Image.md diff --git a/orbit/pkg/packaging/macos_notarize.go b/orbit/pkg/packaging/macos_notarize.go index 7fab97b320..223821324c 100644 --- a/orbit/pkg/packaging/macos_notarize.go +++ b/orbit/pkg/packaging/macos_notarize.go @@ -13,7 +13,7 @@ import ( "github.com/rs/zerolog/log" ) -// Notarize will do the Notarization step. Note that the provided path must be a .zip or a .dmg. +// Notarize will do the Notarization step. Note that the provided path must be a .zip, .dmg or .pkg. func Notarize(path, bundleIdentifier string) error { username, ok := os.LookupEnv("AC_USERNAME") if !ok { @@ -46,7 +46,7 @@ func Notarize(path, bundleIdentifier string) error { return nil } -// Staple will do the "stapling" step of Notarization. Note that this only works on .app and .dmg +// Staple will do the "stapling" step of Notarization. Note that this only works on .app, .pkg and .dmg // (not .zip or plain binaries). func Staple(path string) error { if err := staple.Staple(context.Background(), &staple.Options{File: path}); err != nil { diff --git a/proposals/.keep b/proposals/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/proposals/Fleet-Installers-4-Sandbox.md b/proposals/Fleet-Installers-4-Sandbox.md new file mode 100644 index 0000000000..7043c05fe9 --- /dev/null +++ b/proposals/Fleet-Installers-4-Sandbox.md @@ -0,0 +1,50 @@ +# Fleet Sandbox & Pre-Packaged Fleet-osquery Installers + +## Goals + +1. Improve UX on Fleet Sandbox by offering pre-packaged Fleet-osquery installers. +2. Add the "Pre-Packaged installers" feature to "Fleet Sandbox" as soon as possible (i.e. not block on having a fully functional "Fleet Packager" service). + +## Fleet Sandbox Assumptions + +- We will limit number of teams to T. +- Sandbox has good root CA trusted certificates +- Users won't be allowed to change enroll secrets. + +## Pre-Packaged Installers Plan + +We will need some changes to fleetctl, the pre-provisioner, the Fleet server and UI. + +### fleetctl + +Make all functionality in `fleetctl package` to run in linux. (This change will be needed for the Packager service anyways.) + +PS: Abstract in its own package so that it can be used by Packager service in a next iteration. + +### Pre-provisioner + +Following are the pre-provisioner steps to generate the pre-packaged installers: + +1. Generate T+1 random enroll secrets. + +2. Run `fleetctl package --type={pkg|rpm|deb|msi}` with T+1 enroll secrets (i.e. one for Global and one for each team). +PS: There's some complexity in storing/handling credentials for macOS Signing and Notarization of the packages. + +3. The generated packages will be stored in a S3 bucket accessible by the Fleet server with the following object name format +`$INSTALLERS_DIR/$ENROLL_SECRET/fleet-osquery.$TYPE`, e.g. `/fleet-installers/FzRCZWTlEY2kqzIwk1BE9fru5KuhrlYP/fleet-osquery.pkg`. +We propose using S3 to support multiple Fleet instances serving the requests. + +4. Set comma-separated `FLEET_ENROLL_POOL` environment variable to Fleet server config (Fleet would use those secrets instead of randomly generating one). +The Fleet server will only serve the installers with enroll secret listed in this variable (security check). + +### Fleet Server and UI + +- Fleet server new configuration and new functionality: + - `FLEET_MAX_TEAMS`: Maximum number of teams to allow in the deployment (default 0, disabled). + - `FLEET_DISABLE_ENROLL_CHANGE`: Disallow users from changing enroll secrets (default false). + - `FLEET_PACKAGES_S3_*`: S3 configuration for the retrieval of the pre-packaged installers (default empty). + - `FLEET_ENROLL_POOL`: comma-separated enrolls to use when needed (default empty), must have equals to FLEET_MAX_TEAMS+1 items (default empty). + +- Fleet will serve a new authenticated API (for Sandbox-only): `{GET|HEAD} /api/v1/fleet/download_installer/{enroll}/{type}`, e.g. `GET /api/v1/fleet/download_installer/FzRCZWTlEY2kqzIwk1BE9fru5KuhrlYP/rpm`. + - The UI can make a `HEAD` request to check if an installer exists, if so, then it can display a download button for it, (if not, "show the current UI"? TBD with UI team) + - The API looks for the installer corresponding to the Global/Team the user is looking at, and returns it for download. diff --git a/proposals/Fleet-Installers.md b/proposals/Fleet-Installers.md new file mode 100644 index 0000000000..f6604e36c4 --- /dev/null +++ b/proposals/Fleet-Installers.md @@ -0,0 +1,469 @@ +# Fleet Installers + +## Goal + +[#5757](https://github.com/fleetdm/fleet/issues/5757) + +``` +As a user, I want to be able to download a Fleet-osquery installer (aka Orbit) in the Fleet UI +so that I can add hosts to Fleet without having to know how to successfully generate an +installer with the `fleetctl package` command. + +Figma wireframes: https://www.figma.com/file/hdALBDsrti77QuDNSzLdkx/?node-id=6740%3A267448 +``` + +## Command `fleetctl package` + +Currently users use the `fleetctl package` command to generate Fleet-osquery packages. + +The `fleetctl package` command has the following *required* configuration that is specific to a "Fleet Deployment": +- `--fleet-url`: The URL that the hosts will use to connect to the Fleet server. +- `--enroll-secret`: Global or team enroll secret to use when enrolling the host to Fleet. + +As the goal states, we would like to provide functionality in Fleet to automatically generate and download such packages from the UI. + +## How + +We can implement such functionality in two ways: + +- Option A. Fleet Server to generate such packages itself. +- Option B. Separate "Packager" service. + +There's a lot of platform specific logic and tooling involved in packaging, and one of Fleet's primary goals is to keep deployment/infrastructure simple for On-Prem deployments. +To that end, we believe the best option is Option B, implementing the functionality as a separate service. + +## Packager Service + +The "Fleet Packager" service will implement all the package generation logic. Think of it like offering the `fleetctl package` command functionality but as a REST service. + +```mermaid +graph LR; + A[User-Agent/
Browser]-- Fleet API -->B[Fleet
UI/Server] + A-- Packager
API -->C + subgraph FleetDM Hosted Infrastructure + direction LR + subgraph
pkg.fleetctl.com + direction TB; + C[Packager
Service] + packages[(Generated
packages)] + end + direction TB; + D[tuf.fleetctl.com] + C-- Fetch
Targets -->D; + end + E[Apple's
Notary
Server] + C-- Notary
API ---->E +``` + +Configurations: +- The `Fleet UI/Server` will allow configuring the "Fleet Packager" URL (default value being FleetDM Hosted Packager service, something like `pkg.fleetctl.com`). +- The `Packager Service` will allow configuring an alternative TUF server URL and TUF roots via an environmental variable (default value being FleetDM Hosted TUF, `tuf.fleetctl.com`). + +Both of these configurations will allow users to deploy their own `Packager Service`. + +### Storage + +The generated packages should be stored on encrypted disk and will expire (will be deleted) after a configurable amount of time (default 30m). +There are two reasons we want to expire generated packages soon: +1. To not store user credentials for too long (URL and enroll secret). +2. To free up space. + +We can use "Storage Optimized" instances (see https://aws.amazon.com/ec2/instance-types/). + +#### Notes + +- As a possible future optimization, we could use "Memory Optimized" instances and store packages in RAM instead of using hard disk. +- S3 could also be used to store such packages, but disk storage is needed to generate the packages in the first place. +So hard-disk will be a dependency anyways (and ideally we would like these packages with sensitive credentials to be stored in one location). +- From Roberto: "sounds like the main bottleneck here will be transferring the data over the network to the user doing the request.". +In other words, we should apply all optimizations on the network (like caching, reducing package size, etc.), that will be our main bottleneck +(not CPU or hard-disk access). + +#### Back of the Envelope + +- `.pkg`s use ~70MB of storage. +- `.msi`s use ~20MB of storage. +- `.deb`s and `.rpm`s use ~75MB of storage. + +Assuming the worst case of ~75 MB for each package: +If we have a ~30TB hard disk, it would allow storing ~400_000 packages simultaneously. + +### Network & Credentials + +The service will require network access to (URLs provided via config): + +- TUF server. +- Apple's Notary Server (for generating `.pkg`). + +The packaging service will need the following credentials (provided via config): + +- Apple credentials: + - Codesign identity. + - Username and Password for notarization. +- TUF server update roots (default will be the hardcoded one for FleetDM's hosted TUF server, tuf.fleetctl.com). + +### Packager REST API + +For the MVF (Minimum Viable Feature) we'll need three APIs: one for creation/submission, one for checking status and another one for the actual download of the package. +All the APIs must be rate-limited to prevent abuse of the system. + +#### 1. Package creation + +`POST /create` + +This endpoint will perform the following operations: +1. Check if a `package_id` already exists (and hasn't been expired) with the exact same arguments, if so, return HTTP 200 with the `package_id`. +2. Generate a [random](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)) `package_id`. +3. Dispatch the creation of a package with ID set to `package_id` and the given request parameters. +4. Return HTTP 200 with the `package_id`. + +This endpoint, which is the entrypoint, should be rate-limited by IP. + +##### Request Fields + +| Name | Type | In | Description | +| ----------------- | ------- | ---- | -------------------------------------------------------------------------------------- | +| type | string | body | **Required.** One of the following values "pkg", "msi", "deb", "rpm" | +| fleet_url | string | body | **Required.** The URL that the hosts will use to connect to the Fleet server | +| enroll_secret | string | body | **Required.** Global or team enroll secret to use when enrolling the host to Fleet | +| retry | boolean | body | Retry a failed package generation (default: false) | +| fleet-certificate | string | body | Server certificate chain | +| insecure | boolean | body | Disable TLS certificate verification (default: false) | +| osqueryd-channel | string | body | Update channel of osqueryd to use (default: "stable") | +| desktop-channel | string | body | Update channel of desktop to use (default: "stable") | +| orbit-channel | string | body | Update channel of Orbit to use (default: "stable") | +| disable-updates | boolean | body | Disable auto updates on the generated package (default: false) | +| debug | boolean | body | Enable debug logging in orbit (default: false) | +| fleet-desktop | boolean | body | Include the Fleet Desktop Application in the package (default: false) | +| update-interval | string | body | Interval that Orbit will use to check for new updates (10s, 1h, etc.) (default: 15m0s) | +| osquery-flagfile | string | body | Flagfile to package and provide to osquery (default: empty) | +| service | boolean | body | Install with a persistence service (launchd, systemd, etc.) (default: true) | + +##### Error in Package Generation + +When the set of arguments correspond to a `package_id` that failed to generate, then: +- If `retry` is `false` (default) it will return such `package_id`. +- If `retry` is set to `true`, the service will dispatch a new package build and return a new `package_id`. + +##### Response Fields + +| Name | Type | In | Description | +| ----------------- | ------- | ---- | -------------------------------- | +| package_id | string | body | ID of the package being created. | + +#### 2. Package Status Check + +`GET /status` + +This endpoint allows checking the status of a package being created. +Clients can poll for the status of a package using this API. + +This endpoint should be rate-limited by `package_id`. + +##### Request Fields + +| Name | Type | In | Description | +| ----------------- | ------- | ----- | ------------------------------------------------------------------------ | +| package_id | string | query | **Required.** ID of the package created with `POST /create` | + +##### Response Fields + +| Name | Type | In | Description | +| ----------------- | ------- | ---- | ---------------------------------------------------------------------------- | +| status | string | body | One of the following values "success", "fail", "in-progress", "expired" | +| download_url | string | body | Set to the download URL if status field is "success" | +| stage | string | body | Set to a "stage" string if status field is "in-progress" (e.g. "notarizing") | +| logs | string | body | Contains logs if status is "failed" | + +#### 3. Package Download + +`GET /download/{package_id}` + +This is the API to download the generated package. + +This endpoint should be rate-limited by `package_id`. + +## Sequence Diagram + +Following is the sequence diagram for the happy-path. + +```mermaid +sequenceDiagram + User-Agent/Browser->>Fleet: GET /api/v1/fleet/config + Fleet-->>User-Agent/Browser: packager_url + User-Agent/Browser->>Packager: POST /create + Packager-->>User-Agent/Browser: package_id + Packager-->>TUF Server: Fetch targets + loop + User-Agent/Browser->>Packager: GET /status + Packager-->>User-Agent/Browser: status == "in-progress" + Note over User-Agent/Browser: Retry every ~10 seconds,
until status is "success" + TUF Server->>Packager: Targets + Note over Packager: Build package + Note over Packager: Sign package + rect rgb(128, 128, 128) + Note right of Packager: (When generating macOS pkgs) + Packager-->>Apple's Notary Server: New SubmissionRequest (upload) + Apple's Notary Server->>Packager: NewSubmissionResponse + Note over Packager: Upload to S3
(see Apple docs) + loop Every ~30 seconds, until status is "Accepted" + Packager->>Apple's Notary Server: Get submission status + Apple's Notary Server-->>Packager: status + end + end + end + User-Agent/Browser->>Packager: GET /download/{package_id} + Packager-->>User-Agent/Browser: Package File +``` + +## Package Generation Time + +Some back of the envelope calculations for the time the user clicks Download to the time the installer is downloaded fully. + +### macOS + +Test running `time fleetctl package --type=pkg --fleet-url=... --enroll-secret=... --fleet-desktop` (from Argentina). + +1. Download packages from TUF and generate raw `.pkg` (16 seconds). +2. Signing (assuming this is negligible). +3. Notarization (~3 minutes, from Zach's tests mentioned below). +4. Download from Packager service (~15 seconds to download a 100 MB file). + +Total: ~4 minutes + +### Windows + +Test running `time fleetctl package --type=msi --fleet-url=... --enroll-secret=... --fleet-desktop` (from Argentina). + +1. Download packages from TUF and generate raw `.msi` (~18 seconds). +2. Download from Packager service (~15 seconds to download a 100 MB file). + +Total: ~30 seconds. + +### Linux + +Test running `time fleetctl package --type={deb|rpm} --fleet-url=... --enroll-secret=... --fleet-desktop` (from Argentina). + +1. Download packages from TUF and generate raw `.{deb|rpm}` (~30 seconds). +2. Download from Packager service (~30 seconds to download a 200 MB file). + +Total: ~1 minute. + +### Possible Download Time Optimization + +- The Packager service could cache the TUF targets for a couple of minutes (given that they usually change ~ every three weeks). +This would reduce 15s-30s the time it takes to generate the packages. + +## Platform Specifics + +### macOS + +There are two operations **required** to have a proper macOS installer package (`.pkg`): + +1. Code signing: Used by Gatekeeper to verify the author of the package (identified by Developer ID) +2. Notarization: "Gives users more confidence that the Developer ID-signed software you distribute has been checked by Apple for malicious components." + +The packages are composed by three TUF targets: osquery, Orbit and Fleet Desktop. + +All the TUF targets served by FleetDM's TUF server are signed and notarized: + +- osquery: signed and notarized .app (by Osquery) +- Orbit: signed and notarized executable (by Fleet DM) +- fleet-desktop: signed and notarized .app (by Fleet DM) + +Even if all targets are signed and notarized we must still sign and notarize the `.pkg` installer as a whole, see [#122045](https://developer.apple.com/forums/thread/122045). + +#### Notarization + +The Notarization process can be summarized to the following steps: + +1. Submit/Upload the **signed** package to the Notary Server. +2. Notary server performs automated security checks. +3. Notary generates a ticket, publishes ticket online (so that Gatekeeper can find it) and returns the ticket. +4. Ticket can be stapled to your software to let Gatekeeper know it has been notarized. + +##### Notarization Limitations + +- Notarization completes for most software within 5 minutes, and for 98 percent of software within 15 minutes. Thus our workflow must support users leaving the "Add hosts" page and returning later. +- To help avoid long response times they suggest: to "limit notarizations to 75 per day." So it doesn't look like a hard limit, just something that would help reduce upload response times. +Source: https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow +Though Zach has ran the following test with no issues: +> I was able to Notarize ~200 packages in under 12 hours yesterday with no apparent limiting. +> Note this was using the older Notarization API (slower, about 3 mins per notarization). + +#### Future Optimizations for Notarization + +This is the current structure of a `.pkg` generated with `fleetctl package` (unsigned and unnotarized): +```sh +pkg_expanded +├── Distribution +└── base + ├── Bom + ├── Library + │   └── LaunchDaemons + │   └── com.fleetdm.orbit.plist + ├── PackageInfo + ├── Payload + ├── Scripts + │   └── postinstall + └── opt + └── orbit + ├── bin + │   ├── desktop + │   │   └── macos + │   │   └── stable + │   │   ├── Fleet Desktop.app + │   │   │   └── Contents + │   │   │   ├── CodeResources + │   │   │   ├── Info.plist + │   │   │   ├── MacOS + │   │   │   │   └── fleet-desktop + │   │   │   └── _CodeSignature + │   │   │   └── CodeResources + │   │   └── desktop.app.tar.gz + │   ├── orbit + │   │   └── macos + │   │   └── stable + │   │   └── orbit + │   └── osqueryd + │   └── macos-app + │   └── stable + │   ├── osquery.app + │   │   └── Contents + │   │   ├── Info.plist + │   │   ├── MacOS + │   │   │   └── osqueryd + │   │   ├── PkgInfo + │   │   ├── Resources + │   │   │   └── osqueryctl + │   │   ├── _CodeSignature + │   │   │   └── CodeResources + │   │   └── embedded.provisionprofile + │   └── osqueryd.app.tar.gz + ├── certs.pem + ├── osquery.flags + ├── secret.txt + ├── staging + └── tuf-metadata.json +``` + +##### Remove Unnecessary Files + +The `osqueryd.app.tar.gz` and `desktop.app.tar.gz` files are used by Orbit to compare with remote targets when checking for updates. +A future optimization could replace those `.app.tar.gz` files with a txt file with the hash of such file +(would reduce ~30MB of uncompressed size reduction for the `.pkg`). + +##### Notarized Package without Configs + +The only files that really change when users generate a pkg are: +- Library/LaunchDaemons/com.fleetdm.orbit.plist (contains the configuration, like URLs, update channels, etc.) +- opt/orbit/secret.txt (enroll secret) + +Another idea to consider could be packaging and notarizing code and scripts, but leave specific configs out of the `.pkg`. +Problem: We would need to solve how to apply the additional configuration (the two files above) to installed packages. + +From [#122512](https://developer.apple.com/forums/thread/122512): + +> I thought there might be some apple approved way of adding extra information to a package. + +>> No. The notarisation ticket covers the code signature of the package, and the code signature of the package covers the +>> effective contents of that package. This is pretty much required. For example, one of your goals is to tweak the install scripts, +>> but such scripts execute with enormous privileges and thus must be covered by the code signature, and thus covered by the notarisation ticket. + +--- + +> Is there a Apple recommended method to provide custom packages for different customers? +> Some way to add additional parameters to a package without breaking the notarizartion/signing of it? + +>> Option 2) Change your distribution strategy to distribute a static executable. +>> Download any customer-specific resources and store them in /Library/Application Support. + +### Windows + +Currently we don't support signing of the MSI installers in `fleetctl package`. But this functionality could be added both to the command and the service. +From Zach: +> When we decide to support this (which we should do soon), we can use https://github.com/mtrojnar/osslsigncode. + +We should also research https://github.com/sassoftware/relic (it mentions Windows signature support) + +## Service Implementation + +It looks like we will be able to implement the first iteration of the Packager Service as a Go service running on a Linux server. +The only limitation will be macOS stapling (see below). + +### Scale + +MVP of the service will support running one instance of the service. Probably ok as the majority of the load will be IO, network and disk. + +Nothing prevent us from horizontal scaling by sharding requests by `package_id` (or a `client_id`) in the future. + +### State storing + +For the first iteration, we should pick one of the following simple options: +1. Store state in-memory. Simplest, but not resilient to crashes/restarts. +2. Store state in an embedded disk database, such as: + - https://github.com/dgraph-io/badger (Pure Go) + - https://github.com/etcd-io/bbolt (Pure Go) + - Sqlite3 (Cgo 🙈), see https://www.youtube.com/watch?v=XcAYkriuQ1o + +- We already need disk storage for storing the packages, so option (2) is feasible to do from the get-go. + +### macOS requirements + +- Code Signing: Looks like https://github.com/sassoftware/relic would allow us to sign a `.pkg` in pure Go. +- Notarization: We can implement a Go package that uses the new [Notary API](https://developer.apple.com/documentation/notaryapi). +Limitation: Such API does not offer a way to "staple" the package, thus we would depend on the `stapler` tool for this last step (such tool is only available on macOS). +So if we run the Packager service on a Linux server, we won't be able to support stapling. However, it seems stapling is recommended but not a must, see [#116812](https://developer.apple.com/forums/thread/116812). +> If Gatekeeper can’t find a notarisation ticket stapled to the item, it attempts to get that ticket from the Apple notarisation servers. +> Assuming the Mac is online, this typically works and thus the Gatekeeper check succeeds. + +### Windows requirements + +We need Docker to generate the MSI for Windows (the `fleetctl package` command uses the `fleetdm/wix:latest` docker image to generate them). +We will need to pre-fetch such Docker Image during initialization (to not delay the first request indefinitely). + +PS: We could alternatively try a PoC that uses Wine+WiX on Linux without Docker? + +### Lambda? + +Depending on dependencies we could implement the service as a Lambda Function. Though we could make this a full service to not be tied to a specific provider. + +## Security / Threat model + +What could go wrong? + +### Fleet Credentials + +The MVP of the service will have access to: +- Fleet's Developer Signing Key (for `.pkg` signing). +- Fleet's Apple Connect Username and Password (for `.pkg` notarization). + +From Guillaume: +> If someone manages to compromise this service, they could potentially sign AND notarize malware under Fleet's identity. + +Remediation: Fleet credentials should be stored on a secrets manager, e.g. [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html). + +### User Credentials + +The Fleet-URL and enroll secret are stored within the generated packages. +If the unexpired installers are leaked, users' Fleet URLs and enroll secrets would be compromised. +Attackers could enroll their devices to users' Fleet deployment. + +An attacker with access to an enroll secret can perform the following attacks: +- Feed a Fleet server with fake data (possibly DoS the service). +- Leak the queries configured in Fleet. + +Remediation: All generated packages should be securely deleted when they expire. + +## Sandbox/Demo & Cloud + +The design supports the following deployments: + +- Fleet On-Prem running with FleetDM's Packager and TUF. +- Fleet On-Prem running with On-Prem Packager with FleetDM's TUF server. +- Fleet On-Prem running with On-Prem Packager with On-Prem TUF server. +- Fleet Sandbox/Demo & Cloud (basically all hosted by FleetDM). + +## New Fleetctl Command + +We could add new flags (e.g. `--remote`) to `fleetctl package` to generate the packages using a Packager Service instead of building locally. \ No newline at end of file diff --git a/proposals/Fleetctl-Docker-Image.md b/proposals/Fleetctl-Docker-Image.md new file mode 100644 index 0000000000..d5f462aa45 --- /dev/null +++ b/proposals/Fleetctl-Docker-Image.md @@ -0,0 +1,51 @@ +# Goal + +We need `fleetctl package` functionality to generate all types of packages (PKG, MSI, DEB and RPM) from Linux. + +# How + +Create a new Docker image `fleetdm/fleetctl` that will contain `fleetctl` and all the dependencies ready to create packages. + +Users can then use the image to generate packages +```sh +$ docker run ... fleetdm/fleetctl:latest package --type={pkg|msi|deb|rpm} ... +``` + +## DEB and RPM + +DEB and RPM package generation is already native and no extra dependencies are required (uses https://github.com/goreleaser/nfpm). + +## MSI + +### Packaging + +We will need the same dependencies from `fleetdm/wix:latest` on the new `fleetdm/fleetctl:latest` image. + +### Signing (stretch goal) + +For `.msi` signing functionality: +- The [relic](https://github.com/sassoftware/relic) tool seems to allow `.msi` signing (in Pure Go). +- Alternatively, the [osslsigncode](https://github.com/mtrojnar/osslsigncode) tool could be embedded on the image. + +This is mentioned as a stretch goal because we currently don't have `.msi` signing functionality in `fleetctl package`. + +## PKG + +### Packaging + +To generate a `.pkg` we will need the same dependencies from `fleetdm/bomutils:latest` on the new `fleetdm/fleetctl:latest` image. + +### Signing + +The [relic](https://github.com/sassoftware/relic) tool seems to allow `.pkg` signing (in Pure Go). + +### Notarization + +#### Upload + +We can implement a Go package that uses the new [Notary API](https://developer.apple.com/documentation/notaryapi) to upload and notarize a `.pkg` (pure Go solution). + +#### No Stapling + +The Notary API currently does not offer a way to "staple" a package, and the `stapler` tool that allows this is only available on macOS. +It seems stapling is recommended but not a must, see [#116812](https://developer.apple.com/forums/thread/116812). \ No newline at end of file