1
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
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
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
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
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
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
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
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
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
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
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