refactor gRPC endpoints with launcher packages in mind (#1582)
The launcher service implementation is an adapter around the TLS service. All launcher methods that have an equivalent in TLS pass the business logic to the TLS API. Closes #1565
This commit is contained in:
Generated
+26
-15
@@ -1,6 +1,11 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "git.apache.org/thrift.git"
|
||||
packages = ["lib/go/thrift"]
|
||||
revision = "0dd823580c78a79ae9696eb9b3650e400fff140f"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/VividCortex/mysqlerr"
|
||||
@@ -63,7 +68,7 @@
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-kit/kit"
|
||||
packages = ["endpoint","log","metrics","metrics/internal/lv","metrics/prometheus","transport/http"]
|
||||
packages = ["endpoint","log","metrics","metrics/internal/lv","metrics/prometheus","transport/grpc","transport/http"]
|
||||
revision = "4dc7be5d2d12881735283bcab7352178e190fc71"
|
||||
version = "v0.6.0"
|
||||
|
||||
@@ -91,6 +96,12 @@
|
||||
packages = ["proto","ptypes","ptypes/any","ptypes/duration","ptypes/timestamp"]
|
||||
revision = "130e6b02ab059e7b717a096f397c5b60111cae74"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/google/uuid"
|
||||
packages = ["."]
|
||||
revision = "064e2069ce9c359c118179501254f67d7d37ba24"
|
||||
version = "0.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/context"
|
||||
packages = ["."]
|
||||
@@ -139,12 +150,6 @@
|
||||
revision = "2eee05ed794112d45db504eb05aa693efd2b8b09"
|
||||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/kolide/agent-api"
|
||||
packages = ["."]
|
||||
revision = "f81f25bd36a2e73a762d4a5192d698bae06a7161"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/kolide/goose"
|
||||
@@ -157,6 +162,18 @@
|
||||
packages = ["version"]
|
||||
revision = "566c8f56a6ff7daba204818fbab0f2cb854b3310"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/kolide/launcher"
|
||||
packages = ["service","service/internal/launcherproto","service/uuid"]
|
||||
revision = "b8a2e91e294ee1fb4e39fcc5601d924becc7dbec"
|
||||
version = "0.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/kolide/osquery-go"
|
||||
packages = ["gen/osquery","plugin/distributed","plugin/logger"]
|
||||
revision = "03a792fcca9a86c9a717109c2ad8763d0759105b"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/kr/logfmt"
|
||||
@@ -277,16 +294,10 @@
|
||||
revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/stretchr/objx"
|
||||
packages = ["."]
|
||||
revision = "1a9d0bb9f541897e62256577b352fdbc1fb4fd94"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert","mock","require"]
|
||||
packages = ["assert","require"]
|
||||
revision = "2aa2c176b9dab406a6970f6a55f513e8a8c8b18f"
|
||||
|
||||
[[projects]]
|
||||
@@ -340,6 +351,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "a7e35edc607087f07c0bba174f1c9d02bf5a616d5f488de2a569e03bab69de68"
|
||||
inputs-digest = "c4fd9d0e72067244e44c5d04e91a4af2830b38e3609e8984aa24e1c1337286c2"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
||||
@@ -40,10 +40,6 @@
|
||||
[[constraint]]
|
||||
name = "github.com/jmoiron/sqlx"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/kolide/agent-api"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/kolide/goose"
|
||||
branch = "master"
|
||||
|
||||
+9
-4
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type initializer interface {
|
||||
@@ -134,9 +135,6 @@ the way that the Fleet server works.
|
||||
if err != nil {
|
||||
initFatal(err, "initializing service")
|
||||
}
|
||||
// Instantiate a gRPC service to handle launcher requests.
|
||||
launcher := launcher.New(svc, logger)
|
||||
defer launcher.GracefulStop()
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(1 * time.Hour)
|
||||
@@ -201,8 +199,12 @@ the way that the Fleet server works.
|
||||
healthCheckers[name] = hc
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Instantiate a gRPC service to handle launcher requests.
|
||||
launcher := launcher.New(svc, logger, grpc.NewServer(), healthCheckers)
|
||||
|
||||
r := http.NewServeMux()
|
||||
|
||||
r.Handle("/healthz", prometheus.InstrumentHandler("healthz", health.Handler(httpLogger, healthCheckers)))
|
||||
@@ -269,7 +271,10 @@ the way that the Fleet server works.
|
||||
<-sig //block on signal
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
errs <- srv.Shutdown(ctx)
|
||||
errs <- func() error {
|
||||
launcher.GracefulStop()
|
||||
return srv.Shutdown(ctx)
|
||||
}()
|
||||
}()
|
||||
|
||||
logger.Log("terminated", <-errs)
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
pb "github.com/kolide/agent-api"
|
||||
"github.com/kolide/fleet/server/kolide"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type authMiddleware struct {
|
||||
svc kolide.OsqueryService
|
||||
next pb.ApiServer
|
||||
}
|
||||
|
||||
func newAuthMiddleware(svc kolide.OsqueryService) func(svc pb.ApiServer) pb.ApiServer {
|
||||
return func(next pb.ApiServer) pb.ApiServer {
|
||||
return authMiddleware{
|
||||
svc: svc,
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s authMiddleware) RequestEnrollment(ctx context.Context, req *pb.EnrollmentRequest) (*pb.EnrollmentResponse, error) {
|
||||
return s.next.RequestEnrollment(ctx, req)
|
||||
}
|
||||
|
||||
func (s authMiddleware) RequestConfig(ctx context.Context, req *pb.AgentApiRequest) (*pb.ConfigResponse, error) {
|
||||
authCtx, auth, err := s.authenticateHost(ctx, req.NodeKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if auth.nodeInvalid {
|
||||
return &pb.ConfigResponse{NodeInvalid: auth.nodeInvalid, ErrorCode: auth.errorCode}, nil
|
||||
}
|
||||
return s.next.RequestConfig(authCtx, req)
|
||||
}
|
||||
|
||||
func (s authMiddleware) RequestQueries(ctx context.Context, req *pb.AgentApiRequest) (resp *pb.QueryCollection, err error) {
|
||||
authCtx, auth, err := s.authenticateHost(ctx, req.NodeKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if auth.nodeInvalid {
|
||||
return &pb.QueryCollection{NodeInvalid: auth.nodeInvalid, ErrorCode: auth.errorCode}, nil
|
||||
}
|
||||
return s.next.RequestQueries(authCtx, req)
|
||||
}
|
||||
|
||||
func (s authMiddleware) PublishLogs(ctx context.Context, req *pb.LogCollection) (resp *pb.AgentApiResponse, err error) {
|
||||
authCtx, auth, err := s.authenticateHost(ctx, req.NodeKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if auth.nodeInvalid {
|
||||
return &pb.AgentApiResponse{NodeInvalid: auth.nodeInvalid, ErrorCode: auth.errorCode}, nil
|
||||
}
|
||||
return s.next.PublishLogs(authCtx, req)
|
||||
}
|
||||
|
||||
func (s authMiddleware) PublishResults(ctx context.Context, req *pb.ResultCollection) (resp *pb.AgentApiResponse, err error) {
|
||||
authCtx, auth, err := s.authenticateHost(ctx, req.NodeKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if auth.nodeInvalid {
|
||||
return &pb.AgentApiResponse{NodeInvalid: auth.nodeInvalid, ErrorCode: auth.errorCode}, nil
|
||||
}
|
||||
return s.next.PublishResults(authCtx, req)
|
||||
}
|
||||
|
||||
func (s authMiddleware) CheckHealth(ctx context.Context, coll *pb.AgentApiRequest) (*pb.HealthCheckResponse, error) {
|
||||
// there should not be any auth
|
||||
return s.next.CheckHealth(ctx, coll)
|
||||
}
|
||||
|
||||
type auth struct {
|
||||
nodeInvalid bool
|
||||
errorCode string
|
||||
host *kolide.Host
|
||||
}
|
||||
|
||||
func (s authMiddleware) authenticateHost(ctx context.Context, nodeKey string) (context.Context, *auth, error) {
|
||||
host, err := s.svc.AuthenticateHost(newCtx(ctx), nodeKey)
|
||||
if err != nil {
|
||||
if errEnroll, ok := err.(enrollmentError); ok {
|
||||
return ctx, &auth{nodeInvalid: errEnroll.NodeInvalid(), errorCode: errEnroll.Error()}, nil
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
return withHost(ctx, *host), &auth{host: host}, nil
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
pb "github.com/kolide/agent-api"
|
||||
"github.com/kolide/fleet/server/contexts/host"
|
||||
"github.com/kolide/fleet/server/kolide"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var authTestHost = &kolide.Host{HostName: "jimmy"}
|
||||
var nullHost *kolide.Host
|
||||
|
||||
func TestAuthRequestConfig(t *testing.T) {
|
||||
mockSvc := new(mockOsqueryService)
|
||||
mockSvc.On(
|
||||
"AuthenticateHost",
|
||||
oldContext,
|
||||
"nodekey",
|
||||
).Return(
|
||||
authTestHost,
|
||||
nil,
|
||||
)
|
||||
mockSvc.On(
|
||||
"GetClientConfig",
|
||||
mock.MatchedBy(func(ctx context.Context) bool {
|
||||
if h, ok := host.FromContext(ctx); ok {
|
||||
return h.HostName == authTestHost.HostName
|
||||
}
|
||||
return false
|
||||
}),
|
||||
).Return(
|
||||
&kolide.OsqueryConfig{},
|
||||
nil,
|
||||
)
|
||||
svr := newAuthMiddleware(mockSvc)(&agentBinding{mockSvc})
|
||||
resp, err := svr.RequestConfig(oldContext, &pb.AgentApiRequest{NodeKey: "nodekey"})
|
||||
mockSvc.AssertExpectations(t)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, resp)
|
||||
}
|
||||
|
||||
func TestAuthFailRequestConfig(t *testing.T) {
|
||||
cxtMatcher := mock.MatchedBy(func(ctx context.Context) bool {
|
||||
if h, ok := hostFromContext(ctx); ok {
|
||||
return h.HostName == authTestHost.HostName
|
||||
}
|
||||
return false
|
||||
})
|
||||
mockSvc := new(mockOsqueryService)
|
||||
mockSvc.On(
|
||||
"AuthenticateHost",
|
||||
oldContext,
|
||||
"nodekey",
|
||||
).Return(
|
||||
nullHost,
|
||||
&mockEnrollError{},
|
||||
)
|
||||
mockSvc.On(
|
||||
"GetClientConfig",
|
||||
cxtMatcher,
|
||||
).Return(
|
||||
&kolide.OsqueryConfig{},
|
||||
nil,
|
||||
)
|
||||
svr := newAuthMiddleware(mockSvc)(&agentBinding{mockSvc})
|
||||
resp, err := svr.RequestConfig(oldContext, &pb.AgentApiRequest{NodeKey: "nodekey"})
|
||||
mockSvc.AssertNotCalled(t, "GetClientConfig", newContext)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.True(t, resp.NodeInvalid)
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
pb "github.com/kolide/agent-api"
|
||||
"github.com/kolide/fleet/server/kolide"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var errNotImplmented = errors.New("not implemented")
|
||||
|
||||
// agentBinding implements ApiClient interface and maps gRPC domain functions to the application.
|
||||
type agentBinding struct {
|
||||
service kolide.OsqueryService
|
||||
}
|
||||
|
||||
func newAgentBinding(svc kolide.OsqueryService) pb.ApiServer {
|
||||
return &agentBinding{
|
||||
service: svc,
|
||||
}
|
||||
}
|
||||
|
||||
type enrollmentError interface {
|
||||
NodeInvalid() bool
|
||||
Error() string
|
||||
}
|
||||
|
||||
// Attempt to enroll a host with kolide/cloud
|
||||
func (b *agentBinding) RequestEnrollment(ctx context.Context, req *pb.EnrollmentRequest) (*pb.EnrollmentResponse, error) {
|
||||
var resp pb.EnrollmentResponse
|
||||
nodeKey, err := b.service.EnrollAgent(newCtx(ctx), req.EnrollSecret, req.HostIdentifier)
|
||||
if err != nil {
|
||||
if errEnroll, ok := err.(enrollmentError); ok {
|
||||
resp.NodeInvalid = errEnroll.NodeInvalid()
|
||||
resp.ErrorCode = errEnroll.Error()
|
||||
return &resp, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
resp.NodeKey = nodeKey
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// RequestConfig requests an updated configuration
|
||||
func (b *agentBinding) RequestConfig(ctx context.Context, req *pb.AgentApiRequest) (*pb.ConfigResponse, error) {
|
||||
config, err := b.service.GetClientConfig(newCtx(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Launcher manages plugins so remove them from configuration if they exist.
|
||||
for _, optionName := range []string{"distributed_plugin", "logger_plugin"} {
|
||||
if _, ok := config.Options[optionName]; ok {
|
||||
delete(config.Options, optionName)
|
||||
}
|
||||
}
|
||||
var writer bytes.Buffer
|
||||
if err = json.NewEncoder(&writer).Encode(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.ConfigResponse{ConfigJsonBlob: writer.String()}, nil
|
||||
}
|
||||
|
||||
// RequestQueries request/pull distributed queries
|
||||
func (b *agentBinding) RequestQueries(ctx context.Context, _ *pb.AgentApiRequest) (*pb.QueryCollection, error) {
|
||||
queryMap, _, err := b.service.GetDistributedQueries(newCtx(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var result pb.QueryCollection
|
||||
for id, query := range queryMap {
|
||||
result.Queries = append(result.Queries, &pb.QueryCollection_Query{Id: id, Query: query})
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// StatusLog handles osquery logging messages
|
||||
type StatusLog struct {
|
||||
Severity string `json:"s"`
|
||||
Filename string `json:"f"`
|
||||
Line string `json:"i"`
|
||||
Message string `json:"m"`
|
||||
}
|
||||
|
||||
// convert the json from grpc client to an object suitable
|
||||
// for consumption by fleet
|
||||
func toKolideLog(jsn string) (*kolide.OsqueryStatusLog, error) {
|
||||
var status StatusLog
|
||||
err := json.NewDecoder(bytes.NewBufferString(jsn)).Decode(&status)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &kolide.OsqueryStatusLog{
|
||||
Severity: status.Severity,
|
||||
Filename: status.Filename,
|
||||
Line: status.Line,
|
||||
Message: status.Message,
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// PublishLogs publish logs from osqueryd
|
||||
func (b *agentBinding) PublishLogs(ctx context.Context, coll *pb.LogCollection) (*pb.AgentApiResponse, error) {
|
||||
handler := func(_ context.Context, _ *pb.LogCollection) error { return nil }
|
||||
switch coll.LogType {
|
||||
case pb.LogCollection_RESULT:
|
||||
handler = b.handleResultLogs
|
||||
case pb.LogCollection_STATUS:
|
||||
handler = b.handleStatusLogs
|
||||
}
|
||||
if err := handler(ctx, coll); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.AgentApiResponse{}, nil
|
||||
}
|
||||
|
||||
func (b *agentBinding) handleResultLogs(ctx context.Context, coll *pb.LogCollection) error {
|
||||
var results []kolide.OsqueryResultLog
|
||||
for _, log := range coll.Logs {
|
||||
var result kolide.OsqueryResultLog
|
||||
if err := json.Unmarshal([]byte(log.Data), &result); err != nil {
|
||||
return errors.Wrap(err, "unmarshaling result log")
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
if err := b.service.SubmitResultLogs(newCtx(ctx), results); err != nil {
|
||||
return errors.Wrap(err, "submitting status logs")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *agentBinding) handleStatusLogs(ctx context.Context, coll *pb.LogCollection) error {
|
||||
var statuses []kolide.OsqueryStatusLog
|
||||
for _, record := range coll.Logs {
|
||||
status, err := toKolideLog(record.Data)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "decoding status log")
|
||||
}
|
||||
statuses = append(statuses, *status)
|
||||
}
|
||||
if err := b.service.SubmitStatusLogs(newCtx(ctx), statuses); err != nil {
|
||||
return errors.Wrap(err, "submitting status logs")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PublishResults publish distributed query results
|
||||
func (b *agentBinding) PublishResults(ctx context.Context, coll *pb.ResultCollection) (*pb.AgentApiResponse, error) {
|
||||
results := kolide.OsqueryDistributedQueryResults{}
|
||||
statuses := map[string]string{}
|
||||
for _, result := range coll.Results {
|
||||
statuses[result.Id] = strconv.Itoa(int(result.Status))
|
||||
rows := []map[string]string{}
|
||||
for _, row := range result.Rows {
|
||||
cols := map[string]string{}
|
||||
for _, colVal := range row.Columns {
|
||||
cols[colVal.Name] = colVal.Value
|
||||
}
|
||||
if len(cols) == 0 {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, cols)
|
||||
}
|
||||
results[result.Id] = rows
|
||||
}
|
||||
if err := b.service.SubmitDistributedQueryResults(newCtx(ctx), results, statuses); err != nil {
|
||||
return nil, errors.Wrap(err, "submitting distributed query results")
|
||||
}
|
||||
return &pb.AgentApiResponse{}, nil
|
||||
}
|
||||
|
||||
func (svc *agentBinding) CheckHealth(ctx context.Context, coll *pb.AgentApiRequest) (*pb.HealthCheckResponse, error) {
|
||||
return nil, errNotImplmented
|
||||
}
|
||||
@@ -1,391 +0,0 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
newctx "context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
pb "github.com/kolide/agent-api"
|
||||
"github.com/kolide/fleet/server/kolide"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type mockEnrollError struct{}
|
||||
|
||||
func (ee *mockEnrollError) NodeInvalid() bool { return true }
|
||||
func (ee *mockEnrollError) Error() string { return "enroll failed" }
|
||||
|
||||
type mockOsqueryService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *mockOsqueryService) EnrollAgent(ctx newctx.Context, enrollSecret, hostIdentifier string) (string, error) {
|
||||
args := m.Called(ctx, enrollSecret, hostIdentifier)
|
||||
return args.String(0), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockOsqueryService) AuthenticateHost(ctx newctx.Context, nodeKey string) (*kolide.Host, error) {
|
||||
args := m.Called(ctx, nodeKey)
|
||||
return args.Get(0).(*kolide.Host), args.Error(1)
|
||||
}
|
||||
func (m *mockOsqueryService) GetClientConfig(ctx newctx.Context) (*kolide.OsqueryConfig, error) {
|
||||
args := m.Called(ctx)
|
||||
return args.Get(0).(*kolide.OsqueryConfig), args.Error(1)
|
||||
|
||||
}
|
||||
func (m *mockOsqueryService) GetDistributedQueries(ctx newctx.Context) (map[string]string, uint, error) {
|
||||
args := m.Called(ctx)
|
||||
return args.Get(0).(map[string]string), args.Get(1).(uint), args.Error(2)
|
||||
}
|
||||
func (m *mockOsqueryService) SubmitDistributedQueryResults(ctx newctx.Context, results kolide.OsqueryDistributedQueryResults, statuses map[string]string) error {
|
||||
args := m.Called(ctx, results, statuses)
|
||||
return args.Error(0)
|
||||
}
|
||||
func (m *mockOsqueryService) SubmitStatusLogs(ctx newctx.Context, logs []kolide.OsqueryStatusLog) error {
|
||||
args := m.Called(ctx, logs)
|
||||
return args.Error(0)
|
||||
}
|
||||
func (m *mockOsqueryService) SubmitResultLogs(ctx newctx.Context, logs []kolide.OsqueryResultLog) error {
|
||||
args := m.Called(ctx, logs)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
var oldContext = context.Background()
|
||||
var newContext = newctx.Background()
|
||||
var errMockEnrollError = &mockEnrollError{}
|
||||
var errTestError = errors.New("test error")
|
||||
|
||||
func TestRequestEnrollementHappyPath(t *testing.T) {
|
||||
request := &pb.EnrollmentRequest{
|
||||
EnrollSecret: "supersecret",
|
||||
HostIdentifier: "somehost",
|
||||
}
|
||||
|
||||
mockSvc := new(mockOsqueryService)
|
||||
mockSvc.On(
|
||||
"EnrollAgent",
|
||||
newctx.Background(),
|
||||
request.EnrollSecret,
|
||||
request.HostIdentifier,
|
||||
).Return(
|
||||
"nodekey",
|
||||
nil,
|
||||
)
|
||||
agent := agentBinding{
|
||||
service: mockSvc,
|
||||
}
|
||||
|
||||
resp, err := agent.RequestEnrollment(oldContext, request)
|
||||
mockSvc.AssertExpectations(t)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "nodekey", resp.NodeKey)
|
||||
assert.False(t, resp.NodeInvalid)
|
||||
}
|
||||
|
||||
func TestRequestEnrollmentFailed(t *testing.T) {
|
||||
request := &pb.EnrollmentRequest{
|
||||
EnrollSecret: "supersecret",
|
||||
HostIdentifier: "somehost",
|
||||
}
|
||||
|
||||
mockSvc := new(mockOsqueryService)
|
||||
mockSvc.On(
|
||||
"EnrollAgent",
|
||||
newctx.Background(),
|
||||
request.EnrollSecret,
|
||||
request.HostIdentifier,
|
||||
).Return(
|
||||
"",
|
||||
errMockEnrollError,
|
||||
)
|
||||
agent := agentBinding{
|
||||
service: mockSvc,
|
||||
}
|
||||
|
||||
resp, err := agent.RequestEnrollment(oldContext, request)
|
||||
mockSvc.AssertExpectations(t)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, resp.NodeInvalid)
|
||||
}
|
||||
|
||||
func TestRequestEnrollmentError(t *testing.T) {
|
||||
request := &pb.EnrollmentRequest{
|
||||
EnrollSecret: "supersecret",
|
||||
HostIdentifier: "somehost",
|
||||
}
|
||||
|
||||
mockSvc := new(mockOsqueryService)
|
||||
mockSvc.On(
|
||||
"EnrollAgent",
|
||||
newctx.Background(),
|
||||
request.EnrollSecret,
|
||||
request.HostIdentifier,
|
||||
).Return(
|
||||
"",
|
||||
errTestError,
|
||||
)
|
||||
agent := agentBinding{
|
||||
service: mockSvc,
|
||||
}
|
||||
|
||||
_, err := agent.RequestEnrollment(oldContext, request)
|
||||
mockSvc.AssertExpectations(t)
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, errTestError, err)
|
||||
}
|
||||
|
||||
func TestRequestConfigHappyPath(t *testing.T) {
|
||||
request := &pb.AgentApiRequest{
|
||||
NodeKey: "nodekey",
|
||||
}
|
||||
|
||||
mockSvc := new(mockOsqueryService)
|
||||
mockSvc.On(
|
||||
"GetClientConfig",
|
||||
newctx.Background(),
|
||||
).Return(
|
||||
&kolide.OsqueryConfig{
|
||||
Options: map[string]interface{}{
|
||||
"option1": "optionval",
|
||||
"distributed_plugin": "tls",
|
||||
},
|
||||
Decorators: kolide.Decorators{
|
||||
Load: []string{
|
||||
"SELECT * FROM users u JOIN groups g WHERE u.gid = g.gid",
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
agent := agentBinding{
|
||||
service: mockSvc,
|
||||
}
|
||||
|
||||
resp, err := agent.RequestConfig(oldContext, request)
|
||||
mockSvc.AssertExpectations(t)
|
||||
assert.Nil(t, err)
|
||||
// verify distributed_plugin was removed
|
||||
expectedJSON := "{\"options\":{\"option1\":\"optionval\"},\"decorators\":{\"load\":[\"SELECT * FROM users u JOIN groups g WHERE u.gid = g.gid\"]}}\n"
|
||||
require.NotNil(t, resp)
|
||||
assert.Equal(t, expectedJSON, resp.ConfigJsonBlob)
|
||||
assert.False(t, resp.NodeInvalid)
|
||||
}
|
||||
|
||||
func TestRequestConfigError(t *testing.T) {
|
||||
request := &pb.AgentApiRequest{
|
||||
NodeKey: "nodekey",
|
||||
}
|
||||
var nilConfig *kolide.OsqueryConfig
|
||||
|
||||
mockSvc := new(mockOsqueryService)
|
||||
mockSvc.On(
|
||||
"GetClientConfig",
|
||||
newctx.Background(),
|
||||
).Return(
|
||||
nilConfig,
|
||||
errTestError,
|
||||
)
|
||||
agent := agentBinding{
|
||||
service: mockSvc,
|
||||
}
|
||||
|
||||
resp, err := agent.RequestConfig(oldContext, request)
|
||||
mockSvc.AssertExpectations(t)
|
||||
require.NotNil(t, err)
|
||||
assert.Equal(t, errTestError, err)
|
||||
assert.Nil(t, resp)
|
||||
}
|
||||
|
||||
func TestRequestQueriesHappyPath(t *testing.T) {
|
||||
mockSvc := new(mockOsqueryService)
|
||||
mockSvc.On(
|
||||
"GetDistributedQueries",
|
||||
newctx.Background(),
|
||||
).Return(
|
||||
map[string]string{
|
||||
"query1": "select * from foo;",
|
||||
},
|
||||
uint(0),
|
||||
nil,
|
||||
)
|
||||
agent := agentBinding{mockSvc}
|
||||
qc, err := agent.RequestQueries(oldContext, nil)
|
||||
mockSvc.AssertExpectations(t)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, qc)
|
||||
assert.Len(t, qc.Queries, 1)
|
||||
}
|
||||
|
||||
func TestToKolideLog(t *testing.T) {
|
||||
jsn := "{\"s\":\"0\",\"f\":\"scheduler.cpp\",\"i\":\"73\",\"m\":\"Executing scheduled query pack\\/xxx\\/services: select name, port, protocol from etc_services;\",\"h\":\"DE56C776-2F5A-56DF-81C7-F64EE1BBEC8C\",\"c\":\"Fri Aug 11 22:32:27 2017 UTC\",\"u\":\"1502490747\"}"
|
||||
sl, err := toKolideLog(jsn)
|
||||
require.Nil(t, err, "unexpected error")
|
||||
assert.Equal(t, "0", sl.Severity, "severity mismatch")
|
||||
assert.Equal(t, "scheduler.cpp", sl.Filename, "file name mismatch")
|
||||
assert.Equal(t, "73", sl.Line, "line number mismatch")
|
||||
malformedJSON := "{\"s\":\"0,\"f\":\"scheduler.cpp\",\"i\":\"73\",\"m\":\"Executing scheduled query pack\\/xxx\\/services: select name, port, protocol from etc_services;\",\"h\":\"DE56C776-2F5A-56DF-81C7-F64EE1BBEC8C\",\"c\":\"Fri Aug 11 22:32:27 2017 UTC\",\"u\":\"1502490747\"}"
|
||||
sl, err = toKolideLog(malformedJSON)
|
||||
assert.NotNil(t, err, "malformed json should have erred")
|
||||
assert.Nil(t, sl, "result should be nil on err")
|
||||
}
|
||||
|
||||
func TestPublishStatusLogs(t *testing.T) {
|
||||
statusJSON := "{\"s\":\"0\",\"f\":\"scheduler.cpp\",\"i\":\"73\",\"m\":\"Executing scheduled query pack\\/xxx\\/services: select name, port, protocol from etc_services;\",\"h\":\"DE56C776-2F5A-56DF-81C7-F64EE1BBEC8C\",\"c\":\"Fri Aug 11 22:32:27 2017 UTC\",\"u\":\"1502490747\"}"
|
||||
statusLogCol := &pb.LogCollection{
|
||||
LogType: pb.LogCollection_STATUS,
|
||||
Logs: []*pb.LogCollection_Log{
|
||||
&pb.LogCollection_Log{
|
||||
Data: statusJSON,
|
||||
},
|
||||
},
|
||||
}
|
||||
statuses := []kolide.OsqueryStatusLog{
|
||||
kolide.OsqueryStatusLog{
|
||||
Severity: "0",
|
||||
Filename: "scheduler.cpp",
|
||||
Line: "73",
|
||||
Message: `Executing scheduled query pack/xxx/services: select name, port, protocol from etc_services;`,
|
||||
},
|
||||
}
|
||||
mockSvc := new(mockOsqueryService)
|
||||
mockSvc.On(
|
||||
"SubmitStatusLogs",
|
||||
newContext,
|
||||
statuses,
|
||||
).Return(
|
||||
nil,
|
||||
)
|
||||
agent := agentBinding{mockSvc}
|
||||
resp, err := agent.PublishLogs(oldContext, statusLogCol)
|
||||
mockSvc.AssertExpectations(t)
|
||||
mockSvc.AssertCalled(t, "SubmitStatusLogs", newContext, statuses)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.False(t, resp.NodeInvalid)
|
||||
}
|
||||
|
||||
func TestPublishStatusLogsUnhandledLogType(t *testing.T) {
|
||||
statusJSON := "{\"s\":\"0\",\"f\":\"scheduler.cpp\",\"i\":\"73\",\"m\":\"Executing scheduled query pack\\/xxx\\/services: select name, port, protocol from etc_services;\",\"h\":\"DE56C776-2F5A-56DF-81C7-F64EE1BBEC8C\",\"c\":\"Fri Aug 11 22:32:27 2017 UTC\",\"u\":\"1502490747\"}"
|
||||
statusLogCol := &pb.LogCollection{
|
||||
LogType: pb.LogCollection_AGENT,
|
||||
Logs: []*pb.LogCollection_Log{
|
||||
&pb.LogCollection_Log{
|
||||
Data: statusJSON,
|
||||
},
|
||||
},
|
||||
}
|
||||
statuses := []kolide.OsqueryStatusLog{
|
||||
kolide.OsqueryStatusLog{
|
||||
Severity: "0",
|
||||
Filename: "scheduler.cpp",
|
||||
Line: "73",
|
||||
Message: `Executing scheduled query pack/xxx/services: select name, port, protocol from etc_services;`,
|
||||
},
|
||||
}
|
||||
mockSvc := new(mockOsqueryService)
|
||||
mockSvc.On(
|
||||
"SubmitStatusLogs",
|
||||
newContext,
|
||||
statuses,
|
||||
).Return(
|
||||
nil,
|
||||
)
|
||||
agent := agentBinding{mockSvc}
|
||||
resp, err := agent.PublishLogs(oldContext, statusLogCol)
|
||||
mockSvc.AssertNotCalled(t, "SubmitStatusLogs", newContext, statuses)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.False(t, resp.NodeInvalid)
|
||||
}
|
||||
|
||||
func TestPublishResultLogs(t *testing.T) {
|
||||
resultJSON := "{\"name\":\"pack\\/xxx\\/services\",\"hostIdentifier\":\"DE56C776-2F5A-56DF-81C7-F64EE1BBEC8C\",\"calendarTime\":\"Fri Aug 11 22:16:45 2017 UTC\",\"unixTime\":\"1502489805\",\"decorations\":{\"host_uuid\":\"DE56C776-2F5A-56DF-81C7-F64EE1BBEC8C\",\"hostname\":\"Johns-MacBook-Pro.local\"},\"columns\":{\"name\":\"ms-dotnetster\",\"port\":\"3126\",\"protocol\":\"udp\"},\"action\":\"added\"}"
|
||||
resultLogColl := &pb.LogCollection{
|
||||
LogType: pb.LogCollection_RESULT,
|
||||
Logs: []*pb.LogCollection_Log{
|
||||
&pb.LogCollection_Log{
|
||||
Data: resultJSON,
|
||||
},
|
||||
},
|
||||
}
|
||||
results := []kolide.OsqueryResultLog{
|
||||
kolide.OsqueryResultLog{
|
||||
Name: "pack/xxx/services",
|
||||
HostIdentifier: "DE56C776-2F5A-56DF-81C7-F64EE1BBEC8C",
|
||||
UnixTime: "1502489805",
|
||||
CalendarTime: "Fri Aug 11 22:16:45 2017 UTC",
|
||||
Columns: map[string]string{
|
||||
"name": "ms-dotnetster",
|
||||
"port": "3126",
|
||||
"protocol": "udp",
|
||||
},
|
||||
Action: "added",
|
||||
Decorations: map[string]string{
|
||||
"host_uuid": "DE56C776-2F5A-56DF-81C7-F64EE1BBEC8C",
|
||||
"hostname": "Johns-MacBook-Pro.local",
|
||||
},
|
||||
},
|
||||
}
|
||||
mockSvc := new(mockOsqueryService)
|
||||
mockSvc.On(
|
||||
"SubmitResultLogs",
|
||||
newContext,
|
||||
results,
|
||||
).Return(
|
||||
nil,
|
||||
)
|
||||
agent := agentBinding{mockSvc}
|
||||
resp, err := agent.PublishLogs(oldContext, resultLogColl)
|
||||
mockSvc.AssertExpectations(t)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, resp)
|
||||
}
|
||||
|
||||
func TestPublishResults(t *testing.T) {
|
||||
coll := &pb.ResultCollection{
|
||||
NodeKey: "somekey",
|
||||
Results: []*pb.ResultCollection_Result{
|
||||
&pb.ResultCollection_Result{
|
||||
Id: "myquery",
|
||||
Status: 0,
|
||||
Rows: []*pb.ResultCollection_Result_ResultRow{
|
||||
&pb.ResultCollection_Result_ResultRow{
|
||||
Columns: []*pb.ResultCollection_Result_ResultRow_Column{
|
||||
&pb.ResultCollection_Result_ResultRow_Column{
|
||||
Name: "aColumn",
|
||||
Value: "aValue",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
results := kolide.OsqueryDistributedQueryResults{
|
||||
"myquery": []map[string]string{
|
||||
map[string]string{
|
||||
"aColumn": "aValue",
|
||||
},
|
||||
},
|
||||
}
|
||||
statuses := map[string]string{
|
||||
"myquery": "0",
|
||||
}
|
||||
mockSvc := new(mockOsqueryService)
|
||||
mockSvc.On(
|
||||
"SubmitDistributedQueryResults",
|
||||
newContext,
|
||||
results,
|
||||
statuses,
|
||||
).Return(
|
||||
nil,
|
||||
)
|
||||
agent := agentBinding{mockSvc}
|
||||
resp, err := agent.PublishResults(oldContext, coll)
|
||||
mockSvc.AssertExpectations(t)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, resp)
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
newcontext "context"
|
||||
|
||||
"github.com/kolide/fleet/server/contexts/host"
|
||||
"github.com/kolide/fleet/server/kolide"
|
||||
old "golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type contextKey int
|
||||
|
||||
const hostKey contextKey = 0
|
||||
|
||||
// newCtx is used to map the old golang.com/net/context which we are forced to use
|
||||
// because our generated gRPC code uses it, to the new stdlib context, which is used
|
||||
// by the Fleet application.
|
||||
func newCtx(ctx old.Context) newcontext.Context {
|
||||
if h, ok := ctx.Value(hostKey).(kolide.Host); ok {
|
||||
return host.NewContext(newcontext.Background(), h)
|
||||
}
|
||||
return newcontext.Background()
|
||||
}
|
||||
|
||||
// withHost creates a golang.org/x/net/context containing a host
|
||||
func withHost(ctx old.Context, h kolide.Host) old.Context {
|
||||
return old.WithValue(ctx, hostKey, h)
|
||||
}
|
||||
|
||||
func hostFromContext(ctx old.Context) (kolide.Host, bool) {
|
||||
h, ok := ctx.Value(hostKey).(kolide.Host)
|
||||
return h, ok
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
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")
|
||||
}
|
||||
|
||||
// Launcher manages plugins so remove them from configuration if they exist.
|
||||
for _, optionName := range []string{"distributed_plugin", "logger_plugin"} {
|
||||
if _, ok := config.Options[optionName]; ok {
|
||||
delete(config.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 []kolide.OsqueryResultLog
|
||||
for _, log := range logs {
|
||||
var result kolide.OsqueryResultLog
|
||||
if err := json.Unmarshal([]byte(log), &result); err != nil {
|
||||
return "", "", false, errors.Wrap(err, "unmarshaling result log")
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/kolide/fleet/server/health"
|
||||
"github.com/kolide/fleet/server/kolide"
|
||||
"github.com/kolide/fleet/server/mock"
|
||||
"github.com/kolide/osquery-go/plugin/distributed"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLauncherEnrollment(t *testing.T) {
|
||||
launcher, tls := newTestService(t)
|
||||
ctx := context.Background()
|
||||
|
||||
nodeKey, invalid, err := launcher.RequestEnrollment(ctx, "secret", "identifier")
|
||||
require.Nil(t, err)
|
||||
assert.True(t, tls.EnrollAgentFuncInvoked)
|
||||
assert.False(t, invalid)
|
||||
assert.Equal(t, "noop", nodeKey)
|
||||
|
||||
}
|
||||
|
||||
func TestLauncherRequestConfig(t *testing.T) {
|
||||
launcher, tls := newTestService(t)
|
||||
ctx := context.Background()
|
||||
|
||||
config, invalid, err := launcher.RequestConfig(ctx, "noop")
|
||||
require.Nil(t, err)
|
||||
assert.True(t, tls.AuthenticateHostFuncInvoked)
|
||||
assert.False(t, invalid)
|
||||
assert.Equal(t, `{"options":{"key":"value"},"decorators":{}}`, config)
|
||||
}
|
||||
|
||||
func TestLauncherRequestQueries(t *testing.T) {
|
||||
launcher, tls := newTestService(t)
|
||||
ctx := context.Background()
|
||||
|
||||
result, invalid, err := launcher.RequestQueries(ctx, "noop")
|
||||
require.Nil(t, err)
|
||||
assert.True(t, tls.AuthenticateHostFuncInvoked)
|
||||
assert.False(t, invalid)
|
||||
assert.Equal(t, map[string]string{"noop": `{"key": "value"}`}, result.Queries)
|
||||
}
|
||||
|
||||
func TestLauncherPublishResults(t *testing.T) {
|
||||
launcher, tls := newTestService(t)
|
||||
ctx := context.Background()
|
||||
|
||||
_, _, invalid, err := launcher.PublishResults(
|
||||
ctx,
|
||||
"noop",
|
||||
[]distributed.Result{},
|
||||
)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, tls.AuthenticateHostFuncInvoked)
|
||||
assert.False(t, invalid)
|
||||
|
||||
// test with result
|
||||
var result = map[string]string{"key": "value"}
|
||||
tls.SubmitDistributedQueryResultsFunc = func(
|
||||
ctx context.Context,
|
||||
results kolide.OsqueryDistributedQueryResults,
|
||||
statuses map[string]string) (err error) {
|
||||
assert.Equal(t, results["query"][0], result)
|
||||
return nil
|
||||
}
|
||||
|
||||
_, _, invalid, err = launcher.PublishResults(
|
||||
ctx,
|
||||
"noop",
|
||||
[]distributed.Result{
|
||||
{
|
||||
QueryName: "query",
|
||||
Status: 1,
|
||||
Rows: []map[string]string{result},
|
||||
},
|
||||
},
|
||||
)
|
||||
require.Nil(t, err)
|
||||
assert.False(t, invalid)
|
||||
}
|
||||
|
||||
func newTestService(t *testing.T) (*launcherWrapper, *mock.TLSService) {
|
||||
tls := newTLSService(t)
|
||||
launcher := &launcherWrapper{
|
||||
tls: tls,
|
||||
logger: log.NewNopLogger(),
|
||||
healthCheckers: map[string]health.Checker{
|
||||
"noop": health.Nop(),
|
||||
},
|
||||
}
|
||||
return launcher, tls
|
||||
}
|
||||
|
||||
// NewTLS service returns a mock TLS service where all the methods have a noop implementation.
|
||||
// To test additional behaviors, override the funcs on the TLSService struct.
|
||||
func newTLSService(t *testing.T) *mock.TLSService {
|
||||
return &mock.TLSService{
|
||||
EnrollAgentFunc: func(
|
||||
ctx context.Context,
|
||||
enrollSecret string,
|
||||
hostIdentifier string,
|
||||
) (nodeKey string, err error) {
|
||||
nodeKey = "noop"
|
||||
return
|
||||
|
||||
},
|
||||
|
||||
AuthenticateHostFunc: func(
|
||||
ctx context.Context,
|
||||
nodeKey string,
|
||||
) (host *kolide.Host, err error) {
|
||||
return &kolide.Host{
|
||||
NodeKey: nodeKey,
|
||||
}, nil
|
||||
},
|
||||
GetClientConfigFunc: func(
|
||||
ctx context.Context,
|
||||
) (config *kolide.OsqueryConfig, err error) {
|
||||
return &kolide.OsqueryConfig{
|
||||
Options: map[string]interface{}{
|
||||
"key": "value",
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
|
||||
GetDistributedQueriesFunc: func(
|
||||
ctx context.Context,
|
||||
) (queries map[string]string, accelerate uint, err error) {
|
||||
queries = map[string]string{
|
||||
"noop": `{"key": "value"}`,
|
||||
}
|
||||
return
|
||||
},
|
||||
SubmitDistributedQueryResultsFunc: func(
|
||||
ctx context.Context,
|
||||
results kolide.OsqueryDistributedQueryResults,
|
||||
statuses map[string]string,
|
||||
) (err error) {
|
||||
return
|
||||
},
|
||||
|
||||
SubmitStatusLogsFunc: func(ctx context.Context, logs []kolide.OsqueryStatusLog) (err error) {
|
||||
return
|
||||
},
|
||||
SubmitResultLogsFunc: func(ctx context.Context, logs []kolide.OsqueryResultLog) (err error) {
|
||||
return
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package launcher
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
pb "github.com/kolide/agent-api"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type loggingMiddleware struct {
|
||||
logger kitlog.Logger
|
||||
next pb.ApiServer
|
||||
}
|
||||
|
||||
func newLoggingMiddleware(logger kitlog.Logger) func(svc pb.ApiServer) pb.ApiServer {
|
||||
return func(next pb.ApiServer) pb.ApiServer {
|
||||
return loggingMiddleware{
|
||||
logger: kitlog.With(logger, "component", "gRPC Launcher"),
|
||||
next: next,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s loggingMiddleware) RequestEnrollment(ctx context.Context, req *pb.EnrollmentRequest) (resp *pb.EnrollmentResponse, err error) {
|
||||
defer func(begin time.Time) {
|
||||
s.logger.Log(
|
||||
"method", "RequestEnrollment",
|
||||
"err", err,
|
||||
"took", time.Since(begin),
|
||||
)
|
||||
}(time.Now())
|
||||
return s.next.RequestEnrollment(ctx, req)
|
||||
}
|
||||
|
||||
func (s loggingMiddleware) RequestConfig(ctx context.Context, req *pb.AgentApiRequest) (resp *pb.ConfigResponse, err error) {
|
||||
defer func(begin time.Time) {
|
||||
s.logger.Log(
|
||||
"method", "RequestConfig",
|
||||
"err", err,
|
||||
"took", time.Since(begin),
|
||||
)
|
||||
}(time.Now())
|
||||
return s.next.RequestConfig(ctx, req)
|
||||
}
|
||||
|
||||
func (s loggingMiddleware) RequestQueries(ctx context.Context, req *pb.AgentApiRequest) (resp *pb.QueryCollection, err error) {
|
||||
defer func(begin time.Time) {
|
||||
s.logger.Log(
|
||||
"method", "RequestQueries",
|
||||
"err", err,
|
||||
"took", time.Since(begin),
|
||||
)
|
||||
}(time.Now())
|
||||
return s.next.RequestQueries(ctx, req)
|
||||
}
|
||||
|
||||
func (s loggingMiddleware) PublishLogs(ctx context.Context, req *pb.LogCollection) (resp *pb.AgentApiResponse, err error) {
|
||||
defer func(begin time.Time) {
|
||||
s.logger.Log(
|
||||
"method", "PublishLogs",
|
||||
"err", err,
|
||||
"took", time.Since(begin),
|
||||
)
|
||||
}(time.Now())
|
||||
return s.next.PublishLogs(ctx, req)
|
||||
}
|
||||
|
||||
func (s loggingMiddleware) PublishResults(ctx context.Context, req *pb.ResultCollection) (resp *pb.AgentApiResponse, err error) {
|
||||
defer func(begin time.Time) {
|
||||
s.logger.Log(
|
||||
"method", "PublishResults",
|
||||
"err", err,
|
||||
"took", time.Since(begin),
|
||||
)
|
||||
}(time.Now())
|
||||
return s.next.PublishResults(ctx, req)
|
||||
}
|
||||
|
||||
func (s loggingMiddleware) CheckHealth(ctx context.Context, coll *pb.AgentApiRequest) (resp *pb.HealthCheckResponse, err error) {
|
||||
defer func(begin time.Time) {
|
||||
s.logger.Log(
|
||||
"method", "CheckHealth",
|
||||
"err", err,
|
||||
"took", time.Since(begin),
|
||||
)
|
||||
}(time.Now())
|
||||
return s.next.CheckHealth(ctx, coll)
|
||||
}
|
||||
+27
-14
@@ -5,10 +5,12 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
kitlog "github.com/go-kit/kit/log"
|
||||
pb "github.com/kolide/agent-api"
|
||||
"github.com/kolide/fleet/server/kolide"
|
||||
"github.com/go-kit/kit/log"
|
||||
launcher "github.com/kolide/launcher/service"
|
||||
grpc "google.golang.org/grpc"
|
||||
|
||||
"github.com/kolide/fleet/server/health"
|
||||
"github.com/kolide/fleet/server/kolide"
|
||||
)
|
||||
|
||||
// Handler extends the grpc.Server, providing Handler that allows us to serve
|
||||
@@ -17,25 +19,36 @@ type Handler struct {
|
||||
*grpc.Server
|
||||
}
|
||||
|
||||
// New creates a gRPC server to handler remote requests from launcher.
|
||||
func New(svc kolide.OsqueryService, logger kitlog.Logger, opts ...grpc.ServerOption) *Handler {
|
||||
binding := newAgentBinding(svc)
|
||||
binding = newAuthMiddleware(svc)(binding)
|
||||
binding = newLoggingMiddleware(logger)(binding)
|
||||
|
||||
server := grpc.NewServer(opts...)
|
||||
pb.RegisterApiServer(server, binding)
|
||||
return &Handler{server}
|
||||
// New creates a gRPC server to handle remote requests from launcher.
|
||||
func New(
|
||||
tls kolide.OsqueryService,
|
||||
logger log.Logger,
|
||||
grpcServer *grpc.Server,
|
||||
healthCheckers map[string]health.Checker,
|
||||
) *Handler {
|
||||
var svc launcher.KolideService
|
||||
{
|
||||
svc = &launcherWrapper{
|
||||
tls: tls,
|
||||
logger: logger,
|
||||
healthCheckers: healthCheckers,
|
||||
}
|
||||
svc = launcher.LoggingMiddleware(logger)(svc)
|
||||
}
|
||||
endpoints := launcher.MakeServerEndpoints(svc)
|
||||
server := launcher.NewGRPCServer(endpoints, logger)
|
||||
launcher.RegisterGRPCServer(grpcServer, server)
|
||||
return &Handler{grpcServer}
|
||||
}
|
||||
|
||||
// Handler will route gRPC traffic to the gRPC server, other http traffic
|
||||
// will be routed to normal http handler functions.
|
||||
func (hgprc *Handler) Handler(h http.Handler) http.Handler {
|
||||
func (hgprc *Handler) Handler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
|
||||
hgprc.ServeHTTP(w, r)
|
||||
} else {
|
||||
h.ServeHTTP(w, r)
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package mock
|
||||
|
||||
//go:generate mockimpl -o service_osquery.go "s *TLSService" "kolide.OsqueryService"
|
||||
@@ -0,0 +1,83 @@
|
||||
// Automatically generated by mockimpl. DO NOT EDIT!
|
||||
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/kolide/fleet/server/kolide"
|
||||
)
|
||||
|
||||
var _ kolide.OsqueryService = (*TLSService)(nil)
|
||||
|
||||
type EnrollAgentFunc func(ctx context.Context, enrollSecret string, hostIdentifier string) (nodeKey string, err error)
|
||||
|
||||
type AuthenticateHostFuncI func(ctx context.Context, nodeKey string) (host *kolide.Host, err error)
|
||||
|
||||
type GetClientConfigFunc func(ctx context.Context) (config *kolide.OsqueryConfig, err error)
|
||||
|
||||
type GetDistributedQueriesFunc func(ctx context.Context) (queries map[string]string, accelerate uint, err error)
|
||||
|
||||
type SubmitDistributedQueryResultsFunc func(ctx context.Context, results kolide.OsqueryDistributedQueryResults, statuses map[string]string) (err error)
|
||||
|
||||
type SubmitStatusLogsFunc func(ctx context.Context, logs []kolide.OsqueryStatusLog) (err error)
|
||||
|
||||
type SubmitResultLogsFunc func(ctx context.Context, logs []kolide.OsqueryResultLog) (err error)
|
||||
|
||||
type TLSService struct {
|
||||
EnrollAgentFunc EnrollAgentFunc
|
||||
EnrollAgentFuncInvoked bool
|
||||
|
||||
AuthenticateHostFunc AuthenticateHostFuncI
|
||||
AuthenticateHostFuncInvoked bool
|
||||
|
||||
GetClientConfigFunc GetClientConfigFunc
|
||||
GetClientConfigFuncInvoked bool
|
||||
|
||||
GetDistributedQueriesFunc GetDistributedQueriesFunc
|
||||
GetDistributedQueriesFuncInvoked bool
|
||||
|
||||
SubmitDistributedQueryResultsFunc SubmitDistributedQueryResultsFunc
|
||||
SubmitDistributedQueryResultsFuncInvoked bool
|
||||
|
||||
SubmitStatusLogsFunc SubmitStatusLogsFunc
|
||||
SubmitStatusLogsFuncInvoked bool
|
||||
|
||||
SubmitResultLogsFunc SubmitResultLogsFunc
|
||||
SubmitResultLogsFuncInvoked bool
|
||||
}
|
||||
|
||||
func (s *TLSService) EnrollAgent(ctx context.Context, enrollSecret string, hostIdentifier string) (nodeKey string, err error) {
|
||||
s.EnrollAgentFuncInvoked = true
|
||||
return s.EnrollAgentFunc(ctx, enrollSecret, hostIdentifier)
|
||||
}
|
||||
|
||||
func (s *TLSService) AuthenticateHost(ctx context.Context, nodeKey string) (host *kolide.Host, err error) {
|
||||
s.AuthenticateHostFuncInvoked = true
|
||||
return s.AuthenticateHostFunc(ctx, nodeKey)
|
||||
}
|
||||
|
||||
func (s *TLSService) GetClientConfig(ctx context.Context) (config *kolide.OsqueryConfig, err error) {
|
||||
s.GetClientConfigFuncInvoked = true
|
||||
return s.GetClientConfigFunc(ctx)
|
||||
}
|
||||
|
||||
func (s *TLSService) GetDistributedQueries(ctx context.Context) (queries map[string]string, accelerate uint, err error) {
|
||||
s.GetDistributedQueriesFuncInvoked = true
|
||||
return s.GetDistributedQueriesFunc(ctx)
|
||||
}
|
||||
|
||||
func (s *TLSService) SubmitDistributedQueryResults(ctx context.Context, results kolide.OsqueryDistributedQueryResults, statuses map[string]string) (err error) {
|
||||
s.SubmitDistributedQueryResultsFuncInvoked = true
|
||||
return s.SubmitDistributedQueryResultsFunc(ctx, results, statuses)
|
||||
}
|
||||
|
||||
func (s *TLSService) SubmitStatusLogs(ctx context.Context, logs []kolide.OsqueryStatusLog) (err error) {
|
||||
s.SubmitStatusLogsFuncInvoked = true
|
||||
return s.SubmitStatusLogsFunc(ctx, logs)
|
||||
}
|
||||
|
||||
func (s *TLSService) SubmitResultLogs(ctx context.Context, logs []kolide.OsqueryResultLog) (err error) {
|
||||
s.SubmitResultLogsFuncInvoked = true
|
||||
return s.SubmitResultLogsFunc(ctx, logs)
|
||||
}
|
||||
Reference in New Issue
Block a user