Compare commits

...
9 Commits
20 changed files with 232 additions and 94 deletions
+8 -3
View File
@@ -163,11 +163,16 @@ func (b *BaseApi) ContainerWsSsh(c *gin.Context) {
}
defer wsConn.Close()
cmds := fmt.Sprintf("docker exec %s %s", containerID, command)
cmds := []string{"exec", containerID, command}
if len(user) != 0 {
cmds = fmt.Sprintf("docker exec -u %s %s %s", user, containerID, command)
cmds = []string{"exec", "-u", user, containerID, command}
}
stdout, err := cmd.Exec(cmds)
if cmd.CheckIllegal(user, containerID, command) {
if wshandleError(wsConn, errors.New(" The command contains illegal characters.")) {
return
}
}
stdout, err := cmd.ExecWithCheck("docker", cmds...)
if wshandleError(wsConn, errors.WithMessage(err, stdout)) {
return
}
+3
View File
@@ -197,6 +197,9 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
if strings.Contains(sourceDir, "/") {
itemDir := strings.ReplaceAll(sourceDir[strings.LastIndex(sourceDir, "/"):], "/", "")
aheadDir := sourceDir[:strings.LastIndex(sourceDir, "/")]
if len(aheadDir) == 0 {
aheadDir = "/"
}
path += fmt.Sprintf("-C %s %s", aheadDir, itemDir)
} else {
path = sourceDir
+2
View File
@@ -136,12 +136,14 @@ func (u *DockerService) UpdateConf(req dto.SettingUpdate) error {
switch req.Key {
case "Registries":
req.Value = strings.TrimRight(req.Value, ",")
if len(req.Value) == 0 {
delete(daemonMap, "insecure-registries")
} else {
daemonMap["insecure-registries"] = strings.Split(req.Value, ",")
}
case "Mirrors":
req.Value = strings.TrimRight(req.Value, ",")
if len(req.Value) == 0 {
delete(daemonMap, "registry-mirrors")
} else {
+11 -3
View File
@@ -3,12 +3,14 @@ package service
import (
"context"
"encoding/json"
"fmt"
"os"
"path"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
"github.com/1Panel-dev/1Panel/backend/utils/cmd"
@@ -76,6 +78,9 @@ func (u *ImageRepoService) List() ([]dto.ImageRepoOption, error) {
}
func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error {
if cmd.CheckIllegal(req.Username, req.Password, req.DownloadUrl) {
return buserr.New(constant.ErrRepoConn)
}
imageRepo, _ := imageRepoRepo.Get(commonRepo.WithByName(req.Name))
if imageRepo.ID != 0 {
return constant.ErrRecordExist
@@ -142,6 +147,9 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
if req.ID == 1 {
return errors.New("The default value cannot be deleted !")
}
if cmd.CheckIllegal(req.Username, req.Password, req.DownloadUrl) {
return buserr.New(constant.ErrRepoConn)
}
repo, err := imageRepoRepo.Get(commonRepo.WithByID(req.ID))
if err != nil {
return err
@@ -149,7 +157,7 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
if repo.DownloadUrl != req.DownloadUrl || (!repo.Auth && req.Auth) {
_ = u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update")
if repo.Auth {
_, _ = cmd.Execf("docker logout %s", repo.DownloadUrl)
_, _ = cmd.ExecWithCheck("docker", "logout", repo.DownloadUrl)
}
stdout, err := cmd.Exec("systemctl restart docker")
if err != nil {
@@ -176,9 +184,9 @@ func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error {
}
func (u *ImageRepoService) CheckConn(host, user, password string) error {
stdout, err := cmd.Execf("docker login -u %s -p %s %s", user, password, host)
stdout, err := cmd.ExecWithCheck("docker", "login", "-u", user, "-p", password, host)
if err != nil {
return errors.New(string(stdout))
return fmt.Errorf("stdout: %s, stderr: %v", stdout, err)
}
if strings.Contains(string(stdout), "Login Succeeded") {
return nil
+97 -71
View File
@@ -229,29 +229,29 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
if len(req.Info) != 0 {
command = fmt.Sprintf(" | grep '%s'", req.Info)
}
for i := 0; i < len(fileList); i++ {
if strings.HasPrefix(path.Base(fileList[i]), "secure") {
commandItem := fmt.Sprintf("cat %s | grep -a 'Failed password for' | grep -v 'invalid' %s", fileList[i], command)
dataItem := loadFailedSecureDatas(commandItem)
data.FailedCount += len(dataItem)
data.TotalCount += len(dataItem)
if req.Status != constant.StatusSuccess {
withAppend := len(data.Logs) < req.Page*req.PageSize
if req.Status != constant.StatusSuccess {
if strings.HasPrefix(path.Base(fileList[i]), "secure") {
commandItem := fmt.Sprintf("cat %s | grep -a 'Failed password for' | grep -v 'invalid' %s", fileList[i], command)
dataItem, itemTotal := loadFailedSecureDatas(commandItem, withAppend)
data.FailedCount += itemTotal
data.TotalCount += itemTotal
data.Logs = append(data.Logs, dataItem...)
}
if strings.HasPrefix(path.Base(fileList[i]), "auth.log") {
commandItem := fmt.Sprintf("cat %s | grep -a 'Connection closed by authenticating user' | grep -a 'preauth' %s", fileList[i], command)
dataItem, itemTotal := loadFailedAuthDatas(commandItem, withAppend)
data.FailedCount += itemTotal
data.TotalCount += itemTotal
data.Logs = append(data.Logs, dataItem...)
}
}
if strings.HasPrefix(path.Base(fileList[i]), "auth.log") {
commandItem := fmt.Sprintf("cat %s | grep -a 'Connection closed by authenticating user' | grep -a 'preauth' %s", fileList[i], command)
dataItem := loadFailedAuthDatas(commandItem)
data.FailedCount += len(dataItem)
data.TotalCount += len(dataItem)
if req.Status != constant.StatusSuccess {
data.Logs = append(data.Logs, dataItem...)
}
}
commandItem := fmt.Sprintf("cat %s | grep -a Accepted %s", fileList[i], command)
dataItem := loadSuccessDatas(commandItem)
data.TotalCount += len(dataItem)
if req.Status != constant.StatusFailed {
commandItem := fmt.Sprintf("cat %s | grep -a Accepted %s", fileList[i], command)
dataItem, itemTotal := loadSuccessDatas(commandItem, withAppend)
data.TotalCount += itemTotal
data.Logs = append(data.Logs, dataItem...)
}
}
@@ -279,7 +279,7 @@ func (u *SSHService) LoadLog(req dto.SearchSSHLog) (*dto.SSHLog, error) {
global.LOG.Errorf("load qqwry datas failed: %s", err)
}
var itemLogs []dto.SSHHistory
for i := len(data.Logs) - 1; i >= 0; i-- {
for i := 0; i < len(data.Logs); i++ {
data.Logs[i].Area = qqWry.Find(data.Logs[i].Address).Area
data.Logs[i].Date, _ = time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", timeNow.Year(), data.Logs[i].DateStr), nyc)
itemLogs = append(itemLogs, data.Logs[i])
@@ -293,18 +293,17 @@ func sortFileList(fileNames []string) []string {
if len(fileNames) < 2 {
return fileNames
}
var itemFile []string
if strings.Contains(fileNames[0], "secure") {
if strings.HasPrefix(path.Base(fileNames[0]), "secure") {
var itemFile []string
sort.Slice(fileNames, func(i, j int) bool {
return fileNames[i] < fileNames[j]
return fileNames[i] > fileNames[j]
})
itemFile = append(itemFile, fileNames[1:]...)
itemFile = append(itemFile, fileNames[0])
itemFile = append(itemFile, fileNames[len(fileNames)-1])
itemFile = append(itemFile, fileNames[:len(fileNames)-2]...)
return itemFile
}
sort.Slice(fileNames, func(i, j int) bool {
return fileNames[i] > fileNames[j]
return fileNames[i] < fileNames[j]
})
return fileNames
}
@@ -339,82 +338,109 @@ func updateSSHConf(oldFiles []string, param string, value interface{}) []string
return newFiles
}
func loadSuccessDatas(command string) []dto.SSHHistory {
var datas []dto.SSHHistory
func loadSuccessDatas(command string, withAppend bool) ([]dto.SSHHistory, int) {
var (
datas []dto.SSHHistory
totalNum int
)
stdout2, err := cmd.Exec(command)
if err == nil {
lines := strings.Split(string(stdout2), "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(lines) == 0 {
return datas, 0
}
for i := len(lines) - 1; i >= 0; i-- {
parts := strings.Fields(lines[i])
if len(parts) < 14 {
continue
}
historyItem := dto.SSHHistory{
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,
totalNum++
if withAppend {
historyItem := dto.SSHHistory{
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,
}
datas = append(datas, historyItem)
}
datas = append(datas, historyItem)
}
}
return datas
return datas, totalNum
}
func loadFailedAuthDatas(command string) []dto.SSHHistory {
var datas []dto.SSHHistory
func loadFailedAuthDatas(command string, withAppend bool) ([]dto.SSHHistory, int) {
var (
datas []dto.SSHHistory
totalNum int
)
stdout2, err := cmd.Exec(command)
if err == nil {
lines := strings.Split(string(stdout2), "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(lines) == 0 {
return datas, 0
}
for i := len(lines) - 1; i >= 0; i-- {
parts := strings.Fields(lines[i])
if len(parts) < 14 {
continue
}
historyItem := dto.SSHHistory{
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,
totalNum++
if withAppend {
historyItem := dto.SSHHistory{
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,
}
if strings.Contains(lines[i], ": ") {
historyItem.Message = strings.Split(lines[i], ": ")[1]
}
datas = append(datas, historyItem)
}
if strings.Contains(line, ": ") {
historyItem.Message = strings.Split(line, ": ")[1]
}
datas = append(datas, historyItem)
}
}
return datas
return datas, totalNum
}
func loadFailedSecureDatas(command string) []dto.SSHHistory {
var datas []dto.SSHHistory
func loadFailedSecureDatas(command string, withAppend bool) ([]dto.SSHHistory, int) {
var (
datas []dto.SSHHistory
totalNum int
)
stdout2, err := cmd.Exec(command)
if err == nil {
lines := strings.Split(string(stdout2), "\n")
for _, line := range lines {
parts := strings.Fields(line)
if len(lines) == 0 {
return datas, 0
}
for i := len(lines) - 1; i >= 0; i-- {
parts := strings.Fields(lines[i])
if len(parts) < 14 {
continue
}
historyItem := dto.SSHHistory{
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,
totalNum++
if withAppend {
historyItem := dto.SSHHistory{
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,
}
if strings.Contains(lines[i], ": ") {
historyItem.Message = strings.Split(lines[i], ": ")[1]
}
datas = append(datas, historyItem)
}
if strings.Contains(line, ": ") {
historyItem.Message = strings.Split(line, ": ")[1]
}
datas = append(datas, historyItem)
}
}
return datas
return datas, totalNum
}
func handleGunzip(path string) error {
+1
View File
@@ -105,6 +105,7 @@ var (
ErrInUsed = "ErrInUsed"
ErrObjectInUsed = "ErrObjectInUsed"
ErrPortRules = "ErrPortRules"
ErrRepoConn = "ErrRepoConn"
)
// runtime
+1
View File
@@ -78,6 +78,7 @@ ErrTypeOfRedis: "The recovery file type does not match the current persistence m
#container
ErrInUsed: "{{ .detail }} is in use and cannot be deleted"
ErrObjectInUsed: "This object is in use and cannot be deleted"
ErrRepoConn: "The repository information contains illegal characters"
#runtime
ErrDirNotFound: "The build folder does not exist! Please check file integrity!"
+1
View File
@@ -78,6 +78,7 @@ ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后
#container
ErrInUsed: "{{ .detail }} 正被使用,无法删除"
ErrObjectInUsed: "该对象正被使用,无法删除"
ErrRepoConn: "仓库信息中存在不合法的字符"
#runtime
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"
+1 -1
View File
@@ -15,7 +15,7 @@ func BindDomain() gin.HandlerFunc {
settingRepo := repo.NewISettingRepo()
status, err := settingRepo.Get(settingRepo.WithByKey("BindDomain"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrDomain, constant.ErrTypeInternalServer, err)
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
if len(status.Value) == 0 {
+1 -1
View File
@@ -15,7 +15,7 @@ func WhiteAllow() gin.HandlerFunc {
settingRepo := repo.NewISettingRepo()
status, err := settingRepo.Get(settingRepo.WithByKey("AllowIPs"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrIP, constant.ErrTypeInternalServer, err)
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
+2 -2
View File
@@ -16,7 +16,7 @@ func PasswordExpired() gin.HandlerFunc {
settingRepo := repo.NewISettingRepo()
setting, err := settingRepo.Get(settingRepo.WithByKey("ExpirationDays"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodePasswordExpired, constant.ErrTypePasswordExpired, err)
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypePasswordExpired, err)
return
}
expiredDays, _ := strconv.Atoi(setting.Value)
@@ -27,7 +27,7 @@ func PasswordExpired() gin.HandlerFunc {
extime, err := settingRepo.Get(settingRepo.WithByKey("ExpirationTime"))
if err != nil {
helper.ErrorWithDetail(c, constant.CodePasswordExpired, constant.ErrTypePasswordExpired, err)
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypePasswordExpired, err)
return
}
loc, _ := time.LoadLocation(common.LoadTimeZone())
+38
View File
@@ -5,6 +5,7 @@ import (
"context"
"fmt"
"os/exec"
"strings"
"time"
"github.com/1Panel-dev/1Panel/backend/buserr"
@@ -117,6 +118,43 @@ func Execf(cmdStr string, a ...interface{}) (string, error) {
return stdout.String(), nil
}
func ExecWithCheck(name string, a ...string) (string, error) {
cmd := exec.Command(name, a...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
errMsg := ""
if len(stderr.String()) != 0 {
errMsg = fmt.Sprintf("stderr: %s", stderr.String())
}
if len(stdout.String()) != 0 {
if len(errMsg) != 0 {
errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String())
} else {
errMsg = fmt.Sprintf("stdout: %s", stdout.String())
}
}
return errMsg, err
}
return stdout.String(), nil
}
func CheckIllegal(args ...string) bool {
if args == nil {
return false
}
for _, arg := range args {
if strings.Contains(arg, "&") || strings.Contains(arg, "|") || strings.Contains(arg, ";") ||
strings.Contains(arg, "$") || strings.Contains(arg, "'") || strings.Contains(arg, "`") ||
strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") {
return true
}
}
return false
}
func HasNoPasswordSudo() bool {
cmd2 := exec.Command("sudo", "-n", "ls")
err2 := cmd2.Run()
+6 -6
View File
@@ -5,31 +5,31 @@ import (
)
func Up(filePath string) (string, error) {
stdout, err := cmd.Execf("docker compose -f %s up -d", filePath)
stdout, err := cmd.Execf("docker-compose -f %s up -d", filePath)
return stdout, err
}
func Down(filePath string) (string, error) {
stdout, err := cmd.Execf("docker compose -f %s down --remove-orphans", filePath)
stdout, err := cmd.Execf("docker-compose -f %s down --remove-orphans", filePath)
return stdout, err
}
func Start(filePath string) (string, error) {
stdout, err := cmd.Execf("docker compose -f %s start", filePath)
stdout, err := cmd.Execf("docker-compose -f %s start", filePath)
return stdout, err
}
func Stop(filePath string) (string, error) {
stdout, err := cmd.Execf("docker compose -f %s stop", filePath)
stdout, err := cmd.Execf("docker-compose -f %s stop", filePath)
return stdout, err
}
func Restart(filePath string) (string, error) {
stdout, err := cmd.Execf("docker compose -f %s restart", filePath)
stdout, err := cmd.Execf("docker-compose -f %s restart", filePath)
return stdout, err
}
func Operate(filePath, operation string) (string, error) {
stdout, err := cmd.Execf("docker compose -f %s %s", filePath, operation)
stdout, err := cmd.Execf("docker-compose -f %s %s", filePath, operation)
return stdout, err
}
+2 -2
View File
@@ -18,13 +18,13 @@
:stroke-width="12"
:percentage="uploadPrecent"
></el-progress>
<div v-if="type === 'mysql'" class="el-upload__tip">
<div v-if="type === 'mysql'" style="width: 80%" class="el-upload__tip">
<span class="input-help">{{ $t('database.supportUpType') }}</span>
<span class="input-help">
{{ $t('database.zipFormat') }}
</span>
</div>
<div v-else class="el-upload__tip">
<div v-else style="width: 80%" class="el-upload__tip">
<span class="input-help">{{ $t('website.supportUpType') }}</span>
<span class="input-help">
{{ $t('website.zipFormat', [type + '.json']) }}
+27
View File
@@ -30,6 +30,27 @@ const checkHost = (rule: any, value: any, callback: any) => {
}
};
const checkIllegal = (rule: any, value: any, callback: any) => {
if (value === '' || typeof value === 'undefined' || value == null) {
callback(new Error(i18n.global.t('commons.rule.requiredInput')));
return;
}
if (
value.indexOf('&') !== -1 ||
value.indexOf('|') !== -1 ||
value.indexOf(';') !== -1 ||
value.indexOf('$') !== -1 ||
value.indexOf("'") !== -1 ||
value.indexOf('`') !== -1 ||
value.indexOf('(') !== -1 ||
value.indexOf(')') !== -1
) {
callback(new Error(i18n.global.t('commons.rule.illegalInput')));
} else {
callback();
}
};
const complexityPassword = (rule: any, value: any, callback: any) => {
if (value === '' || typeof value === 'undefined' || value == null) {
callback(new Error(i18n.global.t('commons.rule.complexityPassword')));
@@ -333,6 +354,7 @@ interface CommonRule {
integerNumber: FormItemRule;
ip: FormItemRule;
host: FormItemRule;
illegal: FormItemRule;
port: FormItemRule;
domain: FormItemRule;
databaseName: FormItemRule;
@@ -440,6 +462,11 @@ export const Rules: CommonRule = {
required: true,
trigger: 'blur',
},
illegal: {
validator: checkIllegal,
required: true,
trigger: 'blur',
},
port: {
required: true,
trigger: 'blur',
+1
View File
@@ -135,6 +135,7 @@ const message = {
rePassword: 'The passwords are inconsistent. Please check and re-enter the password',
requiredInput: 'Please enter the required fields',
requiredSelect: 'Please select the required fields',
illegalInput: 'There are illegal characters in the input box.',
commonName: 'Support English, Chinese, numbers, .-, and _ length 1-30',
userName: 'Support English, Chinese, numbers and _ length 3-30',
simpleName: 'Support English, numbers and _ length 1-30',
+1
View File
@@ -138,6 +138,7 @@ const message = {
rePassword: '密码不一致,请检查后重新输入',
requiredInput: '请填写必填项',
requiredSelect: '请选择必选项',
illegalInput: '输入框中存在不合法字符',
commonName: '支持英文、中文、数字、.-和_,长度1-30',
userName: '支持英文、中文、数字和_,长度3-30',
simpleName: '支持英文、数字、_,长度1-30',
@@ -99,6 +99,15 @@
</div>
</el-col>
</el-row>
<div class="page-button">
<fu-table-pagination
v-model:current-page="paginationConfig.currentPage"
v-model:page-size="paginationConfig.pageSize"
v-bind="paginationConfig"
@change="search(req)"
:layout="'total, sizes, prev, pager, next, jumper'"
/>
</div>
</template>
</LayoutContent>
<Detail v-if="showDetail" :id="appId"></Detail>
@@ -116,6 +125,12 @@ import { useI18n } from 'vue-i18n';
const language = useI18n().locale.value;
const paginationConfig = reactive({
currentPage: 1,
pageSize: 50,
total: 0,
});
const req = reactive({
name: '',
tags: [],
@@ -138,9 +153,12 @@ const getColor = (index: number) => {
const search = async (req: App.AppReq) => {
loading.value = true;
req.pageSize = paginationConfig.pageSize;
req.page = paginationConfig.currentPage;
await SearchApp(req)
.then((res) => {
apps.value = res.data.items;
paginationConfig.total = res.data.total;
})
.finally(() => {
loading.value = false;
@@ -260,4 +278,10 @@ onMounted(() => {
border: none;
}
}
.page-button {
float: right;
margin-bottom: 10px;
margin-top: 10px;
}
</style>
@@ -111,10 +111,10 @@ const handleClose = () => {
};
const rules = reactive({
name: [Rules.requiredInput, Rules.name],
downloadUrl: [Rules.requiredInput],
downloadUrl: [Rules.illegal],
protocol: [Rules.requiredSelect],
username: [Rules.requiredInput],
password: [Rules.requiredInput],
username: [Rules.illegal],
password: [Rules.illegal],
auth: [Rules.requiredSelect],
});
+2 -2
View File
@@ -19,10 +19,10 @@
<el-option :label="$t('commons.status.success')" value="Success"></el-option>
<el-option :label="$t('commons.status.failed')" value="Failed"></el-option>
</el-select>
<el-tag type="success" size="large" style="margin-left: 15px">
<el-tag v-if="searchStatus === 'All'" type="success" size="large" style="margin-left: 15px">
{{ $t('commons.status.success') }}: {{ successfulCount }}
</el-tag>
<el-tag type="danger" size="large" style="margin-left: 5px">
<el-tag v-if="searchStatus === 'All'" type="danger" size="large" style="margin-left: 5px">
{{ $t('commons.status.failed') }}: {{ faliedCount }}
</el-tag>
</el-col>