...

Source file src/k8s.io/kubectl/pkg/cmd/create/create_secret_tls.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  	"crypto/tls"
    22  	"fmt"
    23  	"os"
    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  	secretForTLSLong = templates.LongDesc(i18n.T(`
    42  		Create a TLS secret from the given public/private key pair.
    43  
    44  		The public/private key pair must exist beforehand. The public key certificate must be .PEM encoded and match
    45  		the given private key.`))
    46  
    47  	secretForTLSExample = templates.Examples(i18n.T(`
    48  	  # Create a new TLS secret named tls-secret with the given key pair
    49  	  kubectl create secret tls tls-secret --cert=path/to/tls.crt --key=path/to/tls.key`))
    50  )
    51  
    52  // CreateSecretTLSOptions holds the options for 'create secret tls' sub command
    53  type CreateSecretTLSOptions struct {
    54  	// PrintFlags holds options necessary for obtaining a printer
    55  	PrintFlags *genericclioptions.PrintFlags
    56  	PrintObj   func(obj runtime.Object) error
    57  
    58  	// Name is the name of this TLS secret.
    59  	Name string
    60  	// Key is the path to the user's private key.
    61  	Key string
    62  	// Cert is the path to the user's public key certificate.
    63  	Cert string
    64  	// AppendHash; if true, derive a hash from the Secret and append it to the name
    65  	AppendHash bool
    66  
    67  	FieldManager     string
    68  	CreateAnnotation bool
    69  	Namespace        string
    70  	EnforceNamespace bool
    71  
    72  	Client              corev1client.CoreV1Interface
    73  	DryRunStrategy      cmdutil.DryRunStrategy
    74  	ValidationDirective string
    75  
    76  	genericiooptions.IOStreams
    77  }
    78  
    79  // NewSecretTLSOptions creates a new *CreateSecretTLSOptions with default value
    80  func NewSecretTLSOptions(ioStrems genericiooptions.IOStreams) *CreateSecretTLSOptions {
    81  	return &CreateSecretTLSOptions{
    82  		PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
    83  		IOStreams:  ioStrems,
    84  	}
    85  }
    86  
    87  // NewCmdCreateSecretTLS is a macro command for creating secrets to work with TLS client or server
    88  func NewCmdCreateSecretTLS(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
    89  	o := NewSecretTLSOptions(ioStreams)
    90  
    91  	cmd := &cobra.Command{
    92  		Use:                   "tls NAME --cert=path/to/cert/file --key=path/to/key/file [--dry-run=server|client|none]",
    93  		DisableFlagsInUseLine: true,
    94  		Short:                 i18n.T("Create a TLS secret"),
    95  		Long:                  secretForTLSLong,
    96  		Example:               secretForTLSExample,
    97  		Run: func(cmd *cobra.Command, args []string) {
    98  			cmdutil.CheckErr(o.Complete(f, cmd, args))
    99  			cmdutil.CheckErr(o.Validate())
   100  			cmdutil.CheckErr(o.Run())
   101  		},
   102  	}
   103  
   104  	o.PrintFlags.AddFlags(cmd)
   105  
   106  	cmdutil.AddApplyAnnotationFlags(cmd)
   107  	cmdutil.AddValidateFlags(cmd)
   108  	cmdutil.AddDryRunFlag(cmd)
   109  
   110  	cmd.Flags().StringVar(&o.Cert, "cert", o.Cert, i18n.T("Path to PEM encoded public key certificate."))
   111  	cmd.Flags().StringVar(&o.Key, "key", o.Key, i18n.T("Path to private key associated with given certificate."))
   112  	cmd.Flags().BoolVar(&o.AppendHash, "append-hash", o.AppendHash, "Append a hash of the secret to its name.")
   113  
   114  	cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
   115  
   116  	return cmd
   117  }
   118  
   119  // Complete loads data from the command line environment
   120  func (o *CreateSecretTLSOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
   121  	var err error
   122  	o.Name, err = NameFromCommandArgs(cmd, args)
   123  	if err != nil {
   124  		return err
   125  	}
   126  
   127  	restConfig, err := f.ToRESTConfig()
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	o.Client, err = corev1client.NewForConfig(restConfig)
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
   138  
   139  	o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
   150  	printer, err := o.PrintFlags.ToPrinter()
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	o.PrintObj = func(obj runtime.Object) error {
   156  		return printer.PrintObj(obj, o.Out)
   157  	}
   158  
   159  	o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
   160  	if err != nil {
   161  		return err
   162  	}
   163  
   164  	return nil
   165  }
   166  
   167  // Validate checks if CreateSecretTLSOptions hass sufficient value to run
   168  func (o *CreateSecretTLSOptions) Validate() error {
   169  	// TODO: This is not strictly necessary. We can generate a self signed cert
   170  	// if no key/cert is given. The only requirement is that we either get both
   171  	// or none. See test/e2e/ingress_utils for self signed cert generation.
   172  	if len(o.Key) == 0 || len(o.Cert) == 0 {
   173  		return fmt.Errorf("key and cert must be specified")
   174  	}
   175  	return nil
   176  }
   177  
   178  // Run calls createSecretTLS which will create secretTLS based on CreateSecretTLSOptions
   179  // and makes an API call to the server
   180  func (o *CreateSecretTLSOptions) Run() error {
   181  	secretTLS, err := o.createSecretTLS()
   182  	if err != nil {
   183  		return err
   184  	}
   185  	err = util.CreateOrUpdateAnnotation(o.CreateAnnotation, secretTLS, scheme.DefaultJSONEncoder())
   186  	if err != nil {
   187  		return err
   188  	}
   189  	if o.DryRunStrategy != cmdutil.DryRunClient {
   190  		createOptions := metav1.CreateOptions{}
   191  		if o.FieldManager != "" {
   192  			createOptions.FieldManager = o.FieldManager
   193  		}
   194  		createOptions.FieldValidation = o.ValidationDirective
   195  		if o.DryRunStrategy == cmdutil.DryRunServer {
   196  			createOptions.DryRun = []string{metav1.DryRunAll}
   197  		}
   198  		secretTLS, err = o.Client.Secrets(o.Namespace).Create(context.TODO(), secretTLS, createOptions)
   199  		if err != nil {
   200  			return fmt.Errorf("failed to create secret %v", err)
   201  		}
   202  	}
   203  	return o.PrintObj(secretTLS)
   204  }
   205  
   206  // createSecretTLS fills in key value pair from the information given in
   207  // CreateSecretTLSOptions into *corev1.Secret
   208  func (o *CreateSecretTLSOptions) createSecretTLS() (*corev1.Secret, error) {
   209  	namespace := ""
   210  	if o.EnforceNamespace {
   211  		namespace = o.Namespace
   212  	}
   213  	tlsCert, err := readFile(o.Cert)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  	tlsKey, err := readFile(o.Key)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  	if _, err := tls.X509KeyPair(tlsCert, tlsKey); err != nil {
   222  		return nil, err
   223  	}
   224  	// TODO: Add more validation.
   225  	// 1. If the certificate contains intermediates, it is a valid chain.
   226  	// 2. Format etc.
   227  
   228  	secretTLS := newSecretObj(o.Name, namespace, corev1.SecretTypeTLS)
   229  	secretTLS.Data[corev1.TLSCertKey] = []byte(tlsCert)
   230  	secretTLS.Data[corev1.TLSPrivateKeyKey] = []byte(tlsKey)
   231  	if o.AppendHash {
   232  		hash, err := hash.SecretHash(secretTLS)
   233  		if err != nil {
   234  			return nil, err
   235  		}
   236  		secretTLS.Name = fmt.Sprintf("%s-%s", secretTLS.Name, hash)
   237  	}
   238  
   239  	return secretTLS, nil
   240  }
   241  
   242  // readFile just reads a file into a byte array.
   243  func readFile(file string) ([]byte, error) {
   244  	b, err := os.ReadFile(file)
   245  	if err != nil {
   246  		return []byte{}, fmt.Errorf("Cannot read file %v, %v", file, err)
   247  	}
   248  	return b, nil
   249  }
   250  

View as plain text