- Updated from deprecated repo.wallarm.com to meganode.wallarm.com/6.12/ - Installer URL: wallarm-6.12.7.x86_64-glibc.sh - Override with WALLARM_INSTALLER_URL env var - Pipes token+region via stdin to skip interactive prompts - Preflight checks meganode.wallarm.com reachability
222 lines
6.4 KiB
Go
222 lines
6.4 KiB
Go
// Package native implements Wallarm Native Node deployment (no Docker).
|
|
// Ported from native/wallarm-native.sh.
|
|
package native
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"git.sechpoint.app/customer-engineering/wallarm/internal/state"
|
|
)
|
|
|
|
// Constants
|
|
const (
|
|
BaseDir = "/opt/wallarm"
|
|
NodesDir = BaseDir + "/nodes"
|
|
SystemdTemplate = "/etc/systemd/system/wallarm-node@.service"
|
|
)
|
|
|
|
// installerURL returns the correct native node installer for the current architecture.
|
|
// Override with WALLARM_INSTALLER_URL env var to pin a specific version.
|
|
func installerURL() string {
|
|
if u := os.Getenv("WALLARM_INSTALLER_URL"); u != "" {
|
|
return u
|
|
}
|
|
arch := "x86_64"
|
|
suffix := "glibc"
|
|
return fmt.Sprintf("https://meganode.wallarm.com/6.12/wallarm-6.12.7.%s-%s.sh", arch, suffix)
|
|
}
|
|
|
|
// InstallNode deploys a single native Wallarm node.
|
|
// node: node metadata, apiToken: Wallarm API token, apiHost: cloud API host, labels: optional node labels.
|
|
func InstallNode(node state.Node, apiToken, apiHost, labels string) error {
|
|
if apiToken == "" {
|
|
return fmt.Errorf("API token required")
|
|
}
|
|
if apiHost == "" {
|
|
return fmt.Errorf("API host required")
|
|
}
|
|
|
|
workDir := filepath.Join(NodesDir, node.Name)
|
|
installerPath := filepath.Join(workDir, "wallarm-native-node-aio.sh")
|
|
|
|
// 1. Create directories
|
|
for _, d := range []string{
|
|
filepath.Join(workDir, "etc"),
|
|
filepath.Join(workDir, "var", "log"),
|
|
filepath.Join(workDir, "var", "run"),
|
|
} {
|
|
if err := os.MkdirAll(d, 0755); err != nil {
|
|
return fmt.Errorf("mkdir %s: %w", d, err)
|
|
}
|
|
}
|
|
|
|
// 2. Write go-node.yaml config
|
|
configPath := filepath.Join(workDir, "etc", "go-node.yaml")
|
|
config := fmt.Sprintf(`mode: connector-server
|
|
connector:
|
|
address: "%s"
|
|
`, node.Address)
|
|
if err := os.WriteFile(configPath, []byte(config), 0644); err != nil {
|
|
return fmt.Errorf("write config: %w", err)
|
|
}
|
|
|
|
// 3. Download installer if missing
|
|
url := installerURL()
|
|
if _, err := os.Stat(installerPath); os.IsNotExist(err) {
|
|
fmt.Printf("[%s] Downloading installer from meganode.wallarm.com...\n", node.Name)
|
|
cmd := exec.Command("curl", "-fsSL", "-o", installerPath, url)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("download installer: %w\n%s", err, string(out))
|
|
}
|
|
if err := os.Chmod(installerPath, 0755); err != nil {
|
|
return fmt.Errorf("chmod installer: %w", err)
|
|
}
|
|
}
|
|
|
|
// 4. Write environment file
|
|
envFile := filepath.Join(workDir, "env")
|
|
envContent := fmt.Sprintf(`WALLARM_API_TOKEN=%s
|
|
WALLARM_API_HOST=%s
|
|
WALLARM_LABELS=%s
|
|
WALLARM_CONFIG_PATH=%s
|
|
`, apiToken, apiHost, labels, configPath)
|
|
if err := os.WriteFile(envFile, []byte(envContent), 0600); err != nil {
|
|
return fmt.Errorf("write env: %w", err)
|
|
}
|
|
|
|
// 5. Run the meganode installer (piped token + region to avoid interactive prompts)
|
|
fmt.Printf("[%s] Running Wallarm installer...\n", node.Name)
|
|
installCmd := exec.Command(installerPath)
|
|
installCmd.Dir = workDir
|
|
installCmd.Env = append(os.Environ(),
|
|
"WALLARM_API_TOKEN="+apiToken,
|
|
"WALLARM_API_HOST="+apiHost,
|
|
"WALLARM_LABELS="+labels,
|
|
)
|
|
// Pipe token and region as stdin to skip interactive prompts
|
|
installCmd.Stdin = strings.NewReader(apiToken + "\n" + apiHost + "\n")
|
|
|
|
logFile, err := os.Create(filepath.Join(workDir, "install.log"))
|
|
if err == nil {
|
|
installCmd.Stdout = logFile
|
|
installCmd.Stderr = logFile
|
|
defer logFile.Close()
|
|
}
|
|
|
|
if err := installCmd.Run(); err != nil {
|
|
return fmt.Errorf("installation failed — check %s/install.log: %w", workDir, err)
|
|
}
|
|
|
|
// 6. Enable and start systemd service
|
|
serviceName := "wallarm-node@" + node.Name
|
|
for _, action := range []string{"enable", "start"} {
|
|
cmd := exec.Command("systemctl", action, serviceName)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("systemctl %s %s: %w\n%s", action, serviceName, err, string(out))
|
|
}
|
|
}
|
|
|
|
fmt.Printf("[%s] Installation successful. Service: %s\n", node.Name, serviceName)
|
|
return nil
|
|
}
|
|
|
|
// RemoveNode stops the systemd service and deletes the node directory.
|
|
func RemoveNode(nodeName string) error {
|
|
serviceName := "wallarm-node@" + nodeName
|
|
workDir := filepath.Join(NodesDir, nodeName)
|
|
|
|
// Stop and disable
|
|
for _, action := range []string{"stop", "disable"} {
|
|
exec.Command("systemctl", action, serviceName).Run() // ignore errors
|
|
}
|
|
|
|
// Remove directory
|
|
if err := os.RemoveAll(workDir); err != nil {
|
|
return fmt.Errorf("remove %s: %w", workDir, err)
|
|
}
|
|
|
|
fmt.Printf("Node %s removed.\n", nodeName)
|
|
return nil
|
|
}
|
|
|
|
// Status returns the systemd status for a node, or all nodes if nodeName is empty.
|
|
func Status(nodeName string) (string, error) {
|
|
if nodeName != "" {
|
|
cmd := exec.Command("systemctl", "status", "wallarm-node@"+nodeName, "--no-pager")
|
|
out, err := cmd.CombinedOutput()
|
|
return string(out), err
|
|
}
|
|
|
|
entries, err := os.ReadDir(NodesDir)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var sb strings.Builder
|
|
sb.WriteString("Wallarm Nodes:\n")
|
|
for _, e := range entries {
|
|
if e.IsDir() {
|
|
name := e.Name()
|
|
sb.WriteString(fmt.Sprintf("--- %s ---\n", name))
|
|
cmd := exec.Command("systemctl", "status", "wallarm-node@"+name, "--no-pager")
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
sb.WriteString(string(out))
|
|
} else {
|
|
lines := strings.Split(string(out), "\n")
|
|
for i, l := range lines {
|
|
if i >= 5 {
|
|
break
|
|
}
|
|
sb.WriteString(l + "\n")
|
|
}
|
|
}
|
|
sb.WriteString("\n")
|
|
}
|
|
}
|
|
return sb.String(), nil
|
|
}
|
|
|
|
// GenerateSystemdTemplate creates the wallarm-node@.service template unit if it doesn't exist.
|
|
func GenerateSystemdTemplate() error {
|
|
if _, err := os.Stat(SystemdTemplate); err == nil {
|
|
return nil // already exists
|
|
}
|
|
|
|
content := fmt.Sprintf(`[Unit]
|
|
Description=Wallarm Native Node - %%I
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
WorkingDirectory=%s/%%i
|
|
EnvironmentFile=%s/%%i/env
|
|
ExecStart=%s/%%i/wallarm-native-node-aio.sh start
|
|
ExecStop=%s/%%i/wallarm-native-node-aio.sh stop
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
User=root
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
`, NodesDir, NodesDir, NodesDir, NodesDir)
|
|
|
|
if err := os.WriteFile(SystemdTemplate, []byte(content), 0644); err != nil {
|
|
return fmt.Errorf("write systemd template: %w", err)
|
|
}
|
|
|
|
cmd := exec.Command("systemctl", "daemon-reload")
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("daemon-reload: %w\n%s", err, string(out))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CreateNodesDir ensures the /opt/wallarm/nodes directory exists.
|
|
func CreateNodesDir() error {
|
|
return os.MkdirAll(NodesDir, 0755)
|
|
}
|