mirror of
https://github.com/containers/podman.git
synced 2025-05-22 01:27:07 +08:00
manifest inspect: support authentication
Previous tests have worked by pure chance since the client and server ran on the same host; the server picked up the credentials created by the client login. Extend the gating tests and add a new integration test which is further capable of exercising the remote code. Note that fixing authentication support requires adding a new `--authfile` CLi flag to `manifest inspect`. This will at least allow for passing an authfile to be bindings. Username and password are not yet supported. Signed-off-by: Valentin Rothberg <vrothberg@redhat.com>
This commit is contained in:
@ -3,6 +3,8 @@ package manifest
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/containers/common/pkg/auth"
|
||||||
|
"github.com/containers/common/pkg/completion"
|
||||||
"github.com/containers/image/v5/types"
|
"github.com/containers/image/v5/types"
|
||||||
"github.com/containers/podman/v4/cmd/podman/common"
|
"github.com/containers/podman/v4/cmd/podman/common"
|
||||||
"github.com/containers/podman/v4/cmd/podman/registry"
|
"github.com/containers/podman/v4/cmd/podman/registry"
|
||||||
@ -11,6 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
inspectOptions entities.ManifestInspectOptions
|
||||||
tlsVerifyCLI bool
|
tlsVerifyCLI bool
|
||||||
inspectCmd = &cobra.Command{
|
inspectCmd = &cobra.Command{
|
||||||
Use: "inspect [options] IMAGE",
|
Use: "inspect [options] IMAGE",
|
||||||
@ -30,6 +33,9 @@ func init() {
|
|||||||
})
|
})
|
||||||
flags := inspectCmd.Flags()
|
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.BoolP("verbose", "v", false, "Added for Docker compatibility")
|
||||||
_ = flags.MarkHidden("verbose")
|
_ = flags.MarkHidden("verbose")
|
||||||
flags.BoolVar(&tlsVerifyCLI, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry")
|
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 {
|
func inspect(cmd *cobra.Command, args []string) error {
|
||||||
opts := entities.ManifestInspectOptions{}
|
|
||||||
if cmd.Flags().Changed("tls-verify") {
|
if cmd.Flags().Changed("tls-verify") {
|
||||||
opts.SkipTLSVerify = types.NewOptionalBool(!tlsVerifyCLI)
|
inspectOptions.SkipTLSVerify = types.NewOptionalBool(!tlsVerifyCLI)
|
||||||
} else if cmd.Flags().Changed("insecure") {
|
} else if cmd.Flags().Changed("insecure") {
|
||||||
insecure, _ := cmd.Flags().GetBool("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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
####> This option file is used in:
|
####> 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
|
####> If file is edited, make sure the changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--authfile**=*path*
|
#### **--authfile**=*path*
|
||||||
|
@ -15,6 +15,8 @@ A formatted JSON representation of the manifest list or image index.
|
|||||||
|
|
||||||
## OPTIONS
|
## OPTIONS
|
||||||
|
|
||||||
|
@@option authfile
|
||||||
|
|
||||||
@@option tls-verify
|
@@option tls-verify
|
||||||
|
|
||||||
## EXAMPLES
|
## EXAMPLES
|
||||||
|
@ -153,12 +153,19 @@ func ManifestInspect(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
imageEngine := abi.ImageEngine{Libpod: runtime}
|
_, authfile, err := auth.GetCredentials(r)
|
||||||
opts := entities.ManifestInspectOptions{}
|
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 {
|
if _, found := r.URL.Query()["tlsVerify"]; found {
|
||||||
opts.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
|
opts.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
imageEngine := abi.ImageEngine{Libpod: runtime}
|
||||||
rawManifest, err := imageEngine.ManifestInspect(r.Context(), name, opts)
|
rawManifest, err := imageEngine.ManifestInspect(r.Context(), name, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error(w, http.StatusNotFound, err)
|
utils.Error(w, http.StatusNotFound, err)
|
||||||
|
@ -92,7 +92,12 @@ func Inspect(ctx context.Context, name string, options *InspectOptions) (*manife
|
|||||||
params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify()))
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -125,7 +130,12 @@ func InspectListData(ctx context.Context, name string, options *InspectOptions)
|
|||||||
params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify()))
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,10 @@ package manifests
|
|||||||
//
|
//
|
||||||
//go:generate go run ../generator/generator.go InspectOptions
|
//go:generate go run ../generator/generator.go InspectOptions
|
||||||
type InspectOptions struct {
|
type InspectOptions struct {
|
||||||
|
// Authfile - path to an authentication file.
|
||||||
|
Authfile *string
|
||||||
|
// SkipTLSVerify - skip https and certificate validation when
|
||||||
|
// contacting container registries.
|
||||||
SkipTLSVerify *bool
|
SkipTLSVerify *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,21 @@ func (o *InspectOptions) ToParams() (url.Values, error) {
|
|||||||
return util.ToParams(o)
|
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
|
// WithSkipTLSVerify set field SkipTLSVerify to given value
|
||||||
func (o *InspectOptions) WithSkipTLSVerify(value bool) *InspectOptions {
|
func (o *InspectOptions) WithSkipTLSVerify(value bool) *InspectOptions {
|
||||||
o.SkipTLSVerify = &value
|
o.SkipTLSVerify = &value
|
||||||
|
@ -14,6 +14,8 @@ type ManifestCreateOptions struct {
|
|||||||
|
|
||||||
// ManifestInspectOptions provides model for inspecting manifest
|
// ManifestInspectOptions provides model for inspecting manifest
|
||||||
type ManifestInspectOptions struct {
|
type ManifestInspectOptions struct {
|
||||||
|
// Path to an authentication file.
|
||||||
|
Authfile string `json:"-" schema:"-"`
|
||||||
// Should TLS registry certificate be verified?
|
// Should TLS registry certificate be verified?
|
||||||
SkipTLSVerify types.OptionalBool `json:"-" schema:"-"`
|
SkipTLSVerify types.OptionalBool `json:"-" schema:"-"`
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string, opts entities.ManifestInspectOptions) ([]byte, error) {
|
||||||
sys := ir.Libpod.SystemContext()
|
sys := ir.Libpod.SystemContext()
|
||||||
|
|
||||||
|
if opts.Authfile != "" {
|
||||||
|
sys.AuthFilePath = opts.Authfile
|
||||||
|
}
|
||||||
|
|
||||||
sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify
|
sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify
|
||||||
if opts.SkipTLSVerify == types.OptionalBoolTrue {
|
if opts.SkipTLSVerify == types.OptionalBoolTrue {
|
||||||
sys.OCIInsecureSkipTLSVerify = true
|
sys.OCIInsecureSkipTLSVerify = true
|
||||||
|
@ -34,7 +34,7 @@ func (ir *ImageEngine) ManifestExists(ctx context.Context, name string) (*entiti
|
|||||||
|
|
||||||
// ManifestInspect returns contents of manifest list with given name
|
// ManifestInspect returns contents of manifest list with given name
|
||||||
func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string, opts entities.ManifestInspectOptions) ([]byte, error) {
|
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 := opts.SkipTLSVerify; s != types.OptionalBoolUndefined {
|
||||||
if s == types.OptionalBoolTrue {
|
if s == types.OptionalBoolTrue {
|
||||||
options.WithSkipTLSVerify(true)
|
options.WithSkipTLSVerify(true)
|
||||||
|
@ -190,6 +190,44 @@ var _ = Describe("Podman login and logout", func() {
|
|||||||
Expect(session).Should(Exit(0))
|
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() {
|
It("podman login and logout with --tls-verify", func() {
|
||||||
session := podmanTest.Podman([]string{"login", "--username", "podmantest", "--password", "test", "--tls-verify=false", server})
|
session := podmanTest.Podman([]string{"login", "--username", "podmantest", "--password", "test", "--tls-verify=false", server})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
#!/usr/bin/env bats
|
#!/usr/bin/env bats
|
||||||
|
|
||||||
load helpers
|
load helpers
|
||||||
|
load helpers.network
|
||||||
|
load helpers.registry
|
||||||
|
|
||||||
# Regression test for #8931
|
# Regression test for #8931
|
||||||
@test "podman images - bare manifest list" {
|
@test "podman images - bare manifest list" {
|
||||||
@ -20,4 +22,38 @@ load helpers
|
|||||||
run_podman rmi test:1.0
|
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
|
# vim: filetype=sh
|
||||||
|
@ -241,35 +241,6 @@ function _test_skopeo_credential_sharing() {
|
|||||||
rm -f $authfile
|
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 cooperation with skopeo
|
||||||
# END actual tests
|
# END actual tests
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
Reference in New Issue
Block a user