Skip to content

Commit

Permalink
Merge pull request #17600 from sstosh/search-auth-opts
Browse files Browse the repository at this point in the history
Add search --cert-dir, --creds
  • Loading branch information
openshift-merge-robot authored Mar 20, 2023
2 parents 149f229 + 82f2f82 commit 9ddd4f4
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 26 deletions.
29 changes: 25 additions & 4 deletions cmd/podman/images/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"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/containers/podman/v4/pkg/util"
"github.com/spf13/cobra"
)

Expand All @@ -21,10 +22,11 @@ import (
type searchOptionsWrapper struct {
entities.ImageSearchOptions
// CLI only flags
Compatible bool // Docker compat
TLSVerifyCLI bool // Used to convert to an optional bool later
Format string // For go templating
NoTrunc bool
Compatible bool // Docker compat
CredentialsCLI string
TLSVerifyCLI bool // Used to convert to an optional bool later
Format string // For go templating
NoTrunc bool
}

// listEntryTag is a utility structure used for json serialization.
Expand Down Expand Up @@ -100,8 +102,18 @@ func searchFlags(cmd *cobra.Command) {
flags.StringVar(&searchOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
_ = cmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault)

credsFlagName := "creds"
flags.StringVar(&searchOptions.CredentialsCLI, credsFlagName, "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry")
_ = cmd.RegisterFlagCompletionFunc(credsFlagName, completion.AutocompleteNone)

flags.BoolVar(&searchOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries")
flags.BoolVar(&searchOptions.ListTags, "list-tags", false, "List the tags of the input registry")

if !registry.IsRemote() {
certDirFlagName := "cert-dir"
flags.StringVar(&searchOptions.CertDir, certDirFlagName, "", "`Pathname` of a directory containing TLS certificates and keys")
_ = cmd.RegisterFlagCompletionFunc(certDirFlagName, completion.AutocompleteDefault)
}
}

// imageSearch implements the command for searching images.
Expand Down Expand Up @@ -132,6 +144,15 @@ func imageSearch(cmd *cobra.Command, args []string) error {
}
}

if searchOptions.CredentialsCLI != "" {
creds, err := util.ParseRegistryCreds(searchOptions.CredentialsCLI)
if err != nil {
return err
}
searchOptions.Username = creds.Username
searchOptions.Password = creds.Password
}

searchReport, err := registry.ImageEngine().Search(registry.GetContext(), searchTerm, searchOptions.ImageSearchOptions)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion docs/source/markdown/options/cert-dir.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
####> This option file is used in:
####> podman build, container runlabel, image sign, kube play, login, manifest add, manifest push, pull, push
####> podman build, container runlabel, image sign, kube play, login, manifest add, manifest push, pull, push, search
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--cert-dir**=*path*
Expand Down
2 changes: 1 addition & 1 deletion docs/source/markdown/options/creds.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
####> This option file is used in:
####> podman build, container runlabel, kube play, manifest add, manifest push, pull, push
####> podman build, container runlabel, kube play, manifest add, manifest push, pull, push, search
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--creds**=*[username[:password]]*
Expand Down
4 changes: 4 additions & 0 deletions docs/source/markdown/podman-search.1.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,15 @@ Further note that searching without a search term will only work for registries

@@option authfile

@@option cert-dir

#### **--compatible**

After the name and the description, also show the stars, official and automated descriptors as Docker does.
Podman does not show these descriptors by default since they are not supported by most public container registries.

@@option creds

#### **--filter**, **-f**=*filter*

Filter output based on conditions provided (default [])
Expand Down
20 changes: 15 additions & 5 deletions pkg/api/handlers/compat/images_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,33 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
return
}

_, authfile, err := auth.GetCredentials(r)
authconf, authfile, err := auth.GetCredentials(r)
if err != nil {
utils.Error(w, http.StatusBadRequest, err)
return
}
defer auth.RemoveAuthfile(authfile)

var username, password, idToken string
if authconf != nil {
username = authconf.Username
password = authconf.Password
idToken = authconf.IdentityToken
}

filters := []string{}
for key, val := range query.Filters {
filters = append(filters, fmt.Sprintf("%s=%s", key, val[0]))
}

options := entities.ImageSearchOptions{
Authfile: authfile,
Limit: query.Limit,
ListTags: query.ListTags,
Filters: filters,
Authfile: authfile,
Limit: query.Limit,
ListTags: query.ListTags,
Password: password,
Username: username,
IdentityToken: idToken,
Filters: filters,
}
if _, found := r.URL.Query()["tlsVerify"]; found {
options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
Expand Down
4 changes: 0 additions & 4 deletions pkg/api/server/register_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -1014,10 +1014,6 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// type: boolean
// default: false
// - in: query
// name: credentials
// description: "username:password for the registry"
// type: string
// - in: query
// name: Arch
// description: Pull image for the specified architecture.
// type: string
Expand Down
2 changes: 1 addition & 1 deletion pkg/bindings/images/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ func Search(ctx context.Context, term string, options *SearchOptions) ([]entitie
params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify()))
}

header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, "", "")
header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword())
if err != nil {
return nil, err
}
Expand Down
12 changes: 8 additions & 4 deletions pkg/bindings/images/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ type PushOptions struct {
// Manifest type of the pushed image
Format *string
// Password for authenticating against the registry.
Password *string
Password *string `schema:"-"`
// ProgressWriter is a writer where push progress are sent.
// Since API handler for image push is quiet by default, WithQuiet(false) is necessary for
// the writer to receive progress messages.
Expand All @@ -155,7 +155,7 @@ type PushOptions struct {
// RemoveSignatures Discard any pre-existing signatures in the image.
RemoveSignatures *bool
// Username for authenticating against the registry.
Username *string
Username *string `schema:"-"`
// Quiet can be specified to suppress progress when pushing.
Quiet *bool
}
Expand All @@ -175,6 +175,10 @@ type SearchOptions struct {
SkipTLSVerify *bool `schema:"-"`
// ListTags search the available tags of the repository
ListTags *bool
// Username for authenticating against the registry.
Username *string `schema:"-"`
// Password for authenticating against the registry.
Password *string `schema:"-"`
}

// PullOptions are optional options for pulling images
Expand All @@ -196,7 +200,7 @@ type PullOptions struct {
// "newer", "always". An empty string defaults to "always".
Policy *string
// Password for authenticating against the registry.
Password *string
Password *string `schema:"-"`
// ProgressWriter is a writer where pull progress are sent.
ProgressWriter *io.Writer `schema:"-"`
// Quiet can be specified to suppress pull progress when pulling. Ignored
Expand All @@ -205,7 +209,7 @@ type PullOptions struct {
// SkipTLSVerify to skip HTTPS and certificate verification.
SkipTLSVerify *bool `schema:"-"`
// Username for authenticating against the registry.
Username *string
Username *string `schema:"-"`
// Variant will overwrite the local variant for image pulls.
Variant *string
}
Expand Down
30 changes: 30 additions & 0 deletions pkg/bindings/images/types_search_options.go

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

7 changes: 6 additions & 1 deletion pkg/bindings/test/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ var _ = Describe("Podman images", func() {
})

// Test using credentials.
It("tag + push + pull (with credentials)", func() {
It("tag + push + pull + search (with credentials)", func() {

imageRep := "localhost:" + registry.Port + "/test"
imageTag := "latest"
Expand All @@ -65,6 +65,11 @@ var _ = Describe("Podman images", func() {
pullOpts := new(images.PullOptions)
_, err = images.Pull(bt.conn, imageRef, pullOpts.WithSkipTLSVerify(true).WithPassword(registry.Password).WithUsername(registry.User))
Expect(err).ToNot(HaveOccurred())

// Last, but not least, exercise search.
searchOptions := new(images.SearchOptions)
_, err = images.Search(bt.conn, imageRef, searchOptions.WithSkipTLSVerify(true).WithPassword(registry.Password).WithUsername(registry.User))
Expect(err).ToNot(HaveOccurred())
})

// Test using authfile.
Expand Down
10 changes: 10 additions & 0 deletions pkg/domain/entities/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,16 @@ type ImageSearchOptions struct {
// Authfile is the path to the authentication file. Ignored for remote
// calls.
Authfile string
// CertDir is the path to certificate directories. Ignored for remote
// calls.
CertDir string
// Username for authenticating against the registry.
Username string
// Password for authenticating against the registry.
Password string
// IdentityToken is used to authenticate the user and get
// an access token for the registry.
IdentityToken string
// Filters for the search results.
Filters []string
// Limit the number of results.
Expand Down
4 changes: 4 additions & 0 deletions pkg/domain/infra/abi/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,10 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im

searchOptions := &libimage.SearchOptions{
Authfile: opts.Authfile,
CertDirPath: opts.CertDir,
Username: opts.Username,
Password: opts.Password,
IdentityToken: opts.IdentityToken,
Filter: *filter,
Limit: opts.Limit,
NoTrunc: true,
Expand Down
2 changes: 1 addition & 1 deletion pkg/domain/infra/tunnel/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im

options := new(images.SearchOptions)
options.WithAuthfile(opts.Authfile).WithFilters(mappedFilters).WithLimit(opts.Limit)
options.WithListTags(opts.ListTags)
options.WithListTags(opts.ListTags).WithPassword(opts.Password).WithUsername(opts.Username)
if s := opts.SkipTLSVerify; s != types.OptionalBoolUndefined {
if s == types.OptionalBoolTrue {
options.WithSkipTLSVerify(true)
Expand Down
43 changes: 39 additions & 4 deletions test/system/150-login.bats
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,14 @@ function setup() {
openssl req -newkey rsa:4096 -nodes -sha256 \
-keyout $AUTHDIR/domain.key -x509 -days 2 \
-out $AUTHDIR/domain.crt \
-subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=localhost"
-subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=localhost" \
-addext "subjectAltName=DNS:localhost"
fi

# Copy a cert to another directory for --cert-dir option tests
mkdir -p ${PODMAN_LOGIN_WORKDIR}/trusted-registry-cert-dir
cp $CERT ${PODMAN_LOGIN_WORKDIR}/trusted-registry-cert-dir

# Store credentials where container will see them
if [ ! -e $AUTHDIR/htpasswd ]; then
htpasswd -Bbn ${PODMAN_LOGIN_USER} ${PODMAN_LOGIN_PASS} \
Expand Down Expand Up @@ -185,20 +190,42 @@ EOF
"auth error on push"
}

@test "podman push ok" {
function _push_search_test() {
# Preserve image ID for later comparison against push/pulled image
run_podman inspect --format '{{.Id}}' $IMAGE
iid=$output

destname=ok-$(random_string 10 | tr A-Z a-z)-ok
# Use command-line credentials
run_podman push --tls-verify=false \
run_podman push --tls-verify=$1 \
--format docker \
--cert-dir ${PODMAN_LOGIN_WORKDIR}/trusted-registry-cert-dir \
--creds ${PODMAN_LOGIN_USER}:${PODMAN_LOGIN_PASS} \
$IMAGE localhost:${PODMAN_LOGIN_REGISTRY_PORT}/$destname

# Search a pushed image without --cert-dir will be failed if --tls-verify=true
run_podman $2 search --tls-verify=$1 \
--format "table {{.Name}}" \
--creds ${PODMAN_LOGIN_USER}:${PODMAN_LOGIN_PASS} \
localhost:${PODMAN_LOGIN_REGISTRY_PORT}/$destname

# Search a pushed image without --creds will be failed
run_podman 125 search --tls-verify=$1 \
--format "table {{.Name}}" \
--cert-dir ${PODMAN_LOGIN_WORKDIR}/trusted-registry-cert-dir \
localhost:${PODMAN_LOGIN_REGISTRY_PORT}/$destname

# Search a pushed image will be successed
run_podman search --tls-verify=$1 \
--format "table {{.Name}}" \
--cert-dir ${PODMAN_LOGIN_WORKDIR}/trusted-registry-cert-dir \
--creds ${PODMAN_LOGIN_USER}:${PODMAN_LOGIN_PASS} \
localhost:${PODMAN_LOGIN_REGISTRY_PORT}/$destname
is "${lines[1]}" "localhost:${PODMAN_LOGIN_REGISTRY_PORT}/$destname" "search output is destname"

# Yay! Pull it back
run_podman pull --tls-verify=false \
run_podman pull --tls-verify=$1 \
--cert-dir ${PODMAN_LOGIN_WORKDIR}/trusted-registry-cert-dir \
--creds ${PODMAN_LOGIN_USER}:${PODMAN_LOGIN_PASS} \
localhost:${PODMAN_LOGIN_REGISTRY_PORT}/$destname

Expand All @@ -209,6 +236,14 @@ EOF
run_podman rmi $destname
}

@test "podman push and search ok with --tls-verify=false" {
_push_search_test false 0
}

@test "podman push and search ok with --tls-verify=true" {
_push_search_test true 125
}

# END primary podman login/push/pull tests
###############################################################################
# BEGIN cooperation with skopeo
Expand Down

0 comments on commit 9ddd4f4

Please sign in to comment.