Skip to content

Commit

Permalink
Feature/19 use http wrapper in http capability (#25)
Browse files Browse the repository at this point in the history
Co-authored-by: Maarten de Kruijf <[email protected]>
  • Loading branch information
lucamrgs and MaartendeKruijf authored Mar 18, 2024
1 parent 647ee68 commit 382a3e7
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 232 deletions.
134 changes: 13 additions & 121 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 @@ -24,6 +19,7 @@ const (
)

type HttpCapability struct {
soarca_http_request http.IHttpRequest
}

type Empty struct{}
Expand All @@ -35,139 +31,35 @@ func init() {
log = logger.Logger(component, logger.Info, "", logger.Json)
}

func New(httpRequest http.IHttpRequest) *HttpCapability {
return &HttpCapability{soarca_http_request: httpRequest}
}

func (httpCapability *HttpCapability) GetType() string {
return httpApiCapabilityName
}

// 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.Variables) (cacao.Variables, error) {

// Get request data and handle errors
method, url, errmethod := ObtainHttpMethodAndUrlFromCommand(command)
if errmethod != nil {
log.Error(errmethod)
return cacao.NewVariables(), errmethod
}
content_data, errcontent := ObtainHttpRequestContentDataFromCommand(command)
if errcontent != nil {
log.Error(errcontent)
return variables, errcontent
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.NewVariables(), err
}

for key, httpHeaders := range command.Headers {
for _, httpHeader := range httpHeaders {
request.Header.Add(key, httpHeader)
}
}
if target.ID != "" {
if err := verifyAuthInfoMatchesAgentTarget(&target, &authentication); err != nil {
log.Error(err)
return cacao.NewVariables(), err
}

if err := setupAuthHeaders(request, &authentication); err != nil {
log.Error(err)
return cacao.NewVariables(), err
}
}

// Perform request
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
log.Error(err)
return cacao.NewVariables(), err
}
defer response.Body.Close()

responseBytes, err := io.ReadAll(response.Body)
responseBytes, err := httpCapability.soarca_http_request.Request(soarca_http_options)
if err != nil {
log.Error(err)
return cacao.NewVariables(), err
}
respString := string(responseBytes)
sc := response.StatusCode
if sc < 200 || sc > 299 {
return cacao.NewVariables(), errors.New(respString)
}

return cacao.NewVariables(cacao.Variable{Name: httpApiResultVariableName, Value: respString}), nil
variable := cacao.Variable{Name: httpApiResultVariableName, Value: respString}

}

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 cacao.NewVariables(variable), nil

return base64.StdEncoding.DecodeString(contentB64)
}
4 changes: 2 additions & 2 deletions internal/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ func (controller *Controller) NewDecomposer() decomposer.IDecomposer {
ssh := new(ssh.SshCapability)
capabilities := map[string]capability.ICapability{ssh.GetType(): ssh}

http := new(http.HttpCapability)
httpUtil := new(httpUtil.HttpRequest)
http := http.New(httpUtil)
capabilities[http.GetType()] = http

httpUtil := new(httpUtil.HttpRequest)
openc2 := openc2.New(httpUtil)
capabilities[openc2.GetType()] = openc2

Expand Down
102 changes: 51 additions & 51 deletions test/integration/capability/http/http_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,24 @@ import (
"soarca/internal/capability/http"
"soarca/models/cacao"
"soarca/models/execution"
httpUtil "soarca/utils/http"
"testing"

"github.com/google/uuid"
)

func TestHttpConnection(t *testing.T) {
httpCapability := new(http.HttpCapability)
request := httpUtil.HttpRequest{}
httpCapability := http.New(&request)

target := cacao.AgentTarget{
Address: map[cacao.NetAddressType][]string{
"url": {"https://httpbin.org/get"},
},
}
expectedCommand := cacao.Command{
Type: "http-api",
Command: "GET https://httpbin.org/",
Command: "GET / HTTP/1.1",
Headers: map[string][]string{"accept": {"application/json"}},
}

Expand All @@ -34,7 +41,7 @@ func TestHttpConnection(t *testing.T) {
results, err := httpCapability.Execute(
metadata, expectedCommand,
cacao.AuthenticationInformation{},
cacao.AgentTarget{},
target,
cacao.NewVariables(variable1))
if err != nil {
fmt.Println(err)
Expand All @@ -45,31 +52,26 @@ func TestHttpConnection(t *testing.T) {
}

func TestHttpOAuth2(t *testing.T) {
httpCapability := new(http.HttpCapability)
request := httpUtil.HttpRequest{}
httpCapability := http.New(&request)

var oauth2_info = cacao.AuthenticationInformation{
ID: "6ba7b810-9dad-11d1-80b4-00c04fd430c9",
Type: "oauth2",
Token: "this-is-a-test",
}
bearerToken := "test_token"

expectedCommand := cacao.Command{
Type: "http-api",
Command: "GET https://httpbin.org/bearer",
Headers: map[string][]string{"accept": {"application/json"}},
target := cacao.AgentTarget{
Address: map[cacao.NetAddressType][]string{
"url": {"https://httpbin.org/bearer"},
},
AuthInfoIdentifier: "d0c7e6a0-f7fe-464e-9935-e6b3443f5b91",
}

var variable1 = cacao.Variable{
Type: "string",
Name: "test_auth",
Value: "",
auth := cacao.AuthenticationInformation{
Type: cacao.AuthInfoOAuth2Type,
Token: bearerToken,
ID: "d0c7e6a0-f7fe-464e-9935-e6b3443f5b91",
}

var target = cacao.AgentTarget{
ID: "6ba7b810-9dad-11d1-80b4-00c04fd430c0",
Type: "http-api",
Name: "Cybersec APIs",
AuthInfoIdentifier: "6ba7b810-9dad-11d1-80b4-00c04fd430c9",
command := cacao.Command{
Type: "http-api",
Command: "GET / HTTP/1.1",
Headers: map[string][]string{"accept": {"application/json"}},
}

var executionId, _ = uuid.Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
Expand All @@ -78,10 +80,10 @@ func TestHttpOAuth2(t *testing.T) {
metadata := execution.Metadata{ExecutionId: executionId, PlaybookId: playbookId.String(), StepId: stepId.String()}
results, err := httpCapability.Execute(
metadata,
expectedCommand,
oauth2_info,
command,
auth,
target,
cacao.NewVariables(variable1))
cacao.NewVariables())
if err != nil {
fmt.Println(err)
t.Fail()
Expand All @@ -90,43 +92,41 @@ func TestHttpOAuth2(t *testing.T) {
}

func TestHttpBasicAuth(t *testing.T) {
httpCapability := new(http.HttpCapability)
request := httpUtil.HttpRequest{}
httpCapability := http.New(&request)
username := "test"
password := "password"
url := fmt.Sprintf("https://httpbin.org/basic-auth/%s/%s", username, password)

target := cacao.AgentTarget{
Address: map[cacao.NetAddressType][]string{
"url": []string{url},
},
AuthInfoIdentifier: "d0c7e6a0-f7fe-464e-9935-e6b3443f5b91",
}

var basicauth_info = cacao.AuthenticationInformation{
ID: "6ba7b810-9dad-11d1-80b4-00c04fd430c9",
Type: "http-basic",
Username: "username_test",
Password: "password_test",
auth := cacao.AuthenticationInformation{
Type: cacao.AuthInfoHTTPBasicType,
Username: username,
Password: password,
ID: "d0c7e6a0-f7fe-464e-9935-e6b3443f5b91",
}

expectedCommand := cacao.Command{
command := cacao.Command{
Type: "http-api",
Command: "GET https://httpbin.org/hidden-basic-auth/username_test/password_test",
Command: "GET / HTTP/1.1",
Headers: map[string][]string{"accept": {"application/json"}},
}

var variable1 = cacao.Variable{
Type: "string",
Name: "test_auth",
Value: "",
}

var target = cacao.AgentTarget{
ID: "6ba7b810-9dad-11d1-80b4-00c04fd430c0",
Type: "http-api",
Name: "Cybersec APIs",
AuthInfoIdentifier: "6ba7b810-9dad-11d1-80b4-00c04fd430c9",
}
var executionId, _ = uuid.Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
var playbookId, _ = uuid.Parse("d09351a2-a075-40c8-8054-0b7c423db83f")
var stepId, _ = uuid.Parse("81eff59f-d084-4324-9e0a-59e353dbd28f")
metadata := execution.Metadata{ExecutionId: executionId, PlaybookId: playbookId.String(), StepId: stepId.String()}
results, err := httpCapability.Execute(
metadata,
expectedCommand,
basicauth_info,
command,
auth,
target,
cacao.NewVariables(variable1))
cacao.NewVariables())
if err != nil {
fmt.Println(err)
t.Fail()
Expand Down
Loading

0 comments on commit 382a3e7

Please sign in to comment.