Skip to content

Commit

Permalink
Add --insecure && --tls-verify flag to podman manifest
Browse files Browse the repository at this point in the history
--insecure flag for docker compatibility

--tls-verify for syntax compatibility and allow users to inspect
manifests at remote Container Registiries without requiring tls.

Helps fix: containers#14917

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
  • Loading branch information
rhatdan committed Oct 31, 2022
1 parent aca9807 commit 2fa451d
Show file tree
Hide file tree
Showing 13 changed files with 144 additions and 25 deletions.
19 changes: 17 additions & 2 deletions cmd/podman/manifest/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ package manifest
import (
"fmt"

"github.com/containers/image/v5/types"
"github.com/containers/podman/v4/cmd/podman/common"
"github.com/containers/podman/v4/cmd/podman/registry"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/spf13/cobra"
)

var (
inspectCmd = &cobra.Command{
tlsVerifyCLI bool
inspectCmd = &cobra.Command{
Use: "inspect IMAGE",
Short: "Display the contents of a manifest list or image index",
Long: "Display the contents of a manifest list or image index.",
Expand All @@ -25,10 +28,22 @@ func init() {
Command: inspectCmd,
Parent: manifestCmd,
})
flags := inspectCmd.Flags()

flags.BoolVar(&tlsVerifyCLI, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry")
flags.Bool("insecure", false, "Purely for Docker compatibility")
_ = flags.MarkHidden("insecure")
}

func inspect(cmd *cobra.Command, args []string) error {
buf, err := registry.ImageEngine().ManifestInspect(registry.Context(), args[0])
opts := entities.ManifestInspectOptions{}
if cmd.Flags().Changed("tls-verify") {
opts.SkipTLSVerify = types.NewOptionalBool(!tlsVerifyCLI)
} else if cmd.Flags().Changed("insecure") {
insecure, _ := cmd.Flags().GetBool("insecure")
opts.SkipTLSVerify = types.NewOptionalBool(insecure)
}
buf, err := registry.ImageEngine().ManifestInspect(registry.Context(), args[0], opts)
if err != nil {
return err
}
Expand Down
18 changes: 17 additions & 1 deletion pkg/api/handlers/libpod/manifests.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,26 @@ func ManifestExists(w http.ResponseWriter, r *http.Request) {

func ManifestInspect(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
name := utils.GetName(r)
// Wrapper to support 3.x with 4.x libpod
query := struct {
TLSVerify bool `schema:"tlsVerify"`
}{}

if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusBadRequest,
fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
return
}

imageEngine := abi.ImageEngine{Libpod: runtime}
rawManifest, err := imageEngine.ManifestInspect(r.Context(), name)
opts := entities.ManifestInspectOptions{}
if _, found := r.URL.Query()["tlsVerify"]; found {
opts.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
}

rawManifest, err := imageEngine.ManifestInspect(r.Context(), name, opts)
if err != nil {
utils.Error(w, http.StatusNotFound, err)
return
Expand Down
5 changes: 5 additions & 0 deletions pkg/api/server/register_manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error {
// type: string
// required: true
// description: the name or ID of the manifest list
// - in: query
// name: tlsVerify
// type: boolean
// default: true
// description: Require HTTPS and verify signatures when contacting registries.
// responses:
// 200:
// $ref: "#/responses/manifestInspect"
Expand Down
14 changes: 12 additions & 2 deletions pkg/bindings/manifests/manifests.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,23 @@ func Exists(ctx context.Context, name string, options *ExistsOptions) (bool, err
}

// Inspect returns a manifest list for a given name.
func Inspect(ctx context.Context, name string, _ *InspectOptions) (*manifest.Schema2List, error) {
func Inspect(ctx context.Context, name string, options *InspectOptions) (*manifest.Schema2List, error) {
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}

response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/manifests/%s/json", nil, nil, name)
params, err := options.ToParams()
if err != nil {
return nil, 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, nil, http.MethodGet, "/manifests/%s/json", params, nil, name)
if err != nil {
return nil, err
}
Expand Down
1 change: 1 addition & 0 deletions pkg/bindings/manifests/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package manifests
//
//go:generate go run ../generator/generator.go InspectOptions
type InspectOptions struct {
SkipTLSVerify *bool
}

// CreateOptions are optional options for creating manifests
Expand Down
15 changes: 15 additions & 0 deletions pkg/bindings/manifests/types_inspect_options.go

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

2 changes: 1 addition & 1 deletion pkg/domain/entities/engine_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type ImageEngine interface { //nolint:interfacebloat
Untag(ctx context.Context, nameOrID string, tags []string, options ImageUntagOptions) error
ManifestCreate(ctx context.Context, name string, images []string, opts ManifestCreateOptions) (string, error)
ManifestExists(ctx context.Context, name string) (*BoolReport, error)
ManifestInspect(ctx context.Context, name string) ([]byte, error)
ManifestInspect(ctx context.Context, name string, opts ManifestInspectOptions) ([]byte, error)
ManifestAdd(ctx context.Context, listName string, imageNames []string, opts ManifestAddOptions) (string, error)
ManifestAnnotate(ctx context.Context, names, image string, opts ManifestAnnotateOptions) (string, error)
ManifestRemoveDigest(ctx context.Context, names, image string) (string, error)
Expand Down
6 changes: 6 additions & 0 deletions pkg/domain/entities/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ type ManifestCreateOptions struct {
SkipTLSVerify types.OptionalBool `json:"-" schema:"-"`
}

// ManifestInspectOptions provides model for inspecting manifest
type ManifestInspectOptions struct {
// Should TLS registry certificate be verified?
SkipTLSVerify types.OptionalBool `json:"-" schema:"-"`
}

// ManifestAddOptions provides model for adding digests to manifest list
//
// swagger:model
Expand Down
12 changes: 9 additions & 3 deletions pkg/domain/infra/abi/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/containers/image/v5/pkg/shortnames"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/storage"
"github.com/opencontainers/go-digest"
Expand Down Expand Up @@ -67,7 +68,7 @@ func (ir *ImageEngine) ManifestExists(ctx context.Context, name string) (*entiti
}

// ManifestInspect returns the content of a manifest list or image
func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) {
func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string, opts entities.ManifestInspectOptions) ([]byte, error) {
// NOTE: we have to do a bit of a limbo here as `podman manifest
// inspect foo` wants to do a remote-inspect of foo iff "foo" in the
// containers storage is an ordinary image but not a manifest list.
Expand All @@ -77,7 +78,7 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte
if errors.Is(err, storage.ErrImageUnknown) || errors.Is(err, libimage.ErrNotAManifestList) {
// Do a remote inspect if there's no local image or if the
// local image is not a manifest list.
return ir.remoteManifestInspect(ctx, name)
return ir.remoteManifestInspect(ctx, name, opts)
}

return nil, err
Expand All @@ -101,9 +102,14 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte
}

// inspect a remote manifest list.
func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string) ([]byte, error) {
func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string, opts entities.ManifestInspectOptions) ([]byte, error) {
sys := ir.Libpod.SystemContext()

sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify
if opts.SkipTLSVerify == types.OptionalBoolTrue {
sys.OCIInsecureSkipTLSVerify = true
}

resolved, err := shortnames.Resolve(sys, name)
if err != nil {
return nil, err
Expand Down
13 changes: 11 additions & 2 deletions pkg/domain/infra/tunnel/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,17 @@ func (ir *ImageEngine) ManifestExists(ctx context.Context, name string) (*entiti
}

// ManifestInspect returns contents of manifest list with given name
func (ir *ImageEngine) ManifestInspect(_ context.Context, name string) ([]byte, error) {
list, err := manifests.Inspect(ir.ClientCtx, name, nil)
func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string, opts entities.ManifestInspectOptions) ([]byte, error) {
options := new(manifests.InspectOptions)
if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined {
if s == types.OptionalBoolTrue {
options.WithSkipTLSVerify(true)
} else {
options.WithSkipTLSVerify(false)
}
}

list, err := manifests.Inspect(ir.ClientCtx, name, options)
if err != nil {
return nil, fmt.Errorf("getting content of manifest list or image %s: %w", name, err)
}
Expand Down
14 changes: 0 additions & 14 deletions test/system/010-images.bats
Original file line number Diff line number Diff line change
Expand Up @@ -232,20 +232,6 @@ Labels.created_at | 20[0-9-]\\\+T[0-9:]\\\+Z
run_podman rmi ${aaa_name}:${aaa_tag} ${zzz_name}:${zzz_tag}
}

# Regression test for #8931
@test "podman images - bare manifest list" {
# Create an empty manifest list and list images.

run_podman inspect --format '{{.ID}}' $IMAGE
iid=$output

run_podman manifest create test:1.0
run_podman images --format '{{.ID}}' --no-trunc
[[ "$output" == *"sha256:$iid"* ]]

run_podman rmi test:1.0
}

@test "podman images - rmi -af removes all containers and pods" {
pname=$(random_string)
run_podman create --pod new:$pname $IMAGE
Expand Down
21 changes: 21 additions & 0 deletions test/system/012-manifest.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bats

load helpers

# Regression test for #8931
@test "podman images - bare manifest list" {
# Create an empty manifest list and list images.

run_podman inspect --format '{{.ID}}' $IMAGE
iid=$output

run_podman manifest create test:1.0
run_podman manifest inspect --insecure $output
is "$output" ".*\"mediaType\": \"application/vnd.docker.distribution.manifest.list.v2+json\"" "--insecure is a noop want to make sure manifest inspect is successful"
run_podman images --format '{{.ID}}' --no-trunc
is "$output" ".*sha256:$iid" "Original image ID still shown in podman-images output"

run_podman rmi test:1.0
}

# vim: filetype=sh
29 changes: 29 additions & 0 deletions test/system/150-login.bats
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,35 @@ 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
###############################################################################
Expand Down

0 comments on commit 2fa451d

Please sign in to comment.