diff --git a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools.md b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools.md index 799e022c2f..4b3b58e50d 100644 --- a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools.md +++ b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools.md @@ -24,6 +24,7 @@ Collection of additional tools to make airgap easier * [zarf](zarf.md) - DevSecOps Airgap Toolkit * [zarf tools archiver](zarf_tools_archiver.md) - Compress/Decompress tools for Zarf packages * [zarf tools clear-cache](zarf_tools_clear-cache.md) - Clears the configured git and image cache directory +* [zarf tools gen-pki](zarf_tools_gen-pki.md) - Generates a Certificate Authority and PKI chain of trust for the given host * [zarf tools get-git-password](zarf_tools_get-git-password.md) - Returns the push user's password for the Git server * [zarf tools monitor](zarf_tools_monitor.md) - Launch K9s tool for managing K8s clusters * [zarf tools registry](zarf_tools_registry.md) - Collection of registry commands provided by Crane diff --git a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_archiver.md b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_archiver.md index 49b6df0a53..867f4bf3e3 100644 --- a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_archiver.md +++ b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_archiver.md @@ -23,5 +23,5 @@ Compress/Decompress tools for Zarf packages * [zarf tools](zarf_tools.md) - Collection of additional tools to make airgap easier * [zarf tools archiver compress](zarf_tools_archiver_compress.md) - Compress a collection of sources based off of the destination file extension -* [zarf tools archiver decompress](zarf_tools_archiver_decompress.md) - Decompress an archive (package) to a specified location. +* [zarf tools archiver decompress](zarf_tools_archiver_decompress.md) - Decompress an archive (package) to a specified location diff --git a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_archiver_compress.md b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_archiver_compress.md index 8b78023cc5..8d0358c645 100644 --- a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_archiver_compress.md +++ b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_archiver_compress.md @@ -3,7 +3,7 @@ Compress a collection of sources based off of the destination file extension ``` -zarf tools archiver compress [SOURCES] [ARCHIVE] [flags] +zarf tools archiver compress {SOURCES} {ARCHIVE} [flags] ``` ### Options diff --git a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_archiver_decompress.md b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_archiver_decompress.md index d329bfdb03..0c755ebcf3 100644 --- a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_archiver_decompress.md +++ b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_archiver_decompress.md @@ -1,9 +1,9 @@ ## zarf tools archiver decompress -Decompress an archive (package) to a specified location. +Decompress an archive (package) to a specified location ``` -zarf tools archiver decompress [ARCHIVE] [DESTINATION] [flags] +zarf tools archiver decompress {ARCHIVE} {DESTINATION} [flags] ``` ### Options diff --git a/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_gen-pki.md b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_gen-pki.md new file mode 100644 index 0000000000..a18994d20f --- /dev/null +++ b/docs/4-user-guide/1-the-zarf-cli/100-cli-commands/zarf_tools_gen-pki.md @@ -0,0 +1,30 @@ +## zarf tools gen-pki + +Generates a Certificate Authority and PKI chain of trust for the given host + +``` +zarf tools gen-pki {HOST} [flags] +``` + +### Options + +``` + -h, --help help for gen-pki + --sub-alt-name stringArray Specify Subject Alternative Names for the certificate +``` + +### Options inherited from parent commands + +``` + -a, --architecture string Architecture for OCI images + -l, --log-level string Log level when running Zarf. Valid options are: warn, info, debug, trace (default "info") + --no-log-file Disable log file creation + --no-progress Disable fancy UI progress bars, spinners, logos, etc + --tmpdir string Specify the temporary directory to use for intermediate files + --zarf-cache string Specify the location of the Zarf cache directory (default "~/.zarf-cache") +``` + +### SEE ALSO + +* [zarf tools](zarf_tools.md) - Collection of additional tools to make airgap easier + diff --git a/src/cmd/tools.go b/src/cmd/tools.go index b9b5aa2558..9e96995080 100644 --- a/src/cmd/tools.go +++ b/src/cmd/tools.go @@ -8,12 +8,15 @@ import ( "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/internal/k8s" "github.com/defenseunicorns/zarf/src/internal/message" + "github.com/defenseunicorns/zarf/src/internal/pki" k9s "github.com/derailed/k9s/cmd" craneCmd "github.com/google/go-containerregistry/cmd/crane/cmd" "github.com/mholt/archiver/v3" "github.com/spf13/cobra" ) +var subAltNames []string + var toolsCmd = &cobra.Command{ Use: "tools", Aliases: []string{"t"}, @@ -28,7 +31,7 @@ var archiverCmd = &cobra.Command{ } var archiverCompressCmd = &cobra.Command{ - Use: "compress [SOURCES] [ARCHIVE]", + Use: "compress {SOURCES} {ARCHIVE}", Aliases: []string{"c"}, Short: "Compress a collection of sources based off of the destination file extension", Args: cobra.MinimumNArgs(2), @@ -42,9 +45,9 @@ var archiverCompressCmd = &cobra.Command{ } var archiverDecompressCmd = &cobra.Command{ - Use: "decompress [ARCHIVE] [DESTINATION]", + Use: "decompress {ARCHIVE} {DESTINATION}", Aliases: []string{"d"}, - Short: "Decompress an archive (package) to a specified location.", + Short: "Decompress an archive (package) to a specified location", Args: cobra.ExactArgs(2), Run: func(cmd *cobra.Command, args []string) { sourceArchive, destinationPath := args[0], args[1] @@ -104,6 +107,27 @@ var clearCacheCmd = &cobra.Command{ if err := os.RemoveAll(config.GetAbsCachePath()); err != nil { message.Fatalf("Unable to clear the cache driectory %s: %s", config.GetAbsCachePath(), err.Error()) } + message.SuccessF("Successfully cleared the cache from %s", config.GetAbsCachePath()) + }, +} + +var generatePKICmd = &cobra.Command{ + Use: "gen-pki {HOST}", + Aliases: []string{"pki"}, + Short: "Generates a Certificate Authority and PKI chain of trust for the given host", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + pki := pki.GeneratePKI(args[0], subAltNames...) + if err := os.WriteFile("tls.ca", pki.CA, 0644); err != nil { + message.Fatalf(err, "Failed to write the CA file: %s", err.Error()) + } + if err := os.WriteFile("tls.crt", pki.Cert, 0644); err != nil { + message.Fatalf(err, "Failed to write the Certificate file: %s", err.Error()) + } + if err := os.WriteFile("tls.key", pki.Key, 0600); err != nil { + message.Fatalf(err, "Failed to write the Key file: %s", err.Error()) + } + message.SuccessF("Successfully created a chain of trust for %s", args[0]) }, } @@ -117,6 +141,9 @@ func init() { toolsCmd.AddCommand(clearCacheCmd) clearCacheCmd.Flags().StringVar(&config.CommonOptions.CachePath, "zarf-cache", config.ZarfDefaultCachePath, "Specify the location of the Zarf artifact cache (images and git repositories)") + toolsCmd.AddCommand(generatePKICmd) + generatePKICmd.Flags().StringArrayVar(&subAltNames, "sub-alt-name", []string{}, "Specify Subject Alternative Names for the certificate") + archiverCmd.AddCommand(archiverCompressCmd) archiverCmd.AddCommand(archiverDecompressCmd) diff --git a/src/internal/pki/pki.go b/src/internal/pki/pki.go index 6942b29642..721d89269e 100644 --- a/src/internal/pki/pki.go +++ b/src/internal/pki/pki.go @@ -25,8 +25,7 @@ const org = "Zarf Cluster" const validFor = time.Hour * 24 * 375 // GeneratePKI create a CA and signed server keypair -func GeneratePKI(host string) types.GeneratedPKI { - +func GeneratePKI(host string, dnsNames ...string) types.GeneratedPKI { results := types.GeneratedPKI{} ca, caKey, err := generateCA(validFor) @@ -34,7 +33,7 @@ func GeneratePKI(host string) types.GeneratedPKI { message.Fatal(err, "Unable to generate the ephemeral CA") } - hostCert, hostKey, err := generateCert(host, ca, caKey, validFor) + hostCert, hostKey, err := generateCert(host, ca, caKey, validFor, dnsNames...) if err != nil { message.Fatalf(err, "Unable to generate the cert for %s", host) } @@ -120,7 +119,7 @@ func generateCA(validFor time.Duration) (*x509.Certificate, *rsa.PrivateKey, err // generateCert generates a new certificate for the given host using the // provided certificate authority. The cert and key files are stored in // the provided files. -func generateCert(host string, ca *x509.Certificate, caKey *rsa.PrivateKey, validFor time.Duration) (*x509.Certificate, *rsa.PrivateKey, error) { +func generateCert(host string, ca *x509.Certificate, caKey *rsa.PrivateKey, validFor time.Duration, dnsNames ...string) (*x509.Certificate, *rsa.PrivateKey, error) { template := newCertificate(validFor) template.IPAddresses = append(template.IPAddresses, net.ParseIP(config.IPV4Localhost)) @@ -130,6 +129,7 @@ func generateCert(host string, ca *x509.Certificate, caKey *rsa.PrivateKey, vali template.IPAddresses = append(template.IPAddresses, ip) } else { template.DNSNames = append(template.DNSNames, host) + template.DNSNames = append(template.DNSNames, dnsNames...) } template.Subject.CommonName = host diff --git a/src/test/e2e/00_use_cli_test.go b/src/test/e2e/00_use_cli_test.go index d3a57bf074..d1a7a1086f 100644 --- a/src/test/e2e/00_use_cli_test.go +++ b/src/test/e2e/00_use_cli_test.go @@ -114,5 +114,22 @@ func TestUseCLI(t *testing.T) { assert.EqualError(t, err, msg, "Did not receive expected error when reading a directory that should not exist") } - e2e.cleanFiles(shasumTestFilePath, cachePath, otherTmpPath, pkgName) + // Test generation of PKI + tlsCA := "tls.ca" + tlsCert := "tls.crt" + tlsKey := "tls.key" + stdOut, stdErr, err = e2e.execZarfCommand("tools", "gen-pki", "github.com", "--sub-alt-name", "google.com") + require.NoError(t, err, stdOut, stdErr) + require.Contains(t, stdErr, "Successfully created a chain of trust for github.com") + + _, err = os.ReadFile(tlsCA) + require.NoError(t, err) + + _, err = os.ReadFile(tlsCert) + require.NoError(t, err) + + _, err = os.ReadFile(tlsKey) + require.NoError(t, err) + + e2e.cleanFiles(shasumTestFilePath, cachePath, otherTmpPath, pkgName, tlsCA, tlsCert, tlsKey) }