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:
committed by
Jonathon Anderson
parent
856223dadd
commit
a1c11db8cc
127
internal/app/wwctl/configure/keys/main.go
Normal file
127
internal/app/wwctl/configure/keys/main.go
Normal 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
|
||||||
|
}
|
||||||
108
internal/app/wwctl/configure/keys/main_test.go
Normal file
108
internal/app/wwctl/configure/keys/main_test.go
Normal 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")
|
||||||
|
})
|
||||||
|
}
|
||||||
35
internal/app/wwctl/configure/keys/root.go
Normal file
35
internal/app/wwctl/configure/keys/root.go
Normal 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
|
||||||
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
package configure
|
package configure
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/configure"
|
"github.com/warewulf/warewulf/internal/pkg/configure"
|
||||||
|
"github.com/warewulf/warewulf/internal/pkg/util"
|
||||||
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,6 +23,19 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if allFunctions {
|
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()
|
err = configure.WAREWULFD()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/warewulf/warewulf/internal/app/wwctl/configure/dhcp"
|
"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/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/nfs"
|
||||||
"github.com/warewulf/warewulf/internal/app/wwctl/configure/ssh"
|
"github.com/warewulf/warewulf/internal/app/wwctl/configure/ssh"
|
||||||
"github.com/warewulf/warewulf/internal/app/wwctl/configure/tftp"
|
"github.com/warewulf/warewulf/internal/app/wwctl/configure/tftp"
|
||||||
@@ -29,6 +30,7 @@ func init() {
|
|||||||
baseCmd.AddCommand(ssh.GetCommand())
|
baseCmd.AddCommand(ssh.GetCommand())
|
||||||
baseCmd.AddCommand(nfs.GetCommand())
|
baseCmd.AddCommand(nfs.GetCommand())
|
||||||
baseCmd.AddCommand(hostfile.GetCommand())
|
baseCmd.AddCommand(hostfile.GetCommand())
|
||||||
|
baseCmd.AddCommand(keys.GetCommand())
|
||||||
baseCmd.AddCommand(warewulfd.GetCommand())
|
baseCmd.AddCommand(warewulfd.GetCommand())
|
||||||
|
|
||||||
baseCmd.Flags().BoolVarP(&allFunctions, "all", "a", false, "Configure all services")
|
baseCmd.Flags().BoolVarP(&allFunctions, "all", "a", false, "Configure all services")
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ func (conf TFTPConf) Enabled() bool {
|
|||||||
// BaseConf.
|
// BaseConf.
|
||||||
type WarewulfConf struct {
|
type WarewulfConf struct {
|
||||||
Port int `yaml:"port,omitempty" default:"9873"`
|
Port int `yaml:"port,omitempty" default:"9873"`
|
||||||
|
SecurePort int `yaml:"secure port,omitempty" default:"9874"`
|
||||||
SecureP *bool `yaml:"secure,omitempty" default:"true"`
|
SecureP *bool `yaml:"secure,omitempty" default:"true"`
|
||||||
|
EnableHttpsP *bool `yaml:"enable https,omitempty" default:"false"`
|
||||||
UpdateInterval int `yaml:"update interval,omitempty" default:"60"`
|
UpdateInterval int `yaml:"update interval,omitempty" default:"60"`
|
||||||
AutobuildOverlaysP *bool `yaml:"autobuild overlays,omitempty" default:"true"`
|
AutobuildOverlaysP *bool `yaml:"autobuild overlays,omitempty" default:"true"`
|
||||||
EnableHostOverlayP *bool `yaml:"host overlay,omitempty" default:"true"`
|
EnableHostOverlayP *bool `yaml:"host overlay,omitempty" default:"true"`
|
||||||
@@ -54,6 +56,10 @@ func (conf WarewulfConf) Secure() bool {
|
|||||||
return util.BoolP(conf.SecureP)
|
return util.BoolP(conf.SecureP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (conf WarewulfConf) EnableHttps() bool {
|
||||||
|
return util.BoolP(conf.EnableHttpsP)
|
||||||
|
}
|
||||||
|
|
||||||
func (conf WarewulfConf) AutobuildOverlays() bool {
|
func (conf WarewulfConf) AutobuildOverlays() bool {
|
||||||
return util.BoolP(conf.AutobuildOverlaysP)
|
return util.BoolP(conf.AutobuildOverlaysP)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,12 @@ func TestParse(t *testing.T) {
|
|||||||
result: `
|
result: `
|
||||||
warewulf:
|
warewulf:
|
||||||
autobuild overlays: true
|
autobuild overlays: true
|
||||||
|
enable https: false
|
||||||
grubboot: false
|
grubboot: false
|
||||||
host overlay: true
|
host overlay: true
|
||||||
port: 9873
|
port: 9873
|
||||||
secure: true
|
secure: true
|
||||||
|
secure port: 9874
|
||||||
update interval: 60
|
update interval: 60
|
||||||
nfs:
|
nfs:
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -63,10 +65,12 @@ network: 192.168.0.0
|
|||||||
netmask: 255.255.255.0
|
netmask: 255.255.255.0
|
||||||
warewulf:
|
warewulf:
|
||||||
autobuild overlays: true
|
autobuild overlays: true
|
||||||
|
enable https: false
|
||||||
grubboot: false
|
grubboot: false
|
||||||
host overlay: true
|
host overlay: true
|
||||||
port: 9873
|
port: 9873
|
||||||
secure: true
|
secure: true
|
||||||
|
secure port: 9874
|
||||||
update interval: 60
|
update interval: 60
|
||||||
nfs:
|
nfs:
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -111,10 +115,12 @@ network: 192.168.0.0
|
|||||||
netmask: 255.255.0.0
|
netmask: 255.255.0.0
|
||||||
warewulf:
|
warewulf:
|
||||||
autobuild overlays: true
|
autobuild overlays: true
|
||||||
|
enable https: false
|
||||||
grubboot: false
|
grubboot: false
|
||||||
host overlay: true
|
host overlay: true
|
||||||
port: 9873
|
port: 9873
|
||||||
secure: true
|
secure: true
|
||||||
|
secure port: 9874
|
||||||
update interval: 60
|
update interval: 60
|
||||||
nfs:
|
nfs:
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -156,10 +162,12 @@ ipaddr6: "2001:db8::1"
|
|||||||
prefixlen6: "64"
|
prefixlen6: "64"
|
||||||
warewulf:
|
warewulf:
|
||||||
autobuild overlays: true
|
autobuild overlays: true
|
||||||
|
enable https: false
|
||||||
grubboot: false
|
grubboot: false
|
||||||
host overlay: true
|
host overlay: true
|
||||||
port: 9873
|
port: 9873
|
||||||
secure: true
|
secure: true
|
||||||
|
secure port: 9874
|
||||||
update interval: 60
|
update interval: 60
|
||||||
nfs:
|
nfs:
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -230,10 +238,12 @@ netmask: 255.255.255.0
|
|||||||
network: 192.168.200.0
|
network: 192.168.200.0
|
||||||
warewulf:
|
warewulf:
|
||||||
autobuild overlays: true
|
autobuild overlays: true
|
||||||
|
enable https: false
|
||||||
grubboot: false
|
grubboot: false
|
||||||
host overlay: true
|
host overlay: true
|
||||||
port: 9873
|
port: 9873
|
||||||
secure: false
|
secure: false
|
||||||
|
secure port: 9874
|
||||||
update interval: 60
|
update interval: 60
|
||||||
nfs:
|
nfs:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|||||||
104
internal/pkg/configure/keys.go
Normal file
104
internal/pkg/configure/keys.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package configure
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
|
||||||
|
"github.com/warewulf/warewulf/internal/pkg/wwlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
GenKeys checks for existence of x509 keys and creates them if they don't exist.
|
||||||
|
*/
|
||||||
|
func GenKeys() error {
|
||||||
|
conf := warewulfconf.Get()
|
||||||
|
keystore := path.Join(conf.Paths.Sysconfdir, "warewulf", "keys")
|
||||||
|
|
||||||
|
keyFile := path.Join(keystore, "warewulf.key")
|
||||||
|
certFile := path.Join(keystore, "warewulf.crt")
|
||||||
|
pubFile := path.Join(keystore, "warewulf.rsa.pub")
|
||||||
|
wwlog.Verbose("Generating new x509 keys in %s", keystore)
|
||||||
|
priv, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate rsa key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate serial number: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
template := x509.Certificate{
|
||||||
|
SerialNumber: serialNumber,
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: "Warewulf Server",
|
||||||
|
Organization: []string{"Warewulf"},
|
||||||
|
},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 10),
|
||||||
|
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip := net.ParseIP(conf.Ipaddr); ip != nil {
|
||||||
|
template.IPAddresses = append(template.IPAddresses, ip)
|
||||||
|
}
|
||||||
|
if ip := net.ParseIP(conf.Ipaddr6); ip != nil {
|
||||||
|
template.IPAddresses = append(template.IPAddresses, ip)
|
||||||
|
}
|
||||||
|
if conf.Fqdn != "" {
|
||||||
|
template.DNSNames = append(template.DNSNames, conf.Fqdn)
|
||||||
|
}
|
||||||
|
template.DNSNames = append(template.DNSNames, "warewulf")
|
||||||
|
|
||||||
|
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create certificate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open key file for writing: %w", err)
|
||||||
|
}
|
||||||
|
defer keyOut.Close()
|
||||||
|
if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
|
||||||
|
return fmt.Errorf("failed to write data to key file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certOut, err := os.OpenFile(certFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open cert file for writing: %w", err)
|
||||||
|
}
|
||||||
|
defer certOut.Close()
|
||||||
|
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
||||||
|
return fmt.Errorf("failed to write data to cert file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubBytes, err := x509.MarshalPKIXPublicKey(&priv.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal public key: %w", err)
|
||||||
|
}
|
||||||
|
pubOut, err := os.OpenFile(pubFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open public key file for writing: %w", err)
|
||||||
|
}
|
||||||
|
defer pubOut.Close()
|
||||||
|
if err := pem.Encode(pubOut, &pem.Block{Type: "PUBLIC KEY", Bytes: pubBytes}); err != nil {
|
||||||
|
return fmt.Errorf("failed to write data to public key file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -39,6 +39,7 @@ type templateVars struct {
|
|||||||
KernelArgs string
|
KernelArgs string
|
||||||
KernelVersion string
|
KernelVersion string
|
||||||
Root string
|
Root string
|
||||||
|
Https bool
|
||||||
Tags map[string]string
|
Tags map[string]string
|
||||||
NetDevs map[string]*node.NetDev
|
NetDevs map[string]*node.NetDev
|
||||||
}
|
}
|
||||||
@@ -247,6 +248,7 @@ func ProvisionSend(w http.ResponseWriter, req *http.Request) {
|
|||||||
Ipaddr: conf.Ipaddr,
|
Ipaddr: conf.Ipaddr,
|
||||||
Ipaddr6: ipaddr6,
|
Ipaddr6: ipaddr6,
|
||||||
Port: strconv.Itoa(conf.Warewulf.Port),
|
Port: strconv.Itoa(conf.Warewulf.Port),
|
||||||
|
Https: conf.Warewulf.EnableHttps(),
|
||||||
Authority: authority,
|
Authority: authority,
|
||||||
Hostname: remoteNode.Id(),
|
Hostname: remoteNode.Id(),
|
||||||
Hwaddr: rinfo.hwaddr,
|
Hwaddr: rinfo.hwaddr,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -82,6 +83,24 @@ func RunServer() error {
|
|||||||
defaultHandler.ServeHTTP(w, r)
|
defaultHandler.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if conf.Warewulf.EnableHttps() {
|
||||||
|
key := path.Join(conf.Paths.Sysconfdir, "warewulf", "keys", "warewulf.key")
|
||||||
|
crt := path.Join(conf.Paths.Sysconfdir, "warewulf", "keys", "warewulf.crt")
|
||||||
|
|
||||||
|
if !util.IsFile(key) || !util.IsFile(crt) {
|
||||||
|
wwlog.Error("HTTPS enabled but keys not found in %s", path.Join(conf.Paths.Sysconfdir, "warewulf", "keys"))
|
||||||
|
} else {
|
||||||
|
go func() {
|
||||||
|
wwlog.Info("Starting HTTPS service on port %d", conf.Warewulf.SecurePort)
|
||||||
|
if err := http.ListenAndServeTLS(":"+strconv.Itoa(conf.Warewulf.SecurePort), crt, key, dispatchHandler); err != nil {
|
||||||
|
wwlog.Error("Could not start HTTPS service: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wwlog.Info("Starting HTTP service on port %d", daemonPort)
|
||||||
if err := http.ListenAndServe(":"+strconv.Itoa(daemonPort), dispatchHandler); err != nil {
|
if err := http.ListenAndServe(":"+strconv.Itoa(daemonPort), dispatchHandler); err != nil {
|
||||||
return fmt.Errorf("could not start listening service: %w", err)
|
return fmt.Errorf("could not start listening service: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user