mirror of
https://github.com/juanfont/headscale.git
synced 2025-03-01 00:21:18 +01:00
handle register auth errors (#2435)
* handle register auth errors This commit handles register auth errors as the Tailscale clients expect. It returns the error as part of a tailcfg.RegisterResponse and not as a http error. In addition it fixes a nil pointer panic triggered by not handling the errors as part of this chain. Fixes #2434 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * changelog Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> --------- Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
parent
b220fb7d51
commit
bcff0eaae7
@ -14,6 +14,13 @@
|
|||||||
- View of config, policy, filter, ssh policy per node, connected nodes and
|
- View of config, policy, filter, ssh policy per node, connected nodes and
|
||||||
DERPmap
|
DERPmap
|
||||||
|
|
||||||
|
## 0.25.1 (2025-02-18)
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- Fix issue where registration errors are sent correctly
|
||||||
|
[#2435](https://github.com/juanfont/headscale/pull/2435)
|
||||||
|
|
||||||
## 0.25.0 (2025-02-11)
|
## 0.25.0 (2025-02-11)
|
||||||
|
|
||||||
### BREAKING
|
### BREAKING
|
||||||
|
@ -230,6 +230,10 @@ func (ns *noiseServer) NoisePollNetMapHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func regErr(err error) *tailcfg.RegisterResponse {
|
||||||
|
return &tailcfg.RegisterResponse{Error: err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
// NoiseRegistrationHandler handles the actual registration process of a node.
|
// NoiseRegistrationHandler handles the actual registration process of a node.
|
||||||
func (ns *noiseServer) NoiseRegistrationHandler(
|
func (ns *noiseServer) NoiseRegistrationHandler(
|
||||||
writer http.ResponseWriter,
|
writer http.ResponseWriter,
|
||||||
@ -241,52 +245,47 @@ func (ns *noiseServer) NoiseRegistrationHandler(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
registerRequest, registerResponse, err := func() (*tailcfg.RegisterRequest, []byte, error) {
|
registerRequest, registerResponse := func() (*tailcfg.RegisterRequest, *tailcfg.RegisterResponse) {
|
||||||
|
var resp *tailcfg.RegisterResponse
|
||||||
body, err := io.ReadAll(req.Body)
|
body, err := io.ReadAll(req.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return &tailcfg.RegisterRequest{}, regErr(err)
|
||||||
}
|
}
|
||||||
var registerRequest tailcfg.RegisterRequest
|
var regReq tailcfg.RegisterRequest
|
||||||
if err := json.Unmarshal(body, ®isterRequest); err != nil {
|
if err := json.Unmarshal(body, ®Req); err != nil {
|
||||||
return nil, nil, err
|
return ®Req, regErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ns.nodeKey = registerRequest.NodeKey
|
ns.nodeKey = regReq.NodeKey
|
||||||
|
|
||||||
resp, err := ns.headscale.handleRegister(req.Context(), registerRequest, ns.conn.Peer())
|
resp, err = ns.headscale.handleRegister(req.Context(), regReq, ns.conn.Peer())
|
||||||
// TODO(kradalby): Here we could have two error types, one that is surfaced to the client
|
|
||||||
// and one that returns 500.
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
var httpErr HTTPError
|
||||||
|
if errors.As(err, &httpErr) {
|
||||||
|
resp = &tailcfg.RegisterResponse{
|
||||||
|
Error: httpErr.Msg,
|
||||||
|
}
|
||||||
|
return ®Req, resp
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
return ®Req, regErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
respBody, err := json.Marshal(resp)
|
return ®Req, resp
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ®isterRequest, respBody, nil
|
|
||||||
}()
|
}()
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Caller().
|
|
||||||
Err(err).
|
|
||||||
Msg("Error handling registration")
|
|
||||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reject unsupported versions
|
// Reject unsupported versions
|
||||||
if rejectUnsupported(writer, registerRequest.Version, ns.machineKey, registerRequest.NodeKey) {
|
if rejectUnsupported(writer, registerRequest.Version, ns.machineKey, registerRequest.NodeKey) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
respBody, err := json.Marshal(registerResponse)
|
||||||
|
if err != nil {
|
||||||
|
httpError(writer, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
writer.WriteHeader(http.StatusOK)
|
writer.WriteHeader(http.StatusOK)
|
||||||
_, err = writer.Write(registerResponse)
|
writer.Write(respBody)
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Caller().
|
|
||||||
Err(err).
|
|
||||||
Msg("Failed to write response")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -228,3 +228,99 @@ func TestAuthKeyLogoutAndReloginNewUser(t *testing.T) {
|
|||||||
assert.Equal(t, "user1@test.no", status.User[status.Self.UserID].LoginName)
|
assert.Equal(t, "user1@test.no", status.User[status.Self.UserID].LoginName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAuthKeyLogoutAndReloginSameUserExpiredKey(t *testing.T) {
|
||||||
|
IntegrationSkip(t)
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, https := range []bool{true, false} {
|
||||||
|
t.Run(fmt.Sprintf("with-https-%t", https), func(t *testing.T) {
|
||||||
|
scenario, err := NewScenario(dockertestMaxWait())
|
||||||
|
assertNoErr(t, err)
|
||||||
|
defer scenario.ShutdownAssertNoPanics(t)
|
||||||
|
|
||||||
|
spec := map[string]int{
|
||||||
|
"user1": len(MustTestVersions),
|
||||||
|
"user2": len(MustTestVersions),
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := []hsic.Option{hsic.WithTestName("pingallbyip")}
|
||||||
|
if https {
|
||||||
|
opts = append(opts, []hsic.Option{
|
||||||
|
hsic.WithTLS(),
|
||||||
|
}...)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, opts...)
|
||||||
|
assertNoErrHeadscaleEnv(t, err)
|
||||||
|
|
||||||
|
allClients, err := scenario.ListTailscaleClients()
|
||||||
|
assertNoErrListClients(t, err)
|
||||||
|
|
||||||
|
err = scenario.WaitForTailscaleSync()
|
||||||
|
assertNoErrSync(t, err)
|
||||||
|
|
||||||
|
// assertClientsState(t, allClients)
|
||||||
|
|
||||||
|
clientIPs := make(map[TailscaleClient][]netip.Addr)
|
||||||
|
for _, client := range allClients {
|
||||||
|
ips, err := client.IPs()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get IPs for client %s: %s", client.Hostname(), err)
|
||||||
|
}
|
||||||
|
clientIPs[client] = ips
|
||||||
|
}
|
||||||
|
|
||||||
|
headscale, err := scenario.Headscale()
|
||||||
|
assertNoErrGetHeadscale(t, err)
|
||||||
|
|
||||||
|
listNodes, err := headscale.ListNodes()
|
||||||
|
assert.Equal(t, len(listNodes), len(allClients))
|
||||||
|
nodeCountBeforeLogout := len(listNodes)
|
||||||
|
t.Logf("node count before logout: %d", nodeCountBeforeLogout)
|
||||||
|
|
||||||
|
for _, client := range allClients {
|
||||||
|
err := client.Logout()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to logout client %s: %s", client.Hostname(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scenario.WaitForTailscaleLogout()
|
||||||
|
assertNoErrLogout(t, err)
|
||||||
|
|
||||||
|
t.Logf("all clients logged out")
|
||||||
|
|
||||||
|
// if the server is not running with HTTPS, we have to wait a bit before
|
||||||
|
// reconnection as the newest Tailscale client has a measure that will only
|
||||||
|
// reconnect over HTTPS if they saw a noise connection previously.
|
||||||
|
// https://github.com/tailscale/tailscale/commit/1eaad7d3deb0815e8932e913ca1a862afa34db38
|
||||||
|
// https://github.com/juanfont/headscale/issues/2164
|
||||||
|
if !https {
|
||||||
|
time.Sleep(5 * time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
for userName := range spec {
|
||||||
|
key, err := scenario.CreatePreAuthKey(userName, true, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create pre-auth key for user %s: %s", userName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expire the key so it can't be used
|
||||||
|
_, err = headscale.Execute(
|
||||||
|
[]string{
|
||||||
|
"headscale",
|
||||||
|
"preauthkeys",
|
||||||
|
"--user",
|
||||||
|
userName,
|
||||||
|
"expire",
|
||||||
|
key.Key,
|
||||||
|
})
|
||||||
|
assertNoErr(t, err)
|
||||||
|
|
||||||
|
err = scenario.RunTailscaleUp(userName, headscale.GetEndpoint(), key.GetKey())
|
||||||
|
assert.ErrorContains(t, err, "authkey expired")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user