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
12 changes: 12 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,18 @@ rules:
- patch
- update
- watch
- apiGroups:
- ironic.metal3.io
resources:
- ironics
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- monitoring.coreos.com
resources:
Expand Down
19 changes: 6 additions & 13 deletions controllers/provisioning_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ type ensureFunc func(*provisioning.ProvisioningInfo) (bool, error)
// +kubebuilder:rbac:namespace=openshift-machine-api,groups=security.openshift.io,resources=securitycontextconstraints,verbs=use
// +kubebuilder:rbac:namespace=openshift-machine-api,groups=apps,resources=deployments;daemonsets,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:namespace=openshift-machine-api,groups=monitoring.coreos.com,resources=servicemonitors,verbs=create;watch;get;list;patch;delete;update
// +kubebuilder:rbac:namespace=openshift-machine-api,groups=ironic.metal3.io,resources=ironics,verbs=get;list;watch;create;update;patch;delete

// +kubebuilder:rbac:groups=config.openshift.io,resources=proxies,verbs=get;list;watch
// +kubebuilder:rbac:groups=config.openshift.io,resources=infrastructures,verbs=get;list;watch
Expand Down Expand Up @@ -327,15 +328,13 @@ func (r *ProvisioningReconciler) Reconcile(ctx context.Context, req ctrl.Request

for _, ensureResource := range []ensureFunc{
provisioning.EnsureAllSecrets,
provisioning.EnsureMetal3Deployment,
provisioning.EnsureIronicDeployment,
provisioning.EnsureBaremetalOperatorDeployment,
provisioning.EnsureMetal3StateService,
provisioning.EnsureImageCache,
provisioning.EnsureBaremetalOperatorWebhook,
provisioning.EnsureImageCustomizationService,
provisioning.EnsureImageCustomizationDeployment,
provisioning.EnsureIronicProxy,
provisioning.EnsureIronicServiceMonitor,
} {
updated, err := ensureResource(info)
if err != nil {
Expand All @@ -354,8 +353,8 @@ func (r *ProvisioningReconciler) Reconcile(ctx context.Context, req ctrl.Request
}
}

// Determine the status of the baremetal deployment
deploymentState, err := provisioning.GetDeploymentState(r.KubeClient.AppsV1(), ComponentNamespace, baremetalConfig)
// Determine the status of the Ironic deployment
deploymentState, err := provisioning.GetIronicDeploymentState(r.Client, ComponentNamespace)
if err != nil {
err = r.updateCOStatus(ReasonResourceNotFound, "metal3 deployment inaccessible", "")
if err != nil {
Expand Down Expand Up @@ -491,11 +490,8 @@ func (r *ProvisioningReconciler) deleteMetal3Resources(info *provisioning.Provis
if err := provisioning.DeleteValidatingWebhook(info); err != nil {
return errors.Wrap(err, "failed to delete validatingwebhook and service")
}
if err := provisioning.DeleteMetal3Deployment(info); err != nil {
return errors.Wrap(err, "failed to delete metal3 deployment")
}
if err := provisioning.DeleteMetal3StateService(info); err != nil {
return errors.Wrap(err, "failed to delete metal3 service")
if err := provisioning.DeleteIronicDeployment(info); err != nil {
return errors.Wrap(err, "failed to delete Ironic deployment")
}
if err := provisioning.DeleteImageCache(info); err != nil {
return errors.Wrap(err, "failed to delete metal3 image cache")
Expand All @@ -509,9 +505,6 @@ func (r *ProvisioningReconciler) deleteMetal3Resources(info *provisioning.Provis
if err := provisioning.DeleteIronicProxy(info); err != nil {
return errors.Wrap(err, "failed to delete ironic proxy")
}
if err := provisioning.DeleteIronicServiceMonitor(info); err != nil {
return errors.Wrap(err, "failed to delete ironic service monitor")
}
return nil
}

Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/golangci/golangci-lint/v2 v2.1.6
github.com/google/go-cmp v0.7.0
github.com/metal3-io/baremetal-operator/apis v0.0.0
github.com/metal3-io/ironic-standalone-operator/api v0.6.0
github.com/openshift/api v0.0.0-20251015095338-264e80a2b6e7
github.com/openshift/client-go v0.0.0-20251015124057-db0dee36e235
github.com/openshift/library-go v0.0.0-20251029104758-277736d6f195
Expand Down Expand Up @@ -280,3 +281,5 @@ replace github.com/metal3-io/baremetal-operator => github.com/openshift/baremeta
replace github.com/metal3-io/baremetal-operator/apis => github.com/openshift/baremetal-operator/apis v0.0.0-20250318213501-20a31adf14bc

replace github.com/metal3-io/baremetal-operator/pkg/hardwareutils => github.com/openshift/baremetal-operator/pkg/hardwareutils v0.0.0-20250318213501-20a31adf14bc

replace github.com/metal3-io/ironic-standalone-operator/api => github.com/metal3-io/ironic-standalone-operator/api v0.0.0-20251103085234-3d282bf75d50
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,8 @@ github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/metal3-io/ironic-standalone-operator/api v0.0.0-20251103085234-3d282bf75d50 h1:RhbDrPh0jKaoporNi71Lm2iLZg/CncrJ81DDjH9s/jk=
github.com/metal3-io/ironic-standalone-operator/api v0.0.0-20251103085234-3d282bf75d50/go.mod h1:yXS5C+kxOddNmruICpI60RrH7Nk9yQDDUmprWhp3Gcg=
github.com/mgechev/revive v1.9.0 h1:8LaA62XIKrb8lM6VsBSQ92slt/o92z5+hTw3CmrvSrM=
github.com/mgechev/revive v1.9.0/go.mod h1:LAPq3+MgOf7GcL5PlWIkHb0PT7XH4NuC2LdWymhb9Mo=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
// +kubebuilder:scaffold:imports

baremetalv1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
ironicv1alpha1 "github.com/metal3-io/ironic-standalone-operator/api/v1alpha1"
osconfigv1 "github.com/openshift/api/config/v1"
machinev1beta1 "github.com/openshift/api/machine/v1beta1"
osclientset "github.com/openshift/client-go/config/clientset/versioned"
Expand All @@ -60,6 +61,7 @@ func init() {
utilruntime.Must(osconfigv1.AddToScheme(scheme))
utilruntime.Must(machinev1beta1.AddToScheme(scheme))
utilruntime.Must(baremetalv1alpha1.AddToScheme(scheme))
utilruntime.Must(ironicv1alpha1.AddToScheme(scheme))

// +kubebuilder:scaffold:scheme
// The following is needed to read the Infrastructure CR
Expand Down
12 changes: 12 additions & 0 deletions manifests/0000_31_cluster-baremetal-operator_05_rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ rules:
- patch
- update
- watch
- apiGroups:
- ironic.metal3.io
resources:
- ironics
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- monitoring.coreos.com
resources:
Expand Down
13 changes: 4 additions & 9 deletions provisioning/baremetal_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,13 @@ func getDeployKernelUrl() *string {
return &deployKernelUrl
}

func getControlPlanePort(info *ProvisioningInfo) (ironicPort int) {
ironicPort = baremetalIronicPort
if UseIronicProxy(info) {
// Direct access to real services behind the proxy.
ironicPort = ironicPrivatePort
}
return
func getControlPlaneHost(info *ProvisioningInfo) string {
// IrSO creates a service using a name that matches the Ironic CR name
return fmt.Sprintf("%s.%s.svc.cluster.local", ironicServiceName, info.Namespace)
}

func getControlPlaneEndpoint(info *ProvisioningInfo) (ironicEndpoint string) {
ironicPort := getControlPlanePort(info)
ironicEndpoint = fmt.Sprintf("https://%s.%s.svc.cluster.local:%d/%s", stateService, info.Namespace, ironicPort, baremetalIronicEndpointSubpath)
ironicEndpoint = fmt.Sprintf("https://%s/v1/", getControlPlaneHost(info))
return
}

Expand Down
20 changes: 0 additions & 20 deletions provisioning/baremetal_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,26 +166,6 @@ func disabledProvisioning() *provisioningBuilder {
}
}

func configWithPreProvisioningOSDownloadURLs() *provisioningBuilder {
return &provisioningBuilder{
metal3iov1alpha1.ProvisioningSpec{
ProvisioningInterface: "eth0",
ProvisioningIP: "172.30.20.3",
ProvisioningMacAddresses: []string{"34:b3:2d:81:f8:fb", "34:b3:2d:81:f8:fc", "34:b3:2d:81:f8:fd"},
ProvisioningNetworkCIDR: "172.30.20.0/24",
ProvisioningDHCPRange: "172.30.20.11,172.30.20.101",
ProvisioningOSDownloadURL: "http://172.22.0.1/images/rhcos-44.81.202001171431.0-openstack.x86_64.qcow2.gz?sha256=e98f83a2b9d4043719664a2be75fe8134dc6ca1fdbde807996622f8cc7ecd234",
ProvisioningNetwork: "Managed",
PreProvisioningOSDownloadURLs: metal3iov1alpha1.PreProvisioningOSDownloadURLs{
KernelURL: "http://172.22.0.1/images/rhcos-49.84.202107010027-0-live-kernel-x86_64",
InitramfsURL: "http://172.22.0.1/images/rhcos-49.84.202107010027-0-live-initramfs.x86_64.img",
RootfsURL: "http://172.22.0.1/images/rhcos-49.84.202107010027-0-live-rootfs.x86_64.img",
IsoURL: "http://172.22.0.1/images/rhcos-4.9/49.84.202107010027-0/x86_64/rhcos-49.84.202107010027-0-live.x86_64.iso",
},
},
}
}

func (pb *provisioningBuilder) build() *metal3iov1alpha1.ProvisioningSpec {
return &pb.ProvisioningSpec
}
Expand Down
21 changes: 14 additions & 7 deletions provisioning/baremetal_crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package provisioning

import (
"crypto/rand"
"fmt"
"math/big"
"time"

Expand Down Expand Up @@ -53,7 +54,7 @@ func generateRandomPassword() (string, error) {
return string(buf), nil
}

func generateTlsCertificate(provisioningIP string) (TlsCertificate, error) {
func generateTlsCertificate(provisioningIP, namespace string) (TlsCertificate, error) {
caConfig, err := crypto.MakeSelfSignedCAConfig("metal3-ironic", tlsExpiration)
if err != nil {
return TlsCertificate{}, err
Expand All @@ -64,14 +65,20 @@ func generateTlsCertificate(provisioningIP string) (TlsCertificate, error) {
SerialGenerator: &crypto.RandomSerialGenerator{},
}

var host string
if provisioningIP == "" {
host = "localhost"
} else {
host = provisioningIP
// Build set of hostnames/IPs for certificate SANs
hosts := []string{}

if provisioningIP != "" {
hosts = append(hosts, provisioningIP)
}

config, err := ca.MakeServerCert(sets.New(host), tlsExpiration)
// Add Ironic service DNS names
hosts = append(hosts,
fmt.Sprintf("%s.%s.svc", ironicServiceName, namespace),
fmt.Sprintf("%s.%s.svc.cluster.local", ironicServiceName, namespace),
)

config, err := ca.MakeServerCert(sets.New(hosts...), tlsExpiration)

if err != nil {
return TlsCertificate{}, err
Expand Down
72 changes: 70 additions & 2 deletions provisioning/baremetal_crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"k8s.io/client-go/util/cert"
)

func TestGenerateRandomPassword(t *testing.T) {
Expand All @@ -23,7 +24,7 @@ func TestGenerateRandomPassword(t *testing.T) {
}

func TestGenerateTlsCertificate(t *testing.T) {
cert, err := generateTlsCertificate("")
cert, err := generateTlsCertificate("", "test-namespace")
if err != nil {
t.Errorf("Unexpected error while generating a certificate: %s", err)
} else {
Expand All @@ -40,7 +41,7 @@ func TestGenerateTlsCertificate(t *testing.T) {
}

func TestGenerateTlsCertificateWithHost(t *testing.T) {
cert, err := generateTlsCertificate("127.0.0.1")
cert, err := generateTlsCertificate("127.0.0.1", "test-namespace")
if err != nil {
t.Errorf("Unexpected error while generating a certificate: %s", err)
} else {
Expand All @@ -55,3 +56,70 @@ func TestGenerateTlsCertificateWithHost(t *testing.T) {
assert.False(t, expired, "new certificate is already expired")
}
}

func TestGenerateTlsCertificateSANs(t *testing.T) {
testCases := []struct {
name string
provisioningIP string
namespace string
expectedDNSNames []string
expectedIPs []string
}{
{
name: "ironic",
provisioningIP: "192.168.1.100",
namespace: "openshift-machine-api",
expectedDNSNames: []string{
"metal3-ironic.openshift-machine-api.svc",
"metal3-ironic.openshift-machine-api.svc.cluster.local",
},
expectedIPs: []string{"192.168.1.100"},
},
{
name: "localhost",
provisioningIP: "",
namespace: "test-ns",
expectedDNSNames: []string{
"metal3-ironic.test-ns.svc",
"metal3-ironic.test-ns.svc.cluster.local",
},
expectedIPs: nil,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tlsCert, err := generateTlsCertificate(tc.provisioningIP, tc.namespace)
assert.NoError(t, err, "Unexpected error while generating certificate")

certs, err := cert.ParseCertsPEM(tlsCert.certificate)
assert.NoError(t, err, "Failed to parse certificate")
// The certificate chain includes both CA and server cert
assert.GreaterOrEqual(t, len(certs), 1, "Expected at least one certificate")

// The server certificate is the first one in the chain
x509Cert := certs[0]

// Note: library-go adds IP addresses to DNS SANs for compatibility
// with Python, Windows, and other libraries (see crypto.go:1066-1071)
expectedDNSWithIPs := append([]string{}, tc.expectedDNSNames...)
if tc.expectedIPs != nil {
expectedDNSWithIPs = append(expectedDNSWithIPs, tc.expectedIPs...)
}

// Verify DNS names (includes IPs for compatibility)
assert.ElementsMatch(t, expectedDNSWithIPs, x509Cert.DNSNames, "DNS SANs mismatch")

// Verify IP addresses
if tc.expectedIPs != nil {
actualIPs := make([]string, len(x509Cert.IPAddresses))
for i, ip := range x509Cert.IPAddresses {
actualIPs[i] = ip.String()
}
assert.ElementsMatch(t, tc.expectedIPs, actualIPs, "IP SANs mismatch")
} else {
assert.Len(t, x509Cert.IPAddresses, 0, "Expected no IP SANs")
}
})
}
}
Loading