Skip to content

Commit 5011109

Browse files
Wei WengWei Weng
authored andcommitted
do not populate CA
Signed-off-by: Wei Weng <[email protected]>
1 parent a8e0e61 commit 5011109

File tree

2 files changed

+85
-121
lines changed

2 files changed

+85
-121
lines changed

pkg/webhook/webhook.go

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -193,25 +193,20 @@ func NewWebhookConfig(mgr manager.Manager, webhookServiceName string, port int32
193193
webhookCertSecretName: webhookCertSecretName,
194194
}
195195

196-
var caPEM []byte
197-
var err error
198-
199196
if useCertManager {
200-
// When using cert-manager, certificates are mounted as files by Kubernetes
201-
// cert-manager creates tls.crt and tls.key, but we need ca.crt for the webhook config
202-
caPEM, err = w.loadCertManagerCA(certDir)
203-
if err != nil {
204-
return nil, fmt.Errorf("failed to load cert-manager CA certificate: %w", err)
205-
}
197+
// When using cert-manager, the CA bundle is automatically injected by cert-manager's CA injector
198+
// based on the cert-manager.io/inject-ca-from annotation. We don't need to load or set the CA here.
199+
// The certificates (tls.crt and tls.key) are mounted by Kubernetes and used automatically by the webhook server.
200+
klog.V(2).InfoS("Using cert-manager for certificate management", "certDir", certDir)
206201
} else {
207202
// Use self-signed certificate generation (original flow)
208-
caPEM, err = w.genCertificate(certDir)
203+
caPEM, err := w.genCertificate(certDir)
209204
if err != nil {
210205
return nil, fmt.Errorf("failed to generate self-signed certificate: %w", err)
211206
}
207+
w.caPEM = caPEM
212208
}
213209

214-
w.caPEM = caPEM
215210
return &w, nil
216211
}
217212

@@ -675,9 +670,14 @@ func (w *Config) createClientConfig(validationPath string) admv1.WebhookClientCo
675670
}
676671
serviceEndpoint := w.serviceURL + validationPath
677672
serviceRef.Path = ptr.To(validationPath)
678-
config := admv1.WebhookClientConfig{
679-
CABundle: w.caPEM,
673+
config := admv1.WebhookClientConfig{}
674+
675+
// When using cert-manager, leave CABundle empty so cert-manager's CA injector can populate it.
676+
// The cert-manager.io/inject-ca-from annotation triggers automatic CA injection.
677+
if !w.useCertManager {
678+
config.CABundle = w.caPEM
680679
}
680+
681681
switch *w.clientConnectionType {
682682
case options.Service:
683683
config.Service = &serviceRef
@@ -703,26 +703,6 @@ func (w *Config) genCertificate(certDir string) ([]byte, error) {
703703
return caPEM, nil
704704
}
705705

706-
// loadCertManagerCA loads the CA certificate from the mounted cert-manager Secret.
707-
// When using cert-manager, Kubernetes mounts the Secret as files in the certDir.
708-
// cert-manager creates: ca.crt, tls.crt, and tls.key.
709-
// The tls.crt and tls.key are automatically used by the webhook server.
710-
// We only need to read ca.crt for the webhook configuration's CABundle.
711-
func (w *Config) loadCertManagerCA(certDir string) ([]byte, error) {
712-
caPath := filepath.Join(certDir, "ca.crt")
713-
caCert, err := os.ReadFile(caPath)
714-
if err != nil {
715-
return nil, fmt.Errorf("failed to read ca.crt from %s: %w", caPath, err)
716-
}
717-
718-
if len(caCert) == 0 {
719-
return nil, fmt.Errorf("ca.crt is empty at %s", caPath)
720-
}
721-
722-
klog.V(2).InfoS("Successfully loaded CA certificate from cert-manager mounted Secret", "path", caPath)
723-
return caCert, nil
724-
}
725-
726706
// genSelfSignedCert generates the self signed Certificate/Key pair
727707
func (w *Config) genSelfSignedCert() (caPEMByte, certPEMByte, keyPEMByte []byte, err error) {
728708
// CA config

pkg/webhook/webhook_test.go

Lines changed: 72 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package webhook
22

33
import (
4-
"os"
5-
"path/filepath"
64
"strings"
75
"testing"
86

@@ -15,26 +13,7 @@ import (
1513
"github.com/kubefleet-dev/kubefleet/pkg/utils"
1614
)
1715

18-
const mockCA = `-----BEGIN CERTIFICATE-----
19-
MIIBkTCB+wIJAKHHCgVZU7c4MA0GCSqGSIb3DQEBCwUAMBExDzANBgNVBAMMBnRl
20-
c3RDQTAeFw0yNDAxMDEwMDAwMDBaFw0yNTAxMDEwMDAwMDBaMBExDzANBgNVBAMM
21-
BnRlc3RDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwjBCKwYJKoZIhvcN
22-
AQkBFhZmb3JtYXRAZXhhbXBsZS5jb20wHhcNMjQwMTAxMDAwMDAwWhcNMjUwMTAx
23-
MDAwMDAwWjARMQ8wDQYDVQQDDAZ0ZXN0Q0EwXDANBgkqhkiG9w0BAQEFAANLADBI
24-
AkEAwjBCKwYJKoZIhvcNAQEBBQADQQAwPgJBAMIwQisGCSqGSIb3DQEBAQUAA0EA
25-
MD4CQQDCMEIrBgkqhkiG9w0BAQEFAANBADANBgkqhkiG9w0BAQUFAANBAMA=
26-
-----END CERTIFICATE-----`
27-
28-
// setupMockCertManagerFiles creates mock certificate files for testing cert-manager mode.
29-
func setupMockCertManagerFiles(t *testing.T, certDir string) {
30-
t.Helper()
31-
if err := os.WriteFile(filepath.Join(certDir, "ca.crt"), []byte(mockCA), 0600); err != nil {
32-
t.Fatalf("failed to create mock ca.crt: %v", err)
33-
}
34-
t.Cleanup(func() {
35-
os.RemoveAll(certDir)
36-
})
37-
}
16+
const testWebhookServiceName = "test-webhook"
3817

3918
func TestBuildFleetMutatingWebhooks(t *testing.T) {
4019
url := options.WebhookClientConnectionType("url")
@@ -189,11 +168,6 @@ func TestNewWebhookConfig(t *testing.T) {
189168
t.Run(tt.name, func(t *testing.T) {
190169
t.Setenv("POD_NAMESPACE", "test-namespace")
191170

192-
// Create mock certificate files for cert-manager mode
193-
if tt.useCertManager {
194-
setupMockCertManagerFiles(t, tt.certDir)
195-
}
196-
197171
got, err := NewWebhookConfig(tt.mgr, tt.webhookServiceName, tt.port, tt.clientConnectionType, tt.certDir, tt.enableGuardRail, tt.denyModifyMemberClusterLabels, tt.enableWorkload, tt.useCertManager, "fleet-webhook-server-cert", "fleet-webhook-server-cert")
198172
if (err != nil) != tt.wantErr {
199173
t.Errorf("NewWebhookConfig() error = %v, wantErr %v", err, tt.wantErr)
@@ -216,67 +190,6 @@ func TestNewWebhookConfig(t *testing.T) {
216190
})
217191
}
218192
}
219-
func TestLoadCertManagerCA_NotFound(t *testing.T) {
220-
config := &Config{}
221-
_, err := config.loadCertManagerCA("/nonexistent/path")
222-
if err == nil {
223-
t.Error("Expected error when certificate files don't exist")
224-
}
225-
}
226-
227-
func TestLoadCertManagerCA_EmptyFile(t *testing.T) {
228-
dir := t.TempDir()
229-
// Create empty files
230-
if err := os.WriteFile(filepath.Join(dir, "tls.crt"), []byte{}, 0600); err != nil {
231-
t.Fatalf("failed to create empty tls.crt: %v", err)
232-
}
233-
if err := os.WriteFile(filepath.Join(dir, "ca.crt"), []byte{}, 0600); err != nil {
234-
t.Fatalf("failed to create empty ca.crt: %v", err)
235-
}
236-
237-
config := &Config{}
238-
_, err := config.loadCertManagerCA(dir)
239-
if err == nil {
240-
t.Error("Expected error for empty certificate files")
241-
}
242-
if !strings.Contains(err.Error(), "empty") {
243-
t.Errorf("Expected error message to contain 'empty', got: %v", err)
244-
}
245-
}
246-
247-
func TestLoadCertManagerCA_Success(t *testing.T) {
248-
t.Run("loads ca.crt successfully", func(t *testing.T) {
249-
dir := t.TempDir()
250-
caContent := []byte("test-ca-content")
251-
if err := os.WriteFile(filepath.Join(dir, "ca.crt"), caContent, 0600); err != nil {
252-
t.Fatalf("failed to create ca.crt: %v", err)
253-
}
254-
255-
config := &Config{}
256-
result, err := config.loadCertManagerCA(dir)
257-
if err != nil {
258-
t.Errorf("Unexpected error: %v", err)
259-
}
260-
if string(result) != string(caContent) {
261-
t.Errorf("Expected %s, got %s", caContent, result)
262-
}
263-
})
264-
}
265-
266-
func TestNewWebhookConfig_CertManagerNotMounted(t *testing.T) {
267-
t.Setenv("POD_NAMESPACE", "test-namespace")
268-
269-
dir := t.TempDir()
270-
// Don't create any certificate files to simulate cert-manager not ready
271-
272-
_, err := NewWebhookConfig(nil, "test-webhook", 8080, nil, dir, true, true, false, true, "fleet-webhook-server-cert", "fleet-webhook-server-cert")
273-
if err == nil {
274-
t.Error("Expected error when cert-manager certificates not mounted")
275-
}
276-
if !strings.Contains(err.Error(), "failed to load cert-manager CA certificate") {
277-
t.Errorf("Expected error about loading cert-manager CA, got: %v", err)
278-
}
279-
}
280193

281194
func TestNewWebhookConfig_SelfSignedCertError(t *testing.T) {
282195
t.Setenv("POD_NAMESPACE", "test-namespace")
@@ -356,3 +269,74 @@ func TestBuildWebhookAnnotations(t *testing.T) {
356269
})
357270
}
358271
}
272+
273+
func TestCreateClientConfig(t *testing.T) {
274+
serviceConnectionType := options.Service
275+
testCases := map[string]struct {
276+
config Config
277+
validationPath string
278+
expectCABundle bool
279+
}{
280+
"useCertManager is false - CABundle should be set": {
281+
config: Config{
282+
useCertManager: false,
283+
caPEM: []byte("test-ca-bundle"),
284+
serviceNamespace: "fleet-system",
285+
serviceName: "fleet-webhook-service",
286+
servicePort: 443,
287+
clientConnectionType: &serviceConnectionType,
288+
},
289+
validationPath: "/validate",
290+
expectCABundle: true,
291+
},
292+
"useCertManager is true - CABundle should be empty for cert-manager injection": {
293+
config: Config{
294+
useCertManager: true,
295+
caPEM: []byte("test-ca-bundle"),
296+
serviceNamespace: "fleet-system",
297+
serviceName: "fleet-webhook-service",
298+
servicePort: 443,
299+
clientConnectionType: &serviceConnectionType,
300+
},
301+
validationPath: "/validate",
302+
expectCABundle: false,
303+
},
304+
}
305+
306+
for name, tc := range testCases {
307+
t.Run(name, func(t *testing.T) {
308+
clientConfig := tc.config.createClientConfig(tc.validationPath)
309+
310+
if tc.expectCABundle {
311+
if len(clientConfig.CABundle) == 0 {
312+
t.Errorf("Expected CABundle to be set, but it was empty")
313+
}
314+
if diff := cmp.Diff(tc.config.caPEM, clientConfig.CABundle); diff != "" {
315+
t.Errorf("CABundle mismatch (-want +got):\n%s", diff)
316+
}
317+
} else {
318+
if len(clientConfig.CABundle) != 0 {
319+
t.Errorf("Expected CABundle to be empty for cert-manager injection, but got: %v", clientConfig.CABundle)
320+
}
321+
}
322+
323+
// Verify service reference is set correctly
324+
if clientConfig.Service == nil {
325+
t.Errorf("Expected Service to be set")
326+
} else {
327+
if clientConfig.Service.Namespace != tc.config.serviceNamespace {
328+
t.Errorf("Expected Service.Namespace=%s, got %s", tc.config.serviceNamespace, clientConfig.Service.Namespace)
329+
}
330+
if clientConfig.Service.Name != tc.config.serviceName {
331+
t.Errorf("Expected Service.Name=%s, got %s", tc.config.serviceName, clientConfig.Service.Name)
332+
}
333+
if *clientConfig.Service.Port != tc.config.servicePort {
334+
t.Errorf("Expected Service.Port=%d, got %d", tc.config.servicePort, *clientConfig.Service.Port)
335+
}
336+
if *clientConfig.Service.Path != tc.validationPath {
337+
t.Errorf("Expected Service.Path=%s, got %s", tc.validationPath, *clientConfig.Service.Path)
338+
}
339+
}
340+
})
341+
}
342+
}

0 commit comments

Comments
 (0)