...

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

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

     1  /*
     2  Copyright 2015 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  	"fmt"
    22  	"os"
    23  	"path"
    24  	"strings"
    25  
    26  	"github.com/spf13/cobra"
    27  
    28  	corev1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/util/validation"
    32  	"k8s.io/cli-runtime/pkg/genericclioptions"
    33  	"k8s.io/cli-runtime/pkg/genericiooptions"
    34  	corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
    35  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    36  	"k8s.io/kubectl/pkg/scheme"
    37  	"k8s.io/kubectl/pkg/util"
    38  	"k8s.io/kubectl/pkg/util/hash"
    39  	"k8s.io/kubectl/pkg/util/i18n"
    40  	"k8s.io/kubectl/pkg/util/templates"
    41  )
    42  
    43  // NewCmdCreateSecret groups subcommands to create various types of secrets.
    44  // This is the entry point of create_secret.go which will be called by create.go
    45  func NewCmdCreateSecret(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
    46  	cmd := &cobra.Command{
    47  		Use:                   "secret (docker-registry | generic | tls)",
    48  		DisableFlagsInUseLine: true,
    49  		Short:                 i18n.T("Create a secret using a specified subcommand"),
    50  		Long:                  secretLong,
    51  		Run:                   cmdutil.DefaultSubCommandRun(ioStreams.ErrOut),
    52  	}
    53  	cmd.AddCommand(NewCmdCreateSecretDockerRegistry(f, ioStreams))
    54  	cmd.AddCommand(NewCmdCreateSecretTLS(f, ioStreams))
    55  	cmd.AddCommand(NewCmdCreateSecretGeneric(f, ioStreams))
    56  
    57  	return cmd
    58  }
    59  
    60  var (
    61  	secretLong = templates.LongDesc(i18n.T(`
    62  		Create a secret with specified type.
    63  		
    64  		A docker-registry type secret is for accessing a container registry.
    65  
    66  		A generic type secret indicate an Opaque secret type.
    67  
    68  		A tls type secret holds TLS certificate and its associated key.`))
    69  
    70  	secretForGenericLong = templates.LongDesc(i18n.T(`
    71  		Create a secret based on a file, directory, or specified literal value.
    72  
    73  		A single secret may package one or more key/value pairs.
    74  
    75  		When creating a secret based on a file, the key will default to the basename of the file, and the value will
    76  		default to the file content. If the basename is an invalid key or you wish to chose your own, you may specify
    77  		an alternate key.
    78  
    79  		When creating a secret based on a directory, each file whose basename is a valid key in the directory will be
    80  		packaged into the secret. Any directory entries except regular files are ignored (e.g. subdirectories,
    81  		symlinks, devices, pipes, etc).`))
    82  
    83  	secretForGenericExample = templates.Examples(i18n.T(`
    84  	  # Create a new secret named my-secret with keys for each file in folder bar
    85  	  kubectl create secret generic my-secret --from-file=path/to/bar
    86  
    87  	  # Create a new secret named my-secret with specified keys instead of names on disk
    88  	  kubectl create secret generic my-secret --from-file=ssh-privatekey=path/to/id_rsa --from-file=ssh-publickey=path/to/id_rsa.pub
    89  
    90  	  # Create a new secret named my-secret with key1=supersecret and key2=topsecret
    91  	  kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret
    92  
    93  	  # Create a new secret named my-secret using a combination of a file and a literal
    94  	  kubectl create secret generic my-secret --from-file=ssh-privatekey=path/to/id_rsa --from-literal=passphrase=topsecret
    95  
    96  	  # Create a new secret named my-secret from env files
    97  	  kubectl create secret generic my-secret --from-env-file=path/to/foo.env --from-env-file=path/to/bar.env`))
    98  )
    99  
   100  // CreateSecretOptions holds the options for 'create secret' sub command
   101  type CreateSecretOptions struct {
   102  	// PrintFlags holds options necessary for obtaining a printer
   103  	PrintFlags *genericclioptions.PrintFlags
   104  	PrintObj   func(obj runtime.Object) error
   105  
   106  	// Name of secret (required)
   107  	Name string
   108  	// Type of secret (optional)
   109  	Type string
   110  	// FileSources to derive the secret from (optional)
   111  	FileSources []string
   112  	// LiteralSources to derive the secret from (optional)
   113  	LiteralSources []string
   114  	// EnvFileSources to derive the secret from (optional)
   115  	EnvFileSources []string
   116  	// AppendHash; if true, derive a hash from the Secret data and type and append it to the name
   117  	AppendHash bool
   118  
   119  	FieldManager     string
   120  	CreateAnnotation bool
   121  	Namespace        string
   122  	EnforceNamespace bool
   123  
   124  	Client              corev1client.CoreV1Interface
   125  	DryRunStrategy      cmdutil.DryRunStrategy
   126  	ValidationDirective string
   127  
   128  	genericiooptions.IOStreams
   129  }
   130  
   131  // NewSecretOptions creates a new *CreateSecretOptions with default value
   132  func NewSecretOptions(ioStreams genericiooptions.IOStreams) *CreateSecretOptions {
   133  	return &CreateSecretOptions{
   134  		PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
   135  		IOStreams:  ioStreams,
   136  	}
   137  }
   138  
   139  // NewCmdCreateSecretGeneric is a command to create generic secrets from files, directories, or literal values
   140  func NewCmdCreateSecretGeneric(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
   141  	o := NewSecretOptions(ioStreams)
   142  
   143  	cmd := &cobra.Command{
   144  		Use:                   "generic NAME [--type=string] [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run=server|client|none]",
   145  		DisableFlagsInUseLine: true,
   146  		Short:                 i18n.T("Create a secret from a local file, directory, or literal value"),
   147  		Long:                  secretForGenericLong,
   148  		Example:               secretForGenericExample,
   149  		Run: func(cmd *cobra.Command, args []string) {
   150  			cmdutil.CheckErr(o.Complete(f, cmd, args))
   151  			cmdutil.CheckErr(o.Validate())
   152  			cmdutil.CheckErr(o.Run())
   153  		},
   154  	}
   155  	o.PrintFlags.AddFlags(cmd)
   156  
   157  	cmdutil.AddApplyAnnotationFlags(cmd)
   158  	cmdutil.AddValidateFlags(cmd)
   159  	cmdutil.AddDryRunFlag(cmd)
   160  
   161  	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.")
   162  	cmd.Flags().StringArrayVar(&o.LiteralSources, "from-literal", o.LiteralSources, "Specify a key and literal value to insert in secret (i.e. mykey=somevalue)")
   163  	cmd.Flags().StringSliceVar(&o.EnvFileSources, "from-env-file", o.EnvFileSources, "Specify the path to a file to read lines of key=val pairs to create a secret.")
   164  	cmd.Flags().StringVar(&o.Type, "type", o.Type, i18n.T("The type of secret to create"))
   165  	cmd.Flags().BoolVar(&o.AppendHash, "append-hash", o.AppendHash, "Append a hash of the secret to its name.")
   166  
   167  	cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
   168  
   169  	return cmd
   170  }
   171  
   172  // Complete loads data from the command line environment
   173  func (o *CreateSecretOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
   174  	var err error
   175  	o.Name, err = NameFromCommandArgs(cmd, args)
   176  	if err != nil {
   177  		return err
   178  	}
   179  
   180  	restConfig, err := f.ToRESTConfig()
   181  	if err != nil {
   182  		return err
   183  	}
   184  
   185  	o.Client, err = corev1client.NewForConfig(restConfig)
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
   191  
   192  	o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
   193  	if err != nil {
   194  		return err
   195  	}
   196  
   197  	o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
   198  	if err != nil {
   199  		return err
   200  	}
   201  
   202  	cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
   203  	printer, err := o.PrintFlags.ToPrinter()
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	o.PrintObj = func(obj runtime.Object) error {
   209  		return printer.PrintObj(obj, o.Out)
   210  	}
   211  
   212  	o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
   213  	if err != nil {
   214  		return err
   215  	}
   216  
   217  	return nil
   218  }
   219  
   220  // Validate checks if CreateSecretOptions has sufficient value to run
   221  func (o *CreateSecretOptions) Validate() error {
   222  	if len(o.Name) == 0 {
   223  		return fmt.Errorf("name must be specified")
   224  	}
   225  	if len(o.EnvFileSources) > 0 && (len(o.FileSources) > 0 || len(o.LiteralSources) > 0) {
   226  		return fmt.Errorf("from-env-file cannot be combined with from-file or from-literal")
   227  	}
   228  	return nil
   229  }
   230  
   231  // Run calls createSecret which will create secret based on CreateSecretOptions
   232  // and makes an API call to the server
   233  func (o *CreateSecretOptions) Run() error {
   234  	secret, err := o.createSecret()
   235  	if err != nil {
   236  		return err
   237  	}
   238  	err = util.CreateOrUpdateAnnotation(o.CreateAnnotation, secret, scheme.DefaultJSONEncoder())
   239  	if err != nil {
   240  		return err
   241  	}
   242  	if o.DryRunStrategy != cmdutil.DryRunClient {
   243  		createOptions := metav1.CreateOptions{}
   244  		if o.FieldManager != "" {
   245  			createOptions.FieldManager = o.FieldManager
   246  		}
   247  		createOptions.FieldValidation = o.ValidationDirective
   248  		if o.DryRunStrategy == cmdutil.DryRunServer {
   249  			createOptions.DryRun = []string{metav1.DryRunAll}
   250  		}
   251  		secret, err = o.Client.Secrets(o.Namespace).Create(context.TODO(), secret, createOptions)
   252  		if err != nil {
   253  			return fmt.Errorf("failed to create secret %v", err)
   254  		}
   255  	}
   256  
   257  	return o.PrintObj(secret)
   258  }
   259  
   260  // createSecret fills in key value pair from the information given in
   261  // CreateSecretOptions into *corev1.Secret
   262  func (o *CreateSecretOptions) createSecret() (*corev1.Secret, error) {
   263  	namespace := ""
   264  	if o.EnforceNamespace {
   265  		namespace = o.Namespace
   266  	}
   267  	secret := newSecretObj(o.Name, namespace, corev1.SecretType(o.Type))
   268  	if len(o.LiteralSources) > 0 {
   269  		if err := handleSecretFromLiteralSources(secret, o.LiteralSources); err != nil {
   270  			return nil, err
   271  		}
   272  	}
   273  	if len(o.FileSources) > 0 {
   274  		if err := handleSecretFromFileSources(secret, o.FileSources); err != nil {
   275  			return nil, err
   276  		}
   277  	}
   278  	if len(o.EnvFileSources) > 0 {
   279  		if err := handleSecretFromEnvFileSources(secret, o.EnvFileSources); err != nil {
   280  			return nil, err
   281  		}
   282  	}
   283  	if o.AppendHash {
   284  		hash, err := hash.SecretHash(secret)
   285  		if err != nil {
   286  			return nil, err
   287  		}
   288  		secret.Name = fmt.Sprintf("%s-%s", secret.Name, hash)
   289  	}
   290  
   291  	return secret, nil
   292  }
   293  
   294  // newSecretObj will create a new Secret Object given name, namespace and secretType
   295  func newSecretObj(name, namespace string, secretType corev1.SecretType) *corev1.Secret {
   296  	return &corev1.Secret{
   297  		TypeMeta: metav1.TypeMeta{
   298  			APIVersion: corev1.SchemeGroupVersion.String(),
   299  			Kind:       "Secret",
   300  		},
   301  		ObjectMeta: metav1.ObjectMeta{
   302  			Name:      name,
   303  			Namespace: namespace,
   304  		},
   305  		Type: secretType,
   306  		Data: map[string][]byte{},
   307  	}
   308  }
   309  
   310  // handleSecretFromLiteralSources adds the specified literal source
   311  // information into the provided secret
   312  func handleSecretFromLiteralSources(secret *corev1.Secret, literalSources []string) error {
   313  	for _, literalSource := range literalSources {
   314  		keyName, value, err := util.ParseLiteralSource(literalSource)
   315  		if err != nil {
   316  			return err
   317  		}
   318  		if err = addKeyFromLiteralToSecret(secret, keyName, []byte(value)); err != nil {
   319  			return err
   320  		}
   321  	}
   322  
   323  	return nil
   324  }
   325  
   326  // handleSecretFromFileSources adds the specified file source information into the provided secret
   327  func handleSecretFromFileSources(secret *corev1.Secret, fileSources []string) error {
   328  	for _, fileSource := range fileSources {
   329  		keyName, filePath, err := util.ParseFileSource(fileSource)
   330  		if err != nil {
   331  			return err
   332  		}
   333  		fileInfo, err := os.Stat(filePath)
   334  		if err != nil {
   335  			switch err := err.(type) {
   336  			case *os.PathError:
   337  				return fmt.Errorf("error reading %s: %v", filePath, err.Err)
   338  			default:
   339  				return fmt.Errorf("error reading %s: %v", filePath, err)
   340  			}
   341  		}
   342  		// if the filePath is a directory
   343  		if fileInfo.IsDir() {
   344  			if strings.Contains(fileSource, "=") {
   345  				return fmt.Errorf("cannot give a key name for a directory path")
   346  			}
   347  			fileList, err := os.ReadDir(filePath)
   348  			if err != nil {
   349  				return fmt.Errorf("error listing files in %s: %v", filePath, err)
   350  			}
   351  			for _, item := range fileList {
   352  				itemPath := path.Join(filePath, item.Name())
   353  				if item.Type().IsRegular() {
   354  					keyName = item.Name()
   355  					if err := addKeyFromFileToSecret(secret, keyName, itemPath); err != nil {
   356  						return err
   357  					}
   358  				}
   359  			}
   360  			// if the filepath is a file
   361  		} else {
   362  			if err := addKeyFromFileToSecret(secret, keyName, filePath); err != nil {
   363  				return err
   364  			}
   365  		}
   366  
   367  	}
   368  
   369  	return nil
   370  }
   371  
   372  // handleSecretFromEnvFileSources adds the specified env files source information
   373  // into the provided secret
   374  func handleSecretFromEnvFileSources(secret *corev1.Secret, envFileSources []string) error {
   375  	for _, envFileSource := range envFileSources {
   376  		info, err := os.Stat(envFileSource)
   377  		if err != nil {
   378  			switch err := err.(type) {
   379  			case *os.PathError:
   380  				return fmt.Errorf("error reading %s: %v", envFileSource, err.Err)
   381  			default:
   382  				return fmt.Errorf("error reading %s: %v", envFileSource, err)
   383  			}
   384  		}
   385  		if info.IsDir() {
   386  			return fmt.Errorf("env secret file cannot be a directory")
   387  		}
   388  		err = cmdutil.AddFromEnvFile(envFileSource, func(key, value string) error {
   389  			return addKeyFromLiteralToSecret(secret, key, []byte(value))
   390  		})
   391  		if err != nil {
   392  			return err
   393  		}
   394  	}
   395  
   396  	return nil
   397  }
   398  
   399  // addKeyFromFileToSecret adds a key with the given name to a Secret, populating
   400  // the value with the content of the given file path, or returns an error.
   401  func addKeyFromFileToSecret(secret *corev1.Secret, keyName, filePath string) error {
   402  	data, err := os.ReadFile(filePath)
   403  	if err != nil {
   404  		return err
   405  	}
   406  	return addKeyFromLiteralToSecret(secret, keyName, data)
   407  }
   408  
   409  // addKeyFromLiteralToSecret adds the given key and data to the given secret,
   410  // returning an error if the key is not valid or if the key already exists.
   411  func addKeyFromLiteralToSecret(secret *corev1.Secret, keyName string, data []byte) error {
   412  	if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
   413  		return fmt.Errorf("%q is not valid key name for a Secret %s", keyName, strings.Join(errs, ";"))
   414  	}
   415  	if _, entryExists := secret.Data[keyName]; entryExists {
   416  		return fmt.Errorf("cannot add key %s, another key by that name already exists", keyName)
   417  	}
   418  	secret.Data[keyName] = data
   419  
   420  	return nil
   421  }
   422  

View as plain text