Files
object-browser/portal-ui/src/screens/Console/Tenants/AddTenant/AddTenant.tsx
2021-10-25 13:33:46 -05:00

761 lines
25 KiB
TypeScript

// This file is part of MinIO Console Server
// Copyright (c) 2021 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 React, { Fragment, useEffect, useState } from "react";
import get from "lodash/get";
import { connect } from "react-redux";
import Grid from "@mui/material/Grid";
import { LinearProgress } from "@mui/material";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import {
modalBasic,
settingsCommon,
wizardCommon,
} from "../../Common/FormComponents/common/styleLibrary";
import api from "../../../../common/api";
import { generatePoolName } from "../../../../common/utils";
import GenericWizard from "../../Common/GenericWizard/GenericWizard";
import { IWizardElement } from "../../Common/GenericWizard/types";
import { NewServiceAccount } from "../../Common/CredentialsPrompt/types";
import { ErrorResponseHandler, ITenantCreator } from "../../../../common/types";
import { KeyPair } from "../ListTenants/utils";
import { setErrorSnackMessage } from "../../../../actions";
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 { resetAddTenantForm, updateAddField } from "../actions";
import Configure from "./Steps/Configure";
import IdentityProvider from "./Steps/IdentityProvider";
import Security from "./Steps/Security";
import Encryption from "./Steps/Encryption";
import Affinity from "./Steps/Affinity";
import PageHeader from "../../Common/PageHeader/PageHeader";
import history from "../../../../history";
import Images from "./Steps/Images";
interface IAddTenantProps {
setErrorSnackMessage: typeof setErrorSnackMessage;
resetAddTenantForm: typeof resetAddTenantForm;
updateAddField: typeof updateAddField;
fields: IFieldStore;
certificates: ICertificatesItems;
selectedStorageClass: string;
namespace: string;
validPages: string[];
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
buttonContainer: {
textAlign: "right",
},
...modalBasic,
...wizardCommon,
...settingsCommon,
});
const AddTenant = ({
classes,
fields,
certificates,
selectedStorageClass,
namespace,
validPages,
setErrorSnackMessage,
resetAddTenantForm,
}: IAddTenantProps) => {
// Modals
const [showNewCredentials, setShowNewCredentials] = useState<boolean>(false);
const [createdAccount, setCreatedAccount] =
useState<NewServiceAccount | null>(null);
// Fields
const [addSending, setAddSending] = useState<boolean>(false);
/* Send Information to backend */
useEffect(() => {
const tenantName = fields.nameTenant.tenantName;
const selectedStorageClass = fields.nameTenant.selectedStorageClass;
const imageName = fields.configure.imageName;
const customDockerhub = fields.configure.customDockerhub;
const imageRegistry = fields.configure.imageRegistry;
const imageRegistryUsername = fields.configure.imageRegistryUsername;
const imageRegistryPassword = fields.configure.imageRegistryPassword;
const exposeMinIO = fields.configure.exposeMinIO;
const exposeConsole = fields.configure.exposeConsole;
const idpSelection = fields.identityProvider.idpSelection;
const openIDConfigurationURL =
fields.identityProvider.openIDConfigurationURL;
const openIDClientID = fields.identityProvider.openIDClientID;
const openIDClaimName = fields.identityProvider.openIDClaimName;
const openIDCallbackURL = fields.identityProvider.openIDCallbackURL;
const openIDScopes = fields.identityProvider.openIDScopes;
const openIDSecretID = fields.identityProvider.openIDSecretID;
const ADURL = fields.identityProvider.ADURL;
const ADSkipTLS = fields.identityProvider.ADSkipTLS;
const ADServerInsecure = fields.identityProvider.ADServerInsecure;
const ADUserNameSearchFilter =
fields.identityProvider.ADUserNameSearchFilter;
const ADGroupSearchBaseDN = fields.identityProvider.ADGroupSearchBaseDN;
const ADGroupSearchFilter = fields.identityProvider.ADGroupSearchFilter;
const ADGroupNameAttribute = fields.identityProvider.ADGroupNameAttribute;
const ADUserDNs = fields.identityProvider.ADUserDNs;
const ADUserNameFormat = fields.identityProvider.ADUserNameFormat;
const ADLookupBindDN = fields.identityProvider.ADLookupBindDN;
const ADLookupBindPassword = fields.identityProvider.ADLookupBindPassword;
const ADUserDNSearchBaseDN = fields.identityProvider.ADUserDNSearchBaseDN;
const ADUserDNSearchFilter = fields.identityProvider.ADUserDNSearchFilter;
const ADServerStartTLS = fields.identityProvider.ADServerStartTLS;
const accessKeys = fields.identityProvider.accessKeys;
const secretKeys = fields.identityProvider.secretKeys;
const minioCertificates = certificates.minioCertificates;
const caCertificates = certificates.caCertificates;
const consoleCaCertificates = certificates.consoleCaCertificates;
const consoleCertificate = certificates.consoleCertificate;
const serverCertificate = certificates.serverCertificate;
const clientCertificate = certificates.clientCertificate;
const vaultCertificate = certificates.vaultCertificate;
const vaultCA = certificates.vaultCA;
const gemaltoCA = certificates.gemaltoCA;
const enableEncryption = fields.encryption.enableEncryption;
const encryptionType = fields.encryption.encryptionType;
const gemaltoEndpoint = fields.encryption.gemaltoEndpoint;
const gemaltoToken = fields.encryption.gemaltoToken;
const gemaltoDomain = fields.encryption.gemaltoDomain;
const gemaltoRetry = fields.encryption.gemaltoRetry;
const awsEndpoint = fields.encryption.awsEndpoint;
const awsRegion = fields.encryption.awsRegion;
const awsKMSKey = fields.encryption.awsKMSKey;
const awsAccessKey = fields.encryption.awsAccessKey;
const awsSecretKey = fields.encryption.awsSecretKey;
const awsToken = fields.encryption.awsToken;
const vaultEndpoint = fields.encryption.vaultEndpoint;
const vaultEngine = fields.encryption.vaultEngine;
const vaultNamespace = fields.encryption.vaultNamespace;
const vaultPrefix = fields.encryption.vaultPrefix;
const vaultAppRoleEngine = fields.encryption.vaultAppRoleEngine;
const vaultId = fields.encryption.vaultId;
const vaultSecret = fields.encryption.vaultSecret;
const vaultRetry = fields.encryption.vaultRetry;
const vaultPing = fields.encryption.vaultPing;
const azureEndpoint = fields.encryption.azureEndpoint;
const azureTenantID = fields.encryption.azureTenantID;
const azureClientID = fields.encryption.azureClientID;
const azureClientSecret = fields.encryption.azureClientSecret;
const gcpProjectID = fields.encryption.gcpProjectID;
const gcpEndpoint = fields.encryption.gcpEndpoint;
const gcpClientEmail = fields.encryption.gcpClientEmail;
const gcpClientID = fields.encryption.gcpClientID;
const gcpPrivateKeyID = fields.encryption.gcpPrivateKeyID;
const gcpPrivateKey = fields.encryption.gcpPrivateKey;
const enableAutoCert = fields.security.enableAutoCert;
const enableTLS = fields.security.enableTLS;
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;
const logSearchSelectedStorageClass =
fields.configure.logSearchSelectedStorageClass;
const logSearchImage = fields.configure.logSearchImage;
const kesImage = fields.configure.kesImage;
const logSearchPostgresImage = fields.configure.logSearchPostgresImage;
const logSearchPostgresInitImage =
fields.configure.logSearchPostgresInitImage;
const prometheusImage = fields.configure.prometheusImage;
const prometheusSidecarImage = fields.configure.prometheusSidecarImage;
const prometheusInitImage = fields.configure.prometheusInitImage;
const prometheusSelectedStorageClass =
fields.configure.prometheusSelectedStorageClass;
const prometheusVolumeSize = fields.configure.prometheusVolumeSize;
const affinityType = fields.affinity.podAffinity;
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([]);
let affinityObject = {};
switch (affinityType) {
case "default":
affinityObject = {
affinity: getDefaultAffinity(tenantName, poolName),
};
break;
case "nodeSelector":
affinityObject = {
affinity: getNodeSelector(
nodeSelectorLabels,
withPodAntiAffinity,
tenantName,
poolName
),
};
break;
}
const erasureCode = ecParity.split(":")[1];
let dataSend: ITenantCreator = {
name: tenantName,
namespace: namespace,
access_key: "",
secret_key: "",
access_keys: [],
secret_keys: [],
enable_tls: enableTLS && enableAutoCert,
enable_console: true,
enable_prometheus: true,
service_name: "",
image: imageName,
expose_minio: exposeMinIO,
expose_console: exposeConsole,
pools: [
{
name: poolName,
servers: distribution.nodes,
volumes_per_server: distribution.disks,
volume_configuration: {
size: distribution.pvSize,
storage_class_name: selectedStorageClass,
},
resources: {
requests: {
memory: memorySize.request,
},
limits: {
memory: memorySize.limit,
},
},
securityContext: tenantCustom ? tenantSecurityContext : null,
...affinityObject,
},
],
erasureCodingParity: parseInt(erasureCode, 10),
};
if (customDockerhub) {
dataSend = {
...dataSend,
image_registry: {
registry: imageRegistry,
username: imageRegistryUsername,
password: imageRegistryPassword,
},
};
}
if (logSearchCustom) {
dataSend = {
...dataSend,
logSearchConfiguration: {
storageClass: logSearchSelectedStorageClass,
storageSize: parseInt(logSearchVolumeSize),
image: logSearchImage,
postgres_image: logSearchPostgresImage,
postgres_init_image: logSearchPostgresInitImage,
securityContext: logSearchSecurityContext,
postgres_securityContext: logSearchPostgresSecurityContext,
},
};
} else {
dataSend = {
...dataSend,
logSearchConfiguration: {
image: logSearchImage,
postgres_image: logSearchPostgresImage,
postgres_init_image: logSearchPostgresInitImage,
},
};
}
if (prometheusCustom) {
dataSend = {
...dataSend,
prometheusConfiguration: {
storageClass: prometheusSelectedStorageClass,
storageSize: parseInt(prometheusVolumeSize),
image: prometheusImage,
sidecar_image: prometheusSidecarImage,
init_image: prometheusInitImage,
securityContext: prometheusSecurityContext,
},
};
} else {
dataSend = {
...dataSend,
prometheusConfiguration: {
image: prometheusImage,
sidecar_image: prometheusSidecarImage,
init_image: prometheusInitImage,
},
};
}
let tenantCerts: any = null;
let consoleCerts: any = null;
let caCerts: any = null;
let consoleCaCerts: any = null;
if (caCertificates.length > 0) {
caCerts = {
ca_certificates: caCertificates
.map((keyPair: KeyPair) => keyPair.encoded_cert)
.filter((keyPair) => keyPair),
};
}
if (consoleCaCertificates.length > 0) {
consoleCaCerts = {
console_ca_certificates: consoleCaCertificates
.map((keyPair: KeyPair) => keyPair.encoded_cert)
.filter((keyPair) => keyPair),
};
}
if (enableTLS && minioCertificates.length > 0) {
tenantCerts = {
minio: minioCertificates
.map((keyPair: KeyPair) => ({
crt: keyPair.encoded_cert,
key: keyPair.encoded_key,
}))
.filter((keyPair) => keyPair.crt && keyPair.key),
};
}
if (
enableTLS &&
consoleCertificate.encoded_cert !== "" &&
consoleCertificate.encoded_key !== ""
) {
consoleCerts = {
console: {
crt: consoleCertificate.encoded_cert,
key: consoleCertificate.encoded_key,
},
};
}
if (tenantCerts || consoleCerts || caCerts || consoleCaCerts) {
dataSend = {
...dataSend,
tls: {
...tenantCerts,
...consoleCerts,
...caCerts,
...consoleCaCerts,
},
};
}
if (enableEncryption) {
let insertEncrypt = {};
switch (encryptionType) {
case "gemalto":
let gemaltoCAIntroduce = {};
if (gemaltoCA.encoded_cert !== "") {
gemaltoCAIntroduce = {
ca: gemaltoCA.encoded_cert,
};
}
insertEncrypt = {
gemalto: {
keysecure: {
endpoint: gemaltoEndpoint,
credentials: {
token: gemaltoToken,
domain: gemaltoDomain,
retry: parseInt(gemaltoRetry),
},
tls: {
...gemaltoCAIntroduce,
},
},
},
};
break;
case "aws":
insertEncrypt = {
aws: {
secretsmanager: {
endpoint: awsEndpoint,
region: awsRegion,
kmskey: awsKMSKey,
credentials: {
accesskey: awsAccessKey,
secretkey: awsSecretKey,
token: awsToken,
},
},
},
};
break;
case "azure":
insertEncrypt = {
azure: {
keyvault: {
endpoint: azureEndpoint,
credentials: {
tenant_id: azureTenantID,
client_id: azureClientID,
client_secret: azureClientSecret,
},
},
},
};
break;
case "gcp":
insertEncrypt = {
gcp: {
secretmanager: {
project_id: gcpProjectID,
endpoint: gcpEndpoint,
credentials: {
client_email: gcpClientEmail,
client_id: gcpClientID,
private_key_id: gcpPrivateKeyID,
private_key: gcpPrivateKey,
},
},
},
};
break;
case "vault":
let vaultKeyPair = null;
let vaultCAInsert = null;
if (
vaultCertificate.encoded_key !== "" &&
vaultCertificate.encoded_cert !== ""
) {
vaultKeyPair = {
key: vaultCertificate.encoded_key,
crt: vaultCertificate.encoded_cert,
};
}
if (vaultCA.encoded_cert !== "") {
vaultCAInsert = {
ca: vaultCA.encoded_cert,
};
}
let vaultTLS = null;
if (vaultKeyPair || vaultCA) {
vaultTLS = {
tls: {
...vaultKeyPair,
...vaultCAInsert,
},
};
}
insertEncrypt = {
vault: {
endpoint: vaultEndpoint,
engine: vaultEngine,
namespace: vaultNamespace,
prefix: vaultPrefix,
approle: {
engine: vaultAppRoleEngine,
id: vaultId,
secret: vaultSecret,
retry: parseInt(vaultRetry),
},
...vaultTLS,
status: {
ping: parseInt(vaultPing),
},
},
};
break;
}
let encryptionServerKeyPair: any = {};
let encryptionClientKeyPair: any = {};
if (
clientCertificate.encoded_key !== "" &&
clientCertificate.encoded_cert !== ""
) {
encryptionClientKeyPair = {
client: {
key: clientCertificate.encoded_key,
crt: clientCertificate.encoded_cert,
},
};
}
if (
serverCertificate.encoded_key !== "" &&
serverCertificate.encoded_cert !== ""
) {
encryptionServerKeyPair = {
server: {
key: serverCertificate.encoded_key,
crt: serverCertificate.encoded_cert,
},
};
}
dataSend = {
...dataSend,
encryption: {
replicas: kesReplicas,
securityContext: kesSecurityContext,
image: kesImage,
...encryptionClientKeyPair,
...encryptionServerKeyPair,
...insertEncrypt,
},
};
}
let dataIDP: any = {};
switch (idpSelection) {
case "Built-in":
let keyarray = [];
for (let i = 0; i < accessKeys.length; i++) {
keyarray.push({
access_key: accessKeys[i],
secret_key: secretKeys[i],
});
}
dataIDP = {
keys: keyarray,
};
break;
case "OpenID":
dataIDP = {
oidc: {
configuration_url: openIDConfigurationURL,
client_id: openIDClientID,
secret_id: openIDSecretID,
claim_name: openIDClaimName,
callback_url: openIDCallbackURL,
scopes: openIDScopes,
},
};
break;
case "AD":
dataIDP = {
active_directory: {
url: ADURL,
skip_tls_verification: ADSkipTLS,
server_insecure: ADServerInsecure,
username_format: ADUserNameFormat,
username_search_filter: ADUserNameSearchFilter,
group_search_base_dn: ADGroupSearchBaseDN,
group_search_filter: ADGroupSearchFilter,
group_name_attribute: ADGroupNameAttribute,
user_dns: ADUserDNs,
lookup_bind_dn: ADLookupBindDN,
lookup_bind_password: ADLookupBindPassword,
user_dn_search_base_dn: ADUserDNSearchBaseDN,
user_dn_search_filter: ADUserDNSearchFilter,
server_start_tls: ADServerStartTLS,
},
};
break;
}
dataSend = {
...dataSend,
idp: { ...dataIDP },
};
api
.invoke("POST", `/api/v1/tenants`, dataSend)
.then((res) => {
const consoleSAList = get(res, "console", []);
let newSrvAcc: NewServiceAccount = {
idp: get(res, "externalIDP", false),
console: [],
};
if (consoleSAList) {
if (Array.isArray(consoleSAList)) {
const consoleItem = consoleSAList.map((consoleKey) => {
return {
accessKey: consoleKey.access_key,
secretKey: consoleKey.secret_key,
};
});
newSrvAcc.console = consoleItem;
} else {
newSrvAcc = {
console: {
accessKey: res.console.access_key,
secretKey: res.console.secret_key,
},
};
}
}
setAddSending(false);
setShowNewCredentials(true);
setCreatedAccount(newSrvAcc);
})
.catch((err: ErrorResponseHandler) => {
setAddSending(false);
setErrorSnackMessage(err);
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [addSending]);
const cancelButton = {
label: "Cancel",
type: "other",
enabled: true,
action: () => {
history.push("/tenants");
},
};
const requiredPages = [
"nameTenant",
"tenantSize",
"configure",
"affinity",
"identityProvider",
"security",
"encryption",
];
const createButton = {
label: "Create",
type: "submit",
enabled:
!addSending &&
selectedStorageClass !== "" &&
requiredPages.every((v) => validPages.includes(v)),
action: () => {
setAddSending(true);
},
};
const wizardSteps: IWizardElement[] = [
{
label: "Setup",
componentRender: <NameTenant />,
buttons: [cancelButton, createButton],
},
{
label: "Configure",
advancedOnly: true,
componentRender: <Configure />,
buttons: [cancelButton, createButton],
},
{
label: "Images",
advancedOnly: true,
componentRender: <Images />,
buttons: [cancelButton, createButton],
},
{
label: "Pod Placement",
advancedOnly: true,
componentRender: <Affinity />,
buttons: [cancelButton, createButton],
},
{
label: "Identity Provider",
advancedOnly: true,
componentRender: <IdentityProvider />,
buttons: [cancelButton, createButton],
},
{
label: "Security",
advancedOnly: true,
componentRender: <Security />,
buttons: [cancelButton, createButton],
},
{
label: "Encryption",
advancedOnly: true,
componentRender: <Encryption />,
buttons: [cancelButton, createButton],
},
];
let filteredWizardSteps = wizardSteps;
const closeCredentialsModal = () => {
resetAddTenantForm();
history.push("/tenants");
};
return (
<Fragment>
{showNewCredentials && (
<CredentialsPrompt
newServiceAccount={createdAccount}
open={showNewCredentials}
closeModal={() => {
closeCredentialsModal();
}}
entity="Tenant"
/>
)}
<PageHeader label={"Create New Tenant"} />
<Grid container>
{addSending && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
)}
<Grid item xs={12}>
<GenericWizard wizardSteps={filteredWizardSteps} />
</Grid>
</Grid>
</Fragment>
);
};
const mapState = (state: AppState) => ({
namespace: state.tenants.createTenant.fields.nameTenant.namespace,
validPages: state.tenants.createTenant.validPages,
fields: state.tenants.createTenant.fields,
certificates: state.tenants.createTenant.certificates,
selectedStorageClass:
state.tenants.createTenant.fields.nameTenant.selectedStorageClass,
});
const connector = connect(mapState, {
setErrorSnackMessage,
updateAddField,
resetAddTenantForm,
});
export default withStyles(styles)(connector(AddTenant));