diff --git a/cmd/talosctl/cmd/mgmt/gen/ca.go b/cmd/talosctl/cmd/mgmt/gen/ca.go index 6d439d962..eab993452 100644 --- a/cmd/talosctl/cmd/mgmt/gen/ca.go +++ b/cmd/talosctl/cmd/mgmt/gen/ca.go @@ -41,15 +41,23 @@ var genCACmd = &cobra.Command{ return fmt.Errorf("error generating CA: %w", err) } - if err := os.WriteFile(genCACmdFlags.organization+".crt", ca.CrtPEM, 0o600); err != nil { + caCertFile := genCACmdFlags.organization + ".crt" + caHashFile := genCACmdFlags.organization + ".sha256" + caKeyFile := genCACmdFlags.organization + ".key" + + if err := validateFilesExists([]string{caCertFile, caHashFile, caKeyFile}); err != nil { + return err + } + + if err := os.WriteFile(caCertFile, ca.CrtPEM, 0o600); err != nil { return fmt.Errorf("error writing CA certificate: %w", err) } - if err := os.WriteFile(genCACmdFlags.organization+".sha256", []byte(x509.Hash(ca.Crt)), 0o600); err != nil { + if err := os.WriteFile(caHashFile, []byte(x509.Hash(ca.Crt)), 0o600); err != nil { return fmt.Errorf("error writing certificate hash: %w", err) } - if err := os.WriteFile(genCACmdFlags.organization+".key", ca.KeyPEM, 0o600); err != nil { + if err := os.WriteFile(caKeyFile, ca.KeyPEM, 0o600); err != nil { return fmt.Errorf("error writing key: %w", err) } diff --git a/cmd/talosctl/cmd/mgmt/gen/config.go b/cmd/talosctl/cmd/mgmt/gen/config.go index 69daa4eaf..37ffae3ee 100644 --- a/cmd/talosctl/cmd/mgmt/gen/config.go +++ b/cmd/talosctl/cmd/mgmt/gen/config.go @@ -70,7 +70,6 @@ var genConfigCmdFlags struct { withDocs bool withClusterDiscovery bool withKubeSpan bool - force bool withSecrets string } @@ -280,6 +279,7 @@ func validateFlags() error { return err } +// nolint:gocyclo func writeConfigBundle(configBundle *bundle.ConfigBundle, outputPaths configOutputPaths, commentsFlags encoder.CommentsFlags) error { outputTypesSet := slices.ToSet(genConfigCmdFlags.outputTypes) @@ -326,16 +326,14 @@ func writeToDestination(data []byte, destination string, permissions os.FileMode return err } - if !genConfigCmdFlags.force { - if _, err := os.Stat(destination); err == nil { - return fmt.Errorf("%s already exists, use --force to overwrite", destination) - } + if err := validateFileExists(destination); err != nil { + return err } parentDir := filepath.Dir(destination) // Create dir path, ignoring "already exists" messages - if err := os.MkdirAll(parentDir, os.ModePerm); err != nil && !os.IsExist(err) { + if err := os.MkdirAll(parentDir, os.ModePerm); err != nil { return fmt.Errorf("failed to create output dir: %w", err) } @@ -438,7 +436,6 @@ func init() { genConfigCmd.Flags().BoolVarP(&genConfigCmdFlags.withDocs, "with-docs", "", true, "renders all machine configs adding the documentation for each field") genConfigCmd.Flags().BoolVarP(&genConfigCmdFlags.withClusterDiscovery, "with-cluster-discovery", "", true, "enable cluster discovery feature") genConfigCmd.Flags().BoolVarP(&genConfigCmdFlags.withKubeSpan, "with-kubespan", "", false, "enable KubeSpan feature") - genConfigCmd.Flags().BoolVarP(&genConfigCmdFlags.force, "force", "", false, "\"gen\" will overwrite existing files") genConfigCmd.Flags().StringVar(&genConfigCmdFlags.withSecrets, "with-secrets", "", "use a secrets file generated using 'gen secrets'") genConfigCmd.Flags().StringSliceVarP(&genConfigCmdFlags.outputTypes, "output-types", "t", allOutputTypes, fmt.Sprintf("types of outputs to be generated. valid types are: %q", allOutputTypes)) diff --git a/cmd/talosctl/cmd/mgmt/gen/crt.go b/cmd/talosctl/cmd/mgmt/gen/crt.go index 76de67b89..100dbe7ba 100644 --- a/cmd/talosctl/cmd/mgmt/gen/crt.go +++ b/cmd/talosctl/cmd/mgmt/gen/crt.go @@ -85,7 +85,13 @@ var genCrtCmd = &cobra.Command{ return fmt.Errorf("error signing certificate: %s", err) } - if err = os.WriteFile(genCrtCmdFlags.name+".crt", signedCrt.X509CertificatePEM, 0o600); err != nil { + certFile := genCrtCmdFlags.name + ".crt" + + if err = validateFileExists(certFile); err != nil { + return err + } + + if err = os.WriteFile(certFile, signedCrt.X509CertificatePEM, 0o600); err != nil { return fmt.Errorf("error writing certificate: %s", err) } diff --git a/cmd/talosctl/cmd/mgmt/gen/csr.go b/cmd/talosctl/cmd/mgmt/gen/csr.go index 5970cb9e9..66807910c 100644 --- a/cmd/talosctl/cmd/mgmt/gen/csr.go +++ b/cmd/talosctl/cmd/mgmt/gen/csr.go @@ -69,7 +69,13 @@ var genCSRCmd = &cobra.Command{ return fmt.Errorf("error generating CSR: %s", err) } - if err := os.WriteFile(strings.TrimSuffix(genCSRCmdFlags.key, path.Ext(genCSRCmdFlags.key))+".csr", csr.X509CertificateRequestPEM, 0o600); err != nil { + csrFile := strings.TrimSuffix(genCSRCmdFlags.key, path.Ext(genCSRCmdFlags.key)) + ".csr" + + if err := validateFileExists(csrFile); err != nil { + return err + } + + if err := os.WriteFile(csrFile, csr.X509CertificateRequestPEM, 0o600); err != nil { return fmt.Errorf("error writing CSR: %s", err) } diff --git a/cmd/talosctl/cmd/mgmt/gen/gen.go b/cmd/talosctl/cmd/mgmt/gen/gen.go index bdb18317b..b94f24dfb 100644 --- a/cmd/talosctl/cmd/mgmt/gen/gen.go +++ b/cmd/talosctl/cmd/mgmt/gen/gen.go @@ -5,12 +5,46 @@ package gen import ( + "fmt" + "os" + + "github.com/hashicorp/go-multierror" "github.com/spf13/cobra" ) +var genCmdFlags struct { + force bool +} + // Cmd represents the `gen` command. var Cmd = &cobra.Command{ Use: "gen", Short: "Generate CAs, certificates, and private keys", Long: ``, } + +func init() { + Cmd.PersistentFlags().BoolVarP(&genCmdFlags.force, "force", "f", false, "will overwrite existing files") +} + +func validateFileExists(file string) error { + if !genCmdFlags.force { + if _, err := os.Stat(file); err == nil { + return fmt.Errorf("file %q already exists, use --force to overwrite", file) + } + } + + return nil +} + +func validateFilesExists(files []string) error { + var combinedErr multierror.Error + + for _, file := range files { + if err := validateFileExists(file); err != nil { + combinedErr.Errors = append(combinedErr.Errors, err) + } + } + + return combinedErr.ErrorOrNil() +} diff --git a/cmd/talosctl/cmd/mgmt/gen/key.go b/cmd/talosctl/cmd/mgmt/gen/key.go index 68563a1cd..7c7071469 100644 --- a/cmd/talosctl/cmd/mgmt/gen/key.go +++ b/cmd/talosctl/cmd/mgmt/gen/key.go @@ -30,7 +30,13 @@ var genKeyCmd = &cobra.Command{ return fmt.Errorf("error generating key: %w", err) } - if err := os.WriteFile(genKeyCmdFlags.name+".key", key.PrivateKeyPEM, 0o600); err != nil { + keyFile := genKeyCmdFlags.name + ".key" + + if err = validateFileExists(keyFile); err != nil { + return err + } + + if err := os.WriteFile(keyFile, key.PrivateKeyPEM, 0o600); err != nil { return fmt.Errorf("error writing key: %w", err) } diff --git a/cmd/talosctl/cmd/mgmt/gen/keypair.go b/cmd/talosctl/cmd/mgmt/gen/keypair.go index 84518c243..857d93377 100644 --- a/cmd/talosctl/cmd/mgmt/gen/keypair.go +++ b/cmd/talosctl/cmd/mgmt/gen/keypair.go @@ -43,10 +43,18 @@ var genKeypairCmd = &cobra.Command{ if err != nil { return fmt.Errorf("error generating CA: %s", err) } - if err := os.WriteFile(genKeypairCmdFlags.organization+".crt", ca.CrtPEM, 0o600); err != nil { + + certFile := genKeypairCmdFlags.organization + ".crt" + keyFile := genKeypairCmdFlags.organization + ".key" + + if err = validateFilesExists([]string{certFile, keyFile}); err != nil { + return err + } + + if err := os.WriteFile(certFile, ca.CrtPEM, 0o600); err != nil { return fmt.Errorf("error writing certificate: %s", err) } - if err := os.WriteFile(genKeypairCmdFlags.organization+".key", ca.KeyPEM, 0o600); err != nil { + if err := os.WriteFile(keyFile, ca.KeyPEM, 0o600); err != nil { return fmt.Errorf("error writing key: %s", err) } diff --git a/cmd/talosctl/cmd/mgmt/gen/secrets.go b/cmd/talosctl/cmd/mgmt/gen/secrets.go index e1a2121d8..a92518bdb 100644 --- a/cmd/talosctl/cmd/mgmt/gen/secrets.go +++ b/cmd/talosctl/cmd/mgmt/gen/secrets.go @@ -69,6 +69,10 @@ func writeSecretsBundleToFile(bundle *generate.SecretsBundle) error { return err } + if err = validateFileExists(genSecretsCmdFlags.outputFile); err != nil { + return err + } + return os.WriteFile(genSecretsCmdFlags.outputFile, bundleBytes, 0o600) } diff --git a/internal/integration/cli/gen.go b/internal/integration/cli/gen.go index fe7f39726..7fc4d6313 100644 --- a/internal/integration/cli/gen.go +++ b/internal/integration/cli/gen.go @@ -213,6 +213,17 @@ func (suite *GenSuite) TestSecrets() { suite.RunCLI([]string{"gen", "secrets"}, base.StdoutEmpty()) suite.Assert().FileExists("secrets.yaml") + suite.RunCLI([]string{"gen", "secrets"}, base.StdoutEmpty(), base.ShouldFail(), base.StderrMatchFunc(func(output string) error { + expected := "file \"secrets.yaml\" already exists, use --force to overwrite" + if !strings.Contains(output, expected) { + return fmt.Errorf("stdout does not contain %q: %q", expected, output) + } + + return nil + })) + + suite.RunCLI([]string{"gen", "secrets", "--force"}, base.StdoutEmpty()) + defer os.Remove("secrets.yaml") //nolint:errcheck suite.RunCLI([]string{"gen", "secrets", "--output-file", "/tmp/secrets2.yaml"}, base.StdoutEmpty()) diff --git a/website/content/v1.4/reference/cli.md b/website/content/v1.4/reference/cli.md index 5f00c4663..2170cadbe 100644 --- a/website/content/v1.4/reference/cli.md +++ b/website/content/v1.4/reference/cli.md @@ -1283,6 +1283,7 @@ talosctl gen ca [flags] --cluster string Cluster to connect to if a proxy endpoint is used. --context string Context to be used in command -e, --endpoints strings override default endpoints in Talos configuration + -f, --force will overwrite existing files -n, --nodes strings target the specified nodes --talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order. ``` @@ -1314,7 +1315,6 @@ talosctl gen config [flags] --config-patch-control-plane stringArray patch generated machineconfigs (applied to 'init' and 'controlplane' types) --config-patch-worker stringArray patch generated machineconfigs (applied to 'worker' type) --dns-domain string the dns domain to use for cluster (default "cluster.local") - --force "gen" will overwrite existing files -h, --help help for config --install-disk string the disk to install to (default "/dev/sda") --install-image string the image used to perform an installation (default "ghcr.io/siderolabs/installer:latest") @@ -1338,6 +1338,7 @@ talosctl gen config [flags] --cluster string Cluster to connect to if a proxy endpoint is used. --context string Context to be used in command -e, --endpoints strings override default endpoints in Talos configuration + -f, --force will overwrite existing files -n, --nodes strings target the specified nodes --talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order. ``` @@ -1370,6 +1371,7 @@ talosctl gen crt [flags] --cluster string Cluster to connect to if a proxy endpoint is used. --context string Context to be used in command -e, --endpoints strings override default endpoints in Talos configuration + -f, --force will overwrite existing files -n, --nodes strings target the specified nodes --talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order. ``` @@ -1401,6 +1403,7 @@ talosctl gen csr [flags] --cluster string Cluster to connect to if a proxy endpoint is used. --context string Context to be used in command -e, --endpoints strings override default endpoints in Talos configuration + -f, --force will overwrite existing files -n, --nodes strings target the specified nodes --talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order. ``` @@ -1430,6 +1433,7 @@ talosctl gen key [flags] --cluster string Cluster to connect to if a proxy endpoint is used. --context string Context to be used in command -e, --endpoints strings override default endpoints in Talos configuration + -f, --force will overwrite existing files -n, --nodes strings target the specified nodes --talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order. ``` @@ -1460,6 +1464,7 @@ talosctl gen keypair [flags] --cluster string Cluster to connect to if a proxy endpoint is used. --context string Context to be used in command -e, --endpoints strings override default endpoints in Talos configuration + -f, --force will overwrite existing files -n, --nodes strings target the specified nodes --talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order. ``` @@ -1492,6 +1497,7 @@ talosctl gen secrets [flags] --cluster string Cluster to connect to if a proxy endpoint is used. --context string Context to be used in command -e, --endpoints strings override default endpoints in Talos configuration + -f, --force will overwrite existing files -n, --nodes strings target the specified nodes --talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order. ``` @@ -1507,7 +1513,8 @@ Generate CAs, certificates, and private keys ### Options ``` - -h, --help help for gen + -f, --force will overwrite existing files + -h, --help help for gen ``` ### Options inherited from parent commands