├── go.sum ├── go.mod ├── vpn_test.go ├── .gitignore ├── example_test.go ├── client_test.go ├── pcap_test.go ├── request.go ├── pcap.go ├── SECURITY.md ├── files_test.go ├── README.md ├── tasks_test.go ├── cuckoo_test.go ├── memory_test.go ├── machines_test.go ├── vpn.go ├── LICENSE ├── client.go ├── memory.go ├── files.go ├── machines.go ├── cuckoo.go ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md └── tasks.go /go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/godaddy/go-cukoo 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /vpn_test.go: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestVPNStatus(t *testing.T) { 10 | c := getTestingClient() 11 | 12 | vpnStatus, err := c.VPNStatus(context.Background()) 13 | if err != nil { 14 | t.Error(err) 15 | return 16 | } 17 | fmt.Println(vpnStatus) 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package cuckoo_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | cuckoo "github.com/godaddy/go-cukoo" 9 | ) 10 | 11 | func ExampleClient() { 12 | c := cuckoo.New(&cuckoo.Config{APIKey: os.Getenv("API_KEY"), BaseURL: os.Getenv("BASEURL")}) 13 | 14 | status, _ := c.CuckooStatus(context.Background()) 15 | 16 | fmt.Println(status.Version) 17 | } 18 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func getTestingClient() *Client { 10 | return New(&Config{APIKey: os.Getenv("API_KEY"), BaseURL: os.Getenv("BASEURL")}) 11 | } 12 | 13 | func TestClient(t *testing.T) { 14 | c := getTestingClient() 15 | 16 | // Check we have valid credentials 17 | err := c.CheckAuth(context.Background()) 18 | if err != nil { 19 | t.Error(err) 20 | return 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pcap_test.go: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | func TestPcapGet(t *testing.T) { 9 | c := getTestingClient() 10 | 11 | if err := c.CheckAuth(context.Background()); err != nil { 12 | t.Error(err) 13 | return 14 | } 15 | 16 | tasks := make(chan *Task) 17 | go func() { 18 | err := c.ListAllTasks(context.Background(), tasks) 19 | if err != nil { 20 | t.Error(err) 21 | } 22 | }() 23 | 24 | for task := range tasks { 25 | _, err := c.PcapGet(context.Background(), task.ID) 26 | if err != nil { 27 | continue 28 | } 29 | return 30 | } 31 | t.Errorf("could not find pcap to test") 32 | } 33 | -------------------------------------------------------------------------------- /request.go: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | // ErrNotAuthorized is returned when cuckoo replies with HTTP 401 9 | var ErrNotAuthorized = fmt.Errorf("not authorized") 10 | 11 | // MakeRequest performs the provided request adding in the appropriate auth header 12 | // 13 | // It will return ErrNotAuthorized if the auth fails 14 | func (c *Client) MakeRequest(req *http.Request) (*http.Response, error) { 15 | req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.APIKey)) 16 | 17 | resp, err := c.Client.Do(req) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | // TODO: Check if this status code is accurate 23 | if resp.StatusCode == 401 { 24 | return resp, ErrNotAuthorized 25 | } 26 | 27 | return resp, err 28 | } 29 | -------------------------------------------------------------------------------- /pcap.go: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | ) 9 | 10 | // PcapGet Returns the content of the PCAP associated with the given task. 11 | func (c *Client) PcapGet(ctx context.Context, taskID int) (io.ReadCloser, error) { 12 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/pcap/get/%d", c.BaseURL, taskID), nil) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | resp, err := c.MakeRequest(req) 18 | if err != nil { 19 | return nil, err 20 | } 21 | switch resp.StatusCode { 22 | case 404: 23 | return nil, fmt.Errorf("file not found") 24 | case 200: 25 | break 26 | default: 27 | return nil, fmt.Errorf("bad response code: %d", resp.StatusCode) 28 | } 29 | 30 | return resp.Body, nil 31 | } 32 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Issues 2 | 3 | We take security very seriously at GoDaddy. We appreciate your efforts to 4 | responsibly disclose your findings, and will make every effort to acknowledge 5 | your contributions. 6 | 7 | ## Disclosure policy 8 | 9 | In order to give the community time to respond and upgrade, we strongly urge you 10 | report all security issues privately. 11 | 12 | To report a security issue in one of our Open Source projects email us directly 13 | at **oss@godaddy.com** and include the word "SECURITY" in the subject line. 14 | 15 | This mail is delivered to our Open Source Security team. 16 | 17 | ## Security update policy 18 | 19 | After the initial reply to your report, the team will keep you informed of the 20 | progress being made towards a fix and announcement, and may ask for additional 21 | information or guidance. 22 | -------------------------------------------------------------------------------- /files_test.go: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | func TestFilesView(t *testing.T) { 9 | c := getTestingClient() 10 | 11 | // This test is expecting there to be a file with ID 1 on the server 12 | file, err := c.FilesView(context.Background(), &FileID{ID: 1}) 13 | if err != nil { 14 | t.Error(err) 15 | return 16 | } 17 | if file.ID != 1 || file.Md5 == "" || file.Sha1 == "" || file.Sha256 == "" || file.Sha512 == "" { 18 | t.Errorf("File did not return all information") 19 | } 20 | 21 | // Try getting that file 22 | _, err = c.FilesGet(context.Background(), file.Sha256) 23 | if err != nil { 24 | t.Error(err) 25 | return 26 | } 27 | 28 | // Try getting a file that doesn't exist 29 | _, err = c.FilesGet(context.Background(), "notexisting") 30 | if err == nil { 31 | t.Errorf("should have errored on getting file") 32 | return 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cuckoo client library 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/godaddy/go-cuckoo)](https://goreportcard.com/report/github.com/godaddy/go-cuckoo) 4 | [![Documentation](https://godoc.org/github.com/godaddy/go-cuckoo?status.svg)](https://godoc.org/github.com/godaddy/go-cuckoo) 5 | 6 | Original Author: [Connor Lake](mailto:clake1@godaddy.com) 7 | 8 | A simple go client library for the [cuckoo api](https://cuckoo.readthedocs.io/en/latest/usage/api). See the godoc for more details and examples. 9 | 10 | ## Example Usage 11 | 12 | ```go 13 | c := cuckoo.New(os.Getenv("API_KEY"), os.Getenv("BASEURL")) 14 | 15 | status, _ := c.CuckooStatus(context.Background()) 16 | 17 | fmt.Println(status.Version) 18 | ``` 19 | 20 | ## Auth 21 | 22 | Cuckoo uses an API key for auth, you can see more details in the [cuckoo api documentation](https://cuckoo.readthedocs.io/en/latest/usage/api). 23 | -------------------------------------------------------------------------------- /tasks_test.go: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import ( 4 | "archive/zip" 5 | "bytes" 6 | "context" 7 | "fmt" 8 | "io/ioutil" 9 | ) 10 | 11 | func ExampleClient_TasksScreenshots() { 12 | // Create your client with your API key and URL 13 | c := getTestingClient() 14 | 15 | tasks := make(chan *Task) 16 | go c.ListAllTasks(context.Background(), tasks) 17 | 18 | // Find a task with screenshots 19 | for task := range tasks { 20 | // Get all screenshots 21 | screenshots, err := c.TasksScreenshots(context.Background(), task.ID, -1) 22 | if err != nil { 23 | continue 24 | } 25 | 26 | // Read all data and check ZIP contents 27 | allData, err := ioutil.ReadAll(screenshots) 28 | if err != nil { 29 | continue 30 | } 31 | 32 | zipReader, err := zip.NewReader(bytes.NewReader(allData), int64(len(allData))) 33 | if err != nil { 34 | continue 35 | } 36 | 37 | for _, file := range zipReader.File { 38 | fmt.Println("We found a file!") 39 | _ = file.Name 40 | return 41 | } 42 | } 43 | 44 | // Output: We found a file! 45 | } 46 | -------------------------------------------------------------------------------- /cuckoo_test.go: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestCuckooStatus(t *testing.T) { 10 | c := getTestingClient() 11 | 12 | status, err := c.CuckooStatus(context.Background()) 13 | if err != nil { 14 | t.Error(err) 15 | } 16 | 17 | fmt.Println(status.Version) 18 | 19 | // Check machine count 20 | machines, err := c.MachinesList(context.Background()) 21 | if err != nil { 22 | t.Error(err) 23 | } 24 | if status.Machines.Total != int64(len(machines)) { 25 | t.Errorf("discrepency between number of machines %d vs %d", status.Machines.Total, len(machines)) 26 | } 27 | 28 | // Check task count 29 | tasks := make(chan *Task) 30 | go func() { 31 | err := c.ListAllTasks(context.Background(), tasks) 32 | if err != nil { 33 | t.Error(err) 34 | } 35 | }() 36 | taskCount := 0 37 | for range tasks { 38 | taskCount++ 39 | } 40 | 41 | if status.Tasks.Total != int64(taskCount) { 42 | t.Errorf("discrepency between number of tasks %d vs %d", status.Tasks.Total, taskCount) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /memory_test.go: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | func TestMemoryList(t *testing.T) { 9 | c := getTestingClient() 10 | 11 | if err := c.CheckAuth(context.Background()); err != nil { 12 | t.Error(err) 13 | return 14 | } 15 | 16 | tasks := make(chan *Task) 17 | go func() { 18 | err := c.ListAllTasks(context.Background(), tasks) 19 | if err != nil { 20 | t.Error(err) 21 | } 22 | }() 23 | 24 | // File a task that has a memory files 25 | for task := range tasks { 26 | memory, err := c.MemoryList(context.Background(), task.ID) 27 | if err != nil { 28 | if err.Error() == "file or folder not found" { 29 | continue 30 | } 31 | t.Error(err) 32 | return 33 | } 34 | 35 | if len(memory) == 0 { 36 | continue 37 | } 38 | 39 | // Try getting the raw content 40 | _, err = c.MemoryGet(context.Background(), task.ID, 3428) 41 | if err != nil { 42 | t.Error(err) 43 | return 44 | } 45 | 46 | // We found a sample that we could download 47 | return 48 | } 49 | 50 | t.Errorf("did not find a sample to test on") 51 | } 52 | -------------------------------------------------------------------------------- /machines_test.go: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | func TestMachinesList(t *testing.T) { 9 | c := getTestingClient() 10 | 11 | if err := c.CheckAuth(context.Background()); err != nil { 12 | t.Error(err) 13 | return 14 | } 15 | 16 | // This test expects machines to be available on the cuckoo server 17 | machines, err := c.MachinesList(context.Background()) 18 | if err != nil { 19 | t.Error(err) 20 | return 21 | } 22 | if len(machines) == 0 { 23 | t.Errorf("no machines found") 24 | return 25 | } 26 | 27 | machine, err := c.MachinesView(context.Background(), machines[0].Name) 28 | if err != nil { 29 | t.Error(err) 30 | return 31 | } 32 | if machine.Name != machines[0].Name { 33 | t.Errorf("Inconsistency in machine names %s - %s", machine.Name, machines[0].Name) 34 | } 35 | if machine.Name == "" { 36 | t.Errorf("Empty machine name") 37 | } 38 | 39 | // Make sure getting random machine fails 40 | _, err = c.MachinesView(context.Background(), "doesnotexist") 41 | if err == nil { 42 | t.Errorf("should have errored") 43 | return 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /vpn.go: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | ) 9 | 10 | // VPNStatus Returns VPN status. 11 | // For now this decodes to a blank interface. 12 | func (c *Client) VPNStatus(ctx context.Context) (interface{}, error) { 13 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/vpn/status", c.BaseURL), nil) 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | resp, err := c.MakeRequest(req) 19 | if err != nil { 20 | return nil, err 21 | } 22 | switch resp.StatusCode { 23 | case 200: 24 | break 25 | case 404: 26 | return nil, fmt.Errorf("not available") 27 | default: 28 | message := struct { 29 | Message string `json:"message"` 30 | }{} 31 | json.NewDecoder(resp.Body).Decode(&message) 32 | return nil, fmt.Errorf("bad response code: %d, message: %s", resp.StatusCode, message) 33 | } 34 | 35 | // TODO change this to real structure 36 | var status interface{} 37 | err = json.NewDecoder(resp.Body).Decode(&status) 38 | if err != nil { 39 | return nil, fmt.Errorf("cuckoo: status response marshalling error: %w", err) 40 | } 41 | 42 | return status, nil 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 GoDaddy Operating Company, LLC. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | // Package cuckoo is a go client library to interact with the cuckoo REST api 2 | package cuckoo 3 | 4 | import ( 5 | "context" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | const ( 11 | defaultTimeout = time.Second * 30 12 | ) 13 | 14 | // Client to interact with cuckoo 15 | type Client struct { 16 | // Auth that fits in: "Authorization: Bearer %s" 17 | APIKey string 18 | BaseURL string 19 | 20 | // Client used for requests 21 | Client *http.Client 22 | } 23 | 24 | // Config is the configuration required to create a client 25 | type Config struct { 26 | APIKey string 27 | BaseURL string 28 | // Optional, if nil a new client will be created 29 | // with a defaultTimeout 30 | Client *http.Client 31 | } 32 | 33 | // New Creates a new client based on the provided API Key 34 | func New(c *Config) *Client { 35 | client := c.Client 36 | if client == nil { 37 | client = &http.Client{ 38 | Timeout: defaultTimeout, 39 | } 40 | } 41 | 42 | return &Client{ 43 | APIKey: c.APIKey, 44 | BaseURL: c.BaseURL, 45 | Client: client, 46 | } 47 | } 48 | 49 | // CheckAuth returns an error if the APIKey is not valid, or no error if valid. 50 | // 51 | // Under the hood it simply makes a call to /tasks/list 52 | func (c *Client) CheckAuth(ctx context.Context) error { 53 | _, err := c.ListTasks(ctx, 1, 0) 54 | if err != nil { 55 | return err 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /memory.go: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | ) 10 | 11 | // MemoryList Returns a list of memory dump files or one memory dump file associated with the specified task ID. 12 | // 13 | // Returns a []string{} of dump file names 14 | func (c *Client) MemoryList(ctx context.Context, taskID int) ([]string, error) { 15 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/memory/list/%d", c.BaseURL, taskID), nil) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | resp, err := c.MakeRequest(req) 21 | if err != nil { 22 | return nil, err 23 | } 24 | switch resp.StatusCode { 25 | case 404: 26 | return nil, fmt.Errorf("file or folder not found") 27 | case 200: 28 | break 29 | default: 30 | return nil, fmt.Errorf("bad response code: %d", resp.StatusCode) 31 | } 32 | 33 | dumpFiles := struct { 34 | Files []string `json:"dump_files"` 35 | }{} 36 | err = json.NewDecoder(resp.Body).Decode(&dumpFiles) 37 | if err != nil { 38 | return nil, fmt.Errorf("cuckoo: status response marshalling error: %w", err) 39 | } 40 | 41 | return dumpFiles.Files, nil 42 | } 43 | 44 | // MemoryGet Returns one memory dump file associated with the specified task ID. 45 | // 46 | // pid - numerical identifier (pid) of a single memory dump file (e.g. 205, 1908). 47 | // 48 | // Note that depending on your cuckoo setup, sometimes you won't be able to download memory 49 | // dumps. See this issue: 50 | // https://github.com/cuckoosandbox/cuckoo/issues/2327 51 | // 52 | // This function returns the direct reader from the cuckoo api. 53 | func (c *Client) MemoryGet(ctx context.Context, taskID int, pID int) (io.ReadCloser, error) { 54 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/memory/get/%d/%d", c.BaseURL, taskID, pID), nil) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | resp, err := c.MakeRequest(req) 60 | if err != nil { 61 | return nil, err 62 | } 63 | switch resp.StatusCode { 64 | case 404: 65 | return nil, fmt.Errorf("Memory dump not found") 66 | case 200: 67 | break 68 | default: 69 | return nil, fmt.Errorf("bad response code: %d", resp.StatusCode) 70 | } 71 | 72 | return resp.Body, nil 73 | } 74 | -------------------------------------------------------------------------------- /files.go: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | ) 10 | 11 | // Sample is a file sample returned by cuckoo 12 | type Sample struct { 13 | Sha1 string `json:"sha1"` 14 | FileType string `json:"file_type"` 15 | FileSize int64 `json:"file_size"` 16 | Crc32 string `json:"crc32"` 17 | Ssdeep string `json:"ssdeep"` 18 | Sha256 string `json:"sha256"` 19 | Sha512 string `json:"sha512"` 20 | ID int64 `json:"id"` 21 | Md5 string `json:"md5"` 22 | } 23 | 24 | // FileID to look up in cuckoo. You can set any of the 25 | // fields and leave the others blank 26 | type FileID struct { 27 | ID int 28 | MD5 string 29 | SHA256 string 30 | } 31 | 32 | // FilesView Returns details on the file matching either the specified MD5 hash, SHA256 hash or ID. 33 | func (c *Client) FilesView(ctx context.Context, fileID *FileID) (*Sample, error) { 34 | format := "id" 35 | id := fmt.Sprintf("%d", fileID.ID) 36 | switch { 37 | case fileID.MD5 != "": 38 | format = "md5" 39 | id = fileID.MD5 40 | case fileID.SHA256 != "": 41 | format = "sha256" 42 | id = fileID.SHA256 43 | } 44 | 45 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/files/view/%s/%s", c.BaseURL, format, id), nil) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | resp, err := c.MakeRequest(req) 51 | if err != nil { 52 | return nil, err 53 | } 54 | switch resp.StatusCode { 55 | case 404: 56 | return nil, fmt.Errorf("file not found") 57 | case 400: 58 | return nil, fmt.Errorf("invalid lookup term") 59 | case 200: 60 | break 61 | default: 62 | return nil, fmt.Errorf("bad response code: %d", resp.StatusCode) 63 | } 64 | 65 | sample := struct { 66 | Sample *Sample `json:"sample"` 67 | }{} 68 | err = json.NewDecoder(resp.Body).Decode(&sample) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | return sample.Sample, nil 74 | } 75 | 76 | // FilesGet Returns the binary content of the file matching the specified SHA256 hash. 77 | func (c *Client) FilesGet(ctx context.Context, sha256 string) (io.ReadCloser, error) { 78 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/files/get/%s", c.BaseURL, sha256), nil) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | resp, err := c.MakeRequest(req) 84 | if err != nil { 85 | return nil, err 86 | } 87 | switch resp.StatusCode { 88 | case 404: 89 | return nil, fmt.Errorf("file not found") 90 | case 200: 91 | break 92 | default: 93 | return nil, fmt.Errorf("bad response code: %d", resp.StatusCode) 94 | } 95 | 96 | return resp.Body, nil 97 | } 98 | -------------------------------------------------------------------------------- /machines.go: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | ) 9 | 10 | // Machine returned by cuckoo 11 | type Machine struct { 12 | Status interface{} `json:"status"` 13 | Locked bool `json:"locked"` 14 | Name string `json:"name"` 15 | ResultserverIP string `json:"resultserver_ip"` 16 | IP string `json:"ip"` 17 | Tags []string `json:"tags"` 18 | Label string `json:"label"` 19 | LockedChangedOn interface{} `json:"locked_changed_on"` 20 | Platform string `json:"platform"` 21 | Snapshot interface{} `json:"snapshot"` 22 | Interface interface{} `json:"interface"` 23 | StatusChangedOn interface{} `json:"status_changed_on"` 24 | ID int64 `json:"id"` 25 | ResultserverPort string `json:"resultserver_port"` 26 | } 27 | 28 | // MachinesList Returns a list with details on the analysis machines available to Cuckoo. 29 | func (c *Client) MachinesList(ctx context.Context) ([]*Machine, error) { 30 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/machines/list", c.BaseURL), nil) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | resp, err := c.MakeRequest(req) 36 | if err != nil { 37 | return nil, err 38 | } 39 | switch resp.StatusCode { 40 | case 200: 41 | break 42 | default: 43 | return nil, fmt.Errorf("bad response code: %d", resp.StatusCode) 44 | } 45 | 46 | machines := struct { 47 | Machines []*Machine `json:"machines"` 48 | }{} 49 | err = json.NewDecoder(resp.Body).Decode(&machines) 50 | if err != nil { 51 | return nil, fmt.Errorf("cuckoo: status response marshalling error: %w", err) 52 | } 53 | 54 | return machines.Machines, nil 55 | } 56 | 57 | // MachinesView Returns details on the analysis machine associated with the given name. 58 | func (c *Client) MachinesView(ctx context.Context, machineName string) (*Machine, error) { 59 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/machines/view/%s", c.BaseURL, machineName), nil) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | resp, err := c.MakeRequest(req) 65 | if err != nil { 66 | return nil, err 67 | } 68 | switch resp.StatusCode { 69 | case 200: 70 | break 71 | case 404: 72 | return nil, fmt.Errorf("machine not found") 73 | default: 74 | return nil, fmt.Errorf("bad response code: %d", resp.StatusCode) 75 | } 76 | 77 | machine := struct { 78 | Machine *Machine `json:"machine"` 79 | }{} 80 | err = json.NewDecoder(resp.Body).Decode(&machine) 81 | if err != nil { 82 | return nil, fmt.Errorf("cuckoo: status response marshalling error: %w", err) 83 | } 84 | 85 | return machine.Machine, nil 86 | } 87 | -------------------------------------------------------------------------------- /cuckoo.go: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | ) 9 | 10 | // Status is a collection of information on the status of cuckoo 11 | type Status struct { 12 | Tasks Tasks `json:"tasks"` 13 | Diskspace Diskspace `json:"diskspace"` 14 | Version string `json:"version"` 15 | ProtocolVersion int64 `json:"protocol_version"` 16 | Hostname string `json:"hostname"` 17 | Machines Machines `json:"machines"` 18 | } 19 | 20 | // Diskspace reported by cuckoo 21 | type Diskspace struct { 22 | Analyses Analyses `json:"analyses"` 23 | Binaries Analyses `json:"binaries"` 24 | Temporary Analyses `json:"temporary"` 25 | } 26 | 27 | // Analyses reported by cuckoo 28 | type Analyses struct { 29 | Total int64 `json:"total"` 30 | Free int64 `json:"free"` 31 | Used int64 `json:"used"` 32 | } 33 | 34 | // Machines reported by cuckoo 35 | type Machines struct { 36 | Available int64 `json:"available"` 37 | Total int64 `json:"total"` 38 | } 39 | 40 | // Tasks reported by cuckoo 41 | type Tasks struct { 42 | Reported int64 `json:"reported"` 43 | Running int64 `json:"running"` 44 | Total int64 `json:"total"` 45 | Completed int64 `json:"completed"` 46 | Pending int64 `json:"pending"` 47 | } 48 | 49 | // CuckooStatus Returns status of the cuckoo server. 50 | // In version 1.3 the diskspace entry was added. 51 | // The diskspace entry shows the used, free, and total diskspace at the disk where the respective directories can be found. 52 | // The diskspace entry allows monitoring of a Cuckoo node through the Cuckoo API. 53 | // Note that each directory is checked separately as one may create a symlink for $CUCKOO/storage/analyses to a separate harddisk, but keep $CUCKOO/storage/binaries as-is. 54 | // (This feature is only available under Unix!) 55 | // 56 | // In version 1.3 the cpuload entry was also added - the cpuload entry shows the CPU load for the past minute, the past 5 minutes, and the past 15 minutes, respectively. 57 | // (This feature is only available under Unix!) 58 | func (c *Client) CuckooStatus(ctx context.Context) (*Status, error) { 59 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/cuckoo/status", c.BaseURL), nil) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | resp, err := c.MakeRequest(req) 65 | if err != nil { 66 | return nil, err 67 | } 68 | switch resp.StatusCode { 69 | case 200: 70 | break 71 | case 404: 72 | return nil, fmt.Errorf("machine not found") 73 | default: 74 | return nil, fmt.Errorf("bad response code: %d", resp.StatusCode) 75 | } 76 | 77 | status := &Status{} 78 | if err := json.NewDecoder(resp.Body).Decode(status); err != nil { 79 | return nil, fmt.Errorf("cuckoo: status response marshalling error: %w", err) 80 | } 81 | 82 | return status, nil 83 | } 84 | 85 | // Exit Shuts down the server if in debug mode and using the werkzeug server. 86 | func (c *Client) Exit(ctx context.Context) error { 87 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/exit", c.BaseURL), nil) 88 | if err != nil { 89 | return err 90 | } 91 | 92 | resp, err := c.MakeRequest(req) 93 | if err != nil { 94 | return err 95 | } 96 | switch resp.StatusCode { 97 | case 200: 98 | break 99 | case 403: 100 | return fmt.Errorf("this call can only be used in debug mode") 101 | case 500: 102 | return fmt.Errorf("generic 500 error") 103 | default: 104 | return fmt.Errorf("bad response code: %d", resp.StatusCode) 105 | } 106 | 107 | return nil 108 | } 109 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at oss@godaddy.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | 78 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Everyone is welcome to contribute to GoDaddy's Open Source Software. 4 | Contributing doesn’t just mean submitting pull requests. To get involved, 5 | you can report or triage bugs, and participate in discussions on the 6 | evolution of each project. 7 | 8 | No matter how you want to get involved, we ask that you first learn what’s 9 | expected of anyone who participates in the project by reading the Contribution 10 | Guidelines and our [Code of Conduct][coc]. 11 | 12 | ## Answering Questions 13 | 14 | One of the most important and immediate ways you can support this project is 15 | to answer questions on [Github][issues]. Whether you’re 16 | helping a newcomer understand a feature or troubleshooting an edge case with a 17 | seasoned developer, your knowledge and experience with a programming language 18 | can go a long way to help others. 19 | 20 | ## Reporting Bugs 21 | 22 | **Do not report potential security vulnerabilities here. Refer to 23 | [SECURITY.md](./SECURITY.md) for more details about the process of reporting 24 | security vulnerabilities.** 25 | 26 | Before submitting a ticket, please search our [Issue Tracker][issues] to make 27 | sure it does not already exist and have a simple replication of the behavior. If 28 | the issue is isolated to one of the dependencies of this project, please create 29 | a Github issue in that project. All dependencies should be open source software 30 | and can be found on Github. 31 | 32 | Submit a ticket for your issue, assuming one does not already exist: 33 | - Create it on the project's [issue Tracker][issues]. 34 | - Clearly describe the issue by following the template layout 35 | - Make sure to include steps to reproduce the bug. 36 | - A reproducible (unit) test could be helpful in solving the bug. 37 | - Describe the environment that (re)produced the problem. 38 | 39 | ## Triaging bugs or contributing code 40 | 41 | If you're triaging a bug, first make sure that you can reproduce it. Once a bug 42 | can be reproduced, reduce it to the smallest amount of code possible. Reasoning 43 | about a sample or unit test that reproduces a bug in just a few lines of code 44 | is easier than reasoning about a longer sample. 45 | 46 | From a practical perspective, contributions are as simple as: 47 | 1. Fork and clone the repo, [see Github's instructions if you need help.][fork] 48 | 1. Create a branch for your PR with `git checkout -b pr/your-branch-name` 49 | 1. Make changes on the branch of your forked repository. 50 | 1. When committing, reference your issue (if present) and include a note about 51 | the fix. 52 | 1. Please also add/update unit tests for your changes. 53 | 1. Push the changes to your fork and submit a pull request to the 'main 54 | development branch' branch of the projects' repository. 55 | 56 | If you are interested in making a large change and feel unsure about its overall 57 | effect, start with opening an Issue in the project's [Issue Tracker][issues] 58 | with a high-level proposal and discuss it with the core contributors through 59 | Github comments. After reaching a consensus with core 60 | contributors about the change, discuss the best way to go about implementing it. 61 | 62 | > Tip: Keep your master branch pointing at the original repository and make 63 | > pull requests from branches on your fork. To do this, run: 64 | > ``` 65 | > git remote add upstream https://github.com/godaddy/go-cuckoo.git 66 | > git fetch upstream 67 | > git branch --set-upstream-to=upstream/master master 68 | > ``` 69 | > This will add the original repository as a "remote" called "upstream," Then 70 | > fetch the git information from that remote, then set your local master 71 | > branch to use the upstream master branch whenever you run git pull. Then you 72 | > can make all of your pull request branches based on this master branch. 73 | > Whenever you want to update your version of master, do a regular git pull. 74 | 75 | ## Code Review 76 | 77 | Any open source project relies heavily on code review to improve software 78 | quality. All significant changes, by all developers, must be reviewed before 79 | they are committed to the repository. Code reviews are conducted on GitHub 80 | through comments on pull requests or commits. The developer responsible for a 81 | code change is also responsible for making all necessary review-related changes. 82 | 83 | Sometimes code reviews will take longer than you would hope for, especially for 84 | larger features. Here are some accepted ways to speed up review times for your 85 | patches: 86 | 87 | - Review other people’s changes. If you help out, others will more likely be 88 | willing to do the same for you. 89 | - Split your change into multiple smaller changes. The smaller your change, 90 | the higher the probability that somebody will take a quick look at it. 91 | 92 | **Note that anyone is welcome to review and give feedback on a change, but only 93 | people with commit access to the repository can approve it.** 94 | 95 | ## Attribution of Changes 96 | 97 | When contributors submit a change to this project, after that change is 98 | approved, other developers with commit access may commit it for the author. When 99 | doing so, it is important to retain correct attribution of the contribution. 100 | Generally speaking, Git handles attribution automatically. 101 | 102 | ## Code Style and Documentation 103 | 104 | Ensure that your contribution follows the standards set by the project's style 105 | guide with respect to patterns, naming, documentation and testing. 106 | 107 | # Additional Resources 108 | 109 | - [General GitHub Documentation](https://help.github.com/) 110 | - [GitHub Pull Request documentation](https://help.github.com/send-pull-requests/) 111 | 112 | [issues]: https://github.com/godaddy/go-cuckoo/issues 113 | [coc]: ./CODE_OF_CONDUCT.md 114 | [fork]: https://help.github.com/en/articles/fork-a-repo 115 | -------------------------------------------------------------------------------- /tasks.go: -------------------------------------------------------------------------------- 1 | package cuckoo 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | const ( 15 | // Standard number of results to get for paginated requests 16 | resultsPerPage = 10 17 | ) 18 | 19 | // Task Statuses 20 | var ( 21 | StatusPending TaskStatus = "pending" 22 | StatusRunning TaskStatus = "running" 23 | StatusCompleted TaskStatus = "completed" 24 | StatusReported TaskStatus = "reported" 25 | ) 26 | 27 | // ErrTaskNotFound is returned when the task is not found 28 | var ErrTaskNotFound = fmt.Errorf("task not found") 29 | 30 | // TaskStatus is a possible task status from cuckoo (pending, running, completed, reported) 31 | type TaskStatus string 32 | 33 | // Task is a task in cuckoo 34 | type Task struct { 35 | Category string `json:"category"` 36 | Machine interface{} `json:"machine"` 37 | Errors []interface{} `json:"errors"` 38 | Target string `json:"target"` 39 | Package interface{} `json:"package"` 40 | SampleID interface{} `json:"sample_id"` 41 | Guest interface{} `json:"guest"` 42 | Custom interface{} `json:"custom"` 43 | Owner string `json:"owner"` 44 | Priority int64 `json:"priority"` 45 | Platform interface{} `json:"platform"` 46 | Options interface{} `json:"options"` 47 | Status TaskStatus `json:"status"` 48 | EnforceTimeout bool `json:"enforce_timeout"` 49 | Timeout int64 `json:"timeout"` 50 | Memory bool `json:"memory"` 51 | Tags []string `json:"tags"` 52 | ID int `json:"id"` 53 | AddedOn string `json:"added_on"` 54 | CompletedOn interface{} `json:"completed_on"` 55 | } 56 | 57 | // ListAllTasks Sends all tasks on cuckoo to the provided tasks channel. It will 58 | // close the channel once it completes or errors 59 | // 60 | // It will loop through all pages of the api until no more results are found 61 | func (c *Client) ListAllTasks(ctx context.Context, tasksChan chan *Task) error { 62 | defer close(tasksChan) 63 | offset := 0 64 | 65 | // Keep looping until we get all tasks 66 | retryCount := 0 67 | for { 68 | tasks, err := c.ListTasks(ctx, resultsPerPage, offset) 69 | if err != nil { 70 | if strings.Contains(err.Error(), "500") { 71 | if retryCount >= 3 { 72 | return fmt.Errorf("max retries exceeded: %w", err) 73 | } 74 | // This is likely a temporary error, wait a bit and try again 75 | select { 76 | case <-ctx.Done(): 77 | return ctx.Err() 78 | case <-time.After(time.Second * 2): 79 | } 80 | retryCount++ 81 | continue 82 | } 83 | return fmt.Errorf("error listing task page: %w", err) 84 | } 85 | if len(tasks) == 0 { 86 | // No more tasks, return 87 | return nil 88 | } 89 | 90 | retryCount = 0 91 | 92 | // Send all tasks to channel 93 | for _, task := range tasks { 94 | task := task 95 | select { 96 | case <-ctx.Done(): 97 | return ctx.Err() 98 | case tasksChan <- task: 99 | } 100 | } 101 | 102 | offset += len(tasks) 103 | } 104 | } 105 | 106 | // ListTasks returns list of tasks. 107 | // 108 | // limit (optional) (int) - maximum number of returned tasks. 109 | // offset (optional) (int) - data offset. 110 | func (c *Client) ListTasks(ctx context.Context, limit, offset int) ([]*Task, error) { 111 | URL := fmt.Sprintf("%s/tasks/list/%d/%d", c.BaseURL, limit, offset) 112 | req, err := http.NewRequestWithContext(ctx, "GET", URL, nil) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | resp, err := c.MakeRequest(req) 118 | if err != nil { 119 | return nil, err 120 | } 121 | if resp.StatusCode != 200 { 122 | return nil, fmt.Errorf("bad response code: %d", resp.StatusCode) 123 | } 124 | 125 | tasks := struct { 126 | Tasks []*Task `json:"tasks"` 127 | }{} 128 | err = json.NewDecoder(resp.Body).Decode(&tasks) 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | return tasks.Tasks, nil 134 | } 135 | 136 | // ListTasksSample Returns list of tasks for sample. 137 | func (c *Client) ListTasksSample(ctx context.Context, sampleID int) ([]*Task, error) { 138 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/tasks/sample/%d", c.BaseURL, sampleID), nil) 139 | if err != nil { 140 | return nil, err 141 | } 142 | 143 | resp, err := c.MakeRequest(req) 144 | if err != nil { 145 | return nil, err 146 | } 147 | 148 | tasks := []*Task{} 149 | err = json.NewDecoder(resp.Body).Decode(&tasks) 150 | if err != nil { 151 | return nil, err 152 | } 153 | if resp.StatusCode != 200 { 154 | return nil, fmt.Errorf("bad response code: %d", resp.StatusCode) 155 | } 156 | 157 | return tasks, nil 158 | } 159 | 160 | // TasksView Returns details on the task associated with the specified ID. 161 | func (c *Client) TasksView(ctx context.Context, taskID int) (*Task, error) { 162 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/tasks/view/%d", c.BaseURL, taskID), nil) 163 | if err != nil { 164 | return nil, err 165 | } 166 | 167 | resp, err := c.MakeRequest(req) 168 | if err != nil { 169 | return nil, err 170 | } 171 | switch resp.StatusCode { 172 | case 404: 173 | return nil, ErrTaskNotFound 174 | case 200: 175 | break 176 | default: 177 | return nil, fmt.Errorf("bad response code: %d", resp.StatusCode) 178 | } 179 | 180 | task := struct { 181 | Task Task `json:"task"` 182 | }{} 183 | err = json.NewDecoder(resp.Body).Decode(&task) 184 | if err != nil { 185 | return nil, err 186 | } 187 | 188 | return &task.Task, nil 189 | } 190 | 191 | // TasksReschedule Reschedule a task with the specified ID and priority (default priority is 1 if -1 is passed in). 192 | func (c *Client) TasksReschedule(ctx context.Context, taskID, priority int) (err error) { 193 | if priority == -1 { 194 | priority = 1 195 | } 196 | 197 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/tasks/reschedule/%d/%d", c.BaseURL, taskID, priority), nil) 198 | if err != nil { 199 | return err 200 | } 201 | 202 | resp, err := c.MakeRequest(req) 203 | if err != nil { 204 | return err 205 | } 206 | switch resp.StatusCode { 207 | case 404: 208 | return ErrTaskNotFound 209 | case 200: 210 | break 211 | default: 212 | return fmt.Errorf("bad response code: %d", resp.StatusCode) 213 | } 214 | 215 | task := struct { 216 | Status string `json:"status"` 217 | }{} 218 | err = json.NewDecoder(resp.Body).Decode(&task) 219 | if err != nil { 220 | return err 221 | } 222 | if task.Status != "OK" { 223 | return fmt.Errorf("bad returned status: %s", task.Status) 224 | } 225 | 226 | return nil 227 | } 228 | 229 | // TasksDelete Removes the given task from the database and deletes the results. 230 | func (c *Client) TasksDelete(ctx context.Context, taskID int) (err error) { 231 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/tasks/delete/%d", c.BaseURL, taskID), nil) 232 | if err != nil { 233 | return err 234 | } 235 | 236 | resp, err := c.MakeRequest(req) 237 | if err != nil { 238 | return err 239 | } 240 | switch resp.StatusCode { 241 | case 404: 242 | return ErrTaskNotFound 243 | case 500: 244 | body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1024*1024)) // Limit reading incase response is massive for some reason 245 | if err != nil { 246 | body = []byte{} 247 | } 248 | return fmt.Errorf("unable to delete the task, body: %s", body) 249 | case 200: 250 | break 251 | default: 252 | return fmt.Errorf("bad response code: %d", resp.StatusCode) 253 | } 254 | 255 | return nil 256 | } 257 | 258 | // TasksReport Returns the report associated with the specified task ID. 259 | // 260 | // It gets the reports in JSON format by default. The report is very large and dynamic so it returns the http reader 261 | func (c *Client) TasksReport(ctx context.Context, taskID int) (report io.ReadCloser, err error) { 262 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/tasks/report/%d", c.BaseURL, taskID), nil) 263 | if err != nil { 264 | return nil, err 265 | } 266 | 267 | resp, err := c.MakeRequest(req) 268 | if err != nil { 269 | return nil, err 270 | } 271 | switch resp.StatusCode { 272 | case 404: 273 | return nil, fmt.Errorf("report not found") 274 | case 400: 275 | return nil, fmt.Errorf("invalid report format") 276 | case 200: 277 | break 278 | default: 279 | return nil, fmt.Errorf("bad response code: %d", resp.StatusCode) 280 | } 281 | 282 | return resp.Body, nil 283 | } 284 | 285 | // TasksScreenshots Returns one or all screenshots associated with the specified task ID. 286 | // 287 | // If screenshotNumber is -1, all screenshots are returned 288 | // 289 | // It will return a reader from the API reading the ZIP data of the screenshot(s). You can use the zip package to read the files 290 | func (c *Client) TasksScreenshots(ctx context.Context, taskID, screenshotNumber int) (zippedData io.ReadCloser, err error) { 291 | var req *http.Request 292 | if screenshotNumber == -1 { 293 | req, err = http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/tasks/screenshots/%d", c.BaseURL, taskID), nil) 294 | } else { 295 | req, err = http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/tasks/screenshots/%d/%d", c.BaseURL, taskID, screenshotNumber), nil) 296 | } 297 | 298 | if err != nil { 299 | return nil, err 300 | } 301 | 302 | resp, err := c.MakeRequest(req) 303 | if err != nil { 304 | return nil, err 305 | } 306 | switch resp.StatusCode { 307 | case 404: 308 | return nil, fmt.Errorf("file or folder not found") 309 | case 200: 310 | break 311 | default: 312 | return nil, fmt.Errorf("bad response code: %d", resp.StatusCode) 313 | } 314 | 315 | return resp.Body, nil 316 | } 317 | 318 | // TasksReReport Re-run reporting for task associated with the specified task ID. 319 | func (c *Client) TasksReReport(ctx context.Context, taskID int) (err error) { 320 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/tasks/rereport/%d", c.BaseURL, taskID), nil) 321 | 322 | if err != nil { 323 | return err 324 | } 325 | 326 | resp, err := c.MakeRequest(req) 327 | if err != nil { 328 | return err 329 | } 330 | switch resp.StatusCode { 331 | case 404: 332 | return fmt.Errorf("file or folder not found") 333 | case 200: 334 | break 335 | default: 336 | return fmt.Errorf("bad response code: %d", resp.StatusCode) 337 | } 338 | 339 | response := struct { 340 | Success bool `json:"success"` 341 | }{} 342 | err = json.NewDecoder(resp.Body).Decode(&response) 343 | if err != nil { 344 | return err 345 | } 346 | if !response.Success { 347 | return fmt.Errorf("cuckoo returned non success") 348 | } 349 | 350 | return nil 351 | } 352 | 353 | // TasksReboot Add a reboot task to database from an existing analysis ID. 354 | func (c *Client) TasksReboot(ctx context.Context, taskID int) (ID, rebootID int, err error) { 355 | req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/tasks/reboot/%d", c.BaseURL, taskID), nil) 356 | 357 | if err != nil { 358 | return -1, -1, err 359 | } 360 | 361 | resp, err := c.MakeRequest(req) 362 | if err != nil { 363 | return -1, -1, err 364 | } 365 | switch resp.StatusCode { 366 | case 404: 367 | return -1, -1, fmt.Errorf("error creating reboot task") 368 | case 200: 369 | break 370 | default: 371 | return -1, -1, fmt.Errorf("bad response code: %d", resp.StatusCode) 372 | } 373 | 374 | response := struct { 375 | TaskID int `json:"task_id"` 376 | RebootID int `json:"reboot_id"` 377 | }{} 378 | err = json.NewDecoder(resp.Body).Decode(&response) 379 | if err != nil { 380 | return -1, -1, err 381 | } 382 | 383 | return response.TaskID, response.RebootID, nil 384 | } 385 | --------------------------------------------------------------------------------