Skip to content

Commit fba71e2

Browse files
committed
Authorize default SA on DataImportCron PVC source
In case of DataImportCron with PVC source, the controller checks the namespace default ServiceAccount is authorized to clone the source PVC. This is a minimally partial backport of #3970. Signed-off-by: Arnon Gilboa <[email protected]>
1 parent fcc160a commit fba71e2

File tree

6 files changed

+143
-8
lines changed

6 files changed

+143
-8
lines changed

pkg/controller/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ go_library(
4040
"//vendor/github.com/pkg/errors:go_default_library",
4141
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
4242
"//vendor/github.com/robfig/cron/v3:go_default_library",
43+
"//vendor/k8s.io/api/authorization/v1:go_default_library",
4344
"//vendor/k8s.io/api/batch/v1:go_default_library",
4445
"//vendor/k8s.io/api/core/v1:go_default_library",
4546
"//vendor/k8s.io/api/networking/v1:go_default_library",

pkg/controller/dataimportcron-conditions.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@ import (
3030
)
3131

3232
const (
33-
noDigest = "NoDigest"
34-
noImport = "NoImport"
35-
outdated = "Outdated"
36-
scheduled = "ImportScheduled"
37-
inProgress = "ImportProgressing"
38-
upToDate = "UpToDate"
33+
noDigest = "NoDigest"
34+
noImport = "NoImport"
35+
notAuthorized = "NotAuthorized"
36+
outdated = "Outdated"
37+
scheduled = "ImportScheduled"
38+
inProgress = "ImportProgressing"
39+
upToDate = "UpToDate"
3940
)
4041

4142
func updateDataImportCronCondition(cron *cdiv1.DataImportCron, conditionType cdiv1.DataImportCronConditionType, status corev1.ConditionStatus, message, reason string) {

pkg/controller/dataimportcron-controller.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/pkg/errors"
3434
cronexpr "github.com/robfig/cron/v3"
3535

36+
authorizationv1 "k8s.io/api/authorization/v1"
3637
batchv1 "k8s.io/api/batch/v1"
3738
corev1 "k8s.io/api/core/v1"
3839
storagev1 "k8s.io/api/storage/v1"
@@ -889,15 +890,59 @@ func (r *DataImportCronReconciler) createImportDataVolume(ctx context.Context, d
889890
return nil
890891
}
891892
}
892-
893893
dv := r.newSourceDataVolume(dataImportCron, dvName)
894+
if allowed, err := r.authorizeCloneDataVolume(dataImportCron, dv); err != nil {
895+
return err
896+
} else if !allowed {
897+
updateDataImportCronCondition(dataImportCron, cdiv1.DataImportCronProgressing, corev1.ConditionFalse,
898+
"Not authorized to create DataVolume", notAuthorized)
899+
return nil
900+
}
894901
if err := r.client.Create(ctx, dv); err != nil && !k8serrors.IsAlreadyExists(err) {
895902
return err
896903
}
897904

898905
return nil
899906
}
900907

908+
func (r *DataImportCronReconciler) authorizeCloneDataVolume(dataImportCron *cdiv1.DataImportCron, dv *cdiv1.DataVolume) (bool, error) {
909+
if !isPvcSource(dataImportCron) {
910+
return true, nil
911+
}
912+
913+
if resp, err := dv.AuthorizeSA(dv.Namespace, dv.Name, r, dataImportCron.Namespace, "default"); err != nil {
914+
return false, err
915+
} else if !resp.Allowed {
916+
r.log.Info("Not authorized to create DataVolume", "cron", dataImportCron.Name, "reason", resp.Reason)
917+
return false, nil
918+
}
919+
920+
return true, nil
921+
}
922+
923+
func (r *DataImportCronReconciler) CreateSar(sar *authorizationv1.SubjectAccessReview) (*authorizationv1.SubjectAccessReview, error) {
924+
if err := r.client.Create(context.TODO(), sar); err != nil {
925+
return nil, err
926+
}
927+
return sar, nil
928+
}
929+
930+
func (r *DataImportCronReconciler) GetNamespace(name string) (*corev1.Namespace, error) {
931+
ns := &corev1.Namespace{}
932+
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: name}, ns); err != nil {
933+
return nil, err
934+
}
935+
return ns, nil
936+
}
937+
938+
func (r *DataImportCronReconciler) GetDataSource(namespace, name string) (*cdiv1.DataSource, error) {
939+
das := &cdiv1.DataSource{}
940+
if err := r.client.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: name}, das); err != nil {
941+
return nil, err
942+
}
943+
return das, nil
944+
}
945+
901946
func (r *DataImportCronReconciler) handleStorageClassChange(ctx context.Context, dataImportCron *cdiv1.DataImportCron, desiredStorageClass string) error {
902947
digest, ok := dataImportCron.Annotations[AnnSourceDesiredDigest]
903948
if !ok {

pkg/controller/dataimportcron-controller_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -920,7 +920,8 @@ var _ = Describe("All DataImportCron Tests", func() {
920920
Namespace: pvc.Namespace,
921921
},
922922
}
923-
reconciler = createDataImportCronReconciler(cron, pvc)
923+
reconciler = createDataImportCronReconciler(cron, pvc,
924+
&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "default"}})
924925
_, err := reconciler.Reconcile(context.TODO(), cronReq)
925926
Expect(err).ToNot(HaveOccurred())
926927
})

pkg/operator/resources/cluster/controller.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,19 @@ func getControllerClusterPolicyRules() []rbacv1.PolicyRule {
114114
"delete",
115115
},
116116
},
117+
{
118+
APIGroups: []string{
119+
"",
120+
},
121+
Resources: []string{
122+
"namespaces",
123+
},
124+
Verbs: []string{
125+
"get",
126+
"list",
127+
"watch",
128+
},
129+
},
117130
{
118131
APIGroups: []string{
119132
"",
@@ -268,6 +281,17 @@ func getControllerClusterPolicyRules() []rbacv1.PolicyRule {
268281
"update",
269282
},
270283
},
284+
{
285+
APIGroups: []string{
286+
"authorization.k8s.io",
287+
},
288+
Resources: []string{
289+
"subjectaccessreviews",
290+
},
291+
Verbs: []string{
292+
"create",
293+
},
294+
},
271295
}
272296
}
273297

tests/dataimportcron_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,46 @@ var _ = Describe("DataImportCron", Serial, func() {
822822
Expect(dataSource.Spec.Source).To(Equal(expectedSource))
823823
})
824824
})
825+
826+
Context("DataImportCron controller authorization when cloning from PVC source", func() {
827+
var sourceDv *cdiv1.DataVolume
828+
829+
createSourceDv := func(dvNamespace string) {
830+
if dvNamespace != ns {
831+
sourceNamespace, err := f.CreateNamespace(dvNamespace, nil)
832+
Expect(err).ToNot(HaveOccurred())
833+
f.AddNamespaceToDelete(sourceNamespace)
834+
dvNamespace = sourceNamespace.Name
835+
}
836+
837+
sourceDv = utils.NewDataVolumeWithHTTPImport("source-pvc", "1Gi", fmt.Sprintf(utils.TinyCoreIsoURL, f.CdiInstallNs))
838+
sourceDv, err = utils.CreateDataVolumeFromDefinition(f.CdiClient, dvNamespace, sourceDv)
839+
Expect(err).ToNot(HaveOccurred())
840+
f.ForceBindPvcIfDvIsWaitForFirstConsumer(sourceDv)
841+
}
842+
843+
It("[test_id:12407] should create DataVolume when default ServiceAccount created the DataImportCron with source PVC in the same namespace", func() {
844+
createSourceDv(ns)
845+
cron := createDataImportCronWithPVCSource(cronName, sourceDv.Namespace, sourceDv.Name)
846+
_, err := f.CdiClient.CdiV1beta1().DataImportCrons(ns).Create(context.TODO(), cron, metav1.CreateOptions{})
847+
Expect(err).ToNot(HaveOccurred())
848+
waitForConditions(corev1.ConditionFalse, corev1.ConditionTrue)
849+
})
850+
851+
It("[test_id:XXXX] should not create DataVolume when default ServiceAccount created the DataImportCron with source PVC in another namespace", func() {
852+
createSourceDv("source-ns")
853+
cron := createDataImportCronWithPVCSource(cronName, sourceDv.Namespace, sourceDv.Name)
854+
_, err := f.CdiClient.CdiV1beta1().DataImportCrons(ns).Create(context.TODO(), cron, metav1.CreateOptions{})
855+
Expect(err).ToNot(HaveOccurred())
856+
waitForConditions(corev1.ConditionFalse, corev1.ConditionFalse)
857+
cron, err = f.CdiClient.CdiV1beta1().DataImportCrons(ns).Get(context.TODO(), cronName, metav1.GetOptions{})
858+
Expect(err).ToNot(HaveOccurred())
859+
cronCond := controller.FindDataImportCronConditionByType(cron, cdiv1.DataImportCronProgressing)
860+
Expect(cronCond).ToNot(BeNil())
861+
Expect(cronCond.ConditionState.Message).To(Equal("Not authorized to create DataVolume"))
862+
Expect(cronCond.ConditionState.Reason).To(Equal("NotAuthorized"))
863+
})
864+
})
825865
})
826866

827867
func getDataVolumeSourceRegistry(f *framework.Framework) (*cdiv1.DataVolumeSourceRegistry, error) {
@@ -875,6 +915,29 @@ func updateDataSource(clientSet *cdiclientset.Clientset, namespace string, dataS
875915
}
876916
}
877917

918+
func createDataImportCronWithPVCSource(name string, pvcNamespace, pvcName string) *cdiv1.DataImportCron {
919+
return &cdiv1.DataImportCron{
920+
ObjectMeta: metav1.ObjectMeta{
921+
Name: name,
922+
},
923+
Spec: cdiv1.DataImportCronSpec{
924+
Template: cdiv1.DataVolume{
925+
Spec: cdiv1.DataVolumeSpec{
926+
Source: &cdiv1.DataVolumeSource{
927+
PVC: &cdiv1.DataVolumeSourcePVC{
928+
Namespace: pvcNamespace,
929+
Name: pvcName,
930+
},
931+
},
932+
Storage: &cdiv1.StorageSpec{},
933+
},
934+
},
935+
Schedule: scheduleOnceAYear,
936+
ManagedDataSource: "datasource-test",
937+
},
938+
}
939+
}
940+
878941
func retryOnceOnErr(f func() error) Assertion {
879942
err := f()
880943
if err != nil {

0 commit comments

Comments
 (0)