diff --git a/internal/capability/http/http.go b/internal/capability/http/http.go index 7d10a781..4e74e173 100644 --- a/internal/capability/http/http.go +++ b/internal/capability/http/http.go @@ -1,16 +1,11 @@ package http import ( - "bytes" - "encoding/base64" - "errors" - "io" - "net/http" "reflect" "soarca/logger" "soarca/models/cacao" "soarca/models/execution" - "strings" + "soarca/utils/http" ) // Receive HTTP API command data from decomposer/executer @@ -30,9 +25,6 @@ func init() { log = logger.Logger(component, logger.Info, "", logger.Json) } -// What to do if there is no agent or target? -// And maybe no auth info either? - func (httpCapability *HttpCapability) Execute( metadata execution.Metadata, command cacao.Command, @@ -40,125 +32,21 @@ func (httpCapability *HttpCapability) Execute( target cacao.AgentTarget, variables cacao.VariableMap) (cacao.VariableMap, error) { - // Get request data and handle errors - method, url, errmethod := ObtainHttpMethodAndUrlFromCommand(command) - if errmethod != nil { - log.Error(errmethod) - return cacao.VariableMap{}, errmethod - } - content_data, errcontent := ObtainHttpRequestContentDataFromCommand(command) - if errcontent != nil { - log.Error(errcontent) - return variables, errcontent + soarca_http_request := new(http.HttpRequest) + soarca_http_options := http.HttpOptions{ + Target: &target, + Command: &command, + Auth: &authentication, } - // Setup request - request, err := http.NewRequest(method, url, bytes.NewBuffer(content_data)) - if err != nil { - log.Error(err) - return cacao.VariableMap{}, err - } - - for key, httpCapability := range command.Headers { - request.Header.Add(key, httpCapability) - } - if target.ID != "" { - if err := verifyAuthInfoMatchesAgentTarget(&target, &authentication); err != nil { - log.Error(err) - return cacao.VariableMap{}, err - } - - if err := setupAuthHeaders(request, &authentication); err != nil { - log.Error(err) - return cacao.VariableMap{}, err - } - } - - // Perform request - client := &http.Client{} - response, err := client.Do(request) - if err != nil { - log.Error(err) - return cacao.VariableMap{}, err - } - defer response.Body.Close() - - responseBytes, err := io.ReadAll(response.Body) + responseBytes, err := soarca_http_request.Request(soarca_http_options) if err != nil { log.Error(err) return cacao.VariableMap{}, err } respString := string(responseBytes) - sc := response.StatusCode - if sc < 200 || sc > 299 { - return cacao.VariableMap{}, errors.New(respString) - } return cacao.VariableMap{ "__soarca_http_result__": {Name: "result", Value: respString}}, nil } - -func ObtainHttpMethodAndUrlFromCommand( - command cacao.Command) (string, string, error) { - parts := strings.Split(command.Command, " ") - if len(parts) != 2 { - return "", "", errors.New("method or url missing from command") - } - method := parts[0] - url := parts[1] - return method, url, nil -} - -func verifyAuthInfoMatchesAgentTarget( - target *cacao.AgentTarget, authInfo *cacao.AuthenticationInformation) error { - if !(target.AuthInfoIdentifier == authInfo.ID) { - return errors.New("target auth info id does not match auth info object's") - } - return nil -} - -func setupAuthHeaders(request *http.Request, authInfo *cacao.AuthenticationInformation) error { - - authInfoType := authInfo.Type - switch authInfoType { - case cacao.AuthInfoHTTPBasicType: - request.SetBasicAuth(authInfo.Username, authInfo.Password) - case cacao.AuthInfoOAuth2Type: - // TODO: verify correctness - // (https://datatracker.ietf.org/doc/html/rfc6750#section-2.1) - bearer := "Bearer " + authInfo.Token - request.Header.Add("Authorization", bearer) - case "": - // It means that AuthN information is not set - return nil - default: - return errors.New("unsupported authentication type: " + authInfoType) - } - return nil -} - -func ObtainHttpRequestContentDataFromCommand( - command cacao.Command) ([]byte, error) { - // Reads if either command or command_b64 are populated, and - // Returns a byte slice from either - content := command.Content - contentB64 := command.ContentB64 - - var nil_content []byte - - if content == "" && contentB64 == "" { - return nil_content, nil - } - - if content != "" && contentB64 != "" { - log.Warn("both content and content_b64 are populated. using content.") - return []byte(content), nil - } - - if content != "" { - return []byte(content), nil - } - - return base64.StdEncoding.DecodeString(contentB64) -} diff --git a/test/unittest/capability/http/http_test.go b/test/unittest/capability/http/http_test.go index be775175..c5a00680 100644 --- a/test/unittest/capability/http/http_test.go +++ b/test/unittest/capability/http/http_test.go @@ -1,94 +1 @@ -package ssh_test - -import ( - "errors" - "soarca/internal/capability/http" - "soarca/models/cacao" - "testing" - - "github.com/go-playground/assert/v2" -) - -// Tests for data fetching from command -func TestHttpObtainMethodFromCommandValid(t *testing.T) { - - expectedCommand := cacao.Command{ - Type: "http-api", - Command: "POST https://google.com/", - } - - httpMethod, httpUrl, err := http.ObtainHttpMethodAndUrlFromCommand(expectedCommand) - assert.Equal(t, httpMethod, "POST") - assert.Equal(t, httpUrl, "https://google.com/") - assert.Equal(t, err, nil) -} - -func TestHttpObtainMethodAndUrlFromCommandInvalid(t *testing.T) { - - expectedCommand := cacao.Command{ - Type: "http-api", - Command: "https://google.com/", // No method - } - - httpMethod, httpUrl, err := http.ObtainHttpMethodAndUrlFromCommand(expectedCommand) - - assert.Equal(t, httpMethod, "") - assert.Equal(t, httpUrl, "") - assert.Equal(t, err, errors.New("method or url missing from command")) - -} - -// Tests obtain content from command -func TestObtainHttpRequestContentDataFromCommandBothTypes(t *testing.T) { - test_content := "414141" - test_b64_content := "923948a09a" - expectedCommand := cacao.Command{ - Type: "http-api", - Command: "GET 0.0.0.0:80/", - Content: test_content, - ContentB64: test_b64_content, - } - - ret_content, err := http.ObtainHttpRequestContentDataFromCommand(expectedCommand) - - assert.Equal(t, ret_content, []byte(test_content)) - assert.Equal(t, err, nil) -} -func TestObtainHttpRequestContentDataFromCommandB64Only(t *testing.T) { - test_b64_content := "R08gU09BUkNBIQ==" - expectedCommand := cacao.Command{ - Type: "http-api", - Command: "GET 0.0.0.0:80/", - ContentB64: test_b64_content, - } - - ret_content, err := http.ObtainHttpRequestContentDataFromCommand(expectedCommand) - - assert.Equal(t, ret_content, []byte("GO SOARCA!")) - assert.Equal(t, err, nil) -} -func TestObtainHttpRequestContentDataFromCommandPlainTextOnly(t *testing.T) { - test_content := "414141" - expectedCommand := cacao.Command{ - Type: "http-api", - Command: "GET 0.0.0.0:80/", - Content: test_content, - } - - ret_content, err := http.ObtainHttpRequestContentDataFromCommand(expectedCommand) - - assert.Equal(t, ret_content, []byte(test_content)) - assert.Equal(t, err, nil) -} - -func TestObtainHttpRequestContentDataFromCommandEmpty(t *testing.T) { - expectedCommand := cacao.Command{ - Type: "http-api", - Command: "GET 0.0.0.0:80/", - } - - ret_content, err := http.ObtainHttpRequestContentDataFromCommand(expectedCommand) - - assert.Equal(t, ret_content, nil) - assert.Equal(t, err, nil) -} +package http_test diff --git a/test/unittest/utils/http/http_test.go b/test/unittest/utils/http/http_test.go index 5e0ed651..e7eba82a 100644 --- a/test/unittest/utils/http/http_test.go +++ b/test/unittest/utils/http/http_test.go @@ -1,4 +1,4 @@ -package ssh_test +package http_test import ( "encoding/json"