Tenant security context component (#2139)
Added edit Security Context component to Tenant Security screen, and updated API and backend to enable editing
This commit is contained in:
@@ -42,6 +42,9 @@ type TenantMonitoringInfo struct {
|
||||
// disk capacity g b
|
||||
DiskCapacityGB string `json:"diskCapacityGB,omitempty"`
|
||||
|
||||
// fs group
|
||||
FsGroup string `json:"fsGroup,omitempty"`
|
||||
|
||||
// image
|
||||
Image string `json:"image,omitempty"`
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ type TenantSecurityResponse struct {
|
||||
|
||||
// custom certificates
|
||||
CustomCertificates *TenantSecurityResponseCustomCertificates `json:"customCertificates,omitempty"`
|
||||
|
||||
// security context
|
||||
SecurityContext *SecurityContext `json:"securityContext,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this tenant security response
|
||||
@@ -51,6 +54,10 @@ func (m *TenantSecurityResponse) Validate(formats strfmt.Registry) error {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateSecurityContext(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
@@ -76,6 +83,25 @@ func (m *TenantSecurityResponse) validateCustomCertificates(formats strfmt.Regis
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *TenantSecurityResponse) 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")
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName("securityContext")
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validate this tenant security response based on the context it is used
|
||||
func (m *TenantSecurityResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
var res []error
|
||||
@@ -84,6 +110,10 @@ func (m *TenantSecurityResponse) ContextValidate(ctx context.Context, formats st
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.contextValidateSecurityContext(ctx, formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
@@ -106,6 +136,22 @@ func (m *TenantSecurityResponse) contextValidateCustomCertificates(ctx context.C
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *TenantSecurityResponse) 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")
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName("securityContext")
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *TenantSecurityResponse) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
|
||||
@@ -41,6 +41,9 @@ type UpdateTenantSecurityRequest struct {
|
||||
|
||||
// custom certificates
|
||||
CustomCertificates *UpdateTenantSecurityRequestCustomCertificates `json:"customCertificates,omitempty"`
|
||||
|
||||
// security context
|
||||
SecurityContext *SecurityContext `json:"securityContext,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this update tenant security request
|
||||
@@ -51,6 +54,10 @@ func (m *UpdateTenantSecurityRequest) Validate(formats strfmt.Registry) error {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.validateSecurityContext(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
@@ -76,6 +83,25 @@ func (m *UpdateTenantSecurityRequest) validateCustomCertificates(formats strfmt.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *UpdateTenantSecurityRequest) 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")
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName("securityContext")
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validate this update tenant security request based on the context it is used
|
||||
func (m *UpdateTenantSecurityRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
var res []error
|
||||
@@ -84,6 +110,10 @@ func (m *UpdateTenantSecurityRequest) ContextValidate(ctx context.Context, forma
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if err := m.contextValidateSecurityContext(ctx, formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
@@ -106,6 +136,22 @@ func (m *UpdateTenantSecurityRequest) contextValidateCustomCertificates(ctx cont
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *UpdateTenantSecurityRequest) 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")
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName("securityContext")
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *UpdateTenantSecurityRequest) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
|
||||
@@ -4274,6 +4274,9 @@ func init() {
|
||||
"diskCapacityGB": {
|
||||
"type": "string"
|
||||
},
|
||||
"fsGroup": {
|
||||
"type": "string"
|
||||
},
|
||||
"image": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -4381,6 +4384,10 @@ func init() {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityContext": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/securityContext"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4556,6 +4563,10 @@ func init() {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityContext": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/securityContext"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -9655,6 +9666,9 @@ func init() {
|
||||
"diskCapacityGB": {
|
||||
"type": "string"
|
||||
},
|
||||
"fsGroup": {
|
||||
"type": "string"
|
||||
},
|
||||
"image": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -9762,6 +9776,10 @@ func init() {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityContext": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/securityContext"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -9937,6 +9955,10 @@ func init() {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityContext": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/securityContext"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -658,6 +658,7 @@ func parseTenantCertificates(ctx context.Context, clientSet K8sClientI, namespac
|
||||
func getTenantSecurity(ctx context.Context, clientSet K8sClientI, tenant *miniov2.Tenant) (response *models.TenantSecurityResponse, err error) {
|
||||
var minioExternalCertificates []*models.CertificateInfo
|
||||
var minioExternalCaCertificates []*models.CertificateInfo
|
||||
var tenantSecurityContext *models.SecurityContext
|
||||
// Certificates used by MinIO server
|
||||
if minioExternalCertificates, err = parseTenantCertificates(ctx, clientSet, tenant.Namespace, tenant.Spec.ExternalCertSecret); err != nil {
|
||||
return nil, err
|
||||
@@ -666,12 +667,17 @@ func getTenantSecurity(ctx context.Context, clientSet K8sClientI, tenant *miniov
|
||||
if minioExternalCaCertificates, err = parseTenantCertificates(ctx, clientSet, tenant.Namespace, tenant.Spec.ExternalCaCertSecret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Security Context used by MinIO server
|
||||
if tenant.Spec.Pools[0].SecurityContext != nil {
|
||||
tenantSecurityContext = convertK8sSCToModelSC(tenant.Spec.Pools[0].SecurityContext)
|
||||
}
|
||||
return &models.TenantSecurityResponse{
|
||||
AutoCert: tenant.AutoCert(),
|
||||
CustomCertificates: &models.TenantSecurityResponseCustomCertificates{
|
||||
Minio: minioExternalCertificates,
|
||||
MinioCAs: minioExternalCaCertificates,
|
||||
},
|
||||
SecurityContext: tenantSecurityContext,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1026,6 +1032,12 @@ func updateTenantSecurity(ctx context.Context, operatorClient OperatorClientI, c
|
||||
}
|
||||
newMinIOExternalCaCertSecret = append(newMinIOExternalCaCertSecret, certificateSecrets...)
|
||||
}
|
||||
|
||||
// set Security Context
|
||||
var newTenantSecurityContext *corev1.PodSecurityContext
|
||||
newTenantSecurityContext, _ = convertModelSCToK8sSC(params.Body.SecurityContext)
|
||||
minInst.Spec.Pools[0].SecurityContext = newTenantSecurityContext
|
||||
|
||||
// Update External Certificates
|
||||
minInst.Spec.ExternalCertSecret = newMinIOExternalCertSecret
|
||||
minInst.Spec.ExternalCaCertSecret = newMinIOExternalCaCertSecret
|
||||
@@ -1033,6 +1045,7 @@ func updateTenantSecurity(ctx context.Context, operatorClient OperatorClientI, c
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// restart all MinIO pods at the same time
|
||||
err = client.deletePodCollection(ctx, namespace, metav1.DeleteOptions{}, metav1.ListOptions{
|
||||
LabelSelector: fmt.Sprintf("%s=%s", miniov2.TenantLabel, minInst.Name),
|
||||
|
||||
@@ -44,6 +44,13 @@ import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
|
||||
import Loader from "../../Common/Loader/Loader";
|
||||
import TLSCertificate from "../../Common/TLSCertificate/TLSCertificate";
|
||||
import SectionTitle from "../../Common/SectionTitle";
|
||||
import SecurityContextSelector from "../securityContextSelector";
|
||||
import {
|
||||
setRunAsUser,
|
||||
setFSGroup,
|
||||
setRunAsGroup,
|
||||
setRunAsNonRoot,
|
||||
} from "../tenantSecurityContextSlice";
|
||||
|
||||
interface ITenantSecurity {
|
||||
classes: any;
|
||||
@@ -100,6 +107,19 @@ const TenantSecurity = ({ classes }: ITenantSecurity) => {
|
||||
const [minioTLSCaCertificateSecrets, setMinioTLSCaCertificateSecrets] =
|
||||
useState<ICertificateInfo[]>([]);
|
||||
|
||||
const runAsGroup = useSelector(
|
||||
(state: AppState) => state.editTenantSecurityContext.runAsGroup
|
||||
);
|
||||
const runAsUser = useSelector(
|
||||
(state: AppState) => state.editTenantSecurityContext.runAsUser
|
||||
);
|
||||
const fsGroup = useSelector(
|
||||
(state: AppState) => state.editTenantSecurityContext.fsGroup
|
||||
);
|
||||
const runAsNonRoot = useSelector(
|
||||
(state: AppState) => state.editTenantSecurityContext.runAsNonRoot
|
||||
);
|
||||
|
||||
const getTenantSecurityInfo = useCallback(() => {
|
||||
api
|
||||
.invoke(
|
||||
@@ -113,6 +133,10 @@ const TenantSecurity = ({ classes }: ITenantSecurity) => {
|
||||
}
|
||||
setMinioTLSCertificateSecrets(res.customCertificates.minio || []);
|
||||
setMinioTLSCaCertificateSecrets(res.customCertificates.minioCAs || []);
|
||||
dispatch(setRunAsGroup(res.securityContext.runAsGroup));
|
||||
dispatch(setRunAsUser(res.securityContext.runAsUser));
|
||||
dispatch(setFSGroup(res.securityContext.fsGroup));
|
||||
dispatch(setRunAsNonRoot(res.securityContext.runAsNonRoot));
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
@@ -130,6 +154,12 @@ const TenantSecurity = ({ classes }: ITenantSecurity) => {
|
||||
let payload = {
|
||||
autoCert: enableAutoCert,
|
||||
customCertificates: {},
|
||||
securityContext: {
|
||||
runAsGroup: runAsGroup,
|
||||
runAsUser: runAsUser,
|
||||
runAsNonRoot: runAsNonRoot,
|
||||
fsGroup: fsGroup,
|
||||
},
|
||||
};
|
||||
if (enableCustomCerts) {
|
||||
payload["customCertificates"] = {
|
||||
@@ -512,7 +542,21 @@ const TenantSecurity = ({ classes }: ITenantSecurity) => {
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
<Grid item xs={12} className={classes.formFieldRow}>
|
||||
<SecurityContextSelector
|
||||
classes={classes}
|
||||
runAsGroup={runAsGroup}
|
||||
runAsUser={runAsUser}
|
||||
fsGroup={fsGroup}
|
||||
runAsNonRoot={runAsNonRoot}
|
||||
setFSGroup={(value: string) => dispatch(setFSGroup(value))}
|
||||
setRunAsUser={(value: string) => dispatch(setRunAsUser(value))}
|
||||
setRunAsGroup={(value: string) => dispatch(setRunAsGroup(value))}
|
||||
setRunAsNonRoot={(value: boolean) =>
|
||||
dispatch(setRunAsNonRoot(value))
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} textAlign={"right"}>
|
||||
<Button
|
||||
type="submit"
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { IEditTenantSecurityContext } from "./types";
|
||||
|
||||
const initialState: IEditTenantSecurityContext = {
|
||||
securityContextEnabled: false,
|
||||
runAsUser: "1000",
|
||||
runAsGroup: "1000",
|
||||
fsGroup: "1000",
|
||||
runAsNonRoot: true,
|
||||
};
|
||||
|
||||
export const editTenantSecurityContextSlice = createSlice({
|
||||
name: "editTenantSecurityContext",
|
||||
initialState,
|
||||
reducers: {
|
||||
setSecurityContextEnabled: (state, action: PayloadAction<boolean>) => {
|
||||
state.securityContextEnabled = action.payload;
|
||||
},
|
||||
setRunAsUser: (state, action: PayloadAction<string>) => {
|
||||
state.runAsUser = action.payload;
|
||||
},
|
||||
setRunAsGroup: (state, action: PayloadAction<string>) => {
|
||||
state.runAsGroup = action.payload;
|
||||
},
|
||||
setFSGroup: (state, action: PayloadAction<string>) => {
|
||||
state.fsGroup = action.payload;
|
||||
},
|
||||
setRunAsNonRoot: (state, action: PayloadAction<boolean>) => {
|
||||
state.runAsNonRoot = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
setSecurityContextEnabled,
|
||||
setRunAsUser,
|
||||
setRunAsGroup,
|
||||
setFSGroup,
|
||||
setRunAsNonRoot,
|
||||
} = editTenantSecurityContextSlice.actions;
|
||||
|
||||
export default editTenantSecurityContextSlice.reducer;
|
||||
@@ -42,6 +42,7 @@ export interface ICustomCertificates {
|
||||
export interface ITenantSecurityResponse {
|
||||
autoCert: boolean;
|
||||
customCertificates: ICustomCertificates;
|
||||
securityContext: ISecurityContext;
|
||||
}
|
||||
|
||||
export interface IVaultTLS {
|
||||
@@ -346,3 +347,11 @@ export interface IEditMonitoringSecurityContext {
|
||||
fsGroup: string;
|
||||
runAsNonRoot: boolean;
|
||||
}
|
||||
|
||||
export interface IEditTenantSecurityContext {
|
||||
securityContextEnabled: boolean;
|
||||
runAsUser: string;
|
||||
runAsGroup: string;
|
||||
fsGroup: string;
|
||||
runAsNonRoot: boolean;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import addPoolReducer from "./screens/Console/Tenants/TenantDetails/Pools/AddPoo
|
||||
import editPoolReducer from "./screens/Console/Tenants/TenantDetails/Pools/EditPool/editPoolSlice";
|
||||
import editTenantMonitoringReducer from "./screens/Console/Tenants/TenantDetails/tenantMonitoringSlice";
|
||||
import editTenantAuditLoggingReducer from "./screens/Console/Tenants/TenantDetails/tenantAuditLogSlice";
|
||||
import editTenantSecurityContextReducer from "./screens/Console/Tenants/tenantSecurityContextSlice";
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
system: systemReducer,
|
||||
@@ -55,6 +56,7 @@ const rootReducer = combineReducers({
|
||||
editPool: editPoolReducer,
|
||||
editTenantMonitoring: editTenantMonitoringReducer,
|
||||
editTenantLogging: editTenantAuditLoggingReducer,
|
||||
editTenantSecurityContext: editTenantSecurityContextReducer,
|
||||
});
|
||||
|
||||
export const store = configureStore({
|
||||
|
||||
@@ -1491,6 +1491,9 @@ definitions:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/certificateInfo"
|
||||
securityContext:
|
||||
type: object
|
||||
$ref: "#/definitions/securityContext"
|
||||
|
||||
updateTenantSecurityRequest:
|
||||
type: object
|
||||
@@ -1512,7 +1515,10 @@ definitions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
||||
securityContext:
|
||||
type: object
|
||||
$ref: "#/definitions/securityContext"
|
||||
|
||||
certificateInfo:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in New Issue
Block a user