Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

get runtime enrollment details before osq details #1833

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 43 additions & 43 deletions pkg/osquery/enrollment_details.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,62 @@ 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 {
directionless marked this conversation as resolved.
Show resolved Hide resolved
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 := `
SELECT
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,
Expand All @@ -57,7 +85,6 @@ func getEnrollDetails(ctx context.Context, osquerydPath string) (service.Enrollm
os_version,
system_info,
osquery_info;

`

var respBytes bytes.Buffer
Expand All @@ -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 {
Expand All @@ -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
}
Expand All @@ -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
}
11 changes: 8 additions & 3 deletions pkg/osquery/enrollment_details_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
14 changes: 7 additions & 7 deletions pkg/osquery/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
Loading