2016-09-26 18:48:55 +00:00
|
|
|
package service
|
2016-09-04 05:13:42 +00:00
|
|
|
|
|
|
|
import (
|
2017-03-15 15:55:30 +00:00
|
|
|
"context"
|
2021-09-21 17:21:44 +00:00
|
|
|
"crypto/rand"
|
2016-09-04 05:13:42 +00:00
|
|
|
"encoding/json"
|
2016-10-05 00:17:55 +00:00
|
|
|
"fmt"
|
2021-09-21 17:21:44 +00:00
|
|
|
"math/big"
|
2016-10-05 00:17:55 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2021-11-08 14:42:37 +00:00
|
|
|
"sync/atomic"
|
2016-10-05 00:17:55 +00:00
|
|
|
"time"
|
2016-09-04 05:13:42 +00:00
|
|
|
|
2021-08-02 22:06:27 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server"
|
2021-11-15 14:11:38 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
2021-08-02 22:06:27 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/logging"
|
2021-08-24 20:24:52 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
2021-08-11 17:56:11 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/service/osquery_utils"
|
2021-08-02 22:06:27 +00:00
|
|
|
kithttp "github.com/go-kit/kit/transport/http"
|
|
|
|
|
2021-06-26 04:46:51 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
2021-06-06 22:07:29 +00:00
|
|
|
|
2021-06-26 04:46:51 +00:00
|
|
|
hostctx "github.com/fleetdm/fleet/v4/server/contexts/host"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/pubsub"
|
2021-01-19 22:52:29 +00:00
|
|
|
"github.com/go-kit/kit/log"
|
2021-03-09 02:35:17 +00:00
|
|
|
"github.com/go-kit/kit/log/level"
|
2017-01-17 06:03:51 +00:00
|
|
|
"github.com/pkg/errors"
|
2017-04-06 18:55:24 +00:00
|
|
|
"github.com/spf13/cast"
|
2016-09-04 05:13:42 +00:00
|
|
|
)
|
|
|
|
|
2016-09-21 03:08:11 +00:00
|
|
|
type osqueryError struct {
|
2016-09-29 04:21:39 +00:00
|
|
|
message string
|
|
|
|
nodeInvalid bool
|
2016-09-21 03:08:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e osqueryError) Error() string {
|
|
|
|
return e.message
|
|
|
|
}
|
|
|
|
|
2016-09-29 04:21:39 +00:00
|
|
|
func (e osqueryError) NodeInvalid() bool {
|
|
|
|
return e.nodeInvalid
|
|
|
|
}
|
|
|
|
|
2021-11-08 14:42:37 +00:00
|
|
|
var counter = int64(0)
|
|
|
|
|
2021-09-10 17:48:33 +00:00
|
|
|
func (svc Service) AuthenticateHost(ctx context.Context, nodeKey string) (*fleet.Host, bool, error) {
|
2021-06-03 23:24:15 +00:00
|
|
|
// skipauth: Authorization is currently for user endpoints only.
|
|
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
|
2016-09-29 04:21:39 +00:00
|
|
|
if nodeKey == "" {
|
2021-09-10 17:48:33 +00:00
|
|
|
return nil, false, osqueryError{
|
2016-09-29 04:21:39 +00:00
|
|
|
message: "authentication error: missing node key",
|
|
|
|
nodeInvalid: true,
|
|
|
|
}
|
|
|
|
}
|
2017-03-30 15:31:05 +00:00
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
host, err := svc.ds.AuthenticateHost(ctx, nodeKey)
|
2016-09-29 04:21:39 +00:00
|
|
|
if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
root := ctxerr.Cause(err)
|
|
|
|
switch root.(type) {
|
2021-06-06 22:07:29 +00:00
|
|
|
case fleet.NotFoundError:
|
2021-09-10 17:48:33 +00:00
|
|
|
return nil, false, osqueryError{
|
2017-05-25 21:10:12 +00:00
|
|
|
message: "authentication error: invalid node key: " + nodeKey,
|
|
|
|
nodeInvalid: true,
|
|
|
|
}
|
|
|
|
default:
|
2021-09-10 17:48:33 +00:00
|
|
|
return nil, false, osqueryError{
|
2017-05-25 21:10:12 +00:00
|
|
|
message: "authentication error: " + err.Error(),
|
|
|
|
}
|
2016-09-29 04:21:39 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-30 15:31:05 +00:00
|
|
|
|
2021-04-12 23:22:22 +00:00
|
|
|
// Update the "seen" time used to calculate online status. These updates are
|
|
|
|
// batched for MySQL performance reasons. Because this is done
|
|
|
|
// asynchronously, it is possible for the server to shut down before
|
|
|
|
// updating the seen time for these hosts. This seems to be an acceptable
|
|
|
|
// tradeoff as an online host will continue to check in and quickly be
|
|
|
|
// marked online again.
|
|
|
|
svc.seenHostSet.addHostID(host.ID)
|
|
|
|
host.SeenTime = svc.clock.Now()
|
2017-03-30 15:31:05 +00:00
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
return host, svc.debugEnabledForHost(ctx, host), nil
|
2021-09-10 17:48:33 +00:00
|
|
|
}
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
func (svc Service) debugEnabledForHost(ctx context.Context, host *fleet.Host) bool {
|
2021-09-10 17:48:33 +00:00
|
|
|
hlogger := log.With(svc.logger, "host-id", host.ID)
|
2021-09-14 12:11:07 +00:00
|
|
|
ac, err := svc.ds.AppConfig(ctx)
|
2021-09-10 17:48:33 +00:00
|
|
|
if err != nil {
|
|
|
|
level.Debug(hlogger).Log("err", errors.Wrap(err, "getting app config for host debug"))
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
doDebug := false
|
|
|
|
for _, hostID := range ac.ServerSettings.DebugHostIDs {
|
|
|
|
if host.ID == hostID {
|
|
|
|
doDebug = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return doDebug
|
2016-09-29 04:21:39 +00:00
|
|
|
}
|
|
|
|
|
2021-06-01 00:07:51 +00:00
|
|
|
func (svc Service) EnrollAgent(ctx context.Context, enrollSecret, hostIdentifier string, hostDetails map[string](map[string]string)) (string, error) {
|
2021-06-03 23:24:15 +00:00
|
|
|
// skipauth: Authorization is currently for user endpoints only.
|
|
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
|
2021-08-02 22:06:27 +00:00
|
|
|
logIPs(ctx, "hostIdentifier", hostIdentifier)
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
secret, err := svc.ds.VerifyEnrollSecret(ctx, enrollSecret)
|
2017-01-20 19:48:54 +00:00
|
|
|
if err != nil {
|
2020-05-29 16:12:39 +00:00
|
|
|
return "", osqueryError{
|
|
|
|
message: "enroll failed: " + err.Error(),
|
|
|
|
nodeInvalid: true,
|
|
|
|
}
|
2017-01-20 19:48:54 +00:00
|
|
|
}
|
|
|
|
|
2021-07-19 18:08:41 +00:00
|
|
|
nodeKey, err := server.GenerateRandomText(svc.config.Osquery.NodeKeySize)
|
2020-05-29 16:12:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", osqueryError{
|
|
|
|
message: "generate node key failed: " + err.Error(),
|
|
|
|
nodeInvalid: true,
|
|
|
|
}
|
2016-09-04 05:13:42 +00:00
|
|
|
}
|
|
|
|
|
2021-03-09 02:35:17 +00:00
|
|
|
hostIdentifier = getHostIdentifier(svc.logger, svc.config.Osquery.HostIdentifier, hostIdentifier, hostDetails)
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
host, err := svc.ds.EnrollHost(ctx, hostIdentifier, nodeKey, secret.TeamID, svc.config.Osquery.EnrollCooldown)
|
2016-09-04 05:13:42 +00:00
|
|
|
if err != nil {
|
2020-05-29 16:12:39 +00:00
|
|
|
return "", osqueryError{message: "save enroll failed: " + err.Error(), nodeInvalid: true}
|
2016-09-04 05:13:42 +00:00
|
|
|
}
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
appConfig, err := svc.ds.AppConfig(ctx)
|
2021-08-11 17:56:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", osqueryError{message: "save enroll failed: " + err.Error(), nodeInvalid: true}
|
|
|
|
}
|
2019-07-01 23:50:04 +00:00
|
|
|
// Save enrollment details if provided
|
2021-08-11 17:56:11 +00:00
|
|
|
detailQueries := osquery_utils.GetDetailQueries(appConfig)
|
2019-07-01 23:50:04 +00:00
|
|
|
save := false
|
|
|
|
if r, ok := hostDetails["os_version"]; ok {
|
2021-08-11 17:56:11 +00:00
|
|
|
err := detailQueries["os_version"].IngestFunc(svc.logger, host, []map[string]string{r})
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrap(err, "Ingesting os_version")
|
|
|
|
}
|
2019-07-01 23:50:04 +00:00
|
|
|
save = true
|
|
|
|
}
|
|
|
|
if r, ok := hostDetails["osquery_info"]; ok {
|
2021-08-11 17:56:11 +00:00
|
|
|
err := detailQueries["osquery_info"].IngestFunc(svc.logger, host, []map[string]string{r})
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrap(err, "Ingesting osquery_info")
|
|
|
|
}
|
2019-07-01 23:50:04 +00:00
|
|
|
save = true
|
|
|
|
}
|
|
|
|
if r, ok := hostDetails["system_info"]; ok {
|
2021-08-11 17:56:11 +00:00
|
|
|
err := detailQueries["system_info"].IngestFunc(svc.logger, host, []map[string]string{r})
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrap(err, "Ingesting system_info")
|
|
|
|
}
|
2019-07-01 23:50:04 +00:00
|
|
|
save = true
|
|
|
|
}
|
|
|
|
if save {
|
2021-11-08 14:42:37 +00:00
|
|
|
if appConfig.ServerSettings.DeferredSaveHost {
|
|
|
|
go svc.serialSaveHost(host)
|
|
|
|
} else {
|
|
|
|
err = svc.ds.SaveHost(ctx, host)
|
|
|
|
if err != nil {
|
|
|
|
return "", errors.Wrap(err, "save host in enroll agent")
|
|
|
|
}
|
2019-07-01 23:50:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-08 14:42:37 +00:00
|
|
|
return nodeKey, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (svc Service) serialSaveHost(host *fleet.Host) {
|
|
|
|
newVal := atomic.AddInt64(&counter, 1)
|
|
|
|
defer func() {
|
|
|
|
atomic.AddInt64(&counter, -1)
|
|
|
|
}()
|
|
|
|
level.Debug(svc.logger).Log("background", newVal)
|
|
|
|
|
|
|
|
ctx, cancelFunc := context.WithTimeout(context.Background(), 30*time.Second)
|
|
|
|
defer cancelFunc()
|
|
|
|
err := svc.ds.SerialSaveHost(ctx, host)
|
|
|
|
if err != nil {
|
|
|
|
level.Error(svc.logger).Log("background-err", err)
|
|
|
|
}
|
2016-09-04 05:13:42 +00:00
|
|
|
}
|
|
|
|
|
2021-03-09 02:35:17 +00:00
|
|
|
func getHostIdentifier(logger log.Logger, identifierOption, providedIdentifier string, details map[string](map[string]string)) string {
|
|
|
|
switch identifierOption {
|
|
|
|
case "provided":
|
|
|
|
// Use the host identifier already provided in the request.
|
|
|
|
return providedIdentifier
|
|
|
|
|
|
|
|
case "instance":
|
|
|
|
r, ok := details["osquery_info"]
|
|
|
|
if !ok {
|
|
|
|
level.Info(logger).Log(
|
|
|
|
"msg", "could not get host identifier",
|
|
|
|
"reason", "missing osquery_info",
|
|
|
|
"identifier", "instance",
|
|
|
|
)
|
|
|
|
} else if r["instance_id"] == "" {
|
|
|
|
level.Info(logger).Log(
|
|
|
|
"msg", "could not get host identifier",
|
|
|
|
"reason", "missing instance_id in osquery_info",
|
|
|
|
"identifier", "instance",
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
return r["instance_id"]
|
|
|
|
}
|
|
|
|
|
|
|
|
case "uuid":
|
|
|
|
r, ok := details["osquery_info"]
|
|
|
|
if !ok {
|
|
|
|
level.Info(logger).Log(
|
|
|
|
"msg", "could not get host identifier",
|
|
|
|
"reason", "missing osquery_info",
|
|
|
|
"identifier", "uuid",
|
|
|
|
)
|
|
|
|
} else if r["uuid"] == "" {
|
|
|
|
level.Info(logger).Log(
|
|
|
|
"msg", "could not get host identifier",
|
|
|
|
"reason", "missing instance_id in osquery_info",
|
|
|
|
"identifier", "uuid",
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
return r["uuid"]
|
|
|
|
}
|
|
|
|
|
|
|
|
case "hostname":
|
|
|
|
r, ok := details["system_info"]
|
|
|
|
if !ok {
|
|
|
|
level.Info(logger).Log(
|
|
|
|
"msg", "could not get host identifier",
|
|
|
|
"reason", "missing system_info",
|
|
|
|
"identifier", "hostname",
|
|
|
|
)
|
|
|
|
} else if r["hostname"] == "" {
|
|
|
|
level.Info(logger).Log(
|
|
|
|
"msg", "could not get host identifier",
|
|
|
|
"reason", "missing instance_id in system_info",
|
|
|
|
"identifier", "hostname",
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
return r["hostname"]
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
panic("Unknown option for host_identifier: " + identifierOption)
|
|
|
|
}
|
|
|
|
|
|
|
|
return providedIdentifier
|
|
|
|
}
|
|
|
|
|
2021-06-01 00:07:51 +00:00
|
|
|
func (svc *Service) GetClientConfig(ctx context.Context) (map[string]interface{}, error) {
|
2021-06-03 23:24:15 +00:00
|
|
|
// skipauth: Authorization is currently for user endpoints only.
|
|
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
|
2021-08-02 22:06:27 +00:00
|
|
|
logIPs(ctx)
|
|
|
|
|
2016-10-03 03:14:35 +00:00
|
|
|
host, ok := hostctx.FromContext(ctx)
|
|
|
|
if !ok {
|
|
|
|
return nil, osqueryError{message: "internal error: missing host from request context"}
|
|
|
|
}
|
|
|
|
|
2021-05-12 01:15:16 +00:00
|
|
|
baseConfig, err := svc.AgentOptionsForHost(ctx, &host)
|
2017-12-13 23:14:54 +00:00
|
|
|
if err != nil {
|
2021-06-18 16:43:16 +00:00
|
|
|
return nil, osqueryError{message: "internal error: fetch base config: " + err.Error()}
|
2017-12-13 23:14:54 +00:00
|
|
|
}
|
|
|
|
|
2021-09-01 22:17:41 +00:00
|
|
|
config := make(map[string]interface{})
|
|
|
|
|
|
|
|
if baseConfig != nil {
|
|
|
|
err = json.Unmarshal(baseConfig, &config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, osqueryError{message: "internal error: parse base configuration: " + err.Error()}
|
|
|
|
}
|
2016-12-31 17:56:54 +00:00
|
|
|
}
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
packs, err := svc.ds.ListPacksForHost(ctx, host.ID)
|
2016-10-03 03:14:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, osqueryError{message: "database error: " + err.Error()}
|
|
|
|
}
|
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
packConfig := fleet.Packs{}
|
2016-10-03 03:14:35 +00:00
|
|
|
for _, pack := range packs {
|
|
|
|
// first, we must figure out what queries are in this pack
|
2021-09-14 12:11:07 +00:00
|
|
|
queries, err := svc.ds.ListScheduledQueriesInPack(ctx, pack.ID, fleet.ListOptions{})
|
2016-10-03 03:14:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, osqueryError{message: "database error: " + err.Error()}
|
|
|
|
}
|
|
|
|
|
|
|
|
// the serializable osquery config struct expects content in a
|
|
|
|
// particular format, so we do the conversion here
|
2021-06-06 22:07:29 +00:00
|
|
|
configQueries := fleet.Queries{}
|
2016-10-03 03:14:35 +00:00
|
|
|
for _, query := range queries {
|
2021-06-06 22:07:29 +00:00
|
|
|
queryContent := fleet.QueryContent{
|
2016-10-03 03:14:35 +00:00
|
|
|
Query: query.Query,
|
|
|
|
Interval: query.Interval,
|
|
|
|
Platform: query.Platform,
|
|
|
|
Version: query.Version,
|
2018-01-03 00:06:50 +00:00
|
|
|
Removed: query.Removed,
|
|
|
|
Shard: query.Shard,
|
2021-02-21 17:58:36 +00:00
|
|
|
Denylist: query.Denylist,
|
2018-01-03 00:06:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if query.Removed != nil {
|
|
|
|
queryContent.Removed = query.Removed
|
2016-10-03 03:14:35 +00:00
|
|
|
}
|
2017-01-10 22:27:52 +00:00
|
|
|
|
2017-12-04 14:43:43 +00:00
|
|
|
if query.Snapshot != nil && *query.Snapshot {
|
2017-01-10 22:27:52 +00:00
|
|
|
queryContent.Snapshot = query.Snapshot
|
|
|
|
}
|
|
|
|
|
|
|
|
configQueries[query.Name] = queryContent
|
2016-10-03 03:14:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// finally, we add the pack to the client config struct with all of
|
2017-12-13 23:14:54 +00:00
|
|
|
// the pack's queries
|
2021-06-06 22:07:29 +00:00
|
|
|
packConfig[pack.Name] = fleet.PackContent{
|
2016-10-03 03:14:35 +00:00
|
|
|
Platform: pack.Platform,
|
|
|
|
Queries: configQueries,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-13 23:14:54 +00:00
|
|
|
if len(packConfig) > 0 {
|
|
|
|
packJSON, err := json.Marshal(packConfig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, osqueryError{message: "internal error: marshal pack JSON: " + err.Error()}
|
|
|
|
}
|
|
|
|
config["packs"] = json.RawMessage(packJSON)
|
|
|
|
}
|
|
|
|
|
2021-03-05 16:25:46 +00:00
|
|
|
// Save interval values if they have been updated.
|
2017-04-06 18:55:24 +00:00
|
|
|
saveHost := false
|
2017-12-13 23:14:54 +00:00
|
|
|
if options, ok := config["options"].(map[string]interface{}); ok {
|
|
|
|
distributedIntervalVal, ok := options["distributed_interval"]
|
|
|
|
distributedInterval, err := cast.ToUintE(distributedIntervalVal)
|
|
|
|
if ok && err == nil && host.DistributedInterval != distributedInterval {
|
|
|
|
host.DistributedInterval = distributedInterval
|
|
|
|
saveHost = true
|
|
|
|
}
|
2017-04-06 18:55:24 +00:00
|
|
|
|
2017-12-13 23:14:54 +00:00
|
|
|
loggerTLSPeriodVal, ok := options["logger_tls_period"]
|
|
|
|
loggerTLSPeriod, err := cast.ToUintE(loggerTLSPeriodVal)
|
|
|
|
if ok && err == nil && host.LoggerTLSPeriod != loggerTLSPeriod {
|
|
|
|
host.LoggerTLSPeriod = loggerTLSPeriod
|
|
|
|
saveHost = true
|
|
|
|
}
|
2021-03-05 16:25:46 +00:00
|
|
|
|
|
|
|
// Note config_tls_refresh can only be set in the osquery flags (and has
|
|
|
|
// also been deprecated in osquery for quite some time) so is ignored
|
|
|
|
// here.
|
|
|
|
configRefreshVal, ok := options["config_refresh"]
|
|
|
|
configRefresh, err := cast.ToUintE(configRefreshVal)
|
|
|
|
if ok && err == nil && host.ConfigTLSRefresh != configRefresh {
|
|
|
|
host.ConfigTLSRefresh = configRefresh
|
|
|
|
saveHost = true
|
|
|
|
}
|
2017-04-06 18:55:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if saveHost {
|
2021-11-08 14:42:37 +00:00
|
|
|
appConfig, err := svc.ds.AppConfig(ctx)
|
2017-04-06 18:55:24 +00:00
|
|
|
if err != nil {
|
2021-11-08 14:42:37 +00:00
|
|
|
return nil, errors.Wrap(err, "get app config on get client config")
|
|
|
|
}
|
|
|
|
if appConfig.ServerSettings.DeferredSaveHost {
|
|
|
|
go svc.serialSaveHost(&host)
|
|
|
|
} else {
|
|
|
|
err = svc.ds.SaveHost(ctx, &host)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "save host in get client config")
|
|
|
|
}
|
2017-04-06 18:55:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-13 23:14:54 +00:00
|
|
|
return config, nil
|
2016-09-04 05:13:42 +00:00
|
|
|
}
|
|
|
|
|
2021-06-01 00:07:51 +00:00
|
|
|
func (svc *Service) SubmitStatusLogs(ctx context.Context, logs []json.RawMessage) error {
|
2021-06-03 23:24:15 +00:00
|
|
|
// skipauth: Authorization is currently for user endpoints only.
|
|
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
|
2021-08-02 22:06:27 +00:00
|
|
|
logIPs(ctx)
|
|
|
|
|
2019-07-16 22:41:50 +00:00
|
|
|
if err := svc.osqueryLogWriter.Status.Write(ctx, logs); err != nil {
|
2019-04-08 18:47:15 +00:00
|
|
|
return osqueryError{message: "error writing status logs: " + err.Error()}
|
2017-04-03 21:48:50 +00:00
|
|
|
}
|
2016-09-04 05:13:42 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-08-02 22:06:27 +00:00
|
|
|
func logIPs(ctx context.Context, extras ...interface{}) {
|
|
|
|
remoteAddr, _ := ctx.Value(kithttp.ContextKeyRequestRemoteAddr).(string)
|
|
|
|
xForwardedFor, _ := ctx.Value(kithttp.ContextKeyRequestXForwardedFor).(string)
|
|
|
|
logging.WithLevel(
|
|
|
|
logging.WithExtras(
|
|
|
|
logging.WithNoUser(ctx),
|
|
|
|
append(extras, "ip_addr", remoteAddr, "x_for_ip_addr", xForwardedFor)...),
|
|
|
|
level.Debug,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-06-01 00:07:51 +00:00
|
|
|
func (svc *Service) SubmitResultLogs(ctx context.Context, logs []json.RawMessage) error {
|
2021-06-03 23:24:15 +00:00
|
|
|
// skipauth: Authorization is currently for user endpoints only.
|
|
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
|
2021-08-02 22:06:27 +00:00
|
|
|
logIPs(ctx)
|
|
|
|
|
2019-07-16 22:41:50 +00:00
|
|
|
if err := svc.osqueryLogWriter.Result.Write(ctx, logs); err != nil {
|
2019-04-08 18:47:15 +00:00
|
|
|
return osqueryError{message: "error writing result logs: " + err.Error()}
|
2017-04-03 21:48:50 +00:00
|
|
|
}
|
2016-09-29 04:21:39 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-09-21 03:08:11 +00:00
|
|
|
// hostLabelQueryPrefix is appended before the query name when a query is
|
|
|
|
// provided as a label query. This allows the results to be retrieved when
|
|
|
|
// osqueryd writes the distributed query results.
|
2021-02-01 19:39:53 +00:00
|
|
|
const hostLabelQueryPrefix = "fleet_label_query_"
|
2016-09-21 03:08:11 +00:00
|
|
|
|
|
|
|
// hostDetailQueryPrefix is appended before the query name when a query is
|
|
|
|
// provided as a detail query.
|
2021-02-01 19:39:53 +00:00
|
|
|
const hostDetailQueryPrefix = "fleet_detail_query_"
|
2016-09-21 03:08:11 +00:00
|
|
|
|
2020-05-21 15:36:00 +00:00
|
|
|
// hostAdditionalQueryPrefix is appended before the query name when a query is
|
|
|
|
// provided as an additional query (additional info for hosts to retrieve).
|
2021-02-01 19:39:53 +00:00
|
|
|
const hostAdditionalQueryPrefix = "fleet_additional_query_"
|
2020-05-21 15:36:00 +00:00
|
|
|
|
2021-08-24 20:24:52 +00:00
|
|
|
// hostPolicyQueryPrefix is appended before the query name when a query is
|
|
|
|
// provided as a policy query. This allows the results to be retrieved when
|
|
|
|
// osqueryd writes the distributed query results.
|
|
|
|
const hostPolicyQueryPrefix = "fleet_policy_query_"
|
|
|
|
|
2016-11-14 18:22:54 +00:00
|
|
|
// hostDistributedQueryPrefix is appended before the query name when a query is
|
|
|
|
// run from a distributed query campaign
|
2021-02-01 19:39:53 +00:00
|
|
|
const hostDistributedQueryPrefix = "fleet_distributed_query_"
|
2016-11-14 18:22:54 +00:00
|
|
|
|
2021-10-25 18:46:49 +00:00
|
|
|
// detailQueriesForHost returns the map of detail+additional queries that should be executed by
|
|
|
|
// osqueryd to fill in the host details.
|
|
|
|
func (svc *Service) detailQueriesForHost(ctx context.Context, host fleet.Host) (map[string]string, error) {
|
2021-09-21 17:21:44 +00:00
|
|
|
if !svc.shouldUpdate(host.DetailUpdatedAt, svc.config.Osquery.DetailUpdateInterval) && !host.RefetchRequested {
|
2021-10-25 18:46:49 +00:00
|
|
|
return nil, nil
|
2016-10-05 00:17:55 +00:00
|
|
|
}
|
2021-10-25 18:46:49 +00:00
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
config, err := svc.ds.AppConfig(ctx)
|
2021-08-11 17:56:11 +00:00
|
|
|
if err != nil {
|
2021-10-25 18:46:49 +00:00
|
|
|
return nil, errors.Wrap(err, "read app config")
|
2021-08-11 17:56:11 +00:00
|
|
|
}
|
2016-10-05 00:17:55 +00:00
|
|
|
|
2021-10-25 18:46:49 +00:00
|
|
|
queries := make(map[string]string)
|
2021-08-11 17:56:11 +00:00
|
|
|
detailQueries := osquery_utils.GetDetailQueries(config)
|
2016-10-05 00:17:55 +00:00
|
|
|
for name, query := range detailQueries {
|
2021-08-11 17:56:11 +00:00
|
|
|
if query.RunsForPlatform(host.Platform) {
|
2021-04-26 15:44:22 +00:00
|
|
|
queries[hostDetailQueryPrefix+name] = query.Query
|
|
|
|
}
|
2016-09-21 03:08:11 +00:00
|
|
|
}
|
2020-05-21 15:36:00 +00:00
|
|
|
|
2021-08-20 15:27:41 +00:00
|
|
|
if config.HostSettings.AdditionalQueries == nil {
|
2020-05-21 15:36:00 +00:00
|
|
|
// No additional queries set
|
|
|
|
return queries, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var additionalQueries map[string]string
|
2021-08-20 15:27:41 +00:00
|
|
|
if err := json.Unmarshal(*config.HostSettings.AdditionalQueries, &additionalQueries); err != nil {
|
2021-10-25 18:46:49 +00:00
|
|
|
return nil, errors.Wrap(err, "unmarshal additional queries")
|
2020-05-21 15:36:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for name, query := range additionalQueries {
|
|
|
|
queries[hostAdditionalQueryPrefix+name] = query
|
|
|
|
}
|
|
|
|
|
|
|
|
return queries, nil
|
2016-09-21 03:08:11 +00:00
|
|
|
}
|
|
|
|
|
2021-09-21 17:21:44 +00:00
|
|
|
func (svc *Service) shouldUpdate(lastUpdated time.Time, interval time.Duration) bool {
|
|
|
|
var jitter time.Duration
|
|
|
|
if svc.config.Osquery.MaxJitterPercent > 0 {
|
|
|
|
maxJitter := time.Duration(svc.config.Osquery.MaxJitterPercent) * interval / time.Duration(100.0)
|
|
|
|
randDuration, err := rand.Int(rand.Reader, big.NewInt(int64(maxJitter)))
|
|
|
|
if err == nil {
|
|
|
|
jitter = time.Duration(randDuration.Int64())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cutoff := svc.clock.Now().Add(-(interval + jitter))
|
|
|
|
return lastUpdated.Before(cutoff)
|
|
|
|
}
|
|
|
|
|
2021-10-25 18:46:49 +00:00
|
|
|
func (svc *Service) labelQueriesForHost(ctx context.Context, host *fleet.Host) (map[string]string, error) {
|
2021-11-01 18:13:16 +00:00
|
|
|
labelReportedAt := svc.task.GetHostLabelReportedAt(ctx, host)
|
|
|
|
if !svc.shouldUpdate(labelReportedAt, svc.config.Osquery.LabelUpdateInterval) && !host.RefetchRequested {
|
2021-10-25 18:46:49 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
labelQueries, err := svc.ds.LabelQueriesForHost(ctx, host)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "retrieve label queries")
|
|
|
|
}
|
|
|
|
return labelQueries, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (svc *Service) policyQueriesForHost(ctx context.Context, host *fleet.Host) (map[string]string, error) {
|
|
|
|
if !svc.shouldUpdate(host.PolicyUpdatedAt, svc.config.Osquery.PolicyUpdateInterval) && !host.RefetchRequested {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
policyQueries, err := svc.ds.PolicyQueriesForHost(ctx, host)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "retrieve policy queries")
|
|
|
|
}
|
|
|
|
return policyQueries, nil
|
|
|
|
}
|
|
|
|
|
2021-06-01 00:07:51 +00:00
|
|
|
func (svc *Service) GetDistributedQueries(ctx context.Context) (map[string]string, uint, error) {
|
2021-06-03 23:24:15 +00:00
|
|
|
// skipauth: Authorization is currently for user endpoints only.
|
|
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
|
2021-08-02 22:06:27 +00:00
|
|
|
logIPs(ctx)
|
|
|
|
|
2016-09-29 04:21:39 +00:00
|
|
|
host, ok := hostctx.FromContext(ctx)
|
2016-09-26 17:14:39 +00:00
|
|
|
if !ok {
|
2017-03-21 16:17:38 +00:00
|
|
|
return nil, 0, osqueryError{message: "internal error: missing host from request context"}
|
2016-09-21 03:08:11 +00:00
|
|
|
}
|
|
|
|
|
2021-10-25 18:46:49 +00:00
|
|
|
queries := make(map[string]string)
|
|
|
|
|
|
|
|
detailQueries, err := svc.detailQueriesForHost(ctx, host)
|
2020-05-21 15:36:00 +00:00
|
|
|
if err != nil {
|
2021-10-25 18:46:49 +00:00
|
|
|
return nil, 0, osqueryError{message: err.Error()}
|
|
|
|
}
|
|
|
|
for name, query := range detailQueries {
|
|
|
|
queries[name] = query
|
2020-05-21 15:36:00 +00:00
|
|
|
}
|
2016-09-21 03:08:11 +00:00
|
|
|
|
2021-10-25 18:46:49 +00:00
|
|
|
labelQueries, err := svc.labelQueriesForHost(ctx, &host)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, osqueryError{message: err.Error()}
|
|
|
|
}
|
|
|
|
for name, query := range labelQueries {
|
|
|
|
queries[hostLabelQueryPrefix+name] = query
|
2016-09-21 03:08:11 +00:00
|
|
|
}
|
2016-09-04 05:13:42 +00:00
|
|
|
|
2020-03-23 01:33:04 +00:00
|
|
|
liveQueries, err := svc.liveQueryStore.QueriesForHost(host.ID)
|
2016-11-14 18:22:54 +00:00
|
|
|
if err != nil {
|
2020-03-23 01:33:04 +00:00
|
|
|
return nil, 0, osqueryError{message: "retrieve live queries: " + err.Error()}
|
2016-11-14 18:22:54 +00:00
|
|
|
}
|
2020-03-23 01:33:04 +00:00
|
|
|
for name, query := range liveQueries {
|
|
|
|
queries[hostDistributedQueryPrefix+name] = query
|
2016-11-14 18:22:54 +00:00
|
|
|
}
|
2016-09-04 05:13:42 +00:00
|
|
|
|
2021-10-25 18:46:49 +00:00
|
|
|
policyQueries, err := svc.policyQueriesForHost(ctx, &host)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, osqueryError{message: err.Error()}
|
|
|
|
}
|
|
|
|
for name, query := range policyQueries {
|
|
|
|
queries[hostPolicyQueryPrefix+name] = query
|
2021-08-24 20:24:52 +00:00
|
|
|
}
|
|
|
|
|
2017-03-21 16:17:38 +00:00
|
|
|
accelerate := uint(0)
|
2021-06-24 00:32:19 +00:00
|
|
|
if host.Hostname == "" || host.Platform == "" {
|
2017-03-21 16:17:38 +00:00
|
|
|
// Assume this host is just enrolling, and accelerate checkins
|
|
|
|
// (to allow for platform restricted labels to run quickly
|
|
|
|
// after platform is retrieved from details)
|
|
|
|
accelerate = 10
|
|
|
|
}
|
|
|
|
|
|
|
|
return queries, accelerate, nil
|
2016-09-04 05:13:42 +00:00
|
|
|
}
|
|
|
|
|
2016-10-05 00:17:55 +00:00
|
|
|
// ingestDetailQuery takes the results of a detail query and modifies the
|
2021-06-06 22:07:29 +00:00
|
|
|
// provided fleet.Host appropriately.
|
2021-09-14 12:11:07 +00:00
|
|
|
func (svc *Service) ingestDetailQuery(ctx context.Context, host *fleet.Host, name string, rows []map[string]string) error {
|
2016-10-05 00:17:55 +00:00
|
|
|
trimmedQuery := strings.TrimPrefix(name, hostDetailQueryPrefix)
|
2021-08-11 17:56:11 +00:00
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
config, err := svc.ds.AppConfig(ctx)
|
2021-08-11 17:56:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return osqueryError{message: "ingest detail query: " + err.Error()}
|
|
|
|
}
|
|
|
|
|
|
|
|
detailQueries := osquery_utils.GetDetailQueries(config)
|
2016-10-05 00:17:55 +00:00
|
|
|
query, ok := detailQueries[trimmedQuery]
|
|
|
|
if !ok {
|
|
|
|
return osqueryError{message: "unknown detail query " + trimmedQuery}
|
|
|
|
}
|
|
|
|
|
2021-08-11 17:56:11 +00:00
|
|
|
err = query.IngestFunc(svc.logger, host, rows)
|
2016-10-05 00:17:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return osqueryError{
|
|
|
|
message: fmt.Sprintf("ingesting query %s: %s", name, err.Error()),
|
|
|
|
}
|
|
|
|
}
|
2016-12-01 17:00:00 +00:00
|
|
|
|
2016-10-05 00:17:55 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-08-24 20:24:52 +00:00
|
|
|
// ingestMembershipQuery records the results of label queries run by a host
|
|
|
|
func ingestMembershipQuery(
|
|
|
|
prefix string,
|
|
|
|
query string,
|
|
|
|
rows []map[string]string,
|
|
|
|
results map[uint]*bool,
|
|
|
|
failed bool,
|
|
|
|
) error {
|
|
|
|
trimmedQuery := strings.TrimPrefix(query, prefix)
|
2021-08-11 17:56:11 +00:00
|
|
|
trimmedQueryNum, err := strconv.Atoi(osquery_utils.EmptyToZero(trimmedQuery))
|
2017-01-17 06:03:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "converting query from string to int")
|
|
|
|
}
|
2021-08-24 20:24:52 +00:00
|
|
|
// A label/policy query matches if there is at least one result for that
|
2016-10-05 15:56:29 +00:00
|
|
|
// query. We must also store negative results.
|
2021-08-24 20:24:52 +00:00
|
|
|
if failed {
|
|
|
|
results[uint(trimmedQueryNum)] = nil
|
|
|
|
} else {
|
|
|
|
results[uint(trimmedQueryNum)] = ptr.Bool(len(rows) > 0)
|
|
|
|
}
|
|
|
|
|
2016-10-05 00:17:55 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-11-14 18:22:54 +00:00
|
|
|
// ingestDistributedQuery takes the results of a distributed query and modifies the
|
2021-06-06 22:07:29 +00:00
|
|
|
// provided fleet.Host appropriately.
|
2021-09-14 12:11:07 +00:00
|
|
|
func (svc *Service) ingestDistributedQuery(ctx context.Context, host fleet.Host, name string, rows []map[string]string, failed bool, errMsg string) error {
|
2016-11-14 18:22:54 +00:00
|
|
|
trimmedQuery := strings.TrimPrefix(name, hostDistributedQueryPrefix)
|
|
|
|
|
2021-08-11 17:56:11 +00:00
|
|
|
campaignID, err := strconv.Atoi(osquery_utils.EmptyToZero(trimmedQuery))
|
2016-11-14 18:22:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return osqueryError{message: "unable to parse campaign ID: " + trimmedQuery}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write the results to the pubsub store
|
2021-06-06 22:07:29 +00:00
|
|
|
res := fleet.DistributedQueryResult{
|
2016-11-14 18:22:54 +00:00
|
|
|
DistributedQueryCampaignID: uint(campaignID),
|
2019-07-16 22:41:50 +00:00
|
|
|
Host: host,
|
|
|
|
Rows: rows,
|
2016-11-14 18:22:54 +00:00
|
|
|
}
|
Push distributed query errors over results websocket (#878)
As of recently, osquery will report when a distributed query fails. We now
expose errors over the results websocket. When a query errored on the host, the
`error` key in the result will be non-null. Note that osquery currently doesn't
provide any details so the error string will always be "failed". I anticipate
that we will fix this and the string is included for future-proofing.
Successful result:
```
{
"type": "result",
"data": {
"distributed_query_execution_id": 15,
"host": {
... omitted ...
},
"rows": [
{
"hour": "1"
}
],
"error": null
}
}
```
Failed result:
```
{
"type": "result",
"data": {
"distributed_query_execution_id": 14,
"host": {
... omitted ...
},
"rows": [
],
"error": "failed"
}
}
```
2017-01-11 03:34:32 +00:00
|
|
|
if failed {
|
2021-01-19 22:52:29 +00:00
|
|
|
res.Error = &errMsg
|
Push distributed query errors over results websocket (#878)
As of recently, osquery will report when a distributed query fails. We now
expose errors over the results websocket. When a query errored on the host, the
`error` key in the result will be non-null. Note that osquery currently doesn't
provide any details so the error string will always be "failed". I anticipate
that we will fix this and the string is included for future-proofing.
Successful result:
```
{
"type": "result",
"data": {
"distributed_query_execution_id": 15,
"host": {
... omitted ...
},
"rows": [
{
"hour": "1"
}
],
"error": null
}
}
```
Failed result:
```
{
"type": "result",
"data": {
"distributed_query_execution_id": 14,
"host": {
... omitted ...
},
"rows": [
],
"error": "failed"
}
}
```
2017-01-11 03:34:32 +00:00
|
|
|
}
|
2016-11-14 18:22:54 +00:00
|
|
|
|
|
|
|
err = svc.resultStore.WriteResult(res)
|
|
|
|
if err != nil {
|
2021-11-15 14:11:38 +00:00
|
|
|
var pse pubsub.Error
|
|
|
|
ok := errors.As(err, &pse)
|
|
|
|
if !ok || !pse.NoSubscriber() {
|
2016-12-27 15:35:19 +00:00
|
|
|
return osqueryError{message: "writing results: " + err.Error()}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there are no subscribers, the campaign is "orphaned"
|
|
|
|
// and should be closed so that we don't continue trying to
|
|
|
|
// execute that query when we can't write to any subscriber
|
2021-09-14 12:11:07 +00:00
|
|
|
campaign, err := svc.ds.DistributedQueryCampaign(ctx, uint(campaignID))
|
2016-12-27 15:35:19 +00:00
|
|
|
if err != nil {
|
2021-08-24 17:35:03 +00:00
|
|
|
if err := svc.liveQueryStore.StopQuery(strconv.Itoa(campaignID)); err != nil {
|
2021-05-25 03:36:40 +00:00
|
|
|
return osqueryError{message: "stop orphaned campaign after load failure: " + err.Error()}
|
|
|
|
}
|
2016-12-27 15:35:19 +00:00
|
|
|
return osqueryError{message: "loading orphaned campaign: " + err.Error()}
|
|
|
|
}
|
|
|
|
|
2021-08-25 22:28:04 +00:00
|
|
|
if campaign.CreatedAt.After(svc.clock.Now().Add(-1 * time.Minute)) {
|
|
|
|
// Give the client a minute to connect before considering the
|
2020-03-23 01:33:04 +00:00
|
|
|
// campaign orphaned
|
2020-10-06 16:30:24 +00:00
|
|
|
return osqueryError{message: "campaign waiting for listener (please retry)"}
|
2016-12-27 15:35:19 +00:00
|
|
|
}
|
2016-11-14 18:22:54 +00:00
|
|
|
|
2021-06-06 22:07:29 +00:00
|
|
|
if campaign.Status != fleet.QueryComplete {
|
|
|
|
campaign.Status = fleet.QueryComplete
|
2021-09-14 12:11:07 +00:00
|
|
|
if err := svc.ds.SaveDistributedQueryCampaign(ctx, campaign); err != nil {
|
2020-03-23 01:33:04 +00:00
|
|
|
return osqueryError{message: "closing orphaned campaign: " + err.Error()}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-24 17:35:03 +00:00
|
|
|
if err := svc.liveQueryStore.StopQuery(strconv.Itoa(campaignID)); err != nil {
|
2020-03-23 01:33:04 +00:00
|
|
|
return osqueryError{message: "stopping orphaned campaign: " + err.Error()}
|
|
|
|
}
|
2020-10-06 16:30:24 +00:00
|
|
|
|
|
|
|
// No need to record query completion in this case
|
2021-08-25 22:28:04 +00:00
|
|
|
return osqueryError{message: "campaign stopped"}
|
2016-11-14 18:22:54 +00:00
|
|
|
}
|
|
|
|
|
2021-08-24 17:35:03 +00:00
|
|
|
err = svc.liveQueryStore.QueryCompletedByHost(strconv.Itoa(campaignID), host.ID)
|
2016-11-14 18:22:54 +00:00
|
|
|
if err != nil {
|
2020-03-23 01:33:04 +00:00
|
|
|
return osqueryError{message: "record query completion: " + err.Error()}
|
2016-11-14 18:22:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-08-24 20:24:52 +00:00
|
|
|
func (svc *Service) SubmitDistributedQueryResults(
|
|
|
|
ctx context.Context,
|
|
|
|
results fleet.OsqueryDistributedQueryResults,
|
|
|
|
statuses map[string]fleet.OsqueryStatus,
|
|
|
|
messages map[string]string,
|
|
|
|
) error {
|
2021-06-03 23:24:15 +00:00
|
|
|
// skipauth: Authorization is currently for user endpoints only.
|
|
|
|
svc.authz.SkipAuthorization(ctx)
|
|
|
|
|
2021-08-02 22:06:27 +00:00
|
|
|
logIPs(ctx)
|
|
|
|
|
2016-10-05 00:17:55 +00:00
|
|
|
host, ok := hostctx.FromContext(ctx)
|
|
|
|
if !ok {
|
|
|
|
return osqueryError{message: "internal error: missing host from request context"}
|
|
|
|
}
|
|
|
|
|
2021-08-04 13:11:51 +00:00
|
|
|
// Check for host details queries and if so, load host additional.
|
|
|
|
// If we don't do this, we will end up unintentionally dropping
|
|
|
|
// any existing host additional info.
|
2021-05-17 17:29:50 +00:00
|
|
|
for query := range results {
|
2021-08-04 13:11:51 +00:00
|
|
|
if strings.HasPrefix(query, hostDetailQueryPrefix) {
|
2021-09-14 12:11:07 +00:00
|
|
|
fullHost, err := svc.ds.Host(ctx, host.ID)
|
2021-03-09 17:01:26 +00:00
|
|
|
if err != nil {
|
2021-08-04 13:11:51 +00:00
|
|
|
// leave this error return here, we don't want to drop host additionals
|
|
|
|
// if we can't get a host, everything is lost
|
2021-03-09 17:01:26 +00:00
|
|
|
return osqueryError{message: "internal error: load host additional: " + err.Error()}
|
|
|
|
}
|
|
|
|
host = *fullHost
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-30 15:31:05 +00:00
|
|
|
var err error
|
2020-05-21 15:36:00 +00:00
|
|
|
detailUpdated := false // Whether detail or additional was updated
|
2021-06-06 22:07:29 +00:00
|
|
|
additionalResults := make(fleet.OsqueryDistributedQueryResults)
|
2021-10-01 21:27:57 +00:00
|
|
|
additionalUpdated := false
|
2021-08-24 20:24:52 +00:00
|
|
|
labelResults := map[uint]*bool{}
|
|
|
|
policyResults := map[uint]*bool{}
|
2016-10-05 00:17:55 +00:00
|
|
|
for query, rows := range results {
|
2021-08-24 20:24:52 +00:00
|
|
|
// osquery docs say any nonzero (string) value for status indicates a query error
|
|
|
|
status, ok := statuses[query]
|
|
|
|
failed := ok && status != fleet.StatusOK
|
2016-10-05 00:17:55 +00:00
|
|
|
switch {
|
|
|
|
case strings.HasPrefix(query, hostDetailQueryPrefix):
|
2021-09-14 12:11:07 +00:00
|
|
|
err = svc.ingestDetailQuery(ctx, &host, query, rows)
|
2017-01-11 18:48:24 +00:00
|
|
|
detailUpdated = true
|
2020-05-21 15:36:00 +00:00
|
|
|
case strings.HasPrefix(query, hostAdditionalQueryPrefix):
|
|
|
|
name := strings.TrimPrefix(query, hostAdditionalQueryPrefix)
|
|
|
|
additionalResults[name] = rows
|
2021-10-01 21:27:57 +00:00
|
|
|
additionalUpdated = true
|
2016-10-05 00:17:55 +00:00
|
|
|
case strings.HasPrefix(query, hostLabelQueryPrefix):
|
2021-08-24 20:24:52 +00:00
|
|
|
err = ingestMembershipQuery(hostLabelQueryPrefix, query, rows, labelResults, failed)
|
|
|
|
case strings.HasPrefix(query, hostPolicyQueryPrefix):
|
|
|
|
err = ingestMembershipQuery(hostPolicyQueryPrefix, query, rows, policyResults, failed)
|
2016-11-14 18:22:54 +00:00
|
|
|
case strings.HasPrefix(query, hostDistributedQueryPrefix):
|
2021-09-14 12:11:07 +00:00
|
|
|
err = svc.ingestDistributedQuery(ctx, host, query, rows, failed, messages[query])
|
2021-08-25 22:28:04 +00:00
|
|
|
|
2016-10-05 00:17:55 +00:00
|
|
|
default:
|
2016-11-14 18:22:54 +00:00
|
|
|
err = osqueryError{message: "unknown query prefix: " + query}
|
2016-10-05 00:17:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
2021-08-25 22:28:04 +00:00
|
|
|
logging.WithErr(ctx, errors.New("error in live query ingestion"))
|
2021-08-04 13:11:51 +00:00
|
|
|
logging.WithExtras(ctx, "ingestion-err", err)
|
2016-10-05 00:17:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-08 14:42:37 +00:00
|
|
|
ac, err := svc.ds.AppConfig(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "getting app config")
|
|
|
|
}
|
|
|
|
|
2016-10-05 15:56:29 +00:00
|
|
|
if len(labelResults) > 0 {
|
2021-11-08 14:42:37 +00:00
|
|
|
if ac.ServerSettings.DeferredSaveHost {
|
|
|
|
if err := svc.ds.RecordLabelQueryExecutions(ctx, &host, labelResults, svc.clock.Now(), true); err != nil {
|
|
|
|
logging.WithErr(ctx, err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if err := svc.task.RecordLabelQueryExecutions(ctx, &host, labelResults, svc.clock.Now()); err != nil {
|
|
|
|
logging.WithErr(ctx, err)
|
|
|
|
}
|
2016-10-05 15:56:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-24 20:24:52 +00:00
|
|
|
if len(policyResults) > 0 {
|
2021-09-27 19:27:38 +00:00
|
|
|
host.PolicyUpdatedAt = svc.clock.Now()
|
2021-11-08 14:42:37 +00:00
|
|
|
err = svc.ds.RecordPolicyQueryExecutions(ctx, &host, policyResults, svc.clock.Now(), ac.ServerSettings.DeferredSaveHost)
|
2021-08-24 20:24:52 +00:00
|
|
|
if err != nil {
|
|
|
|
logging.WithErr(ctx, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-01 21:27:57 +00:00
|
|
|
if detailUpdated || additionalUpdated {
|
2021-07-13 20:15:38 +00:00
|
|
|
host.Modified = true
|
2021-06-24 00:32:19 +00:00
|
|
|
host.DetailUpdatedAt = svc.clock.Now()
|
2021-10-01 21:27:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if additionalUpdated {
|
2020-05-21 15:36:00 +00:00
|
|
|
additionalJSON, err := json.Marshal(additionalResults)
|
|
|
|
if err != nil {
|
2021-08-04 13:11:51 +00:00
|
|
|
logging.WithErr(ctx, err)
|
|
|
|
} else {
|
|
|
|
additional := json.RawMessage(additionalJSON)
|
|
|
|
host.Additional = &additional
|
2020-05-21 15:36:00 +00:00
|
|
|
}
|
2017-01-11 18:48:24 +00:00
|
|
|
}
|
|
|
|
|
2021-09-14 12:11:07 +00:00
|
|
|
svc.maybeDebugHost(ctx, host, results, statuses, messages)
|
2021-09-10 17:48:33 +00:00
|
|
|
|
2021-10-25 18:46:49 +00:00
|
|
|
if host.RefetchRequested {
|
|
|
|
host.RefetchRequested = false
|
|
|
|
host.Modified = true
|
|
|
|
}
|
|
|
|
|
2021-07-13 20:15:38 +00:00
|
|
|
if host.Modified {
|
2021-11-08 14:42:37 +00:00
|
|
|
appConfig, err := svc.ds.AppConfig(ctx)
|
2017-01-11 18:48:24 +00:00
|
|
|
if err != nil {
|
2021-08-04 13:11:51 +00:00
|
|
|
logging.WithErr(ctx, err)
|
2021-11-08 14:42:37 +00:00
|
|
|
} else {
|
|
|
|
if appConfig.ServerSettings.DeferredSaveHost {
|
|
|
|
go svc.serialSaveHost(&host)
|
|
|
|
} else {
|
|
|
|
err = svc.ds.SaveHost(ctx, &host)
|
|
|
|
if err != nil {
|
|
|
|
logging.WithErr(ctx, err)
|
|
|
|
}
|
|
|
|
}
|
2017-01-11 18:48:24 +00:00
|
|
|
}
|
2016-10-05 00:17:55 +00:00
|
|
|
}
|
|
|
|
|
2016-09-04 05:13:42 +00:00
|
|
|
return nil
|
|
|
|
}
|
2021-09-10 17:48:33 +00:00
|
|
|
|
|
|
|
func (svc *Service) maybeDebugHost(
|
2021-09-14 12:11:07 +00:00
|
|
|
ctx context.Context,
|
2021-09-10 17:48:33 +00:00
|
|
|
host fleet.Host,
|
|
|
|
results fleet.OsqueryDistributedQueryResults,
|
|
|
|
statuses map[string]fleet.OsqueryStatus,
|
|
|
|
messages map[string]string,
|
|
|
|
) {
|
2021-09-14 12:11:07 +00:00
|
|
|
if svc.debugEnabledForHost(ctx, &host) {
|
2021-09-10 17:48:33 +00:00
|
|
|
hlogger := log.With(svc.logger, "host-id", host.ID)
|
|
|
|
|
|
|
|
logJSON(hlogger, host, "host")
|
|
|
|
logJSON(hlogger, results, "results")
|
|
|
|
logJSON(hlogger, statuses, "statuses")
|
|
|
|
logJSON(hlogger, messages, "messages")
|
|
|
|
}
|
|
|
|
}
|