Skip to content

Commit f9d9800

Browse files
authored
Merge pull request #4288 from faiq/faiq/backport-4285
fix: resolve secrets when generating eks userdata (backport #4285)
2 parents 28bc9b8 + a5062ee commit f9d9800

File tree

2 files changed

+165
-1
lines changed

2 files changed

+165
-1
lines changed

bootstrap/eks/controllers/eksconfig_controller.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
apierrors "k8s.io/apimachinery/pkg/api/errors"
2727
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2828
"k8s.io/apimachinery/pkg/runtime"
29+
"k8s.io/apimachinery/pkg/types"
2930
"k8s.io/klog/v2"
3031
"k8s.io/utils/pointer"
3132
ctrl "sigs.k8s.io/controller-runtime"
@@ -142,6 +143,41 @@ func (r *EKSConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
142143
return r.joinWorker(ctx, cluster, config)
143144
}
144145

146+
func (r *EKSConfigReconciler) resolveFiles(ctx context.Context, cfg *eksbootstrapv1.EKSConfig) ([]eksbootstrapv1.File, error) {
147+
collected := make([]eksbootstrapv1.File, 0, len(cfg.Spec.Files))
148+
149+
for i := range cfg.Spec.Files {
150+
in := cfg.Spec.Files[i]
151+
if in.ContentFrom != nil {
152+
data, err := r.resolveSecretFileContent(ctx, cfg.Namespace, in)
153+
if err != nil {
154+
return nil, errors.Wrapf(err, "failed to resolve file source")
155+
}
156+
in.ContentFrom = nil
157+
in.Content = string(data)
158+
}
159+
collected = append(collected, in)
160+
}
161+
162+
return collected, nil
163+
}
164+
165+
func (r *EKSConfigReconciler) resolveSecretFileContent(ctx context.Context, ns string, source eksbootstrapv1.File) ([]byte, error) {
166+
secret := &corev1.Secret{}
167+
key := types.NamespacedName{Namespace: ns, Name: source.ContentFrom.Secret.Name}
168+
if err := r.Client.Get(ctx, key, secret); err != nil {
169+
if apierrors.IsNotFound(err) {
170+
return nil, errors.Wrapf(err, "secret not found: %s", key)
171+
}
172+
return nil, errors.Wrapf(err, "failed to retrieve Secret %q", key)
173+
}
174+
data, ok := secret.Data[source.ContentFrom.Secret.Key]
175+
if !ok {
176+
return nil, errors.Errorf("secret references non-existent secret key: %q", source.ContentFrom.Secret.Key)
177+
}
178+
return data, nil
179+
}
180+
145181
func (r *EKSConfigReconciler) joinWorker(ctx context.Context, cluster *clusterv1.Cluster, config *eksbootstrapv1.EKSConfig) (ctrl.Result, error) {
146182
log := logger.FromContext(ctx)
147183

@@ -187,6 +223,12 @@ func (r *EKSConfigReconciler) joinWorker(ctx context.Context, cluster *clusterv1
187223
}
188224

189225
log.Info("Generating userdata")
226+
files, err := r.resolveFiles(ctx, config)
227+
if err != nil {
228+
log.Info("Failed to resolve files for user data")
229+
conditions.MarkFalse(config, eksbootstrapv1.DataSecretAvailableCondition, eksbootstrapv1.DataSecretGenerationFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
230+
return ctrl.Result{}, err
231+
}
190232

191233
nodeInput := &userdata.NodeInput{
192234
// AWSManagedControlPlane webhooks default and validate EKSClusterName
@@ -204,7 +246,7 @@ func (r *EKSConfigReconciler) joinWorker(ctx context.Context, cluster *clusterv1
204246
Users: config.Spec.Users,
205247
DiskSetup: config.Spec.DiskSetup,
206248
Mounts: config.Spec.Mounts,
207-
Files: config.Spec.Files,
249+
Files: files,
208250
}
209251
if config.Spec.PauseContainer != nil {
210252
nodeInput.PauseContainerAccount = &config.Spec.PauseContainer.AccountNumber

bootstrap/eks/controllers/eksconfig_controller_reconciler_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,128 @@ func TestEKSConfigReconciler(t *testing.T) {
141141
gomega.Expect(string(secret.Data["value"])).To(Equal(string(expectedUserData)))
142142
}).Should(Succeed())
143143
})
144+
t.Run("Should reconcile an EKSConfig and not update data if secret exists and config owner is Machine kind", func(t *testing.T) {
145+
g := NewWithT(t)
146+
amcp := newAMCP("test-cluster")
147+
cluster := newCluster(amcp.Name)
148+
machine := newMachine(cluster, "test-machine")
149+
config := newEKSConfig(machine)
150+
t.Logf(dump("amcp", amcp))
151+
t.Logf(dump("config", config))
152+
t.Logf(dump("machine", machine))
153+
t.Logf(dump("cluster", cluster))
154+
expectedUserData, err := newUserData(cluster.Name, map[string]string{"test-arg": "test-value"})
155+
g.Expect(err).To(BeNil())
156+
g.Expect(testEnv.Client.Create(ctx, amcp)).To(Succeed())
157+
158+
secret := &corev1.Secret{
159+
ObjectMeta: metav1.ObjectMeta{
160+
Namespace: "default",
161+
Name: machine.Name,
162+
},
163+
}
164+
g.Expect(testEnv.Client.Create(ctx, secret)).To(Succeed())
165+
166+
amcpList := &ekscontrolplanev1.AWSManagedControlPlaneList{}
167+
testEnv.Client.List(ctx, amcpList)
168+
t.Logf(dump("stored-amcps", amcpList))
169+
170+
reconciler := EKSConfigReconciler{
171+
Client: testEnv.Client,
172+
}
173+
t.Logf(fmt.Sprintf("Calling reconcile on cluster '%s' and config '%s' should requeue", cluster.Name, config.Name))
174+
g.Eventually(func(gomega Gomega) {
175+
result, err := reconciler.joinWorker(ctx, cluster, config, configOwner("Machine"))
176+
gomega.Expect(err).NotTo(HaveOccurred())
177+
gomega.Expect(result.Requeue).To(BeFalse())
178+
}).Should(Succeed())
179+
180+
t.Logf(fmt.Sprintf("Secret '%s' should exist and be out of date", config.Name))
181+
secretList := &corev1.SecretList{}
182+
testEnv.Client.List(ctx, secretList)
183+
t.Logf(dump("secrets", secretList))
184+
185+
secret = &corev1.Secret{}
186+
g.Eventually(func(gomega Gomega) {
187+
gomega.Expect(testEnv.Client.Get(ctx, client.ObjectKey{
188+
Name: config.Name,
189+
Namespace: "default",
190+
}, secret)).To(Succeed())
191+
gomega.Expect(string(secret.Data["value"])).To(Not(Equal(string(expectedUserData))))
192+
}).Should(Succeed())
193+
})
194+
t.Run("Should Reconcile an EKSConfig with a secret file reference", func(t *testing.T) {
195+
g := NewWithT(t)
196+
amcp := newAMCP("test-cluster")
197+
//nolint: gosec // these are not credentials
198+
secretPath := "/etc/secret.txt"
199+
secretContent := "secretValue"
200+
cluster := newCluster(amcp.Name)
201+
machine := newMachine(cluster, "test-machine")
202+
config := newEKSConfig(machine)
203+
config.Spec.Files = append(config.Spec.Files, eksbootstrapv1.File{
204+
ContentFrom: &eksbootstrapv1.FileSource{
205+
Secret: eksbootstrapv1.SecretFileSource{
206+
Name: "my-secret",
207+
Key: "secretKey",
208+
},
209+
},
210+
Path: secretPath,
211+
})
212+
secret := &corev1.Secret{
213+
ObjectMeta: metav1.ObjectMeta{
214+
Namespace: "default",
215+
Name: "my-secret",
216+
},
217+
Data: map[string][]byte{
218+
"secretKey": []byte(secretContent),
219+
},
220+
}
221+
t.Logf(dump("amcp", amcp))
222+
t.Logf(dump("config", config))
223+
t.Logf(dump("machine", machine))
224+
t.Logf(dump("cluster", cluster))
225+
t.Logf(dump("secret", secret))
226+
g.Expect(testEnv.Client.Create(ctx, secret)).To(Succeed())
227+
g.Expect(testEnv.Client.Create(ctx, amcp)).To(Succeed())
228+
229+
// create a userData with the secret content and check if reconile.joinWorker
230+
// resolves the userdata properly
231+
expectedUserData, err := userdata.NewNode(&userdata.NodeInput{
232+
ClusterName: amcp.Name,
233+
Files: []eksbootstrapv1.File{
234+
{
235+
Content: secretContent,
236+
Path: secretPath,
237+
},
238+
},
239+
KubeletExtraArgs: map[string]string{
240+
"test-arg": "test-value",
241+
},
242+
})
243+
g.Expect(err).To(BeNil())
244+
reconciler := EKSConfigReconciler{
245+
Client: testEnv.Client,
246+
}
247+
t.Logf(fmt.Sprintf("Calling reconcile on cluster '%s' and config '%s' should requeue", cluster.Name, config.Name))
248+
g.Eventually(func(gomega Gomega) {
249+
result, err := reconciler.joinWorker(ctx, cluster, config, configOwner("Machine"))
250+
gomega.Expect(err).NotTo(HaveOccurred())
251+
gomega.Expect(result.Requeue).To(BeFalse())
252+
}).Should(Succeed())
253+
254+
secretList := &corev1.SecretList{}
255+
testEnv.Client.List(ctx, secretList)
256+
t.Logf(dump("secrets", secretList))
257+
gotSecret := &corev1.Secret{}
258+
g.Eventually(func(gomega Gomega) {
259+
gomega.Expect(testEnv.Client.Get(ctx, client.ObjectKey{
260+
Name: config.Name,
261+
Namespace: "default",
262+
}, gotSecret)).To(Succeed())
263+
}).Should(Succeed())
264+
g.Expect(string(gotSecret.Data["value"])).To(Equal(string(expectedUserData)))
265+
})
144266
}
145267

146268
// newCluster return a CAPI cluster object.

0 commit comments

Comments
 (0)