-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8935f9a
commit 5cb8d1f
Showing
8 changed files
with
417 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package fortisiem | ||
|
||
import ( | ||
"context" | ||
"crypto/tls" | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"strconv" | ||
"time" | ||
) | ||
|
||
type FortiSIEMClient struct { | ||
client *http.Client | ||
username string | ||
password string | ||
url string | ||
} | ||
|
||
func NewFortiSIEMClient(url, username, password string, verifySSL bool) FortiSIEMClient { | ||
return FortiSIEMClient{ | ||
username: username, | ||
password: password, | ||
url: url, | ||
client: &http.Client{ | ||
Transport: &http.Transport{ | ||
TLSClientConfig: &tls.Config{InsecureSkipVerify: !verifySSL}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func (f *FortiSIEMClient) FetchAlerts(ctx context.Context, start, end time.Time) ([]map[string]interface{}, error) { | ||
req, err := f.createHTTPRequest(ctx, http.MethodGet, "/phoenix/rest/pub/incident") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
q := req.URL.Query() | ||
|
||
q.Add("timeFrom", strconv.FormatInt(start.UTC().UnixMilli(), 10)) | ||
q.Add("timeTo", strconv.FormatInt(end.UTC().UnixMilli(), 10)) | ||
|
||
req.URL.RawQuery = q.Encode() | ||
|
||
res, err := f.client.Do(req) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
response, err := parseResponse(res) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if response.Pages == 1 { | ||
return response.Data, nil | ||
} | ||
|
||
alerts := response.Data | ||
queryId := response.QueryID | ||
|
||
for page := 2; page <= response.Pages; page++ { | ||
reqP, err := f.createHTTPRequest(ctx, http.MethodGet, fmt.Sprintf("/phoenix/rest/pub/incident/%s/%d", queryId, page)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
resP, err := f.client.Do(reqP) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
responseP, err := parseResponse(resP) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
alerts = append(alerts, responseP.Data...) | ||
} | ||
|
||
return alerts, nil | ||
} | ||
|
||
func parseResponse(res *http.Response) (FortiSIEMFetchAlertsResponse, error) { | ||
var response FortiSIEMFetchAlertsResponse | ||
|
||
defer res.Body.Close() | ||
|
||
if res.StatusCode != http.StatusOK { | ||
return FortiSIEMFetchAlertsResponse{}, fmt.Errorf("status code %d", res.StatusCode) | ||
} | ||
|
||
err := json.NewDecoder(res.Body).Decode(&response) | ||
if err != nil { | ||
return FortiSIEMFetchAlertsResponse{}, err | ||
} | ||
|
||
return response, nil | ||
} | ||
|
||
func (f *FortiSIEMClient) createHTTPRequest(ctx context.Context, method string, path string) (*http.Request, error) { | ||
r, err := url.JoinPath(f.url, path) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
req, err := http.NewRequestWithContext(ctx, method, r, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
req.SetBasicAuth(f.username, f.password) | ||
req.Header.Set("Accept", "application/json") | ||
req.Header.Set("X-Requested-By", "RedCarbon Agent") | ||
|
||
return req, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package fortisiem | ||
|
||
type FortiSIEMFetchAlertsResponse struct { | ||
Data []map[string]interface{} `json:"data"` | ||
Pages int `json:"pages"` | ||
QueryID string `json:"queryId"` | ||
} | ||
|
||
type Incident struct { | ||
IncidentID int `json:"incidentId"` | ||
IncidentTitle string `json:"incidentTitle"` | ||
EventSeverity int `json:"eventSeverity"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package services | ||
|
||
import ( | ||
"connectrpc.com/connect" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"github.com/google/uuid" | ||
"github.com/sirupsen/logrus" | ||
"github.com/spf13/viper" | ||
"pkg.redcarbon.ai/internal/services/fortisiem" | ||
agents_publicv1 "pkg.redcarbon.ai/proto/redcarbon/agents_public/v1" | ||
"pkg.redcarbon.ai/proto/redcarbon/agents_public/v1/agents_publicv1connect" | ||
) | ||
|
||
type srvFortiSIEM struct { | ||
cli fortisiem.FortiSIEMClient | ||
agentsCli agents_publicv1connect.AgentsPublicAPIsV1SrvClient | ||
} | ||
|
||
func newFortiSIEMService(conf *agents_publicv1.FortiSIEMJobConfiguration, agentsCli agents_publicv1connect.AgentsPublicAPIsV1SrvClient) Service { | ||
return &srvFortiSIEM{ | ||
cli: fortisiem.NewFortiSIEMClient(conf.Host, conf.Username, conf.Password, conf.VerifySsl), | ||
agentsCli: agentsCli, | ||
} | ||
} | ||
|
||
func (s srvFortiSIEM) RunService(ctx context.Context) { | ||
l := logrus.WithFields(logrus.Fields{ | ||
"service": "fortisiem", | ||
"trace": uuid.NewString(), | ||
}) | ||
|
||
l.Info("Starting FortiSIEM service") | ||
start, end := retrieveSearchTimeRangeForKey("fortisiem") | ||
|
||
alerts, err := s.cli.FetchAlerts(ctx, start, end) | ||
if err != nil { | ||
l.WithError(err).Error("Error while fetching the alerts") | ||
return | ||
} | ||
|
||
l.Infof("Found %d alerts", len(alerts)) | ||
|
||
for _, alert := range alerts { | ||
incident, err := s.buildIncidentToIngest(alert) | ||
if err != nil { | ||
l.WithError(err).Warn("Error while building the incident to ingest for alert") | ||
} | ||
|
||
req := connect.NewRequest(incident) | ||
req.Header().Set("authorization", fmt.Sprintf("ApiToken %s", viper.Get("auth.access_token"))) | ||
|
||
_, err = s.agentsCli.IngestIncident(ctx, req) | ||
if err != nil { | ||
l.WithError(err).Error("failed to ingest incident") | ||
continue | ||
} | ||
} | ||
|
||
viper.Set("fortisiem.last_execution", end) | ||
|
||
if err := viper.WriteConfig(); err != nil { | ||
l.WithError(err).Error("failed to write config") | ||
} | ||
|
||
l.Info("FortiSIEM service completed") | ||
} | ||
|
||
func (s srvFortiSIEM) buildIncidentToIngest(incident map[string]interface{}) (*agents_publicv1.IngestIncidentRequest, error) { | ||
iStr, err := json.Marshal(incident) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var inc fortisiem.Incident | ||
|
||
err = json.Unmarshal(iStr, &inc) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
idStr := fmt.Sprintf("%d", inc.IncidentID) | ||
|
||
return &agents_publicv1.IngestIncidentRequest{ | ||
Title: inc.IncidentTitle, | ||
Description: inc.IncidentTitle, | ||
RawData: string(iStr), | ||
Severity: mapFortiSIEMSeverity(inc.EventSeverity), | ||
Origin: "fortisiem", | ||
OriginalId: &idStr, | ||
OriginalUrl: nil, | ||
}, nil | ||
} | ||
|
||
func mapFortiSIEMSeverity(sev int) uint32 { | ||
if sev >= 1 && sev <= 4 { | ||
return uint32(10) | ||
} | ||
|
||
if sev >= 5 && sev <= 8 { | ||
return uint32(40) | ||
} | ||
|
||
return uint32(70) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.