Skip to content

Commit 318c392

Browse files
committed
Make TrustedCA.Key required if provided
Assisted-By: Claude Code (commercial license) Signed-off-by: Dmitry Tantsur <dtantsur@protonmail.com>
1 parent 83de7e3 commit 318c392

File tree

3 files changed

+201
-1
lines changed

3 files changed

+201
-1
lines changed

pkg/ironic/ironic.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ func removeIronicDeployment(cctx ControllerContext, ironic *metal3api.Ironic) er
178178

179179
// EnsureIronic deploys Ironic either as a Deployment or as a DaemonSet.
180180
func EnsureIronic(cctx ControllerContext, resources Resources) (status Status, err error) {
181-
if validationErr := ValidateIronic(&resources.Ironic.Spec, nil); validationErr != nil {
181+
if validationErr := resources.Validate(); validationErr != nil {
182182
status = Status{Fatal: validationErr}
183183
return status, nil //nolint:nilerr // validation errors are reported in status, not as return error
184184
}

pkg/ironic/validation.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,42 @@ func validateCASettings(tls *metal3api.TLS) error {
186186

187187
return nil
188188
}
189+
190+
// Validate all resources before using them. This method is a superset of
191+
// ValidateIronic with validations that require access to linked resources.
192+
func (resources *Resources) Validate() error {
193+
if err := ValidateIronic(&resources.Ironic.Spec, nil); err != nil {
194+
return err
195+
}
196+
197+
if resources.Ironic.Spec.TLS.TrustedCA != nil {
198+
key := resources.Ironic.Spec.TLS.TrustedCA.Key
199+
if key != "" && !resources.hasTrustedCAKey(key) {
200+
return fmt.Errorf("resources referenced in tls.trustedCA does not contain the required key %s", key)
201+
}
202+
}
203+
204+
return nil
205+
}
206+
207+
func (resources *Resources) hasTrustedCAKey(key string) bool {
208+
switch {
209+
case resources.TrustedCASecret != nil:
210+
for found := range resources.TrustedCASecret.Data {
211+
if found == key {
212+
return true
213+
}
214+
}
215+
case resources.TrustedCAConfigMap != nil:
216+
for found := range resources.TrustedCAConfigMap.Data {
217+
if found == key {
218+
return true
219+
}
220+
}
221+
default:
222+
// Cannot happen in reality but just in case
223+
return true
224+
}
225+
226+
return false
227+
}

pkg/ironic/validation_test.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"testing"
55

66
"github.com/stretchr/testify/assert"
7+
corev1 "k8s.io/api/core/v1"
78

89
metal3api "github.com/metal3-io/ironic-standalone-operator/api/v1alpha1"
910
)
@@ -493,3 +494,163 @@ func TestValidateCASettings(t *testing.T) {
493494
})
494495
}
495496
}
497+
498+
func TestResourcesValidate(t *testing.T) {
499+
testCases := []struct {
500+
Scenario string
501+
Resources Resources
502+
ExpectedError string
503+
}{
504+
{
505+
Scenario: "minimal valid resources",
506+
Resources: Resources{
507+
Ironic: &metal3api.Ironic{},
508+
},
509+
},
510+
{
511+
Scenario: "trustedCA secret with matching key",
512+
Resources: Resources{
513+
Ironic: &metal3api.Ironic{
514+
Spec: metal3api.IronicSpec{
515+
TLS: metal3api.TLS{
516+
TrustedCA: &metal3api.ResourceReferenceWithKey{
517+
ResourceReference: metal3api.ResourceReference{
518+
Name: "my-ca",
519+
Kind: metal3api.ResourceKindSecret,
520+
},
521+
Key: "ca.crt",
522+
},
523+
},
524+
},
525+
},
526+
TrustedCASecret: &corev1.Secret{
527+
Data: map[string][]byte{
528+
"ca.crt": []byte("cert-data"),
529+
},
530+
},
531+
},
532+
},
533+
{
534+
Scenario: "trustedCA secret with missing key",
535+
Resources: Resources{
536+
Ironic: &metal3api.Ironic{
537+
Spec: metal3api.IronicSpec{
538+
TLS: metal3api.TLS{
539+
TrustedCA: &metal3api.ResourceReferenceWithKey{
540+
ResourceReference: metal3api.ResourceReference{
541+
Name: "my-ca",
542+
Kind: metal3api.ResourceKindSecret,
543+
},
544+
Key: "missing-key",
545+
},
546+
},
547+
},
548+
},
549+
TrustedCASecret: &corev1.Secret{
550+
Data: map[string][]byte{
551+
"ca.crt": []byte("cert-data"),
552+
},
553+
},
554+
},
555+
ExpectedError: "does not contain the required key missing-key",
556+
},
557+
{
558+
Scenario: "trustedCA configmap with matching key",
559+
Resources: Resources{
560+
Ironic: &metal3api.Ironic{
561+
Spec: metal3api.IronicSpec{
562+
TLS: metal3api.TLS{
563+
TrustedCA: &metal3api.ResourceReferenceWithKey{
564+
ResourceReference: metal3api.ResourceReference{
565+
Name: "my-ca",
566+
Kind: metal3api.ResourceKindConfigMap,
567+
},
568+
Key: "ca-bundle.crt",
569+
},
570+
},
571+
},
572+
},
573+
TrustedCAConfigMap: &corev1.ConfigMap{
574+
Data: map[string]string{
575+
"ca-bundle.crt": "cert-data",
576+
},
577+
},
578+
},
579+
},
580+
{
581+
Scenario: "trustedCA configmap with missing key",
582+
Resources: Resources{
583+
Ironic: &metal3api.Ironic{
584+
Spec: metal3api.IronicSpec{
585+
TLS: metal3api.TLS{
586+
TrustedCA: &metal3api.ResourceReferenceWithKey{
587+
ResourceReference: metal3api.ResourceReference{
588+
Name: "my-ca",
589+
Kind: metal3api.ResourceKindConfigMap,
590+
},
591+
Key: "missing-key",
592+
},
593+
},
594+
},
595+
},
596+
TrustedCAConfigMap: &corev1.ConfigMap{
597+
Data: map[string]string{
598+
"ca-bundle.crt": "cert-data",
599+
},
600+
},
601+
},
602+
ExpectedError: "does not contain the required key missing-key",
603+
},
604+
{
605+
Scenario: "trustedCA with empty key skips key check",
606+
Resources: Resources{
607+
Ironic: &metal3api.Ironic{
608+
Spec: metal3api.IronicSpec{
609+
TLS: metal3api.TLS{
610+
TrustedCA: &metal3api.ResourceReferenceWithKey{
611+
ResourceReference: metal3api.ResourceReference{
612+
Name: "my-ca",
613+
Kind: metal3api.ResourceKindConfigMap,
614+
},
615+
},
616+
},
617+
},
618+
},
619+
TrustedCAConfigMap: &corev1.ConfigMap{
620+
Data: map[string]string{
621+
"ca-bundle.crt": "cert-data",
622+
},
623+
},
624+
},
625+
},
626+
{
627+
Scenario: "trustedCA without resource defaults to valid",
628+
Resources: Resources{
629+
Ironic: &metal3api.Ironic{
630+
Spec: metal3api.IronicSpec{
631+
TLS: metal3api.TLS{
632+
TrustedCA: &metal3api.ResourceReferenceWithKey{
633+
ResourceReference: metal3api.ResourceReference{
634+
Name: "my-ca",
635+
Kind: metal3api.ResourceKindConfigMap,
636+
},
637+
Key: "ca.crt",
638+
},
639+
},
640+
},
641+
},
642+
},
643+
},
644+
}
645+
646+
for _, tc := range testCases {
647+
t.Run(tc.Scenario, func(t *testing.T) {
648+
err := tc.Resources.Validate()
649+
if tc.ExpectedError == "" {
650+
assert.NoError(t, err)
651+
} else {
652+
assert.ErrorContains(t, err, tc.ExpectedError)
653+
}
654+
})
655+
}
656+
}

0 commit comments

Comments
 (0)