mirror of
https://github.com/anchore/grype
synced 2024-11-10 06:34:13 +00:00
Add registry certificate verification support (#1232)
* add registry certificate verification support * modify go.mod * rename registry cert options, add docs, and add test Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update to account for changes in anchore/stereoscope#195 Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix cli tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: lishituo <24578666@qq.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
0d5be962d3
commit
bf84e2fa7f
7 changed files with 125 additions and 46 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,5 +1,6 @@
|
|||
/go.work
|
||||
/go.work.sum
|
||||
/.grype.yaml
|
||||
|
||||
CHANGELOG.md
|
||||
VERSION
|
||||
|
|
32
README.md
32
README.md
|
@ -651,23 +651,41 @@ registry:
|
|||
# skip TLS verification when communicating with the registry
|
||||
# same as GRYPE_REGISTRY_INSECURE_SKIP_TLS_VERIFY env var
|
||||
insecure-skip-tls-verify: false
|
||||
|
||||
# use http instead of https when connecting to the registry
|
||||
# same as GRYPE_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
|
||||
# GRYPE_REGISTRY_CA_CERT env var
|
||||
ca-cert: ""
|
||||
|
||||
# credentials for specific registries
|
||||
auth:
|
||||
- # the URL to the registry (e.g. "docker.io", "localhost:5000", etc.)
|
||||
# same as GRYPE_REGISTRY_AUTH_AUTHORITY env var
|
||||
authority: ""
|
||||
# same as GRYPE_REGISTRY_AUTH_USERNAME env var
|
||||
# the URL to the registry (e.g. "docker.io", "localhost:5000", etc.)
|
||||
# GRYPE_REGISTRY_AUTH_AUTHORITY env var
|
||||
- authority: ""
|
||||
|
||||
# GRYPE_REGISTRY_AUTH_USERNAME env var
|
||||
username: ""
|
||||
# same as GRYPE_REGISTRY_AUTH_PASSWORD env var
|
||||
|
||||
# GRYPE_REGISTRY_AUTH_PASSWORD env var
|
||||
password: ""
|
||||
|
||||
# note: token and username/password are mutually exclusive
|
||||
# same as GRYPE_REGISTRY_AUTH_TOKEN env var
|
||||
# GRYPE_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
|
||||
# GRYPE_REGISTRY_AUTH_TLS_CERT env var
|
||||
tls-cert: ""
|
||||
|
||||
# filepath to the client key used for TLS authentication to the registry
|
||||
# GRYPE_REGISTRY_AUTH_TLS_KEY env var
|
||||
tls-key: ""
|
||||
|
||||
# - ... # note, more credentials can be provided via config file only (not env vars)
|
||||
|
||||
|
||||
log:
|
||||
# use structured logging
|
||||
|
|
27
go.mod
27
go.mod
|
@ -7,19 +7,28 @@ require (
|
|||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/adrg/xdg v0.4.0
|
||||
github.com/anchore/bubbly v0.0.0-20230801194016-acdb4981b461
|
||||
github.com/anchore/clio v0.0.0-20230630162005-9535e9dc2817
|
||||
github.com/anchore/go-logger v0.0.0-20230531193951-db5ae83e7dbe
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501
|
||||
github.com/anchore/stereoscope v0.0.0-20230727211946-d1f3d766295e
|
||||
github.com/anchore/sqlite v1.4.6-0.20220607210448-bcc6ee5c4963
|
||||
github.com/anchore/stereoscope v0.0.0-20230829142608-334c2222e137
|
||||
github.com/anchore/syft v0.88.0
|
||||
github.com/bmatcuk/doublestar/v2 v2.0.4
|
||||
github.com/charmbracelet/bubbletea v0.24.2
|
||||
github.com/charmbracelet/lipgloss v0.8.0
|
||||
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/gabriel-vasile/mimetype v1.4.2
|
||||
github.com/gkampitakis/go-snaps v0.4.8
|
||||
github.com/go-test/deep v1.1.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/gookit/color v1.5.4
|
||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2
|
||||
github.com/hashicorp/go-getter v1.7.2
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
|
@ -29,6 +38,7 @@ require (
|
|||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/owenrumney/go-sarif v1.1.1
|
||||
github.com/pkg/profile v1.7.0
|
||||
|
@ -42,6 +52,7 @@ require (
|
|||
github.com/spf13/viper v1.16.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651
|
||||
github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b
|
||||
github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2
|
||||
golang.org/x/term v0.11.0
|
||||
|
@ -49,20 +60,6 @@ require (
|
|||
gorm.io/gorm v1.23.10
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/anchore/bubbly v0.0.0-20230801194016-acdb4981b461
|
||||
github.com/anchore/clio v0.0.0-20230630162005-9535e9dc2817
|
||||
github.com/anchore/go-logger v0.0.0-20230531193951-db5ae83e7dbe
|
||||
github.com/anchore/sqlite v1.4.6-0.20220607210448-bcc6ee5c4963
|
||||
github.com/anchore/syft v0.88.0
|
||||
github.com/charmbracelet/bubbletea v0.24.2
|
||||
github.com/charmbracelet/lipgloss v0.8.0
|
||||
github.com/gkampitakis/go-snaps v0.4.8
|
||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.110.0 // indirect
|
||||
cloud.google.com/go/compute v1.19.3 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -249,8 +249,8 @@ github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwM
|
|||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
|
||||
github.com/anchore/sqlite v1.4.6-0.20220607210448-bcc6ee5c4963 h1:vrf2PYH77vqVJoNR15ZuFJ63qwBMqrmGIt/7VsBhLF8=
|
||||
github.com/anchore/sqlite v1.4.6-0.20220607210448-bcc6ee5c4963/go.mod h1:AVRyXOUP0hTz9Cb8OlD1XnwA8t4lBPfTuwPHmEUuiLc=
|
||||
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/anchore/syft v0.88.0 h1:QRPcXwbQnxcOIfSZ5Sd6psfVQ756VICvx/HUMsIJEBw=
|
||||
github.com/anchore/syft v0.88.0/go.mod h1:6GgbZflKWC7ph2Zjb5wgq0ORKhwDaHG3xcjG84FSMPo=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
|
|
|
@ -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("GRYPE_REGISTRY_AUTH_AUTHORITY"),
|
||||
os.Getenv("GRYPE_REGISTRY_AUTH_USERNAME"),
|
||||
os.Getenv("GRYPE_REGISTRY_AUTH_PASSWORD"),
|
||||
os.Getenv("GRYPE_REGISTRY_AUTH_TOKEN")
|
||||
os.Getenv("GRYPE_REGISTRY_AUTH_TOKEN"),
|
||||
os.Getenv("GRYPE_REGISTRY_AUTH_TLS_CERT"),
|
||||
os.Getenv("GRYPE_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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,47 +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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -101,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 {
|
||||
|
|
|
@ -18,7 +18,7 @@ func TestRegistryAuth(t *testing.T) {
|
|||
assertions: []traitAssertion{
|
||||
assertInOutput("source=OciRegistry"),
|
||||
assertInOutput("localhost:5000/something:latest"),
|
||||
assertInOutput("no registry credentials configured, using the default keychain"),
|
||||
assertInOutput(`no registry credentials configured for "localhost:5000", using the default keychain`),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -57,7 +57,7 @@ func TestRegistryAuth(t *testing.T) {
|
|||
assertions: []traitAssertion{
|
||||
assertInOutput("source=OciRegistry"),
|
||||
assertInOutput("localhost:5000/something:latest"),
|
||||
assertInOutput(`no registry credentials configured, using the default keychain`),
|
||||
assertInOutput(`no registry credentials configured for "localhost:5000", using the default keychain`),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -70,6 +70,17 @@ func TestRegistryAuth(t *testing.T) {
|
|||
assertInOutput("insecure-use-http: true"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "use tls configuration",
|
||||
args: []string{"-vvv", "registry:localhost:5000/something:latest"},
|
||||
env: map[string]string{
|
||||
"GRYPE_REGISTRY_AUTH_TLS_CERT": "place.crt",
|
||||
"GRYPE_REGISTRY_AUTH_TLS_KEY": "place.key",
|
||||
},
|
||||
assertions: []traitAssertion{
|
||||
assertInOutput("using custom TLS credentials from"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
|
Loading…
Reference in a new issue