Skip to content

Commit

Permalink
Merge pull request #35 from gabrielsoltz/update-api-definition
Browse files Browse the repository at this point in the history
Update Semgrep API Definition 1.0.0
  • Loading branch information
gabrielsoltz authored Sep 20, 2024
2 parents 0c0057b + 1544fc7 commit b402b5a
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 32 deletions.
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
module github.com/gabrielsoltz/steampipe-plugin-semgrep

go 1.21.0
toolchain go1.22.5
go 1.22.4

toolchain go1.23.1

require github.com/turbot/steampipe-plugin-sdk/v5 v5.10.4

Expand Down
3 changes: 2 additions & 1 deletion semgrep/table_semgrep_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ func tableDeployment(_ context.Context) *plugin.Table {
func listDeployments(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) {
endpoint := "/deployments"

jsonString, err := connect(ctx, d, endpoint, 0, 100)
emptyParams := map[string]string{}
jsonString, err := connect(ctx, d, endpoint, 0, 100, emptyParams)
if err != nil {
plugin.Logger(ctx).Error("semgrep_deployment.listDeployments", "connection_error", err)
return nil, err
Expand Down
80 changes: 63 additions & 17 deletions semgrep/table_semgrep_finding.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
func tableFinding(_ context.Context) *plugin.Table {
return &plugin.Table{
Name: "semgrep_finding",
Description: "Table for querying Semgrep findings data, including issues and metadata.",
Description: "Table for querying Semgrep code or supply chain findings data.",
List: &plugin.ListConfig{
ParentHydrate: listDeployments,
Hydrate: listFindings,
Expand All @@ -24,23 +24,28 @@ func tableFinding(_ context.Context) *plugin.Table {
Columns: []*plugin.Column{
{Name: "id", Type: proto.ColumnType_STRING, Description: "Unique ID of this finding."},
{Name: "ref", Type: proto.ColumnType_STRING, Description: "External reference to the source of this finding (e.g. PR)."},
{Name: "first_seen_scan_id", Type: proto.ColumnType_INT, Description: "First seen scan."},
{Name: "syntactic_id", Type: proto.ColumnType_STRING, Description: "Syntatic id."},
{Name: "first_seen_scan_id", Type: proto.ColumnType_INT, Description: "Unique ID of the Semgrep scan that first identified this finding."},
{Name: "syntactic_id", Type: proto.ColumnType_STRING, Description: "ID calculated based on a finding's file path, rule identifier and matched code, and index."},
{Name: "match_based_id", Type: proto.ColumnType_STRING, Description: "ID calculated based on a finding's file path, rule id, and the rule index."},
{Name: "state", Type: proto.ColumnType_STRING, Description: "Status of the finding's resolution."},
{Name: "external_ticket", Type: proto.ColumnType_JSON, Description: "External ticket associated with finding."},
{Name: "repository", Type: proto.ColumnType_JSON, Description: "Which repository is this finding a part of, defined via name."},
{Name: "triage_state", Type: proto.ColumnType_STRING, Description: "Status of the finding's triaging."},
{Name: "severity", Type: proto.ColumnType_STRING, Description: "Severity of the rule that triggered the finding. Ranges from low, which would correlate to info, up to high which would correlate to error."},
{Name: "confidence", Type: proto.ColumnType_STRING, Description: "Confidence of the rule that triggered the finding."},
{Name: "line_of_code_url", Type: proto.ColumnType_STRING, Description: "The source URL including file and line number.."},
{Name: "triage_state", Type: proto.ColumnType_STRING, Description: "The finding's triage state. Set by the user and used along with state to generate the final status viewable in the UI."},
{Name: "state", Type: proto.ColumnType_STRING, Description: "The finding's resolution state. Managed only by changes detected at scan time, the state is combined with triage_state to ultimately determine a final status which is exposed in the UI and API."},
{Name: "status", Type: proto.ColumnType_STRING, Description: "The finding's status as exposed in the UI. Status is a derived property combining information from the finding state and triage_state. The triage_state can be used to override the scan state if the finding is still detected."},
{Name: "severity", Type: proto.ColumnType_STRING, Description: "Severity of the finding, derived from the rule that triggered it. Low is equivalent to INFO, Medium to WARNING, and High to ERROR."},
{Name: "confidence", Type: proto.ColumnType_STRING, Description: "Confidence of the finding, derived from the rule that triggered it."},
{Name: "categories", Type: proto.ColumnType_JSON, Description: "The categories of the finding as classified by the associated rule metdata."},
{Name: "relevant_since", Type: proto.ColumnType_TIMESTAMP, Description: "Relevant since."},
{Name: "rule_name", Type: proto.ColumnType_STRING, Description: "Rule name of rule triggering."},
{Name: "rule_message", Type: proto.ColumnType_STRING, Description: "Rule message on the time of rule triggering. Older findings might have the value missing/removed."},
{Name: "created_at", Type: proto.ColumnType_TIMESTAMP, Description: "The timestamp when this finding was created."},
{Name: "relevant_since", Type: proto.ColumnType_TIMESTAMP, Description: "The timestamp when this finding was detected by Semgrep (the first time, or when reintroduced)."},
{Name: "location", Type: proto.ColumnType_JSON, Description: "Location of the record in a file, as reported by Semgrep. If null, then the information does not exist or lacks integrity (older or broken scans)."},
{Name: "sourcing_policy", Type: proto.ColumnType_JSON, Description: "Reference to a policy, with some basic information. If null, then the information does not exist or lacks integrity (older or broken scans)."},
{Name: "triaged_at", Type: proto.ColumnType_TIMESTAMP, Description: "Triaged at."},
{Name: "triage_comment", Type: proto.ColumnType_STRING, Description: "Triage comment."},
{Name: "state_updated_at", Type: proto.ColumnType_TIMESTAMP, Description: "When this issues' state was last updated."},
{Name: "triaged_at", Type: proto.ColumnType_TIMESTAMP, Description: "When the finding was triaged."},
{Name: "triage_comment", Type: proto.ColumnType_STRING, Description: "The detailed comment provided during triage."},
{Name: "triage_reason", Type: proto.ColumnType_STRING, Description: "Reason provided when this issue was triaged."},
{Name: "state_updated_at", Type: proto.ColumnType_TIMESTAMP, Description: "When this issue's state (resolution state) was last updated, as distinct from when the issue was triaged (triaged_at)."},
{Name: "rule", Type: proto.ColumnType_JSON, Description: "Rule that applies to this finding."},
{Name: "assistant", Type: proto.ColumnType_JSON, Description: "Semgrep Assistant data. Only present if Assistant is enabled."},
{Name: "deployment_slug", Type: proto.ColumnType_STRING, Description: "Sanitized machine-readable name of the deployment."},
},
}
Expand All @@ -57,7 +62,8 @@ func listFindings(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateDat

endpoint := "/deployments/" + deployment.Slug + "/findings"

paginatedResponse, err := paginatedResponse(ctx, d, endpoint)
emptyParams := map[string]string{}
paginatedResponse, err := paginatedResponse(ctx, d, endpoint, emptyParams)

if err != nil {
plugin.Logger(ctx).Error("semgrep_finding.listFindings", "connection_error", err)
Expand Down Expand Up @@ -88,23 +94,33 @@ type Finding struct {
FirstSeenScanID int `json:"first_seen_scan_id"`
SyntacticID string `json:"syntactic_id"`
MatchBasedID string `json:"match_based_id"`
State string `json:"state"`
ExternalTicket Ticket `json:"external_ticket"`
Repository Repository `json:"repository"`
LineOfCodeURL string `json:"line_of_code_url"`
TriageState string `json:"triage_state"`
State string `json:"state"`
Status string `json:"status"`
Severity string `json:"severity"`
Confidence string `json:"confidence"`
Categories []string `json:"categories"`
CreatedAt string `json:"created_at"`
RelevantSince string `json:"relevant_since"`
RuleName string `json:"rule_name"`
RuleMessage string `json:"rule_message"`
Location Location `json:"location"`
SourcingPolicy Policy `json:"sourcing_policy"`
TriagedAt string `json:"triaged_at"`
TriageComment string `json:"triage_comment"`
TriageReason string `json:"triage_reason"`
StateUpdatedAt string `json:"state_updated_at"`
Rule Rule `json:"rule"`
Assistant Assistant `json:"assistant"`
DeploymentSlug string `json:"-"`
}

type Ticket struct {
ExternalSlug string `json:"external_slug"`
URL string `json:"url"`
}

type Repository struct {
Name string `json:"name"`
URL string `json:"url"`
Expand All @@ -127,3 +143,33 @@ type Policy struct {
type FindingsResponse struct {
Findings []Finding `json:"findings"`
}

type Rule struct {
Name string `json:"name"`
Message string `json:"message"`
Confidence string `json:"confidence"`
Category string `json:"category"`
SubCategories []string `json:"subcategories"`
VulnerabilityClasses []string `json:"vulnerability_classes"`
CWENames []string `json:"cwe_names"`
OWASPNames []string `json:"owasp_names"`
}

type Assistant struct {
AutoFix struct {
FixCode string `json:"fix_code"`
Explanation string `json:"explanation"`
} `json:"autofix"`
Guidance struct {
Summary string `json:"summary"`
Instructions string `json:"instructions"`
} `json:"guidance"`
AutoTriage struct {
Veredict string `json:"veredict"`
Reason string `json:"reason"`
} `json:"autotriage"`
Component struct {
Tag string `json:"tag"`
Risk string `json:"risk"`
} `json:"component"`
}
15 changes: 9 additions & 6 deletions semgrep/table_semgrep_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ func tableProject(_ context.Context) *plugin.Table {
},
Columns: []*plugin.Column{
{Name: "id", Type: proto.ColumnType_STRING, Description: "Unique ID of this project."},
{Name: "name", Type: proto.ColumnType_STRING, Description: "Name of this project."},
{Name: "url", Type: proto.ColumnType_STRING, Description: "URL of this project."},
{Name: "latest_scan", Type: proto.ColumnType_TIMESTAMP, Description: "Latest scan date of this project."},
{Name: "tags", Type: proto.ColumnType_JSON, Description: "Tags of this project."},
{Name: "name", Type: proto.ColumnType_STRING, Description: "Name of the project."},
{Name: "url", Type: proto.ColumnType_STRING, Description: "URL of the project, if there is one."},
{Name: "tags", Type: proto.ColumnType_JSON, Description: "Tags associated to this project."},
{Name: "created_at", Type: proto.ColumnType_TIMESTAMP, Description: "Time when this project was created."},
{Name: "latest_scan_at", Type: proto.ColumnType_TIMESTAMP, Description: "Time of latest scan, if there is one."},
{Name: "deployment_slug", Type: proto.ColumnType_STRING, Description: "Sanitized machine-readable name of the deployment."},
},
}
Expand All @@ -43,7 +44,8 @@ func listProjects(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateDat

endpoint := "/deployments/" + deployment.Slug + "/projects"

paginatedResponse, err := paginatedResponse(ctx, d, endpoint)
emptyParams := map[string]string{}
paginatedResponse, err := paginatedResponse(ctx, d, endpoint, emptyParams)

if err != nil {
plugin.Logger(ctx).Error("semgrep_project.listProjects", "connection_error", err)
Expand Down Expand Up @@ -72,8 +74,9 @@ type Project struct {
ID int `json:"id"`
Name string `json:"name"`
Url string `json:"url"`
LatestScan string `json:"latest_scan_at"`
Tags []string `json:"tags"`
CreatedAt string `json:"created_at"`
LatestScan string `json:"latest_scan_at"`
DeploymentSlug string `json:"-"`
}

Expand Down
24 changes: 18 additions & 6 deletions semgrep/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,30 @@ import (
"github.com/turbot/steampipe-plugin-sdk/v5/plugin"
)

func connect(ctx context.Context, d *plugin.QueryData, endpoint string, page int, pageSize int) (string, error) {
func connect(ctx context.Context, d *plugin.QueryData, endpoint string, page int, pageSize int, extraParams map[string]string) (string, error) {
var baseUrl, token string

// Prefer config options given in Steampipe
semgrepConfig := GetConfig(d.Connection)

// Get the base URL from environment or configuration
baseUrl = os.Getenv("SEMGREP_URL")
if semgrepConfig.BaseUrl != nil {
baseUrl = *semgrepConfig.BaseUrl
}

// Get the token from environment or configuration
token = os.Getenv("SEMGREP_TOKEN")
if semgrepConfig.Token != nil {
token = *semgrepConfig.Token
}

// Ensure base URL is set
if baseUrl == "" {
return "", errors.New("'baseUrl' must be set in the connection configuration. Edit your connection configuration file and then restart Steampipe")
}

// Ensure token is set
if token == "" {
return "", errors.New("'token' must be set in the connection configuration. Edit your connection configuration file and then restart Steampipe")
}
Expand All @@ -53,6 +57,12 @@ func connect(ctx context.Context, d *plugin.QueryData, endpoint string, page int
queryParams := req.URL.Query()
queryParams.Set("page", fmt.Sprintf("%d", page))
queryParams.Set("page_size", fmt.Sprintf("%d", pageSize))

// Add any extra query parameters if provided
for key, value := range extraParams {
queryParams.Set(key, value)
}

req.URL.RawQuery = queryParams.Encode()

// Make the request
Expand All @@ -69,38 +79,40 @@ func connect(ctx context.Context, d *plugin.QueryData, endpoint string, page int
return "", fmt.Errorf("API returned HTTP status %s", resp.Status)
}

// Read and print the response body
// Read and return the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
plugin.Logger(ctx).Error("Failed to read response body: %v", err)
return "", err
}

return string(body), nil

}

func paginatedResponse(ctx context.Context, d *plugin.QueryData, endpoint string) ([]string, error) {
func paginatedResponse(ctx context.Context, d *plugin.QueryData, endpoint string, extraParams map[string]string) ([]string, error) {
var paginatedResponse []string

page := 0
pageSize := 100

// Iteration for Pagination
for {
jsonString, err := connect(ctx, d, endpoint, page, pageSize)
// Call the connect function with extraParams for query parameters
jsonString, err := connect(ctx, d, endpoint, page, pageSize, extraParams)
if err != nil {
plugin.Logger(ctx).Error("utils.paginatedResponse", "connection_error", err)
return nil, err
}

paginatedResponse = append(paginatedResponse, jsonString)

// Check if we need to stop pagination
if len(jsonString) < pageSize {
break
}
page++

// Increment page number for next iteration
page++
}

return paginatedResponse, nil
Expand Down

0 comments on commit b402b5a

Please sign in to comment.