2021-09-09 05:34:12 +00:00
package main
import (
2023-04-27 11:44:39 +00:00
"crypto/tls"
2021-12-30 01:32:55 +00:00
"encoding/pem"
2021-11-22 14:13:26 +00:00
"errors"
2021-11-15 13:40:58 +00:00
"fmt"
2023-10-27 18:28:54 +00:00
"os"
2021-11-15 13:40:58 +00:00
"path/filepath"
2021-10-27 23:17:41 +00:00
"runtime"
2024-02-05 12:41:06 +00:00
"strings"
2022-04-11 20:42:36 +00:00
"time"
2021-10-27 23:17:41 +00:00
2023-09-25 17:38:03 +00:00
eefleetctl "github.com/fleetdm/fleet/v4/ee/fleetctl"
2021-09-09 05:34:12 +00:00
"github.com/fleetdm/fleet/v4/orbit/pkg/packaging"
2025-01-10 17:27:30 +00:00
"github.com/fleetdm/fleet/v4/orbit/pkg/update"
2024-02-05 12:41:06 +00:00
"github.com/fleetdm/fleet/v4/pkg/filepath_windows"
2023-12-21 17:22:59 +00:00
"github.com/fleetdm/fleet/v4/server/fleet"
2021-11-15 13:40:58 +00:00
"github.com/rs/zerolog"
zlog "github.com/rs/zerolog/log"
"github.com/skratchdot/open-golang/open"
2021-09-09 05:34:12 +00:00
"github.com/urfave/cli/v2"
)
2022-04-27 21:17:20 +00:00
var (
opt packaging . Options
disableOpenFolder bool
)
2021-09-09 05:34:12 +00:00
func packageCommand ( ) * cli . Command {
return & cli . Command {
Name : "package" ,
Aliases : nil ,
2024-04-26 20:10:28 +00:00
Usage : "Create a fleetd agent" ,
2021-09-09 05:34:12 +00:00
Description : "An easy way to create fully boot-strapped installer packages for Windows, macOS, or Linux" ,
Flags : [ ] cli . Flag {
& cli . StringFlag {
Name : "type" ,
Usage : "Type of package to build" ,
Required : true ,
} ,
2024-07-17 20:07:59 +00:00
& cli . StringFlag {
Name : "arch" ,
2025-04-11 14:18:28 +00:00
Usage : "Target CPU Architecture for the installer package (Only supported with '--type' deb, rpm, or msi)" ,
2024-07-17 20:07:59 +00:00
Destination : & opt . Architecture ,
Value : "amd64" ,
} ,
2021-09-09 05:34:12 +00:00
& cli . StringFlag {
Name : "enroll-secret" ,
Usage : "Enroll secret for authenticating to Fleet server" ,
Destination : & opt . EnrollSecret ,
} ,
& cli . StringFlag {
Name : "fleet-url" ,
Usage : "URL (host:port) of Fleet server" ,
Destination : & opt . FleetURL ,
} ,
& cli . StringFlag {
Name : "fleet-certificate" ,
2023-04-27 11:44:39 +00:00
Usage : "Path to the Fleet server certificate chain" ,
2021-09-09 05:34:12 +00:00
Destination : & opt . FleetCertificate ,
} ,
2023-04-27 11:44:39 +00:00
& cli . StringFlag {
Name : "fleet-tls-client-certificate" ,
Usage : "Path to a TLS client certificate to use when connecting to the Fleet server. This functionality is licensed under the Fleet EE License. Usage requires a current Fleet EE subscription." ,
Destination : & opt . FleetTLSClientCertificate ,
} ,
& cli . StringFlag {
Name : "fleet-tls-client-key" ,
Usage : "Path to a TLS client private key to use when connecting to the Fleet server. This functionality is licensed under the Fleet EE License. Usage requires a current Fleet EE subscription." ,
Destination : & opt . FleetTLSClientKey ,
} ,
& cli . StringFlag {
Name : "fleet-desktop-alternative-browser-host" ,
Usage : "Alternative host:port to use for Fleet Desktop in the browser (this may be required when using TLS client authentication in the Fleet server)" ,
Destination : & opt . FleetDesktopAlternativeBrowserHost ,
} ,
2021-09-09 05:34:12 +00:00
& cli . StringFlag {
Name : "identifier" ,
Usage : "Identifier for package product" ,
Value : "com.fleetdm.orbit" ,
Destination : & opt . Identifier ,
} ,
& cli . BoolFlag {
Name : "insecure" ,
Usage : "Disable TLS certificate verification" ,
Destination : & opt . Insecure ,
} ,
& cli . BoolFlag {
Name : "service" ,
Usage : "Install orbit/osquery with a persistence service (launchd, systemd, etc.)" ,
Value : true ,
Destination : & opt . StartService ,
} ,
& cli . StringFlag {
Name : "sign-identity" ,
Usage : "Identity to use for macOS codesigning" ,
Destination : & opt . SignIdentity ,
} ,
& cli . BoolFlag {
Name : "notarize" ,
Usage : "Whether to notarize macOS packages" ,
Destination : & opt . Notarize ,
} ,
& cli . StringFlag {
Name : "osqueryd-channel" ,
Usage : "Update channel of osqueryd to use" ,
Value : "stable" ,
Destination : & opt . OsquerydChannel ,
} ,
2022-03-21 17:53:53 +00:00
& cli . StringFlag {
Name : "desktop-channel" ,
Usage : "Update channel of desktop to use" ,
Value : "stable" ,
Destination : & opt . DesktopChannel ,
} ,
2021-09-09 05:34:12 +00:00
& cli . StringFlag {
Name : "orbit-channel" ,
Usage : "Update channel of Orbit to use" ,
Value : "stable" ,
Destination : & opt . OrbitChannel ,
} ,
2022-02-18 18:42:39 +00:00
& cli . BoolFlag {
Name : "disable-updates" ,
Usage : "Disable auto updates on the generated package" ,
Destination : & opt . DisableUpdates ,
} ,
2021-09-09 05:34:12 +00:00
& cli . StringFlag {
Name : "update-url" ,
Usage : "URL for update server" ,
2025-01-10 17:27:30 +00:00
Value : update . DefaultURL ,
2021-09-09 05:34:12 +00:00
Destination : & opt . UpdateURL ,
} ,
& cli . StringFlag {
Name : "update-roots" ,
Usage : "Root key JSON metadata for update server (from fleetctl updates roots)" ,
Destination : & opt . UpdateRoots ,
} ,
2023-04-27 11:44:39 +00:00
& cli . StringFlag {
Name : "update-tls-certificate" ,
Usage : "Path to the update server TLS certificate chain" ,
Destination : & opt . UpdateTLSServerCertificate ,
} ,
& cli . StringFlag {
Name : "update-tls-client-certificate" ,
Usage : "Path to a TLS client certificate to use when connecting to the update server. This functionality is licensed under the Fleet EE License. Usage requires a current Fleet EE subscription." ,
Destination : & opt . UpdateTLSClientCertificate ,
} ,
& cli . StringFlag {
Name : "update-tls-client-key" ,
Usage : "Path to a TLS client private key to use when connecting to the update server. This functionality is licensed under the Fleet EE License. Usage requires a current Fleet EE subscription." ,
Destination : & opt . UpdateTLSClientKey ,
} ,
2021-11-18 23:06:33 +00:00
& cli . StringFlag {
Name : "osquery-flagfile" ,
Usage : "Flagfile to package and provide to osquery" ,
Destination : & opt . OsqueryFlagfile ,
} ,
2021-09-09 05:34:12 +00:00
& cli . BoolFlag {
Name : "debug" ,
2021-11-15 13:40:58 +00:00
Usage : "Enable debug logging in orbit" ,
2021-09-09 05:34:12 +00:00
Destination : & opt . Debug ,
} ,
2021-11-15 13:40:58 +00:00
& cli . BoolFlag {
Name : "verbose" ,
Usage : "Log detailed information when building the package" ,
} ,
2022-03-21 17:53:53 +00:00
& cli . BoolFlag {
Name : "fleet-desktop" ,
Usage : "Include the Fleet Desktop Application in the package" ,
Destination : & opt . Desktop ,
} ,
2022-04-11 20:42:36 +00:00
& cli . DurationFlag {
Name : "update-interval" ,
Usage : "Interval that Orbit will use to check for new updates (10s, 1h, etc.)" ,
Value : 15 * time . Minute ,
Destination : & opt . OrbitUpdateInterval ,
} ,
2022-04-27 21:17:20 +00:00
& cli . BoolFlag {
Name : "disable-open-folder" ,
Usage : "Disable opening the folder at the end" ,
Destination : & disableOpenFolder ,
} ,
2022-07-11 12:49:13 +00:00
& cli . BoolFlag {
Name : "native-tooling" ,
Usage : "Build the package using native tooling (only available in Linux)" ,
EnvVars : [ ] string { "FLEETCTL_NATIVE_TOOLING" } ,
Destination : & opt . NativeTooling ,
} ,
2023-09-25 17:38:03 +00:00
eefleetctl . LocalWixDirFlag ( & opt . LocalWixDir ) ,
2022-07-25 23:06:10 +00:00
& cli . StringFlag {
Name : "macos-devid-pem-content" ,
Usage : "Dev ID certificate keypair content in PEM format" ,
EnvVars : [ ] string { "FLEETCTL_MACOS_DEVID_PEM_CONTENT" } ,
Destination : & opt . MacOSDevIDCertificateContent ,
} ,
& cli . StringFlag {
Name : "app-store-connect-api-key-id" ,
Usage : "App Store Connect API key used for notarization" ,
EnvVars : [ ] string { "FLEETCTL_APP_STORE_CONNECT_API_KEY_ID" } ,
Destination : & opt . AppStoreConnectAPIKeyID ,
} ,
& cli . StringFlag {
Name : "app-store-connect-api-key-issuer" ,
Usage : "Issuer of the App Store Connect API key" ,
EnvVars : [ ] string { "FLEETCTL_APP_STORE_CONNECT_API_KEY_ISSUER" } ,
Destination : & opt . AppStoreConnectAPIKeyIssuer ,
} ,
& cli . StringFlag {
Name : "app-store-connect-api-key-content" ,
Usage : "Contents of the .p8 App Store Connect API key" ,
EnvVars : [ ] string { "FLEETCTL_APP_STORE_CONNECT_API_KEY_CONTENT" } ,
Destination : & opt . AppStoreConnectAPIKeyContent ,
} ,
2023-04-05 18:02:18 +00:00
& cli . BoolFlag {
Name : "use-system-configuration" ,
2023-04-27 11:44:39 +00:00
Usage : "Try to read --fleet-url and --enroll-secret using configuration in the host (currently only macOS profiles are supported)" ,
2023-04-05 18:02:18 +00:00
EnvVars : [ ] string { "FLEETCTL_USE_SYSTEM_CONFIGURATION" } ,
Destination : & opt . UseSystemConfiguration ,
} ,
2023-08-30 13:18:34 +00:00
& cli . BoolFlag {
Name : "enable-scripts" ,
Usage : "Enable script execution" ,
EnvVars : [ ] string { "FLEETCTL_ENABLE_SCRIPTS" } ,
Destination : & opt . EnableScripts ,
} ,
2023-12-15 18:26:32 +00:00
& cli . StringFlag {
Name : "host-identifier" ,
Usage : "Sets the host identifier that orbit and osquery will use when enrolling to Fleet. Options: 'uuid' and 'instance' (requires Fleet >= v4.42.0)" ,
Value : "uuid" ,
EnvVars : [ ] string { "FLEETCTL_HOST_IDENTIFIER" } ,
Destination : & opt . HostIdentifier ,
} ,
2023-12-21 17:22:59 +00:00
& cli . StringFlag {
Name : "end-user-email" ,
2024-06-24 20:13:04 +00:00
Usage : "End user's email that populates human to host mapping in Fleet (only available on Windows and Linux)" ,
2023-12-21 17:22:59 +00:00
EnvVars : [ ] string { "FLEETCTL_END_USER_EMAIL" } ,
Destination : & opt . EndUserEmail ,
} ,
2024-01-16 12:51:37 +00:00
& cli . BoolFlag {
Name : "disable-keystore" ,
Usage : "Disables the use of the keychain on macOS and Credentials Manager on Windows" ,
EnvVars : [ ] string { "FLEETCTL_DISABLE_KEYSTORE" } ,
Destination : & opt . DisableKeystore ,
} ,
2024-02-05 12:41:06 +00:00
& cli . StringFlag {
Name : "osquery-db" ,
Usage : "Sets a custom osquery database directory, it must be an absolute path (requires orbit >= v1.22.0)" ,
EnvVars : [ ] string { "FLEETCTL_OSQUERY_DB" } ,
Destination : & opt . OsqueryDB ,
} ,
2021-09-09 05:34:12 +00:00
} ,
Action : func ( c * cli . Context ) error {
if opt . FleetURL != "" || opt . EnrollSecret != "" {
if opt . FleetURL == "" || opt . EnrollSecret == "" {
return errors . New ( "--enroll-secret and --fleet-url must be provided together" )
}
}
if opt . Insecure && opt . FleetCertificate != "" {
return errors . New ( "--insecure and --fleet-certificate may not be provided together" )
}
2023-04-27 11:44:39 +00:00
if opt . Insecure && opt . UpdateTLSServerCertificate != "" {
return errors . New ( "--insecure and --update-tls-certificate may not be provided together" )
}
2023-12-15 18:26:32 +00:00
if opt . HostIdentifier != "uuid" && opt . HostIdentifier != "instance" {
return fmt . Errorf ( "--host-identifier=%s is not supported, currently supported values are 'uuid' and 'instance'" , opt . HostIdentifier )
}
2023-04-27 11:44:39 +00:00
// Perform checks on the provided fleet client certificate and key.
if ( opt . FleetTLSClientCertificate != "" ) != ( opt . FleetTLSClientKey != "" ) {
return errors . New ( "must specify both fleet-tls-client-certificate and fleet-tls-client-key" )
}
if opt . FleetTLSClientKey != "" {
if _ , err := tls . LoadX509KeyPair ( opt . FleetTLSClientCertificate , opt . FleetTLSClientKey ) ; err != nil {
return fmt . Errorf ( "error loading fleet client certificate and key: %w" , err )
}
}
// Perform checks on the provided update client certificate and key.
if ( opt . UpdateTLSClientCertificate != "" ) != ( opt . UpdateTLSClientKey != "" ) {
return errors . New ( "must specify both update-tls-client-certificate and update-tls-client-key" )
}
if opt . UpdateTLSClientKey != "" {
if _ , err := tls . LoadX509KeyPair ( opt . UpdateTLSClientCertificate , opt . UpdateTLSClientKey ) ; err != nil {
return fmt . Errorf ( "error loading update client certificate and key: %w" , err )
}
}
2024-02-05 12:41:06 +00:00
if opt . OsqueryDB != "" && ! isAbsolutePath ( opt . OsqueryDB , c . String ( "type" ) ) {
return fmt . Errorf ( "--osquery-db must be an absolute path: %q" , opt . OsqueryDB )
}
2021-10-27 23:17:41 +00:00
if runtime . GOOS == "windows" && c . String ( "type" ) != "msi" {
return errors . New ( "Windows can only build MSI packages." )
}
2022-07-11 12:49:13 +00:00
if opt . NativeTooling && runtime . GOOS != "linux" {
return errors . New ( "native tooling is only available in Linux" )
}
2024-01-30 17:08:21 +00:00
if opt . LocalWixDir != "" && runtime . GOOS != "windows" && runtime . GOOS != "darwin" {
return errors . New (
` Could not use local WiX to generate an osquery installer . This option is only available on Windows and macOS .
2023-09-22 15:49:01 +00:00
Visit https : //wixtoolset.org/ for more information about how to use WiX.`)
}
2023-12-21 17:22:59 +00:00
if opt . EndUserEmail != "" {
if ! fleet . IsLooseEmail ( opt . EndUserEmail ) {
return errors . New ( "Invalid email address specified for --end-user-email." )
}
2024-06-18 15:10:19 +00:00
switch c . String ( "type" ) {
case "msi" , "deb" , "rpm" :
// ok
default :
return errors . New ( "Can only set --end-user-email when building an MSI, DEB, or RPM package." )
}
2023-12-21 17:22:59 +00:00
}
2021-12-30 01:32:55 +00:00
if opt . FleetCertificate != "" {
err := checkPEMCertificate ( opt . FleetCertificate )
if err != nil {
2023-04-27 11:44:39 +00:00
return fmt . Errorf ( "failed to read fleet server certificate %q: %w" , opt . FleetCertificate , err )
}
}
if opt . UpdateTLSServerCertificate != "" {
err := checkPEMCertificate ( opt . UpdateTLSServerCertificate )
if err != nil {
return fmt . Errorf ( "failed to read update server certificate %q: %w" , opt . UpdateTLSServerCertificate , err )
2021-12-30 01:32:55 +00:00
}
}
2023-04-05 18:02:18 +00:00
if opt . UseSystemConfiguration && c . String ( "type" ) != "pkg" {
return errors . New ( "--use-system-configuration is only available for pkg installers" )
}
2024-07-17 20:07:59 +00:00
linuxPackage := false
switch c . String ( "type" ) {
case "deb" , "rpm" :
linuxPackage = true
}
2025-04-11 14:18:28 +00:00
windowsPackage := c . String ( "type" ) == "msi"
2024-07-17 20:07:59 +00:00
2025-04-11 14:18:28 +00:00
if opt . Architecture != packaging . ArchAmd64 && ! ( linuxPackage || windowsPackage ) {
2024-07-17 20:07:59 +00:00
return fmt . Errorf ( "can't use '--arch' with '--type %s'" , c . String ( "type" ) )
}
if opt . Architecture != packaging . ArchAmd64 && opt . Architecture != packaging . ArchArm64 {
return errors . New ( "arch must be one of ('amd64', 'arm64')" )
}
2021-11-15 13:40:58 +00:00
var buildFunc func ( packaging . Options ) ( string , error )
2021-09-09 05:34:12 +00:00
switch c . String ( "type" ) {
case "pkg" :
2025-04-11 14:18:28 +00:00
opt . NativePlatform = "darwin"
2021-11-15 13:40:58 +00:00
buildFunc = packaging . BuildPkg
2021-09-09 05:34:12 +00:00
case "deb" :
2025-04-11 14:18:28 +00:00
if opt . Architecture == packaging . ArchAmd64 {
opt . NativePlatform = "linux"
} else {
opt . NativePlatform = "linux-arm64"
}
2021-11-15 13:40:58 +00:00
buildFunc = packaging . BuildDeb
2021-09-09 05:34:12 +00:00
case "rpm" :
2025-04-11 14:18:28 +00:00
if opt . Architecture == packaging . ArchAmd64 {
opt . NativePlatform = "linux"
} else {
opt . NativePlatform = "linux-arm64"
}
2021-11-15 13:40:58 +00:00
buildFunc = packaging . BuildRPM
2021-09-09 05:34:12 +00:00
case "msi" :
2025-04-11 14:18:28 +00:00
if opt . Architecture == packaging . ArchAmd64 {
opt . NativePlatform = "windows"
} else {
opt . NativePlatform = "windows-arm64"
}
2021-11-15 13:40:58 +00:00
buildFunc = packaging . BuildMSI
2021-09-09 05:34:12 +00:00
default :
return errors . New ( "type must be one of ('pkg', 'deb', 'rpm', 'msi')" )
}
2021-11-15 13:40:58 +00:00
// disable detailed logging unless verbose is set
if ! c . Bool ( "verbose" ) {
zlog . Logger = zerolog . Nop ( )
}
2024-02-06 14:30:00 +00:00
fmt . Println ( "Generating your fleetd agent..." )
2024-01-30 17:08:21 +00:00
path , err := buildFunc ( opt )
2021-11-15 13:40:58 +00:00
if err != nil {
return err
}
2022-08-24 18:52:32 +00:00
2021-11-15 13:40:58 +00:00
path , _ = filepath . Abs ( path )
fmt . Printf ( `
2024-02-06 14:30:00 +00:00
Success ! You generated fleetd at % s
2021-11-15 13:40:58 +00:00
2025-03-04 18:21:07 +00:00
To add hosts to Fleet , install fleetd .
Learn how : https : //fleetdm.com/learn-more-about/enrolling-hosts
` , path )
2022-04-27 21:17:20 +00:00
if ! disableOpenFolder {
2022-12-05 22:50:49 +00:00
open . Start ( filepath . Dir ( path ) ) //nolint:errcheck
2022-04-27 21:17:20 +00:00
}
2021-11-15 13:40:58 +00:00
return nil
2021-09-09 05:34:12 +00:00
} ,
}
}
2021-12-30 01:32:55 +00:00
func checkPEMCertificate ( path string ) error {
2023-10-27 18:28:54 +00:00
cert , err := os . ReadFile ( path )
2021-12-30 01:32:55 +00:00
if err != nil {
return err
}
if p , _ := pem . Decode ( cert ) ; p == nil {
return errors . New ( "invalid PEM file" )
}
return nil
}
2024-02-05 12:41:06 +00:00
// isAbsolutePath returns whether a path is absolute.
// It does not make use of filepath.IsAbs to support
// checking Windows paths from Go code running in unix.
func isAbsolutePath ( path , pkgType string ) bool {
if pkgType == "msi" {
return filepath_windows . IsAbs ( path )
}
return strings . HasPrefix ( path , "/" ) // this is the unix implementation of filepath.IsAbs
}