2021-09-15 19:27:53 +00:00
package service
import (
"bytes"
2022-03-07 18:10:55 +00:00
"compress/gzip"
2021-09-15 19:27:53 +00:00
"context"
"encoding/json"
2022-03-07 18:10:55 +00:00
"fmt"
2021-09-15 19:27:53 +00:00
"io"
"net/http"
"strings"
"testing"
"time"
2021-11-24 20:56:54 +00:00
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
2021-09-15 19:27:53 +00:00
"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 ( ) )
2022-05-10 15:29:17 +00:00
users , server := RunServerForTestsWithDS ( s . T ( ) , s . ds , & TestServerOpts { Logger : logger } )
2021-09-15 19:27:53 +00:00
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;" ) ,
}
2022-01-31 21:35:22 +00:00
var createResp createQueryResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/queries" , params , http . StatusOK , & createResp )
2021-09-15 19:27:53 +00:00
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" ] )
2022-04-05 15:35:53 +00:00
assert . Equal ( t , "/api/latest/fleet/login" , kv [ "uri" ] )
2021-09-15 19:27:53 +00:00
case 1 :
assert . Equal ( t , "debug" , kv [ "level" ] )
assert . Equal ( t , "GET" , kv [ "method" ] )
2022-04-05 15:35:53 +00:00
assert . Equal ( t , "/api/latest/fleet/config" , kv [ "uri" ] )
2021-09-15 19:27:53 +00:00
assert . Equal ( t , "admin1@example.com" , kv [ "user" ] )
2022-09-28 20:35:54 +00:00
case 2 :
2021-12-07 15:52:43 +00:00
assert . Equal ( t , "debug" , kv [ "level" ] )
2021-09-15 19:27:53 +00:00
assert . Equal ( t , "POST" , kv [ "method" ] )
2022-04-05 15:35:53 +00:00
assert . Equal ( t , "/api/latest/fleet/queries" , kv [ "uri" ] )
2021-09-15 19:27:53 +00:00
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 ( )
}
}
}
2022-09-27 19:32:46 +00:00
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 ( )
}
}
2021-09-15 19:27:53 +00:00
func ( s * integrationLoggerTestSuite ) TestOsqueryEndpointsLogErrors ( ) {
t := s . T ( )
_ , err := s . ds . NewHost ( context . Background ( ) , & fleet . Host {
DetailUpdatedAt : time . Now ( ) ,
LabelUpdatedAt : time . Now ( ) ,
2021-09-27 19:27:38 +00:00
PolicyUpdatedAt : time . Now ( ) ,
2021-09-15 19:27:53 +00:00
SeenTime : time . Now ( ) ,
2022-12-26 21:32:39 +00:00
NodeKey : ptr . String ( t . Name ( ) + "1234" ) ,
2021-09-15 19:27:53 +00:00
UUID : "1" ,
Hostname : "foo.local" ,
2022-12-26 21:32:39 +00:00
OsqueryHostID : ptr . String ( t . Name ( ) ) ,
2021-09-15 19:27:53 +00:00
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":[} ` ) ) )
2022-04-05 15:35:53 +00:00
req , _ := http . NewRequest ( "POST" , s . server . URL + "/api/osquery/log" , requestBody )
2021-11-24 20:56:54 +00:00
client := fleethttp . NewClient ( )
Add UUID to Fleet errors and clean up error msgs (#10411)
#8129
Apart from fixing the issue in #8129, this change also introduces UUIDs
to Fleet errors. To be able to match a returned error from the API to a
error in the Fleet logs. See
https://fleetdm.slack.com/archives/C019WG4GH0A/p1677780622769939 for
more context.
Samples with the changes in this PR:
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d ''
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "Expected JSON Body"
}
],
"uuid": "a01f6e10-354c-4ff0-b96e-1f64adb500b0"
}
```
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d 'asd'
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "json decoder error"
}
],
"uuid": "5f716a64-7550-464b-a1dd-e6a505a9f89d"
}
```
```
curl -k -X GET -H "Authorization: Bearer badtoken" "https://localhost:8080/api/latest/fleet/teams"
{
"message": "Authentication required",
"errors": [
{
"name": "base",
"reason": "Authentication required"
}
],
"uuid": "efe45bc0-f956-4bf9-ba4f-aa9020a9aaaf"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Authorization header required",
"errors": [
{
"name": "base",
"reason": "Authorization header required"
}
],
"uuid": "57f78cd0-4559-464f-9df7-36c9ef7c89b3"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Permission Denied",
"uuid": "7f0220ad-6de7-4faf-8b6c-8d7ff9d2ca06"
}
```
- [X] Changes file added for user-visible changes in `changes/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [X] Documented any API changes (docs/Using-Fleet/REST-API.md or
docs/Contributing/API-for-contributors.md)
- ~[ ] Documented any permissions changes~
- ~[ ] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)~
- ~[ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for
new osquery data ingestion features.~
- [X] Added/updated tests
- [X] Manual QA for all new/changed functionality
- For Orbit and Fleet Desktop changes:
- [X] Manual QA must be performed in the three main OSs, macOS, Windows
and Linux.
- ~[ ] Auto-update manual QA, from released version of component to new
version (see [tools/tuf/test](../tools/tuf/test/README.md)).~
2023-03-13 16:44:06 +00:00
resp , err := client . Do ( req )
require . NoError ( t , err )
jsn := struct {
Message string ` json:"message" `
Errs [ ] map [ string ] string ` json:"errors,omitempty" `
UUID string ` json:"uuid" `
} { }
err = json . NewDecoder ( resp . Body ) . Decode ( & jsn )
require . NoError ( t , err )
assert . Equal ( t , "Bad request" , jsn . Message )
assert . Len ( t , jsn . Errs , 1 )
assert . Equal ( t , "base" , jsn . Errs [ 0 ] [ "name" ] )
assert . Equal ( t , "json decoder error" , jsn . Errs [ 0 ] [ "reason" ] )
require . NotEmpty ( t , jsn . UUID )
2021-09-15 19:27:53 +00:00
logString := s . buf . String ( )
Add UUID to Fleet errors and clean up error msgs (#10411)
#8129
Apart from fixing the issue in #8129, this change also introduces UUIDs
to Fleet errors. To be able to match a returned error from the API to a
error in the Fleet logs. See
https://fleetdm.slack.com/archives/C019WG4GH0A/p1677780622769939 for
more context.
Samples with the changes in this PR:
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d ''
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "Expected JSON Body"
}
],
"uuid": "a01f6e10-354c-4ff0-b96e-1f64adb500b0"
}
```
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d 'asd'
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "json decoder error"
}
],
"uuid": "5f716a64-7550-464b-a1dd-e6a505a9f89d"
}
```
```
curl -k -X GET -H "Authorization: Bearer badtoken" "https://localhost:8080/api/latest/fleet/teams"
{
"message": "Authentication required",
"errors": [
{
"name": "base",
"reason": "Authentication required"
}
],
"uuid": "efe45bc0-f956-4bf9-ba4f-aa9020a9aaaf"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Authorization header required",
"errors": [
{
"name": "base",
"reason": "Authorization header required"
}
],
"uuid": "57f78cd0-4559-464f-9df7-36c9ef7c89b3"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Permission Denied",
"uuid": "7f0220ad-6de7-4faf-8b6c-8d7ff9d2ca06"
}
```
- [X] Changes file added for user-visible changes in `changes/` or
`orbit/changes/`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [X] Documented any API changes (docs/Using-Fleet/REST-API.md or
docs/Contributing/API-for-contributors.md)
- ~[ ] Documented any permissions changes~
- ~[ ] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)~
- ~[ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for
new osquery data ingestion features.~
- [X] Added/updated tests
- [X] Manual QA for all new/changed functionality
- For Orbit and Fleet Desktop changes:
- [X] Manual QA must be performed in the three main OSs, macOS, Windows
and Linux.
- ~[ ] Auto-update manual QA, from released version of component to new
version (see [tools/tuf/test](../tools/tuf/test/README.md)).~
2023-03-13 16:44:06 +00:00
assert . Contains ( t , logString , ` invalid character '}' looking for beginning of value","level":"info","path":"/api/osquery/log","uuid":" ` + jsn . UUID + ` "} ` , logString )
2021-09-15 19:27:53 +00:00
}
2022-03-07 18:10:55 +00:00
func ( s * integrationLoggerTestSuite ) TestSubmitLog ( ) {
2021-09-15 19:27:53 +00:00
t := s . T ( )
2022-03-07 18:10:55 +00:00
h , err := s . ds . NewHost ( context . Background ( ) , & fleet . Host {
2021-09-15 19:27:53 +00:00
DetailUpdatedAt : time . Now ( ) ,
LabelUpdatedAt : time . Now ( ) ,
2021-09-27 19:27:38 +00:00
PolicyUpdatedAt : time . Now ( ) ,
2021-09-15 19:27:53 +00:00
SeenTime : time . Now ( ) ,
2022-12-26 21:32:39 +00:00
NodeKey : ptr . String ( t . Name ( ) + "1234" ) ,
2021-09-15 19:27:53 +00:00
UUID : "1" ,
Hostname : "foo.local" ,
PrimaryIP : "192.168.1.1" ,
PrimaryMac : "30-65-EC-6F-C4-58" ,
2022-12-26 21:32:39 +00:00
OsqueryHostID : ptr . String ( t . Name ( ) ) ,
2021-09-15 19:27:53 +00:00
} )
require . NoError ( t , err )
2022-03-07 18:10:55 +00:00
// submit status logs
2021-09-15 19:27:53 +00:00
req := submitLogsRequest {
2022-12-26 21:32:39 +00:00
NodeKey : * h . NodeKey ,
2021-09-15 19:27:53 +00:00
LogType : "status" ,
Data : nil ,
}
res := submitLogsResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/log" , req , http . StatusOK , & res )
2021-09-15 19:27:53 +00:00
logString := s . buf . String ( )
2022-03-07 18:10:55 +00:00
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 {
2022-12-26 21:32:39 +00:00
NodeKey : * h . NodeKey ,
2022-03-07 18:10:55 +00:00
LogType : "result" ,
Data : nil ,
}
res = submitLogsResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/log" , req , http . StatusOK , & res )
2022-03-07 18:10:55 +00:00
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 {
2022-12-26 21:32:39 +00:00
NodeKey : * h . NodeKey ,
2022-03-07 18:10:55 +00:00
LogType : "unknown" ,
Data : nil ,
}
var errRes map [ string ] string
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/log" , req , http . StatusInternalServerError , & errRes )
2022-03-07 18:10:55 +00:00
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
2022-12-26 21:32:39 +00:00
} ` , * h . NodeKey )
2022-03-07 18:10:55 +00:00
require . NoError ( t , err )
require . NoError ( t , gw . Close ( ) )
2022-04-05 15:35:53 +00:00
s . DoRawWithHeaders ( "POST" , "/api/osquery/log" , body . Bytes ( ) , http . StatusOK , map [ string ] string { "Content-Encoding" : "gzip" } )
2022-03-07 18:10:55 +00:00
logString = s . buf . String ( )
assert . Equal ( t , 1 , strings . Count ( logString , ` "ip_addr" ` ) )
2021-09-15 19:27:53 +00:00
assert . Equal ( t , 1 , strings . Count ( logString , "x_for_ip_addr" ) )
2022-03-07 18:10:55 +00:00
// submit same payload without specifying gzip encoding fails
2022-10-18 12:43:16 +00:00
s . DoRawWithHeaders ( "POST" , "/api/osquery/log" , body . Bytes ( ) , http . StatusBadRequest , nil )
2021-09-15 19:27:53 +00:00
}
func ( s * integrationLoggerTestSuite ) TestEnrollAgentLogsErrors ( ) {
t := s . T ( )
_ , err := s . ds . NewHost ( context . Background ( ) , & fleet . Host {
DetailUpdatedAt : time . Now ( ) ,
LabelUpdatedAt : time . Now ( ) ,
2021-09-27 19:27:38 +00:00
PolicyUpdatedAt : time . Now ( ) ,
2021-09-15 19:27:53 +00:00
SeenTime : time . Now ( ) ,
2022-12-26 21:32:39 +00:00
NodeKey : ptr . String ( "1234" ) ,
2021-09-15 19:27:53 +00:00
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 )
2022-04-05 15:35:53 +00:00
s . DoRawNoAuth ( "POST" , "/api/osquery/enroll" , j , http . StatusUnauthorized )
2021-09-15 19:27:53 +00:00
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 ) )
2021-12-07 15:52:43 +00:00
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 ` )
2021-09-15 19:27:53 +00:00
}