Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions data/data/install.openshift.io_installconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ spec:
type: string
hostName:
type: string
macAddress:
type: string
password:
type: string
username:
Expand Down Expand Up @@ -1743,6 +1745,8 @@ spec:
type: string
hostName:
type: string
macAddress:
type: string
password:
type: string
username:
Expand Down Expand Up @@ -3305,6 +3309,8 @@ spec:
type: string
hostName:
type: string
macAddress:
type: string
password:
type: string
username:
Expand Down
25 changes: 22 additions & 3 deletions pkg/asset/agent/agentconfig/fencingcredentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"os"
"strings"

"github.com/pkg/errors"
"github.com/sirupsen/logrus"
Expand All @@ -15,6 +16,7 @@ import (
"github.com/openshift/installer/pkg/asset/agent"
"github.com/openshift/installer/pkg/asset/agent/workflow"
"github.com/openshift/installer/pkg/types"
"github.com/openshift/installer/pkg/validate"
)

// fencingCredentialsFilename is the asset output path for fencing credentials.
Expand Down Expand Up @@ -154,13 +156,17 @@ func (f *FencingCredentials) validateFencingCredentials() field.ErrorList {
basePath := field.NewPath("controlPlane", "fencing", "credentials")

seenHostnames := make(map[string]int)
seenMACs := make(map[string]int)

for i, cred := range f.Config.Credentials {
credPath := basePath.Index(i)

if cred.HostName == "" {
allErrs = append(allErrs, field.Required(credPath.Child("hostname"), "hostname is required for fencing credential"))
} else {
if cred.HostName == "" && cred.MACAddress == "" {
allErrs = append(allErrs, field.Required(credPath,
"at least one of hostname or macAddress must be provided"))
}

if cred.HostName != "" {
// Check for duplicate hostnames
if prevIdx, exists := seenHostnames[cred.HostName]; exists {
allErrs = append(allErrs, field.Duplicate(credPath.Child("hostname"),
Expand All @@ -169,6 +175,19 @@ func (f *FencingCredentials) validateFencingCredentials() field.ErrorList {
seenHostnames[cred.HostName] = i
}

if cred.MACAddress != "" {
if err := validate.MAC(cred.MACAddress); err != nil {
allErrs = append(allErrs, field.Invalid(credPath.Child("macAddress"),
cred.MACAddress, err.Error()))
}
normalizedMAC := strings.ToLower(cred.MACAddress)
if prevIdx, exists := seenMACs[normalizedMAC]; exists {
allErrs = append(allErrs, field.Duplicate(credPath.Child("macAddress"),
fmt.Sprintf("macAddress %q already defined at index %d", cred.MACAddress, prevIdx)))
}
seenMACs[normalizedMAC] = i
Comment on lines +178 to +188
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use canonical MACs for duplicate detection.

strings.ToLower catches case-only duplicates, but not equivalent valid formats like AA-BB-CC-DD-EE-01 vs aa:bb:cc:dd:ee:01. Normalize from the parsed MAC before inserting into seenMACs.

Proposed fix
 import (
 	"context"
 	"fmt"
+	"net"
 	"os"
-	"strings"
@@
 		if cred.MACAddress != "" {
 			if err := validate.MAC(cred.MACAddress); err != nil {
 				allErrs = append(allErrs, field.Invalid(credPath.Child("macAddress"),
 					cred.MACAddress, err.Error()))
-			}
-			normalizedMAC := strings.ToLower(cred.MACAddress)
-			if prevIdx, exists := seenMACs[normalizedMAC]; exists {
-				allErrs = append(allErrs, field.Duplicate(credPath.Child("macAddress"),
-					fmt.Sprintf("macAddress %q already defined at index %d", cred.MACAddress, prevIdx)))
-			}
-			seenMACs[normalizedMAC] = i
+			} else {
+				parsedMAC, _ := net.ParseMAC(cred.MACAddress)
+				normalizedMAC := parsedMAC.String()
+				if prevIdx, exists := seenMACs[normalizedMAC]; exists {
+					allErrs = append(allErrs, field.Duplicate(credPath.Child("macAddress"),
+						fmt.Sprintf("macAddress %q already defined at index %d", cred.MACAddress, prevIdx)))
+				}
+				seenMACs[normalizedMAC] = i
+			}
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if cred.MACAddress != "" {
if err := validate.MAC(cred.MACAddress); err != nil {
allErrs = append(allErrs, field.Invalid(credPath.Child("macAddress"),
cred.MACAddress, err.Error()))
}
normalizedMAC := strings.ToLower(cred.MACAddress)
if prevIdx, exists := seenMACs[normalizedMAC]; exists {
allErrs = append(allErrs, field.Duplicate(credPath.Child("macAddress"),
fmt.Sprintf("macAddress %q already defined at index %d", cred.MACAddress, prevIdx)))
}
seenMACs[normalizedMAC] = i
if cred.MACAddress != "" {
if err := validate.MAC(cred.MACAddress); err != nil {
allErrs = append(allErrs, field.Invalid(credPath.Child("macAddress"),
cred.MACAddress, err.Error()))
} else {
parsedMAC, _ := net.ParseMAC(cred.MACAddress)
normalizedMAC := parsedMAC.String()
if prevIdx, exists := seenMACs[normalizedMAC]; exists {
allErrs = append(allErrs, field.Duplicate(credPath.Child("macAddress"),
fmt.Sprintf("macAddress %q already defined at index %d", cred.MACAddress, prevIdx)))
}
seenMACs[normalizedMAC] = i
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/asset/agent/agentconfig/fencingcredentials.go` around lines 178 - 188,
The duplicate detection currently uses strings.ToLower on cred.MACAddress which
misses equivalent formats; after validate.MAC(cred.MACAddress) succeeds, parse
the address (e.g., net.ParseMAC or equivalent) and derive a canonical form (use
the parsed hardware address's String() which yields lower-case colon-separated
bytes) and use that canonical value as the key into seenMACs instead of
strings.ToLower; if parsing unexpectedly fails, append a field.Invalid to
credPath.Child("macAddress") and skip duplicate logic. Ensure you update
references to normalizedMAC (the key) and leave the user-facing message using
the original cred.MACAddress or include the canonical form if desired.

}

if cred.Address == "" {
allErrs = append(allErrs, field.Required(credPath.Child("address"), "BMC address is required for fencing credential"))
}
Expand Down
Loading