diff --git a/cmd/podman/manifest/inspect.go b/cmd/podman/manifest/inspect.go index 6542e6cafb..0e4875a988 100644 --- a/cmd/podman/manifest/inspect.go +++ b/cmd/podman/manifest/inspect.go @@ -3,6 +3,8 @@ package manifest import ( "fmt" + "github.com/containers/common/pkg/auth" + "github.com/containers/common/pkg/completion" "github.com/containers/image/v5/types" "github.com/containers/podman/v4/cmd/podman/common" "github.com/containers/podman/v4/cmd/podman/registry" @@ -11,8 +13,9 @@ import ( ) var ( - tlsVerifyCLI bool - inspectCmd = &cobra.Command{ + inspectOptions entities.ManifestInspectOptions + tlsVerifyCLI bool + inspectCmd = &cobra.Command{ Use: "inspect [options] IMAGE", Short: "Display the contents of a manifest list or image index", Long: "Display the contents of a manifest list or image index.", @@ -30,6 +33,9 @@ func init() { }) flags := inspectCmd.Flags() + authfileFlagName := "authfile" + flags.StringVar(&inspectOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + _ = inspectCmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault) flags.BoolP("verbose", "v", false, "Added for Docker compatibility") _ = flags.MarkHidden("verbose") flags.BoolVar(&tlsVerifyCLI, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry") @@ -38,14 +44,13 @@ func init() { } func inspect(cmd *cobra.Command, args []string) error { - opts := entities.ManifestInspectOptions{} if cmd.Flags().Changed("tls-verify") { - opts.SkipTLSVerify = types.NewOptionalBool(!tlsVerifyCLI) + inspectOptions.SkipTLSVerify = types.NewOptionalBool(!tlsVerifyCLI) } else if cmd.Flags().Changed("insecure") { insecure, _ := cmd.Flags().GetBool("insecure") - opts.SkipTLSVerify = types.NewOptionalBool(insecure) + inspectOptions.SkipTLSVerify = types.NewOptionalBool(insecure) } - buf, err := registry.ImageEngine().ManifestInspect(registry.Context(), args[0], opts) + buf, err := registry.ImageEngine().ManifestInspect(registry.Context(), args[0], inspectOptions) if err != nil { return err } diff --git a/docs/source/markdown/options/authfile.md b/docs/source/markdown/options/authfile.md index 576b9b5fe7..57416efe91 100644 --- a/docs/source/markdown/options/authfile.md +++ b/docs/source/markdown/options/authfile.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman auto update, build, container runlabel, create, image sign, kube play, login, logout, manifest add, manifest push, pull, push, run, search +####> podman auto update, build, container runlabel, create, image sign, kube play, login, logout, manifest add, manifest inspect, manifest push, pull, push, run, search ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--authfile**=*path* diff --git a/docs/source/markdown/podman-manifest-inspect.1.md.in b/docs/source/markdown/podman-manifest-inspect.1.md.in index 89993eaa8a..27ce5f4326 100644 --- a/docs/source/markdown/podman-manifest-inspect.1.md.in +++ b/docs/source/markdown/podman-manifest-inspect.1.md.in @@ -15,6 +15,8 @@ A formatted JSON representation of the manifest list or image index. ## OPTIONS +@@option authfile + @@option tls-verify ## EXAMPLES diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index bc0f1c628c..b94b937b97 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -153,12 +153,19 @@ func ManifestInspect(w http.ResponseWriter, r *http.Request) { return } - imageEngine := abi.ImageEngine{Libpod: runtime} - opts := entities.ManifestInspectOptions{} + _, authfile, err := auth.GetCredentials(r) + if err != nil { + utils.Error(w, http.StatusBadRequest, err) + return + } + defer auth.RemoveAuthfile(authfile) + + opts := entities.ManifestInspectOptions{Authfile: authfile} if _, found := r.URL.Query()["tlsVerify"]; found { opts.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) } + imageEngine := abi.ImageEngine{Libpod: runtime} rawManifest, err := imageEngine.ManifestInspect(r.Context(), name, opts) if err != nil { utils.Error(w, http.StatusNotFound, err) diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go index 4c40b2dcab..45df54af2c 100644 --- a/pkg/bindings/manifests/manifests.go +++ b/pkg/bindings/manifests/manifests.go @@ -92,7 +92,12 @@ func Inspect(ctx context.Context, name string, options *InspectOptions) (*manife params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify())) } - response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/manifests/%s/json", params, nil, name) + header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, "", "") + if err != nil { + return nil, err + } + + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/manifests/%s/json", params, header, name) if err != nil { return nil, err } @@ -125,7 +130,12 @@ func InspectListData(ctx context.Context, name string, options *InspectOptions) params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify())) } - response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/manifests/%s/json", params, nil, name) + header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, "", "") + if err != nil { + return nil, err + } + + response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/manifests/%s/json", params, header, name) if err != nil { return nil, err } diff --git a/pkg/bindings/manifests/types.go b/pkg/bindings/manifests/types.go index d0485e9319..c9e14b1233 100644 --- a/pkg/bindings/manifests/types.go +++ b/pkg/bindings/manifests/types.go @@ -4,6 +4,10 @@ package manifests // //go:generate go run ../generator/generator.go InspectOptions type InspectOptions struct { + // Authfile - path to an authentication file. + Authfile *string + // SkipTLSVerify - skip https and certificate validation when + // contacting container registries. SkipTLSVerify *bool } diff --git a/pkg/bindings/manifests/types_inspect_options.go b/pkg/bindings/manifests/types_inspect_options.go index fb0c397aec..791dbf3e6a 100644 --- a/pkg/bindings/manifests/types_inspect_options.go +++ b/pkg/bindings/manifests/types_inspect_options.go @@ -17,6 +17,21 @@ func (o *InspectOptions) ToParams() (url.Values, error) { return util.ToParams(o) } +// WithAuthfile set field Authfile to given value +func (o *InspectOptions) WithAuthfile(value string) *InspectOptions { + o.Authfile = &value + return o +} + +// GetAuthfile returns value of field Authfile +func (o *InspectOptions) GetAuthfile() string { + if o.Authfile == nil { + var z string + return z + } + return *o.Authfile +} + // WithSkipTLSVerify set field SkipTLSVerify to given value func (o *InspectOptions) WithSkipTLSVerify(value bool) *InspectOptions { o.SkipTLSVerify = &value diff --git a/pkg/domain/entities/manifest.go b/pkg/domain/entities/manifest.go index 030dc4b6d1..819266980a 100644 --- a/pkg/domain/entities/manifest.go +++ b/pkg/domain/entities/manifest.go @@ -14,6 +14,8 @@ type ManifestCreateOptions struct { // ManifestInspectOptions provides model for inspecting manifest type ManifestInspectOptions struct { + // Path to an authentication file. + Authfile string `json:"-" schema:"-"` // Should TLS registry certificate be verified? SkipTLSVerify types.OptionalBool `json:"-" schema:"-"` } diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go index bc60e231ef..224c8488b9 100644 --- a/pkg/domain/infra/abi/manifest.go +++ b/pkg/domain/infra/abi/manifest.go @@ -106,6 +106,10 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string, opts en func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string, opts entities.ManifestInspectOptions) ([]byte, error) { sys := ir.Libpod.SystemContext() + if opts.Authfile != "" { + sys.AuthFilePath = opts.Authfile + } + sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify if opts.SkipTLSVerify == types.OptionalBoolTrue { sys.OCIInsecureSkipTLSVerify = true diff --git a/pkg/domain/infra/tunnel/manifest.go b/pkg/domain/infra/tunnel/manifest.go index a94009d6fc..ca708ab0b0 100644 --- a/pkg/domain/infra/tunnel/manifest.go +++ b/pkg/domain/infra/tunnel/manifest.go @@ -34,7 +34,7 @@ func (ir *ImageEngine) ManifestExists(ctx context.Context, name string) (*entiti // ManifestInspect returns contents of manifest list with given name func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string, opts entities.ManifestInspectOptions) ([]byte, error) { - options := new(manifests.InspectOptions) + options := new(manifests.InspectOptions).WithAuthfile(opts.Authfile) if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined { if s == types.OptionalBoolTrue { options.WithSkipTLSVerify(true) diff --git a/test/e2e/login_logout_test.go b/test/e2e/login_logout_test.go index 69b62e27a6..5574e7b24a 100644 --- a/test/e2e/login_logout_test.go +++ b/test/e2e/login_logout_test.go @@ -190,6 +190,44 @@ var _ = Describe("Podman login and logout", func() { Expect(session).Should(Exit(0)) }) + It("podman manifest with --authfile", func() { + os.Unsetenv("REGISTRY_AUTH_FILE") + + authFile := filepath.Join(podmanTest.TempDir, "auth.json") + session := podmanTest.Podman([]string{"login", "--username", "podmantest", "--password", "test", "--authfile", authFile, server}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + readAuthInfo(authFile) + + session = podmanTest.Podman([]string{"manifest", "create", testImg}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"manifest", "push", testImg}) + session.WaitWithDefaultTimeout() + Expect(session).To(ExitWithError()) + Expect(session.ErrorToString()).To(ContainSubstring(": authentication required")) + + session = podmanTest.Podman([]string{"manifest", "push", "--authfile", authFile, testImg}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + // Now remove the local manifest to trigger remote inspection + session = podmanTest.Podman([]string{"manifest", "rm", testImg}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"manifest", "inspect", testImg}) + session.WaitWithDefaultTimeout() + Expect(session).To(ExitWithError()) + Expect(session.ErrorToString()).To(ContainSubstring(": authentication required")) + + session = podmanTest.Podman([]string{"manifest", "inspect", "--authfile", authFile, testImg}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + }) + It("podman login and logout with --tls-verify", func() { session := podmanTest.Podman([]string{"login", "--username", "podmantest", "--password", "test", "--tls-verify=false", server}) session.WaitWithDefaultTimeout() diff --git a/test/system/012-manifest.bats b/test/system/012-manifest.bats index fcf73721af..df90a135f2 100644 --- a/test/system/012-manifest.bats +++ b/test/system/012-manifest.bats @@ -1,6 +1,8 @@ #!/usr/bin/env bats load helpers +load helpers.network +load helpers.registry # Regression test for #8931 @test "podman images - bare manifest list" { @@ -20,4 +22,38 @@ load helpers run_podman rmi test:1.0 } +@test "podman manifest --tls-verify and --authfile" { + skip_if_remote "running a local registry doesn't work with podman-remote" + start_registry + authfile=${PODMAN_LOGIN_WORKDIR}/auth-$(random_string 10).json + run_podman login --tls-verify=false \ + --username ${PODMAN_LOGIN_USER} \ + --password-stdin \ + --authfile=$authfile \ + localhost:${PODMAN_LOGIN_REGISTRY_PORT} <<<"${PODMAN_LOGIN_PASS}" + is "$output" "Login Succeeded!" "output from podman login" + + manifest1="localhost:${PODMAN_LOGIN_REGISTRY_PORT}/test:1.0" + run_podman manifest create $manifest1 + mid=$output + run_podman manifest push --authfile=$authfile \ + --tls-verify=false $mid \ + $manifest1 + run_podman manifest rm $manifest1 + + # Default is to require TLS; also test explicit opts + for opt in '' '--insecure=false' '--tls-verify=true' "--authfile=$authfile"; do + run_podman 125 manifest inspect $opt $manifest1 + assert "$output" =~ "Error: reading image \"docker://$manifest1\": pinging container registry localhost:${PODMAN_LOGIN_REGISTRY_PORT}:.*x509" \ + "TLE check: fails (as expected) with ${opt:-default}" + done + + run_podman manifest inspect --authfile=$authfile --tls-verify=false $manifest1 + is "$output" ".*\"mediaType\": \"application/vnd.docker.distribution.manifest.list.v2+json\"" "Verify --tls-verify=false --authfile works against an insecure registry" + run_podman manifest inspect --authfile=$authfile --insecure $manifest1 + is "$output" ".*\"mediaType\": \"application/vnd.docker.distribution.manifest.list.v2+json\"" "Verify --insecure --authfile works against an insecure registry" + REGISTRY_AUTH_FILE=$authfile run_podman manifest inspect --tls-verify=false $manifest1 + is "$output" ".*\"mediaType\": \"application/vnd.docker.distribution.manifest.list.v2+json\"" "Verify --tls-verify=false with REGISTRY_AUTH_FILE works against an insecure registry" +} + # vim: filetype=sh diff --git a/test/system/150-login.bats b/test/system/150-login.bats index 118350c2f6..c6d6501454 100644 --- a/test/system/150-login.bats +++ b/test/system/150-login.bats @@ -241,35 +241,6 @@ function _test_skopeo_credential_sharing() { rm -f $authfile } -@test "podman manifest --tls-verify - basic test" { - run_podman login --tls-verify=false \ - --username ${PODMAN_LOGIN_USER} \ - --password-stdin \ - localhost:${PODMAN_LOGIN_REGISTRY_PORT} <<<"${PODMAN_LOGIN_PASS}" - is "$output" "Login Succeeded!" "output from podman login" - - manifest1="localhost:${PODMAN_LOGIN_REGISTRY_PORT}/test:1.0" - run_podman manifest create $manifest1 - mid=$output - run_podman manifest push --authfile=$authfile \ - --tls-verify=false $mid \ - $manifest1 - run_podman manifest rm $manifest1 - run_podman manifest inspect --insecure $manifest1 - is "$output" ".*\"mediaType\": \"application/vnd.docker.distribution.manifest.list.v2+json\"" "Verify --insecure works against an insecure registry" - run_podman 125 manifest inspect --insecure=false $manifest1 - is "$output" ".*Error: reading image \"docker://$manifest1\": pinging container registry localhost:${PODMAN_LOGIN_REGISTRY_PORT}:" "Verify --insecure=false fails" - run_podman manifest inspect --tls-verify=false $manifest1 - is "$output" ".*\"mediaType\": \"application/vnd.docker.distribution.manifest.list.v2+json\"" "Verify --tls-verify=false works against an insecure registry" - run_podman 125 manifest inspect --tls-verify=true $manifest1 - is "$output" ".*Error: reading image \"docker://$manifest1\": pinging container registry localhost:${PODMAN_LOGIN_REGISTRY_PORT}:" "Verify --tls-verify=true fails" - - # Now log out - run_podman logout localhost:${PODMAN_LOGIN_REGISTRY_PORT} - is "$output" "Removed login credentials for localhost:${PODMAN_LOGIN_REGISTRY_PORT}" \ - "output from podman logout" -} - # END cooperation with skopeo # END actual tests ###############################################################################