Skip to content

Commit

Permalink
Do not use existing auto-generated tokens
Browse files Browse the repository at this point in the history
Kubernetes legacy ServiceAccount tokens will generate warnings on use
and be garbage collected if not used for one year, starting with
Kubernetes 1.30.

Use an explicitly created token secret with a fixed name, based on the
service account name.

Signed-off-by: Max Gautier <[email protected]>
  • Loading branch information
VannTen committed Sep 18, 2024
1 parent e6f71cc commit d94dc7e
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 56 deletions.
67 changes: 16 additions & 51 deletions util/clusterauth/clusterauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,69 +249,34 @@ func GetServiceAccountBearerToken(clientset kubernetes.Interface, ns string, sa
return string(secret.Data["token"]), nil
}

// getOrCreateServiceAccountTokenSecret will check if a ServiceAccount
// already has a kubernetes.io/service-account-token secret associated
// with it or creates one if the ServiceAccount doesn't have one. This
// was added to help add k8s v1.24+ clusters.
// getOrCreateServiceAccountTokenSecret will create a
// kubernetes.io/service-account-token secret associated with a
// ServiceAccount named '<service account name>-long-lived-token', or
// use the existing one with that name.
// This was added to help add k8s v1.24+ clusters.
func getOrCreateServiceAccountTokenSecret(clientset kubernetes.Interface, sa, ns string) (string, error) {
// Wait for sa to have secret, but don't wait too
// long for 1.24+ clusters
var serviceAccount *corev1.ServiceAccount
err := wait.PollUntilContextTimeout(context.Background(), 500*time.Millisecond, 30*time.Second, true, func(ctx context.Context) (bool, error) {
ctx, cancel := context.WithTimeout(ctx, common.ClusterAuthRequestTimeout)
defer cancel()
var getErr error
serviceAccount, getErr = clientset.CoreV1().ServiceAccounts(ns).Get(ctx, sa, metav1.GetOptions{})
if getErr != nil {
return false, fmt.Errorf("failed to get serviceaccount %q: %w", sa, getErr)
}
return true, nil
})
if err != nil && !wait.Interrupted(err) {
return "", fmt.Errorf("failed to get serviceaccount token secret: %w", err)
}
if serviceAccount == nil {
log.Errorf("Unexpected nil serviceaccount '%s/%s' with no error returned", ns, sa)
return "", fmt.Errorf("failed to create serviceaccount token secret: nil serviceaccount returned for '%s/%s' with no error", ns, sa)
}

outerCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
for _, s := range serviceAccount.Secrets {
innerCtx, cancel := context.WithTimeout(outerCtx, common.ClusterAuthRequestTimeout)
defer cancel()
existingSecret, err := clientset.CoreV1().Secrets(ns).Get(innerCtx, s.Name, metav1.GetOptions{})
if err != nil {
return "", fmt.Errorf("failed to retrieve secret %q: %w", s.Name, err)
}
if existingSecret.Type == corev1.SecretTypeServiceAccountToken {
return existingSecret.Name, nil
}
}

return createServiceAccountToken(clientset, serviceAccount)
}

func createServiceAccountToken(clientset kubernetes.Interface, serviceAccount *corev1.ServiceAccount) (string, error) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
GenerateName: serviceAccount.Name + "-token-",
Namespace: serviceAccount.Namespace,
Name: sa + "-long-lived-token",
Namespace: ns,
Annotations: map[string]string{
corev1.ServiceAccountNameKey: serviceAccount.Name,
corev1.ServiceAccountNameKey: sa,
},
},
Type: corev1.SecretTypeServiceAccountToken,
}

ctx, cancel := context.WithTimeout(context.Background(), common.ClusterAuthRequestTimeout)
defer cancel()
secret, err := clientset.CoreV1().Secrets(serviceAccount.Namespace).Create(ctx, secret, metav1.CreateOptions{})
if err != nil {
return "", fmt.Errorf("failed to create secret for serviceaccount %q: %w", serviceAccount.Name, err)
}
_, err := clientset.CoreV1().Secrets(ns).Create(ctx, secret, metav1.CreateOptions{})

log.Infof("Created bearer token secret for ServiceAccount %q", serviceAccount.Name)
if apierr.IsAlreadyExists(err) {
log.Infof("Using existing bearer token secret %q for ServiceAccount %q", secret.Name, sa)
} else if err != nil {
return "", fmt.Errorf("failed to create secret %q for serviceaccount %q: %w", secret.Name, sa, err)
} else {
log.Infof("Created bearer token secret %q for ServiceAccount %q", secret.Name, sa)
}

return secret.Name, nil
}
Expand Down
4 changes: 2 additions & 2 deletions util/clusterauth/clusterauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ func Test_getOrCreateServiceAccountTokenSecret_NoSecretForSA(t *testing.T) {

got, err := getOrCreateServiceAccountTokenSecret(cs, ArgoCDManagerServiceAccount, ns.Name)
require.NoError(t, err)
assert.Contains(t, got, "argocd-manager-token-")
assert.Equal(t, ArgoCDManagerServiceAccount+"-long-lived-token", got)

obj, err := cs.Tracker().Get(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"},
ns.Name, ArgoCDManagerServiceAccount)
Expand Down Expand Up @@ -331,7 +331,7 @@ func Test_getOrCreateServiceAccountTokenSecret_SAHasSecret(t *testing.T) {

got, err := getOrCreateServiceAccountTokenSecret(cs, ArgoCDManagerServiceAccount, ns.Name)
require.NoError(t, err)
assert.Equal(t, "sa-secret", got)
assert.Equal(t, ArgoCDManagerServiceAccount+"-long-lived-token", got)

obj, err := cs.Tracker().Get(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"},
ns.Name, ArgoCDManagerServiceAccount)
Expand Down
5 changes: 2 additions & 3 deletions util/clusterauth/testdata/argocd-manager-sa-token.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ metadata:
kubernetes.io/service-account.name: argocd-manager
kubernetes.io/service-account.uid: 91dd37cf-8d92-11e9-a091-d65f2ae7fa8d
creationTimestamp: "2019-06-13T04:30:24Z"
generateName: argocd-manager-token-
name: argocd-manager-token-tj79r
name: argocd-manager-long-lived-token
namespace: kube-system
resourceVersion: "133010"
selfLink: /api/v1/namespaces/kube-system/secrets/argocd-manager-token-tj79r
uid: f657d67e-8d93-11e9-a091-d65f2ae7fa8d
type: kubernetes.io/service-account-token
type: kubernetes.io/service-account-token

0 comments on commit d94dc7e

Please sign in to comment.