Skip to content

Commit 9b31697

Browse files
authored
feat(image): Set User-Agent header for Trivy container registry requests (#6868)
Signed-off-by: m.nabokikh <[email protected]>
1 parent 089b953 commit 9b31697

File tree

11 files changed

+100
-29
lines changed

11 files changed

+100
-29
lines changed

goreleaser-canary.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ builds:
66
ldflags:
77
- -s -w
88
- "-extldflags '-static'"
9-
- -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}}
9+
- -X github.com/aquasecurity/trivy/pkg/version/app.ver={{.Version}}
1010
env:
1111
- CGO_ENABLED=0
1212
goos:

goreleaser.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ builds:
66
ldflags:
77
- -s -w
88
- "-extldflags '-static'"
9-
- -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}}
9+
- -X github.com/aquasecurity/trivy/pkg/version/app.ver={{.Version}}
1010
env:
1111
- CGO_ENABLED=0
1212
goos:
@@ -26,7 +26,7 @@ builds:
2626
ldflags:
2727
- -s -w
2828
- "-extldflags '-static'"
29-
- -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}}
29+
- -X github.com/aquasecurity/trivy/pkg/version/app.ver={{.Version}}
3030
env:
3131
- CGO_ENABLED=0
3232
goos:
@@ -41,7 +41,7 @@ builds:
4141
ldflags:
4242
- -s -w
4343
- "-extldflags '-static'"
44-
- -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}}
44+
- -X github.com/aquasecurity/trivy/pkg/version/app.ver={{.Version}}
4545
env:
4646
- CGO_ENABLED=0
4747
goos:
@@ -57,7 +57,7 @@ builds:
5757
ldflags:
5858
- -s -w
5959
- "-extldflags '-static'"
60-
- -X github.com/aquasecurity/trivy/pkg/version.ver={{.Version}}
60+
- -X github.com/aquasecurity/trivy/pkg/version/app.ver={{.Version}}
6161
env:
6262
- CGO_ENABLED=0
6363
goos:

magefiles/magefile.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func buildLdflags() (string, error) {
4848
if err != nil {
4949
return "", err
5050
}
51-
return fmt.Sprintf("-s -w -X=github.com/aquasecurity/trivy/pkg/version.ver=%s", ver), nil
51+
return fmt.Sprintf("-s -w -X=github.com/aquasecurity/trivy/pkg/version/app.ver=%s", ver), nil
5252
}
5353

5454
type Tool mg.Namespace

pkg/commands/app.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/aquasecurity/trivy/pkg/plugin"
2929
"github.com/aquasecurity/trivy/pkg/types"
3030
"github.com/aquasecurity/trivy/pkg/version"
31+
"github.com/aquasecurity/trivy/pkg/version/app"
3132
xstrings "github.com/aquasecurity/trivy/pkg/x/strings"
3233
)
3334

@@ -178,7 +179,7 @@ func NewRootCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
178179
Args: cobra.NoArgs,
179180
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
180181
// Set the Trivy version here so that we can override version printer.
181-
cmd.Version = version.AppVersion()
182+
cmd.Version = app.Version()
182183

183184
// viper.BindPFlag cannot be called in init().
184185
// cf. https://github.com/spf13/cobra/issues/875

pkg/dependency/parser/golang/binary/parse.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ func isValidSemVer(ver string) bool {
217217

218218
// versionPrefix returns version prefix from `-ldflags` flag key
219219
// e.g.
220-
// - `github.com/aquasecurity/trivy/pkg/version.version` => `version`
220+
// - `github.com/aquasecurity/trivy/pkg/version/app.ver` => `version`
221221
// - `github.com/google/go-containerregistry/cmd/crane/common.ver` => `common`
222222
func versionPrefix(s string) string {
223223
// Trim module part.

pkg/dependency/parser/golang/binary/parse_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func TestParser_ParseLDFlags(t *testing.T) {
168168
"-s",
169169
"-w",
170170
"-X=foo=bar",
171-
"-X='github.com/aquasecurity/trivy/pkg/version.version=v0.50.1'",
171+
"-X='github.com/aquasecurity/trivy/pkg/version/app.ver=v0.50.1'",
172172
},
173173
},
174174
want: "v0.50.1",
@@ -194,7 +194,7 @@ func TestParser_ParseLDFlags(t *testing.T) {
194194
"-s",
195195
"-w",
196196
"-X=foo=bar",
197-
"-X='github.com/aquasecurity/trivy/pkg/version.ver=v0.50.1'",
197+
"-X='github.com/aquasecurity/trivy/pkg/version/app.ver=v0.50.1'",
198198
},
199199
},
200200
want: "v0.50.1",

pkg/flag/options.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
"github.com/aquasecurity/trivy/pkg/plugin"
2424
"github.com/aquasecurity/trivy/pkg/result"
2525
"github.com/aquasecurity/trivy/pkg/types"
26-
"github.com/aquasecurity/trivy/pkg/version"
26+
"github.com/aquasecurity/trivy/pkg/version/app"
2727
)
2828

2929
type FlagType interface {
@@ -602,7 +602,7 @@ func (f *Flags) Bind(cmd *cobra.Command) error {
602602
func (f *Flags) ToOptions(args []string) (Options, error) {
603603
var err error
604604
opts := Options{
605-
AppVersion: version.AppVersion(),
605+
AppVersion: app.Version(),
606606
}
607607

608608
if f.GlobalFlagGroup != nil {

pkg/remote/remote.go

+12-8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package remote
33
import (
44
"context"
55
"crypto/tls"
6+
"fmt"
67
"net"
78
"net/http"
89
"time"
@@ -11,6 +12,7 @@ import (
1112
"github.com/google/go-containerregistry/pkg/name"
1213
v1 "github.com/google/go-containerregistry/pkg/v1"
1314
"github.com/google/go-containerregistry/pkg/v1/remote"
15+
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
1416
v1types "github.com/google/go-containerregistry/pkg/v1/types"
1517
"github.com/hashicorp/go-multierror"
1618
"github.com/samber/lo"
@@ -19,14 +21,15 @@ import (
1921
"github.com/aquasecurity/trivy/pkg/fanal/image/registry"
2022
"github.com/aquasecurity/trivy/pkg/fanal/types"
2123
"github.com/aquasecurity/trivy/pkg/log"
24+
"github.com/aquasecurity/trivy/pkg/version/app"
2225
)
2326

2427
type Descriptor = remote.Descriptor
2528

2629
// Get is a wrapper of google/go-containerregistry/pkg/v1/remote.Get
2730
// so that it can try multiple authentication methods.
2831
func Get(ctx context.Context, ref name.Reference, option types.RegistryOptions) (*Descriptor, error) {
29-
transport, err := httpTransport(option)
32+
tr, err := httpTransport(option)
3033
if err != nil {
3134
return nil, xerrors.Errorf("failed to create http transport: %w", err)
3235
}
@@ -35,7 +38,7 @@ func Get(ctx context.Context, ref name.Reference, option types.RegistryOptions)
3538
// Try each authentication method until it succeeds
3639
for _, authOpt := range authOptions(ctx, ref, option) {
3740
remoteOpts := []remote.Option{
38-
remote.WithTransport(transport),
41+
remote.WithTransport(tr),
3942
authOpt,
4043
}
4144

@@ -71,7 +74,7 @@ func Get(ctx context.Context, ref name.Reference, option types.RegistryOptions)
7174
// Image is a wrapper of google/go-containerregistry/pkg/v1/remote.Image
7275
// so that it can try multiple authentication methods.
7376
func Image(ctx context.Context, ref name.Reference, option types.RegistryOptions) (v1.Image, error) {
74-
transport, err := httpTransport(option)
77+
tr, err := httpTransport(option)
7578
if err != nil {
7679
return nil, xerrors.Errorf("failed to create http transport: %w", err)
7780
}
@@ -80,7 +83,7 @@ func Image(ctx context.Context, ref name.Reference, option types.RegistryOptions
8083
// Try each authentication method until it succeeds
8184
for _, authOpt := range authOptions(ctx, ref, option) {
8285
remoteOpts := []remote.Option{
83-
remote.WithTransport(transport),
86+
remote.WithTransport(tr),
8487
authOpt,
8588
}
8689
index, err := remote.Image(ref, remoteOpts...)
@@ -98,7 +101,7 @@ func Image(ctx context.Context, ref name.Reference, option types.RegistryOptions
98101
// Referrers is a wrapper of google/go-containerregistry/pkg/v1/remote.Referrers
99102
// so that it can try multiple authentication methods.
100103
func Referrers(ctx context.Context, d name.Digest, option types.RegistryOptions) (v1.ImageIndex, error) {
101-
transport, err := httpTransport(option)
104+
tr, err := httpTransport(option)
102105
if err != nil {
103106
return nil, xerrors.Errorf("failed to create http transport: %w", err)
104107
}
@@ -107,7 +110,7 @@ func Referrers(ctx context.Context, d name.Digest, option types.RegistryOptions)
107110
// Try each authentication method until it succeeds
108111
for _, authOpt := range authOptions(ctx, d, option) {
109112
remoteOpts := []remote.Option{
110-
remote.WithTransport(transport),
113+
remote.WithTransport(tr),
111114
authOpt,
112115
}
113116
index, err := remote.Referrers(d, remoteOpts...)
@@ -122,7 +125,7 @@ func Referrers(ctx context.Context, d name.Digest, option types.RegistryOptions)
122125
return nil, errs
123126
}
124127

125-
func httpTransport(option types.RegistryOptions) (*http.Transport, error) {
128+
func httpTransport(option types.RegistryOptions) (http.RoundTripper, error) {
126129
d := &net.Dialer{
127130
Timeout: 10 * time.Minute,
128131
}
@@ -138,7 +141,8 @@ func httpTransport(option types.RegistryOptions) (*http.Transport, error) {
138141
tr.TLSClientConfig.Certificates = []tls.Certificate{cert}
139142
}
140143

141-
return tr, nil
144+
tripper := transport.NewUserAgent(tr, fmt.Sprintf("trivy/%s", app.Version()))
145+
return tripper, nil
142146
}
143147

144148
func authOptions(ctx context.Context, ref name.Reference, option types.RegistryOptions) []remote.Option {

pkg/remote/remote_test.go

+64
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import (
44
"context"
55
"encoding/base64"
66
"fmt"
7+
"net/http"
78
"net/http/httptest"
89
"os"
910
"path/filepath"
11+
"sync"
1012
"testing"
1113

1214
"github.com/google/go-containerregistry/pkg/name"
@@ -17,6 +19,7 @@ import (
1719
"github.com/aquasecurity/testdocker/auth"
1820
"github.com/aquasecurity/testdocker/registry"
1921
"github.com/aquasecurity/trivy/pkg/fanal/types"
22+
"github.com/aquasecurity/trivy/pkg/version/app"
2023
)
2124

2225
func setupPrivateRegistry() *httptest.Server {
@@ -32,6 +35,7 @@ func setupPrivateRegistry() *httptest.Server {
3235
},
3336
})
3437

38+
tr.Config.Handler = newUserAgentsTrackingHandler(tr.Config.Handler)
3539
return tr
3640
}
3741

@@ -206,3 +210,63 @@ func TestGet(t *testing.T) {
206210
})
207211
}
208212
}
213+
214+
type userAgentsTrackingHandler struct {
215+
hr http.Handler
216+
217+
mu sync.Mutex
218+
agents map[string]struct{}
219+
}
220+
221+
func newUserAgentsTrackingHandler(hr http.Handler) *userAgentsTrackingHandler {
222+
return &userAgentsTrackingHandler{hr: hr, agents: make(map[string]struct{})}
223+
}
224+
225+
func (uh *userAgentsTrackingHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
226+
for _, agent := range r.Header["User-Agent"] {
227+
// Skip test framework user agent
228+
if agent != "Go-http-client/1.1" {
229+
uh.agents[agent] = struct{}{}
230+
}
231+
}
232+
uh.hr.ServeHTTP(rw, r)
233+
}
234+
235+
func setupAgentTrackingRegistry() (*httptest.Server, *userAgentsTrackingHandler) {
236+
imagePaths := map[string]string{
237+
"v2/library/alpine:3.10": "../fanal/test/testdata/alpine-310.tar.gz",
238+
}
239+
tr := registry.NewDockerRegistry(registry.Option{
240+
Images: imagePaths,
241+
})
242+
243+
tracker := newUserAgentsTrackingHandler(tr.Config.Handler)
244+
tr.Config.Handler = tracker
245+
246+
return tr, tracker
247+
}
248+
249+
func TestUserAgents(t *testing.T) {
250+
tr, tracker := setupAgentTrackingRegistry()
251+
defer tr.Close()
252+
253+
serverAddr := tr.Listener.Addr().String()
254+
255+
n, err := name.ParseReference(fmt.Sprintf("%s/library/alpine:3.10", serverAddr))
256+
require.NoError(t, err)
257+
258+
_, err = Get(context.Background(), n, types.RegistryOptions{
259+
Credentials: []types.Credential{
260+
{
261+
Username: "test",
262+
Password: "testpass",
263+
},
264+
},
265+
Insecure: true,
266+
})
267+
require.NoError(t, err)
268+
269+
require.Len(t, tracker.agents, 1)
270+
_, ok := tracker.agents[fmt.Sprintf("trivy/%s go-containerregistry", app.Version())]
271+
require.True(t, ok, `user-agent header equals to "trivy/dev go-containerregistry"`)
272+
}

pkg/version/app/version.go

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package app
2+
3+
var (
4+
ver = "dev"
5+
)
6+
7+
func Version() string {
8+
return ver
9+
}

pkg/version/version.go

+2-9
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,9 @@ import (
88
javadb "github.com/aquasecurity/trivy-java-db/pkg/db"
99
"github.com/aquasecurity/trivy/pkg/log"
1010
"github.com/aquasecurity/trivy/pkg/policy"
11+
"github.com/aquasecurity/trivy/pkg/version/app"
1112
)
1213

13-
var (
14-
ver = "dev"
15-
)
16-
17-
func AppVersion() string {
18-
return ver
19-
}
20-
2114
type VersionInfo struct {
2215
Version string `json:",omitempty"`
2316
VulnerabilityDB *metadata.Metadata `json:",omitempty"`
@@ -99,7 +92,7 @@ func NewVersionInfo(cacheDir string) VersionInfo {
9992
}
10093

10194
return VersionInfo{
102-
Version: ver,
95+
Version: app.Version(),
10396
VulnerabilityDB: dbMeta,
10497
JavaDB: javadbMeta,
10598
CheckBundle: pbMeta,

0 commit comments

Comments
 (0)