...

Source file src/k8s.io/kubectl/pkg/cmd/certificates/certificates.go

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

     1  /*
     2  Copyright 2016 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 certificates
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  
    24  	"github.com/spf13/cobra"
    25  
    26  	certificatesv1 "k8s.io/api/certificates/v1"
    27  	corev1 "k8s.io/api/core/v1"
    28  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/cli-runtime/pkg/genericclioptions"
    33  	"k8s.io/cli-runtime/pkg/genericiooptions"
    34  	"k8s.io/cli-runtime/pkg/printers"
    35  	"k8s.io/cli-runtime/pkg/resource"
    36  	v1 "k8s.io/client-go/kubernetes/typed/certificates/v1"
    37  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    38  	"k8s.io/kubectl/pkg/scheme"
    39  	"k8s.io/kubectl/pkg/util/i18n"
    40  	"k8s.io/kubectl/pkg/util/templates"
    41  )
    42  
    43  // NewCmdCertificate returns `certificate` Cobra command
    44  func NewCmdCertificate(restClientGetter genericclioptions.RESTClientGetter, ioStreams genericiooptions.IOStreams) *cobra.Command {
    45  	cmd := &cobra.Command{
    46  		Use:                   "certificate SUBCOMMAND",
    47  		DisableFlagsInUseLine: true,
    48  		Short:                 i18n.T("Modify certificate resources"),
    49  		Long:                  i18n.T("Modify certificate resources."),
    50  		Run: func(cmd *cobra.Command, args []string) {
    51  			cmd.Help()
    52  		},
    53  	}
    54  
    55  	cmd.AddCommand(NewCmdCertificateApprove(restClientGetter, ioStreams))
    56  	cmd.AddCommand(NewCmdCertificateDeny(restClientGetter, ioStreams))
    57  
    58  	return cmd
    59  }
    60  
    61  // CertificateOptions declares the arguments accepted by the certificate command
    62  type CertificateOptions struct {
    63  	resource.FilenameOptions
    64  
    65  	PrintFlags *genericclioptions.PrintFlags
    66  	PrintObj   printers.ResourcePrinterFunc
    67  
    68  	csrNames    []string
    69  	outputStyle string
    70  
    71  	certificatesV1Client v1.CertificatesV1Interface
    72  	builder              *resource.Builder
    73  
    74  	genericiooptions.IOStreams
    75  }
    76  
    77  // NewCertificateOptions creates CertificateOptions struct for `certificate` command
    78  func NewCertificateOptions(ioStreams genericiooptions.IOStreams, operation string) *CertificateOptions {
    79  	return &CertificateOptions{
    80  		PrintFlags: genericclioptions.NewPrintFlags(operation).WithTypeSetter(scheme.Scheme),
    81  		IOStreams:  ioStreams,
    82  	}
    83  }
    84  
    85  // Complete loads data from the command environment
    86  func (o *CertificateOptions) Complete(restClientGetter genericclioptions.RESTClientGetter, cmd *cobra.Command, args []string) error {
    87  	o.csrNames = args
    88  	o.outputStyle = cmdutil.GetFlagString(cmd, "output")
    89  
    90  	printer, err := o.PrintFlags.ToPrinter()
    91  	if err != nil {
    92  		return err
    93  	}
    94  
    95  	o.PrintObj = func(obj runtime.Object, out io.Writer) error {
    96  		return printer.PrintObj(obj, out)
    97  	}
    98  
    99  	o.builder = resource.NewBuilder(restClientGetter)
   100  
   101  	clientConfig, err := restClientGetter.ToRESTConfig()
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	o.certificatesV1Client, err = v1.NewForConfig(clientConfig)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	return nil
   112  }
   113  
   114  // Validate checks if the provided `certificate` arguments are valid
   115  func (o *CertificateOptions) Validate() error {
   116  	if len(o.csrNames) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) {
   117  		return fmt.Errorf("one or more CSRs must be specified as <name> or -f <filename>")
   118  	}
   119  	return nil
   120  }
   121  
   122  // NewCmdCertificateApprove returns the `certificate approve` Cobra command
   123  func NewCmdCertificateApprove(restClientGetter genericclioptions.RESTClientGetter, ioStreams genericiooptions.IOStreams) *cobra.Command {
   124  	o := NewCertificateOptions(ioStreams, "approved")
   125  
   126  	cmd := &cobra.Command{
   127  		Use:                   "approve (-f FILENAME | NAME)",
   128  		DisableFlagsInUseLine: true,
   129  		Short:                 i18n.T("Approve a certificate signing request"),
   130  		Long: templates.LongDesc(i18n.T(`
   131  		Approve a certificate signing request.
   132  
   133  		kubectl certificate approve allows a cluster admin to approve a certificate
   134  		signing request (CSR). This action tells a certificate signing controller to
   135  		issue a certificate to the requester with the attributes requested in the CSR.
   136  
   137  		SECURITY NOTICE: Depending on the requested attributes, the issued certificate
   138  		can potentially grant a requester access to cluster resources or to authenticate
   139  		as a requested identity. Before approving a CSR, ensure you understand what the
   140  		signed certificate can do.
   141  		`)),
   142  		Example: templates.Examples(i18n.T(`
   143  			# Approve CSR 'csr-sqgzp'
   144  			kubectl certificate approve csr-sqgzp
   145  		`)),
   146  		Run: func(cmd *cobra.Command, args []string) {
   147  			cmdutil.CheckErr(o.Complete(restClientGetter, cmd, args))
   148  			cmdutil.CheckErr(o.Validate())
   149  			cmdutil.CheckErr(o.RunCertificateApprove(cmdutil.GetFlagBool(cmd, "force")))
   150  		},
   151  	}
   152  
   153  	o.PrintFlags.AddFlags(cmd)
   154  
   155  	cmd.Flags().Bool("force", false, "Update the CSR even if it is already approved.")
   156  	cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to update")
   157  
   158  	return cmd
   159  }
   160  
   161  // RunCertificateApprove approves a certificate signing request
   162  func (o *CertificateOptions) RunCertificateApprove(force bool) error {
   163  	return o.modifyCertificateCondition(
   164  		o.builder,
   165  		force,
   166  		addConditionIfNeeded(string(certificatesv1.CertificateDenied), string(certificatesv1.CertificateApproved), "KubectlApprove", "This CSR was approved by kubectl certificate approve."),
   167  	)
   168  }
   169  
   170  // NewCmdCertificateDeny returns the `certificate deny` Cobra command
   171  func NewCmdCertificateDeny(restClientGetter genericclioptions.RESTClientGetter, ioStreams genericiooptions.IOStreams) *cobra.Command {
   172  	o := NewCertificateOptions(ioStreams, "denied")
   173  
   174  	cmd := &cobra.Command{
   175  		Use:                   "deny (-f FILENAME | NAME)",
   176  		DisableFlagsInUseLine: true,
   177  		Short:                 i18n.T("Deny a certificate signing request"),
   178  		Long: templates.LongDesc(i18n.T(`
   179  		Deny a certificate signing request.
   180  
   181  		kubectl certificate deny allows a cluster admin to deny a certificate
   182  		signing request (CSR). This action tells a certificate signing controller to
   183  		not to issue a certificate to the requester.
   184  		`)),
   185  		Example: templates.Examples(i18n.T(`
   186  			# Deny CSR 'csr-sqgzp'
   187  			kubectl certificate deny csr-sqgzp
   188  		`)),
   189  		Run: func(cmd *cobra.Command, args []string) {
   190  			cmdutil.CheckErr(o.Complete(restClientGetter, cmd, args))
   191  			cmdutil.CheckErr(o.Validate())
   192  			cmdutil.CheckErr(o.RunCertificateDeny(cmdutil.GetFlagBool(cmd, "force")))
   193  		},
   194  	}
   195  
   196  	o.PrintFlags.AddFlags(cmd)
   197  
   198  	cmd.Flags().Bool("force", false, "Update the CSR even if it is already denied.")
   199  	cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to update")
   200  
   201  	return cmd
   202  }
   203  
   204  // RunCertificateDeny denies a certificate signing request
   205  func (o *CertificateOptions) RunCertificateDeny(force bool) error {
   206  	return o.modifyCertificateCondition(
   207  		o.builder,
   208  		force,
   209  		addConditionIfNeeded(string(certificatesv1.CertificateApproved), string(certificatesv1.CertificateDenied), "KubectlDeny", "This CSR was denied by kubectl certificate deny."),
   210  	)
   211  }
   212  
   213  func (o *CertificateOptions) modifyCertificateCondition(builder *resource.Builder, force bool, modify func(csr runtime.Object) (runtime.Object, bool, error)) error {
   214  	var found int
   215  	r := builder.
   216  		Unstructured().
   217  		ContinueOnError().
   218  		FilenameParam(false, &o.FilenameOptions).
   219  		ResourceNames("certificatesigningrequests", o.csrNames...).
   220  		RequireObject(true).
   221  		Flatten().
   222  		Latest().
   223  		Do()
   224  	err := r.Visit(func(info *resource.Info, err error) error {
   225  		if err != nil {
   226  			return err
   227  		}
   228  		for i := 0; ; i++ {
   229  			obj, ok := info.Object.(*unstructured.Unstructured)
   230  			if !ok {
   231  				return fmt.Errorf("expected *unstructured.Unstructured, got %T", obj)
   232  			}
   233  			if want, got := certificatesv1.Kind("CertificateSigningRequest"), obj.GetObjectKind().GroupVersionKind().GroupKind(); want != got {
   234  				return fmt.Errorf("can only handle %s objects, got %s", want.String(), got.String())
   235  			}
   236  			var csr runtime.Object
   237  			// get a typed object
   238  			csr, err = o.certificatesV1Client.CertificateSigningRequests().Get(context.TODO(), obj.GetName(), metav1.GetOptions{})
   239  			if apierrors.IsNotFound(err) {
   240  				return fmt.Errorf("could not find v1 version of %s: %v", obj.GetName(), err)
   241  			}
   242  			if err != nil {
   243  				return err
   244  			}
   245  
   246  			modifiedCSR, hasCondition, err := modify(csr)
   247  			if err != nil {
   248  				return err
   249  			}
   250  			if !hasCondition || force {
   251  				if mCSR, ok := modifiedCSR.(*certificatesv1.CertificateSigningRequest); ok {
   252  					_, err = o.certificatesV1Client.CertificateSigningRequests().UpdateApproval(context.TODO(), mCSR.Name, mCSR, metav1.UpdateOptions{})
   253  				} else {
   254  					return fmt.Errorf("can only handle certificates.k8s.io CertificateSigningRequest objects, got %T", mCSR)
   255  				}
   256  
   257  				if apierrors.IsConflict(err) && i < 10 {
   258  					if err := info.Get(); err != nil {
   259  						return err
   260  					}
   261  					continue
   262  				}
   263  				if err != nil {
   264  					return err
   265  				}
   266  			}
   267  			break
   268  		}
   269  		found++
   270  
   271  		return o.PrintObj(info.Object, o.Out)
   272  	})
   273  	if found == 0 && err == nil {
   274  		fmt.Fprintf(o.Out, "No resources found\n")
   275  	}
   276  	return err
   277  }
   278  
   279  func addConditionIfNeeded(mustNotHaveConditionType, conditionType, reason, message string) func(runtime.Object) (runtime.Object, bool, error) {
   280  	return func(obj runtime.Object) (runtime.Object, bool, error) {
   281  		if csr, ok := obj.(*certificatesv1.CertificateSigningRequest); ok {
   282  			var alreadyHasCondition bool
   283  			for _, c := range csr.Status.Conditions {
   284  				if string(c.Type) == mustNotHaveConditionType {
   285  					return nil, false, fmt.Errorf("certificate signing request %q is already %s", csr.Name, c.Type)
   286  				}
   287  				if string(c.Type) == conditionType {
   288  					alreadyHasCondition = true
   289  				}
   290  			}
   291  			if alreadyHasCondition {
   292  				return csr, true, nil
   293  			}
   294  			csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1.CertificateSigningRequestCondition{
   295  				Type:           certificatesv1.RequestConditionType(conditionType),
   296  				Status:         corev1.ConditionTrue,
   297  				Reason:         reason,
   298  				Message:        message,
   299  				LastUpdateTime: metav1.Now(),
   300  			})
   301  			return csr, false, nil
   302  		} else {
   303  			return csr, false, nil
   304  		}
   305  	}
   306  }
   307  

View as plain text