Skip to content

Commit

Permalink
http capability uses http wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
lucamrgs committed Mar 7, 2024
1 parent 0255ab2 commit af1aeeb
Show file tree
Hide file tree
Showing 3 changed files with 9 additions and 214 deletions.
126 changes: 7 additions & 119 deletions internal/capability/http/http.go
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -30,135 +25,28 @@ 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,
authentication cacao.AuthenticationInformation,
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)
}
95 changes: 1 addition & 94 deletions test/unittest/capability/http/http_test.go
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion test/unittest/utils/http/http_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ssh_test
package http_test

import (
"encoding/json"
Expand Down

0 comments on commit af1aeeb

Please sign in to comment.