Add support for manual edit of KES configuration file (#2354)

Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
This commit is contained in:
Lenin Alevski
2022-10-07 08:15:56 -07:00
committed by GitHub
parent 189331f465
commit 9e7a40abc8
16 changed files with 813 additions and 638 deletions

View File

@@ -54,6 +54,9 @@ type EncryptionConfiguration struct {
// image
Image string `json:"image,omitempty"`
// raw
Raw string `json:"raw,omitempty"`
// replicas
Replicas string `json:"replicas,omitempty"`
@@ -93,6 +96,8 @@ func (m *EncryptionConfiguration) UnmarshalJSON(raw []byte) error {
Image string `json:"image,omitempty"`
Raw string `json:"raw,omitempty"`
Replicas string `json:"replicas,omitempty"`
SecretsToBeDeleted []string `json:"secretsToBeDeleted"`
@@ -119,6 +124,8 @@ func (m *EncryptionConfiguration) UnmarshalJSON(raw []byte) error {
m.Image = dataAO1.Image
m.Raw = dataAO1.Raw
m.Replicas = dataAO1.Replicas
m.SecretsToBeDeleted = dataAO1.SecretsToBeDeleted
@@ -154,6 +161,8 @@ func (m EncryptionConfiguration) MarshalJSON() ([]byte, error) {
Image string `json:"image,omitempty"`
Raw string `json:"raw,omitempty"`
Replicas string `json:"replicas,omitempty"`
SecretsToBeDeleted []string `json:"secretsToBeDeleted"`
@@ -177,6 +186,8 @@ func (m EncryptionConfiguration) MarshalJSON() ([]byte, error) {
dataAO1.Image = m.Image
dataAO1.Raw = m.Raw
dataAO1.Replicas = m.Replicas
dataAO1.SecretsToBeDeleted = m.SecretsToBeDeleted

View File

@@ -54,6 +54,9 @@ type EncryptionConfigurationResponse struct {
// mtls client
MtlsClient *CertificateInfo `json:"mtls_client,omitempty"`
// raw
Raw string `json:"raw,omitempty"`
// replicas
Replicas string `json:"replicas,omitempty"`
@@ -90,6 +93,8 @@ func (m *EncryptionConfigurationResponse) UnmarshalJSON(raw []byte) error {
MtlsClient *CertificateInfo `json:"mtls_client,omitempty"`
Raw string `json:"raw,omitempty"`
Replicas string `json:"replicas,omitempty"`
SecurityContext *SecurityContext `json:"securityContext,omitempty"`
@@ -114,6 +119,8 @@ func (m *EncryptionConfigurationResponse) UnmarshalJSON(raw []byte) error {
m.MtlsClient = dataAO1.MtlsClient
m.Raw = dataAO1.Raw
m.Replicas = dataAO1.Replicas
m.SecurityContext = dataAO1.SecurityContext
@@ -147,6 +154,8 @@ func (m EncryptionConfigurationResponse) MarshalJSON() ([]byte, error) {
MtlsClient *CertificateInfo `json:"mtls_client,omitempty"`
Raw string `json:"raw,omitempty"`
Replicas string `json:"replicas,omitempty"`
SecurityContext *SecurityContext `json:"securityContext,omitempty"`
@@ -168,6 +177,8 @@ func (m EncryptionConfigurationResponse) MarshalJSON() ([]byte, error) {
dataAO1.MtlsClient = m.MtlsClient
dataAO1.Raw = m.Raw
dataAO1.Replicas = m.Replicas
dataAO1.SecurityContext = m.SecurityContext

View File

@@ -3060,6 +3060,9 @@ func init() {
"image": {
"type": "string"
},
"raw": {
"type": "string"
},
"replicas": {
"type": "string"
},
@@ -3116,6 +3119,9 @@ func init() {
"type": "object",
"$ref": "#/definitions/certificateInfo"
},
"raw": {
"type": "string"
},
"replicas": {
"type": "string"
},
@@ -9126,6 +9132,9 @@ func init() {
"image": {
"type": "string"
},
"raw": {
"type": "string"
},
"replicas": {
"type": "string"
},
@@ -9182,6 +9191,9 @@ func init() {
"type": "object",
"$ref": "#/definitions/certificateInfo"
},
"raw": {
"type": "string"
},
"replicas": {
"type": "string"
},

View File

@@ -329,6 +329,8 @@ func tenantEncryptionInfo(ctx context.Context, operatorClient OperatorClientI, c
}
if rawConfiguration, ok := configSecret.Data["server-config.yaml"]; ok {
kesConfiguration := &kes.ServerConfig{}
// return raw configuration in case the user wants to edit KES configuration manually
encryptConfig.Raw = string(rawConfiguration)
err := yaml.Unmarshal(rawConfiguration, kesConfiguration)
if err != nil {
return nil, err
@@ -452,7 +454,7 @@ func tenantEncryptionInfo(ctx context.Context, operatorClient OperatorClientI, c
}
return encryptConfig, nil
}
return nil, errors.New("encryption configuration not found")
return nil, xerrors.ErrEncryptionConfigNotFound
}
// getTenantEncryptionResponse is a wrapper for tenantEncryptionInfo
@@ -476,7 +478,7 @@ func getTenantEncryptionInfoResponse(session *models.Principal, params operator_
}
configuration, err := tenantEncryptionInfo(ctx, &opClient, &k8sClient, params.Namespace, params)
if err != nil {
return nil, xerrors.ErrorWithContext(ctx, err, xerrors.ErrEncryptionConfigNotFound)
return nil, xerrors.ErrorWithContext(ctx, err)
}
return configuration, nil
}
@@ -627,16 +629,6 @@ func createOrReplaceExternalCertSecrets(ctx context.Context, clientSet K8sClient
}
func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sClientI, ns string, encryptionCfg *models.EncryptionConfiguration, kesConfigurationSecretName, kesClientCertSecretName, tenantName string) (*corev1.LocalObjectReference, *miniov2.LocalCertificateReference, error) {
// delete KES configuration secret if exists
if err := clientSet.deleteSecret(ctx, ns, kesConfigurationSecretName, metav1.DeleteOptions{}); err != nil {
// log the errors if any and continue
xerrors.LogError("deleting secret name %s failed: %v, continuing..", kesConfigurationSecretName, err)
}
// delete KES client cert secret if exists
if err := clientSet.deleteSecret(ctx, ns, kesClientCertSecretName, metav1.DeleteOptions{}); err != nil {
// log the errors if any and continue
xerrors.LogError("deleting secret name %s failed: %v, continuing..", kesClientCertSecretName, err)
}
// if autoCert is enabled then Operator will generate the client certificates, calculate the client cert identity
// and pass it to KES via the ${MINIO_KES_IDENTITY} variable
clientCrtIdentity := "${MINIO_KES_IDENTITY}"
@@ -841,6 +833,11 @@ func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sCl
// if mTLSCertificates contains elements we create the kubernetes secret
var clientCertSecretReference *miniov2.LocalCertificateReference
if len(mTLSCertificates) > 0 {
// delete KES client cert secret only if new client certificates are provided
if err := clientSet.deleteSecret(ctx, ns, kesClientCertSecretName, metav1.DeleteOptions{}); err != nil {
// log the errors if any and continue
xerrors.LogError("deleting secret name %s failed: %v, continuing..", kesClientCertSecretName, err)
}
// Secret to store KES mTLS kesConfiguration to authenticate against a KMS
kesClientCertSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
@@ -861,11 +858,32 @@ func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sCl
Name: kesClientCertSecretName,
}
}
// Generate Yaml kesConfiguration for KES
serverConfigYaml, err := yaml.Marshal(kesConfig)
if err != nil {
return nil, nil, err
var serverRawConfig []byte
var err error
if encryptionCfg.Raw != "" {
serverRawConfig = []byte(encryptionCfg.Raw)
// verify provided configuration is in valid YAML format
var configTest kes.ServerConfig
err = yaml.Unmarshal(serverRawConfig, &configTest)
if err != nil {
return nil, nil, err
}
} else {
// Generate Yaml kesConfiguration for KES
serverRawConfig, err = yaml.Marshal(kesConfig)
if err != nil {
return nil, nil, err
}
}
// delete KES configuration secret if exists
if err := clientSet.deleteSecret(ctx, ns, kesConfigurationSecretName, metav1.DeleteOptions{}); err != nil {
// log the errors if any and continue
xerrors.LogError("deleting secret name %s failed: %v, continuing..", kesConfigurationSecretName, err)
}
// Secret to store KES server kesConfiguration
kesConfigurationSecret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
@@ -876,7 +894,7 @@ func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sCl
},
Immutable: &imm,
Data: map[string][]byte{
"server-config.yaml": serverConfigYaml,
"server-config.yaml": serverRawConfig,
},
}
_, err = clientSet.createSecret(ctx, ns, &kesConfigurationSecret, metav1.CreateOptions{})

View File

@@ -51,6 +51,9 @@ import GCPKMSAdd from "./Encryption/GCPKMSAdd";
import GemaltoKMSAdd from "./Encryption/GemaltoKMSAdd";
import AWSKMSAdd from "./Encryption/AWSKMSAdd";
import SelectWrapper from "../../../Common/FormComponents/SelectWrapper/SelectWrapper";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import CodeMirrorWrapper from "../../../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper";
interface IEncryptionProps {
classes: any;
@@ -88,6 +91,12 @@ const Encryption = ({ classes }: IEncryptionProps) => {
const replicas = useSelector(
(state: AppState) => state.createTenant.fields.encryption.replicas
);
const rawConfiguration = useSelector(
(state: AppState) => state.createTenant.fields.encryption.rawConfiguration
);
const encryptionTab = useSelector(
(state: AppState) => state.createTenant.fields.encryption.encryptionTab
);
const enableEncryption = useSelector(
(state: AppState) => state.createTenant.fields.encryption.enableEncryption
);
@@ -175,6 +184,11 @@ const Encryption = ({ classes }: IEncryptionProps) => {
if (enableEncryption) {
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "rawConfiguration",
required: encryptionTab > 0,
value: rawConfiguration,
},
{
fieldKey: "replicas",
required: true,
@@ -239,7 +253,6 @@ const Encryption = ({ classes }: IEncryptionProps) => {
}
const commonVal = commonFormValidation(encryptionValidation);
dispatch(
isPageValid({
pageName: "encryption",
@@ -249,6 +262,8 @@ const Encryption = ({ classes }: IEncryptionProps) => {
setValidationErrors(commonVal);
}, [
rawConfiguration,
encryptionTab,
enableEncryption,
encryptionType,
gcpProjectID,
@@ -309,29 +324,64 @@ const Encryption = ({ classes }: IEncryptionProps) => {
{enableEncryption && (
<Fragment>
<Grid item xs={12} className={classes.encryptionTypeOptions}>
<RadioGroupSelector
currentSelection={encryptionType}
id="encryptionType"
name="encryptionType"
label="Encryption Options"
onChange={(e) => {
updateField("encryptionType", e.target.value);
<Grid item xs={12}>
<Tabs
value={encryptionTab}
onChange={(e: React.ChangeEvent<{}>, value: number) => {
updateField("encryptionTab", value);
}}
selectorOptions={[
{ label: "Vault", value: "vault" },
{ label: "AWS", value: "aws" },
{ label: "Gemalto", value: "gemalto" },
{ label: "GCP", value: "gcp" },
{ label: "Azure", value: "azure" },
]}
/>
indicatorColor="primary"
textColor="primary"
aria-label="cluster-tabs"
variant="scrollable"
scrollButtons="auto"
>
<Tab id="kms-options" label="Options" />
<Tab id="kms-raw-configuration" label="Raw Edit" />
</Tabs>
</Grid>
{encryptionType === "vault" && <VaultKMSAdd />}
{encryptionType === "azure" && <AzureKMSAdd />}
{encryptionType === "gcp" && <GCPKMSAdd />}
{encryptionType === "aws" && <AWSKMSAdd />}
{encryptionType === "gemalto" && <GemaltoKMSAdd />}
{encryptionTab ? (
<Fragment>
<Grid item xs={12}>
<CodeMirrorWrapper
value={rawConfiguration}
mode={"yaml"}
onBeforeChange={(editor, data, value) => {
updateField("rawConfiguration", value);
}}
editorHeight={"550px"}
/>
</Grid>
</Fragment>
) : (
<Fragment>
<Grid item xs={12} className={classes.encryptionTypeOptions}>
<RadioGroupSelector
currentSelection={encryptionType}
id="encryptionType"
name="encryptionType"
label="KMS"
onChange={(e) => {
updateField("encryptionType", e.target.value);
}}
selectorOptions={[
{ label: "Vault", value: "vault" },
{ label: "AWS", value: "aws" },
{ label: "Gemalto", value: "gemalto" },
{ label: "GCP", value: "gcp" },
{ label: "Azure", value: "azure" },
]}
/>
</Grid>
{encryptionType === "vault" && <VaultKMSAdd />}
{encryptionType === "azure" && <AzureKMSAdd />}
{encryptionType === "gcp" && <GCPKMSAdd />}
{encryptionType === "aws" && <AWSKMSAdd />}
{encryptionType === "gemalto" && <GemaltoKMSAdd />}
</Fragment>
)}
<div className={classes.headerElement}>
<h4 className={classes.h3Section}>Additional Configurations</h4>
</div>

View File

@@ -48,6 +48,9 @@ const AWSKMSAdd = () => {
const dispatch = useAppDispatch();
const classes = useStyles();
const encryptionTab = useSelector(
(state: AppState) => state.createTenant.fields.encryption.encryptionTab
);
const awsEndpoint = useSelector(
(state: AppState) => state.createTenant.fields.encryption.awsEndpoint
);
@@ -72,29 +75,31 @@ const AWSKMSAdd = () => {
useEffect(() => {
let encryptionValidation: IValidation[] = [];
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "aws_endpoint",
required: true,
value: awsEndpoint,
},
{
fieldKey: "aws_region",
required: true,
value: awsRegion,
},
{
fieldKey: "aws_accessKey",
required: true,
value: awsAccessKey,
},
{
fieldKey: "aws_secretKey",
required: true,
value: awsSecretKey,
},
];
if (!encryptionTab) {
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "aws_endpoint",
required: true,
value: awsEndpoint,
},
{
fieldKey: "aws_region",
required: true,
value: awsRegion,
},
{
fieldKey: "aws_accessKey",
required: true,
value: awsAccessKey,
},
{
fieldKey: "aws_secretKey",
required: true,
value: awsSecretKey,
},
];
}
const commonVal = commonFormValidation(encryptionValidation);
@@ -106,7 +111,14 @@ const AWSKMSAdd = () => {
);
setValidationErrors(commonVal);
}, [awsEndpoint, awsRegion, awsSecretKey, awsAccessKey, dispatch]);
}, [
encryptionTab,
awsEndpoint,
awsRegion,
awsSecretKey,
awsAccessKey,
dispatch,
]);
// Common
const updateField = useCallback(

View File

@@ -48,6 +48,9 @@ const AzureKMSAdd = () => {
const dispatch = useAppDispatch();
const classes = useStyles();
const encryptionTab = useSelector(
(state: AppState) => state.createTenant.fields.encryption.encryptionTab
);
const azureEndpoint = useSelector(
(state: AppState) => state.createTenant.fields.encryption.azureEndpoint
);
@@ -67,29 +70,31 @@ const AzureKMSAdd = () => {
useEffect(() => {
let encryptionValidation: IValidation[] = [];
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "azure_endpoint",
required: true,
value: azureEndpoint,
},
{
fieldKey: "azure_tenant_id",
required: true,
value: azureTenantID,
},
{
fieldKey: "azure_client_id",
required: true,
value: azureClientID,
},
{
fieldKey: "azure_client_secret",
required: true,
value: azureClientSecret,
},
];
if (!encryptionTab) {
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "azure_endpoint",
required: true,
value: azureEndpoint,
},
{
fieldKey: "azure_tenant_id",
required: true,
value: azureTenantID,
},
{
fieldKey: "azure_client_id",
required: true,
value: azureClientID,
},
{
fieldKey: "azure_client_secret",
required: true,
value: azureClientSecret,
},
];
}
const commonVal = commonFormValidation(encryptionValidation);
@@ -102,6 +107,7 @@ const AzureKMSAdd = () => {
setValidationErrors(commonVal);
}, [
encryptionTab,
azureEndpoint,
azureTenantID,
azureClientID,

View File

@@ -53,6 +53,9 @@ const GemaltoKMSAdd = () => {
const dispatch = useAppDispatch();
const classes = useStyles();
const encryptionTab = useSelector(
(state: AppState) => state.createTenant.fields.encryption.encryptionTab
);
const gemaltoCA = useSelector(
(state: AppState) => state.createTenant.certificates.gemaltoCA
);
@@ -75,31 +78,33 @@ const GemaltoKMSAdd = () => {
useEffect(() => {
let encryptionValidation: IValidation[] = [];
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "gemalto_endpoint",
required: true,
value: gemaltoEndpoint,
},
{
fieldKey: "gemalto_token",
required: true,
value: gemaltoToken,
},
{
fieldKey: "gemalto_domain",
required: true,
value: gemaltoDomain,
},
{
fieldKey: "gemalto_retry",
required: false,
value: gemaltoRetry,
customValidation: parseInt(gemaltoRetry) < 0,
customValidationMessage: "Value needs to be 0 or greater",
},
];
if (!encryptionTab) {
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "gemalto_endpoint",
required: true,
value: gemaltoEndpoint,
},
{
fieldKey: "gemalto_token",
required: true,
value: gemaltoToken,
},
{
fieldKey: "gemalto_domain",
required: true,
value: gemaltoDomain,
},
{
fieldKey: "gemalto_retry",
required: false,
value: gemaltoRetry,
customValidation: parseInt(gemaltoRetry) < 0,
customValidationMessage: "Value needs to be 0 or greater",
},
];
}
const commonVal = commonFormValidation(encryptionValidation);
@@ -111,7 +116,14 @@ const GemaltoKMSAdd = () => {
);
setValidationErrors(commonVal);
}, [gemaltoEndpoint, gemaltoToken, gemaltoDomain, gemaltoRetry, dispatch]);
}, [
encryptionTab,
gemaltoEndpoint,
gemaltoToken,
gemaltoDomain,
gemaltoRetry,
dispatch,
]);
// Common
const updateField = useCallback(

View File

@@ -55,6 +55,9 @@ const VaultKMSAdd = () => {
const dispatch = useAppDispatch();
const classes = useStyles();
const encryptionTab = useSelector(
(state: AppState) => state.createTenant.fields.encryption.encryptionTab
);
const vaultEndpoint = useSelector(
(state: AppState) => state.createTenant.fields.encryption.vaultEndpoint
);
@@ -95,38 +98,40 @@ const VaultKMSAdd = () => {
useEffect(() => {
let encryptionValidation: IValidation[] = [];
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "vault_endpoint",
required: true,
value: vaultEndpoint,
},
{
fieldKey: "vault_id",
required: true,
value: vaultId,
},
{
fieldKey: "vault_secret",
required: true,
value: vaultSecret,
},
{
fieldKey: "vault_ping",
required: false,
value: vaultPing,
customValidation: parseInt(vaultPing) < 0,
customValidationMessage: "Value needs to be 0 or greater",
},
{
fieldKey: "vault_retry",
required: false,
value: vaultRetry,
customValidation: parseInt(vaultRetry) < 0,
customValidationMessage: "Value needs to be 0 or greater",
},
];
if (!encryptionTab) {
encryptionValidation = [
...encryptionValidation,
{
fieldKey: "vault_endpoint",
required: true,
value: vaultEndpoint,
},
{
fieldKey: "vault_id",
required: true,
value: vaultId,
},
{
fieldKey: "vault_secret",
required: true,
value: vaultSecret,
},
{
fieldKey: "vault_ping",
required: false,
value: vaultPing,
customValidation: parseInt(vaultPing) < 0,
customValidationMessage: "Value needs to be 0 or greater",
},
{
fieldKey: "vault_retry",
required: false,
value: vaultRetry,
customValidation: parseInt(vaultRetry) < 0,
customValidationMessage: "Value needs to be 0 or greater",
},
];
}
const commonVal = commonFormValidation(encryptionValidation);
@@ -139,6 +144,7 @@ const VaultKMSAdd = () => {
setValidationErrors(commonVal);
}, [
encryptionTab,
vaultEndpoint,
vaultEngine,
vaultId,

View File

@@ -180,6 +180,8 @@ const initialState: ICreateTenant = {
enableTLS: true,
},
encryption: {
rawConfiguration: "",
encryptionTab: 0,
enableEncryption: false,
encryptionType: "vault",
gemaltoEndpoint: "",

View File

@@ -70,6 +70,8 @@ export const createTenantAsync = createAsyncThunk(
const vaultCertificate = certificates.vaultCertificate;
const vaultCA = certificates.vaultCA;
const gemaltoCA = certificates.gemaltoCA;
const rawConfiguration = fields.encryption.rawConfiguration;
const encryptionTab = fields.encryption.encryptionTab;
const enableEncryption = fields.encryption.enableEncryption;
const encryptionType = fields.encryption.encryptionType;
const gemaltoEndpoint = fields.encryption.gemaltoEndpoint;
@@ -490,6 +492,7 @@ export const createTenantAsync = createAsyncThunk(
dataSend = {
...dataSend,
encryption: {
raw: encryptionTab ? rawConfiguration : "",
replicas: kesReplicas,
securityContext: kesSecurityContext,
image: kesImage,

View File

@@ -96,6 +96,7 @@ export interface IGemaltoConfiguration {
}
export interface ITenantEncryptionResponse {
raw: string;
image: string;
replicas: string;
securityContext: ISecurityContext;
@@ -219,6 +220,8 @@ export interface ISecurityFields {
}
export interface IEncryptionFields {
rawConfiguration: string;
encryptionTab: number;
enableEncryption: boolean;
encryptionType: string;
gemaltoEndpoint: string;

View File

@@ -162,6 +162,10 @@ func ErrorWithContext(ctx context.Context, err ...interface{}) *models.Error {
errorCode = 404
errorMessage = ErrSSENotConfigured.Error()
}
if errors.Is(err1, ErrEncryptionConfigNotFound) {
errorCode = 404
errorMessage = err1.Error()
}
// account change password
if errors.Is(err1, ErrChangePassword) {
errorCode = 403

View File

@@ -82,7 +82,7 @@ func TestError(t *testing.T) {
"ErrUnableToUpdateTenantCertificates": {code: 500, err: ErrUnableToUpdateTenantCertificates},
"ErrUpdatingEncryptionConfig": {code: 500, err: ErrUpdatingEncryptionConfig},
"ErrDeletingEncryptionConfig": {code: 500, err: ErrDeletingEncryptionConfig},
"ErrEncryptionConfigNotFound": {code: 500, err: ErrEncryptionConfigNotFound},
"ErrEncryptionConfigNotFound": {code: 404, err: ErrEncryptionConfigNotFound},
}
for k, e := range appErrors {

View File

@@ -2233,6 +2233,8 @@ definitions:
- $ref: "#/definitions/metadataFields"
- type: object
properties:
raw:
type: string
image:
type: string
replicas:
@@ -2271,6 +2273,8 @@ definitions:
- $ref: "#/definitions/metadataFields"
- type: object
properties:
raw:
type: string
image:
type: string
replicas: