diff --git a/.gitignore b/.gitignore index d38c4eed344..4cfd0ada7a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ go.work go.work.sum +/.syft.yaml /bin /.bin /build diff --git a/README.md b/README.md index f1e6970e0e7..95d72403918 100644 --- a/README.md +++ b/README.md @@ -681,19 +681,35 @@ registry: # SYFT_REGISTRY_INSECURE_USE_HTTP env var insecure-use-http: false + # filepath to a CA certificate (or directory containing *.crt, *.cert, *.pem) used to generate the client certificate + # SYFT_REGISTRY_CA_CERT env var + ca-cert: "" + # credentials for specific registries auth: # the URL to the registry (e.g. "docker.io", "localhost:5000", etc.) # SYFT_REGISTRY_AUTH_AUTHORITY env var - authority: "" + # SYFT_REGISTRY_AUTH_USERNAME env var username: "" + # SYFT_REGISTRY_AUTH_PASSWORD env var password: "" + # note: token and username/password are mutually exclusive # SYFT_REGISTRY_AUTH_TOKEN env var token: "" - # - ... # note, more credentials can be provided via config file only + + # filepath to the client certificate used for TLS authentication to the registry + # SYFT_REGISTRY_AUTH_TLS_CERT env var + tls-cert: "" + + # filepath to the client key used for TLS authentication to the registry + # SYFT_REGISTRY_AUTH_TLS_KEY env var + tls-key: "" + + # - ... # note, more credentials can be provided via config file only (not env vars) # generate an attested SBOM attest: diff --git a/go.mod b/go.mod index ab4219ff04f..100b1c7fd44 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,48 @@ module github.com/anchore/syft go 1.19 require ( + github.com/CycloneDX/cyclonedx-go v0.7.1 + github.com/Masterminds/semver v1.5.0 + github.com/Masterminds/sprig/v3 v3.2.3 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/acobaugh/osrelease v0.1.0 github.com/adrg/xdg v0.4.0 + github.com/anchore/bubbly v0.0.0-20230801194016-acdb4981b461 + github.com/anchore/clio v0.0.0-20230602170917-e747e60c4aa0 + github.com/anchore/go-logger v0.0.0-20230531193951-db5ae83e7dbe github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 + github.com/anchore/stereoscope v0.0.0-20230829142608-334c2222e137 + // we are hinting brotli to latest due to warning when installing archiver v3: + // go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption + github.com/andybalholm/brotli v1.0.4 // indirect + github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 github.com/bmatcuk/doublestar/v4 v4.6.0 + github.com/charmbracelet/bubbletea v0.24.2 + github.com/charmbracelet/lipgloss v0.8.0 + github.com/dave/jennifer v1.7.0 + github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da + github.com/docker/distribution v2.8.2+incompatible + github.com/docker/docker v24.0.5+incompatible github.com/dustin/go-humanize v1.0.1 github.com/facebookincubator/nvdtools v0.1.5 + github.com/github/go-spdx/v2 v2.1.2 + github.com/gkampitakis/go-snaps v0.4.7 + github.com/go-git/go-billy/v5 v5.4.1 + github.com/go-git/go-git/v5 v5.8.1 github.com/go-test/deep v1.1.0 github.com/google/go-cmp v0.5.9 + github.com/google/go-containerregistry v0.16.1 + github.com/google/licensecheck v0.3.1 github.com/google/uuid v1.3.1 github.com/gookit/color v1.5.4 github.com/hashicorp/go-multierror v1.1.1 + github.com/invopop/jsonschema v0.7.0 github.com/jinzhu/copier v0.4.0 + github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 + github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mholt/archiver/v3 v3.5.1 github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5 @@ -26,7 +52,12 @@ require ( github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/mapstructure v1.5.0 github.com/olekukonko/tablewriter v0.0.5 + github.com/opencontainers/go-digest v1.0.0 github.com/pelletier/go-toml v1.9.5 + github.com/pkg/errors v0.9.1 // indirect + github.com/saferwall/pe v1.4.4 + github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d + github.com/sassoftware/go-rpmutils v0.2.0 // pinned to pull in 386 arch fix: https://github.com/scylladb/go-set/commit/cc7b2070d91ebf40d233207b633e28f5bd8f03a5 github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e github.com/sergi/go-diff v1.3.1 @@ -37,47 +68,18 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 + github.com/vbatts/go-mtree v0.5.3 github.com/vifraa/gopom v1.0.0 github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5 github.com/xeipuuv/gojsonschema v1.2.0 + github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1 + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/exp v0.0.0-20230202163644-54bba9f4231b golang.org/x/mod v0.12.0 golang.org/x/net v0.14.0 golang.org/x/term v0.11.0 gopkg.in/yaml.v2 v2.4.0 -) - -require ( - github.com/CycloneDX/cyclonedx-go v0.7.1 - github.com/Masterminds/semver v1.5.0 - github.com/Masterminds/sprig/v3 v3.2.3 - github.com/anchore/bubbly v0.0.0-20230801194016-acdb4981b461 - github.com/anchore/clio v0.0.0-20230602170917-e747e60c4aa0 - github.com/anchore/go-logger v0.0.0-20230531193951-db5ae83e7dbe - github.com/anchore/stereoscope v0.0.0-20230727211946-d1f3d766295e - github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 - github.com/charmbracelet/bubbletea v0.24.2 - github.com/charmbracelet/lipgloss v0.8.0 - github.com/dave/jennifer v1.7.0 - github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da - github.com/docker/distribution v2.8.2+incompatible - github.com/docker/docker v24.0.5+incompatible - github.com/github/go-spdx/v2 v2.1.2 - github.com/gkampitakis/go-snaps v0.4.7 - github.com/go-git/go-billy/v5 v5.4.1 - github.com/go-git/go-git/v5 v5.8.1 - github.com/google/go-containerregistry v0.16.1 - github.com/google/licensecheck v0.3.1 - github.com/invopop/jsonschema v0.7.0 - github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 - github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b - github.com/opencontainers/go-digest v1.0.0 - github.com/saferwall/pe v1.4.4 - github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d - github.com/sassoftware/go-rpmutils v0.2.0 - github.com/vbatts/go-mtree v0.5.3 - github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1 - golang.org/x/exp v0.0.0-20230202163644-54bba9f4231b gopkg.in/yaml.v3 v3.0.1 modernc.org/sqlite v1.25.0 ) @@ -201,14 +203,6 @@ require ( modernc.org/token v1.0.1 // indirect ) -require ( - // we are hinting brotli to latest due to warning when installing archiver v3: - // go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption - github.com/andybalholm/brotli v1.0.4 // indirect - github.com/pkg/errors v0.9.1 // indirect - golang.org/x/crypto v0.12.0 // indirect -) - retract ( v0.53.2 v0.53.1 // Published accidentally with incorrect license in depdencies diff --git a/go.sum b/go.sum index ae40a3b0f62..172e79e07a8 100644 --- a/go.sum +++ b/go.sum @@ -106,8 +106,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8= github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4= -github.com/anchore/stereoscope v0.0.0-20230727211946-d1f3d766295e h1:S6IhYpsBCpvphlHA1tN0glSG/kjVvFzC6OJuU2qW5Pc= -github.com/anchore/stereoscope v0.0.0-20230727211946-d1f3d766295e/go.mod h1:0LsgHgXO4QFnk2hsYwtqd3fR18PIZXlFLIl2qb9tu3g= +github.com/anchore/stereoscope v0.0.0-20230829142608-334c2222e137 h1:ZuiAV3lYKbGPkZR42UpoKecp/9aFU38CZSjaxjXCMYc= +github.com/anchore/stereoscope v0.0.0-20230829142608-334c2222e137/go.mod h1:jkylrnuv++srUa2rsqCnG0n1ShD+0k2Xa6YtoNoRjAY= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= diff --git a/internal/config/registry.go b/internal/config/registry.go index 7e7e7132bbb..6b0e5dc9c6b 100644 --- a/internal/config/registry.go +++ b/internal/config/registry.go @@ -16,30 +16,37 @@ type RegistryCredentials struct { Password string `yaml:"-" json:"-" mapstructure:"password"` // IMPORTANT: do not show the token in any YAML/JSON output (sensitive information) Token string `yaml:"-" json:"-" mapstructure:"token"` + + TLSCert string `yaml:"tls-cert,omitempty" json:"tls-cert,omitempty" mapstructure:"tls-cert"` + TLSKey string `yaml:"tls-key,omitempty" json:"tls-key,omitempty" mapstructure:"tls-key"` } type registry struct { InsecureSkipTLSVerify bool `yaml:"insecure-skip-tls-verify" json:"insecure-skip-tls-verify" mapstructure:"insecure-skip-tls-verify"` InsecureUseHTTP bool `yaml:"insecure-use-http" json:"insecure-use-http" mapstructure:"insecure-use-http"` Auth []RegistryCredentials `yaml:"auth" json:"auth" mapstructure:"auth"` + CACert string `yaml:"ca-cert" json:"ca-cert" mapstructure:"ca-cert"` } func (cfg registry) loadDefaultValues(v *viper.Viper) { v.SetDefault("registry.insecure-skip-tls-verify", false) v.SetDefault("registry.insecure-use-http", false) v.SetDefault("registry.auth", []RegistryCredentials{}) + v.SetDefault("registry.ca-cert", "") } //nolint:unparam func (cfg *registry) parseConfigValues() error { // there may be additional credentials provided by env var that should be appended to the set of credentials - authority, username, password, token := + authority, username, password, token, tlsCert, tlsKey := os.Getenv("SYFT_REGISTRY_AUTH_AUTHORITY"), os.Getenv("SYFT_REGISTRY_AUTH_USERNAME"), os.Getenv("SYFT_REGISTRY_AUTH_PASSWORD"), - os.Getenv("SYFT_REGISTRY_AUTH_TOKEN") + os.Getenv("SYFT_REGISTRY_AUTH_TOKEN"), + os.Getenv("SYFT_REGISTRY_AUTH_TLS_CERT"), + os.Getenv("SYFT_REGISTRY_AUTH_TLS_KEY") - if hasNonEmptyCredentials(username, password, token) { + if hasNonEmptyCredentials(username, password, token, tlsCert, tlsKey) { // note: we prepend the credentials such that the environment variables take precedence over on-disk configuration. cfg.Auth = append([]RegistryCredentials{ { @@ -47,29 +54,38 @@ func (cfg *registry) parseConfigValues() error { Username: username, Password: password, Token: token, + TLSCert: tlsCert, + TLSKey: tlsKey, }, }, cfg.Auth...) } return nil } -func hasNonEmptyCredentials(username, password, token string) bool { - return password != "" && username != "" || token != "" +func hasNonEmptyCredentials(username, password, token, tlsCert, tlsKey string) bool { + hasUserPass := username != "" && password != "" + hasToken := token != "" + hasTLSMaterial := tlsCert != "" && tlsKey != "" + return hasUserPass || hasToken || hasTLSMaterial } func (cfg *registry) ToOptions() *image.RegistryOptions { var auth = make([]image.RegistryCredentials, len(cfg.Auth)) for i, a := range cfg.Auth { auth[i] = image.RegistryCredentials{ - Authority: a.Authority, - Username: a.Username, - Password: a.Password, - Token: a.Token, + Authority: a.Authority, + Username: a.Username, + Password: a.Password, + Token: a.Token, + ClientCert: a.TLSCert, + ClientKey: a.TLSKey, } } + return &image.RegistryOptions{ InsecureSkipTLSVerify: cfg.InsecureSkipTLSVerify, InsecureUseHTTP: cfg.InsecureUseHTTP, Credentials: auth, + CAFileOrDir: cfg.CACert, } } diff --git a/internal/config/registry_test.go b/internal/config/registry_test.go index c98511c1e28..034384e6161 100644 --- a/internal/config/registry_test.go +++ b/internal/config/registry_test.go @@ -11,48 +11,60 @@ import ( func TestHasNonEmptyCredentials(t *testing.T) { tests := []struct { - username, password, token string - expected bool + username, password, token, cert, key string + expected bool }{ { - "", "", "", + "", "", "", "", "", false, }, { - "user", "", "", + "user", "", "", "", "", false, }, { - "", "pass", "", + "", "pass", "", "", "", false, }, { - "", "pass", "tok", + "", "pass", "tok", "", "", true, }, { - "user", "", "tok", + "user", "", "tok", "", "", true, }, { - "", "", "tok", + "", "", "tok", "", "", true, }, { - "user", "pass", "tok", + "user", "pass", "tok", "", "", true, }, { - "user", "pass", "", + "user", "pass", "", "", "", true, }, + { + "", "", "", "cert", "key", + true, + }, + { + "", "", "", "cert", "", + false, + }, + { + "", "", "", "", "key", + false, + }, } for _, test := range tests { t.Run(fmt.Sprintf("%+v", test), func(t *testing.T) { - assert.Equal(t, test.expected, hasNonEmptyCredentials(test.username, test.password, test.token)) + assert.Equal(t, test.expected, hasNonEmptyCredentials(test.username, test.password, test.token, test.cert, test.key)) }) } } @@ -102,6 +114,29 @@ func Test_registry_ToOptions(t *testing.T) { Credentials: []image.RegistryCredentials{}, }, }, + { + name: "provide all tls configuration", + input: registry{ + CACert: "ca.crt", + InsecureSkipTLSVerify: true, + Auth: []RegistryCredentials{ + { + TLSCert: "client.crt", + TLSKey: "client.key", + }, + }, + }, + expected: image.RegistryOptions{ + CAFileOrDir: "ca.crt", + InsecureSkipTLSVerify: true, + Credentials: []image.RegistryCredentials{ + { + ClientCert: "client.crt", + ClientKey: "client.key", + }, + }, + }, + }, } for _, test := range tests { diff --git a/test/cli/packages_cmd_test.go b/test/cli/packages_cmd_test.go index 41bc2bcc074..e9b87ae4894 100644 --- a/test/cli/packages_cmd_test.go +++ b/test/cli/packages_cmd_test.go @@ -258,7 +258,7 @@ func TestPackagesCmdFlags(t *testing.T) { func TestRegistryAuth(t *testing.T) { host := "localhost:17" image := fmt.Sprintf("%s/something:latest", host) - args := []string{"packages", "-vv", fmt.Sprintf("registry:%s", image)} + args := []string{"packages", "-vvv", fmt.Sprintf("registry:%s", image)} tests := []struct { name string @@ -272,7 +272,7 @@ func TestRegistryAuth(t *testing.T) { assertions: []traitAssertion{ assertInOutput("source=OciRegistry"), assertInOutput(image), - assertInOutput("no registry credentials configured, using the default keychain"), + assertInOutput(fmt.Sprintf("no registry credentials configured for %q, using the default keychain", host)), }, }, { @@ -311,7 +311,7 @@ func TestRegistryAuth(t *testing.T) { assertions: []traitAssertion{ assertInOutput("source=OciRegistry"), assertInOutput(image), - assertInOutput(`no registry credentials configured, using the default keychain`), + assertInOutput(fmt.Sprintf(`no registry credentials configured for %q, using the default keychain`, host)), }, }, { @@ -324,6 +324,17 @@ func TestRegistryAuth(t *testing.T) { assertInOutput("insecure-use-http: true"), }, }, + { + name: "use tls configuration", + args: args, + env: map[string]string{ + "SYFT_REGISTRY_AUTH_TLS_CERT": "place.crt", + "SYFT_REGISTRY_AUTH_TLS_KEY": "place.key", + }, + assertions: []traitAssertion{ + assertInOutput("using custom TLS credentials from"), + }, + }, } for _, test := range tests {