From 6b491710c92ae7eaadc52c440981f769b541dcee Mon Sep 17 00:00:00 2001 From: ssongliu <73214554+ssongliu@users.noreply.github.com> Date: Tue, 19 Dec 2023 16:20:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20SSH=20=E6=94=AF=E6=8C=81=E5=90=8C?= =?UTF-8?q?=E6=97=B6=E7=9B=91=E5=90=AC=20IPv4=20=E5=8F=8A=20IPv6=20(#3381)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs #3359 --- backend/app/service/ssh.go | 48 +++++---- frontend/src/lang/modules/en.ts | 6 +- frontend/src/lang/modules/tw.ts | 4 +- frontend/src/lang/modules/zh.ts | 4 +- .../views/container/network/create/index.vue | 30 ++---- .../src/views/host/ssh/ssh/address/index.vue | 100 +++++++++++++++--- frontend/src/views/host/ssh/ssh/index.vue | 9 +- 7 files changed, 138 insertions(+), 63 deletions(-) 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('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') }} - +