Skip to content

Commit 6827ce3

Browse files
authored
Merge pull request #4285 from faiq/faiq/resolve-eks-userdata-secrets
fix: resolve secrets when generating eks userdata
2 parents 9060eaa + c529470 commit 6827ce3

File tree

2 files changed

+115
-1
lines changed

2 files changed

+115
-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"
@@ -145,6 +146,41 @@ func (r *EKSConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
145146
return r.joinWorker(ctx, cluster, config, configOwner)
146147
}
147148

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

@@ -191,6 +227,12 @@ func (r *EKSConfigReconciler) joinWorker(ctx context.Context, cluster *clusterv1
191227
}
192228

193229
log.Info("Generating userdata")
230+
files, err := r.resolveFiles(ctx, config)
231+
if err != nil {
232+
log.Info("Failed to resolve files for user data")
233+
conditions.MarkFalse(config, eksbootstrapv1.DataSecretAvailableCondition, eksbootstrapv1.DataSecretGenerationFailedReason, clusterv1.ConditionSeverityWarning, err.Error())
234+
return ctrl.Result{}, err
235+
}
194236

195237
nodeInput := &userdata.NodeInput{
196238
// AWSManagedControlPlane webhooks default and validate EKSClusterName
@@ -208,7 +250,7 @@ func (r *EKSConfigReconciler) joinWorker(ctx context.Context, cluster *clusterv1
208250
Users: config.Spec.Users,
209251
DiskSetup: config.Spec.DiskSetup,
210252
Mounts: config.Spec.Mounts,
211-
Files: config.Spec.Files,
253+
Files: files,
212254
}
213255
if config.Spec.PauseContainer != nil {
214256
nodeInput.PauseContainerAccount = &config.Spec.PauseContainer.AccountNumber

bootstrap/eks/controllers/eksconfig_controller_reconciler_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,78 @@ func TestEKSConfigReconciler(t *testing.T) {
203203
gomega.Expect(string(secret.Data["value"])).To(Not(Equal(string(expectedUserData))))
204204
}).Should(Succeed())
205205
})
206+
t.Run("Should Reconcile an EKSConfig with a secret file reference", func(t *testing.T) {
207+
g := NewWithT(t)
208+
amcp := newAMCP("test-cluster")
209+
//nolint: gosec // these are not credentials
210+
secretPath := "/etc/secret.txt"
211+
secretContent := "secretValue"
212+
cluster := newCluster(amcp.Name)
213+
machine := newMachine(cluster, "test-machine")
214+
config := newEKSConfig(machine)
215+
config.Spec.Files = append(config.Spec.Files, eksbootstrapv1.File{
216+
ContentFrom: &eksbootstrapv1.FileSource{
217+
Secret: eksbootstrapv1.SecretFileSource{
218+
Name: "my-secret",
219+
Key: "secretKey",
220+
},
221+
},
222+
Path: secretPath,
223+
})
224+
secret := &corev1.Secret{
225+
ObjectMeta: metav1.ObjectMeta{
226+
Namespace: "default",
227+
Name: "my-secret",
228+
},
229+
Data: map[string][]byte{
230+
"secretKey": []byte(secretContent),
231+
},
232+
}
233+
t.Logf(dump("amcp", amcp))
234+
t.Logf(dump("config", config))
235+
t.Logf(dump("machine", machine))
236+
t.Logf(dump("cluster", cluster))
237+
t.Logf(dump("secret", secret))
238+
g.Expect(testEnv.Client.Create(ctx, secret)).To(Succeed())
239+
g.Expect(testEnv.Client.Create(ctx, amcp)).To(Succeed())
240+
241+
// create a userData with the secret content and check if reconile.joinWorker
242+
// resolves the userdata properly
243+
expectedUserData, err := userdata.NewNode(&userdata.NodeInput{
244+
ClusterName: amcp.Name,
245+
Files: []eksbootstrapv1.File{
246+
{
247+
Content: secretContent,
248+
Path: secretPath,
249+
},
250+
},
251+
KubeletExtraArgs: map[string]string{
252+
"test-arg": "test-value",
253+
},
254+
})
255+
g.Expect(err).To(BeNil())
256+
reconciler := EKSConfigReconciler{
257+
Client: testEnv.Client,
258+
}
259+
t.Logf(fmt.Sprintf("Calling reconcile on cluster '%s' and config '%s' should requeue", cluster.Name, config.Name))
260+
g.Eventually(func(gomega Gomega) {
261+
result, err := reconciler.joinWorker(ctx, cluster, config, configOwner("Machine"))
262+
gomega.Expect(err).NotTo(HaveOccurred())
263+
gomega.Expect(result.Requeue).To(BeFalse())
264+
}).Should(Succeed())
265+
266+
secretList := &corev1.SecretList{}
267+
testEnv.Client.List(ctx, secretList)
268+
t.Logf(dump("secrets", secretList))
269+
gotSecret := &corev1.Secret{}
270+
g.Eventually(func(gomega Gomega) {
271+
gomega.Expect(testEnv.Client.Get(ctx, client.ObjectKey{
272+
Name: config.Name,
273+
Namespace: "default",
274+
}, gotSecret)).To(Succeed())
275+
}).Should(Succeed())
276+
g.Expect(string(gotSecret.Data["value"])).To(Equal(string(expectedUserData)))
277+
})
206278
}
207279

208280
// newCluster return a CAPI cluster object.

0 commit comments

Comments
 (0)