github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/cache/remote_test.go (about) 1 package cache_test 2 3 import ( 4 "context" 5 "net/http" 6 "net/http/httptest" 7 "strings" 8 "testing" 9 "time" 10 11 google_protobuf "github.com/golang/protobuf/ptypes/empty" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 "github.com/twitchtv/twirp" 15 "golang.org/x/xerrors" 16 17 "github.com/devseccon/trivy/pkg/cache" 18 fcache "github.com/devseccon/trivy/pkg/fanal/cache" 19 "github.com/devseccon/trivy/pkg/fanal/types" 20 rpcCache "github.com/devseccon/trivy/rpc/cache" 21 rpcScanner "github.com/devseccon/trivy/rpc/scanner" 22 ) 23 24 type mockCacheServer struct { 25 cache fcache.Cache 26 } 27 28 func (s *mockCacheServer) PutArtifact(_ context.Context, in *rpcCache.PutArtifactRequest) (*google_protobuf.Empty, error) { 29 if strings.Contains(in.ArtifactId, "invalid") { 30 return &google_protobuf.Empty{}, xerrors.New("invalid image ID") 31 } 32 return &google_protobuf.Empty{}, nil 33 } 34 35 func (s *mockCacheServer) PutBlob(_ context.Context, in *rpcCache.PutBlobRequest) (*google_protobuf.Empty, error) { 36 if strings.Contains(in.DiffId, "invalid") { 37 return &google_protobuf.Empty{}, xerrors.New("invalid layer ID") 38 } 39 return &google_protobuf.Empty{}, nil 40 } 41 42 func (s *mockCacheServer) MissingBlobs(_ context.Context, in *rpcCache.MissingBlobsRequest) (*rpcCache.MissingBlobsResponse, error) { 43 var layerIDs []string 44 for _, layerID := range in.BlobIds[:len(in.BlobIds)-1] { 45 if strings.Contains(layerID, "invalid") { 46 return nil, xerrors.New("invalid layer ID") 47 } 48 layerIDs = append(layerIDs, layerID) 49 } 50 return &rpcCache.MissingBlobsResponse{MissingArtifact: true, MissingBlobIds: layerIDs}, nil 51 } 52 53 func (s *mockCacheServer) DeleteBlobs(_ context.Context, in *rpcCache.DeleteBlobsRequest) (*google_protobuf.Empty, error) { 54 for _, blobId := range in.GetBlobIds() { 55 if strings.Contains(blobId, "invalid") { 56 return &google_protobuf.Empty{}, xerrors.New("invalid layer ID") 57 } 58 } 59 return &google_protobuf.Empty{}, nil 60 } 61 62 func withToken(base http.Handler, token, tokenHeader string) http.Handler { 63 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 64 if token != "" && token != r.Header.Get(tokenHeader) { 65 rpcScanner.WriteError(w, twirp.NewError(twirp.Unauthenticated, "invalid token")) 66 return 67 } 68 base.ServeHTTP(w, r) 69 }) 70 } 71 72 func TestRemoteCache_PutArtifact(t *testing.T) { 73 mux := http.NewServeMux() 74 layerHandler := rpcCache.NewCacheServer(new(mockCacheServer), nil) 75 mux.Handle(rpcCache.CachePathPrefix, withToken(layerHandler, "valid-token", "Trivy-Token")) 76 ts := httptest.NewServer(mux) 77 78 type args struct { 79 imageID string 80 imageInfo types.ArtifactInfo 81 customHeaders http.Header 82 } 83 tests := []struct { 84 name string 85 args args 86 wantErr string 87 }{ 88 { 89 name: "happy path", 90 args: args{ 91 imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", 92 imageInfo: types.ArtifactInfo{ 93 SchemaVersion: 1, 94 Architecture: "amd64", 95 Created: time.Time{}, 96 DockerVersion: "18.06", 97 OS: "linux", 98 HistoryPackages: []types.Package{ 99 { 100 Name: "musl", 101 Version: "1.2.3", 102 }, 103 }, 104 }, 105 customHeaders: http.Header{ 106 "Trivy-Token": []string{"valid-token"}, 107 }, 108 }, 109 }, 110 { 111 name: "sad path", 112 args: args{ 113 imageID: "sha256:invalid", 114 imageInfo: types.ArtifactInfo{ 115 SchemaVersion: 1, 116 Architecture: "amd64", 117 Created: time.Time{}, 118 DockerVersion: "18.06", 119 OS: "linux", 120 HistoryPackages: []types.Package{ 121 { 122 Name: "musl", 123 Version: "1.2.3", 124 }, 125 }, 126 }, 127 customHeaders: http.Header{ 128 "Trivy-Token": []string{"valid-token"}, 129 }, 130 }, 131 wantErr: "twirp error internal", 132 }, 133 { 134 name: "sad path: invalid token", 135 args: args{ 136 imageID: "sha256:invalid", 137 customHeaders: http.Header{ 138 "Trivy-Token": []string{"invalid-token"}, 139 }, 140 }, 141 wantErr: "twirp error unauthenticated", 142 }, 143 } 144 for _, tt := range tests { 145 t.Run(tt.name, func(t *testing.T) { 146 c := cache.NewRemoteCache(ts.URL, tt.args.customHeaders, false) 147 err := c.PutArtifact(tt.args.imageID, tt.args.imageInfo) 148 if tt.wantErr != "" { 149 require.NotNil(t, err, tt.name) 150 assert.Contains(t, err.Error(), tt.wantErr, tt.name) 151 return 152 } else { 153 assert.NoError(t, err, tt.name) 154 } 155 }) 156 } 157 } 158 159 func TestRemoteCache_PutBlob(t *testing.T) { 160 mux := http.NewServeMux() 161 layerHandler := rpcCache.NewCacheServer(new(mockCacheServer), nil) 162 mux.Handle(rpcCache.CachePathPrefix, withToken(layerHandler, "valid-token", "Trivy-Token")) 163 ts := httptest.NewServer(mux) 164 165 type args struct { 166 diffID string 167 layerInfo types.BlobInfo 168 customHeaders http.Header 169 } 170 tests := []struct { 171 name string 172 args args 173 wantErr string 174 }{ 175 { 176 name: "happy path", 177 args: args{ 178 diffID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", 179 customHeaders: http.Header{ 180 "Trivy-Token": []string{"valid-token"}, 181 }, 182 }, 183 }, 184 { 185 name: "sad path", 186 args: args{ 187 diffID: "sha256:invalid", 188 customHeaders: http.Header{ 189 "Trivy-Token": []string{"valid-token"}, 190 }, 191 }, 192 wantErr: "twirp error internal", 193 }, 194 { 195 name: "sad path: invalid token", 196 args: args{ 197 diffID: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", 198 customHeaders: http.Header{ 199 "Trivy-Token": []string{"invalid-token"}, 200 }, 201 }, 202 wantErr: "twirp error unauthenticated", 203 }, 204 } 205 for _, tt := range tests { 206 t.Run(tt.name, func(t *testing.T) { 207 c := cache.NewRemoteCache(ts.URL, tt.args.customHeaders, false) 208 err := c.PutBlob(tt.args.diffID, tt.args.layerInfo) 209 if tt.wantErr != "" { 210 require.NotNil(t, err, tt.name) 211 assert.Contains(t, err.Error(), tt.wantErr, tt.name) 212 return 213 } else { 214 assert.NoError(t, err, tt.name) 215 } 216 }) 217 } 218 } 219 220 func TestRemoteCache_MissingBlobs(t *testing.T) { 221 mux := http.NewServeMux() 222 layerHandler := rpcCache.NewCacheServer(new(mockCacheServer), nil) 223 mux.Handle(rpcCache.CachePathPrefix, withToken(layerHandler, "valid-token", "Trivy-Token")) 224 ts := httptest.NewServer(mux) 225 226 type args struct { 227 imageID string 228 layerIDs []string 229 customHeaders http.Header 230 } 231 tests := []struct { 232 name string 233 args args 234 wantMissingImage bool 235 wantMissingLayerIDs []string 236 wantErr string 237 }{ 238 { 239 name: "happy path", 240 args: args{ 241 imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", 242 layerIDs: []string{ 243 "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", 244 "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", 245 }, 246 customHeaders: http.Header{ 247 "Trivy-Token": []string{"valid-token"}, 248 }, 249 }, 250 wantMissingImage: true, 251 wantMissingLayerIDs: []string{ 252 "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02", 253 }, 254 }, 255 { 256 name: "sad path", 257 args: args{ 258 imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", 259 layerIDs: []string{ 260 "sha256:invalid", 261 "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", 262 }, 263 customHeaders: http.Header{ 264 "Trivy-Token": []string{"valid-token"}, 265 }, 266 }, 267 wantErr: "twirp error internal", 268 }, 269 { 270 name: "sad path with invalid token", 271 args: args{ 272 imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", 273 layerIDs: []string{ 274 "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5", 275 }, 276 customHeaders: http.Header{ 277 "Trivy-Token": []string{"invalid-token"}, 278 }, 279 }, 280 wantErr: "twirp error unauthenticated", 281 }, 282 } 283 for _, tt := range tests { 284 t.Run(tt.name, func(t *testing.T) { 285 c := cache.NewRemoteCache(ts.URL, tt.args.customHeaders, false) 286 gotMissingImage, gotMissingLayerIDs, err := c.MissingBlobs(tt.args.imageID, tt.args.layerIDs) 287 if tt.wantErr != "" { 288 require.NotNil(t, err, tt.name) 289 assert.Contains(t, err.Error(), tt.wantErr, tt.name) 290 return 291 } else { 292 require.NoError(t, err, tt.name) 293 } 294 295 assert.Equal(t, tt.wantMissingImage, gotMissingImage) 296 assert.Equal(t, tt.wantMissingLayerIDs, gotMissingLayerIDs) 297 }) 298 } 299 } 300 301 func TestRemoteCache_PutArtifactInsecure(t *testing.T) { 302 ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) 303 defer ts.Close() 304 305 type args struct { 306 imageID string 307 imageInfo types.ArtifactInfo 308 insecure bool 309 } 310 tests := []struct { 311 name string 312 args args 313 wantErr string 314 }{ 315 { 316 name: "happy path", 317 args: args{ 318 imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", 319 imageInfo: types.ArtifactInfo{}, 320 insecure: true, 321 }, 322 }, 323 { 324 name: "sad path", 325 args: args{ 326 imageID: "sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a", 327 imageInfo: types.ArtifactInfo{}, 328 insecure: false, 329 }, 330 wantErr: "failed to do request", 331 }, 332 } 333 for _, tt := range tests { 334 t.Run(tt.name, func(t *testing.T) { 335 c := cache.NewRemoteCache(ts.URL, nil, tt.args.insecure) 336 err := c.PutArtifact(tt.args.imageID, tt.args.imageInfo) 337 if tt.wantErr != "" { 338 require.Error(t, err) 339 assert.Contains(t, err.Error(), tt.wantErr) 340 return 341 } 342 assert.NoError(t, err, tt.name) 343 }) 344 } 345 }