Merge pull request #17600 from sstosh/search-auth-opts

Add search --cert-dir, --creds
This commit is contained in:
OpenShift Merge Robot
2023-03-20 04:00:04 -04:00
committed by GitHub
14 changed files with 145 additions and 26 deletions

View File

@ -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"
)
@ -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.
@ -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.
@ -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

View File

@ -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*

View File

@ -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]]*

View File

@ -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 [])

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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.
@ -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
}
@ -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
@ -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
@ -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
}

View File

@ -91,3 +91,33 @@ func (o *SearchOptions) GetListTags() bool {
}
return *o.ListTags
}
// WithUsername set field Username to given value
func (o *SearchOptions) WithUsername(value string) *SearchOptions {
o.Username = &value
return o
}
// GetUsername returns value of field Username
func (o *SearchOptions) GetUsername() string {
if o.Username == nil {
var z string
return z
}
return *o.Username
}
// WithPassword set field Password to given value
func (o *SearchOptions) WithPassword(value string) *SearchOptions {
o.Password = &value
return o
}
// GetPassword returns value of field Password
func (o *SearchOptions) GetPassword() string {
if o.Password == nil {
var z string
return z
}
return *o.Password
}

View File

@ -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"
@ -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.

View File

@ -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.

View File

@ -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,

View File

@ -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)

View File

@ -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} \
@ -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
@ -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