diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 374eaa13..899c3163 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -166,8 +166,10 @@ jobs: - name: Install WiX Toolset run: dotnet tool install -g wix - - name: Add WiX UI extension - run: wix extension add WixToolset.UI.wixext + - name: Add WiX extensions + run: | + wix extension add WixToolset.UI.wixext/6.0.2 + wix extension add WixToolset.Util.wixext/6.0.2 - name: Build MSI installer run: | diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go index 84b6c039..736ff25b 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -17,6 +17,12 @@ import ( "github.com/AikidoSec/safechain-internals/internal/version" ) +const ( + DaemonStatusLogInterval = 1 * time.Hour + ProxyStartMaxRetries = 20 + ProxyStartRetryInterval = 3 * time.Minute +) + type Config struct { ConfigPath string LogLevel string @@ -33,6 +39,11 @@ type Daemon struct { ingress *ingress.Server logRotator *utils.LogRotator logReaper *utils.LogReaper + + proxyRetryCount int + proxyLastRetryTime time.Time + + daemonLastStatusLogTime time.Time } func New(ctx context.Context, cancel context.CancelFunc, config *Config) (*Daemon, error) { @@ -213,19 +224,72 @@ func (d *Daemon) Uninstall(ctx context.Context, removeScanners bool) error { return nil } -func (d *Daemon) heartbeat() error { - if !d.proxy.IsProxyRunning() { - log.Println("Proxy is not running, starting it...") - if err := d.startProxyAndInstallCA(d.ctx); err != nil { - return fmt.Errorf("failed to start proxy and install CA: %v", err) +func (d *Daemon) printDaemonStatus() { + log.Println("Daemon status:") + if d.proxy.IsProxyRunning() { + proxyVersion, _ := d.proxy.Version() + log.Printf("\t- Proxy: %s", proxyVersion) + } else { + log.Println("\t- Proxy: not running!") + } + + for _, scannerName := range d.registry.List() { + scanner, err := d.registry.Get(scannerName) + if err != nil { + continue } - if d.proxy.IsProxyRunning() { - log.Println("Proxy started successfully") - } else { - log.Println("Failed to start proxy, will try again later") + if scanner.IsInstalled(d.ctx) { + log.Printf("\t- %s: %s", scannerName, scanner.Version(d.ctx)) } + } +} + +func (d *Daemon) handleProxy() (shouldRetry bool, err error) { + if d.proxy.IsProxyRunning() { + return true, nil + } + + if d.proxyRetryCount >= ProxyStartMaxRetries { + // Exit daemon loop if proxy start retry limit is reached + return false, fmt.Errorf("proxy start retry limit reached (%d attempts), not retrying", d.proxyRetryCount) + } + + if !d.proxyLastRetryTime.IsZero() && time.Since(d.proxyLastRetryTime) < ProxyStartRetryInterval { + log.Printf("Proxy is not running, waiting for retry interval (%s) before next attempt", ProxyStartRetryInterval) + return true, nil + } + + d.proxyRetryCount++ + d.proxyLastRetryTime = time.Now() + log.Printf("Proxy is not running, starting it... (attempt %d/%d)", d.proxyRetryCount, ProxyStartMaxRetries) + + if err := d.startProxyAndInstallCA(d.ctx); err != nil { + log.Printf("Failed to start proxy and install CA: %v", err) + return true, nil + } + + if d.proxy.IsProxyRunning() { + log.Println("Proxy started successfully") + d.proxyRetryCount = 0 + d.proxyLastRetryTime = time.Time{} } else { - log.Println("Proxy is running") + log.Printf("Failed to start proxy, will try again later") + } + return true, nil +} + +func (d *Daemon) heartbeat() error { + shouldRetry, err := d.handleProxy() + if !shouldRetry { + return fmt.Errorf("failed to handle proxy: %v", err) + } + if err != nil { + log.Printf("Failed to start proxy: %v", err) + } + + if time.Since(d.daemonLastStatusLogTime) >= DaemonStatusLogInterval { + d.printDaemonStatus() + d.daemonLastStatusLogTime = time.Now() } return nil } diff --git a/internal/platform/platform_windows_helpers.go b/internal/platform/platform_windows_helpers.go index f4c29cbd..20ae64b4 100644 --- a/internal/platform/platform_windows_helpers.go +++ b/internal/platform/platform_windows_helpers.go @@ -162,8 +162,6 @@ func runAsLoggedInUser(binaryPath string, args []string) (string, error) { return "", err } - log.Printf("Found active user session: %d", sessionID) - var userToken windows.Token if err := windows.WTSQueryUserToken(sessionID, &userToken); err != nil { return "", fmt.Errorf("WTSQueryUserToken failed: %v", err) @@ -192,7 +190,6 @@ func runAsLoggedInUser(binaryPath string, args []string) (string, error) { return "", fmt.Errorf("runProcessAsUser failed: %v", err) } - log.Printf("Process %s completed successfully as logged-in user", binaryPath) return output, nil } diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index d6f77ea6..922106b5 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "time" "github.com/AikidoSec/safechain-internals/internal/platform" @@ -55,6 +56,23 @@ func (p *Proxy) WaitForProxyToBeReady() error { } } +func (p *Proxy) Version() (string, error) { + cmd := exec.Command(filepath.Join(platform.GetConfig().BinaryDir, platform.SafeChainProxyBinaryName), "--version") + output, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("failed to get proxy version: %v", err) + } + trimmed := strings.TrimSpace(string(output)) + if trimmed == "" { + return "", fmt.Errorf("proxy version output is empty") + } + parts := strings.Split(trimmed, " ") + if len(parts) == 0 { + return "", fmt.Errorf("failed to parse proxy version from output: %q", trimmed) + } + return parts[len(parts)-1], nil +} + func (p *Proxy) Start(ctx context.Context, proxyIngressAddr string) error { config := platform.GetConfig() p.ctx, p.cancel = context.WithCancel(ctx) diff --git a/internal/scanner/githook/githook.go b/internal/scanner/githook/githook.go index 7057d65f..b3699365 100644 --- a/internal/scanner/githook/githook.go +++ b/internal/scanner/githook/githook.go @@ -16,6 +16,10 @@ func (s *GitHookScanner) Name() string { return "githook" } +func (s *GitHookScanner) Version(ctx context.Context) string { + return "" +} + func (s *GitHookScanner) Install(ctx context.Context) error { select { case <-ctx.Done(): diff --git a/internal/scanner/safechain/safechain.go b/internal/scanner/safechain/safechain.go index 7be90462..5f746ea1 100644 --- a/internal/scanner/safechain/safechain.go +++ b/internal/scanner/safechain/safechain.go @@ -22,11 +22,11 @@ func New() scanner.Scanner { } func (s *SafechainScanner) Name() string { - return "safechain" + return "Safechain" } func (s *SafechainScanner) Version(ctx context.Context) string { - output, err := platform.RunAsCurrentUser(ctx, platform.GetConfig().SafeChainBinaryPath, []string{"-v"}) + output, err := platform.RunAsCurrentUser(context.WithValue(ctx, "disable_logging", true), platform.GetConfig().SafeChainBinaryPath, []string{"-v"}) if err != nil { return "" } diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index b5ef21fb..c3f2d72c 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -6,6 +6,7 @@ import ( type Scanner interface { Name() string + Version(ctx context.Context) string Install(ctx context.Context) error Uninstall(ctx context.Context) error IsInstalled(ctx context.Context) bool diff --git a/internal/scanner/vscode/vscode.go b/internal/scanner/vscode/vscode.go index 5f306e13..6b5744de 100644 --- a/internal/scanner/vscode/vscode.go +++ b/internal/scanner/vscode/vscode.go @@ -16,6 +16,10 @@ func (s *VSCodeScanner) Name() string { return "vscode" } +func (s *VSCodeScanner) Version(ctx context.Context) string { + return "" +} + func (s *VSCodeScanner) Install(ctx context.Context) error { select { case <-ctx.Done(): diff --git a/internal/utils/utils.go b/internal/utils/utils.go index af5953b0..df64b925 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -88,7 +88,6 @@ func DetectArch() string { } } - func DownloadBinary(ctx context.Context, url, destPath string) error { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { @@ -208,11 +207,17 @@ func RunCommands(ctx context.Context, commands []Command) ([]string, error) { } func RunCommandWithEnv(ctx context.Context, env []string, command string, args ...string) (string, error) { - log.Printf("Running command: %s %s", command, strings.Join(args, " ")) + disableLogging, ok := ctx.Value("disable_logging").(bool) + if !ok { + disableLogging = false + } + if !disableLogging { + log.Printf("Running command: %s %s", command, strings.Join(args, " ")) + } cmd := exec.CommandContext(ctx, command, args...) cmd.Env = append(os.Environ(), env...) output, err := cmd.CombinedOutput() - if err != nil { + if err != nil && !disableLogging { log.Printf("\t- Command error: %v", err) log.Printf("\t- Command output: %s", string(output)) } diff --git a/packaging/macos/com.aikidosecurity.safechainultimate.plist b/packaging/macos/com.aikidosecurity.safechainultimate.plist index 546109ac..9d9bded0 100644 --- a/packaging/macos/com.aikidosecurity.safechainultimate.plist +++ b/packaging/macos/com.aikidosecurity.safechainultimate.plist @@ -4,6 +4,8 @@ KeepAlive + ThrottleInterval + 60 Label com.aikidosecurity.safechainultimate LimitLoadToSessionType diff --git a/packaging/windows/SafeChainUltimate.wxs b/packaging/windows/SafeChainUltimate.wxs index 2043889b..37dedfcf 100644 --- a/packaging/windows/SafeChainUltimate.wxs +++ b/packaging/windows/SafeChainUltimate.wxs @@ -1,6 +1,7 @@ + xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui" + xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util"> +