From ff9681284f62a168d317fec5c381d2a7d13ea0cd Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sun, 29 Dec 2024 12:08:44 +0100 Subject: [PATCH 01/41] Add file-check --- cmd/file_check.go | 281 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 cmd/file_check.go diff --git a/cmd/file_check.go b/cmd/file_check.go new file mode 100644 index 0000000..ba9a863 --- /dev/null +++ b/cmd/file_check.go @@ -0,0 +1,281 @@ +/* +Copyright © 2024 Alessandro Riva + +Licensed under the MIT License. +See the LICENSE file for details. +*/ +package cmd + +import ( + "bufio" + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "time" + "mime/multipart" + "strings" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +const virusTotalBaseURL = "https://www.virustotal.com/api/v3" +const virusTotalFileReportEndpoint = "/files/" +const virusTotalFileUploadEndpoint = "/files" + +// fileCheckCmd represents the file-check command +var fileCheckCmd = &cobra.Command{ + Use: "file-check [file]", + Short: "Check file for suspicious content and upload to VirusTotal if not present", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + filePath := args[0] + checkFileOnVirusTotal(filePath) + }, +} + +func init() { + rootCmd.AddCommand(fileCheckCmd) +} + +func checkFileOnVirusTotal(filePath string) { + apiKey := viper.GetString("api_keys.virustotal.api_key") + if apiKey == "" { + log.Fatal("VirusTotal API key missing! Please set it in the config file.") + } + + hash, err := calculateSHA256(filePath) + if err != nil { + log.Fatalf("Error calculating file hash: %v", err) + } + + fmt.Printf("File SHA256: %s\n", hash) + + // Check if file already exists in VirusTotal + if fileExistsInVirusTotal(apiKey, hash) { + fmt.Println("File already analyzed on VirusTotal.") + } else { + // Ask for confirmation before uploading + if confirmUpload() { + fmt.Println("Uploading file to VirusTotal for analysis...") + uploadFileToVirusTotal(apiKey, filePath) + } else { + fmt.Println("Upload canceled.") + } + } +} + +func confirmUpload() bool { + reader := bufio.NewReader(os.Stdin) + fmt.Print("File not found on VirusTotal. Do you want to upload it for analysis? (y/n): ") + response, err := reader.ReadString('\n') + if err != nil { + log.Fatalf("Error reading input: %v", err) + } + response = strings.TrimSpace(strings.ToLower(response)) + return response == "y" || response == "yes" +} + +func calculateSHA256(filePath string) (string, error) { + file, err := os.Open(filePath) + if err != nil { + return "", fmt.Errorf("could not open file: %w", err) + } + defer file.Close() + + hash := sha256.New() + if _, err := io.Copy(hash, file); err != nil { + return "", fmt.Errorf("could not hash file: %w", err) + } + + return hex.EncodeToString(hash.Sum(nil)), nil +} + +func fileExistsInVirusTotal(apiKey, hash string) bool { + client := &http.Client{} + req, err := http.NewRequest("GET", virusTotalBaseURL+virusTotalFileReportEndpoint+hash, nil) + if err != nil { + log.Fatalf("Error creating request: %v", err) + } + req.Header.Set("x-apikey", apiKey) + req.Header.Add("accept", "application/json") + + + resp, err := client.Do(req) + if err != nil { + log.Fatalf("Error making request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return false // File not found on VirusTotal + } + + if resp.StatusCode != http.StatusOK { + log.Fatalf("Unexpected response from VirusTotal: %v", resp.Status) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatalf("Error reading response body: %v", err) + } + + var result map[string]interface{} + if err := json.Unmarshal(body, &result); err != nil { + log.Fatalf("Error parsing JSON response: %v", err) + } + + fmt.Printf("VirusTotal Analysis Summary: %v\n", result["data"]) + + return true +} + +func uploadFileToVirusTotal(apiKey, filePath string) { + + // Create a buffer to hold the multipart form data + var b bytes.Buffer + writer := multipart.NewWriter(&b) + + file, err := os.Open(filePath) + if err != nil { + log.Fatalf("Could not open file for upload: %v", err) + } + defer file.Close() + + + part, err := writer.CreateFormFile("file", filePath) + if err != nil { + log.Fatalf("CreateFormFile: %v", err) + } + + // Copy the file content to the form file field + _, err = io.Copy(part, file) + if err != nil { + log.Fatalf("io.Copy: %v", err) + } + + // Close the writer to finalize the multipart form + err = writer.Close() + if err != nil { + log.Fatalf("writer.Close: %v", err) + } + + + req, err := http.NewRequest("POST", virusTotalBaseURL+virusTotalFileUploadEndpoint, &b) + if err != nil { + log.Fatalf("Error creating upload request: %v", err) + } + req.Header.Set("x-apikey", apiKey) + req.Header.Set("Content-Type", writer.FormDataContentType()) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Fatalf("Error uploading file: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + log.Fatalf("Unexpected response from VirusTotal upload: %v", req) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatalf("Error reading upload response body: %v", err) + } + + var result map[string]interface{} + if err := json.Unmarshal(body, &result); err != nil { + log.Fatalf("Error parsing upload response JSON: %v", err) + } + + // Extract the File ID for querying the report + fileID, ok := result["data"].(map[string]interface{})["id"].(string) + if !ok { + log.Fatalf("File ID not found in response") + } + fmt.Println("File uploaded successfully. Fetching scan report...") + fetchVirusTotalReport(apiKey, fileID) + + + // fmt.Println("File uploaded successfully. VirusTotal scan results:") + // fmt.Printf("Upload Response: %v\n", result["data"]) +} + +func fetchVirusTotalReport(apiKey, fileID string) { + client := &http.Client{} + url := fmt.Sprintf("%s%s%s", virusTotalBaseURL, virusTotalFileReportEndpoint, fileID) + + for attempts := 0; attempts < 10; attempts++ { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Fatalf("Error creating request: %v", err) + } + req.Header.Set("x-apikey", apiKey) + req.Header.Set("accept", "application/json") + + resp, err := client.Do(req) + if err != nil { + log.Fatalf("Error making request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + fmt.Println("Report not ready. Retrying in 10 seconds...") + time.Sleep(10 * time.Second) + continue + } + + if resp.StatusCode != http.StatusOK { + log.Fatalf("Unexpected response from VirusTotal report API: %v", resp) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatalf("Error reading response body: %v", err) + } + + var result map[string]interface{} + if err := json.Unmarshal(body, &result); err != nil { + log.Fatalf("Error parsing JSON response: %v", err) + } + displayVirusTotalReport(result) + fmt.Println("VirusTotal Scan Report:") + fmt.Printf("%v\n", result["data"]) + return + } + + fmt.Println("Report could not be retrieved within the timeout period.") +} + +func displayVirusTotalReport(report map[string]interface{}) { + data, ok := report["data"].(map[string]interface{}) + if !ok { + log.Fatalf("Malformed report response") + } + + attributes, ok := data["attributes"].(map[string]interface{}) + if !ok { + log.Fatalf("Report attributes not found") + } + + fmt.Println("VirusTotal Scan Report:") + fmt.Printf("Resource: %s\n", data["id"]) + fmt.Printf("Last Analysis Stats: %v\n", attributes["last_analysis_stats"]) + fmt.Println("Antivirus Results:") + + results, ok := attributes["last_analysis_results"].(map[string]interface{}) + if !ok { + log.Fatalf("Analysis results not found") + } + + for av, details := range results { + detailMap := details.(map[string]interface{}) + fmt.Printf("- %s: %s\n", av, detailMap["result"]) + } +} From b6daedf4c8433d94698a98fb3502b64707b203af Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Thu, 2 Jan 2025 20:24:18 +0100 Subject: [PATCH 02/41] Fix file check --- cmd/file_check.go | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/cmd/file_check.go b/cmd/file_check.go index ba9a863..39b6176 100644 --- a/cmd/file_check.go +++ b/cmd/file_check.go @@ -13,15 +13,15 @@ import ( "encoding/hex" "encoding/json" "fmt" + "github.com/spf13/cobra" + "github.com/spf13/viper" "io" "log" + "mime/multipart" "net/http" "os" - "time" - "mime/multipart" "strings" - "github.com/spf13/cobra" - "github.com/spf13/viper" + "time" ) const virusTotalBaseURL = "https://www.virustotal.com/api/v3" @@ -59,6 +59,7 @@ func checkFileOnVirusTotal(filePath string) { // Check if file already exists in VirusTotal if fileExistsInVirusTotal(apiKey, hash) { fmt.Println("File already analyzed on VirusTotal.") + } else { // Ask for confirmation before uploading if confirmUpload() { @@ -105,7 +106,6 @@ func fileExistsInVirusTotal(apiKey, hash string) bool { req.Header.Set("x-apikey", apiKey) req.Header.Add("accept", "application/json") - resp, err := client.Do(req) if err != nil { log.Fatalf("Error making request: %v", err) @@ -117,7 +117,7 @@ func fileExistsInVirusTotal(apiKey, hash string) bool { } if resp.StatusCode != http.StatusOK { - log.Fatalf("Unexpected response from VirusTotal: %v", resp.Status) + log.Fatalf("Unexpected response from VirusTotal: %v", resp) } body, err := io.ReadAll(resp.Body) @@ -130,13 +130,13 @@ func fileExistsInVirusTotal(apiKey, hash string) bool { log.Fatalf("Error parsing JSON response: %v", err) } - fmt.Printf("VirusTotal Analysis Summary: %v\n", result["data"]) + displayVirusTotalReport(result) return true } func uploadFileToVirusTotal(apiKey, filePath string) { - + // Create a buffer to hold the multipart form data var b bytes.Buffer writer := multipart.NewWriter(&b) @@ -147,7 +147,6 @@ func uploadFileToVirusTotal(apiKey, filePath string) { } defer file.Close() - part, err := writer.CreateFormFile("file", filePath) if err != nil { log.Fatalf("CreateFormFile: %v", err) @@ -165,7 +164,6 @@ func uploadFileToVirusTotal(apiKey, filePath string) { log.Fatalf("writer.Close: %v", err) } - req, err := http.NewRequest("POST", virusTotalBaseURL+virusTotalFileUploadEndpoint, &b) if err != nil { log.Fatalf("Error creating upload request: %v", err) @@ -201,10 +199,6 @@ func uploadFileToVirusTotal(apiKey, filePath string) { } fmt.Println("File uploaded successfully. Fetching scan report...") fetchVirusTotalReport(apiKey, fileID) - - - // fmt.Println("File uploaded successfully. VirusTotal scan results:") - // fmt.Printf("Upload Response: %v\n", result["data"]) } func fetchVirusTotalReport(apiKey, fileID string) { From b06f3e00f702cf9b709a1237eecd04dc1958374c Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Thu, 2 Jan 2025 20:24:44 +0100 Subject: [PATCH 03/41] Refactor check IP --- cmd/ip.go | 32 +++++++++++++++++++++----------- internal/apis/abuseipdb.go | 5 +++-- internal/apis/greynoise.go | 5 +++-- internal/apis/ipinfo.go | 5 +++-- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/cmd/ip.go b/cmd/ip.go index 0c73bab..159b236 100644 --- a/cmd/ip.go +++ b/cmd/ip.go @@ -9,6 +9,7 @@ package cmd import ( "fmt" "log" + "net" "os" "soc-cli/internal/apis" "soc-cli/internal/util" @@ -24,19 +25,28 @@ import ( var reportLimit = 3 var reportMaxLen int -func analyzeIP(ip string) { +func analyzeIP(inputIP string) { + + ip := net.ParseIP(inputIP) + if ip == nil { + color.Red("Invalid IP address.") + os.Exit(1) + } // Validate provided IP address - if util.IPRegex.MatchString(ip) { - if util.RFC1918Regex.MatchString(ip) { - fmt.Printf("The IP provided %s is a RFC1918 bogus IP address.\n", ip) - os.Exit(0) - } else if ip == "127.0.0.1" { - fmt.Printf("The IP provided %s is a loopback IP address.\n", ip) - os.Exit(0) - } - } else { - log.Fatalf("The IP provided %s is not a valid IPv4 address.\n", ip) + switch { + case ip.IsPrivate(): + color.Red("The IP %s is a RFC1918 bogus IP address.\n", ip) + os.Exit(0) + case ip.IsLoopback(): + color.Red("The IP %s is a loopback IP address.\n", ip) + os.Exit(0) + case ip.IsMulticast(): + color.Red("The IP %s is a multicast IP address.\n", ip) + os.Exit(0) + case ip.To16() != nil && ip.To4() == nil: + color.Red("IPv6 addresses are not supported yet.") + os.Exit(0) } greyNoiseApiKey := viper.GetString("api_keys.greynoise.api_key") diff --git a/internal/apis/abuseipdb.go b/internal/apis/abuseipdb.go index 5f07f6a..0cd8b9e 100644 --- a/internal/apis/abuseipdb.go +++ b/internal/apis/abuseipdb.go @@ -9,6 +9,7 @@ package apis import ( "fmt" "log" + "net" "soc-cli/internal/util" ) @@ -38,8 +39,8 @@ type abuseIPDBResponse struct { } // getAbuseIPDBInfo fetches data from AbuseIPDB for a specific IP address -func GetAbuseIPDBInfo(ip string, apiKey string) *abuseIPDBResponse { - apiUrl := fmt.Sprintf(abuseAPIURL, ip) +func GetAbuseIPDBInfo(ip net.IP, apiKey string) *abuseIPDBResponse { + apiUrl := fmt.Sprintf(abuseAPIURL, ip.String()) headers := map[string]string{ "Key": apiKey, diff --git a/internal/apis/greynoise.go b/internal/apis/greynoise.go index 7735c52..40d3057 100644 --- a/internal/apis/greynoise.go +++ b/internal/apis/greynoise.go @@ -9,6 +9,7 @@ package apis import ( "fmt" "log" + "net" "soc-cli/internal/util" ) @@ -24,8 +25,8 @@ type greyNoiseInfo struct { } // Get threat intelligence from GreyNoise API -func GetGreyNoiseData(ip string, apiKey string) *greyNoiseInfo { - apiUrl := fmt.Sprintf(greyNoiseAPIURL, ip) +func GetGreyNoiseData(ip net.IP, apiKey string) *greyNoiseInfo { + apiUrl := fmt.Sprintf(greyNoiseAPIURL, ip.String()) headers := map[string]string{ "key": apiKey, diff --git a/internal/apis/ipinfo.go b/internal/apis/ipinfo.go index 19a7dea..727ff1d 100644 --- a/internal/apis/ipinfo.go +++ b/internal/apis/ipinfo.go @@ -9,6 +9,7 @@ package apis import ( "fmt" "log" + "net" "soc-cli/internal/util" ) @@ -21,8 +22,8 @@ type ipInfo struct { Org string `json:"org"` } -func GetIPInfo(ip string, apiKey string) *ipInfo { - apiUrl := fmt.Sprintf(ipInfoAPIURL, ip, apiKey) +func GetIPInfo(ip net.IP, apiKey string) *ipInfo { + apiUrl := fmt.Sprintf(ipInfoAPIURL, ip.String(), apiKey) var info ipInfo From cde5083332071e05b8c8c8eef07818af4755b3bc Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Thu, 2 Jan 2025 20:55:56 +0100 Subject: [PATCH 04/41] Smaller function --- cmd/ip.go | 10 +++++++--- internal/util/util.go | 4 ++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/cmd/ip.go b/cmd/ip.go index 159b236..d4a9282 100644 --- a/cmd/ip.go +++ b/cmd/ip.go @@ -25,9 +25,8 @@ import ( var reportLimit = 3 var reportMaxLen int -func analyzeIP(inputIP string) { - - ip := net.ParseIP(inputIP) +func checkInput(input string) { + ip := net.ParseIP(input) if ip == nil { color.Red("Invalid IP address.") os.Exit(1) @@ -49,6 +48,11 @@ func analyzeIP(inputIP string) { os.Exit(0) } + analyzeIP(ip) +} + +func analyzeIP(ip net.IP) { + greyNoiseApiKey := viper.GetString("api_keys.greynoise.api_key") if greyNoiseApiKey == "" { log.Println("GreyNoise API key is missing! Please set the greynoise api_key in config.yaml file") diff --git a/internal/util/util.go b/internal/util/util.go index abe2f86..58f5aad 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -64,3 +64,7 @@ func formatDuration(d time.Duration) string { } return "just now" } + +func IsValidDomain(domain string) bool { + return DomainRegex.MatchString(domain) +} From cccb1c4b4361dd71d37eb75c00faf4a0998b22c1 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Thu, 2 Jan 2025 20:57:22 +0100 Subject: [PATCH 05/41] Fix --- cmd/ip.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/ip.go b/cmd/ip.go index d4a9282..fb20afd 100644 --- a/cmd/ip.go +++ b/cmd/ip.go @@ -140,8 +140,8 @@ var ipCmd = &cobra.Command{ Short: "Analyze an IP address for geolocation, ASN, and threat status", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { - ip := args[0] - analyzeIP(ip) + input := args[0] + checkInput(input) }, } From 555d36677205c5ebc0918b09cb4b1b3146b5fc21 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Fri, 3 Jan 2025 21:07:35 +0100 Subject: [PATCH 06/41] Red color for errors --- cmd/ip.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/ip.go b/cmd/ip.go index fb20afd..5d0f16d 100644 --- a/cmd/ip.go +++ b/cmd/ip.go @@ -8,7 +8,6 @@ package cmd import ( "fmt" - "log" "net" "os" "soc-cli/internal/apis" @@ -55,17 +54,17 @@ func analyzeIP(ip net.IP) { greyNoiseApiKey := viper.GetString("api_keys.greynoise.api_key") if greyNoiseApiKey == "" { - log.Println("GreyNoise API key is missing! Please set the greynoise api_key in config.yaml file") + color.Red("GreyNoise API key is missing! Please set the greynoise api_key in config.yaml file") } ipInfoApiKey := viper.GetString("api_keys.ipinfo.api_key") if ipInfoApiKey == "" { - log.Println("API key is missing! Please set the ipinfo api_key in config.yaml file") + color.Red("API key is missing! Please set the ipinfo api_key in config.yaml file") } abuseIPDBApiKey := viper.GetString("api_keys.abuseipdb.api_key") if abuseIPDBApiKey == "" { - log.Println("API key is missing! Please set the abuseipdb api_key in config.yaml file") + color.Red("API key is missing! Please set the abuseipdb api_key in config.yaml file") } // Fetch IpInfo api From d94b9b4594cfb9a36223d08fadb8c97a29c68cb4 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Fri, 3 Jan 2025 21:18:30 +0100 Subject: [PATCH 07/41] Base64 encode and decode --- cmd/b64d.go | 35 +++++++++++++++++++++++++++++++++++ cmd/b64e.go | 30 ++++++++++++++++++++++++++++++ cmd/misc.go | 21 +++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 cmd/b64d.go create mode 100644 cmd/b64e.go create mode 100644 cmd/misc.go diff --git a/cmd/b64d.go b/cmd/b64d.go new file mode 100644 index 0000000..df013da --- /dev/null +++ b/cmd/b64d.go @@ -0,0 +1,35 @@ +/* +Copyright © 2025 Alessandro Riva + +Licensed under the MIT License. +See the LICENSE file for details. +*/ +package cmd + +import ( + "encoding/base64" + "fmt" + + "github.com/spf13/cobra" + "github.com/fatih/color" +) + +var b64dCmd = &cobra.Command{ + Use: "b64d ", + Short: "Decode a Base64 string", + Long: "Decode a Base64-encoded string and display the original content.", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + base64Str := args[0] + decoded, err := base64.StdEncoding.DecodeString(base64Str) + if err != nil { + color.Red("Error decoding Base64 string: %v", err) + } + fmt.Println(string(decoded)) + }, +} + +// Register the b64d subcommand under misc +func init() { + miscCmd.AddCommand(b64dCmd) +} diff --git a/cmd/b64e.go b/cmd/b64e.go new file mode 100644 index 0000000..0f4ec3d --- /dev/null +++ b/cmd/b64e.go @@ -0,0 +1,30 @@ +/* +Copyright © 2025 Alessandro Riva + +Licensed under the MIT License. +See the LICENSE file for details. +*/ +package cmd + +import ( + "encoding/base64" + "fmt" + "github.com/spf13/cobra" +) + +var b64eCmd = &cobra.Command{ + Use: "b64e ", + Short: "Encode a string to Base64", + Long: "Encode a string to its Base64 representation.", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + input := args[0] + encoded := base64.StdEncoding.EncodeToString([]byte(input)) + fmt.Println(encoded) + }, +} + +// Register the b64e subcommand under misc +func init() { + miscCmd.AddCommand(b64eCmd) +} \ No newline at end of file diff --git a/cmd/misc.go b/cmd/misc.go new file mode 100644 index 0000000..f2fa249 --- /dev/null +++ b/cmd/misc.go @@ -0,0 +1,21 @@ +/* +Copyright © 2025 Alessandro Riva + +Licensed under the MIT License. +See the LICENSE file for details. +*/ +package cmd + +import ( + "github.com/spf13/cobra" +) + +var miscCmd = &cobra.Command{ + Use: "misc", + Short: "Miscellaneous utilities", + Long: "A collection of miscellaneous utilities for various tasks.", +} + +func init() { + rootCmd.AddCommand(miscCmd) +} From 97d42dc5fe353eade7f81d6a57a0395dfe8eeea9 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sat, 4 Jan 2025 14:41:12 +0100 Subject: [PATCH 08/41] Parsed VT response --- cmd/file_check.go | 49 ++++++++++++++-------------------- internal/apis/virustotal.go | 52 +++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 30 deletions(-) create mode 100644 internal/apis/virustotal.go diff --git a/cmd/file_check.go b/cmd/file_check.go index 39b6176..7935eb3 100644 --- a/cmd/file_check.go +++ b/cmd/file_check.go @@ -13,6 +13,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "github.com/fatih/color" "github.com/spf13/cobra" "github.com/spf13/viper" "io" @@ -20,6 +21,7 @@ import ( "mime/multipart" "net/http" "os" + "soc-cli/internal/apis" "strings" "time" ) @@ -46,19 +48,21 @@ func init() { func checkFileOnVirusTotal(filePath string) { apiKey := viper.GetString("api_keys.virustotal.api_key") if apiKey == "" { - log.Fatal("VirusTotal API key missing! Please set it in the config file.") + color.Red("VirusTotal API key missing! Please set it in the config file.") + os.Exit(1) } hash, err := calculateSHA256(filePath) if err != nil { - log.Fatalf("Error calculating file hash: %v", err) + color.Red("Error calculating file hash: %v", err) + os.Exit(1) } fmt.Printf("File SHA256: %s\n", hash) // Check if file already exists in VirusTotal if fileExistsInVirusTotal(apiKey, hash) { - fmt.Println("File already analyzed on VirusTotal.") + color.Green("File already analyzed on VirusTotal.") } else { // Ask for confirmation before uploading @@ -125,7 +129,7 @@ func fileExistsInVirusTotal(apiKey, hash string) bool { log.Fatalf("Error reading response body: %v", err) } - var result map[string]interface{} + var result apis.VTResponse if err := json.Unmarshal(body, &result); err != nil { log.Fatalf("Error parsing JSON response: %v", err) } @@ -234,42 +238,27 @@ func fetchVirusTotalReport(apiKey, fileID string) { log.Fatalf("Error reading response body: %v", err) } - var result map[string]interface{} + var result apis.VTResponse if err := json.Unmarshal(body, &result); err != nil { log.Fatalf("Error parsing JSON response: %v", err) } displayVirusTotalReport(result) fmt.Println("VirusTotal Scan Report:") - fmt.Printf("%v\n", result["data"]) return } - fmt.Println("Report could not be retrieved within the timeout period.") + color.Red("Report could not be retrieved within the timeout period.") } -func displayVirusTotalReport(report map[string]interface{}) { - data, ok := report["data"].(map[string]interface{}) - if !ok { - log.Fatalf("Malformed report response") - } - - attributes, ok := data["attributes"].(map[string]interface{}) - if !ok { - log.Fatalf("Report attributes not found") - } - - fmt.Println("VirusTotal Scan Report:") - fmt.Printf("Resource: %s\n", data["id"]) - fmt.Printf("Last Analysis Stats: %v\n", attributes["last_analysis_stats"]) - fmt.Println("Antivirus Results:") +func displayVirusTotalReport(report apis.VTResponse) { - results, ok := attributes["last_analysis_results"].(map[string]interface{}) - if !ok { - log.Fatalf("Analysis results not found") - } + color.Blue("VirusTotal Scan Report:") - for av, details := range results { - detailMap := details.(map[string]interface{}) - fmt.Printf("- %s: %s\n", av, detailMap["result"]) - } + fmt.Printf("\nType: %s\n", report.Data.Type) + fmt.Printf("Magic: %v\n", report.Data.Attributes.Magic) + fmt.Printf("Self Link: %s\n", report.Data.Links.Self) + fmt.Printf("Reputation: %d\n", report.Data.Attributes.Reputation) + fmt.Printf("Meaningful Name: %s\n", report.Data.Attributes.MeaningfulName) + fmt.Printf("Analysis result: malicious %v, undetected %v, harmless %v\n", report.Data.Attributes.LastAnalysisStats.Malicious, report.Data.Attributes.LastAnalysisStats.Suspicious, report.Data.Attributes.LastAnalysisStats.Harmless) + fmt.Printf("SHA256: %s\n", report.Data.Attributes.Sha256) } diff --git a/internal/apis/virustotal.go b/internal/apis/virustotal.go new file mode 100644 index 0000000..f5c7a73 --- /dev/null +++ b/internal/apis/virustotal.go @@ -0,0 +1,52 @@ +/* +Copyright © 2025 Alessandro Riva + +Licensed under the MIT License. +See the LICENSE file for details. +*/ +package apis + +// Define the structs to match the JSON structure +type VTResponse struct { + Data Data `json:"data"` +} + +type Data struct { + ID string `json:"id"` + Type string `json:"type"` + Links Links `json:"links"` + Attributes Attributes `json:"attributes"` +} + +type Links struct { + Self string `json:"self"` +} + +type Attributes struct { + Reputation int `json:"reputation"` + LastModificationDate int64 `json:"last_modification_date"` + Magic string `json:"magic"` + Ssdeep string `json:"ssdeep"` + MeaningfulName string `json:"meaningful_name"` + TypeDescription string `json:"type_description"` + Vhash string `json:"vhash"` + Sha256 string `json:"sha256"` + TypeTags []string `json:"type_tags"` + TimesSubmitted int `json:"times_submitted"` + LastAnalysisStats LastAnalysisStats `json:"last_analysis_stats"` + TypeExtension string `json:"type_extension"` + TypeTag string `json:"type_tag"` + LastSubmissionDate int64 `json:"last_submission_date"` + Sha1 string `json:"sha1"` +} + +type LastAnalysisStats struct { + Malicious int `json:"malicious"` + Suspicious int `json:"suspicious"` + Undetected int `json:"undetected"` + Harmless int `json:"harmless"` + Timeout int `json:"timeout"` + ConfirmedTimeout int `json:"confirmed-timeout"` + Failure int `json:"failure"` + TypeUnsupported int `json:"type-unsupported"` +} From 9dcab4e1c7233dbf982441bac2c655f0365dc801 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sat, 4 Jan 2025 14:55:25 +0100 Subject: [PATCH 09/41] Fixes --- cmd/file_check.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/cmd/file_check.go b/cmd/file_check.go index 7935eb3..9bc86c6 100644 --- a/cmd/file_check.go +++ b/cmd/file_check.go @@ -61,8 +61,9 @@ func checkFileOnVirusTotal(filePath string) { fmt.Printf("File SHA256: %s\n", hash) // Check if file already exists in VirusTotal - if fileExistsInVirusTotal(apiKey, hash) { + if exist, report := fileExistsInVirusTotal(apiKey, hash); exist { color.Green("File already analyzed on VirusTotal.") + displayVirusTotalReport(report) } else { // Ask for confirmation before uploading @@ -77,7 +78,7 @@ func checkFileOnVirusTotal(filePath string) { func confirmUpload() bool { reader := bufio.NewReader(os.Stdin) - fmt.Print("File not found on VirusTotal. Do you want to upload it for analysis? (y/n): ") + fmt.Print("File not found on VirusTotal. Do you want to upload it for analysis? (y/N): ") response, err := reader.ReadString('\n') if err != nil { log.Fatalf("Error reading input: %v", err) @@ -101,7 +102,7 @@ func calculateSHA256(filePath string) (string, error) { return hex.EncodeToString(hash.Sum(nil)), nil } -func fileExistsInVirusTotal(apiKey, hash string) bool { +func fileExistsInVirusTotal(apiKey, hash string) (exist bool, report apis.VTResponse) { client := &http.Client{} req, err := http.NewRequest("GET", virusTotalBaseURL+virusTotalFileReportEndpoint+hash, nil) if err != nil { @@ -117,7 +118,7 @@ func fileExistsInVirusTotal(apiKey, hash string) bool { defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { - return false // File not found on VirusTotal + return false, apis.VTResponse{} // File not found on VirusTotal } if resp.StatusCode != http.StatusOK { @@ -129,14 +130,11 @@ func fileExistsInVirusTotal(apiKey, hash string) bool { log.Fatalf("Error reading response body: %v", err) } - var result apis.VTResponse - if err := json.Unmarshal(body, &result); err != nil { + if err := json.Unmarshal(body, &report); err != nil { log.Fatalf("Error parsing JSON response: %v", err) } - displayVirusTotalReport(result) - - return true + return true, report } func uploadFileToVirusTotal(apiKey, filePath string) { From c91c0521d87e406faaa1098af6e523b8ce49a7e4 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sat, 4 Jan 2025 15:26:52 +0100 Subject: [PATCH 10/41] Add statuscode and remove redundant code --- cmd/file_check.go | 32 ++++++++------------------------ internal/apis/abuseipdb.go | 2 +- internal/apis/greynoise.go | 2 +- internal/apis/ipinfo.go | 2 +- internal/apis/whodat.go | 2 +- internal/util/api.go | 12 ++++++------ 6 files changed, 18 insertions(+), 34 deletions(-) diff --git a/cmd/file_check.go b/cmd/file_check.go index 9bc86c6..814cbaf 100644 --- a/cmd/file_check.go +++ b/cmd/file_check.go @@ -22,6 +22,7 @@ import ( "net/http" "os" "soc-cli/internal/apis" + "soc-cli/internal/util" "strings" "time" ) @@ -103,35 +104,19 @@ func calculateSHA256(filePath string) (string, error) { } func fileExistsInVirusTotal(apiKey, hash string) (exist bool, report apis.VTResponse) { - client := &http.Client{} - req, err := http.NewRequest("GET", virusTotalBaseURL+virusTotalFileReportEndpoint+hash, nil) - if err != nil { - log.Fatalf("Error creating request: %v", err) - } - req.Header.Set("x-apikey", apiKey) - req.Header.Add("accept", "application/json") - resp, err := client.Do(req) - if err != nil { - log.Fatalf("Error making request: %v", err) + headers := map[string]string{ + "X-Apikey": apiKey, + "Accept": "application/json", } - defer resp.Body.Close() - if resp.StatusCode == http.StatusNotFound { - return false, apis.VTResponse{} // File not found on VirusTotal - } - - if resp.StatusCode != http.StatusOK { - log.Fatalf("Unexpected response from VirusTotal: %v", resp) - } - - body, err := io.ReadAll(resp.Body) + statusCode, err := util.MakeGETRequest(virusTotalBaseURL+virusTotalFileReportEndpoint+hash, headers, &report) if err != nil { - log.Fatalf("Error reading response body: %v", err) + log.Fatalf("Error fetching VirusTotal: %v", err) } - if err := json.Unmarshal(body, &report); err != nil { - log.Fatalf("Error parsing JSON response: %v", err) + if statusCode == http.StatusNotFound { + return false, apis.VTResponse{} // File not found on VirusTotal } return true, report @@ -258,5 +243,4 @@ func displayVirusTotalReport(report apis.VTResponse) { fmt.Printf("Reputation: %d\n", report.Data.Attributes.Reputation) fmt.Printf("Meaningful Name: %s\n", report.Data.Attributes.MeaningfulName) fmt.Printf("Analysis result: malicious %v, undetected %v, harmless %v\n", report.Data.Attributes.LastAnalysisStats.Malicious, report.Data.Attributes.LastAnalysisStats.Suspicious, report.Data.Attributes.LastAnalysisStats.Harmless) - fmt.Printf("SHA256: %s\n", report.Data.Attributes.Sha256) } diff --git a/internal/apis/abuseipdb.go b/internal/apis/abuseipdb.go index 0cd8b9e..13fcc99 100644 --- a/internal/apis/abuseipdb.go +++ b/internal/apis/abuseipdb.go @@ -49,7 +49,7 @@ func GetAbuseIPDBInfo(ip net.IP, apiKey string) *abuseIPDBResponse { var data abuseIPDBResponse - err := util.MakeGETRequest(apiUrl, headers, &data) + _, err := util.MakeGETRequest(apiUrl, headers, &data) if err != nil { log.Fatalf("Error fetching AbuseIPDB info: %v", err) } diff --git a/internal/apis/greynoise.go b/internal/apis/greynoise.go index 40d3057..d62bcec 100644 --- a/internal/apis/greynoise.go +++ b/internal/apis/greynoise.go @@ -34,7 +34,7 @@ func GetGreyNoiseData(ip net.IP, apiKey string) *greyNoiseInfo { var greyNoiseData greyNoiseInfo - err := util.MakeGETRequest(apiUrl, headers, &greyNoiseData) + _, err := util.MakeGETRequest(apiUrl, headers, &greyNoiseData) if err != nil { log.Fatalf("Error fetching AbuseIPDB info: %v", err) } diff --git a/internal/apis/ipinfo.go b/internal/apis/ipinfo.go index 727ff1d..a94c6bc 100644 --- a/internal/apis/ipinfo.go +++ b/internal/apis/ipinfo.go @@ -27,7 +27,7 @@ func GetIPInfo(ip net.IP, apiKey string) *ipInfo { var info ipInfo - err := util.MakeGETRequest(apiUrl, nil, &info) + _, err := util.MakeGETRequest(apiUrl, nil, &info) if err != nil { log.Fatalf("Error fetching IP info: %v", err) } diff --git a/internal/apis/whodat.go b/internal/apis/whodat.go index 237f873..afe6d29 100644 --- a/internal/apis/whodat.go +++ b/internal/apis/whodat.go @@ -68,7 +68,7 @@ func GetWhoisData(domain string) (*DomainInfo, error) { var whois DomainInfo - err := util.MakeGETRequest(apiUrl, nil, &whois) + _, err := util.MakeGETRequest(apiUrl, nil, &whois) return &whois, err } diff --git a/internal/util/api.go b/internal/util/api.go index 47639eb..f2cc72b 100644 --- a/internal/util/api.go +++ b/internal/util/api.go @@ -26,10 +26,10 @@ func init() { } } -func MakeGETRequest(url string, headers map[string]string, target interface{}) error { +func MakeGETRequest(url string, headers map[string]string, target interface{}) (sc int, err error) { req, err := http.NewRequest("GET", url, nil) if err != nil { - return fmt.Errorf("error creating request: %w", err) + return 0, fmt.Errorf("error creating request: %w", err) } for key, value := range headers { @@ -47,13 +47,13 @@ func MakeGETRequest(url string, headers map[string]string, target interface{}) e client := &http.Client{} resp, err := client.Do(req) if err != nil { - return fmt.Errorf("error making request: %w", err) + return resp.StatusCode, fmt.Errorf("error making request: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { - return fmt.Errorf("error reading response body: %w", err) + return resp.StatusCode, fmt.Errorf("error reading response body: %w", err) } if debug { @@ -62,10 +62,10 @@ func MakeGETRequest(url string, headers map[string]string, target interface{}) e err = json.Unmarshal(body, target) if err != nil { - return fmt.Errorf("error unmarshalling JSON response: %w", err) + return resp.StatusCode, fmt.Errorf("error unmarshalling JSON response: %w", err) } - return nil + return resp.StatusCode, nil } func MakePOSTRequest(url string, headers map[string]string, body interface{}, target interface{}) error { From c513ad5cc8bac994f791359c7f41f57b3888cb2d Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sat, 4 Jan 2025 20:03:49 +0100 Subject: [PATCH 11/41] Fix links in email --- cmd/email.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/cmd/email.go b/cmd/email.go index cf976a3..d9d0025 100644 --- a/cmd/email.go +++ b/cmd/email.go @@ -13,6 +13,7 @@ import ( "io" "mime" "mime/multipart" + "mime/quotedprintable" "net/mail" "os" "soc-cli/internal/util" @@ -35,6 +36,11 @@ func init() { // analyzeEmail processes the .eml file and extracts attachments and links func analyzeEmail(filePath string) { + if !strings.HasSuffix(strings.ToLower(filePath), ".eml") { + color.Red("The provided file is not an .eml file.") + return + + } file, err := os.Open(filePath) if err != nil { fmt.Println("Error opening file:", err) @@ -93,7 +99,20 @@ func analyzeEmail(filePath string) { } else { // Handle single-part emails (just extract links) body, _ := io.ReadAll(msg.Body) - extractLinks(string(body)) + // Check for Content-Transfer-Encoding + encoding := msg.Header.Get("Content-Transfer-Encoding") + if strings.ToLower(encoding) == "quoted-printable" { + // Decode quoted-printable content + reader := quotedprintable.NewReader(strings.NewReader(string(body))) + decodedBody, err := io.ReadAll(reader) + if err != nil { + fmt.Println("Error decoding quoted-printable content:", err) + return + } + extractLinks(string(decodedBody)) + } else { + extractLinks(string(body)) + } } } From 4d8bf2d70cdb92026d3baaed60ccaa8b01d3a4b9 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sat, 4 Jan 2025 20:22:43 +0100 Subject: [PATCH 12/41] Better email code --- cmd/email.go | 59 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/cmd/email.go b/cmd/email.go index d9d0025..ad9e9c1 100644 --- a/cmd/email.go +++ b/cmd/email.go @@ -20,6 +20,15 @@ import ( "strings" ) +const ( + emlExtension = ".eml" + contentTypeHeader = "Content-Type" + transferEncodingHeader = "Content-Transfer-Encoding" + receivedSPFHeader = "Received-SPF" + dkimSignatureHeader = "DKIM-Signature" + authenticationResultsHeader = "Authentication-Results" +) + var analyzeEmailCmd = &cobra.Command{ Use: "email [file]", Short: "Analyze an email in .eml format for attachments and links", @@ -36,7 +45,7 @@ func init() { // analyzeEmail processes the .eml file and extracts attachments and links func analyzeEmail(filePath string) { - if !strings.HasSuffix(strings.ToLower(filePath), ".eml") { + if !strings.HasSuffix(strings.ToLower(filePath), emlExtension) { color.Red("The provided file is not an .eml file.") return @@ -54,39 +63,24 @@ func analyzeEmail(filePath string) { fmt.Println("Error parsing .eml file:", err) return } - color.Blue("Main information:") - fmt.Println("From:", msg.Header.Get("From")) - fmt.Println("To:", msg.Header.Get("To")) - fmt.Println("Subject:", msg.Header.Get("Subject")) - fmt.Println("Date:", msg.Header.Get("Date")) - fmt.Println("Return-Path:", msg.Header.Get("Return-Path")) + + printEmailHeaders(msg) // Check for SPF information - spfHeader := msg.Header.Get("Received-SPF") - if spfHeader != "" { - fmt.Println(color.BlueString("\nSPF Information:\n"), spfHeader) - } else { - fmt.Println(color.BlueString("\nSPF Information:\n") + "No Received-SPF header found.") - } + printHeaderInfo(msg.Header.Get(receivedSPFHeader), "SPF Information") // Extract DKIM Information - dkimHeader := msg.Header.Get("DKIM-Signature") - if dkimHeader != "" { - color.Blue("\nDKIM Information:") - fmt.Println(dkimHeader) - } else { - fmt.Println(color.BlueString("\nDKIM Information:\n") + "No DKIM-Signature header found.") - } + printHeaderInfo(msg.Header.Get(dkimSignatureHeader), "DKIM Information") // Extract DMARC Information from Authentication-Results header - authResults := msg.Header.Get("Authentication-Results") + authResults := msg.Header.Get(authenticationResultsHeader) if authResults != "" { extractDMARCDKIM(authResults) } else { fmt.Println("\nDMARC Information: No Authentication-Results header found.") } - mediaType, params, err := mime.ParseMediaType(msg.Header.Get("Content-Type")) + mediaType, params, err := mime.ParseMediaType(msg.Header.Get(contentTypeHeader)) if err != nil { fmt.Println("Error parsing content type:", err) return @@ -100,7 +94,7 @@ func analyzeEmail(filePath string) { // Handle single-part emails (just extract links) body, _ := io.ReadAll(msg.Body) // Check for Content-Transfer-Encoding - encoding := msg.Header.Get("Content-Transfer-Encoding") + encoding := msg.Header.Get(transferEncodingHeader) if strings.ToLower(encoding) == "quoted-printable" { // Decode quoted-printable content reader := quotedprintable.NewReader(strings.NewReader(string(body))) @@ -128,7 +122,7 @@ func processMultipart(mr *multipart.Reader) { return } - contentType := part.Header.Get("Content-Type") + contentType := part.Header.Get(contentTypeHeader) disposition := part.Header.Get("Content-Disposition") // If it's an attachment, list it @@ -183,3 +177,20 @@ func extractLinks(body string) { color.Blue("\nNo links found in the email.") } } + +func printEmailHeaders(msg *mail.Message) { + color.Blue("Main information:") + fmt.Println("From:", msg.Header.Get("From")) + fmt.Println("To:", msg.Header.Get("To")) + fmt.Println("Subject:", msg.Header.Get("Subject")) + fmt.Println("Date:", msg.Header.Get("Date")) + fmt.Println("Return-Path:", msg.Header.Get("Return-Path")) +} + +func printHeaderInfo(headerValue, headerName string) { + if headerValue != "" { + fmt.Println(color.BlueString("\n%s:\n", headerName), headerValue) + } else { + fmt.Println(color.BlueString("\n%s:\n", headerName) + "No information found.") + } +} From 372551bea4cab95bb3f9f401dc8f9c0ba25a32cc Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sat, 4 Jan 2025 22:42:32 +0100 Subject: [PATCH 13/41] Better IP code --- cmd/ip.go | 88 ++++++++++++++++++++++---------------- internal/apis/abuseipdb.go | 6 +-- internal/apis/greynoise.go | 6 +-- internal/apis/ipinfo.go | 6 +-- 4 files changed, 60 insertions(+), 46 deletions(-) diff --git a/cmd/ip.go b/cmd/ip.go index 5d0f16d..7fd702a 100644 --- a/cmd/ip.go +++ b/cmd/ip.go @@ -7,24 +7,31 @@ See the LICENSE file for details. package cmd import ( + "errors" "fmt" + "github.com/fatih/color" + "github.com/rodaine/table" + "github.com/spf13/cobra" + "github.com/spf13/viper" "net" "os" "soc-cli/internal/apis" "soc-cli/internal/util" "strings" "time" +) - "github.com/fatih/color" - "github.com/rodaine/table" - "github.com/spf13/cobra" - "github.com/spf13/viper" +const ( + reportLimit = 3 + defaultReportMaxLen = 100 + greyNoiseAPIKeyMsg = "GreyNoise API key is missing! Please set the greynoise api_key in config.yaml file" + ipInfoAPIKeyMsg = "API key is missing! Please set the ipinfo api_key in config.yaml file" + abuseIPDBAPIKeyMsg = "API key is missing! Please set the abuseipdb api_key in config.yaml file" ) -var reportLimit = 3 var reportMaxLen int -func checkInput(input string) { +func checkInput(input string) error { ip := net.ParseIP(input) if ip == nil { color.Red("Invalid IP address.") @@ -34,52 +41,58 @@ func checkInput(input string) { // Validate provided IP address switch { case ip.IsPrivate(): - color.Red("The IP %s is a RFC1918 bogus IP address.\n", ip) - os.Exit(0) + return fmt.Errorf("the IP %s is a RFC1918 bogus IP address", ip) case ip.IsLoopback(): - color.Red("The IP %s is a loopback IP address.\n", ip) - os.Exit(0) + return fmt.Errorf("the IP %s is a loopback IP address", ip) case ip.IsMulticast(): - color.Red("The IP %s is a multicast IP address.\n", ip) - os.Exit(0) + return fmt.Errorf("the IP %s is a multicast IP address", ip) case ip.To16() != nil && ip.To4() == nil: - color.Red("IPv6 addresses are not supported yet.") - os.Exit(0) + return fmt.Errorf("IPv6 addresses are not supported yet") } analyzeIP(ip) + return nil } -func analyzeIP(ip net.IP) { - - greyNoiseApiKey := viper.GetString("api_keys.greynoise.api_key") - if greyNoiseApiKey == "" { - color.Red("GreyNoise API key is missing! Please set the greynoise api_key in config.yaml file") +func checkAPIKeys() error { + if viper.GetString("api_keys.greynoise.api_key") == "" { + return errors.New(greyNoiseAPIKeyMsg) } - - ipInfoApiKey := viper.GetString("api_keys.ipinfo.api_key") - if ipInfoApiKey == "" { - color.Red("API key is missing! Please set the ipinfo api_key in config.yaml file") + if viper.GetString("api_keys.ipinfo.api_key") == "" { + return errors.New(ipInfoAPIKeyMsg) } + if viper.GetString("api_keys.abuseipdb.api_key") == "" { + return errors.New(abuseIPDBAPIKeyMsg) + } + return nil +} + +func analyzeIP(ip net.IP) { - abuseIPDBApiKey := viper.GetString("api_keys.abuseipdb.api_key") - if abuseIPDBApiKey == "" { - color.Red("API key is missing! Please set the abuseipdb api_key in config.yaml file") + if err := checkAPIKeys(); err != nil { + color.Red(err.Error()) + os.Exit(1) } - // Fetch IpInfo api - ipInfoData := apis.GetIPInfo(ip, ipInfoApiKey) + // Fetch IP information + ipInfoData := apis.GetIPInfo(ip, viper.GetString("api_keys.ipinfo.api_key")) + greyNoiseData := apis.GetGreyNoiseData(ip, viper.GetString("api_keys.greynoise.api_key")) + abuseIPDBData := apis.GetAbuseIPDBInfo(ip, viper.GetString("api_keys.abuseipdb.api_key")) - // Fetch GreyNoise threat intelligence - greyNoiseData := apis.GetGreyNoiseData(ip, greyNoiseApiKey) + printIPInfo(ipInfoData) + printGreyNoiseData(greyNoiseData) + printAbuseIPDBData(abuseIPDBData) - abuseIPDBData := apis.GetAbuseIPDBInfo(ip, abuseIPDBApiKey) +} - // Print the IP information +func printIPInfo(ipInfoData *apis.IPInfo) { color.Blue("IP information from IPInfo") fmt.Printf("IP: %s\nHostname: %s\nOrg: %s\nCountry: %s\n", ipInfoData.IP, ipInfoData.Hostname, ipInfoData.Org, ipInfoData.Country) +} + +func printGreyNoiseData(greyNoiseData *apis.GreyNoiseInfo) { if greyNoiseData != nil { color.Blue("\nGreyNoise Threat Intelligence") @@ -93,7 +106,9 @@ func analyzeIP(ip net.IP) { fmt.Printf("Noise: %v\nRiot: %v\nClassification: %s\nName: %s\nLink: %s\n", greyNoiseData.Noise, greyNoiseData.Riot, classification, greyNoiseData.Name, greyNoiseData.Link) } +} +func printAbuseIPDBData(abuseIPDBData *apis.AbuseIPDBResponse) { if abuseIPDBData != nil { color.Blue("\nAbuseIPDB report") if abuseIPDBData.Data.TotalReports == 0 { @@ -127,11 +142,7 @@ func analyzeIP(ip net.IP) { } - } else { - color.Red("An error has occured.") - os.Exit(1) } - } var ipCmd = &cobra.Command{ @@ -140,7 +151,10 @@ var ipCmd = &cobra.Command{ Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { input := args[0] - checkInput(input) + if err := checkInput(input); err != nil { + color.Red("Error: %v", err) + os.Exit(1) + } }, } diff --git a/internal/apis/abuseipdb.go b/internal/apis/abuseipdb.go index 13fcc99..3f38851 100644 --- a/internal/apis/abuseipdb.go +++ b/internal/apis/abuseipdb.go @@ -15,7 +15,7 @@ import ( const abuseAPIURL = "https://api.abuseipdb.com/api/v2/check?ipAddress=%s&maxAgeInDays=90&verbose" -type abuseIPDBResponse struct { +type AbuseIPDBResponse struct { Data struct { IPAddress string `json:"ipAddress"` IsPublic bool `json:"isPublic"` @@ -39,7 +39,7 @@ type abuseIPDBResponse struct { } // getAbuseIPDBInfo fetches data from AbuseIPDB for a specific IP address -func GetAbuseIPDBInfo(ip net.IP, apiKey string) *abuseIPDBResponse { +func GetAbuseIPDBInfo(ip net.IP, apiKey string) *AbuseIPDBResponse { apiUrl := fmt.Sprintf(abuseAPIURL, ip.String()) headers := map[string]string{ @@ -47,7 +47,7 @@ func GetAbuseIPDBInfo(ip net.IP, apiKey string) *abuseIPDBResponse { "Accept": "application/json", } - var data abuseIPDBResponse + var data AbuseIPDBResponse _, err := util.MakeGETRequest(apiUrl, headers, &data) if err != nil { diff --git a/internal/apis/greynoise.go b/internal/apis/greynoise.go index d62bcec..3a8a717 100644 --- a/internal/apis/greynoise.go +++ b/internal/apis/greynoise.go @@ -15,7 +15,7 @@ import ( const greyNoiseAPIURL = "https://api.greynoise.io/v3/community/%s" -type greyNoiseInfo struct { +type GreyNoiseInfo struct { IP string `json:"ip"` Noise bool `json:"noise"` Riot bool `json:"riot"` @@ -25,14 +25,14 @@ type greyNoiseInfo struct { } // Get threat intelligence from GreyNoise API -func GetGreyNoiseData(ip net.IP, apiKey string) *greyNoiseInfo { +func GetGreyNoiseData(ip net.IP, apiKey string) *GreyNoiseInfo { apiUrl := fmt.Sprintf(greyNoiseAPIURL, ip.String()) headers := map[string]string{ "key": apiKey, } - var greyNoiseData greyNoiseInfo + var greyNoiseData GreyNoiseInfo _, err := util.MakeGETRequest(apiUrl, headers, &greyNoiseData) if err != nil { diff --git a/internal/apis/ipinfo.go b/internal/apis/ipinfo.go index a94c6bc..bc7b023 100644 --- a/internal/apis/ipinfo.go +++ b/internal/apis/ipinfo.go @@ -15,17 +15,17 @@ import ( const ipInfoAPIURL = "https://ipinfo.io/%s?token=%s" -type ipInfo struct { +type IPInfo struct { IP string `json:"ip"` Country string `json:"country"` Hostname string `json:"hostname"` Org string `json:"org"` } -func GetIPInfo(ip net.IP, apiKey string) *ipInfo { +func GetIPInfo(ip net.IP, apiKey string) *IPInfo { apiUrl := fmt.Sprintf(ipInfoAPIURL, ip.String(), apiKey) - var info ipInfo + var info IPInfo _, err := util.MakeGETRequest(apiUrl, nil, &info) if err != nil { From 02df83ecd789638daf2e2afa3345b67ebdd7c837 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sun, 5 Jan 2025 13:17:54 +0100 Subject: [PATCH 14/41] Bump Go version --- go.mod | 25 ++++++++++++------------- go.sum | 48 +++++++++++++++++++++++------------------------- 2 files changed, 35 insertions(+), 38 deletions(-) diff --git a/go.mod b/go.mod index fdd9e77..835beb6 100644 --- a/go.mod +++ b/go.mod @@ -1,35 +1,34 @@ module soc-cli -go 1.22.7 +go 1.23.4 require ( - github.com/fatih/color v1.17.0 + github.com/fatih/color v1.18.0 + github.com/rodaine/table v1.3.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 ) require ( - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/magiconair/properties v1.8.7 // indirect + github.com/magiconair/properties v1.8.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/rodaine/table v1.3.0 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/sagikazarmark/locafero v0.6.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.9.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e1cb65a..967b083 100644 --- a/go.sum +++ b/go.sum @@ -3,14 +3,13 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= @@ -20,37 +19,39 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rodaine/table v1.3.0 h1:4/3S3SVkHnVZX91EHFvAMV7K42AnJ0XuymRR2C5HlGE= github.com/rodaine/table v1.3.0/go.mod h1:47zRsHar4zw0jgxGxL9YtFfs7EGN6B/TaS+/Dmk4WxU= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= +github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -61,7 +62,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -69,18 +69,16 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 h1:9kj3STMvgqy3YA4VQXBrN7925ICMxD5wzMRcgA30588= +golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 841472751a6bb0bf1eb06a2d29b78fd7f2adbc29 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sun, 12 Jan 2025 11:30:27 +0100 Subject: [PATCH 15/41] Warn missing API keys --- cmd/ip.go | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/cmd/ip.go b/cmd/ip.go index 7fd702a..19a391b 100644 --- a/cmd/ip.go +++ b/cmd/ip.go @@ -7,7 +7,6 @@ See the LICENSE file for details. package cmd import ( - "errors" "fmt" "github.com/fatih/color" "github.com/rodaine/table" @@ -24,9 +23,9 @@ import ( const ( reportLimit = 3 defaultReportMaxLen = 100 - greyNoiseAPIKeyMsg = "GreyNoise API key is missing! Please set the greynoise api_key in config.yaml file" - ipInfoAPIKeyMsg = "API key is missing! Please set the ipinfo api_key in config.yaml file" - abuseIPDBAPIKeyMsg = "API key is missing! Please set the abuseipdb api_key in config.yaml file" + greyNoiseAPIKeyMsg = "GreyNoise API key is missing! Please set the greynoise api_key in config.yaml file." + ipInfoAPIKeyMsg = "IPInfo API key is missing! Please set the ipinfo api_key in config.yaml file." + abuseIPDBAPIKeyMsg = "AbuseIPDB API key is missing! Please set the abuseipdb api_key in config.yaml file." ) var reportMaxLen int @@ -54,34 +53,43 @@ func checkInput(input string) error { return nil } -func checkAPIKeys() error { +func checkAPIKeys() []string { + var missingKeys []string if viper.GetString("api_keys.greynoise.api_key") == "" { - return errors.New(greyNoiseAPIKeyMsg) + missingKeys = append(missingKeys, greyNoiseAPIKeyMsg) } if viper.GetString("api_keys.ipinfo.api_key") == "" { - return errors.New(ipInfoAPIKeyMsg) + missingKeys = append(missingKeys, ipInfoAPIKeyMsg) } if viper.GetString("api_keys.abuseipdb.api_key") == "" { - return errors.New(abuseIPDBAPIKeyMsg) + missingKeys = append(missingKeys, abuseIPDBAPIKeyMsg) } - return nil + return missingKeys } func analyzeIP(ip net.IP) { - - if err := checkAPIKeys(); err != nil { - color.Red(err.Error()) - os.Exit(1) + missingKeys := checkAPIKeys() + if len(missingKeys) > 0 { + for _, msg := range missingKeys { + color.Yellow(msg) + } } // Fetch IP information - ipInfoData := apis.GetIPInfo(ip, viper.GetString("api_keys.ipinfo.api_key")) - greyNoiseData := apis.GetGreyNoiseData(ip, viper.GetString("api_keys.greynoise.api_key")) - abuseIPDBData := apis.GetAbuseIPDBInfo(ip, viper.GetString("api_keys.abuseipdb.api_key")) + if viper.GetString("api_keys.ipinfo.api_key") != "" { + ipInfoData := apis.GetIPInfo(ip, viper.GetString("api_keys.ipinfo.api_key")) + printIPInfo(ipInfoData) + } + + if viper.GetString("api_keys.greynoise.api_key") != "" { + greyNoiseData := apis.GetGreyNoiseData(ip, viper.GetString("api_keys.greynoise.api_key")) + printGreyNoiseData(greyNoiseData) + } - printIPInfo(ipInfoData) - printGreyNoiseData(greyNoiseData) - printAbuseIPDBData(abuseIPDBData) + if viper.GetString("api_keys.abuseipdb.api_key") != "" { + abuseIPDBData := apis.GetAbuseIPDBInfo(ip, viper.GetString("api_keys.abuseipdb.api_key")) + printAbuseIPDBData(abuseIPDBData) + } } From f2ec122f701ef7e2ffe6366a080ee4dac0140bb9 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sun, 12 Jan 2025 14:30:07 +0100 Subject: [PATCH 16/41] Updated .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a1987c9..47f9477 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build/ +.idea/ *.eml \ No newline at end of file From 7d5ce48e3ff610a9a036be2203260993f8059f4b Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sun, 12 Jan 2025 14:30:39 +0100 Subject: [PATCH 17/41] Better email code --- cmd/email.go | 84 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 28 deletions(-) diff --git a/cmd/email.go b/cmd/email.go index ad9e9c1..848af3b 100644 --- a/cmd/email.go +++ b/cmd/email.go @@ -45,11 +45,10 @@ func init() { // analyzeEmail processes the .eml file and extracts attachments and links func analyzeEmail(filePath string) { - if !strings.HasSuffix(strings.ToLower(filePath), emlExtension) { - color.Red("The provided file is not an .eml file.") + if !isValidEmlFile(filePath) { return - } + file, err := os.Open(filePath) if err != nil { fmt.Println("Error opening file:", err) @@ -80,38 +79,49 @@ func analyzeEmail(filePath string) { fmt.Println("\nDMARC Information: No Authentication-Results header found.") } - mediaType, params, err := mime.ParseMediaType(msg.Header.Get(contentTypeHeader)) - if err != nil { - fmt.Println("Error parsing content type:", err) - return + processEmailBody(msg) +} + +// isValidEmlFile checks if the provided file path has a valid .eml extension +func isValidEmlFile(filePath string) bool { + if !strings.HasSuffix(strings.ToLower(filePath), emlExtension) { + color.Red("The provided file is not an .eml file.") + return false } + return true +} + +// processEmailBody processes the email body based on its content type +func processEmailBody(msg *mail.Message) { + mediaType, params, err := mime.ParseMediaType(msg.Header.Get(contentTypeHeader)) + handleError(err, "Error parsing content type:") if strings.HasPrefix(mediaType, "multipart/") { - // Handle multipart emails (usually contains attachments and text) mr := multipart.NewReader(msg.Body, params["boundary"]) processMultipart(mr) } else { - // Handle single-part emails (just extract links) - body, _ := io.ReadAll(msg.Body) - // Check for Content-Transfer-Encoding - encoding := msg.Header.Get(transferEncodingHeader) - if strings.ToLower(encoding) == "quoted-printable" { - // Decode quoted-printable content - reader := quotedprintable.NewReader(strings.NewReader(string(body))) - decodedBody, err := io.ReadAll(reader) - if err != nil { - fmt.Println("Error decoding quoted-printable content:", err) - return - } - extractLinks(string(decodedBody)) - } else { - extractLinks(string(body)) - } + handleSinglePartEmail(msg) + } +} + +// handleSinglePartEmail handles single-part emails and extracts links +func handleSinglePartEmail(msg *mail.Message) { + body, _ := io.ReadAll(msg.Body) + encoding := msg.Header.Get(transferEncodingHeader) + + if strings.ToLower(encoding) == "quoted-printable" { + reader := quotedprintable.NewReader(strings.NewReader(string(body))) + decodedBody, err := io.ReadAll(reader) + handleError(err, "Error decoding quoted-printable content:") + extractLinks(string(decodedBody)) + } else { + extractLinks(string(body)) } } // processMultipart processes multipart emails for attachments and links func processMultipart(mr *multipart.Reader) { + attachmentsFound := false for { part, err := mr.NextPart() if err == io.EOF { @@ -127,17 +137,20 @@ func processMultipart(mr *multipart.Reader) { // If it's an attachment, list it if strings.Contains(disposition, "attachment") { - fileName := part.FileName() - if fileName == "" { - fileName = "unnamed attachment" + if !attachmentsFound { + color.Blue("\nAttachments:") + attachmentsFound = true } - fmt.Printf("Attachment: %s (MIME type: %s)\n", fileName, contentType) + handleAttachment(part, contentType) } else { // Otherwise, it's likely part of the email body (text or HTML) body, _ := io.ReadAll(part) extractLinks(string(body)) } } + if !attachmentsFound { + fmt.Println("\nNo attachments found.") + } } // extractDMARCDKIM extracts DMARC and DKIM results from the Authentication-Results header @@ -178,6 +191,21 @@ func extractLinks(body string) { } } +func handleAttachment(part *multipart.Part, contentType string) { + fileName := part.FileName() + if fileName == "" { + fileName = "unnamed attachment" + } + + fmt.Printf("Attachment: %s (MIME type: %s)\n", fileName, contentType) +} + +func handleError(err error, message string) { + if err != nil { + fmt.Println(message, err) + } +} + func printEmailHeaders(msg *mail.Message) { color.Blue("Main information:") fmt.Println("From:", msg.Header.Get("From")) From 9269b03af80394890a128c153f950ef6e97b4fa7 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Wed, 15 Jan 2025 21:47:53 +0100 Subject: [PATCH 18/41] Fix --- cmd/ip.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ip.go b/cmd/ip.go index 19a391b..cdc70f5 100644 --- a/cmd/ip.go +++ b/cmd/ip.go @@ -167,6 +167,6 @@ var ipCmd = &cobra.Command{ } func init() { - ipCmd.Flags().IntVarP(&reportMaxLen, "length", "l", 50, "AbuseIPDB report max length") + ipCmd.Flags().IntVarP(&reportMaxLen, "length", "l", defaultReportMaxLen, "AbuseIPDB report max length") rootCmd.AddCommand(ipCmd) } From 7ffe95d1045d64c18505d7417f7a4a55dce0a6c0 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Fri, 24 Jan 2025 21:52:51 +0100 Subject: [PATCH 19/41] Add title in urlscan --- cmd/urlscan.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/urlscan.go b/cmd/urlscan.go index a590f02..1b38142 100644 --- a/cmd/urlscan.go +++ b/cmd/urlscan.go @@ -31,6 +31,7 @@ type urlScanResult struct { Domain string `json:"domain"` Country string `json:"country"` IP string `json:"ip"` + Title string `json:"title"` } `json:"page"` Task struct { ReportURL string `json:"reportURL"` @@ -102,8 +103,9 @@ func fetchURLScanResult(scanID string) (*urlScanResult, error) { func displayResults(scanResult urlScanResult) { fmt.Printf("Scan Results for URL: %s\n", scanResult.Page.URL) fmt.Printf("Domain: %s\n", scanResult.Page.Domain) - fmt.Printf("Country: %s\n", scanResult.Page.Country) + fmt.Printf("Title: %s\n", scanResult.Page.Title) fmt.Printf("IP: %s\n", scanResult.Page.IP) + fmt.Printf("Country: %s\n", scanResult.Page.Country) fmt.Printf("Link: %s\n", scanResult.Task.ReportURL) if scanResult.Verdict.Malicious { fmt.Println("Verdict: " + color.RedString("MALICIOUS")) From 69d81994eefd4fd29055886256c3e810e0bcbefe Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sun, 26 Jan 2025 19:40:51 +0100 Subject: [PATCH 20/41] Fix scan link --- cmd/file_check.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/file_check.go b/cmd/file_check.go index 814cbaf..c0e5378 100644 --- a/cmd/file_check.go +++ b/cmd/file_check.go @@ -28,6 +28,7 @@ import ( ) const virusTotalBaseURL = "https://www.virustotal.com/api/v3" +const virusTotalFileGuiURL = "https://www.virustotal.com/gui/file/%s" const virusTotalFileReportEndpoint = "/files/" const virusTotalFileUploadEndpoint = "/files" @@ -234,13 +235,14 @@ func fetchVirusTotalReport(apiKey, fileID string) { } func displayVirusTotalReport(report apis.VTResponse) { + scanUrl := fmt.Sprintf(virusTotalFileGuiURL, report.Data.Attributes.Sha256) color.Blue("VirusTotal Scan Report:") fmt.Printf("\nType: %s\n", report.Data.Type) + fmt.Printf("Meaningful Name: %s\n", report.Data.Attributes.MeaningfulName) fmt.Printf("Magic: %v\n", report.Data.Attributes.Magic) - fmt.Printf("Self Link: %s\n", report.Data.Links.Self) fmt.Printf("Reputation: %d\n", report.Data.Attributes.Reputation) - fmt.Printf("Meaningful Name: %s\n", report.Data.Attributes.MeaningfulName) + fmt.Printf("Link: %s\n", scanUrl) fmt.Printf("Analysis result: malicious %v, undetected %v, harmless %v\n", report.Data.Attributes.LastAnalysisStats.Malicious, report.Data.Attributes.LastAnalysisStats.Suspicious, report.Data.Attributes.LastAnalysisStats.Harmless) } From c6fc19a898cb56cc1e3315b203b2714086ac1da3 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Fri, 31 Jan 2025 20:31:15 +0100 Subject: [PATCH 21/41] Urlscan fixes --- cmd/urlscan.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/urlscan.go b/cmd/urlscan.go index 1b38142..744fee0 100644 --- a/cmd/urlscan.go +++ b/cmd/urlscan.go @@ -103,7 +103,9 @@ func fetchURLScanResult(scanID string) (*urlScanResult, error) { func displayResults(scanResult urlScanResult) { fmt.Printf("Scan Results for URL: %s\n", scanResult.Page.URL) fmt.Printf("Domain: %s\n", scanResult.Page.Domain) - fmt.Printf("Title: %s\n", scanResult.Page.Title) + if title := scanResult.Page.Title; title != "" { + fmt.Printf("Title: %s\n", title) + } fmt.Printf("IP: %s\n", scanResult.Page.IP) fmt.Printf("Country: %s\n", scanResult.Page.Country) fmt.Printf("Link: %s\n", scanResult.Task.ReportURL) @@ -128,7 +130,8 @@ var urlScanCmd = &cobra.Command{ log.Fatalf("Error submitting URL for scan: %v", err) } - color.Blue("URL submitted successfully. Awaiting results...") + color.Green("URL submitted successfully.") + color.Blue("Awaiting results...") // Fetch the scan results scanResult, err := fetchURLScanResult(scanID) From 63bc0020c745cad6224a99e0144bffc590e20f6d Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Fri, 31 Jan 2025 20:53:16 +0100 Subject: [PATCH 22/41] Better email header printing --- cmd/email.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/cmd/email.go b/cmd/email.go index 848af3b..04847c9 100644 --- a/cmd/email.go +++ b/cmd/email.go @@ -206,13 +206,22 @@ func handleError(err error, message string) { } } +func printHeader(headerName, headerValue string) { + if headerValue != "" { + fmt.Printf("%s: %s\n", color.CyanString(headerName), headerValue) + } +} + func printEmailHeaders(msg *mail.Message) { color.Blue("Main information:") - fmt.Println("From:", msg.Header.Get("From")) - fmt.Println("To:", msg.Header.Get("To")) - fmt.Println("Subject:", msg.Header.Get("Subject")) - fmt.Println("Date:", msg.Header.Get("Date")) - fmt.Println("Return-Path:", msg.Header.Get("Return-Path")) + printHeader("From", msg.Header.Get("From")) + printHeader("To", msg.Header.Get("To")) + printHeader("Cc", msg.Header.Get("Cc")) + printHeader("Bcc", msg.Header.Get("Bcc")) + printHeader("Subject", msg.Header.Get("Subject")) + printHeader("Date", msg.Header.Get("Date")) + printHeader("Reply-To", msg.Header.Get("Reply-To")) + printHeader("Return-Path", msg.Header.Get("Return-Path")) } func printHeaderInfo(headerValue, headerName string) { From 85c4928e2bfb9ba8cc66eb96a18b740e6b502397 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Fri, 31 Jan 2025 21:07:24 +0100 Subject: [PATCH 23/41] Better defang code --- cmd/defang.go | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/cmd/defang.go b/cmd/defang.go index f4cb458..4c03cfb 100644 --- a/cmd/defang.go +++ b/cmd/defang.go @@ -10,10 +10,10 @@ import ( "bufio" "fmt" "github.com/spf13/cobra" - "log" "os" "runtime" "soc-cli/internal/logic" + "github.com/fatih/color" "strings" ) @@ -21,27 +21,30 @@ var defangCmd = &cobra.Command{ Use: "defang [input]", Short: "Defang a URL or email address to make it safe for sharing", Args: cobra.MaximumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { - var input string + Run: executeDefang, +} - if len(args) > 0 { - input = args[0] - } else { - if !isInputFromPipe() { - fmt.Print("Enter URL or email to defang: ") - } +func executeDefang(cmd *cobra.Command, args []string) { + input := getInput(args) + defanged := logic.Defang(input) + fmt.Println(defanged) +} - reader := bufio.NewReader(os.Stdin) - in, err := reader.ReadString('\n') - if err != nil { - log.Fatalf("Error reading input: %v", err) - } - input = strings.TrimSpace(in) +func getInput(args []string) string { + if len(args) > 0 { + return args[0] + } else { + if !isInputFromPipe() { + fmt.Print("Enter URL or email to defang: ") } - defanged := logic.Defang(input) - fmt.Println(defanged) - }, + reader := bufio.NewReader(os.Stdin) + in, err := reader.ReadString('\n') + if err != nil { + color.Red("Error reading input: %v", err) + } + return strings.TrimSpace(in) + } } func init() { From e04f3e1d4583f51085b04ae3de807b8cc180d0d4 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Fri, 31 Jan 2025 21:37:19 +0100 Subject: [PATCH 24/41] Defang URL if malicious or flag is set --- cmd/urlscan.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/cmd/urlscan.go b/cmd/urlscan.go index 744fee0..07c92b1 100644 --- a/cmd/urlscan.go +++ b/cmd/urlscan.go @@ -14,11 +14,13 @@ import ( "github.com/spf13/viper" "log" "net/http" + "soc-cli/internal/logic" "soc-cli/internal/util" "time" ) var defautVisibility = "private" // public, unlisted or private +var defangFlag bool const ( urlscanScanApi = "https://urlscan.io/api/v1/scan/" @@ -101,15 +103,23 @@ func fetchURLScanResult(scanID string) (*urlScanResult, error) { } func displayResults(scanResult urlScanResult) { + isMalicious := scanResult.Verdict.Malicious + domain := scanResult.Page.Domain + fmt.Printf("Scan Results for URL: %s\n", scanResult.Page.URL) - fmt.Printf("Domain: %s\n", scanResult.Page.Domain) + + if isMalicious || defangFlag { + domain = logic.DefangURL(domain) + } + fmt.Printf("Domain: %s\n", domain) + if title := scanResult.Page.Title; title != "" { fmt.Printf("Title: %s\n", title) } fmt.Printf("IP: %s\n", scanResult.Page.IP) fmt.Printf("Country: %s\n", scanResult.Page.Country) fmt.Printf("Link: %s\n", scanResult.Task.ReportURL) - if scanResult.Verdict.Malicious { + if isMalicious { fmt.Println("Verdict: " + color.RedString("MALICIOUS")) } else { fmt.Println("Verdict: " + color.GreenString("SAFE")) @@ -144,5 +154,6 @@ var urlScanCmd = &cobra.Command{ } func init() { + urlScanCmd.Flags().BoolVar(&defangFlag, "defang", false, "Defang the URL") rootCmd.AddCommand(urlScanCmd) } From 1dbcd7ee3b6f8fee3b3d2209ecef4732625e4ae7 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sat, 1 Feb 2025 09:45:31 +0100 Subject: [PATCH 25/41] Small fix --- cmd/ip.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/ip.go b/cmd/ip.go index cdc70f5..cd23654 100644 --- a/cmd/ip.go +++ b/cmd/ip.go @@ -104,11 +104,12 @@ func printGreyNoiseData(greyNoiseData *apis.GreyNoiseInfo) { if greyNoiseData != nil { color.Blue("\nGreyNoise Threat Intelligence") - classification := greyNoiseData.Classification - if classification == "malicious" { - classification = color.RedString(strings.ToUpper(classification)) - } else if classification == "benign" { - classification = color.GreenString(strings.ToUpper(classification)) + classification := strings.ToUpper(greyNoiseData.Classification) + switch classification { + case "MALICIOUS": + classification = color.RedString(classification) + case "BENIGN": + classification = color.GreenString(classification) } fmt.Printf("Noise: %v\nRiot: %v\nClassification: %s\nName: %s\nLink: %s\n", From 5415f565c93e41aa9689acb515813a38d8188fdf Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sat, 1 Feb 2025 14:20:12 +0100 Subject: [PATCH 26/41] Better ip output --- cmd/ip.go | 34 +++++++++++++++++++++++++++------- internal/apis/greynoise.go | 3 ++- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/cmd/ip.go b/cmd/ip.go index cd23654..fc8430a 100644 --- a/cmd/ip.go +++ b/cmd/ip.go @@ -16,6 +16,7 @@ import ( "os" "soc-cli/internal/apis" "soc-cli/internal/util" + "strconv" "strings" "time" ) @@ -95,11 +96,27 @@ func analyzeIP(ip net.IP) { func printIPInfo(ipInfoData *apis.IPInfo) { color.Blue("IP information from IPInfo") - fmt.Printf("IP: %s\nHostname: %s\nOrg: %s\nCountry: %s\n", - ipInfoData.IP, ipInfoData.Hostname, ipInfoData.Org, ipInfoData.Country) + printEntry("IP", ipInfoData.IP) + printEntry("Hostname", ipInfoData.Hostname) + printEntry("Org", ipInfoData.Org) + printEntry("Country", ipInfoData.Country) } +func printEntry(entryName, entryValue string) { + if entryValue == "" { + return + } + fmt.Printf("%s: %s\n", color.CyanString(entryName), entryValue) +} + +func printYesNo(val bool) string { + if val { + return color.GreenString("YES") + } + return color.RedString("NO") +} + func printGreyNoiseData(greyNoiseData *apis.GreyNoiseInfo) { if greyNoiseData != nil { color.Blue("\nGreyNoise Threat Intelligence") @@ -112,8 +129,11 @@ func printGreyNoiseData(greyNoiseData *apis.GreyNoiseInfo) { classification = color.GreenString(classification) } - fmt.Printf("Noise: %v\nRiot: %v\nClassification: %s\nName: %s\nLink: %s\n", - greyNoiseData.Noise, greyNoiseData.Riot, classification, greyNoiseData.Name, greyNoiseData.Link) + printEntry("Noise", printYesNo(greyNoiseData.Noise)) + printEntry("Riot", printYesNo(greyNoiseData.Riot)) + printEntry("Classification", classification) + printEntry("Message", greyNoiseData.Message) + printEntry("Link", greyNoiseData.Link) } } @@ -128,9 +148,9 @@ func printAbuseIPDBData(abuseIPDBData *apis.AbuseIPDBResponse) { lastReportDate, _ := time.Parse(time.RFC3339, abuseIPDBData.Data.LastReportedAt) // Print AbuseIPDB info - fmt.Printf("Abuse Confidence Score: %d\n", abuseIPDBData.Data.AbuseConfidenceScore) - fmt.Printf("Total Reports: %d\n", abuseIPDBData.Data.TotalReports) - fmt.Printf("Last Reported At: %s\n", lastReportDate.Format("Monday, January 2, 2006")) + printEntry("Abuse Confidence Score", strconv.Itoa(abuseIPDBData.Data.AbuseConfidenceScore)) + printEntry("Abuse Confidence Score", strconv.Itoa(abuseIPDBData.Data.TotalReports)) + printEntry("Last Reported At", lastReportDate.Format("Monday, January 2, 2006")) // Print the individual reports if available if len(abuseIPDBData.Data.Reports) > 0 { diff --git a/internal/apis/greynoise.go b/internal/apis/greynoise.go index 3a8a717..b1433a2 100644 --- a/internal/apis/greynoise.go +++ b/internal/apis/greynoise.go @@ -20,8 +20,9 @@ type GreyNoiseInfo struct { Noise bool `json:"noise"` Riot bool `json:"riot"` Classification string `json:"classification"` - Name string `json:"name"` Link string `json:"link"` + LastSeen string `json:"last_seen"` + Message string `json:"message"` } // Get threat intelligence from GreyNoise API From c4cf24329c63755f9553923c1897d369e3a76165 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sat, 1 Feb 2025 14:30:49 +0100 Subject: [PATCH 27/41] Moved PrintEntry func --- cmd/ip.go | 38 ++++++++++++-------------------------- internal/util/printing.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 26 deletions(-) create mode 100644 internal/util/printing.go diff --git a/cmd/ip.go b/cmd/ip.go index fc8430a..d4654cd 100644 --- a/cmd/ip.go +++ b/cmd/ip.go @@ -96,27 +96,13 @@ func analyzeIP(ip net.IP) { func printIPInfo(ipInfoData *apis.IPInfo) { color.Blue("IP information from IPInfo") - printEntry("IP", ipInfoData.IP) - printEntry("Hostname", ipInfoData.Hostname) - printEntry("Org", ipInfoData.Org) - printEntry("Country", ipInfoData.Country) + util.PrintEntry("IP", ipInfoData.IP) + util.PrintEntry("Hostname", ipInfoData.Hostname) + util.PrintEntry("Org", ipInfoData.Org) + util.PrintEntry("Country", ipInfoData.Country) } -func printEntry(entryName, entryValue string) { - if entryValue == "" { - return - } - fmt.Printf("%s: %s\n", color.CyanString(entryName), entryValue) -} - -func printYesNo(val bool) string { - if val { - return color.GreenString("YES") - } - return color.RedString("NO") -} - func printGreyNoiseData(greyNoiseData *apis.GreyNoiseInfo) { if greyNoiseData != nil { color.Blue("\nGreyNoise Threat Intelligence") @@ -129,11 +115,11 @@ func printGreyNoiseData(greyNoiseData *apis.GreyNoiseInfo) { classification = color.GreenString(classification) } - printEntry("Noise", printYesNo(greyNoiseData.Noise)) - printEntry("Riot", printYesNo(greyNoiseData.Riot)) - printEntry("Classification", classification) - printEntry("Message", greyNoiseData.Message) - printEntry("Link", greyNoiseData.Link) + util.PrintEntry("Noise", util.PrintYesNo(greyNoiseData.Noise)) + util.PrintEntry("Riot", util.PrintYesNo(greyNoiseData.Riot)) + util.PrintEntry("Classification", classification) + util.PrintEntry("Message", greyNoiseData.Message) + util.PrintEntry("Link", greyNoiseData.Link) } } @@ -148,9 +134,9 @@ func printAbuseIPDBData(abuseIPDBData *apis.AbuseIPDBResponse) { lastReportDate, _ := time.Parse(time.RFC3339, abuseIPDBData.Data.LastReportedAt) // Print AbuseIPDB info - printEntry("Abuse Confidence Score", strconv.Itoa(abuseIPDBData.Data.AbuseConfidenceScore)) - printEntry("Abuse Confidence Score", strconv.Itoa(abuseIPDBData.Data.TotalReports)) - printEntry("Last Reported At", lastReportDate.Format("Monday, January 2, 2006")) + util.PrintEntry("Abuse Confidence Score", strconv.Itoa(abuseIPDBData.Data.AbuseConfidenceScore)) + util.PrintEntry("Abuse Confidence Score", strconv.Itoa(abuseIPDBData.Data.TotalReports)) + util.PrintEntry("Last Reported At", lastReportDate.Format("Monday, January 2, 2006")) // Print the individual reports if available if len(abuseIPDBData.Data.Reports) > 0 { diff --git a/internal/util/printing.go b/internal/util/printing.go new file mode 100644 index 0000000..5180fa6 --- /dev/null +++ b/internal/util/printing.go @@ -0,0 +1,36 @@ +/* +Copyright © 2024 Alessandro Riva + +Licensed under the MIT License. +See the LICENSE file for details. +*/ +package util + +import ( + "fmt" + "github.com/fatih/color" // Make sure to import the color package +) + +func PrintEntry(entryName string, entryValue interface{}) { + if entryValue != nil { + switch v := entryValue.(type) { + case string: + if v != "" { + fmt.Printf("%s: %s\n", color.CyanString(entryName), v) + } + case bool: + fmt.Printf("%s: %t\n", color.CyanString(entryName), v) + case int: + fmt.Printf("%s: %d\n", color.CyanString(entryName), v) + default: + fmt.Printf("%s: %v\n", color.CyanString(entryName), v) + } + } +} + +func PrintYesNo(val bool) string { + if val { + return color.GreenString("YES") + } + return color.RedString("NO") +} From f5a7b6b41929054f46b1f885e2f1d53346501b29 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sat, 1 Feb 2025 14:41:54 +0100 Subject: [PATCH 28/41] Add reports flag --- cmd/ip.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/cmd/ip.go b/cmd/ip.go index d4654cd..ce230b9 100644 --- a/cmd/ip.go +++ b/cmd/ip.go @@ -22,14 +22,15 @@ import ( ) const ( - reportLimit = 3 - defaultReportMaxLen = 100 - greyNoiseAPIKeyMsg = "GreyNoise API key is missing! Please set the greynoise api_key in config.yaml file." - ipInfoAPIKeyMsg = "IPInfo API key is missing! Please set the ipinfo api_key in config.yaml file." - abuseIPDBAPIKeyMsg = "AbuseIPDB API key is missing! Please set the abuseipdb api_key in config.yaml file." + defaultReportEntries = 3 + defaultReportMaxLen = 100 + greyNoiseAPIKeyMsg = "GreyNoise API key is missing! Please set the greynoise api_key in config.yaml file." + ipInfoAPIKeyMsg = "IPInfo API key is missing! Please set the ipinfo api_key in config.yaml file." + abuseIPDBAPIKeyMsg = "AbuseIPDB API key is missing! Please set the abuseipdb api_key in config.yaml file." ) var reportMaxLen int +var reportEntries int func checkInput(input string) error { ip := net.ParseIP(input) @@ -119,6 +120,7 @@ func printGreyNoiseData(greyNoiseData *apis.GreyNoiseInfo) { util.PrintEntry("Riot", util.PrintYesNo(greyNoiseData.Riot)) util.PrintEntry("Classification", classification) util.PrintEntry("Message", greyNoiseData.Message) + util.PrintEntry("Last seen", greyNoiseData.LastSeen) util.PrintEntry("Link", greyNoiseData.Link) } } @@ -147,12 +149,13 @@ func printAbuseIPDBData(abuseIPDBData *apis.AbuseIPDBResponse) { tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) for index, report := range abuseIPDBData.Data.Reports { - if index > reportLimit { + if index >= reportEntries { break } humanTime, _ := util.HumanReadableDate(report.ReportedAt) tbl.AddRow(humanTime, report.ReporterCountry, util.ShortStr(report.Comment, reportMaxLen)) } + fmt.Println() tbl.Print() } @@ -175,5 +178,6 @@ var ipCmd = &cobra.Command{ func init() { ipCmd.Flags().IntVarP(&reportMaxLen, "length", "l", defaultReportMaxLen, "AbuseIPDB report max length") + ipCmd.Flags().IntVarP(&reportEntries, "reports", "r", defaultReportEntries, "AbuseIPDB reports to show") rootCmd.AddCommand(ipCmd) } From 9f9d16b8f3fc3c94dcf0babf1ee96c58f55dfd0e Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sat, 1 Feb 2025 21:18:25 +0100 Subject: [PATCH 29/41] Colored urlscan output --- cmd/b64e.go | 2 +- cmd/urlscan.go | 24 +++++++++++++----------- internal/util/printing.go | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/cmd/b64e.go b/cmd/b64e.go index 0f4ec3d..1518878 100644 --- a/cmd/b64e.go +++ b/cmd/b64e.go @@ -27,4 +27,4 @@ var b64eCmd = &cobra.Command{ // Register the b64e subcommand under misc func init() { miscCmd.AddCommand(b64eCmd) -} \ No newline at end of file +} diff --git a/cmd/urlscan.go b/cmd/urlscan.go index 07c92b1..c42d814 100644 --- a/cmd/urlscan.go +++ b/cmd/urlscan.go @@ -105,24 +105,26 @@ func fetchURLScanResult(scanID string) (*urlScanResult, error) { func displayResults(scanResult urlScanResult) { isMalicious := scanResult.Verdict.Malicious domain := scanResult.Page.Domain - - fmt.Printf("Scan Results for URL: %s\n", scanResult.Page.URL) + scannedUrl := scanResult.Page.URL if isMalicious || defangFlag { + + scannedUrl = logic.DefangURL(scannedUrl) domain = logic.DefangURL(domain) } - fmt.Printf("Domain: %s\n", domain) - if title := scanResult.Page.Title; title != "" { - fmt.Printf("Title: %s\n", title) - } - fmt.Printf("IP: %s\n", scanResult.Page.IP) - fmt.Printf("Country: %s\n", scanResult.Page.Country) - fmt.Printf("Link: %s\n", scanResult.Task.ReportURL) + util.PrintEntry("Scan Results for URL", scannedUrl) + util.PrintEntry("Domain", domain) + + util.PrintEntry("Title", scanResult.Page.Title) + + util.PrintEntry("IP", scanResult.Page.IP) + util.PrintEntry("Country", scanResult.Page.Country) + util.PrintEntry("Link", scanResult.Task.ReportURL) if isMalicious { - fmt.Println("Verdict: " + color.RedString("MALICIOUS")) + util.PrintEntry("Verdict", color.RedString("MALICIOUS")) } else { - fmt.Println("Verdict: " + color.GreenString("SAFE")) + util.PrintEntry("Verdict", color.GreenString("SAFE")) } } diff --git a/internal/util/printing.go b/internal/util/printing.go index 5180fa6..9d4461d 100644 --- a/internal/util/printing.go +++ b/internal/util/printing.go @@ -8,7 +8,7 @@ package util import ( "fmt" - "github.com/fatih/color" // Make sure to import the color package + "github.com/fatih/color" ) func PrintEntry(entryName string, entryValue interface{}) { From 424f7702df0763e91cab4232125179ed68dd5a8a Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Mon, 3 Feb 2025 20:47:44 +0100 Subject: [PATCH 30/41] Fix --- cmd/ip.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/ip.go b/cmd/ip.go index ce230b9..d2d8b04 100644 --- a/cmd/ip.go +++ b/cmd/ip.go @@ -16,7 +16,6 @@ import ( "os" "soc-cli/internal/apis" "soc-cli/internal/util" - "strconv" "strings" "time" ) @@ -136,8 +135,8 @@ func printAbuseIPDBData(abuseIPDBData *apis.AbuseIPDBResponse) { lastReportDate, _ := time.Parse(time.RFC3339, abuseIPDBData.Data.LastReportedAt) // Print AbuseIPDB info - util.PrintEntry("Abuse Confidence Score", strconv.Itoa(abuseIPDBData.Data.AbuseConfidenceScore)) - util.PrintEntry("Abuse Confidence Score", strconv.Itoa(abuseIPDBData.Data.TotalReports)) + util.PrintEntry("Abuse Confidence Score", abuseIPDBData.Data.AbuseConfidenceScore) + util.PrintEntry("Total Reports", abuseIPDBData.Data.TotalReports) util.PrintEntry("Last Reported At", lastReportDate.Format("Monday, January 2, 2006")) // Print the individual reports if available From fb2c952db2850ac2b05a49eec16189b8580fa007 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Thu, 6 Feb 2025 20:37:02 +0100 Subject: [PATCH 31/41] Renamed functions --- cmd/file_check.go | 2 +- cmd/urlscan.go | 2 +- internal/apis/abuseipdb.go | 2 +- internal/apis/greynoise.go | 2 +- internal/apis/ipinfo.go | 2 +- internal/apis/whodat.go | 2 +- internal/util/api.go | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/file_check.go b/cmd/file_check.go index c0e5378..06af4d4 100644 --- a/cmd/file_check.go +++ b/cmd/file_check.go @@ -111,7 +111,7 @@ func fileExistsInVirusTotal(apiKey, hash string) (exist bool, report apis.VTResp "Accept": "application/json", } - statusCode, err := util.MakeGETRequest(virusTotalBaseURL+virusTotalFileReportEndpoint+hash, headers, &report) + statusCode, err := util.HTTPGetJSON(virusTotalBaseURL+virusTotalFileReportEndpoint+hash, headers, &report) if err != nil { log.Fatalf("Error fetching VirusTotal: %v", err) } diff --git a/cmd/urlscan.go b/cmd/urlscan.go index c42d814..19cff28 100644 --- a/cmd/urlscan.go +++ b/cmd/urlscan.go @@ -54,7 +54,7 @@ func submitURLScan(url string) (string, error) { var result map[string]interface{} - err := util.MakePOSTRequest(urlscanScanApi, map[string]string{"API-Key": apiKey}, requestBody, &result) + err := util.HTTPPostJSON(urlscanScanApi, map[string]string{"API-Key": apiKey}, requestBody, &result) if err != nil { return "", fmt.Errorf("failed to submit URL scan request: %v", err) diff --git a/internal/apis/abuseipdb.go b/internal/apis/abuseipdb.go index 3f38851..978aaf7 100644 --- a/internal/apis/abuseipdb.go +++ b/internal/apis/abuseipdb.go @@ -49,7 +49,7 @@ func GetAbuseIPDBInfo(ip net.IP, apiKey string) *AbuseIPDBResponse { var data AbuseIPDBResponse - _, err := util.MakeGETRequest(apiUrl, headers, &data) + _, err := util.HTTPGetJSON(apiUrl, headers, &data) if err != nil { log.Fatalf("Error fetching AbuseIPDB info: %v", err) } diff --git a/internal/apis/greynoise.go b/internal/apis/greynoise.go index b1433a2..68d9887 100644 --- a/internal/apis/greynoise.go +++ b/internal/apis/greynoise.go @@ -35,7 +35,7 @@ func GetGreyNoiseData(ip net.IP, apiKey string) *GreyNoiseInfo { var greyNoiseData GreyNoiseInfo - _, err := util.MakeGETRequest(apiUrl, headers, &greyNoiseData) + _, err := util.HTTPGetJSON(apiUrl, headers, &greyNoiseData) if err != nil { log.Fatalf("Error fetching AbuseIPDB info: %v", err) } diff --git a/internal/apis/ipinfo.go b/internal/apis/ipinfo.go index bc7b023..0b5c7be 100644 --- a/internal/apis/ipinfo.go +++ b/internal/apis/ipinfo.go @@ -27,7 +27,7 @@ func GetIPInfo(ip net.IP, apiKey string) *IPInfo { var info IPInfo - _, err := util.MakeGETRequest(apiUrl, nil, &info) + _, err := util.HTTPGetJSON(apiUrl, nil, &info) if err != nil { log.Fatalf("Error fetching IP info: %v", err) } diff --git a/internal/apis/whodat.go b/internal/apis/whodat.go index afe6d29..b5ed6e8 100644 --- a/internal/apis/whodat.go +++ b/internal/apis/whodat.go @@ -68,7 +68,7 @@ func GetWhoisData(domain string) (*DomainInfo, error) { var whois DomainInfo - _, err := util.MakeGETRequest(apiUrl, nil, &whois) + _, err := util.HTTPGetJSON(apiUrl, nil, &whois) return &whois, err } diff --git a/internal/util/api.go b/internal/util/api.go index f2cc72b..26d51a0 100644 --- a/internal/util/api.go +++ b/internal/util/api.go @@ -26,7 +26,7 @@ func init() { } } -func MakeGETRequest(url string, headers map[string]string, target interface{}) (sc int, err error) { +func HTTPGetJSON(url string, headers map[string]string, target interface{}) (sc int, err error) { req, err := http.NewRequest("GET", url, nil) if err != nil { return 0, fmt.Errorf("error creating request: %w", err) @@ -68,7 +68,7 @@ func MakeGETRequest(url string, headers map[string]string, target interface{}) ( return resp.StatusCode, nil } -func MakePOSTRequest(url string, headers map[string]string, body interface{}, target interface{}) error { +func HTTPPostJSON(url string, headers map[string]string, body interface{}, target interface{}) error { // Marshal the body into JSON jsonBody, err := json.Marshal(body) if err != nil { From 1b8814f1dc73050380d33a06c00cf6fa779add63 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Thu, 6 Feb 2025 20:51:53 +0100 Subject: [PATCH 32/41] Handle defanged IP --- cmd/ip.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/ip.go b/cmd/ip.go index d2d8b04..775a002 100644 --- a/cmd/ip.go +++ b/cmd/ip.go @@ -32,6 +32,9 @@ var reportMaxLen int var reportEntries int func checkInput(input string) error { + // Handle defanged IP + input = strings.ReplaceAll(input, "[.]", ".") + ip := net.ParseIP(input) if ip == nil { color.Red("Invalid IP address.") From 319797f0adbbdb8df6e524206bc6a73b72cabe58 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Thu, 6 Feb 2025 20:52:16 +0100 Subject: [PATCH 33/41] Myip command --- cmd/myip.go | 47 ++++++++++++++++++++++++++++++++++++++++++++ internal/util/api.go | 25 +++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 cmd/myip.go diff --git a/cmd/myip.go b/cmd/myip.go new file mode 100644 index 0000000..48579ae --- /dev/null +++ b/cmd/myip.go @@ -0,0 +1,47 @@ +/* +Copyright © 2025 Alessandro Riva + +Licensed under the MIT License. +See the LICENSE file for details. +*/ +package cmd + +import ( + "fmt" + "github.com/spf13/cobra" + "net" + "soc-cli/internal/util" + "github.com/fatih/color" + "strings" +) + +func getMyIP() net.IP { + + headers := map[string]string{ + "User-Agent": "curl/8.9.1", + } + + url := "https://ip.me" + + body, err := util.GetRaw(url, headers) + if err != nil { + color.Red("Error fetching API: %v", err) + } + + ip := strings.TrimSpace(string(body)) + return net.ParseIP(ip) +} + +var myipCmd = &cobra.Command{ + Use: "myip", + Short: "Get your ip address", + Long: "Get your ip address using ip.me API", + Run: func(cmd *cobra.Command, args []string) { + ip := getMyIP() + fmt.Println(ip) + }, +} + +func init() { + miscCmd.AddCommand(myipCmd) +} diff --git a/internal/util/api.go b/internal/util/api.go index 26d51a0..52e7914 100644 --- a/internal/util/api.go +++ b/internal/util/api.go @@ -26,6 +26,31 @@ func init() { } } +func GetRaw(url string, headers map[string]string) (body []byte, err error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + for key, value := range headers { + req.Header.Set(key, value) + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("error making request: %w", err) + } + defer resp.Body.Close() + + body, err = io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + + return body, nil +} + func HTTPGetJSON(url string, headers map[string]string, target interface{}) (sc int, err error) { req, err := http.NewRequest("GET", url, nil) if err != nil { From a4f827b1ef06c3edb7c3baaea785553748742866 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Tue, 11 Feb 2025 20:17:35 +0100 Subject: [PATCH 34/41] Better defang code --- cmd/defang.go | 46 +++++---------------------------------- go.mod | 3 ++- go.sum | 6 +++-- internal/util/printing.go | 23 ++++++++++++++++++++ 4 files changed, 35 insertions(+), 43 deletions(-) diff --git a/cmd/defang.go b/cmd/defang.go index 4c03cfb..c5d5bdd 100644 --- a/cmd/defang.go +++ b/cmd/defang.go @@ -7,14 +7,10 @@ See the LICENSE file for details. package cmd import ( - "bufio" "fmt" "github.com/spf13/cobra" - "os" - "runtime" "soc-cli/internal/logic" - "github.com/fatih/color" - "strings" + "soc-cli/internal/util" ) var defangCmd = &cobra.Command{ @@ -25,46 +21,16 @@ var defangCmd = &cobra.Command{ } func executeDefang(cmd *cobra.Command, args []string) { - input := getInput(args) - defanged := logic.Defang(input) - fmt.Println(defanged) -} - -func getInput(args []string) string { + var input string if len(args) > 0 { - return args[0] + input = args[0] } else { - if !isInputFromPipe() { - fmt.Print("Enter URL or email to defang: ") - } - - reader := bufio.NewReader(os.Stdin) - in, err := reader.ReadString('\n') - if err != nil { - color.Red("Error reading input: %v", err) - } - return strings.TrimSpace(in) + input = util.GetPromptedInput("Enter URL or email to defang: ") } + defanged := logic.Defang(input) + fmt.Println(defanged) } func init() { rootCmd.AddCommand(defangCmd) } - -// isInputFromPipe checks if the standard input is coming from a pipe -func isInputFromPipe() bool { - // Check if stdin is a terminal - return !isTerminal(os.Stdin.Fd()) -} - -// isTerminal checks if the given file descriptor is a terminal -func isTerminal(fd uintptr) bool { - return runtime.GOOS != "windows" && isatty(fd) -} - -// isatty checks if the file descriptor is a terminal (Unix-like systems) -func isatty(fd uintptr) bool { - // Use the syscall package to check if the file descriptor is a terminal - // This is a simplified version; you may need to import "golang.org/x/sys/unix" for a complete implementation - return false // Placeholder; implement actual check if needed -} diff --git a/go.mod b/go.mod index 835beb6..4c83574 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/rodaine/table v1.3.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 + golang.org/x/term v0.29.0 ) require ( @@ -27,7 +28,7 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 // indirect - golang.org/x/sys v0.29.0 // indirect + golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.21.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 967b083..3a49f3e 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,10 @@ golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 h1:9kj3STMvgqy3YA4VQXBrN7925 golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/util/printing.go b/internal/util/printing.go index 9d4461d..b83f5e0 100644 --- a/internal/util/printing.go +++ b/internal/util/printing.go @@ -7,8 +7,12 @@ See the LICENSE file for details. package util import ( + "bufio" "fmt" "github.com/fatih/color" + "golang.org/x/term" + "os" + "strings" ) func PrintEntry(entryName string, entryValue interface{}) { @@ -34,3 +38,22 @@ func PrintYesNo(val bool) string { } return color.RedString("NO") } + +// getPromptedInput prompts the user for input if the standard input is not a pipe +func GetPromptedInput(prompt string) string { + if !isInputFromPipe() { + fmt.Print(prompt) + } + + reader := bufio.NewReader(os.Stdin) + in, err := reader.ReadString('\n') + if err != nil { + color.Red("Error reading input: %v", err) + } + return strings.TrimSpace(in) +} + +// isInputFromPipe checks if the standard input is coming from a pipe +func isInputFromPipe() bool { + return !term.IsTerminal(int(os.Stdin.Fd())) +} From e777b3f5d781e7dd79026ff833ba30e429852ab4 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Wed, 19 Feb 2025 21:10:08 +0100 Subject: [PATCH 35/41] Add color in IP command --- cmd/ip.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/ip.go b/cmd/ip.go index 775a002..c5a9f6b 100644 --- a/cmd/ip.go +++ b/cmd/ip.go @@ -112,10 +112,12 @@ func printGreyNoiseData(greyNoiseData *apis.GreyNoiseInfo) { classification := strings.ToUpper(greyNoiseData.Classification) switch classification { - case "MALICIOUS": - classification = color.RedString(classification) case "BENIGN": classification = color.GreenString(classification) + case "MALICIOUS": + classification = color.RedString(classification) + case "SUSPICIOUS": + classification = color.YellowString(classification) } util.PrintEntry("Noise", util.PrintYesNo(greyNoiseData.Noise)) From b3ebe29e7a217a1deac7842ebdb7077e61c529f9 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Tue, 11 Mar 2025 20:22:34 +0100 Subject: [PATCH 36/41] Remove debug in api.go --- internal/util/api.go | 41 ++--------------------------------------- 1 file changed, 2 insertions(+), 39 deletions(-) diff --git a/internal/util/api.go b/internal/util/api.go index 52e7914..9ea941b 100644 --- a/internal/util/api.go +++ b/internal/util/api.go @@ -11,21 +11,9 @@ import ( "encoding/json" "fmt" "io" - "log" "net/http" - "os" - "strconv" ) -var debug bool - -func init() { - // Check if SOC_DEBUG is set and enable debug mode if it is - if val, exists := os.LookupEnv("SOC_DEBUG"); exists { - debug, _ = strconv.ParseBool(val) - } -} - func GetRaw(url string, headers map[string]string) (body []byte, err error) { req, err := http.NewRequest("GET", url, nil) if err != nil { @@ -51,7 +39,7 @@ func GetRaw(url string, headers map[string]string) (body []byte, err error) { return body, nil } -func HTTPGetJSON(url string, headers map[string]string, target interface{}) (sc int, err error) { +func HTTPGetJSON(url string, headers map[string]string, target any) (sc int, err error) { req, err := http.NewRequest("GET", url, nil) if err != nil { return 0, fmt.Errorf("error creating request: %w", err) @@ -61,14 +49,6 @@ func HTTPGetJSON(url string, headers map[string]string, target interface{}) (sc req.Header.Set(key, value) } - // Log request details if debug is enabled - if debug { - log.Printf("Making API request to URL: %s", url) - for key, value := range headers { - log.Printf("Header: %s = %s", key, value) - } - } - client := &http.Client{} resp, err := client.Do(req) if err != nil { @@ -81,10 +61,6 @@ func HTTPGetJSON(url string, headers map[string]string, target interface{}) (sc return resp.StatusCode, fmt.Errorf("error reading response body: %w", err) } - if debug { - log.Printf("Response body: %s", string(body)) - } - err = json.Unmarshal(body, target) if err != nil { return resp.StatusCode, fmt.Errorf("error unmarshalling JSON response: %w", err) @@ -93,7 +69,7 @@ func HTTPGetJSON(url string, headers map[string]string, target interface{}) (sc return resp.StatusCode, nil } -func HTTPPostJSON(url string, headers map[string]string, body interface{}, target interface{}) error { +func HTTPPostJSON(url string, headers map[string]string, body any, target interface{}) error { // Marshal the body into JSON jsonBody, err := json.Marshal(body) if err != nil { @@ -112,15 +88,6 @@ func HTTPPostJSON(url string, headers map[string]string, body interface{}, targe // Set Content-Type header to application/json req.Header.Set("Content-Type", "application/json") - // Log request details if debug is enabled - if debug { - log.Printf("Making API request to URL: %s", url) - for key, value := range headers { - log.Printf("Header: %s = %s", key, value) - } - log.Printf("Request body: %s", string(jsonBody)) - } - client := &http.Client{} resp, err := client.Do(req) if err != nil { @@ -133,10 +100,6 @@ func HTTPPostJSON(url string, headers map[string]string, body interface{}, targe return fmt.Errorf("error reading response body: %w", err) } - if debug { - log.Printf("Response body: %s", string(bodyResp)) - } - err = json.Unmarshal(bodyResp, target) if err != nil { return fmt.Errorf("error unmarshalling JSON response: %w", err) From c6d6f82cef916c140f415f0fa1f071eb8f44bc8c Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sat, 15 Mar 2025 20:51:02 +0100 Subject: [PATCH 37/41] Better config --- config/config.go | 63 +++++++++++++++++++++--------------------------- main.go | 7 ++---- 2 files changed, 30 insertions(+), 40 deletions(-) diff --git a/config/config.go b/config/config.go index b97193d..a5e9b7d 100644 --- a/config/config.go +++ b/config/config.go @@ -8,61 +8,54 @@ package config import ( "fmt" - "github.com/spf13/viper" "os" "path/filepath" -) -const configTemplate = `api_keys: - urlscan: - api_key: your-urlscan-api-key + "log/slog" + + "github.com/fatih/color" + + "github.com/spf13/viper" +) - ipinfo: - api_key: your-ipinfo-api-key -` +func InitConfig() error { -func EnsureConfigExists() error { home, err := os.UserHomeDir() if err != nil { return fmt.Errorf("could not find home directory: %v", err) } - configDir := filepath.Join(home, ".config", "soc-cli") - configFile := filepath.Join(configDir, "config.yaml") + configPath := filepath.Join(home, ".config", "soc-cli") // Create the directory if it doesn't exist - if _, err := os.Stat(configDir); os.IsNotExist(err) { - if err := os.MkdirAll(configDir, os.ModePerm); err != nil { + if _, err := os.Stat(configPath); os.IsNotExist(err) { + if err := os.MkdirAll(configPath, os.ModePerm); err != nil { return fmt.Errorf("could not create config directory: %v", err) } } - // Create file with default config if doesn't exist - if _, err := os.Stat(configFile); os.IsNotExist(err) { - defaultConfig := []byte(configTemplate) - if err := os.WriteFile(configFile, defaultConfig, 0644); err != nil { - return fmt.Errorf("could not create config file: %v", err) - } - fmt.Println("A new configuration file was created at:", configFile) - } - - return nil -} - -func LoadConfig() error { - home, err := os.UserHomeDir() - if err != nil { - return fmt.Errorf("could not find home directory: %v", err) - } - - configPath := filepath.Join(home, ".config", "soc-cli") - viper.SetConfigName("config") viper.SetConfigType("yaml") viper.AddConfigPath(configPath) - if err := viper.ReadInConfig(); err != nil { - return fmt.Errorf("error reading config file: %v", err) + viper.SetDefault("api_keys.urlscan.api_key", "") + viper.SetDefault("api_keys.ipinfo.api_key", "") + viper.SetDefault("api_keys.greynoise.api_key", "") + viper.SetDefault("api_keys.abuseipdb.api_key", "") + viper.SetDefault("api_keys.virustotal.api_key", "") + + if err := viper.SafeWriteConfig(); err != nil { + if _, ok := err.(viper.ConfigFileAlreadyExistsError); ok { + slog.Debug("Config file already exists, reading existing config") + if err := viper.ReadInConfig(); err != nil { + return fmt.Errorf("error reading config file: %v", err) + } + } else { + return fmt.Errorf("error writing config file: %v", err) + } + } else { + color.Green("First execution, config file created") + os.Exit(0) } return nil diff --git a/main.go b/main.go index 05872af..eea1833 100644 --- a/main.go +++ b/main.go @@ -13,12 +13,9 @@ import ( ) func main() { - if err := config.EnsureConfigExists(); err != nil { - log.Fatalf("Error ensuring config exists: %v", err) + if err := config.InitConfig(); err != nil { + log.Fatalf("Error initializing config: %v", err) } - if err := config.LoadConfig(); err != nil { - log.Fatalf("Failed to load config: %v", err) - } cmd.Execute() } From f85bb01ae206fd18631cef0c6c64317eefb0f93c Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sun, 20 Apr 2025 19:22:09 +0200 Subject: [PATCH 38/41] Moved config --- {config => internal/config}/config.go | 0 main.go | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {config => internal/config}/config.go (100%) diff --git a/config/config.go b/internal/config/config.go similarity index 100% rename from config/config.go rename to internal/config/config.go diff --git a/main.go b/main.go index eea1833..b9881f3 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,7 @@ package main import ( "log" "soc-cli/cmd" - "soc-cli/config" + "soc-cli/internal/config" ) func main() { From 42b999423d90eddae5b40262bc420ae0e4fd2942 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Sun, 20 Apr 2025 20:39:15 +0200 Subject: [PATCH 39/41] Update go.mod --- go.mod | 31 +++++++++++-------------- go.sum | 71 ++++++++++++++++++++++++---------------------------------- 2 files changed, 42 insertions(+), 60 deletions(-) diff --git a/go.mod b/go.mod index 4c83574..d2a66fc 100644 --- a/go.mod +++ b/go.mod @@ -1,35 +1,30 @@ module soc-cli -go 1.23.4 +go 1.24 require ( github.com/fatih/color v1.18.0 github.com/rodaine/table v1.3.0 - github.com/spf13/cobra v1.8.1 - github.com/spf13/viper v1.19.0 - golang.org/x/term v0.29.0 + github.com/spf13/cobra v1.9.1 + github.com/spf13/viper v1.20.1 + golang.org/x/term v0.31.0 ) require ( - github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/magiconair/properties v1.8.9 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/sagikazarmark/locafero v0.6.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/sagikazarmark/locafero v0.9.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/afero v1.14.0 // indirect github.com/spf13/cast v1.7.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.21.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3a49f3e..d403307 100644 --- a/go.sum +++ b/go.sum @@ -1,40 +1,33 @@ -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= -github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rodaine/table v1.3.0 h1:4/3S3SVkHnVZX91EHFvAMV7K42AnJ0XuymRR2C5HlGE= @@ -42,22 +35,20 @@ github.com/rodaine/table v1.3.0/go.mod h1:47zRsHar4zw0jgxGxL9YtFfs7EGN6B/TaS+/Dm github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= -github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= +github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= -github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -65,27 +56,23 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ 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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 h1:9kj3STMvgqy3YA4VQXBrN7925ICMxD5wzMRcgA30588= -golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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= From 23d8fbd4325cd144a37ac54ce34140d3c4b808ee Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Tue, 20 May 2025 19:09:54 +0200 Subject: [PATCH 40/41] chore: cleaner hashing code --- cmd/hash.go | 9 +++------ internal/logic/hashing.go | 9 ++++++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/hash.go b/cmd/hash.go index bb2dada..06e2e92 100644 --- a/cmd/hash.go +++ b/cmd/hash.go @@ -40,12 +40,9 @@ func showHashes(filePath string, asJson bool) { defer file.Close() - md5Digest := logic.ComputeMd5(file) - // Reset the file pointer to the beginning - file.Seek(0, 0) - sha1Digest := logic.ComputeSha1(file) - file.Seek(0, 0) - sha256Digest := logic.ComputeSha256(file) + md5Digest := logic.ComputeFileMd5(file) + sha1Digest := logic.ComputeFileSha1(file) + sha256Digest := logic.ComputeFileSha256(file) if asJson { diff --git a/internal/logic/hashing.go b/internal/logic/hashing.go index 9bb85a8..ab2a173 100644 --- a/internal/logic/hashing.go +++ b/internal/logic/hashing.go @@ -16,8 +16,9 @@ import ( "os" ) -func ComputeMd5(file *os.File) string { +func ComputeFileMd5(file *os.File) string { hmd5 := md5.New() + file.Seek(0, 0) if _, err := io.Copy(hmd5, file); err != nil { log.Fatal("failed to calculate MD5 of file: %w", err) } @@ -26,8 +27,9 @@ func ComputeMd5(file *os.File) string { return hexmd5 } -func ComputeSha1(file *os.File) string { +func ComputeFileSha1(file *os.File) string { h1 := sha1.New() + file.Seek(0, 0) if _, err := io.Copy(h1, file); err != nil { log.Fatal("failed to calculate SHA1 of file: %w", err) } @@ -36,8 +38,9 @@ func ComputeSha1(file *os.File) string { return hex1 } -func ComputeSha256(file *os.File) string { +func ComputeFileSha256(file *os.File) string { h256 := sha256.New() + file.Seek(0, 0) if _, err := io.Copy(h256, file); err != nil { log.Fatal("failed to calculate SHA256 of file: %w", err) } From 87c5e7fd7ff54a5d606c1ee2492791ea51f45bc4 Mon Sep 17 00:00:00 2001 From: Alessandro Riva Date: Wed, 21 May 2025 21:01:41 +0200 Subject: [PATCH 41/41] feat: first version --- cmd/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/version.go b/cmd/version.go index b86f555..9b0b727 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -13,7 +13,7 @@ import ( "log" ) -var Version = "dev" +var Version = "v01.0" type verOutput struct { Version string `json:"version"`