- 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
157 lines
4.5 KiB
Go
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"
|
|
} |