You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by ju...@apache.org on 2020/06/06 01:35:37 UTC

[incubator-apisix-dashboard] 01/01: feat: refactor SSL

This is an automated email from the ASF dual-hosted git repository.

juzhiyuan pushed a commit to branch feat-ssl
in repository https://gitbox.apache.org/repos/asf/incubator-apisix-dashboard.git

commit 8b7a68b5290900e8a39a07ddf204b4e51149438c
Author: juzhiyuan <jj...@gmail.com>
AuthorDate: Sat Jun 6 09:35:13 2020 +0800

    feat: refactor SSL
---
 package.json                                       |  1 -
 src/pages/ssl/Create.tsx                           |  5 +-
 .../ssl/components/CertificateUploader/index.tsx   | 54 +++++----------
 src/pages/ssl/components/Step2/index.tsx           | 81 +++++++++++-----------
 src/pages/ssl/service.ts                           | 28 +++++++-
 yarn.lock                                          |  5 --
 6 files changed, 85 insertions(+), 89 deletions(-)

diff --git a/package.json b/package.json
index 1e38476..947a740 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,6 @@
     "classnames": "^2.2.6",
     "lodash": "^4.17.15",
     "moment": "^2.25.3",
-    "node-forge": "^0.9.1",
     "nzh": "^1.0.3",
     "omit.js": "^1.0.2",
     "path-to-regexp": "2.4.0",
diff --git a/src/pages/ssl/Create.tsx b/src/pages/ssl/Create.tsx
index 0ec6ff4..836a00a 100644
--- a/src/pages/ssl/Create.tsx
+++ b/src/pages/ssl/Create.tsx
@@ -1,7 +1,6 @@
 import React, { useState } from 'react';
 import { Card, Steps } from 'antd';
 import { PageHeaderWrapper } from '@ant-design/pro-layout';
-import { UploadFile } from 'antd/es/upload/interface';
 import Step1 from './components/Step1';
 import Step2 from './components/Step2';
 import Step3 from './components/Step3';
@@ -15,9 +14,7 @@ export interface FormData {
   sni?: string;
   cert?: string;
   key?: string;
-  expireTime?: Date;
-  publicKeyFileList?: UploadFile[];
-  privateKeyFileList?: UploadFile[];
+  expireTime?: Date | string;
 }
 
 export interface StepProps {
diff --git a/src/pages/ssl/components/CertificateUploader/index.tsx b/src/pages/ssl/components/CertificateUploader/index.tsx
index aaa35d6..135240d 100644
--- a/src/pages/ssl/components/CertificateUploader/index.tsx
+++ b/src/pages/ssl/components/CertificateUploader/index.tsx
@@ -1,36 +1,31 @@
 import React from 'react';
-import { Form, Button, Upload, message } from 'antd';
+import { Form, Button, Upload } from 'antd';
 import { UploadOutlined } from '@ant-design/icons';
 import { UploadFile } from 'antd/lib/upload/interface';
 import { useForm } from 'antd/es/form/util';
-import forge from 'node-forge';
-import { FormData } from '../../Create';
 import styles from '../../Create.less';
 
-export interface AltName {
-  value: string;
-}
-
 export type UploadType = 'PUBLIC_KEY' | 'PRIVATE_KEY';
 export interface UploadPublicSuccessData {
-  sni: string;
   cert: string;
-  expireTime: Date;
-  publicKeyFileList: UploadFile[];
+  publicKeyList: UploadFile[];
 }
 export interface UploadPrivateSuccessData {
   key: string;
-  privateKeyFileList: UploadFile[];
+  privateKeyList: UploadFile[];
 }
 
 interface UploaderProps {
+  data: {
+    publicKeyList: UploadFile[];
+    privateKeyList: UploadFile[];
+  };
   onSuccess(data: UploadPublicSuccessData | UploadPrivateSuccessData): void;
   onRemove(type: UploadType): void;
-  data: FormData;
 }
 
 const CertificateUploader: React.FC<UploaderProps> = ({ onSuccess, onRemove, data }) => {
-  const { publicKeyFileList = [], privateKeyFileList = [] } = data;
+  const { publicKeyList = [], privateKeyList = [] } = data;
   const [form] = useForm();
 
   const genUploadFile = (name = ''): UploadFile => {
@@ -50,26 +45,15 @@ const CertificateUploader: React.FC<UploaderProps> = ({ onSuccess, onRemove, dat
     fileReader.onload = function (event) {
       const { result } = event.currentTarget as any;
       if (type === 'PUBLIC_KEY') {
-        try {
-          const cert = forge.pki.certificateFromPem(result);
-          const altNames = (cert.extensions.find((item) => item.name === 'subjectAltName')
-            .altNames as AltName[])
-            .map((item) => item.value)
-            .join(';');
-          const uploadPublicData: UploadPublicSuccessData = {
-            sni: altNames,
-            cert: result,
-            expireTime: cert.validity.notAfter,
-            publicKeyFileList: [genUploadFile(fileName)],
-          };
-          onSuccess(uploadPublicData);
-        } catch (error) {
-          message.error('证书解析失败');
-        }
+        const uploadPublicData: UploadPublicSuccessData = {
+          cert: result,
+          publicKeyList: [genUploadFile(fileName)],
+        };
+        onSuccess(uploadPublicData);
       } else {
         const uploadprivateData: UploadPrivateSuccessData = {
           key: result,
-          privateKeyFileList: [genUploadFile(fileName)],
+          privateKeyList: [genUploadFile(fileName)],
         };
         onSuccess(uploadprivateData);
       }
@@ -85,26 +69,24 @@ const CertificateUploader: React.FC<UploaderProps> = ({ onSuccess, onRemove, dat
     <Form form={form} layout="horizontal" className={styles.stepForm}>
       <Form.Item>
         <Upload
-          accept=".pem"
           className={styles.stepForm}
           onRemove={() => onRemove('PUBLIC_KEY')}
-          fileList={publicKeyFileList}
+          fileList={publicKeyList}
           beforeUpload={(file, fileList) => beforeUpload(file, fileList, 'PUBLIC_KEY')}
         >
-          <Button disabled={publicKeyFileList.length === 1}>
+          <Button disabled={publicKeyList.length === 1}>
             <UploadOutlined /> 点击上传公钥
           </Button>
         </Upload>
       </Form.Item>
       <Form.Item>
         <Upload
-          accept=".key"
           className={styles.stepForm}
           onRemove={() => onRemove('PRIVATE_KEY')}
-          fileList={privateKeyFileList}
+          fileList={privateKeyList}
           beforeUpload={(file, fileList) => beforeUpload(file, fileList, 'PRIVATE_KEY')}
         >
-          <Button disabled={privateKeyFileList.length === 1}>
+          <Button disabled={privateKeyList.length === 1}>
             <UploadOutlined /> 点击上传私钥
           </Button>
         </Upload>
diff --git a/src/pages/ssl/components/Step2/index.tsx b/src/pages/ssl/components/Step2/index.tsx
index 51c320f..d15dc0e 100644
--- a/src/pages/ssl/components/Step2/index.tsx
+++ b/src/pages/ssl/components/Step2/index.tsx
@@ -1,71 +1,70 @@
-import React from 'react';
-import { Form, Button, message } from 'antd';
-import forge from 'node-forge';
+import React, { useState } from 'react';
+import { Form, Button } from 'antd';
+import { UploadFile } from 'antd/lib/upload/interface';
+
 import CertificateForm from '../CertificateForm';
-import {
-  CertificateUploader,
-  UploadPublicSuccessData,
-  UploadPrivateSuccessData,
-  UploadType,
-  AltName,
-} from '../CertificateUploader';
+import { CertificateUploader, UploadType } from '../CertificateUploader';
 import { StepProps } from '../../Create';
+import { verifyKeyPaire } from '../../service';
 
 const Step2: React.FC<StepProps> = ({ onStepChange, onFormChange, data }) => {
   const [form] = Form.useForm();
   const { validateFields } = form;
 
+  const [publicKeyList, setPublicKeyList] = useState<UploadFile[]>([]);
+  const [privateKeyList, setPrivateKeyList] = useState<UploadFile[]>([]);
+
   const onValidateForm = async () => {
-    if (data.createType === 'UPLOAD') {
-      if (!data.key || !data.cert) {
-        message.error('请检查证书');
-        return;
-      }
-      onStepChange(2);
-    }
-    if (data.createType === 'INPUT') {
-      validateFields().then((value) => {
-        try {
-          const cert = forge.pki.certificateFromPem(value.cert);
-          const altNames = (cert.extensions.find((item) => item.name === 'subjectAltName')
-            .altNames as AltName[])
-            .map((item) => item.value)
-            .join(';');
-          onFormChange({ ...value, sni: altNames, expireTime: cert.validity.notAfter });
-          onStepChange(2);
-        } catch (error) {
-          message.error('证书解析失败');
-        }
+    let keyPaire = { cert: '', key: '' };
+    validateFields()
+      .then((value) => {
+        keyPaire = { cert: value.cert, key: value.key };
+        return verifyKeyPaire(value.cert, value.key);
+      })
+      .then((_data) => {
+        const { snis, validity_end } = _data.data;
+        onFormChange({
+          ...keyPaire,
+          sni: snis.join(';'),
+          expireTime: new Date(validity_end * 1000).toLocaleString(),
+        });
+        onStepChange(2);
       });
-    }
-  };
-  const onUploadSuccess = (
-    uploadSuccessData: UploadPublicSuccessData | UploadPrivateSuccessData,
-  ) => {
-    onFormChange(uploadSuccessData);
   };
+
   const onRemove = (type: UploadType) => {
     if (type === 'PUBLIC_KEY') {
       onFormChange({
-        publicKeyFileList: [],
         cert: '',
         sni: '',
         expireTime: undefined,
       });
+      setPublicKeyList([]);
     } else {
       onFormChange({
-        privateKeyFileList: [],
         key: '',
       });
+      setPrivateKeyList([]);
     }
   };
   return (
     <>
-      {Boolean(data.createType === 'INPUT') && (
+      <div style={data.createType === 'INPUT' ? {} : { display: 'none' }}>
         <CertificateForm mode="EDIT" form={form} data={data} />
-      )}
+      </div>
       {Boolean(data.createType === 'UPLOAD') && (
-        <CertificateUploader onSuccess={onUploadSuccess} onRemove={onRemove} data={data} />
+        <CertificateUploader
+          onSuccess={(_data: any) => {
+            form.setFieldsValue(_data);
+            if (_data.cert) {
+              setPublicKeyList(_data.publicKeyList);
+            } else {
+              setPrivateKeyList(_data.privateKeyList);
+            }
+          }}
+          onRemove={onRemove}
+          data={{ publicKeyList, privateKeyList }}
+        />
       )}
       <div style={{ width: '100%', textAlign: 'center' }}>
         <Button
diff --git a/src/pages/ssl/service.ts b/src/pages/ssl/service.ts
index c699570..d122aa6 100644
--- a/src/pages/ssl/service.ts
+++ b/src/pages/ssl/service.ts
@@ -2,7 +2,7 @@ import { request } from 'umi';
 import { transformFetchListData, transformFetchItemData } from '@/transforms/global';
 
 export const fetchList = () =>
-  request('/ssl').then((data) => transformFetchListData<SSLModule.SSL>(data));
+  request('/ssls').then((data) => transformFetchListData<SSLModule.SSL>(data));
 
 export const fetchItem = (key: string) =>
   request(`/ssl/${key}`).then((data) => transformFetchItemData<SSLModule.SSL>(data));
@@ -13,7 +13,31 @@ export const remove = (key: string) =>
   });
 
 export const create = (data: SSLModule.SSL) =>
-  request('/ssl', {
+  request('/ssls', {
     method: 'POST',
     data,
   });
+
+type VerifyKeyPaireProps = {
+  code: string;
+  msg: string;
+  data: {
+    id: string;
+    create_time: number;
+    update_time: number;
+    validity_start: number;
+    validity_end: number;
+    snis: string[];
+    status: number;
+  };
+};
+
+/**
+ * 1. 校验证书是否匹配
+ * 2. 解析公钥内容
+ * */
+export const verifyKeyPaire = (cert = '', key = ''): Promise<VerifyKeyPaireProps> =>
+  request('/check_ssl_cert', {
+    method: 'POST',
+    data: { cert, key },
+  });
diff --git a/yarn.lock b/yarn.lock
index af5da19..b1b516e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11998,11 +11998,6 @@ node-fetch@^1.0.1:
     encoding "^0.1.11"
     is-stream "^1.0.1"
 
-node-forge@^0.9.1:
-  version "0.9.1"
-  resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz#775368e6846558ab6676858a4d8c6e8d16c677b5"
-  integrity sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==
-
 node-import-ts@^1.0.1, node-import-ts@^1.0.2:
   version "1.0.5"
   resolved "https://registry.npmjs.org/node-import-ts/-/node-import-ts-1.0.5.tgz#2b5aef3c8fa9babc9d1f948ddfeb62231c18d4f8"