Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement TS2021 protocol in headscale #738

Merged
merged 77 commits into from
Aug 21, 2022
Merged
Changes from 1 commit
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
6e8e2bf
Generate and read the Noise private key
juanfont Aug 13, 2022
3e8f0e9
Added support for Noise clients in /key handler
juanfont Aug 13, 2022
014e7ab
Make private key errors constants
juanfont Aug 13, 2022
b261d19
Added Noise upgrade handler and Noise mux
juanfont Aug 13, 2022
be24bac
Add noise mux and Noise path to base router
juanfont Aug 13, 2022
fdd0c50
Added helper method to fetch machines by any nodekey + tests
juanfont Aug 13, 2022
1880035
Add registration handler over Noise protocol
juanfont Aug 13, 2022
c7cea9e
updated paths
juanfont Aug 14, 2022
7a91c82
Merge branch 'main' into hs2021-v2
juanfont Aug 14, 2022
39b85b0
Move getMapResponse into reusable function by TS2019 and TS2021
juanfont Aug 14, 2022
9994fce
Fixed some linting errors
juanfont Aug 14, 2022
281ae59
Update integration tests to work with Noise protocol
juanfont Aug 14, 2022
ade1b73
Output an error when a user runs headscale without noise_private_key_…
juanfont Aug 14, 2022
3bea208
Some linting fixes
juanfont Aug 14, 2022
eb8d8f1
And more linting stuff
juanfont Aug 14, 2022
20d2615
Check json encoder errors
juanfont Aug 14, 2022
b67cff5
Merge branch 'main' into hs2021-v2
juanfont Aug 14, 2022
ff46f3f
Move reusable method to common api file
juanfont Aug 14, 2022
cab828c
Fixed unit tests to load config
juanfont Aug 14, 2022
78a179c
Minor update in docs
juanfont Aug 14, 2022
0d0042b
Added zstd constant for linting
juanfont Aug 14, 2022
c10142f
Added noise poll handler
juanfont Aug 14, 2022
1f3032a
Merge branch 'main' into hs2021-v2
juanfont Aug 14, 2022
b301405
Merge branch 'hs2021-v2' of https://github.com/juanfont/headscale int…
juanfont Aug 14, 2022
0f09e19
Updated go.mod checksum
juanfont Aug 14, 2022
aaa33cf
Minor change in router
juanfont Aug 14, 2022
ab18c72
Support for Noise machines in getPeers
juanfont Aug 14, 2022
e640c6d
Fixes in Noise poll (clients should work now)
juanfont Aug 14, 2022
d0898ec
Move common parts of the protocol to dedicated file
juanfont Aug 14, 2022
db89fde
Added file for legacy protocol
juanfont Aug 14, 2022
35f3dee
Move Noise API to new file
juanfont Aug 14, 2022
f4bab6b
Created common methods for keep and map poll responses
juanfont Aug 14, 2022
df8ecdb
Working on common codebase for poll, starting with legacy
juanfont Aug 14, 2022
7cc227d
Added Noise field to logging
juanfont Aug 14, 2022
e29b344
Move Noise poll to new file, and use common poll
juanfont Aug 14, 2022
704a19b
Removed legacy method to generate MapResponse
juanfont Aug 14, 2022
f599bea
Fixed issue when not using compression
juanfont Aug 14, 2022
5cf9eed
Minor logging corrections
juanfont Aug 15, 2022
b8980b9
More minor logging stuff
juanfont Aug 15, 2022
8db7629
Fix config file in integration tests for Noise
juanfont Aug 15, 2022
865f1ff
Fix issues with DERP integration tests due to tailscale/tailscale#4323
juanfont Aug 15, 2022
c701f9e
Merge branch 'main' into hs2021-v2
juanfont Aug 15, 2022
b3cf528
Use CapVer to offer Noise only to supported clients
juanfont Aug 15, 2022
128ec67
Merge branch 'hs2021-v2' of https://github.com/juanfont/headscale int…
juanfont Aug 15, 2022
eb461d0
Enable HEAD and unstable in integration tests
juanfont Aug 15, 2022
0db7fc5
Mark all namespaces to lastChange now
juanfont Aug 16, 2022
c0fe1ab
Use node_key to find peers
juanfont Aug 16, 2022
ce53bb0
Minor changes to HEAD Dockerfile
juanfont Aug 16, 2022
b71a881
Retry magicdns tests
juanfont Aug 16, 2022
ba07bac
Use IPv4 in the tests
juanfont Aug 16, 2022
8a707de
Add local Docker DNS server (makes resolving http://headscale more re…
juanfont Aug 18, 2022
7185f8d
Only use released versions in public integration tests
juanfont Aug 18, 2022
f43a83a
Find out IPv4 for taildrop
juanfont Aug 18, 2022
cf731fa
Catch retry error in taildrop send
juanfont Aug 18, 2022
67ffebc
Merge branch 'main' into hs2021-v2
juanfont Aug 18, 2022
e4d961c
Merge branch 'hs2021-v2' of https://github.com/juanfont/headscale int…
juanfont Aug 18, 2022
f9a2a2b
Add docker DNS IP to the remaining files
juanfont Aug 18, 2022
9d430d3
Update noise.go
juanfont Aug 18, 2022
a87a963
Expanded response marshal methods to support legacy and Noise
juanfont Aug 19, 2022
e2bffd4
Make legacy protocol use common methods for client registration
juanfont Aug 19, 2022
a33b5a5
Merge branch 'hs2021-v2' of https://github.com/juanfont/headscale int…
juanfont Aug 19, 2022
43ad0d4
Removed unused method
juanfont Aug 19, 2022
b6e3cd8
Fixed minor linting things
juanfont Aug 19, 2022
e43713a
Merge branch 'main' into hs2021-v2
juanfont Aug 19, 2022
c894db3
Use common core for noise registration
juanfont Aug 19, 2022
922b8b5
Merge branch 'hs2021-v2' of https://github.com/juanfont/headscale int…
juanfont Aug 19, 2022
2f55413
Move comment up
juanfont Aug 19, 2022
e9906b5
Use upstream AcceptHTTP for the Noise upgrade
juanfont Aug 19, 2022
6aec520
Merge branch 'hs2021-v2' of https://github.com/juanfont/headscale int…
juanfont Aug 19, 2022
04e4fa7
Updated dependencies
juanfont Aug 19, 2022
175dfa1
Update flake.nix sum
juanfont Aug 19, 2022
f0a8a28
Clarified why we have a different key
juanfont Aug 19, 2022
e20e818
Integrate expiration fixes (#754) in TS2021 branch
juanfont Aug 20, 2022
4424a9a
Noise private key now a nested field in config
juanfont Aug 21, 2022
71d22dc
Added missing files
juanfont Aug 21, 2022
3ab1487
Merge branch 'main' into hs2021-v2
juanfont Aug 21, 2022
4aafe6c
Added line in CHANGELOG
juanfont Aug 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions noise.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package headscale

import (
"encoding/base64"
"net/http"

"github.com/rs/zerolog/log"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"tailscale.com/control/controlbase"
"tailscale.com/net/netutil"
)

const (
errWrongConnectionUpgrade = Error("wrong connection upgrade")
errCannotHijack = Error("cannot hijack connection")
errNoiseHandshakeFailed = Error("noise handshake failed")
)

const (
// ts2021UpgradePath is the path that the server listens on for the WebSockets upgrade.
ts2021UpgradePath = "/ts2021"

// upgradeHeader is the value of the Upgrade HTTP header used to
// indicate the Tailscale control protocol.
upgradeHeaderValue = "tailscale-control-protocol"

// handshakeHeaderName is the HTTP request header that can
// optionally contain base64-encoded initial handshake
// payload, to save an RTT.
handshakeHeaderName = "X-Tailscale-Handshake"
)

// NoiseUpgradeHandler is to upgrade the connection and hijack the net.Conn
// in order to use the Noise-based TS2021 protocol. Listens in /ts2021.
func (h *Headscale) NoiseUpgradeHandler(
writer http.ResponseWriter,
req *http.Request,
) {
log.Trace().Caller().Msgf("Noise upgrade handler for client %s", req.RemoteAddr)

// Under normal circumpstances, we should be able to use the controlhttp.AcceptHTTP()
juanfont marked this conversation as resolved.
Show resolved Hide resolved
// function to do this - kindly left there by the Tailscale authors for us to use.
// (https://github.com/tailscale/tailscale/blob/main/control/controlhttp/server.go)
//
// When we used to use Gin, we had troubles here as Gin seems to do some
juanfont marked this conversation as resolved.
Show resolved Hide resolved
// fun stuff, and not flusing the writer properly.
// So have getNoiseConnection() that is essentially an AcceptHTTP, but in our side.
noiseConn, err := h.getNoiseConnection(writer, req)
if err != nil {
log.Error().Err(err).Msg("noise upgrade failed")
http.Error(writer, err.Error(), http.StatusInternalServerError)

return
}

server := http.Server{}
server.Handler = h2c.NewHandler(h.noiseMux, &http2.Server{})
server.Serve(netutil.NewOneConnListener(noiseConn, nil))
}

// getNoiseConnection is basically AcceptHTTP from tailscale
// TODO(juan): Figure out why we need to do this at all.
func (h *Headscale) getNoiseConnection(
juanfont marked this conversation as resolved.
Show resolved Hide resolved
writer http.ResponseWriter,
req *http.Request,
) (*controlbase.Conn, error) {
next := req.Header.Get("Upgrade")
if next == "" {
http.Error(writer, errWrongConnectionUpgrade.Error(), http.StatusBadRequest)

return nil, errWrongConnectionUpgrade
}
if next != upgradeHeaderValue {
http.Error(writer, errWrongConnectionUpgrade.Error(), http.StatusBadRequest)

return nil, errWrongConnectionUpgrade
}

initB64 := req.Header.Get(handshakeHeaderName)
if initB64 == "" {
log.Warn().
Caller().
Msg("no handshake header")
http.Error(writer, "missing Tailscale handshake header", http.StatusBadRequest)

return nil, errWrongConnectionUpgrade
}

init, err := base64.StdEncoding.DecodeString(initB64)
if err != nil {
log.Warn().Err(err).Msg("invalid handshake header")
http.Error(writer, "invalid tailscale handshake header", http.StatusBadRequest)

return nil, errWrongConnectionUpgrade
}

hijacker, ok := writer.(http.Hijacker)
if !ok {
log.Error().Caller().Err(err).Msgf("Hijack failed")
http.Error(writer, errCannotHijack.Error(), http.StatusInternalServerError)

return nil, errCannotHijack
}

// This is what changes from the original AcceptHTTP() function.
writer.Header().Set("Upgrade", upgradeHeaderValue)
writer.Header().Set("Connection", "upgrade")
writer.WriteHeader(http.StatusSwitchingProtocols)
// end

netConn, conn, err := hijacker.Hijack()
if err != nil {
log.Error().Caller().Err(err).Msgf("Hijack failed")
http.Error(writer, "HTTP does not support general TCP support", http.StatusInternalServerError)
return nil, errCannotHijack
}
if err := conn.Flush(); err != nil {
netConn.Close()

return nil, errCannotHijack
}

netConn = netutil.NewDrainBufConn(netConn, conn.Reader)

nc, err := controlbase.Server(req.Context(), netConn, *h.noisePrivateKey, init)
if err != nil {
netConn.Close()

return nil, errNoiseHandshakeFailed
}

return nc, nil
}