Skip to content

OSAC-3770: add bmcdiscovery package for Redfish system path discovery - #358

Merged
omer-vishlitzky merged 1 commit into
osac-project:mainfrom
mennyaboush:feat/OSAC-3770-bmc-redfish-discovery
Aug 19, 2026
Merged

OSAC-3770: add bmcdiscovery package for Redfish system path discovery#358
omer-vishlitzky merged 1 commit into
osac-project:mainfrom
mennyaboush:feat/OSAC-3770-bmc-redfish-discovery

Conversation

@mennyaboush

@mennyaboush mennyaboush commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add internal/bmcdiscovery/ package to bare-metal-fulfillment-operator implementing Priority 2 of the BMC address discovery strategy from the OSAC-1339 BCM backend design
  • Extracts BMC info from device interface data, classifies protocols (rf0→Redfish, ipmi0→IPMI, ilo0→iLO, drac0→iDRAC), validates BMC targets (loopback/link-local/port rejection), and discovers Redfish system paths via MAC-address matching
  • Adds github.com/stmcginnis/gofish v0.24.0 as a new dependency for Redfish API interaction behind a Discoverer interface for testability

Design

This is a standalone package with no dependencies on bcmclient or inventory types — it accepts []DeviceInterface (three string fields) and returns a validated BMC URL. The Discoverer interface abstracts the gofish Redfish client so callers can inject mocks. The package will be consumed by internal/inventory/bcm.go's AssignHost when the upstream PRs (#228, #353, #354) merge.

Validation

  • 103 unit tests (Ginkgo/Gomega), lint clean, full test suite green
  • Validated against a real Dell PowerEdge R450 iDRAC (10 tests covering all protocol types, case-insensitive MAC matching, wrong credentials, context timeout)
  • Real iDRAC test results documented in osac-1339/reference/bmc-discovery-real-validation.md (not in this PR — workspace-local)

Test plan

  • make lint passes with 0 issues
  • make test passes (all existing tests unaffected)
  • 103 unit tests cover protocol classification, BMC extraction, URL construction, target validation, and full Resolve() flow
  • Validated GofishDiscoverer against real Dell iDRAC with MAC matching across 6 NICs (embedded + PCIe slot)
  • Reviewer verifies package API surface is compatible with internal/inventory/bcm.go AssignHost needs

Depends on: #228 (bcmclient), #353 (inventory adapter), #354 (wiring) — this PR can merge independently but the package is consumed after those land.

Jira: OSAC-3770

@openshift-ci-robot

openshift-ci-robot commented Aug 16, 2026

Copy link
Copy Markdown

@mennyaboush: This pull request references OSAC-3770 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "5.1.0" version, but no target version was set.

Details

In response to this:

Summary

  • Add internal/bmcdiscovery/ package to bare-metal-fulfillment-operator implementing Priority 2 of the BMC address discovery strategy from the OSAC-1339 BCM backend design
  • Extracts BMC info from device interface data, classifies protocols (rf0→Redfish, ipmi0→IPMI, ilo0→iLO, drac0→iDRAC), validates BMC targets (loopback/link-local/port rejection), and discovers Redfish system paths via MAC-address matching
  • Adds github.com/stmcginnis/gofish v0.24.0 as a new dependency for Redfish API interaction behind a Discoverer interface for testability

Design

This is a standalone package with no dependencies on bcmclient or inventory types — it accepts []DeviceInterface (three string fields) and returns a validated BMC URL. The Discoverer interface abstracts the gofish Redfish client so callers can inject mocks. The package will be consumed by internal/inventory/bcm.go's AssignHost when the upstream PRs (#228, #353, #354) merge.

Design deviation: The EP places BMC discovery logic inside internal/inventory/bcm.go. This PR extracts it into a separate package for testability and potential reuse. This is consistent with the existing deviations (internal/bcmclient/, internal/baremetalhost/). The deviation will be documented in EP PR #204.

Validation

  • 103 unit tests (Ginkgo/Gomega), lint clean, full test suite green
  • Validated against a real Dell PowerEdge R450 iDRAC (10 tests covering all protocol types, case-insensitive MAC matching, wrong credentials, context timeout)
  • Real iDRAC test results documented in osac-1339/reference/bmc-discovery-real-validation.md (not in this PR — workspace-local)

Test plan

  • make lint passes with 0 issues
  • make test passes (all existing tests unaffected)
  • 103 unit tests cover protocol classification, BMC extraction, URL construction, target validation, and full Resolve() flow
  • Validated GofishDiscoverer against real Dell iDRAC with MAC matching across 6 NICs (embedded + PCIe slot)
  • Reviewer verifies package API surface is compatible with internal/inventory/bcm.go AssignHost needs

Depends on: #228 (bcmclient), #353 (inventory adapter), #354 (wiring) — this PR can merge independently but the package is consumed after those land.

Jira: OSAC-3770

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci
openshift-ci Bot requested review from carbonin and larsks August 16, 2026 17:03
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d547b7c3-2e1e-48ed-a0af-e0c68cad722c


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mennyaboush mennyaboush changed the title OSAC-3770: add bmcdiscovery package for Redfish system path discovery [wip] OSAC-3770: add bmcdiscovery package for Redfish system path discovery Aug 16, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 16, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:04 PM UTC · Completed 5:25 PM UTC

Commit: 508c88d · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 16, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [edge-case] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:112ExtractBMCInfo does not validate that the matched interface has a non-empty IP address. An empty DeviceInterface.IP produces an invalid BMC URL (e.g., ipmi:// with no host) that passes ValidateBMCAddress's scheme-only check. The error surfaces as an opaque BMH registration failure rather than a clear validation error at construction time.
    Remediation: Validate iface.IP != "" in ExtractBMCInfo before returning, e.g.: if iface.IP == "" { return nil, fmt.Errorf("%w: interface %q has no IP address", ErrInvalidBMCTarget, iface.Name) }

Low

  • [edge-case] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:150Resolve dereferences info.Protocol without a nil check. A nil *BMCInfo causes a panic. As an internal API where *BMCInfo is only constructed via ExtractBMCInfo, a nil argument would be a programming error, but a defensive nil guard is inexpensive.

  • [edge-case] bare-metal-fulfillment-operator/internal/bmcdiscovery/redfish.go:77 — MAC comparison uses strings.EqualFold (handles case) but not format normalization. Different MAC separators (colon vs dash vs period) would cause match failure. DMTF Redfish mandates colons, but some vendor BMC firmwares may deviate.

  • [edge-case] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:133buildRedfishAddress concatenates systemPath without validating it starts with /. A missing leading slash produces a malformed URL.

  • [injection-vuln] bare-metal-fulfillment-operator/internal/bmcdiscovery/redfish.go:78DiscoverSystemPath returns system.ODataID from the BMC Redfish response without path validation. The threat model is weak (the operator already sends credentials to the BMC), but validating the path starts with /redfish/v1/Systems/ is cheap hardening.

  • [injection-vuln] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:112BMCInfo.IP is populated from DeviceInterface.IP without IP address validation. formatHost uses net.ParseIP only for IPv6 bracket wrapping; parse failure falls through to raw string interpolation. See also: [edge-case] finding at this location.

  • [api-shape] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:52DeviceInterface struct lacks JSON tags. Sibling packages consistently use JSON tags on structs representing external system data. Whether tags are needed depends on the integration wiring in downstream PR OSAC-3765: Wire BMH manager, RBAC, and Helm bcmCerts #354.

  • [error-handling-idiom] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:159 — Nil-discoverer error uses fmt.Errorf without %w sentinel wrapping, unlike all other error paths in this package. Callers cannot use errors.Is to distinguish this failure.

  • [pattern-inconsistency] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery_test.go — Tests mix errors.Is() with separate assertion and Gomega's MatchError. Consistent style within the new package would be cleaner.

  • [naming-trajectory] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:52DeviceInterface defines own fields rather than reusing bcmclient types. Intentional per PR design, but integration wiring (PR OSAC-3765: Wire BMH manager, RBAC, and Helm bcmCerts #354) will need a mapping layer.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run

Review

Findings

Medium

  • [SSRF / Unvalidated Redfish System Path] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:173 — The systemPath returned by discoverer.DiscoverSystemPath() (i.e., system.ODataID from the Redfish API response) is concatenated directly into the BMC URL via buildRedfishAddress() without path-component validation. ValidateBMCAddress() validates the scheme and host but does not inspect the path. A compromised BMC could return a crafted ODataID containing path traversal sequences (/../..), query parameters (?), or fragments (#). The attack requires a pre-compromised BMC and the resulting URL is stored in a Metal3 BareMetalHost CR rather than used for direct HTTP requests by this code, so the practical risk is constrained. Defense-in-depth path validation is still worthwhile.
    Remediation: Validate systemPath before use — require it starts with /redfish/v1/Systems/, reject paths containing .., ?, #, or ://.

Low

  • [validation-surface-area] bare-metal-fulfillment-operator/internal/bmcdiscovery/validation.go — The allowedSchemes map includes bare "https" as a valid scheme, but no code path in this package produces a bare https:// BMC address (buildStaticAddress produces ipmi:// and buildRedfishAddress produces <prefix>+https://). If ValidateBMCAddress is later used to validate externally-sourced addresses, a bare https:// URL would pass validation despite not being a valid Metal3 BMC address format. Consider removing the bare "https" entry.

  • [nil-deref] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.goResolve() dereferences info.IP on its first line via ValidateBMCTarget(info.IP) without a nil check on the info parameter. The discoverer parameter gets an explicit nil check later in the function, creating an inconsistent defensive contract. A nil info would produce a panic rather than a descriptive error.

  • [TLS-insecure-default] bare-metal-fulfillment-operator/internal/bmcdiscovery/redfish.go:27GofishDiscoverer exposes InsecureSkipVerify which disables TLS certificate verification when true. The default is secure (false), and this pattern matches Metal3's own DisableCertificateVerification field used in the bare-metal management domain. A CACertPath option would be a useful future enhancement for environments that can distribute CA bundles to the operator.

  • [SSRF-allowlist] bare-metal-fulfillment-operator/internal/bmcdiscovery/validation.goValidateBMCTarget() uses a denylist approach (loopback, link-local, multicast, broadcast, unspecified) rather than an allowlist of management CIDRs. The BMC IP comes from a trusted internal inventory system, and the current denylist covers the most dangerous address classes (including cloud metadata via the link-local range). A configurable CIDR allowlist would provide additional defense-in-depth.

Previous run (2)

Review

Findings

Low

  • [edge-case] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:187Resolve does not validate the bootMAC parameter before passing it to the Discoverer. An empty string could match an ethernet interface with an empty MACAddress field in the Redfish response, silently returning an incorrect system path. Remediation: validate with net.ParseMAC before calling discoverer.DiscoverSystemPath.

  • [nil-deref] bare-metal-fulfillment-operator/internal/bmcdiscovery/redfish.go:68client.GetService() is called without a nil check. While gofish's ConnectContext populates the Service field on success, a defensive nil check would guard against unexpected library behavior with diverse BMC firmware.

  • [test-inadequate] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery_test.go — No Resolve-level test exercises the full IPv6 path. Individual functions (buildStaticAddress, buildRedfishAddress, ValidateBMCAddress) cover IPv6 bracketing, but the end-to-end flow through ValidateBMCTargetformatHostValidateBMCAddress is not tested with IPv6.

  • [path-traversal] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:174buildRedfishAddress concatenates systemPath from the BMC's ODataID without path validation. Risk is limited since the BMC is already an authenticated endpoint, but validating the path format (starts with /redfish/v1/Systems/, no query/fragment/..) would be defense-in-depth.

  • [naming-consistency] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:104extractBMCInfo filters on the magic string "NetworkBmcInterface". A named constant would make the BCM contract more explicit for future maintainers.

  • [error-handling-idiom] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:200 — Error wrapping uses "<noun> failed for %s" pattern while sibling packages (management/openstack.go) use "failed to <verb>". Minor style inconsistency.

  • [api-shape] bare-metal-fulfillment-operator/internal/bmcdiscovery/validation.go:38ValidateBMCTarget and ValidateBMCAddress are exported but currently only called internally by Resolve. Consider whether these need to be part of the public API surface.

  • [pattern-inconsistency] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery_suite_test.go:22 — Ginkgo/Gomega dot-imports use //nolint:revive,staticcheck directives, but sibling controller/suite_test.go omits them. Check whether the linter config already suppresses these warnings globally.

Previous run (3)

Review

Findings

Medium

  • [logic-error] bare-metal-fulfillment-operator/internal/bmcdiscovery/redfish.go:43GofishDiscoverer.DiscoverSystemPath constructs the Redfish endpoint as "https://" + bmcIP without wrapping IPv6 addresses in brackets. For an IPv6 BMC IP like 2001:db8::1, this produces https://2001:db8::1 which is not a valid RFC 3986 URI. The rest of the package handles this correctly via formatHost() (used by buildStaticAddress and buildRedfishAddress), but GofishDiscoverer does not call it. This will cause connection failures when discovering Redfish system paths on IPv6-addressed BMCs.
    Remediation: Use formatHost(bmcIP) when constructing the endpoint: Endpoint: "https://" + formatHost(bmcIP).

Low

  • [SSRF] bare-metal-fulfillment-operator/internal/bmcdiscovery/validation.go:36ValidateBMCTarget does not reject non-link-local multicast addresses. It checks IsLinkLocalMulticast() but not IsMulticast(). General multicast addresses like 239.1.1.1 or ff05::1 pass validation, even though multicast addresses are never valid BMC targets. Practical risk is low since multicast endpoints cannot serve HTTP responses, but this is a defense-in-depth gap.

  • [documentation-comments] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:93 — Doc comments on unexported functions use capitalized/exported-style names: // IsRedfishCompatible for isRedfishCompatible, // ExtractBMCInfo for extractBMCInfo, // BuildStaticAddress for buildStaticAddress, // BuildRedfishAddress for buildRedfishAddress. Go convention is that doc comments should start with the actual (lowercase) function name.

  • [input-validation] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:139buildRedfishAddress interpolates systemPath from a remote BMC's ODataID response (untrusted network input) without validating it is a legitimate Redfish system path. ValidateBMCAddress validates scheme, host, and port after construction, but does not inspect the path component.

  • [missing-test] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery_test.go — No test for Resolve when DeviceInterface has ChildType: "NetworkBmcInterface" but an empty IP field. The code path would produce a BMCInfo with empty IP, rejected by ValidateBMCTarget — but this edge case is not documented by a test.

  • [edge-case] bare-metal-fulfillment-operator/internal/bmcdiscovery/validation.go:81 — Scheme extraction via strings.Index(address, "://") finds the first occurrence. Correct for all current address formats, but the approach would misbehave if a future scheme contained :// before the authority delimiter.

  • [design-deviation] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go — PR description claims consistency with internal/bcmclient/ as an existing deviation from the EP, but that package does not yet exist on main (introduced by unmerged PR OSAC-3759: Add BCM HTTP client #228). The internal/baremetalhost/ precedent alone is sufficient.

  • [dependency-chain-risk] Package is dead code until upstream PRs (OSAC-3759: Add BCM HTTP client #228, OSAC-4067: Add BCM inventory adapter #353, OSAC-3765: Wire BMH manager, RBAC, and Helm bcmCerts #354) land. If those PRs change the DeviceInterface shape or BMC address format expectations, this package will need corresponding updates.

  • [code-organization] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:168formatHost calls net.ParseIP(ip) twice on the same input. Parse once and reuse the result.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (4)

Review

Findings

Medium

  • [logic-error] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:97 — IPv6 IPMI addresses fail validation. formatHost wraps IPv6 in brackets (e.g., [2001:db8::1]), buildStaticAddress produces ipmi://[2001:db8::1], but validateIPMIAddress calls splitHostPort on the bracketed address. When no port is present, net.SplitHostPort returns an error and splitHostPort returns the bracketed string unchanged. ValidateBMCTarget then calls net.ParseIP("[2001:db8::1]") which returns nil — brackets are not valid IP syntax. All IPMI addresses with IPv6 BMC IPs fail validation.
    Remediation: In splitHostPort, strip surrounding brackets when net.SplitHostPort returns an error. Add a test for ValidateBMCAddress("ipmi://[2001:db8::1]").

  • [logic-error] bare-metal-fulfillment-operator/internal/bmcdiscovery/redfish.go:30GofishDiscoverer.DiscoverSystemPath builds the Redfish endpoint as "https://" + bmcIP. For an IPv6 address like 2001:db8::1, this produces https://2001:db8::1 — an invalid URL per RFC 2732 (IPv6 literals in URIs require brackets). The formatHost helper exists in the same package and correctly brackets IPv6 addresses, but is not used here. gofish.ConnectContext will fail for any IPv6 BMC.
    Remediation: Use formatHost(bmcIP) when constructing the endpoint: Endpoint: "https://" + formatHost(bmcIP).

  • [nil-deref] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:113Resolve accepts a Discoverer parameter that may be nil. The IPMI path returns before reaching the discoverer call (safe), but for Redfish-compatible protocols (redfish, iLO, iDRAC), a nil discoverer causes a panic at discoverer.DiscoverSystemPath(...). Callers cannot predict which protocol a device will have, so nil must be handled.
    Remediation: Add a nil check before calling discoverer.DiscoverSystemPath: if discoverer == nil { return "", fmt.Errorf("discoverer is required for %s protocol", info.Protocol) }.

Low

  • [SSRF / Input Validation] bare-metal-fulfillment-operator/internal/bmcdiscovery/redfish.go:73system.ODataID from the Redfish API response is used as the path component in the constructed BMC address without validation. While ValidateBMCAddress re-parses the URL and validates the host IP (preventing host-override attacks), the path content is not sanitized. A compromised BMC could return an ODataID with unexpected path content that downstream consumers (e.g., Ironic) interpret differently.
    Remediation: Validate that systemPath matches an expected Redfish resource path pattern (e.g., ^/redfish/v1/Systems/[A-Za-z0-9._-]+$) before embedding in the address.

  • [missing-test] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery_test.go — No tests exercise IPv6 BMC addresses through the full Resolve flow or through ValidateBMCAddress with bracketed IPv6. The IPv6 logic in formatHost and splitHostPort is untested end-to-end, which allowed the IPv6 validation bug above to go undetected.
    Remediation: Add test cases for Resolve with IPv6 IPMI and Redfish interfaces, and for ValidateBMCAddress with bracketed IPv6 addresses.

  • [validation-gap] bare-metal-fulfillment-operator/internal/bmcdiscovery/validation.go:21ValidateBMCTarget blocks loopback, unspecified, link-local unicast, and link-local multicast addresses, but does not block non-link-local multicast (e.g., 239.1.1.1) or broadcast (255.255.255.255). Defense-in-depth improvement.
    Remediation: Add if ip.IsMulticast() { ... } check to ValidateBMCTarget.

  • [comment-style] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:143 — Doc comments for unexported functions (isRedfishCompatible, extractBMCInfo, buildStaticAddress, buildRedfishAddress) use exported-style capitalization (e.g., // IsRedfishCompatible returns... for isRedfishCompatible). Per godoc convention, comments on unexported functions should not start with the capitalized function name.
    Remediation: Update comments to match actual function name casing, or export the functions if they are intended to be part of the public API.

  • [error-variable-grouping] bare-metal-fulfillment-operator/internal/bmcdiscovery/validation.go:28ErrInvalidBMCTarget is declared alone in validation.go while other sentinel errors (ErrNoBMCInterface, ErrUnsupportedBMCType, ErrNoMACMatch) are grouped in a var block in bmcdiscovery.go. The established pattern in this operator groups all sentinel errors for a package in a single location.
    Remediation: Move ErrInvalidBMCTarget to the existing var block in bmcdiscovery.go.


Labels: PR adds new Go package for BMC discovery in bare-metal-fulfillment-operator


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (5)

Review

Findings

Medium

  • [error-handling] bare-metal-fulfillment-operator/internal/bmcdiscovery/redfish.go:66 — When iterating Redfish systems in DiscoverSystemPath, errors from system.EthernetInterfaces() are silently swallowed with continue. If the correct system has the matching MAC but the EthernetInterfaces() call fails due to a transient error (network timeout, rate limit), the function skips that system and returns ErrNoMACMatch. The caller cannot distinguish "MAC genuinely not found" from "failed to query the system that has the MAC."
    Remediation: Accumulate errors from system.EthernetInterfaces() (e.g., with errors.Join) and wrap them into the returned error when no MAC match is found.

  • [edge-case] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:103 — IPv6 BMC addresses produce malformed URIs. BuildStaticAddress formats as ipmi://%s and BuildRedfishAddress formats as %s+https://%s%s, both using the raw IP string. For IPv6 addresses (e.g., 2001:db8::1), this produces URIs like ipmi://2001:db8::1 which are not RFC 3986-compliant — IPv6 addresses in URI authority require brackets ([2001:db8::1]). ValidateBMCTarget accepts global-unicast IPv6, so these malformed URIs can reach downstream consumers (Metal3/Ironic).
    Remediation: Detect IPv6 addresses (check net.ParseIP(ip).To4() == nil) and wrap them in brackets for URI construction.

  • [stale-doc] bare-metal-fulfillment-operator/AGENTS.md:55 — The Repository Structure tree and Key Subsystems table in AGENTS.md enumerate all internal/ subdirectories and packages but do not include the new internal/bmcdiscovery/ directory added by this PR.
    Remediation: Add bmcdiscovery/ to both the Repository Structure tree and the Key Subsystems table in AGENTS.md.

Low

  • [edge-case] bare-metal-fulfillment-operator/internal/bmcdiscovery/validation.go:23ValidateBMCTarget does not reject the unspecified address (0.0.0.0, ::) or non-link-local multicast addresses. While unlikely in practice, they are never valid BMC targets.

  • [TLS-verification-bypass] bare-metal-fulfillment-operator/internal/bmcdiscovery/redfish.go:36GofishDiscoverer.InsecureSkipVerify disables TLS certificate verification when true. Defaults to false (safe). Add a doc comment noting this must only be enabled in non-production environments, and verify the eventual caller sets it safely.

  • [interface-compliance-assertion] bare-metal-fulfillment-operator/internal/bmcdiscovery/redfish.go — Missing compile-time interface compliance assertion var _ Discoverer = (*GofishDiscoverer)(nil).

  • [error-message-format] bare-metal-fulfillment-operator/internal/bmcdiscovery/redfish.go:55 — Error messages use present participle ("connecting to Redfish at %s") instead of the codebase pattern "failed to <action>: %w".

  • [error-message-format] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go:124 — Error message uses lowercase "bmc" (bmc target validation failed) while the rest of the file consistently capitalizes as "BMC".

  • [design-deviation] bare-metal-fulfillment-operator/internal/bmcdiscovery/bmcdiscovery.go — The PR acknowledges a design deviation from the Enhancement Proposal (EP places discovery in internal/inventory/bcm.go; this PR extracts to a separate package). Ensure EP PR OSAC-3734: add label-gate workflow for merge queue #204 is updated before this PR leaves WIP status.

  • [wip-status-mismatch] — PR title includes [wip] and carries the do-not-merge/work-in-progress label, but is not a GitHub Draft PR. Consider converting to draft status to prevent accidental approval.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 17, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 11:15 AM UTC · Ended 11:26 AM UTC

Commit: 9aab676 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 17, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:27 AM UTC · Completed 11:46 AM UTC

Commit: 34b2c18 · View workflow run →

@mennyaboush mennyaboush changed the title [wip] OSAC-3770: add bmcdiscovery package for Redfish system path discovery OSAC-3770: add bmcdiscovery package for Redfish system path discovery Aug 17, 2026
fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added go Pull requests that update go code enhancement New feature or request labels Aug 17, 2026
port := parsed.Port()

if port != "" && port != "443" {
return fmt.Errorf("%w: non-standard Redfish port %s (expected 443)", ErrInvalidBMCTarget, port)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is it really an issue?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I validate that staff as all the other validation in this pr as we described at the design.
I understand why you think its redundant... as you said its trusted server.
to summaries the design notes in there is the requirements:

  1. Allowed URL schemes — https, ipmi, redfish-virtualmedia+https, idrac-virtualmedia+https, ilo5-virtualmedia+https
  2. Rejected targets — loopback (127.0.0.0/8, ::1), link-local (169.254.0.0/16, fe80::/10), cloud metadata (169.254.169.254)
  3. Rejected ports — only 443 (Redfish) and 623 (IPMI)
  4. No caching on failure — return actionable error, don't cache

the combination of that requirement demand the validation we can see in that package.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I mean, an alternative port is not something suspicious, contrary to a malformed URL... unless there's something I miss

// ValidateBMCTarget checks that a BMC IP address is safe to connect to.
// Rejects loopback, link-local, and cloud metadata addresses.
func ValidateBMCTarget(bmcIP string) error {
ip := net.ParseIP(bmcIP)

@adriengentil adriengentil Aug 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

not sure if we need to validate that much, the data comes from a trusted source set by the cloud provider admin, it's not a user input. I agree that we need to make sure that the bmc target is compatible with metal3 though.

limitations under the License.
*/

// Package bmcdiscovery extracts BMC connection information from device

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

add that this discovery is aimed at being used by metal3's BMH

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 19, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 12:31 PM UTC · Completed 12:47 PM UTC

Commit: 9690fba · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed ready-for-merge All reviewers approved — ready to merge labels Aug 19, 2026
Add internal/bmcdiscovery/ to bare-metal-fulfillment-operator, implementing
Priority 2 of the BMC address discovery strategy from the OSAC-1339 design.

The package discovers BMC addresses for Metal3 BareMetalHost CRs. It
classifies BMC protocols from interface names, discovers Redfish system
paths via MAC-address matching using gofish, and validates BMC URL
schemes against Metal3/BMO supported protocols.

API:
- Resolve(*BMCInfo, ...) — main entry point, takes pre-built BMCInfo
- ExtractBMCInfo(interfaces, childType) — optional helper to extract
  BMCInfo from a device interface list, parameterized by childType so
  any inventory backend can use it
- Discoverer interface — abstracts the Redfish client for testability

Validated against a real Dell PowerEdge R450 iDRAC.

Signed-off-by: Menny Aboush <maboush@redhat.com>
Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: MENNY ABOUSH <maboush@maboush-thinkpadt14gen5.raanaii.csb>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 19, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 2:42 PM UTC · Ended 3:02 PM UTC

Commit: 71c85d6 · View workflow run →

@openshift-ci

openshift-ci Bot commented Aug 19, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: adriengentil, mennyaboush

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.

}
return &BMCInfo{
IP: iface.IP,
Protocol: protocol,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] edge-case

ExtractBMCInfo does not validate that the matched interface has a non-empty IP address. An empty DeviceInterface.IP produces an invalid BMC URL (e.g., 'ipmi://' with no host) that passes ValidateBMCAddress's scheme-only check. The error surfaces as an opaque BMH registration failure rather than a clear validation error at construction time.

Suggested fix: Validate iface.IP != "" in ExtractBMCInfo before returning, e.g.: if iface.IP == "" { return nil, fmt.Errorf("%w: interface %q has no IP address", ErrInvalidBMCTarget, iface.Name) }

password string,
discoverer Discoverer,
) (string, error) {
if !isRedfishCompatible(info.Protocol) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] edge-case

Resolve dereferences info.Protocol without a nil check. A nil *BMCInfo causes a panic. As an internal API where *BMCInfo is only constructed via ExtractBMCInfo, a nil argument would be a programming error, but a defensive nil guard is inexpensive.

queryErrors = append(queryErrors, fmt.Errorf("system %s: %w", system.ODataID, err))
continue
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] edge-case

MAC comparison uses strings.EqualFold (handles case) but not format normalization. Different MAC separators (colon vs dash vs period) would cause match failure. DMTF Redfish mandates colons, but some vendor BMC firmwares may deviate.

func buildRedfishAddress(bmcIP string, protocol Protocol, systemPath string) string {
prefix := redfishCompatiblePrefixes[protocol]
return fmt.Sprintf("%s+https://%s%s", prefix, formatHost(bmcIP), systemPath)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] edge-case

buildRedfishAddress concatenates systemPath without validating it starts with '/'. A missing leading slash produces a malformed URL.

continue
}

for _, eth := range ethInterfaces {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] injection-vuln

DiscoverSystemPath returns system.ODataID from the BMC Redfish response without path validation. The threat model is weak (the operator already sends credentials to the BMC), but validating the path starts with /redfish/v1/Systems/ is cheap hardening.

}
return &BMCInfo{
IP: iface.IP,
Protocol: protocol,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] injection-vuln

BMCInfo.IP is populated from DeviceInterface.IP without IP address validation. formatHost uses net.ParseIP only for IPv6 bracket wrapping; parse failure falls through to raw string interpolation. See also: [edge-case] finding at this location.


// DeviceInterface represents a network interface entry from a device's
// interface list. Only the fields needed for BMC discovery are included.
type DeviceInterface struct {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] api-shape

DeviceInterface struct lacks JSON tags. Sibling packages consistently use JSON tags on structs representing external system data. Whether tags are needed depends on the integration wiring in downstream PR #354.

}

if discoverer == nil {
return "", fmt.Errorf("discoverer is required for %s protocol", info.Protocol)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] error-handling-idiom

Nil-discoverer error uses fmt.Errorf without %w sentinel wrapping, unlike all other error paths in this package. Callers cannot use errors.Is to distinguish this failure.


// DeviceInterface represents a network interface entry from a device's
// interface list. Only the fields needed for BMC discovery are included.
type DeviceInterface struct {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] naming-trajectory

DeviceInterface defines own fields rather than reusing bcmclient types. Intentional per PR design, but integration wiring (PR #354) will need a mapping layer.

@fullsend-ai-review fullsend-ai-review Bot removed the requires-manual-review Review requires human judgment label Aug 19, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:42 PM UTC · Completed 3:02 PM UTC

Commit: 71c85d6 · View workflow run →

@omer-vishlitzky
omer-vishlitzky dismissed fullsend-ai-review[bot]’s stale review August 19, 2026 15:02

Auto-dismissed: only Prow labels gate merging

@omer-vishlitzky
omer-vishlitzky added this pull request to the merge queue Aug 19, 2026
Merged via the queue into osac-project:main with commit 36851ee Aug 19, 2026
103 of 105 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved enhancement New feature or request go Pull requests that update go code jira/valid-reference lgtm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants