Skip to content

Conversation

@mabulgu
Copy link
Contributor

@mabulgu mabulgu commented Oct 22, 2025

What this PR does / why we need it:

Adds per-host registry authentication for oci:// provisioning images.
New optional field on BareMetalHost:

spec:
  image:
    url: oci://<registry>/<repo>/<artifact>:<tag|digest>
    authSecretName: <k8s-secret-name>  # optional; same namespace as the BMH

When set, the controller validates the Kubernetes Docker-config secret (kubernetes.io/dockerconfigjson or kubernetes.io/dockercfg), selects the correct registry entry (supports exact host and host:port), and uses those credentials during provisioning so private OCI artefacts can be fetched on a per-host basis. Public images continue to work without credentials.
This removes a blocker for users who need different registries/accounts per machine.

Assisted-By: Claude-4.5-sonnet

@metal3-io-bot metal3-io-bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Oct 22, 2025
@metal3-io-bot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign zaneb for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found 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

@metal3-io-bot metal3-io-bot added the needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. label Oct 22, 2025
@metal3-io-bot
Copy link
Contributor

Hi @mabulgu. Thanks for your PR.

I'm waiting for a metal3-io member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

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 kubernetes-sigs/prow repository.

@metal3-io-bot metal3-io-bot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Oct 22, 2025
@mabulgu mabulgu force-pushed the feature/bmo-per-host-oci-auth branch from 898fffc to 9594af2 Compare October 22, 2025 10:52
@mabulgu mabulgu marked this pull request as ready for review October 27, 2025 09:48
@mabulgu mabulgu changed the title [WIP] ✨ Accept per-host pull secrets for customer OCI registries ✨ Accept per-host pull secrets for customer OCI registries Oct 27, 2025
@metal3-io-bot metal3-io-bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Oct 27, 2025
@dtantsur
Copy link
Member

/ok-to-test

@metal3-io-bot metal3-io-bot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Oct 27, 2025
@MahnoorAsghar
Copy link
Contributor

It might be too much code to be squashing into one commit...what do we think?

@tuminoid
Copy link
Member

tuminoid commented Oct 28, 2025

It might be too much code to be squashing into one commit...what do we think?

10 is definitely not right either. One for implementation, one for tesrts, and one for docs? There is plenty of work left though, given the conditions need work per Dmitry's review.

@mabulgu
Copy link
Contributor Author

mabulgu commented Nov 3, 2025

Thanks for your comments!

@MahnoorAsghar > It might be too much code to be squashing into one commit...what do we think?

As soon as we itemize them and they are in the same context, I don't think so. I am going to remove everything related to conditions as they were like extra, but everything else share the same context and can be in the same squash commit IMO.

@tuminoid > One for implementation, one for tesrts, and one for docs

+1 for docs -1 for tests as tests are a part of the "implementation". Without tests, I would not count it as implemented.

What I will do is: seperating the code commit (which will have less changes than the current changes because of the condition revert) and the commit for docs.

@tuminoid
Copy link
Member

tuminoid commented Nov 3, 2025

Thanks for your comments!
@tuminoid > One for implementation, one for tesrts, and one for docs

+1 for docs -1 for tests as tests are a part of the "implementation". Without tests, I would not count it as implemented.

This is fine as well, but its quite common to implement tests in separate commit in same PR. Makes it maybe easier to manage, but like said, I'm 100% fine with code+tests in same commit.

@tuminoid
Copy link
Member

tuminoid commented Nov 9, 2025

/retest

@mabulgu mabulgu force-pushed the feature/bmo-per-host-oci-auth branch 2 times, most recently from b18570e to 27c9860 Compare November 11, 2025 15:19
@mabulgu mabulgu requested a review from dtantsur November 11, 2025 15:22
@mabulgu
Copy link
Contributor Author

mabulgu commented Nov 11, 2025

@dtantsur I applied your suggestions. pls check when you have time. You will find the relevant commetns resolved but pls feel free to reopen them if you feel any of them are not implemented the way you suggested

@mabulgu
Copy link
Contributor Author

mabulgu commented Nov 12, 2025

Not sure if the e2e test filure is related to my changes as it seems to be related to the BMC management credentials

@tuminoid
Copy link
Member

/retest

@metal3-io-bot metal3-io-bot added the needs-rebase Indicates that a PR cannot be merged because it has merge conflicts with HEAD. label Nov 15, 2025
@mabulgu mabulgu force-pushed the feature/bmo-per-host-oci-auth branch from 27c9860 to c51576d Compare November 17, 2025 10:19
@metal3-io-bot metal3-io-bot removed the needs-rebase Indicates that a PR cannot be merged because it has merge conflicts with HEAD. label Nov 17, 2025
@mabulgu mabulgu force-pushed the feature/bmo-per-host-oci-auth branch from 66fb281 to eb91b4a Compare December 3, 2025 11:25
@metal3-io-bot metal3-io-bot added the needs-rebase Indicates that a PR cannot be merged because it has merge conflicts with HEAD. label Dec 3, 2025
@mabulgu mabulgu force-pushed the feature/bmo-per-host-oci-auth branch from dbe5eb3 to ff70b04 Compare December 3, 2025 14:17
@metal3-io-bot metal3-io-bot removed the needs-rebase Indicates that a PR cannot be merged because it has merge conflicts with HEAD. label Dec 3, 2025
@MahnoorAsghar
Copy link
Contributor

Other than the small CI linting failure, Dmitry's comments, and the many commits (I think this should be <5 commits), looks good to me

@mabulgu
Copy link
Contributor Author

mabulgu commented Dec 3, 2025

Other than the small CI linting failure, Dmitry's comments, and the many commits (I think this should be <5 commits), looks good to me

hey @MahnoorAsghar! yes as we discussed before this will be squashed before the merge for a better commit history. That will be the last thing to do as some update requests might occur and I don't want to restructure the commit structure each time. I will do it once I am sure everything is addressed. thanks for your comment!

Adds support for per-host registry authentication for oci:// provisioning
images via a new optional field spec.image.authSecretName on BareMetalHost.

When set, the controller validates the Kubernetes Docker-config secret
(kubernetes.io/dockerconfigjson or kubernetes.io/dockercfg), extracts
credentials for the matching registry, and passes them to Ironic during
provisioning.

Signed-off-by: mabulgu <[email protected]>
Adds unit tests for:
- ImageAuthValidator validation logic
- Docker config credential extraction
- Registry host parsing with url.Parse
- Various edge cases (missing secrets, wrong types, etc.)

Signed-off-by: mabulgu <[email protected]>
@mabulgu mabulgu force-pushed the feature/bmo-per-host-oci-auth branch from 052e08d to a9b7554 Compare December 4, 2025 14:43
Adds documentation for the new spec.image.authSecretName field that
enables per-host OCI registry authentication for provisioning images.

Includes:
- Field description and usage example
- Secret requirements (type, namespace, format)
- Example Secret manifest

Signed-off-by: mabulgu <[email protected]>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds per-host OCI registry authentication support to the baremetal-operator, enabling provisioning of images from private OCI registries on a per-host basis.

Key Changes:

  • Added authSecretName field to the Image API for referencing Docker config secrets
  • Implemented credential extraction logic using the dockercfg library to parse Kubernetes Docker config secrets
  • Integrated authentication into the provisioning workflow, passing base64-encoded credentials to Ironic

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
apis/metal3.io/v1alpha1/baremetalhost_types.go Added AuthSecretName field to Image type and improved IsOCI() to be case-insensitive
apis/metal3.io/v1alpha1/zz_generated.deepcopy.go Generated deepcopy code for new AuthSecretName field
pkg/secretutils/dockerconfig.go New file implementing Docker config secret parsing and credential extraction
pkg/secretutils/dockerconfig_test.go Unit tests for credential extraction logic
internal/controller/metal3.io/image_auth_validator.go New validator for image authentication secrets with event recording
internal/controller/metal3.io/image_auth_validator_test.go Tests for auth secret validation
internal/controller/metal3.io/baremetalhost_controller.go Integrated auth secret extraction into provisioning action
pkg/provisioner/provisioner.go Added ImagePullSecret field to ProvisionData
pkg/provisioner/ironic/ironic.go Updated deploy methods to pass credentials to Ironic instance_info
go.mod / go.sum Added github.com/cpuguy83/dockercfg dependency
config/base/crds/bases/metal3.io_baremetalhosts.yaml CRD definition for new authSecretName field
config/render/capm3.yaml Rendered CRD with new field
docs/api.md Documentation for OCI image authentication feature

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +247 to +248
"username": "testuser",
"password": "testpass",
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

The Docker config format is incorrect. The standard Docker config uses an "auth" field containing base64-encoded "username:password", not separate "username" and "password" fields. Replace lines 246-249 with:

"different-registry.com": map[string]interface{}{
    "auth": base64.StdEncoding.EncodeToString([]byte("testuser:testpass")),
},
Suggested change
"username": "testuser",
"password": "testpass",
"auth": base64.StdEncoding.EncodeToString([]byte("testuser:testpass")),

Copilot uses AI. Check for mistakes.
Comment on lines +328 to +329
"username": "testuser",
"password": "testpass",
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

The Docker config format is incorrect. The standard Docker config uses an "auth" field containing base64-encoded "username:password", not separate "username" and "password" fields. Replace lines 327-330 with:

"registry.example.com": map[string]interface{}{
    "auth": base64.StdEncoding.EncodeToString([]byte("testuser:testpass")),
},
Suggested change
"username": "testuser",
"password": "testpass",
"auth": base64.StdEncoding.EncodeToString([]byte("testuser:testpass")),

Copilot uses AI. Check for mistakes.
Comment on lines +310 to +319
// Helper function to check substring.
func containsSubstring(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}

Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

[nitpick] The containsSubstring function reimplements the standard library's strings.Contains(). Replace this custom implementation with strings.Contains(s, substr) from the standard library.

Suggested change
// Helper function to check substring.
func containsSubstring(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}

Copilot uses AI. Check for mistakes.

c := fake.NewClientBuilder().WithScheme(scheme).Build()
recorder := record.NewFakeRecorder(10)
secretManager := secretutils.NewSecretManager(t.Context(), testLogger(t), c, c)
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

The method t.Context() does not exist in Go versions prior to 1.24, which is not yet released. Since the go.mod specifies a non-existent version, tests using t.Context() will fail to compile. Replace t.Context() with context.Background() or context.TODO(), and import the context package.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

LOL bot being in 2024.

return "", fmt.Errorf("failed to parse image URL: %w", err)
}

if parsed.Scheme != "oci" {
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

The scheme check parsed.Scheme != "oci" is case-sensitive, but Image.IsOCI() uses case-insensitive matching (strings.ToLower()). This inconsistency could cause URLs like "OCI://registry.example.com/image" to pass the IsOCI() check but fail in extractRegistryHost(). Use strings.EqualFold(parsed.Scheme, "oci") instead for case-insensitive comparison.

Suggested change
if parsed.Scheme != "oci" {
if !strings.EqualFold(parsed.Scheme, "oci") {

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +12
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/url"

"github.com/cpuguy83/dockercfg"
corev1 "k8s.io/api/core/v1"
)
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

Missing strings import needed for case-insensitive scheme comparison. Add "strings" to the import block to use either strings.EqualFold() or strings.ToLower().

Copilot uses AI. Check for mistakes.
Comment on lines +166 to +167
"username": "testuser",
"password": "testpass",
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

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

The Docker config format is incorrect. The standard Docker config uses an "auth" field containing base64-encoded "username:password", not separate "username" and "password" fields. This test will fail because the dockercfg library expects the standard format. Use the helper function createDockerConfigJSONSecret() from pkg/secretutils/dockerconfig_test.go or format the config with an "auth" field like:

"registry.example.com": map[string]interface{}{
    "auth": base64.StdEncoding.EncodeToString([]byte("testuser:testpass")),
},
Suggested change
"username": "testuser",
"password": "testpass",
"auth": base64.StdEncoding.EncodeToString([]byte("testuser:testpass")),

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ok-to-test Indicates a non-member PR verified by an org member that is safe to test. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants