From 5ab05438f0b9b2a59e9e7a9f945fccfa9ff53c7a Mon Sep 17 00:00:00 2001 From: Pete Markowsky Date: Thu, 19 Sep 2024 12:29:27 -0400 Subject: [PATCH] Fix Santa sync protocol for versions greater than 2024.5 --- internal/handlers/ruledownload/clean_sync.go | 10 +++++++- internal/handlers/ruledownload/feed_sync.go | 10 +++++++- internal/handlers/ruledownload/handler.go | 25 ++++++++++++------- .../handlers/ruledownload/handler_test.go | 8 +----- internal/handlers/ruledownload/request.go | 3 ++- internal/handlers/ruledownload/response.go | 2 +- 6 files changed, 38 insertions(+), 20 deletions(-) diff --git a/internal/handlers/ruledownload/clean_sync.go b/internal/handlers/ruledownload/clean_sync.go index 603d317..f75d552 100644 --- a/internal/handlers/ruledownload/clean_sync.go +++ b/internal/handlers/ruledownload/clean_sync.go @@ -1,6 +1,7 @@ package ruledownload import ( + "encoding/json" "log" "net/http" @@ -46,11 +47,18 @@ func (d concreteGlobalRuleDownloader) handle(machineID string, cursor ruledownlo rules[i] = rule.SantaRule } + // Marshal the cursor to a string + jsonCursor, err := json.Marshal(nextCursor) + if err != nil { + log.Printf(" json.Marshal Error %s", err.Error()) + return response.APIResponse(http.StatusInternalServerError, err) + } + return response.APIResponse( http.StatusOK, RuledownloadResponse{ Rules: DDBRulesToResponseRules(rules), - Cursor: &nextCursor, + Cursor: string(jsonCursor), }, ) } diff --git a/internal/handlers/ruledownload/feed_sync.go b/internal/handlers/ruledownload/feed_sync.go index f56eabd..fa38de1 100644 --- a/internal/handlers/ruledownload/feed_sync.go +++ b/internal/handlers/ruledownload/feed_sync.go @@ -1,6 +1,7 @@ package ruledownload import ( + "encoding/json" "log" "net/http" @@ -70,11 +71,18 @@ func (d concreteFeedRuleDownloader) handle(machineID string, cursor ruledownload rules[i] = rule.SantaRule } + // Marshal the cursor to a string + jsonCursor, err := json.Marshal(nextCursor) + if err != nil { + log.Printf(" json.Marshal Error %s", err.Error()) + return response.APIResponse(http.StatusInternalServerError, err) + } + return response.APIResponse( http.StatusOK, RuledownloadResponse{ Rules: DDBRulesToResponseRules(rules), - Cursor: &nextCursor, + Cursor: string(jsonCursor), }, ) } diff --git a/internal/handlers/ruledownload/handler.go b/internal/handlers/ruledownload/handler.go index 657eb5c..559ff63 100644 --- a/internal/handlers/ruledownload/handler.go +++ b/internal/handlers/ruledownload/handler.go @@ -13,12 +13,13 @@ import ( ) // RuleDownloadHandler handles requests to the /ruledownload and /ruledownload/{machine_id} API endpoints -// During every sync, Santa sensors make successive POST requests to the /ruledownload endpoint to paginate -// through all rules. -// When given a blank postbody (e.g. {}), it indicates the very first request in a sequence. If the -// API returns a "cursor" in the response body, this cursor will be sent back verbatim in a subsequent postbody. -// When a response does not return a "cursor" in the body, it signals that there are no more items to page -// through, and the sensor will stop sending requests. +// +// During every sync, Santa sensors make successive POST requests to the /ruledownload endpoint to paginate +// through all rules. +// When given a blank postbody (e.g. {}), it indicates the very first request in a sequence. If the +// API returns a "cursor" in the response body, this cursor will be sent back verbatim in a subsequent postbody. +// When a response does not return a "cursor" in the body, it signals that there are no more items to page +// through, and the sensor will stop sending requests. type PostRuledownloadHandler struct { booted bool cursorService ruledownloadCursorService @@ -27,7 +28,6 @@ type PostRuledownloadHandler struct { mhandler machineRuleDownloder } -// func (h *PostRuledownloadHandler) Boot() (err error) { if h.booted { return @@ -59,12 +59,10 @@ func (h *PostRuledownloadHandler) Boot() (err error) { return } -// func (h *PostRuledownloadHandler) Handles(request events.APIGatewayProxyRequest) bool { return request.Resource == "/ruledownload/{machine_id}" && request.HTTPMethod == "POST" } -// func (h *PostRuledownloadHandler) Handle(request events.APIGatewayProxyRequest) (*events.APIGatewayProxyResponse, error) { machineID, ok := request.PathParameters["machine_id"] if !ok { @@ -81,6 +79,15 @@ func (h *PostRuledownloadHandler) Handle(request events.APIGatewayProxyRequest) return response.APIResponse(http.StatusBadRequest, response.ErrInvalidBodyResponse) } + if ruledownloadRequest.RawCursor != "" { + ruledownloadRequest.Cursor = &ruledownloadCursor{} + err = json.Unmarshal([]byte(ruledownloadRequest.RawCursor), ruledownloadRequest.Cursor) + if err != nil { + log.Printf(" Failed to unmarshall cursor Error %s", err.Error()) + return response.APIResponse(http.StatusBadRequest, response.ErrInvalidBodyResponse) + } + } + return h.handleRuleDownload(machineID, ruledownloadRequest) } diff --git a/internal/handlers/ruledownload/handler_test.go b/internal/handlers/ruledownload/handler_test.go index a2f0b81..9705775 100644 --- a/internal/handlers/ruledownload/handler_test.go +++ b/internal/handlers/ruledownload/handler_test.go @@ -8,9 +8,7 @@ import ( "github.com/stretchr/testify/assert" ) -// // Test Mocks -// type mockCursorService func(req RuledownloadRequest, machineID string) (cursor ruledownloadCursor, err error) func (m mockCursorService) ConstructCursor(req RuledownloadRequest, machineID string) (cursor ruledownloadCursor, err error) { @@ -35,17 +33,13 @@ func (m mockMachineRuleDownloader) handle(machineID string, ruledownloadRequest return m(machineID, ruledownloadRequest) } -// // Coerce our mocks to conform to the interfaces that they are intended to implement -// var _ ruledownloadCursorService = mockCursorService(nil) var _ globalRuleDownloader = mockGlobalRuleDownloader(nil) var _ feedRuleDownloader = mockFeedRuleDownloader(nil) var _ machineRuleDownloder = mockMachineRuleDownloader(nil) -// // Actual Tests -// func Test_PostRuledownloadHandler_SendToCorrectHandler(t *testing.T) { type test struct { cursor ruledownloadCursor @@ -180,7 +174,7 @@ func Test_PostRuledownloadHandler_CursorServiceReceivesCorrectRequest(t *testing PathParameters: map[string]string{ "machine_id": machineID, }, - Body: `{"cursor":{"strategy":2,"batch_size":7,"page":2,"pk":"AAAA","sk":"eeeeee"}}`, + Body: `{"cursor": "{\"strategy\":2, \"batch_size\":7,\"page\":2,\"pk\":\"AAAA\",\"sk\":\"eeeeee\"}"}`, } _, err := handler.Handle(request) diff --git a/internal/handlers/ruledownload/request.go b/internal/handlers/ruledownload/request.go index a4f5129..5dffd0d 100644 --- a/internal/handlers/ruledownload/request.go +++ b/internal/handlers/ruledownload/request.go @@ -4,5 +4,6 @@ package ruledownload type RuledownloadRequest struct { // Cursor is, verbatim, the Cursor that is returned to a sensor in a previous RuledownloadResponse // On the very first rule download request in a flight sequence, there will be no cursor provided. - Cursor *ruledownloadCursor `json:"cursor,omitempty"` + RawCursor string `json:"cursor,omitempty"` + Cursor *ruledownloadCursor `json:"-"` } diff --git a/internal/handlers/ruledownload/response.go b/internal/handlers/ruledownload/response.go index f1b42ed..e4775d3 100644 --- a/internal/handlers/ruledownload/response.go +++ b/internal/handlers/ruledownload/response.go @@ -10,7 +10,7 @@ type RuledownloadResponse struct { Rules []RuledownloadRule `json:"rules"` // When a cursor is returned by the server, it is an indicator to the Santa sensor that there are // additional rules to be paginated through. This cursor is passed to the next request. - Cursor *ruledownloadCursor `json:"cursor,omitempty"` + Cursor string `json:"cursor,omitempty"` } // RuledownloadRule is a single rule returned in a RuledownloadResponse