From 9fef0eb3f34d07dde1aee7798992934ce664396b Mon Sep 17 00:00:00 2001 From: Tristan Cacqueray Date: Sat, 18 May 2019 01:37:48 +0000 Subject: [PATCH 1/2] vendor: update containers/image Depends-On: https://github.com/containers/image/pull/631 Signed-off-by: Tristan Cacqueray --- vendor.conf | 2 +- .../containers/image/docker/docker_client.go | 44 ++++++++----------- .../image/docker/docker_image_src.go | 27 +++++++----- ...gnature.5.md => containers-signature.5.md} | 18 ++++---- .../sysregistriesv2/system_registries_v2.go | 26 ++++------- .../transports/alltransports/alltransports.go | 12 +++++ .../containers/image/types/types.go | 1 + 7 files changed, 66 insertions(+), 64 deletions(-) rename vendor/github.com/containers/image/docs/{atomic-signature.5.md => containers-signature.5.md} (95%) diff --git a/vendor.conf b/vendor.conf index 0a16afa9f6..e8b2cd3a45 100644 --- a/vendor.conf +++ b/vendor.conf @@ -2,7 +2,7 @@ github.com/urfave/cli v1.20.0 github.com/kr/pretty v0.1.0 github.com/kr/text v0.1.0 -github.com/containers/image ff926d3c79684793a2135666a2cb738f44ba33dc +github.com/containers/image 2c0349c99af7d90694b3faa0e9bde404d407b145 github.com/containers/buildah 810efa340ab43753034e2ed08ec290e4abab7e72 github.com/vbauerster/mpb v3.3.4 github.com/mattn/go-isatty v0.0.4 diff --git a/vendor/github.com/containers/image/docker/docker_client.go b/vendor/github.com/containers/image/docker/docker_client.go index 503d63eae3..81a43e0fab 100644 --- a/vendor/github.com/containers/image/docker/docker_client.go +++ b/vendor/github.com/containers/image/docker/docker_client.go @@ -23,7 +23,7 @@ import ( "github.com/containers/image/types" "github.com/docker/distribution/registry/client" "github.com/docker/go-connections/tlsconfig" - "github.com/opencontainers/go-digest" + digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -84,27 +84,27 @@ type dockerClient struct { sys *types.SystemContext registry string + // tlsClientConfig is setup by newDockerClient and will be used and updated + // by detectProperties(). Callers can edit tlsClientConfig.InsecureSkipVerify in the meantime. + tlsClientConfig *tls.Config // The following members are not set by newDockerClient and must be set by callers if needed. username string password string signatureBase signatureStorageBase scope authScope + // The following members are detected registry properties: // They are set after a successful detectProperties(), and never change afterwards. - scheme string // Empty value also used to indicate detectProperties() has not yet succeeded. + client *http.Client + scheme string challenges []challenge supportsSignatures bool - // The tlsClientConfig is setup during the creation of the dockerClient and - // will be updated by detectPropertiesHelper(). Any HTTP request the - // dockerClient does will be done by this TLS client configuration. - tlsClientConfig *tls.Config // Private state for setupRequestAuth (key: string, value: bearerToken) tokenCache sync.Map - // detectPropertiesError caches the initial error. - detectPropertiesError error - // detectPropertiesOnce is used to execuute detectProperties() at most once in in makeRequest(). - detectPropertiesOnce sync.Once + // Private state for detectProperties: + detectPropertiesOnce sync.Once // detectPropertiesOnce is used to execute detectProperties() at most once. + detectPropertiesError error // detectPropertiesError caches the initial error. } type authScope struct { @@ -439,18 +439,11 @@ func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url } } logrus.Debugf("%s %s", method, url) - - // Build the transport and do the request by using the clients tlsclientconfig - return c.doHTTP(req) -} - -// doHttp uses the clients internal TLS configuration for doing the -// provided HTTP request. It returns the response and an error on failure. -func (c *dockerClient) doHTTP(req *http.Request) (*http.Response, error) { - tr := tlsclientconfig.NewTransport() - tr.TLSClientConfig = c.tlsClientConfig - httpClient := &http.Client{Transport: tr} - return httpClient.Do(req) + res, err := c.client.Do(req) + if err != nil { + return nil, err + } + return res, nil } // we're using the challenges from the /v2/ ping response and not the one from the destination @@ -558,15 +551,14 @@ func (c *dockerClient) getBearerToken(ctx context.Context, challenge challenge, // detectPropertiesHelper performs the work of detectProperties which executes // it at most once. func (c *dockerClient) detectPropertiesHelper(ctx context.Context) error { - if c.scheme != "" { - return nil - } - // We overwrite the TLS clients `InsecureSkipVerify` only if explicitly // specified by the system context if c.sys != nil && c.sys.DockerInsecureSkipTLSVerify != types.OptionalBoolUndefined { c.tlsClientConfig.InsecureSkipVerify = c.sys.DockerInsecureSkipTLSVerify == types.OptionalBoolTrue } + tr := tlsclientconfig.NewTransport() + tr.TLSClientConfig = c.tlsClientConfig + c.client = &http.Client{Transport: tr} ping := func(scheme string) error { url := fmt.Sprintf(resolvedPingV2URL, scheme, c.registry) diff --git a/vendor/github.com/containers/image/docker/docker_image_src.go b/vendor/github.com/containers/image/docker/docker_image_src.go index 1a424fd314..c8fdb407c7 100644 --- a/vendor/github.com/containers/image/docker/docker_image_src.go +++ b/vendor/github.com/containers/image/docker/docker_image_src.go @@ -16,7 +16,7 @@ import ( "github.com/containers/image/pkg/sysregistriesv2" "github.com/containers/image/types" "github.com/docker/distribution/registry/client" - "github.com/opencontainers/go-digest" + digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -71,16 +71,15 @@ func newImageSource(ctx context.Context, sys *types.SystemContext, ref dockerRef Endpoint: sysregistriesv2.Endpoint{ Location: ref.ref.String(), }, + Prefix: ref.ref.String(), } } + primaryDomain := reference.Domain(ref.ref) // Found the registry within the sysregistriesv2 configuration. Now we test // all endpoints for the manifest availability. If a working image source // was found, it will be used for all future pull actions. - var ( - imageSource *dockerImageSource - manifestLoadErr error - ) + manifestLoadErr := errors.New("Internal error: newImageSource returned without trying any endpoint") for _, endpoint := range append(registry.Mirrors, registry.Endpoint) { logrus.Debugf("Trying to pull %q from endpoint %q", ref.ref, endpoint.Location) @@ -93,7 +92,15 @@ func newImageSource(ctx context.Context, sys *types.SystemContext, ref dockerRef return nil, err } - client, err := newDockerClientFromRef(sys, dockerRef, false, "pull") + endpointSys := sys + // sys.DockerAuthConfig does not explicitly specify a registry; we must not blindly send the credentials intended for the primary endpoint to mirrors. + if endpointSys != nil && endpointSys.DockerAuthConfig != nil && reference.Domain(dockerRef.ref) != primaryDomain { + copy := *endpointSys + copy.DockerAuthConfig = nil + endpointSys = © + } + + client, err := newDockerClientFromRef(endpointSys, dockerRef, false, "pull") if err != nil { return nil, err } @@ -106,12 +113,10 @@ func newImageSource(ctx context.Context, sys *types.SystemContext, ref dockerRef manifestLoadErr = testImageSource.ensureManifestIsLoaded(ctx) if manifestLoadErr == nil { - imageSource = testImageSource - break + return testImageSource, nil } } - - return imageSource, manifestLoadErr + return nil, manifestLoadErr } // Reference returns the reference used to set up this source, _as specified by the user_ @@ -347,7 +352,7 @@ func (s *dockerImageSource) getOneSignature(ctx context.Context, url *url.URL) ( return nil, false, err } req = req.WithContext(ctx) - res, err := s.c.doHTTP(req) + res, err := s.c.client.Do(req) if err != nil { return nil, false, err } diff --git a/vendor/github.com/containers/image/docs/atomic-signature.5.md b/vendor/github.com/containers/image/docs/containers-signature.5.md similarity index 95% rename from vendor/github.com/containers/image/docs/atomic-signature.5.md rename to vendor/github.com/containers/image/docs/containers-signature.5.md index 8a1fad5d01..5b99e7c4f3 100644 --- a/vendor/github.com/containers/image/docs/atomic-signature.5.md +++ b/vendor/github.com/containers/image/docs/containers-signature.5.md @@ -1,10 +1,10 @@ -% atomic-signature(5) Atomic signature format +% container-signature(5) Container signature format % Miloslav Trmač % March 2017 -# Atomic signature format +# Container signature format -This document describes the format of “atomic” container signatures, +This document describes the format of container signatures, as implemented by the `github.com/containers/image/signature` package. Most users should be able to consume these signatures by using the `github.com/containers/image/signature` package @@ -21,7 +21,7 @@ an automated build system as a result of an automated build, a company IT department approving the image for production) under a specified _identity_ (e.g. an OS base image / specific application, with a specific version). -An atomic container signature consists of a cryptographic signature which identifies +A container signature consists of a cryptographic signature which identifies and authenticates who signed the image, and carries as a signed payload a JSON document. The JSON document identifies the image being signed, claims a specific identity of the image and if applicable, contains other information about the image. @@ -34,7 +34,7 @@ associating more than one signature with an image. ## The cryptographic signature -As distributed, the atomic container signature is a blob which contains a cryptographic signature +As distributed, the container signature is a blob which contains a cryptographic signature in an industry-standard format, carrying a signed JSON payload (i.e. the blob contains both the JSON document and a signature of the JSON document; it is not a “detached signature” with independent blobs containing the JSON document and a cryptographic signature). @@ -46,7 +46,7 @@ that this is not necessary and the configured expected public key provides anoth of the expected cryptographic signature format. Such metadata may be added in the future for newly added cryptographic signature formats, if necessary.) -Consumers of atomic container signatures SHOULD verify the cryptographic signature +Consumers of container signatures SHOULD verify the cryptographic signature against one or more trusted public keys (e.g. defined in a [policy.json signature verification policy file](policy.json.md)) before parsing or processing the JSON payload in _any_ way, @@ -89,7 +89,7 @@ and if the semantics of the invalid document, as created by such an implementati The top-level value of the JSON document MUST be a JSON object with exactly two members, `critical` and `optional`, each a JSON object. -The `critical` object MUST contain a `type` member identifying the document as an atomic container signature +The `critical` object MUST contain a `type` member identifying the document as a container signature (as defined [below](#criticaltype)) and signature consumers MUST reject signatures which do not have this member or in which this member does not have the expected value. @@ -210,7 +210,7 @@ Consumers still SHOULD reject any signature where a member of an `optional` obje If present, this MUST be a JSON string, identifying the name and version of the software which has created the signature. -The contents of this string is not defined in detail; however each implementation creating atomic container signatures: +The contents of this string is not defined in detail; however each implementation creating container signatures: - SHOULD define the contents to unambiguously define the software in practice (e.g. it SHOULD contain the name of the software, not only the version number) - SHOULD use a build and versioning process which ensures that the contents of this string (e.g. an included version number) @@ -221,7 +221,7 @@ The contents of this string is not defined in detail; however each implementatio (e.g. the version of the implementation SHOULD NOT be only a git hash, because they don’t have an easily defined ordering; the string should contain a version number, or at least a date of the commit). -Consumers of atomic container signatures MAY recognize specific values or sets of values of `optional.creator` +Consumers of container signatures MAY recognize specific values or sets of values of `optional.creator` (perhaps augmented with `optional.timestamp`), and MAY change their processing of the signature based on these values (usually to acommodate violations of this specification in past versions of the signing software which cannot be fixed retroactively), diff --git a/vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go b/vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go index 9589f3829c..99ae65774e 100644 --- a/vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go +++ b/vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go @@ -39,26 +39,18 @@ type Endpoint struct { // endpoints `location` from the `ref` and creates a new named reference from it. // The function errors if the newly created reference is not parsable. func (e *Endpoint) RewriteReference(ref reference.Named, prefix string) (reference.Named, error) { - if ref == nil { - return nil, fmt.Errorf("provided reference is nil") - } - if prefix == "" { - return ref, nil - } refString := ref.String() - if refMatchesPrefix(refString, prefix) { - newNamedRef := strings.Replace(refString, prefix, e.Location, 1) - newParsedRef, err := reference.ParseNamed(newNamedRef) - if newParsedRef != nil { - logrus.Debugf("reference rewritten from '%v' to '%v'", refString, newParsedRef.String()) - } - if err != nil { - return nil, errors.Wrapf(err, "error rewriting reference") - } - return newParsedRef, nil + if !refMatchesPrefix(refString, prefix) { + return nil, fmt.Errorf("invalid prefix '%v' for reference '%v'", prefix, refString) } - return nil, fmt.Errorf("invalid prefix '%v' for reference '%v'", prefix, refString) + newNamedRef := strings.Replace(refString, prefix, e.Location, 1) + newParsedRef, err := reference.ParseNamed(newNamedRef) + if err != nil { + return nil, errors.Wrapf(err, "error rewriting reference") + } + logrus.Debugf("reference rewritten from '%v' to '%v'", refString, newParsedRef.String()) + return newParsedRef, nil } // Registry represents a registry. diff --git a/vendor/github.com/containers/image/transports/alltransports/alltransports.go b/vendor/github.com/containers/image/transports/alltransports/alltransports.go index 23b2782f6b..2335c567fc 100644 --- a/vendor/github.com/containers/image/transports/alltransports/alltransports.go +++ b/vendor/github.com/containers/image/transports/alltransports/alltransports.go @@ -22,6 +22,7 @@ import ( // ParseImageName converts a URL-like image name to a types.ImageReference. func ParseImageName(imgName string) (types.ImageReference, error) { + // Keep this in sync with TransportFromImageName! parts := strings.SplitN(imgName, ":", 2) if len(parts) != 2 { return nil, errors.Errorf(`Invalid image name "%s", expected colon-separated transport:reference`, imgName) @@ -32,3 +33,14 @@ func ParseImageName(imgName string) (types.ImageReference, error) { } return transport.ParseReference(parts[1]) } + +// TransportFromImageName converts an URL-like name to a types.ImageTransport or nil when +// the transport is unknown or when the input is invalid. +func TransportFromImageName(imageName string) types.ImageTransport { + // Keep this in sync with ParseImageName! + parts := strings.SplitN(imageName, ":", 2) + if len(parts) == 2 { + return transports.Get(parts[0]) + } + return nil +} diff --git a/vendor/github.com/containers/image/types/types.go b/vendor/github.com/containers/image/types/types.go index 9fdab2314a..7895043481 100644 --- a/vendor/github.com/containers/image/types/types.go +++ b/vendor/github.com/containers/image/types/types.go @@ -401,6 +401,7 @@ type ImageInspectInfo struct { } // DockerAuthConfig contains authorization information for connecting to a registry. +// the value of Username and Password can be empty for accessing the registry anonymously type DockerAuthConfig struct { Username string Password string From b46d16f48cec75228bba03572bb2aac895b36187 Mon Sep 17 00:00:00 2001 From: Tristan Cacqueray Date: Thu, 16 May 2019 01:22:56 +0000 Subject: [PATCH 2/2] rootless: don't create a namespace unless for containers-storage This change fixes skopeo usage in restricted environment such as bubblewrap where it doesn't need extra capabilities or user namespace to perform its action. Close #649 Signed-off-by: Tristan Cacqueray --- cmd/skopeo/copy.go | 14 +++++++++----- cmd/skopeo/delete.go | 14 +++++++++----- cmd/skopeo/inspect.go | 9 +++++++-- cmd/skopeo/layers.go | 8 ++++++-- cmd/skopeo/unshare.go | 4 ++++ cmd/skopeo/unshare_linux.go | 12 ++++++++++++ cmd/skopeo/utils.go | 4 ---- 7 files changed, 47 insertions(+), 18 deletions(-) diff --git a/cmd/skopeo/copy.go b/cmd/skopeo/copy.go index 9e1d093b21..edf2a59f18 100644 --- a/cmd/skopeo/copy.go +++ b/cmd/skopeo/copy.go @@ -50,7 +50,6 @@ func copyCmd(global *globalOptions) cli.Command { `, strings.Join(transports.ListNames(), ", ")), ArgsUsage: "SOURCE-IMAGE DESTINATION-IMAGE", Action: commandAction(opts.run), - Before: needsRexec, // FIXME: Do we need to namespace the GPG aspect? Flags: append(append(append([]cli.Flag{ cli.StringSliceFlag{ @@ -86,6 +85,11 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error { if len(args) != 2 { return errorShouldDisplayUsage{errors.New("Exactly two arguments expected")} } + imageNames := args + + if err := reexecIfNecessaryForImages(imageNames...); err != nil { + return err + } policyContext, err := opts.global.getPolicyContext() if err != nil { @@ -93,13 +97,13 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error { } defer policyContext.Destroy() - srcRef, err := alltransports.ParseImageName(args[0]) + srcRef, err := alltransports.ParseImageName(imageNames[0]) if err != nil { - return fmt.Errorf("Invalid source name %s: %v", args[0], err) + return fmt.Errorf("Invalid source name %s: %v", imageNames[0], err) } - destRef, err := alltransports.ParseImageName(args[1]) + destRef, err := alltransports.ParseImageName(imageNames[1]) if err != nil { - return fmt.Errorf("Invalid destination name %s: %v", args[1], err) + return fmt.Errorf("Invalid destination name %s: %v", imageNames[1], err) } sourceCtx, err := opts.srcImage.newSystemContext() diff --git a/cmd/skopeo/delete.go b/cmd/skopeo/delete.go index 2072f5565c..016c70be13 100644 --- a/cmd/skopeo/delete.go +++ b/cmd/skopeo/delete.go @@ -24,9 +24,8 @@ func deleteCmd(global *globalOptions) cli.Command { image: imageOpts, } return cli.Command{ - Before: needsRexec, - Name: "delete", - Usage: "Delete image IMAGE-NAME", + Name: "delete", + Usage: "Delete image IMAGE-NAME", Description: fmt.Sprintf(` Delete an "IMAGE_NAME" from a transport @@ -45,10 +44,15 @@ func (opts *deleteOptions) run(args []string, stdout io.Writer) error { if len(args) != 1 { return errors.New("Usage: delete imageReference") } + imageName := args[0] - ref, err := alltransports.ParseImageName(args[0]) + if err := reexecIfNecessaryForImages(imageName); err != nil { + return err + } + + ref, err := alltransports.ParseImageName(imageName) if err != nil { - return fmt.Errorf("Invalid source name %s: %v", args[0], err) + return fmt.Errorf("Invalid source name %s: %v", imageName, err) } sys, err := opts.image.newSystemContext() diff --git a/cmd/skopeo/inspect.go b/cmd/skopeo/inspect.go index 9edf8632b1..95ff5002a5 100644 --- a/cmd/skopeo/inspect.go +++ b/cmd/skopeo/inspect.go @@ -68,7 +68,6 @@ func inspectCmd(global *globalOptions) cli.Command { Destination: &opts.config, }, }, sharedFlags...), imageFlags...), - Before: needsRexec, Action: commandAction(opts.run), } } @@ -80,7 +79,13 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error) if len(args) != 1 { return errors.New("Exactly one argument expected") } - img, err := parseImage(ctx, opts.image, args[0]) + imageName := args[0] + + if err := reexecIfNecessaryForImages(imageName); err != nil { + return err + } + + img, err := parseImage(ctx, opts.image, imageName) if err != nil { return err } diff --git a/cmd/skopeo/layers.go b/cmd/skopeo/layers.go index 3a09d8bd3e..9899b360eb 100644 --- a/cmd/skopeo/layers.go +++ b/cmd/skopeo/layers.go @@ -32,7 +32,6 @@ func layersCmd(global *globalOptions) cli.Command { Name: "layers", Usage: "Get layers of IMAGE-NAME", ArgsUsage: "IMAGE-NAME [LAYER...]", - Before: needsRexec, Hidden: true, Action: commandAction(opts.run), Flags: append(sharedFlags, imageFlags...), @@ -44,6 +43,11 @@ func (opts *layersOptions) run(args []string, stdout io.Writer) (retErr error) { if len(args) == 0 { return errors.New("Usage: layers imageReference [layer...]") } + imageName := args[0] + + if err := reexecIfNecessaryForImages(imageName); err != nil { + return err + } ctx, cancel := opts.global.commandTimeoutContext() defer cancel() @@ -53,7 +57,7 @@ func (opts *layersOptions) run(args []string, stdout io.Writer) (retErr error) { return err } cache := blobinfocache.DefaultCache(sys) - rawSource, err := parseImageSource(ctx, opts.image, args[0]) + rawSource, err := parseImageSource(ctx, opts.image, imageName) if err != nil { return err } diff --git a/cmd/skopeo/unshare.go b/cmd/skopeo/unshare.go index 1e9a391dd9..13b1686f6d 100644 --- a/cmd/skopeo/unshare.go +++ b/cmd/skopeo/unshare.go @@ -5,3 +5,7 @@ package main func maybeReexec() error { return nil } + +func reexecIfNecessaryForImages(inputImageNames ...string) error { + return nil +} diff --git a/cmd/skopeo/unshare_linux.go b/cmd/skopeo/unshare_linux.go index f9291c0c0c..c3e8905091 100644 --- a/cmd/skopeo/unshare_linux.go +++ b/cmd/skopeo/unshare_linux.go @@ -2,6 +2,8 @@ package main import ( "github.com/containers/buildah/pkg/unshare" + "github.com/containers/image/storage" + "github.com/containers/image/transports/alltransports" "github.com/pkg/errors" "github.com/syndtr/gocapability/capability" ) @@ -32,3 +34,13 @@ func maybeReexec() error { } return nil } + +func reexecIfNecessaryForImages(imageNames ...string) error { + // Check if container-storage are used before doing unshare + for _, imageName := range imageNames { + if alltransports.TransportFromImageName(imageName).Name() == storage.Transport.Name() { + return maybeReexec() + } + } + return nil +} diff --git a/cmd/skopeo/utils.go b/cmd/skopeo/utils.go index df4c48c60c..320aec8389 100644 --- a/cmd/skopeo/utils.go +++ b/cmd/skopeo/utils.go @@ -16,10 +16,6 @@ type errorShouldDisplayUsage struct { error } -func needsRexec(c *cli.Context) error { - return maybeReexec() -} - // commandAction intermediates between the cli.ActionFunc interface and the real handler, // primarily to ensure that cli.Context is not available to the handler, which in turn // makes sure that the cli.String() etc. flag access functions are not used,