From 5423e18b56aa86fad475dbf202b938ccb1edabe8 Mon Sep 17 00:00:00 2001 From: John Gardiner Myers Date: Mon, 21 Jun 2021 20:58:51 -0700 Subject: [PATCH] Add 'kops promote keypair' command --- cmd/kops/BUILD.bazel | 2 + cmd/kops/promote.go | 52 +++++++++++ cmd/kops/promote_keypair.go | 156 +++++++++++++++++++++++++++++++ cmd/kops/root.go | 1 + docs/cli/kops.md | 1 + docs/cli/kops_promote.md | 50 ++++++++++ docs/cli/kops_promote_keypair.md | 58 ++++++++++++ 7 files changed, 320 insertions(+) create mode 100644 cmd/kops/promote.go create mode 100644 cmd/kops/promote_keypair.go create mode 100644 docs/cli/kops_promote.md create mode 100644 docs/cli/kops_promote_keypair.md diff --git a/cmd/kops/BUILD.bazel b/cmd/kops/BUILD.bazel index f730cdc3a5..4a6fb998a8 100644 --- a/cmd/kops/BUILD.bazel +++ b/cmd/kops/BUILD.bazel @@ -37,6 +37,8 @@ go_library( "get_keypairs.go", "get_secrets.go", "main.go", + "promote.go", + "promote_keypair.go", "replace.go", "rollingupdate.go", "rollingupdatecluster.go", diff --git a/cmd/kops/promote.go b/cmd/kops/promote.go new file mode 100644 index 0000000000..eae5494ef1 --- /dev/null +++ b/cmd/kops/promote.go @@ -0,0 +1,52 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "io" + + "github.com/spf13/cobra" + "k8s.io/kops/cmd/kops/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + promoteLong = templates.LongDesc(i18n.T(` + Promote a resource.`)) + + promoteExample = templates.Examples(i18n.T(` + # Promote the newest ca keypair to be the primary. + kops promote keypair ca +`)) + + promoteShort = i18n.T(`Promote a resource.`) +) + +func NewCmdPromote(f *util.Factory, out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "promote", + Short: promoteShort, + Long: promoteLong, + Example: promoteExample, + } + + // create subcommands + cmd.AddCommand(NewCmdPromoteKeypair(f, out)) + + return cmd +} diff --git a/cmd/kops/promote_keypair.go b/cmd/kops/promote_keypair.go new file mode 100644 index 0000000000..2cbe4a714e --- /dev/null +++ b/cmd/kops/promote_keypair.go @@ -0,0 +1,156 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + "io" + "math/big" + + "github.com/spf13/cobra" + "k8s.io/klog/v2" + "k8s.io/kops/cmd/kops/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + promoteKeypairLong = templates.LongDesc(i18n.T(` + Promote a keypair to be the primary, used for signing. + `)) + + promoteKeypairExample = templates.Examples(i18n.T(` + # Promote the newest ca keypair to be the primary. + kops promote keypair ca \ + --name k8s-cluster.example.com --state s3://my-state-store + + # Promote a specific service-account keypair to be the primary. + kops promote keypair service-account 5938372002934847 \ + --name k8s-cluster.example.com --state s3://my-state-store + `)) + + promoteKeypairShort = i18n.T(`Promote a keypair to be the primary, used for signing.`) +) + +type PromoteKeypairOptions struct { + ClusterName string + Keyset string + KeypairID string +} + +// NewCmdPromoteKeypair returns a promote keypair command. +func NewCmdPromoteKeypair(f *util.Factory, out io.Writer) *cobra.Command { + options := &PromoteKeypairOptions{} + + cmd := &cobra.Command{ + Use: "keypair KEYSET [ID]", + Short: promoteKeypairShort, + Long: promoteKeypairLong, + Example: promoteKeypairExample, + Run: func(cmd *cobra.Command, args []string) { + ctx := context.TODO() + + options.ClusterName = rootCommand.ClusterName() + + if options.ClusterName == "" { + exitWithError(fmt.Errorf("--name is required")) + return + } + + if len(args) == 0 { + exitWithError(fmt.Errorf("must specify name of keyset promote keypair in")) + } + if len(args) > 2 { + exitWithError(fmt.Errorf("can only promote to one keyset at a time")) + } + options.Keyset = args[0] + if len(args) > 1 { + options.KeypairID = args[1] + } + + err := RunPromoteKeypair(ctx, f, out, options) + if err != nil { + exitWithError(err) + } + }, + } + + return cmd +} + +// RunPromoteKeypair promotes a keypair. +func RunPromoteKeypair(ctx context.Context, f *util.Factory, out io.Writer, options *PromoteKeypairOptions) error { + if keysetCommonNames[options.Keyset] == "" { + return fmt.Errorf("promoting keypairs for %q is not supported", options.Keyset) + } + + cluster, err := GetCluster(ctx, f, options.ClusterName) + if err != nil { + return fmt.Errorf("error getting cluster: %q: %v", options.ClusterName, err) + } + + clientSet, err := f.Clientset() + if err != nil { + return fmt.Errorf("error getting clientset: %v", err) + } + + keyStore, err := clientSet.KeyStore(cluster) + if err != nil { + return fmt.Errorf("error getting keystore: %v", err) + } + + keyset, err := keyStore.FindKeyset(options.Keyset) + if err != nil { + return fmt.Errorf("reading keyset: %v", err) + } else if keyset == nil { + return fmt.Errorf("keyset not found") + } + + keypairID := options.KeypairID + if keypairID == "" { + highestId := big.NewInt(0) + for id, item := range keyset.Items { + if item.PrivateKey != nil { + itemId, ok := big.NewInt(0).SetString(id, 10) + if ok && highestId.Cmp(itemId) < 0 { + highestId = itemId + } + } + } + + keypairID = highestId.String() + if keypairID == keyset.Primary.Id { + return fmt.Errorf("no keypair newer than current primary %s", keypairID) + } + } else if item := keyset.Items[keypairID]; item != nil { + if item.PrivateKey == nil { + return fmt.Errorf("keypair has no private key") + } + } else { + return fmt.Errorf("keypair not found") + } + + keyset.Primary = keyset.Items[keypairID] + err = keyStore.StoreKeyset(options.Keyset, keyset) + if err != nil { + return fmt.Errorf("error writing keyset: %v", err) + } + + klog.Infof("promoted keypair %s", keypairID) + return nil +} diff --git a/cmd/kops/root.go b/cmd/kops/root.go index 8f7e50d2de..535391063c 100644 --- a/cmd/kops/root.go +++ b/cmd/kops/root.go @@ -145,6 +145,7 @@ func NewCmdRoot(f *util.Factory, out io.Writer) *cobra.Command { cmd.AddCommand(NewCmdExport(f, out)) cmd.AddCommand(NewCmdGet(f, out)) cmd.AddCommand(commands.NewCmdHelpers(f, out)) + cmd.AddCommand(NewCmdPromote(f, out)) cmd.AddCommand(NewCmdUpdate(f, out)) cmd.AddCommand(NewCmdReplace(f, out)) cmd.AddCommand(NewCmdRollingUpdate(f, out)) diff --git a/docs/cli/kops.md b/docs/cli/kops.md index 787a51bb8e..59d58408b2 100644 --- a/docs/cli/kops.md +++ b/docs/cli/kops.md @@ -44,6 +44,7 @@ kOps is Kubernetes Operations. * [kops edit](kops_edit.md) - Edit clusters and other resources. * [kops export](kops_export.md) - Export configuration. * [kops get](kops_get.md) - Get one or many resources. +* [kops promote](kops_promote.md) - Promote a resource. * [kops replace](kops_replace.md) - Replace cluster resources. * [kops rolling-update](kops_rolling-update.md) - Rolling update a cluster. * [kops set](kops_set.md) - Set fields on clusters and other resources. diff --git a/docs/cli/kops_promote.md b/docs/cli/kops_promote.md new file mode 100644 index 0000000000..f99c8e15b5 --- /dev/null +++ b/docs/cli/kops_promote.md @@ -0,0 +1,50 @@ + + + +## kops promote + +Promote a resource. + +### Synopsis + +Promote a resource. + +### Examples + +``` + # Promote the newest ca keypair to be the primary. + kops promote keypair ca +``` + +### Options + +``` + -h, --help help for promote +``` + +### Options inherited from parent commands + +``` + --add_dir_header If true, adds the file directory to the header of the log messages + --alsologtostderr log to standard error as well as files + --config string yaml config file (default is $HOME/.kops.yaml) + --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log_dir string If non-empty, write log files in this directory + --log_file string If non-empty, use this log file + --log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files (default true) + --name string Name of cluster. Overrides KOPS_CLUSTER_NAME environment variable + --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level) + --skip_headers If true, avoid header prefixes in the log messages + --skip_log_headers If true, avoid headers when opening log files + --state string Location of state storage (kops 'config' file). Overrides KOPS_STATE_STORE environment variable + --stderrthreshold severity logs at or above this threshold go to stderr (default 2) + -v, --v Level number for the log level verbosity + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO + +* [kops](kops.md) - kOps is Kubernetes Operations. +* [kops promote keypair](kops_promote_keypair.md) - Promote a keypair to be the primary, used for signing. + diff --git a/docs/cli/kops_promote_keypair.md b/docs/cli/kops_promote_keypair.md new file mode 100644 index 0000000000..b5a3adf73f --- /dev/null +++ b/docs/cli/kops_promote_keypair.md @@ -0,0 +1,58 @@ + + + +## kops promote keypair + +Promote a keypair to be the primary, used for signing. + +### Synopsis + +Promote a keypair to be the primary, used for signing. + +``` +kops promote keypair KEYSET [ID] [flags] +``` + +### Examples + +``` + # Promote the newest ca keypair to be the primary. + kops promote keypair ca \ + --name k8s-cluster.example.com --state s3://my-state-store + + # Promote a specific service-account keypair to be the primary. + kops promote keypair service-account 5938372002934847 \ + --name k8s-cluster.example.com --state s3://my-state-store +``` + +### Options + +``` + -h, --help help for keypair +``` + +### Options inherited from parent commands + +``` + --add_dir_header If true, adds the file directory to the header of the log messages + --alsologtostderr log to standard error as well as files + --config string yaml config file (default is $HOME/.kops.yaml) + --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log_dir string If non-empty, write log files in this directory + --log_file string If non-empty, use this log file + --log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files (default true) + --name string Name of cluster. Overrides KOPS_CLUSTER_NAME environment variable + --one_output If true, only write logs to their native severity level (vs also writing to each lower severity level) + --skip_headers If true, avoid header prefixes in the log messages + --skip_log_headers If true, avoid headers when opening log files + --state string Location of state storage (kops 'config' file). Overrides KOPS_STATE_STORE environment variable + --stderrthreshold severity logs at or above this threshold go to stderr (default 2) + -v, --v Level number for the log level verbosity + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO + +* [kops promote](kops_promote.md) - Promote a resource. +