Skip to content

Commit

Permalink
Resolves containers#13629 Add RegistryAuthHeader to manifest push
Browse files Browse the repository at this point in the history
Signed-off-by: Jason Montleon <jmontleo@redhat.com>
  • Loading branch information
jason authored and jmontleon committed Mar 25, 2022
1 parent a416fd6 commit 3d19293
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 8 deletions.
38 changes: 38 additions & 0 deletions pkg/api/handlers/libpod/manifests.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,25 @@ func ManifestAdd(w http.ResponseWriter, r *http.Request) {
return
}

authconf, authfile, err := auth.GetCredentials(r)
if err != nil {
utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse registry header for %s", r.URL.String()))
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
query.ManifestAddOptions.All = query.All
if sys := runtime.SystemContext(); sys != nil {
query.ManifestAddOptions.CertDir = sys.DockerCertPath
}

name := utils.GetName(r)
if _, err := runtime.LibimageRuntime().LookupManifestList(name); err != nil {
utils.Error(w, http.StatusNotFound, err)
Expand Down Expand Up @@ -350,6 +369,25 @@ func ManifestModify(w http.ResponseWriter, r *http.Request) {
return
}

authconf, authfile, err := auth.GetCredentials(r)
if err != nil {
utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse registry header for %s", r.URL.String()))
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
body.ManifestAddOptions.All = body.All
if sys := runtime.SystemContext(); sys != nil {
body.ManifestAddOptions.CertDir = sys.DockerCertPath
}

var report entities.ManifestModifyReport
switch {
case strings.EqualFold("update", body.Operation):
Expand Down
34 changes: 27 additions & 7 deletions pkg/bindings/manifests/manifests.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,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/version"
Expand Down Expand Up @@ -102,6 +104,9 @@ func Add(ctx context.Context, name string, options *AddOptions) (string, error)
OSFeatures: nil,
OSVersion: options.OSVersion,
Variant: options.Variant,
Username: options.Username,
Password: options.Password,
Authfile: options.Authfile,
}
optionsv4.WithOperation("update")
return Modify(ctx, name, options.Images, &optionsv4)
Expand All @@ -118,11 +123,16 @@ 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
}

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", nil, header, name)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -179,6 +189,11 @@ 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
}

params, err := options.ToParams()
if err != nil {
return "", err
Expand All @@ -192,18 +207,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
Expand All @@ -223,7 +238,12 @@ 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
}

response, err := conn.DoRequest(ctx, reader, http.MethodPut, "/manifests/%s", nil, header, name)
if err != nil {
return "", err
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/bindings/manifests/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ type AddOptions struct {
OS *string
OSVersion *string
Variant *string
Authfile *string
Password *string
Username *string
}

//go:generate go run ../generator/generator.go RemoveOptions
Expand All @@ -50,5 +53,7 @@ type ModifyOptions struct {
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
}
45 changes: 45 additions & 0 deletions pkg/bindings/manifests/types_add_options.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions pkg/bindings/manifests/types_modify_options.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/domain/infra/tunnel/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
49 changes: 49 additions & 0 deletions test/e2e/manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,55 @@ var _ = Describe("Podman manifest", func() {
))
})

It("authenticated push", func() {
if podmanTest.Host.Arch == "ppc64le" {
Skip("No registry image for ppc64le")
}
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))

lock := GetPortLock("5000")
defer lock.Unlock()

session = podmanTest.Podman([]string{"run", "-d", "-p", "5000:5000", "--name", "registry", "-e", "REGISTRY_AUTH=htpasswd", "-e",
"REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm", "-e", "REGISTRY_AUTH_HTPASSWD_PATH=/htpasswd",
"--entrypoint", "/bin/sh", registry, "-c",
"htpasswd -Bbc /htpasswd podmantest test && /entrypoint.sh /etc/docker/registry/config.yml"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

if !WaitContainerReady(podmanTest, "registry", "listening on", 20, 1) {
Skip("Cannot start docker registry.")
}

session = podmanTest.Podman([]string{"logs", "registry"})
session.WaitWithDefaultTimeout()

session = podmanTest.Podman([]string{"tag", ALPINE, "localhost:5000/alpine:latest"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

push := podmanTest.Podman([]string{"push", "--creds=podmantest:test", "--format=v2s2", "localhost:5000/alpine:latest"})
push.WaitWithDefaultTimeout()
Expect(push).Should(Exit(0))

session = podmanTest.Podman([]string{"manifest", "add", "--creds=podmantest:test", "foo", "localhost:5000/alpine:latest"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

push = podmanTest.Podman([]string{"manifest", "push", "--creds=podmantest:test", "foo", "localhost:5000/credstest"})
push.WaitWithDefaultTimeout()
Expect(push).Should(Exit(0))

push = podmanTest.Podman([]string{"manifest", "push", "--creds=podmantest:wrongpasswd", "foo", "localhost:5000/credstest"})
push.WaitWithDefaultTimeout()
Expect(push).To(ExitWithError())
})

It("push --rm", func() {
SkipIfRemote("remote does not support --rm")
session := podmanTest.Podman([]string{"manifest", "create", "foo"})
Expand Down

0 comments on commit 3d19293

Please sign in to comment.