From 5e1af357f86ee9213e12b41f966b40a003ec6410 Mon Sep 17 00:00:00 2001 From: Lukas Piwowarski Date: Tue, 27 Aug 2024 07:19:49 -0400 Subject: [PATCH 1/2] Disable AllowPrivilegeEscalation This PR ensures that test pods do not run with allowPrivilegeEscalation. In order to be able to run pods without the privilege escalation flag, the following changes had to be made: - Removal of mounting of the localtime and machine-id files (required hostPath). These files are not required for successfull functioning of the test-operator. - Removal of the CAP_AUDIT_WRITE flag. - Dropping of of all capabilities by default. However, for certain set of test-operator functionalities the parameters mentioned above are required to be enabled. Because of that we are introducing insecure flag that can be turnd on whenever functionality ,for example like extraRPMs, is required. --- .../test.openstack.org_ansibletests.yaml | 19 +++++++++ .../test.openstack.org_horizontests.yaml | 10 +++++ api/bases/test.openstack.org_tempests.yaml | 10 +++++ api/bases/test.openstack.org_tobikoes.yaml | 19 +++++++++ api/v1beta1/ansibletest_types.go | 4 ++ api/v1beta1/ansibletest_webhook.go | 11 ++++- api/v1beta1/common.go | 27 ++++++++++++ api/v1beta1/common_webhook.go | 21 ++++++++++ api/v1beta1/horizontest_types.go | 3 +- api/v1beta1/horizontest_webhook.go | 11 ++++- api/v1beta1/tempest_types.go | 2 + api/v1beta1/tempest_webhook.go | 36 +++++++++++++++- api/v1beta1/tobiko_types.go | 4 ++ api/v1beta1/tobiko_webhook.go | 15 ++++++- api/v1beta1/zz_generated.deepcopy.go | 41 +++++++++++++++++++ .../test.openstack.org_ansibletests.yaml | 19 +++++++++ .../test.openstack.org_horizontests.yaml | 10 +++++ .../bases/test.openstack.org_tempests.yaml | 10 +++++ .../bases/test.openstack.org_tobikoes.yaml | 19 +++++++++ controllers/ansibletest_controller.go | 2 + controllers/tobiko_controller.go | 2 + pkg/ansibletest/job.go | 24 +++++------ pkg/ansibletest/volumes.go | 26 ------------ pkg/horizontest/job.go | 23 +++++------ pkg/horizontest/volumes.go | 26 ------------ pkg/tempest/job.go | 21 ++++------ pkg/tempest/volumes.go | 26 ------------ pkg/tobiko/job.go | 24 +++++------ pkg/tobiko/volumes.go | 26 ------------ pkg/util/common.go | 39 ++++++++++++++++++ 30 files changed, 365 insertions(+), 165 deletions(-) create mode 100644 api/v1beta1/common_webhook.go create mode 100644 pkg/util/common.go diff --git a/api/bases/test.openstack.org_ansibletests.yaml b/api/bases/test.openstack.org_ansibletests.yaml index 981796cd..ed0da618 100644 --- a/api/bases/test.openstack.org_ansibletests.yaml +++ b/api/bases/test.openstack.org_ansibletests.yaml @@ -125,6 +125,16 @@ spec: description: OpenStackConfigSecret is the name of the Secret containing the secure.yaml type: string + privileged: + default: false + description: 'Use with caution! This parameter specifies whether test-operator + should spawn test pods with allowedPrivilegedEscalation: true and + the default capabilities on top of capabilities that are usually + needed by the test pods (NET_ADMIN, NET_RAW). This parameter is + deemed insecure but it is needed for certain test-operator functionalities + to work properly (e.g.: extraRPMs in Tempest CR, or certain set + of tobiko tests).' + type: boolean storageClass: default: local-storage description: StorageClass used to create PVCs that store the logs @@ -203,6 +213,15 @@ spec: description: OpenStackConfigSecret is the name of the Secret containing the secure.yaml type: string + privileged: + description: 'Use with caution! This parameter specifies whether + test-operator should spawn test pods with allowedPrivilegedEscalation: + true and the default capabilities on top of capabilities that + are usually needed by the test pods (NET_ADMIN, NET_RAW). + This parameter is deemed insecure but it is needed for certain + test-operator functionalities to work properly (e.g.: extraRPMs + in Tempest CR, or certain set of tobiko tests).' + type: boolean stepName: description: Name of a workflow step. The step name will be used for example to create a logs directory. diff --git a/api/bases/test.openstack.org_horizontests.yaml b/api/bases/test.openstack.org_horizontests.yaml index 80fdda05..03b95d7b 100644 --- a/api/bases/test.openstack.org_horizontests.yaml +++ b/api/bases/test.openstack.org_horizontests.yaml @@ -106,6 +106,16 @@ spec: description: Password is the password for the user running the Horizon tests. type: string + privileged: + default: false + description: 'Use with caution! This parameter specifies whether test-operator + should spawn test pods with allowedPrivilegedEscalation: true and + the default capabilities on top of capabilities that are usually + needed by the test pods (NET_ADMIN, NET_RAW). This parameter is + deemed insecure but it is needed for certain test-operator functionalities + to work properly (e.g.: extraRPMs in Tempest CR, or certain set + of tobiko tests).' + type: boolean projectName: default: horizontest description: ProjectName is the name of the OpenStack project for diff --git a/api/bases/test.openstack.org_tempests.yaml b/api/bases/test.openstack.org_tempests.yaml index a4719bc4..f6950792 100644 --- a/api/bases/test.openstack.org_tempests.yaml +++ b/api/bases/test.openstack.org_tempests.yaml @@ -133,6 +133,16 @@ spec: if multiple instances of test-operator related CRs exist. If you want to turn off this behaviour then set this option to true. type: boolean + privileged: + default: false + description: 'Use with caution! This parameter specifies whether test-operator + should spawn test pods with allowedPrivilegedEscalation: true and + the default capabilities on top of capabilities that are usually + needed by the test pods (NET_ADMIN, NET_RAW). This parameter is + deemed insecure but it is needed for certain test-operator functionalities + to work properly (e.g.: extraRPMs in Tempest CR, or certain set + of tobiko tests).' + type: boolean storageClass: default: local-storage description: Name of a storage class that is used to create PVCs for diff --git a/api/bases/test.openstack.org_tobikoes.yaml b/api/bases/test.openstack.org_tobikoes.yaml index 62cb520a..90ff7468 100644 --- a/api/bases/test.openstack.org_tobikoes.yaml +++ b/api/bases/test.openstack.org_tobikoes.yaml @@ -102,6 +102,16 @@ spec: default: "" description: Private Key type: string + privileged: + default: false + description: 'Use with caution! This parameter specifies whether test-operator + should spawn test pods with allowedPrivilegedEscalation: true and + the default capabilities on top of capabilities that are usually + needed by the test pods (NET_ADMIN, NET_RAW). This parameter is + deemed insecure but it is needed for certain test-operator functionalities + to work properly (e.g.: extraRPMs in Tempest CR, or certain set + of tobiko tests).' + type: boolean publicKey: default: "" description: Public Key @@ -208,6 +218,15 @@ spec: privateKey: description: Private Key type: string + privileged: + description: 'Use with caution! This parameter specifies whether + test-operator should spawn test pods with allowedPrivilegedEscalation: + true and the default capabilities on top of capabilities that + are usually needed by the test pods (NET_ADMIN, NET_RAW). + This parameter is deemed insecure but it is needed for certain + test-operator functionalities to work properly (e.g.: extraRPMs + in Tempest CR, or certain set of tobiko tests).' + type: boolean publicKey: description: Public Key type: string diff --git a/api/v1beta1/ansibletest_types.go b/api/v1beta1/ansibletest_types.go index e0161cf6..87779480 100644 --- a/api/v1beta1/ansibletest_types.go +++ b/api/v1beta1/ansibletest_types.go @@ -26,6 +26,8 @@ import ( // AnsibleTestSpec defines the desired state of AnsibleTest type AnsibleTestSpec struct { + CommonParameters `json:",inline"` + // +operator-sdk:csv:customresourcedefinitions:type=spec // +kubebuilder:validation:Optional // Extra configmaps for mounting in the pod. @@ -126,6 +128,8 @@ type AnsibleTestSpec struct { } type AnsibleTestWorkflowSpec struct { + WorkflowCommonParameters `json:",inline"` + // +operator-sdk:csv:customresourcedefinitions:type=spec // +kubebuilder:validation:Optional // Extra configmaps for mounting in the pod diff --git a/api/v1beta1/ansibletest_webhook.go b/api/v1beta1/ansibletest_webhook.go index 195b7b8d..16eaa739 100644 --- a/api/v1beta1/ansibletest_webhook.go +++ b/api/v1beta1/ansibletest_webhook.go @@ -23,6 +23,8 @@ limitations under the License. package v1beta1 import ( + "fmt" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -59,8 +61,13 @@ var _ webhook.Validator = &AnsibleTest{} func (r *AnsibleTest) ValidateCreate() (admission.Warnings, error) { ansibletestlog.Info("validate create", "name", r.Name) - // TODO(user): fill in your validation logic upon object creation. - return nil, nil + var allWarnings admission.Warnings + + if r.Spec.Privileged { + allWarnings = append(allWarnings, fmt.Sprintf(WarnPrivilegedModeOn, "AnsibleTest")) + } + + return allWarnings, nil } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type diff --git a/api/v1beta1/common.go b/api/v1beta1/common.go index 34d467ef..2b1d81cf 100644 --- a/api/v1beta1/common.go +++ b/api/v1beta1/common.go @@ -16,6 +16,33 @@ limitations under the License. package v1beta1 +type CommonParameters struct { + // +operator-sdk:csv:customresourcedefinitions:type=spec + // +kubebuilder:validation:optional + // +kubebuilder:default=false + // +optional + // Use with caution! This parameter specifies whether test-operator should spawn test + // pods with allowedPrivilegedEscalation: true and the default capabilities on + // top of capabilities that are usually needed by the test pods (NET_ADMIN, NET_RAW). + // This parameter is deemed insecure but it is needed for certain test-operator + // functionalities to work properly (e.g.: extraRPMs in Tempest CR, or certain set + // of tobiko tests). + Privileged bool `json:"privileged"` +} + +type WorkflowCommonParameters struct { + // +operator-sdk:csv:customresourcedefinitions:type=spec + // +kubebuilder:validation:optional + // +optional + // Use with caution! This parameter specifies whether test-operator should spawn test + // pods with allowedPrivilegedEscalation: true and the default capabilities on + // top of capabilities that are usually needed by the test pods (NET_ADMIN, NET_RAW). + // This parameter is deemed insecure but it is needed for certain test-operator + // functionalities to work properly (e.g.: extraRPMs in Tempest CR, or certain set + // of tobiko tests). + Privileged *bool `json:"privileged,omitempty"` +} + type extraConfigmapsMounts struct { // +operator-sdk:csv:customresourcedefinitions:type=spec // +kubebuilder:validation:Required diff --git a/api/v1beta1/common_webhook.go b/api/v1beta1/common_webhook.go new file mode 100644 index 00000000..ad383e26 --- /dev/null +++ b/api/v1beta1/common_webhook.go @@ -0,0 +1,21 @@ +package v1beta1 + +const ( + // ErrPrivilegedModeRequired + ErrPrivilegedModeRequired = "%s.Spec.Privileged is requied in order to successfully " + + "execute tests with the provided configuration." +) + +const ( + // WarnPrivilegedModeOn + WarnPrivilegedModeOn = "%s.Spec.Privileged is set to true. This means that test pods " + + "are spawned with allowPrivilegedEscalation: true and default " + + "capabilities on top of those required by the test operator " + + "(NET_ADMIN, NET_RAW)." + + // WarnPrivilegedModeOff + WarnPrivilegedModeOff = "%[1]s.Spec.Privileged is set to false. Note, that a certain " + + "set of tests might fail, as this configuration may be " + + "required for the tests to run successfully. Before enabling" + + "this parameter, consult documentation of the %[1]s CR." +) diff --git a/api/v1beta1/horizontest_types.go b/api/v1beta1/horizontest_types.go index 577c0146..6d4094a4 100644 --- a/api/v1beta1/horizontest_types.go +++ b/api/v1beta1/horizontest_types.go @@ -26,6 +26,8 @@ import ( // HorizonTestSpec defines the desired state of HorizonTest type HorizonTestSpec struct { + CommonParameters `json:",inline"` + // +kubebuilder:validation:Required // +operator-sdk:csv:customresourcedefinitions:type=spec // +kubebuilder:default:="local-storage" @@ -146,7 +148,6 @@ type HorizonTestStatus struct { // NetworkAttachments status of the deployment pods NetworkAttachments map[string][]string `json:"networkAttachments,omitempty"` - } // +kubebuilder:object:root=true diff --git a/api/v1beta1/horizontest_webhook.go b/api/v1beta1/horizontest_webhook.go index d3b9852a..d5118a65 100644 --- a/api/v1beta1/horizontest_webhook.go +++ b/api/v1beta1/horizontest_webhook.go @@ -23,6 +23,8 @@ limitations under the License. package v1beta1 import ( + "fmt" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -59,8 +61,13 @@ var _ webhook.Validator = &HorizonTest{} func (r *HorizonTest) ValidateCreate() (admission.Warnings, error) { horizontestlog.Info("validate create", "name", r.Name) - // TODO(user): fill in your validation logic upon object creation. - return nil, nil + var allWarnings admission.Warnings + + if r.Spec.Privileged { + allWarnings = append(allWarnings, fmt.Sprintf(WarnPrivilegedModeOn, "HorizonTest")) + } + + return allWarnings, nil } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type diff --git a/api/v1beta1/tempest_types.go b/api/v1beta1/tempest_types.go index 5c426bc2..fb9e3b45 100644 --- a/api/v1beta1/tempest_types.go +++ b/api/v1beta1/tempest_types.go @@ -377,6 +377,8 @@ type TempestconfRunSpec struct { // TempestSpec - configuration of execution of tempest. For specific configuration // of tempest see TempestRunSpec and for discover-tempest-config see TempestconfRunSpec. type TempestSpec struct { + CommonParameters `json:",inline"` + // +operator-sdk:csv:customresourcedefinitions:type=spec // +kubebuilder:validation:Optional // Extra configmaps for mounting in the pod. diff --git a/api/v1beta1/tempest_webhook.go b/api/v1beta1/tempest_webhook.go index 82a5463e..d0377096 100644 --- a/api/v1beta1/tempest_webhook.go +++ b/api/v1beta1/tempest_webhook.go @@ -24,10 +24,13 @@ package v1beta1 import ( "errors" + "fmt" "github.com/google/go-cmp/cmp" - + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" @@ -72,6 +75,10 @@ func (spec *TempestSpec) Default() { } } +func (r *Tempest) PrivilegedRequired() bool { + return len(r.Spec.TempestRun.ExtraImages) > 0 +} + // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. //+kubebuilder:webhook:path=/validate-test-openstack-org-v1beta1-tempest,mutating=false,failurePolicy=fail,sideEffects=None,groups=test.openstack.org,resources=tempests,verbs=create;update,versions=v1beta1,name=vtempest.kb.io,admissionReviewVersions=v1 @@ -81,10 +88,35 @@ var _ webhook.Validator = &Tempest{} func (r *Tempest) ValidateCreate() (admission.Warnings, error) { tempestlog.Info("validate create", "name", r.Name) + var allErrs field.ErrorList + var allWarnings admission.Warnings + if len(r.Spec.Workflow) > 0 && r.Spec.Debug { return nil, errors.New("Workflow variable must be empty to run debug mode!") } - return nil, nil + + if !r.Spec.Privileged && r.PrivilegedRequired() { + allErrs = append(allErrs, &field.Error{ + Type: field.ErrorTypeRequired, + BadValue: r.Spec.Privileged, + Detail: fmt.Sprintf(ErrPrivilegedModeRequired, "Tempest"), + }, + ) + } + + if r.Spec.Privileged { + allWarnings = append(allWarnings, fmt.Sprintf(WarnPrivilegedModeOn, "Tempest")) + } + + if len(allErrs) > 0 { + return allWarnings, apierrors.NewInvalid( + schema.GroupKind{ + Group: GroupVersion.WithKind("Tempest").Group, + Kind: GroupVersion.WithKind("Tempest").Kind, + }, r.GetName(), allErrs) + } + + return allWarnings, nil } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type diff --git a/api/v1beta1/tobiko_types.go b/api/v1beta1/tobiko_types.go index e3281612..f5f5a7cb 100644 --- a/api/v1beta1/tobiko_types.go +++ b/api/v1beta1/tobiko_types.go @@ -37,6 +37,8 @@ type Hash struct { // TobikoSpec defines the desired state of Tobiko type TobikoSpec struct { + CommonParameters `json:",inline"` + // +kubebuilder:validation:Required // +operator-sdk:csv:customresourcedefinitions:type=spec // +kubebuilder:default:="local-storage" @@ -153,6 +155,8 @@ type TobikoSpec struct { } type TobikoWorkflowSpec struct { + WorkflowCommonParameters `json:",inline"` + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec // StorageClass used to create PVCs that store the logs diff --git a/api/v1beta1/tobiko_webhook.go b/api/v1beta1/tobiko_webhook.go index 6f47b374..23031526 100644 --- a/api/v1beta1/tobiko_webhook.go +++ b/api/v1beta1/tobiko_webhook.go @@ -23,6 +23,8 @@ limitations under the License. package v1beta1 import ( + "fmt" + "errors" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" @@ -60,10 +62,19 @@ var _ webhook.Validator = &Tobiko{} func (r *Tobiko) ValidateCreate() (admission.Warnings, error) { tobikolog.Info("validate create", "name", r.Name) + var allWarnings admission.Warnings + + if r.Spec.Privileged { + allWarnings = append(allWarnings, fmt.Sprintf(WarnPrivilegedModeOn, "Tobiko")) + } else { + allWarnings = append(allWarnings, fmt.Sprintf(WarnPrivilegedModeOff, "Tobiko")) + } + if len(r.Spec.Workflow) > 0 && r.Spec.Debug { - return nil, errors.New("Workflow variable must be empty to run debug mode!") + return allWarnings, errors.New("Workflow variable must be empty to run debug mode!") } - return nil, nil + + return allWarnings, nil } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 25540441..93a9e850 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -89,6 +89,7 @@ func (in *AnsibleTestList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AnsibleTestSpec) DeepCopyInto(out *AnsibleTestSpec) { *out = *in + out.CommonParameters = in.CommonParameters if in.ExtraConfigmapsMounts != nil { in, out := &in.ExtraConfigmapsMounts, &out.ExtraConfigmapsMounts *out = make([]extraConfigmapsMounts, len(*in)) @@ -165,6 +166,7 @@ func (in *AnsibleTestStatus) DeepCopy() *AnsibleTestStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AnsibleTestWorkflowSpec) DeepCopyInto(out *AnsibleTestWorkflowSpec) { *out = *in + in.WorkflowCommonParameters.DeepCopyInto(&out.WorkflowCommonParameters) if in.ExtraConfigmapsMounts != nil { in, out := &in.ExtraConfigmapsMounts, &out.ExtraConfigmapsMounts *out = make([]extraConfigmapsMounts, len(*in)) @@ -202,6 +204,21 @@ func (in *AnsibleTestWorkflowSpec) DeepCopy() *AnsibleTestWorkflowSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CommonParameters) DeepCopyInto(out *CommonParameters) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommonParameters. +func (in *CommonParameters) DeepCopy() *CommonParameters { + if in == nil { + return nil + } + out := new(CommonParameters) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExternalPluginType) DeepCopyInto(out *ExternalPluginType) { *out = *in @@ -325,6 +342,7 @@ func (in *HorizonTestList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HorizonTestSpec) DeepCopyInto(out *HorizonTestSpec) { *out = *in + out.CommonParameters = in.CommonParameters if in.BackoffLimit != nil { in, out := &in.BackoffLimit, &out.BackoffLimit *out = new(int32) @@ -493,6 +511,7 @@ func (in *TempestRunSpec) DeepCopy() *TempestRunSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TempestSpec) DeepCopyInto(out *TempestSpec) { *out = *in + out.CommonParameters = in.CommonParameters if in.ExtraConfigmapsMounts != nil { in, out := &in.ExtraConfigmapsMounts, &out.ExtraConfigmapsMounts *out = make([]extraConfigmapsMounts, len(*in)) @@ -671,6 +690,7 @@ func (in *TobikoList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TobikoSpec) DeepCopyInto(out *TobikoSpec) { *out = *in + out.CommonParameters = in.CommonParameters if in.NodeSelector != nil { in, out := &in.NodeSelector, &out.NodeSelector *out = make(map[string]string, len(*in)) @@ -761,6 +781,7 @@ func (in *TobikoStatus) DeepCopy() *TobikoStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TobikoWorkflowSpec) DeepCopyInto(out *TobikoWorkflowSpec) { *out = *in + in.WorkflowCommonParameters.DeepCopyInto(&out.WorkflowCommonParameters) if in.NodeSelector != nil { in, out := &in.NodeSelector, &out.NodeSelector *out = new(map[string]string) @@ -815,6 +836,26 @@ func (in *TobikoWorkflowSpec) DeepCopy() *TobikoWorkflowSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkflowCommonParameters) DeepCopyInto(out *WorkflowCommonParameters) { + *out = *in + if in.Privileged != nil { + in, out := &in.Privileged, &out.Privileged + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkflowCommonParameters. +func (in *WorkflowCommonParameters) DeepCopy() *WorkflowCommonParameters { + if in == nil { + return nil + } + out := new(WorkflowCommonParameters) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkflowTempestRunSpec) DeepCopyInto(out *WorkflowTempestRunSpec) { *out = *in diff --git a/config/crd/bases/test.openstack.org_ansibletests.yaml b/config/crd/bases/test.openstack.org_ansibletests.yaml index 981796cd..ed0da618 100644 --- a/config/crd/bases/test.openstack.org_ansibletests.yaml +++ b/config/crd/bases/test.openstack.org_ansibletests.yaml @@ -125,6 +125,16 @@ spec: description: OpenStackConfigSecret is the name of the Secret containing the secure.yaml type: string + privileged: + default: false + description: 'Use with caution! This parameter specifies whether test-operator + should spawn test pods with allowedPrivilegedEscalation: true and + the default capabilities on top of capabilities that are usually + needed by the test pods (NET_ADMIN, NET_RAW). This parameter is + deemed insecure but it is needed for certain test-operator functionalities + to work properly (e.g.: extraRPMs in Tempest CR, or certain set + of tobiko tests).' + type: boolean storageClass: default: local-storage description: StorageClass used to create PVCs that store the logs @@ -203,6 +213,15 @@ spec: description: OpenStackConfigSecret is the name of the Secret containing the secure.yaml type: string + privileged: + description: 'Use with caution! This parameter specifies whether + test-operator should spawn test pods with allowedPrivilegedEscalation: + true and the default capabilities on top of capabilities that + are usually needed by the test pods (NET_ADMIN, NET_RAW). + This parameter is deemed insecure but it is needed for certain + test-operator functionalities to work properly (e.g.: extraRPMs + in Tempest CR, or certain set of tobiko tests).' + type: boolean stepName: description: Name of a workflow step. The step name will be used for example to create a logs directory. diff --git a/config/crd/bases/test.openstack.org_horizontests.yaml b/config/crd/bases/test.openstack.org_horizontests.yaml index 80fdda05..03b95d7b 100644 --- a/config/crd/bases/test.openstack.org_horizontests.yaml +++ b/config/crd/bases/test.openstack.org_horizontests.yaml @@ -106,6 +106,16 @@ spec: description: Password is the password for the user running the Horizon tests. type: string + privileged: + default: false + description: 'Use with caution! This parameter specifies whether test-operator + should spawn test pods with allowedPrivilegedEscalation: true and + the default capabilities on top of capabilities that are usually + needed by the test pods (NET_ADMIN, NET_RAW). This parameter is + deemed insecure but it is needed for certain test-operator functionalities + to work properly (e.g.: extraRPMs in Tempest CR, or certain set + of tobiko tests).' + type: boolean projectName: default: horizontest description: ProjectName is the name of the OpenStack project for diff --git a/config/crd/bases/test.openstack.org_tempests.yaml b/config/crd/bases/test.openstack.org_tempests.yaml index a4719bc4..f6950792 100644 --- a/config/crd/bases/test.openstack.org_tempests.yaml +++ b/config/crd/bases/test.openstack.org_tempests.yaml @@ -133,6 +133,16 @@ spec: if multiple instances of test-operator related CRs exist. If you want to turn off this behaviour then set this option to true. type: boolean + privileged: + default: false + description: 'Use with caution! This parameter specifies whether test-operator + should spawn test pods with allowedPrivilegedEscalation: true and + the default capabilities on top of capabilities that are usually + needed by the test pods (NET_ADMIN, NET_RAW). This parameter is + deemed insecure but it is needed for certain test-operator functionalities + to work properly (e.g.: extraRPMs in Tempest CR, or certain set + of tobiko tests).' + type: boolean storageClass: default: local-storage description: Name of a storage class that is used to create PVCs for diff --git a/config/crd/bases/test.openstack.org_tobikoes.yaml b/config/crd/bases/test.openstack.org_tobikoes.yaml index 62cb520a..90ff7468 100644 --- a/config/crd/bases/test.openstack.org_tobikoes.yaml +++ b/config/crd/bases/test.openstack.org_tobikoes.yaml @@ -102,6 +102,16 @@ spec: default: "" description: Private Key type: string + privileged: + default: false + description: 'Use with caution! This parameter specifies whether test-operator + should spawn test pods with allowedPrivilegedEscalation: true and + the default capabilities on top of capabilities that are usually + needed by the test pods (NET_ADMIN, NET_RAW). This parameter is + deemed insecure but it is needed for certain test-operator functionalities + to work properly (e.g.: extraRPMs in Tempest CR, or certain set + of tobiko tests).' + type: boolean publicKey: default: "" description: Public Key @@ -208,6 +218,15 @@ spec: privateKey: description: Private Key type: string + privileged: + description: 'Use with caution! This parameter specifies whether + test-operator should spawn test pods with allowedPrivilegedEscalation: + true and the default capabilities on top of capabilities that + are usually needed by the test pods (NET_ADMIN, NET_RAW). + This parameter is deemed insecure but it is needed for certain + test-operator functionalities to work properly (e.g.: extraRPMs + in Tempest CR, or certain set of tobiko tests).' + type: boolean publicKey: description: Public Key type: string diff --git a/controllers/ansibletest_controller.go b/controllers/ansibletest_controller.go index 34b79602..fad4f8da 100644 --- a/controllers/ansibletest_controller.go +++ b/controllers/ansibletest_controller.go @@ -256,6 +256,7 @@ func (r *AnsibleTestReconciler) Reconcile(ctx context.Context, req ctrl.Request) envVars, workflowOverrideParams := r.PrepareAnsibleEnv(instance, externalWorkflowCounter) logsPVCName := r.GetPVCLogsName(instance, 0) containerImage, err := r.GetContainerImage(ctx, workflowOverrideParams["ContainerImage"], instance) + privileged := r.OverwriteAnsibleWithWorkflow(instance.Spec, "Privileged", "pbool", externalWorkflowCounter).(bool) if err != nil { return ctrl.Result{}, err } @@ -270,6 +271,7 @@ func (r *AnsibleTestReconciler) Reconcile(ctx context.Context, req ctrl.Request) workflowOverrideParams, externalWorkflowCounter, containerImage, + privileged, ) ansibleTestsJob := job.NewJob( jobDef, diff --git a/controllers/tobiko_controller.go b/controllers/tobiko_controller.go index 46da1bdf..f3313dd7 100644 --- a/controllers/tobiko_controller.go +++ b/controllers/tobiko_controller.go @@ -307,6 +307,7 @@ func (r *TobikoReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res jobName := r.GetJobName(instance, externalWorkflowCounter) logsPVCName := r.GetPVCLogsName(instance, workflowStepNum) containerImage, err := r.GetContainerImage(ctx, instance.Spec.ContainerImage, instance) + privileged := r.OverwriteValueWithWorkflow(instance.Spec, "Privileged", "pbool", externalWorkflowCounter).(bool) if err != nil { return ctrl.Result{}, err } @@ -322,6 +323,7 @@ func (r *TobikoReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res mountKubeconfig, envVars, containerImage, + privileged, ) tobikoJob := job.NewJob( jobDef, diff --git a/pkg/ansibletest/job.go b/pkg/ansibletest/job.go index 53857ce2..1e2a2eed 100644 --- a/pkg/ansibletest/job.go +++ b/pkg/ansibletest/job.go @@ -4,6 +4,7 @@ import ( "github.com/openstack-k8s-operators/lib-common/modules/common/env" testv1beta1 "github.com/openstack-k8s-operators/test-operator/api/v1beta1" + util "github.com/openstack-k8s-operators/test-operator/pkg/util" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -20,6 +21,7 @@ func Job( workflowOverrideParams map[string]string, externalWorkflowCounter int, containerImage string, + privileged bool, ) *batchv1.Job { runAsUser := int64(227) @@ -27,6 +29,9 @@ func Job( parallelism := int32(1) completions := int32(1) + capabilities := []corev1.Capability{"NET_ADMIN", "NET_RAW"} + securityContext := util.GetSecurityContext(runAsUser, capabilities, privileged) + job := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: jobName, @@ -51,19 +56,12 @@ func Job( }, Containers: []corev1.Container{ { - Name: instance.Name, - Image: containerImage, - Args: []string{}, - Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), - VolumeMounts: GetVolumeMounts(mountCerts, instance, externalWorkflowCounter), - SecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{"NET_ADMIN", "NET_RAW", "CAP_AUDIT_WRITE"}, - }, - SeccompProfile: &corev1.SeccompProfile{ - Type: corev1.SeccompProfileTypeRuntimeDefault, - }, - }, + Name: instance.Name, + Image: containerImage, + Args: []string{}, + Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), + VolumeMounts: GetVolumeMounts(mountCerts, instance, externalWorkflowCounter), + SecurityContext: &securityContext, }, }, Volumes: GetVolumes( diff --git a/pkg/ansibletest/volumes.go b/pkg/ansibletest/volumes.go index deda0848..9cebba05 100644 --- a/pkg/ansibletest/volumes.go +++ b/pkg/ansibletest/volumes.go @@ -21,22 +21,6 @@ func GetVolumes( //source_type := corev1.HostPathDirectoryOrCreate volumes := []corev1.Volume{ - { - Name: "etc-machine-id", - VolumeSource: corev1.VolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: "/etc/machine-id", - }, - }, - }, - { - Name: "etc-localtime", - VolumeSource: corev1.VolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: "/etc/localtime", - }, - }, - }, { Name: "openstack-config", VolumeSource: corev1.VolumeSource{ @@ -145,16 +129,6 @@ func GetVolumes( // GetVolumeMounts - func GetVolumeMounts(mountCerts bool, instance *testv1beta1.AnsibleTest, externalWorkflowCounter int) []corev1.VolumeMount { volumeMounts := []corev1.VolumeMount{ - { - Name: "etc-machine-id", - MountPath: "/etc/machine-id", - ReadOnly: true, - }, - { - Name: "etc-localtime", - MountPath: "/etc/localtime", - ReadOnly: true, - }, { Name: "test-operator-logs", MountPath: "/var/lib/AnsibleTests/external_files", diff --git a/pkg/horizontest/job.go b/pkg/horizontest/job.go index c8321c24..f4d56e09 100644 --- a/pkg/horizontest/job.go +++ b/pkg/horizontest/job.go @@ -4,6 +4,7 @@ import ( "github.com/openstack-k8s-operators/lib-common/modules/common/env" testv1beta1 "github.com/openstack-k8s-operators/test-operator/api/v1beta1" + util "github.com/openstack-k8s-operators/test-operator/pkg/util" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -27,6 +28,9 @@ func Job( parallelism := int32(1) completions := int32(1) + capabilities := []corev1.Capability{"NET_ADMIN", "NET_RAW"} + securityContext := util.GetSecurityContext(runAsUser, capabilities, instance.Spec.Privileged) + job := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: jobName, @@ -51,19 +55,12 @@ func Job( }, Containers: []corev1.Container{ { - Name: instance.Name, - Image: containerImage, - Args: []string{}, - Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), - VolumeMounts: GetVolumeMounts(mountCerts, mountKeys, mountKubeconfig), - SecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{"NET_ADMIN", "NET_RAW", "CAP_AUDIT_WRITE"}, - }, - SeccompProfile: &corev1.SeccompProfile{ - Type: corev1.SeccompProfileTypeRuntimeDefault, - }, - }, + Name: instance.Name, + Image: containerImage, + Args: []string{}, + Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), + VolumeMounts: GetVolumeMounts(mountCerts, mountKeys, mountKubeconfig), + SecurityContext: &securityContext, }, }, Volumes: GetVolumes( diff --git a/pkg/horizontest/volumes.go b/pkg/horizontest/volumes.go index 4197dc47..48953194 100644 --- a/pkg/horizontest/volumes.go +++ b/pkg/horizontest/volumes.go @@ -31,22 +31,6 @@ func GetVolumes( }, }, }, - { - Name: "etc-machine-id", - VolumeSource: corev1.VolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: "/etc/machine-id", - }, - }, - }, - { - Name: "etc-localtime", - VolumeSource: corev1.VolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: "/etc/localtime", - }, - }, - }, { Name: "horizontest-clouds-config", VolumeSource: corev1.VolumeSource{ @@ -117,16 +101,6 @@ func GetVolumes( // GetVolumeMounts - func GetVolumeMounts(mountCerts bool, mountKeys bool, mountKubeconfig bool) []corev1.VolumeMount { volumeMounts := []corev1.VolumeMount{ - { - Name: "etc-machine-id", - MountPath: "/etc/machine-id", - ReadOnly: true, - }, - { - Name: "etc-localtime", - MountPath: "/etc/localtime", - ReadOnly: true, - }, { Name: "test-operator-logs", MountPath: "/var/lib/horizontest/external_files", diff --git a/pkg/tempest/job.go b/pkg/tempest/job.go index 575f1b00..7ac1b2a6 100644 --- a/pkg/tempest/job.go +++ b/pkg/tempest/job.go @@ -4,6 +4,7 @@ import ( "github.com/openstack-k8s-operators/lib-common/modules/common/env" testv1beta1 "github.com/openstack-k8s-operators/test-operator/api/v1beta1" + util "github.com/openstack-k8s-operators/test-operator/pkg/util" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -26,6 +27,7 @@ func Job( envVars := map[string]env.Setter{} runAsUser := int64(42480) runAsGroup := int64(42480) + securityContext := util.GetSecurityContext(runAsUser, []corev1.Capability{}, instance.Spec.Privileged) job := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ @@ -55,19 +57,12 @@ func Job( NodeSelector: instance.Spec.NodeSelector, Containers: []corev1.Container{ { - Name: instance.Name + "-tests-runner", - Image: containerImage, - Args: []string{}, - Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), - VolumeMounts: GetVolumeMounts(mountCerts, mountSSHKey, instance), - SecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{"CAP_AUDIT_WRITE"}, - }, - SeccompProfile: &corev1.SeccompProfile{ - Type: corev1.SeccompProfileTypeRuntimeDefault, - }, - }, + Name: instance.Name + "-tests-runner", + Image: containerImage, + Args: []string{}, + Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), + VolumeMounts: GetVolumeMounts(mountCerts, mountSSHKey, instance), + SecurityContext: &securityContext, EnvFrom: []corev1.EnvFromSource{ { ConfigMapRef: &corev1.ConfigMapEnvSource{ diff --git a/pkg/tempest/volumes.go b/pkg/tempest/volumes.go index 917c2c05..261874eb 100644 --- a/pkg/tempest/volumes.go +++ b/pkg/tempest/volumes.go @@ -21,22 +21,6 @@ func GetVolumes( //source_type := corev1.HostPathDirectoryOrCreate volumes := []corev1.Volume{ - { - Name: "etc-machine-id", - VolumeSource: corev1.VolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: "/etc/machine-id", - }, - }, - }, - { - Name: "etc-localtime", - VolumeSource: corev1.VolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: "/etc/localtime", - }, - }, - }, { Name: "config-data", VolumeSource: corev1.VolumeSource{ @@ -135,16 +119,6 @@ func GetVolumes( // GetVolumeMounts - func GetVolumeMounts(mountCerts bool, mountSSHKey bool, instance *testv1beta1.Tempest) []corev1.VolumeMount { volumeMounts := []corev1.VolumeMount{ - { - Name: "etc-machine-id", - MountPath: "/etc/machine-id", - ReadOnly: true, - }, - { - Name: "etc-localtime", - MountPath: "/etc/localtime", - ReadOnly: true, - }, { Name: "config-data", MountPath: "/etc/test_operator", diff --git a/pkg/tobiko/job.go b/pkg/tobiko/job.go index 7731766c..29474d40 100644 --- a/pkg/tobiko/job.go +++ b/pkg/tobiko/job.go @@ -4,6 +4,7 @@ import ( "github.com/openstack-k8s-operators/lib-common/modules/common/env" testv1beta1 "github.com/openstack-k8s-operators/test-operator/api/v1beta1" + util "github.com/openstack-k8s-operators/test-operator/pkg/util" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -21,6 +22,7 @@ func Job( mountKubeconfig bool, envVars map[string]env.Setter, containerImage string, + privileged bool, ) *batchv1.Job { runAsUser := int64(42495) @@ -28,6 +30,9 @@ func Job( parallelism := int32(1) completions := int32(1) + capabilities := []corev1.Capability{"NET_ADMIN", "NET_RAW"} + securityContext := util.GetSecurityContext(runAsUser, capabilities, privileged) + // Note(lpiwowar): Once the webhook is implemented move all the logic of merging // the workflows there. job := &batchv1.Job{ @@ -57,19 +62,12 @@ func Job( NodeSelector: instance.Spec.NodeSelector, Containers: []corev1.Container{ { - Name: instance.Name, - Image: containerImage, - Args: []string{}, - Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), - VolumeMounts: GetVolumeMounts(mountCerts, mountKeys, mountKubeconfig), - SecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{"NET_ADMIN", "NET_RAW", "CAP_AUDIT_WRITE"}, - }, - SeccompProfile: &corev1.SeccompProfile{ - Type: corev1.SeccompProfileTypeRuntimeDefault, - }, - }, + Name: instance.Name, + Image: containerImage, + Args: []string{}, + Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), + VolumeMounts: GetVolumeMounts(mountCerts, mountKeys, mountKubeconfig), + SecurityContext: &securityContext, }, }, Volumes: GetVolumes( diff --git a/pkg/tobiko/volumes.go b/pkg/tobiko/volumes.go index 751753d9..3ebdb442 100644 --- a/pkg/tobiko/volumes.go +++ b/pkg/tobiko/volumes.go @@ -32,22 +32,6 @@ func GetVolumes( }, }, }, - { - Name: "etc-machine-id", - VolumeSource: corev1.VolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: "/etc/machine-id", - }, - }, - }, - { - Name: "etc-localtime", - VolumeSource: corev1.VolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: "/etc/localtime", - }, - }, - }, { Name: "tobiko-clouds-config", VolumeSource: corev1.VolumeSource{ @@ -148,16 +132,6 @@ func GetVolumes( // GetVolumeMounts - func GetVolumeMounts(mountCerts bool, mountKeys bool, mountKubeconfig bool) []corev1.VolumeMount { volumeMounts := []corev1.VolumeMount{ - { - Name: "etc-machine-id", - MountPath: "/etc/machine-id", - ReadOnly: true, - }, - { - Name: "etc-localtime", - MountPath: "/etc/localtime", - ReadOnly: true, - }, { Name: "test-operator-logs", MountPath: "/var/lib/tobiko/external_files", diff --git a/pkg/util/common.go b/pkg/util/common.go new file mode 100644 index 00000000..62b82401 --- /dev/null +++ b/pkg/util/common.go @@ -0,0 +1,39 @@ +package util + +import ( + corev1 "k8s.io/api/core/v1" +) + +func GetSecurityContext( + runAsUser int64, + addCapabilities []corev1.Capability, + privileged bool, +) corev1.SecurityContext { + falseVar := false + trueVar := true + + securityContext := corev1.SecurityContext{ + RunAsUser: &runAsUser, + RunAsGroup: &runAsUser, + AllowPrivilegeEscalation: &falseVar, + Capabilities: &corev1.Capabilities{ + Add: addCapabilities, + }, + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + }, + } + + if privileged { + // We need to run pods with AllowPrivilegedEscalation: true to remove + // nosuid from the pod (in order to be able to run sudo) + securityContext.AllowPrivilegeEscalation = &trueVar + } + + if !privileged { + // We need to keep default capabilities in order to be able to use sudo + securityContext.Capabilities.Drop = []corev1.Capability{"ALL"} + } + + return securityContext +} From 37d95b3871c037484a4c15e34d18231375f5ca3b Mon Sep 17 00:00:00 2001 From: Lukas Piwowarski Date: Fri, 30 Aug 2024 07:12:59 -0400 Subject: [PATCH 2/2] Fix go fmt issues Files modified with this commit used spaces for indentation. However, the recommended character for indentation in golang is tab. This commit fixes such an incorrect usage of the spaces. All fixes were generated with: go fmt ./... --- api/v1beta1/tempest_types_workflow.go | 1 - api/v1beta1/tempest_webhook.go | 42 ++--- api/v1beta1/tobiko_types.go | 233 +++++++++++++------------- 3 files changed, 137 insertions(+), 139 deletions(-) diff --git a/api/v1beta1/tempest_types_workflow.go b/api/v1beta1/tempest_types_workflow.go index 0a54fd1f..936c16c6 100644 --- a/api/v1beta1/tempest_types_workflow.go +++ b/api/v1beta1/tempest_types_workflow.go @@ -261,7 +261,6 @@ type WorkflowTempestSpec struct { // test pods that are spawned by the test-operator. Tolerations *[]corev1.Toleration `json:"tolerations,omitempty"` - // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec // OpenStackConfigMap is the name of the ConfigMap containing the clouds.yaml diff --git a/api/v1beta1/tempest_webhook.go b/api/v1beta1/tempest_webhook.go index d0377096..029a92c4 100644 --- a/api/v1beta1/tempest_webhook.go +++ b/api/v1beta1/tempest_webhook.go @@ -23,18 +23,18 @@ limitations under the License. package v1beta1 import ( - "errors" + "errors" "fmt" "github.com/google/go-cmp/cmp" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apimachinery/pkg/util/validation/field" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) // TempestDefaults - @@ -61,12 +61,12 @@ var _ webhook.Defaulter = &Tempest{} func (r *Tempest) Default() { tempestlog.Info("default", "name", r.Name) - r.Spec.Default() + r.Spec.Default() } // Default - set defaults for this Tempest spec. func (spec *TempestSpec) Default() { - if spec.ContainerImage == "" { + if spec.ContainerImage == "" { spec.ContainerImage = tempestDefaults.ContainerImageURL } @@ -91,16 +91,16 @@ func (r *Tempest) ValidateCreate() (admission.Warnings, error) { var allErrs field.ErrorList var allWarnings admission.Warnings - if len(r.Spec.Workflow) > 0 && r.Spec.Debug { - return nil, errors.New("Workflow variable must be empty to run debug mode!") - } + if len(r.Spec.Workflow) > 0 && r.Spec.Debug { + return nil, errors.New("Workflow variable must be empty to run debug mode!") + } if !r.Spec.Privileged && r.PrivilegedRequired() { allErrs = append(allErrs, &field.Error{ - Type: field.ErrorTypeRequired, - BadValue: r.Spec.Privileged, - Detail: fmt.Sprintf(ErrPrivilegedModeRequired, "Tempest"), - }, + Type: field.ErrorTypeRequired, + BadValue: r.Spec.Privileged, + Detail: fmt.Sprintf(ErrPrivilegedModeRequired, "Tempest"), + }, ) } @@ -108,13 +108,13 @@ func (r *Tempest) ValidateCreate() (admission.Warnings, error) { allWarnings = append(allWarnings, fmt.Sprintf(WarnPrivilegedModeOn, "Tempest")) } - if len(allErrs) > 0 { - return allWarnings, apierrors.NewInvalid( - schema.GroupKind{ - Group: GroupVersion.WithKind("Tempest").Group, - Kind: GroupVersion.WithKind("Tempest").Kind, - }, r.GetName(), allErrs) - } + if len(allErrs) > 0 { + return allWarnings, apierrors.NewInvalid( + schema.GroupKind{ + Group: GroupVersion.WithKind("Tempest").Group, + Kind: GroupVersion.WithKind("Tempest").Kind, + }, r.GetName(), allErrs) + } return allWarnings, nil } @@ -130,8 +130,8 @@ func (r *Tempest) ValidateUpdate(old runtime.Object) (admission.Warnings, error) if !cmp.Equal(oldTempest.Spec, r.Spec) { warnings := admission.Warnings{} - warnings = append(warnings, "You are updating an already existing instance of a " + - "Tempest CR! Be aware that changes won't be applied.") + warnings = append(warnings, "You are updating an already existing instance of a "+ + "Tempest CR! Be aware that changes won't be applied.") return warnings, errors.New("Updating an existing Tempest CR is not supported!") } diff --git a/api/v1beta1/tobiko_types.go b/api/v1beta1/tobiko_types.go index f5f5a7cb..06ebbb54 100644 --- a/api/v1beta1/tobiko_types.go +++ b/api/v1beta1/tobiko_types.go @@ -41,23 +41,23 @@ type TobikoSpec struct { // +kubebuilder:validation:Required // +operator-sdk:csv:customresourcedefinitions:type=spec - // +kubebuilder:default:="local-storage" - // StorageClass used to create PVCs that store the logs + // +kubebuilder:default="local-storage" + // StorageClass used to create PVCs that store the logs StorageClass string `json:"storageClass"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec // This value contains a nodeSelector value that is applied to test pods // spawned by the test operator. NodeSelector map[string]string `json:"nodeSelector,omitempty"` // +kubebuilder:validation:Optional - // +kubebuilder:default:=false - // Activate debug mode. When debug mode is activated any error encountered - // inside the test-pod causes that the pod will be kept alive indefinitely - // (stuck in "Running" phase) or until the corresponding Tobiko CR is deleted. - // This allows the user to debug any potential troubles with `oc rsh`. - Debug bool `json:"debug"` + // +kubebuilder:default=false + // Activate debug mode. When debug mode is activated any error encountered + // inside the test-pod causes that the pod will be kept alive indefinitely + // (stuck in "Running" phase) or until the corresponding Tobiko CR is deleted. + // This allows the user to debug any potential troubles with `oc rsh`. + Debug bool `json:"debug"` // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec @@ -67,102 +67,102 @@ type TobikoSpec struct { // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // +kubebuilder:default:="py3" - // Test environment - Testenv string `json:"testenv"` + // +kubebuilder:default:="py3" + // Test environment + Testenv string `json:"testenv"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // +kubebuilder:default:="" - // String including any options to pass to pytest when it runs tobiko tests - PytestAddopts string `json:"pytestAddopts"` + // +kubebuilder:default:="" + // String including any options to pass to pytest when it runs tobiko tests + PytestAddopts string `json:"pytestAddopts"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // +kubebuilder:default:=false - // Boolean specifying whether tobiko tests create new resources or re-use those previously created - PreventCreate bool `json:"preventCreate"` + // +kubebuilder:default:=false + // Boolean specifying whether tobiko tests create new resources or re-use those previously created + PreventCreate bool `json:"preventCreate"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // +kubebuilder:default:=0 - // Number of processes/workers used to run tobiko tests - value 0 results in automatic decission - NumProcesses uint8 `json:"numProcesses"` + // +kubebuilder:default:=0 + // Number of processes/workers used to run tobiko tests - value 0 results in automatic decission + NumProcesses uint8 `json:"numProcesses"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // +kubebuilder:default:="" - // Tobiko version - Version string `json:"version"` + // +kubebuilder:default:="" + // Tobiko version + Version string `json:"version"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // +kubebuilder:default:="" - // tobiko.conf - Config string `json:"config"` + // +kubebuilder:default:="" + // tobiko.conf + Config string `json:"config"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // +kubebuilder:default:="" - // Private Key - PrivateKey string `json:"privateKey"` + // +kubebuilder:default:="" + // Private Key + PrivateKey string `json:"privateKey"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // +kubebuilder:default:="" - // Public Key - PublicKey string `json:"publicKey"` + // +kubebuilder:default:="" + // Public Key + PublicKey string `json:"publicKey"` // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // +kubebuilder:default:="" - // Container image for tobiko - ContainerImage string `json:"containerImage"` + // +kubebuilder:default:="" + // Container image for tobiko + ContainerImage string `json:"containerImage"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // +kubebuilder:default:=false + // +kubebuilder:default:=false // By default test-operator executes the test-pods sequentially if multiple // instances of test-operator related CRs exist. To run test-pods in parallel // set this option to true. - Parallel bool `json:"parallel"` + Parallel bool `json:"parallel"` // BackoffLimit allows to define the maximum number of retried executions (defaults to 0). // +operator-sdk:csv:customresourcedefinitions:type=spec - // +kubebuilder:validation:Optional - // +kubebuilder:default:=0 - // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:number"} - BackoffLimit *int32 `json:"backoffLimit"` + // +kubebuilder:validation:Optional + // +kubebuilder:default:=0 + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:number"} + BackoffLimit *int32 `json:"backoffLimit"` - // Name of a secret that contains a kubeconfig. The kubeconfig is mounted under /var/lib/tobiko/.kube/config - // in the test pod. + // Name of a secret that contains a kubeconfig. The kubeconfig is mounted under /var/lib/tobiko/.kube/config + // in the test pod. // +operator-sdk:csv:customresourcedefinitions:type=spec - // +kubebuilder:validation:Optional - // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:number"} - KubeconfigSecretName string `json:"kubeconfigSecretName,omitempty"` + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:number"} + KubeconfigSecretName string `json:"kubeconfigSecretName,omitempty"` - // +kubebuilder:validation:Optional - // +operator-sdk:csv:customresourcedefinitions:type=spec - // NetworkAttachments is a list of NetworkAttachment resource names to expose - // the services to the given network - NetworkAttachments []string `json:"networkAttachments,omitempty"` + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // NetworkAttachments is a list of NetworkAttachment resource names to expose + // the services to the given network + NetworkAttachments []string `json:"networkAttachments,omitempty"` - // A parameter that contains a workflow definition. + // A parameter that contains a workflow definition. // +operator-sdk:csv:customresourcedefinitions:type=spec - // +kubebuilder:validation:Optional - // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:number"} - Workflow []TobikoWorkflowSpec `json:"workflow,omitempty"` + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:number"} + Workflow []TobikoWorkflowSpec `json:"workflow,omitempty"` } type TobikoWorkflowSpec struct { WorkflowCommonParameters `json:",inline"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // StorageClass used to create PVCs that store the logs - StorageClass string `json:"storageClass,omitempty"` + // StorageClass used to create PVCs that store the logs + StorageClass string `json:"storageClass,omitempty"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec // This value contains a nodeSelector value that is applied to test pods // spawned by the test operator. @@ -174,75 +174,74 @@ type TobikoWorkflowSpec struct { // test pods that are spawned by the test-operator. Tolerations *[]corev1.Toleration `json:"tolerations,omitempty"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // Test environment - Testenv string `json:"testenv,omitempty"` + // Test environment + Testenv string `json:"testenv,omitempty"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // String including any options to pass to pytest when it runs tobiko tests - PytestAddopts string `json:"pytestAddopts,omitempty"` + // String including any options to pass to pytest when it runs tobiko tests + PytestAddopts string `json:"pytestAddopts,omitempty"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // Boolean specifying whether tobiko tests create new resources or re-use those previously created - PreventCreate *bool `json:"preventCreate,omitempty"` + // Boolean specifying whether tobiko tests create new resources or re-use those previously created + PreventCreate *bool `json:"preventCreate,omitempty"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // Number of processes/workers used to run tobiko tests - value 0 results in automatic decission - NumProcesses *uint8 `json:"numProcesses,omitempty"` + // Number of processes/workers used to run tobiko tests - value 0 results in automatic decission + NumProcesses *uint8 `json:"numProcesses,omitempty"` - // +kubebuilder:validation:Optional - // +operator-sdk:csv:customresourcedefinitions:type=spec - // NetworkAttachments is a list of NetworkAttachment resource names to expose - // the services to the given network - NetworkAttachments []string `json:"networkAttachments,omitempty"` + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // NetworkAttachments is a list of NetworkAttachment resource names to expose + // the services to the given network + NetworkAttachments []string `json:"networkAttachments,omitempty"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // Tobiko version - Version string `json:"version,omitempty"` + // Tobiko version + Version string `json:"version,omitempty"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // tobiko.conf - Config string `json:"config,omitempty"` + // tobiko.conf + Config string `json:"config,omitempty"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // Private Key - PrivateKey string `json:"privateKey,omitempty"` + // Private Key + PrivateKey string `json:"privateKey,omitempty"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // Public Key - PublicKey string `json:"publicKey,omitempty"` + // Public Key + PublicKey string `json:"publicKey,omitempty"` - // +kubebuilder:validation:Optional + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec - // Container image for tobiko - ContainerImage string `json:"containerImage,omitempty"` - - // BackoffLimit allows to define the maximum number of retried executions (defaults to 0). - // +kubebuilder:default:=0 - // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:number"} - BackoffLimit *int32 `json:"backoffLimit,omitempty"` - - // Name of a secret that contains a kubeconfig. The kubeconfig is mounted under /var/lib/tobiko/.kube/config - // in the test pod. - // +kubebuilder:validation:Optional - // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:number"} - KubeconfigSecretName string `json:"kubeconfigSecretName,omitempty"` - - // A parameter that contains a definition of a single workflow step. - // +kubebuilder:validation:Required + // Container image for tobiko + ContainerImage string `json:"containerImage,omitempty"` + + // BackoffLimit allows to define the maximum number of retried executions (defaults to 0). + // +kubebuilder:default:=0 + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:number"} + BackoffLimit *int32 `json:"backoffLimit,omitempty"` + + // Name of a secret that contains a kubeconfig. The kubeconfig is mounted under /var/lib/tobiko/.kube/config + // in the test pod. + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:number"} + KubeconfigSecretName string `json:"kubeconfigSecretName,omitempty"` + + // A parameter that contains a definition of a single workflow step. + // +kubebuilder:validation:Required // +kubebuilder:validation:MaxLength:=100 // +operator-sdk:csv:customresourcedefinitions:type=spec - // +kubebuilder:default:="" - // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:number"} - StepName string `json:"stepName"` + // +kubebuilder:default:="" + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:number"} + StepName string `json:"stepName"` } // TobikoStatus defines the observed state of Tobiko