package launcher import ( "context" "encoding/json" "fmt" "strconv" "github.com/go-kit/kit/log" "github.com/kolide/osquery-go/plugin/distributed" "github.com/kolide/osquery-go/plugin/logger" "github.com/pkg/errors" "github.com/kolide/fleet/server/contexts/host" "github.com/kolide/fleet/server/health" "github.com/kolide/fleet/server/kolide" ) // launcherWrapper wraps the TLS interface. type launcherWrapper struct { tls kolide.OsqueryService logger log.Logger healthCheckers map[string]health.Checker } func (svc *launcherWrapper) RequestEnrollment(ctx context.Context, enrollSecret, hostIdentifier string) (string, bool, error) { nodeKey, err := svc.tls.EnrollAgent(ctx, enrollSecret, hostIdentifier) if err != nil { if authErr, ok := err.(nodeInvalidErr); ok { 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, errors.Wrap(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, errors.Wrap(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, accelerate, err := svc.tls.GetDistributedQueries(newCtx) if err != nil { return nil, false, errors.Wrap(err, "get queries for launcher") } result := &distributed.GetQueriesResult{ Queries: queryMap, AccelerateSeconds: int(accelerate), } 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, errors.Wrap(err, "authenticate launcher") } switch logType { case logger.LogTypeStatus: var statuses []kolide.OsqueryStatusLog for _, log := range logs { // StatusLog handles osquery logging messages var statusLog = struct { Severity string `json:"s"` Filename string `json:"f"` Line string `json:"i"` Message string `json:"m"` }{} if err := json.Unmarshal([]byte(log), &statusLog); err != nil { return "", "", false, errors.Wrap(err, "decode status log from launcher") } statuses = append(statuses, kolide.OsqueryStatusLog{ Severity: statusLog.Severity, Filename: statusLog.Filename, Line: statusLog.Line, Message: statusLog.Message, }) } err = svc.tls.SubmitStatusLogs(newCtx, statuses) return "", "", false, errors.Wrap(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, errors.Wrap(err, "submit result logs from launcher") default: // We have a logTypeAgent which is not there in the osquery-go enum. // See https://github.com/kolide/launcher/issues/183 panic(fmt.Sprintf("%s log type not implemented", logType)) } } 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(kolide.OsqueryDistributedQueryResults, len(results)) statuses := make(map[string]string, len(results)) for _, result := range results { statuses[result.QueryName] = strconv.Itoa(result.Status) osqueryResults[result.QueryName] = result.Rows } err = svc.tls.SubmitDistributedQueryResults(newCtx, osqueryResults, statuses) return "", "", false, errors.Wrap(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 kolide.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 { if authErr, ok := err.(nodeInvalidErr); ok { return ctx, authErr.NodeInvalid(), err } return ctx, false, err } ctx = host.NewContext(ctx, *node) return ctx, false, nil } type nodeInvalidErr interface { error NodeInvalid() bool }