Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,37 @@ var _ = Describe("FeatureStore Controller - Feast CronJob", func() {
startingDeadlineSeconds := int64(5)
Expect(cronJob.Spec.StartingDeadlineSeconds).To(Equal(&startingDeadlineSeconds))

// verify CronJob uses a dedicated SA, separate from the feature-server SA
cronJobSAName := services.GetFeastServiceName(resource, services.CronJobFeastType)
deploymentSAName := objMeta.Name
podSpec := cronJob.Spec.JobTemplate.Spec.Template.Spec
Expect(podSpec.ServiceAccountName).To(Equal(cronJobSAName))
Expect(podSpec.ServiceAccountName).NotTo(Equal(deploymentSAName))

// verify the dedicated CronJob SA exists
cronJobSA := &corev1.ServiceAccount{}
err = k8sClient.Get(ctx, types.NamespacedName{
Name: cronJobSAName,
Namespace: objMeta.Namespace,
}, cronJobSA)
Expect(err).NotTo(HaveOccurred())
Expect(controllerutil.HasControllerReference(cronJobSA)).To(BeTrue())

// verify restricted pod security context
Expect(podSpec.SecurityContext).NotTo(BeNil())
Expect(*podSpec.SecurityContext.RunAsNonRoot).To(BeTrue())
Expect(*podSpec.SecurityContext.RunAsUser).To(Equal(int64(1001)))
Expect(podSpec.SecurityContext.SeccompProfile).NotTo(BeNil())
Expect(podSpec.SecurityContext.SeccompProfile.Type).To(Equal(corev1.SeccompProfileTypeRuntimeDefault))

// verify restricted container security context
for _, c := range append(podSpec.InitContainers, podSpec.Containers...) {
Expect(c.SecurityContext).NotTo(BeNil())
Expect(*c.SecurityContext.AllowPrivilegeEscalation).To(BeFalse())
Expect(c.SecurityContext.Capabilities).NotTo(BeNil())
Expect(c.SecurityContext.Capabilities.Drop).To(ContainElement(corev1.Capability("ALL")))
}

checkCronJob(resource.Status.Applied.CronJob, cronJob.Spec)
})

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

// verify CronJob uses a dedicated SA, separate from the feature-server SA
cronJobSAName := services.GetFeastServiceName(resource, services.CronJobFeastType)
podSpec := cronJob.Spec.JobTemplate.Spec.Template.Spec
Expect(podSpec.ServiceAccountName).To(Equal(cronJobSAName))
Expect(podSpec.ServiceAccountName).NotTo(Equal(objMeta.Name))

checkCronJob(resource.Status.Applied.CronJob, cronJob.Spec)
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ var _ = Describe("FeatureStore Controller", func() {
saList := corev1.ServiceAccountList{}
err = k8sClient.List(ctx, &saList, listOpts)
Expect(err).NotTo(HaveOccurred())
Expect(saList.Items).To(HaveLen(1))
Expect(saList.Items).To(HaveLen(2))

svcList := corev1.ServiceList{}
err = k8sClient.List(ctx, &svcList, listOpts)
Expand Down
52 changes: 49 additions & 3 deletions infra/feast-operator/internal/controller/services/cronjob.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import (
)

func (feast *FeastServices) deployCronJob() error {
if err := feast.createCronJobServiceAccount(); err != nil {
return feast.setFeastServiceCondition(err, CronJobFeastType)
}
if err := feast.createCronJobRole(); err != nil {
return feast.setFeastServiceCondition(err, CronJobFeastType)
}
Expand Down Expand Up @@ -146,8 +149,15 @@ func (feast *FeastServices) setCronJob(cronJob *batchv1.CronJob) error {

func (feast *FeastServices) getCronJobPodSpec() corev1.PodSpec {
podSpec := corev1.PodSpec{
ServiceAccountName: feast.initFeastSA().Name,
ServiceAccountName: feast.initCronJobSA().Name,
RestartPolicy: corev1.RestartPolicyNever,
SecurityContext: &corev1.PodSecurityContext{
RunAsNonRoot: boolPtr(true),
RunAsUser: int64Ptr(1001),
SeccompProfile: &corev1.SeccompProfile{
Type: corev1.SeccompProfileTypeRuntimeDefault,
},
},
}
feast.setCronJobContainers(&podSpec)
return podSpec
Expand All @@ -167,7 +177,7 @@ func (feast *FeastServices) setCronJobContainers(podSpec *corev1.PodSpec) {
}

func (feast *FeastServices) getCronJobContainer(containerName, cronJobCmd string) corev1.Container {
return *getContainer(
container := getContainer(
containerName,
"",
[]string{
Expand All @@ -178,6 +188,42 @@ func (feast *FeastServices) getCronJobContainer(containerName, cronJobCmd string
feast.Handler.FeatureStore.Status.Applied.CronJob.ContainerConfigs.ContainerConfigs,
"",
)
container.SecurityContext = &corev1.SecurityContext{
AllowPrivilegeEscalation: boolPtr(false),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
}
return *container
}

func (feast *FeastServices) createCronJobServiceAccount() error {
logger := log.FromContext(feast.Handler.Context)
sa := feast.initCronJobSA()
if op, err := controllerutil.CreateOrUpdate(feast.Handler.Context, feast.Handler.Client, sa, controllerutil.MutateFn(func() error {
return feast.setCronJobServiceAccount(sa)
})); err != nil {
return err
} else if op == controllerutil.OperationResultCreated || op == controllerutil.OperationResultUpdated {
logger.Info("Successfully reconciled", "ServiceAccount", sa.Name, "operation", op)
}
return nil
}

func (feast *FeastServices) initCronJobSA() *corev1.ServiceAccount {
sa := &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: feast.getCronJobRoleName(),
Namespace: feast.Handler.FeatureStore.Namespace,
},
}
sa.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("ServiceAccount"))
return sa
}

func (feast *FeastServices) setCronJobServiceAccount(sa *corev1.ServiceAccount) error {
sa.Labels = feast.getFeastTypeLabels(CronJobFeastType)
return controllerutil.SetControllerReference(feast.Handler.FeatureStore, sa, feast.Handler.Scheme)
}

func (feast *FeastServices) createCronJobRole() error {
Expand Down Expand Up @@ -254,7 +300,7 @@ func (feast *FeastServices) setCronJobRoleBinding(roleBinding *rbacv1.RoleBindin
roleBinding.Labels = feast.getFeastTypeLabels(CronJobFeastType)
roleBinding.Subjects = []rbacv1.Subject{{
Kind: rbacv1.ServiceAccountKind,
Name: feast.initFeastSA().Name,
Name: feast.initCronJobSA().Name,
Namespace: feast.Handler.FeatureStore.Namespace,
}}
roleBinding.RoleRef = rbacv1.RoleRef{
Expand Down
Loading