...

Source file src/k8s.io/kubectl/pkg/cmd/create/create_secret_docker.go

Documentation: k8s.io/kubectl/pkg/cmd/create

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package create
    18  
    19  import (
    20  	"context"
    21  	"encoding/base64"
    22  	"encoding/json"
    23  	"fmt"
    24  
    25  	"github.com/spf13/cobra"
    26  	corev1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/cli-runtime/pkg/genericclioptions"
    30  	"k8s.io/cli-runtime/pkg/genericiooptions"
    31  	corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
    32  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    33  	"k8s.io/kubectl/pkg/scheme"
    34  	"k8s.io/kubectl/pkg/util"
    35  	"k8s.io/kubectl/pkg/util/hash"
    36  	"k8s.io/kubectl/pkg/util/i18n"
    37  	"k8s.io/kubectl/pkg/util/templates"
    38  )
    39  
    40  var (
    41  	secretForDockerRegistryLong = templates.LongDesc(i18n.T(`
    42  		Create a new secret for use with Docker registries.
    43  
    44  		Dockercfg secrets are used to authenticate against Docker registries.
    45  
    46  		When using the Docker command line to push images, you can authenticate to a given registry by running:
    47  			'$ docker login DOCKER_REGISTRY_SERVER --username=DOCKER_USER --password=DOCKER_PASSWORD --email=DOCKER_EMAIL'.
    48  
    49  	That produces a ~/.dockercfg file that is used by subsequent 'docker push' and 'docker pull' commands to
    50  		authenticate to the registry. The email address is optional.
    51  
    52  		When creating applications, you may have a Docker registry that requires authentication.  In order for the
    53  		nodes to pull images on your behalf, they must have the credentials.  You can provide this information
    54  		by creating a dockercfg secret and attaching it to your service account.`))
    55  
    56  	secretForDockerRegistryExample = templates.Examples(i18n.T(`
    57  		  # If you do not already have a .dockercfg file, create a dockercfg secret directly
    58  		  kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
    59  
    60  		  # Create a new secret named my-secret from ~/.docker/config.json
    61  		  kubectl create secret docker-registry my-secret --from-file=.dockerconfigjson=path/to/.docker/config.json`))
    62  )
    63  
    64  // DockerConfigJSON represents a local docker auth config file
    65  // for pulling images.
    66  type DockerConfigJSON struct {
    67  	Auths DockerConfig `json:"auths" datapolicy:"token"`
    68  	// +optional
    69  	HttpHeaders map[string]string `json:"HttpHeaders,omitempty" datapolicy:"token"`
    70  }
    71  
    72  // DockerConfig represents the config file used by the docker CLI.
    73  // This config that represents the credentials that should be used
    74  // when pulling images from specific image repositories.
    75  type DockerConfig map[string]DockerConfigEntry
    76  
    77  // DockerConfigEntry holds the user information that grant the access to docker registry
    78  type DockerConfigEntry struct {
    79  	Username string `json:"username,omitempty"`
    80  	Password string `json:"password,omitempty" datapolicy:"password"`
    81  	Email    string `json:"email,omitempty"`
    82  	Auth     string `json:"auth,omitempty" datapolicy:"token"`
    83  }
    84  
    85  // CreateSecretDockerRegistryOptions holds the options for 'create secret docker-registry' sub command
    86  type CreateSecretDockerRegistryOptions struct {
    87  	// PrintFlags holds options necessary for obtaining a printer
    88  	PrintFlags *genericclioptions.PrintFlags
    89  	PrintObj   func(obj runtime.Object) error
    90  
    91  	// Name of secret (required)
    92  	Name string
    93  	// FileSources to derive the secret from (optional)
    94  	FileSources []string
    95  	// Username for registry (required)
    96  	Username string
    97  	// Email for registry (optional)
    98  	Email string
    99  	// Password for registry (required)
   100  	Password string `datapolicy:"password"`
   101  	// Server for registry (required)
   102  	Server string
   103  	// AppendHash; if true, derive a hash from the Secret and append it to the name
   104  	AppendHash bool
   105  
   106  	FieldManager     string
   107  	CreateAnnotation bool
   108  	Namespace        string
   109  	EnforceNamespace bool
   110  
   111  	Client              corev1client.CoreV1Interface
   112  	DryRunStrategy      cmdutil.DryRunStrategy
   113  	ValidationDirective string
   114  
   115  	genericiooptions.IOStreams
   116  }
   117  
   118  // NewSecretDockerRegistryOptions creates a new *CreateSecretDockerRegistryOptions with default value
   119  func NewSecretDockerRegistryOptions(ioStreams genericiooptions.IOStreams) *CreateSecretDockerRegistryOptions {
   120  	return &CreateSecretDockerRegistryOptions{
   121  		Server:     "https://index.docker.io/v1/",
   122  		PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
   123  		IOStreams:  ioStreams,
   124  	}
   125  }
   126  
   127  // NewCmdCreateSecretDockerRegistry is a macro command for creating secrets to work with Docker registries
   128  func NewCmdCreateSecretDockerRegistry(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
   129  	o := NewSecretDockerRegistryOptions(ioStreams)
   130  
   131  	cmd := &cobra.Command{
   132  		Use:                   "docker-registry NAME --docker-username=user --docker-password=password --docker-email=email [--docker-server=string] [--from-file=[key=]source] [--dry-run=server|client|none]",
   133  		DisableFlagsInUseLine: true,
   134  		Short:                 i18n.T("Create a secret for use with a Docker registry"),
   135  		Long:                  secretForDockerRegistryLong,
   136  		Example:               secretForDockerRegistryExample,
   137  		Run: func(cmd *cobra.Command, args []string) {
   138  			cmdutil.CheckErr(o.Complete(f, cmd, args))
   139  			cmdutil.CheckErr(o.Validate())
   140  			cmdutil.CheckErr(o.Run())
   141  		},
   142  	}
   143  
   144  	o.PrintFlags.AddFlags(cmd)
   145  
   146  	cmdutil.AddApplyAnnotationFlags(cmd)
   147  	cmdutil.AddValidateFlags(cmd)
   148  	cmdutil.AddDryRunFlag(cmd)
   149  
   150  	cmd.Flags().StringVar(&o.Username, "docker-username", o.Username, i18n.T("Username for Docker registry authentication"))
   151  	cmd.Flags().StringVar(&o.Password, "docker-password", o.Password, i18n.T("Password for Docker registry authentication"))
   152  	cmd.Flags().StringVar(&o.Email, "docker-email", o.Email, i18n.T("Email for Docker registry"))
   153  	cmd.Flags().StringVar(&o.Server, "docker-server", o.Server, i18n.T("Server location for Docker registry"))
   154  	cmd.Flags().BoolVar(&o.AppendHash, "append-hash", o.AppendHash, "Append a hash of the secret to its name.")
   155  	cmd.Flags().StringSliceVar(&o.FileSources, "from-file", o.FileSources, "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used.  Specifying a directory will iterate each named file in the directory that is a valid secret key.")
   156  
   157  	cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
   158  
   159  	return cmd
   160  }
   161  
   162  // Complete loads data from the command line environment
   163  func (o *CreateSecretDockerRegistryOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
   164  	var err error
   165  	o.Name, err = NameFromCommandArgs(cmd, args)
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	restConfig, err := f.ToRESTConfig()
   171  	if err != nil {
   172  		return err
   173  	}
   174  
   175  	o.Client, err = corev1client.NewForConfig(restConfig)
   176  	if err != nil {
   177  		return err
   178  	}
   179  
   180  	o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
   181  
   182  	o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
   183  	if err != nil {
   184  		return err
   185  	}
   186  
   187  	o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
   193  	printer, err := o.PrintFlags.ToPrinter()
   194  	if err != nil {
   195  		return err
   196  	}
   197  
   198  	o.PrintObj = func(obj runtime.Object) error {
   199  		return printer.PrintObj(obj, o.Out)
   200  	}
   201  
   202  	o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
   203  	if err != nil {
   204  		return err
   205  	}
   206  
   207  	return nil
   208  }
   209  
   210  // Validate checks if CreateSecretDockerRegistryOptions has sufficient value to run
   211  func (o *CreateSecretDockerRegistryOptions) Validate() error {
   212  	if len(o.Name) == 0 {
   213  		return fmt.Errorf("name must be specified")
   214  	}
   215  	if len(o.FileSources) == 0 && (len(o.Username) == 0 || len(o.Password) == 0 || len(o.Server) == 0) {
   216  		return fmt.Errorf("either --from-file or the combination of --docker-username, --docker-password and --docker-server is required")
   217  	}
   218  	return nil
   219  }
   220  
   221  // Run calls createSecretDockerRegistry which will create secretDockerRegistry based on CreateSecretDockerRegistryOptions
   222  // and makes an API call to the server
   223  func (o *CreateSecretDockerRegistryOptions) Run() error {
   224  	secretDockerRegistry, err := o.createSecretDockerRegistry()
   225  	if err != nil {
   226  		return err
   227  	}
   228  	err = util.CreateOrUpdateAnnotation(o.CreateAnnotation, secretDockerRegistry, scheme.DefaultJSONEncoder())
   229  	if err != nil {
   230  		return err
   231  	}
   232  	if o.DryRunStrategy != cmdutil.DryRunClient {
   233  		createOptions := metav1.CreateOptions{}
   234  		if o.FieldManager != "" {
   235  			createOptions.FieldManager = o.FieldManager
   236  		}
   237  		createOptions.FieldValidation = o.ValidationDirective
   238  		if o.DryRunStrategy == cmdutil.DryRunServer {
   239  			createOptions.DryRun = []string{metav1.DryRunAll}
   240  		}
   241  		secretDockerRegistry, err = o.Client.Secrets(o.Namespace).Create(context.TODO(), secretDockerRegistry, createOptions)
   242  		if err != nil {
   243  			return fmt.Errorf("failed to create secret %v", err)
   244  		}
   245  	}
   246  
   247  	return o.PrintObj(secretDockerRegistry)
   248  }
   249  
   250  // createSecretDockerRegistry fills in key value pair from the information given in
   251  // CreateSecretDockerRegistryOptions into *corev1.Secret
   252  func (o *CreateSecretDockerRegistryOptions) createSecretDockerRegistry() (*corev1.Secret, error) {
   253  	namespace := ""
   254  	if o.EnforceNamespace {
   255  		namespace = o.Namespace
   256  	}
   257  	secretDockerRegistry := newSecretObj(o.Name, namespace, corev1.SecretTypeDockerConfigJson)
   258  	if len(o.FileSources) > 0 {
   259  		if err := handleSecretFromFileSources(secretDockerRegistry, o.FileSources); err != nil {
   260  			return nil, err
   261  		}
   262  	} else {
   263  		dockerConfigJSONContent, err := handleDockerCfgJSONContent(o.Username, o.Password, o.Email, o.Server)
   264  		if err != nil {
   265  			return nil, err
   266  		}
   267  		secretDockerRegistry.Data[corev1.DockerConfigJsonKey] = dockerConfigJSONContent
   268  	}
   269  	if o.AppendHash {
   270  		hash, err := hash.SecretHash(secretDockerRegistry)
   271  		if err != nil {
   272  			return nil, err
   273  		}
   274  		secretDockerRegistry.Name = fmt.Sprintf("%s-%s", secretDockerRegistry.Name, hash)
   275  	}
   276  	return secretDockerRegistry, nil
   277  }
   278  
   279  // handleDockerCfgJSONContent serializes a ~/.docker/config.json file
   280  func handleDockerCfgJSONContent(username, password, email, server string) ([]byte, error) {
   281  	dockerConfigAuth := DockerConfigEntry{
   282  		Username: username,
   283  		Password: password,
   284  		Email:    email,
   285  		Auth:     encodeDockerConfigFieldAuth(username, password),
   286  	}
   287  	dockerConfigJSON := DockerConfigJSON{
   288  		Auths: map[string]DockerConfigEntry{server: dockerConfigAuth},
   289  	}
   290  
   291  	return json.Marshal(dockerConfigJSON)
   292  }
   293  
   294  // encodeDockerConfigFieldAuth returns base64 encoding of the username and password string
   295  func encodeDockerConfigFieldAuth(username, password string) string {
   296  	fieldValue := username + ":" + password
   297  	return base64.StdEncoding.EncodeToString([]byte(fieldValue))
   298  }
   299  

View as plain text