115 lines
4.2 KiB
Go
115 lines
4.2 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"sync/atomic"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/oracle/oci-go-sdk/v65/core"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestWireGuardIgnoresWebHealth(t *testing.T) {
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
if r.Method != http.MethodGet || r.URL.Path != "/metrics" {
|
||
|
|
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||
|
|
}
|
||
|
|
_, _ = w.Write([]byte("oci_l4_relay_component_healthy{component=\"web_allow\"} 0\n"))
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
if err := waitForWireGuard(context.Background(), server.URL+"/metrics", time.Second); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWireGuardRejectsUnavailableTunnel(t *testing.T) {
|
||
|
|
for _, status := range []int{http.StatusBadGateway, http.StatusServiceUnavailable} {
|
||
|
|
t.Run(http.StatusText(status), func(t *testing.T) {
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
|
|
w.WriteHeader(status)
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
err := waitForWireGuard(context.Background(), server.URL+"/metrics", 20*time.Millisecond)
|
||
|
|
if !errors.Is(err, context.DeadlineExceeded) {
|
||
|
|
t.Fatalf("expected recovery deadline, got %v", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWireGuardDoesNotFollowRedirects(t *testing.T) {
|
||
|
|
var calls atomic.Int32
|
||
|
|
destination := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
|
|
calls.Add(1)
|
||
|
|
w.WriteHeader(http.StatusOK)
|
||
|
|
}))
|
||
|
|
defer destination.Close()
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
http.Redirect(w, r, destination.URL, http.StatusFound)
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
err := waitForWireGuard(context.Background(), server.URL+"/metrics", 20*time.Millisecond)
|
||
|
|
if !errors.Is(err, context.DeadlineExceeded) || calls.Load() != 0 {
|
||
|
|
t.Fatalf("error = %v, redirected calls = %d", err, calls.Load())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWireGuardCancelsBlockedRequest(t *testing.T) {
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
|
||
|
|
<-r.Context().Done()
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
|
||
|
|
defer cancel()
|
||
|
|
if err := waitForWireGuard(ctx, server.URL+"/metrics", time.Minute); !errors.Is(err, context.DeadlineExceeded) {
|
||
|
|
t.Fatalf("expected cancellation, got %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestPollStopsUpdatingAfterWireGuardRecovery(t *testing.T) {
|
||
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||
|
|
defer cancel()
|
||
|
|
observations := 0
|
||
|
|
opnsense := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
|
|
observations++
|
||
|
|
if observations == 5 {
|
||
|
|
cancel()
|
||
|
|
}
|
||
|
|
_, _ = w.Write([]byte(`{"rows":[{"identifier":"wan","description":"WAN","ipv4":[{"ipaddr":"203.0.113.7"}]}]}`))
|
||
|
|
}))
|
||
|
|
defer opnsense.Close()
|
||
|
|
var probes atomic.Int32
|
||
|
|
tunnel := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
|
|
probes.Add(1)
|
||
|
|
_, _ = w.Write([]byte("oci_l4_relay_component_healthy{component=\"web_allow\"} 0\n"))
|
||
|
|
}))
|
||
|
|
defer tunnel.Close()
|
||
|
|
nsg := &recordingNSGClient{rules: []core.SecurityRule{returnedRule("rule", "203.0.113.7/32", 51820)}}
|
||
|
|
reconciler := &ociReconciler{
|
||
|
|
client: nsg, networkSecurityGroup: "nsg", securityRule: "rule", wireguardDestinationPort: 51820,
|
||
|
|
wireguardURL: tunnel.URL + "/metrics", recoveryTimeout: time.Second, probeWireGuard: waitForWireGuard,
|
||
|
|
}
|
||
|
|
client := &opnsenseClient{baseURL: opnsense.URL, http: opnsense.Client()}
|
||
|
|
_ = poll(ctx, client, "WAN", time.Millisecond, 2, reconciler)
|
||
|
|
if observations != 5 || nsg.calls != 1 || probes.Load() != 1 {
|
||
|
|
t.Fatalf("observations = %d, NSG updates = %d, tunnel probes = %d", observations, nsg.calls, probes.Load())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMetricsURL(t *testing.T) {
|
||
|
|
for _, value := range []string{"", "https://relay.test/metrics", "http://relay.test/", "http://relay.test/metrics?query=1", "http://key:[email protected]/metrics"} {
|
||
|
|
t.Run(value, func(t *testing.T) {
|
||
|
|
t.Setenv("WG_STATUS_URL", value)
|
||
|
|
if _, err := requiredMetricsURL("WG_STATUS_URL"); err == nil {
|
||
|
|
t.Fatal("expected invalid endpoint rejection")
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
t.Setenv("WG_STATUS_URL", "http://relay.test:19100/metrics")
|
||
|
|
if _, err := requiredMetricsURL("WG_STATUS_URL"); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
}
|