mirror of
https://github.com/juanfont/headscale.git
synced 2024-12-02 03:33:05 +00:00
Compare commits
4 commits
73e038fa5c
...
47eb7f644c
Author | SHA1 | Date | |
---|---|---|---|
|
47eb7f644c | ||
|
ff091dd56b | ||
|
63a9d16e79 | ||
|
ae0acf1084 |
8 changed files with 93 additions and 87 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
|
||||||
- TestPolicyBrokenConfigCommand
|
|
||||||
- TestDERPVerifyEndpoint
|
- TestDERPVerifyEndpoint
|
||||||
|
- TestPolicyBrokenConfigCommand
|
||||||
- TestResolveMagicDNS
|
- TestResolveMagicDNS
|
||||||
- TestValidateResolvConf
|
- TestValidateResolvConf
|
||||||
- TestDERPServerScenario
|
- TestDERPServerScenario
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# For testing purposes only
|
# For testing purposes only
|
||||||
|
|
||||||
FROM golang:alpine AS build-env
|
FROM golang:1.22-alpine AS build-env
|
||||||
|
|
||||||
WORKDIR /go/src
|
WORKDIR /go/src
|
||||||
|
|
||||||
|
|
|
@ -64,41 +64,54 @@ 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")
|
||||||
var derpAdmitClientRequest tailcfg.DERPAdmitClientRequest
|
http.Error(writer, "Internal error", http.StatusInternalServerError)
|
||||||
if err := json.Unmarshal(body, &derpAdmitClientRequest); err != nil {
|
return
|
||||||
return false, fmt.Errorf("cannot parse derpAdmitClientRequest: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
var derpAdmitClientRequest tailcfg.DERPAdmitClientRequest
|
||||||
|
if err := json.Unmarshal(body, &derpAdmitClientRequest); err != nil {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Err(err).
|
||||||
|
Msg("Cannot parse derpAdmitClientRequest")
|
||||||
|
http.Error(writer, "Internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes, err := h.db.ListNodes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Caller().
|
Caller().
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Failed to verify client")
|
Msg("Cannot list nodes")
|
||||||
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,16 +223,6 @@ 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,7 +13,6 @@ 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) {
|
||||||
|
@ -45,34 +44,50 @@ func TestDERPVerifyEndpoint(t *testing.T) {
|
||||||
)
|
)
|
||||||
assertNoErr(t, err)
|
assertNoErr(t, err)
|
||||||
|
|
||||||
derpMap := tailcfg.DERPMap{
|
derpConfig := "regions:\n"
|
||||||
Regions: map[int]*tailcfg.DERPRegion{
|
derpConfig += " 900:\n"
|
||||||
900: {
|
derpConfig += " regionid: 900\n"
|
||||||
RegionID: 900,
|
derpConfig += " regioncode: test-derpverify\n"
|
||||||
RegionCode: "test-derpverify",
|
derpConfig += " regionname: TestDerpVerify\n"
|
||||||
RegionName: "TestDerpVerify",
|
derpConfig += " nodes:\n"
|
||||||
Nodes: []*tailcfg.DERPNode{
|
derpConfig += " - name: TestDerpVerify\n"
|
||||||
{
|
derpConfig += " regionid: 900\n"
|
||||||
Name: "TestDerpVerify",
|
derpConfig += " hostname: " + derper.GetHostname() + "\n"
|
||||||
RegionID: 900,
|
derpConfig += " stunport: " + derper.GetSTUNPort() + "\n"
|
||||||
HostName: derper.GetHostname(),
|
derpConfig += " stunonly: false\n"
|
||||||
STUNPort: derper.GetSTUNPort(),
|
derpConfig += " derpport: " + derper.GetDERPPort() + "\n"
|
||||||
STUNOnly: false,
|
|
||||||
DERPPort: derper.GetDERPPort(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{tsic.WithCACert(derper.GetCert())},
|
headscale, err := scenario.Headscale(
|
||||||
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.WithDERPConfig(derpMap))
|
hsic.WithCustomDERPServerOnly([]byte(derpConfig)),
|
||||||
|
)
|
||||||
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,
|
||||||
tsic.hostname+"-network",
|
fmt.Sprintf("%s-network", tsic.hostname),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to create network: %s", err)
|
log.Fatalf("failed to create network: %s", err)
|
||||||
|
@ -135,15 +135,14 @@ func New(
|
||||||
opt(dsic)
|
opt(dsic)
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmdArgs strings.Builder
|
cmdArgs := "--hostname=" + hostname
|
||||||
fmt.Fprintf(&cmdArgs, "--hostname=%s", hostname)
|
cmdArgs += " --certmode=manual"
|
||||||
fmt.Fprintf(&cmdArgs, " --certmode=manual")
|
cmdArgs += " --certdir=" + DERPerCertRoot
|
||||||
fmt.Fprintf(&cmdArgs, " --certdir=%s", DERPerCertRoot)
|
cmdArgs += " --a=:" + strconv.Itoa(dsic.derpPort)
|
||||||
fmt.Fprintf(&cmdArgs, " --a=:%d", dsic.derpPort)
|
cmdArgs += " --stun=true"
|
||||||
fmt.Fprintf(&cmdArgs, " --stun=true")
|
cmdArgs += " --stun-port=" + strconv.Itoa(dsic.stunPort)
|
||||||
fmt.Fprintf(&cmdArgs, " --stun-port=%d", dsic.stunPort)
|
|
||||||
if dsic.withVerifyClientURL != "" {
|
if dsic.withVerifyClientURL != "" {
|
||||||
fmt.Fprintf(&cmdArgs, " --verify-client-url=%s", dsic.withVerifyClientURL)
|
cmdArgs += " --verify-client-url=" + dsic.withVerifyClientURL
|
||||||
}
|
}
|
||||||
|
|
||||||
runOptions := &dockertest.RunOptions{
|
runOptions := &dockertest.RunOptions{
|
||||||
|
@ -151,7 +150,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.String()},
|
Entrypoint: []string{"/bin/sh", "-c", "/bin/sleep 3 ; update-ca-certificates ; derper " + cmdArgs},
|
||||||
ExposedPorts: []string{
|
ExposedPorts: []string{
|
||||||
"80/tcp",
|
"80/tcp",
|
||||||
fmt.Sprintf("%d/tcp", dsic.derpPort),
|
fmt.Sprintf("%d/tcp", dsic.derpPort),
|
||||||
|
@ -226,7 +225,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,7 +238,6 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,18 +267,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() int {
|
func (t *DERPServerInContainer) GetSTUNPort() string {
|
||||||
return t.stunPort
|
return strconv.Itoa(t.stunPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDERPPort returns the DERP port of the DERPer instance.
|
// GetDERPPort returns the DERP port of the DERPer instance.
|
||||||
func (t *DERPServerInContainer) GetDERPPort() int {
|
func (t *DERPServerInContainer) GetDERPPort() string {
|
||||||
return t.derpPort
|
return strconv.Itoa(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(), strconv.Itoa(t.GetDERPPort())) + "/"
|
url := "https://" + net.JoinHostPort(t.GetHostname(), 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,7 +25,6 @@ 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 (
|
||||||
|
@ -217,17 +216,10 @@ func WithEmbeddedDERPServerOnly() Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithDERPConfig configures Headscale use a custom
|
// WithCustomDERPServerOnly configures Headscale use a custom
|
||||||
// DERP server only.
|
// DERP server only.
|
||||||
func WithDERPConfig(derpMap tailcfg.DERPMap) Option {
|
func WithCustomDERPServerOnly(contents []byte) 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("tsic.DebugDERPRegion() called with unsupported version: " + t.version)
|
panic(fmt.Sprintf("tsic.DebugDERPRegion() called with unsupported version: %s", t.version))
|
||||||
}
|
}
|
||||||
|
|
||||||
command := []string{
|
command := []string{
|
||||||
|
@ -687,18 +687,17 @@ 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) // nolint
|
fmt.Printf("stderr: %s\n", stderr)
|
||||||
|
|
||||||
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 report ipnstate.DebugDERPRegionReport
|
var st ipnstate.DebugDERPRegionReport
|
||||||
err = json.Unmarshal([]byte(result), &report)
|
err = json.Unmarshal([]byte(result), &st)
|
||||||
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 &report, err
|
return &st, 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