Skip to content

Commit

Permalink
Merge pull request #53 from pmarkowsky/fix-handlers
Browse files Browse the repository at this point in the history
Fix Santa sync protocol for versions of Santa >= 2024.6
  • Loading branch information
radsec authored Nov 17, 2024
2 parents f5ec879 + 5ab0543 commit 7a6ebed
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 20 deletions.
10 changes: 9 additions & 1 deletion internal/handlers/ruledownload/clean_sync.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ruledownload

import (
"encoding/json"
"log"
"net/http"

Expand Down Expand Up @@ -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),
},
)
}
10 changes: 9 additions & 1 deletion internal/handlers/ruledownload/feed_sync.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ruledownload

import (
"encoding/json"
"log"
"net/http"

Expand Down Expand Up @@ -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),
},
)
}
25 changes: 16 additions & 9 deletions internal/handlers/ruledownload/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -27,7 +28,6 @@ type PostRuledownloadHandler struct {
mhandler machineRuleDownloder
}

//
func (h *PostRuledownloadHandler) Boot() (err error) {
if h.booted {
return
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}

Expand Down
8 changes: 1 addition & 7 deletions internal/handlers/ruledownload/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion internal/handlers/ruledownload/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:"-"`
}
2 changes: 1 addition & 1 deletion internal/handlers/ruledownload/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 7a6ebed

Please sign in to comment.