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 }