From ff433549b64c0aabebea903bb88c580b47a2a12b Mon Sep 17 00:00:00 2001 From: Lenin Alevski Date: Tue, 19 Oct 2021 09:42:36 -0700 Subject: [PATCH] Operator-UI security context configuration (#1089) - fix: check all pages are valid in Add tenant wizard before enabling Create button - Added: security context menu configuration for MinIO, logsearch api, postgres, prometheus and KES Signed-off-by: Lenin Alevski --- models/encryption_configuration.go | 51 ++ models/log_search_configuration.go | 97 ++- models/pool.go | 42 ++ models/security_context.go | 6 +- operatorapi/embedded_spec.go | 50 +- operatorapi/operator_tenants.go | 50 +- operatorapi/operator_tenants_helper.go | 25 + portal-ui/src/common/types.ts | 9 +- .../Console/Tenants/AddTenant/AddTenant.tsx | 30 +- .../Tenants/AddTenant/Steps/Affinity.tsx | 3 - .../Tenants/AddTenant/Steps/Configure.tsx | 552 +++++++++++++++++- .../Tenants/AddTenant/Steps/Encryption.tsx | 164 ++++++ .../Tenants/AddTenant/Steps/Images.tsx | 4 + .../src/screens/Console/Tenants/reducer.ts | 87 ++- .../src/screens/Console/Tenants/types.ts | 14 + swagger-operator.yml | 21 +- 16 files changed, 1162 insertions(+), 43 deletions(-) diff --git a/models/encryption_configuration.go b/models/encryption_configuration.go index b98f9906f..15d27cbf7 100644 --- a/models/encryption_configuration.go +++ b/models/encryption_configuration.go @@ -57,6 +57,9 @@ type EncryptionConfiguration struct { // replicas Replicas string `json:"replicas,omitempty"` + // security context + SecurityContext *SecurityContext `json:"securityContext,omitempty"` + // server Server *KeyPairConfiguration `json:"server,omitempty"` @@ -89,6 +92,8 @@ func (m *EncryptionConfiguration) UnmarshalJSON(raw []byte) error { Replicas string `json:"replicas,omitempty"` + SecurityContext *SecurityContext `json:"securityContext,omitempty"` + Server *KeyPairConfiguration `json:"server,omitempty"` Vault *VaultConfiguration `json:"vault,omitempty"` @@ -111,6 +116,8 @@ func (m *EncryptionConfiguration) UnmarshalJSON(raw []byte) error { m.Replicas = dataAO1.Replicas + m.SecurityContext = dataAO1.SecurityContext + m.Server = dataAO1.Server m.Vault = dataAO1.Vault @@ -142,6 +149,8 @@ func (m EncryptionConfiguration) MarshalJSON() ([]byte, error) { Replicas string `json:"replicas,omitempty"` + SecurityContext *SecurityContext `json:"securityContext,omitempty"` + Server *KeyPairConfiguration `json:"server,omitempty"` Vault *VaultConfiguration `json:"vault,omitempty"` @@ -161,6 +170,8 @@ func (m EncryptionConfiguration) MarshalJSON() ([]byte, error) { dataAO1.Replicas = m.Replicas + dataAO1.SecurityContext = m.SecurityContext + dataAO1.Server = m.Server dataAO1.Vault = m.Vault @@ -202,6 +213,10 @@ func (m *EncryptionConfiguration) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateSecurityContext(formats); err != nil { + res = append(res, err) + } + if err := m.validateServer(formats); err != nil { res = append(res, err) } @@ -306,6 +321,24 @@ func (m *EncryptionConfiguration) validateGemalto(formats strfmt.Registry) error return nil } +func (m *EncryptionConfiguration) validateSecurityContext(formats strfmt.Registry) error { + + if swag.IsZero(m.SecurityContext) { // not required + return nil + } + + if m.SecurityContext != nil { + if err := m.SecurityContext.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("securityContext") + } + return err + } + } + + return nil +} + func (m *EncryptionConfiguration) validateServer(formats strfmt.Registry) error { if swag.IsZero(m.Server) { // not required @@ -371,6 +404,10 @@ func (m *EncryptionConfiguration) ContextValidate(ctx context.Context, formats s res = append(res, err) } + if err := m.contextValidateSecurityContext(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateServer(ctx, formats); err != nil { res = append(res, err) } @@ -455,6 +492,20 @@ func (m *EncryptionConfiguration) contextValidateGemalto(ctx context.Context, fo return nil } +func (m *EncryptionConfiguration) contextValidateSecurityContext(ctx context.Context, formats strfmt.Registry) error { + + if m.SecurityContext != nil { + if err := m.SecurityContext.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("securityContext") + } + return err + } + } + + return nil +} + func (m *EncryptionConfiguration) contextValidateServer(ctx context.Context, formats strfmt.Registry) error { if m.Server != nil { diff --git a/models/log_search_configuration.go b/models/log_search_configuration.go index f13e20794..9ea7e19bf 100644 --- a/models/log_search_configuration.go +++ b/models/log_search_configuration.go @@ -25,6 +25,7 @@ package models import ( "context" + "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" ) @@ -43,6 +44,12 @@ type LogSearchConfiguration struct { // postgres init image PostgresInitImage string `json:"postgres_init_image,omitempty"` + // postgres security context + PostgresSecurityContext *SecurityContext `json:"postgres_securityContext,omitempty"` + + // security context + SecurityContext *SecurityContext `json:"securityContext,omitempty"` + // storage class StorageClass string `json:"storageClass,omitempty"` @@ -52,11 +59,99 @@ type LogSearchConfiguration struct { // Validate validates this log search configuration func (m *LogSearchConfiguration) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validatePostgresSecurityContext(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSecurityContext(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } return nil } -// ContextValidate validates this log search configuration based on context it is used +func (m *LogSearchConfiguration) validatePostgresSecurityContext(formats strfmt.Registry) error { + if swag.IsZero(m.PostgresSecurityContext) { // not required + return nil + } + + if m.PostgresSecurityContext != nil { + if err := m.PostgresSecurityContext.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("postgres_securityContext") + } + return err + } + } + + return nil +} + +func (m *LogSearchConfiguration) validateSecurityContext(formats strfmt.Registry) error { + if swag.IsZero(m.SecurityContext) { // not required + return nil + } + + if m.SecurityContext != nil { + if err := m.SecurityContext.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("securityContext") + } + return err + } + } + + return nil +} + +// ContextValidate validate this log search configuration based on the context it is used func (m *LogSearchConfiguration) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidatePostgresSecurityContext(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateSecurityContext(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *LogSearchConfiguration) contextValidatePostgresSecurityContext(ctx context.Context, formats strfmt.Registry) error { + + if m.PostgresSecurityContext != nil { + if err := m.PostgresSecurityContext.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("postgres_securityContext") + } + return err + } + } + + return nil +} + +func (m *LogSearchConfiguration) contextValidateSecurityContext(ctx context.Context, formats strfmt.Registry) error { + + if m.SecurityContext != nil { + if err := m.SecurityContext.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("securityContext") + } + return err + } + } + return nil } diff --git a/models/pool.go b/models/pool.go index aaa8d3750..fa933a722 100644 --- a/models/pool.go +++ b/models/pool.go @@ -48,6 +48,9 @@ type Pool struct { // resources Resources *PoolResources `json:"resources,omitempty"` + // security context + SecurityContext *SecurityContext `json:"securityContext,omitempty"` + // servers // Required: true Servers *int64 `json:"servers"` @@ -76,6 +79,10 @@ func (m *Pool) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateSecurityContext(formats); err != nil { + res = append(res, err) + } + if err := m.validateServers(formats); err != nil { res = append(res, err) } @@ -132,6 +139,23 @@ func (m *Pool) validateResources(formats strfmt.Registry) error { return nil } +func (m *Pool) validateSecurityContext(formats strfmt.Registry) error { + if swag.IsZero(m.SecurityContext) { // not required + return nil + } + + if m.SecurityContext != nil { + if err := m.SecurityContext.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("securityContext") + } + return err + } + } + + return nil +} + func (m *Pool) validateServers(formats strfmt.Registry) error { if err := validate.Required("servers", "body", m.Servers); err != nil { @@ -195,6 +219,10 @@ func (m *Pool) ContextValidate(ctx context.Context, formats strfmt.Registry) err res = append(res, err) } + if err := m.contextValidateSecurityContext(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateTolerations(ctx, formats); err != nil { res = append(res, err) } @@ -237,6 +265,20 @@ func (m *Pool) contextValidateResources(ctx context.Context, formats strfmt.Regi return nil } +func (m *Pool) contextValidateSecurityContext(ctx context.Context, formats strfmt.Registry) error { + + if m.SecurityContext != nil { + if err := m.SecurityContext.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("securityContext") + } + return err + } + } + + return nil +} + func (m *Pool) contextValidateTolerations(ctx context.Context, formats strfmt.Registry) error { if err := m.Tolerations.ContextValidate(ctx, formats); err != nil { diff --git a/models/security_context.go b/models/security_context.go index 08d670110..83fabb2cc 100644 --- a/models/security_context.go +++ b/models/security_context.go @@ -38,11 +38,11 @@ type SecurityContext struct { // fs group // Required: true - FsGroup *int64 `json:"fsGroup"` + FsGroup *string `json:"fsGroup"` // run as group // Required: true - RunAsGroup *int64 `json:"runAsGroup"` + RunAsGroup *string `json:"runAsGroup"` // run as non root // Required: true @@ -50,7 +50,7 @@ type SecurityContext struct { // run as user // Required: true - RunAsUser *int64 `json:"runAsUser"` + RunAsUser *string `json:"runAsUser"` } // Validate validates this security context diff --git a/operatorapi/embedded_spec.go b/operatorapi/embedded_spec.go index f0d00e3d6..8fd0f82b8 100644 --- a/operatorapi/embedded_spec.go +++ b/operatorapi/embedded_spec.go @@ -1732,6 +1732,10 @@ func init() { "replicas": { "type": "string" }, + "securityContext": { + "type": "object", + "$ref": "#/definitions/securityContext" + }, "server": { "type": "object", "$ref": "#/definitions/keyPairConfiguration" @@ -2136,6 +2140,14 @@ func init() { "postgres_init_image": { "type": "string" }, + "postgres_securityContext": { + "type": "object", + "$ref": "#/definitions/securityContext" + }, + "securityContext": { + "type": "object", + "$ref": "#/definitions/securityContext" + }, "storageClass": { "type": "string", "default": "" @@ -2445,6 +2457,10 @@ func init() { "resources": { "$ref": "#/definitions/poolResources" }, + "securityContext": { + "type": "object", + "$ref": "#/definitions/securityContext" + }, "servers": { "type": "integer" }, @@ -2778,19 +2794,16 @@ func init() { ], "properties": { "fsGroup": { - "type": "integer", - "format": "int64" + "type": "string" }, "runAsGroup": { - "type": "integer", - "format": "int64" + "type": "string" }, "runAsNonRoot": { "type": "boolean" }, "runAsUser": { - "type": "integer", - "format": "int64" + "type": "string" } } }, @@ -5570,6 +5583,10 @@ func init() { "replicas": { "type": "string" }, + "securityContext": { + "type": "object", + "$ref": "#/definitions/securityContext" + }, "server": { "type": "object", "$ref": "#/definitions/keyPairConfiguration" @@ -5962,6 +5979,14 @@ func init() { "postgres_init_image": { "type": "string" }, + "postgres_securityContext": { + "type": "object", + "$ref": "#/definitions/securityContext" + }, + "securityContext": { + "type": "object", + "$ref": "#/definitions/securityContext" + }, "storageClass": { "type": "string", "default": "" @@ -6205,6 +6230,10 @@ func init() { "resources": { "$ref": "#/definitions/poolResources" }, + "securityContext": { + "type": "object", + "$ref": "#/definitions/securityContext" + }, "servers": { "type": "integer" }, @@ -6469,19 +6498,16 @@ func init() { ], "properties": { "fsGroup": { - "type": "integer", - "format": "int64" + "type": "string" }, "runAsGroup": { - "type": "integer", - "format": "int64" + "type": "string" }, "runAsNonRoot": { "type": "boolean" }, "runAsUser": { - "type": "integer", - "format": "int64" + "type": "string" } } }, diff --git a/operatorapi/operator_tenants.go b/operatorapi/operator_tenants.go index 11aa3e9cc..5815bf860 100644 --- a/operatorapi/operator_tenants.go +++ b/operatorapi/operator_tenants.go @@ -1147,6 +1147,14 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre minInst.Spec.KES.Labels = tenantReq.Encryption.Labels minInst.Spec.KES.Annotations = tenantReq.Encryption.Annotations minInst.Spec.KES.NodeSelector = tenantReq.Encryption.NodeSelector + + if tenantReq.Encryption.SecurityContext != nil { + sc, err := parseSecurityContext(tenantReq.Encryption.SecurityContext) + if err != nil { + return nil, prepareError(err) + } + minInst.Spec.KES.SecurityContext = sc + } } // External TLS CA certificates for MinIO if tenantReq.TLS != nil && len(tenantReq.TLS.CaCertificates) > 0 { @@ -1229,6 +1237,8 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre logSearchImage := "" logSearchPgImage := "" logSearchPgInitImage := "" + var logSearchSecurityContext *corev1.PodSecurityContext + var logSearchPgSecurityContext *corev1.PodSecurityContext if tenantReq.LogSearchConfiguration != nil { if tenantReq.LogSearchConfiguration.StorageSize != nil { @@ -1249,6 +1259,22 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre if tenantReq.LogSearchConfiguration.PostgresInitImage != "" { logSearchPgInitImage = tenantReq.LogSearchConfiguration.PostgresInitImage } + // if security context for logSearch is present, configure it. + if tenantReq.LogSearchConfiguration.SecurityContext != nil { + sc, err := parseSecurityContext(tenantReq.LogSearchConfiguration.SecurityContext) + if err != nil { + return nil, prepareError(err) + } + logSearchSecurityContext = sc + } + // if security context for logSearch is present, configure it. + if tenantReq.LogSearchConfiguration.PostgresSecurityContext != nil { + sc, err := parseSecurityContext(tenantReq.LogSearchConfiguration.PostgresSecurityContext) + if err != nil { + return nil, prepareError(err) + } + logSearchPgSecurityContext = sc + } } logSearchDiskSpace := resource.NewQuantity(diskSpaceFromAPI, resource.DecimalExponent) @@ -1290,6 +1316,12 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre if logSearchPgInitImage != "" { minInst.Spec.Log.Db.InitImage = logSearchPgInitImage } + if logSearchSecurityContext != nil { + minInst.Spec.Log.SecurityContext = logSearchSecurityContext + } + if logSearchPgSecurityContext != nil { + minInst.Spec.Log.Db.SecurityContext = logSearchPgSecurityContext + } prometheusDiskSpace := 5 // Default is 5 by API prometheusStorageClass := "" // Default is "" @@ -1336,13 +1368,11 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre } // if security context for prometheus is present, configure it. if tenantReq.PrometheusConfiguration != nil && tenantReq.PrometheusConfiguration.SecurityContext != nil { - sc := tenantReq.PrometheusConfiguration.SecurityContext - minInst.Spec.Prometheus.SecurityContext = &corev1.PodSecurityContext{ - RunAsUser: sc.RunAsUser, - RunAsGroup: sc.RunAsGroup, - RunAsNonRoot: sc.RunAsNonRoot, - FSGroup: sc.FsGroup, + sc, err := parseSecurityContext(tenantReq.PrometheusConfiguration.SecurityContext) + if err != nil { + return nil, prepareError(err) } + minInst.Spec.Prometheus.SecurityContext = sc } // expose services @@ -1912,6 +1942,14 @@ func parseTenantPoolRequest(poolParams *models.Pool) (*miniov2.Pool, error) { Affinity: affinity, Tolerations: tolerations, } + // if security context for Tenant is present, configure it. + if poolParams.SecurityContext != nil { + sc, err := parseSecurityContext(poolParams.SecurityContext) + if err != nil { + return nil, err + } + pool.SecurityContext = sc + } return pool, nil } diff --git a/operatorapi/operator_tenants_helper.go b/operatorapi/operator_tenants_helper.go index dda9c8b2d..10f238ca4 100644 --- a/operatorapi/operator_tenants_helper.go +++ b/operatorapi/operator_tenants_helper.go @@ -38,6 +38,31 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// parseSecurityContext validate and return securityContext for pods +func parseSecurityContext(sc *models.SecurityContext) (*corev1.PodSecurityContext, error) { + if sc == nil { + return nil, errors.New("invalid security context") + } + runAsUser, err := strconv.ParseInt(*sc.RunAsUser, 10, 64) + if err != nil { + return nil, err + } + RunAsGroup, err := strconv.ParseInt(*sc.RunAsGroup, 10, 64) + if err != nil { + return nil, err + } + FsGroup, err := strconv.ParseInt(*sc.FsGroup, 10, 64) + if err != nil { + return nil, err + } + return &corev1.PodSecurityContext{ + RunAsUser: &runAsUser, + RunAsGroup: &RunAsGroup, + RunAsNonRoot: sc.RunAsNonRoot, + FSGroup: &FsGroup, + }, nil +} + // tenantUpdateCertificates receives the keyPair certificates (public and private keys) for Minio and Console and will try // to replace the existing kubernetes secrets with the new values, then will restart the affected pods so the new volumes can be mounted func tenantUpdateCertificates(ctx context.Context, operatorClient OperatorClientI, clientSet K8sClientI, namespace string, params operator_api.TenantUpdateCertificateParams) error { diff --git a/portal-ui/src/common/types.ts b/portal-ui/src/common/types.ts index 1d0fd108d..401e9f7d8 100644 --- a/portal-ui/src/common/types.ts +++ b/portal-ui/src/common/types.ts @@ -14,7 +14,10 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import { ILabelKeyPair } from "../screens/Console/Tenants/types"; +import { + ILabelKeyPair, + ISecurityContext, +} from "../screens/Console/Tenants/types"; export interface ITenantsObject { tenants: ITenant[]; @@ -323,6 +326,7 @@ export interface IPoolModel { affinity?: IAffinityModel; tolerations?: ITolerationModel[]; resources?: IResourceModel; + securityContext?: ISecurityContext | null; } export interface IUpdatePool { @@ -370,6 +374,8 @@ export interface LogSearchConfiguration { image: string; postgres_image: string; postgres_init_image: string; + securityContext?: ISecurityContext; + postgres_securityContext?: ISecurityContext; } export interface PrometheusConfiguration { @@ -378,6 +384,7 @@ export interface PrometheusConfiguration { image: string; sidecar_image: string; init_image: string; + securityContext?: ISecurityContext; } export interface AffinityConfiguration { diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx index be1336362..129fbac39 100644 --- a/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx @@ -39,7 +39,7 @@ import { getDefaultAffinity, getNodeSelector } from "../TenantDetails/utils"; import CredentialsPrompt from "../../Common/CredentialsPrompt/CredentialsPrompt"; import NameTenant from "./Steps/NameTenant"; import { AppState } from "../../../../store"; -import { ICertificatesItems, IFieldStore } from "../types"; +import { ICertificatesItems, IFieldStore, ISecurityContext } from "../types"; import { resetAddTenantForm, updateAddField } from "../actions"; import Configure from "./Steps/Configure"; import IdentityProvider from "./Steps/IdentityProvider"; @@ -172,6 +172,7 @@ const AddTenant = ({ const ecParity = fields.tenantSize.ecParity; const distribution = fields.tenantSize.distribution; const memorySize = fields.tenantSize.memorySize; + const tenantCustom = fields.configure.tenantCustom; const logSearchCustom = fields.configure.logSearchCustom; const prometheusCustom = fields.configure.prometheusCustom; const logSearchVolumeSize = fields.configure.logSearchVolumeSize; @@ -192,6 +193,15 @@ const AddTenant = ({ const nodeSelectorLabels = fields.affinity.nodeSelectorLabels; const withPodAntiAffinity = fields.affinity.withPodAntiAffinity; + const tenantSecurityContext = fields.configure.tenantSecurityContext; + const logSearchSecurityContext = fields.configure.logSearchSecurityContext; + const logSearchPostgresSecurityContext = + fields.configure.logSearchPostgresSecurityContext; + const prometheusSecurityContext = + fields.configure.prometheusSecurityContext; + const kesSecurityContext = fields.encryption.kesSecurityContext; + const kesReplicas = fields.encryption.replicas; + if (addSending) { const poolName = generatePoolName([]); @@ -248,6 +258,7 @@ const AddTenant = ({ memory: memorySize.limit, }, }, + securityContext: tenantCustom ? tenantSecurityContext : null, ...affinityObject, }, ], @@ -274,6 +285,8 @@ const AddTenant = ({ image: logSearchImage, postgres_image: logSearchPostgresImage, postgres_init_image: logSearchPostgresInitImage, + securityContext: logSearchSecurityContext, + postgres_securityContext: logSearchPostgresSecurityContext, }, }; } else { @@ -296,6 +309,7 @@ const AddTenant = ({ image: prometheusImage, sidecar_image: prometheusSidecarImage, init_image: prometheusInitImage, + securityContext: prometheusSecurityContext, }, }; } else { @@ -517,6 +531,8 @@ const AddTenant = ({ dataSend = { ...dataSend, encryption: { + replicas: kesReplicas, + securityContext: kesSecurityContext, image: kesImage, ...encryptionClientKeyPair, ...encryptionServerKeyPair, @@ -626,14 +642,22 @@ const AddTenant = ({ history.push("/tenants"); }, }; - + const requiredPages = [ + "nameTenant", + "tenantSize", + "configure", + "affinity", + "identityProvider", + "security", + "encryption", + ]; const createButton = { label: "Create", type: "submit", enabled: !addSending && selectedStorageClass !== "" && - validPages.includes("tenantSize"), + requiredPages.every((v) => validPages.includes(v)), action: () => { setAddSending(true); }, diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Affinity.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Affinity.tsx index 19c3533c9..0fd4e576b 100644 --- a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Affinity.tsx +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Affinity.tsx @@ -135,9 +135,6 @@ const Affinity = ({ .map((kvp) => `${kvp.key}=${kvp.value}`) .filter((kvs, i, a) => a.indexOf(kvs) === i); const vl = vlr.join("&"); - - console.log(vl); - updateField("nodeSelectorLabels", vl); } }, [keyValuePairs, updateField]); diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Configure.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Configure.tsx index dcced5cc7..df212876a 100644 --- a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Configure.tsx +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Configure.tsx @@ -32,6 +32,7 @@ import { import FormSwitchWrapper from "../../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper"; import InputBoxWrapper from "../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; import SelectWrapper from "../../../Common/FormComponents/SelectWrapper/SelectWrapper"; +import { ISecurityContext } from "../../types"; interface IConfigureProps { updateAddField: typeof updateAddField; @@ -47,6 +48,7 @@ interface IConfigureProps { exposeMinIO: boolean; exposeConsole: boolean; prometheusCustom: boolean; + tenantCustom: boolean; logSearchCustom: boolean; logSearchVolumeSize: string; logSearchSizeFactor: string; @@ -62,6 +64,10 @@ interface IConfigureProps { prometheusSidecarImage: string; prometheusInitImage: string; selectedStorageClass: string; + tenantSecurityContext: ISecurityContext; + logSearchSecurityContext: ISecurityContext; + logSearchPostgresSecurityContext: ISecurityContext; + prometheusSecurityContext: ISecurityContext; } const styles = (theme: Theme) => @@ -85,6 +91,7 @@ const Configure = ({ exposeMinIO, exposeConsole, prometheusCustom, + tenantCustom, logSearchCustom, logSearchVolumeSize, logSearchSizeFactor, @@ -102,6 +109,10 @@ const Configure = ({ updateAddField, isPageValid, selectedStorageClass, + tenantSecurityContext, + logSearchSecurityContext, + logSearchPostgresSecurityContext, + prometheusSecurityContext, }: IConfigureProps) => { const [validationErrors, setValidationErrors] = useState({}); @@ -116,7 +127,38 @@ const Configure = ({ // Validation useEffect(() => { let customAccountValidation: IValidation[] = []; - + if (tenantCustom) { + customAccountValidation = [ + ...customAccountValidation, + { + fieldKey: "tenant_securityContext_runAsUser", + required: true, + value: tenantSecurityContext.runAsUser, + customValidation: + tenantSecurityContext.runAsUser === "" || + parseInt(tenantSecurityContext.runAsUser) < 0, + customValidationMessage: `runAsUser must be present and be 0 or more`, + }, + { + fieldKey: "tenant_securityContext_runAsGroup", + required: true, + value: tenantSecurityContext.runAsGroup, + customValidation: + tenantSecurityContext.runAsGroup === "" || + parseInt(tenantSecurityContext.runAsGroup) < 0, + customValidationMessage: `runAsGroup must be present and be 0 or more`, + }, + { + fieldKey: "tenant_securityContext_fsGroup", + required: true, + value: tenantSecurityContext.fsGroup, + customValidation: + tenantSecurityContext.fsGroup === "" || + parseInt(tenantSecurityContext.fsGroup) < 0, + customValidationMessage: `fsGroup must be present and be 0 or more`, + }, + ]; + } if (prometheusCustom) { customAccountValidation = [ ...customAccountValidation, @@ -133,7 +175,34 @@ const Configure = ({ value: prometheusVolumeSize, customValidation: prometheusVolumeSize === "" || parseInt(prometheusVolumeSize) <= 0, - customValidationMessage: `Volume size must be present and be greatter than 0`, + customValidationMessage: `Volume size must be present and be greater than 0`, + }, + { + fieldKey: "prometheus_securityContext_runAsUser", + required: true, + value: prometheusSecurityContext.runAsUser, + customValidation: + prometheusSecurityContext.runAsUser === "" || + parseInt(prometheusSecurityContext.runAsUser) < 0, + customValidationMessage: `runAsUser must be present and be 0 or more`, + }, + { + fieldKey: "prometheus_securityContext_runAsGroup", + required: true, + value: prometheusSecurityContext.runAsGroup, + customValidation: + prometheusSecurityContext.runAsGroup === "" || + parseInt(prometheusSecurityContext.runAsGroup) < 0, + customValidationMessage: `runAsGroup must be present and be 0 or more`, + }, + { + fieldKey: "prometheus_securityContext_fsGroup", + required: true, + value: prometheusSecurityContext.fsGroup, + customValidation: + prometheusSecurityContext.fsGroup === "" || + parseInt(prometheusSecurityContext.fsGroup) < 0, + customValidationMessage: `fsGroup must be present and be 0 or more`, }, ]; } @@ -155,6 +224,60 @@ const Configure = ({ logSearchVolumeSize === "" || parseInt(logSearchVolumeSize) <= 0, customValidationMessage: `Volume size must be present and be greatter than 0`, }, + { + fieldKey: "logSearch_securityContext_runAsUser", + required: true, + value: logSearchSecurityContext.runAsUser, + customValidation: + logSearchSecurityContext.runAsUser === "" || + parseInt(logSearchSecurityContext.runAsUser) < 0, + customValidationMessage: `runAsUser must be present and be 0 or more`, + }, + { + fieldKey: "logSearch_securityContext_runAsGroup", + required: true, + value: logSearchSecurityContext.runAsGroup, + customValidation: + logSearchSecurityContext.runAsGroup === "" || + parseInt(logSearchSecurityContext.runAsGroup) < 0, + customValidationMessage: `runAsGroup must be present and be 0 or more`, + }, + { + fieldKey: "logSearch_securityContext_fsGroup", + required: true, + value: logSearchSecurityContext.fsGroup, + customValidation: + logSearchSecurityContext.fsGroup === "" || + parseInt(logSearchSecurityContext.fsGroup) < 0, + customValidationMessage: `fsGroup must be present and be 0 or more`, + }, + { + fieldKey: "postgres_securityContext_runAsUser", + required: true, + value: logSearchPostgresSecurityContext.runAsUser, + customValidation: + logSearchPostgresSecurityContext.runAsUser === "" || + parseInt(logSearchPostgresSecurityContext.runAsUser) < 0, + customValidationMessage: `runAsUser must be present and be 0 or more`, + }, + { + fieldKey: "postgres_securityContext_runAsGroup", + required: true, + value: prometheusSecurityContext.runAsGroup, + customValidation: + logSearchPostgresSecurityContext.runAsGroup === "" || + parseInt(logSearchPostgresSecurityContext.runAsGroup) < 0, + customValidationMessage: `runAsGroup must be present and be 0 or more`, + }, + { + fieldKey: "postgres_securityContext_fsGroup", + required: true, + value: logSearchPostgresSecurityContext.fsGroup, + customValidation: + logSearchPostgresSecurityContext.fsGroup === "" || + parseInt(logSearchPostgresSecurityContext.fsGroup) < 0, + customValidationMessage: `fsGroup must be present and be 0 or more`, + }, ]; } @@ -267,11 +390,16 @@ const Configure = ({ imageRegistryPassword, isPageValid, prometheusCustom, + tenantCustom, logSearchCustom, prometheusSelectedStorageClass, prometheusVolumeSize, logSearchSelectedStorageClass, logSearchVolumeSize, + tenantSecurityContext, + logSearchSecurityContext, + logSearchPostgresSecurityContext, + prometheusSecurityContext, ]); useEffect(() => { @@ -351,10 +479,121 @@ const Configure = ({

Additional Configurations

- Configure Storage Classes & Storage size for Log Search and Prometheus - add-ons + Configure SecurityContext, Storage Classes & Storage size for Log + Search, Prometheus add-ons and your Tenant
+ + { + const targetD = e.target; + const checked = targetD.checked; + + updateField("tenantCustom", checked); + }} + label={"Override Tenant defaults"} + /> + + {tenantCustom && ( + + + SecurityContext for MinIO + +
+
+ +
+
+ ) => { + updateField("tenantSecurityContext", { + ...tenantSecurityContext, + runAsUser: e.target.value, + }); + cleanValidation("tenant_securityContext_runAsUser"); + }} + label="Run As User" + value={tenantSecurityContext.runAsUser} + required + error={ + validationErrors["tenant_securityContext_runAsUser"] || "" + } + min="0" + /> +
+
+ ) => { + updateField("tenantSecurityContext", { + ...tenantSecurityContext, + runAsGroup: e.target.value, + }); + cleanValidation("tenant_securityContext_runAsGroup"); + }} + label="Run As Group" + value={tenantSecurityContext.runAsGroup} + required + error={ + validationErrors["tenant_securityContext_runAsGroup"] || "" + } + min="0" + /> +
+
+ ) => { + updateField("tenantSecurityContext", { + ...tenantSecurityContext, + fsGroup: e.target.value, + }); + cleanValidation("tenant_securityContext_fsGroup"); + }} + label="FsGroup" + value={tenantSecurityContext.fsGroup} + required + error={ + validationErrors["tenant_securityContext_fsGroup"] || "" + } + min="0" + /> +
+
+
+
+ +
+ { + const targetD = e.target; + const checked = targetD.checked; + updateField("tenantSecurityContext", { + ...tenantSecurityContext, + runAsNonRoot: checked, + }); + }} + label={"Do not run as Root"} + /> +
+
+
+ )} -
+ + + SecurityContext for LogSearch + +
+
+ +
+
+ ) => { + updateField("logSearchSecurityContext", { + ...logSearchSecurityContext, + runAsUser: e.target.value, + }); + cleanValidation("logSearch_securityContext_runAsUser"); + }} + label="Run As User" + value={logSearchSecurityContext.runAsUser} + required + error={ + validationErrors["logSearch_securityContext_runAsUser"] || + "" + } + min="0" + /> +
+
+ ) => { + updateField("logSearchSecurityContext", { + ...logSearchSecurityContext, + runAsGroup: e.target.value, + }); + cleanValidation("logSearch_securityContext_runAsGroup"); + }} + label="Run As Group" + value={logSearchSecurityContext.runAsGroup} + required + error={ + validationErrors[ + "logSearch_securityContext_runAsGroup" + ] || "" + } + min="0" + /> +
+
+ ) => { + updateField("logSearchSecurityContext", { + ...logSearchSecurityContext, + fsGroup: e.target.value, + }); + cleanValidation("logSearch_securityContext_fsGroup"); + }} + label="FsGroup" + value={logSearchSecurityContext.fsGroup} + required + error={ + validationErrors["logSearch_securityContext_fsGroup"] || + "" + } + min="0" + /> +
+
+
+
+ +
+ { + const targetD = e.target; + const checked = targetD.checked; + updateField("logSearchSecurityContext", { + ...logSearchSecurityContext, + runAsNonRoot: checked, + }); + }} + label={"Do not run as Root"} + /> +
+
+
+ + + SecurityContext for PostgreSQL + +
+
+ +
+
+ ) => { + updateField("logSearchPostgresSecurityContext", { + ...logSearchPostgresSecurityContext, + runAsUser: e.target.value, + }); + cleanValidation("postgres_securityContext_runAsUser"); + }} + label="Run As User" + value={logSearchPostgresSecurityContext.runAsUser} + required + error={ + validationErrors["postgres_securityContext_runAsUser"] || + "" + } + min="0" + /> +
+
+ ) => { + updateField("logSearchPostgresSecurityContext", { + ...logSearchPostgresSecurityContext, + runAsGroup: e.target.value, + }); + cleanValidation("postgres_securityContext_runAsGroup"); + }} + label="Run As Group" + value={logSearchPostgresSecurityContext.runAsGroup} + required + error={ + validationErrors["postgres_securityContext_runAsGroup"] || + "" + } + min="0" + /> +
+
+ ) => { + updateField("logSearchPostgresSecurityContext", { + ...logSearchPostgresSecurityContext, + fsGroup: e.target.value, + }); + cleanValidation("postgres_securityContext_fsGroup"); + }} + label="FsGroup" + value={logSearchPostgresSecurityContext.fsGroup} + required + error={ + validationErrors["postgres_securityContext_fsGroup"] || "" + } + min="0" + /> +
+
+
+
+ +
+ { + const targetD = e.target; + const checked = targetD.checked; + updateField("logSearchPostgresSecurityContext", { + ...logSearchPostgresSecurityContext, + runAsNonRoot: checked, + }); + }} + label={"Do not run as Root"} + /> +
+
+
)} @@ -464,7 +896,105 @@ const Configure = ({ -
+ + + SecurityContext for Prometheus + +
+
+ +
+
+ ) => { + updateField("prometheusSecurityContext", { + ...prometheusSecurityContext, + runAsUser: e.target.value, + }); + cleanValidation("prometheus_securityContext_runAsUser"); + }} + label="Run As User" + value={prometheusSecurityContext.runAsUser} + required + error={ + validationErrors[ + "prometheus_securityContext_runAsUser" + ] || "" + } + min="0" + /> +
+
+ ) => { + updateField("prometheusSecurityContext", { + ...prometheusSecurityContext, + runAsGroup: e.target.value, + }); + cleanValidation("prometheus_securityContext_runAsGroup"); + }} + label="Run As Group" + value={prometheusSecurityContext.runAsGroup} + required + error={ + validationErrors[ + "prometheus_securityContext_runAsGroup" + ] || "" + } + min="0" + /> +
+
+ ) => { + updateField("prometheusSecurityContext", { + ...prometheusSecurityContext, + fsGroup: e.target.value, + }); + cleanValidation("prometheus_securityContext_fsGroup"); + }} + label="FsGroup" + value={prometheusSecurityContext.fsGroup} + required + error={ + validationErrors["prometheus_securityContext_fsGroup"] || + "" + } + min="0" + /> +
+
+
+
+ +
+ { + const targetD = e.target; + const checked = targetD.checked; + updateField("prometheusSecurityContext", { + ...prometheusSecurityContext, + runAsNonRoot: checked, + }); + }} + label={"Do not run as Root"} + /> +
+
+
)} @@ -485,6 +1015,7 @@ const mapState = (state: AppState) => ({ exposeConsole: state.tenants.createTenant.fields.configure.exposeConsole, prometheusCustom: state.tenants.createTenant.fields.configure.prometheusCustom, + tenantCustom: state.tenants.createTenant.fields.configure.tenantCustom, logSearchCustom: state.tenants.createTenant.fields.configure.logSearchCustom, logSearchVolumeSize: state.tenants.createTenant.fields.configure.logSearchVolumeSize, @@ -511,6 +1042,15 @@ const mapState = (state: AppState) => ({ state.tenants.createTenant.fields.configure.prometheusInitImage, selectedStorageClass: state.tenants.createTenant.fields.nameTenant.selectedStorageClass, + tenantSecurityContext: + state.tenants.createTenant.fields.configure.tenantSecurityContext, + logSearchSecurityContext: + state.tenants.createTenant.fields.configure.logSearchSecurityContext, + logSearchPostgresSecurityContext: + state.tenants.createTenant.fields.configure + .logSearchPostgresSecurityContext, + prometheusSecurityContext: + state.tenants.createTenant.fields.configure.prometheusSecurityContext, }); const connector = connect(mapState, { diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Encryption.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Encryption.tsx index 9c05bda6e..578175655 100644 --- a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Encryption.tsx +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Encryption.tsx @@ -43,6 +43,7 @@ import { IValidation, } from "../../../../../utils/validationFunctions"; import { KeyPair } from "../../ListTenants/utils"; +import { ISecurityContext } from "../../types"; interface IEncryptionProps { classes: any; @@ -94,6 +95,8 @@ interface IEncryptionProps { vaultCertificate: KeyPair; vaultCA: KeyPair; gemaltoCA: KeyPair; + kesSecurityContext: ISecurityContext; + replicas: string; } const styles = (theme: Theme) => @@ -155,6 +158,8 @@ const Encryption = ({ vaultCertificate, vaultCA, gemaltoCA, + kesSecurityContext, + replicas, }: IEncryptionProps) => { const [validationErrors, setValidationErrors] = useState({}); @@ -187,6 +192,44 @@ const Encryption = ({ let encryptionValidation: IValidation[] = []; if (enableEncryption) { + encryptionValidation = [ + ...encryptionValidation, + { + fieldKey: "replicas", + required: true, + value: replicas, + customValidation: parseInt(replicas) < 1, + customValidationMessage: "Replicas needs to be 1 or greater", + }, + { + fieldKey: "kes_securityContext_runAsUser", + required: true, + value: kesSecurityContext.runAsUser, + customValidation: + kesSecurityContext.runAsUser === "" || + parseInt(kesSecurityContext.runAsUser) < 0, + customValidationMessage: `runAsUser must be present and be 0 or more`, + }, + { + fieldKey: "kes_securityContext_runAsGroup", + required: true, + value: kesSecurityContext.runAsGroup, + customValidation: + kesSecurityContext.runAsGroup === "" || + parseInt(kesSecurityContext.runAsGroup) < 0, + customValidationMessage: `runAsGroup must be present and be 0 or more`, + }, + { + fieldKey: "kes_securityContext_fsGroup", + required: true, + value: kesSecurityContext.fsGroup, + customValidation: + kesSecurityContext.fsGroup === "" || + parseInt(kesSecurityContext.fsGroup) < 0, + customValidationMessage: `fsGroup must be present and be 0 or more`, + }, + ]; + if (enableCustomCerts) { encryptionValidation = [ ...encryptionValidation, @@ -368,6 +411,8 @@ const Encryption = ({ serverCertificate.encoded_cert, clientCertificate.encoded_key, clientCertificate.encoded_cert, + kesSecurityContext, + replicas, ]); return ( @@ -961,6 +1006,122 @@ const Encryption = ({ )} +
+

Additional Configurations

+
+ + + + ) => { + updateField("replicas", e.target.value); + cleanValidation("replicas"); + }} + label="Replicas" + value={replicas} + required + error={validationErrors["replicas"] || ""} + /> + + + + SecurityContext for KES pods + + +
+ +
+
+ ) => { + updateField("kesSecurityContext", { + ...kesSecurityContext, + runAsUser: e.target.value, + }); + cleanValidation("kes_securityContext_runAsUser"); + }} + label="Run As User" + value={kesSecurityContext.runAsUser} + required + error={ + validationErrors["kes_securityContext_runAsUser"] || "" + } + min="0" + /> +
+
+ ) => { + updateField("kesSecurityContext", { + ...kesSecurityContext, + runAsGroup: e.target.value, + }); + cleanValidation("kes_securityContext_runAsGroup"); + }} + label="Run As Group" + value={kesSecurityContext.runAsGroup} + required + error={ + validationErrors["kes_securityContext_runAsGroup"] || "" + } + min="0" + /> +
+
+ ) => { + updateField("kesSecurityContext", { + ...kesSecurityContext, + fsGroup: e.target.value, + }); + cleanValidation("kes_securityContext_fsGroup"); + }} + label="FsGroup" + value={kesSecurityContext.fsGroup} + required + error={ + validationErrors["kes_securityContext_fsGroup"] || "" + } + min="0" + /> +
+
+
+
+ +
+ { + const targetD = e.target; + const checked = targetD.checked; + updateField("kesSecurityContext", { + ...kesSecurityContext, + runAsNonRoot: checked, + }); + }} + label={"Do not run as Root"} + /> +
+
+
+
)} @@ -968,6 +1129,7 @@ const Encryption = ({ }; const mapState = (state: AppState) => ({ + replicas: state.tenants.createTenant.fields.encryption.replicas, enableEncryption: state.tenants.createTenant.fields.encryption.enableEncryption, encryptionType: state.tenants.createTenant.fields.encryption.encryptionType, @@ -1014,6 +1176,8 @@ const mapState = (state: AppState) => ({ gemaltoCA: state.tenants.createTenant.certificates.gemaltoCA, enableCustomCerts: state.tenants.createTenant.fields.security.enableCustomCerts, + kesSecurityContext: + state.tenants.createTenant.fields.encryption.kesSecurityContext, }); const connector = connect(mapState, { diff --git a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Images.tsx b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Images.tsx index 491779c01..0067595b5 100644 --- a/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Images.tsx +++ b/portal-ui/src/screens/Console/Tenants/AddTenant/Steps/Images.tsx @@ -46,6 +46,7 @@ interface IImagesProps { exposeMinIO: boolean; exposeConsole: boolean; prometheusCustom: boolean; + tenantCustom: boolean; logSearchCustom: boolean; logSearchVolumeSize: string; logSearchSizeFactor: string; @@ -84,6 +85,7 @@ const Images = ({ exposeMinIO, exposeConsole, prometheusCustom, + tenantCustom, logSearchCustom, logSearchVolumeSize, logSearchSizeFactor, @@ -266,6 +268,7 @@ const Images = ({ imageRegistryPassword, isPageValid, prometheusCustom, + tenantCustom, logSearchCustom, prometheusSelectedStorageClass, prometheusVolumeSize, @@ -507,6 +510,7 @@ const mapState = (state: AppState) => ({ exposeConsole: state.tenants.createTenant.fields.configure.exposeConsole, prometheusCustom: state.tenants.createTenant.fields.configure.prometheusCustom, + tenantCustom: state.tenants.createTenant.fields.configure.tenantCustom, logSearchCustom: state.tenants.createTenant.fields.configure.logSearchCustom, logSearchVolumeSize: state.tenants.createTenant.fields.configure.logSearchVolumeSize, diff --git a/portal-ui/src/screens/Console/Tenants/reducer.ts b/portal-ui/src/screens/Console/Tenants/reducer.ts index 04e97c6cd..4e178d7e4 100644 --- a/portal-ui/src/screens/Console/Tenants/reducer.ts +++ b/portal-ui/src/screens/Console/Tenants/reducer.ts @@ -43,6 +43,7 @@ import { TENANT_DETAILS_SET_CURRENT_TENANT, TENANT_DETAILS_SET_TENANT, TENANT_DETAILS_SET_TAB, + ISecurityContext, } from "./types"; import { KeyPair } from "./ListTenants/utils"; import { getRandomString } from "./utils"; @@ -50,7 +51,16 @@ import { getRandomString } from "./utils"; const initialState: ITenantState = { createTenant: { page: 0, - validPages: [], + // We can assume all the other pages are valid with default configuration except for 'nameTenant' + // because the user still have to choose a namespace and a name for the tenant + validPages: [ + "tenantSize", + "configure", + "affinity", + "identityProvider", + "security", + "encryption", + ], advancedModeOn: false, storageClasses: [], limitSize: {}, @@ -69,6 +79,7 @@ const initialState: ITenantState = { imageRegistryPassword: "", exposeMinIO: true, exposeConsole: true, + tenantCustom: false, logSearchCustom: false, prometheusCustom: false, logSearchVolumeSize: "5", @@ -84,6 +95,30 @@ const initialState: ITenantState = { prometheusImage: "", prometheusSidecarImage: "", prometheusInitImage: "", + tenantSecurityContext: { + runAsUser: "1000", + runAsGroup: "1000", + fsGroup: "1000", + runAsNonRoot: true, + }, + logSearchSecurityContext: { + runAsUser: "1000", + runAsGroup: "1000", + fsGroup: "1000", + runAsNonRoot: true, + }, + logSearchPostgresSecurityContext: { + runAsUser: "999", + runAsGroup: "999", + fsGroup: "999", + runAsNonRoot: true, + }, + prometheusSecurityContext: { + runAsUser: "1000", + runAsGroup: "1000", + fsGroup: "1000", + runAsNonRoot: true, + }, }, identityProvider: { idpSelection: "Built-in", @@ -148,6 +183,13 @@ const initialState: ITenantState = { gcpPrivateKeyID: "", gcpPrivateKey: "", enableCustomCertsForKES: false, + replicas: "1", + kesSecurityContext: { + runAsUser: "1000", + runAsGroup: "1000", + fsGroup: "1000", + runAsNonRoot: true, + }, }, tenantSize: { volumeSize: "100", @@ -523,7 +565,16 @@ export function tenantsReducer( ...state, createTenant: { page: 0, - validPages: [], + // We can assume all the other pages are valid with default configuration except for 'nameTenant' + // because the user still have to choose a namespace and a name for the tenant + validPages: [ + "tenantSize", + "configure", + "affinity", + "identityProvider", + "security", + "encryption", + ], advancedModeOn: false, storageClasses: [], limitSize: {}, @@ -542,6 +593,7 @@ export function tenantsReducer( imageRegistryPassword: "", exposeMinIO: true, exposeConsole: true, + tenantCustom: false, logSearchCustom: false, prometheusCustom: false, logSearchVolumeSize: "5", @@ -557,6 +609,30 @@ export function tenantsReducer( prometheusImage: "", prometheusSidecarImage: "", prometheusInitImage: "", + tenantSecurityContext: { + runAsUser: "1000", + runAsGroup: "1000", + fsGroup: "1000", + runAsNonRoot: true, + }, + logSearchSecurityContext: { + runAsUser: "1000", + runAsGroup: "1000", + fsGroup: "1000", + runAsNonRoot: true, + }, + logSearchPostgresSecurityContext: { + runAsUser: "999", + runAsGroup: "999", + fsGroup: "999", + runAsNonRoot: true, + }, + prometheusSecurityContext: { + runAsUser: "1000", + runAsGroup: "1000", + fsGroup: "1000", + runAsNonRoot: true, + }, }, identityProvider: { idpSelection: "Built-in", @@ -621,6 +697,13 @@ export function tenantsReducer( gcpPrivateKeyID: "", gcpPrivateKey: "", enableCustomCertsForKES: false, + replicas: "1", + kesSecurityContext: { + runAsUser: "1000", + runAsGroup: "1000", + fsGroup: "1000", + runAsNonRoot: true, + }, }, tenantSize: { volumeSize: "100", diff --git a/portal-ui/src/screens/Console/Tenants/types.ts b/portal-ui/src/screens/Console/Tenants/types.ts index e717e9084..9ba82861d 100644 --- a/portal-ui/src/screens/Console/Tenants/types.ts +++ b/portal-ui/src/screens/Console/Tenants/types.ts @@ -121,6 +121,13 @@ export interface INameTenantFields { selectedStorageClass: string; } +export interface ISecurityContext { + runAsUser: string; + runAsGroup: string; + runAsNonRoot: boolean; + fsGroup: string; +} + export interface IConfigureFields { customImage: boolean; imageName: string; @@ -131,6 +138,7 @@ export interface IConfigureFields { exposeMinIO: boolean; exposeConsole: boolean; prometheusCustom: boolean; + tenantCustom: boolean; logSearchCustom: boolean; logSearchVolumeSize: string; logSearchSizeFactor: string; @@ -145,6 +153,10 @@ export interface IConfigureFields { prometheusImage: string; prometheusSidecarImage: string; prometheusInitImage: string; + tenantSecurityContext: ISecurityContext; + logSearchSecurityContext: ISecurityContext; + logSearchPostgresSecurityContext: ISecurityContext; + prometheusSecurityContext: ISecurityContext; } export interface IIdentityProviderFields { @@ -212,6 +224,8 @@ export interface IEncryptionFields { gcpPrivateKeyID: string; gcpPrivateKey: string; enableCustomCertsForKES: boolean; + replicas: string; + kesSecurityContext: ISecurityContext; } export interface ITenantSizeFields { diff --git a/swagger-operator.yml b/swagger-operator.yml index 7c951e916..ef322a521 100644 --- a/swagger-operator.yml +++ b/swagger-operator.yml @@ -1329,6 +1329,12 @@ definitions: default: 5 image: type: string + securityContext: + type: object + $ref: '#/definitions/securityContext' + postgres_securityContext: + type: object + $ref: '#/definitions/securityContext' postgres_image: type: string postgres_init_image: @@ -1452,6 +1458,9 @@ definitions: azure: type: object $ref: "#/definitions/azureConfiguration" + securityContext: + type: object + $ref: '#/definitions/securityContext' vaultConfiguration: type: object @@ -1696,6 +1705,9 @@ definitions: $ref: "#/definitions/poolAffinity" tolerations: $ref: "#/definitions/poolTolerations" + securityContext: + type: object + $ref: '#/definitions/securityContext' poolTolerations: description: Tolerations allows users to set entries like effect, @@ -2332,14 +2344,11 @@ definitions: - fsGroup properties: runAsUser: - type: integer - format: int64 + type: string runAsGroup: - type: integer - format: int64 + type: string runAsNonRoot: type: boolean fsGroup: - type: integer - format: int64 + type: string