From 7c78dd78ff67c53db1ec8e73a2417815501e7e92 Mon Sep 17 00:00:00 2001 From: jannfis Date: Fri, 5 Dec 2025 18:54:02 +0000 Subject: [PATCH 1/4] feat(cli): agent create can now use existing TLS certificates Signed-off-by: jannfis Assisted-by: Cursor --- cmd/ctl/agent.go | 94 ++++++++++++- cmd/ctl/agent_test.go | 312 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 401 insertions(+), 5 deletions(-) diff --git a/cmd/ctl/agent.go b/cmd/ctl/agent.go index 806419f1..e773c03f 100644 --- a/cmd/ctl/agent.go +++ b/cmd/ctl/agent.go @@ -98,10 +98,76 @@ func generateAgentClientCert(agentName string, clt *kube.KubernetesClient) (clie return } +// parseSecretRef parses a secret reference in the format "namespace/name" or "name". +// If no namespace is specified, the default namespace is used. +func parseSecretRef(secretRef, defaultNamespace string) (namespace, name string) { + parts := strings.SplitN(secretRef, "/", 2) + if len(parts) == 2 { + return parts[0], parts[1] + } + return defaultNamespace, secretRef +} + +// readTLSFromExistingSecrets reads TLS certificate, key and CA data from existing secrets. +// The TLS secret should contain keys tls.crt and tls.key. +// The CA secret should contain the key ca.crt. +// Secret references can be in the format "namespace/name" or just "name" (uses principal namespace). +func readTLSFromExistingSecrets(ctx context.Context, clt *kube.KubernetesClient, tlsSecretRef, caSecretRef string) (clientCert string, clientKey string, caData string, err error) { + // Parse secret references to extract namespace and name + tlsNamespace, tlsSecretName := parseSecretRef(tlsSecretRef, globalOpts.principalNamespace) + caNamespace, caSecretName := parseSecretRef(caSecretRef, globalOpts.principalNamespace) + + // Read TLS certificate and key from the TLS secret + tlsSecret, err := clt.Clientset.CoreV1().Secrets(tlsNamespace).Get(ctx, tlsSecretName, metav1.GetOptions{}) + if err != nil { + err = fmt.Errorf("could not read TLS secret %s/%s: %w", tlsNamespace, tlsSecretName, err) + return + } + + certData, ok := tlsSecret.Data["tls.crt"] + if !ok { + err = fmt.Errorf("TLS secret %s/%s does not contain key 'tls.crt'", tlsNamespace, tlsSecretName) + return + } + keyData, ok := tlsSecret.Data["tls.key"] + if !ok { + err = fmt.Errorf("TLS secret %s/%s does not contain key 'tls.key'", tlsNamespace, tlsSecretName) + return + } + + // Validate that the certificate and key form a valid pair + _, err = tls.X509KeyPair(certData, keyData) + if err != nil { + err = fmt.Errorf("invalid TLS certificate/key pair in secret %s/%s: %w", tlsNamespace, tlsSecretName, err) + return + } + + // Read CA certificate from the CA secret + caSecret, err := clt.Clientset.CoreV1().Secrets(caNamespace).Get(ctx, caSecretName, metav1.GetOptions{}) + if err != nil { + err = fmt.Errorf("could not read CA secret %s/%s: %w", caNamespace, caSecretName, err) + return + } + + caCertData, ok := caSecret.Data["ca.crt"] + if !ok { + err = fmt.Errorf("CA secret %s/%s does not contain key 'ca.crt'", caNamespace, caSecretName) + return + } + + clientCert = string(certData) + clientKey = string(keyData) + caData = string(caCertData) + + return +} + func NewAgentCreateCommand() *cobra.Command { var ( - rpServer string - addLabels []string + rpServer string + addLabels []string + tlsFromSecret string + caFromSecret string ) command := &cobra.Command{ Short: "Create a new agent configuration", @@ -114,6 +180,11 @@ func NewAgentCreateCommand() *cobra.Command { agentName := args[0] ctx := context.TODO() + // Validate that both --tls-from-secret and --ca-from-secret are provided together + if (tlsFromSecret != "" && caFromSecret == "") || (tlsFromSecret == "" && caFromSecret != "") { + cmdutil.Fatal("Both --tls-from-secret and --ca-from-secret must be provided together") + } + // A set of labels for the cluster secret labels := make(map[string]string) if len(addLabels) > 0 { @@ -145,9 +216,20 @@ func NewAgentCreateCommand() *cobra.Command { cmdutil.Fatal("Agent %s exists.", agentName) } - clientCert, clientKey, caData, err := generateAgentClientCert(agentName, clt) - if err != nil { - cmdutil.Fatal("%v", err) + var clientCert, clientKey, caData string + + if tlsFromSecret != "" && caFromSecret != "" { + // Read TLS credentials from existing secrets + clientCert, clientKey, caData, err = readTLSFromExistingSecrets(ctx, clt, tlsFromSecret, caFromSecret) + if err != nil { + cmdutil.Fatal("%v", err) + } + } else { + // Generate certificates from the PKI + clientCert, clientKey, caData, err = generateAgentClientCert(agentName, clt) + if err != nil { + cmdutil.Fatal("%v", err) + } } // Construct Argo CD cluster configuration @@ -184,6 +266,8 @@ func NewAgentCreateCommand() *cobra.Command { } command.Flags().StringVar(&rpServer, "resource-proxy-server", "argocd-agent-resource-proxy:9090", "Address of principal's resource-proxy") command.Flags().StringSliceVarP(&addLabels, "label", "l", []string{}, "Additional labels for the agent") + command.Flags().StringVar(&tlsFromSecret, "tls-from-secret", "", "Name of an existing secret containing TLS certificate and key (keys: tls.crt, tls.key). Format: [namespace/]name") + command.Flags().StringVar(&caFromSecret, "ca-from-secret", "", "Name of an existing secret containing CA certificate (key: ca.crt). Format: [namespace/]name") return command } diff --git a/cmd/ctl/agent_test.go b/cmd/ctl/agent_test.go index 992aa3a5..49e668ed 100644 --- a/cmd/ctl/agent_test.go +++ b/cmd/ctl/agent_test.go @@ -15,10 +15,16 @@ package main import ( + "context" "testing" + "github.com/argoproj-labs/argocd-agent/internal/kube" + "github.com/argoproj-labs/argocd-agent/test/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kubefake "k8s.io/client-go/kubernetes/fake" ) func Test_serverURL(t *testing.T) { @@ -241,3 +247,309 @@ func Test_serverURL(t *testing.T) { }) } } + +func Test_parseSecretRef(t *testing.T) { + testCases := []struct { + name string + secretRef string + defaultNamespace string + expectedNamespace string + expectedName string + }{ + { + name: "simple name without namespace", + secretRef: "my-secret", + defaultNamespace: "default-ns", + expectedNamespace: "default-ns", + expectedName: "my-secret", + }, + { + name: "name with namespace", + secretRef: "custom-ns/my-secret", + defaultNamespace: "default-ns", + expectedNamespace: "custom-ns", + expectedName: "my-secret", + }, + { + name: "name with namespace containing slashes in name", + secretRef: "ns/secret/with/slashes", + defaultNamespace: "default-ns", + expectedNamespace: "ns", + expectedName: "secret/with/slashes", + }, + { + name: "empty namespace prefix", + secretRef: "/my-secret", + defaultNamespace: "default-ns", + expectedNamespace: "", + expectedName: "my-secret", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + namespace, name := parseSecretRef(tc.secretRef, tc.defaultNamespace) + assert.Equal(t, tc.expectedNamespace, namespace) + assert.Equal(t, tc.expectedName, name) + }) + } +} + +func Test_readTLSFromExistingSecrets(t *testing.T) { + // Load test certificate data + certPem := testutil.MustReadFile("testdata/001_test_cert.pem") + keyPem := testutil.MustReadFile("testdata/001_test_key.pem") + + // Save original globalOpts and restore after test + originalNamespace := globalOpts.principalNamespace + defer func() { + globalOpts.principalNamespace = originalNamespace + }() + globalOpts.principalNamespace = "argocd" + + t.Run("Valid TLS and CA secrets in default namespace", func(t *testing.T) { + tlsSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tls-secret", + Namespace: "argocd", + }, + Data: map[string][]byte{ + "tls.crt": certPem, + "tls.key": keyPem, + }, + } + caSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ca-secret", + Namespace: "argocd", + }, + Data: map[string][]byte{ + "ca.crt": certPem, // Use cert as CA for testing + }, + } + + clientset := kubefake.NewSimpleClientset(tlsSecret, caSecret) + clt := &kube.KubernetesClient{Clientset: clientset} + + clientCert, clientKey, caData, err := readTLSFromExistingSecrets(context.TODO(), clt, "my-tls-secret", "my-ca-secret") + require.NoError(t, err) + assert.Equal(t, string(certPem), clientCert) + assert.Equal(t, string(keyPem), clientKey) + assert.Equal(t, string(certPem), caData) + }) + + t.Run("Valid TLS and CA secrets with explicit namespace", func(t *testing.T) { + tlsSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tls-secret", + Namespace: "custom-ns", + }, + Data: map[string][]byte{ + "tls.crt": certPem, + "tls.key": keyPem, + }, + } + caSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ca-secret", + Namespace: "other-ns", + }, + Data: map[string][]byte{ + "ca.crt": certPem, + }, + } + + clientset := kubefake.NewSimpleClientset(tlsSecret, caSecret) + clt := &kube.KubernetesClient{Clientset: clientset} + + clientCert, clientKey, caData, err := readTLSFromExistingSecrets(context.TODO(), clt, "custom-ns/my-tls-secret", "other-ns/my-ca-secret") + require.NoError(t, err) + assert.Equal(t, string(certPem), clientCert) + assert.Equal(t, string(keyPem), clientKey) + assert.Equal(t, string(certPem), caData) + }) + + t.Run("TLS secret not found", func(t *testing.T) { + caSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ca-secret", + Namespace: "argocd", + }, + Data: map[string][]byte{ + "ca.crt": certPem, + }, + } + + clientset := kubefake.NewSimpleClientset(caSecret) + clt := &kube.KubernetesClient{Clientset: clientset} + + _, _, _, err := readTLSFromExistingSecrets(context.TODO(), clt, "my-tls-secret", "my-ca-secret") + require.Error(t, err) + assert.Contains(t, err.Error(), "could not read TLS secret argocd/my-tls-secret") + }) + + t.Run("CA secret not found", func(t *testing.T) { + tlsSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tls-secret", + Namespace: "argocd", + }, + Data: map[string][]byte{ + "tls.crt": certPem, + "tls.key": keyPem, + }, + } + + clientset := kubefake.NewSimpleClientset(tlsSecret) + clt := &kube.KubernetesClient{Clientset: clientset} + + _, _, _, err := readTLSFromExistingSecrets(context.TODO(), clt, "my-tls-secret", "my-ca-secret") + require.Error(t, err) + assert.Contains(t, err.Error(), "could not read CA secret argocd/my-ca-secret") + }) + + t.Run("TLS secret missing tls.crt key", func(t *testing.T) { + tlsSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tls-secret", + Namespace: "argocd", + }, + Data: map[string][]byte{ + "tls.key": keyPem, + }, + } + caSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ca-secret", + Namespace: "argocd", + }, + Data: map[string][]byte{ + "ca.crt": certPem, + }, + } + + clientset := kubefake.NewSimpleClientset(tlsSecret, caSecret) + clt := &kube.KubernetesClient{Clientset: clientset} + + _, _, _, err := readTLSFromExistingSecrets(context.TODO(), clt, "my-tls-secret", "my-ca-secret") + require.Error(t, err) + assert.Contains(t, err.Error(), "does not contain key 'tls.crt'") + }) + + t.Run("TLS secret missing tls.key key", func(t *testing.T) { + tlsSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tls-secret", + Namespace: "argocd", + }, + Data: map[string][]byte{ + "tls.crt": certPem, + }, + } + caSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ca-secret", + Namespace: "argocd", + }, + Data: map[string][]byte{ + "ca.crt": certPem, + }, + } + + clientset := kubefake.NewSimpleClientset(tlsSecret, caSecret) + clt := &kube.KubernetesClient{Clientset: clientset} + + _, _, _, err := readTLSFromExistingSecrets(context.TODO(), clt, "my-tls-secret", "my-ca-secret") + require.Error(t, err) + assert.Contains(t, err.Error(), "does not contain key 'tls.key'") + }) + + t.Run("CA secret missing ca.crt key", func(t *testing.T) { + tlsSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tls-secret", + Namespace: "argocd", + }, + Data: map[string][]byte{ + "tls.crt": certPem, + "tls.key": keyPem, + }, + } + caSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ca-secret", + Namespace: "argocd", + }, + Data: map[string][]byte{ + "other-key": certPem, + }, + } + + clientset := kubefake.NewSimpleClientset(tlsSecret, caSecret) + clt := &kube.KubernetesClient{Clientset: clientset} + + _, _, _, err := readTLSFromExistingSecrets(context.TODO(), clt, "my-tls-secret", "my-ca-secret") + require.Error(t, err) + assert.Contains(t, err.Error(), "does not contain key 'ca.crt'") + }) + + t.Run("Invalid TLS certificate/key pair", func(t *testing.T) { + tlsSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tls-secret", + Namespace: "argocd", + }, + Data: map[string][]byte{ + "tls.crt": []byte("invalid cert"), + "tls.key": []byte("invalid key"), + }, + } + caSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ca-secret", + Namespace: "argocd", + }, + Data: map[string][]byte{ + "ca.crt": certPem, + }, + } + + clientset := kubefake.NewSimpleClientset(tlsSecret, caSecret) + clt := &kube.KubernetesClient{Clientset: clientset} + + _, _, _, err := readTLSFromExistingSecrets(context.TODO(), clt, "my-tls-secret", "my-ca-secret") + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid TLS certificate/key pair") + }) + + t.Run("Secrets in different namespaces", func(t *testing.T) { + tlsSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tls-secret", + Namespace: "tls-namespace", + }, + Data: map[string][]byte{ + "tls.crt": certPem, + "tls.key": keyPem, + }, + } + caSecret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca-secret", + Namespace: "ca-namespace", + }, + Data: map[string][]byte{ + "ca.crt": certPem, + }, + } + + clientset := kubefake.NewSimpleClientset(tlsSecret, caSecret) + clt := &kube.KubernetesClient{Clientset: clientset} + + clientCert, clientKey, caData, err := readTLSFromExistingSecrets(context.TODO(), clt, "tls-namespace/tls-secret", "ca-namespace/ca-secret") + require.NoError(t, err) + assert.Equal(t, string(certPem), clientCert) + assert.Equal(t, string(keyPem), clientKey) + assert.Equal(t, string(certPem), caData) + }) +} From 894fe64c856bfe200cb196bad5d69af1796a4648 Mon Sep 17 00:00:00 2001 From: jannfis Date: Fri, 5 Dec 2025 19:21:01 +0000 Subject: [PATCH 2/4] Add some test data Signed-off-by: jannfis --- cmd/ctl/testdata/001_test_cert.pem | 21 +++++++++++++++++++++ cmd/ctl/testdata/001_test_key.pem | 28 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 cmd/ctl/testdata/001_test_cert.pem create mode 100644 cmd/ctl/testdata/001_test_key.pem diff --git a/cmd/ctl/testdata/001_test_cert.pem b/cmd/ctl/testdata/001_test_cert.pem new file mode 100644 index 00000000..dee7cb09 --- /dev/null +++ b/cmd/ctl/testdata/001_test_cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZTCCAk2gAwIBAgIUFAk2kiIEyjS0nCZd/ymtM1qKlfYwDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE +CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAeFw0yNDEyMTcxNDE5MDhaFw0yNTEyMTcx +NDE5MDhaMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAa +BgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCiKV5G7Pu9aC/ZapHuu3yAC6XrVNAgcPHVK0/oQZWCn9/w+3gO +8LZKo9x0rgz7zrlNjg+lR2lQIQ37Zk6b2tVBhl2O9Qm8HShoY17etBuJJI2a7Ru7 +j4IW+GPDON2POiQwFQxoXo3AKyCEzohV7vJ0ShF0M5viJA6Y5jcVSzkQXrDqt1m6 +hLA3mVxkFZ76qCSnOzMoA80+hHWpI64rgZKsKlWQPWe9fE+oIp/JyKU4TXhP/Kua +6ulkQ31dP0gzpANWaROvU3NUy0O+pxyGqc6lpFD94DqqTV8EVh1CknA3KO3pfmX+ +skHm5jL4DYdPn8ciNibRETh+8AhWjOQcNXnFAgMBAAGjUzBRMB0GA1UdDgQWBBQL +rIsnR3OoNtm7InHaaaJhYhFG2zAfBgNVHSMEGDAWgBQLrIsnR3OoNtm7InHaaaJh +YhFG2zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB/EECtLv5D +7gFkz3NxcaJfjvGPO2vTSJx79T0ndYKwXqqGisOXfY2SVkq3B03HMRCeTNWJmEie +Gd5zxdBw/lVYFmhJmBzGchD7t4eDx0Yavm8kyc2oU1HAWgZBxtye54ZjAYqBFsrw +qYRiJH5Zr1+8Xacc4usoNGxeGDZWtNrz/qku7pb1pdBGQzUADI+d3DG8+XfhMZtr +jpuq+kZfR/GvFKxHMwyjRw1vwRDL0FYbDtcIbqqJTsR9lxYLhtYbOSzCkGqjmbpQ +rUZ4rsIYeXh+N7vbIVqYjjbmA6fB1N0S3/svVuHtzXxfbuzxBcdDdPrZr3VmoZIh +et6RvI7KUqx+ +-----END CERTIFICATE----- diff --git a/cmd/ctl/testdata/001_test_key.pem b/cmd/ctl/testdata/001_test_key.pem new file mode 100644 index 00000000..e29d93aa --- /dev/null +++ b/cmd/ctl/testdata/001_test_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCiKV5G7Pu9aC/Z +apHuu3yAC6XrVNAgcPHVK0/oQZWCn9/w+3gO8LZKo9x0rgz7zrlNjg+lR2lQIQ37 +Zk6b2tVBhl2O9Qm8HShoY17etBuJJI2a7Ru7j4IW+GPDON2POiQwFQxoXo3AKyCE +zohV7vJ0ShF0M5viJA6Y5jcVSzkQXrDqt1m6hLA3mVxkFZ76qCSnOzMoA80+hHWp +I64rgZKsKlWQPWe9fE+oIp/JyKU4TXhP/Kua6ulkQ31dP0gzpANWaROvU3NUy0O+ +pxyGqc6lpFD94DqqTV8EVh1CknA3KO3pfmX+skHm5jL4DYdPn8ciNibRETh+8AhW +jOQcNXnFAgMBAAECggEADOfH61MZkcWvO9lXCy3RRwt7mpKhtwM7a8tKTQydznXU +x66WVnINFTxHOONP1eEU2Y7lwIdCDVt5a7mE+12wvR8+u42AHIAhjXU4a/bffiGc +7L3UCDBFRSNiz9BAabv8fKB1iqouaSMa2mw+vhVfJvwXZC0QnzBewl0H+IzdBL1e +g4JLylJk1k0YIStzI1qDP32t78mXuNvHPeQqDzZaFPHcAP2okMCCVMiCspnfa4RO +lvpW6IC1rr0cJYmYqkTFDft2ny/OaSKNc7bw9/OKwP+SaZywXQcZfMeS3iDpHdgs +1PbXpTjGayPU1zFLac4ChdxuPMIKZ1bWD8pUC1dWvQKBgQDjCvYhNGZn7zf3dBH8 +hIzPauDcR/Bawcxqd8Wgbvu/zTbicxdLni0ECrRz625eJatkG9GdRGvtRlWzZUbC +FYSEV1SnfJkmaCl3tcY3AZpnb/aEgb+kHQl0vMUgciCOsDjYTYz47is+7Yp1qXor +QmSK0kTcE3KyjVipukEuSImvLwKBgQC22AJObwHv9wEfIPRBrfbAxrRrDuQ0V6tk +S2TlHCpFCh00ksBuj7jmvDq92hqAZhqSeuZtZx8EyFam6HT0l/EYzTgG9RMxaHhP +PIshry5S0FK2pnsZIE+vpdXRr1uFFVRPqtj4HP17jvvEW3pQXXYNtMs3FPkfTyGq +XR+PZRGJSwKBgCDng8hIKddCSiAoyDqKk0W0PaZvHpxondGITjH0I7Qmb5/eAjBJ +WkjNrF1ob3RhjTdS+MwMEIAww1behKS4LZ5ocbJcUm3Ihsn8pB9wsgnvphCKJVYJ +h0dN3FvZbnJ/g52Fj7q7+bSDBKAM0dHXK28bDjO+9c5+wazHe47ToHCtAoGAZqZk +vRXzN34rogdFOe5pnpavyX7lvUEO1tLBBSNH09S2ysIsyKVlgBxiuh1NTZKFDoFz +Bi6jqnKyuye8KWl4EJ19++Hw8YceLBXoYnPQBOwx05spdtS+B/WJUhwpvFBaMhPP +lZPo90oxrG5S//VIhq9ee0EKD3rEgrmfM0jhjHsCgYAH7V5L3HnhRdhMPb5Ggvxm +dH00H5t52OnJTxiCjqOFl02BdsRWkweyPFSecACC64oEDJ2OEZvTP9tAj+OUy3Sf +/pPeVFagWYvijo5fOjKreqViwB8ipFk6syZTLhCyfN9um1qfjuad1IjGwU3Cwklh +0RiX8twiB5IDY3fDvuBB1Q== +-----END PRIVATE KEY----- From 348728e5ee00054836ea4a94f7facc152fc895ed Mon Sep 17 00:00:00 2001 From: Jann Fischer Date: Fri, 5 Dec 2025 14:22:33 -0500 Subject: [PATCH 3/4] Update cmd/ctl/agent.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Jann Fischer --- cmd/ctl/agent.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/ctl/agent.go b/cmd/ctl/agent.go index e773c03f..75e12a51 100644 --- a/cmd/ctl/agent.go +++ b/cmd/ctl/agent.go @@ -103,6 +103,9 @@ func generateAgentClientCert(agentName string, clt *kube.KubernetesClient) (clie func parseSecretRef(secretRef, defaultNamespace string) (namespace, name string) { parts := strings.SplitN(secretRef, "/", 2) if len(parts) == 2 { + if parts[0] == "" { + return defaultNamespace, parts[1] + } return parts[0], parts[1] } return defaultNamespace, secretRef From 5f4e8ac89de01f147c41f6d57e2f96ef61fc67eb Mon Sep 17 00:00:00 2001 From: jannfis Date: Fri, 5 Dec 2025 19:37:46 +0000 Subject: [PATCH 4/4] Fix test case Signed-off-by: jannfis --- cmd/ctl/agent_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ctl/agent_test.go b/cmd/ctl/agent_test.go index 49e668ed..b5f35115 100644 --- a/cmd/ctl/agent_test.go +++ b/cmd/ctl/agent_test.go @@ -281,7 +281,7 @@ func Test_parseSecretRef(t *testing.T) { name: "empty namespace prefix", secretRef: "/my-secret", defaultNamespace: "default-ns", - expectedNamespace: "", + expectedNamespace: "default-ns", expectedName: "my-secret", }, }