Support requiring TLS for API access

Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
Jonathon Anderson
2026-03-06 16:24:06 -07:00
parent 779434331c
commit 395fa2c283
10 changed files with 30 additions and 16 deletions

View File

@@ -117,7 +117,7 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
} }
tlsConfig := &tls.Config{MinVersion: tls.VersionTLS13} tlsConfig := &tls.Config{MinVersion: tls.VersionTLS13}
if conf.Warewulf.EnableTLS() { if conf.Warewulf.TLSEnabled() {
caCert, err := os.ReadFile("/warewulf/tls/warewulf.crt") caCert, err := os.ReadFile("/warewulf/tls/warewulf.crt")
if err != nil { if err != nil {
wwlog.Error("failed to read ca cert: %s", err) wwlog.Error("failed to read ca cert: %s", err)
@@ -260,7 +260,7 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
port := conf.Warewulf.Port port := conf.Warewulf.Port
scheme := "http" scheme := "http"
if conf.Warewulf.EnableTLS() { if conf.Warewulf.TLSEnabled() {
port = conf.Warewulf.TlsPort port = conf.Warewulf.TlsPort
scheme = "https" scheme = "https"
} }

View File

@@ -71,7 +71,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
return nil return nil
} }
if !conf.Warewulf.EnableTLS() { if !conf.Warewulf.TLSEnabled() {
fmt.Fprintf(cmd.OutOrStdout(), "TLS is not enabled in warewulf.conf\n") fmt.Fprintf(cmd.OutOrStdout(), "TLS is not enabled in warewulf.conf\n")
return nil return nil
} }

View File

@@ -29,6 +29,7 @@ func (n IPNet) IPNet() net.IPNet {
type APIConf struct { type APIConf struct {
EnabledP *bool `yaml:"enabled,omitempty" default:"false"` EnabledP *bool `yaml:"enabled,omitempty" default:"false"`
TLSEnabledP *bool `yaml:"tls,omitempty"`
AllowedNets []IPNet `yaml:"allowed subnets,omitempty" default:"[\"127.0.0.0/8\", \"::1/128\"]"` AllowedNets []IPNet `yaml:"allowed subnets,omitempty" default:"[\"127.0.0.0/8\", \"::1/128\"]"`
} }
@@ -49,3 +50,7 @@ func (conf *APIConf) Unmarshal(unmarshal func(interface{}) error) error {
func (conf APIConf) Enabled() bool { func (conf APIConf) Enabled() bool {
return util.BoolP(conf.EnabledP) return util.BoolP(conf.EnabledP)
} }
func (conf APIConf) TLSEnabled() bool {
return util.BoolP(conf.TLSEnabledP)
}

View File

@@ -44,7 +44,7 @@ type WarewulfConf struct {
Port int `yaml:"port,omitempty" default:"9873"` Port int `yaml:"port,omitempty" default:"9873"`
TlsPort int `yaml:"tls port,omitempty" default:"9874"` TlsPort int `yaml:"tls port,omitempty" default:"9874"`
SecureP *bool `yaml:"secure,omitempty" default:"true"` SecureP *bool `yaml:"secure,omitempty" default:"true"`
EnableTLSP *bool `yaml:"tls,omitempty" default:"false"` TLSEnabledP *bool `yaml:"tls,omitempty"`
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"`
@@ -56,8 +56,8 @@ func (conf WarewulfConf) Secure() bool {
return util.BoolP(conf.SecureP) return util.BoolP(conf.SecureP)
} }
func (conf WarewulfConf) EnableTLS() bool { func (conf WarewulfConf) TLSEnabled() bool {
return util.BoolP(conf.EnableTLSP) return util.BoolP(conf.TLSEnabledP)
} }
func (conf WarewulfConf) AutobuildOverlays() bool { func (conf WarewulfConf) AutobuildOverlays() bool {

View File

@@ -17,7 +17,6 @@ func TestParse(t *testing.T) {
result: ` result: `
warewulf: warewulf:
autobuild overlays: true autobuild overlays: true
tls: false
grubboot: false grubboot: false
host overlay: true host overlay: true
port: 9873 port: 9873
@@ -65,7 +64,6 @@ network: 192.168.0.0
netmask: 255.255.255.0 netmask: 255.255.255.0
warewulf: warewulf:
autobuild overlays: true autobuild overlays: true
tls: false
grubboot: false grubboot: false
host overlay: true host overlay: true
port: 9873 port: 9873
@@ -115,7 +113,6 @@ network: 192.168.0.0
netmask: 255.255.0.0 netmask: 255.255.0.0
warewulf: warewulf:
autobuild overlays: true autobuild overlays: true
tls: false
grubboot: false grubboot: false
host overlay: true host overlay: true
port: 9873 port: 9873
@@ -162,7 +159,6 @@ ipaddr6: "2001:db8::1"
prefixlen6: "64" prefixlen6: "64"
warewulf: warewulf:
autobuild overlays: true autobuild overlays: true
tls: false
grubboot: false grubboot: false
host overlay: true host overlay: true
port: 9873 port: 9873
@@ -238,7 +234,6 @@ netmask: 255.255.255.0
network: 192.168.200.0 network: 192.168.200.0
warewulf: warewulf:
autobuild overlays: true autobuild overlays: true
tls: false
grubboot: false grubboot: false
host overlay: true host overlay: true
port: 9873 port: 9873

View File

@@ -23,7 +23,7 @@ import (
// Returns true if new keys were generated. // Returns true if new keys were generated.
func TLS(force bool) (bool, error) { func TLS(force bool) (bool, error) {
conf := warewulfconf.Get() conf := warewulfconf.Get()
if !conf.Warewulf.EnableTLS() { if !conf.Warewulf.TLSEnabled() {
return false, nil return false, nil
} }

View File

@@ -58,7 +58,7 @@ func buildTemplateVars(conf *warewulfconf.WarewulfYaml, rinfo parsedRequest, rem
Ipaddr: conf.Ipaddr, Ipaddr: conf.Ipaddr,
Ipaddr6: ipaddr6, Ipaddr6: ipaddr6,
Port: strconv.Itoa(conf.Warewulf.Port), Port: strconv.Itoa(conf.Warewulf.Port),
TLS: conf.Warewulf.EnableTLS(), TLS: conf.Warewulf.TLSEnabled(),
Authority: authority, Authority: authority,
Hostname: remoteNode.Id(), Hostname: remoteNode.Id(),
Hwaddr: rinfo.hwaddr, Hwaddr: rinfo.hwaddr,

View File

@@ -95,7 +95,7 @@ func HandleSystemOverlay(w http.ResponseWriter, req *http.Request) {
// If TLS is enabled, returns 403 Forbidden for plain-HTTP requests. // If TLS is enabled, returns 403 Forbidden for plain-HTTP requests.
// If an explicit ?overlay= list is present, delegates to HandleOverlayList. // If an explicit ?overlay= list is present, delegates to HandleOverlayList.
func HandleRuntimeOverlay(w http.ResponseWriter, req *http.Request) { func HandleRuntimeOverlay(w http.ResponseWriter, req *http.Request) {
if config.Get().Warewulf.EnableTLS() && req.TLS == nil { if config.Get().Warewulf.TLSEnabled() && req.TLS == nil {
wwlog.Denied("runtime overlay requested over insecure connection") wwlog.Denied("runtime overlay requested over insecure connection")
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
return return

View File

@@ -37,6 +37,17 @@ func (h *slashFix) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.mux.ServeHTTP(w, r) h.mux.ServeHTTP(w, r)
} }
func requireTLS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.TLS == nil {
wwlog.Denied("API request over insecure connection")
w.WriteHeader(http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
func configureRootHandler(apiHandler http.Handler) *slashFix { func configureRootHandler(apiHandler http.Handler) *slashFix {
var wwHandler http.ServeMux var wwHandler http.ServeMux
wwHandler.HandleFunc("/provision/", warewulfd.HandleProvision) wwHandler.HandleFunc("/provision/", warewulfd.HandleProvision)
@@ -89,13 +100,16 @@ func RunServer() error {
var apiHandler http.Handler var apiHandler http.Handler
if conf.API != nil && conf.API.Enabled() { if conf.API != nil && conf.API.Enabled() {
apiHandler = api.Handler(auth, conf.API.AllowedIPNets()) apiHandler = api.Handler(auth, conf.API.AllowedIPNets())
if conf.API.TLSEnabled() {
apiHandler = requireTLS(apiHandler)
}
} }
httpHandler := configureRootHandler(apiHandler) httpHandler := configureRootHandler(apiHandler)
errChan := make(chan error, 2) errChan := make(chan error, 2)
if conf.Warewulf.EnableTLS() { if conf.Warewulf.TLSEnabled() {
key := path.Join(conf.Paths.Sysconfdir, "warewulf", "tls", "warewulf.key") key := path.Join(conf.Paths.Sysconfdir, "warewulf", "tls", "warewulf.key")
crt := path.Join(conf.Paths.Sysconfdir, "warewulf", "tls", "warewulf.crt") crt := path.Join(conf.Paths.Sysconfdir, "warewulf", "tls", "warewulf.crt")

View File

@@ -11,6 +11,6 @@ WWIPMI_WRITE="{{$.Ipmi.Write.Bool}}"
{{- if $.Ipmi.Tags.vlan }} {{- if $.Ipmi.Tags.vlan }}
WWIPMI_VLAN="{{$.Ipmi.Tags.vlan}}" WWIPMI_VLAN="{{$.Ipmi.Tags.vlan}}"
{{- end }} {{- end }}
WWTLS={{$.Warewulf.EnableTLS}} WWTLS={{$.Warewulf.TLSEnabled}}
WWTLSPORT={{$.Warewulf.TlsPort}} WWTLSPORT={{$.Warewulf.TlsPort}}
WWIPADDR={{$.Ipaddr}} WWIPADDR={{$.Ipaddr}}