- Cross-platform support (Windows/Linux) with Windows Updates and Winget - Added dependency confirmation workflow and refresh token authentication - New screenshots: History, Live Operations, Windows Agent Details - Local CLI features with terminal output and cache system - Updated known limitations - Proxmox integration is broken - Organized docs to docs/ folder and updated .gitignore - Probably introduced a dozen bugs with Windows agents - stay tuned
192 lines
6.2 KiB
Go
192 lines
6.2 KiB
Go
package installer
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"text/template"
|
|
)
|
|
|
|
// SudoersConfig represents the sudoers configuration for the RedFlag agent
|
|
const SudoersTemplate = `# RedFlag Agent minimal sudo permissions
|
|
# This file is generated automatically during RedFlag agent installation
|
|
# Location: /etc/sudoers.d/redflag-agent
|
|
|
|
# APT package management commands
|
|
redflag-agent ALL=(root) NOPASSWD: /usr/bin/apt-get update
|
|
redflag-agent ALL=(root) NOPASSWD: /usr/bin/apt-get install -y *
|
|
redflag-agent ALL=(root) NOPASSWD: /usr/bin/apt-get upgrade -y
|
|
redflag-agent ALL=(root) NOPASSWD: /usr/bin/apt-get install --dry-run --yes *
|
|
|
|
# DNF package management commands
|
|
redflag-agent ALL=(root) NOPASSWD: /usr/bin/dnf refresh -y
|
|
redflag-agent ALL=(root) NOPASSWD: /usr/bin/dnf install -y *
|
|
redflag-agent ALL=(root) NOPASSWD: /usr/bin/dnf upgrade -y
|
|
redflag-agent ALL=(root) NOPASSWD: /usr/bin/dnf install --assumeno --downloadonly *
|
|
|
|
# Docker operations (alternative approach - uncomment if using Docker group instead of sudo)
|
|
# redflag-agent ALL=(root) NOPASSWD: /usr/bin/docker pull *
|
|
# redflag-agent ALL=(root) NOPASSWD: /usr/bin/docker image inspect *
|
|
# redflag-agent ALL=(root) NOPASSWD: /usr/bin/docker manifest inspect *
|
|
`
|
|
|
|
// SudoersInstaller handles the installation of sudoers configuration
|
|
type SudoersInstaller struct{}
|
|
|
|
// NewSudoersInstaller creates a new sudoers installer
|
|
func NewSudoersInstaller() *SudoersInstaller {
|
|
return &SudoersInstaller{}
|
|
}
|
|
|
|
// InstallSudoersConfig installs the sudoers configuration
|
|
func (s *SudoersInstaller) InstallSudoersConfig() error {
|
|
// Create the sudoers configuration content
|
|
tmpl, err := template.New("sudoers").Parse(SudoersTemplate)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse sudoers template: %w", err)
|
|
}
|
|
|
|
// Ensure the sudoers.d directory exists
|
|
sudoersDir := "/etc/sudoers.d"
|
|
if _, err := os.Stat(sudoersDir); os.IsNotExist(err) {
|
|
if err := os.MkdirAll(sudoersDir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create sudoers.d directory: %w", err)
|
|
}
|
|
}
|
|
|
|
// Create the sudoers file
|
|
sudoersFile := filepath.Join(sudoersDir, "redflag-agent")
|
|
file, err := os.OpenFile(sudoersFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0440)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create sudoers file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
// Write the template to the file
|
|
if err := tmpl.Execute(file, nil); err != nil {
|
|
return fmt.Errorf("failed to write sudoers configuration: %w", err)
|
|
}
|
|
|
|
// Verify the sudoers file syntax
|
|
if err := s.validateSudoersFile(sudoersFile); err != nil {
|
|
// Remove the invalid file
|
|
os.Remove(sudoersFile)
|
|
return fmt.Errorf("invalid sudoers configuration: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Successfully installed sudoers configuration at: %s\n", sudoersFile)
|
|
return nil
|
|
}
|
|
|
|
// validateSudoersFile validates the syntax of a sudoers file
|
|
func (s *SudoersInstaller) validateSudoersFile(sudoersFile string) error {
|
|
// Use visudo to validate the sudoers file
|
|
cmd := exec.Command("visudo", "-c", "-f", sudoersFile)
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("sudoers validation failed: %v\nOutput: %s", err, string(output))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CreateRedflagAgentUser creates the redflag-agent user if it doesn't exist
|
|
func (s *SudoersInstaller) CreateRedflagAgentUser() error {
|
|
// Check if user already exists
|
|
if _, err := os.Stat("/var/lib/redflag-agent"); err == nil {
|
|
fmt.Println("redflag-agent user already exists")
|
|
return nil
|
|
}
|
|
|
|
// Create the user with systemd as a system user
|
|
commands := [][]string{
|
|
{"useradd", "-r", "-s", "/bin/false", "-d", "/var/lib/redflag-agent", "redflag-agent"},
|
|
{"mkdir", "-p", "/var/lib/redflag-agent"},
|
|
{"chown", "redflag-agent:redflag-agent", "/var/lib/redflag-agent"},
|
|
}
|
|
|
|
for _, cmdArgs := range commands {
|
|
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to execute %v: %v\nOutput: %s", cmdArgs, err, string(output))
|
|
}
|
|
}
|
|
|
|
fmt.Println("Successfully created redflag-agent user")
|
|
return nil
|
|
}
|
|
|
|
// SetupDockerGroup adds the redflag-agent user to the docker group (alternative to sudo for Docker)
|
|
func (s *SudoersInstaller) SetupDockerGroup() error {
|
|
// Check if docker group exists
|
|
if _, err := os.Stat("/var/run/docker.sock"); os.IsNotExist(err) {
|
|
fmt.Println("Docker is not installed, skipping docker group setup")
|
|
return nil
|
|
}
|
|
|
|
// Add user to docker group
|
|
cmd := exec.Command("usermod", "-aG", "docker", "redflag-agent")
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to add redflag-agent to docker group: %v\nOutput: %s", err, string(output))
|
|
}
|
|
|
|
fmt.Println("Successfully added redflag-agent to docker group")
|
|
return nil
|
|
}
|
|
|
|
// CreateSystemdService creates a systemd service file for the agent
|
|
func (s *SudoersInstaller) CreateSystemdService() error {
|
|
const serviceTemplate = `[Unit]
|
|
Description=RedFlag Update Agent
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=redflag-agent
|
|
Group=redflag-agent
|
|
WorkingDirectory=/var/lib/redflag-agent
|
|
ExecStart=/usr/local/bin/redflag-agent
|
|
Restart=always
|
|
RestartSec=30
|
|
|
|
# Security hardening
|
|
NoNewPrivileges=true
|
|
ProtectSystem=strict
|
|
ProtectHome=true
|
|
ReadWritePaths=/var/lib/redflag-agent
|
|
PrivateTmp=true
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
`
|
|
|
|
serviceFile := "/etc/systemd/system/redflag-agent.service"
|
|
file, err := os.OpenFile(serviceFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create systemd service file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
if _, err := file.WriteString(serviceTemplate); err != nil {
|
|
return fmt.Errorf("failed to write systemd service file: %w", err)
|
|
}
|
|
|
|
// Reload systemd
|
|
if err := exec.Command("systemctl", "daemon-reload").Run(); err != nil {
|
|
return fmt.Errorf("failed to reload systemd: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Successfully created systemd service at: %s\n", serviceFile)
|
|
return nil
|
|
}
|
|
|
|
// Cleanup removes sudoers configuration
|
|
func (s *SudoersInstaller) Cleanup() error {
|
|
sudoersFile := "/etc/sudoers.d/redflag-agent"
|
|
if _, err := os.Stat(sudoersFile); err == nil {
|
|
if err := os.Remove(sudoersFile); err != nil {
|
|
return fmt.Errorf("failed to remove sudoers file: %w", err)
|
|
}
|
|
fmt.Println("Successfully removed sudoers configuration")
|
|
}
|
|
return nil
|
|
} |