Compare commits
2 Commits
3d2374cc98
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f4a5c98328 | |||
| 5145016c4e |
@@ -127,7 +127,7 @@ Greywall reads from `~/.config/greywall/greywall.json` by default (or `~/Library
|
||||
|
||||
Use `greywall --settings ./custom.json` to specify a different config file.
|
||||
|
||||
By default (when connected to GreyHaven), traffic routes through the GreyHaven SOCKS5 proxy at `localhost:42052` with DNS via `localhost:42053`.
|
||||
By default, traffic routes through the GreyProxy SOCKS5 proxy at `localhost:43052` with DNS via `localhost:43053`.
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"gitea.app.monadical.io/monadical/greywall/internal/config"
|
||||
"gitea.app.monadical.io/monadical/greywall/internal/platform"
|
||||
"gitea.app.monadical.io/monadical/greywall/internal/proxy"
|
||||
"gitea.app.monadical.io/monadical/greywall/internal/sandbox"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -55,8 +56,8 @@ func main() {
|
||||
Long: `greywall is a command-line tool that runs commands in a sandboxed environment
|
||||
with network and filesystem restrictions.
|
||||
|
||||
By default, traffic is routed through the GreyHaven SOCKS5 proxy at localhost:42051
|
||||
with DNS via localhost:42053. Use --proxy and --dns to override, or configure in
|
||||
By default, traffic is routed through the GreyProxy SOCKS5 proxy at localhost:43052
|
||||
with DNS via localhost:43053. Use --proxy and --dns to override, or configure in
|
||||
your settings file at ~/.config/greywall/greywall.json (or ~/Library/Application Support/greywall/greywall.json on macOS).
|
||||
|
||||
On Linux, greywall uses tun2socks for truly transparent proxying: all TCP/UDP traffic
|
||||
@@ -98,8 +99,8 @@ Configuration file format:
|
||||
rootCmd.Flags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging")
|
||||
rootCmd.Flags().BoolVarP(&monitor, "monitor", "m", false, "Monitor and log sandbox violations")
|
||||
rootCmd.Flags().StringVarP(&settingsPath, "settings", "s", "", "Path to settings file (default: OS config directory)")
|
||||
rootCmd.Flags().StringVar(&proxyURL, "proxy", "", "External SOCKS5 proxy URL (default: socks5://localhost:42052)")
|
||||
rootCmd.Flags().StringVar(&dnsAddr, "dns", "", "DNS server address on host (default: localhost:42053)")
|
||||
rootCmd.Flags().StringVar(&proxyURL, "proxy", "", "External SOCKS5 proxy URL (default: socks5://localhost:43052)")
|
||||
rootCmd.Flags().StringVar(&dnsAddr, "dns", "", "DNS server address on host (default: localhost:43053)")
|
||||
rootCmd.Flags().StringVarP(&cmdString, "c", "c", "", "Run command string directly (like sh -c)")
|
||||
rootCmd.Flags().StringArrayVarP(&exposePorts, "port", "p", nil, "Expose port for inbound connections (can be used multiple times)")
|
||||
rootCmd.Flags().BoolVarP(&showVersion, "version", "v", false, "Show version information")
|
||||
@@ -111,6 +112,8 @@ Configuration file format:
|
||||
|
||||
rootCmd.AddCommand(newCompletionCmd(rootCmd))
|
||||
rootCmd.AddCommand(newTemplatesCmd())
|
||||
rootCmd.AddCommand(newCheckCmd())
|
||||
rootCmd.AddCommand(newSetupCmd())
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
@@ -121,11 +124,7 @@ Configuration file format:
|
||||
|
||||
func runCommand(cmd *cobra.Command, args []string) error {
|
||||
if showVersion {
|
||||
fmt.Printf("greywall - lightweight, container-free sandbox for running untrusted commands\n")
|
||||
fmt.Printf(" Version: %s\n", version)
|
||||
fmt.Printf(" Built: %s\n", buildTime)
|
||||
fmt.Printf(" Commit: %s\n", gitCommit)
|
||||
sandbox.PrintDependencyStatus()
|
||||
fmt.Printf("greywall %s\n", version)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -231,18 +230,18 @@ func runCommand(cmd *cobra.Command, args []string) error {
|
||||
cfg.Network.DnsAddr = dnsAddr
|
||||
}
|
||||
|
||||
// GreyHaven defaults: when no proxy or DNS is configured (neither via CLI
|
||||
// nor config file), use the standard GreyHaven infrastructure ports.
|
||||
// GreyProxy defaults: when no proxy or DNS is configured (neither via CLI
|
||||
// nor config file), use the standard GreyProxy ports.
|
||||
if cfg.Network.ProxyURL == "" {
|
||||
cfg.Network.ProxyURL = "socks5://localhost:42052"
|
||||
cfg.Network.ProxyURL = "socks5://localhost:43052"
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "[greywall] Defaulting proxy to socks5://localhost:42052\n")
|
||||
fmt.Fprintf(os.Stderr, "[greywall] Defaulting proxy to socks5://localhost:43052\n")
|
||||
}
|
||||
}
|
||||
if cfg.Network.DnsAddr == "" {
|
||||
cfg.Network.DnsAddr = "localhost:42053"
|
||||
cfg.Network.DnsAddr = "localhost:43053"
|
||||
if debug {
|
||||
fmt.Fprintf(os.Stderr, "[greywall] Defaulting DNS to localhost:42053\n")
|
||||
fmt.Fprintf(os.Stderr, "[greywall] Defaulting DNS to localhost:43053\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,6 +411,89 @@ func extractCommandName(args []string, cmdStr string) string {
|
||||
return filepath.Base(name)
|
||||
}
|
||||
|
||||
// newCheckCmd creates the check subcommand for diagnostics.
|
||||
func newCheckCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "check",
|
||||
Short: "Check greywall status, dependencies, and greyproxy connectivity",
|
||||
Long: `Run diagnostics to check greywall readiness.
|
||||
|
||||
Shows version information, platform dependencies, security features,
|
||||
and greyproxy installation/running status.`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: runCheck,
|
||||
}
|
||||
}
|
||||
|
||||
func runCheck(_ *cobra.Command, _ []string) error {
|
||||
fmt.Printf("greywall - lightweight, container-free sandbox for running untrusted commands\n")
|
||||
fmt.Printf(" Version: %s\n", version)
|
||||
fmt.Printf(" Built: %s\n", buildTime)
|
||||
fmt.Printf(" Commit: %s\n", gitCommit)
|
||||
|
||||
sandbox.PrintDependencyStatus()
|
||||
|
||||
fmt.Printf("\n Greyproxy:\n")
|
||||
status := proxy.Detect()
|
||||
if status.Installed {
|
||||
if status.Version != "" {
|
||||
fmt.Printf(" ✓ installed (v%s) at %s\n", status.Version, status.Path)
|
||||
} else {
|
||||
fmt.Printf(" ✓ installed at %s\n", status.Path)
|
||||
}
|
||||
if status.Running {
|
||||
fmt.Printf(" ✓ running (SOCKS5 :43052, DNS :43053, Dashboard :43080)\n")
|
||||
} else {
|
||||
fmt.Printf(" ✗ not running\n")
|
||||
fmt.Printf(" Start with: greywall setup\n")
|
||||
}
|
||||
} else {
|
||||
fmt.Printf(" ✗ not installed\n")
|
||||
fmt.Printf(" Install with: greywall setup\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newSetupCmd creates the setup subcommand for installing greyproxy.
|
||||
func newSetupCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "setup",
|
||||
Short: "Install and start greyproxy (network proxy for sandboxed commands)",
|
||||
Long: `Downloads and installs greyproxy from GitHub releases.
|
||||
|
||||
greyproxy provides SOCKS5 proxying and DNS resolution for sandboxed commands.
|
||||
The installer will:
|
||||
1. Download the latest greyproxy release for your platform
|
||||
2. Install the binary to ~/.local/bin/greyproxy
|
||||
3. Register and start a systemd user service`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: runSetup,
|
||||
}
|
||||
}
|
||||
|
||||
func runSetup(_ *cobra.Command, _ []string) error {
|
||||
status := proxy.Detect()
|
||||
|
||||
if status.Installed && status.Running {
|
||||
fmt.Printf("greyproxy is already installed (v%s) and running.\n", status.Version)
|
||||
fmt.Printf("Run 'greywall check' for full status.\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
if status.Installed && !status.Running {
|
||||
if err := proxy.Start(os.Stderr); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("greyproxy started.\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
return proxy.Install(proxy.InstallOptions{
|
||||
Output: os.Stderr,
|
||||
})
|
||||
}
|
||||
|
||||
// newCompletionCmd creates the completion subcommand for shell completions.
|
||||
func newCompletionCmd(rootCmd *cobra.Command) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
|
||||
126
internal/proxy/detect.go
Normal file
126
internal/proxy/detect.go
Normal file
@@ -0,0 +1,126 @@
|
||||
// Package proxy provides greyproxy detection, installation, and lifecycle management.
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
healthURL = "http://localhost:43080/api/health"
|
||||
healthTimeout = 2 * time.Second
|
||||
cmdTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
// GreyproxyStatus holds the detected state of greyproxy.
|
||||
type GreyproxyStatus struct {
|
||||
Installed bool // found via exec.LookPath
|
||||
Path string // full path from LookPath
|
||||
Version string // parsed version (e.g. "0.1.1")
|
||||
Running bool // health endpoint responded with valid greyproxy response
|
||||
RunningErr error // error from the running check (for diagnostics)
|
||||
}
|
||||
|
||||
// healthResponse is the expected JSON from GET /api/health.
|
||||
type healthResponse struct {
|
||||
Service string `json:"service"`
|
||||
Version string `json:"version"`
|
||||
Status string `json:"status"`
|
||||
Ports map[string]int `json:"ports"`
|
||||
}
|
||||
|
||||
var versionRegex = regexp.MustCompile(`^greyproxy\s+(\S+)`)
|
||||
|
||||
// Detect checks greyproxy installation status, version, and whether it's running.
|
||||
// This function never returns an error; all detection failures are captured
|
||||
// in the GreyproxyStatus fields so the caller can present them diagnostically.
|
||||
func Detect() *GreyproxyStatus {
|
||||
s := &GreyproxyStatus{}
|
||||
|
||||
// 1. Check if installed
|
||||
s.Path, s.Installed = checkInstalled()
|
||||
|
||||
// 2. Check if running (via health endpoint)
|
||||
running, ver, err := checkRunning()
|
||||
s.Running = running
|
||||
s.RunningErr = err
|
||||
if running && ver != "" {
|
||||
s.Version = ver
|
||||
}
|
||||
|
||||
// 3. Version fallback: if installed but version not yet known, parse from CLI
|
||||
if s.Installed && s.Version == "" {
|
||||
s.Version, _ = checkVersion(s.Path)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// checkInstalled uses exec.LookPath to find greyproxy on PATH.
|
||||
func checkInstalled() (path string, found bool) {
|
||||
p, err := exec.LookPath("greyproxy")
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
return p, true
|
||||
}
|
||||
|
||||
// checkVersion runs "greyproxy -V" and parses the output.
|
||||
// Expected format: "greyproxy 0.1.1 (go1.x linux/amd64)"
|
||||
func checkVersion(binaryPath string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), cmdTimeout)
|
||||
defer cancel()
|
||||
|
||||
out, err := exec.CommandContext(ctx, binaryPath, "-V").Output() //nolint:gosec // binaryPath comes from exec.LookPath
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to run greyproxy -V: %w", err)
|
||||
}
|
||||
|
||||
matches := versionRegex.FindStringSubmatch(strings.TrimSpace(string(out)))
|
||||
if len(matches) < 2 {
|
||||
return "", fmt.Errorf("unexpected version output: %s", strings.TrimSpace(string(out)))
|
||||
}
|
||||
return matches[1], nil
|
||||
}
|
||||
|
||||
// checkRunning hits GET http://localhost:43080/api/health and verifies
|
||||
// the response is from greyproxy (not some other service on that port).
|
||||
// Returns running status, version string from health response, and any error.
|
||||
func checkRunning() (bool, string, error) {
|
||||
client := &http.Client{Timeout: healthTimeout}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), healthTimeout)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, healthURL, nil)
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req) //nolint:gosec // healthURL is a hardcoded localhost constant
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("health check failed: %w", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return false, "", fmt.Errorf("health check returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var health healthResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&health); err != nil {
|
||||
return false, "", fmt.Errorf("failed to parse health response: %w", err)
|
||||
}
|
||||
|
||||
if health.Service != "greyproxy" {
|
||||
return false, "", fmt.Errorf("unexpected service: %q (expected greyproxy)", health.Service)
|
||||
}
|
||||
|
||||
return true, health.Version, nil
|
||||
}
|
||||
258
internal/proxy/install.go
Normal file
258
internal/proxy/install.go
Normal file
@@ -0,0 +1,258 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
githubOwner = "greyhavenhq"
|
||||
githubRepo = "greyproxy"
|
||||
apiTimeout = 15 * time.Second
|
||||
)
|
||||
|
||||
// release represents a GitHub release.
|
||||
type release struct {
|
||||
TagName string `json:"tag_name"`
|
||||
Assets []asset `json:"assets"`
|
||||
}
|
||||
|
||||
// asset represents a release asset.
|
||||
type asset struct {
|
||||
Name string `json:"name"`
|
||||
BrowserDownloadURL string `json:"browser_download_url"`
|
||||
}
|
||||
|
||||
// InstallOptions controls the greyproxy installation behavior.
|
||||
type InstallOptions struct {
|
||||
Output io.Writer // progress output (typically os.Stderr)
|
||||
}
|
||||
|
||||
// Install downloads the latest greyproxy release and runs "greyproxy install".
|
||||
func Install(opts InstallOptions) error {
|
||||
if opts.Output == nil {
|
||||
opts.Output = os.Stderr
|
||||
}
|
||||
|
||||
// 1. Fetch latest release
|
||||
_, _ = fmt.Fprintf(opts.Output, "Fetching latest greyproxy release...\n")
|
||||
rel, err := fetchLatestRelease()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch latest release: %w", err)
|
||||
}
|
||||
ver := strings.TrimPrefix(rel.TagName, "v")
|
||||
_, _ = fmt.Fprintf(opts.Output, "Latest version: %s\n", ver)
|
||||
|
||||
// 2. Find the correct asset for this platform
|
||||
assetURL, assetName, err := resolveAssetURL(rel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintf(opts.Output, "Downloading %s...\n", assetName)
|
||||
|
||||
// 3. Download to temp file
|
||||
archivePath, err := downloadAsset(assetURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("download failed: %w", err)
|
||||
}
|
||||
defer func() { _ = os.Remove(archivePath) }()
|
||||
|
||||
// 4. Extract
|
||||
_, _ = fmt.Fprintf(opts.Output, "Extracting...\n")
|
||||
extractDir, err := extractTarGz(archivePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("extraction failed: %w", err)
|
||||
}
|
||||
defer func() { _ = os.RemoveAll(extractDir) }()
|
||||
|
||||
// 5. Find the greyproxy binary in extracted content
|
||||
binaryPath := filepath.Join(extractDir, "greyproxy")
|
||||
if _, err := os.Stat(binaryPath); err != nil {
|
||||
return fmt.Errorf("greyproxy binary not found in archive")
|
||||
}
|
||||
|
||||
// 6. Shell out to "greyproxy install"
|
||||
_, _ = fmt.Fprintf(opts.Output, "\n")
|
||||
if err := runGreyproxyInstall(binaryPath); err != nil {
|
||||
return fmt.Errorf("greyproxy install failed: %w", err)
|
||||
}
|
||||
|
||||
// 7. Verify
|
||||
_, _ = fmt.Fprintf(opts.Output, "\nVerifying installation...\n")
|
||||
status := Detect()
|
||||
if status.Installed {
|
||||
_, _ = fmt.Fprintf(opts.Output, "greyproxy %s installed at %s\n", status.Version, status.Path)
|
||||
if status.Running {
|
||||
_, _ = fmt.Fprintf(opts.Output, "greyproxy is running.\n")
|
||||
}
|
||||
} else {
|
||||
_, _ = fmt.Fprintf(opts.Output, "Warning: greyproxy not found on PATH after install.\n")
|
||||
_, _ = fmt.Fprintf(opts.Output, "Ensure ~/.local/bin is in your PATH.\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetchLatestRelease queries the GitHub API for the latest greyproxy release.
|
||||
func fetchLatestRelease() (*release, error) {
|
||||
apiURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", githubOwner, githubRepo)
|
||||
|
||||
client := &http.Client{Timeout: apiTimeout}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), apiTimeout)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Accept", "application/vnd.github+json")
|
||||
req.Header.Set("User-Agent", "greywall-setup")
|
||||
|
||||
resp, err := client.Do(req) //nolint:gosec // apiURL is built from hardcoded constants
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GitHub API request failed: %w", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("GitHub API returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var rel release
|
||||
if err := json.NewDecoder(resp.Body).Decode(&rel); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse release response: %w", err)
|
||||
}
|
||||
return &rel, nil
|
||||
}
|
||||
|
||||
// resolveAssetURL finds the correct asset download URL for the current OS/arch.
|
||||
func resolveAssetURL(rel *release) (downloadURL, name string, err error) {
|
||||
ver := strings.TrimPrefix(rel.TagName, "v")
|
||||
osName := runtime.GOOS
|
||||
archName := runtime.GOARCH
|
||||
|
||||
expected := fmt.Sprintf("greyproxy_%s_%s_%s.tar.gz", ver, osName, archName)
|
||||
|
||||
for _, a := range rel.Assets {
|
||||
if a.Name == expected {
|
||||
return a.BrowserDownloadURL, a.Name, nil
|
||||
}
|
||||
}
|
||||
return "", "", fmt.Errorf("no release asset found for %s/%s (expected: %s)", osName, archName, expected)
|
||||
}
|
||||
|
||||
// downloadAsset downloads a URL to a temp file, returning its path.
|
||||
func downloadAsset(downloadURL string) (string, error) {
|
||||
client := &http.Client{Timeout: 5 * time.Minute}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req) //nolint:gosec // downloadURL comes from GitHub API response
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("download returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "greyproxy-*.tar.gz")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(tmpFile, resp.Body); err != nil {
|
||||
_ = tmpFile.Close()
|
||||
_ = os.Remove(tmpFile.Name()) //nolint:gosec // tmpFile.Name() is from os.CreateTemp, not user input
|
||||
return "", err
|
||||
}
|
||||
_ = tmpFile.Close()
|
||||
|
||||
return tmpFile.Name(), nil
|
||||
}
|
||||
|
||||
// extractTarGz extracts a .tar.gz archive to a temp directory, returning the dir path.
|
||||
func extractTarGz(archivePath string) (string, error) {
|
||||
f, err := os.Open(archivePath) //nolint:gosec // archivePath is a temp file we created
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
gz, err := gzip.NewReader(f)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create gzip reader: %w", err)
|
||||
}
|
||||
defer func() { _ = gz.Close() }()
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "greyproxy-extract-*")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tr := tar.NewReader(gz)
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(tmpDir)
|
||||
return "", fmt.Errorf("tar read error: %w", err)
|
||||
}
|
||||
|
||||
// Sanitize: only extract regular files with safe names
|
||||
name := filepath.Base(header.Name)
|
||||
if name == "." || name == ".." || strings.Contains(header.Name, "..") {
|
||||
continue
|
||||
}
|
||||
|
||||
target := filepath.Join(tmpDir, name) //nolint:gosec // name is sanitized via filepath.Base and path traversal check above
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeReg:
|
||||
out, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.FileMode(header.Mode)) //nolint:gosec // mode from tar header of trusted archive
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(tmpDir)
|
||||
return "", err
|
||||
}
|
||||
if _, err := io.Copy(out, io.LimitReader(tr, 256<<20)); err != nil { // 256 MB limit per file
|
||||
_ = out.Close()
|
||||
_ = os.RemoveAll(tmpDir)
|
||||
return "", err
|
||||
}
|
||||
_ = out.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return tmpDir, nil
|
||||
}
|
||||
|
||||
// runGreyproxyInstall shells out to the extracted greyproxy binary with "install" arg.
|
||||
// Stdin/stdout/stderr are passed through so the interactive [y/N] prompt works.
|
||||
func runGreyproxyInstall(binaryPath string) error {
|
||||
cmd := exec.Command(binaryPath, "install") //nolint:gosec // binaryPath is from our extracted archive
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
30
internal/proxy/start.go
Normal file
30
internal/proxy/start.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Start runs "greyproxy service start" to start the greyproxy service.
|
||||
func Start(output io.Writer) error {
|
||||
if output == nil {
|
||||
output = os.Stderr
|
||||
}
|
||||
|
||||
path, found := checkInstalled()
|
||||
if !found {
|
||||
return fmt.Errorf("greyproxy not found on PATH")
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(output, "Starting greyproxy service...\n")
|
||||
cmd := exec.Command(path, "service", "start") //nolint:gosec // path comes from exec.LookPath
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("failed to start greyproxy service: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user