diff --git a/backend/app/service/ssh.go b/backend/app/service/ssh.go
index 474d829be..303813b6a 100644
--- a/backend/app/service/ssh.go
+++ b/backend/app/service/ssh.go
@@ -48,7 +48,7 @@ func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
Status: constant.StatusEnable,
Message: "",
Port: "22",
- ListenAddress: "0.0.0.0",
+ ListenAddress: "",
PasswordAuthentication: "yes",
PubkeyAuthentication: "yes",
PermitRootLogin: "yes",
@@ -90,7 +90,12 @@ func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) {
data.Port = strings.ReplaceAll(line, "Port ", "")
}
if strings.HasPrefix(line, "ListenAddress ") {
- data.ListenAddress = strings.ReplaceAll(line, "ListenAddress ", "")
+ itemAddr := strings.ReplaceAll(line, "ListenAddress ", "")
+ if len(data.ListenAddress) != 0 {
+ data.ListenAddress += ("," + itemAddr)
+ } else {
+ data.ListenAddress = itemAddr
+ }
}
if strings.HasPrefix(line, "PasswordAuthentication ") {
data.PasswordAuthentication = strings.ReplaceAll(line, "PasswordAuthentication ", "")
@@ -366,33 +371,34 @@ func sortFileList(fileNames []sshFileItem) []sshFileItem {
return fileNames
}
-func updateSSHConf(oldFiles []string, param string, value interface{}) []string {
- hasKey := false
+func updateSSHConf(oldFiles []string, param string, value string) []string {
+ var valueItems []string
+ if param != "ListenAddress" {
+ valueItems = append(valueItems, value)
+ } else {
+ if value != "" {
+ valueItems = strings.Split(value, ",")
+ }
+ }
var newFiles []string
for _, line := range oldFiles {
- if strings.HasPrefix(line, param+" ") {
- newFiles = append(newFiles, fmt.Sprintf("%s %v", param, value))
- hasKey = true
+ lineItem := strings.TrimSpace(line)
+ if (strings.HasPrefix(lineItem, param) || strings.HasPrefix(lineItem, fmt.Sprintf("#%s", param))) && len(valueItems) != 0 {
+ newFiles = append(newFiles, fmt.Sprintf("%s %s", param, valueItems[0]))
+ valueItems = valueItems[1:]
+ continue
+ }
+ if strings.HasPrefix(lineItem, param) && len(valueItems) == 0 {
+ newFiles = append(newFiles, fmt.Sprintf("#%s", line))
continue
}
newFiles = append(newFiles, line)
}
- if !hasKey {
- newFiles = []string{}
- for _, line := range oldFiles {
- if strings.HasPrefix(line, fmt.Sprintf("#%s ", param)) && !hasKey {
- newFiles = append(newFiles, fmt.Sprintf("%s %v", param, value))
- hasKey = true
- continue
- }
- newFiles = append(newFiles, line)
+ if len(valueItems) != 0 {
+ for _, item := range valueItems {
+ newFiles = append(newFiles, fmt.Sprintf("%s %s", param, item))
}
}
- if !hasKey {
- newFiles = []string{}
- newFiles = append(newFiles, oldFiles...)
- newFiles = append(newFiles, fmt.Sprintf("%s %v", param, value))
- }
return newFiles
}
diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts
index 91910214f..8fc96540a 100644
--- a/frontend/src/lang/modules/en.ts
+++ b/frontend/src/lang/modules/en.ts
@@ -1120,8 +1120,10 @@ const message = {
'Modifying the configuration file may cause service availability. Exercise caution when performing this operation. Do you want to continue?',
portHelper: 'Specifies the port number monitored by the SSH service. The default port number is 22.',
listenAddress: 'Listening address',
- addressHelper:
- 'Specify the IP address monitored by the SSH service. The default value is 0.0.0.0. That is, all network interfaces are monitored.',
+ allV4V6: '0.0.0.0:{0}(IPv4) and :::{0}(IPv6)',
+ listenHelper:
+ 'Canceling IPv4 and IPv6 settings simultaneously will listen on both 0.0.0.0:{0}(IPv4) and :::{0}(IPv6)',
+ addressHelper: 'Specify the IP address on which the SSH service will listen',
permitRootLogin: 'root user',
rootSettingHelper: 'The default login mode is SSH for user root.',
rootHelper1: 'Allow SSH login',
diff --git a/frontend/src/lang/modules/tw.ts b/frontend/src/lang/modules/tw.ts
index 559c92cfb..eab77dc3e 100644
--- a/frontend/src/lang/modules/tw.ts
+++ b/frontend/src/lang/modules/tw.ts
@@ -1066,7 +1066,9 @@ const message = {
port: '連接端口',
portHelper: '指定 SSH 服務監聽的端口號,默認為 22。',
listenAddress: '監聽地址',
- addressHelper: '指定 SSH 服務監聽的 IP 地址,默認為 0.0.0.0,即所有的網絡接口都會被監聽。',
+ allV4V6: '0.0.0.0:{0}(IPv4)和 :::{0}(IPv6)',
+ listenHelper: '同時取消 IPv4 和 IPv6 設置,將會同時監聽 0.0.0.0:{0}(IPv4) 和 :::{0}(IPv6)',
+ addressHelper: '指定 SSH 服務監聽的 IP 地址',
permitRootLogin: 'root 用戶',
rootSettingHelper: 'root 用戶 SSH 登錄方式,默認所有 SSH 登錄。',
rootHelper1: '允許 SSH 登錄',
diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts
index 9b797c164..6d8cd24fa 100644
--- a/frontend/src/lang/modules/zh.ts
+++ b/frontend/src/lang/modules/zh.ts
@@ -1067,7 +1067,9 @@ const message = {
port: '连接端口',
portHelper: '指定 SSH 服务监听的端口号,默认为 22。',
listenAddress: '监听地址',
- addressHelper: '指定 SSH 服务监听的 IP 地址,默认为 0.0.0.0,即所有的网络接口都会被监听。',
+ allV4V6: '0.0.0.0:{0}(IPv4) 和 :::{0}(IPv6)',
+ listenHelper: '同时取消 IPv4 和 IPv6 设置,将会同时监听 0.0.0.0:{0}(IPv4) 和 :::{0}(IPv6)',
+ addressHelper: '指定 SSH 服务监听的 IP 地址',
permitRootLogin: 'root 用户',
rootSettingHelper: 'root 用户 SSH 登录方式,默认所有 SSH 登录。',
rootHelper1: '允许 SSH 登录',
diff --git a/frontend/src/views/container/network/create/index.vue b/frontend/src/views/container/network/create/index.vue
index 5a3eac14d..93034beb0 100644
--- a/frontend/src/views/container/network/create/index.vue
+++ b/frontend/src/views/container/network/create/index.vue
@@ -150,7 +150,7 @@ import { ElForm } from 'element-plus';
import { createNetwork } from '@/api/modules/container';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { MsgSuccess } from '@/utils/message';
-import { checkIpV6 } from '@/utils/util';
+import { checkIp, checkIpV6 } from '@/utils/util';
const loading = ref(false);
@@ -214,9 +214,7 @@ function checkGateway(rule: any, value: any, callback: any) {
if (value === '') {
callback();
}
- const reg =
- /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;
- if (!reg.test(value) && value !== '') {
+ if (checkIp(value)) {
return callback(new Error(i18n.global.t('commons.rule.formatErr')));
}
callback();
@@ -225,27 +223,11 @@ function checkGateway(rule: any, value: any, callback: any) {
function checkGatewayV6(rule: any, value: any, callback: any) {
if (value === '') {
callback();
- } else {
- const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])';
- const IPv4AddressFormat = `(${IPv4SegmentFormat}[.]){3}${IPv4SegmentFormat}`;
- const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})';
- const IPv6AddressRegExp = new RegExp(
- '^(' +
- `(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|` +
- `(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|` +
- `(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|` +
- `(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|` +
- `(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|` +
- `(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|` +
- `(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|` +
- `(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` +
- ')(%[0-9a-zA-Z-.:]{1,})?$',
- );
- if (!IPv6AddressRegExp.test(value) && value !== '') {
- return callback(new Error(i18n.global.t('commons.rule.formatErr')));
- }
- callback();
}
+ if (checkIpV6(value)) {
+ return callback(new Error(i18n.global.t('commons.rule.formatErr')));
+ }
+ callback();
}
function checkCidr(rule: any, value: any, callback: any) {
diff --git a/frontend/src/views/host/ssh/ssh/address/index.vue b/frontend/src/views/host/ssh/ssh/address/index.vue
index 582469615..a8ee8ca7d 100644
--- a/frontend/src/views/host/ssh/ssh/address/index.vue
+++ b/frontend/src/views/host/ssh/ssh/address/index.vue
@@ -10,15 +10,40 @@
-
+
-
-
+
+
+
+ {{ $t('ssh.listenHelper', [form.port]) }}
+
+
+
+
+
+ {{ $t('setting.bindAll') }}
+
+
+
+
+
+ {{ $t('setting.bindAll') }}
+
+
@@ -39,26 +64,66 @@ import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { ElMessageBox, FormInstance } from 'element-plus';
-import { Rules } from '@/global/form-rules';
import { updateSSH } from '@/api/modules/host';
import DrawerHeader from '@/components/drawer-header/index.vue';
+import { checkIp, checkIpV6 } from '@/utils/util';
const emit = defineEmits<{ (e: 'search'): void }>();
interface DialogProps {
+ port: number;
address: string;
}
const drawerVisible = ref();
const loading = ref();
const form = reactive({
- listenAddress: '0.0.0.0',
+ port: 22,
+ ipv4All: false,
+ ipv6All: false,
+ listenAddressV4: '',
+ listenAddressV6: '',
});
+const rules = reactive({
+ listenAddressV4: [{ validator: checkIPv4, trigger: 'blur' }],
+ listenAddressV6: [{ validator: checkIPv6, trigger: 'blur' }],
+});
+
+function checkIPv4(rule: any, value: any, callback: any) {
+ if (value === '') {
+ callback();
+ }
+ if (checkIp(value)) {
+ return callback(new Error(i18n.global.t('commons.rule.ip')));
+ }
+ callback();
+}
+
+function checkIPv6(rule: any, value: any, callback: any) {
+ if (value === '') {
+ callback();
+ }
+ if (checkIpV6(value)) {
+ return callback(new Error(i18n.global.t('commons.rule.ip')));
+ }
+ callback();
+}
+
const formRef = ref();
const acceptParams = (params: DialogProps): void => {
- form.listenAddress = params.address;
+ let items = params.address.split(',');
+ for (const item of items) {
+ if (item.indexOf(':') !== -1) {
+ form.listenAddressV6 = item;
+ form.ipv6All = item === '::';
+ continue;
+ }
+ form.listenAddressV4 = item;
+ form.ipv4All = item === '0.0.0.0';
+ }
+ form.port = params.port;
drawerVisible.value = true;
};
@@ -66,8 +131,19 @@ const onSave = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
+ let itemAddr = [];
+ if (form.listenAddressV4 !== '') {
+ itemAddr.push(form.listenAddressV4);
+ }
+ if (form.listenAddressV6 !== '') {
+ itemAddr.push(form.listenAddressV6);
+ }
+ let addr =
+ itemAddr.join(',') === '' || itemAddr.join(',') === '0.0.0.0,::'
+ ? i18n.global.t('ssh.allV4V6', [form.port])
+ : itemAddr.join(',');
ElMessageBox.confirm(
- i18n.global.t('ssh.sshChangeHelper', [i18n.global.t('ssh.listenAddress'), form.listenAddress]),
+ i18n.global.t('ssh.sshChangeHelper', [i18n.global.t('ssh.listenAddress'), addr]),
i18n.global.t('ssh.sshChange'),
{
confirmButtonText: i18n.global.t('commons.button.confirm'),
@@ -79,7 +155,7 @@ const onSave = async (formEl: FormInstance | undefined) => {
let params = {
key: 'ListenAddress',
oldValue: '',
- newValue: form.listenAddress,
+ newValue: itemAddr.join(','),
};
loading.value = true;
await updateSSH(params)
diff --git a/frontend/src/views/host/ssh/ssh/index.vue b/frontend/src/views/host/ssh/ssh/index.vue
index 00e25b911..6ad9be2fd 100644
--- a/frontend/src/views/host/ssh/ssh/index.vue
+++ b/frontend/src/views/host/ssh/ssh/index.vue
@@ -71,7 +71,7 @@
{{ $t('ssh.portHelper') }}
-
+
{{ $t('commons.button.set') }}
@@ -184,6 +184,7 @@ const form = reactive({
message: '',
port: 22,
listenAddress: '',
+ listenAddressItem: '',
passwordAuthentication: 'yes',
pubkeyAuthentication: 'yes',
encryptionMode: '',
@@ -222,7 +223,7 @@ const onChangeRoot = () => {
rootsRef.value.acceptParams({ permitRootLogin: form.permitRootLogin });
};
const onChangeAddress = () => {
- addressRef.value.acceptParams({ address: form.listenAddress });
+ addressRef.value.acceptParams({ address: form.listenAddress, port: form.port });
};
const onOperate = async (operation: string) => {
@@ -325,6 +326,10 @@ const search = async () => {
form.port = Number(res.data.port);
autoStart.value = res.data.autoStart ? 'enable' : 'disable';
form.listenAddress = res.data.listenAddress;
+ form.listenAddressItem =
+ form.listenAddress === '' || form.listenAddress === '0.0.0.0,::'
+ ? i18n.global.t('ssh.allV4V6', [form.port])
+ : form.listenAddress;
form.passwordAuthentication = res.data.passwordAuthentication;
form.pubkeyAuthentication = res.data.pubkeyAuthentication;
form.permitRootLogin = res.data.permitRootLogin;