From 2d130b5e37415f06fce1053b21a9a67a0c4fb717 Mon Sep 17 00:00:00 2001 From: xusongfu Date: Fri, 20 May 2022 17:36:06 +0800 Subject: [PATCH] feat: Function optimization in certificate 1. Add client_authentication:'MANDATORY' to the params if use CA certificate 2. Add validator for domain name 3. Add pem validator for certificate content and private key Change-Id: If6197357ee995678e25bb73e44787c016e66e83e --- src/locales/en.json | 9 ++- src/locales/zh.json | 9 ++- .../containers/Certificate/actions/Create.jsx | 80 +++++++++++++++++-- .../Listener/Actions/CreateListener.jsx | 1 + .../actions/StepCreate/index.jsx | 1 + src/resources/octavia/secrets.jsx | 45 +++++++++++ src/utils/validate.js | 9 +++ 7 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 src/resources/octavia/secrets.jsx diff --git a/src/locales/en.json b/src/locales/en.json index eda9643d..10a11f54 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1507,7 +1507,9 @@ "Please confirm your password!": "Please confirm your password!", "Please enter JSON in the correct format!": "Please enter JSON in the correct format!", "Please enter URL!": "Please enter URL!", - "Please enter a correct domain starting with \"http://\" or \"https://\"!": "Please enter a correct domain starting with \"http://\" or \"https://\"!", + "Please enter a correct certificate content, format is refer to the left tip!": "Please enter a correct certificate content, format is refer to the left tip!", + "Please enter a correct domain, format is refer to the left tip!": "Please enter a correct domain, format is refer to the left tip!", + "Please enter a correct private key, format is refer to the left tip!": "Please enter a correct private key, format is refer to the left tip!", "Please enter a file link starting with \"http://\" or \"https://\"!": "Please enter a file link starting with \"http://\" or \"https://\"!", "Please enter a memory page size, such as: 1024, 1024MB": "Please enter a memory page size, such as: 1024, 1024MB", "Please enter a valid ASCII code": "Please enter a valid ASCII code", @@ -2031,14 +2033,17 @@ "The affiliated Domain cannot be modified after creation": "The affiliated Domain cannot be modified after creation", "The amphora instance is required for load balancing service setup and is not recommended": "The amphora instance is required for load balancing service setup and is not recommended", "The associated floating IP, virtual adapter, volume and other resources will be automatically disassociated.": "The associated floating IP, virtual adapter, volume and other resources will be automatically disassociated.", + "The certificate contains information such as the public key and signature of the certificate. The extension of the certificate is \"pem\" or \"crt\", you can directly enter certificate content or upload certificate file.": "The certificate contains information such as the public key and signature of the certificate. The extension of the certificate is \"pem\" or \"crt\", you can directly enter certificate content or upload certificate file.", "The creation instruction has been issued, please refresh to see the actual situation in the list.": "The creation instruction has been issued, please refresh to see the actual situation in the list.", "The creation instruction was issued successfully, instance: {name}. \n You can wait for a few seconds to follow the changes of the list data or manually refresh the data to get the final display result.": "The creation instruction was issued successfully, instance: {name}. \n You can wait for a few seconds to follow the changes of the list data or manually refresh the data to get the final display result.", "The current operation can be performed when the instance is online:": "The current operation can be performed when the instance is online:", "The current operation requires the instance to be shut down:": "The current operation requires the instance to be shut down:", "The description can be up to 255 characters long.": "The description can be up to 255 characters long.", + "The domain name can only be composed of letters, numbers, dashes, in A dash cannot be at the beginning or end, and a single string cannot exceed more than 63 characters, separated by dots; At most can support 30 domain names, separated by commas;The length of a single domain name does not exceed 100 characters, and the total length degree does not exceed 1024 characters.": "The domain name can only be composed of letters, numbers, dashes, in A dash cannot be at the beginning or end, and a single string cannot exceed more than 63 characters, separated by dots; At most can support 30 domain names, separated by commas;The length of a single domain name does not exceed 100 characters, and the total length degree does not exceed 1024 characters.", "The entire inspection process takes 5 to 10 minutes, so you need to be patient. After the registration is completed, the node configuration status will return to the manageable status.": "The entire inspection process takes 5 to 10 minutes, so you need to be patient. After the registration is completed, the node configuration status will return to the manageable status.", "The feasible configuration of cloud-init or cloudbase-init service in the image is not synced to image's properties, so the Login Name is unknown.": "The feasible configuration of cloud-init or cloudbase-init service in the image is not synced to image's properties, so the Login Name is unknown.", "The file with the same name will be overwritten.": "The file with the same name will be overwritten.", + "The format of the certificate content is: by \"----BEGIN CERTIFICATE-----\" as the beginning,\"-----END CERTIFICATE----\" as the end, 64 characters per line, the last line does not exceed 64 characters, and there cannot be blank lines.": "The format of the certificate content is: by \"----BEGIN CERTIFICATE-----\" as the beginning,\"-----END CERTIFICATE----\" as the end, 64 characters per line, the last line does not exceed 64 characters, and there cannot be blank lines.", "The instance architecture diagram mainly shows the overall architecture composition of the instance. If you need to view the network topology of the instance, please go to: ": "The instance architecture diagram mainly shows the overall architecture composition of the instance. If you need to view the network topology of the instance, please go to: ", "The instance deleted immediately cannot be restored": "The instance deleted immediately cannot be restored", "The instance is not shut down, unable to restore.": "The instance is not shut down, unable to restore.", @@ -2067,6 +2072,8 @@ "The password must not be the same as the previous {num}": "The password must not be the same as the previous {num}", "The port created here will be automatically deleted when detach. If you need a reusable port, please go to the Virtual Adapter page to create and attach the port to instance.": "The port created here will be automatically deleted when detach. If you need a reusable port, please go to the Virtual Adapter page to create and attach the port to instance.", "The port of this fip is in use, Please change another port.": "The port of this fip is in use, Please change another port.", + "The private key content format is: with \"-----BEGIN RSA PRIVATE KEY-----\" as the beginning,\"-----END RSA PRIVATE KEY-----\" as the end, 64 characters per line, the last line does not exceed 64 characters, and there cannot be blank lines.": "The private key content format is: with \"-----BEGIN RSA PRIVATE KEY-----\" as the beginning,\"-----END RSA PRIVATE KEY-----\" as the end, 64 characters per line, the last line does not exceed 64 characters, and there cannot be blank lines.", + "The private key of the certificate, the extension of the private key is \"key\", you can directly enter the content of the private key file or upload a private key that conforms to the format document.": "The private key of the certificate, the extension of the private key is \"key\", you can directly enter the content of the private key file or upload a private key that conforms to the format document.", "The resource class of the scheduled node needs to correspond to the resource class name of the flavor used by the ironic instance (for example, the resource class name of the scheduling node is baremetal.with-GPU, and the custom resource class name of the flavor is CUSTOM_BAREMETAL_WITH_GPU=1).": "The resource class of the scheduled node needs to correspond to the resource class name of the flavor used by the ironic instance (for example, the resource class name of the scheduling node is baremetal.with-GPU, and the custom resource class name of the flavor is CUSTOM_BAREMETAL_WITH_GPU=1).", "The security group is similar to the firewall function and is used to set up network access control. ": "The security group is similar to the firewall function and is used to set up network access control. ", "The security group is similar to the firewall function for setting up network access control, or you can go to the console and create a new security group. (Note: The security group you selected will work on all virtual LANS on the instances.)": "The security group is similar to the firewall function for setting up network access control, or you can go to the console and create a new security group. (Note: The security group you selected will work on all virtual LANS on the instances.)", diff --git a/src/locales/zh.json b/src/locales/zh.json index c6ad8e77..61e0aa25 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -1507,7 +1507,9 @@ "Please confirm your password!": "请确认您的密码", "Please enter JSON in the correct format!": "请输入正确格式的JSON!", "Please enter URL!": "请输入URL!", - "Please enter a correct domain starting with \"http://\" or \"https://\"!": "请输入以“http://”或“https://”开头的域名!", + "Please enter a correct certificate content, format is refer to the left tip!": "请输入正确的证书内容,格式参考左边提示!", + "Please enter a correct domain, format is refer to the left tip!": "请输入正确的域名,格式参考左边提示!", + "Please enter a correct private key, format is refer to the left tip!": "请输入正确的密钥,格式参考左边提示!", "Please enter a file link starting with \"http://\" or \"https://\"!": "请输入以“http://”或“https://”开头的文件链接!", "Please enter a memory page size, such as: 1024, 1024MB": "请输入内存页大小,如:1024, 1024MB", "Please enter a valid ASCII code": "请输入有效的ASCII码", @@ -2031,14 +2033,17 @@ "The affiliated Domain cannot be modified after creation": "所属域不可修改", "The amphora instance is required for load balancing service setup and is not recommended": "amphora 相关的云主机为负载均衡服务搭建所需,不建议选择", "The associated floating IP, virtual adapter, volume and other resources will be automatically disassociated.": "绑定的浮动IP、网卡、云硬盘等资源将自动解绑。", + "The certificate contains information such as the public key and signature of the certificate. The extension of the certificate is \"pem\" or \"crt\", you can directly enter certificate content or upload certificate file.": "证书包含证书的公钥和签名等信息,证书扩展名为”pem”或”crt”,您可直接输入证书内容或上传证书文件。", "The creation instruction has been issued, please refresh to see the actual situation in the list.": "创建指令已下发,请刷新查看云主机列表中的实际情况。", "The creation instruction was issued successfully, instance: {name}. \n You can wait for a few seconds to follow the changes of the list data or manually refresh the data to get the final display result.": "创建指令下发成功,实例名称:{name}。 \n 您可等待几秒关注列表数据的变更或是手动刷新数据,以获取最终展示结果。", "The current operation can be performed when the instance is online:": "当前操作可在云主机在线状态下进行:", "The current operation requires the instance to be shut down:": "当前操作需要云主机在关机状态下进行:", "The description can be up to 255 characters long.": "描述最长为255字符", + "The domain name can only be composed of letters, numbers, dashes, in A dash cannot be at the beginning or end, and a single string cannot exceed more than 63 characters, separated by dots; At most can support 30 domain names, separated by commas;The length of a single domain name does not exceed 100 characters, and the total length degree does not exceed 1024 characters.": "域名只能由字母,数字,中划线组成,中划线不能在开头或末尾,单个字符串不超过63个字符,字符串间以点分隔;最多可支持30个域名,域名间以英文逗号分隔;单个域名长度不超过100个字符,且总长度不超过1024个字符。", "The entire inspection process takes 5 to 10 minutes, so you need to be patient. After the registration is completed, the node configuration status will return to the manageable status.": "检查的整个过程需要耗费 5 到 10 分钟时间,您需要耐心等待。在完成注册后,节点配置状态会重新回到可管理状态。", "The feasible configuration of cloud-init or cloudbase-init service in the image is not synced to image's properties, so the Login Name is unknown.": "镜像中的cloud-init或cloudbase-init服务的预制配置未同步至镜像属性, 登录名未知", "The file with the same name will be overwritten.": "对同名文件将会进行文件覆盖操作。", + "The format of the certificate content is: by \"----BEGIN CERTIFICATE-----\" as the beginning,\"-----END CERTIFICATE----\" as the end, 64 characters per line, the last line does not exceed 64 characters, and there cannot be blank lines.": "证书内容格式为:以”-----BEGIN CERTIFICATE-----”作为开头,以“-----END CERTIFICATE----”作为结尾,每行64字符,最后一行不超过64字符,不能有空行。", "The instance architecture diagram mainly shows the overall architecture composition of the instance. If you need to view the network topology of the instance, please go to: ": "云主机架构图主要展示云主机的总体架构组成。如果需要查看云主机的网络拓扑,请转到:", "The instance deleted immediately cannot be restored": "立即删除的云主机无法恢复", "The instance is not shut down, unable to restore.": "云主机不处于关机状态,不支持恢复备份操作。", @@ -2067,6 +2072,8 @@ "The password must not be the same as the previous {num}": "用户新密码不能与前{num}次密码相同", "The port created here will be automatically deleted when detach. If you need a reusable port, please go to the Virtual Adapter page to create and attach the port to instance.": "此处创建的网卡会在卸载的时候被自动删除,如果需要可复用的网卡,请前往虚拟网卡页面创建再从虚拟网卡页面绑定云主机。", "The port of this fip is in use, Please change another port.": "FIP的此端口已经在使用中,请更换另一个端口。", + "The private key content format is: with \"-----BEGIN RSA PRIVATE KEY-----\" as the beginning,\"-----END RSA PRIVATE KEY-----\" as the end, 64 characters per line, the last line does not exceed 64 characters, and there cannot be blank lines.": "私钥内容格式为:以“-----BEGIN RSA PRIVATE KEY-----”,以“-----END RSA PRIVATE KEY-----”作为结尾,每行64字符,最后一行不超过64字符,不能有空行。", + "The private key of the certificate, the extension of the private key is \"key\", you can directly enter the content of the private key file or upload a private key that conforms to the format document.": "证书的私钥,私钥扩展名为”key”,您可直接输入私钥文件内容或上传符合格式的私钥文件。", "The resource class of the scheduled node needs to correspond to the resource class name of the flavor used by the ironic instance (for example, the resource class name of the scheduling node is baremetal.with-GPU, and the custom resource class name of the flavor is CUSTOM_BAREMETAL_WITH_GPU=1).": "被调度节点的资源类需要与裸机实例使用的云主机类型的资源类名称对应(比如:调度节点的资源类名称为 baremetal.with-GPU,云主机类型的资源类名称为CUSTOM_BAREMETAL_WITH_GPU=1 )。", "The security group is similar to the firewall function and is used to set up network access control. ": "安全组类似防火墙功能,用于设置网络访问控制。", "The security group is similar to the firewall function for setting up network access control, or you can go to the console and create a new security group. (Note: The security group you selected will work on all virtual LANS on the instances.)": "安全组类似防火墙功能,用于设置网络访问控制,您也可以前往控制台新建安全组。(注:您所选的安全组将作用于云主机的全部虚拟网卡。)", diff --git a/src/pages/network/containers/Certificate/actions/Create.jsx b/src/pages/network/containers/Certificate/actions/Create.jsx index 476fae6d..74ff2c1a 100644 --- a/src/pages/network/containers/Certificate/actions/Create.jsx +++ b/src/pages/network/containers/Certificate/actions/Create.jsx @@ -15,7 +15,12 @@ import { inject, observer } from 'mobx-react'; import { ModalAction } from 'containers/Action'; import { getOptions } from 'utils/index'; +import { isDomain } from 'utils/validate'; import { certificateMode } from 'resources/octavia/lb'; +import { + certificateContentTip, + certificateKeyPairTip, +} from 'resources/octavia/secrets'; import globalContainersStore from 'stores/barbican/containers'; import moment from 'moment'; @@ -34,6 +39,14 @@ export class CreateAction extends ModalAction { return t('Create Certificate'); } + static get modalSize() { + return 'large'; + } + + getModalSize() { + return 'large'; + } + get defaultValue() { const data = { mode: 'SERVER', @@ -47,17 +60,64 @@ export class CreateAction extends ModalAction { validateDomain = (rule, value) => { if (value === undefined || value === '') return Promise.resolve(); - const urlReg = /^https?:\/\/(.*)/; - if (!urlReg.test(value)) { + const domains = value.split(','); + const allCorrect = domains.every((it) => it.length <= 100 && isDomain(it)); + if (domains.length > 30 || !allCorrect) { return Promise.reject( - t( - 'Please enter a correct domain starting with "http://" or "https://"!' - ) + t('Please enter a correct domain, format is refer to the left tip!') ); } return Promise.resolve(); }; + validateCertificateContent = (rule, value) => { + if (!value) return Promise.reject(); + const keys = value.split(/\n/g); + const start = keys.shift(); + const end = keys.pop(); + const middleCorrect = keys.every((it, index) => { + if (index === keys.length - 1) { + return it.length <= 64; + } + return it.length === 64; + }); + if ( + start === '-----BEGIN CERTIFICATE-----' && + end === '-----END CERTIFICATE-----' && + middleCorrect + ) { + return Promise.resolve(); + } + return Promise.reject( + t( + 'Please enter a correct certificate content, format is refer to the left tip!' + ) + ); + }; + + validateCertificateKeyPair = (rule, value) => { + if (!value) return Promise.reject(); + const keys = value.split(/\n/g); + const start = keys.shift(); + const end = keys.pop(); + const middleCorrect = keys.every((it, index) => { + if (index === keys.length - 1) { + return it.length <= 64; + } + return it.length === 64; + }); + if ( + start === '-----BEGIN RSA PRIVATE KEY-----' && + end === '-----END RSA PRIVATE KEY-----' && + middleCorrect + ) { + return Promise.resolve(); + } + return Promise.reject( + t('Please enter a correct private key, format is refer to the left tip!') + ); + }; + get formItems() { const { mode } = this.state; return [ @@ -80,6 +140,8 @@ export class CreateAction extends ModalAction { type: 'textarea-from-file', placeholder: t('PEM encoding'), accept: '.crt,.pem', + tip: certificateContentTip, + validator: this.validateCertificateContent, required: true, }, { @@ -88,16 +150,22 @@ export class CreateAction extends ModalAction { type: 'textarea-from-file', placeholder: t('PEM encoding'), accept: '.key,.pem', + tip: certificateKeyPairTip, + validator: this.validateCertificateKeyPair, required: true, display: mode === 'SERVER', }, { name: 'domain', label: t('Domain Name'), - type: 'input', + type: 'textarea', placeholder: t('Please input'), + maxLength: 1024, hidden: mode === 'CA', validator: this.validateDomain, + tip: t( + 'The domain name can only be composed of letters, numbers, dashes, in A dash cannot be at the beginning or end, and a single string cannot exceed more than 63 characters, separated by dots; At most can support 30 domain names, separated by commas;The length of a single domain name does not exceed 100 characters, and the total length degree does not exceed 1024 characters.' + ), extra: t( 'If it is an SNI type certificate, a domain name needs to be specified' ), diff --git a/src/pages/network/containers/LoadBalancers/Listener/Actions/CreateListener.jsx b/src/pages/network/containers/LoadBalancers/Listener/Actions/CreateListener.jsx index f45c9487..1f13730b 100644 --- a/src/pages/network/containers/LoadBalancers/Listener/Actions/CreateListener.jsx +++ b/src/pages/network/containers/LoadBalancers/Listener/Actions/CreateListener.jsx @@ -214,6 +214,7 @@ export class Create extends ModalAction { if (client_ca_tls_container_ref) { data.client_ca_tls_container_ref = client_ca_tls_container_ref.selectedRows[0].secret_ref; + data.client_authentication = 'MANDATORY'; } if (sni_container_refs) { data.sni_container_refs = [ diff --git a/src/pages/network/containers/LoadBalancers/LoadBalancerInstance/actions/StepCreate/index.jsx b/src/pages/network/containers/LoadBalancers/LoadBalancerInstance/actions/StepCreate/index.jsx index 066dcf61..1846a6b3 100644 --- a/src/pages/network/containers/LoadBalancers/LoadBalancerInstance/actions/StepCreate/index.jsx +++ b/src/pages/network/containers/LoadBalancers/LoadBalancerInstance/actions/StepCreate/index.jsx @@ -117,6 +117,7 @@ export class StepCreate extends StepAction { ) { listenerData.client_ca_tls_container_ref = listener_client_ca_tls_container_ref.selectedRows[0].secret_ref; + listenerData.client_authentication = 'MANDATORY'; } if (listener_sni_enabled && listener_sni_container_refs) { listenerData.sni_container_refs = [ diff --git a/src/resources/octavia/secrets.jsx b/src/resources/octavia/secrets.jsx new file mode 100644 index 00000000..d6b2704a --- /dev/null +++ b/src/resources/octavia/secrets.jsx @@ -0,0 +1,45 @@ +// Copyright 2021 99cloud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from 'react'; + +export const certificateContentTip = ( +
+

+ {t( + 'The certificate contains information such as the public key and signature of the certificate. The extension of the certificate is "pem" or "crt", you can directly enter certificate content or upload certificate file.' + )} +

+

+ {t( + 'The format of the certificate content is: by "----BEGIN CERTIFICATE-----" as the beginning,"-----END CERTIFICATE----" as the end, 64 characters per line, the last line does not exceed 64 characters, and there cannot be blank lines.' + )} +

+
+); + +export const certificateKeyPairTip = ( +
+

+ {t( + 'The private key of the certificate, the extension of the private key is "key", you can directly enter the content of the private key file or upload a private key that conforms to the format document.' + )} +

+

+ {t( + 'The private key content format is: with "-----BEGIN RSA PRIVATE KEY-----" as the beginning,"-----END RSA PRIVATE KEY-----" as the end, 64 characters per line, the last line does not exceed 64 characters, and there cannot be blank lines.' + )} +

+
+); diff --git a/src/utils/validate.js b/src/utils/validate.js index 937a7450..87fcb1dc 100644 --- a/src/utils/validate.js +++ b/src/utils/validate.js @@ -50,6 +50,8 @@ const ipv6CidrOnly = const asciiRegex = /^[\x00-\x7f]*$/; // eslint-disable-line const swiftFileNameRegex = /^[A-Za-z\u4e00-\u9fa5]+[A-Za-z\u4e00-\u9fa5\d-.]{2,62}$/; +const domainRegex = + /^[a-zA-Z0-9]([-a-zA-Z0-9]{0,62}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([-a-zA-Z0-9]{0,62}[a-zA-Z0-9])?)*$/; export const regex = { cidr, @@ -72,6 +74,13 @@ export const regex = { swiftFileNameRegex, }; +export const isDomain = (value) => { + if (value && isString(value)) { + return domainRegex.test(value); + } + return false; +}; + export const isPhoneNumber = (value) => isValidPhoneNumber(value); export const isEmailNumber = (value) => emailRegex.test(value);