Skip to content

Commit ca9bb3a

Browse files
committed
feat: adding x509 validation
Signed-off-by: Doug Edgar <[email protected]>
1 parent 07a6dd8 commit ca9bb3a

File tree

2 files changed

+101
-10
lines changed

2 files changed

+101
-10
lines changed

controllers/llamastackdistribution_controller.go

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package controllers
1818

1919
import (
2020
"context"
21+
"crypto/x509"
2122
"encoding/json"
2223
"encoding/pem"
2324
"errors"
@@ -426,7 +427,28 @@ func (r *LlamaStackDistributionReconciler) reconcileUserAndCABundleConfigMaps(ct
426427
}
427428

428429
func (r *LlamaStackDistributionReconciler) reconcileManagedCABundle(ctx context.Context, instance *llamav1alpha1.LlamaStackDistribution) error {
430+
logger := log.FromContext(ctx)
431+
managedConfigMapName := getManagedCABundleConfigMapName(instance)
432+
429433
if !r.hasCABundleConfigMap(instance) && !r.hasODHTrustedCABundle(ctx, instance) {
434+
// No CA bundles configured, delete managed ConfigMap if it exists
435+
existingConfigMap := &corev1.ConfigMap{}
436+
err := r.Get(ctx, types.NamespacedName{
437+
Name: managedConfigMapName,
438+
Namespace: instance.Namespace,
439+
}, existingConfigMap)
440+
441+
if err == nil {
442+
// ConfigMap exists but is no longer needed, delete it
443+
logger.Info("Deleting unused managed CA bundle ConfigMap", "configMap", managedConfigMapName)
444+
if delErr := r.Delete(ctx, existingConfigMap); delErr != nil && !k8serrors.IsNotFound(delErr) {
445+
return fmt.Errorf("failed to delete unused managed CA bundle ConfigMap: %w", delErr)
446+
}
447+
logger.Info("Successfully deleted unused managed CA bundle ConfigMap", "configMap", managedConfigMapName)
448+
} else if !k8serrors.IsNotFound(err) {
449+
// Unexpected error
450+
return fmt.Errorf("failed to check for managed CA bundle ConfigMap: %w", err)
451+
}
430452
return nil
431453
}
432454

@@ -1239,9 +1261,9 @@ func (r *LlamaStackDistributionReconciler) hasODHTrustedCABundle(ctx context.Con
12391261

12401262
// gatherCABundleData collects all CA certificate data from source ConfigMaps and concatenates them.
12411263
// This function implements security measures to prevent injection attacks:
1242-
// - Validates all data as valid PEM before processing.
1264+
// - Validates PEM structure and X.509 certificate format during processing.
12431265
// - Enforces size limits to prevent resource exhaustion.
1244-
// - Only extracts valid CERTIFICATE blocks using PEM decoder.
1266+
// - Only extracts valid CERTIFICATE blocks using PEM decoder and X.509 parser.
12451267
func (r *LlamaStackDistributionReconciler) gatherCABundleData(ctx context.Context, instance *llamav1alpha1.LlamaStackDistribution) (string, error) {
12461268
logger := log.FromContext(ctx)
12471269
collector := &certificateCollector{logger: logger}
@@ -1291,10 +1313,20 @@ func (c *certificateCollector) concatenate() (string, error) {
12911313
return "", errors.New("failed to find valid certificates in CA bundle ConfigMaps")
12921314
}
12931315

1294-
concatenated := strings.Join(c.certificates, "\n")
1316+
// Use strings.Builder for efficient memory usage with large bundles
1317+
var builder strings.Builder
1318+
builder.Grow(c.totalSize + len(c.certificates)) // Pre-allocate with space for newlines
1319+
for i, cert := range c.certificates {
1320+
if i > 0 {
1321+
builder.WriteString("\n")
1322+
}
1323+
builder.WriteString(cert)
1324+
}
1325+
1326+
concatenated := builder.String()
12951327
c.logger.Info("Successfully gathered CA bundle data",
12961328
"totalCertificates", c.certificateCount,
1297-
"totalSize", c.totalSize)
1329+
"totalSize", len(concatenated))
12981330

12991331
return concatenated, nil
13001332
}
@@ -1324,8 +1356,12 @@ func (r *LlamaStackDistributionReconciler) gatherExplicitCABundle(ctx context.Co
13241356
}
13251357

13261358
func (r *LlamaStackDistributionReconciler) gatherODHCABundle(ctx context.Context, instance *llamav1alpha1.LlamaStackDistribution, collector *certificateCollector) error {
1327-
// ODH bundle is optional, so we ignore errors from detection
1328-
configMap, keys, _ := r.detectODHTrustedCABundle(ctx, instance)
1359+
configMap, keys, err := r.detectODHTrustedCABundle(ctx, instance)
1360+
if err != nil {
1361+
// Log but don't fail - ODH bundle is optional
1362+
collector.logger.V(1).Info("Could not detect ODH trusted CA bundle", "error", err)
1363+
return nil
1364+
}
13291365
if configMap == nil || len(keys) == 0 {
13301366
return nil
13311367
}
@@ -1378,8 +1414,9 @@ func (r *LlamaStackDistributionReconciler) processODHConfigMapKeys(configMap *co
13781414
}
13791415

13801416
// extractValidCertificates extracts only valid CERTIFICATE blocks from PEM data.
1381-
// This function validates all PEM blocks in the data.
1382-
// It filters out non-certificate PEM blocks (e.g., private keys, public keys).
1417+
// This function validates PEM structure and X.509 certificate format for all blocks.
1418+
// It filters out non-certificate PEM blocks (e.g., private keys, public keys) and
1419+
// rejects invalid X.509 certificates.
13831420
// Returns: (certificates as strings, total size, certificate count, error).
13841421
func extractValidCertificates(data []byte, keyName string) ([]string, int, int, error) {
13851422
if len(data) == 0 {
@@ -1403,6 +1440,11 @@ func extractValidCertificates(data []byte, keyName string) ([]string, int, int,
14031440
continue
14041441
}
14051442

1443+
// Validate that this is actually a valid X.509 certificate
1444+
if _, err := x509.ParseCertificate(block.Bytes); err != nil {
1445+
return nil, 0, 0, fmt.Errorf("failed to parse X.509 certificate from key '%s': %w", keyName, err)
1446+
}
1447+
14061448
// Re-encode the certificate to ensure it's properly formatted
14071449
certPEM := pem.EncodeToMemory(block)
14081450
if certPEM == nil {
@@ -1476,10 +1518,16 @@ func (r *LlamaStackDistributionReconciler) reconcileManagedCABundleConfigMap(ctx
14761518
// ConfigMap exists, update it if the data has changed
14771519
if existingConfigMap.Data[ManagedCABundleKey] != caBundleData {
14781520
logger.Info("Updating managed CA bundle ConfigMap", "configMap", managedConfigMapName)
1521+
// Use Patch instead of Update to avoid race conditions
1522+
patch := client.MergeFrom(existingConfigMap.DeepCopy())
14791523
existingConfigMap.Data = desiredConfigMap.Data
14801524
existingConfigMap.Labels = desiredConfigMap.Labels
1481-
if err := r.Update(ctx, existingConfigMap); err != nil {
1482-
return fmt.Errorf("failed to update managed CA bundle ConfigMap: %w", err)
1525+
if err := r.Patch(ctx, existingConfigMap, patch); err != nil {
1526+
if k8serrors.IsConflict(err) {
1527+
// Conflict detected, will be retried by controller
1528+
return fmt.Errorf("failed to patch managed CA bundle ConfigMap (conflict): %w", err)
1529+
}
1530+
return fmt.Errorf("failed to patch managed CA bundle ConfigMap: %w", err)
14831531
}
14841532
logger.Info("Successfully updated managed CA bundle ConfigMap", "configMap", managedConfigMapName)
14851533
} else {

controllers/llamastackdistribution_controller_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,4 +752,47 @@ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1234567890ABCDEF
752752
require.Contains(t, err.Error(), "failed to find valid certificates",
753753
"error should indicate no valid certificates")
754754
})
755+
756+
t.Run("rejects invalid X.509 certificates", func(t *testing.T) {
757+
// --- arrange ---
758+
namespace := createTestNamespace(t, "test-reject-invalid-x509")
759+
760+
// Create source ConfigMap with malformed certificate data
761+
// This has correct PEM structure but invalid X.509 certificate data
762+
sourceConfigMap := &corev1.ConfigMap{
763+
ObjectMeta: metav1.ObjectMeta{
764+
Name: "source-with-invalid-cert",
765+
Namespace: namespace.Name,
766+
},
767+
Data: map[string]string{
768+
"ca-bundle.crt": `-----BEGIN CERTIFICATE-----
769+
InvalidCertificateDataThatIsNotValidX509
770+
-----END CERTIFICATE-----`,
771+
},
772+
}
773+
require.NoError(t, k8sClient.Create(t.Context(), sourceConfigMap))
774+
775+
instance := NewDistributionBuilder().
776+
WithName("test-reject-invalid").
777+
WithNamespace(namespace.Name).
778+
WithCABundle("source-with-invalid-cert", nil).
779+
Build()
780+
781+
require.NoError(t, k8sClient.Create(t.Context(), instance))
782+
t.Cleanup(func() { _ = k8sClient.Delete(t.Context(), instance) })
783+
784+
// --- act ---
785+
reconciler := createTestReconciler()
786+
_, err := reconciler.Reconcile(t.Context(), ctrl.Request{
787+
NamespacedName: types.NamespacedName{
788+
Name: instance.Name,
789+
Namespace: instance.Namespace,
790+
},
791+
})
792+
793+
// --- assert ---
794+
require.Error(t, err, "reconciliation should fail for invalid X.509 certificate")
795+
require.Contains(t, err.Error(), "failed to parse X.509 certificate",
796+
"error should indicate X.509 parsing failure")
797+
})
755798
}

0 commit comments

Comments
 (0)