From d3a0cf6e84588cd932508cf093cf9bdb973afa1f Mon Sep 17 00:00:00 2001 From: zack olson Date: Tue, 13 Aug 2024 12:29:19 -0400 Subject: [PATCH 1/9] add identifier to launcher.flags, add ServiceName helpers to launcher package and integrate with windows svc --- cmd/launcher/svc_config_windows.go | 94 ++++++++++++------------------ pkg/launcher/options.go | 5 ++ pkg/launcher/pkg_utils_darwin.go | 28 +++++++++ pkg/launcher/pkg_utils_linux.go | 28 +++++++++ pkg/launcher/pkg_utils_windows.go | 29 +++++++++ pkg/packaging/packaging.go | 1 + 6 files changed, 128 insertions(+), 57 deletions(-) create mode 100644 pkg/launcher/pkg_utils_darwin.go create mode 100644 pkg/launcher/pkg_utils_linux.go create mode 100644 pkg/launcher/pkg_utils_windows.go diff --git a/cmd/launcher/svc_config_windows.go b/cmd/launcher/svc_config_windows.go index 05ebf7cbf..cbc009c1f 100644 --- a/cmd/launcher/svc_config_windows.go +++ b/cmd/launcher/svc_config_windows.go @@ -5,6 +5,7 @@ package main import ( "context" + "fmt" "log/slog" "time" @@ -15,8 +16,7 @@ import ( ) const ( - launcherServiceName = `LauncherKolideK2Svc` - launcherServiceRegistryKeyName = `SYSTEM\CurrentControlSet\Services\LauncherKolideK2Svc` + launcherServiceRegistryKeyNameFmt = `SYSTEM\CurrentControlSet\Services\%s` // DelayedAutostart is type REG_DWORD, i.e. uint32. We want to turn off delayed autostart. delayedAutostartName = `DelayedAutostart` @@ -35,6 +35,10 @@ func checkServiceConfiguration(logger *slog.Logger, opts *launcher.Options) { return } + // get the service name to generate the service key + launcherServiceName := launcher.ServiceName(opts.Identifier) + launcherServiceRegistryKeyName := fmt.Sprintf(launcherServiceRegistryKeyNameFmt, launcherServiceName) + // Get launcher service key launcherServiceKey, err := registry.OpenKey(registry.LOCAL_MACHINE, launcherServiceRegistryKeyName, registry.ALL_ACCESS) if err != nil { @@ -64,9 +68,33 @@ func checkServiceConfiguration(logger *slog.Logger, opts *launcher.Options) { // Check to see if we need to update the service to depend on Dnscache checkDependOnService(launcherServiceKey, logger) - checkRestartActions(logger) + sman, err := mgr.Connect() + if err != nil { + logger.Log(context.TODO(), slog.LevelError, + "connecting to service control manager", + "err", err, + ) + + return + } + + defer sman.Disconnect() + + launcherService, err := sman.OpenService(launcherServiceName) + if err != nil { + logger.Log(context.TODO(), slog.LevelError, + "opening the launcher service from control manager", + "err", err, + ) - setRecoveryActions(context.TODO(), logger) + return + } + + defer launcherService.Close() + + checkRestartActions(logger, launcherService) + + setRecoveryActions(context.TODO(), logger, launcherService) } // checkDelayedAutostart checks the current value of `DelayedAutostart` (whether to wait ~2 minutes @@ -137,32 +165,8 @@ func checkDependOnService(launcherServiceKey registry.Key, logger *slog.Logger) // sets it to true if required. See https://learn.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_failure_actions_flag // if we choose to implement restart backoff, that logic must be added here (it is not exposed via wix). See the "Windows Service Manager" // doc in Notion for additional details on configurability -func checkRestartActions(logger *slog.Logger) { - sman, err := mgr.Connect() - if err != nil { - logger.Log(context.TODO(), slog.LevelError, - "connecting to service control manager", - "err", err, - ) - - return - } - - defer sman.Disconnect() - - launcherService, err := sman.OpenService(launcherServiceName) - if err != nil { - logger.Log(context.TODO(), slog.LevelError, - "opening the launcher service from control manager", - "err", err, - ) - - return - } - - defer launcherService.Close() - - curFlag, err := launcherService.RecoveryActionsOnNonCrashFailures() +func checkRestartActions(logger *slog.Logger, service *mgr.Service) { + curFlag, err := service.RecoveryActionsOnNonCrashFailures() if err != nil { logger.Log(context.TODO(), slog.LevelError, "querying for current RecoveryActionsOnNonCrashFailures flag", @@ -176,7 +180,7 @@ func checkRestartActions(logger *slog.Logger) { return } - if err = launcherService.SetRecoveryActionsOnNonCrashFailures(true); err != nil { + if err = service.SetRecoveryActionsOnNonCrashFailures(true); err != nil { logger.Log(context.TODO(), slog.LevelError, "setting RecoveryActionsOnNonCrashFailures flag", "err", err, @@ -190,31 +194,7 @@ func checkRestartActions(logger *slog.Logger) { // setRecoveryActions sets the recovery actions for the launcher service. // previously defined via wix ServicConfig Element (Util Extension) https://wixtoolset.org/docs/v3/xsd/util/serviceconfig/ -func setRecoveryActions(ctx context.Context, logger *slog.Logger) { - sman, err := mgr.Connect() - if err != nil { - logger.Log(ctx, slog.LevelError, - "connecting to service control manager", - "err", err, - ) - - return - } - - defer sman.Disconnect() - - launcherService, err := sman.OpenService(launcherServiceName) - if err != nil { - logger.Log(ctx, slog.LevelError, - "opening the launcher service from control manager", - "err", err, - ) - - return - } - - defer launcherService.Close() - +func setRecoveryActions(ctx context.Context, logger *slog.Logger, service *mgr.Service) { recoveryActions := []mgr.RecoveryAction{ { // first failure @@ -233,7 +213,7 @@ func setRecoveryActions(ctx context.Context, logger *slog.Logger) { }, } - if err := launcherService.SetRecoveryActions(recoveryActions, 24*60*60); err != nil { // 24 hours + if err := service.SetRecoveryActions(recoveryActions, 24*60*60); err != nil { // 24 hours logger.Log(ctx, slog.LevelError, "setting RecoveryActions", "err", err, diff --git a/pkg/launcher/options.go b/pkg/launcher/options.go index c65a419c8..55c03a780 100644 --- a/pkg/launcher/options.go +++ b/pkg/launcher/options.go @@ -134,6 +134,9 @@ type Options struct { // LocalDevelopmentPath is the path to a local build of launcher to test against, rather than finding the latest version in the library LocalDevelopmentPath string + + // Identifier is the key used to identify/namespace a single launcher installation (e.g. kolide-k2) + Identifier string } // ConfigFilePath returns the path to launcher's launcher.flags file. If the path @@ -254,6 +257,7 @@ func ParseOptions(subcommandName string, args []string) (*Options, error) { flIAmBreakingEELicense = flagset.Bool("i-am-breaking-ee-license", false, "Skip license check before running localserver (default: false)") flDelayStart = flagset.Duration("delay_start", 0*time.Second, "How much time to wait before starting launcher") flLocalDevelopmentPath = flagset.String("localdev_path", "", "Path to local launcher build") + flPackageIdentifier = flagset.String("identifier", "kolide-k2", "packaging identifier used to determine service names, paths, etc. (default: kolide-k2)") // deprecated options, kept for any kind of config file compatibility _ = flagset.String("debug_log_file", "", "DEPRECATED") @@ -388,6 +392,7 @@ func ParseOptions(subcommandName string, args []string) (*Options, error) { Debug: *flDebug, DelayStart: *flDelayStart, DisableControlTLS: disableControlTLS, + Identifier: *flPackageIdentifier, InsecureControlTLS: insecureControlTLS, EnableInitialRunner: *flInitialRunner, WatchdogEnabled: *flWatchdogEnabled, diff --git a/pkg/launcher/pkg_utils_darwin.go b/pkg/launcher/pkg_utils_darwin.go new file mode 100644 index 000000000..fd014f9fa --- /dev/null +++ b/pkg/launcher/pkg_utils_darwin.go @@ -0,0 +1,28 @@ +//go:build darwin +// +build darwin + +package launcher + +import ( + "fmt" + "regexp" + "strings" +) + +// allow alphanumeric characters plus - or _ within the identifier, we will replace anything else with a dash +var darwinIdentifierWhitelistRegex = regexp.MustCompile(`[^a-zA-Z0-9-_]+`) + +const defaultLauncherIdentifier string = "kolide-k2" + +// ServiceName embeds the given identifier into our service name template after sanitization, +// and returns the service name (label) generated to match our packaging logic +func ServiceName(identifier string) string { + // this check might be overkill but is intended to prevent any backwards compatibility/misconfiguration issues + if strings.TrimSpace(identifier) == "" { + identifier = defaultLauncherIdentifier + } + + sanitizedServiceName := darwinIdentifierWhitelistRegex.ReplaceAllString(identifier, "-") + + return fmt.Sprintf("com.%s.launcher", sanitizedServiceName) +} diff --git a/pkg/launcher/pkg_utils_linux.go b/pkg/launcher/pkg_utils_linux.go new file mode 100644 index 000000000..5b1ee7b75 --- /dev/null +++ b/pkg/launcher/pkg_utils_linux.go @@ -0,0 +1,28 @@ +//go:build linux +// +build linux + +package launcher + +import ( + "fmt" + "regexp" + "strings" +) + +// allow alphanumeric characters plus - or _ within the identifier, we will replace anything else with a dash +var linuxIdentifierWhitelistRegex = regexp.MustCompile(`[^a-zA-Z0-9-_]+`) + +const defaultLauncherIdentifier string = "kolide-k2" + +// ServiceName embeds the given identifier into our service name template after sanitization, +// and returns the service name (label) generated to match our packaging logic +func ServiceName(identifier string) string { + // this check might be overkill but is intended to prevent any backwards compatibility/misconfiguration issues + if strings.TrimSpace(identifier) == "" { + identifier = defaultLauncherIdentifier + } + + sanitizedServiceName := linuxIdentifierWhitelistRegex.ReplaceAllString(identifier, "-") + + return fmt.Sprintf("launcher.%s.service", sanitizedServiceName) +} diff --git a/pkg/launcher/pkg_utils_windows.go b/pkg/launcher/pkg_utils_windows.go new file mode 100644 index 000000000..cf5e6b4a0 --- /dev/null +++ b/pkg/launcher/pkg_utils_windows.go @@ -0,0 +1,29 @@ +//go:build windows +// +build windows + +package launcher + +import ( + "fmt" + "regexp" + "strings" + + "github.com/serenize/snaker" +) + +var nonAlphanumericRegex = regexp.MustCompile(`[^a-zA-Z0-9]+`) + +const defaultLauncherIdentifier string = "kolide-k2" + +// ServiceName embeds the given identifier into our service name template after sanitization, +// and returns the camelCased service name generated to match our packaging logic +func ServiceName(identifier string) string { + // this check might be overkill but is intended to prevent any backwards compatibility/misconfiguration issues + if strings.TrimSpace(identifier) == "" { + identifier = defaultLauncherIdentifier + } + + sanitizedServiceName := nonAlphanumericRegex.ReplaceAllString(identifier, "_") // e.g. identifier=kolide-k2 becomes kolide_k2 + sanitizedServiceName = fmt.Sprintf("launcher_%s_svc", sanitizedServiceName) // wrapped as launcher_kolide_k2_svc + return snaker.SnakeToCamel(sanitizedServiceName) // will produce LauncherKolideK2Svc +} diff --git a/pkg/packaging/packaging.go b/pkg/packaging/packaging.go index b7a7982fd..c10f55dd5 100644 --- a/pkg/packaging/packaging.go +++ b/pkg/packaging/packaging.go @@ -124,6 +124,7 @@ func (p *PackageOptions) Build(ctx context.Context, packageWriter io.Writer, tar "root_directory": p.canonicalizeRootDir(p.rootDir), "osqueryd_path": p.canonicalizePath(filepath.Join(p.binDir, "osqueryd")), "enroll_secret_path": p.canonicalizePath(filepath.Join(p.confDir, "secret")), + "identifier": p.Identifier, } launcherBoolFlags := []string{} From bf9c0bb0474491007c28678f26310903a0e65c14 Mon Sep 17 00:00:00 2001 From: zack olson Date: Wed, 14 Aug 2024 10:17:47 -0400 Subject: [PATCH 2/9] add windows serviceName tests --- ee/debug/shipper/shipper.go | 1 + pkg/launcher/pkg_utils_windows_test.go | 49 ++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 pkg/launcher/pkg_utils_windows_test.go diff --git a/ee/debug/shipper/shipper.go b/ee/debug/shipper/shipper.go index 41e1ef983..0db33efc5 100644 --- a/ee/debug/shipper/shipper.go +++ b/ee/debug/shipper/shipper.go @@ -268,6 +268,7 @@ func enrollSecret(k types.Knapsack) string { return k.EnrollSecret() } + // TODO this will need to respect the identifier when determining the secret file location for dual-launcher installations b, err := os.ReadFile(launcher.DefaultPath(launcher.SecretFile)) if err != nil { return "" diff --git a/pkg/launcher/pkg_utils_windows_test.go b/pkg/launcher/pkg_utils_windows_test.go new file mode 100644 index 000000000..63866489f --- /dev/null +++ b/pkg/launcher/pkg_utils_windows_test.go @@ -0,0 +1,49 @@ +//go:build windows +// +build windows + +package launcher + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_ServiceName(t *testing.T) { + t.Parallel() + + for _, tt := range []struct { + testCaseName string + identifier string + expectedServiceName string + }{ + { + testCaseName: "empty identifier expecting default service name", + identifier: " ", + expectedServiceName: "LauncherKolideK2Svc", + }, + { + testCaseName: "default identifier expecting default service name", + identifier: "kolide-k2", + expectedServiceName: "LauncherKolideK2Svc", + }, + { + testCaseName: "preprod identifier expecting preprod service name", + identifier: "kolide-preprod-k2", + expectedServiceName: "LauncherKolidePreprodK2Svc", + }, + { + testCaseName: "mangled identifier expecting default service name", + identifier: "kolide-!@_k2", + expectedServiceName: "LauncherKolideK2Svc", + }, + } { + tt := tt + t.Run(tt.testCaseName, func(t *testing.T) { + t.Parallel() + + serviceName := ServiceName(tt.identifier) + require.Equal(t, tt.expectedServiceName, serviceName, "expected sanitized service name value to match") + }) + } +} From 63b8965f5aeb4252822dbaba930f16a55b30a031 Mon Sep 17 00:00:00 2001 From: zack olson Date: Wed, 14 Aug 2024 11:25:51 -0400 Subject: [PATCH 3/9] remove unused ServiceName implementations, fix up options tests --- pkg/launcher/options_test.go | 1 + pkg/launcher/pkg_utils_darwin.go | 28 ---------------------------- pkg/launcher/pkg_utils_linux.go | 28 ---------------------------- 3 files changed, 1 insertion(+), 56 deletions(-) delete mode 100644 pkg/launcher/pkg_utils_darwin.go delete mode 100644 pkg/launcher/pkg_utils_linux.go diff --git a/pkg/launcher/options_test.go b/pkg/launcher/options_test.go index 25fbc4222..5734f3d3a 100644 --- a/pkg/launcher/options_test.go +++ b/pkg/launcher/options_test.go @@ -265,6 +265,7 @@ func getArgsAndResponse() (map[string]string, *Options) { WatchdogDelaySec: 120, WatchdogMemoryLimitMB: 600, WatchdogUtilizationLimitPercent: 50, + Identifier: "kolide-k2", } return args, opts diff --git a/pkg/launcher/pkg_utils_darwin.go b/pkg/launcher/pkg_utils_darwin.go deleted file mode 100644 index fd014f9fa..000000000 --- a/pkg/launcher/pkg_utils_darwin.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build darwin -// +build darwin - -package launcher - -import ( - "fmt" - "regexp" - "strings" -) - -// allow alphanumeric characters plus - or _ within the identifier, we will replace anything else with a dash -var darwinIdentifierWhitelistRegex = regexp.MustCompile(`[^a-zA-Z0-9-_]+`) - -const defaultLauncherIdentifier string = "kolide-k2" - -// ServiceName embeds the given identifier into our service name template after sanitization, -// and returns the service name (label) generated to match our packaging logic -func ServiceName(identifier string) string { - // this check might be overkill but is intended to prevent any backwards compatibility/misconfiguration issues - if strings.TrimSpace(identifier) == "" { - identifier = defaultLauncherIdentifier - } - - sanitizedServiceName := darwinIdentifierWhitelistRegex.ReplaceAllString(identifier, "-") - - return fmt.Sprintf("com.%s.launcher", sanitizedServiceName) -} diff --git a/pkg/launcher/pkg_utils_linux.go b/pkg/launcher/pkg_utils_linux.go deleted file mode 100644 index 5b1ee7b75..000000000 --- a/pkg/launcher/pkg_utils_linux.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build linux -// +build linux - -package launcher - -import ( - "fmt" - "regexp" - "strings" -) - -// allow alphanumeric characters plus - or _ within the identifier, we will replace anything else with a dash -var linuxIdentifierWhitelistRegex = regexp.MustCompile(`[^a-zA-Z0-9-_]+`) - -const defaultLauncherIdentifier string = "kolide-k2" - -// ServiceName embeds the given identifier into our service name template after sanitization, -// and returns the service name (label) generated to match our packaging logic -func ServiceName(identifier string) string { - // this check might be overkill but is intended to prevent any backwards compatibility/misconfiguration issues - if strings.TrimSpace(identifier) == "" { - identifier = defaultLauncherIdentifier - } - - sanitizedServiceName := linuxIdentifierWhitelistRegex.ReplaceAllString(identifier, "-") - - return fmt.Sprintf("launcher.%s.service", sanitizedServiceName) -} From 9b67fb2d267d301b561d1650fa23df5afff68aad Mon Sep 17 00:00:00 2001 From: zack olson Date: Wed, 14 Aug 2024 14:11:00 -0400 Subject: [PATCH 4/9] use shared defaultLauncherIdentifier const --- pkg/launcher/options.go | 4 +++- pkg/launcher/options_test.go | 2 +- pkg/launcher/pkg_utils_windows.go | 2 -- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/launcher/options.go b/pkg/launcher/options.go index 55c03a780..7d2b71e00 100644 --- a/pkg/launcher/options.go +++ b/pkg/launcher/options.go @@ -17,6 +17,8 @@ import ( "github.com/peterbourgon/ff/v3" ) +const defaultLauncherIdentifier string = "kolide-k2" + // Options is the set of options that may be configured for Launcher. type Options struct { // KolideServerURL is the URL of the management server to connect to. @@ -257,7 +259,7 @@ func ParseOptions(subcommandName string, args []string) (*Options, error) { flIAmBreakingEELicense = flagset.Bool("i-am-breaking-ee-license", false, "Skip license check before running localserver (default: false)") flDelayStart = flagset.Duration("delay_start", 0*time.Second, "How much time to wait before starting launcher") flLocalDevelopmentPath = flagset.String("localdev_path", "", "Path to local launcher build") - flPackageIdentifier = flagset.String("identifier", "kolide-k2", "packaging identifier used to determine service names, paths, etc. (default: kolide-k2)") + flPackageIdentifier = flagset.String("identifier", defaultLauncherIdentifier, "packaging identifier used to determine service names, paths, etc. (default: kolide-k2)") // deprecated options, kept for any kind of config file compatibility _ = flagset.String("debug_log_file", "", "DEPRECATED") diff --git a/pkg/launcher/options_test.go b/pkg/launcher/options_test.go index 5734f3d3a..c8dadf3bf 100644 --- a/pkg/launcher/options_test.go +++ b/pkg/launcher/options_test.go @@ -265,7 +265,7 @@ func getArgsAndResponse() (map[string]string, *Options) { WatchdogDelaySec: 120, WatchdogMemoryLimitMB: 600, WatchdogUtilizationLimitPercent: 50, - Identifier: "kolide-k2", + Identifier: defaultLauncherIdentifier, } return args, opts diff --git a/pkg/launcher/pkg_utils_windows.go b/pkg/launcher/pkg_utils_windows.go index cf5e6b4a0..9681410bb 100644 --- a/pkg/launcher/pkg_utils_windows.go +++ b/pkg/launcher/pkg_utils_windows.go @@ -13,8 +13,6 @@ import ( var nonAlphanumericRegex = regexp.MustCompile(`[^a-zA-Z0-9]+`) -const defaultLauncherIdentifier string = "kolide-k2" - // ServiceName embeds the given identifier into our service name template after sanitization, // and returns the camelCased service name generated to match our packaging logic func ServiceName(identifier string) string { From 5fd96f4fd4d3e84e4eab87e54c7586a0fa76023a Mon Sep 17 00:00:00 2001 From: zack olson Date: Wed, 14 Aug 2024 16:11:03 -0400 Subject: [PATCH 5/9] make enrollSecret for flare shipper read from knapsack's EnrollSecretPath --- ee/debug/shipper/shipper.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ee/debug/shipper/shipper.go b/ee/debug/shipper/shipper.go index 0db33efc5..08b4c32fe 100644 --- a/ee/debug/shipper/shipper.go +++ b/ee/debug/shipper/shipper.go @@ -268,7 +268,17 @@ func enrollSecret(k types.Knapsack) string { return k.EnrollSecret() } + if k != nil && k.EnrollSecretPath() != "" { + secret, err := os.ReadFile(k.EnrollSecretPath()) + if err != nil { + return "" + } + + return string(secret) + } + // TODO this will need to respect the identifier when determining the secret file location for dual-launcher installations + // this will specifically be an issue when flare is triggered standalone (without config path specified) b, err := os.ReadFile(launcher.DefaultPath(launcher.SecretFile)) if err != nil { return "" From 978c39aad69fdf26bb93302fe06a172c44c10b9e Mon Sep 17 00:00:00 2001 From: zack olson Date: Wed, 14 Aug 2024 17:51:03 -0400 Subject: [PATCH 6/9] fixup shipper tests --- ee/debug/shipper/shipper_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ee/debug/shipper/shipper_test.go b/ee/debug/shipper/shipper_test.go index 58953a90a..158625c60 100644 --- a/ee/debug/shipper/shipper_test.go +++ b/ee/debug/shipper/shipper_test.go @@ -32,6 +32,7 @@ func TestShip(t *testing.T) { //nolint:paralleltest mockKnapsack: func(t *testing.T) *typesMocks.Knapsack { k := typesMocks.NewKnapsack(t) k.On("EnrollSecret").Return("") + k.On("EnrollSecretPath").Return("") return k }, assertion: assert.NoError, @@ -45,6 +46,7 @@ func TestShip(t *testing.T) { //nolint:paralleltest mockKnapsack: func(t *testing.T) *typesMocks.Knapsack { k := typesMocks.NewKnapsack(t) k.On("EnrollSecret").Return("") + k.On("EnrollSecretPath").Return("") return k }, assertion: assert.NoError, From 6cf90e4f957c07d0242b934790884c68335da5a7 Mon Sep 17 00:00:00 2001 From: zack olson Date: Thu, 15 Aug 2024 12:02:12 -0400 Subject: [PATCH 7/9] add multiple installation check to flare --- ee/debug/checkups/checkups.go | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/ee/debug/checkups/checkups.go b/ee/debug/checkups/checkups.go index fb54f1769..49c7e076b 100644 --- a/ee/debug/checkups/checkups.go +++ b/ee/debug/checkups/checkups.go @@ -23,13 +23,16 @@ import ( "fmt" "io" "log/slog" + "os" "path" + "path/filepath" "runtime" "strings" "time" "github.com/kolide/kit/version" "github.com/kolide/launcher/ee/agent/types" + "github.com/kolide/launcher/pkg/launcher" ) type Status string @@ -302,10 +305,62 @@ func RunFlare(ctx context.Context, k types.Knapsack, flareStream io.WriteCloser, } } + // note we do not check errors or do anything to complicate the normal flare process + // from the multiple installation check. this is not (at this time) an expected production complication + noteMultipleInstallations(flare) + // we could defer this close, but we want to return any errors return close() } +// noteMultipleInstallations checks for whether the results of running flare for this installation may be complicated +// by multiple installations. This is less of an issue when the flare is run in situ, but should be noted because +// we may need to pay closer attention to the results. When run standalone without a config argument passed, it would be +// possible for flare to default to reading the directories for the wrong installation +func noteMultipleInstallations(z *zip.Writer) { + defaultPath := strings.TrimSuffix(launcher.DefaultPath(launcher.BinDirectory), string(filepath.Separator)) + pathParts := strings.Split(defaultPath, string(filepath.Separator)) + + if len(pathParts) < 3 { + return + } + + if runtime.GOOS == "windows" { // strip the bin for windows + pathParts = pathParts[:len(pathParts)-1] + } + + // now strip off the directory which should note the identifier. we are + // going to list everything in the parent directory and count kolide references + // to get an idea of the total potential installations + pathParts = pathParts[:len(pathParts)-1] + + // now put the path back together + baseDir := strings.Join(pathParts, string(filepath.Separator)) + + entries, err := os.ReadDir(baseDir) + if err != nil { + return + } + + matchingDirs := make([]string, 0) + for _, e := range entries { + if strings.Contains(strings.ToLower(e.Name()), "kolide") { + matchingDirs = append(matchingDirs, e.Name()) + } + } + + if len(matchingDirs) <= 1 { + return // nothing to note, standard single installation + } + + w, err := z.Create("MULTIPLE_LAUNCHER_INSTALLS_DETECTED") + if err != nil { + return + } + + json.NewEncoder(w).Encode(matchingDirs) +} + func writeFlareEnv(z *zip.Writer, runtimeEnvironment runtimeEnvironmentType) error { if _, err := z.Create(fmt.Sprintf("FLARE_RUNNING_%s", strings.ReplaceAll(strings.ToUpper(string(runtimeEnvironment)), " ", "_"))); err != nil { return fmt.Errorf("making env note file: %s", err) From 000258631b76f68429560d27ee5e894b8ecc20c9 Mon Sep 17 00:00:00 2001 From: zack olson Date: Fri, 16 Aug 2024 14:34:43 -0400 Subject: [PATCH 8/9] only write out identifier to flags file if non-default --- pkg/launcher/options.go | 4 ++-- pkg/launcher/options_test.go | 2 +- pkg/packaging/packaging.go | 9 ++++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pkg/launcher/options.go b/pkg/launcher/options.go index 7d2b71e00..3b5b7f3ce 100644 --- a/pkg/launcher/options.go +++ b/pkg/launcher/options.go @@ -17,7 +17,7 @@ import ( "github.com/peterbourgon/ff/v3" ) -const defaultLauncherIdentifier string = "kolide-k2" +const DefaultLauncherIdentifier string = "kolide-k2" // Options is the set of options that may be configured for Launcher. type Options struct { @@ -259,7 +259,7 @@ func ParseOptions(subcommandName string, args []string) (*Options, error) { flIAmBreakingEELicense = flagset.Bool("i-am-breaking-ee-license", false, "Skip license check before running localserver (default: false)") flDelayStart = flagset.Duration("delay_start", 0*time.Second, "How much time to wait before starting launcher") flLocalDevelopmentPath = flagset.String("localdev_path", "", "Path to local launcher build") - flPackageIdentifier = flagset.String("identifier", defaultLauncherIdentifier, "packaging identifier used to determine service names, paths, etc. (default: kolide-k2)") + flPackageIdentifier = flagset.String("identifier", DefaultLauncherIdentifier, "packaging identifier used to determine service names, paths, etc. (default: kolide-k2)") // deprecated options, kept for any kind of config file compatibility _ = flagset.String("debug_log_file", "", "DEPRECATED") diff --git a/pkg/launcher/options_test.go b/pkg/launcher/options_test.go index c8dadf3bf..3146f6c81 100644 --- a/pkg/launcher/options_test.go +++ b/pkg/launcher/options_test.go @@ -265,7 +265,7 @@ func getArgsAndResponse() (map[string]string, *Options) { WatchdogDelaySec: 120, WatchdogMemoryLimitMB: 600, WatchdogUtilizationLimitPercent: 50, - Identifier: defaultLauncherIdentifier, + Identifier: DefaultLauncherIdentifier, } return args, opts diff --git a/pkg/packaging/packaging.go b/pkg/packaging/packaging.go index c10f55dd5..8a1027370 100644 --- a/pkg/packaging/packaging.go +++ b/pkg/packaging/packaging.go @@ -16,6 +16,7 @@ import ( "text/template" "github.com/kolide/kit/fsutil" + "github.com/kolide/launcher/pkg/launcher" "github.com/kolide/launcher/pkg/packagekit" "go.opencensus.io/trace" @@ -124,7 +125,13 @@ func (p *PackageOptions) Build(ctx context.Context, packageWriter io.Writer, tar "root_directory": p.canonicalizeRootDir(p.rootDir), "osqueryd_path": p.canonicalizePath(filepath.Join(p.binDir, "osqueryd")), "enroll_secret_path": p.canonicalizePath(filepath.Join(p.confDir, "secret")), - "identifier": p.Identifier, + } + + // to avoid writing additional flags without a real need (newly introduced flags + // can cause issues with rolling back), we only set the identifier in the flags file + // if it is not the default value + if p.Identifier != launcher.DefaultLauncherIdentifier { + launcherMapFlags["identifier"] = p.Identifier } launcherBoolFlags := []string{} From 56de51009cfa18b8748f2e1ccb7687aa5ff92f54 Mon Sep 17 00:00:00 2001 From: zack olson Date: Fri, 16 Aug 2024 14:43:41 -0400 Subject: [PATCH 9/9] fix windows pkg util --- pkg/launcher/pkg_utils_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/launcher/pkg_utils_windows.go b/pkg/launcher/pkg_utils_windows.go index 9681410bb..d90a9fb33 100644 --- a/pkg/launcher/pkg_utils_windows.go +++ b/pkg/launcher/pkg_utils_windows.go @@ -18,7 +18,7 @@ var nonAlphanumericRegex = regexp.MustCompile(`[^a-zA-Z0-9]+`) func ServiceName(identifier string) string { // this check might be overkill but is intended to prevent any backwards compatibility/misconfiguration issues if strings.TrimSpace(identifier) == "" { - identifier = defaultLauncherIdentifier + identifier = DefaultLauncherIdentifier } sanitizedServiceName := nonAlphanumericRegex.ReplaceAllString(identifier, "_") // e.g. identifier=kolide-k2 becomes kolide_k2