Resolves #13629 Add RegistryAuthHeader to manifest push

Signed-off-by: Jason Montleon <jmontleo@redhat.com>
This commit is contained in:
jason 2022-03-24 16:07:36 -04:00 committed by Jason Montleon
parent a416fd6de4
commit 3cc1739373
8 changed files with 297 additions and 37 deletions

View File

@ -36,6 +36,8 @@ do
fi fi
done done
cp hack/podman-registry /bin
# Make sure cni network plugins directory exists # Make sure cni network plugins directory exists
mkdir -p /etc/cni/net.d mkdir -p /etc/cni/net.d

View File

@ -162,13 +162,35 @@ func ManifestAdd(w http.ResponseWriter, r *http.Request) {
// Wrapper to support 3.x with 4.x libpod // Wrapper to support 3.x with 4.x libpod
query := struct { query := struct {
entities.ManifestAddOptions entities.ManifestAddOptions
Images []string Images []string
TLSVerify bool `schema:"tlsVerify"`
}{} }{}
if err := json.NewDecoder(r.Body).Decode(&query); err != nil { if err := json.NewDecoder(r.Body).Decode(&query); err != nil {
utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
return 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) name := utils.GetName(r)
if _, err := runtime.LibimageRuntime().LookupManifestList(name); err != nil { if _, err := runtime.LibimageRuntime().LookupManifestList(name); err != nil {
utils.Error(w, http.StatusNotFound, err) 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)) utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "error pushing image %q", query.Destination))
return return
} }
utils.WriteResponse(w, http.StatusOK, digest) utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: digest})
} }
// ManifestPush push image to registry // ManifestPush push image to registry
@ -350,6 +372,24 @@ func ManifestModify(w http.ResponseWriter, r *http.Request) {
return 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 var report entities.ManifestModifyReport
switch { switch {
case strings.EqualFold("update", body.Operation): case strings.EqualFold("update", body.Operation):

View File

@ -11,7 +11,9 @@ import (
"github.com/blang/semver" "github.com/blang/semver"
"github.com/containers/image/v5/manifest" "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/api/handlers"
"github.com/containers/podman/v4/pkg/auth"
"github.com/containers/podman/v4/pkg/bindings" "github.com/containers/podman/v4/pkg/bindings"
"github.com/containers/podman/v4/pkg/bindings/images" "github.com/containers/podman/v4/pkg/bindings/images"
"github.com/containers/podman/v4/version" "github.com/containers/podman/v4/version"
@ -93,15 +95,19 @@ func Add(ctx context.Context, name string, options *AddOptions) (string, error)
if bindings.ServiceVersion(ctx).GTE(semver.MustParse("4.0.0")) { if bindings.ServiceVersion(ctx).GTE(semver.MustParse("4.0.0")) {
optionsv4 := ModifyOptions{ optionsv4 := ModifyOptions{
All: options.All, All: options.All,
Annotations: options.Annotation, Annotations: options.Annotation,
Arch: options.Arch, Arch: options.Arch,
Features: options.Features, Features: options.Features,
Images: options.Images, Images: options.Images,
OS: options.OS, OS: options.OS,
OSFeatures: nil, OSFeatures: nil,
OSVersion: options.OSVersion, OSVersion: options.OSVersion,
Variant: options.Variant, Variant: options.Variant,
Username: options.Username,
Password: options.Password,
Authfile: options.Authfile,
SkipTLSVerify: options.SkipTLSVerify,
} }
optionsv4.WithOperation("update") optionsv4.WithOperation("update")
return Modify(ctx, name, options.Images, &optionsv4) return Modify(ctx, name, options.Images, &optionsv4)
@ -118,11 +124,27 @@ func Add(ctx context.Context, name string, options *AddOptions) (string, error)
} }
reader := strings.NewReader(opts) 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] 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)) 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 { if err != nil {
return "", err return "", err
} }
@ -179,6 +201,14 @@ func Push(ctx context.Context, name, destination string, options *images.PushOpt
return "", err 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() params, err := options.ToParams()
if err != nil { if err != nil {
return "", err return "", err
@ -192,18 +222,18 @@ func Push(ctx context.Context, name, destination string, options *images.PushOpt
var response *bindings.APIResponse var response *bindings.APIResponse
if bindings.ServiceVersion(ctx).GTE(semver.MustParse("4.0.0")) { 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 { } else {
params.Set("image", name) params.Set("image", name)
params.Set("destination", destination) 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 { if err != nil {
return "", err return "", err
} }
defer response.Body.Close() 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 // Modify modifies the given manifest list using options and the optional list of images
@ -223,7 +253,23 @@ func Modify(ctx context.Context, name string, images []string, options *ModifyOp
} }
reader := strings.NewReader(opts) 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 { if err != nil {
return "", err return "", err
} }

View File

@ -20,14 +20,18 @@ type ExistsOptions struct {
//go:generate go run ../generator/generator.go AddOptions //go:generate go run ../generator/generator.go AddOptions
// AddOptions are optional options for adding manifest lists // AddOptions are optional options for adding manifest lists
type AddOptions struct { type AddOptions struct {
All *bool All *bool
Annotation map[string]string Annotation map[string]string
Arch *string Arch *string
Features []string Features []string
Images []string Images []string
OS *string OS *string
OSVersion *string OSVersion *string
Variant *string Variant *string
Authfile *string
Password *string
Username *string
SkipTLSVerify *bool
} }
//go:generate go run ../generator/generator.go RemoveOptions //go:generate go run ../generator/generator.go RemoveOptions
@ -40,15 +44,18 @@ type RemoveOptions struct {
type ModifyOptions struct { type ModifyOptions struct {
// Operation values are "update", "remove" and "annotate". This allows the service to // Operation values are "update", "remove" and "annotate". This allows the service to
// efficiently perform each update on a manifest list. // efficiently perform each update on a manifest list.
Operation *string Operation *string
All *bool // All when true, operate on all images in a manifest list that may be included in Images 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 Annotations map[string]string // Annotations to add to manifest list
Arch *string // Arch overrides the architecture for the image Arch *string // Arch overrides the architecture for the image
Features []string // Feature list 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 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 OS *string // OS overrides the operating system for the image
OSFeatures []string // OS features for the image OSFeatures []string // OS features for the image
OSVersion *string // OSVersion overrides the operating system for the image OSVersion *string // OSVersion overrides the operating system for the image
Variant *string // Variant overrides the operating system variant for the image Variant *string // Variant overrides the operating system variant for the image
Authfile *string
Password *string
Username *string
SkipTLSVerify *bool
} }

View File

@ -136,3 +136,63 @@ func (o *AddOptions) GetVariant() string {
} }
return *o.Variant 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
}

View File

@ -166,3 +166,63 @@ func (o *ModifyOptions) GetVariant() string {
} }
return *o.Variant 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
}

View File

@ -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) { 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 := 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.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 { if len(opts.Annotation) != 0 {
annotations := make(map[string]string) annotations := make(map[string]string)
for _, annotationSpec := range opts.Annotation { for _, annotationSpec := range opts.Annotation {
@ -61,6 +62,13 @@ func (ir *ImageEngine) ManifestAdd(_ context.Context, name string, imageNames []
} }
options.WithAnnotation(annotations) 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) id, err := manifests.Add(ir.ClientCtx, name, options)
if err != nil { if err != nil {

View File

@ -5,6 +5,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
podmanRegistry "github.com/containers/podman/v4/hack/podman-registry-go"
. "github.com/containers/podman/v4/test/utils" . "github.com/containers/podman/v4/test/utils"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "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() { It("push --rm", func() {
SkipIfRemote("remote does not support --rm") SkipIfRemote("remote does not support --rm")
session := podmanTest.Podman([]string{"manifest", "create", "foo"}) session := podmanTest.Podman([]string{"manifest", "create", "foo"})