Skip to content

Commit d326bca

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 kubevirt#3970. Signed-off-by: Arnon Gilboa <[email protected]>
1 parent 1c31f5a commit d326bca

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"
@@ -748,15 +749,59 @@ func (r *DataImportCronReconciler) createImportDataVolume(ctx context.Context, d
748749
return nil
749750
}
750751
}
751-
752752
dv := r.newSourceDataVolume(dataImportCron, dvName)
753+
if allowed, err := r.authorizeCloneDataVolume(dataImportCron, dv); err != nil {
754+
return err
755+
} else if !allowed {
756+
updateDataImportCronCondition(dataImportCron, cdiv1.DataImportCronProgressing, corev1.ConditionFalse,
757+
"Not authorized to create DataVolume", notAuthorized)
758+
return nil
759+
}
753760
if err := r.client.Create(ctx, dv); err != nil && !k8serrors.IsAlreadyExists(err) {
754761
return err
755762
}
756763

757764
return nil
758765
}
759766

767+
func (r *DataImportCronReconciler) authorizeCloneDataVolume(dataImportCron *cdiv1.DataImportCron, dv *cdiv1.DataVolume) (bool, error) {
768+
if !isPvcSource(dataImportCron) {
769+
return true, nil
770+
}
771+
772+
if resp, err := dv.AuthorizeSA(dv.Namespace, dv.Name, r, dataImportCron.Namespace, "default"); err != nil {
773+
return false, err
774+
} else if !resp.Allowed {
775+
r.log.Info("Not authorized to create DataVolume", "cron", dataImportCron.Name, "reason", resp.Reason)
776+
return false, nil
777+
}
778+
779+
return true, nil
780+
}
781+
782+
func (r *DataImportCronReconciler) CreateSar(sar *authorizationv1.SubjectAccessReview) (*authorizationv1.SubjectAccessReview, error) {
783+
if err := r.client.Create(context.TODO(), sar); err != nil {
784+
return nil, err
785+
}
786+
return sar, nil
787+
}
788+
789+
func (r *DataImportCronReconciler) GetNamespace(name string) (*corev1.Namespace, error) {
790+
ns := &corev1.Namespace{}
791+
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: name}, ns); err != nil {
792+
return nil, err
793+
}
794+
return ns, nil
795+
}
796+
797+
func (r *DataImportCronReconciler) GetDataSource(namespace, name string) (*cdiv1.DataSource, error) {
798+
das := &cdiv1.DataSource{}
799+
if err := r.client.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: name}, das); err != nil {
800+
return nil, err
801+
}
802+
return das, nil
803+
}
804+
760805
func (r *DataImportCronReconciler) handleStorageClassChange(ctx context.Context, dataImportCron *cdiv1.DataImportCron, desiredStorageClass string) error {
761806
digest, ok := dataImportCron.Annotations[AnnSourceDesiredDigest]
762807
if !ok {

pkg/controller/dataimportcron-controller_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,8 @@ var _ = Describe("All DataImportCron Tests", func() {
862862
Namespace: pvc.Namespace,
863863
},
864864
}
865-
reconciler = createDataImportCronReconciler(cron, pvc)
865+
reconciler = createDataImportCronReconciler(cron, pvc,
866+
&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "default"}})
866867
_, err := reconciler.Reconcile(context.TODO(), cronReq)
867868
Expect(err).ToNot(HaveOccurred())
868869
})

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
@@ -792,6 +792,46 @@ var _ = Describe("DataImportCron", Serial, func() {
792792
Expect(dataSource.Spec.Source).To(Equal(expectedSource))
793793
})
794794
})
795+
796+
Context("DataImportCron controller authorization when cloning from PVC source", func() {
797+
var sourceDv *cdiv1.DataVolume
798+
799+
createSourceDv := func(dvNamespace string) {
800+
if dvNamespace != ns {
801+
sourceNamespace, err := f.CreateNamespace(dvNamespace, nil)
802+
Expect(err).ToNot(HaveOccurred())
803+
f.AddNamespaceToDelete(sourceNamespace)
804+
dvNamespace = sourceNamespace.Name
805+
}
806+
807+
sourceDv = utils.NewDataVolumeWithHTTPImport("source-pvc", "1Gi", fmt.Sprintf(utils.TinyCoreIsoURL, f.CdiInstallNs))
808+
sourceDv, err = utils.CreateDataVolumeFromDefinition(f.CdiClient, dvNamespace, sourceDv)
809+
Expect(err).ToNot(HaveOccurred())
810+
f.ForceBindPvcIfDvIsWaitForFirstConsumer(sourceDv)
811+
}
812+
813+
It("[test_id:12407] should create DataVolume when default ServiceAccount created the DataImportCron with source PVC in the same namespace", func() {
814+
createSourceDv(ns)
815+
cron := createDataImportCronWithPVCSource(cronName, sourceDv.Namespace, sourceDv.Name)
816+
_, err := f.CdiClient.CdiV1beta1().DataImportCrons(ns).Create(context.TODO(), cron, metav1.CreateOptions{})
817+
Expect(err).ToNot(HaveOccurred())
818+
waitForConditions(corev1.ConditionFalse, corev1.ConditionTrue)
819+
})
820+
821+
It("[test_id:XXXX] should not create DataVolume when default ServiceAccount created the DataImportCron with source PVC in another namespace", func() {
822+
createSourceDv("source-ns")
823+
cron := createDataImportCronWithPVCSource(cronName, sourceDv.Namespace, sourceDv.Name)
824+
_, err := f.CdiClient.CdiV1beta1().DataImportCrons(ns).Create(context.TODO(), cron, metav1.CreateOptions{})
825+
Expect(err).ToNot(HaveOccurred())
826+
waitForConditions(corev1.ConditionFalse, corev1.ConditionFalse)
827+
cron, err = f.CdiClient.CdiV1beta1().DataImportCrons(ns).Get(context.TODO(), cronName, metav1.GetOptions{})
828+
Expect(err).ToNot(HaveOccurred())
829+
cronCond := controller.FindDataImportCronConditionByType(cron, cdiv1.DataImportCronProgressing)
830+
Expect(cronCond).ToNot(BeNil())
831+
Expect(cronCond.ConditionState.Message).To(Equal("Not authorized to create DataVolume"))
832+
Expect(cronCond.ConditionState.Reason).To(Equal("NotAuthorized"))
833+
})
834+
})
795835
})
796836

797837
func getDataVolumeSourceRegistry(f *framework.Framework) (*cdiv1.DataVolumeSourceRegistry, error) {
@@ -845,6 +885,29 @@ func updateDataSource(clientSet *cdiclientset.Clientset, namespace string, dataS
845885
}
846886
}
847887

888+
func createDataImportCronWithPVCSource(name string, pvcNamespace, pvcName string) *cdiv1.DataImportCron {
889+
return &cdiv1.DataImportCron{
890+
ObjectMeta: metav1.ObjectMeta{
891+
Name: name,
892+
},
893+
Spec: cdiv1.DataImportCronSpec{
894+
Template: cdiv1.DataVolume{
895+
Spec: cdiv1.DataVolumeSpec{
896+
Source: &cdiv1.DataVolumeSource{
897+
PVC: &cdiv1.DataVolumeSourcePVC{
898+
Namespace: pvcNamespace,
899+
Name: pvcName,
900+
},
901+
},
902+
Storage: &cdiv1.StorageSpec{},
903+
},
904+
},
905+
Schedule: scheduleOnceAYear,
906+
ManagedDataSource: "datasource-test",
907+
},
908+
}
909+
}
910+
848911
func retryOnceOnErr(f func() error) Assertion {
849912
err := f()
850913
if err != nil {

0 commit comments

Comments
 (0)