enable https and key handling

- add key generation and import for warewulfd
- add https port to warewulfd
- configuration optiions for port and

keys
This commit is contained in:
Christian Goll
2025-12-01 16:15:16 +01:00
committed by Jonathon Anderson
parent 856223dadd
commit a1c11db8cc
10 changed files with 429 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
package keys
import (
"crypto/x509"
"encoding/pem"
"fmt"
"os"
"path"
"strings"
"text/tabwriter"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/config"
"github.com/warewulf/warewulf/internal/pkg/configure"
"github.com/warewulf/warewulf/internal/pkg/util"
)
func CobraRunE(cmd *cobra.Command, args []string) error {
conf := config.Get()
keystore := path.Join(conf.Paths.Sysconfdir, "warewulf", "keys")
if err := os.MkdirAll(keystore, 0755); err != nil {
return fmt.Errorf("could not create keystore directory: %w", err)
}
keyFile := path.Join(keystore, "warewulf.key")
certFile := path.Join(keystore, "warewulf.crt")
if importPath != "" {
info, err := os.Stat(importPath)
if err != nil {
return fmt.Errorf("could not access import path: %w", err)
}
var sourceKey, sourceCert string
if info.IsDir() {
sourceKey = path.Join(importPath, "warewulf.key")
sourceCert = path.Join(importPath, "warewulf.crt")
} else {
return fmt.Errorf("import path must be a directory containing warewulf.key and warewulf.crt")
}
if err := util.CopyFile(sourceKey, keyFile); err != nil {
return fmt.Errorf("failed to import key: %w", err)
}
if err := util.CopyFile(sourceCert, certFile); err != nil {
return fmt.Errorf("failed to import cert: %w", err)
}
fmt.Fprintf(cmd.OutOrStdout(), "Imported keys from %s\n", importPath)
if err := configure.WAREWULFD(); err != nil {
return fmt.Errorf("failed to restart warewulfd: %w", err)
}
return nil
}
if exportPath != "" {
if err := os.MkdirAll(exportPath, 0755); err != nil {
return fmt.Errorf("could not create export directory: %w", err)
}
if err := util.CopyFile(keyFile, path.Join(exportPath, "warewulf.key")); err != nil {
return fmt.Errorf("failed to export key: %w", err)
}
if err := util.CopyFile(certFile, path.Join(exportPath, "warewulf.crt")); err != nil {
return fmt.Errorf("failed to export cert: %w", err)
}
fmt.Fprintf(cmd.OutOrStdout(), "Exported keys to %s\n", exportPath)
return nil
}
if util.IsFile(keyFile) && util.IsFile(certFile) && !force {
if create {
fmt.Fprintf(cmd.OutOrStdout(), "Keys already exist in %s\n", keystore)
}
} else {
if create {
if err := configure.GenKeys(); err != nil {
return err
}
if err := configure.WAREWULFD(); err != nil {
return fmt.Errorf("failed to restart warewulfd: %w", err)
}
} else {
return fmt.Errorf("keys not found in: %s", keystore)
}
}
w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "Private Key:\t%s\n", keyFile)
fmt.Fprintf(w, "Certificate:\t%s\n", certFile)
keyBytes, err := os.ReadFile(keyFile)
if err == nil {
block, _ := pem.Decode(keyBytes)
if block != nil {
if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
fmt.Fprintf(w, "Key Size:\t%d bits\n", key.N.BitLen())
}
}
}
certBytes, err := os.ReadFile(certFile)
if err == nil {
block, _ := pem.Decode(certBytes)
if block != nil {
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
fmt.Fprintf(w, "Issuer:\t%s\n", cert.Issuer)
fmt.Fprintf(w, "Subject:\t%s\n", cert.Subject)
fmt.Fprintf(w, "Valid From:\t%s\n", cert.NotBefore)
fmt.Fprintf(w, "Valid Until:\t%s\n", cert.NotAfter)
fmt.Fprintf(w, "Serial Nr:\t%s\n", cert.SerialNumber)
var ipStrings []string
for _, ip := range cert.IPAddresses {
ipStrings = append(ipStrings, ip.String())
}
fmt.Fprintf(w, "IP Addresses:\t%s\n", strings.Join(ipStrings, ", "))
fmt.Fprintf(w, "DNS Names:\t%s\n", strings.Join(cert.DNSNames, ", "))
}
}
}
w.Flush()
return nil
}

View File

@@ -0,0 +1,108 @@
package keys
import (
"bytes"
"path"
"testing"
"github.com/stretchr/testify/assert"
"github.com/warewulf/warewulf/internal/pkg/testenv"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func Test_Keys(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll()
// Define a keystore path within the test environment
keystoreRelPath := "etc/warewulf/keys"
keystorePath := env.GetPath(keystoreRelPath)
// Reload configuration to pick up the change
env.Configure()
t.Run("keys create", func(t *testing.T) {
baseCmd := GetCommand()
// Reset flags
create = true
importPath = ""
exportPath = ""
baseCmd.SetArgs([]string{"--create"})
buf := new(bytes.Buffer)
baseCmd.SetOut(buf)
baseCmd.SetErr(buf)
wwlog.SetLogWriter(buf)
err := baseCmd.Execute()
assert.NoError(t, err)
assert.FileExists(t, path.Join(keystorePath, "warewulf.key"))
assert.FileExists(t, path.Join(keystorePath, "warewulf.crt"))
assert.FileExists(t, path.Join(keystorePath, "warewulf.rsa.pub"))
})
t.Run("keys exist check", func(t *testing.T) {
baseCmd := GetCommand()
// Reset flags
create = true // Even with create, it should say they exist
importPath = ""
exportPath = ""
baseCmd.SetArgs([]string{"--create"})
buf := new(bytes.Buffer)
baseCmd.SetOut(buf)
baseCmd.SetErr(buf)
wwlog.SetLogWriter(buf)
err := baseCmd.Execute()
assert.NoError(t, err)
assert.Contains(t, buf.String(), "Keys already exist")
})
t.Run("keys display", func(t *testing.T) {
baseCmd := GetCommand()
// Reset flags
create = false
importPath = ""
exportPath = ""
baseCmd.SetArgs([]string{})
buf := new(bytes.Buffer)
baseCmd.SetOut(buf)
baseCmd.SetErr(buf)
wwlog.SetLogWriter(buf)
err := baseCmd.Execute()
assert.NoError(t, err)
assert.Contains(t, buf.String(), "Private Key:")
assert.Contains(t, buf.String(), "Certificate:")
assert.Contains(t, buf.String(), "Key Size:")
assert.Contains(t, buf.String(), "Issuer:")
assert.Contains(t, buf.String(), "Subject:")
assert.Contains(t, buf.String(), "Valid From:")
assert.Contains(t, buf.String(), "Valid Until:")
})
t.Run("keys export", func(t *testing.T) {
exportRelPath := "exported_keys"
exportFullPath := env.GetPath(exportRelPath)
baseCmd := GetCommand()
// Reset flags
create = false
importPath = ""
exportPath = exportFullPath
baseCmd.SetArgs([]string{"--export", exportFullPath})
buf := new(bytes.Buffer)
baseCmd.SetOut(buf)
baseCmd.SetErr(buf)
wwlog.SetLogWriter(buf)
err := baseCmd.Execute()
assert.NoError(t, err)
assert.FileExists(t, path.Join(exportFullPath, "warewulf.key"))
assert.FileExists(t, path.Join(exportFullPath, "warewulf.crt"))
assert.Contains(t, buf.String(), "Exported keys to")
})
}

View File

@@ -0,0 +1,35 @@
package keys
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
)
var (
baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "keys [OPTIONS]",
Short: "Manage and initialize x509 keys",
Long: "This application allows you to manage the x509 keys for Warewulf\n" +
"based on the configuration in the warewulf.conf file.",
RunE: CobraRunE,
Args: cobra.NoArgs,
ValidArgsFunction: completions.None,
}
importPath string
exportPath string
create bool
force bool
)
func init() {
baseCmd.PersistentFlags().StringVar(&importPath, "import", "", "Import keys from directory")
baseCmd.PersistentFlags().StringVar(&exportPath, "export", "", "Export keys to directory")
baseCmd.PersistentFlags().BoolVar(&create, "create", false, "Create keys if they do not exist")
baseCmd.PersistentFlags().BoolVarP(&force, "force", "f", false, "Enforce creation of keys even if they exist")
}
// GetCommand returns the root cobra.Command for the application.
func GetCommand() *cobra.Command {
return baseCmd
}

View File

@@ -1,11 +1,14 @@
package configure
import (
"fmt"
"os"
"path"
"github.com/spf13/cobra"
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
"github.com/warewulf/warewulf/internal/pkg/configure"
"github.com/warewulf/warewulf/internal/pkg/util"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
@@ -20,6 +23,19 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
}
if allFunctions {
keystore := path.Join(conf.Paths.Sysconfdir, "warewulf", "keys")
keyFile := path.Join(keystore, "warewulf.key")
certFile := path.Join(keystore, "warewulf.crt")
if !util.IsFile(keyFile) || !util.IsFile(certFile) {
err = configure.GenKeys()
if err != nil {
return err
}
} else {
fmt.Printf("Keys already exist in %s\n", keystore)
}
err = configure.WAREWULFD()
if err != nil {
return err

View File

@@ -4,6 +4,7 @@ import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/configure/dhcp"
"github.com/warewulf/warewulf/internal/app/wwctl/configure/hostfile"
"github.com/warewulf/warewulf/internal/app/wwctl/configure/keys"
"github.com/warewulf/warewulf/internal/app/wwctl/configure/nfs"
"github.com/warewulf/warewulf/internal/app/wwctl/configure/ssh"
"github.com/warewulf/warewulf/internal/app/wwctl/configure/tftp"
@@ -29,6 +30,7 @@ func init() {
baseCmd.AddCommand(ssh.GetCommand())
baseCmd.AddCommand(nfs.GetCommand())
baseCmd.AddCommand(hostfile.GetCommand())
baseCmd.AddCommand(keys.GetCommand())
baseCmd.AddCommand(warewulfd.GetCommand())
baseCmd.Flags().BoolVarP(&allFunctions, "all", "a", false, "Configure all services")