2021-10-27 04:12:44 +00:00
|
|
|
// 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.
|
2017-10-24 23:55:32 +00:00
|
|
|
package launcher
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/go-kit/kit/log"
|
|
|
|
"github.com/kolide/osquery-go/plugin/distributed"
|
|
|
|
"github.com/kolide/osquery-go/plugin/logger"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
2021-06-26 04:46:51 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/host"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/health"
|
2017-10-24 23:55:32 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// launcherWrapper wraps the TLS interface.
|
|
|
|
type launcherWrapper struct {
|
2021-06-06 22:07:29 +00:00
|
|
|
tls fleet.OsqueryService
|
2017-10-24 23:55:32 +00:00
|
|
|
logger log.Logger
|
|
|
|
healthCheckers map[string]health.Checker
|
|
|
|
}
|
|
|
|
|
|
|
|
func (svc *launcherWrapper) RequestEnrollment(ctx context.Context, enrollSecret, hostIdentifier string) (string, bool, error) {
|
2019-07-01 23:50:04 +00:00
|
|
|
nodeKey, err := svc.tls.EnrollAgent(ctx, enrollSecret, hostIdentifier, map[string](map[string]string){})
|
2017-10-24 23:55:32 +00:00
|
|
|
if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
var authErr nodeInvalidErr
|
|
|
|
if errors.As(err, &authErr) {
|
2017-10-24 23:55:32 +00:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2017-12-13 23:14:54 +00:00
|
|
|
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)
|
2017-10-24 23:55:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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:
|
2017-12-12 15:43:33 +00:00
|
|
|
var statuses []json.RawMessage
|
2017-10-24 23:55:32 +00:00
|
|
|
for _, log := range logs {
|
2017-12-12 15:43:33 +00:00
|
|
|
statuses = append(statuses, []byte(log))
|
2017-10-24 23:55:32 +00:00
|
|
|
}
|
|
|
|
err = svc.tls.SubmitStatusLogs(newCtx, statuses)
|
|
|
|
return "", "", false, errors.Wrap(err, "submit status logs from launcher")
|
|
|
|
case logger.LogTypeSnapshot, logger.LogTypeString:
|
2017-11-19 00:59:32 +00:00
|
|
|
var results []json.RawMessage
|
2017-10-24 23:55:32 +00:00
|
|
|
for _, log := range logs {
|
2017-11-19 00:59:32 +00:00
|
|
|
results = append(results, []byte(log))
|
2017-10-24 23:55:32 +00:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
osqueryResults := make(fleet.OsqueryDistributedQueryResults, len(results))
|
|
|
|
statuses := make(map[string]fleet.OsqueryStatus, len(results))
|
2017-10-24 23:55:32 +00:00
|
|
|
|
|
|
|
for _, result := range results {
|
2021-06-06 22:07:29 +00:00
|
|
|
statuses[result.QueryName] = fleet.OsqueryStatus(result.Status)
|
2017-10-24 23:55:32 +00:00
|
|
|
osqueryResults[result.QueryName] = result.Rows
|
|
|
|
}
|
|
|
|
|
2021-01-19 22:52:29 +00:00
|
|
|
// TODO can Launcher expose the error messages?
|
|
|
|
messages := make(map[string]string)
|
|
|
|
err = svc.tls.SubmitDistributedQueryResults(newCtx, osqueryResults, statuses, messages)
|
2017-10-24 23:55:32 +00:00
|
|
|
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.
|
2021-06-06 22:07:29 +00:00
|
|
|
// In the fleet.OsqueryService authentication is done via endpoint middleware, but all launcher endpoints require
|
2017-10-24 23:55:32 +00:00
|
|
|
// 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) {
|
2021-09-10 17:48:33 +00:00
|
|
|
node, _, err := svc.tls.AuthenticateHost(ctx, nodeKey)
|
2017-10-24 23:55:32 +00:00
|
|
|
if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
var authErr nodeInvalidErr
|
|
|
|
if errors.As(err, &authErr) {
|
2017-10-24 23:55:32 +00:00
|
|
|
return ctx, authErr.NodeInvalid(), err
|
|
|
|
}
|
|
|
|
return ctx, false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx = host.NewContext(ctx, *node)
|
|
|
|
return ctx, false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type nodeInvalidErr interface {
|
|
|
|
error
|
|
|
|
NodeInvalid() bool
|
|
|
|
}
|