Skip to content

Commit 5a12f03

Browse files
committed
feat(agent): implement TLS support for syslog integrations.
1 parent ca1fda5 commit 5a12f03

File tree

8 files changed

+531
-21
lines changed

8 files changed

+531
-21
lines changed

agent/config/const.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ var (
3636
MESSAGE_HEADER = "utm_stack_agent_ds"
3737
BatchCapacity = 100
3838

39+
// TLS Configuration for Integrations
40+
IntegrationCertPath = filepath.Join(utils.GetMyPath(), "certs", "integration.crt")
41+
IntegrationKeyPath = filepath.Join(utils.GetMyPath(), "certs", "integration.key")
42+
IntegrationCAPath = filepath.Join(utils.GetMyPath(), "certs", "integration-ca.crt")
43+
44+
3945
// MaxConnectionTime = 120 * time.Second
4046
// SERV_NAME = "UTMStackAgent"
4147
// SERV_LOG = "utmstack_agent.log"

agent/main.go

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,88 @@ func main() {
8484
serv.InstallService()
8585
fmt.Println("[OK]")
8686
fmt.Println("UTMStackAgent service installed correctly")
87+
8788
case "enable-integration", "disable-integration":
8889
fmt.Println("Changing integration status ...")
8990
integration := os.Args[2]
9091
proto := os.Args[3]
9192

92-
port, err := modules.ChangeIntegrationStatus(integration, proto, (arg == "enable-integration"))
93+
tlsEnabled := false
94+
for _, arg := range os.Args[4:] {
95+
if arg == "--tls" {
96+
tlsEnabled = true
97+
break
98+
}
99+
}
100+
101+
var port string
102+
var err error
103+
104+
if arg == "enable-integration" && tlsEnabled {
105+
port, err = modules.ChangeIntegrationStatus(integration, proto, true, true)
106+
} else if arg == "enable-integration" {
107+
port, err = modules.ChangeIntegrationStatus(integration, proto, true, false)
108+
} else {
109+
port, err = modules.ChangeIntegrationStatus(integration, proto, false)
110+
}
111+
93112
if err != nil {
94-
fmt.Println("Error trying to change integration status: ", err)
113+
fmt.Println("Error:", err)
114+
os.Exit(1)
115+
}
116+
117+
if arg == "enable-integration" && tlsEnabled {
118+
fmt.Printf("Integration %s %s enabled with TLS on port %s\n", integration, proto, port)
119+
} else if arg == "enable-integration" {
120+
fmt.Printf("Integration %s %s enabled on port %s\n", integration, proto, port)
121+
} else {
122+
fmt.Printf("Integration %s %s disabled (port %s freed)\n", integration, proto, port)
123+
}
124+
time.Sleep(5 * time.Second)
125+
126+
case "load-tls-certs":
127+
if len(os.Args) < 4 {
128+
fmt.Println("Usage: ./utmstack_agent load-tls-certs <certificate_path> <private_key_path> [ca_certificate_path]")
129+
fmt.Println("Example: ./utmstack_agent load-tls-certs /path/to/server.crt /path/to/server.key /path/to/ca.crt")
130+
os.Exit(1)
131+
}
132+
133+
userCertPath := os.Args[2]
134+
userKeyPath := os.Args[3]
135+
var userCAPath string
136+
if len(os.Args) > 4 {
137+
userCAPath = os.Args[4]
138+
}
139+
140+
fmt.Println("Loading user TLS certificates ...")
141+
142+
fmt.Print("Validating certificate files ... ")
143+
if err := utils.ValidateIntegrationCertificates(userCertPath, userKeyPath); err != nil {
144+
fmt.Printf("\nError: Invalid certificate files: %v\n", err)
95145
os.Exit(1)
96146
}
97-
fmt.Printf("Action %s %s %s correctly in port %s\n", arg, integration, proto, port)
147+
fmt.Println("[OK]")
148+
149+
fmt.Print("Installing certificates ... ")
150+
src := utils.CertificateFiles{
151+
CertPath: userCertPath,
152+
KeyPath: userKeyPath,
153+
CAPath: userCAPath,
154+
}
155+
dest := utils.CertificateFiles{
156+
CertPath: config.IntegrationCertPath,
157+
KeyPath: config.IntegrationKeyPath,
158+
CAPath: config.IntegrationCAPath,
159+
}
160+
if err := utils.LoadUserCertificatesWithStruct(src, dest); err != nil {
161+
fmt.Printf("\nError loading certificates: %v\n", err)
162+
os.Exit(1)
163+
}
164+
fmt.Println("[OK]")
165+
166+
fmt.Println("TLS certificates loaded successfully!")
98167
time.Sleep(5 * time.Second)
168+
99169
case "change-port":
100170
fmt.Println("Changing integration port ...")
101171
integration := os.Args[2]
@@ -109,6 +179,7 @@ func main() {
109179
}
110180
fmt.Printf("Port changed correctly from %s to %s\n", old, port)
111181
time.Sleep(5 * time.Second)
182+
112183
case "uninstall":
113184
fmt.Print("Uninstalling UTMStackAgent service ...")
114185

@@ -160,6 +231,17 @@ func Help() {
160231
fmt.Println(" uninstall Uninstall the UTMStackAgent service")
161232
fmt.Println(" help Display this help message")
162233
fmt.Println()
234+
fmt.Println("TLS Certificate Management:")
235+
fmt.Println(" # Load your own certificates (RECOMMENDED)")
236+
fmt.Println(" ./utmstack_agent load-tls-certs /path/to/server.crt /path/to/server.key /path/to/ca.crt")
237+
fmt.Println(" ./utmstack_agent load-tls-certs /path/to/server.crt /path/to/server.key # Without CA")
238+
fmt.Println()
239+
fmt.Println("TLS Integration Examples:")
240+
fmt.Println(" ./utmstack_agent enable-integration syslog tcp --tls # Enable with TLS")
241+
fmt.Println(" ./utmstack_agent enable-integration syslog tcp # Enable without TLS (default)")
242+
fmt.Println(" ./utmstack_agent disable-integration syslog tcp # Disable (auto-disables TLS)")
243+
fmt.Println(" ./utmstack_agent check-tls-certs # Check certificate status")
244+
fmt.Println()
163245
fmt.Println("Note:")
164246
fmt.Println(" - Make sure to run commands with appropriate permissions.")
165247
fmt.Println(" - All commands require administrative privileges.")

agent/modules/configuration.go

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ import (
55
"net"
66
"os"
77
"strings"
8+
"time"
89

910
"github.com/utmstack/UTMStack/agent/config"
1011
"github.com/utmstack/UTMStack/agent/utils"
1112
)
1213

1314
type Port struct {
14-
IsListen bool `json:"enabled"`
15-
Port string `json:"value"`
15+
IsListen bool `json:"enabled"`
16+
Port string `json:"value"`
17+
TLSEnabled bool `json:"tls_enabled,omitempty"`
1618
}
1719

1820
type Integration struct {
@@ -59,7 +61,7 @@ func ConfigureCollectorFirstTime() error {
5961
return WriteCollectorConfig(integrations, config.CollectorFileName)
6062
}
6163

62-
func ChangeIntegrationStatus(logTyp string, proto string, isEnabled bool) (string, error) {
64+
func ChangeIntegrationStatus(logTyp string, proto string, isEnabled bool, tlsOptions ...bool) (string, error) {
6365
var port string
6466
cnf, err := ReadCollectorConfig()
6567
if err != nil {
@@ -78,9 +80,49 @@ func ChangeIntegrationStatus(logTyp string, proto string, isEnabled bool) (strin
7880
case "tcp":
7981
integration.TCP.IsListen = isEnabled
8082
port = integration.TCP.Port
83+
84+
// Handle TLS configuration if specified
85+
if len(tlsOptions) > 0 && isEnabled {
86+
if tlsOptions[0] {
87+
// Enable TLS
88+
integration.TCP.TLSEnabled = true
89+
mod := GetModule(logTyp)
90+
if mod != nil && mod.IsPortListen(proto) {
91+
mod.DisablePort(proto)
92+
time.Sleep(100 * time.Millisecond)
93+
err := mod.EnablePort(proto, true)
94+
if err != nil {
95+
return "", fmt.Errorf("error enabling TLS on running module: %v", err)
96+
}
97+
}
98+
} else {
99+
// Disable TLS
100+
integration.TCP.TLSEnabled = false
101+
mod := GetModule(logTyp)
102+
if mod != nil && mod.IsPortListen(proto) {
103+
mod.DisablePort(proto)
104+
time.Sleep(100 * time.Millisecond)
105+
err := mod.EnablePort(proto, false)
106+
if err != nil {
107+
return "", fmt.Errorf("error disabling TLS on running module: %v", err)
108+
}
109+
}
110+
}
111+
}
112+
113+
// Auto-disable TLS when disabling integration
114+
if !isEnabled {
115+
integration.TCP.TLSEnabled = false
116+
}
117+
81118
case "udp":
82119
integration.UDP.IsListen = isEnabled
83120
port = integration.UDP.Port
121+
122+
// TLS validation for UDP
123+
if len(tlsOptions) > 0 && tlsOptions[0] {
124+
return "", fmt.Errorf("TLS is not supported for UDP protocol. Use TCP for TLS connections")
125+
}
84126
}
85127

86128
cnf.Integrations[logTyp] = integration
@@ -145,7 +187,11 @@ func WriteCollectorConfig(integrations map[string]Integration, filename string)
145187
for name, integration := range integrations {
146188
fileContent += fmt.Sprintf(" \"%s\": {\n", name)
147189
if integration.TCP.Port != "" {
148-
fileContent += fmt.Sprintf(" \"tcp_port\": {\"enabled\": %t, \"value\": \"%s\"},\n", integration.TCP.IsListen, integration.TCP.Port)
190+
fileContent += fmt.Sprintf(" \"tcp_port\": {\"enabled\": %t, \"value\": \"%s\"", integration.TCP.IsListen, integration.TCP.Port)
191+
if integration.TCP.TLSEnabled {
192+
fileContent += fmt.Sprintf(", \"tls_enabled\": %t", integration.TCP.TLSEnabled)
193+
}
194+
fileContent += "},\n"
149195
}
150196
if integration.UDP.Port != "" {
151197
fileContent += fmt.Sprintf(" \"udp_port\": {\"enabled\": %t, \"value\": \"%s\"},\n", integration.UDP.IsListen, integration.UDP.Port)
@@ -184,3 +230,73 @@ func WriteCollectorConfigFromModules(mod []Module, filename string) error {
184230
}
185231
return WriteCollectorConfig(integrations, filename)
186232
}
233+
234+
func EnableTLSForIntegration(logTyp string, proto string) (string, error) {
235+
cnf, err := ReadCollectorConfig()
236+
if err != nil {
237+
return "", fmt.Errorf("error reading collector config: %v", err)
238+
}
239+
240+
if valid := config.ValidateModuleType(logTyp); valid == "nil" {
241+
return "", fmt.Errorf("invalid integration: %s", logTyp)
242+
}
243+
244+
integration := cnf.Integrations[logTyp]
245+
var port string
246+
247+
switch proto {
248+
case "tcp":
249+
if integration.TCP.Port == "" {
250+
return "", fmt.Errorf("TCP port not configured for %s", logTyp)
251+
}
252+
port = integration.TCP.Port
253+
integration.TCP.TLSEnabled = true
254+
255+
mod := GetModule(logTyp)
256+
if mod != nil && mod.IsPortListen(proto) {
257+
mod.DisablePort(proto)
258+
time.Sleep(100 * time.Millisecond)
259+
err := mod.EnablePort(proto, true)
260+
if err != nil {
261+
return port, fmt.Errorf("error enabling TLS on running module: %v", err)
262+
}
263+
}
264+
case "udp":
265+
return "", fmt.Errorf("TLS not supported for UDP protocol")
266+
default:
267+
return "", fmt.Errorf("invalid protocol: %s", proto)
268+
}
269+
270+
cnf.Integrations[logTyp] = integration
271+
return port, WriteCollectorConfig(cnf.Integrations, config.CollectorFileName)
272+
}
273+
274+
func DisableTLSForIntegration(logTyp string, proto string) error {
275+
cnf, err := ReadCollectorConfig()
276+
if err != nil {
277+
return fmt.Errorf("error reading collector config: %v", err)
278+
}
279+
280+
integration := cnf.Integrations[logTyp]
281+
switch proto {
282+
case "tcp":
283+
integration.TCP.TLSEnabled = false
284+
285+
mod := GetModule(logTyp)
286+
if mod != nil && mod.IsPortListen(proto) {
287+
mod.DisablePort(proto)
288+
time.Sleep(100 * time.Millisecond)
289+
err := mod.EnablePort(proto, false)
290+
if err != nil {
291+
return fmt.Errorf("error disabling TLS on running module: %v", err)
292+
}
293+
}
294+
case "udp":
295+
return fmt.Errorf("TLS not supported for UDP protocol")
296+
default:
297+
return fmt.Errorf("invalid protocol: %s", proto)
298+
}
299+
300+
cnf.Integrations[logTyp] = integration
301+
return WriteCollectorConfig(cnf.Integrations, config.CollectorFileName)
302+
}

agent/modules/modules.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type Module interface {
2020
IsPortListen(proto string) bool
2121
SetNewPort(proto string, port string)
2222
GetPort(proto string) string
23-
EnablePort(proto string)
23+
EnablePort(proto string, enableTLS bool) error
2424
DisablePort(proto string)
2525
}
2626

@@ -88,7 +88,12 @@ func StartModules() {
8888
if changeAllowed {
8989
moCache[index].SetNewPort(proto, port)
9090
if conf[1] {
91-
moCache[index].EnablePort(proto)
91+
enableTLS := proto == "tcp" && cnf.TCP.TLSEnabled
92+
93+
err := moCache[index].EnablePort(proto, enableTLS)
94+
if err != nil {
95+
utils.Logger.ErrorF("error enabling port for %s %s: %v", intType, proto, err)
96+
}
9297
}
9398
} else {
9499
utils.Logger.Info("change in port %s protocol %s not allowed for %s or out range %s-%s", port, proto, intType, config.PortRangeMin, config.PortRangeMax)

agent/modules/netflow.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,20 @@ func GetNetflowModule() *NetflowModule {
4545
return netflowModule
4646
}
4747

48-
func (m *NetflowModule) EnablePort(proto string) {
48+
func (m *NetflowModule) EnablePort(proto string, enableTLS bool) error {
4949
if proto == "udp" && !m.IsEnabled {
50+
// NetFlow over UDP doesn't support TLS
51+
if enableTLS {
52+
utils.Logger.Info("TLS not supported for NetFlow (UDP protocol), ignoring TLS flag")
53+
}
54+
5055
utils.Logger.Info("Server %s listening in port: %s protocol: UDP", m.DataType, config.ProtoPorts[config.DataTypeNetflow].UDP)
5156
m.IsEnabled = true
5257

5358
port, err := strconv.Atoi(config.ProtoPorts[config.DataTypeNetflow].UDP)
5459
if err != nil {
5560
utils.Logger.ErrorF("error converting port to int: %v", err)
56-
return
61+
return err
5762
}
5863

5964
m.Listener, err = net.ListenUDP("udp", &net.UDPAddr{
@@ -62,7 +67,7 @@ func (m *NetflowModule) EnablePort(proto string) {
6267
})
6368
if err != nil {
6469
utils.Logger.ErrorF("error listening netflow: %v", err)
65-
return
70+
return err
6671
}
6772

6873
m.CTX, m.Cancel = context.WithCancel(context.Background())
@@ -122,6 +127,7 @@ func (m *NetflowModule) EnablePort(proto string) {
122127
}
123128
}()
124129
}
130+
return nil
125131
}
126132

127133
func (m *NetflowModule) DisablePort(proto string) {

0 commit comments

Comments
 (0)