From 16c041e3eb06750ef59c7f1a6eb411fafb711e1c Mon Sep 17 00:00:00 2001 From: zeaslity Date: Thu, 27 Feb 2025 10:57:58 +0800 Subject: [PATCH] Add base system configuration commands for agent-wdd - Implemented new base commands for system configuration: * swap: Disable system swap * selinux: Disable SELinux * firewall: Stop and disable firewalld and ufw * sysconfig: Modify system sysctl configuration * ssh: Add SSH-related subcommands (key, port, config) - Updated Config.go to initialize ConfigCache with default values - Added new utility functions in FileUtils.go for file content manipulation - Extended Excutor.go with HardCodeCommandExecutor method --- agent-wdd/cmd/Base.go | 238 ++++++++++++++++++++++++++++ agent-wdd/cmd/beans/SshSysConfig.go | 203 ++++++++++++++++++++++++ agent-wdd/config/Config.go | 14 +- agent-wdd/op/Excutor.go | 31 ++++ agent-wdd/utils/FileUtils.go | 93 +++++++++++ 5 files changed, 578 insertions(+), 1 deletion(-) create mode 100644 agent-wdd/cmd/beans/SshSysConfig.go diff --git a/agent-wdd/cmd/Base.go b/agent-wdd/cmd/Base.go index c669c78..7286b6f 100644 --- a/agent-wdd/cmd/Base.go +++ b/agent-wdd/cmd/Base.go @@ -52,6 +52,124 @@ func addBaseSubcommands(cmd *cobra.Command) { addDockerComposeSubcommands(dockerComposeCmd) // 其他base子命令... + swapCmd := &cobra.Command{ + Use: "swap", + Short: "关闭系统的Swap", + Run: func(cmd *cobra.Command, args []string) { + log.Info("Swap 关闭!") + // 实现这个函数,能够关闭centos或者ubuntu系统的swap + + // 备份文件存在,pass + if !utils.FileExistAndNotNull("/etc/fstab_back_wdd_swap") { + utils.AppendOverwriteContentToFile( + "/etc/fstab", + "/etc/fstab_back_wdd_swap", + ) + } + // 执行关闭操作 + op.SingleLineCommandExecutor([]string{ + "swapoff", + "-a", + }) + + op.SingleLineCommandExecutor([]string{ + "sed", + "-i", + "/swap/d", + "/etc/fstab", + }) + + log.Info("Swap 关闭成功!") + }, + } + + selinuxCmd := &cobra.Command{ + Use: "selinux", + Short: "关闭selinux", + Run: func(cmd *cobra.Command, args []string) { + log.Info("Selinux 关闭!") + + // 如果configCache的OS为空,则收集OS信息 + if config.ConfigCache.Agent.OS.Hostname == "" { + log.Warning("ConfigCache OS is nil") + config.ConfigCache.Agent.OS.Gather() + config.ConfigCache.Agent.OS.SaveConfig() + } + + os := config.ConfigCache.Agent.OS + if os.IsUbuntuType { + log.Info("Ubuntu 系统,跳过关闭selinux!") + return + } else { + op.SingleLineCommandExecutor([]string{ + "setenforce", + "0", + }) + + // 备份一下/etc/selinux/config + if !utils.FileExistAndNotNull("/etc/selinux/config_back_wdd_selinux") { + utils.AppendOverwriteContentToFile( + "/etc/selinux/config", + "/etc/selinux/config_back_wdd_selinux", + ) + } + + // 持久化关闭selinux + utils.FindAndDeleteContentInFile("SELINUX=enforcing", "/etc/selinux/config") + utils.FindAndDeleteContentInFile("SELINUX=permissive", "/etc/selinux/config") + utils.FindAndDeleteContentInFile("SELINUX=disabled", "/etc/selinux/config") + utils.AppendContentToFile("SELINUX=disabled", "/etc/selinux/config") + } + + log.Info("Selinux 关闭成功!") + }, + } + + firewallCmd := &cobra.Command{ + Use: "firewall", + Short: "关闭防火墙", + Run: func(cmd *cobra.Command, args []string) { + log.Info("Firewall 关闭!") + + // 调用systemd关闭firewalld + op.SystemdDown("firewalld") + op.SystemdDisable("firewalld") + // 调用systemd关闭ufw + op.SystemdDown("ufw") + op.SystemdDisable("ufw") + + // 清空路由表 + log.Info("清空路由表...") + op.HardCodeCommandExecutor("iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -t raw -F") + op.HardCodeCommandExecutor("ip6tables -F && ip6tables -t nat -F && ip6tables -t mangle -F && ip6tables -t raw -F") + + log.Info("Firewall 关闭成功!") + }, + } + + sysconfigCmd := &cobra.Command{ + Use: "sysconfig", + Short: "修改系统的sysconfig", + Run: func(cmd *cobra.Command, args []string) { + log.Info("Sysconfig 修改!") + + // 修改系统的sysconfig + sysctlConfigFile := "/etc/sysctl.d/wdd-k8s.conf" + + if !utils.AppendOverwriteContentToFile(beans.SysctlConfig, sysctlConfigFile) { + log.Error("[ModifySysConfigBastion] - error appending sysctl config to sysctl.d !") + return + } + + op.SingleLineCommandExecutor([]string{ + "sysctl", + "-p", + sysctlConfigFile, + }) + + log.Info("Sysconfig 修改成功!") + }, + } // 通用工具安装 commonToolsInstall := &cobra.Command{ @@ -80,14 +198,134 @@ func addBaseSubcommands(cmd *cobra.Command) { }, } + sshCmd := &cobra.Command{ + Use: "ssh", + Short: "修改ssh配置", + } + addSSHSubcommands(sshCmd) + cmd.AddCommand( dockerCmd, dockerComposeCmd, + swapCmd, commonToolsInstall, + selinuxCmd, + firewallCmd, + sysconfigCmd, + sshCmd, // 其他命令... ) } +func addSSHSubcommands(sshCmd *cobra.Command) { + keyCmd := &cobra.Command{ + Use: "key", + Short: "安装默认的ssh-key", + Run: func(cmd *cobra.Command, args []string) { + log.Info("安装默认的ssh-key!") + + // 创建.ssh目录 + utils.CreateFolder("/root/.ssh") + + // 检查密钥是否存在 + if utils.FileExistAndNotNull("/root/.ssh/id_ed25519") && + utils.FileExistAndNotNull("/root/.ssh/id_ed25519.pub") && + utils.FindContentInFile("wdd@cmii.com", "/root/.ssh/authorized_keys") { + log.Info("SSH密钥已存在,无需重新安装。") + return + } + + // 下载标准的私钥和公钥 + if !utils.AppendOverwriteContentToFile(beans.Ed25519PrivateKey, "/root/.ssh/id_ed25519") { + log.Error("[InstallDefaultSSHKey] - error appending private ssh key to authorized_keys !") + return + } + if !utils.AppendOverwriteContentToFile(beans.Ed25519PublicKey, "/root/.ssh/id_ed25519.pub") { + log.Error("[InstallDefaultSSHKey] - error appending public ssh key to authorized_keys !") + return + } + + // 写入到authorized_keys + if !utils.AppendFileToFile("/root/.ssh/id_ed25519.pub", "/root/.ssh/authorized_keys") { + log.Error("[InstallDefaultSSHKey] - error appending ssh key to authorized_keys !") + return + } + + // 设置权限 + op.SingleLineCommandExecutor([]string{ + "chmod", + "600", + "/root/.ssh/id_ed25519", + }) + + // 检查 + if utils.FindContentInFile("wdd@cmii.com", "/root/.ssh/authorized_keys") { + log.Info("[InstallDefaultSSHKey] - install success !") + } else { + log.Error("[InstallDefaultSSHKey] - authorized_keys don't contain the ssh-pub key !") + } + }, + } + + portCmd := &cobra.Command{ + Use: "port", + Short: "修改ssh端口", + Run: func(cmd *cobra.Command, args []string) { + log.Info("修改ssh端口!") + + // 检查参数 + if len(args) > 0 { + fmt.Printf("modify ssh port to: %s\n", args[0]) + } + + // 没有传递参数,使用默认参数 + port := "22333" + log.Info("[ModifySSHPort] modify ssh port to: %s", port) + + // 修改ssh端口 + utils.AppendContentToFile(fmt.Sprintf("Port %s", port), "/etc/ssh/sshd_config") + + // 重启ssh服务 + ok, resultLog := op.SystemdRestart("sshd") + if !ok { + log.Error("[ModifySSHPort] restart sshd error: %s", resultLog) + return + } + + log.Info("[ModifySSHPort] modify ssh port to: %s success!", port) + + }, + } + + configCmd := &cobra.Command{ + Use: "config", + Short: "修改ssh配置 为wdd默认配置!", + Run: func(cmd *cobra.Command, args []string) { + log.Info("修改ssh配置 为wdd默认配置!") + + // 备份文件 + if !utils.FileExistAndNotNull("/etc/ssh/sshd_config_back_wdd_ssh") { + utils.AppendOverwriteContentToFile("/etc/ssh/sshd_config", "/etc/ssh/sshd_config_back_wdd_ssh") + } + + // 修改ssh配置 + utils.AppendContentToFile(beans.DefaultSshdConfig, "/etc/ssh/sshd_config") + + // 重启ssh服务 + ok, resultLog := op.SystemdRestart("sshd") + if !ok { + log.Error("sshd 重启失败: %s", resultLog) + return + } + + log.Info("[sshd配置修改] 成功!") + + }, + } + + sshCmd.AddCommand(keyCmd, portCmd, configCmd) +} + // 添加docker子命令 func addDockerSubcommands(cmd *cobra.Command) { onlineCmd := &cobra.Command{ diff --git a/agent-wdd/cmd/beans/SshSysConfig.go b/agent-wdd/cmd/beans/SshSysConfig.go new file mode 100644 index 0000000..66ae8c6 --- /dev/null +++ b/agent-wdd/cmd/beans/SshSysConfig.go @@ -0,0 +1,203 @@ +package beans + +var Ed25519PrivateKey = `-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDk8R4KXGgDa5H2r8HrqW1klShoSISV20sLiXZPZPfeLwAAAJCIan+LiGp/ +iwAAAAtzc2gtZWQyNTUxOQAAACDk8R4KXGgDa5H2r8HrqW1klShoSISV20sLiXZPZPfeLw +AAAEDhnul+q0TNTgrO9kfmGsFhtn/rGRIrmhFostjem/QlZuTxHgpcaANrkfavweupbWSV +KGhIhJXbSwuJdk9k994vAAAADHdkZEBjbWlpLmNvbQE= +-----END OPENSSH PRIVATE KEY----- +` + +var Ed25519PublicKey = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOTxHgpcaANrkfavweupbWSVKGhIhJXbSwuJdk9k994v wdd@cmii.com +` + +var DefaultSshdConfig = ` +# OCTOPUS AGENT DEFAULT SSHD CONFIG - WDD + +# This is the sshd server system-wide configuration file. See +# sshd_config(5) for more information. + +# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin + +# The strategy used for options in the default sshd_config shipped with +# OpenSSH is to specify options with their default value where +# possible, but leave them commented. Uncommented options override the +# default value. + +#Include /etc/ssh/sshd_config.d/*.conf + +Port 22 +Port 22333 +AddressFamily any +ListenAddress 0.0.0.0 +ListenAddress :: + +#HostKey /etc/ssh/ssh_host_rsa_key +#HostKey /etc/ssh/ssh_host_ecdsa_key +#HostKey /etc/ssh/ssh_host_ed25519_key + +# Ciphers and keying +#RekeyLimit default none + +# Logging +#SyslogFacility AUTH +#LogLevel INFO + +# Authentication: + +#LoginGraceTime 2m +#PermitRootLogin prohibit-password +#StrictModes yes +#MaxAuthTries 6 +#MaxSessions 10 + +#PubkeyAuthentication yes + +# Expect .ssh/authorized_keys2 to be disregarded by default in future. +#AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2 + +#AuthorizedPrincipalsFile none + +#AuthorizedKeysCommand none +#AuthorizedKeysCommandUser nobody + +# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts +#HostbasedAuthentication no +# Change to yes if you don't trust ~/.ssh/known_hosts for +# HostbasedAuthentication +#IgnoreUserKnownHosts no +# Don't read the user's ~/.rhosts and ~/.shosts files +#IgnoreRhosts yes + +# To disable tunneled clear text passwords, change to no here! +#PasswordAuthentication yes +PermitEmptyPasswords no + +# Change to yes to enable challenge-response passwords (beware issues with +# some PAM modules and threads) +ChallengeResponseAuthentication no + +# Kerberos options +#KerberosAuthentication no +#KerberosOrLocalPasswd yes +#KerberosTicketCleanup yes +#KerberosGetAFSToken no + +# GSSAPI options +#GSSAPIAuthentication no +#GSSAPICleanupCredentials yes +#GSSAPIStrictAcceptorCheck yes +#GSSAPIKeyExchange no + +# Set this to 'yes' to enable PAM authentication, account processing, +# and session processing. If this is enabled, PAM authentication will +# be allowed through the ChallengeResponseAuthentication and +# PasswordAuthentication. Depending on your PAM configuration, +# PAM authentication via ChallengeResponseAuthentication may bypass +# the setting of "PermitRootLogin without-password". +# If you just want the PAM account and session checks to run without +# PAM authentication, then enable this but set PasswordAuthentication +# and ChallengeResponseAuthentication to 'no'. +UsePAM yes + +AllowAgentForwarding yes +AllowTcpForwarding yes +#GatewayPorts no +X11Forwarding yes +#X11DisplayOffset 10 +#X11UseLocalhost yes +#PermitTTY yes +PrintMotd no +#PrintLastLog yes +TCPKeepAlive yes +#PermitUserEnvironment no +#Compression delayed +#ClientAliveInterval 0 +#ClientAliveCountMax 3 +#UseDNS no +#PidFile /var/run/sshd.pid +#MaxStartups 10:30:100 +#PermitTunnel no +#ChrootDirectory none +#VersionAddendum none + +# no default banner path +#Banner none + +# Allow client to pass locale environment variables +AcceptEnv LANG LC_* + +# override default of no subsystems +Subsystem sftp /usr/lib/openssh/sftp-server + +# Example of overriding settings on a per-user basis +#Match User anoncvs +# X11Forwarding no +# AllowTcpForwarding no +# PermitTTY no +# ForceCommand cvs server +PasswordAuthentication yes +PermitRootLogin yes +StrictModes no +ClientAliveInterval 30 +ClientAliveCountMax 60 +` + +var SysctlConfig = ` +# 开启 IPv4 路由转发 +net.ipv4.ip_forward = 1 + +# 禁用 IPv6 +net.ipv6.conf.all.disable_ipv6 = 1 +net.ipv6.conf.default.disable_ipv6 = 1 + +# 开启 IPv4 转发 +net.ipv4.conf.all.forwarding = 1 +net.ipv4.conf.default.forwarding = 1 + +# 开启 IPv4 连接跟踪 +net.ipv4.tcp_syncookies = 1 + +# 开启 IPv4 连接跟踪 +net.ipv4.tcp_tw_recycle = 1 + +# 开启 IPv4 连接跟踪 +net.ipv4.tcp_tw_reuse = 1 + +# 开启 IPv4 连接跟踪 +net.ipv4.tcp_fin_timeout = 30 + +# 开启 IPv4 连接跟踪 +net.ipv4.tcp_keepalive_time = 1200 + +# 开启 IPv4 连接跟踪 +net.ipv4.ip_local_port_range = 1024 65535 + +# 开启 IPv4 连接跟踪 +net.ipv4.tcp_max_syn_backlog = 8192 + +# 开启 IPv4 连接跟踪 +net.ipv4.tcp_max_tw_buckets = 5000 + +# 开启 IPv4 连接跟踪 +net.ipv4.tcp_max_orphans = 32768 + +# 开启 IPv4 连接跟踪 +net.ipv4.tcp_synack_retries = 2 + +# 开启 IPv4 连接跟踪 +net.ipv4.tcp_syn_retries = 2 + +# 开启 IPv4 连接跟踪 +net.ipv4.tcp_synflood_protect = 1000 + +# 开启 IPv4 连接跟踪 +net.ipv4.tcp_timestamps = 1 + +# 开启 IPv4 连接跟踪 +net.ipv4.tcp_window_scaling = 1 + +# 开启 IPv4 连接跟踪 +net.ipv4.tcp_rmem = 4096 87380 4194304 +` diff --git a/agent-wdd/config/Config.go b/agent-wdd/config/Config.go index 510444b..4f9a5c2 100644 --- a/agent-wdd/config/Config.go +++ b/agent-wdd/config/Config.go @@ -12,7 +12,19 @@ import ( var WddConfigFilePath = "/usr/local/etc/wdd/agent-wdd-config.yaml" -var ConfigCache *Config +// ConfigCache 配置缓存 +var ConfigCache = &Config{ + TimeStamp: "", + ModifiedTimes: 0, + Agent: Agent{ + OS: OS{}, + Network: Network{}, + CPU: CPU{}, + Mem: Memory{}, + Swap: Swap{}, + Disks: []Disk{}, + }, +} func init() { // 根据运行的操作系统不同, 修改 WddConfigFilePath 的位置 diff --git a/agent-wdd/op/Excutor.go b/agent-wdd/op/Excutor.go index f37787d..6b49e91 100644 --- a/agent-wdd/op/Excutor.go +++ b/agent-wdd/op/Excutor.go @@ -161,6 +161,37 @@ func PipeLineCommandExecutor(pipeLineCommand [][]string) (ok bool, resultLog []s return success, output } +// HardCodeCommandExecutor 执行硬编码命令,返回执行结果和输出内容 +// hardCodeCommand: 硬编码命令,如 []string{"echo", "hello"} +// 返回值: +// +// bool - 命令是否执行成功(true为成功,false为失败) +// []string - 合并后的标准输出和标准错误内容(按行分割) +func HardCodeCommandExecutor(hardCodeCommand string) (ok bool, resultLog []string) { + + log.Info("[Excutor] - start => %v", hardCodeCommand) + + if hardCodeCommand == "" { + return false, nil + } + + // 执行命令 + cmd := exec.Command(hardCodeCommand) + + // 执行命令并获取错误信息 + err := cmd.Run() + + // 合并输出结果 + stdoutBuf := bytes.Buffer{} + stderrBuf := bytes.Buffer{} + cmd.Stdout = &stdoutBuf + cmd.Stderr = &stderrBuf + + output := mergeOutput(&stdoutBuf, &stderrBuf) + + return err == nil, output +} + func main() { // 成功案例 success, output := SingleLineCommandExecutor([]string{"ls", "-l"}) diff --git a/agent-wdd/utils/FileUtils.go b/agent-wdd/utils/FileUtils.go index 01884ed..ae88399 100644 --- a/agent-wdd/utils/FileUtils.go +++ b/agent-wdd/utils/FileUtils.go @@ -8,6 +8,7 @@ import ( "os" "os/user" "path/filepath" + "strings" ) // AppendFileToFile 将源文件的内容添加到目标文件 @@ -178,6 +179,15 @@ func FileExists(fileFullPath string) bool { return !info.IsDir() } +// CreateFolder 创建文件夹,如果文件夹存在,则返回true,否则返回false +func CreateFolder(folderName string) bool { + // 递归创建 类似于 mkdir -p folderName + if _, err := os.Stat(folderName); os.IsNotExist(err) { + return os.MkdirAll(folderName, 0755) == nil + } + return true +} + // FileOrFolderExists 文件或者目录是否返回true,不存在返回false func FileOrFolderExists(fileFullPath string) bool { _, err := os.Stat(fileFullPath) @@ -298,6 +308,89 @@ func ReadAllContentFromFile(fileFullPath string) (result []string) { return result } +// FindContentInFile 在文件中查找内容,如果存在,则返回true,否则返回false 类似于grep -q content targetFile +func FindContentInFile(content string, targetFile string) bool { + + // 读取文件内容 + fileContent, err := os.ReadFile(targetFile) + if err != nil { + log.Error("[FindContentInFile] - Error reading file: %s , error is %s", targetFile, err.Error()) + return false + } + + // 将文件内容按行分割 + lines := strings.Split(string(fileContent), "\n") + + // 遍历每一行 + for _, line := range lines { + if strings.Contains(line, content) { + return true + } + } + return false +} + +// FindAndReplaceContentInFile 在文件中查找内容,如果存在,则替换,如果存在多个,则替换全部 类似于sed -i 's/oldContent/newContent/g' targetFile +func FindAndReplaceContentInFile(oldContent string, newContent string, targetFile string) bool { + + // 读取文件内容 + fileContent, err := os.ReadFile(targetFile) + if err != nil { + log.Error("[FindAndReplaceContentInFile] - Error reading file: %s , error is %s", targetFile, err.Error()) + return false + } + + // 将文件内容按行分割 + lines := strings.Split(string(fileContent), "\n") + + // 遍历每一行 + for i, line := range lines { + if strings.Contains(line, oldContent) { + lines[i] = strings.Replace(line, oldContent, newContent, -1) + } + } + + // 将修改后的内容写回文件 + err = os.WriteFile(targetFile, []byte(strings.Join(lines, "\n")), 0644) + if err != nil { + log.Error("[FindAndReplaceContentInFile] - Error writing file: %s , error is %s", targetFile, err.Error()) + return false + } + + return true +} + +// FindAndDeleteContentInFile 在文件中查找内容,如果存在,则删除,如果存在多个,则删除全部 类似于sed -i '/content/d' targetFile +func FindAndDeleteContentInFile(content string, targetFile string) bool { + + // 读取文件内容 + fileContent, err := os.ReadFile(targetFile) + if err != nil { + log.Error("[FindAndDeleteContentInFile] - Error reading file: %s , error is %s", targetFile, err.Error()) + return false + } + + // 将文件内容按行分割 + lines := strings.Split(string(fileContent), "\n") + + // 过滤掉包含指定内容的行 + var newLines []string + for _, line := range lines { + if !strings.Contains(line, content) { + newLines = append(newLines, line) + } + } + + // 将过滤后的内容写回文件 + err = os.WriteFile(targetFile, []byte(strings.Join(newLines, "\n")), 0644) + if err != nil { + log.Error("[FindAndDeleteContentInFile] - Error writing file: %s , error is %s", targetFile, err.Error()) + return false + } + + return true +} + // MoveFolerToAnother 将源文件夹的所有文件递归移动到目标文件夹 func MoveFolerToAnother(srcDir, dstDir string) error { // 读取源文件夹中的所有条目