diff --git a/cmd/kops/create_keypair.go b/cmd/kops/create_keypair.go index d4b9ae5a4db9f..08ce985c3f5ce 100644 --- a/cmd/kops/create_keypair.go +++ b/cmd/kops/create_keypair.go @@ -26,7 +26,9 @@ import ( "time" "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/klog/v2" + "k8s.io/kops/pkg/commands/commandutils" "k8s.io/kops/cmd/kops/util" "k8s.io/kops/pkg/pki" @@ -39,7 +41,22 @@ import ( var ( createKeypairLong = templates.LongDesc(i18n.T(` Add a CA certificate and private key to a keyset. - `)) + + If neither a certificate nor a private key is provided, a new self-signed + certificate and private key will be generated. + + If no certificate is provided but a private key is, a self-signed + certificate will be generated from the provided private key. + + If a certificate is provided but no private key is, the certificate + will be added to the keyset without a private key. Such a certificate + cannot be made primary. + + One of the certificate/private key pairs in each keyset must be primary. + The primary keypair is the one used to issue certificates (or, for the + "service-account" keyset, service-account tokens). As a consequence, the + first entry in a keyset must be made primary. + `)) createKeypairExample = templates.Examples(i18n.T(` Add a CA certificate and private key to a keyset. @@ -59,43 +76,45 @@ type CreateKeypairOptions struct { Primary bool } -var keysetCommonNames = map[string]string{ - "ca": "kubernetes", - "etcd-clients-ca-cilium": "etcd-clients-ca-cilium", - "service-account": "service-account", -} +var rotatableKeysets = sets.NewString( + "ca", + "etcd-clients-ca-cilium", + "service-account", +) // NewCmdCreateKeypair returns a create keypair command. func NewCmdCreateKeypair(f *util.Factory, out io.Writer) *cobra.Command { options := &CreateKeypairOptions{} cmd := &cobra.Command{ - Use: "keypair KEYSET", + Use: "keypair keyset", Short: createKeypairShort, Long: createKeypairLong, Example: createKeypairExample, - Run: func(cmd *cobra.Command, args []string) { - ctx := context.TODO() - - options.ClusterName = rootCommand.ClusterName() + Args: func(cmd *cobra.Command, args []string) error { + options.ClusterName = rootCommand.ClusterName(true) if options.ClusterName == "" { - exitWithError(fmt.Errorf("--name is required")) - return + return fmt.Errorf("--name is required") } if len(args) == 0 { - exitWithError(fmt.Errorf("must specify name of keyset to add keypair to")) - } - if len(args) != 1 { - exitWithError(fmt.Errorf("can only add to one keyset at a time")) + return fmt.Errorf("must specify name of keyset to add keypair to") } + options.Keyset = args[0] - err := RunCreateKeypair(ctx, f, out, options) - if err != nil { - exitWithError(err) + if len(args) != 1 { + return fmt.Errorf("can only add to one keyset at a time") } + + return nil + }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return completeCreateKeyset(options, args, toComplete) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return RunCreateKeypair(context.TODO(), f, out, options) }, } @@ -108,8 +127,7 @@ func NewCmdCreateKeypair(f *util.Factory, out io.Writer) *cobra.Command { // RunCreateKeypair adds a custom CA certificate and private key. func RunCreateKeypair(ctx context.Context, f *util.Factory, out io.Writer, options *CreateKeypairOptions) error { - commonName := keysetCommonNames[options.Keyset] - if commonName == "" { + if !rotatableKeysets.Has(options.Keyset) { return fmt.Errorf("adding keypair to %q is not supported", options.Keyset) } @@ -151,6 +169,10 @@ func RunCreateKeypair(ctx context.Context, f *util.Factory, out io.Writer, optio } } + commonName := options.Keyset + if commonName == "ca" { + commonName = "kubernetes" + } req := pki.IssueCertRequest{ Type: "ca", Subject: pkix.Name{CommonName: "cn=" + commonName}, @@ -203,3 +225,61 @@ func RunCreateKeypair(ctx context.Context, f *util.Factory, out io.Writer, optio } return nil } + +func completeCreateKeyset(options *CreateKeypairOptions, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + commandutils.ConfigureKlogForCompletion() + + options.ClusterName = rootCommand.ClusterName(false) + + if options.ClusterName == "" { + return []string{"--name"}, cobra.ShellCompDirectiveNoFileComp + } + + ctx := context.TODO() + cluster, err := GetCluster(ctx, &rootCommand, options.ClusterName) + if err != nil { + return commandutils.CompletionError("getting cluster", err) + } + + clientSet, err := rootCommand.Clientset() + if err != nil { + return commandutils.CompletionError("getting clientset", err) + } + + keyStore, err := clientSet.KeyStore(cluster) + if err != nil { + return commandutils.CompletionError("getting keystore", err) + } + + if len(args) == 0 { + list, err := keyStore.ListKeysets() + if err != nil { + return commandutils.CompletionError("listing keysets", err) + } + + var keysets []string + for name := range list { + if rotatableKeysets.Has(name) { + keysets = append(keysets, name) + } + } + + return keysets, cobra.ShellCompDirectiveNoFileComp + } + + if len(args) > 1 { + return commandutils.CompletionError("too many arguments", nil) + } + + var flags []string + if options.CertPath == "" { + flags = append(flags, "--cert") + } + if options.PrivateKeyPath == "" { + flags = append(flags, "--key") + } + if !options.Primary && (options.CertPath == "" || options.PrivateKeyPath != "") { + flags = append(flags, "--primary") + } + return flags, cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/kops/create_secret_cilium_encryptionconfig.go b/cmd/kops/create_secret_cilium_encryptionconfig.go index fd3cbb65419c8..2b07320be4fe0 100644 --- a/cmd/kops/create_secret_cilium_encryptionconfig.go +++ b/cmd/kops/create_secret_cilium_encryptionconfig.go @@ -78,7 +78,7 @@ func NewCmdCreateSecretCiliumEncryptionConfig(f *util.Factory, out io.Writer) *c exitWithError(err) } - options.ClusterName = rootCommand.ClusterName() + options.ClusterName = rootCommand.ClusterName(true) err = RunCreateSecretCiliumEncryptionConfig(ctx, f, os.Stdout, options) if err != nil { diff --git a/cmd/kops/create_secret_dockerconfig.go b/cmd/kops/create_secret_dockerconfig.go index f19d684089fc9..369757582307c 100644 --- a/cmd/kops/create_secret_dockerconfig.go +++ b/cmd/kops/create_secret_dockerconfig.go @@ -83,7 +83,7 @@ func NewCmdCreateSecretDockerConfig(f *util.Factory, out io.Writer) *cobra.Comma exitWithError(err) } - options.ClusterName = rootCommand.ClusterName() + options.ClusterName = rootCommand.ClusterName(true) err = RunCreateSecretDockerConfig(ctx, f, os.Stdout, options) if err != nil { diff --git a/cmd/kops/create_secret_encryptionconfig.go b/cmd/kops/create_secret_encryptionconfig.go index 49d2049ffa723..45c93c6367eb6 100644 --- a/cmd/kops/create_secret_encryptionconfig.go +++ b/cmd/kops/create_secret_encryptionconfig.go @@ -78,7 +78,7 @@ func NewCmdCreateSecretEncryptionConfig(f *util.Factory, out io.Writer) *cobra.C exitWithError(err) } - options.ClusterName = rootCommand.ClusterName() + options.ClusterName = rootCommand.ClusterName(true) err = RunCreateSecretEncryptionConfig(ctx, f, os.Stdout, options) if err != nil { diff --git a/cmd/kops/create_secret_sshpublickey.go b/cmd/kops/create_secret_sshpublickey.go index ae2d506fd5727..fd5550dac8780 100644 --- a/cmd/kops/create_secret_sshpublickey.go +++ b/cmd/kops/create_secret_sshpublickey.go @@ -73,7 +73,7 @@ func NewCmdCreateSecretPublicKey(f *util.Factory, out io.Writer) *cobra.Command exitWithError(err) } - options.ClusterName = rootCommand.ClusterName() + options.ClusterName = rootCommand.ClusterName(true) err = RunCreateSecretPublicKey(ctx, f, os.Stdout, options) if err != nil { diff --git a/cmd/kops/create_secret_weave_encryptionconfig.go b/cmd/kops/create_secret_weave_encryptionconfig.go index bb62b021b1cf6..d946af5b201cb 100644 --- a/cmd/kops/create_secret_weave_encryptionconfig.go +++ b/cmd/kops/create_secret_weave_encryptionconfig.go @@ -79,7 +79,7 @@ func NewCmdCreateSecretWeaveEncryptionConfig(f *util.Factory, out io.Writer) *co exitWithError(err) } - options.ClusterName = rootCommand.ClusterName() + options.ClusterName = rootCommand.ClusterName(true) err = RunCreateSecretWeaveEncryptionConfig(ctx, f, options) if err != nil { diff --git a/cmd/kops/delete_instance.go b/cmd/kops/delete_instance.go index 4b7e9259c20c1..cae9599073bfc 100644 --- a/cmd/kops/delete_instance.go +++ b/cmd/kops/delete_instance.go @@ -129,7 +129,7 @@ func NewCmdDeleteInstance(f *util.Factory, out io.Writer) *cobra.Command { cmd.Run = func(cmd *cobra.Command, args []string) { ctx := context.TODO() - clusterName := rootCommand.ClusterName() + clusterName := rootCommand.ClusterName(true) if clusterName == "" { exitWithError(fmt.Errorf("--name is required")) diff --git a/cmd/kops/delete_instancegroup.go b/cmd/kops/delete_instancegroup.go index 130060b4febda..ccc4d5d289d5b 100644 --- a/cmd/kops/delete_instancegroup.go +++ b/cmd/kops/delete_instancegroup.go @@ -78,7 +78,7 @@ func NewCmdDeleteInstanceGroup(f *util.Factory, out io.Writer) *cobra.Command { groupName := args[0] options.GroupName = groupName - options.ClusterName = rootCommand.ClusterName() + options.ClusterName = rootCommand.ClusterName(true) if !options.Yes { message := fmt.Sprintf("Do you really want to delete instance group %q? This action cannot be undone.", groupName) diff --git a/cmd/kops/delete_secret.go b/cmd/kops/delete_secret.go index e521d19ae3541..9f86d3a0942e5 100644 --- a/cmd/kops/delete_secret.go +++ b/cmd/kops/delete_secret.go @@ -71,7 +71,7 @@ func NewCmdDeleteSecret(f *util.Factory, out io.Writer) *cobra.Command { options.SecretID = args[2] } - options.ClusterName = rootCommand.ClusterName() + options.ClusterName = rootCommand.ClusterName(true) err := RunDeleteSecret(ctx, f, out, options) if err != nil { diff --git a/cmd/kops/distrust_keypair.go b/cmd/kops/distrust_keypair.go index a677a40c52029..f4b028fa08dc0 100644 --- a/cmd/kops/distrust_keypair.go +++ b/cmd/kops/distrust_keypair.go @@ -62,7 +62,7 @@ func NewCmdDistrustKeypair(f *util.Factory, out io.Writer) *cobra.Command { Run: func(cmd *cobra.Command, args []string) { ctx := context.TODO() - options.ClusterName = rootCommand.ClusterName() + options.ClusterName = rootCommand.ClusterName(true) if options.ClusterName == "" { exitWithError(fmt.Errorf("--name is required")) return diff --git a/cmd/kops/get_assets.go b/cmd/kops/get_assets.go index a25f397bc0be8..ac2a0d6c28115 100644 --- a/cmd/kops/get_assets.go +++ b/cmd/kops/get_assets.go @@ -98,7 +98,7 @@ func NewCmdGetAssets(f *util.Factory, out io.Writer, getOptions *GetOptions) *co func RunGetAssets(ctx context.Context, f *util.Factory, out io.Writer, options *GetAssetsOptions) error { - clusterName := rootCommand.ClusterName() + clusterName := rootCommand.ClusterName(true) options.clusterName = clusterName if clusterName == "" { return fmt.Errorf("--name is required") diff --git a/cmd/kops/get_instancegroups.go b/cmd/kops/get_instancegroups.go index ed7e05575f349..a4d34eba5bb33 100644 --- a/cmd/kops/get_instancegroups.go +++ b/cmd/kops/get_instancegroups.go @@ -82,7 +82,7 @@ func NewCmdGetInstanceGroups(f *util.Factory, out io.Writer, getOptions *GetOpti func RunGetInstanceGroups(ctx context.Context, options *GetInstanceGroupsOptions, args []string) error { out := os.Stdout - clusterName := rootCommand.ClusterName() + clusterName := rootCommand.ClusterName(true) if clusterName == "" { return fmt.Errorf("--name is required") } diff --git a/cmd/kops/get_instances.go b/cmd/kops/get_instances.go index c92857991f033..2bd0a47b51d73 100644 --- a/cmd/kops/get_instances.go +++ b/cmd/kops/get_instances.go @@ -83,7 +83,7 @@ func RunGetInstances(ctx context.Context, f *util.Factory, out io.Writer, option return err } - clusterName := rootCommand.ClusterName() + clusterName := rootCommand.ClusterName(true) options.clusterName = clusterName if clusterName == "" { return fmt.Errorf("--name is required") diff --git a/cmd/kops/promote_keypair.go b/cmd/kops/promote_keypair.go index d99e3bf5ef3bb..a2a723eccfe68 100644 --- a/cmd/kops/promote_keypair.go +++ b/cmd/kops/promote_keypair.go @@ -65,7 +65,7 @@ func NewCmdPromoteKeypair(f *util.Factory, out io.Writer) *cobra.Command { Run: func(cmd *cobra.Command, args []string) { ctx := context.TODO() - options.ClusterName = rootCommand.ClusterName() + options.ClusterName = rootCommand.ClusterName(true) if options.ClusterName == "" { exitWithError(fmt.Errorf("--name is required")) @@ -95,7 +95,7 @@ func NewCmdPromoteKeypair(f *util.Factory, out io.Writer) *cobra.Command { // RunPromoteKeypair promotes a keypair. func RunPromoteKeypair(ctx context.Context, f *util.Factory, out io.Writer, options *PromoteKeypairOptions) error { - if keysetCommonNames[options.Keyset] == "" { + if !rotatableKeysets.Has(options.Keyset) { return fmt.Errorf("promoting keypairs for %q is not supported", options.Keyset) } diff --git a/cmd/kops/rollingupdatecluster.go b/cmd/kops/rollingupdatecluster.go index 90ec626b3165b..2a5ec980300f3 100644 --- a/cmd/kops/rollingupdatecluster.go +++ b/cmd/kops/rollingupdatecluster.go @@ -208,7 +208,7 @@ func NewCmdRollingUpdateCluster(f *util.Factory, out io.Writer) *cobra.Command { return } - clusterName := rootCommand.ClusterName() + clusterName := rootCommand.ClusterName(true) if clusterName == "" { exitWithError(fmt.Errorf("--name is required")) return diff --git a/cmd/kops/root.go b/cmd/kops/root.go index 461a36667f2df..a2736afed6a4d 100644 --- a/cmd/kops/root.go +++ b/cmd/kops/root.go @@ -235,22 +235,14 @@ func (c *RootCmd) ProcessArgs(args []string) error { return fmt.Errorf("expected a single to be passed as an argument") } -func (c *RootCmd) ClusterName() string { +func (c *RootCmd) ClusterName(verbose bool) string { if c.clusterName != "" { return c.clusterName } - c.clusterName = ClusterNameFromKubecfg() - - return c.clusterName -} - -func ClusterNameFromKubecfg() string { // Read from kubeconfig pathOptions := clientcmd.NewDefaultPathOptions() - clusterName := "" - config, err := pathOptions.GetStartingConfig() if err != nil { klog.Warningf("error reading kubecfg: %v", err) @@ -263,20 +255,14 @@ func ClusterNameFromKubecfg() string { } else if context.Cluster == "" { klog.Warningf("context %q in kubecfg did not have a cluster", config.CurrentContext) } else { - fmt.Fprintf(os.Stderr, "Using cluster from kubectl context: %s\n\n", context.Cluster) - clusterName = context.Cluster + if verbose { + fmt.Fprintf(os.Stderr, "Using cluster from kubectl context: %s\n\n", context.Cluster) + } + c.clusterName = context.Cluster } } - //config, err := readKubectlClusterConfig() - //if err != nil { - // klog.Warningf("error reading kubecfg: %v", err) - //} else if config != nil && config.Name != "" { - // fmt.Fprintf(os.Stderr, "Using cluster from kubectl context: %s\n\n", config.Name) - // c.clusterName = config.Name - //} - - return clusterName + return c.clusterName } func (c *RootCmd) Clientset() (simple.Clientset, error) { @@ -284,7 +270,7 @@ func (c *RootCmd) Clientset() (simple.Clientset, error) { } func (c *RootCmd) Cluster(ctx context.Context) (*kopsapi.Cluster, error) { - clusterName := c.ClusterName() + clusterName := c.ClusterName(true) if clusterName == "" { return nil, fmt.Errorf("--name is required") } diff --git a/cmd/kops/set_cluster.go b/cmd/kops/set_cluster.go index f1161239b92c3..da92b856a42f7 100644 --- a/cmd/kops/set_cluster.go +++ b/cmd/kops/set_cluster.go @@ -70,7 +70,7 @@ func NewCmdSetCluster(f *util.Factory, out io.Writer) *cobra.Command { } if options.ClusterName == "" { - options.ClusterName = rootCommand.ClusterName() + options.ClusterName = rootCommand.ClusterName(true) } if err := commands.RunSetCluster(ctx, f, cmd, out, options); err != nil { diff --git a/cmd/kops/set_instancegroups.go b/cmd/kops/set_instancegroups.go index 1c723769db24e..dbc89c6a24a36 100644 --- a/cmd/kops/set_instancegroups.go +++ b/cmd/kops/set_instancegroups.go @@ -73,7 +73,7 @@ func NewCmdSetInstancegroup(f *util.Factory, out io.Writer) *cobra.Command { } } - options.ClusterName = rootCommand.ClusterName() + options.ClusterName = rootCommand.ClusterName(true) if err := commands.RunSetInstancegroup(ctx, f, cmd, out, options); err != nil { exitWithError(err) diff --git a/cmd/kops/toolbox_dump.go b/cmd/kops/toolbox_dump.go index 6c85053cfa2d7..ee50ecc2513ce 100644 --- a/cmd/kops/toolbox_dump.go +++ b/cmd/kops/toolbox_dump.go @@ -87,7 +87,7 @@ func NewCmdToolboxDump(f *util.Factory, out io.Writer) *cobra.Command { exitWithError(err) } - options.ClusterName = rootCommand.ClusterName() + options.ClusterName = rootCommand.ClusterName(true) err := RunToolboxDump(ctx, f, out, options) if err != nil { diff --git a/cmd/kops/toolbox_instance_selector.go b/cmd/kops/toolbox_instance_selector.go index 35a2c5f3652d0..c992d7f0f9b30 100644 --- a/cmd/kops/toolbox_instance_selector.go +++ b/cmd/kops/toolbox_instance_selector.go @@ -189,7 +189,7 @@ func RunToolboxInstanceSelector(ctx context.Context, f *util.Factory, args []str return fmt.Errorf("Can only specify one instance group name at a time") } igName := args[0] - clusterName := rootCommand.ClusterName() + clusterName := rootCommand.ClusterName(true) flags, err := processAndValidateFlags(commandline) if err != nil { diff --git a/cmd/kops/toolbox_template.go b/cmd/kops/toolbox_template.go index 1329437a962ef..30bd66c141344 100644 --- a/cmd/kops/toolbox_template.go +++ b/cmd/kops/toolbox_template.go @@ -88,7 +88,7 @@ func NewCmdToolboxTemplate(f *util.Factory, out io.Writer) *cobra.Command { if err := rootCommand.ProcessArgs(args); err != nil { exitWithError(err) } - options.clusterName = rootCommand.ClusterName() + options.clusterName = rootCommand.ClusterName(true) if err := runToolBoxTemplate(f, out, options); err != nil { exitWithError(err) diff --git a/cmd/kops/unset_cluster.go b/cmd/kops/unset_cluster.go index 4a07b4bb4d489..b52f74116f5fe 100644 --- a/cmd/kops/unset_cluster.go +++ b/cmd/kops/unset_cluster.go @@ -62,7 +62,7 @@ func NewCmdUnsetCluster(f *util.Factory, out io.Writer) *cobra.Command { } if options.ClusterName == "" { - options.ClusterName = rootCommand.ClusterName() + options.ClusterName = rootCommand.ClusterName(true) } if err := commands.RunUnsetCluster(ctx, f, cmd, out, options); err != nil { diff --git a/cmd/kops/unset_instancegroups.go b/cmd/kops/unset_instancegroups.go index a616ef4e4e406..acfd76d04a44f 100644 --- a/cmd/kops/unset_instancegroups.go +++ b/cmd/kops/unset_instancegroups.go @@ -63,7 +63,7 @@ func NewCmdUnsetInstancegroup(f *util.Factory, out io.Writer) *cobra.Command { } } - options.ClusterName = rootCommand.ClusterName() + options.ClusterName = rootCommand.ClusterName(true) if err := commands.RunUnsetInstancegroup(ctx, f, cmd, out, options); err != nil { exitWithError(err) diff --git a/cmd/kops/update_cluster.go b/cmd/kops/update_cluster.go index 1c3b2e2512034..cbea3c4caebbc 100644 --- a/cmd/kops/update_cluster.go +++ b/cmd/kops/update_cluster.go @@ -110,7 +110,7 @@ func NewCmdUpdateCluster(f *util.Factory, out io.Writer) *cobra.Command { exitWithError(err) } - clusterName := rootCommand.ClusterName() + clusterName := rootCommand.ClusterName(true) if _, err := RunUpdateCluster(ctx, f, clusterName, out, options); err != nil { exitWithError(err) diff --git a/docs/cli/kops_create_keypair.md b/docs/cli/kops_create_keypair.md index eaa78526ee437..800be5c2b7b5e 100644 --- a/docs/cli/kops_create_keypair.md +++ b/docs/cli/kops_create_keypair.md @@ -9,8 +9,16 @@ Add a CA certificate and private key to a keyset. Add a CA certificate and private key to a keyset. + If neither a certificate nor a private key is provided, a new self-signed certificate and private key will be generated. + + If no certificate is provided but a private key is, a self-signed certificate will be generated from the provided private key. + + If a certificate is provided but no private key is, the certificate will be added to the keyset without a private key. Such a certificate cannot be made primary. + + One of the certificate/private key pairs in each keyset must be primary. The primary keypair is the one used to issue certificates (or, for the "service-account" keyset, service-account tokens). As a consequence, the first entry in a keyset must be made primary. + ``` -kops create keypair KEYSET [flags] +kops create keypair keyset [flags] ``` ### Examples diff --git a/pkg/commands/commandutils/error.go b/pkg/commands/commandutils/error.go index 2d7c304a0a895..7f56295c65649 100644 --- a/pkg/commands/commandutils/error.go +++ b/pkg/commands/commandutils/error.go @@ -24,6 +24,10 @@ import ( // CompletionError a helper function to logs and return an error from a Cobra completion function. func CompletionError(prefix string, err error) ([]string, cobra.ShellCompDirective) { - cobra.CompError(fmt.Sprintf("%s: %v", prefix, err)) + if err == nil { + cobra.CompError(prefix) + } else { + cobra.CompError(fmt.Sprintf("%s: %v", prefix, err)) + } return nil, cobra.ShellCompDirectiveError | cobra.ShellCompDirectiveNoFileComp } diff --git a/pkg/commands/commandutils/klog.go b/pkg/commands/commandutils/klog.go index 2d31f3242eb0b..4ea518b205c1a 100644 --- a/pkg/commands/commandutils/klog.go +++ b/pkg/commands/commandutils/klog.go @@ -24,6 +24,7 @@ import ( // ConfigureKlogForCompletion configures Klog to not interfere with Cobra completion functions. func ConfigureKlogForCompletion() { klog.SetOutput(&toCompDebug{}) + klog.LogToStderr(false) } type toCompDebug struct{}