Skip to content

Commit 8f3e581

Browse files
add vulnerability scan (#585)
This PR adds vulnerability scanning for new connector releases ## How Three new commands have been added: 1. `download-artifacts`: This command is responsible for downloading connector artifacts (CLI Plugins, targz file, Docker Images) 2. `scan trivy`: This command scans the downloaded connector artifacts using Trivy 3. `scan gokakashi`: This commands scans all connector docker images using Gokakashi Trivy scan runs on new PRs Gokakashi scan runs once a day periodically ## Review This PR refactors and changes a few files. I've broken the changes down by commits. I'd recommend using commits to review this. ## Tests Tests for the new commands (and functions) are not yet present. I'll be adding them in a follow up PR --------- Co-authored-by: Daniel Harvey <[email protected]>
1 parent fe95b19 commit 8f3e581

File tree

19 files changed

+1225
-40
lines changed

19 files changed

+1225
-40
lines changed

.github/workflows/gokakashi-scan.yaml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Vulnerability Scan using Gokakashi
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
paths:
8+
- .github/workflows/gokakashi-scan.yaml # run on changes to this workflow file
9+
- registry-automation/** # run on changes to the registry automation files
10+
schedule:
11+
- cron: '0 0 * * *' # everyday at midnight
12+
13+
jobs:
14+
gokakashi-scan:
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v2
20+
with:
21+
fetch_depth: 1
22+
23+
- name: Setup Go
24+
uses: actions/setup-go@v4
25+
with:
26+
go-version: 1.22.x
27+
28+
- name: Download gokakashi
29+
id: download_gokakashi
30+
run: |
31+
# Define URL and output path
32+
URL="https://github.com/shinobistack/gokakashi/releases/download/v0.2.0/gokakashi-linux-amd64"
33+
DEST="$RUNNER_TEMP/gokakashi"
34+
35+
# Download and make it executable
36+
curl -L "$URL" -o "$DEST"
37+
chmod +x "$DEST"
38+
echo "Gokakashi binary downloaded to $DEST"
39+
40+
echo "gokakashi_path=$DEST" >> "$GITHUB_OUTPUT"
41+
42+
- name: Run Gokakashi Scan
43+
run: |
44+
cd registry-automation
45+
go run main.go scan gokakashi \
46+
--files "../registry/*/*/releases/*/connector-packaging.json" \
47+
--server "${{ secrets.GOKAKASHI_SERVER_URL }}" \
48+
--token "${{ secrets.GOKAKASHI_API_TOKEN }}" \
49+
--policy ci-platform \
50+
--cf-access-client-id "${{ secrets.GOKAKASHI_CF_ACCESS_CLIENT_ID }}" \
51+
--cf-access-client-secret "${{ secrets.GOKAKASHI_CF_ACCESS_CLIENT_SECRET }}" \
52+
--binary-path "$GOKAKASHI_PATH"
53+
env:
54+
GOKAKASHI_PATH: ${{ steps.download_gokakashi.outputs.gokakashi_path }}

.github/workflows/pr-scan.yaml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Vulnerability Scan for Pull Requests
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
paths:
8+
- registry/**/connector-packing.json
9+
- .github/workflows/pr-scan.yaml
10+
11+
jobs:
12+
trivy-scan:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout repository
17+
uses: actions/checkout@v2
18+
with:
19+
fetch_depth: 1
20+
21+
- name: Get all connector version package changes
22+
id: connector-version-changed-files
23+
uses: tj-actions/[email protected]
24+
with:
25+
json: true
26+
escape_json: false
27+
files: |
28+
registry/**
29+
30+
- name: Print out all the changed filse
31+
env:
32+
ADDED_FILES: ${{ steps.connector-version-changed-files.outputs.added_files }}
33+
MODIFIED_FILES: ${{ steps.connector-version-changed-files.outputs.modified_files }}
34+
DELETED_FILES: ${{ steps.connector-version-changed-files.outputs.deleted_files }}
35+
run: |
36+
echo "{\"added_files\": $ADDED_FILES, \"modified_files\": $MODIFIED_FILES, \"deleted_files\": $DELETED_FILES}" > changed_files.json
37+
38+
- name: List changed files
39+
id: list_files
40+
run: |
41+
cat changed_files.json
42+
43+
- name: Setup Go
44+
uses: actions/setup-go@v4
45+
with:
46+
go-version: 1.21.x
47+
48+
- name: Run Scan
49+
env:
50+
CHANGED_FILES_PATH: "changed_files.json"
51+
run: |
52+
mv changed_files.json registry-automation/changed_files.json
53+
cd registry-automation
54+
go run main.go scan trivy

registry-automation/cmd/ci.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ var ciCmd = &cobra.Command{
3434
var ciCmdArgs ConnectorRegistryArgs
3535

3636
func init() {
37-
rootCmd.AddCommand(ciCmd)
37+
RootCmd.AddCommand(ciCmd)
3838

3939
// Path for the changed files in the PR
4040
var changedFilesPathEnv = os.Getenv("CHANGED_FILES_PATH")
@@ -626,7 +626,7 @@ func uploadConnectorVersionPackage(ciCtx Context, connector Connector, version s
626626
return connectorVersion, fmt.Errorf("invalid or undefined TGZ URL: %v", tgzUrl)
627627
}
628628

629-
connectorVersionMetadata, connectorMetadataTgzPath, err := pkg.GetConnectorVersionMetadata(tgzUrl,
629+
connectorVersionMetadata, connectorMetadataTgzPath, _, err := pkg.GetConnectorVersionMetadata(tgzUrl,
630630
connector.Namespace, connector.Name, version)
631631
if err != nil {
632632
return connectorVersion, err
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package cmd
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"log"
8+
"os"
9+
"path/filepath"
10+
11+
"github.com/hasura/ndc-hub/registry-automation/pkg/ndchub"
12+
"github.com/spf13/cobra"
13+
)
14+
15+
var downloadArtifactsCmd = &cobra.Command{
16+
Use: "download-artifacts",
17+
Short: "Downloads the artifacts from the connector registry",
18+
Run: runDownloadArtifactsCmd,
19+
}
20+
21+
var downloadArtifactsCmdArgs ConnectorRegistryArgs
22+
23+
func init() {
24+
RootCmd.AddCommand(downloadArtifactsCmd)
25+
var changedFilesPathEnv = os.Getenv("CHANGED_FILES_PATH") // this file contains the list of changed files
26+
downloadArtifactsCmd.PersistentFlags().StringVar(&downloadArtifactsCmdArgs.ChangedFilesPath, "changed-files-path", changedFilesPathEnv, "path to a line-separated list of changed files in the PR")
27+
if changedFilesPathEnv == "" {
28+
downloadArtifactsCmd.MarkPersistentFlagRequired("changed-files-path")
29+
}
30+
}
31+
32+
type ArtifactDownloadOptions struct {
33+
ChangedFilesPath string
34+
SingleFilePath string
35+
}
36+
37+
type ArtifactOption func(*ArtifactDownloadOptions)
38+
39+
func WithChangedFilesPath(path string) ArtifactOption {
40+
return func(o *ArtifactDownloadOptions) {
41+
o.ChangedFilesPath = path
42+
}
43+
}
44+
45+
func WithSingleFile(path string) ArtifactOption {
46+
return func(o *ArtifactDownloadOptions) {
47+
o.SingleFilePath = path
48+
}
49+
}
50+
51+
func DownloadArtifacts(opts ...ArtifactOption) ([]*ndchub.ConnectorArtifacts, error) {
52+
options := &ArtifactDownloadOptions{}
53+
for _, opt := range opts {
54+
opt(options)
55+
}
56+
57+
if options.ChangedFilesPath == "" && options.SingleFilePath == "" {
58+
return nil, fmt.Errorf("at least one of ChangedFilesPath or SingleFilePath must be provided")
59+
}
60+
61+
if options.ChangedFilesPath != "" {
62+
connectorPackagingFiles, err := getConnectorPackagingFilesFromChangedFiles(options.ChangedFilesPath)
63+
if err != nil {
64+
return nil, fmt.Errorf("failed to get connector packaging files from changed files: %w", err)
65+
}
66+
return downloadArtifacts(connectorPackagingFiles)
67+
} else {
68+
// If a single file path is provided, we can directly download the artifacts for that file
69+
artifacts, err := downloadArtifactsUtil(options.SingleFilePath)
70+
artifactsArr := []*ndchub.ConnectorArtifacts{artifacts}
71+
return artifactsArr, err
72+
}
73+
}
74+
75+
func getChangedFiles(filepath string) (*ChangedFiles, error) {
76+
changedFilesContent, err := os.Open(filepath)
77+
if err != nil {
78+
// log.Fatalf("Failed to open the file: %v, err: %v", ciCmdArgs.ChangedFilesPath, err)
79+
return nil, fmt.Errorf("failed to open the file: %v, err: %w", downloadArtifactsCmdArgs.ChangedFilesPath, err)
80+
}
81+
defer changedFilesContent.Close()
82+
83+
// Read the changed file's contents. This file contains all the changed files in the PR
84+
changedFilesByteValue, err := io.ReadAll(changedFilesContent)
85+
if err != nil {
86+
// log.Fatalf("Failed to read the changed files JSON file: %v", err)
87+
return nil, fmt.Errorf("failed to read the changed files JSON file: %w", err)
88+
}
89+
90+
var changedFiles *ChangedFiles = &ChangedFiles{}
91+
err = json.Unmarshal(changedFilesByteValue, changedFiles)
92+
if err != nil {
93+
// log.Fatalf("Failed to unmarshal the changed files content: %v", err)
94+
return nil, fmt.Errorf("failed to unmarshal the changed files content: %w", err)
95+
}
96+
97+
return changedFiles, nil
98+
}
99+
100+
// for now, since we're only adding support for newly added connectors, we will only check for the added files
101+
// in the added files, we only need files that end with connector-packaging.json
102+
func filterConnectorPackagingFiles(changedFiles *ChangedFiles) []string {
103+
// Filter the changed files to only include the ones that are in the connector registry
104+
var filteredChangedFiles []string = make([]string, 0)
105+
for _, file := range changedFiles.Added {
106+
if isConnectorPackagingFile(file) {
107+
filteredChangedFiles = append(filteredChangedFiles, file)
108+
}
109+
}
110+
111+
log.Printf("Filtered connector packaging files: %v", filteredChangedFiles)
112+
return filteredChangedFiles
113+
}
114+
115+
func isConnectorPackagingFile(path string) bool {
116+
return filepath.Base(path) == "connector-packaging.json"
117+
}
118+
119+
func getConnectorPackaging(filePath string) (*ndchub.ConnectorPackaging, error) {
120+
if !filepath.IsAbs(filePath) {
121+
filePath = filepath.Join("../", filePath) // the filepaths returned by the changed files action are relative to the root of the repository. Therefore, we need to prepend "../" to the path to get the correct path
122+
}
123+
ndcHubConnectorPackaging, err := ndchub.GetConnectorPackaging(filePath)
124+
if err != nil {
125+
return nil, fmt.Errorf("failed to get the connector packaging for file %s: %w", filePath, err)
126+
}
127+
return ndcHubConnectorPackaging, nil
128+
}
129+
130+
func getConnectorPackagingFilesFromChangedFiles(changedFilesPath string) ([]string, error) {
131+
// Get the changed files from the PR
132+
changedFiles, err := getChangedFiles(changedFilesPath)
133+
if err != nil {
134+
return nil, err
135+
}
136+
137+
// Get the connector packaging files (connector-packaging.json) from the changed files
138+
connectorPackagingFiles := filterConnectorPackagingFiles(changedFiles)
139+
return connectorPackagingFiles, nil
140+
}
141+
142+
func downloadArtifacts(connectorPackagingFiles []string) ([]*ndchub.ConnectorArtifacts, error) {
143+
artifactList := make([]*ndchub.ConnectorArtifacts, 0)
144+
for _, file := range connectorPackagingFiles {
145+
log.Printf("\n\n") // for more readable logs
146+
artifacts, err := downloadArtifactsUtil(file)
147+
if err != nil {
148+
return nil, fmt.Errorf("failed to download artifacts for file %s: %w", file, err)
149+
}
150+
artifactList = append(artifactList, artifacts)
151+
}
152+
153+
return artifactList, nil
154+
}
155+
156+
func downloadArtifactsUtil(connectorPackagingFilePath string) (*ndchub.ConnectorArtifacts, error) {
157+
connectorPackaging, err := getConnectorPackaging(connectorPackagingFilePath)
158+
if err != nil {
159+
return nil, fmt.Errorf("failed to get the connector packaging: %w", err)
160+
}
161+
162+
connectorMetadata, _, extractedTgzPath, err := ndchub.GetPackagingSpec(connectorPackaging.URI,
163+
connectorPackaging.Namespace,
164+
connectorPackaging.Name,
165+
connectorPackaging.Version,
166+
)
167+
168+
if err != nil {
169+
return nil, fmt.Errorf("failed to get connector metadata for %s/%s:%s: %w",
170+
connectorPackaging.Namespace, connectorPackaging.Name, connectorPackaging.Version, err)
171+
}
172+
173+
artifacts, err := connectorMetadata.GetArtifacts(extractedTgzPath)
174+
if err != nil {
175+
return nil, fmt.Errorf("failed to get the artifacts for %s/%s:%s: %w",
176+
connectorPackaging.Namespace, connectorPackaging.Name, connectorPackaging.Version, err)
177+
}
178+
179+
return artifacts, nil
180+
}
181+
182+
func runDownloadArtifactsCmd(cmd *cobra.Command, args []string) {
183+
artifacts, err := DownloadArtifacts(WithChangedFilesPath(downloadArtifactsCmdArgs.ChangedFilesPath))
184+
if err != nil {
185+
fmt.Printf("Failed to download artifacts: %v\n", err)
186+
os.Exit(1)
187+
}
188+
189+
// Print artifactList as JSON
190+
artifactListJSON, err := json.MarshalIndent(artifacts, "", " ")
191+
if err != nil {
192+
fmt.Printf("Failed to marshal artifact list to JSON: %v\n", err)
193+
return
194+
}
195+
fmt.Println(string(artifactListJSON))
196+
}

registry-automation/cmd/e2e.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func init() {
5050

5151
e2eCmd.AddCommand(e2eChangedCmd, e2eLatest, e2eAll)
5252

53-
rootCmd.AddCommand(e2eCmd)
53+
RootCmd.AddCommand(e2eCmd)
5454
}
5555

5656
func e2eChanged(cmd *cobra.Command, args []string) {

registry-automation/cmd/root.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ import (
66
"github.com/spf13/cobra"
77
)
88

9-
// rootCmd represents the base command when called without any subcommands
10-
var rootCmd = &cobra.Command{
9+
// RootCmd represents the base command when called without any subcommands
10+
var RootCmd = &cobra.Command{
1111
Use: "registry-automation",
1212
Short: "Commands associated with automation for the hub registry",
1313
}
1414

1515
// Execute adds all child commands to the root command and sets flags appropriately.
1616
// This is called by main.main(). It only needs to happen once to the rootCmd.
1717
func Execute() {
18-
err := rootCmd.Execute()
18+
err := RootCmd.Execute()
1919
if err != nil {
2020
os.Exit(1)
2121
}

0 commit comments

Comments
 (0)