Skip to content

Commit d24027f

Browse files
Support PostgreSQL connection with SSL #1243 (#1244)
Support PostgreSQL connection with SSL --------- Co-authored-by: LinkinStars <[email protected]>
1 parent ed2a5ba commit d24027f

File tree

7 files changed

+302
-17
lines changed

7 files changed

+302
-17
lines changed

configs/config.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ data:
2424
connection: "/data/sqlite3/answer.db"
2525
cache:
2626
file_path: "/data/cache/cache.db"
27+
ssl:
28+
enabled: "no"
29+
mode: "require"
30+
cert_file: "/data/cache/ssl/certs/server-ca.pem"
31+
key_file: "/data/cache/ssl/certs/client-cert.pem"
32+
pem_file: "/data/cache/ssl/certs/client-key.pem"
2733
i18n:
2834
bundle_dir: "/data/i18n"
2935
swaggerui:

i18n/en_US.yaml

+17
Original file line numberDiff line numberDiff line change
@@ -1649,6 +1649,23 @@ ui:
16491649
label: Database file
16501650
placeholder: /data/answer.db
16511651
msg: Database file cannot be empty.
1652+
ssl_enabled:
1653+
label: Enable SSL
1654+
ssl_enabled_on:
1655+
label: On
1656+
ssl_enabled_off:
1657+
label: Off
1658+
ssl_mode:
1659+
label: SSL Mode
1660+
key_file:
1661+
placeholder: Key file path
1662+
msg: Path to Key file cannot be empty
1663+
cert_file:
1664+
placeholder: Cert file path
1665+
msg: Path to Cert file cannot be empty
1666+
pem_file:
1667+
placeholder: Pem file path
1668+
msg: Path to Pem file cannot be empty
16521669
config_yaml:
16531670
title: Create config.yaml
16541671
label: The config.yaml file created.

i18n/zh_CN.yaml

+17
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,23 @@ ui:
16131613
label: 数据库文件
16141614
placeholder: /data/answer.db
16151615
msg: 数据库文件不能为空。
1616+
ssl_enabled:
1617+
label: 使能够 SSL
1618+
ssl_enabled_on:
1619+
label:
1620+
ssl_enabled_off:
1621+
label:
1622+
ssl_mode:
1623+
label: SSL 模式
1624+
key_file:
1625+
placeholder: Key 文件路径
1626+
msg: 文件路径不能为空
1627+
cert_file:
1628+
placeholder: Cert 文件路径
1629+
msg: 文件路径不能为空
1630+
pem_file:
1631+
placeholder: Pem 文件路径
1632+
msg: 文件路径不能为空
16161633
config_yaml:
16171634
title: 创建 config.yaml
16181635
label: 已创建 config.yaml 文件。

internal/install/install_req.go

+32-8
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ import (
2727
"github.com/apache/answer/internal/base/reason"
2828
"github.com/apache/answer/internal/base/validator"
2929
"github.com/apache/answer/pkg/checker"
30+
"github.com/apache/answer/pkg/dir"
3031
"github.com/segmentfault/pacman/errors"
32+
"github.com/segmentfault/pacman/log"
3133
"xorm.io/xorm/schemas"
3234
)
3335

@@ -40,12 +42,17 @@ type CheckConfigFileResp struct {
4042

4143
// CheckDatabaseReq check database
4244
type CheckDatabaseReq struct {
43-
DbType string `validate:"required,oneof=postgres sqlite3 mysql" json:"db_type"`
44-
DbUsername string `json:"db_username"`
45-
DbPassword string `json:"db_password"`
46-
DbHost string `json:"db_host"`
47-
DbName string `json:"db_name"`
48-
DbFile string `json:"db_file"`
45+
DbType string `validate:"required,oneof=postgres sqlite3 mysql" json:"db_type"`
46+
DbUsername string `json:"db_username"`
47+
DbPassword string `json:"db_password"`
48+
DbHost string `json:"db_host"`
49+
DbName string `json:"db_name"`
50+
DbFile string `json:"db_file"`
51+
Ssl bool `json:"ssl_enabled"`
52+
SslMode string `json:"ssl_mode"`
53+
SslCrt string `json:"pem_file"`
54+
SslKey string `json:"key_file"`
55+
SslCrtClient string `json:"cert_file"`
4956
}
5057

5158
// GetConnection get connection string
@@ -59,8 +66,25 @@ func (r *CheckDatabaseReq) GetConnection() string {
5966
}
6067
if r.DbType == string(schemas.POSTGRES) {
6168
host, port := parsePgSQLHostPort(r.DbHost)
62-
return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
63-
host, port, r.DbUsername, r.DbPassword, r.DbName)
69+
if !r.Ssl {
70+
return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
71+
host, port, r.DbUsername, r.DbPassword, r.DbName)
72+
} else if r.SslMode == "require" {
73+
return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
74+
host, port, r.DbUsername, r.DbPassword, r.DbName, r.SslMode)
75+
} else if r.SslMode == "verify-ca" || r.SslMode == "verify-full" {
76+
if dir.CheckFileExist(r.SslCrt) {
77+
log.Warnf("ssl crt file not exist: %s", r.SslCrt)
78+
}
79+
if dir.CheckFileExist(r.SslCrtClient) {
80+
log.Warnf("ssl crt client file not exist: %s", r.SslCrtClient)
81+
}
82+
if dir.CheckFileExist(r.SslKey) {
83+
log.Warnf("ssl key file not exist: %s", r.SslKey)
84+
}
85+
return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s sslrootcert=%s sslcert=%s sslkey=%s",
86+
host, port, r.DbUsername, r.DbPassword, r.DbName, r.SslMode, r.SslCrt, r.SslCrtClient, r.SslKey)
87+
}
6488
}
6589
return ""
6690
}

internal/service/question_common/question.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ import (
2323
"context"
2424
"encoding/json"
2525
"fmt"
26-
"github.com/apache/answer/internal/service/siteinfo_common"
2726
"math"
2827
"strings"
2928
"time"
3029

30+
"github.com/apache/answer/internal/service/siteinfo_common"
31+
3132
"github.com/apache/answer/internal/base/constant"
3233
"github.com/apache/answer/internal/base/data"
3334
"github.com/apache/answer/internal/base/handler"

ui/src/pages/Install/components/SecondStep/index.tsx

+191-8
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919

2020
import { FC, FormEvent } from 'react';
21-
import { Form, Button } from 'react-bootstrap';
21+
import { Form, Button, Row, Col } from 'react-bootstrap';
2222
import { useTranslation } from 'react-i18next';
2323

2424
import Progress from '../Progress';
@@ -46,13 +46,36 @@ const sqlData = [
4646
},
4747
];
4848

49+
const sslModes = [
50+
{
51+
value: 'require',
52+
},
53+
{
54+
value: 'verify-ca',
55+
},
56+
{
57+
value: 'verify-full',
58+
},
59+
];
60+
4961
const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
5062
const { t } = useTranslation('translation', { keyPrefix: 'install' });
5163

5264
const checkValidated = (): boolean => {
5365
let bol = true;
54-
const { db_type, db_username, db_password, db_host, db_name, db_file } =
55-
data;
66+
const {
67+
db_type,
68+
db_username,
69+
db_password,
70+
db_host,
71+
db_name,
72+
db_file,
73+
ssl_enabled,
74+
ssl_mode,
75+
key_file,
76+
cert_file,
77+
pem_file,
78+
} = data;
5679

5780
if (db_type.value !== 'sqlite3') {
5881
if (!db_username.value) {
@@ -63,7 +86,6 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
6386
errorMsg: t('db_username.msg'),
6487
};
6588
}
66-
6789
if (!db_password.value) {
6890
bol = false;
6991
data.db_password = {
@@ -81,7 +103,6 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
81103
errorMsg: t('db_host.msg'),
82104
};
83105
}
84-
85106
if (!db_name.value) {
86107
bol = false;
87108
data.db_name = {
@@ -90,6 +111,34 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
90111
errorMsg: t('db_name.msg'),
91112
};
92113
}
114+
if (db_type.value === 'postgres') {
115+
if (ssl_enabled.value && ssl_mode.value !== 'require') {
116+
if (!key_file.value) {
117+
bol = false;
118+
data.key_file = {
119+
value: '',
120+
isInvalid: true,
121+
errorMsg: t('key_file.msg'),
122+
};
123+
}
124+
if (!pem_file.value) {
125+
bol = false;
126+
data.pem_file = {
127+
value: '',
128+
isInvalid: true,
129+
errorMsg: t('pem_file.msg'),
130+
};
131+
}
132+
if (!cert_file.value) {
133+
bol = false;
134+
data.cert_file = {
135+
value: '',
136+
isInvalid: true,
137+
errorMsg: t('cert_file.msg'),
138+
};
139+
}
140+
}
141+
}
93142
} else if (!db_file.value) {
94143
bol = false;
95144
data.db_file = {
@@ -179,12 +228,147 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
179228
});
180229
}}
181230
/>
182-
183231
<Form.Control.Feedback type="invalid">
184232
{data.db_password.errorMsg}
185233
</Form.Control.Feedback>
186234
</Form.Group>
187-
235+
{data.db_type.value === 'postgres' && (
236+
<Form.Group controlId="ssl_enabled" className="mb-3">
237+
<Form.Label>{t('ssl_enabled.label')}</Form.Label>
238+
<Form.Check
239+
type="switch"
240+
label={`${
241+
data.ssl_enabled.value
242+
? t('ssl_enabled_on.label')
243+
: t('ssl_enabled_off.label')
244+
}`}
245+
checked={data.ssl_enabled.value}
246+
onChange={(e) => {
247+
changeCallback({
248+
ssl_enabled: {
249+
value: e.target.checked,
250+
isInvalid: false,
251+
errorMsg: '',
252+
},
253+
ssl_mode: {
254+
value: 'require',
255+
isInvalid: false,
256+
errorMsg: '',
257+
},
258+
key_file: {
259+
value: '',
260+
isInvalid: false,
261+
errorMsg: '',
262+
},
263+
cert_file: {
264+
value: '',
265+
isInvalid: false,
266+
errorMsg: '',
267+
},
268+
pem_file: {
269+
value: '',
270+
isInvalid: false,
271+
errorMsg: '',
272+
},
273+
});
274+
}}
275+
/>
276+
</Form.Group>
277+
)}
278+
{data.db_type.value === 'postgres' && data.ssl_enabled.value && (
279+
<Form.Group controlId="sslmodeOptionsDropdown" className="mb-3">
280+
<Form.Label>{t('ssl_mode.label')}</Form.Label>
281+
<Form.Select
282+
value={data.ssl_mode.value}
283+
onChange={(e) => {
284+
changeCallback({
285+
ssl_mode: {
286+
value: e.target.value,
287+
isInvalid: false,
288+
errorMsg: '',
289+
},
290+
});
291+
}}>
292+
{sslModes.map((item) => {
293+
return (
294+
<option value={item.value} key={item.value}>
295+
{item.value}
296+
</option>
297+
);
298+
})}
299+
</Form.Select>
300+
</Form.Group>
301+
)}
302+
{data.db_type.value === 'postgres' &&
303+
data.ssl_enabled.value &&
304+
(data.ssl_mode.value === 'verify-ca' ||
305+
data.ssl_mode.value === 'verify-full') && (
306+
<Row className="mb-3">
307+
<Form.Group as={Col} controlId="key_file">
308+
<Form.Control
309+
placeholder={t('key_file.placeholder')}
310+
aria-label="key_file"
311+
aria-describedby="basic-addon1"
312+
isInvalid={data.key_file.isInvalid}
313+
onChange={(e) => {
314+
changeCallback({
315+
key_file: {
316+
value: e.target.value,
317+
isInvalid: false,
318+
errorMsg: '',
319+
},
320+
});
321+
}}
322+
required
323+
/>
324+
<Form.Control.Feedback type="invalid">
325+
{`${data.key_file.errorMsg}`}
326+
</Form.Control.Feedback>
327+
</Form.Group>
328+
<Form.Group as={Col} controlId="cert_file">
329+
<Form.Control
330+
placeholder={t('cert_file.placeholder')}
331+
aria-label="cert_file"
332+
aria-describedby="basic-addon1"
333+
isInvalid={data.cert_file.isInvalid}
334+
onChange={(e) => {
335+
changeCallback({
336+
cert_file: {
337+
value: e.target.value,
338+
isInvalid: false,
339+
errorMsg: '',
340+
},
341+
});
342+
}}
343+
required
344+
/>
345+
<Form.Control.Feedback type="invalid">
346+
{`${data.cert_file.errorMsg}`}
347+
</Form.Control.Feedback>
348+
</Form.Group>
349+
<Form.Group as={Col} controlId="pem_file">
350+
<Form.Control
351+
placeholder={t('pem_file.placeholder')}
352+
aria-label="pem_file"
353+
aria-describedby="basic-addon1"
354+
isInvalid={data.pem_file.isInvalid}
355+
onChange={(e) => {
356+
changeCallback({
357+
pem_file: {
358+
value: e.target.value,
359+
isInvalid: false,
360+
errorMsg: '',
361+
},
362+
});
363+
}}
364+
required
365+
/>
366+
<Form.Control.Feedback type="invalid">
367+
{`${data.pem_file.errorMsg}`}
368+
</Form.Control.Feedback>
369+
</Form.Group>
370+
</Row>
371+
)}
188372
<Form.Group controlId="db_host" className="mb-3">
189373
<Form.Label>{t('db_host.label')}</Form.Label>
190374
<Form.Control
@@ -206,7 +390,6 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
206390
{data.db_host.errorMsg}
207391
</Form.Control.Feedback>
208392
</Form.Group>
209-
210393
<Form.Group controlId="name" className="mb-3">
211394
<Form.Label>{t('db_name.label')}</Form.Label>
212395
<Form.Control

0 commit comments

Comments
 (0)