diff options
Diffstat (limited to 'tests/e2e/vulnerability_test.go')
| -rw-r--r-- | tests/e2e/vulnerability_test.go | 109 |
1 files changed, 109 insertions, 0 deletions
diff --git a/tests/e2e/vulnerability_test.go b/tests/e2e/vulnerability_test.go new file mode 100644 index 0000000..a8c2dfb --- /dev/null +++ b/tests/e2e/vulnerability_test.go @@ -0,0 +1,109 @@ +package e2e + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "git.theodohertyfamily.com/tools/wg-wrap/internal/cli" +) + +// TestEditorArgumentSplitting verifies that the editor is correctly split into command and arguments. +func TestEditorArgumentSplitting(t *testing.T) { + // Create a dummy profile file so the check passes + tmpDir := t.TempDir() + profileName := "editor-test" + profilePath := tmpDir + "/" + profileName + ".conf" + err := os.WriteFile(profilePath, []byte("[Interface]\nPrivateKey = 00000000000000000000000000000000"), 0644) + if err != nil { + t.Fatal(err) + } + + app := cli.NewApp([]string{"wg-wrap", "profile", "configure", profileName}) + app.ConfigDir = tmpDir + + // Set EDITOR to something that includes an argument. + // We use a non-existent command that clearly contains a space. + if err := os.Setenv("EDITOR", "nonexistent-editor -v"); err != nil { + t.Fatalf("failed to set EDITOR: %v", err) + } + defer func() { + _ = os.Unsetenv("EDITOR") + }() + + err = app.Route() + + if err == nil { + t.Fatal("Expected error when calling nonexistent editor, but got nil") + } + + // If it fails because it can't find "nonexistent-editor -v" (the whole string), it's the bug. + // If it fails because it can't find "nonexistent-editor" (split), it's correct. + if strings.Contains(err.Error(), "nonexistent-editor -v") { + t.Errorf("BUG: Editor was not split into arguments. Error contains the full string: %v", err) + } else { + t.Logf("Success: Editor was likely split correctly. Error: %v", err) + } +} + +// TestDNSLeak proves that the child process leaks to the host DNS if the +// isolation fails or if /etc/resolv.conf is not correctly bound. +func TestDNSLeak(t *testing.T) { + if testing.Short() { + t.Skip("Skipping DNS leak test in short mode") + } + + binaryPath, err := GetBinaryPath() + if err != nil { + t.Skipf("Skipping test: %v", err) + } + + // 1. Setup a profile with a fake, unreachable DNS server. + tmpConfigDir := t.TempDir() + tmpRuntimeDir := t.TempDir() + profileName := "dns-leak-test" + + profilesDir := filepath.Join(tmpConfigDir, "wg-wrap", "profiles") + if err := os.MkdirAll(profilesDir, 0755); err != nil { + t.Fatal(err) + } + profilePath := filepath.Join(profilesDir, profileName+".conf") + + // Use a fake DNS server (10.0.0.53) and a valid-looking config. + conf := `[Interface] +Address = 10.0.0.2/24 +PrivateKey = 0000000000000000000000000000000000000000000000000000000000000000 +DNS = 10.0.0.53 + +[Peer] +PublicKey = 0000000000000000000000000000000000000000000000000000000000000000 +AllowedIPs = 0.0.0.0/0 +Endpoint = 1.1.1.1:51820 +` + err = os.WriteFile(profilePath, []byte(conf), 0644) + if err != nil { + t.Fatal(err) + } + + // 2. Run a command that performs a DNS lookup using exec.Command on the wg-wrap binary. + // We use 'timeout 3 getent hosts google.com' to ensure it fails quickly instead of waiting on timeouts. + cmd := exec.Command(binaryPath, "--profile", profileName, "--", "timeout", "3", "getent", "hosts", "google.com") + cmd.Env = append(os.Environ(), + fmt.Sprintf("XDG_CONFIG_HOME=%s", tmpConfigDir), + fmt.Sprintf("XDG_RUNTIME_DIR=%s", tmpRuntimeDir), + ) + + // If the DNS isolation works, it should fail to resolve because 10.0.0.53 is fake. + // If it leaks, it will resolve using the host's DNS. + out, err := cmd.CombinedOutput() + + // If err is nil, it means getent returned 0, which means it RESOLVED the host -> LEAK! + if err == nil { + t.Errorf("BUG: DNS Leak detected! The command resolved the hostname using the host's DNS instead of failing via the fake VPN DNS. Output: %s", string(out)) + } else { + t.Logf("Success: Command failed as expected (DNS isolated). Error: %v, Output: %s", err, string(out)) + } +} |
