forked from linuxboot/contest
-
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.
Signed-off-by: Fabian Wienand <[email protected]>
- Loading branch information
Fabian Wienand
committed
Aug 20, 2024
1 parent
320e5e6
commit a2452e8
Showing
3 changed files
with
284 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package binarly | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/linuxboot/contest/pkg/event" | ||
"github.com/linuxboot/contest/pkg/event/testevent" | ||
"github.com/linuxboot/contest/pkg/events" | ||
"github.com/linuxboot/contest/pkg/test" | ||
"github.com/linuxboot/contest/pkg/xcontext" | ||
"github.com/linuxboot/contest/plugins/teststeps" | ||
"github.com/linuxboot/contest/plugins/teststeps/abstraction/options" | ||
) | ||
|
||
const ( | ||
parametersKeyword = "parameters" | ||
) | ||
|
||
const ( | ||
defaultTimeout time.Duration = 60 * time.Minute | ||
) | ||
|
||
type parameters struct { | ||
Token string `json:"token"` | ||
File string `json:"file_path"` | ||
} | ||
|
||
// Name is the name used to look this plugin up. | ||
var Name = "Binarly Report" | ||
|
||
// TestStep implementation for the exec plugin | ||
type TestStep struct { | ||
parameters | ||
options options.Parameters | ||
} | ||
|
||
// Run executes the step. | ||
func (ts *TestStep) Run(ctx xcontext.Context, ch test.TestStepChannels, params test.TestStepParameters, ev testevent.Emitter, resumeState json.RawMessage) (json.RawMessage, error) { | ||
tr := NewTargetRunner(ts, ev) | ||
return teststeps.ForEachTarget(Name, ctx, ch, tr.Run) | ||
} | ||
|
||
func (ts *TestStep) populateParams(stepParams test.TestStepParameters) error { | ||
var parameters, optionsParams *test.Param | ||
|
||
if parameters = stepParams.GetOne(parametersKeyword); parameters.IsEmpty() { | ||
return fmt.Errorf("parameters cannot be empty") | ||
} | ||
|
||
if err := json.Unmarshal(parameters.JSON(), &ts.parameters); err != nil { | ||
return fmt.Errorf("failed to deserialize parameters: %v", err) | ||
} | ||
|
||
optionsParams = stepParams.GetOne(options.Keyword) | ||
|
||
if !optionsParams.IsEmpty() { | ||
if err := json.Unmarshal(optionsParams.JSON(), &ts.options); err != nil { | ||
return fmt.Errorf("failed to deserialize options: %v", err) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// ValidateParameters validates the parameters associated to the step | ||
func (ts *TestStep) ValidateParameters(_ xcontext.Context, stepParams test.TestStepParameters) error { | ||
return ts.populateParams(stepParams) | ||
} | ||
|
||
// New initializes and returns a new exec step. | ||
func New() test.TestStep { | ||
return &TestStep{} | ||
} | ||
|
||
// Load returns the name, factory and events which are needed to register the step. | ||
func Load() (string, test.TestStepFactory, []event.Name) { | ||
return Name, New, events.Events | ||
} |
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,33 @@ | ||
package binarly | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"time" | ||
) | ||
|
||
// Name returns the name of the Step | ||
func (ts TestStep) Name() string { | ||
return Name | ||
} | ||
|
||
// Function to format teststep information and append it to a string builder. | ||
func (ts TestStep) writeTestStep(builders ...*strings.Builder) { | ||
for _, builder := range builders { | ||
|
||
builder.WriteString("\n") | ||
|
||
builder.WriteString(" Parameter:\n") | ||
builder.WriteString(" Token: *hidden*\n") | ||
builder.WriteString(fmt.Sprintf(" File: %s\n", ts.parameters.File)) | ||
builder.WriteString("\n") | ||
|
||
builder.WriteString(" Options:\n") | ||
builder.WriteString(fmt.Sprintf(" Timeout: %s\n", time.Duration(ts.options.Timeout))) | ||
builder.WriteString("\n") | ||
|
||
builder.WriteString("Default Values:\n") | ||
builder.WriteString(fmt.Sprintf(" Timeout: %s\n", defaultTimeout)) | ||
builder.WriteString("\n\n") | ||
} | ||
} |
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,171 @@ | ||
package binarly | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"mime/multipart" | ||
"net/http" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
"time" | ||
|
||
"github.com/linuxboot/contest/pkg/event/testevent" | ||
"github.com/linuxboot/contest/pkg/events" | ||
"github.com/linuxboot/contest/pkg/target" | ||
"github.com/linuxboot/contest/pkg/xcontext" | ||
"github.com/linuxboot/contest/plugins/teststeps/abstraction/options" | ||
) | ||
|
||
type Error struct { | ||
Msg string `json:"error"` | ||
} | ||
|
||
type TargetRunner struct { | ||
ts *TestStep | ||
ev testevent.Emitter | ||
} | ||
|
||
func NewTargetRunner(ts *TestStep, ev testevent.Emitter) *TargetRunner { | ||
return &TargetRunner{ | ||
ts: ts, | ||
ev: ev, | ||
} | ||
} | ||
|
||
type StartScanResponse struct { | ||
ID string `json:"id"` | ||
Status string `json:"status"` | ||
} | ||
|
||
func (r *TargetRunner) Run(ctx xcontext.Context, target *target.Target) error { | ||
var outputBuf strings.Builder | ||
|
||
ctx, cancel := options.NewOptions(ctx, defaultTimeout, r.ts.options.Timeout) | ||
defer cancel() | ||
|
||
outputBuf.WriteString(fmt.Sprintf("Running test step %s\n", Name)) | ||
|
||
if r.ts.Token == "" { | ||
err := fmt.Errorf("you need to provide a token") | ||
outputBuf.WriteString(fmt.Sprintf("%v", err)) | ||
|
||
return events.EmitError(ctx, outputBuf.String(), target, r.ev) | ||
} | ||
|
||
client := &http.Client{} | ||
|
||
id, err := postFileToAPI(ctx, client, r.ts.File, r.ts.Token) | ||
if err != nil { | ||
outputBuf.WriteString(fmt.Sprintf("Failed to post file to binarly: %v", err)) | ||
|
||
return events.EmitError(ctx, outputBuf.String(), target, r.ev) | ||
} | ||
|
||
result, err := awaitResult(ctx, client, id, r.ts.Token) | ||
if err != nil { | ||
outputBuf.WriteString(fmt.Sprintf("Failed to get results from binarly: %v", err)) | ||
|
||
return events.EmitError(ctx, outputBuf.String(), target, r.ev) | ||
} | ||
|
||
var data json.RawMessage | ||
events.EmitOuput(ctx, "binarly", data, target, r.ev) | ||
|
||
events.EmitLog(ctx, outputBuf.String(), target, r.ev) | ||
|
||
events.EmitLog(ctx, outputBuf.String(), target, r.ev) | ||
|
||
return nil | ||
} | ||
|
||
func postFileToAPI(ctx context.Context, client *http.Client, filePath string, token string) (string, error) { | ||
// Prepare the file to be uploaded | ||
file, err := os.Open(filePath) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to open file: %v", err) | ||
} | ||
defer file.Close() | ||
|
||
// Create a new multipart form request | ||
body := &bytes.Buffer{} | ||
writer := multipart.NewWriter(body) | ||
part, err := writer.CreateFormFile("file", filepath.Base(file.Name())) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to create form file: %v", err) | ||
} | ||
_, err = io.Copy(part, file) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to copy file: %v", err) | ||
} | ||
writer.Close() | ||
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://risk-hunt-production-serve-y6yijivpnq-uc.a.run.app/scan", body) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to create request: %v", err) | ||
} | ||
|
||
req.Header.Set("Authorization", "Bearer "+token) | ||
req.Header.Set("Content-Type", writer.FormDataContentType()) | ||
|
||
resp, err := client.Do(req) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to send request: %v", err) | ||
} | ||
defer resp.Body.Close() | ||
|
||
var response StartScanResponse | ||
json.NewDecoder(resp.Body).Decode(&response) | ||
if err = json.NewDecoder(resp.Body).Decode(&response); err != nil { | ||
return "", fmt.Errorf("failed to decode response: %v", err) | ||
} | ||
|
||
if response.Status != "ok" { | ||
return "", fmt.Errorf("failed to start scan: %v", response.Status) | ||
} | ||
|
||
return response.ID, nil | ||
} | ||
|
||
type ScanStatusResponse struct { | ||
FileName string `json:"file_name"` | ||
Scan json.RawMessage `json:"scan"` | ||
Ratings json.RawMessage `json:"ratings"` | ||
Status string `json:"status"` | ||
UploadTime string `json:"upload_time"` | ||
} | ||
|
||
func awaitResult(ctx context.Context, client *http.Client, id string, token string) (json.RawMessage, error) { | ||
var statusResponse ScanStatusResponse | ||
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("https://risk-hunt-production-serve-y6yijivpnq-uc.a.run.app/scan/%s", id), nil) | ||
if err != nil { | ||
return json.RawMessage{}, fmt.Errorf("failed to create request: %v", err) | ||
} | ||
req.Header.Set("Authorization", "Bearer "+token) | ||
|
||
for { | ||
select { | ||
case <-ctx.Done(): | ||
return json.RawMessage{}, fmt.Errorf("timed out waiting for results") | ||
case <-time.After(30 * time.Second): | ||
resp, err := client.Do(req) | ||
if err != nil { | ||
return json.RawMessage{}, fmt.Errorf("failed to send request: %v", err) | ||
} | ||
defer resp.Body.Close() | ||
|
||
if err = json.NewDecoder(resp.Body).Decode(&statusResponse); err != nil { | ||
return json.RawMessage{}, fmt.Errorf("failed to decode response: %v", err) | ||
} | ||
|
||
// Check the status | ||
if statusResponse.Status == "ok" { | ||
return statusResponse.Scan, nil | ||
} | ||
} | ||
} | ||
} |