Skip to content

Commit b8e32ad

Browse files
fix: Separate CronJob and feature-server ServiceAccounts
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent a9219d9 commit b8e32ad

3 files changed

Lines changed: 87 additions & 4 deletions

File tree

infra/feast-operator/internal/controller/featurestore_controller_cronjob_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,37 @@ var _ = Describe("FeatureStore Controller - Feast CronJob", func() {
124124
startingDeadlineSeconds := int64(5)
125125
Expect(cronJob.Spec.StartingDeadlineSeconds).To(Equal(&startingDeadlineSeconds))
126126

127+
// verify CronJob uses a dedicated SA, separate from the feature-server SA
128+
cronJobSAName := services.GetFeastServiceName(resource, services.CronJobFeastType)
129+
deploymentSAName := objMeta.Name
130+
podSpec := cronJob.Spec.JobTemplate.Spec.Template.Spec
131+
Expect(podSpec.ServiceAccountName).To(Equal(cronJobSAName))
132+
Expect(podSpec.ServiceAccountName).NotTo(Equal(deploymentSAName))
133+
134+
// verify the dedicated CronJob SA exists
135+
cronJobSA := &corev1.ServiceAccount{}
136+
err = k8sClient.Get(ctx, types.NamespacedName{
137+
Name: cronJobSAName,
138+
Namespace: objMeta.Namespace,
139+
}, cronJobSA)
140+
Expect(err).NotTo(HaveOccurred())
141+
Expect(controllerutil.HasControllerReference(cronJobSA)).To(BeTrue())
142+
143+
// verify restricted pod security context
144+
Expect(podSpec.SecurityContext).NotTo(BeNil())
145+
Expect(*podSpec.SecurityContext.RunAsNonRoot).To(BeTrue())
146+
Expect(*podSpec.SecurityContext.RunAsUser).To(Equal(int64(1001)))
147+
Expect(podSpec.SecurityContext.SeccompProfile).NotTo(BeNil())
148+
Expect(podSpec.SecurityContext.SeccompProfile.Type).To(Equal(corev1.SeccompProfileTypeRuntimeDefault))
149+
150+
// verify restricted container security context
151+
for _, c := range append(podSpec.InitContainers, podSpec.Containers...) {
152+
Expect(c.SecurityContext).NotTo(BeNil())
153+
Expect(*c.SecurityContext.AllowPrivilegeEscalation).To(BeFalse())
154+
Expect(c.SecurityContext.Capabilities).NotTo(BeNil())
155+
Expect(c.SecurityContext.Capabilities.Drop).To(ContainElement(corev1.Capability("ALL")))
156+
}
157+
127158
checkCronJob(resource.Status.Applied.CronJob, cronJob.Spec)
128159
})
129160

@@ -244,6 +275,12 @@ var _ = Describe("FeatureStore Controller - Feast CronJob", func() {
244275
Expect(cronJob.Spec.StartingDeadlineSeconds).To(Equal(&startingDeadlineSeconds))
245276
Expect(cronJob.Spec.JobTemplate.Spec.Parallelism).To(Equal(&int32Var))
246277

278+
// verify CronJob uses a dedicated SA, separate from the feature-server SA
279+
cronJobSAName := services.GetFeastServiceName(resource, services.CronJobFeastType)
280+
podSpec := cronJob.Spec.JobTemplate.Spec.Template.Spec
281+
Expect(podSpec.ServiceAccountName).To(Equal(cronJobSAName))
282+
Expect(podSpec.ServiceAccountName).NotTo(Equal(objMeta.Name))
283+
247284
checkCronJob(resource.Status.Applied.CronJob, cronJob.Spec)
248285
})
249286
})

infra/feast-operator/internal/controller/featurestore_controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ var _ = Describe("FeatureStore Controller", func() {
745745
saList := corev1.ServiceAccountList{}
746746
err = k8sClient.List(ctx, &saList, listOpts)
747747
Expect(err).NotTo(HaveOccurred())
748-
Expect(saList.Items).To(HaveLen(1))
748+
Expect(saList.Items).To(HaveLen(2))
749749

750750
svcList := corev1.ServiceList{}
751751
err = k8sClient.List(ctx, &svcList, listOpts)

infra/feast-operator/internal/controller/services/cronjob.go

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import (
1616
)
1717

1818
func (feast *FeastServices) deployCronJob() error {
19+
if err := feast.createCronJobServiceAccount(); err != nil {
20+
return feast.setFeastServiceCondition(err, CronJobFeastType)
21+
}
1922
if err := feast.createCronJobRole(); err != nil {
2023
return feast.setFeastServiceCondition(err, CronJobFeastType)
2124
}
@@ -146,8 +149,15 @@ func (feast *FeastServices) setCronJob(cronJob *batchv1.CronJob) error {
146149

147150
func (feast *FeastServices) getCronJobPodSpec() corev1.PodSpec {
148151
podSpec := corev1.PodSpec{
149-
ServiceAccountName: feast.initFeastSA().Name,
152+
ServiceAccountName: feast.initCronJobSA().Name,
150153
RestartPolicy: corev1.RestartPolicyNever,
154+
SecurityContext: &corev1.PodSecurityContext{
155+
RunAsNonRoot: boolPtr(true),
156+
RunAsUser: int64Ptr(1001),
157+
SeccompProfile: &corev1.SeccompProfile{
158+
Type: corev1.SeccompProfileTypeRuntimeDefault,
159+
},
160+
},
151161
}
152162
feast.setCronJobContainers(&podSpec)
153163
return podSpec
@@ -167,7 +177,7 @@ func (feast *FeastServices) setCronJobContainers(podSpec *corev1.PodSpec) {
167177
}
168178

169179
func (feast *FeastServices) getCronJobContainer(containerName, cronJobCmd string) corev1.Container {
170-
return *getContainer(
180+
container := getContainer(
171181
containerName,
172182
"",
173183
[]string{
@@ -178,6 +188,42 @@ func (feast *FeastServices) getCronJobContainer(containerName, cronJobCmd string
178188
feast.Handler.FeatureStore.Status.Applied.CronJob.ContainerConfigs.ContainerConfigs,
179189
"",
180190
)
191+
container.SecurityContext = &corev1.SecurityContext{
192+
AllowPrivilegeEscalation: boolPtr(false),
193+
Capabilities: &corev1.Capabilities{
194+
Drop: []corev1.Capability{"ALL"},
195+
},
196+
}
197+
return *container
198+
}
199+
200+
func (feast *FeastServices) createCronJobServiceAccount() error {
201+
logger := log.FromContext(feast.Handler.Context)
202+
sa := feast.initCronJobSA()
203+
if op, err := controllerutil.CreateOrUpdate(feast.Handler.Context, feast.Handler.Client, sa, controllerutil.MutateFn(func() error {
204+
return feast.setCronJobServiceAccount(sa)
205+
})); err != nil {
206+
return err
207+
} else if op == controllerutil.OperationResultCreated || op == controllerutil.OperationResultUpdated {
208+
logger.Info("Successfully reconciled", "ServiceAccount", sa.Name, "operation", op)
209+
}
210+
return nil
211+
}
212+
213+
func (feast *FeastServices) initCronJobSA() *corev1.ServiceAccount {
214+
sa := &corev1.ServiceAccount{
215+
ObjectMeta: metav1.ObjectMeta{
216+
Name: feast.getCronJobRoleName(),
217+
Namespace: feast.Handler.FeatureStore.Namespace,
218+
},
219+
}
220+
sa.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("ServiceAccount"))
221+
return sa
222+
}
223+
224+
func (feast *FeastServices) setCronJobServiceAccount(sa *corev1.ServiceAccount) error {
225+
sa.Labels = feast.getFeastTypeLabels(CronJobFeastType)
226+
return controllerutil.SetControllerReference(feast.Handler.FeatureStore, sa, feast.Handler.Scheme)
181227
}
182228

183229
func (feast *FeastServices) createCronJobRole() error {
@@ -254,7 +300,7 @@ func (feast *FeastServices) setCronJobRoleBinding(roleBinding *rbacv1.RoleBindin
254300
roleBinding.Labels = feast.getFeastTypeLabels(CronJobFeastType)
255301
roleBinding.Subjects = []rbacv1.Subject{{
256302
Kind: rbacv1.ServiceAccountKind,
257-
Name: feast.initFeastSA().Name,
303+
Name: feast.initCronJobSA().Name,
258304
Namespace: feast.Handler.FeatureStore.Namespace,
259305
}}
260306
roleBinding.RoleRef = rbacv1.RoleRef{

0 commit comments

Comments
 (0)