mirror of
https://github.com/juanfont/headscale.git
synced 2026-03-18 23:44:07 +01:00
Make embedded DERP server and TLS the default configuration for all integration tests, replacing the per-test opt-in model that led to inconsistent and flaky test behavior. Infrastructure changes: - DefaultConfigEnv() includes embedded DERP server settings - New() auto-generates a proper CA + server TLS certificate pair - CA cert is installed into container trust stores and returned by GetCert() so clients and internal tools (curl) trust the server - CreateCertificate() now returns (caCert, cert, key) instead of discarding the CA certificate - Add WithPublicDERP() and WithoutTLS() opt-out options - Remove WithTLS(), WithEmbeddedDERPServerOnly(), and WithDERPAsIP() since all their behavior is now the default or unnecessary Test cleanup: - Remove all redundant WithTLS/WithEmbeddedDERPServerOnly/WithDERPAsIP calls from test files - Give every test a unique WithTestName by parameterizing aclScenario, sshScenario, and derpServerScenario helpers - Add WithTestName to tests that were missing it - Document all non-standard options with inline comments explaining why each is needed Updates #3139
135 lines
3.8 KiB
Go
135 lines
3.8 KiB
Go
package integration
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/juanfont/headscale/hscontrol/util"
|
|
"github.com/juanfont/headscale/integration/dsic"
|
|
"github.com/juanfont/headscale/integration/hsic"
|
|
"github.com/juanfont/headscale/integration/integrationutil"
|
|
"github.com/juanfont/headscale/integration/tsic"
|
|
"github.com/stretchr/testify/require"
|
|
"tailscale.com/derp"
|
|
"tailscale.com/derp/derphttp"
|
|
"tailscale.com/net/netmon"
|
|
"tailscale.com/tailcfg"
|
|
"tailscale.com/types/key"
|
|
)
|
|
|
|
func TestDERPVerifyEndpoint(t *testing.T) {
|
|
IntegrationSkip(t)
|
|
|
|
// Generate random hostname for the headscale instance
|
|
hash, err := util.GenerateRandomStringDNSSafe(6)
|
|
require.NoError(t, err)
|
|
|
|
testName := "derpverify"
|
|
hostname := fmt.Sprintf("hs-%s-%s", testName, hash)
|
|
|
|
headscalePort := 8080
|
|
|
|
// Create cert for headscale
|
|
caHeadscale, certHeadscale, keyHeadscale, err := integrationutil.CreateCertificate(hostname)
|
|
require.NoError(t, err)
|
|
|
|
spec := ScenarioSpec{
|
|
NodesPerUser: len(MustTestVersions),
|
|
Users: []string{"user1"},
|
|
}
|
|
|
|
scenario, err := NewScenario(spec)
|
|
|
|
require.NoError(t, err)
|
|
defer scenario.ShutdownAssertNoPanics(t)
|
|
|
|
derper, err := scenario.CreateDERPServer("head",
|
|
dsic.WithCACert(caHeadscale),
|
|
dsic.WithVerifyClientURL(fmt.Sprintf("https://%s/verify", net.JoinHostPort(hostname, strconv.Itoa(headscalePort)))),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
derpRegion := tailcfg.DERPRegion{
|
|
RegionCode: "test-derpverify",
|
|
RegionName: "TestDerpVerify",
|
|
Nodes: []*tailcfg.DERPNode{
|
|
{
|
|
Name: "TestDerpVerify",
|
|
RegionID: 900,
|
|
HostName: derper.GetHostname(),
|
|
STUNPort: derper.GetSTUNPort(),
|
|
STUNOnly: false,
|
|
DERPPort: derper.GetDERPPort(),
|
|
InsecureForTests: true,
|
|
},
|
|
},
|
|
}
|
|
derpMap := tailcfg.DERPMap{
|
|
Regions: map[int]*tailcfg.DERPRegion{
|
|
900: &derpRegion,
|
|
},
|
|
}
|
|
|
|
// WithHostname is used instead of WithTestName because the hostname
|
|
// must match the pre-generated TLS certificate created above.
|
|
// The test name "derpverify" is embedded in the hostname variable.
|
|
//
|
|
// WithCACert passes the external DERP server's certificate so
|
|
// tailscale clients trust it. WithCustomTLS and WithDERPConfig
|
|
// configure headscale to use the external DERP server created
|
|
// above instead of the default embedded one.
|
|
err = scenario.CreateHeadscaleEnv([]tsic.Option{tsic.WithCACert(derper.GetCert())},
|
|
hsic.WithHostname(hostname),
|
|
hsic.WithPort(headscalePort),
|
|
hsic.WithCustomTLS(caHeadscale, certHeadscale, keyHeadscale),
|
|
hsic.WithDERPConfig(derpMap))
|
|
requireNoErrHeadscaleEnv(t, err)
|
|
|
|
allClients, err := scenario.ListTailscaleClients()
|
|
requireNoErrListClients(t, err)
|
|
|
|
fakeKey := key.NewNode()
|
|
DERPVerify(t, fakeKey, derpRegion, false)
|
|
|
|
for _, client := range allClients {
|
|
nodeKey, err := client.GetNodePrivateKey()
|
|
require.NoError(t, err)
|
|
DERPVerify(t, *nodeKey, derpRegion, true)
|
|
}
|
|
}
|
|
|
|
func DERPVerify(
|
|
t *testing.T,
|
|
nodeKey key.NodePrivate,
|
|
region tailcfg.DERPRegion,
|
|
expectSuccess bool,
|
|
) {
|
|
t.Helper()
|
|
|
|
c := derphttp.NewRegionClient(nodeKey, t.Logf, netmon.NewStatic(), func() *tailcfg.DERPRegion {
|
|
return ®ion
|
|
})
|
|
defer c.Close()
|
|
|
|
var result error
|
|
|
|
err := c.Connect(t.Context())
|
|
if err != nil {
|
|
result = fmt.Errorf("client Connect: %w", err)
|
|
}
|
|
|
|
if m, err := c.Recv(); err != nil { //nolint:noinlineerr
|
|
result = fmt.Errorf("client first Recv: %w", err)
|
|
} else if v, ok := m.(derp.ServerInfoMessage); !ok {
|
|
result = fmt.Errorf("client first Recv was unexpected type %T", v) //nolint:err113
|
|
}
|
|
|
|
if expectSuccess && result != nil {
|
|
t.Fatalf("DERP verify failed unexpectedly for client %s. Expected success but got error: %v", nodeKey.Public(), result)
|
|
} else if !expectSuccess && result == nil {
|
|
t.Fatalf("DERP verify succeeded unexpectedly for client %s. Expected failure but it succeeded.", nodeKey.Public())
|
|
}
|
|
}
|