github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/test/integration/registry_test.go (about)

     1  //go:build integration
     2  // +build integration
     3  
     4  package integration
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net/url"
    11  	"os"
    12  	"path/filepath"
    13  	"testing"
    14  
    15  	dockercontainer "github.com/docker/docker/api/types/container"
    16  	"github.com/docker/docker/client"
    17  	"github.com/docker/go-connections/nat"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/stretchr/testify/require"
    20  	testcontainers "github.com/testcontainers/testcontainers-go"
    21  	"github.com/testcontainers/testcontainers-go/wait"
    22  
    23  	"github.com/devseccon/trivy/pkg/fanal/analyzer"
    24  	_ "github.com/devseccon/trivy/pkg/fanal/analyzer/all"
    25  	"github.com/devseccon/trivy/pkg/fanal/applier"
    26  	"github.com/devseccon/trivy/pkg/fanal/artifact"
    27  	aimage "github.com/devseccon/trivy/pkg/fanal/artifact/image"
    28  	"github.com/devseccon/trivy/pkg/fanal/cache"
    29  	"github.com/devseccon/trivy/pkg/fanal/image"
    30  	testdocker "github.com/devseccon/trivy/pkg/fanal/test/integration/docker"
    31  	"github.com/devseccon/trivy/pkg/fanal/types"
    32  )
    33  
    34  const (
    35  	registryImage    = "registry:2"
    36  	registryPort     = "5443/tcp"
    37  	registryUsername = "testuser"
    38  	registryPassword = "testpassword"
    39  )
    40  
    41  func TestTLSRegistry(t *testing.T) {
    42  	ctx := context.Background()
    43  
    44  	baseDir, err := filepath.Abs(".")
    45  	require.NoError(t, err)
    46  
    47  	t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true")
    48  	req := testcontainers.ContainerRequest{
    49  		Name:         "registry",
    50  		Image:        registryImage,
    51  		ExposedPorts: []string{registryPort},
    52  		Env: map[string]string{
    53  			"REGISTRY_HTTP_ADDR":            "0.0.0.0:5443",
    54  			"REGISTRY_HTTP_TLS_CERTIFICATE": "/certs/cert.pem",
    55  			"REGISTRY_HTTP_TLS_KEY":         "/certs/key.pem",
    56  			"REGISTRY_AUTH":                 "htpasswd",
    57  			"REGISTRY_AUTH_HTPASSWD_PATH":   "/auth/htpasswd",
    58  			"REGISTRY_AUTH_HTPASSWD_REALM":  "Registry Realm",
    59  		},
    60  		Mounts: testcontainers.Mounts(
    61  			testcontainers.BindMount(filepath.Join(baseDir, "data", "registry", "certs"), "/certs"),
    62  			testcontainers.BindMount(filepath.Join(baseDir, "data", "registry", "auth"), "/auth"),
    63  		),
    64  		HostConfigModifier: func(hostConfig *dockercontainer.HostConfig) {
    65  			hostConfig.AutoRemove = true
    66  		},
    67  		WaitingFor: wait.ForLog("listening on [::]:5443"),
    68  	}
    69  
    70  	registryC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
    71  		ContainerRequest: req,
    72  		Started:          true,
    73  	})
    74  	require.NoError(t, err)
    75  	defer registryC.Terminate(ctx)
    76  
    77  	registryURL, err := getRegistryURL(ctx, registryC, registryPort)
    78  	require.NoError(t, err)
    79  
    80  	config := testdocker.RegistryConfig{
    81  		URL:      registryURL,
    82  		Username: registryUsername,
    83  		Password: registryPassword,
    84  	}
    85  
    86  	testCases := []struct {
    87  		name         string
    88  		imageName    string
    89  		imageFile    string
    90  		option       types.ImageOptions
    91  		login        bool
    92  		expectedOS   types.OS
    93  		expectedRepo types.Repository
    94  		wantErr      bool
    95  	}{
    96  		{
    97  			name:      "happy path",
    98  			imageName: "ghcr.io/devseccon/trivy-test-images:alpine-310",
    99  			imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz",
   100  			option: types.ImageOptions{
   101  				RegistryOptions: types.RegistryOptions{
   102  					Credentials: []types.Credential{
   103  						{
   104  							Username: registryUsername,
   105  							Password: registryPassword,
   106  						},
   107  					},
   108  					Insecure: true,
   109  				},
   110  			},
   111  			expectedOS: types.OS{
   112  				Name:   "3.10.2",
   113  				Family: "alpine",
   114  			},
   115  			expectedRepo: types.Repository{
   116  				Family:  "alpine",
   117  				Release: "3.10",
   118  			},
   119  			wantErr: false,
   120  		},
   121  		{
   122  			name:      "happy path with docker login",
   123  			imageName: "ghcr.io/devseccon/trivy-test-images:alpine-310",
   124  			imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz",
   125  			option: types.ImageOptions{
   126  				RegistryOptions: types.RegistryOptions{
   127  					Insecure: true,
   128  				},
   129  			},
   130  			login: true,
   131  			expectedOS: types.OS{
   132  				Name:   "3.10.2",
   133  				Family: "alpine",
   134  			},
   135  			expectedRepo: types.Repository{
   136  				Family:  "alpine",
   137  				Release: "3.10",
   138  			},
   139  			wantErr: false,
   140  		},
   141  		{
   142  			name:      "sad path: tls verify",
   143  			imageName: "ghcr.io/devseccon/trivy-test-images:alpine-310",
   144  			imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz",
   145  			option: types.ImageOptions{
   146  				RegistryOptions: types.RegistryOptions{
   147  					Credentials: []types.Credential{
   148  						{
   149  							Username: registryUsername,
   150  							Password: registryPassword,
   151  						},
   152  					},
   153  				},
   154  			},
   155  			wantErr: true,
   156  		},
   157  		{
   158  			name:      "sad path: no credential",
   159  			imageName: "ghcr.io/devseccon/trivy-test-images:alpine-310",
   160  			imageFile: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz",
   161  			option: types.ImageOptions{
   162  				RegistryOptions: types.RegistryOptions{
   163  					Insecure: true,
   164  				},
   165  			},
   166  			wantErr: true,
   167  		},
   168  	}
   169  
   170  	for _, tc := range testCases {
   171  		t.Run(tc.name, func(t *testing.T) {
   172  			d, err := testdocker.New()
   173  			require.NoError(t, err)
   174  
   175  			// 0. Set the image source to remote
   176  			tc.option.ImageSources = types.ImageSources{types.RemoteImageSource}
   177  
   178  			// 1. Load a test image from the tar file, tag it and push to the test registry.
   179  			err = d.ReplicateImage(ctx, tc.imageName, tc.imageFile, config)
   180  			require.NoError(t, err)
   181  
   182  			if tc.login {
   183  				err = d.Login(config)
   184  				require.NoError(t, err)
   185  
   186  				defer d.Logout(config)
   187  			}
   188  
   189  			// 2. Analyze it
   190  			imageRef := fmt.Sprintf("%s/%s", registryURL.Host, tc.imageName)
   191  			imageDetail, err := analyze(ctx, imageRef, tc.option)
   192  			require.Equal(t, tc.wantErr, err != nil, err)
   193  			if err != nil {
   194  				return
   195  			}
   196  
   197  			assert.Equal(t, tc.expectedOS, imageDetail.OS)
   198  			assert.Equal(t, &tc.expectedRepo, imageDetail.Repository)
   199  		})
   200  	}
   201  }
   202  
   203  func getRegistryURL(ctx context.Context, registryC testcontainers.Container, exposedPort nat.Port) (*url.URL, error) {
   204  	ip, err := registryC.Host(ctx)
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  
   209  	port, err := registryC.MappedPort(ctx, exposedPort)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  
   214  	urlStr := fmt.Sprintf("https://%s:%s", ip, port.Port())
   215  	return url.Parse(urlStr)
   216  }
   217  
   218  func analyze(ctx context.Context, imageRef string, opt types.ImageOptions) (*types.ArtifactDetail, error) {
   219  	d, err := ioutil.TempDir("", "TestRegistry-*")
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  	defer os.RemoveAll(d)
   224  
   225  	c, err := cache.NewFSCache(d)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  
   230  	cli, err := client.NewClientWithOpts(client.FromEnv)
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  	cli.NegotiateAPIVersion(ctx)
   235  
   236  	img, cleanup, err := image.NewContainerImage(ctx, imageRef, opt)
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  	defer cleanup()
   241  
   242  	ar, err := aimage.NewArtifact(img, c, artifact.Option{
   243  		DisabledAnalyzers: []analyzer.Type{
   244  			analyzer.TypeExecutable,
   245  			analyzer.TypeLicenseFile,
   246  		},
   247  	})
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  
   252  	ap := applier.NewApplier(c)
   253  
   254  	imageInfo, err := ar.Inspect(ctx)
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  
   259  	imageDetail, err := ap.ApplyLayers(imageInfo.ID, imageInfo.BlobIDs)
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  	return &imageDetail, nil
   264  }