Skip to content

Commit 31ef0ea

Browse files
committed
Fix CodeRabbit critical and major issues
- Fix CSR approval to use /approval subresource instead of /status - Fix certificate URI construction to use ODataID instead of UUID - Fix certificate type detection to use URI path instead of Type field - Document CertificateSecretRef namespace in API comments - Fix CSR name length to respect 253 char Kubernetes limit - Fix certificate install race by persisting status before BMC call - Fix signer allowlist to include configured signer name - Simplify checkCertificateExpiration (remove unused error return)
1 parent 152f1b3 commit 31ef0ea

5 files changed

Lines changed: 65 additions & 41 deletions

File tree

api/v1alpha1/bmc_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ type BMCStatus struct {
288288
CertificateSigningRequestRef *string `json:"certificateSigningRequestRef,omitempty"`
289289

290290
// CertificateSecretRef references the Secret containing the installed certificate.
291+
// The Secret is created in the metal-operator controller's namespace.
291292
// +optional
292293
CertificateSecretRef *v1.LocalObjectReference `json:"certificateSecretRef,omitempty"`
293294
}

bmc/bmc.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,16 @@ const (
326326
CertificateTypeCA CertificateType = "PEM"
327327
)
328328

329+
// CertificatePurpose defines the purpose/usage of a certificate.
330+
type CertificatePurpose string
331+
332+
const (
333+
// CertificatePurposeHTTPS indicates an HTTPS/TLS server certificate
334+
CertificatePurposeHTTPS CertificatePurpose = "HTTPS"
335+
// CertificatePurposeCA indicates a CA certificate
336+
CertificatePurposeCA CertificatePurpose = "CA"
337+
)
338+
329339
// CSRRequest contains parameters for generating a Certificate Signing Request.
330340
type CSRRequest struct {
331341
// CommonName is the common name for the certificate (typically hostname or IP)

bmc/redfish.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,13 +1179,15 @@ func (r *RedfishBaseBMC) GenerateCSR(ctx context.Context, req CSRRequest) (*CSRR
11791179

11801180
// Get manager to find certificate collection
11811181
managers, err := r.client.GetService().Managers()
1182-
if err != nil || len(managers) == 0 {
1182+
if err != nil {
11831183
return nil, fmt.Errorf("failed to get managers: %w", err)
11841184
}
1185+
if len(managers) == 0 {
1186+
return nil, fmt.Errorf("no managers found")
1187+
}
11851188
manager := managers[0]
11861189

1187-
// Construct certificate collection URI
1188-
certCollectionURI := fmt.Sprintf("/redfish/v1/Managers/%s/NetworkProtocol/HTTPS/Certificates", manager.UUID)
1190+
certCollectionURI := manager.ODataID + "/NetworkProtocol/HTTPS/Certificates"
11891191

11901192
// Set default key algorithm if not specified
11911193
keyAlgorithm := req.KeyPairAlgorithm
@@ -1233,13 +1235,15 @@ func (r *RedfishBaseBMC) InstallCertificate(ctx context.Context, certificatePEM
12331235

12341236
// Get manager to find certificate collection
12351237
managers, err := r.client.GetService().Managers()
1236-
if err != nil || len(managers) == 0 {
1238+
if err != nil {
12371239
return fmt.Errorf("failed to get managers: %w", err)
12381240
}
1241+
if len(managers) == 0 {
1242+
return fmt.Errorf("no managers found")
1243+
}
12391244
manager := managers[0]
12401245

1241-
// Certificate collection URI
1242-
certCollectionURI := fmt.Sprintf("/redfish/v1/Managers/%s/NetworkProtocol/HTTPS/Certificates", manager.UUID)
1246+
certCollectionURI := manager.ODataID + "/NetworkProtocol/HTTPS/Certificates"
12431247

12441248
// Prepare certificate installation payload
12451249
payload := map[string]any{
@@ -1269,13 +1273,15 @@ func (r *RedfishBaseBMC) InstallCertificate(ctx context.Context, certificatePEM
12691273
func (r *RedfishBaseBMC) GetCertificates(ctx context.Context) ([]CertificateInfo, error) {
12701274
// Get manager to find certificate collection
12711275
managers, err := r.client.GetService().Managers()
1272-
if err != nil || len(managers) == 0 {
1276+
if err != nil {
12731277
return nil, fmt.Errorf("failed to get managers: %w", err)
12741278
}
1279+
if len(managers) == 0 {
1280+
return nil, fmt.Errorf("no managers found")
1281+
}
12751282
manager := managers[0]
12761283

1277-
// Certificate collection URI
1278-
certCollectionURI := fmt.Sprintf("/redfish/v1/Managers/%s/NetworkProtocol/HTTPS/Certificates", manager.UUID)
1284+
certCollectionURI := manager.ODataID + "/NetworkProtocol/HTTPS/Certificates"
12791285

12801286
resp, err := r.client.Get(certCollectionURI)
12811287
if err != nil {

config/crd/bases/metal.ironcore.dev_bmcs.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,9 @@ spec:
228228
type: string
229229
type: object
230230
certificateSecretRef:
231-
description: CertificateSecretRef references the Secret containing
232-
the installed certificate.
231+
description: |-
232+
CertificateSecretRef references the Secret containing the installed certificate.
233+
The Secret is created in the metal-operator controller's namespace.
233234
properties:
234235
name:
235236
default: ""

internal/controller/bmc_controller.go

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,7 @@ func (r *BMCReconciler) reconcile(ctx context.Context, bmcObj *metalv1alpha1.BMC
254254
log.Error(condErr, "Failed to update certificate condition")
255255
}
256256
}
257-
if err := r.checkCertificateExpiration(ctx, bmcObj); err != nil {
258-
log.Error(err, "Failed to check certificate expiration")
259-
}
257+
r.checkCertificateExpiration(ctx, bmcObj)
260258

261259
log.V(1).Info("Reconciled BMC")
262260
return ctrl.Result{}, nil
@@ -760,9 +758,21 @@ func (r *BMCReconciler) initiateCertificateRequest(ctx context.Context, bmcObj *
760758
signerName = DefaultCertificateSignerName
761759
}
762760

761+
// Generate safe CSR name (max 253 chars for Kubernetes metadata.name)
762+
// Format: bmc-<bmcName>-<shortUID>
763+
// Ensure bmcName is truncated if needed to stay under limit
764+
uidStr := string(bmcObj.UID)
765+
shortUID := uidStr[:8] // First 8 chars of UUID
766+
maxBMCNameLen := 253 - 4 - 1 - 8 - 1 // 253 - "bmc-" - "-" - shortUID - null = 239
767+
bmcName := bmcObj.Name
768+
if len(bmcName) > maxBMCNameLen {
769+
bmcName = bmcName[:maxBMCNameLen]
770+
}
771+
csrName := fmt.Sprintf("bmc-%s-%s", bmcName, shortUID)
772+
763773
k8sCSR := &certificatesv1.CertificateSigningRequest{
764774
ObjectMeta: metav1.ObjectMeta{
765-
Name: fmt.Sprintf("bmc-%s-%s", bmcObj.Name, string(bmcObj.UID[:8])),
775+
Name: csrName,
766776
Labels: map[string]string{
767777
"metal.ironcore.dev/bmc": bmcObj.Name,
768778
},
@@ -987,13 +997,7 @@ func (r *BMCReconciler) installCertificate(ctx context.Context, bmcObj *metalv1a
987997
return fmt.Errorf("certificate missing ServerAuth extended key usage")
988998
}
989999

990-
if err := bmcClient.InstallCertificate(ctx, string(k8sCSR.Status.Certificate), bmc.CertificateTypeHTTPS); err != nil {
991-
_ = r.updateConditions(ctx, bmcObj, true, bmcCertificateInstalledConditionType,
992-
corev1.ConditionFalse, bmcCertificateInstallFailedReason,
993-
fmt.Sprintf("Failed to install certificate: %v", err))
994-
return err
995-
}
996-
1000+
// Create/update secret first
9971001
secret := &corev1.Secret{
9981002
ObjectMeta: metav1.ObjectMeta{
9991003
Name: fmt.Sprintf("bmc-%s-cert", bmcObj.Name),
@@ -1017,18 +1021,28 @@ func (r *BMCReconciler) installCertificate(ctx context.Context, bmcObj *metalv1a
10171021
}
10181022
log.Info("Certificate secret created or updated", "operation", opResult)
10191023

1024+
// Persist secret reference to status before calling BMC
10201025
bmcBase := bmcObj.DeepCopy()
10211026
bmcObj.Status.CertificateSecretRef = &corev1.LocalObjectReference{Name: secret.Name}
10221027
bmcObj.Status.CertificateSigningRequestRef = nil
10231028

1024-
if err := r.updateCertificateInfo(ctx, bmcObj, bmcClient); err != nil {
1025-
log.Error(err, "Failed to update certificate info")
1029+
if err := r.Status().Patch(ctx, bmcObj, client.MergeFrom(bmcBase)); err != nil {
1030+
return fmt.Errorf("failed to persist certificate secret reference: %w", err)
10261031
}
10271032

1028-
if err := r.Status().Patch(ctx, bmcObj, client.MergeFrom(bmcBase)); err != nil {
1033+
// Now install certificate on BMC
1034+
// If this fails, on retry we'll have secretRef set and can skip secret creation
1035+
if err := bmcClient.InstallCertificate(ctx, string(k8sCSR.Status.Certificate), bmc.CertificateTypeHTTPS); err != nil {
1036+
_ = r.updateConditions(ctx, bmcObj, true, bmcCertificateInstalledConditionType,
1037+
corev1.ConditionFalse, bmcCertificateInstallFailedReason,
1038+
fmt.Sprintf("Failed to install certificate: %v", err))
10291039
return err
10301040
}
10311041

1042+
if err := r.updateCertificateInfo(ctx, bmcObj, bmcClient); err != nil {
1043+
log.Error(err, "Failed to update certificate info")
1044+
}
1045+
10321046
log.Info("Certificate installed successfully")
10331047
_ = r.updateConditions(ctx, bmcObj, true, bmcCertificateInstalledConditionType,
10341048
corev1.ConditionTrue, bmcCertificateInstalledReason,
@@ -1037,11 +1051,11 @@ func (r *BMCReconciler) installCertificate(ctx context.Context, bmcObj *metalv1a
10371051
return nil
10381052
}
10391053

1040-
func (r *BMCReconciler) checkCertificateExpiration(ctx context.Context, bmcObj *metalv1alpha1.BMC) error {
1054+
func (r *BMCReconciler) checkCertificateExpiration(ctx context.Context, bmcObj *metalv1alpha1.BMC) {
10411055
log := ctrl.LoggerFrom(ctx)
10421056

10431057
if bmcObj.Status.CertificateInfo == nil {
1044-
return nil
1058+
return
10451059
}
10461060

10471061
renewalThreshold := r.DefaultCertificateRenewalThreshold
@@ -1058,29 +1072,21 @@ func (r *BMCReconciler) checkCertificateExpiration(ctx context.Context, bmcObj *
10581072
_ = r.updateConditions(ctx, bmcObj, true, bmcCertificateExpiringConditionType,
10591073
corev1.ConditionTrue, bmcCertificateExpiringSoonReason,
10601074
fmt.Sprintf("Certificate expires in %s", timeUntilExpiry))
1061-
1062-
if bmcObj.Spec.CertificateManagementPolicy != nil &&
1063-
*bmcObj.Spec.CertificateManagementPolicy == metalv1alpha1.CertificateManagementPolicyAutomatic {
1064-
bmcBase := bmcObj.DeepCopy()
1065-
bmcObj.Status.CertificateSecretRef = nil
1066-
if err := r.Status().Patch(ctx, bmcObj, client.MergeFrom(bmcBase)); err != nil {
1067-
return err
1068-
}
1069-
}
10701075
} else {
10711076
_ = r.updateConditions(ctx, bmcObj, true, bmcCertificateExpiringConditionType,
10721077
corev1.ConditionFalse, bmcCertificateValidReason,
10731078
fmt.Sprintf("Certificate valid until %s", expiryTime))
10741079
}
10751080
}
1076-
1077-
return nil
10781081
}
10791082

10801083
func (r *BMCReconciler) approveCSR(ctx context.Context, csr *certificatesv1.CertificateSigningRequest) error {
10811084
allowedSigners := []string{
10821085
DefaultCertificateSignerName,
10831086
}
1087+
if r.DefaultCertificateSignerName != "" && r.DefaultCertificateSignerName != DefaultCertificateSignerName {
1088+
allowedSigners = append(allowedSigners, r.DefaultCertificateSignerName)
1089+
}
10841090

10851091
if !slices.Contains(allowedSigners, csr.Spec.SignerName) {
10861092
return fmt.Errorf("controller not authorized to approve signer: %s (only allowed: %v)",
@@ -1096,7 +1102,7 @@ func (r *BMCReconciler) approveCSR(ctx context.Context, csr *certificatesv1.Cert
10961102
LastTransitionTime: metav1.Now(),
10971103
})
10981104

1099-
if err := r.Status().Update(ctx, csr); err != nil {
1105+
if err := r.SubResource("approval").Update(ctx, csr); err != nil {
11001106
return err
11011107
}
11021108

@@ -1178,9 +1184,9 @@ func (r *BMCReconciler) updateCertificateInfo(ctx context.Context, bmcObj *metal
11781184
return err
11791185
}
11801186

1181-
// Find HTTPS certificate
1187+
// Find HTTPS certificate (determined by URI path, not CertificateType field)
11821188
for _, cert := range certs {
1183-
if cert.Type == bmc.CertificateTypeHTTPS {
1189+
if strings.Contains(cert.URI, "/HTTPS/Certificates") {
11841190
notBefore, _ := time.Parse(time.RFC3339, cert.ValidNotBefore)
11851191
notAfter, _ := time.Parse(time.RFC3339, cert.ValidNotAfter)
11861192

0 commit comments

Comments
 (0)