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"
2021-12-30 01:32:55 +00:00
"io/ioutil"
2021-11-15 13:40:58 +00:00
"path/filepath"
2021-10-27 23:17:41 +00:00
"runtime"
2022-08-24 18:52:32 +00:00
"strings"
2022-04-11 20:42:36 +00:00
"time"
2021-10-27 23:17:41 +00:00
2021-09-09 05:34:12 +00:00
"github.com/fleetdm/fleet/v4/orbit/pkg/packaging"
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 ,
Usage : "Create an Orbit installer package" ,
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 ,
} ,
& 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 . StringFlag {
Name : "version" ,
Usage : "Version for package product" ,
Destination : & opt . Version ,
} ,
& 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" ,
Value : "https://tuf.fleetctl.com" ,
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 ,
} ,
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 ,
} ,
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" )
}
// 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 )
}
}
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" )
}
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" )
}
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" :
2021-11-15 13:40:58 +00:00
buildFunc = packaging . BuildPkg
2021-09-09 05:34:12 +00:00
case "deb" :
2021-11-15 13:40:58 +00:00
buildFunc = packaging . BuildDeb
2021-09-09 05:34:12 +00:00
case "rpm" :
2021-11-15 13:40:58 +00:00
buildFunc = packaging . BuildRPM
2021-09-09 05:34:12 +00:00
case "msi" :
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 ( )
}
2022-08-24 18:52:32 +00:00
const maxAttempts = 9 // see #5732
var (
attempts int
path string
err error
)
for attempts < maxAttempts {
attempts ++
if attempts > 1 {
fmt . Printf ( "Generating your osquery installer [attempt %d/%d]...\n\n" , attempts , maxAttempts )
} else {
fmt . Println ( "Generating your osquery installer..." )
}
path , err = buildFunc ( opt )
if err == nil || ! shouldRetry ( c . String ( "type" ) , opt , err ) {
break
}
}
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 ( `
Success ! You generated an osquery installer at % s
To add this device to Fleet , double - click to open your installer .
To add other devices to Fleet , distribute this installer using Chef , Ansible , Jamf , or Puppet . Learn how : https : //fleetdm.com/docs/using-fleet/adding-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
2022-08-24 18:52:32 +00:00
func shouldRetry ( pkgType string , opt packaging . Options , err error ) bool {
if pkgType != "msi" || runtime . GOOS != "darwin" || runtime . GOARCH != "arm64" {
return false
}
// building an MSI on macos M1, check if the error is one that should be retried
errStr := err . Error ( )
switch {
case strings . Contains ( errStr , "package root files: heat failed" ) :
return true
case strings . Contains ( errStr , "build package: candle failed" ) :
return true
case strings . Contains ( errStr , "build package: light failed" ) :
return true
default :
return false
}
}
2021-12-30 01:32:55 +00:00
func checkPEMCertificate ( path string ) error {
cert , err := ioutil . ReadFile ( path )
if err != nil {
return err
}
if p , _ := pem . Decode ( cert ) ; p == nil {
return errors . New ( "invalid PEM file" )
}
return nil
}