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(() => {