Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ All notable changes to `src-cli` are documented in this file.

## Unreleased

### Added

- SBOM support: Added `--image` and `--exclude-image` flags to `src sbom fetch` for filtering which docker images SBOMs are fetched for. Both flags support glob patterns (e.g., `frontend`, `*api*`) and comma-separated lists. The `sourcegraph/` image name prefix is optional.

## 6.4.0

## 6.3.0
Expand Down
36 changes: 33 additions & 3 deletions cmd/src/sbom_fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,32 @@ func init() {

Usage:

src sbom fetch -v <version>
src sbom fetch -v <version> [--image <image-patterns>] [--exclude-image <exclude-patterns>]

Examples:

$ src sbom fetch -v 5.8.0 # Fetch all SBOMs for the 5.8.0 release
$ src sbom fetch -v 5.8.0 # Fetch all SBOMs for the 5.8.0 release

$ src sbom fetch -v 5.8.123 -internal -d /tmp/sboms # Fetch all SBOMs for the internal 5.8.123 release and store them in /tmp/sboms
$ src sbom fetch -v 5.8.0 --image frontend # Fetch SBOM only for the frontend image

$ src sbom fetch -v 5.8.0 --image "redis*" # Fetch SBOMs for all images with names beginning with 'redis'

$ src sbom fetch -v 5.8.0 --image "frontend,redis*" # Fetch SBOMs for frontend, and all redis images

$ src sbom fetch -v 5.8.0 --exclude-image "sg,*redis*" # Fetch SBOMs for all images, except sg and redis

$ src sbom fetch -v 5.8.0 --image "postgres*" --exclude-image "*exporter*" # Fetch SBOMs for all postgres images, except exporters

$ src sbom fetch -v 5.8.123 -internal -d /tmp/sboms # Fetch all SBOMs for the internal 5.8.123 release and store them in /tmp/sboms
`

flagSet := flag.NewFlagSet("fetch", flag.ExitOnError)
versionFlag := flagSet.String("v", "", "The version of Sourcegraph to fetch SBOMs for.")
outputDirFlag := flagSet.String("d", "sourcegraph-sboms", "The directory to store validated SBOMs in.")
internalReleaseFlag := flagSet.Bool("internal", false, "Fetch SBOMs for an internal release. Defaults to false.")
insecureIgnoreTransparencyLogFlag := flagSet.Bool("insecure-ignore-tlog", false, "Disable transparency log verification. Defaults to false.")
imageFlag := flagSet.String("image", "", "Filter list of image names, to only fetch SBOMs for Docker images with names matching these patterns. Supports literal names, like frontend, and glob patterns like '*postgres*'. Multiple patterns can be specified as a comma-separated list (e.g., 'frontend,*postgres-1?-*'). The 'sourcegraph/' prefix is optional. If not specified, SBOMs for all images are fetched.")
excludeImageFlag := flagSet.String("exclude-image", "", "Exclude Docker images with names matching these patterns from being fetched. Supports the same formats as --image. Takes precedence over --image filters.")

handler := func(args []string) error {
c := cosignConfig{
Expand Down Expand Up @@ -73,6 +85,24 @@ Examples:
c.insecureIgnoreTransparencyLog = true
}

if imageFlag != nil && *imageFlag != "" {
// Parse comma-separated patterns
patterns := strings.Split(*imageFlag, ",")
for i, pattern := range patterns {
patterns[i] = strings.TrimSpace(pattern)
}
c.imageFilters = patterns
}

if excludeImageFlag != nil && *excludeImageFlag != "" {
// Parse comma-separated exclude patterns
patterns := strings.Split(*excludeImageFlag, ",")
for i, pattern := range patterns {
patterns[i] = strings.TrimSpace(pattern)
}
c.excludeImageFilters = patterns
}

out := output.NewOutput(flagSet.Output(), output.OutputOpts{Verbose: *verbose})

if err := verifyCosign(); err != nil {
Expand Down
40 changes: 39 additions & 1 deletion cmd/src/sbom_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/http"
"os/exec"
"path"
"path/filepath"
"regexp"
"strings"
"time"
Expand All @@ -26,6 +27,8 @@ type cosignConfig struct {
version string
internalRelease bool
insecureIgnoreTransparencyLog bool
imageFilters []string
excludeImageFilters []string
}

// TokenResponse represents the JSON response from dockerHub's token service
Expand Down Expand Up @@ -251,7 +254,21 @@ func (c cosignConfig) getImageList() ([]string, error) {
if image != "" {
// Strip off a version suffix if present
parts := strings.SplitN(image, ":", 2)
images = append(images, parts[0])
imageName := parts[0]

// If the --image arg was provided, and if the image name doesn't match any of the filters
// then skip this image
if len(c.imageFilters) > 0 && !matchesImageFilter(c.imageFilters, imageName) {
continue
}

// If the --exclude-image arg was provided, and if the image name does match any of the filters
// then skip this image
if len(c.excludeImageFilters) > 0 && matchesImageFilter(c.excludeImageFilters, imageName) {
continue
}

images = append(images, imageName)
}
}

Expand All @@ -270,3 +287,24 @@ func (c *cosignConfig) getImageReleaseListURL() string {
return fmt.Sprintf("%s/release/%s/%s", imageListBaseURL, c.version, imageListFilename)
}
}

// matchesImageFilter checks if the image name from the list of published images
// matches any user-provided --image or --exclude-image glob patterns
// It matches against both the full image name, and the image name without the "sourcegraph/" prefix.
func matchesImageFilter(patterns []string, imageName string) bool {
for _, pattern := range patterns {
// Try matching against the full image name
if matched, _ := filepath.Match(pattern, imageName); matched {
return true
}

// Try matching against the image name without "sourcegraph/" prefix
if strings.HasPrefix(imageName, "sourcegraph/") {
shortName := strings.TrimPrefix(imageName, "sourcegraph/")
if matched, _ := filepath.Match(pattern, shortName); matched {
return true
}
}
}
return false
}
Loading