mirror of
https://github.com/juanfont/headscale.git
synced 2024-12-02 03:33:05 +00:00
Compare commits
12 commits
47eb7f644c
...
73e038fa5c
Author | SHA1 | Date | |
---|---|---|---|
|
73e038fa5c | ||
|
dfebd176df | ||
|
bb79471139 | ||
|
28a40c0019 | ||
|
b0f074d18d | ||
|
689c0983dc | ||
|
4f294c243c | ||
|
b3beb73f3f | ||
|
b6adc84dc6 | ||
|
c726224e53 | ||
|
a5f27b2265 | ||
|
13454a3f2d |
8 changed files with 93 additions and 99 deletions
2
.github/workflows/test-integration.yaml
vendored
2
.github/workflows/test-integration.yaml
vendored
|
@ -37,8 +37,8 @@ jobs:
|
||||||
- TestNodeRenameCommand
|
- TestNodeRenameCommand
|
||||||
- TestNodeMoveCommand
|
- TestNodeMoveCommand
|
||||||
- TestPolicyCommand
|
- TestPolicyCommand
|
||||||
- TestDERPVerifyEndpoint
|
|
||||||
- TestPolicyBrokenConfigCommand
|
- TestPolicyBrokenConfigCommand
|
||||||
|
- TestDERPVerifyEndpoint
|
||||||
- TestResolveMagicDNS
|
- TestResolveMagicDNS
|
||||||
- TestValidateResolvConf
|
- TestValidateResolvConf
|
||||||
- TestDERPServerScenario
|
- TestDERPServerScenario
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# For testing purposes only
|
# For testing purposes only
|
||||||
|
|
||||||
FROM golang:1.22-alpine AS build-env
|
FROM golang:alpine AS build-env
|
||||||
|
|
||||||
WORKDIR /go/src
|
WORKDIR /go/src
|
||||||
|
|
||||||
|
|
|
@ -64,54 +64,41 @@ func (h *Headscale) VerifyHandler(
|
||||||
) {
|
) {
|
||||||
if req.Method != http.MethodPost {
|
if req.Method != http.MethodPost {
|
||||||
http.Error(writer, "Wrong method", http.StatusMethodNotAllowed)
|
http.Error(writer, "Wrong method", http.StatusMethodNotAllowed)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("handler", "/verify").
|
Str("handler", "/verify").
|
||||||
Msg("verify client")
|
Msg("verify client")
|
||||||
|
|
||||||
|
doVerify := func() (bool, error) {
|
||||||
body, err := io.ReadAll(req.Body)
|
body, err := io.ReadAll(req.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
return false, fmt.Errorf("cannot read request body: %w", err)
|
||||||
Str("handler", "/verify").
|
|
||||||
Err(err).
|
|
||||||
Msg("Cannot read request body")
|
|
||||||
http.Error(writer, "Internal error", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var derpAdmitClientRequest tailcfg.DERPAdmitClientRequest
|
var derpAdmitClientRequest tailcfg.DERPAdmitClientRequest
|
||||||
if err := json.Unmarshal(body, &derpAdmitClientRequest); err != nil {
|
if err := json.Unmarshal(body, &derpAdmitClientRequest); err != nil {
|
||||||
log.Error().
|
return false, fmt.Errorf("cannot parse derpAdmitClientRequest: %w", err)
|
||||||
Caller().
|
|
||||||
Err(err).
|
|
||||||
Msg("Cannot parse derpAdmitClientRequest")
|
|
||||||
http.Error(writer, "Internal error", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nodes, err := h.db.ListNodes()
|
nodes, err := h.db.ListNodes()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("cannot list nodes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes.ContainsNodeKey(derpAdmitClientRequest.NodePublic), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
allow, err := doVerify()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Caller().
|
Caller().
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot list nodes")
|
Msg("Failed to verify client")
|
||||||
http.Error(writer, "Internal error", http.StatusInternalServerError)
|
http.Error(writer, "Internal error", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, node := range nodes {
|
|
||||||
log.Debug().Str("node", node.NodeKey.String()).Msg("Node")
|
|
||||||
}
|
|
||||||
|
|
||||||
allow := false
|
|
||||||
// Check if the node is in the list of nodes
|
|
||||||
for _, node := range nodes {
|
|
||||||
if node.NodeKey == derpAdmitClientRequest.NodePublic {
|
|
||||||
allow = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := tailcfg.DERPAdmitClientResponse{
|
resp := tailcfg.DERPAdmitClientResponse{
|
||||||
Allow: allow,
|
Allow: allow,
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,6 +223,16 @@ func (nodes Nodes) FilterByIP(ip netip.Addr) Nodes {
|
||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (nodes Nodes) ContainsNodeKey(nodeKey key.NodePublic) bool {
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node.NodeKey == nodeKey {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (node *Node) Proto() *v1.Node {
|
func (node *Node) Proto() *v1.Node {
|
||||||
nodeProto := &v1.Node{
|
nodeProto := &v1.Node{
|
||||||
Id: uint64(node.ID),
|
Id: uint64(node.ID),
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/juanfont/headscale/integration/hsic"
|
"github.com/juanfont/headscale/integration/hsic"
|
||||||
"github.com/juanfont/headscale/integration/integrationutil"
|
"github.com/juanfont/headscale/integration/integrationutil"
|
||||||
"github.com/juanfont/headscale/integration/tsic"
|
"github.com/juanfont/headscale/integration/tsic"
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDERPVerifyEndpoint(t *testing.T) {
|
func TestDERPVerifyEndpoint(t *testing.T) {
|
||||||
|
@ -44,50 +45,34 @@ func TestDERPVerifyEndpoint(t *testing.T) {
|
||||||
)
|
)
|
||||||
assertNoErr(t, err)
|
assertNoErr(t, err)
|
||||||
|
|
||||||
derpConfig := "regions:\n"
|
derpMap := tailcfg.DERPMap{
|
||||||
derpConfig += " 900:\n"
|
Regions: map[int]*tailcfg.DERPRegion{
|
||||||
derpConfig += " regionid: 900\n"
|
900: {
|
||||||
derpConfig += " regioncode: test-derpverify\n"
|
RegionID: 900,
|
||||||
derpConfig += " regionname: TestDerpVerify\n"
|
RegionCode: "test-derpverify",
|
||||||
derpConfig += " nodes:\n"
|
RegionName: "TestDerpVerify",
|
||||||
derpConfig += " - name: TestDerpVerify\n"
|
Nodes: []*tailcfg.DERPNode{
|
||||||
derpConfig += " regionid: 900\n"
|
{
|
||||||
derpConfig += " hostname: " + derper.GetHostname() + "\n"
|
Name: "TestDerpVerify",
|
||||||
derpConfig += " stunport: " + derper.GetSTUNPort() + "\n"
|
RegionID: 900,
|
||||||
derpConfig += " stunonly: false\n"
|
HostName: derper.GetHostname(),
|
||||||
derpConfig += " derpport: " + derper.GetDERPPort() + "\n"
|
STUNPort: derper.GetSTUNPort(),
|
||||||
|
STUNOnly: false,
|
||||||
|
DERPPort: derper.GetDERPPort(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
headscale, err := scenario.Headscale(
|
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{tsic.WithCACert(derper.GetCert())},
|
||||||
hsic.WithHostname(hostname),
|
hsic.WithHostname(hostname),
|
||||||
hsic.WithPort(headscalePort),
|
hsic.WithPort(headscalePort),
|
||||||
hsic.WithCustomTLS(certHeadscale, keyHeadscale),
|
hsic.WithCustomTLS(certHeadscale, keyHeadscale),
|
||||||
hsic.WithHostnameAsServerURL(),
|
hsic.WithHostnameAsServerURL(),
|
||||||
hsic.WithCustomDERPServerOnly([]byte(derpConfig)),
|
hsic.WithDERPConfig(derpMap))
|
||||||
)
|
|
||||||
assertNoErrHeadscaleEnv(t, err)
|
assertNoErrHeadscaleEnv(t, err)
|
||||||
|
|
||||||
for userName, clientCount := range spec {
|
|
||||||
err = scenario.CreateUser(userName)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create user %s: %s", userName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = scenario.CreateTailscaleNodesInUser(userName, "all", clientCount, tsic.WithCACert(derper.GetCert()))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create tailscale nodes in user %s: %s", userName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := scenario.CreatePreAuthKey(userName, true, true)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create pre-auth key for user %s: %s", userName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to run tailscale up for user %s: %s", userName, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allClients, err := scenario.ListTailscaleClients()
|
allClients, err := scenario.ListTailscaleClients()
|
||||||
assertNoErrListClients(t, err)
|
assertNoErrListClients(t, err)
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ func WithOrCreateNetwork(network *dockertest.Network) Option {
|
||||||
|
|
||||||
network, err := dockertestutil.GetFirstOrCreateNetwork(
|
network, err := dockertestutil.GetFirstOrCreateNetwork(
|
||||||
tsic.pool,
|
tsic.pool,
|
||||||
fmt.Sprintf("%s-network", tsic.hostname),
|
tsic.hostname+"-network",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to create network: %s", err)
|
log.Fatalf("failed to create network: %s", err)
|
||||||
|
@ -135,14 +135,15 @@ func New(
|
||||||
opt(dsic)
|
opt(dsic)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdArgs := "--hostname=" + hostname
|
var cmdArgs strings.Builder
|
||||||
cmdArgs += " --certmode=manual"
|
fmt.Fprintf(&cmdArgs, "--hostname=%s", hostname)
|
||||||
cmdArgs += " --certdir=" + DERPerCertRoot
|
fmt.Fprintf(&cmdArgs, " --certmode=manual")
|
||||||
cmdArgs += " --a=:" + strconv.Itoa(dsic.derpPort)
|
fmt.Fprintf(&cmdArgs, " --certdir=%s", DERPerCertRoot)
|
||||||
cmdArgs += " --stun=true"
|
fmt.Fprintf(&cmdArgs, " --a=:%d", dsic.derpPort)
|
||||||
cmdArgs += " --stun-port=" + strconv.Itoa(dsic.stunPort)
|
fmt.Fprintf(&cmdArgs, " --stun=true")
|
||||||
|
fmt.Fprintf(&cmdArgs, " --stun-port=%d", dsic.stunPort)
|
||||||
if dsic.withVerifyClientURL != "" {
|
if dsic.withVerifyClientURL != "" {
|
||||||
cmdArgs += " --verify-client-url=" + dsic.withVerifyClientURL
|
fmt.Fprintf(&cmdArgs, " --verify-client-url=%s", dsic.withVerifyClientURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
runOptions := &dockertest.RunOptions{
|
runOptions := &dockertest.RunOptions{
|
||||||
|
@ -150,7 +151,7 @@ func New(
|
||||||
Networks: []*dockertest.Network{dsic.network},
|
Networks: []*dockertest.Network{dsic.network},
|
||||||
ExtraHosts: dsic.withExtraHosts,
|
ExtraHosts: dsic.withExtraHosts,
|
||||||
// we currently need to give us some time to inject the certificate further down.
|
// we currently need to give us some time to inject the certificate further down.
|
||||||
Entrypoint: []string{"/bin/sh", "-c", "/bin/sleep 3 ; update-ca-certificates ; derper " + cmdArgs},
|
Entrypoint: []string{"/bin/sh", "-c", "/bin/sleep 3 ; update-ca-certificates ; derper " + cmdArgs.String()},
|
||||||
ExposedPorts: []string{
|
ExposedPorts: []string{
|
||||||
"80/tcp",
|
"80/tcp",
|
||||||
fmt.Sprintf("%d/tcp", dsic.derpPort),
|
fmt.Sprintf("%d/tcp", dsic.derpPort),
|
||||||
|
@ -225,6 +226,7 @@ func New(
|
||||||
return nil, fmt.Errorf("failed to write TLS key to container: %w", err)
|
return nil, fmt.Errorf("failed to write TLS key to container: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return dsic, nil
|
return dsic, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,6 +240,7 @@ func (t *DERPServerInContainer) Shutdown() error {
|
||||||
fmt.Errorf("failed to save log: %w", err),
|
fmt.Errorf("failed to save log: %w", err),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.pool.Purge(t.container)
|
return t.pool.Purge(t.container)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,18 +270,18 @@ func (t *DERPServerInContainer) GetHostname() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSTUNPort returns the STUN port of the DERPer instance.
|
// GetSTUNPort returns the STUN port of the DERPer instance.
|
||||||
func (t *DERPServerInContainer) GetSTUNPort() string {
|
func (t *DERPServerInContainer) GetSTUNPort() int {
|
||||||
return strconv.Itoa(t.stunPort)
|
return t.stunPort
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDERPPort returns the DERP port of the DERPer instance.
|
// GetDERPPort returns the DERP port of the DERPer instance.
|
||||||
func (t *DERPServerInContainer) GetDERPPort() string {
|
func (t *DERPServerInContainer) GetDERPPort() int {
|
||||||
return strconv.Itoa(t.derpPort)
|
return t.derpPort
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitForRunning blocks until the DERPer instance is ready to be used.
|
// WaitForRunning blocks until the DERPer instance is ready to be used.
|
||||||
func (t *DERPServerInContainer) WaitForRunning() error {
|
func (t *DERPServerInContainer) WaitForRunning() error {
|
||||||
url := "https://" + net.JoinHostPort(t.GetHostname(), t.GetDERPPort()) + "/"
|
url := "https://" + net.JoinHostPort(t.GetHostname(), strconv.Itoa(t.GetDERPPort())) + "/"
|
||||||
log.Printf("waiting for DERPer to be ready at %s", url)
|
log.Printf("waiting for DERPer to be ready at %s", url)
|
||||||
|
|
||||||
insecureTransport := http.DefaultTransport.(*http.Transport).Clone() //nolint
|
insecureTransport := http.DefaultTransport.(*http.Transport).Clone() //nolint
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/juanfont/headscale/integration/integrationutil"
|
"github.com/juanfont/headscale/integration/integrationutil"
|
||||||
"github.com/ory/dockertest/v3"
|
"github.com/ory/dockertest/v3"
|
||||||
"github.com/ory/dockertest/v3/docker"
|
"github.com/ory/dockertest/v3/docker"
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -216,10 +217,17 @@ func WithEmbeddedDERPServerOnly() Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithCustomDERPServerOnly configures Headscale use a custom
|
// WithDERPConfig configures Headscale use a custom
|
||||||
// DERP server only.
|
// DERP server only.
|
||||||
func WithCustomDERPServerOnly(contents []byte) Option {
|
func WithDERPConfig(derpMap tailcfg.DERPMap) Option {
|
||||||
return func(hsic *HeadscaleInContainer) {
|
return func(hsic *HeadscaleInContainer) {
|
||||||
|
contents, err := json.Marshal(derpMap)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to marshal DERP map: %s", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
hsic.env["HEADSCALE_DERP_PATHS"] = "/etc/headscale/derp.yml"
|
hsic.env["HEADSCALE_DERP_PATHS"] = "/etc/headscale/derp.yml"
|
||||||
hsic.filesInContainer = append(hsic.filesInContainer,
|
hsic.filesInContainer = append(hsic.filesInContainer,
|
||||||
fileInContainer{
|
fileInContainer{
|
||||||
|
|
|
@ -675,7 +675,7 @@ func (t *TailscaleInContainer) watchIPN(ctx context.Context) (*ipn.Notify, error
|
||||||
|
|
||||||
func (t *TailscaleInContainer) DebugDERPRegion(region string) (*ipnstate.DebugDERPRegionReport, error) {
|
func (t *TailscaleInContainer) DebugDERPRegion(region string) (*ipnstate.DebugDERPRegionReport, error) {
|
||||||
if !util.TailscaleVersionNewerOrEqual("1.34", t.version) {
|
if !util.TailscaleVersionNewerOrEqual("1.34", t.version) {
|
||||||
panic(fmt.Sprintf("tsic.DebugDERPRegion() called with unsupported version: %s", t.version))
|
panic("tsic.DebugDERPRegion() called with unsupported version: " + t.version)
|
||||||
}
|
}
|
||||||
|
|
||||||
command := []string{
|
command := []string{
|
||||||
|
@ -687,17 +687,18 @@ func (t *TailscaleInContainer) DebugDERPRegion(region string) (*ipnstate.DebugDE
|
||||||
|
|
||||||
result, stderr, err := t.Execute(command)
|
result, stderr, err := t.Execute(command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("stderr: %s\n", stderr)
|
fmt.Printf("stderr: %s\n", stderr) // nolint
|
||||||
|
|
||||||
return nil, fmt.Errorf("failed to execute tailscale debug derp command: %w", err)
|
return nil, fmt.Errorf("failed to execute tailscale debug derp command: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var st ipnstate.DebugDERPRegionReport
|
var report ipnstate.DebugDERPRegionReport
|
||||||
err = json.Unmarshal([]byte(result), &st)
|
err = json.Unmarshal([]byte(result), &report)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal tailscale derp region report: %w", err)
|
return nil, fmt.Errorf("failed to unmarshal tailscale derp region report: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &st, err
|
return &report, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Netcheck returns the current Netcheck Report (netcheck.Report) of the Tailscale instance.
|
// Netcheck returns the current Netcheck Report (netcheck.Report) of the Tailscale instance.
|
||||||
|
|
Loading…
Reference in a new issue