Skip to content

Commit

Permalink
Merge pull request #9 from CallMeGreg/CallMeGreg/release-cleanup
Browse files Browse the repository at this point in the history
CallMeGreg/release-cleanup
  • Loading branch information
CallMeGreg authored Dec 22, 2023
2 parents 6d3cec0 + 27b0f8e commit 4a396d3
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 42 deletions.
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Overview
This project is a GitHub CLI (`gh`) extension that provides commands for interacting with secret scanning alerts. Primary uses include:
- Listing secret scanning alerts for an enterprise, organization, or repository
- Verifying if a secret is valid
- Verifying if secret alerts are still active
- Opening issues in repos that contain valid secrets

# Supported Token Types
Expand Down Expand Up @@ -103,9 +103,7 @@ Use "secret-scanning [command] --help" for more information about a command.
```

# Demo
This example first lists the alerts for an organization with the `alerts` subcommand, and then verifies the secrets with the `verify` subcommand. The `-c` flag is used to generate a csv report of the results, and the `-i` flag is used to create an issue in any repository that contains a valid secret.

https://github.com/CallMeGreg/gh-secret-scanning/assets/110078080/58f685a2-52a8-4478-92f9-d7468065ede5

This example first lists the alerts for an organization with the `alerts` subcommand, and then verifies the secrets with the `verify` subcommand. The `-c` flag is used to generate a csv report of the results, and the `-i` flag is used to create issues in any repository that contains a valid secret.

https://github.com/CallMeGreg/gh-secret-scanning/assets/110078080/fa8d7b08-1a2c-4522-ae96-5c3aab60107d

7 changes: 3 additions & 4 deletions cmd/alerts.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"fmt"
"log"
"math"
"net/url"
"strconv"
Expand Down Expand Up @@ -79,10 +78,10 @@ func runAlerts(cmd *cobra.Command, args []string) (err error) {
}

for page := 1; page <= pages; page++ {
log.Printf("Processing page: %d\n", page)
fmt.Println("Processing page: " + strconv.Itoa(page))
_, nextPage, err := callGitHubAPI(client, requestPath, &pageOfSecretAlerts, GET)
if err != nil {
log.Printf("ERROR: Unable to get alerts for target: %s\n", requestPath)
fmt.Println("ERROR: Unable to get alerts for target: " + requestPath)
return err
}
for _, secretAlert := range pageOfSecretAlerts {
Expand All @@ -108,7 +107,7 @@ func runAlerts(cmd *cobra.Command, args []string) (err error) {

// pretty print all of the response details:
if !quiet {
prettyPrintAlerts(sortedAlerts, false)
err = prettyPrintAlerts(sortedAlerts, false)
}

// optionally generate a csv report of the results:
Expand Down
50 changes: 26 additions & 24 deletions cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"regexp"
Expand Down Expand Up @@ -84,7 +83,7 @@ func createGitHubSecretAlertsAPIPath(scope string, target string) (apiURL string
replacer := strings.NewReplacer("{owner}", owner, "{repo}", repo)
apiURL = replacer.Replace(repositoryAlertsURL)
default:
log.Fatal("Invalid API target.")
err = fmt.Errorf("Invalid API target.")
}
return apiURL, err
}
Expand Down Expand Up @@ -159,13 +158,13 @@ func callGitHubAPI(client *api.RESTClient, requestPath string, parseType interfa
nextPage := response.Header.Get("Link")
responseBody, err := io.ReadAll(response.Body)
if err != nil {
log.Println("ERROR: Unable to read next page link")
fmt.Println("ERROR: Unable to read next page link")
return response.StatusCode, nextPage, err
}

err = decodeJSONResponse(responseBody, &parseType)
if err != nil {
log.Println("ERROR: Unable to decode JSON response")
fmt.Println("ERROR: Unable to decode JSON response")
return response.StatusCode, nextPage, err
}

Expand All @@ -176,7 +175,7 @@ func decodeJSONResponse(body []byte, parseType interface{}) error {
decoder := json.NewDecoder(bytes.NewReader(body))
err := decoder.Decode(&parseType)
if err != nil {
log.Println("ERROR: Unable to decode JSON response")
fmt.Println("ERROR: Unable to decode JSON response")
return err
}

Expand Down Expand Up @@ -263,7 +262,7 @@ func getScopeAndTarget() (scope string, target string, err error) {
return scope, target, err
}

func prettyPrintAlerts(alerts []Alert, validity_check bool) {
func prettyPrintAlerts(alerts []Alert, validity_check bool) (err error) {
counter := 0
if len(alerts) > 0 {
terminal := term.FromEnv()
Expand Down Expand Up @@ -327,14 +326,16 @@ func prettyPrintAlerts(alerts []Alert, validity_check bool) {
counter++
}
if err := t.Render(); err != nil {
log.Fatal(err)
err = fmt.Errorf("error rendering table: %v", err)
return err
}
}
if limit < len(alerts) {
fmt.Println(Blue("Fetched " + strconv.Itoa(limit) + " secret alerts."))
} else {
fmt.Println(Blue("Fetched " + strconv.Itoa(len(alerts)) + " secret alerts."))
}
return err
}

func generateCSVReport(alerts []Alert, scope string, validity_check bool) (err error) {
Expand All @@ -348,7 +349,8 @@ func generateCSVReport(alerts []Alert, scope string, validity_check bool) (err e
// Create a CSV file
file, err := os.Create(filename)
if err != nil {
log.Fatal(err)
fmt.Println("ERROR: Error creating CSV file.")
return err
}
defer file.Close()
// Initialize CSV writer
Expand Down Expand Up @@ -383,7 +385,8 @@ func generateCSVReport(alerts []Alert, scope string, validity_check bool) (err e
counter++
}
if err := writer.Error(); err != nil {
log.Fatal(err)
fmt.Println("ERROR: Error writing to CSV file.")
return err
}
fmt.Println(Blue("CSV report generated: " + filename))
return err
Expand All @@ -406,7 +409,7 @@ func verifyAlerts(alerts []Alert) (alertsOutput []Alert, err error) {
}
client, err := api.NewHTTPClient(opts)
if err != nil {
log.Println("ERROR: Unable to create HTTP client")
fmt.Println("ERROR: Unable to create HTTP client.")
return alerts, err
}
// send a request to the validation endpoint:
Expand All @@ -420,20 +423,20 @@ func verifyAlerts(alerts []Alert) (alertsOutput []Alert, err error) {
response, err = client.Do(req)
// response, err = client.Post(alert.Validity_endpoint, secret_validation_content_type, body)
if err != nil {
log.Println("ERROR: Unable to send " + secret_validation_method + " request to " + alert.Validity_endpoint)
fmt.Println("WARNING: Unable to send " + secret_validation_method + " request to " + alert.Validity_endpoint)
continue
}
alert.Validity_response_code = strconv.Itoa(response.StatusCode)
defer response.Body.Close()
} else if secret_validation_method == "GET" {
response, err = client.Get(alert.Validity_endpoint)
if err != nil {
log.Println("ERROR: Unable to send " + secret_validation_method + " request to " + alert.Validity_endpoint)
fmt.Println("WARNING: Unable to send " + secret_validation_method + " request to " + alert.Validity_endpoint)
continue
}
alert.Validity_response_code = strconv.Itoa(response.StatusCode)
} else {
log.Println("ERROR: Invalid HTTP method for validation endpoint")
fmt.Println("WARNING: Invalid HTTP method for validation endpoint for " + alert.Secret_type + " secret type.")
continue
}
expected_body_key := SupportedProviders[provider][secret_type]["ExpectedBodyKey"]
Expand All @@ -443,7 +446,7 @@ func verifyAlerts(alerts []Alert) (alertsOutput []Alert, err error) {
} else if alert.Validity_response_code == "200" {
alert.Validity_boolean = true
if verbose {
log.Println(Yellow("CONFIRMED: Alert " + strconv.Itoa(alert.Number) + " in " + alert.Repository.Full_name + " is valid."))
fmt.Println(Yellow("CONFIRMED: Alert " + strconv.Itoa(alert.Number) + " in " + alert.Repository.Full_name + " is valid."))
}
} else {
alert.Validity_boolean = false
Expand All @@ -454,7 +457,7 @@ func verifyAlerts(alerts []Alert) (alertsOutput []Alert, err error) {
if alert.Validity_response_code == "200" {
alert.Validity_boolean = true
if verbose {
log.Println(Yellow("CONFIRMED: Alert " + strconv.Itoa(alert.Number) + " in " + alert.Repository.Full_name + " is valid."))
fmt.Println(Yellow("CONFIRMED: Alert " + strconv.Itoa(alert.Number) + " in " + alert.Repository.Full_name + " is valid."))
}
} else {
alert.Validity_boolean = false
Expand All @@ -468,16 +471,15 @@ func verifyAlerts(alerts []Alert) (alertsOutput []Alert, err error) {
func checkForExpectedBody(response *http.Response, expected_body_key string, expected_body_value string, alert Alert) (validity_boolean bool) {
body, err := io.ReadAll(response.Body)
if err != nil {
log.Println("ERROR: Unable to read response body")
fmt.Println("ERROR: Unable to read response body for alert " + strconv.Itoa(alert.Number) + " in " + alert.Repository.Full_name)
return false
}
var response_body map[string]interface{}
err = json.Unmarshal(body, &response_body)
if err != nil {
log.Println("ERROR: Unable to unmarshal response body")
fmt.Println("ERROR: Unable to unmarshal response body for alert " + strconv.Itoa(alert.Number) + " in " + alert.Repository.Full_name)
return false
}
log.Println(response_body)
body_value := response_body[expected_body_key]
if body_value.(bool) {
// convert response_value to a string
Expand All @@ -486,7 +488,7 @@ func checkForExpectedBody(response *http.Response, expected_body_key string, exp
if body_value == expected_body_value {
alert.Validity_boolean = true
if verbose {
log.Println(Yellow("CONFIRMED: Alert " + strconv.Itoa(alert.Number) + " in " + alert.Repository.Full_name + " is valid."))
fmt.Println(Yellow("CONFIRMED: Alert " + strconv.Itoa(alert.Number) + " in " + alert.Repository.Full_name + " is valid."))
}
} else {
alert.Validity_boolean = false
Expand All @@ -503,7 +505,7 @@ func checkEnterpriseServerAPI(alert Alert, client *http.Client, secret_validatio
req.Header.Set("User-Agent", "gh-secret-scanning")
response, err := client.Do(req)
if err != nil {
log.Println("ERROR: Unable to send " + secret_validation_method + " request to " + alert.Validity_endpoint)
fmt.Println("WARNING: Unable to send " + secret_validation_method + " request to " + alert.Validity_endpoint)
}
alert.Validity_response_code = strconv.Itoa(response.StatusCode)
alert.Validity_endpoint = enterprise_server_api_endpoint
Expand All @@ -518,19 +520,19 @@ func createIssuesForValidAlerts(alerts []Alert) (err error) {
alertsByRepo[alert.Repository.Full_name] = append(alertsByRepo[alert.Repository.Full_name], alert)
}
for repo, alerts := range alertsByRepo {
// Check if there is at least one confirmed valid secret alert
// check if there is at least one confirmed valid secret alert
hasValidAlert := false
for _, alert := range alerts {
if alert.Validity_boolean {
hasValidAlert = true
break
}
}
// If there is no confirmed valid secret alert, skip this repository
// if there is no confirmed valid secret alert, skip this repository
if !hasValidAlert {
continue
}
// Create a string with the details of the alerts
// create a string with the details of the alerts
details := "**Please promptly revoke the following secrets and confirm in the provider's logs that they have not been used maliciously:**\n\n"
details += "| Alert ID | Secret Type | Alert Link |\n"
details += "| --- | --- | --- |\n"
Expand All @@ -539,7 +541,7 @@ func createIssuesForValidAlerts(alerts []Alert) (err error) {
details += fmt.Sprintf("| %d | %s | [Link](%s) |\n", alert.Number, alert.Secret_type, alert.HTML_URL)
}
}
// Create the issue
// create the issue
var repo_with_host string
if host == "" {
repo_with_host = repo
Expand Down
17 changes: 8 additions & 9 deletions cmd/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"fmt"
"log"
"math"
"net/url"
"strconv"
Expand Down Expand Up @@ -79,10 +78,10 @@ func runVerify(cmd *cobra.Command, args []string) (err error) {
}

for page := 1; page <= pages; page++ {
log.Printf("Processing page: %d\n", page)
fmt.Println("Processing page: " + strconv.Itoa(page))
_, nextPage, err := callGitHubAPI(client, requestPath, &pageOfSecretAlerts, GET)
if err != nil {
log.Printf("ERROR: Unable to get alerts for target: %s\n", requestPath)
fmt.Println("ERROR: Unable to get alerts for target: " + requestPath)
return err
}
for _, secretAlert := range pageOfSecretAlerts {
Expand All @@ -109,8 +108,8 @@ func runVerify(cmd *cobra.Command, args []string) (err error) {
// verify which secret alerts are confirmed valid:
verifiedAlerts, err := verifyAlerts(sortedAlerts)
if err != nil {
// Log to console
fmt.Println("ERROR sending verify request: " + err.Error())
// print to console
fmt.Println("WARNING: issues encountered while sending verify requests.")
}
// pretty print with validity status
if !quiet {
Expand All @@ -127,10 +126,10 @@ func runVerify(cmd *cobra.Command, args []string) (err error) {
// optionally create an issue for each repository that contains at least one valid secret alert:
if createIssues {
err = createIssuesForValidAlerts(verifiedAlerts)
}
if err != nil {
fmt.Println(err)
return err
if err != nil {
fmt.Println(err)
return err
}
}
return err
}

0 comments on commit 4a396d3

Please sign in to comment.