Skip to content

Commit 93ba849

Browse files
authored
Merge pull request #1097 from utmstack/bugfix/10.7.0/windows-arm-experimental
Bugfix/10.7.0/windows arm experimental
2 parents 68e5c0e + 2f83668 commit 93ba849

File tree

8 files changed

+230
-132
lines changed

8 files changed

+230
-132
lines changed

agent/collectors/collectors.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ func getCollectorsInstances() []Collector {
2222
var collectors []Collector
2323
switch runtime.GOOS {
2424
case "windows":
25-
collectors = append(collectors, Winlogbeat{})
26-
collectors = append(collectors, Filebeat{})
25+
collectors = append(collectors, Windows{})
26+
if runtime.GOARCH == "amd64" {
27+
collectors = append(collectors, Filebeat{})
28+
}
2729
case "linux":
2830
collectors = append(collectors, Filebeat{})
2931
}

agent/collectors/filebeat.go

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -74,36 +74,29 @@ func (f Filebeat) Install() error {
7474
return fmt.Errorf("error reloading daemon: %v", err)
7575
}
7676

77-
family, err := utils.DetectLinuxFamily()
77+
err := utils.Execute("systemctl", filebLogPath, "enable", config.ModulesServName)
7878
if err != nil {
79-
return err
79+
return fmt.Errorf("%s", err)
8080
}
8181

82-
if family == "debian" || family == "rhel" {
83-
err := utils.Execute("systemctl", filebLogPath, "enable", config.ModulesServName)
84-
if err != nil {
85-
return fmt.Errorf("%s", err)
86-
}
87-
88-
err = utils.Execute("systemctl", filebLogPath, "start", config.ModulesServName)
89-
if err != nil {
90-
return fmt.Errorf("%s", err)
91-
}
92-
93-
err = utils.Execute("./filebeat", filebLogPath, "modules", "enable", "system")
94-
if err != nil {
95-
return fmt.Errorf("%s", err)
96-
}
97-
98-
err = utils.Execute("sed", filepath.Join(filebLogPath, "modules.d"), "-i", "s/enabled: false/enabled: true/g", "system.yml")
99-
if err != nil {
100-
return fmt.Errorf("%s", err)
101-
}
102-
103-
err = utils.Execute("systemctl", filebLogPath, "restart", config.ModulesServName)
104-
if err != nil {
105-
return fmt.Errorf("%s", err)
106-
}
82+
err = utils.Execute("systemctl", filebLogPath, "start", config.ModulesServName)
83+
if err != nil {
84+
return fmt.Errorf("%s", err)
85+
}
86+
87+
err = utils.Execute("./filebeat", filebLogPath, "modules", "enable", "system")
88+
if err != nil {
89+
return fmt.Errorf("%s", err)
90+
}
91+
92+
err = utils.Execute("sed", filepath.Join(filebLogPath, "modules.d"), "-i", "s/enabled: false/enabled: true/g", "system.yml")
93+
if err != nil {
94+
return fmt.Errorf("%s", err)
95+
}
96+
97+
err = utils.Execute("systemctl", filebLogPath, "restart", config.ModulesServName)
98+
if err != nil {
99+
return fmt.Errorf("%s", err)
107100
}
108101
}
109102
}
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
//go:build windows && amd64
2+
// +build windows,amd64
3+
14
package collectors
25

36
import (
@@ -10,9 +13,9 @@ import (
1013
"github.com/utmstack/UTMStack/agent/utils"
1114
)
1215

13-
type Winlogbeat struct{}
16+
type Windows struct{}
1417

15-
func (w Winlogbeat) Install() error {
18+
func (w Windows) Install() error {
1619
path := utils.GetMyPath()
1720

1821
winlogPath := filepath.Join(path, "beats", "winlogbeat")
@@ -59,7 +62,7 @@ func (w Winlogbeat) Install() error {
5962
return nil
6063
}
6164

62-
func (w Winlogbeat) SendSystemLogs() {
65+
func (w Windows) SendSystemLogs() {
6366
logLinesChan := make(chan []string)
6467
path := utils.GetMyPath()
6568
winbLogPath := filepath.Join(path, "beats", "winlogbeat", "logs")
@@ -82,7 +85,7 @@ func (w Winlogbeat) SendSystemLogs() {
8285
}
8386
}
8487

85-
func (w Winlogbeat) Uninstall() error {
88+
func (w Windows) Uninstall() error {
8689
if isInstalled, err := utils.CheckIfServiceIsInstalled(config.WinServName); err != nil {
8790
return fmt.Errorf("error checking if %s is running: %v", config.WinServName, err)
8891
} else if isInstalled {

agent/collectors/windows_arm64.go

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
//go:build windows && arm64
2+
// +build windows,arm64
3+
4+
package collectors
5+
6+
import (
7+
"github.com/utmstack/UTMStack/agent/config"
8+
"github.com/utmstack/UTMStack/agent/logservice"
9+
"os"
10+
"os/exec"
11+
"path/filepath"
12+
"strings"
13+
"time"
14+
15+
"github.com/threatwinds/validations"
16+
"github.com/utmstack/UTMStack/agent/utils"
17+
)
18+
19+
type Windows struct{}
20+
21+
const PowerShellScript = `
22+
<#
23+
.SYNOPSIS
24+
Collects Windows Application, System, and Security logs from the last 5 minutes, then prints them to the console in a compact, single-line JSON format,
25+
emulating the field structure that Winlogbeat typically produces.
26+
27+
.DESCRIPTION
28+
1. Retrieves the last 5 minutes of Windows logs (Application, System, Security) using FilterHashtable (no post-fetch filtering).
29+
2. Maps event properties to a schema similar to Winlogbeat's, including:
30+
- @timestamp
31+
- message
32+
- event.code
33+
- event.provider
34+
- event.kind
35+
- winlog fields (e.g. record_id, channel, activity_id, etc.)
36+
3. Prints each log record as a single-line JSON object with no indentation/extra spacing.
37+
4. If no logs are found, the script produces no output at all.
38+
#>
39+
40+
# Suppress any runtime errors that would clutter the console.
41+
$ErrorActionPreference = 'SilentlyContinue'
42+
43+
# Calculate the start time for filtering
44+
$startTime = (Get-Date).AddSeconds(-30)
45+
46+
# Retrieve logs with filter hashtable
47+
$applicationLogs = Get-WinEvent -FilterHashtable @{ LogName='Application'; StartTime=$startTime }
48+
$systemLogs = Get-WinEvent -FilterHashtable @{ LogName='System'; StartTime=$startTime }
49+
$securityLogs = Get-WinEvent -FilterHashtable @{ LogName='Security'; StartTime=$startTime }
50+
51+
# Safeguard against null results
52+
if (-not $applicationLogs) { $applicationLogs = @() }
53+
if (-not $systemLogs) { $systemLogs = @() }
54+
if (-not $securityLogs) { $securityLogs = @() }
55+
56+
# Combine them
57+
$recentLogs = $applicationLogs + $systemLogs + $securityLogs
58+
59+
# If no logs are found, produce no output at all
60+
if (-not $recentLogs) {
61+
return
62+
}
63+
64+
# Function to convert the raw Properties array to a dictionary-like object under winlog.event_data
65+
function Convert-PropertiesToEventData {
66+
param([Object[]] $Properties)
67+
68+
# If nothing is there, return an empty hashtable
69+
if (-not $Properties) { return @{} }
70+
71+
# Winlogbeat places custom fields under winlog.event_data.
72+
# Typically, it tries to parse known keys, but we'll do a simple best-effort approach:
73+
# We'll create paramN = <value> pairs for each array index.
74+
$eventData = [ordered]@{}
75+
76+
for ($i = 0; $i -lt $Properties.Count; $i++) {
77+
$value = $Properties[$i].Value
78+
79+
# If the property is itself an object with nested fields, we can flatten or store as-is.
80+
# We'll store as-is for clarity.
81+
# We'll name them param1, param2, param3,... unless you'd like more specific field logic.
82+
$paramName = "param$($i+1)"
83+
84+
$eventData[$paramName] = $value
85+
}
86+
87+
return $eventData
88+
}
89+
90+
# Transform each event into a structure emulating Winlogbeat
91+
foreach ($rawEvent in $recentLogs) {
92+
# Convert TimeCreated to a universal ISO8601 string
93+
$timestamp = $rawEvent.TimeCreated.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
94+
95+
# Build the top-level document
96+
$doc = [ordered]@{
97+
# Matches Winlogbeat's typical top-level timestamp field
98+
'@timestamp' = $timestamp
99+
100+
# The main message content from the event
101+
'message' = $rawEvent.Message
102+
103+
# "event" block: minimal example
104+
'event' = [ordered]@{
105+
'code' = $rawEvent.Id # event_id in Winlogbeat is typically a string or numeric
106+
'provider' = $rawEvent.ProviderName
107+
'kind' = 'event'
108+
'created' = $timestamp # or you could omit if desired
109+
}
110+
111+
# "winlog" block: tries to mirror Winlogbeat's structure for Windows
112+
'winlog' = [ordered]@{
113+
'record_id' = $rawEvent.RecordId
114+
'computer_name' = $rawEvent.MachineName
115+
'channel' = $rawEvent.LogName
116+
'provider_name' = $rawEvent.ProviderName
117+
'provider_guid' = $rawEvent.ProviderId
118+
'process' = [ordered]@{
119+
'pid' = $rawEvent.ProcessId
120+
'thread' = @{
121+
'id' = $rawEvent.ThreadId
122+
}
123+
}
124+
'event_id' = $rawEvent.Id
125+
'version' = $rawEvent.Version
126+
'activity_id' = $rawEvent.ActivityId
127+
'related_activity_id'= $rawEvent.RelatedActivityId
128+
'task' = $rawEvent.TaskDisplayName
129+
'opcode' = $rawEvent.OpcodeDisplayName
130+
'keywords' = $rawEvent.KeywordsDisplayNames
131+
'time_created' = $timestamp
132+
# Convert "Properties" into a dictionary for event_data
133+
'event_data' = Convert-PropertiesToEventData $rawEvent.Properties
134+
}
135+
}
136+
137+
# Convert our object to JSON (with no extra formatting).
138+
$json = $doc | ConvertTo-Json -Depth 20
139+
140+
# Remove all newlines and indentation for a single-line representation
141+
$compactJson = $json -replace '(\r?\n\s*)+', ''
142+
143+
# Output the line
144+
Write-Output $compactJson
145+
}
146+
`
147+
148+
func (w Windows) Install() error {
149+
path := utils.GetMyPath()
150+
collectorPath := filepath.Join(path, "collector.ps1")
151+
err := os.WriteFile(collectorPath, []byte(PowerShellScript), 0644)
152+
return err
153+
}
154+
155+
func (w Windows) SendSystemLogs() {
156+
path := utils.GetMyPath()
157+
collectorPath := filepath.Join(path, "collector.ps1")
158+
159+
for {
160+
select {
161+
case <-time.After(30 * time.Second):
162+
go func() {
163+
cmd := exec.Command("Powershell.exe", "-File", collectorPath)
164+
165+
output, err := cmd.Output()
166+
if err != nil {
167+
_ = utils.Logger.ErrorF("error executing powershell script: %v", err)
168+
return
169+
}
170+
171+
logLines := strings.Split(string(output), "\n")
172+
173+
validatedLogs := make([]string, 0, len(logLines))
174+
175+
for logLine := range logLines {
176+
validatedLog, _, err := validations.ValidateString(logLine, false)
177+
if err != nil {
178+
_ = utils.Logger.ErrorF("error validating log: %s: %v", logLine, err)
179+
continue
180+
}
181+
182+
validatedLogs = append(validatedLogs, validatedLog)
183+
}
184+
185+
logservice.LogQueue <- logservice.LogPipe{
186+
Src: string(config.DataTypeWindowsAgent),
187+
Logs: validatedLogs,
188+
}
189+
}()
190+
}
191+
}
192+
}
193+
194+
func (w Windows) Uninstall() error {
195+
path := utils.GetMyPath()
196+
collectorPath := filepath.Join(path, "collector.ps1")
197+
err := os.Remove(collectorPath)
198+
return err
199+
}

agent/utils/address.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,3 @@ func GetIPAddress() (string, error) {
2222

2323
return "", errors.New("failed to get IP address")
2424
}
25-
26-
func IsPortUsed(proto string, port string) bool {
27-
conn, err := net.Listen(proto, ":"+port)
28-
if err != nil {
29-
return true
30-
}
31-
conn.Close()
32-
return false
33-
}

agent/utils/delay.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,3 @@ func IncrementReconnectDelay(delay time.Duration, maxReconnectDelay time.Duratio
99
}
1010
return delay
1111
}
12-
13-
func IncrementReconnectTime(currentTime time.Duration, delay time.Duration, maxReconnectTime time.Duration) time.Duration {
14-
currentTime = currentTime + delay
15-
if currentTime >= maxReconnectTime {
16-
currentTime = maxReconnectTime
17-
}
18-
return currentTime
19-
}

agent/utils/os.go

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,13 @@ package utils
33
import (
44
"fmt"
55
"os"
6-
"os/exec"
76
"os/user"
87
"strconv"
98
"strings"
109

1110
"github.com/elastic/go-sysinfo"
1211
)
1312

14-
func DetectLinuxFamily() (string, error) {
15-
var pmCommands map[string]string = map[string]string{
16-
"debian": "apt list",
17-
"rhel": "yum list",
18-
}
19-
20-
for dist, command := range pmCommands {
21-
cmd := strings.Split(command, " ")
22-
var err error
23-
24-
if len(cmd) > 1 {
25-
_, err = exec.Command(cmd[0], cmd[1:]...).Output()
26-
} else {
27-
_, err = exec.Command(cmd[0]).Output()
28-
}
29-
30-
if err == nil {
31-
return dist, nil
32-
}
33-
}
34-
return "", fmt.Errorf("unknown distribution")
35-
}
36-
3713
type OSInfo struct {
3814
Hostname string
3915
OsType string

0 commit comments

Comments
 (0)