diff --git a/backend/app/api/v1/ssh.go b/backend/app/api/v1/ssh.go index dfda7b09c..1376e21bb 100644 --- a/backend/app/api/v1/ssh.go +++ b/backend/app/api/v1/ssh.go @@ -50,6 +50,33 @@ func (b *BaseApi) UpdateSSH(c *gin.Context) { helper.SuccessWithData(c, nil) } +// @Tags SSH +// @Summary Update host ssh setting by file +// @Description 上传文件更新 SSH 配置 +// @Accept json +// @Param request body dto.SSHConf true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /host/conffile/update [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"修改 SSH 配置文件","formatEN":"update SSH conf"} +func (b *BaseApi) UpdateSSHByfile(c *gin.Context) { + var req dto.SSHConf + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := global.VALID.Struct(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + if err := sshService.UpdateByFile(req.File); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} + // @Tags SSH // @Summary Generate host ssh secret // @Description 生成 ssh 密钥 diff --git a/backend/app/dto/ssh.go b/backend/app/dto/ssh.go index a4429d7c3..11ea751bb 100644 --- a/backend/app/dto/ssh.go +++ b/backend/app/dto/ssh.go @@ -20,6 +20,9 @@ type GenerateLoad struct { EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"` } +type SSHConf struct { + File string `json:"file"` +} type SearchSSHLog struct { PageInfo Info string `json:"info"` @@ -33,7 +36,8 @@ type SSHLog struct { } type SSHHistory struct { Date time.Time `json:"date"` - Belong string `json:"belong"` + DateStr string `json:"dateStr"` + IsLocal bool `json:"isLocal"` User string `json:"user"` AuthMode string `json:"authMode"` Address string `json:"address"` diff --git a/backend/app/service/ssh.go b/backend/app/service/ssh.go index 182d382b9..18dbb32ae 100644 --- a/backend/app/service/ssh.go +++ b/backend/app/service/ssh.go @@ -2,6 +2,7 @@ package service import ( "fmt" + "net" "os" "os/user" "path" @@ -13,6 +14,7 @@ import ( "github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/constant" "github.com/1Panel-dev/1Panel/backend/utils/cmd" + "github.com/1Panel-dev/1Panel/backend/utils/common" "github.com/1Panel-dev/1Panel/backend/utils/files" ) @@ -22,6 +24,7 @@ type SSHService struct{} type ISSHService interface { GetSSHInfo() (*dto.SSHInfo, error) + UpdateByFile(value string) error Update(key, value string) error GenerateSSH(req dto.GenerateSSH) error LoadSSHSecret(mode string) (string, error) @@ -87,6 +90,36 @@ func (u *SSHService) Update(key, value string) error { if _, err = file.WriteString(strings.Join(newFiles, "\n")); err != nil { return err } + sudo := "" + hasSudo := cmd.HasNoPasswordSudo() + if hasSudo { + sudo = "sudo" + } + if key == "Port" { + stdout, _ := cmd.Exec("getenforce") + if stdout == "Enforcing\n" { + _, _ = cmd.Execf("%s semanage port -a -t ssh_port_t -p tcp %s", sudo, value) + } + } + _, _ = cmd.Execf("%s systemctl restart sshd", sudo) + return nil +} + +func (u *SSHService) UpdateByFile(value string) error { + file, err := os.OpenFile(sshPath, os.O_WRONLY|os.O_TRUNC, 0666) + if err != nil { + return err + } + defer file.Close() + if _, err = file.WriteString(value); err != nil { + return err + } + sudo := "" + hasSudo := cmd.HasNoPasswordSudo() + if hasSudo { + sudo = "sudo" + } + _, _ = cmd.Execf("%s systemctl restart sshd", sudo) return nil } @@ -198,6 +231,15 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) { } data.SuccessfulCount = data.TotalCount - data.FailedCount + timeNow := time.Now() + nyc, _ := time.LoadLocation(common.LoadTimeZone()) + for i := 0; i < len(data.Logs); i++ { + data.Logs[i].IsLocal = isPrivateIP(net.ParseIP(data.Logs[i].Address)) + data.Logs[i].Date, _ = time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", timeNow.Year(), data.Logs[i].DateStr), nyc) + if data.Logs[i].Date.After(timeNow) { + data.Logs[i].Date = data.Logs[i].Date.AddDate(-1, 0, 0) + } + } sort.Slice(data.Logs, func(i, j int) bool { return data.Logs[i].Date.After(data.Logs[j].Date) }) @@ -249,27 +291,22 @@ func updateSSHConf(oldFiles []string, param string, value interface{}) []string func loadSuccessDatas(command string) []dto.SSHHistory { var datas []dto.SSHHistory - timeNow := time.Now() stdout2, err := cmd.Exec(command) if err == nil { lines := strings.Split(string(stdout2), "\n") for _, line := range lines { parts := strings.Fields(line) - if len(parts) != 14 { + if len(parts) < 14 { continue } historyItem := dto.SSHHistory{ - Belong: parts[3], + DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]), AuthMode: parts[6], User: parts[8], Address: parts[10], Port: parts[12], Status: constant.StatusSuccess, } - historyItem.Date, _ = time.Parse("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s %s %s", timeNow.Year(), parts[0], parts[1], parts[2])) - if historyItem.Date.After(timeNow) { - historyItem.Date = historyItem.Date.AddDate(-1, 0, 0) - } datas = append(datas, historyItem) } } @@ -278,29 +315,24 @@ func loadSuccessDatas(command string) []dto.SSHHistory { func loadFailedAuthDatas(command string) []dto.SSHHistory { var datas []dto.SSHHistory - timeNow := time.Now() stdout2, err := cmd.Exec(command) if err == nil { lines := strings.Split(string(stdout2), "\n") for _, line := range lines { parts := strings.Fields(line) - if len(parts) != 15 { + if len(parts) < 14 { continue } historyItem := dto.SSHHistory{ - Belong: parts[3], + DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]), AuthMode: parts[8], User: parts[10], Address: parts[11], Port: parts[13], Status: constant.StatusFailed, } - historyItem.Date, _ = time.Parse("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s %s %s", timeNow.Year(), parts[0], parts[1], parts[2])) - if historyItem.Date.After(timeNow) { - historyItem.Date = historyItem.Date.AddDate(-1, 0, 0) - } if strings.Contains(line, ": ") { - historyItem.Message = strings.Split(line, ": ")[0] + historyItem.Message = strings.Split(line, ": ")[1] } datas = append(datas, historyItem) } @@ -310,29 +342,24 @@ func loadFailedAuthDatas(command string) []dto.SSHHistory { func loadFailedSecureDatas(command string) []dto.SSHHistory { var datas []dto.SSHHistory - timeNow := time.Now() stdout2, err := cmd.Exec(command) if err == nil { lines := strings.Split(string(stdout2), "\n") for _, line := range lines { parts := strings.Fields(line) - if len(parts) != 14 { + if len(parts) < 14 { continue } historyItem := dto.SSHHistory{ - Belong: parts[3], + DateStr: fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]), AuthMode: parts[6], User: parts[8], Address: parts[10], Port: parts[12], Status: constant.StatusFailed, } - historyItem.Date, _ = time.Parse("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s %s %s", timeNow.Year(), parts[0], parts[1], parts[2])) - if historyItem.Date.After(timeNow) { - historyItem.Date = historyItem.Date.AddDate(-1, 0, 0) - } if strings.Contains(line, ": ") { - historyItem.Message = strings.Split(line, ": ")[0] + historyItem.Message = strings.Split(line, ": ")[1] } datas = append(datas, historyItem) } @@ -346,3 +373,16 @@ func handleGunzip(path string) error { } return nil } +func isPrivateIP(ip net.IP) bool { + if ip4 := ip.To4(); ip4 != nil { + switch true { + case ip4[0] == 10: + return true + case ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31: + return true + case ip4[0] == 192 && ip4[1] == 168: + return true + } + } + return false +} diff --git a/backend/app/service/ssh_test.go b/backend/app/service/ssh_test.go deleted file mode 100644 index c37da5b85..000000000 --- a/backend/app/service/ssh_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package service - -import ( - "fmt" - "os" - "path" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/1Panel-dev/1Panel/backend/constant" - "github.com/1Panel-dev/1Panel/backend/utils/cmd" -) - -func TestCa(t *testing.T) { - var ( - fileList []string - datas []history - successfulCount int - failedCount int - ) - baseDir := "/Users/slooop/Downloads" - if err := filepath.Walk(baseDir, func(pathItem string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if !info.IsDir() && strings.HasPrefix(info.Name(), "secure") || strings.HasPrefix(info.Name(), "auth") { - fileList = append(fileList, strings.ReplaceAll(pathItem, ".gz", "")) - } - return nil - }); err != nil { - fmt.Println(err) - } - for i := 0; i < len(fileList); i++ { - if strings.HasPrefix(path.Base(fileList[i]), "secure") { - dataItem := loadDatas2(fmt.Sprintf("cat %s | grep -a 'Failed password for' | grep -v 'invalid'", fileList[i]), 14, constant.StatusFailed) - failedCount += len(dataItem) - datas = append(datas, dataItem...) - } - if strings.HasPrefix(path.Base(fileList[i]), "auth.log") { - dataItem := loadDatas2(fmt.Sprintf("cat %s | grep -a 'Connection closed by authenticating user' | grep -a 'preauth'", fileList[i]), 15, constant.StatusFailed) - failedCount += len(dataItem) - datas = append(datas, dataItem...) - } - dataItem := loadDatas2(fmt.Sprintf("cat %s | grep Accepted", fileList[i]), 14, constant.StatusSuccess) - datas = append(datas, dataItem...) - } - successfulCount = len(datas) - failedCount - fmt.Println(len(datas), successfulCount, failedCount) -} - -func loadDatas2(command string, length int, status string) []history { - var datas []history - stdout2, err := cmd.Exec(command) - if err == nil { - lines := strings.Split(string(stdout2), "\n") - for _, line := range lines { - parts := strings.Fields(line) - if len(parts) != length { - continue - } - historyItem := history{ - Belong: parts[3], - User: parts[8], - AuthMode: parts[6], - Address: parts[10], - Port: parts[12], - Status: status, - } - dateStr := fmt.Sprintf("%d %s %s %s", time.Now().Year(), parts[0], parts[1], parts[2]) - historyItem.Date, _ = time.Parse("2006 Jan 2 15:04:05", dateStr) - // if err != nil { - // historyItem.Date, _ = time.Parse("2006 Jan 2 15:04:05", dateStr) - // } - fmt.Println(dateStr + "===>" + historyItem.Date.Format("2006.01.02 15:04:05")) - datas = append(datas, historyItem) - } - } - return datas -} - -func TestCas(t *testing.T) { - ss := "2023 May 9 14:48:28" - kk, err := time.Parse("2006 Jan 2 15:04:05", ss) - fmt.Println(kk, err) -} - -type history struct { - Date time.Time - Belong string - User string - AuthMode string - Address string - Port string - Status string - Message string -} diff --git a/backend/router/ro_host.go b/backend/router/ro_host.go index ed576acb3..3e75a5d04 100644 --- a/backend/router/ro_host.go +++ b/backend/router/ro_host.go @@ -40,6 +40,7 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) { hostRouter.POST("/ssh/generate", baseApi.GenerateSSH) hostRouter.POST("/ssh/secret", baseApi.LoadSSHSecret) hostRouter.POST("/ssh/log", baseApi.LoadSSHLogs) + hostRouter.POST("/ssh/conffile/update", baseApi.UpdateSSHByfile) hostRouter.GET("/command", baseApi.ListCommand) hostRouter.POST("/command", baseApi.CreateCommand) diff --git a/frontend/src/api/interface/host.ts b/frontend/src/api/interface/host.ts index 38499cc73..9d353475e 100644 --- a/frontend/src/api/interface/host.ts +++ b/frontend/src/api/interface/host.ts @@ -129,7 +129,7 @@ export namespace Host { } export interface sshHistory { date: Date; - belong: string; + isLocal: boolean; user: string; authMode: string; address: string; diff --git a/frontend/src/api/modules/host.ts b/frontend/src/api/modules/host.ts index a043768bf..b2ea23cee 100644 --- a/frontend/src/api/modules/host.ts +++ b/frontend/src/api/modules/host.ts @@ -104,6 +104,9 @@ export const getSSHInfo = () => { export const updateSSH = (key: string, value: string) => { return http.post(`/hosts/ssh/update`, { key: key, value: value }); }; +export const updateSSHByfile = (file: string) => { + return http.post(`/hosts/ssh/conffile/update`, { file: file }); +}; export const generateSecret = (params: Host.SSHGenerate) => { return http.post(`/hosts/ssh/generate`, params); }; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index d461662ef..68084e84e 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -815,6 +815,45 @@ const message = { 'The default user of the PHP operating environment: the user group is 1000:1000, it is normal that the users inside and outside the container show inconsistencies', searchHelper: 'Support wildcards such as *', }, + ssh: { + sshChange: 'SSH Setting', + sshChangeHelper: 'Are you sure to change the SSH {0} configuration to {1}?', + sshFileChangeHelper: + 'Modifying the configuration file may cause service availability. Exercise caution when performing this operation. Do you want to continue?', + port: 'Port', + 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.', + permitRootLogin: 'root user', + rootSettingHelper: 'The default login mode is SSH for user root.', + rootHelper1: 'Allow SSH login', + rootHelper2: 'Disable SSH login', + rootHelper3: 'Only key login is allowed', + rootHelper4: 'Only predefined commands can be executed. No other operations can be performed.', + passwordAuthentication: 'Password auth', + pwdAuthHelper: 'Whether to enable password authentication. This parameter is enabled by default.', + pubkeyAuthentication: 'Key auth', + key: 'Key', + pubkey: 'Key info', + encryptionMode: 'Encryption mode', + passwordHelper: 'Please enter a 6-10 digit encryption password', + generate: 'Generate key', + reGenerate: 'Regenerate key', + keyAuthHelper: 'Whether to enable key authentication. This parameter is enabled by default.', + useDNS: 'useDNS', + dnsHelper: + 'Controls whether the DNS resolution function is enabled on the SSH server to verify the identity of the connection.', + loginLogs: 'SSH login log', + loginUser: 'User', + loginMode: 'Login mode', + authenticating: 'Key', + publickey: 'Key', + password: 'Password', + belong: 'Belong', + local: 'Local', + remote: 'Remote', + }, setting: { all: 'All', panel: 'Panel', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 30e68fa6f..a029067c8 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -823,6 +823,7 @@ const message = { ssh: { sshChange: 'SSH 配置修改', sshChangeHelper: '确认将 SSH {0} 配置修改为 {1} 吗?', + sshFileChangeHelper: '直接修改配置文件可能会导致服务不可用,请谨慎操作,是否继续?', port: '连接端口', portHelper: '指定 SSH 服务监听的端口号,默认为 22。', listenAddress: '监听地址', @@ -832,7 +833,7 @@ const message = { rootHelper1: '允许 SSH 登录', rootHelper2: '禁止 SSH 登录', rootHelper3: '仅允许密钥登录', - rootHelper4: '仅允许带密码的密钥登录', + rootHelper4: '仅允许执行预先定义的命令,不能进行其他操作。', passwordAuthentication: '密码认证', pwdAuthHelper: '是否启用密码认证,默认启用。', pubkeyAuthentication: '密钥认证', @@ -849,7 +850,11 @@ const message = { loginUser: '用户', loginMode: '登录方式', authenticating: '密钥', + publickey: '密钥', password: '密码', + belong: '归属地', + local: '内网', + remote: '外网', }, setting: { all: '全部', diff --git a/frontend/src/views/host/ssh/log/index.vue b/frontend/src/views/host/ssh/log/index.vue index 92e49f89d..ff415fb52 100644 --- a/frontend/src/views/host/ssh/log/index.vue +++ b/frontend/src/views/host/ssh/log/index.vue @@ -6,8 +6,10 @@