Skip to content

Commit 69edd6f

Browse files
authored
Merge branch 'master' into docs
2 parents e35524c + 87f0bac commit 69edd6f

File tree

10 files changed

+583
-153
lines changed

10 files changed

+583
-153
lines changed

.github/workflows/intercept.yml

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: "Intercept Mock Code Scanning - Integration"
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
branch:
7+
description: 'Branch to scan'
8+
required: true
9+
default: 'master'
10+
push:
11+
branches: [ master, integration/github ]
12+
schedule:
13+
- cron: '0 0 * * 0'
14+
15+
16+
jobs:
17+
analyze:
18+
name: Mock Code Scanning with Intercept
19+
runs-on: ubuntu-latest
20+
21+
steps:
22+
- name: Checkout repository
23+
uses: actions/checkout@v2
24+
25+
- name: Compile intercept
26+
run: |
27+
make build && cp release/intercept .
28+
29+
- name: Run intercept with mock policy
30+
run: |
31+
./intercept audit --policy playground/policies/mock_github.yaml --target playground/targets/ -vvvv
32+
33+
- name: Find SARIF file
34+
id: find-sarif
35+
run: |
36+
SARIF_FILE=$(find . -type f -regex ".*intercept_[a-zA-Z0-9]+\.sarif\.json" -print -quit)
37+
echo "SARIF_FILE=${SARIF_FILE}" >> $GITHUB_OUTPUT
38+
39+
- name: Upload SARIF file
40+
uses: github/codeql-action/upload-sarif@v2
41+
with:
42+
sarif_file: ${{ steps.find-sarif.outputs.SARIF_FILE }}

cmd/hook.go

+83-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ package cmd
55

66
import (
77
"bytes"
8+
"crypto/hmac"
9+
"crypto/rand"
10+
"crypto/sha256"
811
"crypto/tls"
12+
"encoding/base64"
913
"encoding/json"
1014
"fmt"
1115
"reflect"
@@ -78,6 +82,42 @@ func convertLogsToNDJSON(logs interface{}) (string, error) {
7882
return strings.Join(ndjsonLines, "\n"), nil
7983
}
8084

85+
func GenerateWebhookSecret() (string, error) {
86+
// Generate 32 bytes of random data
87+
randomBytes := make([]byte, 32)
88+
_, err := rand.Read(randomBytes)
89+
if err != nil {
90+
return "", fmt.Errorf("failed to generate random bytes: %w", err)
91+
}
92+
93+
// Encode the random bytes to base64
94+
secret := base64.URLEncoding.EncodeToString(randomBytes)
95+
96+
// Prefix the secret with "whsec_" to match the format of the example
97+
return "whsec_" + secret, nil
98+
}
99+
100+
func calculateWebhookSignature(payload []byte, secret string) (string, error) {
101+
// Split the secret and decode the base64 part
102+
parts := strings.SplitN(secret, "_", 2)
103+
if len(parts) != 2 {
104+
return "", fmt.Errorf("invalid secret format")
105+
}
106+
secretBytes, err := base64.URLEncoding.DecodeString(parts[1])
107+
if err != nil {
108+
return "", fmt.Errorf("failed to decode secret: %w", err)
109+
}
110+
111+
// Create the HMAC
112+
h := hmac.New(sha256.New, secretBytes)
113+
h.Write(payload)
114+
115+
// Get the result and encode to base64
116+
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
117+
118+
return signature, nil
119+
}
120+
81121
// event types same as log types : minimal, results, policy, report
82122
// custom modifiers : policy+bulk, report+bulk,
83123

@@ -168,6 +208,22 @@ func PostResultsToWebhooks(sarifReport SARIFReport) error {
168208
// Prepare the request
169209
req := client.R()
170210
req.SetHeaders(hook.Headers)
211+
212+
// Set custom User-Agent
213+
userAgent := fmt.Sprintf("intercept/%s", buildVersion)
214+
req.SetHeader("User-Agent", userAgent)
215+
216+
// Convert payload to []byte for signature calculation
217+
var payloadBytes []byte
218+
if payloadType == "x-ndjson" {
219+
payloadBytes = []byte(payload.(string))
220+
} else {
221+
payloadBytes, _ = json.Marshal(payload)
222+
}
223+
// Calculate and set the signature
224+
signature, _ := calculateWebhookSignature(payloadBytes, webhookSecret)
225+
req.SetHeader("X-Signature", signature)
226+
171227
req.SetBody(payload)
172228

173229
// Set content type based on payloadType
@@ -196,13 +252,15 @@ func PostResultsToWebhooks(sarifReport SARIFReport) error {
196252
time.Sleep(retryDelay)
197253
}
198254
}
255+
// Flatten hook.EventTypes to a single string
256+
flatEventTypes := strings.Join(hook.EventTypes, ",")
199257

200258
if err != nil {
201-
log.Error().Err(err).Str("hook", hook.Name).Msg("Failed to post to webhook")
259+
log.Error().Err(err).Str("hook", hook.Name).Str("event_type", flatEventTypes).Msg("Failed to post to webhook")
202260
} else if !resp.IsSuccess() {
203-
log.Error().Str("hook", hook.Name).Int("status", resp.StatusCode()).Msg("Webhook request failed")
261+
log.Error().Str("hook", hook.Name).Str("event_type", flatEventTypes).Int("status", resp.StatusCode()).Msg("Webhook request failed")
204262
} else {
205-
log.Info().Str("hook", hook.Name).Msg("Successfully posted to webhook")
263+
log.Info().Str("hook", hook.Name).Str("event_type", flatEventTypes).Str("payload_type", payloadType).Msg("Successfully posted to webhook")
206264
}
207265
}
208266

@@ -330,6 +388,22 @@ func PostReportToWebhooks(sarifReport SARIFReport) error {
330388
// Prepare the request
331389
req := client.R()
332390
req.SetHeaders(hook.Headers)
391+
392+
// Set custom User-Agent
393+
userAgent := fmt.Sprintf("intercept/%s", buildVersion)
394+
req.SetHeader("User-Agent", userAgent)
395+
396+
// Convert payload to []byte for signature calculation
397+
var payloadBytes []byte
398+
if payloadType == "x-ndjson" {
399+
payloadBytes = []byte(payload.(string))
400+
} else {
401+
payloadBytes, _ = json.Marshal(payload)
402+
}
403+
// Calculate and set the signature
404+
signature, _ := calculateWebhookSignature(payloadBytes, webhookSecret)
405+
req.SetHeader("X-Signature", signature)
406+
333407
req.SetBody(payload)
334408

335409
// if containsString(hook.EventTypes, "bulk") {
@@ -361,12 +435,15 @@ func PostReportToWebhooks(sarifReport SARIFReport) error {
361435
}
362436
}
363437

438+
// Flatten hook.EventTypes to a single string
439+
flatEventTypes := strings.Join(hook.EventTypes, ",")
440+
364441
if err != nil {
365-
log.Error().Err(err).Str("hook", hook.Name).Msg("Failed to post to webhook")
442+
log.Error().Err(err).Str("hook", hook.Name).Str("event_type", flatEventTypes).Msg("Failed to post to webhook")
366443
} else if !resp.IsSuccess() {
367-
log.Error().Str("hook", hook.Name).Int("status", resp.StatusCode()).Msg("Webhook request failed")
444+
log.Error().Str("hook", hook.Name).Str("event_type", flatEventTypes).Int("status", resp.StatusCode()).Msg("Webhook request failed")
368445
} else {
369-
log.Info().Str("hook", hook.Name).Msg("Successfully posted to webhook")
446+
log.Info().Str("hook", hook.Name).Str("event_type", flatEventTypes).Str("payload_type", payloadType).Msg("Successfully posted to webhook")
370447
}
371448
}
372449

cmd/observe.go

+14
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ var (
3131
observeReport string
3232
observeMode string
3333
observeIndex string
34+
webhookSecret string
3435
reportMutex sync.Mutex
3536
reportDir string = "_status"
3637
allFileInfos []FileInfo
@@ -112,6 +113,19 @@ func runObserve(cmd *cobra.Command, args []string) {
112113

113114
observeConfig = GetConfig()
114115

116+
if len(observeConfig.Hooks) > 0 {
117+
if observeConfig.Flags.WebhookSecret != "" {
118+
webhookSecret = os.Getenv(observeConfig.Flags.WebhookSecret)
119+
} else {
120+
webhookSecret, err = GenerateWebhookSecret()
121+
122+
if err != nil {
123+
log.Fatal().Err(err).Msg("Failed to generate webhook secret")
124+
}
125+
}
126+
log.Info().Str("webhook_secret", webhookSecret).Msg("Webhook Secret for X-Signature")
127+
}
128+
115129
// Needed for scan/assure/schema policies
116130
if observeConfig.Flags.Target != "" {
117131
targetDir = observeConfig.Flags.Target

cmd/policy.go

+33-4
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ import (
1212
)
1313

1414
type PolicyFile struct {
15-
Config Config `yaml:"Config"`
16-
Version string `yaml:"Version"`
17-
Namespace string `yaml:"Namespace"`
18-
Policies []Policy `yaml:"Policies"`
15+
Config Config `yaml:"Config"`
16+
Version string `yaml:"Version"`
17+
Namespace string `yaml:"Namespace"`
18+
Policies []Policy `yaml:"Policies"`
19+
SARIFRules []SARIFRule `json:"sarif_rules,omitempty"`
1920
}
2021

2122
type Config struct {
@@ -32,6 +33,7 @@ type Config struct {
3233
Tags []string `yaml:"tags,omitempty"`
3334
PolicySchedule string `yaml:"policy_schedule,omitempty"`
3435
ReportSchedule string `yaml:"report_schedule,omitempty"`
36+
WebhookSecret string `yaml:"webhook_secret_env,omitempty"`
3537
} `yaml:"Flags,omitempty"`
3638
Metadata struct {
3739
HostOS string `yaml:"host_os,omitempty"`
@@ -91,6 +93,7 @@ type Metadata struct {
9193
Score string `yaml:"score"`
9294
MsgSolution string `yaml:"msg_solution"`
9395
MsgError string `yaml:"msg_error"`
96+
HelpURL string `yaml:"help_url"`
9497
TargetInfo []string `yaml:"target_info,omitempty"`
9598
}
9699

@@ -141,13 +144,39 @@ func LoadPolicyFile(filename string) (*PolicyFile, error) {
141144

142145
// log.Debug().Interface("raw config", policyFile.Config).Msg("Raw Config data")
143146

147+
rules := make([]SARIFRule, 0, len(policyFile.Policies))
148+
144149
// Generate intercept_id for each policy, add its own ID as a tag for easy filtering with tags flag
145150
for i := range policyFile.Policies {
146151
policyFile.Policies[i].ID = NormalizePolicyName(policyFile.Policies[i].ID)
147152
policyFile.Policies[i].InterceptID = intercept_run_id + "-" + NormalizeFilename(policyFile.Policies[i].ID)
148153
policyFile.Policies[i].Metadata.Tags = append(policyFile.Policies[i].Metadata.Tags, policyFile.Policies[i].ID)
154+
155+
// Generate rule entry for SARIF
156+
rule := SARIFRule{
157+
ID: policyFile.Policies[i].ID,
158+
ShortDescription: ShortDescription{
159+
Text: policyFile.Policies[i].Metadata.Description,
160+
},
161+
FullDescription: &FullDescription{
162+
Text: policyFile.Policies[i].Metadata.MsgError,
163+
},
164+
HelpURI: policyFile.Policies[i].Metadata.HelpURL,
165+
Help: &Help{
166+
Text: policyFile.Policies[i].Metadata.MsgSolution,
167+
},
168+
Properties: Properties{
169+
Category: policyFile.Policies[i].Metadata.Tags[0],
170+
Tags: policyFile.Policies[i].Metadata.Tags,
171+
},
172+
}
173+
rules = append(rules, rule)
174+
149175
}
150176

177+
// Add rules to policyFile
178+
policyFile.SARIFRules = rules
179+
151180
return &policyFile, nil
152181
}
153182

cmd/root.go

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ var (
3030
hostIps string
3131
buildVersion string
3232
buildSignature string
33+
smVersion string
3334

3435
debugOutput bool
3536

@@ -81,6 +82,7 @@ func init() {
8182

8283
// running id
8384
intercept_run_id = ksuid.New().String()
85+
smVersion = strings.Split(strings.TrimPrefix(buildVersion, "v"), "-")[0]
8486

8587
// Setup logging based on verbosity flag
8688
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {

cmd/runtime.go

+15-2
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,15 @@ func generateRuntimeSARIFReport(policy Policy, gossResult GossResult) (SARIFRepo
134134
{
135135
Tool: Tool{
136136
Driver: Driver{
137-
Name: "INTERCEPT",
138-
Version: buildVersion,
137+
FullName: fmt.Sprintf("%s %s", "INTERCEPT", buildVersion),
138+
Name: "INTERCEPT",
139+
Version: smVersion,
140+
SemanticVersion: smVersion,
141+
InformationURI: "https://intercept.cc",
142+
Rules: policyData.SARIFRules,
139143
},
140144
},
145+
141146
Results: []Result{},
142147
Invocations: []Invocation{
143148
{
@@ -214,6 +219,14 @@ func generateRuntimeSARIFReport(policy Policy, gossResult GossResult) (SARIFRepo
214219
{
215220
PhysicalLocation: PhysicalLocation{
216221
ArtifactLocation: ArtifactLocation{URI: "N/A"},
222+
Region: Region{
223+
StartLine: 1,
224+
StartColumn: 1,
225+
EndColumn: 1,
226+
Snippet: Snippet{
227+
Text: "N/A",
228+
},
229+
},
217230
},
218231
},
219232
},

0 commit comments

Comments
 (0)