diff --git a/pkg/osquery/enrollment_details.go b/pkg/osquery/enrollment_details.go index c3c961ad2..a8382bfef 100644 --- a/pkg/osquery/enrollment_details.go +++ b/pkg/osquery/enrollment_details.go @@ -19,25 +19,55 @@ import ( "github.com/pkg/errors" ) -func getEnrollDetails(ctx context.Context, osquerydPath string) (service.EnrollmentDetails, error) { +// getEnrollDetails returns an EnrollmentDetails struct with populated with details it can fetch without osquery. +// To get the rest of the details, pass the struct to getOsqEnrollDetails. +func getRuntimeEnrollDetails() service.EnrollmentDetails { + details := service.EnrollmentDetails{ + OSPlatform: runtime.GOOS, + OSPlatformLike: runtime.GOOS, + LauncherVersion: version.Version().Version, + GOOS: runtime.GOOS, + GOARCH: runtime.GOARCH, + } + + // Pull in some launcher key info. These depend on the agent package, and we'll need to check for nils + if agent.LocalDbKeys().Public() != nil { + if key, err := x509.MarshalPKIXPublicKey(agent.LocalDbKeys().Public()); err == nil { + // der is a binary format, so convert to b64 + details.LauncherLocalKey = base64.StdEncoding.EncodeToString(key) + } + } + + if agent.HardwareKeys().Public() != nil { + if key, err := x509.MarshalPKIXPublicKey(agent.HardwareKeys().Public()); err == nil { + // der is a binary format, so convert to b64 + details.LauncherHardwareKey = base64.StdEncoding.EncodeToString(key) + details.LauncherHardwareKeySource = agent.HardwareKeys().Type() + } + } + + return details +} + +// getOsqEnrollDetails queries osquery for enrollment details and populates the EnrollmentDetails struct. +// It's expected that the caller has initially populated the struct with runtimeEnrollDetails by calling getRuntimeEnrollDetails. +func getOsqEnrollDetails(ctx context.Context, osquerydPath string, details *service.EnrollmentDetails) error { ctx, span := traces.StartSpan(ctx) defer span.End() - var details service.EnrollmentDetails - // To facilitate manual testing around missing enrollment details, // there is a environmental variable to trigger the failure condition if os.Getenv("LAUNCHER_DEBUG_ENROLL_DETAILS_ERROR") == "true" { - return details, errors.New("Skipping enrollment details") + return errors.New("Skipping enrollment details") } // If the binary doesn't exist, bail out early. if info, err := os.Stat(osquerydPath); os.IsNotExist(err) { - return details, fmt.Errorf("no binary at %s", osquerydPath) + return fmt.Errorf("no binary at %s", osquerydPath) } else if info.IsDir() { - return details, fmt.Errorf("%s is a directory", osquerydPath) + return fmt.Errorf("%s is a directory", osquerydPath) } else if err != nil { - return details, fmt.Errorf("statting %s: %w", osquerydPath, err) + return fmt.Errorf("statting %s: %w", osquerydPath, err) } query := ` @@ -45,8 +75,6 @@ func getEnrollDetails(ctx context.Context, osquerydPath string) (service.Enrollm osquery_info.version as osquery_version, os_version.build as os_build, os_version.name as os_name, - os_version.platform as os_platform, - os_version.platform_like as os_platform_like, os_version.version as os_version, system_info.hardware_model, system_info.hardware_serial, @@ -57,7 +85,6 @@ func getEnrollDetails(ctx context.Context, osquerydPath string) (service.Enrollm os_version, system_info, osquery_info; - ` var respBytes bytes.Buffer @@ -69,25 +96,25 @@ func getEnrollDetails(ctx context.Context, osquerydPath string) (service.Enrollm runsimple.WithStderr(&stderrBytes), ) if err != nil { - return details, fmt.Errorf("create osquery for enrollment details: %w", err) + return fmt.Errorf("create osquery for enrollment details: %w", err) } osqCtx, osqCancel := context.WithTimeout(ctx, 5*time.Second) defer osqCancel() if sqlErr := osq.RunSql(osqCtx, []byte(query)); osqCtx.Err() != nil { - return details, fmt.Errorf("query enrollment details context error: %w: stderr: %s", osqCtx.Err(), stderrBytes.String()) + return fmt.Errorf("query enrollment details context error: %w: stderr: %s", osqCtx.Err(), stderrBytes.String()) } else if sqlErr != nil { - return details, fmt.Errorf("query enrollment details: %w; stderr: %s", sqlErr, stderrBytes.String()) + return fmt.Errorf("query enrollment details: %w; stderr: %s", sqlErr, stderrBytes.String()) } var resp []map[string]string if err := json.Unmarshal(respBytes.Bytes(), &resp); err != nil { - return details, fmt.Errorf("json decode enrollment details: %w; stderr: %s", err, stderrBytes.String()) + return fmt.Errorf("json decode enrollment details: %w; stderr: %s", err, stderrBytes.String()) } if len(resp) < 1 { - return details, fmt.Errorf("expected at least one row from the enrollment details query: stderr: %s", stderrBytes.String()) + return fmt.Errorf("expected at least one row from the enrollment details query: stderr: %s", stderrBytes.String()) } if val, ok := resp[0]["os_version"]; ok { @@ -99,12 +126,6 @@ func getEnrollDetails(ctx context.Context, osquerydPath string) (service.Enrollm if val, ok := resp[0]["os_name"]; ok { details.OSName = val } - if val, ok := resp[0]["os_platform"]; ok { - details.OSPlatform = val - } - if val, ok := resp[0]["os_platform_like"]; ok { - details.OSPlatformLike = val - } if val, ok := resp[0]["osquery_version"]; ok { details.OsqueryVersion = val } @@ -122,26 +143,5 @@ func getEnrollDetails(ctx context.Context, osquerydPath string) (service.Enrollm details.HardwareUUID = val } - // This runs before the extensions are registered. These mirror the - // underlying tables. - details.LauncherVersion = version.Version().Version - details.GOOS = runtime.GOOS - details.GOARCH = runtime.GOARCH - - // Pull in some launcher key info. These depend on the agent package, and we'll need to check for nils - if agent.LocalDbKeys().Public() != nil { - if key, err := x509.MarshalPKIXPublicKey(agent.LocalDbKeys().Public()); err == nil { - // der is a binary format, so convert to b64 - details.LauncherLocalKey = base64.StdEncoding.EncodeToString(key) - } - } - if agent.HardwareKeys().Public() != nil { - if key, err := x509.MarshalPKIXPublicKey(agent.HardwareKeys().Public()); err == nil { - // der is a binary format, so convert to b64 - details.LauncherHardwareKey = base64.StdEncoding.EncodeToString(key) - details.LauncherHardwareKeySource = agent.HardwareKeys().Type() - } - } - - return details, nil + return nil } diff --git a/pkg/osquery/enrollment_details_test.go b/pkg/osquery/enrollment_details_test.go index c82161ffc..a872f44a0 100644 --- a/pkg/osquery/enrollment_details_test.go +++ b/pkg/osquery/enrollment_details_test.go @@ -6,26 +6,31 @@ import ( "path/filepath" "testing" + "github.com/kolide/launcher/pkg/service" "github.com/stretchr/testify/require" ) func Test_getEnrollDetails_binaryNotExist(t *testing.T) { t.Parallel() - _, err1 := getEnrollDetails(context.TODO(), filepath.Join("some", "fake", "path", "to", "osqueryd")) + var details service.EnrollmentDetails + + err1 := getOsqEnrollDetails(context.TODO(), filepath.Join("some", "fake", "path", "to", "osqueryd"), &details) require.Error(t, err1, "expected error when path does not exist") - _, err2 := getEnrollDetails(context.TODO(), t.TempDir()) + err2 := getOsqEnrollDetails(context.TODO(), t.TempDir(), &details) require.Error(t, err2, "expected error when path is directory") } func Test_getEnrollDetails_executionError(t *testing.T) { t.Parallel() + var details service.EnrollmentDetails + currentExecutable, err := os.Executable() require.NoError(t, err, "could not get current executable for test") // We expect getEnrollDetails to fail when called against an executable that is not osquery - _, err = getEnrollDetails(context.TODO(), currentExecutable) + err = getOsqEnrollDetails(context.TODO(), currentExecutable, &details) require.Error(t, err, "should not have been able to get enroll details with non-osqueryd executable") } diff --git a/pkg/osquery/extension.go b/pkg/osquery/extension.go index df3bf3e6b..710a56e98 100644 --- a/pkg/osquery/extension.go +++ b/pkg/osquery/extension.go @@ -417,32 +417,32 @@ func (e *Extension) Enroll(ctx context.Context) (string, bool, error) { // We used to see the enrollment details fail, but now that we're running as an exec, // it seems less likely. Try a couple times, but backoff fast. - var enrollDetails service.EnrollmentDetails + enrollDetails := getRuntimeEnrollDetails() if osqPath := e.knapsack.LatestOsquerydPath(ctx); osqPath == "" { e.slogger.Log(ctx, slog.LevelInfo, - "skipping enrollment details, no osqueryd path, this is probably CI", + "skipping enrollment osquery details, no osqueryd path, this is probably CI", ) span.AddEvent("skipping_enrollment_details") } else { if err := backoff.WaitFor(func() error { - enrollDetails, err = getEnrollDetails(ctx, osqPath) + err = getOsqEnrollDetails(ctx, osqPath, &enrollDetails) if err != nil { e.slogger.Log(ctx, slog.LevelDebug, - "getEnrollDetails failed in backoff", + "getOsqEnrollDetails failed in backoff", "err", err, ) } return err }, 30*time.Second, 5*time.Second); err != nil { if os.Getenv("LAUNCHER_DEBUG_ENROLL_DETAILS_REQUIRED") == "true" { - return "", true, fmt.Errorf("query enrollment details: %w", err) + return "", true, fmt.Errorf("query osq enrollment details: %w", err) } e.slogger.Log(ctx, slog.LevelError, - "failed to get enrollment details with retries, moving on", + "failed to get osq enrollment details with retries, moving on", "err", err, ) - traces.SetError(span, fmt.Errorf("query enrollment details: %w", err)) + traces.SetError(span, fmt.Errorf("query osq enrollment details: %w", err)) } else { span.AddEvent("got_enrollment_details") }