272 lines
7.6 KiB
Go
272 lines
7.6 KiB
Go
package native
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.sechpoint.app/customer-engineering/wallarm/internal/state"
|
|
)
|
|
|
|
const (
|
|
BaseDir = "/opt/fw"
|
|
DeployTo = "/opt/wallarm"
|
|
Symlink = "/opt/wallarm"
|
|
)
|
|
|
|
func installerURL() string {
|
|
if u := os.Getenv("WALLARM_INSTALLER_URL"); u != "" {
|
|
return u
|
|
}
|
|
return "https://storage.googleapis.com/meganode_storage/6.12/wallarm-6.12.5.x86_64-glibc.sh"
|
|
}
|
|
|
|
func InstallNode(node state.Node, apiToken, apiHost, labels string) error {
|
|
if apiToken == "" {
|
|
return fmt.Errorf("API token required")
|
|
}
|
|
_ = labels
|
|
|
|
instanceDir := filepath.Join(BaseDir, node.Name, "wallarm")
|
|
installerPath := filepath.Join(BaseDir, "wallarm-aio.sh")
|
|
|
|
// 1. Prepare clean /opt/wallarm for this deployment
|
|
os.RemoveAll(DeployTo)
|
|
os.MkdirAll(DeployTo, 0755)
|
|
|
|
// 2. Kill any existing process on the target port, then start per-instance NGINX
|
|
port := "80"
|
|
if idx := strings.LastIndex(node.Address, ":"); idx != -1 {
|
|
port = node.Address[idx+1:]
|
|
}
|
|
killPort(port)
|
|
copyNginx(DeployTo)
|
|
startNginx(DeployTo, node, port)
|
|
|
|
// 3. Download AIO once
|
|
if _, err := os.Stat(installerPath); os.IsNotExist(err) {
|
|
fmt.Printf("[%s] Downloading installer...\n", node.Name)
|
|
cmd := exec.Command("curl", "-fsSL", "-o", installerPath, installerURL())
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("download: %w\n%s", err, string(out))
|
|
}
|
|
os.Chmod(installerPath, 0755)
|
|
}
|
|
|
|
// 4. Extract AIO to /opt/wallarm
|
|
fmt.Printf("[%s] Extracting...\n", node.Name)
|
|
cmd := exec.Command("bash", installerPath, "--noexec", "--keep", "--target", DeployTo, "--noprogress", "--accept")
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("extract: %w\n%s", err, string(out))
|
|
}
|
|
|
|
// 5. Fix: recreate env.list in background while setup.sh runs
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
default:
|
|
os.WriteFile(filepath.Join(DeployTo, "env.list"),
|
|
[]byte("# wallarm env\n"), 0644)
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
}
|
|
}()
|
|
|
|
// 6. Load Wallarm NGINX module + register node
|
|
fmt.Printf("[%s] Configuring NGINX module...\n", node.Name)
|
|
exec.Command("bash", filepath.Join(DeployTo, "pick-module.sh")).Run()
|
|
|
|
// 6. Register node — skip if this node was previously deployed
|
|
existingState, _ := state.Load()
|
|
alreadyRegistered := false
|
|
if existingState != nil {
|
|
for _, n := range existingState.Nodes {
|
|
if n.Name == node.Name {
|
|
alreadyRegistered = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if alreadyRegistered {
|
|
fmt.Printf("[%s] Already registered, skipping.\n", node.Name)
|
|
} else {
|
|
fmt.Printf("[%s] Registering node (this may take 30-60s)...\n", node.Name)
|
|
registerCmd := exec.Command(filepath.Join(DeployTo, "register-node"),
|
|
"job:register",
|
|
"-token", apiToken,
|
|
"-host", apiHost,
|
|
)
|
|
registerCmd.Dir = DeployTo
|
|
devNull, _ := os.Open(os.DevNull)
|
|
if devNull != nil {
|
|
registerCmd.Stdin = devNull
|
|
defer devNull.Close()
|
|
}
|
|
// Set environment from env.list
|
|
envData, _ := os.ReadFile(filepath.Join(DeployTo, "env.list"))
|
|
for _, line := range strings.Split(string(envData), "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
registerCmd.Env = append(registerCmd.Env, line)
|
|
}
|
|
registerCmd.Env = append(registerCmd.Env, os.Environ()...)
|
|
regLog, _ := os.Create(filepath.Join(DeployTo, "register.log"))
|
|
if regLog != nil {
|
|
registerCmd.Stdout = regLog
|
|
registerCmd.Stderr = regLog
|
|
}
|
|
regErr := registerCmd.Run()
|
|
if regLog != nil {
|
|
regLog.Close()
|
|
}
|
|
if regErr != nil {
|
|
data, _ := os.ReadFile(filepath.Join(DeployTo, "register.log"))
|
|
return fmt.Errorf("register: %w\n%s", regErr, string(data))
|
|
}
|
|
}
|
|
|
|
// 8. Move /opt/wallarm → /opt/fw/{name}/wallarm
|
|
os.MkdirAll(filepath.Join(BaseDir, node.Name), 0755)
|
|
os.RemoveAll(instanceDir)
|
|
os.Rename(DeployTo, instanceDir)
|
|
|
|
// 9. Create symlink so ELF binaries find their interpreter
|
|
os.Remove(Symlink)
|
|
os.Symlink(instanceDir, Symlink)
|
|
|
|
// 10. Patch paths + systemd
|
|
patchPaths(instanceDir)
|
|
GenerateSystemdTemplate()
|
|
svc := "wallarm-node@" + node.Name
|
|
exec.Command("systemctl", "enable", svc).Run()
|
|
exec.Command("systemctl", "start", svc).Run()
|
|
fmt.Printf("[%s] Done. Installed to %s (systemctl status %s)\n", node.Name, instanceDir, svc)
|
|
return nil
|
|
}
|
|
|
|
func copyNginx(dir string) {
|
|
dst := filepath.Join(dir, "nginx", "sbin", "nginx")
|
|
if _, err := os.Stat(dst); err == nil {
|
|
return
|
|
}
|
|
os.MkdirAll(filepath.Dir(dst), 0755)
|
|
if path, err := exec.LookPath("nginx"); err == nil {
|
|
exec.Command("cp", path, dst).Run()
|
|
return
|
|
}
|
|
for _, pm := range [][]string{
|
|
{"apt-get", "install", "-y", "-qq", "nginx"},
|
|
{"yum", "install", "-y", "-q", "nginx"},
|
|
} {
|
|
if _, err := exec.LookPath(pm[0]); err == nil {
|
|
exec.Command(pm[0], pm[1:]...).Run()
|
|
if path, err := exec.LookPath("nginx"); err == nil {
|
|
exec.Command("cp", path, dst).Run()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func cloudFromHost(host string) string {
|
|
if strings.Contains(host, "us1") {
|
|
return "US"
|
|
}
|
|
return "EU"
|
|
}
|
|
|
|
func CreateNodesDir() error { return os.MkdirAll(BaseDir, 0755) }
|
|
|
|
func GenerateSystemdTemplate() error {
|
|
tmpl := `/etc/systemd/system/wallarm-node@.service`
|
|
content := fmt.Sprintf(`[Unit]
|
|
Description=Wallarm Node - %%i
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
WorkingDirectory=%s/%%i/wallarm
|
|
EnvironmentFile=-%s/%%i/wallarm/env.list
|
|
ExecStart=%s/%%i/wallarm/usr/bin/python3.10 %s/%%i/wallarm/usr/bin/supervisord -c %s/%%i/wallarm/etc/supervisord.conf
|
|
ExecStop=%s/%%i/wallarm/usr/bin/python3.10 %s/%%i/wallarm/usr/bin/supervisord -c %s/%%i/wallarm/etc/supervisord.conf shutdown
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
User=root
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
`, BaseDir, BaseDir, BaseDir, BaseDir, BaseDir, BaseDir, BaseDir, BaseDir)
|
|
os.WriteFile(tmpl, []byte(content), 0644)
|
|
exec.Command("systemctl", "daemon-reload").Run()
|
|
return nil
|
|
}
|
|
|
|
func RemoveNode(name string) error {
|
|
exec.Command("systemctl", "stop", "wallarm-node@"+name).Run()
|
|
exec.Command("systemctl", "disable", "wallarm-node@"+name).Run()
|
|
os.RemoveAll(filepath.Join(BaseDir, name))
|
|
return nil
|
|
}
|
|
|
|
func Status(name string) (string, error) {
|
|
out, _ := exec.Command("systemctl", "status", "wallarm-node@"+name, "--no-pager").CombinedOutput()
|
|
return string(out), nil
|
|
}
|
|
|
|
func startNginx(dir string, node state.Node, port string) {
|
|
nginxDir := filepath.Join(dir, "nginx")
|
|
confDir := filepath.Join(nginxDir, "conf")
|
|
os.MkdirAll(confDir, 0755)
|
|
confPath := filepath.Join(confDir, "nginx.conf")
|
|
os.WriteFile(confPath, []byte(fmt.Sprintf(`
|
|
worker_processes auto;
|
|
pid %s/nginx.pid;
|
|
error_log %s/error.log;
|
|
events { worker_connections 10240; }
|
|
http {
|
|
access_log %s/access.log;
|
|
server {
|
|
listen %s;
|
|
server_name _;
|
|
location /health { return 200; }
|
|
}
|
|
}
|
|
`, nginxDir, nginxDir, nginxDir, port)), 0644)
|
|
|
|
bin := filepath.Join(nginxDir, "sbin", "nginx")
|
|
cmd := exec.Command(bin, "-c", confPath, "-p", nginxDir)
|
|
cmd.Dir = nginxDir
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
fmt.Printf("[%s] NGINX start: %v\n%s\n", node.Name, err, string(out))
|
|
} else {
|
|
fmt.Printf("[%s] NGINX started on port %s\n", node.Name, port)
|
|
}
|
|
}
|
|
|
|
func killPort(port string) {
|
|
// Find and kill any process listening on the target port
|
|
out, _ := exec.Command("fuser", "-k", port+"/tcp").CombinedOutput()
|
|
if len(out) > 0 {
|
|
fmt.Printf(" Killed existing process on port %s\n", port)
|
|
}
|
|
}
|
|
|
|
func patchPaths(dir string) {
|
|
old := "/opt/wallarm"
|
|
// Patch all text config files (not binaries)
|
|
for _, pattern := range []string{
|
|
"*.sh", "*.list", "*.conf", "*.yaml", "*.yml", "*.json",
|
|
} {
|
|
exec.Command("find", dir, "-name", pattern, "-exec",
|
|
"sed", "-i", fmt.Sprintf("s|%s|%s|g", old, dir), "{}", ";").Run()
|
|
}
|
|
}
|