mirror of
https://github.com/containers/podman.git
synced 2025-07-15 03:02:52 +08:00
Merge pull request #17600 from sstosh/search-auth-opts
Add search --cert-dir, --creds
This commit is contained in:
@ -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
|
||||
|
@ -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*
|
||||
|
@ -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]]*
|
||||
|
@ -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 [])
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user