package api_logs import ( "github.com/thoas/go-funk" "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/ip_utils" "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/number_utils" "net/url" "strings" "time" "github.com/aws/aws-lambda-go/events" ) func GenerateIncomingAPILog(startTime time.Time, requestID *string, claim map[string]interface{}, req events.APIGatewayProxyRequest, res events.APIGatewayProxyResponse) ApiLog { endTime := time.Now() currentRequestID := "" if requestID != nil { currentRequestID = *requestID } var authType string var authUsername string if req.RequestContext.Identity.CognitoAuthenticationType != "" { authType = "cognito" split := strings.Split(req.RequestContext.Identity.CognitoAuthenticationProvider, ":") if len(split) > 0 { authUsername = split[len(split)-1] // = part after last ':' } } else { authType = "iam" split := strings.Split(req.RequestContext.Identity.UserArn, ":user/") if len(split) > 0 { authUsername = split[len(split)-1] // = part after ':user/' } } userID, _ := claim["UserID"].(int64) username, _ := claim["Username"].(string) accountID, _ := claim["AccountID"].(int64) providerID, _ := claim["ProviderID"].(int64) if accountID == 0 { if accountIDParam, ok := req.QueryStringParameters["account_id"]; ok { if i64, err := number_utils.StringToInt64(accountIDParam); err == nil && i64 > 0 { accountID = i64 } } } if providerID == 0 { if providerIDParam, ok := req.QueryStringParameters["provider_id"]; ok { if i64, err := number_utils.StringToInt64(providerIDParam); err == nil && i64 > 0 { providerID = i64 } } } typeString := "api-incoming" if funk.Contains(req.Path, "webhook") { typeString = "webhook-incoming" } // Remove the API key in the header if req.Headers["authorization"] != "" { req.Headers["authorization"] = "***" } if req.Headers["Authorization"] != "" { req.Headers["Authorization"] = "***" } apiLog := ApiLog{ StartTime: startTime, EndTime: endTime, DurMs: endTime.Sub(startTime).Milliseconds(), Type: typeString, Method: req.HTTPMethod, Address: req.RequestContext.DomainName, Path: req.Path, ResponseCode: res.StatusCode, RequestID: currentRequestID, InitialAuthType: authType, InitialAuthUsername: authUsername, SourceIP: ip_utils.GetRequestSourceIP(&req, nil), UserAgent: req.RequestContext.Identity.UserAgent, UserID: userID, Username: username, AccountID: accountID, ProviderID: providerID, Request: ApiLogRequest{ Headers: req.Headers, QueryParameters: req.QueryStringParameters, BodySize: len(req.Body), Body: req.Body, }, Response: ApiLogResponse{ Headers: res.Headers, BodySize: len(res.Body), Body: res.Body, }, } // also copy multi-value query parameters to the log as CSV array values for n, as := range req.MultiValueQueryStringParameters { apiLog.Request.QueryParameters[n] = "[" + strings.Join(as, ",") + "]" } return apiLog } func GenerateOutgoingAPILog(startTime time.Time, requestID *string, claim map[string]interface{}, urlString string, method string, requestBody string, requestHeaders map[string]string, responseBody string, responseHeaders map[string]string, responseCode int) ApiLog { endTime := time.Now() currentRequestID := "" if requestID != nil { currentRequestID = *requestID } userID, _ := claim["UserID"].(int64) username, _ := claim["Username"].(string) accountID, _ := claim["AccountID"].(int64) providerID, _ := claim["ProviderID"].(int64) params := map[string]string{} path := "" address := "" parsedURL, err := url.Parse(urlString) if err == nil { for n, v := range parsedURL.Query() { params[n] = strings.Join(v, ",") } path = parsedURL.Path address = parsedURL.Host } typeString := "api-outgoing" for k, v := range requestHeaders { if strings.ToLower(k) == "x-bobgroup-type" && strings.ToLower(v) == "webhook" { typeString = "webhook-outgoing" break } } apiLog := ApiLog{ StartTime: startTime, EndTime: endTime, DurMs: endTime.Sub(startTime).Milliseconds(), Type: typeString, Method: method, Path: path, Address: address, ResponseCode: responseCode, RequestID: currentRequestID, UserID: userID, Username: username, AccountID: accountID, ProviderID: providerID, Request: ApiLogRequest{ Headers: requestHeaders, QueryParameters: params, BodySize: len(requestBody), Body: requestBody, }, Response: ApiLogResponse{ Headers: responseHeaders, BodySize: len(responseBody), Body: responseBody, }, } return apiLog } // ApiLog is the SQS event details struct encoded as JSON document, sent to SQS, to be logged for each API handler executed. type ApiLog struct { StartTime time.Time `json:"start_time"` EndTime time.Time `json:"end_time"` DurMs int64 `json:"duration_ms"` // duration in milliseconds Type string `json:"type"` // incoming-api or outgoing-api Method string `json:"method"` Address string `json:"address"` // server address for incoming and outgoing Path string `json:"path"` ResponseCode int `json:"response_code"` RequestID string `json:"request_id"` InitialAuthUsername string `json:"initial_auth_username,omitempty"` InitialAuthType string `json:"initial_auth_type,omitempty"` AccountID int64 `json:"account_id,omitempty"` ProviderID int64 `json:"provider_id,omitempty"` UserID int64 `json:"user_id,omitempty"` Username string `json:"username,omitempty"` SourceIP string `json:"source_ip,omitempty"` // only logged for incoming API UserAgent string `json:"user_agent,omitempty"` // only for incoming, indicate type of browser when UI GraphQLQueryRoot string `json:"graphql_query_root,omitempty"` Request ApiLogRequest `json:"request"` Response ApiLogResponse `json:"response"` } type ApiLogRequest struct { Headers map[string]string `json:"headers,omitempty" search:"flattened"` QueryParameters map[string]string `json:"query_parameters,omitempty" search:"flattened"` BodySize int `json:"body_size" search:"long"` // set even when body is truncated/omitted Body string `json:"body,omitempty"` // json body as a string } type ApiLogResponse struct { Headers map[string]string `json:"headers,omitempty" search:"flattened"` BodySize int `json:"body_size"` // set even when body is truncated/omitted Body string `json:"body,omitempty"` // json content as a string }