@@ -16,21 +16,19 @@ package cluster
1616
1717import (
1818 "context"
19- "crypto/x509"
2019 "errors"
2120 "fmt"
2221 "time"
2322
24- "github.com/argoproj-labs/argocd-agent/internal/config"
2523 "github.com/argoproj-labs/argocd-agent/internal/event"
26- "github.com/argoproj-labs/argocd-agent/internal/tlsutil"
2724 "github.com/redis/go-redis/v9"
2825 "github.com/redis/go-redis/v9/maintnotifications"
2926 "github.com/sirupsen/logrus"
3027
3128 appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
3229 cacheutil "github.com/argoproj/argo-cd/v3/util/cache"
3330 appstatecache "github.com/argoproj/argo-cd/v3/util/cache/appstate"
31+ "github.com/argoproj/argo-cd/v3/util/db"
3432 v1 "k8s.io/api/core/v1"
3533 apierrors "k8s.io/apimachinery/pkg/api/errors"
3634 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -191,36 +189,35 @@ func NewClusterCacheInstance(redisAddress, redisPassword string, redisCompressio
191189 return clusterCache , nil
192190}
193191
194- // CreateCluster creates a cluster secret for an agent's cluster on the principal.
195- // If sharedClientCertSecretName is provided, it reads the TLS credentials from that secret
196- // (shared client cert mode). Otherwise, it generates a new client certificate signed
197- // by the principal's CA (legacy mode, requires CA private key).
198- func CreateCluster (ctx context.Context , kubeclient kubernetes.Interface , namespace , agentName , resourceProxyAddress , sharedClientCertSecretName string ) error {
199- var clientCert , clientKey , caData string
200- var err error
201-
202- if sharedClientCertSecretName != "" {
203- // Shared client cert mode: read from existing secret
204- log ().WithFields (logrus.Fields {
205- "agent" : agentName ,
206- "secret" : sharedClientCertSecretName ,
207- }).Info ("Using shared client certificate mode for cluster registration" )
208-
209- clientCert , clientKey , caData , err = readSharedClientCertFromSecret (ctx , kubeclient , namespace , sharedClientCertSecretName )
210- if err != nil {
211- return fmt .Errorf ("could not read shared client certificate from secret %s: %v" , sharedClientCertSecretName , err )
212- }
213- } else {
214- // Legacy mode: generate client certificate signed by principal's CA
215- log ().WithField ("agent" , agentName ).Info ("Generating unique client certificate for cluster registration" )
192+ // TokenIssuer is an interface for issuing resource proxy tokens.
193+ // This is a subset of the issuer.Issuer interface to avoid circular dependencies.
194+ type TokenIssuer interface {
195+ IssueResourceProxyToken (agentName string ) (string , error )
196+ }
216197
217- clientCert , clientKey , caData , err = generateAgentClientCert (ctx , kubeclient , namespace , agentName , config .SecretNamePrincipalCA )
218- if err != nil {
219- return fmt .Errorf ("could not generate client certificate: %v" , err )
220- }
198+ // CreateClusterWithBearerToken creates a cluster secret for an agent using both mTLS and JWT bearer token authentication.
199+ // - The shared client certificate (mTLS) proves the request comes from a trusted ArgoCD server
200+ // - The JWT bearer token identifies which specific agent is being accessed
201+ func CreateClusterWithBearerToken (ctx context.Context , kubeclient kubernetes.Interface , namespace , agentName , resourceProxyAddress string , tokenIssuer TokenIssuer , clientCertSecretName string ) error {
202+ logCtx := log ().WithField ("agent" , agentName ).WithField ("process" , "self-registration" )
203+ logCtx .Info ("Creating self-registered cluster secret with shared client cert and bearer token" )
204+
205+ // Generate bearer token for this agent
206+ bearerToken , err := tokenIssuer .IssueResourceProxyToken (agentName )
207+ if err != nil {
208+ logCtx .WithError (err ).Error ("Failed to issue resource proxy token" )
209+ return fmt .Errorf ("could not issue resource proxy token: %v" , err )
210+ }
211+ logCtx .Info ("Successfully issued resource proxy token" )
212+
213+ // Read shared client certificate for mTLS
214+ clientCert , clientKey , caData , err := readClientCertFromSecret (ctx , kubeclient , namespace , clientCertSecretName )
215+ if err != nil {
216+ logCtx .WithError (err ).Error ("Failed to read client certificate from secret" )
217+ return fmt .Errorf ("could not read client certificate from secret %s: %v" , clientCertSecretName , err )
221218 }
219+ logCtx .Info ("Successfully read client certificate from secret" )
222220
223- // Note: this structure has to be same as manual creation done by `argocd-agentctl agent create <agent_name>`
224221 cluster := & appv1.Cluster {
225222 Server : fmt .Sprintf ("https://%s?agentName=%s" , resourceProxyAddress , agentName ),
226223 Name : agentName ,
@@ -229,6 +226,7 @@ func CreateCluster(ctx context.Context, kubeclient kubernetes.Interface, namespa
229226 LabelKeySelfRegisteredCluster : "true" ,
230227 },
231228 Config : appv1.ClusterConfig {
229+ BearerToken : bearerToken ,
232230 TLSClientConfig : appv1.TLSClientConfig {
233231 CertData : []byte (clientCert ),
234232 KeyData : []byte (clientKey ),
@@ -237,97 +235,140 @@ func CreateCluster(ctx context.Context, kubeclient kubernetes.Interface, namespa
237235 },
238236 }
239237
240- // Convert cluster object to Kubernetes secret object
241238 secret := & v1.Secret {
242239 ObjectMeta : metav1.ObjectMeta {
243240 Name : getClusterSecretName (agentName ),
244241 Namespace : namespace ,
245242 },
246243 }
247244 if err := ClusterToSecret (cluster , secret ); err != nil {
245+ logCtx .WithError (err ).Error ("Failed to convert cluster to secret" )
248246 return fmt .Errorf ("could not convert cluster to secret: %v" , err )
249247 }
250248
251- // Create the secret to register the agent's cluster.
252249 if _ , err = kubeclient .CoreV1 ().Secrets (namespace ).Create (ctx , secret , metav1.CreateOptions {}); err != nil {
253250 if apierrors .IsAlreadyExists (err ) {
251+ logCtx .Info ("Cluster secret already exists, skipping creation" )
254252 return nil
255253 }
256- return fmt .Errorf ("could not create cluster secret to register agent's cluster: %v" , err )
254+ logCtx .WithError (err ).Error ("Failed to create cluster secret" )
255+ return fmt .Errorf ("could not create cluster secret: %w" , err )
256+ }
257+
258+ logCtx .Info ("Successfully created cluster secret with shared client cert and bearer token" )
259+ return nil
260+ }
261+
262+ // IsClusterSelfRegistered checks if a cluster secret was created by self-registration.
263+ func IsClusterSelfRegistered (ctx context.Context , kubeclient kubernetes.Interface , namespace , agentName string ) (bool , error ) {
264+ secret , err := kubeclient .CoreV1 ().Secrets (namespace ).Get (ctx , getClusterSecretName (agentName ), metav1.GetOptions {})
265+ if err != nil {
266+ return false , err
267+ }
268+ return secret .Labels [LabelKeySelfRegisteredCluster ] == "true" , nil
269+ }
270+
271+ // UpdateClusterBearerToken updates an existing cluster secret with a new bearer token.
272+ // This is used when the signing key rotates and existing tokens become invalid.
273+ func UpdateClusterBearerToken (ctx context.Context , kubeclient kubernetes.Interface , namespace , agentName string , tokenIssuer TokenIssuer ) error {
274+ logCtx := log ().WithField ("agent" , agentName ).WithField ("process" , "self-registration" )
275+ logCtx .Info ("Updating cluster secret bearer token" )
276+
277+ secretName := getClusterSecretName (agentName )
278+
279+ secret , err := kubeclient .CoreV1 ().Secrets (namespace ).Get (ctx , secretName , metav1.GetOptions {})
280+ if err != nil {
281+ logCtx .WithError (err ).Error ("Failed to get cluster secret" )
282+ return fmt .Errorf ("could not get cluster secret: %v" , err )
283+ }
284+
285+ cluster , err := db .SecretToCluster (secret )
286+ if err != nil {
287+ logCtx .WithError (err ).Error ("Failed to parse cluster secret" )
288+ return fmt .Errorf ("could not parse cluster secret: %v" , err )
289+ }
290+
291+ // Generate new bearer token
292+ bearerToken , err := tokenIssuer .IssueResourceProxyToken (agentName )
293+ if err != nil {
294+ logCtx .WithError (err ).Error ("Failed to issue new resource proxy token" )
295+ return fmt .Errorf ("could not issue resource proxy token: %v" , err )
296+ }
297+ logCtx .Info ("Successfully issued new resource proxy token" )
298+
299+ cluster .Config .BearerToken = bearerToken
300+
301+ if err := ClusterToSecret (cluster , secret ); err != nil {
302+ logCtx .WithError (err ).Error ("Failed to convert cluster to secret" )
303+ return fmt .Errorf ("could not convert cluster to secret: %v" , err )
304+ }
305+
306+ if _ , err = kubeclient .CoreV1 ().Secrets (namespace ).Update (ctx , secret , metav1.UpdateOptions {}); err != nil {
307+ logCtx .WithError (err ).Error ("Failed to update cluster secret" )
308+ return fmt .Errorf ("could not update cluster secret: %v" , err )
257309 }
258310
311+ logCtx .Info ("Successfully updated cluster secret bearer token" )
259312 return nil
260313}
261314
262- // readSharedClientCertFromSecret reads TLS credentials from an existing Kubernetes TLS secret.
315+ // readClientCertFromSecret reads TLS credentials from an existing Kubernetes TLS secret.
263316// The secret should contain tls.crt, tls.key, and ca.crt keys.
264- func readSharedClientCertFromSecret (ctx context.Context , kubeclient kubernetes.Interface , namespace , sharedClientCertSecretName string ) (clientCert , clientKey , caData string , err error ) {
265- secret , err := kubeclient .CoreV1 ().Secrets (namespace ).Get (ctx , sharedClientCertSecretName , metav1.GetOptions {})
317+ func readClientCertFromSecret (ctx context.Context , kubeclient kubernetes.Interface , namespace , secretName string ) (clientCert , clientKey , caData string , err error ) {
318+ secret , err := kubeclient .CoreV1 ().Secrets (namespace ).Get (ctx , secretName , metav1.GetOptions {})
266319 if err != nil {
267- return "" , "" , "" , fmt .Errorf ("could not read TLS secret %s/%s: %v " , namespace , sharedClientCertSecretName , err )
320+ return "" , "" , "" , fmt .Errorf ("could not read TLS secret %s/%s: %w " , namespace , secretName , err )
268321 }
269322
270323 certData , ok := secret .Data ["tls.crt" ]
271324 if ! ok {
272- return "" , "" , "" , fmt .Errorf ("secret %s/%s missing tls.crt" , namespace , sharedClientCertSecretName )
325+ return "" , "" , "" , fmt .Errorf ("secret %s/%s missing tls.crt" , namespace , secretName )
273326 }
274327
275328 keyData , ok := secret .Data ["tls.key" ]
276329 if ! ok {
277- return "" , "" , "" , fmt .Errorf ("secret %s/%s missing tls.key" , namespace , sharedClientCertSecretName )
330+ return "" , "" , "" , fmt .Errorf ("secret %s/%s missing tls.key" , namespace , secretName )
278331 }
279332
280333 // CA cert can be in ca.crt (cert-manager style) or tls.crt of a separate CA secret
281334 caBytes , ok := secret .Data ["ca.crt" ]
282335 if ! ok {
283- return "" , "" , "" , fmt .Errorf ("secret %s/%s missing ca.crt" , namespace , sharedClientCertSecretName )
336+ return "" , "" , "" , fmt .Errorf ("secret %s/%s missing ca.crt" , namespace , secretName )
284337 }
285338
286339 return string (certData ), string (keyData ), string (caBytes ), nil
287340}
288341
289- // ClusterSecretExists checks if a cluster secret exists for the given agent.
290- func ClusterSecretExists (ctx context.Context , kubeclient kubernetes.Interface , namespace , agentName string ) (bool , error ) {
291- if _ , err := kubeclient .CoreV1 ().Secrets (namespace ).Get (ctx , getClusterSecretName (agentName ), metav1.GetOptions {}); err != nil {
292- if apierrors .IsNotFound (err ) {
293- return false , nil
294- }
295- return false , err
296- }
297- return true , nil
298- }
299-
300- func generateAgentClientCert (ctx context.Context , kubeclient kubernetes.Interface , namespace , agentName , caSecretName string ) (clientCert , clientKey , caData string , err error ) {
342+ // GetClusterBearerToken retrieves the bearer token from an existing cluster secret.
343+ func GetClusterBearerToken (ctx context.Context , kubeclient kubernetes.Interface , namespace , agentName string ) (string , error ) {
344+ logCtx := log ().WithField ("agent" , agentName ).WithField ("process" , "self-registration" )
345+ logCtx .Info ("Retrieving bearer token from cluster secret" )
301346
302- // Read the CA certificate from the principal's CA secret
303- tlsCert , err := tlsutil . TLSCertFromSecret ( ctx , kubeclient , namespace , caSecretName )
347+ secretName := getClusterSecretName ( agentName )
348+ secret , err := kubeclient . CoreV1 (). Secrets ( namespace ). Get ( ctx , secretName , metav1. GetOptions {} )
304349 if err != nil {
305- err = fmt . Errorf ( "could not read CA secret: %v" , err )
306- return
350+ logCtx . WithError ( err ). Info ( "Failed to get cluster secret" )
351+ return "" , fmt . Errorf ( "could not get cluster secret: %v" , err )
307352 }
308353
309- // Parse CA certificate from PEM format
310- signerCert , err := x509 .ParseCertificate (tlsCert .Certificate [0 ])
354+ cluster , err := db .SecretToCluster (secret )
311355 if err != nil {
312- err = fmt . Errorf ( "could not parse CA certificate: %v" , err )
313- return
356+ logCtx . WithError ( err ). Error ( "Failed to parse cluster secret" )
357+ return "" , fmt . Errorf ( "could not parse cluster secret: %v" , err )
314358 }
315359
316- // Generate a client cert with agent name as CN and sign it with the CA's cert and key
317- clientCert , clientKey , err = tlsutil .GenerateClientCertificate (agentName , signerCert , tlsCert .PrivateKey )
318- if err != nil {
319- err = fmt .Errorf ("could not create client cert: %v" , err )
320- return
321- }
360+ return cluster .Config .BearerToken , nil
361+ }
322362
323- // Convert CA certificate to PEM format
324- caData , err = tlsutil .CertDataToPEM (tlsCert .Certificate [0 ])
325- if err != nil {
326- err = fmt .Errorf ("could not convert CA certificate to PEM format: %v" , err )
327- return
363+ // ClusterSecretExists checks if a cluster secret exists for the given agent.
364+ func ClusterSecretExists (ctx context.Context , kubeclient kubernetes.Interface , namespace , agentName string ) (bool , error ) {
365+ if _ , err := kubeclient .CoreV1 ().Secrets (namespace ).Get (ctx , getClusterSecretName (agentName ), metav1.GetOptions {}); err != nil {
366+ if apierrors .IsNotFound (err ) {
367+ return false , nil
368+ }
369+ return false , err
328370 }
329-
330- return
371+ return true , nil
331372}
332373
333374func getClusterSecretName (agentName string ) string {
0 commit comments