diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index 864c784846..906a898b2b 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -36,6 +36,8 @@ do fi done +cp hack/podman-registry /bin + # Make sure cni network plugins directory exists mkdir -p /etc/cni/net.d diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index ad662f32c7..b823a56b6b 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -162,13 +162,35 @@ func ManifestAdd(w http.ResponseWriter, r *http.Request) { // Wrapper to support 3.x with 4.x libpod query := struct { entities.ManifestAddOptions - Images []string + Images []string + TLSVerify bool `schema:"tlsVerify"` }{} if err := json.NewDecoder(r.Body).Decode(&query); err != nil { utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) return } + authconf, authfile, err := auth.GetCredentials(r) + if err != nil { + utils.Error(w, http.StatusBadRequest, err) + return + } + defer auth.RemoveAuthfile(authfile) + var username, password string + if authconf != nil { + username = authconf.Username + password = authconf.Password + } + query.ManifestAddOptions.Authfile = authfile + query.ManifestAddOptions.Username = username + query.ManifestAddOptions.Password = password + if sys := runtime.SystemContext(); sys != nil { + query.ManifestAddOptions.CertDir = sys.DockerCertPath + } + if _, found := r.URL.Query()["tlsVerify"]; found { + query.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) + } + name := utils.GetName(r) if _, err := runtime.LibimageRuntime().LookupManifestList(name); err != nil { utils.Error(w, http.StatusNotFound, err) @@ -271,7 +293,7 @@ func ManifestPushV3(w http.ResponseWriter, r *http.Request) { utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "error pushing image %q", query.Destination)) return } - utils.WriteResponse(w, http.StatusOK, digest) + utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: digest}) } // ManifestPush push image to registry @@ -350,6 +372,24 @@ func ManifestModify(w http.ResponseWriter, r *http.Request) { return } + authconf, authfile, err := auth.GetCredentials(r) + if err != nil { + utils.Error(w, http.StatusBadRequest, err) + return + } + defer auth.RemoveAuthfile(authfile) + var username, password string + if authconf != nil { + username = authconf.Username + password = authconf.Password + } + body.ManifestAddOptions.Authfile = authfile + body.ManifestAddOptions.Username = username + body.ManifestAddOptions.Password = password + if sys := runtime.SystemContext(); sys != nil { + body.ManifestAddOptions.CertDir = sys.DockerCertPath + } + var report entities.ManifestModifyReport switch { case strings.EqualFold("update", body.Operation): diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go index f7cd0d2620..70b3819f57 100644 --- a/pkg/bindings/manifests/manifests.go +++ b/pkg/bindings/manifests/manifests.go @@ -10,7 +10,9 @@ import ( "github.com/blang/semver" "github.com/containers/image/v5/manifest" + imageTypes "github.com/containers/image/v5/types" "github.com/containers/podman/v4/pkg/api/handlers" + "github.com/containers/podman/v4/pkg/auth" "github.com/containers/podman/v4/pkg/bindings" "github.com/containers/podman/v4/pkg/bindings/images" "github.com/containers/podman/v4/pkg/domain/entities" @@ -95,15 +97,19 @@ func Add(ctx context.Context, name string, options *AddOptions) (string, error) if bindings.ServiceVersion(ctx).GTE(semver.MustParse("4.0.0")) { optionsv4 := ModifyOptions{ - All: options.All, - Annotations: options.Annotation, - Arch: options.Arch, - Features: options.Features, - Images: options.Images, - OS: options.OS, - OSFeatures: nil, - OSVersion: options.OSVersion, - Variant: options.Variant, + All: options.All, + Annotations: options.Annotation, + Arch: options.Arch, + Features: options.Features, + Images: options.Images, + OS: options.OS, + OSFeatures: nil, + OSVersion: options.OSVersion, + Variant: options.Variant, + Username: options.Username, + Password: options.Password, + Authfile: options.Authfile, + SkipTLSVerify: options.SkipTLSVerify, } optionsv4.WithOperation("update") return Modify(ctx, name, options.Images, &optionsv4) @@ -120,11 +126,27 @@ func Add(ctx context.Context, name string, options *AddOptions) (string, error) } reader := strings.NewReader(opts) - headers := make(http.Header) + header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword()) + if err != nil { + return "", err + } + + params, err := options.ToParams() + if err != nil { + return "", err + } + // SkipTLSVerify is special. We need to delete the param added by + // ToParams() and change the key and flip the bool + if options.SkipTLSVerify != nil { + params.Del("SkipTLSVerify") + params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify())) + } + v := version.APIVersion[version.Libpod][version.MinimalAPI] - headers.Add("API-Version", + header.Add("API-Version", fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)) - response, err := conn.DoRequest(ctx, reader, http.MethodPost, "/manifests/%s/add", nil, headers, name) + + response, err := conn.DoRequest(ctx, reader, http.MethodPost, "/manifests/%s/add", params, header, name) if err != nil { return "", err } @@ -159,6 +181,14 @@ func Push(ctx context.Context, name, destination string, options *images.PushOpt return "", err } + header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword()) + if err != nil { + return "", err + } + v := version.APIVersion[version.Libpod][version.MinimalAPI] + header.Add("API-Version", + fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)) + params, err := options.ToParams() if err != nil { return "", err @@ -172,18 +202,18 @@ func Push(ctx context.Context, name, destination string, options *images.PushOpt var response *bindings.APIResponse if bindings.ServiceVersion(ctx).GTE(semver.MustParse("4.0.0")) { - response, err = conn.DoRequest(ctx, nil, http.MethodPost, "/manifests/%s/registry/%s", params, nil, name, destination) + response, err = conn.DoRequest(ctx, nil, http.MethodPost, "/manifests/%s/registry/%s", params, header, name, destination) } else { params.Set("image", name) params.Set("destination", destination) - response, err = conn.DoRequest(ctx, nil, http.MethodPost, "/manifests/%s/push", params, nil, name) + response, err = conn.DoRequest(ctx, nil, http.MethodPost, "/manifests/%s/push", params, header, name) } if err != nil { return "", err } defer response.Body.Close() - return idr.ID, err + return idr.ID, response.Process(&idr) } // Modify modifies the given manifest list using options and the optional list of images @@ -203,7 +233,23 @@ func Modify(ctx context.Context, name string, images []string, options *ModifyOp } reader := strings.NewReader(opts) - response, err := conn.DoRequest(ctx, reader, http.MethodPut, "/manifests/%s", nil, nil, name) + header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword()) + if err != nil { + return "", err + } + + params, err := options.ToParams() + if err != nil { + return "", err + } + // SkipTLSVerify is special. We need to delete the param added by + // ToParams() and change the key and flip the bool + if options.SkipTLSVerify != nil { + params.Del("SkipTLSVerify") + params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify())) + } + + response, err := conn.DoRequest(ctx, reader, http.MethodPut, "/manifests/%s", params, header, name) if err != nil { return "", err } diff --git a/pkg/bindings/manifests/types.go b/pkg/bindings/manifests/types.go index 5ff28ee301..d0b0b2e717 100644 --- a/pkg/bindings/manifests/types.go +++ b/pkg/bindings/manifests/types.go @@ -20,14 +20,18 @@ type ExistsOptions struct { //go:generate go run ../generator/generator.go AddOptions // AddOptions are optional options for adding manifest lists type AddOptions struct { - All *bool - Annotation map[string]string - Arch *string - Features []string - Images []string - OS *string - OSVersion *string - Variant *string + All *bool + Annotation map[string]string + Arch *string + Features []string + Images []string + OS *string + OSVersion *string + Variant *string + Authfile *string + Password *string + Username *string + SkipTLSVerify *bool } //go:generate go run ../generator/generator.go RemoveOptions @@ -40,15 +44,18 @@ type RemoveOptions struct { type ModifyOptions struct { // Operation values are "update", "remove" and "annotate". This allows the service to // efficiently perform each update on a manifest list. - Operation *string - All *bool // All when true, operate on all images in a manifest list that may be included in Images - Annotations map[string]string // Annotations to add to manifest list - Arch *string // Arch overrides the architecture for the image - Features []string // Feature list for the image - Images []string // Images is an optional list of images to add/remove to/from manifest list depending on operation - OS *string // OS overrides the operating system for the image - OSFeatures []string // OS features for the image - OSVersion *string // OSVersion overrides the operating system for the image - Variant *string // Variant overrides the operating system variant for the image - + Operation *string + All *bool // All when true, operate on all images in a manifest list that may be included in Images + Annotations map[string]string // Annotations to add to manifest list + Arch *string // Arch overrides the architecture for the image + Features []string // Feature list for the image + Images []string // Images is an optional list of images to add/remove to/from manifest list depending on operation + OS *string // OS overrides the operating system for the image + OSFeatures []string // OS features for the image + OSVersion *string // OSVersion overrides the operating system for the image + Variant *string // Variant overrides the operating system variant for the image + Authfile *string + Password *string + Username *string + SkipTLSVerify *bool } diff --git a/pkg/bindings/manifests/types_add_options.go b/pkg/bindings/manifests/types_add_options.go index 0696a69b63..5ba1cc5fa9 100644 --- a/pkg/bindings/manifests/types_add_options.go +++ b/pkg/bindings/manifests/types_add_options.go @@ -136,3 +136,63 @@ func (o *AddOptions) GetVariant() string { } return *o.Variant } + +// WithAuthfile set field Authfile to given value +func (o *AddOptions) WithAuthfile(value string) *AddOptions { + o.Authfile = &value + return o +} + +// GetAuthfile returns value of field Authfile +func (o *AddOptions) GetAuthfile() string { + if o.Authfile == nil { + var z string + return z + } + return *o.Authfile +} + +// WithPassword set field Password to given value +func (o *AddOptions) WithPassword(value string) *AddOptions { + o.Password = &value + return o +} + +// GetPassword returns value of field Password +func (o *AddOptions) GetPassword() string { + if o.Password == nil { + var z string + return z + } + return *o.Password +} + +// WithUsername set field Username to given value +func (o *AddOptions) WithUsername(value string) *AddOptions { + o.Username = &value + return o +} + +// GetUsername returns value of field Username +func (o *AddOptions) GetUsername() string { + if o.Username == nil { + var z string + return z + } + return *o.Username +} + +// WithSkipTLSVerify set field SkipTLSVerify to given value +func (o *AddOptions) WithSkipTLSVerify(value bool) *AddOptions { + o.SkipTLSVerify = &value + return o +} + +// GetSkipTLSVerify returns value of field SkipTLSVerify +func (o *AddOptions) GetSkipTLSVerify() bool { + if o.SkipTLSVerify == nil { + var z bool + return z + } + return *o.SkipTLSVerify +} diff --git a/pkg/bindings/manifests/types_modify_options.go b/pkg/bindings/manifests/types_modify_options.go index 6d75c1e5f4..9d2ed26138 100644 --- a/pkg/bindings/manifests/types_modify_options.go +++ b/pkg/bindings/manifests/types_modify_options.go @@ -166,3 +166,63 @@ func (o *ModifyOptions) GetVariant() string { } return *o.Variant } + +// WithAuthfile set field Authfile to given value +func (o *ModifyOptions) WithAuthfile(value string) *ModifyOptions { + o.Authfile = &value + return o +} + +// GetAuthfile returns value of field Authfile +func (o *ModifyOptions) GetAuthfile() string { + if o.Authfile == nil { + var z string + return z + } + return *o.Authfile +} + +// WithPassword set field Password to given value +func (o *ModifyOptions) WithPassword(value string) *ModifyOptions { + o.Password = &value + return o +} + +// GetPassword returns value of field Password +func (o *ModifyOptions) GetPassword() string { + if o.Password == nil { + var z string + return z + } + return *o.Password +} + +// WithUsername set field Username to given value +func (o *ModifyOptions) WithUsername(value string) *ModifyOptions { + o.Username = &value + return o +} + +// GetUsername returns value of field Username +func (o *ModifyOptions) GetUsername() string { + if o.Username == nil { + var z string + return z + } + return *o.Username +} + +// WithSkipTLSVerify set field SkipTLSVerify to given value +func (o *ModifyOptions) WithSkipTLSVerify(value bool) *ModifyOptions { + o.SkipTLSVerify = &value + return o +} + +// GetSkipTLSVerify returns value of field SkipTLSVerify +func (o *ModifyOptions) GetSkipTLSVerify() bool { + if o.SkipTLSVerify == nil { + var z bool + return z + } + return *o.SkipTLSVerify +} diff --git a/pkg/domain/infra/tunnel/manifest.go b/pkg/domain/infra/tunnel/manifest.go index d2efed8d30..9ac3fdb83a 100644 --- a/pkg/domain/infra/tunnel/manifest.go +++ b/pkg/domain/infra/tunnel/manifest.go @@ -50,6 +50,7 @@ func (ir *ImageEngine) ManifestInspect(_ context.Context, name string) ([]byte, func (ir *ImageEngine) ManifestAdd(_ context.Context, name string, imageNames []string, opts entities.ManifestAddOptions) (string, error) { options := new(manifests.AddOptions).WithAll(opts.All).WithArch(opts.Arch).WithVariant(opts.Variant) options.WithFeatures(opts.Features).WithImages(imageNames).WithOS(opts.OS).WithOSVersion(opts.OSVersion) + options.WithUsername(opts.Username).WithPassword(opts.Password).WithAuthfile(opts.Authfile) if len(opts.Annotation) != 0 { annotations := make(map[string]string) for _, annotationSpec := range opts.Annotation { @@ -61,6 +62,13 @@ func (ir *ImageEngine) ManifestAdd(_ context.Context, name string, imageNames [] } options.WithAnnotation(annotations) } + if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined { + if s == types.OptionalBoolTrue { + options.WithSkipTLSVerify(true) + } else { + options.WithSkipTLSVerify(false) + } + } id, err := manifests.Add(ir.ClientCtx, name, options) if err != nil { diff --git a/test/e2e/manifest_test.go b/test/e2e/manifest_test.go index eaa9cdae66..6e029d3a46 100644 --- a/test/e2e/manifest_test.go +++ b/test/e2e/manifest_test.go @@ -5,6 +5,7 @@ import ( "path/filepath" "strings" + podmanRegistry "github.com/containers/podman/v4/hack/podman-registry-go" . "github.com/containers/podman/v4/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -272,6 +273,42 @@ var _ = Describe("Podman manifest", func() { )) }) + It("authenticated push", func() { + registry, err := podmanRegistry.Start() + Expect(err).To(BeNil()) + + session := podmanTest.Podman([]string{"manifest", "create", "foo"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"pull", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"tag", ALPINE, "localhost:" + registry.Port + "/alpine:latest"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + push := podmanTest.Podman([]string{"push", "--tls-verify=false", "--creds=" + registry.User + ":" + registry.Password, "--format=v2s2", "localhost:" + registry.Port + "/alpine:latest"}) + push.WaitWithDefaultTimeout() + Expect(push).Should(Exit(0)) + + session = podmanTest.Podman([]string{"manifest", "add", "--tls-verify=false", "--creds=" + registry.User + ":" + registry.Password, "foo", "localhost:" + registry.Port + "/alpine:latest"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + push = podmanTest.Podman([]string{"manifest", "push", "--tls-verify=false", "--creds=" + registry.User + ":" + registry.Password, "foo", "localhost:" + registry.Port + "/credstest"}) + push.WaitWithDefaultTimeout() + Expect(push).Should(Exit(0)) + + push = podmanTest.Podman([]string{"manifest", "push", "--tls-verify=false", "--creds=podmantest:wrongpasswd", "foo", "localhost:" + registry.Port + "/credstest"}) + push.WaitWithDefaultTimeout() + Expect(push).To(ExitWithError()) + + err = registry.Stop() + Expect(err).To(BeNil()) + }) + It("push --rm", func() { SkipIfRemote("remote does not support --rm") session := podmanTest.Podman([]string{"manifest", "create", "foo"})