1
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
44
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
101 type CreateSecretOptions struct {
102
103 PrintFlags *genericclioptions.PrintFlags
104 PrintObj func(obj runtime.Object) error
105
106
107 Name string
108
109 Type string
110
111 FileSources []string
112
113 LiteralSources []string
114
115 EnvFileSources []string
116
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
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
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
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
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
232
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
261
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
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
311
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
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
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
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
373
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
400
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
410
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