diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index b2ddfd5cc..c1719a822 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -864,6 +864,8 @@ components: $ref: '#/components/schemas/GeoLocationCheck' peer_network_range_check: $ref: '#/components/schemas/PeerNetworkRangeCheck' + process_check: + $ref: '#/components/schemas/ProcessCheck' NBVersionCheck: description: Posture check for the version of NetBird type: object @@ -952,6 +954,31 @@ components: required: - ranges - action + ProcessCheck: + description: Posture Check for binaries exist and are running in the peer’s system + type: object + properties: + processes: + type: array + items: + $ref: '#/components/schemas/Process' + required: + - processes + Process: + description: Describe the operational activity within peer's system. + type: object + properties: + path: + description: Path to the process executable file in a Unix-like operating system + type: string + example: "/usr/local/bin/netbird" + windows_path: + description: Path to the process executable file in a Windows operating system + type: string + example: "C:\ProgramData\NetBird\netbird.exe" + required: + - path + - windows_path Location: description: Describe geographical location information type: object diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index c007663a4..2d324102e 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -201,6 +201,9 @@ type Checks struct { // PeerNetworkRangeCheck Posture check for allow or deny access based on peer local network addresses PeerNetworkRangeCheck *PeerNetworkRangeCheck `json:"peer_network_range_check,omitempty"` + + // ProcessCheck Posture Check for binaries exist and are running in the peer’s system + ProcessCheck *ProcessCheck `json:"process_check,omitempty"` } // City Describe city geographical location information @@ -910,6 +913,20 @@ type PostureCheckUpdate struct { Name string `json:"name"` } +// Process Describe the operational activity within peer's system. +type Process struct { + // Path Path to the process executable file in a Unix-like operating system + Path string `json:"path"` + + // WindowsPath Path to the process executable file in a Windows operating system + WindowsPath string `json:"windows_path"` +} + +// ProcessCheck Posture Check for binaries exist and are running in the peer’s system +type ProcessCheck struct { + Processes []Process `json:"processes"` +} + // Route defines model for Route. type Route struct { // Description Route description diff --git a/management/server/http/posture_checks_handler.go b/management/server/http/posture_checks_handler.go index fcccc1997..48b256043 100644 --- a/management/server/http/posture_checks_handler.go +++ b/management/server/http/posture_checks_handler.go @@ -221,6 +221,10 @@ func (p *PostureChecksHandler) savePostureChecks( } } + if processCheck := req.Checks.ProcessCheck; processCheck != nil { + postureChecks.Checks.ProcessCheck = toProcessCheck(processCheck) + } + if err := p.accountManager.SavePostureChecks(account.Id, user.Id, &postureChecks); err != nil { util.WriteError(err, w) return @@ -235,7 +239,7 @@ func validatePostureChecksUpdate(req api.PostureCheckUpdate) error { } if req.Checks == nil || (req.Checks.NbVersionCheck == nil && req.Checks.OsVersionCheck == nil && - req.Checks.GeoLocationCheck == nil && req.Checks.PeerNetworkRangeCheck == nil) { + req.Checks.GeoLocationCheck == nil && req.Checks.PeerNetworkRangeCheck == nil && req.Checks.ProcessCheck == nil) { return status.Errorf(status.InvalidArgument, "posture checks shouldn't be empty") } @@ -292,6 +296,21 @@ func validatePostureChecksUpdate(req api.PostureCheckUpdate) error { } } + if processCheck := req.Checks.ProcessCheck; processCheck != nil { + if len(processCheck.Processes) == 0 { + return status.Errorf(status.InvalidArgument, "processes for process check shouldn't be empty") + } + + for _, process := range processCheck.Processes { + if process.WindowsPath == "" { + return status.Errorf(status.InvalidArgument, "windows path for process check shouldn't be empty") + } + if process.Path == "" { + return status.Errorf(status.InvalidArgument, "path for process check shouldn't be empty") + } + } + } + return nil } @@ -322,6 +341,10 @@ func toPostureChecksResponse(postureChecks *posture.Checks) *api.PostureCheck { checks.PeerNetworkRangeCheck = toPeerNetworkRangeCheckResponse(postureChecks.Checks.PeerNetworkRangeCheck) } + if postureChecks.Checks.ProcessCheck != nil { + checks.ProcessCheck = toProcessCheckResponse(postureChecks.Checks.ProcessCheck) + } + return &api.PostureCheck{ Id: postureChecks.ID, Name: postureChecks.Name, @@ -396,3 +419,28 @@ func toPeerNetworkRangeCheck(check *api.PeerNetworkRangeCheck) (*posture.PeerNet Action: string(check.Action), }, nil } + +func toProcessCheckResponse(check *posture.ProcessCheck) *api.ProcessCheck { + processes := make([]api.Process, 0, len(check.Processes)) + for _, process := range check.Processes { + processes = append(processes, (api.Process)(process)) + } + + return &api.ProcessCheck{ + Processes: processes, + } +} + +func toProcessCheck(check *api.ProcessCheck) *posture.ProcessCheck { + processes := make([]posture.Process, 0, len(check.Processes)) + for _, process := range check.Processes { + processes = append(processes, posture.Process{ + Path: process.Path, + WindowsPath: process.WindowsPath, + }) + } + + return &posture.ProcessCheck{ + Processes: processes, + } +} diff --git a/management/server/http/posture_checks_handler_test.go b/management/server/http/posture_checks_handler_test.go index 70e803214..18194e128 100644 --- a/management/server/http/posture_checks_handler_test.go +++ b/management/server/http/posture_checks_handler_test.go @@ -433,6 +433,43 @@ func TestPostureCheckUpdate(t *testing.T) { handler.geolocationManager = nil }, }, + { + name: "Create Posture Checks Process Check", + requestType: http.MethodPost, + requestPath: "/api/posture-checks", + requestBody: bytes.NewBuffer( + []byte(`{ + "name": "default", + "description": "default", + "checks": { + "process_check": { + "processes": [ + { + "path": "/usr/local/bin/netbird", + "windows_path": "C:\\ProgramData\\NetBird\\netbird.exe" + } + ] + } + } + }`)), + expectedStatus: http.StatusOK, + expectedBody: true, + expectedPostureCheck: &api.PostureCheck{ + Id: "postureCheck", + Name: "default", + Description: str("default"), + Checks: api.Checks{ + ProcessCheck: &api.ProcessCheck{ + Processes: []api.Process{ + { + Path: "/usr/local/bin/netbird", + WindowsPath: "C:\\ProgramData\\NetBird\\netbird.exe", + }, + }, + }, + }, + }, + }, { name: "Create Posture Checks Invalid Check", requestType: http.MethodPost, @@ -937,4 +974,45 @@ func TestPostureCheck_validatePostureChecksUpdate(t *testing.T) { }, ) assert.Error(t, err) + + // valid process check + processCheck := api.ProcessCheck{ + Processes: []api.Process{ + { + Path: "/usr/local/bin/netbird", + WindowsPath: "C:\\ProgramData\\NetBird\\netbird.exe", + }, + }, + } + err = validatePostureChecksUpdate(api.PostureCheckUpdate{Name: "Default", Checks: &api.Checks{ProcessCheck: &processCheck}}) + assert.NoError(t, err) + + // invalid process check + processCheck = api.ProcessCheck{ + Processes: make([]api.Process, 0), + } + err = validatePostureChecksUpdate(api.PostureCheckUpdate{Name: "Default", Checks: &api.Checks{ProcessCheck: &processCheck}}) + assert.Error(t, err) + + // invalid process check + processCheck = api.ProcessCheck{ + Processes: []api.Process{ + { + Path: "/usr/local/bin/netbird", + }, + }, + } + err = validatePostureChecksUpdate(api.PostureCheckUpdate{Name: "Default", Checks: &api.Checks{ProcessCheck: &processCheck}}) + assert.Error(t, err) + + // invalid process check + processCheck = api.ProcessCheck{ + Processes: []api.Process{ + { + WindowsPath: "C:\\ProgramData\\NetBird\\netbird.exe", + }, + }, + } + err = validatePostureChecksUpdate(api.PostureCheckUpdate{Name: "Default", Checks: &api.Checks{ProcessCheck: &processCheck}}) + assert.Error(t, err) }