<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #38889 PLEASE READ BELOW before looking at file changes Before converting individual files/packages to slog, we generally need to make these 2 changes to make the conversion easier: - Replace uses of `kitlog.With` since they are not fully compatible with our kitlog adapter - Directly use the kitlog adapter logger type instead of the kitlog interface, which will let us have direct access to the underlying slog logger: `*logging.Logger` Note: that I did not replace absolutely all uses of `kitlog.Logger`, but I did remove all uses of `kitlog.With` except for these due to complexity: - server/logging/filesystem.go and the other log writers (webhook, firehose, kinesis, lambda, pubsub, nats) - server/datastore/mysql/nanomdm_storage.go (adapter pattern) - server/vulnerabilities/nvd/* (cascades to CLI tools) - server/service/osquery_utils/queries.go (callback type signatures cascade broadly) - cmd/maintained-apps/ (standalone, so can be transitioned later all at once) Most of the changes in this PR follow these patterns: - `kitlog.Logger` type → `*logging.Logger` - `kitlog.With(logger, ...)` → `logger.With(...)` - `kitlog.NewNopLogger() → logging.NewNopLogger()`, including similar variations such as `logging.NewLogfmtLogger(w)` and `logging.NewJSONLogger(w)` - removed many now-unused kitlog imports Unique changes that the PR review should focus on: - server/platform/logging/kitlog_adapter.go: Core adapter changes - server/platform/logging/logging.go: New convenience functions - server/service/integration_logger_test.go: Test changes for slog # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. - Was added in previous PR ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Migrated the codebase to a unified internal structured logging system for more consistent, reliable logs and observability. * No user-facing functionality changed; runtime behavior and APIs remain compatible. * **Tests** * Updated tests to use the new logging helpers to ensure consistent test logging and validation. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
180 lines
6.2 KiB
Go
180 lines
6.2 KiB
Go
// Package launcher provides a transport for Kolide Launcher (https://github.com/kolide/launcher)
|
|
// clients to connect to Fleet. These clients use a gRPC transport to hit the equivalent API
|
|
// endpoints.
|
|
//
|
|
// Fleet intends to maintain support for Launcher as long as the Launcher API remains as closely
|
|
// mapped to the osquery API as it is currently, and Fleet users continue to use Launcher. In the
|
|
// future as more Fleet users migrate to Orbit, this may be deprecated.
|
|
package launcher
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
|
"github.com/fleetdm/fleet/v4/server/contexts/host"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/health"
|
|
"github.com/fleetdm/fleet/v4/server/platform/logging"
|
|
"github.com/kolide/launcher/pkg/service"
|
|
"github.com/osquery/osquery-go/plugin/distributed"
|
|
"github.com/osquery/osquery-go/plugin/logger"
|
|
)
|
|
|
|
// launcherWrapper wraps the TLS interface.
|
|
type launcherWrapper struct {
|
|
tls fleet.OsqueryService
|
|
logger *logging.Logger
|
|
healthCheckers map[string]health.Checker
|
|
}
|
|
|
|
func (svc *launcherWrapper) RequestEnrollment(ctx context.Context, enrollSecret, hostIdentifier string, _ service.EnrollmentDetails) (string, bool, error) {
|
|
nodeKey, err := svc.tls.EnrollOsquery(ctx, enrollSecret, hostIdentifier, map[string](map[string]string){})
|
|
if err != nil {
|
|
var authErr nodeInvalidErr
|
|
if errors.As(err, &authErr) {
|
|
return "", authErr.NodeInvalid(), err
|
|
}
|
|
return "", false, err
|
|
}
|
|
return nodeKey, false, nil
|
|
}
|
|
|
|
func (svc *launcherWrapper) RequestConfig(ctx context.Context, nodeKey string) (string, bool, error) {
|
|
newCtx, invalid, err := svc.authenticateHost(ctx, nodeKey)
|
|
if err != nil {
|
|
return "", invalid, err
|
|
}
|
|
|
|
config, err := svc.tls.GetClientConfig(newCtx)
|
|
if err != nil {
|
|
return "", false, ctxerr.Wrap(ctx, err, "get config for launcher")
|
|
}
|
|
|
|
if options, ok := config["options"].(map[string]interface{}); ok {
|
|
// Launcher manages plugins so remove them from configuration if they exist.
|
|
for _, optionName := range []string{"distributed_plugin", "logger_plugin"} {
|
|
delete(options, optionName)
|
|
}
|
|
}
|
|
|
|
configJSON, err := json.Marshal(config)
|
|
if err != nil {
|
|
return "", false, ctxerr.Wrap(ctx, err, "encoding config for launcher")
|
|
}
|
|
|
|
return string(configJSON), false, nil
|
|
}
|
|
|
|
func (svc *launcherWrapper) RequestQueries(ctx context.Context, nodeKey string) (*distributed.GetQueriesResult, bool, error) {
|
|
newCtx, invalid, err := svc.authenticateHost(ctx, nodeKey)
|
|
if err != nil {
|
|
return nil, invalid, err
|
|
}
|
|
|
|
queryMap, discoveryMap, accelerate, err := svc.tls.GetDistributedQueries(newCtx)
|
|
if err != nil {
|
|
return nil, false, ctxerr.Wrap(ctx, err, "get queries for launcher")
|
|
}
|
|
|
|
result := &distributed.GetQueriesResult{
|
|
Queries: queryMap,
|
|
Discovery: discoveryMap,
|
|
AccelerateSeconds: int(accelerate), //nolint:gosec // dismiss G115
|
|
}
|
|
|
|
return result, false, nil
|
|
}
|
|
|
|
func (svc *launcherWrapper) PublishLogs(ctx context.Context, nodeKey string, logType logger.LogType, logs []string) (string, string, bool, error) {
|
|
newCtx, invalid, err := svc.authenticateHost(ctx, nodeKey)
|
|
if err != nil {
|
|
return "", "", invalid, ctxerr.Wrap(ctx, err, "authenticate launcher")
|
|
}
|
|
|
|
switch logType {
|
|
case logger.LogTypeStatus:
|
|
var statuses []json.RawMessage
|
|
for _, log := range logs {
|
|
statuses = append(statuses, []byte(log))
|
|
}
|
|
err = svc.tls.SubmitStatusLogs(newCtx, statuses)
|
|
return "", "", false, ctxerr.Wrap(ctx, err, "submit status logs from launcher")
|
|
case logger.LogTypeSnapshot, logger.LogTypeString:
|
|
var results []json.RawMessage
|
|
for _, log := range logs {
|
|
results = append(results, []byte(log))
|
|
}
|
|
err = svc.tls.SubmitResultLogs(newCtx, results)
|
|
return "", "", false, ctxerr.Wrap(ctx, err, "submit result logs from launcher")
|
|
default:
|
|
return "", "", false, ctxerr.Wrap(ctx,
|
|
&fleet.BadRequestError{Message: fmt.Sprintf("log type %q not implemented", logType)},
|
|
"unsupported log type",
|
|
)
|
|
|
|
}
|
|
}
|
|
|
|
func (svc *launcherWrapper) PublishResults(ctx context.Context, nodeKey string, results []distributed.Result) (string, string, bool, error) {
|
|
newCtx, invalid, err := svc.authenticateHost(ctx, nodeKey)
|
|
if err != nil {
|
|
return "", "", invalid, err
|
|
}
|
|
|
|
osqueryResults := make(fleet.OsqueryDistributedQueryResults, len(results))
|
|
statuses := make(map[string]fleet.OsqueryStatus, len(results))
|
|
stats := make(map[string]*fleet.Stats, len(results))
|
|
|
|
for _, result := range results {
|
|
statuses[result.QueryName] = fleet.OsqueryStatus(result.Status)
|
|
osqueryResults[result.QueryName] = result.Rows
|
|
if result.QueryStats != nil {
|
|
stats[result.QueryName] = &fleet.Stats{
|
|
WallTimeMs: uint64(result.QueryStats.WallTimeMs), //nolint:gosec // dismiss G115
|
|
UserTime: uint64(result.QueryStats.UserTime), //nolint:gosec // dismiss G115
|
|
SystemTime: uint64(result.QueryStats.SystemTime), //nolint:gosec // dismiss G115
|
|
Memory: uint64(result.QueryStats.Memory), //nolint:gosec // dismiss G115
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO can Launcher expose the error messages?
|
|
messages := make(map[string]string)
|
|
err = svc.tls.SubmitDistributedQueryResults(newCtx, osqueryResults, statuses, messages, stats)
|
|
return "", "", false, ctxerr.Wrap(ctx, err, "submit launcher results")
|
|
}
|
|
|
|
func (svc *launcherWrapper) CheckHealth(ctx context.Context) (int32, error) {
|
|
healthy := health.CheckHealth(svc.logger, svc.healthCheckers)
|
|
if !healthy {
|
|
return 1, nil
|
|
}
|
|
return 0, nil
|
|
}
|
|
|
|
// authenticateHost verifies the host node key using the TLS API and returns back a
|
|
// context which includes the host as a context value.
|
|
// In the fleet.OsqueryService authentication is done via endpoint middleware, but all launcher endpoints require
|
|
// an explicit return for NodeInvalid, so we check in this helper method instead.
|
|
func (svc *launcherWrapper) authenticateHost(ctx context.Context, nodeKey string) (context.Context, bool, error) {
|
|
node, _, err := svc.tls.AuthenticateHost(ctx, nodeKey)
|
|
if err != nil {
|
|
var authErr nodeInvalidErr
|
|
if errors.As(err, &authErr) {
|
|
return ctx, authErr.NodeInvalid(), err
|
|
}
|
|
return ctx, false, err
|
|
}
|
|
|
|
ctx = host.NewContext(ctx, node)
|
|
return ctx, false, nil
|
|
}
|
|
|
|
type nodeInvalidErr interface {
|
|
error
|
|
NodeInvalid() bool
|
|
}
|