Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Enforce NonAdminBackup spec field values #1584

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
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
9 changes: 9 additions & 0 deletions api/v1alpha1/oadp_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,15 @@ type NonAdmin struct {
// Enables non admin feature, by default is disabled
// +optional
Enable *bool `json:"enable,omitempty"`

// which bakup spec field values to enforce
// +optional
EnforceBackupSpec *velero.BackupSpec `json:"enforceBackupSpec,omitempty"`

// which restore spec field values to enforce
// +optional
// EnforceRestoreSpecs *velero.RestoreSpec `json:"enforceRestoreSpecs,omitempty"`
// TODO not allow enforcing backupName!!!!
}

// DataMover defines the various config for DPA data mover
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions bundle/manifests/oadp-operator.clusterserviceversion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,12 @@ spec:
spec:
clusterPermissions:
- rules:
- apiGroups:
- oadp.openshift.io
resources:
- dataprotectionapplications
verbs:
- list
- apiGroups:
- oadp.openshift.io
resources:
Expand Down
449 changes: 449 additions & 0 deletions bundle/manifests/oadp.openshift.io_dataprotectionapplications.yaml

Large diffs are not rendered by default.

449 changes: 449 additions & 0 deletions config/crd/bases/oadp.openshift.io_dataprotectionapplications.yaml

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions config/non-admin-controller_rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ kind: ClusterRole
metadata:
name: non-admin-controller-role
rules:
- apiGroups:
- oadp.openshift.io
resources:
- dataprotectionapplications
verbs:
- list
- apiGroups:
- oadp.openshift.io
resources:
Expand Down
33 changes: 29 additions & 4 deletions controllers/nonadmin_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package controllers
import (
"fmt"
"os"
"reflect"

"github.com/go-logr/logr"
velero "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"golang.org/x/exp/maps"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
Expand All @@ -22,6 +24,8 @@ import (
const (
nonAdminObjectName = "non-admin-controller"
controlPlaneKey = "control-plane"

enforcedBackupSpecKey = "enforced-backup-spec"
)

var (
Expand All @@ -36,6 +40,9 @@ var (
"app.kubernetes.io/name": "deployment",
"app.kubernetes.io/part-of": common.OADPOperator,
}

previousEnforcedBackupSpec *velero.BackupSpec = nil
dpaBackupSpecResourceVersion = ""
)

func (r *DPAReconciler) ReconcileNonAdminController(log logr.Logger) (bool, error) {
Expand Down Expand Up @@ -116,14 +123,13 @@ func (r *DPAReconciler) ReconcileNonAdminController(log logr.Logger) (bool, erro
}

func (r *DPAReconciler) buildNonAdminDeployment(deploymentObject *appsv1.Deployment) error {
dpa := r.dpa
nonAdminImage := r.getNonAdminImage()
imagePullPolicy, err := common.GetImagePullPolicy(dpa.Spec.ImagePullPolicy, nonAdminImage)
imagePullPolicy, err := common.GetImagePullPolicy(r.dpa.Spec.ImagePullPolicy, nonAdminImage)
if err != nil {
r.Log.Error(err, "imagePullPolicy regex failed")
}
ensureRequiredLabels(deploymentObject)
err = ensureRequiredSpecs(deploymentObject, nonAdminImage, imagePullPolicy)
err = ensureRequiredSpecs(deploymentObject, r.dpa, nonAdminImage, imagePullPolicy)
if err != nil {
return err
}
Expand All @@ -143,23 +149,42 @@ func ensureRequiredLabels(deploymentObject *appsv1.Deployment) {
}
}

func ensureRequiredSpecs(deploymentObject *appsv1.Deployment, image string, imagePullPolicy corev1.PullPolicy) error {
func ensureRequiredSpecs(deploymentObject *appsv1.Deployment, dpa *oadpv1alpha1.DataProtectionApplication, image string, imagePullPolicy corev1.PullPolicy) error {
namespaceEnvVar := corev1.EnvVar{
Name: "WATCH_NAMESPACE",
Value: deploymentObject.Namespace,
}
if len(dpaBackupSpecResourceVersion) == 0 || !reflect.DeepEqual(dpa.Spec.NonAdmin.EnforceBackupSpec, previousEnforcedBackupSpec) {
dpaBackupSpecResourceVersion = dpa.GetResourceVersion()
}
previousEnforcedBackupSpec = dpa.Spec.NonAdmin.EnforceBackupSpec
// TODO same thing for restore
enforcedSpecAnnotation := map[string]string{
enforcedBackupSpecKey: dpaBackupSpecResourceVersion,
}

deploymentObject.Spec.Replicas = ptr.To(int32(1))
deploymentObject.Spec.Selector = &metav1.LabelSelector{
MatchLabels: controlPlaneLabel,
}

templateObjectLabels := deploymentObject.Spec.Template.GetLabels()
if templateObjectLabels == nil {
deploymentObject.Spec.Template.SetLabels(controlPlaneLabel)
} else {
templateObjectLabels[controlPlaneKey] = controlPlaneLabel[controlPlaneKey]
deploymentObject.Spec.Template.SetLabels(templateObjectLabels)
}

templateObjectAnnotations := deploymentObject.Spec.Template.GetAnnotations()
if templateObjectAnnotations == nil {
deploymentObject.Spec.Template.SetAnnotations(enforcedSpecAnnotation)
} else {
templateObjectAnnotations[enforcedBackupSpecKey] = enforcedSpecAnnotation[enforcedBackupSpecKey]
// TODO same thing for restore
deploymentObject.Spec.Template.SetAnnotations(templateObjectAnnotations)
}

nonAdminContainerFound := false
if len(deploymentObject.Spec.Template.Spec.Containers) == 0 {
deploymentObject.Spec.Template.Spec.Containers = []corev1.Container{{
Expand Down
83 changes: 81 additions & 2 deletions controllers/nonadmin_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/go-logr/logr"
"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
"github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -241,7 +242,13 @@ var _ = ginkgo.Describe("Test ReconcileNonAdminController function", func() {
})

func TestDPAReconcilerBuildNonAdminDeployment(t *testing.T) {
r := &DPAReconciler{dpa: &oadpv1alpha1.DataProtectionApplication{}}
r := &DPAReconciler{dpa: &oadpv1alpha1.DataProtectionApplication{
Spec: oadpv1alpha1.DataProtectionApplicationSpec{
NonAdmin: &oadpv1alpha1.NonAdmin{
Enable: ptr.To(true),
},
},
}}
t.Setenv("RELATED_IMAGE_NON_ADMIN_CONTROLLER", defaultNonAdminImage)
deployment := createTestDeployment("test-build-deployment")
err := r.buildNonAdminDeployment(deployment)
Expand Down Expand Up @@ -283,7 +290,17 @@ func TestEnsureRequiredLabels(t *testing.T) {

func TestEnsureRequiredSpecs(t *testing.T) {
deployment := createTestDeployment("test-ensure-spec")
err := ensureRequiredSpecs(deployment, defaultNonAdminImage, corev1.PullAlways)
dpa := &oadpv1alpha1.DataProtectionApplication{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "123456789",
},
Spec: oadpv1alpha1.DataProtectionApplicationSpec{
NonAdmin: &oadpv1alpha1.NonAdmin{
Enable: ptr.To(true),
},
},
}
err := ensureRequiredSpecs(deployment, dpa, defaultNonAdminImage, corev1.PullAlways)
if err != nil {
t.Errorf("ensureRequiredSpecs() errored out: %v", err)
}
Expand All @@ -296,6 +313,68 @@ func TestEnsureRequiredSpecs(t *testing.T) {
if deployment.Spec.Template.Spec.Containers[0].Image != defaultNonAdminImage {
t.Errorf("Deployment has wrong Image: %v", deployment.Spec.Template.Spec.Containers[0].Image)
}
if len(deployment.Spec.Template.Annotations[enforcedBackupSpecKey]) == 0 {
t.Errorf("Deployment does not have Annotation")
}
previousEnforcedBackupSpec := deployment.DeepCopy().Spec.Template.Annotations[enforcedBackupSpecKey]
updatedDPA := &oadpv1alpha1.DataProtectionApplication{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "147258369",
},
Spec: oadpv1alpha1.DataProtectionApplicationSpec{
NonAdmin: &oadpv1alpha1.NonAdmin{
Enable: ptr.To(true),
},
},
}
err = ensureRequiredSpecs(deployment, updatedDPA, defaultNonAdminImage, corev1.PullAlways)
if err != nil {
t.Errorf("ensureRequiredSpecs() errored out: %v", err)
}
if previousEnforcedBackupSpec != deployment.Spec.Template.Annotations[enforcedBackupSpecKey] {
t.Errorf("Deployment have different Annotation")
}
updatedDPA = &oadpv1alpha1.DataProtectionApplication{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "987654321",
},
Spec: oadpv1alpha1.DataProtectionApplicationSpec{
NonAdmin: &oadpv1alpha1.NonAdmin{
Enable: ptr.To(true),
EnforceBackupSpec: &v1.BackupSpec{
SnapshotMoveData: ptr.To(false),
},
},
},
}
err = ensureRequiredSpecs(deployment, updatedDPA, defaultNonAdminImage, corev1.PullAlways)
if err != nil {
t.Errorf("ensureRequiredSpecs() errored out: %v", err)
}
if previousEnforcedBackupSpec == deployment.Spec.Template.Annotations[enforcedBackupSpecKey] {
t.Errorf("Deployment does not have different Annotation")
}
previousEnforcedBackupSpec = deployment.DeepCopy().Spec.Template.Annotations[enforcedBackupSpecKey]
updatedDPA = &oadpv1alpha1.DataProtectionApplication{
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "112233445",
},
Spec: oadpv1alpha1.DataProtectionApplicationSpec{
NonAdmin: &oadpv1alpha1.NonAdmin{
Enable: ptr.To(true),
EnforceBackupSpec: &v1.BackupSpec{
SnapshotMoveData: ptr.To(false),
},
},
},
}
err = ensureRequiredSpecs(deployment, updatedDPA, defaultNonAdminImage, corev1.PullAlways)
if err != nil {
t.Errorf("ensureRequiredSpecs() errored out: %v", err)
}
if previousEnforcedBackupSpec != deployment.Spec.Template.Annotations[enforcedBackupSpecKey] {
t.Errorf("Deployment have different Annotation")
}
}

func TestDPAReconcilerCheckNonAdminEnabled(t *testing.T) {
Expand Down
8 changes: 7 additions & 1 deletion controllers/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package controllers
import (
"errors"
"fmt"
"reflect"
"strings"

mapset "github.com/deckarep/golang-set/v2"
Expand Down Expand Up @@ -75,7 +76,12 @@ func (r *DPAReconciler) ValidateDataProtectionCR(log logr.Logger) (bool, error)
// validate non-admin enable and tech-preview-ack
if r.checkNonAdminEnabled() {
if !(dpa.Spec.UnsupportedOverrides[oadpv1alpha1.TechPreviewAck] == TrueVal) {
return false, errors.New("in order to enable/disable the non-admin feature please set dpa.spec.unsupportedOverrides[tech-preview-ack]: 'true'")
return false, errors.New("in order to enable the non-admin feature please set dpa.spec.unsupportedOverrides[tech-preview-ack]: 'true'")
}
if r.dpa.Spec.NonAdmin.EnforceBackupSpec != nil {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added this to avoid headaches with users creating bad configuration

I think this is NAC responsibility, not user. If user can set this, then all NonAdminBackups will fail (or only of a specific namespace)

do you agree with this safety check?

(if yes, other fields should be added here?)

if !reflect.ValueOf(r.dpa.Spec.NonAdmin.EnforceBackupSpec.IncludedNamespaces).IsZero() {
return false, errors.New("admin users can not set DPA spec.nonAdmin.enforceBackupSpecs.includedNamespaces field")
}
}
}

Expand Down
55 changes: 54 additions & 1 deletion controllers/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
"k8s.io/utils/pointer"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"

oadpv1alpha1 "github.com/openshift/oadp-operator/api/v1alpha1"
Expand Down Expand Up @@ -1394,7 +1395,7 @@ func TestDPAReconciler_ValidateDataProtectionCR(t *testing.T) {
},
objects: []client.Object{},
wantErr: true,
messageErr: "in order to enable/disable the non-admin feature please set dpa.spec.unsupportedOverrides[tech-preview-ack]: 'true'",
messageErr: "in order to enable the non-admin feature please set dpa.spec.unsupportedOverrides[tech-preview-ack]: 'true'",
},
{
name: "given invalid DPA CR aws and legacy-aws plugins both specified",
Expand All @@ -1420,6 +1421,58 @@ func TestDPAReconciler_ValidateDataProtectionCR(t *testing.T) {
wantErr: true,
messageErr: "aws and legacy-aws can not be both specified in DPA spec.configuration.velero.defaultPlugins",
},
{
name: "[invalid] DPA CR: spec.nonAdmin.enforceBackupSpec.includedNamespaces set",
dpa: &oadpv1alpha1.DataProtectionApplication{
ObjectMeta: metav1.ObjectMeta{
Name: "test-DPA-CR",
Namespace: "test-ns",
},
Spec: oadpv1alpha1.DataProtectionApplicationSpec{
Configuration: &oadpv1alpha1.ApplicationConfig{
Velero: &oadpv1alpha1.VeleroConfig{
NoDefaultBackupLocation: true,
},
},
BackupImages: ptr.To(false),
NonAdmin: &oadpv1alpha1.NonAdmin{
Enable: ptr.To(true),
EnforceBackupSpec: &v1.BackupSpec{
IncludedNamespaces: []string{"banana"},
},
},
UnsupportedOverrides: map[oadpv1alpha1.UnsupportedImageKey]string{
oadpv1alpha1.TechPreviewAck: "true",
},
},
},
wantErr: true,
messageErr: "admin users can not set DPA spec.nonAdmin.enforceBackupSpecs.includedNamespaces field",
},
{
name: "[valid] DPA CR: spec.nonAdmin.enable true",
dpa: &oadpv1alpha1.DataProtectionApplication{
ObjectMeta: metav1.ObjectMeta{
Name: "test-DPA-CR",
Namespace: "test-ns",
},
Spec: oadpv1alpha1.DataProtectionApplicationSpec{
Configuration: &oadpv1alpha1.ApplicationConfig{
Velero: &oadpv1alpha1.VeleroConfig{
NoDefaultBackupLocation: true,
},
},
BackupImages: ptr.To(false),
NonAdmin: &oadpv1alpha1.NonAdmin{
Enable: ptr.To(true),
},
UnsupportedOverrides: map[oadpv1alpha1.UnsupportedImageKey]string{
oadpv1alpha1.TechPreviewAck: "true",
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
tt.objects = append(tt.objects, tt.dpa)
Expand Down