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: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ go 1.26.7

require (
github.com/BurntSushi/toml v1.6.0
github.com/CycloneDX/cyclonedx-go v0.12.0
github.com/Masterminds/semver/v3 v3.5.0
github.com/aws/aws-sdk-go-v2/config v1.33.7
github.com/aws/aws-sdk-go-v2/service/ecr v1.66.2
Expand All @@ -17,6 +16,7 @@ require (
github.com/git-pkgs/magic v0.5.0
github.com/git-pkgs/purl v0.1.21
github.com/git-pkgs/registries v0.10.0
github.com/git-pkgs/sbom v0.1.7
github.com/git-pkgs/spdx v0.3.3
github.com/git-pkgs/vers v0.7.2
github.com/git-pkgs/vulns v0.2.4
Expand All @@ -26,7 +26,6 @@ require (
github.com/opencontainers/go-digest v1.0.0
github.com/prometheus/client_golang v1.25.0
github.com/prometheus/client_model v0.6.3
github.com/spdx/tools-golang v0.5.7
github.com/swaggo/swag v1.16.6
gocloud.dev v0.46.0
golang.org/x/mod v0.41.0
Expand All @@ -49,7 +48,6 @@ require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/anchore/go-struct-converter v0.1.0 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.47.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.11 // indirect
Expand Down
22 changes: 2 additions & 20 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 h1:4iB+IesclUX
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/CycloneDX/cyclonedx-go v0.12.0 h1:/7Jum36UA6V043tQZ/fE3jf+Nf9gn/qxUFfd7QReMy8=
github.com/CycloneDX/cyclonedx-go v0.12.0/go.mod h1:V2577HhxDDCDLYfkm55WJrz16nHTfyQZwcWUBSG7Z28=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0 h1:l7+6kwRMJNwdCvYdDl7Eax+wzEYHSnNY7zrrfbhDdTA=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
Expand All @@ -52,8 +50,6 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/anchore/go-struct-converter v0.1.0 h1:2rDRssAl6mgKBSLNiVCMADgZRhoqtw9dedlWa0OhD30=
github.com/anchore/go-struct-converter v0.1.0/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/aws/aws-sdk-go-v2 v1.47.1 h1:uOIZnp4PK3ZhKI0dNrJrhTEsLxbpXHTAJlwoS1pvAtw=
Expand Down Expand Up @@ -99,8 +95,6 @@ github.com/aws/smithy-go v1.28.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqx
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
github.com/cenk/backoff v2.2.1+incompatible h1:djdFT7f4gF2ttuzRKPbMOWgZajgesItGLwG5FTQKmmE=
github.com/cenk/backoff v2.2.1+incompatible/go.mod h1:7FtoeaSnHoZnmZzz47cM35Y9nSW7tNyaidugnHTaFDE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
Expand All @@ -110,8 +104,6 @@ github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7X
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ecosyste-ms/ecosystems-go v0.5.0 h1:qZqgar3Do9RrtW327OXlsrGKiRvNdeX8CmXb1aX4Njo=
Expand Down Expand Up @@ -145,6 +137,8 @@ github.com/git-pkgs/purl v0.1.21 h1:PDZUX43lxIiXPn2eHcsNiiiwQo+HajntZ/4TwSvCGlw=
github.com/git-pkgs/purl v0.1.21/go.mod h1:4dOmris48ehjiKmWBqSmMHguUQht48BLeXW+hQUnuj0=
github.com/git-pkgs/registries v0.10.0 h1:0goaC5mxw9MglS/XOm2De7fkPBWCCerelm7RPrPh9hQ=
github.com/git-pkgs/registries v0.10.0/go.mod h1:bKxgdy/PwnzQXL2FnOktmSQHTKk6B85MA4VtvvYItPc=
github.com/git-pkgs/sbom v0.1.7 h1:4eboy/Yed/SC9ZcC/xT50iywHPzWbmCU7azDb5T1RbA=
github.com/git-pkgs/sbom v0.1.7/go.mod h1:KIkZX0v31l62SeFsi2qsQXcZRVrAXvIdPQ4x4en+G2M=
github.com/git-pkgs/spdx v0.3.3 h1:xXlUoZzjDSkwxC6mWHfaW+kc8a0+LH6d+gGHuZWftqI=
github.com/git-pkgs/spdx v0.3.3/go.mod h1:n9rAicgw+Wqtfursju6oKaAL6cUCTiIS926mb1KWoKU=
github.com/git-pkgs/vers v0.7.2 h1:Ctex1O+Ai/FONmyDaw37EolBXg0og6UdxbCjjYbeRHs=
Expand Down Expand Up @@ -253,8 +247,6 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjL
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.25.0 h1:cDtW1TcFsMi1RJc8khwCKB4VhzwV04aKqbYtNOXuiK0=
github.com/prometheus/client_golang v1.25.0/go.mod h1:j2X/4/kaQHLPnmyEU8zjmkndo1T317VpCIQEml8LTKk=
github.com/prometheus/client_model v0.6.3 h1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPTB1qyxiEo=
Expand All @@ -271,8 +263,6 @@ github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 h1:18kd+8ZUlt/ARXhljq+
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
github.com/rubyist/circuitbreaker v2.2.1+incompatible h1:KUKd/pV8Geg77+8LNDwdow6rVCAYOp8+kHUyFvL6Mhk=
github.com/rubyist/circuitbreaker v2.2.1+incompatible/go.mod h1:Ycs3JgJADPuzJDwffe12k6BZT8hxVi6lFK+gWYJLN4A=
github.com/spdx/tools-golang v0.5.7 h1:+sWcKGnhwp3vLdMqPcLdA6QK679vd86cK9hQWH3AwCg=
github.com/spdx/tools-golang v0.5.7/go.mod h1:jg7w0LOpoNAw6OxKEzCoqPC2GCTj45LyTlVmXubDsYw=
github.com/spiffe/go-spiffe/v2 v2.7.0 h1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4=
github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
Expand All @@ -283,16 +273,8 @@ github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWD
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
github.com/terminalstatic/go-xsd-validate v0.1.8 h1:UVrTCy1j3DhwaYTTUF+QYO/Nan13S0tf+Jwi+p45Bf0=
github.com/terminalstatic/go-xsd-validate v0.1.8/go.mod h1:1kb47fi2c6onlf+B7UrrQ9VYraOhcYwFm3iG+J6F4Zo=
github.com/ulikunitz/xz v0.5.16 h1:ld6NyySjx5lowVKwJvMRLnW5nxKX/xnpSiFYZ/Lxur0=
github.com/ulikunitz/xz v0.5.16/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/detectors/gcp v1.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw=
Expand Down
78 changes: 9 additions & 69 deletions internal/mirror/source.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
package mirror

import (
"bytes"
"context"
"encoding/json"
"fmt"

cdx "github.com/CycloneDX/cyclonedx-go"
"github.com/git-pkgs/purl"
"github.com/git-pkgs/registries"
_ "github.com/git-pkgs/registries/all"
spdxjson "github.com/spdx/tools-golang/json"
"github.com/spdx/tools-golang/spdx"
spdxtv "github.com/spdx/tools-golang/tagvalue"
"github.com/git-pkgs/sbom"
)

// PackageVersion identifies a specific package version to mirror.
Expand Down Expand Up @@ -130,75 +125,20 @@ func (s *SBOMSource) Enumerate(ctx context.Context, fn func(PackageVersion) erro
}

func (s *SBOMSource) extractPURLs() ([]string, error) {
// Try CycloneDX first
if purls, err := extractCycloneDXPURLs(s.Data); err == nil && len(purls) > 0 {
return purls, nil
}

// Try SPDX JSON
if purls, err := extractSPDXJSONPURLs(s.Data); err == nil && len(purls) > 0 {
return purls, nil
}

// Try SPDX tag-value
if purls, err := extractSPDXTVPURLs(s.Data); err == nil && len(purls) > 0 {
return purls, nil
}

return nil, fmt.Errorf("could not parse SBOM as CycloneDX or SPDX")
}

func extractCycloneDXPURLs(data []byte) ([]string, error) {
bom := new(cdx.BOM)
if err := json.Unmarshal(data, bom); err != nil {
// Try XML
decoder := cdx.NewBOMDecoder(bytes.NewReader(data), cdx.BOMFileFormatXML)
bom = new(cdx.BOM)
if err := decoder.Decode(bom); err != nil {
return nil, err
}
}

if bom.Components == nil {
return nil, nil
}

var purls []string
for _, c := range *bom.Components {
if c.PackageURL != "" {
purls = append(purls, c.PackageURL)
}
}
return purls, nil
}

func extractSPDXJSONPURLs(data []byte) ([]string, error) {
doc, err := spdxjson.Read(bytes.NewReader(data))
if err != nil {
return nil, err
}
return extractSPDXDocPURLs(doc), nil
}

func extractSPDXTVPURLs(data []byte) ([]string, error) {
doc, err := spdxtv.Read(bytes.NewReader(data))
doc, err := sbom.Parse(s.Data)
if err != nil {
return nil, err
}
return extractSPDXDocPURLs(doc), nil
}

func extractSPDXDocPURLs(doc *spdx.Document) []string {
if doc == nil {
return nil
}
var purls []string
for _, pkg := range doc.Packages {
for _, ref := range pkg.PackageExternalReferences {
if ref.RefType == "purl" {
for _, p := range doc.Packages {
for _, ref := range p.ExternalRefs {
if ref.Type == "purl" && ref.Locator != "" {
purls = append(purls, ref.Locator)
}
}
}
return purls
if len(purls) == 0 {
return nil, fmt.Errorf("SBOM contains no package URLs")
}
return purls, nil
}
74 changes: 74 additions & 0 deletions internal/mirror/source_formats_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package mirror

import (
"context"
"reflect"
"testing"
)

func TestSBOMSourcePreservesPackageIdentities(t *testing.T) {
for name, data := range map[string]string{
"CycloneDX JSON": `{"components":[
{"name":"shared","version":"1.0","purl":"pkg:npm/%40alpha/shared@1.0"},
{"name":"shared","version":"1.0","purl":"pkg:npm/%40beta/shared@1.0"}]}`,
"CycloneDX XML": `<bom xmlns="http://cyclonedx.org/schema/bom/1.6"><components>
<component type="library"><name>shared</name><version>1.0</version><purl>pkg:npm/%40alpha/shared@1.0</purl></component>
<component type="library"><name>shared</name><version>1.0</version><purl>pkg:npm/%40beta/shared@1.0</purl></component>
</components></bom>`,
"SPDX JSON": `{"spdxVersion":"SPDX-2.3","packages":[{"name":"shared","versionInfo":"1.0","externalRefs":[
{"referenceType":"purl","referenceLocator":"pkg:npm/%40alpha/shared@1.0"},
{"referenceType":"purl","referenceLocator":"pkg:npm/%40beta/shared@1.0"}]}]}`,
"SPDX tag-value": `DataLicense: CC0-1.0
SPDXVersion: SPDX-2.3
SPDXID: SPDXRef-DOCUMENT
DocumentName: example
DocumentNamespace: https://example.com/sbom
PackageName: shared
SPDXID: SPDXRef-shared
PackageVersion: 1.0
ExternalRef: PACKAGE-MANAGER purl pkg:npm/%40alpha/shared@1.0
ExternalRef: PACKAGE-MANAGER purl pkg:npm/%40beta/shared@1.0
`,
} {
t.Run(name, func(t *testing.T) {
source := &SBOMSource{Data: []byte(data), Name: name}
var got []PackageVersion
err := source.Enumerate(context.Background(), func(pv PackageVersion) error {
got = append(got, pv)
return nil
})
if err != nil {
t.Fatal(err)
}
want := []PackageVersion{
{Ecosystem: "npm", Name: "@alpha/shared", Version: "1.0"},
{Ecosystem: "npm", Name: "@beta/shared", Version: "1.0"},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("Enumerate = %+v, want %+v", got, want)
}
})
}
}

func TestSBOMSourcePreservesImageOptions(t *testing.T) {
source := &SBOMSource{
Data: []byte(`{"bomFormat":"CycloneDX","components":[{"name":"alpine","purl":"pkg:docker/library/alpine@3.20?repository_url=ghcr.io&arch=arm64"}]}`),
Platforms: []string{"linux/amd64"},
}
var got []PackageVersion
if err := source.Enumerate(context.Background(), func(pv PackageVersion) error {
got = append(got, pv)
return nil
}); err != nil {
t.Fatal(err)
}
want := []PackageVersion{{
Ecosystem: "docker", Name: "library/alpine", Version: "3.20",
Qualifiers: map[string]string{"repository_url": "ghcr.io", "arch": "arm64"},
Platforms: []string{"linux/amd64"},
}}
if !reflect.DeepEqual(got, want) {
t.Errorf("Enumerate = %+v, want %+v", got, want)
}
}
2 changes: 0 additions & 2 deletions internal/mirror/source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,6 @@ func TestSBOMSourceEmptyCycloneDX(t *testing.T) {
"bomFormat": "CycloneDX",
"specVersion": "1.4",
}
// This should fall through to SPDX parsing, which will also fail,
// resulting in an error about not being able to parse
source := &SBOMSource{Data: marshalJSON(t, bom)}
err := source.Enumerate(context.Background(), func(pv PackageVersion) error {
return nil
Expand Down
Loading