mirror of
https://github.com/veops/oneterm.git
synced 2025-10-12 18:50:54 +08:00
feat(backend): add command template
This commit is contained in:
377
backend/internal/service/command_init.go
Normal file
377
backend/internal/service/command_init.go
Normal file
@@ -0,0 +1,377 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"github.com/samber/lo"
|
||||
|
||||
myi18n "github.com/veops/oneterm/internal/i18n"
|
||||
"github.com/veops/oneterm/internal/model"
|
||||
"github.com/veops/oneterm/pkg/db"
|
||||
"github.com/veops/oneterm/pkg/logger"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CommandDefinition defines a command with i18n message keys
|
||||
type CommandDefinition struct {
|
||||
NameKey *i18n.Message
|
||||
DescriptionKey *i18n.Message
|
||||
Cmd string
|
||||
IsRe bool
|
||||
RiskLevel model.CommandRiskLevel
|
||||
Category model.CommandCategory
|
||||
TagKeys []*i18n.Message
|
||||
}
|
||||
|
||||
// TemplateDefinition defines a template with i18n message keys
|
||||
type TemplateDefinition struct {
|
||||
NameKey *i18n.Message
|
||||
DescriptionKey *i18n.Message
|
||||
Category model.CommandCategory
|
||||
CommandRefs []string // References to command names by i18n key IDs
|
||||
}
|
||||
|
||||
// getLocalizer creates a localizer for the given context (defaults to English)
|
||||
func getLocalizer(ctx context.Context) *i18n.Localizer {
|
||||
// Default to English, as this runs during system startup without user context
|
||||
return i18n.NewLocalizer(myi18n.Bundle, "en")
|
||||
}
|
||||
|
||||
// localizeMessage localizes a message using the given localizer
|
||||
func localizeMessage(localizer *i18n.Localizer, msg *i18n.Message) string {
|
||||
result, _ := localizer.Localize(&i18n.LocalizeConfig{DefaultMessage: msg})
|
||||
return result
|
||||
}
|
||||
|
||||
// localizeTags localizes an array of tag message keys
|
||||
func localizeTags(localizer *i18n.Localizer, tagKeys []*i18n.Message) []string {
|
||||
return lo.Map(tagKeys, func(msg *i18n.Message, _ int) string {
|
||||
return localizeMessage(localizer, msg)
|
||||
})
|
||||
}
|
||||
|
||||
// InitBuiltinCommands initializes predefined dangerous commands on system startup
|
||||
func InitBuiltinCommands() error {
|
||||
ctx := context.Background()
|
||||
localizer := getLocalizer(ctx)
|
||||
|
||||
logger.L().Info("Starting initialization of predefined dangerous commands")
|
||||
|
||||
// Initialize commands
|
||||
commands := getPreDefinedCommands()
|
||||
for _, cmdDef := range commands {
|
||||
if err := createCommandIfNotExists(localizer, cmdDef); err != nil {
|
||||
logger.L().Error("Failed to create predefined command",
|
||||
zap.String("name_key", cmdDef.NameKey.ID),
|
||||
zap.Error(err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize templates
|
||||
templates := getPreDefinedTemplates()
|
||||
for _, tmplDef := range templates {
|
||||
if err := createTemplateIfNotExists(localizer, tmplDef); err != nil {
|
||||
logger.L().Error("Failed to create predefined template",
|
||||
zap.String("name_key", tmplDef.NameKey.ID),
|
||||
zap.Error(err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
logger.L().Info("Predefined dangerous commands initialization completed successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// createCommandIfNotExists creates a command if it doesn't exist
|
||||
func createCommandIfNotExists(localizer *i18n.Localizer, cmdDef CommandDefinition) error {
|
||||
name := localizeMessage(localizer, cmdDef.NameKey)
|
||||
|
||||
var existingCmd model.Command
|
||||
err := db.DB.Where("name = ? AND is_global = ?", name, true).First(&existingCmd).Error
|
||||
if err == nil {
|
||||
logger.L().Debug("Predefined command already exists, skipping", zap.String("name", name))
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create new command
|
||||
newCmd := &model.Command{
|
||||
Name: name,
|
||||
Cmd: cmdDef.Cmd,
|
||||
IsRe: cmdDef.IsRe,
|
||||
Enable: true,
|
||||
Category: cmdDef.Category,
|
||||
RiskLevel: cmdDef.RiskLevel,
|
||||
Description: localizeMessage(localizer, cmdDef.DescriptionKey),
|
||||
Tags: localizeTags(localizer, cmdDef.TagKeys),
|
||||
IsGlobal: true,
|
||||
CreatorId: 1, // System user
|
||||
UpdaterId: 1,
|
||||
}
|
||||
|
||||
if err := db.DB.Create(newCmd).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.L().Info("Successfully created predefined command",
|
||||
zap.String("name", name),
|
||||
zap.Int("risk_level", int(cmdDef.RiskLevel)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createTemplateIfNotExists creates a template if it doesn't exist
|
||||
func createTemplateIfNotExists(localizer *i18n.Localizer, tmplDef TemplateDefinition) error {
|
||||
name := localizeMessage(localizer, tmplDef.NameKey)
|
||||
|
||||
var existingTemplate model.CommandTemplate
|
||||
err := db.DB.Where("name = ? AND is_builtin = ?", name, true).First(&existingTemplate).Error
|
||||
if err == nil {
|
||||
logger.L().Debug("Predefined template already exists, skipping", zap.String("name", name))
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get command IDs by localized names
|
||||
cmdIds, err := getCommandIdsByLocalizedNames(localizer, tmplDef.CommandRefs)
|
||||
if err != nil {
|
||||
logger.L().Warn("Some commands not found for template",
|
||||
zap.String("template", name),
|
||||
zap.Error(err))
|
||||
}
|
||||
|
||||
// Create new template
|
||||
newTemplate := &model.CommandTemplate{
|
||||
Name: name,
|
||||
Description: localizeMessage(localizer, tmplDef.DescriptionKey),
|
||||
Category: tmplDef.Category,
|
||||
CmdIds: cmdIds,
|
||||
IsBuiltin: true,
|
||||
CreatorId: 1, // System user
|
||||
}
|
||||
|
||||
if err := db.DB.Create(newTemplate).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.L().Info("Successfully created predefined template",
|
||||
zap.String("name", name),
|
||||
zap.Int("command_count", len(cmdIds)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getCommandIdsByLocalizedNames retrieves command IDs by their localized names
|
||||
func getCommandIdsByLocalizedNames(localizer *i18n.Localizer, nameKeys []string) (model.Slice[int], error) {
|
||||
// Convert i18n keys to localized names
|
||||
localizedNames := lo.Map(nameKeys, func(keyID string, _ int) string {
|
||||
// Find the message by ID in our predefined commands
|
||||
cmdDefs := getPreDefinedCommands()
|
||||
if cmdDef, found := lo.Find(cmdDefs, func(def CommandDefinition) bool {
|
||||
return def.NameKey.ID == keyID
|
||||
}); found {
|
||||
return localizeMessage(localizer, cmdDef.NameKey)
|
||||
}
|
||||
return keyID // fallback to key ID if not found
|
||||
})
|
||||
|
||||
var commands []model.Command
|
||||
if err := db.DB.Where("name IN ? AND is_global = ?", localizedNames, true).Find(&commands).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return lo.Map(commands, func(cmd model.Command, _ int) int {
|
||||
return cmd.Id
|
||||
}), nil
|
||||
}
|
||||
|
||||
// getPreDefinedCommands returns command definitions with i18n message keys
|
||||
func getPreDefinedCommands() []CommandDefinition {
|
||||
return []CommandDefinition{
|
||||
// Critical dangerous commands (RiskLevel: 3)
|
||||
{
|
||||
NameKey: myi18n.CmdDeleteRootDir,
|
||||
DescriptionKey: myi18n.CmdDeleteRootDirDesc,
|
||||
Cmd: "^rm\\s+(-rf?|--recursive\\s+--force?)\\s+/\\s*$",
|
||||
IsRe: true,
|
||||
RiskLevel: model.RiskLevelCritical,
|
||||
Category: model.CategorySecurity,
|
||||
TagKeys: []*i18n.Message{myi18n.TagDangerous, myi18n.TagDelete, myi18n.TagSystem},
|
||||
},
|
||||
{
|
||||
NameKey: myi18n.CmdDeleteSystemDirs,
|
||||
DescriptionKey: myi18n.CmdDeleteSystemDirsDesc,
|
||||
Cmd: "^rm\\s+(-rf?|--recursive\\s+--force?)\\s+/(bin|boot|dev|etc|lib|lib64|proc|root|sbin|sys|usr)(/.*)?$",
|
||||
IsRe: true,
|
||||
RiskLevel: model.RiskLevelCritical,
|
||||
Category: model.CategorySecurity,
|
||||
TagKeys: []*i18n.Message{myi18n.TagDangerous, myi18n.TagDelete, myi18n.TagSystemDirs},
|
||||
},
|
||||
{
|
||||
NameKey: myi18n.CmdDiskDestruction,
|
||||
DescriptionKey: myi18n.CmdDiskDestructionDesc,
|
||||
Cmd: "^dd\\s+if=/dev/(zero|random|urandom)\\s+of=/dev/[sh]d[a-z]+",
|
||||
IsRe: true,
|
||||
RiskLevel: model.RiskLevelCritical,
|
||||
Category: model.CategorySecurity,
|
||||
TagKeys: []*i18n.Message{myi18n.TagDangerous, myi18n.TagDisk, myi18n.TagDestruction},
|
||||
},
|
||||
{
|
||||
NameKey: myi18n.CmdFormatDisk,
|
||||
DescriptionKey: myi18n.CmdFormatDiskDesc,
|
||||
Cmd: "^(mkfs|fdisk|parted|gdisk)\\.(ext[234]|xfs|btrfs|ntfs|fat32)\\s+/dev/[sh]d[a-z]+",
|
||||
IsRe: true,
|
||||
RiskLevel: model.RiskLevelCritical,
|
||||
Category: model.CategorySecurity,
|
||||
TagKeys: []*i18n.Message{myi18n.TagDangerous, myi18n.TagFormat, myi18n.TagDisk},
|
||||
},
|
||||
{
|
||||
NameKey: myi18n.CmdForkBomb,
|
||||
DescriptionKey: myi18n.CmdForkBombDesc,
|
||||
Cmd: ":(\\)|\\{\\s*\\|\\s*:\\s*&\\s*\\};\\s*:",
|
||||
IsRe: true,
|
||||
RiskLevel: model.RiskLevelCritical,
|
||||
Category: model.CategorySecurity,
|
||||
TagKeys: []*i18n.Message{myi18n.TagDangerous, myi18n.TagAttack, myi18n.TagResourceExhaustion},
|
||||
},
|
||||
|
||||
// Dangerous commands (RiskLevel: 2)
|
||||
{
|
||||
NameKey: myi18n.CmdSystemReboot,
|
||||
DescriptionKey: myi18n.CmdSystemRebootDesc,
|
||||
Cmd: "^(shutdown|reboot|halt|poweroff|init\\s+[06])\\b",
|
||||
IsRe: true,
|
||||
RiskLevel: model.RiskLevelDanger,
|
||||
Category: model.CategorySystem,
|
||||
TagKeys: []*i18n.Message{myi18n.TagReboot, myi18n.TagShutdown, myi18n.TagSystem},
|
||||
},
|
||||
{
|
||||
NameKey: myi18n.CmdModifySystemFiles,
|
||||
DescriptionKey: myi18n.CmdModifySystemFilesDesc,
|
||||
Cmd: "^(vi|vim|nano|emacs|cat\\s*>|echo\\s*>)\\s+/(etc|boot|sys)/.+",
|
||||
IsRe: true,
|
||||
RiskLevel: model.RiskLevelDanger,
|
||||
Category: model.CategorySystem,
|
||||
TagKeys: []*i18n.Message{myi18n.TagEdit, myi18n.TagSystemFiles, myi18n.TagConfig},
|
||||
},
|
||||
{
|
||||
NameKey: myi18n.CmdDropDatabase,
|
||||
DescriptionKey: myi18n.CmdDropDatabaseDesc,
|
||||
Cmd: "^(mysql|psql|mongo).*drop\\s+(database|schema|collection)\\s+\\w+",
|
||||
IsRe: true,
|
||||
RiskLevel: model.RiskLevelDanger,
|
||||
Category: model.CategoryDatabase,
|
||||
TagKeys: []*i18n.Message{myi18n.TagDatabase, myi18n.TagDelete, myi18n.TagDrop},
|
||||
},
|
||||
{
|
||||
NameKey: myi18n.CmdTruncateTable,
|
||||
DescriptionKey: myi18n.CmdTruncateTableDesc,
|
||||
Cmd: "^(mysql|psql).*\\b(truncate\\s+table|delete\\s+from\\s+\\w+\\s*;?)\\s*$",
|
||||
IsRe: true,
|
||||
RiskLevel: model.RiskLevelDanger,
|
||||
Category: model.CategoryDatabase,
|
||||
TagKeys: []*i18n.Message{myi18n.TagDatabase, myi18n.TagClear, myi18n.TagTruncate},
|
||||
},
|
||||
{
|
||||
NameKey: myi18n.CmdModifyPermissions,
|
||||
DescriptionKey: myi18n.CmdModifyPermissionsDesc,
|
||||
Cmd: "^(chmod|chown|chgrp)\\s+(777|666|755)\\s+/",
|
||||
IsRe: true,
|
||||
RiskLevel: model.RiskLevelDanger,
|
||||
Category: model.CategorySecurity,
|
||||
TagKeys: []*i18n.Message{myi18n.TagPermissions, myi18n.TagChmod, myi18n.TagSecurity},
|
||||
},
|
||||
|
||||
// Warning level commands (RiskLevel: 1)
|
||||
{
|
||||
NameKey: myi18n.CmdDropTable,
|
||||
DescriptionKey: myi18n.CmdDropTableDesc,
|
||||
Cmd: "^(mysql|psql).*drop\\s+table\\s+\\w+",
|
||||
IsRe: true,
|
||||
RiskLevel: model.RiskLevelWarning,
|
||||
Category: model.CategoryDatabase,
|
||||
TagKeys: []*i18n.Message{myi18n.TagDatabase, myi18n.TagDelete, myi18n.TagTable},
|
||||
},
|
||||
{
|
||||
NameKey: myi18n.CmdServiceControl,
|
||||
DescriptionKey: myi18n.CmdServiceControlDesc,
|
||||
Cmd: "^(systemctl|service)\\s+(stop|restart|disable)\\s+\\w+",
|
||||
IsRe: true,
|
||||
RiskLevel: model.RiskLevelWarning,
|
||||
Category: model.CategorySystem,
|
||||
TagKeys: []*i18n.Message{myi18n.TagService, myi18n.TagSystemctl, myi18n.TagControl},
|
||||
},
|
||||
{
|
||||
NameKey: myi18n.CmdNetworkConfig,
|
||||
DescriptionKey: myi18n.CmdNetworkConfigDesc,
|
||||
Cmd: "^(iptables|ufw|firewall-cmd|ip\\s+route)\\s+",
|
||||
IsRe: true,
|
||||
RiskLevel: model.RiskLevelWarning,
|
||||
Category: model.CategoryNetwork,
|
||||
TagKeys: []*i18n.Message{myi18n.TagNetwork, myi18n.TagFirewall, myi18n.TagRouting},
|
||||
},
|
||||
{
|
||||
NameKey: myi18n.CmdUserManagement,
|
||||
DescriptionKey: myi18n.CmdUserManagementDesc,
|
||||
Cmd: "^(useradd|userdel|usermod|passwd)\\s+",
|
||||
IsRe: true,
|
||||
RiskLevel: model.RiskLevelWarning,
|
||||
Category: model.CategorySecurity,
|
||||
TagKeys: []*i18n.Message{myi18n.TagUser, myi18n.TagManagement, myi18n.TagSecurity},
|
||||
},
|
||||
{
|
||||
NameKey: myi18n.CmdKernelModule,
|
||||
DescriptionKey: myi18n.CmdKernelModuleDesc,
|
||||
Cmd: "^(modprobe|rmmod|insmod)\\s+",
|
||||
IsRe: true,
|
||||
RiskLevel: model.RiskLevelWarning,
|
||||
Category: model.CategorySystem,
|
||||
TagKeys: []*i18n.Message{myi18n.TagKernel, myi18n.TagModule, myi18n.TagSystem},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// getPreDefinedTemplates returns template definitions with i18n message keys
|
||||
func getPreDefinedTemplates() []TemplateDefinition {
|
||||
return []TemplateDefinition{
|
||||
{
|
||||
NameKey: myi18n.TmplBasicSecurity,
|
||||
DescriptionKey: myi18n.TmplBasicSecurityDesc,
|
||||
Category: model.CategorySecurity,
|
||||
CommandRefs: []string{"CmdDeleteRootDir", "CmdDeleteSystemDirs", "CmdDiskDestruction", "CmdFormatDisk", "CmdForkBomb"},
|
||||
},
|
||||
{
|
||||
NameKey: myi18n.TmplDatabaseProtection,
|
||||
DescriptionKey: myi18n.TmplDatabaseProtectionDesc,
|
||||
Category: model.CategoryDatabase,
|
||||
CommandRefs: []string{"CmdDropDatabase", "CmdTruncateTable", "CmdDropTable"},
|
||||
},
|
||||
{
|
||||
NameKey: myi18n.TmplServiceRestrictions,
|
||||
DescriptionKey: myi18n.TmplServiceRestrictionsDesc,
|
||||
Category: model.CategorySystem,
|
||||
CommandRefs: []string{"CmdSystemReboot", "CmdModifySystemFiles", "CmdServiceControl"},
|
||||
},
|
||||
{
|
||||
NameKey: myi18n.TmplNetworkSecurity,
|
||||
DescriptionKey: myi18n.TmplNetworkSecurityDesc,
|
||||
Category: model.CategorySecurity,
|
||||
CommandRefs: []string{"CmdNetworkConfig", "CmdModifyPermissions", "CmdUserManagement"},
|
||||
},
|
||||
{
|
||||
NameKey: myi18n.TmplDevEnvironment,
|
||||
DescriptionKey: myi18n.TmplDevEnvironmentDesc,
|
||||
Category: model.CategorySecurity,
|
||||
CommandRefs: []string{"CmdDeleteRootDir", "CmdFormatDisk", "CmdForkBomb"},
|
||||
},
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user