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

linkinstar pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/answer.git


The following commit(s) were added to refs/heads/dev by this push:
     new d24027f3 Support PostgreSQL connection with SSL #1243 (#1244)
d24027f3 is described below

commit d24027f359d5c186f40e5ac83a4317d8f3158f1a
Author: unical1988 <[email protected]>
AuthorDate: Fri Mar 14 07:14:06 2025 +0100

    Support PostgreSQL connection with SSL #1243 (#1244)
    
    Support PostgreSQL connection with SSL
    
    ---------
    
    Co-authored-by: LinkinStars <[email protected]>
---
 configs/config.yaml                                |   6 +
 i18n/en_US.yaml                                    |  17 ++
 i18n/zh_CN.yaml                                    |  17 ++
 internal/install/install_req.go                    |  40 ++++-
 internal/service/question_common/question.go       |   3 +-
 .../pages/Install/components/SecondStep/index.tsx  | 199 ++++++++++++++++++++-
 ui/src/pages/Install/index.tsx                     |  37 ++++
 7 files changed, 302 insertions(+), 17 deletions(-)

diff --git a/configs/config.yaml b/configs/config.yaml
index 52671010..865bea57 100644
--- a/configs/config.yaml
+++ b/configs/config.yaml
@@ -24,6 +24,12 @@ data:
     connection: "/data/sqlite3/answer.db"
   cache:
     file_path: "/data/cache/cache.db"
+  ssl:
+    enabled: "no"
+    mode: "require"
+    cert_file: "/data/cache/ssl/certs/server-ca.pem"           
+    key_file: "/data/cache/ssl/certs/client-cert.pem"
+    pem_file: "/data/cache/ssl/certs/client-key.pem"             
 i18n:
   bundle_dir: "/data/i18n"
 swaggerui:
diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml
index f72646b7..647e7a21 100644
--- a/i18n/en_US.yaml
+++ b/i18n/en_US.yaml
@@ -1649,6 +1649,23 @@ ui:
       label: Database file
       placeholder: /data/answer.db
       msg: Database file cannot be empty.
+    ssl_enabled:
+      label: Enable SSL
+    ssl_enabled_on:
+      label: On
+    ssl_enabled_off:
+      label: Off
+    ssl_mode:
+      label: SSL Mode
+    key_file:
+      placeholder: Key file path
+      msg: Path to Key file cannot be empty
+    cert_file:
+      placeholder: Cert file path
+      msg: Path to Cert file cannot be empty
+    pem_file:
+      placeholder: Pem file path
+      msg: Path to Pem file cannot be empty
     config_yaml:
       title: Create config.yaml
       label: The config.yaml file created.
diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml
index 48f00cd6..71c2a756 100644
--- a/i18n/zh_CN.yaml
+++ b/i18n/zh_CN.yaml
@@ -1613,6 +1613,23 @@ ui:
       label: 数据库文件
       placeholder: /data/answer.db
       msg: 数据库文件不能为空。
+    ssl_enabled:
+      label: 使能够 SSL
+    ssl_enabled_on:
+      label: 开
+    ssl_enabled_off:
+      label: 关
+    ssl_mode:
+      label: SSL 模式
+    key_file:
+      placeholder: Key 文件路径
+      msg: 文件路径不能为空
+    cert_file:
+      placeholder: Cert 文件路径
+      msg: 文件路径不能为空
+    pem_file:
+      placeholder: Pem 文件路径
+      msg: 文件路径不能为空
     config_yaml:
       title: 创建 config.yaml
       label: 已创建 config.yaml 文件。
diff --git a/internal/install/install_req.go b/internal/install/install_req.go
index 8b2db0a8..939492c8 100644
--- a/internal/install/install_req.go
+++ b/internal/install/install_req.go
@@ -27,7 +27,9 @@ import (
        "github.com/apache/answer/internal/base/reason"
        "github.com/apache/answer/internal/base/validator"
        "github.com/apache/answer/pkg/checker"
+       "github.com/apache/answer/pkg/dir"
        "github.com/segmentfault/pacman/errors"
+       "github.com/segmentfault/pacman/log"
        "xorm.io/xorm/schemas"
 )
 
@@ -40,12 +42,17 @@ type CheckConfigFileResp struct {
 
 // CheckDatabaseReq check database
 type CheckDatabaseReq struct {
-       DbType     string `validate:"required,oneof=postgres sqlite3 mysql" 
json:"db_type"`
-       DbUsername string `json:"db_username"`
-       DbPassword string `json:"db_password"`
-       DbHost     string `json:"db_host"`
-       DbName     string `json:"db_name"`
-       DbFile     string `json:"db_file"`
+       DbType       string `validate:"required,oneof=postgres sqlite3 mysql" 
json:"db_type"`
+       DbUsername   string `json:"db_username"`
+       DbPassword   string `json:"db_password"`
+       DbHost       string `json:"db_host"`
+       DbName       string `json:"db_name"`
+       DbFile       string `json:"db_file"`
+       Ssl          bool   `json:"ssl_enabled"`
+       SslMode      string `json:"ssl_mode"`
+       SslCrt       string `json:"pem_file"`
+       SslKey       string `json:"key_file"`
+       SslCrtClient string `json:"cert_file"`
 }
 
 // GetConnection get connection string
@@ -59,8 +66,25 @@ func (r *CheckDatabaseReq) GetConnection() string {
        }
        if r.DbType == string(schemas.POSTGRES) {
                host, port := parsePgSQLHostPort(r.DbHost)
-               return fmt.Sprintf("host=%s port=%s user=%s password=%s 
dbname=%s sslmode=disable",
-                       host, port, r.DbUsername, r.DbPassword, r.DbName)
+               if !r.Ssl {
+                       return fmt.Sprintf("host=%s port=%s user=%s password=%s 
dbname=%s sslmode=disable",
+                               host, port, r.DbUsername, r.DbPassword, 
r.DbName)
+               } else if r.SslMode == "require" {
+                       return fmt.Sprintf("host=%s port=%s user=%s password=%s 
dbname=%s sslmode=%s",
+                               host, port, r.DbUsername, r.DbPassword, 
r.DbName, r.SslMode)
+               } else if r.SslMode == "verify-ca" || r.SslMode == 
"verify-full" {
+                       if dir.CheckFileExist(r.SslCrt) {
+                               log.Warnf("ssl crt file not exist: %s", 
r.SslCrt)
+                       }
+                       if dir.CheckFileExist(r.SslCrtClient) {
+                               log.Warnf("ssl crt client file not exist: %s", 
r.SslCrtClient)
+                       }
+                       if dir.CheckFileExist(r.SslKey) {
+                               log.Warnf("ssl key file not exist: %s", 
r.SslKey)
+                       }
+                       return fmt.Sprintf("host=%s port=%s user=%s password=%s 
dbname=%s sslmode=%s sslrootcert=%s sslcert=%s sslkey=%s",
+                               host, port, r.DbUsername, r.DbPassword, 
r.DbName, r.SslMode, r.SslCrt, r.SslCrtClient, r.SslKey)
+               }
        }
        return ""
 }
diff --git a/internal/service/question_common/question.go 
b/internal/service/question_common/question.go
index a0a5f413..5e5f9dbe 100644
--- a/internal/service/question_common/question.go
+++ b/internal/service/question_common/question.go
@@ -23,11 +23,12 @@ import (
        "context"
        "encoding/json"
        "fmt"
-       "github.com/apache/answer/internal/service/siteinfo_common"
        "math"
        "strings"
        "time"
 
+       "github.com/apache/answer/internal/service/siteinfo_common"
+
        "github.com/apache/answer/internal/base/constant"
        "github.com/apache/answer/internal/base/data"
        "github.com/apache/answer/internal/base/handler"
diff --git a/ui/src/pages/Install/components/SecondStep/index.tsx 
b/ui/src/pages/Install/components/SecondStep/index.tsx
index bf43f554..3206f82c 100644
--- a/ui/src/pages/Install/components/SecondStep/index.tsx
+++ b/ui/src/pages/Install/components/SecondStep/index.tsx
@@ -18,7 +18,7 @@
  */
 
 import { FC, FormEvent } from 'react';
-import { Form, Button } from 'react-bootstrap';
+import { Form, Button, Row, Col } from 'react-bootstrap';
 import { useTranslation } from 'react-i18next';
 
 import Progress from '../Progress';
@@ -46,13 +46,36 @@ const sqlData = [
   },
 ];
 
+const sslModes = [
+  {
+    value: 'require',
+  },
+  {
+    value: 'verify-ca',
+  },
+  {
+    value: 'verify-full',
+  },
+];
+
 const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
   const { t } = useTranslation('translation', { keyPrefix: 'install' });
 
   const checkValidated = (): boolean => {
     let bol = true;
-    const { db_type, db_username, db_password, db_host, db_name, db_file } =
-      data;
+    const {
+      db_type,
+      db_username,
+      db_password,
+      db_host,
+      db_name,
+      db_file,
+      ssl_enabled,
+      ssl_mode,
+      key_file,
+      cert_file,
+      pem_file,
+    } = data;
 
     if (db_type.value !== 'sqlite3') {
       if (!db_username.value) {
@@ -63,7 +86,6 @@ const Index: FC<Props> = ({ visible, data, changeCallback, 
nextCallback }) => {
           errorMsg: t('db_username.msg'),
         };
       }
-
       if (!db_password.value) {
         bol = false;
         data.db_password = {
@@ -81,7 +103,6 @@ const Index: FC<Props> = ({ visible, data, changeCallback, 
nextCallback }) => {
           errorMsg: t('db_host.msg'),
         };
       }
-
       if (!db_name.value) {
         bol = false;
         data.db_name = {
@@ -90,6 +111,34 @@ const Index: FC<Props> = ({ visible, data, changeCallback, 
nextCallback }) => {
           errorMsg: t('db_name.msg'),
         };
       }
+      if (db_type.value === 'postgres') {
+        if (ssl_enabled.value && ssl_mode.value !== 'require') {
+          if (!key_file.value) {
+            bol = false;
+            data.key_file = {
+              value: '',
+              isInvalid: true,
+              errorMsg: t('key_file.msg'),
+            };
+          }
+          if (!pem_file.value) {
+            bol = false;
+            data.pem_file = {
+              value: '',
+              isInvalid: true,
+              errorMsg: t('pem_file.msg'),
+            };
+          }
+          if (!cert_file.value) {
+            bol = false;
+            data.cert_file = {
+              value: '',
+              isInvalid: true,
+              errorMsg: t('cert_file.msg'),
+            };
+          }
+        }
+      }
     } else if (!db_file.value) {
       bol = false;
       data.db_file = {
@@ -179,12 +228,147 @@ const Index: FC<Props> = ({ visible, data, 
changeCallback, nextCallback }) => {
                 });
               }}
             />
-
             <Form.Control.Feedback type="invalid">
               {data.db_password.errorMsg}
             </Form.Control.Feedback>
           </Form.Group>
-
+          {data.db_type.value === 'postgres' && (
+            <Form.Group controlId="ssl_enabled" className="mb-3">
+              <Form.Label>{t('ssl_enabled.label')}</Form.Label>
+              <Form.Check
+                type="switch"
+                label={`${
+                  data.ssl_enabled.value
+                    ? t('ssl_enabled_on.label')
+                    : t('ssl_enabled_off.label')
+                }`}
+                checked={data.ssl_enabled.value}
+                onChange={(e) => {
+                  changeCallback({
+                    ssl_enabled: {
+                      value: e.target.checked,
+                      isInvalid: false,
+                      errorMsg: '',
+                    },
+                    ssl_mode: {
+                      value: 'require',
+                      isInvalid: false,
+                      errorMsg: '',
+                    },
+                    key_file: {
+                      value: '',
+                      isInvalid: false,
+                      errorMsg: '',
+                    },
+                    cert_file: {
+                      value: '',
+                      isInvalid: false,
+                      errorMsg: '',
+                    },
+                    pem_file: {
+                      value: '',
+                      isInvalid: false,
+                      errorMsg: '',
+                    },
+                  });
+                }}
+              />
+            </Form.Group>
+          )}
+          {data.db_type.value === 'postgres' && data.ssl_enabled.value && (
+            <Form.Group controlId="sslmodeOptionsDropdown" className="mb-3">
+              <Form.Label>{t('ssl_mode.label')}</Form.Label>
+              <Form.Select
+                value={data.ssl_mode.value}
+                onChange={(e) => {
+                  changeCallback({
+                    ssl_mode: {
+                      value: e.target.value,
+                      isInvalid: false,
+                      errorMsg: '',
+                    },
+                  });
+                }}>
+                {sslModes.map((item) => {
+                  return (
+                    <option value={item.value} key={item.value}>
+                      {item.value}
+                    </option>
+                  );
+                })}
+              </Form.Select>
+            </Form.Group>
+          )}
+          {data.db_type.value === 'postgres' &&
+            data.ssl_enabled.value &&
+            (data.ssl_mode.value === 'verify-ca' ||
+              data.ssl_mode.value === 'verify-full') && (
+              <Row className="mb-3">
+                <Form.Group as={Col} controlId="key_file">
+                  <Form.Control
+                    placeholder={t('key_file.placeholder')}
+                    aria-label="key_file"
+                    aria-describedby="basic-addon1"
+                    isInvalid={data.key_file.isInvalid}
+                    onChange={(e) => {
+                      changeCallback({
+                        key_file: {
+                          value: e.target.value,
+                          isInvalid: false,
+                          errorMsg: '',
+                        },
+                      });
+                    }}
+                    required
+                  />
+                  <Form.Control.Feedback type="invalid">
+                    {`${data.key_file.errorMsg}`}
+                  </Form.Control.Feedback>
+                </Form.Group>
+                <Form.Group as={Col} controlId="cert_file">
+                  <Form.Control
+                    placeholder={t('cert_file.placeholder')}
+                    aria-label="cert_file"
+                    aria-describedby="basic-addon1"
+                    isInvalid={data.cert_file.isInvalid}
+                    onChange={(e) => {
+                      changeCallback({
+                        cert_file: {
+                          value: e.target.value,
+                          isInvalid: false,
+                          errorMsg: '',
+                        },
+                      });
+                    }}
+                    required
+                  />
+                  <Form.Control.Feedback type="invalid">
+                    {`${data.cert_file.errorMsg}`}
+                  </Form.Control.Feedback>
+                </Form.Group>
+                <Form.Group as={Col} controlId="pem_file">
+                  <Form.Control
+                    placeholder={t('pem_file.placeholder')}
+                    aria-label="pem_file"
+                    aria-describedby="basic-addon1"
+                    isInvalid={data.pem_file.isInvalid}
+                    onChange={(e) => {
+                      changeCallback({
+                        pem_file: {
+                          value: e.target.value,
+                          isInvalid: false,
+                          errorMsg: '',
+                        },
+                      });
+                    }}
+                    required
+                  />
+                  <Form.Control.Feedback type="invalid">
+                    {`${data.pem_file.errorMsg}`}
+                  </Form.Control.Feedback>
+                </Form.Group>
+              </Row>
+            )}
           <Form.Group controlId="db_host" className="mb-3">
             <Form.Label>{t('db_host.label')}</Form.Label>
             <Form.Control
@@ -206,7 +390,6 @@ const Index: FC<Props> = ({ visible, data, changeCallback, 
nextCallback }) => {
               {data.db_host.errorMsg}
             </Form.Control.Feedback>
           </Form.Group>
-
           <Form.Group controlId="name" className="mb-3">
             <Form.Label>{t('db_name.label')}</Form.Label>
             <Form.Control
diff --git a/ui/src/pages/Install/index.tsx b/ui/src/pages/Install/index.tsx
index 3eb1ac89..ce7889b9 100644
--- a/ui/src/pages/Install/index.tsx
+++ b/ui/src/pages/Install/index.tsx
@@ -136,6 +136,31 @@ const Index: FC = () => {
       isInvalid: false,
       errorMsg: '',
     },
+    ssl_enabled: {
+      value: false,
+      isInvalid: false,
+      errorMsg: '',
+    },
+    ssl_mode: {
+      value: '',
+      isInvalid: false,
+      errorMsg: '',
+    },
+    pem_file: {
+      value: '',
+      isInvalid: false,
+      errorMsg: '',
+    },
+    key_file: {
+      value: '',
+      isInvalid: false,
+      errorMsg: '',
+    },
+    cert_file: {
+      value: '',
+      isInvalid: false,
+      errorMsg: '',
+    },
   });
 
   const { update, user } = loggedUserInfoStore();
@@ -156,6 +181,8 @@ const Index: FC = () => {
           db_username: { ...updatedFormData.db_username, value: 'postgres' },
           db_password: { ...updatedFormData.db_password, value: 'postgres' },
           db_host: { ...updatedFormData.db_host, value: 'db:5432' },
+          ssl_enabled: { ...updatedFormData.ssl_enabled, value: false },
+          ssl_mode: { ...updatedFormData.ssl_mode, value: '' },
         };
       }
       return updatedFormData;
@@ -192,6 +219,11 @@ const Index: FC = () => {
       db_host: formData.db_host.value,
       db_name: formData.db_name.value,
       db_file: formData.db_file.value,
+      ssl_enabled: formData.ssl_enabled.value,
+      ssl_mode: formData.ssl_mode.value,
+      pem_file: formData.pem_file.value,
+      key_file: formData.key_file.value,
+      cert_file: formData.cert_file.value,
     };
     installInit(params)
       .then(() => {
@@ -211,6 +243,11 @@ const Index: FC = () => {
       db_host: formData.db_host.value,
       db_name: formData.db_name.value,
       db_file: formData.db_file.value,
+      ssl_enabled: formData.ssl_enabled.value,
+      ssl_mode: formData.ssl_mode.value,
+      pem_file: formData.pem_file.value,
+      key_file: formData.key_file.value,
+      cert_file: formData.cert_file.value,
     };
     dbCheck(params)
       .then(() => {

Reply via email to