Files
Redflag/aggregator-agent/internal/scanner/dnf.go
Fimeg e40cb14945 Fix module paths for GitHub repository structure
- Update go.mod files to use github.com/Fimeg/RedFlag module path
- Fix all import statements across server and agent code
- Resolves build errors when cloning from GitHub
- Utils package (version comparison) is actually needed and working
2025-10-29 11:53:20 -04:00

157 lines
4.5 KiB
Go

package scanner
import (
"bufio"
"bytes"
"fmt"
"os/exec"
"regexp"
"strings"
"github.com/Fimeg/RedFlag/aggregator-agent/internal/client"
)
// DNFScanner scans for DNF/RPM package updates
type DNFScanner struct{}
// NewDNFScanner creates a new DNF scanner
func NewDNFScanner() *DNFScanner {
return &DNFScanner{}
}
// IsAvailable checks if DNF is available on this system
func (s *DNFScanner) IsAvailable() bool {
_, err := exec.LookPath("dnf")
return err == nil
}
// Scan scans for available DNF updates
func (s *DNFScanner) Scan() ([]client.UpdateReportItem, error) {
// Check for updates (don't update cache to avoid needing sudo)
cmd := exec.Command("dnf", "check-update")
output, err := cmd.Output()
if err != nil {
// dnf check-update returns exit code 100 when updates are available
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 100 {
// Updates are available, continue processing
} else {
return nil, fmt.Errorf("failed to run dnf check-update: %w", err)
}
}
return parseDNFOutput(output)
}
func parseDNFOutput(output []byte) ([]client.UpdateReportItem, error) {
var updates []client.UpdateReportItem
scanner := bufio.NewScanner(bytes.NewReader(output))
// Regex to parse dnf check-update output:
// package-name.version arch new-version
re := regexp.MustCompile(`^([^\s]+)\.([^\s]+)\s+([^\s]+)\s+([^\s]+)$`)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
// Skip empty lines and header/footer
if line == "" ||
strings.HasPrefix(line, "Last metadata") ||
strings.HasPrefix(line, "Dependencies") ||
strings.HasPrefix(line, "Obsoleting") ||
strings.Contains(line, "Upgraded") {
continue
}
matches := re.FindStringSubmatch(line)
if len(matches) < 5 {
continue
}
packageName := matches[1]
arch := matches[2]
repoAndVersion := matches[3]
newVersion := matches[4]
// Extract repository and current version from repoAndVersion
// Format is typically: repo-version current-version
parts := strings.Fields(repoAndVersion)
var repository, currentVersion string
if len(parts) >= 2 {
repository = parts[0]
currentVersion = parts[1]
} else if len(parts) == 1 {
repository = parts[0]
// Try to get current version from rpm
currentVersion = getInstalledVersion(packageName)
}
// Determine severity based on repository and update type
severity := determineSeverity(repository, packageName, newVersion)
update := client.UpdateReportItem{
PackageType: "dnf",
PackageName: packageName,
CurrentVersion: currentVersion,
AvailableVersion: newVersion,
Severity: severity,
RepositorySource: repository,
Metadata: map[string]interface{}{
"architecture": arch,
},
}
updates = append(updates, update)
}
return updates, nil
}
// getInstalledVersion gets the currently installed version of a package
func getInstalledVersion(packageName string) string {
cmd := exec.Command("rpm", "-q", "--queryformat", "%{VERSION}", packageName)
output, err := cmd.Output()
if err != nil {
return "unknown"
}
return strings.TrimSpace(string(output))
}
// determineSeverity determines the severity of an update based on repository and package information
func determineSeverity(repository, packageName, newVersion string) string {
// Security updates
if strings.Contains(strings.ToLower(repository), "security") ||
strings.Contains(strings.ToLower(repository), "updates") ||
strings.Contains(strings.ToLower(packageName), "security") ||
strings.Contains(strings.ToLower(packageName), "selinux") ||
strings.Contains(strings.ToLower(packageName), "crypto") ||
strings.Contains(strings.ToLower(packageName), "openssl") ||
strings.Contains(strings.ToLower(packageName), "gnutls") {
return "critical"
}
// Kernel updates are important
if strings.Contains(strings.ToLower(packageName), "kernel") {
return "important"
}
// Core system packages
if strings.Contains(strings.ToLower(packageName), "glibc") ||
strings.Contains(strings.ToLower(packageName), "systemd") ||
strings.Contains(strings.ToLower(packageName), "bash") ||
strings.Contains(strings.ToLower(packageName), "coreutils") {
return "important"
}
// Development tools
if strings.Contains(strings.ToLower(packageName), "gcc") ||
strings.Contains(strings.ToLower(packageName), "python") ||
strings.Contains(strings.ToLower(packageName), "nodejs") ||
strings.Contains(strings.ToLower(packageName), "java") ||
strings.Contains(strings.ToLower(packageName), "go") {
return "moderate"
}
// Default severity
return "low"
}