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  }