diff --git a/cmd/talosctl/cmd/mgmt/gen/crt.go b/cmd/talosctl/cmd/mgmt/gen/crt.go index 7e7cd7a59..34e7542cb 100644 --- a/cmd/talosctl/cmd/mgmt/gen/crt.go +++ b/cmd/talosctl/cmd/mgmt/gen/crt.go @@ -38,7 +38,7 @@ var genCrtCmd = &cobra.Command{ caPemBlock, _ := pem.Decode(caBytes) if caPemBlock == nil { - return fmt.Errorf("error decoding cert PEM: %s", err) + return fmt.Errorf("error decoding cert PEM") } caCrt, err := stdlibx509.ParseCertificate(caPemBlock.Bytes) @@ -53,7 +53,7 @@ var genCrtCmd = &cobra.Command{ keyPemBlock, _ := pem.Decode(keyBytes) if keyPemBlock == nil { - return fmt.Errorf("error decoding key PEM: %s", err) + return fmt.Errorf("error decoding key PEM") } caKey, err := stdlibx509.ParsePKCS8PrivateKey(keyPemBlock.Bytes) @@ -68,7 +68,7 @@ var genCrtCmd = &cobra.Command{ csrPemBlock, _ := pem.Decode(csrBytes) if csrPemBlock == nil { - return fmt.Errorf("error parsing CSR PEM: %s", err) + return fmt.Errorf("error parsing CSR PEM") } ccsr, err := stdlibx509.ParseCertificateRequest(csrPemBlock.Bytes) diff --git a/cmd/talosctl/cmd/mgmt/gen/csr.go b/cmd/talosctl/cmd/mgmt/gen/csr.go index 3db59c852..9a00e9fa2 100644 --- a/cmd/talosctl/cmd/mgmt/gen/csr.go +++ b/cmd/talosctl/cmd/mgmt/gen/csr.go @@ -40,7 +40,7 @@ var genCSRCmd = &cobra.Command{ pemBlock, _ := pem.Decode(keyBytes) if pemBlock == nil { - return fmt.Errorf("error decoding PEM: %s", err) + return fmt.Errorf("error decoding PEM") } keyEC, err := stdlibx509.ParsePKCS8PrivateKey(pemBlock.Bytes) diff --git a/cmd/talosctl/cmd/talos/config.go b/cmd/talosctl/cmd/talos/config.go index a7323b159..07e5c1058 100644 --- a/cmd/talosctl/cmd/talos/config.go +++ b/cmd/talosctl/cmd/talos/config.go @@ -5,16 +5,21 @@ package talos import ( + "bytes" "context" + "crypto/x509" "encoding/base64" + "encoding/pem" "fmt" "io/ioutil" "os" "sort" "strings" "text/tabwriter" + "text/template" "time" + "github.com/dustin/go-humanize" "github.com/spf13/cobra" "google.golang.org/protobuf/types/known/durationpb" @@ -323,6 +328,88 @@ var configNewCmd = &cobra.Command{ }, } +// configNewCmd represents the `config info` command output template. +var configInfoCmdTemplate = template.Must(template.New("configInfoCmdTemplate").Option("missingkey=error").Parse(strings.TrimSpace(` +Current context: {{ .Context }} +Nodes: {{ .Nodes }} +Endpoints: {{ .Endpoints }} +Roles: {{ .Roles }} +Certificate expires: {{ .CertTTL }} ({{ .CertNotAfter }}) +`))) + +// configInfoCommand implements `config info` command logic. +// +//nolint:goconst +func configInfoCommand(config *clientconfig.Config, now time.Time) (string, error) { + context := config.Contexts[config.Context] + + b, err := base64.StdEncoding.DecodeString(context.Crt) + if err != nil { + return "", err + } + + block, _ := pem.Decode(b) + if block == nil { + return "", fmt.Errorf("error decoding PEM") + } + + crt, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return "", err + } + + roles, _ := role.Parse(crt.Subject.Organization) + + nodesS := "not defined" + if len(context.Nodes) > 0 { + nodesS = strings.Join(context.Nodes, ", ") + } + + endpointsS := "not defined" + if len(context.Endpoints) > 0 { + endpointsS = strings.Join(context.Endpoints, ", ") + } + + rolesS := "not defined" + if s := roles.Strings(); len(s) > 0 { + rolesS = strings.Join(s, ", ") + } + + var res bytes.Buffer + err = configInfoCmdTemplate.Execute(&res, map[string]string{ + "Context": config.Context, + "Nodes": nodesS, + "Endpoints": endpointsS, + "Roles": rolesS, + "CertTTL": humanize.RelTime(crt.NotAfter, now, "ago", "from now"), + "CertNotAfter": crt.NotAfter.UTC().Format("2006-01-02"), + }) + + return res.String() + "\n", err +} + +// configInfoCmd represents the `config info` command. +var configInfoCmd = &cobra.Command{ + Use: "info", + Short: "Show information about the current context", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + c, err := openConfigAndContext("") + if err != nil { + return err + } + + res, err := configInfoCommand(c, time.Now()) + if err != nil { + return err + } + + fmt.Print(res) + + return nil + }, +} + func init() { configCmd.AddCommand( configEndpointCmd, @@ -332,6 +419,7 @@ func init() { configGetContextsCmd, configMergeCmd, configNewCmd, + configInfoCmd, ) configAddCmd.Flags().StringVar(&configAddCmdFlags.ca, "ca", "", "the path to the CA certificate") diff --git a/cmd/talosctl/cmd/talos/config_test.go b/cmd/talosctl/cmd/talos/config_test.go new file mode 100644 index 000000000..65a2bd18f --- /dev/null +++ b/cmd/talosctl/cmd/talos/config_test.go @@ -0,0 +1,106 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package talos //nolint:testpackage // to test unexported function + +import ( + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + clientconfig "github.com/talos-systems/talos/pkg/machinery/client/config" +) + +func TestConfigInfoCommand(t *testing.T) { + t.Parallel() + + now := time.Date(2021, 7, 5, 22, 6, 42, 0, time.UTC) + + //nolint:lll + testCases := []struct { + name string + config string + expected string + }{ + { + name: "Default", + config: ` +context: no-roles +contexts: + no-roles: + endpoints: + - 172.20.1.2 + - 172.20.1.3 + - 172.20.1.4 + nodes: + - 172.20.1.2 + ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJQekNCOHFBREFnRUNBaEVBblpENTFNNW0zUmNMNUZwWXVYUkRWVEFGQmdNclpYQXdFREVPTUF3R0ExVUUKQ2hNRmRHRnNiM013SGhjTk1qRXdOekExTVRFeE9ETXpXaGNOTXpFd056QXpNVEV4T0RNeldqQVFNUTR3REFZRApWUVFLRXdWMFlXeHZjekFxTUFVR0F5dGxjQU1oQUFPYU93TVBaaDNHaE9kd2ZtSjYxcUhheUxDakFzdGcrWlNJCmpLbVV0WXR2bzJFd1h6QU9CZ05WSFE4QkFmOEVCQU1DQW9Rd0hRWURWUjBsQkJZd0ZBWUlLd1lCQlFVSEF3RUcKQ0NzR0FRVUZCd01DTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRkhLNzFyajVOTmxad1J1VgpXcFc4bkNabit2YWxNQVVHQXl0bGNBTkJBTjlMQ2d2RzltSDdka3RSRTVQTFJJT25VcTNORnZaMTl1SHF5bWttCjJwQVR2SU02cXpMS0x0NXUyWEtBVFUzcDZDQWFGVVpOMkJhVjV0amVTeXZSWkFzPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJNakNCNWFBREFnRUNBaEFyalRDdnc3TElYZVRPTjFSTnhqeFNNQVVHQXl0bGNEQVFNUTR3REFZRFZRUUsKRXdWMFlXeHZjekFlRncweU1UQTNNRFV4TVRFNE16TmFGdzB6TVRBM01ETXhNVEU0TXpOYU1CTXhFVEFQQmdOVgpCQW9UQ0c5ek9tRmtiV2x1TUNvd0JRWURLMlZ3QXlFQTI2bDQ0eU1ZRTAvZUVUVEtsQXBTZGhlMEgzOEhGRDBnClh1Q2VFdWZ0YnZpalVqQlFNQTRHQTFVZER3RUIvd1FFQXdJSGdEQWRCZ05WSFNVRUZqQVVCZ2dyQmdFRkJRY0QKQVFZSUt3WUJCUVVIQXdJd0h3WURWUjBqQkJnd0ZvQVVjcnZXdVBrMDJWbkJHNVZhbGJ5Y0ptZjY5cVV3QlFZRApLMlZ3QTBFQXhlN3Qrb2tZUURoNlZOQ0ZLenlqTmVuWkhmQ1MrRTdFdUYyVC9kcjRkU3JXcko2eXYvaE5ZalJqCnhNb0grU3dLak5kU2trNnJhWHdWb0ZwY3JraWRBdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + key: LS0tLS1CRUdJTiBFRDI1NTE5IFBSSVZBVEUgS0VZLS0tLS0KTUM0Q0FRQXdCUVlESzJWd0JDSUVJQWhmdzFISzBKVFgzVjh2K09wZ2J5dXQ2VzN0OWhVaGczQW5pK043WTFTNAotLS0tLUVORCBFRDI1NTE5IFBSSVZBVEUgS0VZLS0tLS0K +`, + expected: strings.TrimSpace(` +Current context: no-roles +Nodes: 172.20.1.2 +Endpoints: 172.20.1.2, 172.20.1.3, 172.20.1.4 +Roles: os:admin +Certificate expires: 10 years from now (2031-07-03) +`) + "\n", + }, + { + name: "NoRoles", + config: ` +context: no-roles +contexts: + no-roles: + endpoints: + - 172.20.1.2 + ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJQekNCOHFBREFnRUNBaEVBblpENTFNNW0zUmNMNUZwWXVYUkRWVEFGQmdNclpYQXdFREVPTUF3R0ExVUUKQ2hNRmRHRnNiM013SGhjTk1qRXdOekExTVRFeE9ETXpXaGNOTXpFd056QXpNVEV4T0RNeldqQVFNUTR3REFZRApWUVFLRXdWMFlXeHZjekFxTUFVR0F5dGxjQU1oQUFPYU93TVBaaDNHaE9kd2ZtSjYxcUhheUxDakFzdGcrWlNJCmpLbVV0WXR2bzJFd1h6QU9CZ05WSFE4QkFmOEVCQU1DQW9Rd0hRWURWUjBsQkJZd0ZBWUlLd1lCQlFVSEF3RUcKQ0NzR0FRVUZCd01DTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRkhLNzFyajVOTmxad1J1VgpXcFc4bkNabit2YWxNQVVHQXl0bGNBTkJBTjlMQ2d2RzltSDdka3RSRTVQTFJJT25VcTNORnZaMTl1SHF5bWttCjJwQVR2SU02cXpMS0x0NXUyWEtBVFUzcDZDQWFGVVpOMkJhVjV0amVTeXZSWkFzPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJIekNCMHFBREFnRUNBaEFpcVA1MjN0NkJWNWZmNTVhUlBOWW1NQVVHQXl0bGNEQVFNUTR3REFZRFZRUUsKRXdWMFlXeHZjekFlRncweU1UQTNNRFV4TVRNeE5UWmFGdzB6TVRBM01ETXhNVE14TlRaYU1BQXdLakFGQmdNcgpaWEFESVFCNTlmL2h0MG1tOUJqL3I4b0VsdjFUU3VVcG5kazlwang3Mm10MUpxZGEyNk5TTUZBd0RnWURWUjBQCkFRSC9CQVFEQWdlQU1CMEdBMVVkSlFRV01CUUdDQ3NHQVFVRkJ3TUJCZ2dyQmdFRkJRY0RBakFmQmdOVkhTTUUKR0RBV2dCUnl1OWE0K1RUWldjRWJsVnFWdkp3bVovcjJwVEFGQmdNclpYQURRUUNXUnAzcHB6YkM5ZzlmWC9RRgp0ZGg1eFY2YVYvdTVVdTFkU05TNmQ5VFFlUE00c1NXL1U5UGViNEpvK3Uzd1lPblBlb3huSWoxNzJOdTBQTm81CldPa0MKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + key: LS0tLS1CRUdJTiBFRDI1NTE5IFBSSVZBVEUgS0VZLS0tLS0KTUM0Q0FRQXdCUVlESzJWd0JDSUVJREJybmN1UEV2RHFPRjhqWWJMNUxvNWhSUkZ2cXBPVWZud2RMOHRPdzdFRgotLS0tLUVORCBFRDI1NTE5IFBSSVZBVEUgS0VZLS0tLS0K +`, + expected: strings.TrimSpace(` +Current context: no-roles +Nodes: not defined +Endpoints: 172.20.1.2 +Roles: not defined +Certificate expires: 10 years from now (2031-07-03) +`) + "\n", + }, + { + name: "FutureRole", + config: ` +context: future-role +contexts: + future-role: + ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJQekNCOHFBREFnRUNBaEVBblpENTFNNW0zUmNMNUZwWXVYUkRWVEFGQmdNclpYQXdFREVPTUF3R0ExVUUKQ2hNRmRHRnNiM013SGhjTk1qRXdOekExTVRFeE9ETXpXaGNOTXpFd056QXpNVEV4T0RNeldqQVFNUTR3REFZRApWUVFLRXdWMFlXeHZjekFxTUFVR0F5dGxjQU1oQUFPYU93TVBaaDNHaE9kd2ZtSjYxcUhheUxDakFzdGcrWlNJCmpLbVV0WXR2bzJFd1h6QU9CZ05WSFE4QkFmOEVCQU1DQW9Rd0hRWURWUjBsQkJZd0ZBWUlLd1lCQlFVSEF3RUcKQ0NzR0FRVUZCd01DTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRkhLNzFyajVOTmxad1J1VgpXcFc4bkNabit2YWxNQVVHQXl0bGNBTkJBTjlMQ2d2RzltSDdka3RSRTVQTFJJT25VcTNORnZaMTl1SHF5bWttCjJwQVR2SU02cXpMS0x0NXUyWEtBVFUzcDZDQWFGVVpOMkJhVjV0amVTeXZSWkFzPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJNekNCNXFBREFnRUNBaEFQL1MrVGx0WTBHdGk5Q1g0UDNVZStNQVVHQXl0bGNEQVFNUTR3REFZRFZRUUsKRXdWMFlXeHZjekFlRncweU1UQTNNRFV4TVRRek1qRmFGdzB6TVRBM01ETXhNVFF6TWpGYU1CUXhFakFRQmdOVgpCQW9UQ1c5ek9tWjFkSFZ5WlRBcU1BVUdBeXRsY0FNaEFLck04NmtPYm1MdGw5OVdpdzFFL29pdnl2YXVqVmNkCmlQTk82TVhQNGxEMm8xSXdVREFPQmdOVkhROEJBZjhFQkFNQ0I0QXdIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUgKQXdFR0NDc0dBUVVGQndNQ01COEdBMVVkSXdRWU1CYUFGSEs3MXJqNU5ObFp3UnVWV3BXOG5DWm4rdmFsTUFVRwpBeXRsY0FOQkFDSno3NTlGeVFJMXlIWTJNRG9vZDNrZjdZeG9HRG1Hem1nNllqRHJueXAxWGpaQ3o1Q1RQbm9jCjhxWlAyTXE5MDJnOXZSSUh1dm84N0NIZjJacTNGZ1U9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + key: LS0tLS1CRUdJTiBFRDI1NTE5IFBSSVZBVEUgS0VZLS0tLS0KTUM0Q0FRQXdCUVlESzJWd0JDSUVJS1RCRDIyZDBLVnNTek5iSkNBdjNObUVnL1VWOTk4SHZvY2NGb1lDOEJ1bAotLS0tLUVORCBFRDI1NTE5IFBSSVZBVEUgS0VZLS0tLS0K +`, + expected: strings.TrimSpace(` +Current context: future-role +Nodes: not defined +Endpoints: not defined +Roles: os:future +Certificate expires: 10 years from now (2031-07-03) +`) + "\n", + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + config, err := clientconfig.FromString(tc.config) + require.NoError(t, err) + + actual, err := configInfoCommand(config, now) + assert.NoError(t, err) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/website/content/docs/v0.11/Reference/cli.md b/website/content/docs/v0.11/Reference/cli.md index 657372fa4..07b5a1e88 100644 --- a/website/content/docs/v0.11/Reference/cli.md +++ b/website/content/docs/v0.11/Reference/cli.md @@ -425,6 +425,33 @@ talosctl config endpoint ... [flags] * [talosctl config](#talosctl-config) - Manage the client configuration file (talosconfig) +## talosctl config info + +Show information about the current context + +``` +talosctl config info [flags] +``` + +### Options + +``` + -h, --help help for info +``` + +### Options inherited from parent commands + +``` + --context string Context to be used in command + -e, --endpoints strings override default endpoints in Talos configuration + -n, --nodes strings target the specified nodes + --talosconfig string The path to the Talos configuration file (default "/home/user/.talos/config") +``` + +### SEE ALSO + +* [talosctl config](#talosctl-config) - Manage the client configuration file (talosconfig) + ## talosctl config merge Merge additional contexts from another client configuration file @@ -538,6 +565,7 @@ Manage the client configuration file (talosconfig) * [talosctl config context](#talosctl-config-context) - Set the current context * [talosctl config contexts](#talosctl-config-contexts) - List defined contexts * [talosctl config endpoint](#talosctl-config-endpoint) - Set the endpoint(s) for the current context +* [talosctl config info](#talosctl-config-info) - Show information about the current context * [talosctl config merge](#talosctl-config-merge) - Merge additional contexts from another client configuration file * [talosctl config new](#talosctl-config-new) - Generate a new client configuration file * [talosctl config node](#talosctl-config-node) - Set the node(s) for the current context