diff --git a/README.md b/README.md index 9bd1dbd..ffd5d3e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,45 @@ -# Mini-Scan +# Mini-Scan Submission + +A submission of the mini-scan take home assignment described below. + +-- + +The solution was implemented using an sqlite3 database. + +One challenging aspect of the project was an issue with running sqlite3 in docker with a CGO_ENABLED=0. + +To overcome this obstacle, a base image that supports CGO_ENABLED=1 (frolvlad/alpine-glibc) was used. + +Next steps include: + +1) Switching to a PostgreSQL database. +2) Handling failures with a DLQ and/or passing the timestamp in the message to make sure the most recent update is saved. +3) Pushing to the cloud to test horizontal scaling which the emulater does not support. +4) Actually scan the local network. + +--- + +To test changes to `cmd/proessor/main.go` by examining the processor's collection of scan results run + +`docker system prune --all --volumes --force` + +`docker compose up --build` # this runs unit tests + +`docker-compose run --entrypoint /bin/sh processor` + +`sqlite3 /data/processor.db` + +`select * from scans` + + +Unit tests are available in cmd/processor and pkg/scanning for your enjoyment. + +Thank You and have a great day! + + +----- + +# Original Assignment Hello! diff --git a/cmd/processor/Dockerfile b/cmd/processor/Dockerfile new file mode 100644 index 0000000..e29107d --- /dev/null +++ b/cmd/processor/Dockerfile @@ -0,0 +1,18 @@ +FROM golang:1.20 AS builder + +# Build +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download && go mod verify +COPY . . +CMD ["go", "test", "-v", "./..."] +CMD ["go", "test", "-v", "pkg/scanning/..."] + +RUN CGO_ENABLED=1 go build -o processor ./cmd/processor + +FROM frolvlad/alpine-glibc +RUN apk update && apk add --no-cache sqlite sqlite-dev +WORKDIR app +COPY --from=builder /src/processor . +EXPOSE 8080 +CMD ["/app/processor"] diff --git a/cmd/processor/main.go b/cmd/processor/main.go new file mode 100644 index 0000000..8f3b711 --- /dev/null +++ b/cmd/processor/main.go @@ -0,0 +1,105 @@ +package main + +import ( + "context" + "encoding/json" + "flag" + "time" + + "database/sql" + _ "github.com/mattn/go-sqlite3" + + "cloud.google.com/go/pubsub" + "github.com/censys/scan-takehome/pkg/scanning" + +) + +var ( + services = []string{"HTTP", "SSH", "DNS"} // conider sharing with scanner and performing validation here + dbFile = flag.String("db", "/data/processor.db", "Database File") +) + + +// Interfaces to allow for mocking +// Consider using gomock to use *sql.DB instead of DBInterface and +// *pubsub.Subscription instead of SubscriptionInterface + +type DBInterface interface { + Exec(query string, args ...interface{}) (sql.Result, error) +} +type SubscriptionInterface interface { + Receive(ctx context.Context, f func(ctx context.Context, msg *pubsub.Message)) error +} + + +// main loop that recieves messsges and inserts/updates them in database + +func main_loop(ctx context.Context, subscription SubscriptionInterface, db DBInterface) { + err := subscription.Receive(ctx, func(ctx context.Context, msg *pubsub.Message) { + var scan scanning.Scan + if err := json.Unmarshal(msg.Data, &scan); err != nil { + panic(err) + } + + var serviceResp string + + switch scan.DataVersion { + case scanning.V1: + serviceResp = scan.ParseV1Data() + case scanning.V2: + serviceResp = scan.ParseV2Data() + default: + panic("Invalid DataVersion in scan") + } + + // consider including timestamp in scan so retried messages don't overwrite new ones + _, err := db.Exec("INSERT OR REPLACE INTO scans (ip, port, service, response, scan_time) VALUES (?, ?, ?, ?, ?)", + scan.Ip, scan.Port, scan.Service, serviceResp, time.Now()) + + if err != nil { + panic("Database insert failed") + } + + msg.Ack() + }) + + if err != nil { + panic(err) + } + +} + +func main() { + projectId := flag.String("project", "test-project", "GCP Project ID") + subscriptionId := flag.String("subscription", "scan-sub", "GCP PubSub Subscription ID") + flag.Parse() + + ctx := context.Background() + + client, err := pubsub.NewClient(ctx, *projectId) + if err != nil { + panic(err) + } + + subscription := client.Subscription(*subscriptionId) + + db, err := sql.Open("sqlite3", *dbFile) + if err != nil { + panic(err) + } + + createTableSQL := `CREATE TABLE IF NOT EXISTS scans ( + ip TEXT NOT NULL, + port INTEGER NOT NULL, + service TEXT NOT NULL, + response TEXT NOT NULL, + scan_time DATETIME NOT NULL, + PRIMARY KEY (ip, port, service) + );` + + _, err = db.Exec(createTableSQL) + if err != nil { + panic(err) + } + main_loop(ctx, subscription, db) +} diff --git a/cmd/processor/main_test.go b/cmd/processor/main_test.go new file mode 100644 index 0000000..de0ad25 --- /dev/null +++ b/cmd/processor/main_test.go @@ -0,0 +1,88 @@ +package main + +import ( + "context" + "database/sql" + "testing" + "fmt" + + "github.com/golang/mock/gomock" + "cloud.google.com/go/pubsub" + "github.com/stretchr/testify/assert" +) + +// SubscriptionReceiver is an interface that mocks pubsub.Subscription's methods +type SubscriptionReceiver interface { + Receive(ctx context.Context, f func(ctx context.Context, msg *pubsub.Message)) error +} + +// MockDB is used to mock the database interactions for testing +type MockDB struct { + // Mock DB connection + ExecFunc func(query string, args ...interface{}) (sql.Result, error) +} + +func (m *MockDB) Exec(query string, args ...interface{}) (sql.Result, error) { + return m.ExecFunc(query, args...) +} + +// MockSubscription is used to mock pubsub.Subscription for testing +type MockSubscription struct { + ReceiveFunc func(ctx context.Context, handler func(ctx context.Context, msg *pubsub.Message)) error +} + +func (m *MockSubscription) Receive(ctx context.Context, handler func(ctx context.Context, msg *pubsub.Message)) error { + return m.ReceiveFunc(ctx, handler) +} + +func TestMainLoop(t *testing.T) { + t.Log("Starting test of main_loop") + // Mock context + ctx := context.Background() + + // Create mock controller + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + // Mock database interaction + mockDB := &MockDB{ + ExecFunc: func(query string, args ...interface{}) (sql.Result, error) { + t.Logf("In MockDB insert %s, %s", query, args) + // Simulate a successful database insert + if query == "INSERT OR REPLACE INTO scans (ip, port, service, response, scan_time) VALUES (?, ?, ?, ?, ?)" && + args[3] != "abdul" { + t.Log("Succesful Insert") + return nil, nil + } + t.Log("In MockDB error condition") + return nil, fmt.Errorf("database insert failed") + }, + } + + // Mock Pub/Sub subscription + mockSub := &MockSubscription{ + ReceiveFunc: func(ctx context.Context, handler func(ctx context.Context, msg *pubsub.Message)) error { + // Create a mock Pub/Sub message + mockMsg1 := &pubsub.Message{ + Data: []byte(`{"ip":"192.168.1.1","port":80,"service":"HTTP","data_version":1,"data":{"response_bytes_utf8":[97,98,99,100]}}`), + } + + // Create second mock Pub/Sub message (error case) + mockMsg2 := &pubsub.Message{ + Data: []byte(`{"ip":"192.168.1.1","port":80,"service":"HTTP","data_version":2,"data":{"response_str":"abdul"}}`), + } + + t.Log("Sending first message") + handler(ctx, mockMsg1) // Simulate receiving message 1 + t.Log("Sending second message") + handler(ctx, mockMsg2) // Simulate receiving message 2 + + return nil + }, + } + + // Test that the first message was processed correctly and inserted into the database and the second message panics + + assert.Panics(t, func() { main_loop(ctx, mockSub, mockDB) }, "Function should panic") + +} diff --git a/cmd/processor/processor b/cmd/processor/processor new file mode 100755 index 0000000..5ce9d90 Binary files /dev/null and b/cmd/processor/processor differ diff --git a/cmd/scanner/main.go b/cmd/scanner/main.go index 3623ef7..6f999bd 100644 --- a/cmd/scanner/main.go +++ b/cmd/scanner/main.go @@ -19,6 +19,7 @@ var ( func main() { projectId := flag.String("project", "test-project", "GCP Project ID") topicId := flag.String("topic", "scan-topic", "GCP PubSub Topic ID") + flag.Parse() ctx := context.Background() diff --git a/docker-compose.yml b/docker-compose.yml index 4b5c5f3..75220a6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3' services: # Starts the P/S emulator @@ -40,3 +39,22 @@ services: build: context: . dockerfile: ./cmd/scanner/Dockerfile + + # Runs the "processor" + processor: + depends_on: + mk-subscription: + condition: service_completed_successfully + environment: + PUBSUB_EMULATOR_HOST: pubsub:8085 + PUBSUB_PROJECT_ID: test-project + DB_PATH: /data/processor.db + build: + context: . + dockerfile: ./cmd/processor/Dockerfile + volumes: + - processor-data:/data + +volumes: + processor-data: + diff --git a/go.mod b/go.mod index 9f43ddb..df79862 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,18 @@ module github.com/censys/scan-takehome go 1.20 -require cloud.google.com/go/pubsub v1.33.0 +require ( + cloud.google.com/go/pubsub v1.33.0 + github.com/golang/mock v1.6.0 + github.com/matryer/is v1.4.1 + github.com/stretchr/testify v1.10.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) require ( cloud.google.com/go v0.110.2 // indirect @@ -15,6 +26,7 @@ require ( github.com/google/s2a-go v0.1.4 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.11.0 // indirect + github.com/mattn/go-sqlite3 v1.14.20 go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.17.0 // indirect golang.org/x/net v0.17.0 // indirect diff --git a/go.sum b/go.sum index eeaf1cd..7b5358c 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,7 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -36,6 +37,8 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -69,6 +72,11 @@ github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5 github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-sqlite3 v1.14.20 h1:BAZ50Ns0OFBNxdAqFhbZqdPcht1Xlb16pDCqkq1spr0= +github.com/mattn/go-sqlite3 v1.14.20/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -80,11 +88,15 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -94,6 +106,7 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -106,6 +119,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= @@ -118,6 +132,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -127,7 +142,9 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -149,8 +166,10 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= @@ -192,10 +211,12 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/scanning/types.go b/pkg/scanning/types.go index 5cbfd7b..506bfb0 100644 --- a/pkg/scanning/types.go +++ b/pkg/scanning/types.go @@ -1,5 +1,9 @@ package scanning +import ( + "encoding/json" +) + const ( Version = iota V1 @@ -22,3 +26,36 @@ type V1Data struct { type V2Data struct { ResponseStr string `json:"response_str"` } + + +// parse out any scan's service data +func (scan Scan) parseData() ([]byte) { + dataMap := scan.Data.(map[string]interface{}) + dataBytes, err := json.Marshal(dataMap) + if err != nil { + panic(err) + } + return dataBytes +} + +func (scan Scan) ParseV1Data() string { + var respData V1Data + dataBytes := scan.parseData() + err := json.Unmarshal(dataBytes, &respData) + if err != nil { + panic(err) + } + return string(respData.ResponseBytesUtf8) +} + +func (scan Scan) ParseV2Data() string { + var respData V2Data + dataBytes := scan.parseData() + err := json.Unmarshal(dataBytes, &respData) + if err != nil { + panic(err) + } + return string(respData.ResponseStr) +} + + diff --git a/pkg/scanning/types_test.go b/pkg/scanning/types_test.go new file mode 100644 index 0000000..0b8639a --- /dev/null +++ b/pkg/scanning/types_test.go @@ -0,0 +1,102 @@ +package scanning + +import ( + "encoding/json" + "testing" + "github.com/stretchr/testify/assert" +) + +// Mock data for V1 and V2 responses +var v1Response = `{"response_bytes_utf8": [65, 66, 67]}` +var v2Response = `{"response_str": "Success"}` + +func TestParseData(t *testing.T) { + scan := Scan{ + Ip: "192.168.1.1", + Port: 8080, + Service: "http", + Timestamp: 1617181723, + DataVersion: V1, + Data: map[string]interface{}{ + "response_bytes_utf8": []byte{97, 98, 100, 117, 108}, + }, + } + + dataBytes := scan.parseData() + var response V1Data + err := json.Unmarshal(dataBytes, &response) + assert.NoError(t, err, "unmarshaling data should not return an error") + assert.Equal(t, "abdul", string(response.ResponseBytesUtf8), "response should match") +} + +func TestParseV1Data(t *testing.T) { + scan := Scan{ + Ip: "192.168.1.1", + Port: 8080, + Service: "http", + Timestamp: 1617181723, + DataVersion: V1, + Data: map[string]interface{}{ + "response_bytes_utf8": []byte{65, 66, 67}, + }, + } + + result := scan.ParseV1Data() + expected := "ABC" // Expected output for the byte slice [65, 66, 67] + assert.Equal(t, expected, result, "Parsed V1 data should match expected result") +} + +func TestParseV2Data(t *testing.T) { + scan := Scan{ + Ip: "192.168.1.1", + Port: 8080, + Service: "http", + Timestamp: 1617181723, + DataVersion: V2, + Data: map[string]interface{}{ + "response_str": "Success", + }, + } + + result := scan.ParseV2Data() + expected := "Success" // Expected output for response_str + assert.Equal(t, expected, result, "Parsed V2 data should match expected result") +} + +func TestParseV1Data_InvalidJSON(t *testing.T) { + scan := Scan{ + Ip: "192.168.1.1", + Port: 8080, + Service: "http", + Timestamp: 1617181723, + DataVersion: V1, + Data: map[string]interface{}{ + "response_bytes_utf8": "invalid_bytes", + }, + } + + // Expecting a panic due to unmarshaling error (invalid type) + assert.Panics(t, func() { + scan.ParseV1Data() + }, "Parsing invalid V1 data should panic") +} + +func TestParseV2Data_InvalidJSON(t *testing.T) { + scan := Scan{ + Ip: "192.168.1.1", + Port: 8080, + Service: "http", + Timestamp: 1617181723, + DataVersion: V2, + Data: map[string]interface{}{ + "response_str": 123, // Invalid type, should be string + }, + } + + // Expecting a panic due to unmarshaling error (invalid type) + assert.Panics(t, func() { + scan.ParseV2Data() + }, "Parsing invalid V2 data should panic") +} + +