Skip to content

Commit 14b6f7f

Browse files
committed
Creates token secret for service account
This patch changes the default behavior of waiting for the generation of a default secret for the service account and propagate that over to the guest cluster. Instead, alongwith the service account a secret of type "kubernetes.io/service-account-token" is created to host the token for the service account. The controller waits for the realization of this secret and then propagates this data over to the guest cluster. This is required since 1.24 disables the auto generation of a default token secret for every service account created. Signed-off-by: Sagar Muchhal <[email protected]>
1 parent 3f63bbc commit 14b6f7f

File tree

3 files changed

+106
-53
lines changed

3 files changed

+106
-53
lines changed

controllers/serviceaccount_controller.go

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,10 @@ func AddServiceAccountProviderControllerToManager(ctx *context.ControllerManager
8989
&source.Kind{Type: &vmwarev1.ProviderServiceAccount{}},
9090
handler.EnqueueRequestsFromMapFunc(r.providerServiceAccountToVSphereCluster),
9191
).
92+
// Watches the secrets to re-enqueue once the service account token is set
9293
Watches(
93-
&source.Kind{Type: &corev1.ServiceAccount{}},
94-
handler.EnqueueRequestsFromMapFunc(r.serviceAccountToVSphereCluster),
94+
&source.Kind{Type: &corev1.Secret{}},
95+
handler.EnqueueRequestsFromMapFunc(r.secretToVSphereCluster),
9596
).
9697
Complete(r)
9798
}
@@ -228,18 +229,22 @@ func (r ServiceAccountReconciler) ensureProviderServiceAccounts(ctx *vmwareconte
228229
if err := r.ensureServiceAccountConfigMap(ctx.ClusterContext, pSvcAccount); err != nil {
229230
return errors.Wrapf(err, "unable to sync configmap for provider serviceaccount %s", pSvcAccount.Name)
230231
}
232+
// 3. Create secret of Service account token type for the service account
233+
if err := r.ensureServiceAccountSecret(ctx.ClusterContext, pSvcAccount); err != nil {
234+
return errors.Wrapf(err, "unable to create provider serviceaccount secret %s", getServiceAccountSecretName(pSvcAccount))
235+
}
231236

232-
// 3. Create the associated role for the service account
237+
// 4. Create the associated role for the service account
233238
if err := r.ensureRole(ctx.ClusterContext, pSvcAccount); err != nil {
234239
return errors.Wrapf(err, "unable to create role for provider serviceaccount %s", pSvcAccount.Name)
235240
}
236241

237-
// 4. Create the associated roleBinding for the service account
242+
// 5. Create the associated roleBinding for the service account
238243
if err := r.ensureRoleBinding(ctx.ClusterContext, pSvcAccount); err != nil {
239244
return errors.Wrapf(err, "unable to create rolebinding for provider serviceaccount %s", pSvcAccount.Name)
240245
}
241246

242-
// 5. Sync the service account with the target
247+
// 6. Sync the service account with the target
243248
if err := r.syncServiceAccountSecret(ctx, pSvcAccount); err != nil {
244249
return errors.Wrapf(err, "unable to sync secret for provider serviceaccount %s", pSvcAccount.Name)
245250
}
@@ -269,6 +274,34 @@ func (r ServiceAccountReconciler) ensureServiceAccount(ctx *vmwarecontext.Cluste
269274
return nil
270275
}
271276

277+
func (r ServiceAccountReconciler) ensureServiceAccountSecret(ctx *vmwarecontext.ClusterContext, pSvcAccount vmwarev1.ProviderServiceAccount) error {
278+
secret := corev1.Secret{
279+
Type: corev1.SecretTypeServiceAccountToken,
280+
ObjectMeta: metav1.ObjectMeta{
281+
Name: getServiceAccountSecretName(pSvcAccount),
282+
Namespace: pSvcAccount.Namespace,
283+
Annotations: map[string]string{
284+
// denotes that this secret holds the token for the service account
285+
corev1.ServiceAccountNameKey: getServiceAccountName(pSvcAccount),
286+
},
287+
},
288+
}
289+
290+
logger := ctx.Logger.WithValues("providerserviceaccount", pSvcAccount.Name, "secret", secret.Name)
291+
err := controllerutil.SetControllerReference(&pSvcAccount, &secret, ctx.Scheme)
292+
if err != nil {
293+
return err
294+
}
295+
logger.V(4).Info("Creating service account secret")
296+
err = ctx.Client.Create(ctx, &secret)
297+
if err != nil && !apierrors.IsAlreadyExists(err) {
298+
// Note: We skip updating the service account because the token controller updates the service account with a
299+
// secret and we don't want to overwrite it with an empty secret.
300+
return err
301+
}
302+
return nil
303+
}
304+
272305
func (r ServiceAccountReconciler) ensureRole(ctx *vmwarecontext.ClusterContext, pSvcAccount vmwarev1.ProviderServiceAccount) error {
273306
role := rbacv1.Role{
274307
ObjectMeta: metav1.ObjectMeta{
@@ -323,29 +356,23 @@ func (r ServiceAccountReconciler) ensureRoleBinding(ctx *vmwarecontext.ClusterCo
323356

324357
func (r ServiceAccountReconciler) syncServiceAccountSecret(ctx *vmwarecontext.GuestClusterContext, pSvcAccount vmwarev1.ProviderServiceAccount) error {
325358
logger := ctx.Logger.WithValues("providerserviceaccount", pSvcAccount.Name)
326-
logger.V(4).Info("Attempting to sync secret for provider service account")
327-
var svcAccount corev1.ServiceAccount
328-
err := ctx.Client.Get(ctx, types.NamespacedName{Name: getServiceAccountName(pSvcAccount), Namespace: pSvcAccount.Namespace}, &svcAccount)
359+
logger.V(4).Info("Attempting to sync token secret for provider service account")
360+
361+
secretName := getServiceAccountSecretName(pSvcAccount)
362+
logger.V(4).Info("Fetching secret for service account token details", "secret", secretName)
363+
var svcAccountTokenSecret corev1.Secret
364+
err := ctx.Client.Get(ctx, types.NamespacedName{Name: secretName, Namespace: pSvcAccount.Namespace}, &svcAccountTokenSecret)
329365
if err != nil {
330366
return err
331367
}
332-
// Check if token secret exists
333-
if len(svcAccount.Secrets) == 0 {
334-
// Note: We don't have to requeue here because we have a watch on the service account and the cluster should be reconciled
335-
// when a secret is added to the service account by the token controller.
336-
logger.Info("Skipping sync secret for provider service account: serviceaccount has no secrets", "serviceaccount", svcAccount.Name)
368+
// Check if token data exists
369+
if len(svcAccountTokenSecret.Data) == 0 {
370+
// Note: We don't have to requeue here because we have a watch on the secret and the cluster should be reconciled
371+
// when a secret has token data populated.
372+
logger.Info("Skipping sync secret for provider service account: secret has no data", "secret", secretName)
337373
return nil
338374
}
339375

340-
// Choose the default secret
341-
secretRef := svcAccount.Secrets[0]
342-
logger.V(4).Info("Fetching secret for provider service account", "secret", secretRef.Name)
343-
var sourceSecret corev1.Secret
344-
err = ctx.Client.Get(ctx, types.NamespacedName{Name: secretRef.Name, Namespace: svcAccount.Namespace}, &sourceSecret)
345-
if err != nil {
346-
return err
347-
}
348-
349376
// Create the target namespace if it is not existing
350377
targetNamespace := &corev1.Namespace{
351378
ObjectMeta: metav1.ObjectMeta{
@@ -372,7 +399,7 @@ func (r ServiceAccountReconciler) syncServiceAccountSecret(ctx *vmwarecontext.Gu
372399
}
373400
logger.V(4).Info("Creating or updating secret in cluster", "namespace", targetSecret.Namespace, "name", targetSecret.Name)
374401
_, err = controllerutil.CreateOrPatch(ctx, ctx.GuestClient, targetSecret, func() error {
375-
targetSecret.Data = sourceSecret.Data
402+
targetSecret.Data = svcAccountTokenSecret.Data
376403
return nil
377404
})
378405
return err
@@ -468,6 +495,10 @@ func getServiceAccountName(pSvcAccount vmwarev1.ProviderServiceAccount) string {
468495
return pSvcAccount.Name
469496
}
470497

498+
func getServiceAccountSecretName(pSvcAccount vmwarev1.ProviderServiceAccount) string {
499+
return fmt.Sprintf("%s-secret", pSvcAccount.Name)
500+
}
501+
471502
func getSystemServiceAccountFullName(pSvcAccount vmwarev1.ProviderServiceAccount) string {
472503
return fmt.Sprintf("%s.%s.%s", systemServiceAccountPrefix, getServiceAccountNamespace(pSvcAccount), getServiceAccountName(pSvcAccount))
473504
}
@@ -484,6 +515,33 @@ func GetCMNamespaceName() types.NamespacedName {
484515
}
485516
}
486517

518+
// secretToVSphereCluster is a mapper function used to enqueue reconcile.Request objects.
519+
// It accepts a Secret object owned by the controller and fetches the service account
520+
// that contains the token and creates a reconcile.Request for the vmwarev1.VSphereCluster object.
521+
func (r ServiceAccountReconciler) secretToVSphereCluster(o client.Object) []reconcile.Request {
522+
secret, ok := o.(*corev1.Secret)
523+
if !ok {
524+
return nil
525+
}
526+
527+
ownerRef := metav1.GetControllerOf(secret)
528+
if ownerRef != nil && ownerRef.Kind == kindProviderServiceAccount {
529+
if !metav1.HasAnnotation(secret.ObjectMeta, corev1.ServiceAccountNameKey) {
530+
return nil
531+
}
532+
svcAccountName := secret.GetAnnotations()[corev1.ServiceAccountNameKey]
533+
svcAccount := &corev1.ServiceAccount{}
534+
if err := r.Client.Get(r.Context, client.ObjectKey{
535+
Namespace: secret.Namespace,
536+
Name: svcAccountName,
537+
}, svcAccount); err != nil {
538+
return nil
539+
}
540+
return r.serviceAccountToVSphereCluster(svcAccount)
541+
}
542+
return nil
543+
}
544+
487545
// serviceAccountToVSphereCluster is a mapper function used to enqueue reconcile.Request objects.
488546
// From the watched object owned by this controller, it creates reconcile.Request object
489547
// for the vmwarev1.VSphereCluster object that owns the watched object.

controllers/serviceaccount_controller_suite_test.go

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

1919
import (
2020
goctx "context"
21+
"fmt"
2122
"strings"
2223
"time"
2324

@@ -35,13 +36,10 @@ import (
3536
)
3637

3738
const (
38-
testProviderSvcAccountName = "test-pvcsi"
39-
40-
testTargetNS = "test-pvcsi-system"
41-
testTargetSecret = "test-pvcsi-secret" // nolint:gosec
42-
testSvcAccountSecretName = testProviderSvcAccountName + "-token-abcdef"
43-
testSystemSvcAcctNs = "test-system-svc-acct-namespace"
44-
testSystemSvcAcctCM = "test-system-svc-acct-cm"
39+
testTargetNS = "test-pvcsi-system"
40+
testTargetSecret = "test-pvcsi-secret" //nolint:gosec
41+
testSystemSvcAcctNs = "test-system-svc-acct-namespace"
42+
testSystemSvcAcctCM = "test-system-svc-acct-cm"
4543

4644
testSecretToken = "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklp" // nolint:gosec
4745
)
@@ -94,15 +92,14 @@ func assertNoEntities(ctx goctx.Context, ctrlClient client.Client, namespace str
9492
func assertServiceAccountAndUpdateSecret(ctx goctx.Context, ctrlClient client.Client, namespace, name string) {
9593
svcAccount := &corev1.ServiceAccount{}
9694
assertEventuallyExistsInNamespace(ctx, ctrlClient, namespace, name, svcAccount)
97-
// Update the service account with a prototype secret
98-
secret := getTestSvcAccountSecret(namespace, testSvcAccountSecretName)
99-
Expect(ctrlClient.Create(ctx, secret)).To(Succeed())
100-
svcAccount.Secrets = []corev1.ObjectReference{
101-
{
102-
Name: testSvcAccountSecretName,
103-
},
95+
secret := &corev1.Secret{}
96+
assertEventuallyExistsInNamespace(ctx, ctrlClient, namespace, fmt.Sprintf("%s-secret", name), secret)
97+
98+
// Update the data on the secret
99+
secret.Data = map[string][]byte{
100+
"token": []byte(testSecretToken),
104101
}
105-
Expect(ctrlClient.Update(ctx, svcAccount)).To(Succeed())
102+
Expect(ctrlClient.Update(ctx, secret)).To(Succeed())
106103
}
107104

108105
func assertTargetSecret(ctx goctx.Context, guestClient client.Client, namespace, name string) { // nolint
@@ -159,9 +156,8 @@ func assertRoleBinding(_ *helpers.UnitTestContextForController, ctrlClient clien
159156
}))
160157
}
161158

162-
// nolint
163-
func assertProviderServiceAccountsCondition(vCluster *vmwarev1.VSphereCluster, status corev1.ConditionStatus,
164-
message string, reason string, severity clusterv1.ConditionSeverity) {
159+
// assertProviderServiceAccountsCondition asserts the condition on the ProviderServiceAccount CR.
160+
func assertProviderServiceAccountsCondition(vCluster *vmwarev1.VSphereCluster, status corev1.ConditionStatus, message string, reason string, severity clusterv1.ConditionSeverity) { //nolint
165161
c := conditions.Get(vCluster, vmwarev1.ProviderServiceAccountsReadyCondition)
166162
Expect(c).NotTo(BeNil())
167163
Expect(c.Status).To(Equal(status))
@@ -246,18 +242,6 @@ func getSystemServiceAccountsConfigMap(namespace, name string) *corev1.ConfigMap
246242
}
247243
}
248244

249-
func getTestSvcAccountSecret(namespace, name string) *corev1.Secret {
250-
return &corev1.Secret{
251-
ObjectMeta: metav1.ObjectMeta{
252-
Name: name,
253-
Namespace: namespace,
254-
},
255-
Data: map[string][]byte{
256-
"token": []byte(testSecretToken),
257-
},
258-
}
259-
}
260-
261245
func getTestRoleWithGetPod(namespace, name string) *rbacv1.Role {
262246
return &rbacv1.Role{
263247
ObjectMeta: metav1.ObjectMeta{

controllers/serviceaccount_controller_unit_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package controllers
1818

1919
import (
20+
"fmt"
2021
"os"
2122

2223
. "github.com/onsi/ginkgo"
@@ -79,6 +80,16 @@ func unitTestsReconcileNormal() {
7980
getTestProviderServiceAccount(namespace, vsphereCluster, false),
8081
}
8182
})
83+
It("should create a service account and a secret", func() {
84+
_, err := reconciler.ReconcileNormal(ctx.GuestClusterContext)
85+
Expect(err).NotTo(HaveOccurred())
86+
87+
svcAccount := &corev1.ServiceAccount{}
88+
assertEventuallyExistsInNamespace(ctx, ctx.Client, namespace, vsphereCluster.GetName(), svcAccount)
89+
90+
secret := &corev1.Secret{}
91+
assertEventuallyExistsInNamespace(ctx, ctx.Client, namespace, fmt.Sprintf("%s-secret", vsphereCluster.GetName()), secret)
92+
})
8293
Context("When serviceaccount secret is created", func() {
8394
It("Should reconcile", func() {
8495
assertTargetNamespace(ctx, ctx.GuestClient, testTargetNS, false)

0 commit comments

Comments
 (0)