Skip to content

Commit

Permalink
Merge pull request #239 from bugsnag/integrity_header
Browse files Browse the repository at this point in the history
Add integrity header, change to command pattern in test app
  • Loading branch information
DariaKunoichi authored Aug 21, 2024
2 parents fe77eac + d9b0f51 commit 2be2a54
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 133 deletions.
18 changes: 8 additions & 10 deletions features/fixtures/app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ FROM golang:${GO_VERSION}-alpine
RUN apk update && apk upgrade && apk add git bash build-base

ENV GOPATH /app
ENV GO111MODULE="on"

COPY testbuild /app/src/github.com/bugsnag/bugsnag-go
COPY features /app/src/features
COPY v2 /app/src/github.com/bugsnag/bugsnag-go/v2
WORKDIR /app/src/github.com/bugsnag/bugsnag-go/v2

# Ensure subsequent steps are re-run if the GO_VERSION variable changes
Expand All @@ -20,17 +22,13 @@ RUN if [[ $(echo -e "1.11\n$GO_VERSION\n1.16" | sort -V | head -2 | tail -1) ==
go install ./...; \
fi

# Copy test scenarios
COPY ./app /app/src/test
WORKDIR /app/src/test
WORKDIR /app/src/features/fixtures/app

# Create app module - avoid locking bugsnag dep by not checking it in
# Skip on old versions of Go which pre-date modules
RUN if [[ $GO_VERSION != '1.11' && $GO_VERSION != '1.12' ]]; then \
go mod init && go mod tidy; \
echo "replace github.com/bugsnag/bugsnag-go/v2 => /app/src/github.com/bugsnag/bugsnag-go/v2" >> go.mod; \
go mod tidy; \
fi
RUN go mod init && go mod tidy && \
echo "replace github.com/bugsnag/bugsnag-go/v2 => /app/src/github.com/bugsnag/bugsnag-go/v2" >> go.mod && \
go mod tidy

RUN chmod +x run.sh
CMD ["/app/src/test/run.sh"]
CMD ["/app/src/features/fixtures/app/run.sh"]
42 changes: 42 additions & 0 deletions features/fixtures/app/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import (
"encoding/json"
"fmt"
"net/http"
"time"
)

const DEFAULT_MAZE_ADDRESS = "http://localhost:9339"

type Command struct {
Action string `json:"action,omitempty"`
ScenarioName string `json:"scenario_name,omitempty"`
APIKey string `json:"api_key,omitempty"`
NotifyEndpoint string `json:"notify_endpoint,omitempty"`
SessionsEndpoint string `json:"sessions_endpoint,omitempty"`
UUID string `json:"uuid,omitempty"`
RunUUID string `json:"run_uuid,omitempty"`
}

func GetCommand(mazeAddress string) Command {
var command Command
mazeURL := fmt.Sprintf("%+v/command", mazeAddress)
client := http.Client{Timeout: 2 * time.Second}
res, err := client.Get(mazeURL)
if err != nil {
fmt.Printf("[Bugsnag] Error while receiving command: %+v\n", err)
return command
}

if res != nil {
err = json.NewDecoder(res.Body).Decode(&command)
res.Body.Close()
if err != nil {
fmt.Printf("[Bugsnag] Error while decoding command: %+v\n", err)
return command
}
}

return command
}
127 changes: 33 additions & 94 deletions features/fixtures/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,115 +2,54 @@ package main

import (
"context"
"flag"
"fmt"
"log"
"os"
"runtime"
"strconv"
"strings"
"time"

bugsnag "github.com/bugsnag/bugsnag-go/v2"
)

func configureBasicBugsnag(testcase string, ctx context.Context) {
config := bugsnag.Configuration{
APIKey: os.Getenv("API_KEY"),
AppVersion: os.Getenv("APP_VERSION"),
AppType: os.Getenv("APP_TYPE"),
Hostname: os.Getenv("HOSTNAME"),
MainContext: ctx,
}

if notifyReleaseStages := os.Getenv("NOTIFY_RELEASE_STAGES"); notifyReleaseStages != "" {
config.NotifyReleaseStages = strings.Split(notifyReleaseStages, ",")
}
var scenariosMap = map[string]func(Command) func(){}

if releaseStage := os.Getenv("RELEASE_STAGE"); releaseStage != "" {
config.ReleaseStage = releaseStage
}

if filters := os.Getenv("PARAMS_FILTERS"); filters != "" {
config.ParamsFilters = []string{filters}
}

sync, err := strconv.ParseBool(os.Getenv("SYNCHRONOUS"))
if err == nil {
config.Synchronous = sync
}

acs, err := strconv.ParseBool(os.Getenv("AUTO_CAPTURE_SESSIONS"))
if err == nil {
config.AutoCaptureSessions = acs
func main() {
addr := os.Getenv("DEFAULT_MAZE_ADDRESS")
if addr == "" {
addr = DEFAULT_MAZE_ADDRESS
}

switch testcase {
case "endpoint-notify":
config.Endpoints = bugsnag.Endpoints{Notify: os.Getenv("BUGSNAG_ENDPOINT")}
case "endpoint-session":
config.Endpoints = bugsnag.Endpoints{Sessions: os.Getenv("BUGSNAG_ENDPOINT")}
default:
config.Endpoints = bugsnag.Endpoints{
Notify: os.Getenv("BUGSNAG_ENDPOINT"),
Sessions: os.Getenv("BUGSNAG_ENDPOINT"),
}
endpoints := bugsnag.Endpoints{
Notify: fmt.Sprintf("%+v/notify", addr),
Sessions: fmt.Sprintf("%+v/sessions", addr),
}
bugsnag.Configure(config)

time.Sleep(200 * time.Millisecond)
// HAS TO RUN FIRST BECAUSE OF PANIC WRAP
// https://github.com/bugsnag/panicwrap/blob/master/panicwrap.go#L177-L203
bugsnag.Configure(bugsnag.Configuration{
APIKey: "166f5ad3590596f9aa8d601ea89af845",
Endpoints: endpoints,
})
// Increase publish rate for testing
bugsnag.DefaultSessionPublishInterval = time.Millisecond * 100
}

func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

test := flag.String("test", "handled", "what the app should send, either handled, unhandled, session, autonotify")
flag.Parse()

configureBasicBugsnag(*test, ctx)
time.Sleep(100 * time.Millisecond) // Ensure tests are less flaky by ensuring the start-up session gets sent

switch *test {
case "unhandled":
unhandledCrash()
case "handled", "endpoint-legacy", "endpoint-notify", "endpoint-session":
handledError()
case "handled-with-callback":
handledCallbackError()
case "session":
session()
case "autonotify":
autonotify()
case "metadata":
metadata()
case "onbeforenotify":
onBeforeNotify()
case "filtered":
filtered()
case "recover":
dontDie()
case "session-and-error":
sessionAndError()
case "send-and-exit":
sendAndExit()
case "user":
user()
case "multiple-handled":
multipleHandled()
case "multiple-unhandled":
multipleUnhandled()
case "make-unhandled-with-callback":
handledToUnhandled()
case "nested-error":
nestedHandledError()
default:
log.Println("Not a valid test flag: " + *test)
os.Exit(1)
bugsnag.DefaultSessionPublishInterval = time.Millisecond * 50

// Listening to the OS Signals
ticker := time.NewTicker(1 * time.Second)
for {
select {
case <-ticker.C:
command := GetCommand(addr)
fmt.Printf("[Bugsnag] Received command: %+v\n", command)
if command.Action != "run-scenario" {
continue
}
prepareScenarioFunc, ok := scenariosMap[command.ScenarioName]
if ok {
scenarioFunc := prepareScenarioFunc(command)
scenarioFunc()
time.Sleep(200 * time.Millisecond)
}
}
}

}

func multipleHandled() {
Expand Down
6 changes: 3 additions & 3 deletions features/fixtures/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
version: '3.4'
services:
app:
build:
context: .
dockerfile: app/Dockerfile
context: ../../
dockerfile: ./features/fixtures/app/Dockerfile
args:
- GO_VERSION
environment:
- DEFAULT_MAZE_ADDRESS
- API_KEY
- ERROR_CLASS
- BUGSNAG_ENDPOINT
Expand Down
41 changes: 41 additions & 0 deletions features/steps/go_steps.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,45 @@
require 'net/http'
require 'os'

When('I run {string}') do |scenario_name|
execute_command 'run-scenario', scenario_name
end

When('I configure the base endpoint') do
steps %(
When I set environment variable "DEFAULT_MAZE_ADDRESS" to "http://#{local_ip}:9339"
)
end

def execute_command(action, scenario_name = '')
address = $address ? $address : "#{local_ip}:9339"

command = {
action: action,
scenario_name: scenario_name,
notify_endpoint: "http://#{address}/notify",
sessions_endpoint: "http://#{address}/sessions",
api_key: $api_key,
}

$logger.debug("Queuing command: #{command}")
Maze::Server.commands.add command

# Ensure fixture has read the command
count = 900
sleep 0.1 until Maze::Server.commands.remaining.empty? || (count -= 1) < 1
raise 'Test fixture did not GET /command' unless Maze::Server.commands.remaining.empty?
end

def local_ip
if OS.mac?
'host.docker.internal'
else
ip_addr = `ifconfig | grep -Eo 'inet (addr:)?([0-9]*\\\.){3}[0-9]*' | grep -v '127.0.0.1'`
ip_list = /((?:[0-9]*\.){3}[0-9]*)/.match(ip_addr)
ip_list.captures.first
end
end

Given("I set environment variable {string} to the app directory") do |key|
step("I set environment variable \"#{key}\" to \"/app/src/test/\"")
Expand Down
23 changes: 7 additions & 16 deletions features/support/env.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
require 'fileutils'
require 'socket'
require 'timeout'

testBuildFolder = 'features/fixtures/testbuild'

FileUtils.rm_rf(testBuildFolder)
Dir.mkdir testBuildFolder

# Copy the existing dir
`find . -name '*.go' -o -name 'go.sum' -o -name 'go.mod' \
-not -path "./examples/*" \
-not -path "./testutil/*" \
-not -path "./v2/testutil/*" \
-not -path "./features/*" \
-not -name '*_test.go' | cpio -pdm #{testBuildFolder}`
Before do
$address = nil
$api_key = "166f5ad3590596f9aa8d601ea89af845"
steps %(
When I configure the base endpoint
)
end
14 changes: 10 additions & 4 deletions v2/headers/prefixed.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package headers

import "time"
import (
"fmt"
"time"
)

// PrefixedHeaders returns a map of Content-Type and the 'Bugsnag-' headers for
// API key, payload version, and the time at which the request is being sent.
func PrefixedHeaders(apiKey, payloadVersion, sha1 string) map[string]string {
integrityHeader := fmt.Sprintf("sha1 %v", sha1)

//PrefixedHeaders returns a map of Content-Type and the 'Bugsnag-' headers for
//API key, payload version, and the time at which the request is being sent.
func PrefixedHeaders(apiKey, payloadVersion string) map[string]string {
return map[string]string{
"Content-Type": "application/json",
"Bugsnag-Api-Key": apiKey,
"Bugsnag-Payload-Version": payloadVersion,
"Bugsnag-Sent-At": time.Now().UTC().Format(time.RFC3339),
"Bugsnag-Integrity": integrityHeader,
}
}
7 changes: 5 additions & 2 deletions v2/headers/prefixed_test.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
package headers

import (
"fmt"
"strings"
"testing"
"time"
)

const APIKey = "abcd1234abcd1234"
const testPayloadVersion = "3"
const testSHA = "5e13ae4640ae4ae0e09c05b7bb060f544dabd042"

func TestConstantBugsnagPrefixedHeaders(t *testing.T) {
headers := PrefixedHeaders(APIKey, testPayloadVersion)
headers := PrefixedHeaders(APIKey, testPayloadVersion, testSHA)
testCases := []struct {
header string
expected string
}{
{header: "Content-Type", expected: "application/json"},
{header: "Bugsnag-Api-Key", expected: APIKey},
{header: "Bugsnag-Payload-Version", expected: testPayloadVersion},
{header: "Bugsnag-Integrity", expected: fmt.Sprintf("sha1 %v", testSHA)},
}
for _, tc := range testCases {
t.Run(tc.header, func(st *testing.T) {
Expand All @@ -29,7 +32,7 @@ func TestConstantBugsnagPrefixedHeaders(t *testing.T) {
}

func TestTimeDependentBugsnagPrefixedHeaders(t *testing.T) {
headers := PrefixedHeaders(APIKey, testPayloadVersion)
headers := PrefixedHeaders(APIKey, testPayloadVersion, testSHA)
sentAtString := headers["Bugsnag-Sent-At"]
if !strings.HasSuffix(sentAtString, "Z") {
t.Errorf("Error when setting Bugsnag-Sent-At header: %s, doesn't end with a Z", sentAtString)
Expand Down
Loading

0 comments on commit 2be2a54

Please sign in to comment.