mirror of https://github.com/containers/podman.git
Merge pull request #13653 from jmontleon/fix-manifest-push-header
Resolves #13629 Add RegistryAuthHeader to manifest push
This commit is contained in:
commit
56b2937f87
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"})
|
||||
|
|
Loading…
Reference in New Issue