fleet/server/service/integration_logger_test.go
gillespi314 94dd1c3745
Ingest pending MDM hosts (#9065)
Co-authored-by @roperzh
2022-12-26 15:32:39 -06:00

293 lines
8.2 KiB
Go

package service
import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"testing"
"time"
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
func TestIntegrationLoggerTestSuite(t *testing.T) {
testingSuite := new(integrationLoggerTestSuite)
testingSuite.s = &testingSuite.Suite
suite.Run(t, testingSuite)
}
type integrationLoggerTestSuite struct {
withServer
suite.Suite
buf *bytes.Buffer
}
func (s *integrationLoggerTestSuite) SetupSuite() {
s.withDS.SetupSuite("integrationLoggerTestSuite")
s.buf = new(bytes.Buffer)
logger := log.NewJSONLogger(s.buf)
logger = level.NewFilter(logger, level.AllowDebug())
users, server := RunServerForTestsWithDS(s.T(), s.ds, &TestServerOpts{Logger: logger})
s.server = server
s.users = users
}
func (s *integrationLoggerTestSuite) TearDownTest() {
s.buf.Reset()
}
func (s *integrationLoggerTestSuite) TestLogger() {
t := s.T()
s.token = getTestAdminToken(t, s.server)
s.getConfig()
params := fleet.QueryPayload{
Name: ptr.String("somequery"),
Description: ptr.String("desc"),
Query: ptr.String("select 1 from osquery;"),
}
var createResp createQueryResponse
s.DoJSON("POST", "/api/latest/fleet/queries", params, http.StatusOK, &createResp)
logs := s.buf.String()
parts := strings.Split(strings.TrimSpace(logs), "\n")
assert.Len(t, parts, 3)
for i, part := range parts {
kv := make(map[string]string)
err := json.Unmarshal([]byte(part), &kv)
require.NoError(t, err)
assert.NotEqual(t, "", kv["took"])
switch i {
case 0:
assert.Equal(t, "info", kv["level"])
assert.Equal(t, "POST", kv["method"])
assert.Equal(t, "/api/latest/fleet/login", kv["uri"])
case 1:
assert.Equal(t, "debug", kv["level"])
assert.Equal(t, "GET", kv["method"])
assert.Equal(t, "/api/latest/fleet/config", kv["uri"])
assert.Equal(t, "admin1@example.com", kv["user"])
case 2:
assert.Equal(t, "debug", kv["level"])
assert.Equal(t, "POST", kv["method"])
assert.Equal(t, "/api/latest/fleet/queries", kv["uri"])
assert.Equal(t, "admin1@example.com", kv["user"])
assert.Equal(t, "somequery", kv["name"])
assert.Equal(t, "select 1 from osquery;", kv["sql"])
default:
t.Fail()
}
}
}
func (s *integrationLoggerTestSuite) TestLoggerLogin() {
t := s.T()
type logEntry struct {
key string
val string
}
testCases := []struct {
loginRequest loginRequest
expectedStatus int
expectedLogs []logEntry
}{
{
loginRequest: loginRequest{Email: testUsers["admin1"].Email, Password: testUsers["admin1"].PlaintextPassword},
expectedStatus: http.StatusOK,
expectedLogs: []logEntry{{"email", testUsers["admin1"].Email}},
},
{
loginRequest: loginRequest{Email: testUsers["admin1"].Email, Password: "n074v411dp455w02d"},
expectedStatus: http.StatusUnauthorized,
expectedLogs: []logEntry{
{"email", testUsers["admin1"].Email},
{"level", "error"},
{"internal", "invalid password"},
},
},
{
loginRequest: loginRequest{Email: "h4x0r@3x4mp13.c0m", Password: "n074v411dp455w02d"},
expectedStatus: http.StatusUnauthorized,
expectedLogs: []logEntry{
{"email", "h4x0r@3x4mp13.c0m"},
{"level", "error"},
{"internal", "user not found"},
},
},
}
var resp loginResponse
for _, tt := range testCases {
s.DoJSON("POST", "/api/latest/fleet/login", tt.loginRequest, tt.expectedStatus, &resp)
logString := s.buf.String()
parts := strings.Split(strings.TrimSpace(logString), "\n")
require.Len(t, parts, 1)
logData := make(map[string]string)
require.NoError(t, json.Unmarshal([]byte(parts[0]), &logData))
require.NotContains(t, logData, "user") // logger context is set to skip user
for _, e := range tt.expectedLogs {
assert.Equal(t, logData[e.key], e.val)
}
s.buf.Reset()
}
}
func (s *integrationLoggerTestSuite) TestOsqueryEndpointsLogErrors() {
t := s.T()
_, err := s.ds.NewHost(context.Background(), &fleet.Host{
DetailUpdatedAt: time.Now(),
LabelUpdatedAt: time.Now(),
PolicyUpdatedAt: time.Now(),
SeenTime: time.Now(),
NodeKey: ptr.String(t.Name() + "1234"),
UUID: "1",
Hostname: "foo.local",
OsqueryHostID: ptr.String(t.Name()),
PrimaryIP: "192.168.1.1",
PrimaryMac: "30-65-EC-6F-C4-58",
})
require.NoError(t, err)
requestBody := io.NopCloser(bytes.NewBuffer([]byte(`{"node_key":"1234","log_type":"status","data":[}`)))
req, _ := http.NewRequest("POST", s.server.URL+"/api/osquery/log", requestBody)
client := fleethttp.NewClient()
_, err = client.Do(req)
require.Nil(t, err)
logString := s.buf.String()
assert.Contains(t, logString, `invalid character '}' looking for beginning of value","level":"info","path":"/api/osquery/log"}
`, logString)
}
func (s *integrationLoggerTestSuite) TestSubmitLog() {
t := s.T()
h, err := s.ds.NewHost(context.Background(), &fleet.Host{
DetailUpdatedAt: time.Now(),
LabelUpdatedAt: time.Now(),
PolicyUpdatedAt: time.Now(),
SeenTime: time.Now(),
NodeKey: ptr.String(t.Name() + "1234"),
UUID: "1",
Hostname: "foo.local",
PrimaryIP: "192.168.1.1",
PrimaryMac: "30-65-EC-6F-C4-58",
OsqueryHostID: ptr.String(t.Name()),
})
require.NoError(t, err)
// submit status logs
req := submitLogsRequest{
NodeKey: *h.NodeKey,
LogType: "status",
Data: nil,
}
res := submitLogsResponse{}
s.DoJSON("POST", "/api/osquery/log", req, http.StatusOK, &res)
logString := s.buf.String()
assert.Equal(t, 1, strings.Count(logString, `"ip_addr"`))
assert.Equal(t, 1, strings.Count(logString, "x_for_ip_addr"))
s.buf.Reset()
// submit results logs
req = submitLogsRequest{
NodeKey: *h.NodeKey,
LogType: "result",
Data: nil,
}
res = submitLogsResponse{}
s.DoJSON("POST", "/api/osquery/log", req, http.StatusOK, &res)
logString = s.buf.String()
assert.Equal(t, 1, strings.Count(logString, `"ip_addr"`))
assert.Equal(t, 1, strings.Count(logString, "x_for_ip_addr"))
s.buf.Reset()
// submit invalid type logs
req = submitLogsRequest{
NodeKey: *h.NodeKey,
LogType: "unknown",
Data: nil,
}
var errRes map[string]string
s.DoJSON("POST", "/api/osquery/log", req, http.StatusInternalServerError, &errRes)
assert.Contains(t, errRes["error"], "unknown log type")
s.buf.Reset()
// submit gzip-encoded request
var body bytes.Buffer
gw := gzip.NewWriter(&body)
_, err = fmt.Fprintf(gw, `{
"node_key": %q,
"log_type": "status",
"data": null
}`, *h.NodeKey)
require.NoError(t, err)
require.NoError(t, gw.Close())
s.DoRawWithHeaders("POST", "/api/osquery/log", body.Bytes(), http.StatusOK, map[string]string{"Content-Encoding": "gzip"})
logString = s.buf.String()
assert.Equal(t, 1, strings.Count(logString, `"ip_addr"`))
assert.Equal(t, 1, strings.Count(logString, "x_for_ip_addr"))
// submit same payload without specifying gzip encoding fails
s.DoRawWithHeaders("POST", "/api/osquery/log", body.Bytes(), http.StatusBadRequest, nil)
}
func (s *integrationLoggerTestSuite) TestEnrollAgentLogsErrors() {
t := s.T()
_, err := s.ds.NewHost(context.Background(), &fleet.Host{
DetailUpdatedAt: time.Now(),
LabelUpdatedAt: time.Now(),
PolicyUpdatedAt: time.Now(),
SeenTime: time.Now(),
NodeKey: ptr.String("1234"),
UUID: "1",
Hostname: "foo.local",
PrimaryIP: "192.168.1.1",
PrimaryMac: "30-65-EC-6F-C4-58",
})
require.NoError(t, err)
j, err := json.Marshal(&enrollAgentRequest{
EnrollSecret: "1234",
HostIdentifier: "4321",
HostDetails: nil,
})
require.NoError(t, err)
s.DoRawNoAuth("POST", "/api/osquery/enroll", j, http.StatusUnauthorized)
parts := strings.Split(strings.TrimSpace(s.buf.String()), "\n")
require.Len(t, parts, 1)
logData := make(map[string]json.RawMessage)
require.NoError(t, json.Unmarshal([]byte(parts[0]), &logData))
assert.Equal(t, `"error"`, string(logData["level"]))
assert.Contains(t, string(logData["err"]), `"enroll failed:`)
assert.Contains(t, string(logData["err"]), `no matching secret found`)
}