Repository: metal3-io/baremetal-operator
Pull request #1068 (2022-01-13 10:45:58)

Check if bmc hostname follows DNS Standard
Adds a function to check if the bmc.address follows DNS standard and exports the GetParsedURL function to make it available in other validations.


Patch:
diff --git a/pkg/hardwareutils/bmc/access.go b/pkg/hardwareutils/bmc/access.go
index 751fcf8f..960b9eff 100644
--- a/pkg/hardwareutils/bmc/access.go
+++ b/pkg/hardwareutils/bmc/access.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"net"
 	"net/url"
+	"regexp"
 	"strings"
 
 	"github.com/pkg/errors"
@@ -86,8 +87,8 @@ type AccessDetails interface {
 	BuildBIOSSettings(firmwareConfig *FirmwareConfig) (settings []map[string]string, err error)
 }
 
-func getParsedURL(address string) (parsedURL *url.URL, err error) {
-	// Start by assuming "type://host:port"
+func GetParsedURL(address string) (parsedURL *url.URL, err error) {
+
 	parsedURL, err = url.Parse(address)
 	if err != nil {
 		// We failed to parse the URL, but it may just be a host or
@@ -127,6 +128,12 @@ func getParsedURL(address string) (parsedURL *url.URL, err error) {
 			}
 		}
 	}
+
+	// Check for expected hostname format
+	if err := checkDNSValid(parsedURL.Hostname()); err != nil {
+		return nil, errors.Wrap(err, "failed to parse BMC address information")
+	}
+
 	return parsedURL, nil
 }
 
@@ -138,7 +145,7 @@ func NewAccessDetails(address string, disableCertificateVerification bool) (Acce
 		return nil, errors.New("missing BMC address")
 	}
 
-	parsedURL, err := getParsedURL(address)
+	parsedURL, err := GetParsedURL(address)
 	if err != nil {
 		return nil, err
 	}
@@ -150,3 +157,23 @@ func NewAccessDetails(address string, disableCertificateVerification bool) (Acce
 
 	return factory(parsedURL, disableCertificateVerification)
 }
+
+func checkDNSValid(address string) error {
+
+	// Allowing empty BMC address
+	if address == "" {
+		return nil
+	}
+
+	// Check if its a IPv6/IPv4 address
+	if net.ParseIP(address) != nil {
+		return nil
+	}
+
+	// Check if BMC address hostname follows DNS Standard
+	valid, _ := regexp.MatchString(`^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9])$`, address)
+	if !valid {
+		return fmt.Errorf("BMC address hostname/IP : [%s] is invalid", address)
+	}
+	return nil
+}


Test patch:
diff --git a/pkg/hardwareutils/bmc/access_test.go b/pkg/hardwareutils/bmc/access_test.go
index 9dcd84e2..d1ec605a 100644
--- a/pkg/hardwareutils/bmc/access_test.go
+++ b/pkg/hardwareutils/bmc/access_test.go
@@ -50,6 +50,38 @@ func TestParse(t *testing.T) {
 			Path:     "",
 		},
 
+		{
+			Scenario: "valid dns hostname",
+			Address:  "my.examplehost.com",
+			Type:     "ipmi",
+			Port:     "",
+			Host:     "my.examplehost.com",
+			Hostname: "my.examplehost.com",
+			Path:     "",
+		},
+
+		{
+			Scenario:    "invalid dns hostname",
+			Address:     "my-.examplehost.com",
+			Type:        "ipmi",
+			Port:        "",
+			Host:        "my-.examplehost.com",
+			Hostname:    "my-.examplehost.com",
+			Path:        "",
+			ExpectError: true,
+		},
+
+		{
+			Scenario:    "invalid ipv6 host address",
+			Address:     "[fe80::fc33:62ff:fe33:8xff]:6223",
+			Type:        "ipmi",
+			Port:        "6223",
+			Host:        "fe80::fc33:62ff:fe33.8xff",
+			Hostname:    "[fe80::fc33:62ff:fe33:8xff]:6223",
+			Path:        "",
+			ExpectError: true,
+		},
+
 		{
 			Scenario: "host and port",
 			Address:  "192.168.122.1:6233",
@@ -368,7 +400,7 @@ func TestParse(t *testing.T) {
 		},
 	} {
 		t.Run(tc.Scenario, func(t *testing.T) {
-			url, err := getParsedURL(tc.Address)
+			url, err := GetParsedURL(tc.Address)
 
 			if tc.ExpectError {
 				if err == nil {