/
registry_provider.go
130 lines (108 loc) · 4.67 KB
/
registry_provider.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package oci
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
containerregistryV1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/khulnasoft-labs/goscope/internal/log"
"github.com/khulnasoft-labs/goscope/pkg/file"
"github.com/khulnasoft-labs/goscope/pkg/image"
)
// RegistryImageProvider is an image.Provider capable of fetching and representing a container image fetched from a remote registry (described by the OCI distribution spec).
type RegistryImageProvider struct {
imageStr string
tmpDirGen *file.TempDirGenerator
registryOptions image.RegistryOptions
platform *image.Platform
}
// NewProviderFromRegistry creates a new provider instance for a specific image that will later be cached to the given directory.
func NewProviderFromRegistry(imgStr string, tmpDirGen *file.TempDirGenerator, registryOptions image.RegistryOptions, platform *image.Platform) *RegistryImageProvider {
return &RegistryImageProvider{
imageStr: imgStr,
tmpDirGen: tmpDirGen,
registryOptions: registryOptions,
platform: platform,
}
}
// Provide an image object that represents the cached docker image tar fetched a registry.
func (p *RegistryImageProvider) Provide(ctx context.Context, userMetadata ...image.AdditionalMetadata) (*image.Image, error) {
log.Debugf("pulling image info directly from registry image=%q", p.imageStr)
imageTempDir, err := p.tmpDirGen.NewDirectory("oci-registry-image")
if err != nil {
return nil, err
}
ref, err := name.ParseReference(p.imageStr, prepareReferenceOptions(p.registryOptions)...)
if err != nil {
return nil, fmt.Errorf("unable to parse registry reference=%q: %+v", p.imageStr, err)
}
descriptor, err := remote.Get(ref, prepareRemoteOptions(ctx, ref, p.registryOptions, p.platform)...)
if err != nil {
return nil, fmt.Errorf("failed to get image descriptor from registry: %+v", err)
}
img, err := descriptor.Image()
if err != nil {
return nil, fmt.Errorf("failed to get image from registry: %+v", err)
}
// craft a repo digest from the registry reference and the known digest
// note: the descriptor is fetched from the registry, and the descriptor digest is the same as the repo digest
repoDigest := fmt.Sprintf("%s/%s@%s", ref.Context().RegistryStr(), ref.Context().RepositoryStr(), descriptor.Digest.String())
metadata := []image.AdditionalMetadata{
image.WithRepoDigests(repoDigest),
}
// make a best effort to get the manifest, should not block getting an image though if it fails
if manifestBytes, err := img.RawManifest(); err == nil {
metadata = append(metadata, image.WithManifest(manifestBytes))
}
if p.platform != nil {
metadata = append(metadata,
image.WithArchitecture(p.platform.Architecture, p.platform.Variant),
image.WithOS(p.platform.OS),
)
}
// apply user-supplied metadata last to override any default behavior
metadata = append(metadata, userMetadata...)
return image.New(img, p.tmpDirGen, imageTempDir, metadata...), nil
}
func prepareReferenceOptions(registryOptions image.RegistryOptions) []name.Option {
var options []name.Option
if registryOptions.InsecureUseHTTP {
options = append(options, name.Insecure)
}
return options
}
func prepareRemoteOptions(ctx context.Context, ref name.Reference, registryOptions image.RegistryOptions, p *image.Platform) (options []remote.Option) {
options = append(options, remote.WithContext(ctx))
if registryOptions.InsecureSkipTLSVerify {
t := &http.Transport{
//nolint: gosec
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
options = append(options, remote.WithTransport(t))
}
if p != nil {
options = append(options, remote.WithPlatform(containerregistryV1.Platform{
Architecture: p.Architecture,
OS: p.OS,
Variant: p.Variant,
}))
}
// note: the authn.Authenticator and authn.Keychain options are mutually exclusive, only one may be provided.
// If no explicit authenticator can be found, check if explicit Keychain has
// been provided, and if not, then fallback to the default keychain.
authenticator := registryOptions.Authenticator(ref.Context().RegistryStr())
switch {
case authenticator != nil:
options = append(options, remote.WithAuth(authenticator))
case registryOptions.Keychain != nil:
options = append(options, remote.WithAuthFromKeychain(registryOptions.Keychain))
default:
// use the Keychain specified from a docker config file.
log.Debugf("no registry credentials configured, using the default keychain")
options = append(options, remote.WithAuthFromKeychain(authn.DefaultKeychain))
}
return options
}