Brad Fitzpatrick 73c371f784 cmd/derper: permit port 80 in ACE targets
Updates tailscale/corp#32168
Updates tailscale/corp#32226

Change-Id: Iddc017b060c76e6eab8f6d0c989a775bcaae3518
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2025-09-17 13:06:05 -07:00

78 lines
2.5 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// TODO: docs about all this
package main
import (
"errors"
"fmt"
"net"
"net/http"
"strings"
"tailscale.com/derp"
"tailscale.com/net/connectproxy"
)
// serveConnect handles a CONNECT request for ACE support.
func serveConnect(s *derp.Server, w http.ResponseWriter, r *http.Request) {
if !*flagACEEnabled {
http.Error(w, "CONNECT not enabled", http.StatusForbidden)
return
}
if r.TLS == nil {
// This should already be enforced by the caller of serveConnect, but
// double check.
http.Error(w, "CONNECT requires TLS", http.StatusForbidden)
return
}
ch := &connectproxy.Handler{
Check: func(hostPort string) error {
host, port, err := net.SplitHostPort(hostPort)
if err != nil {
return err
}
if port != "443" && port != "80" {
// There are only two types of CONNECT requests the client makes
// via ACE: requests for /key (port 443) and requests to upgrade
// to the bidirectional ts2021 Noise protocol.
//
// The ts2021 layer can bootstrap over port 80 (http) or port
// 443 (https).
//
// Without ACE, we prefer port 80 to avoid unnecessary double
// encryption. But enough places require TLS+port 443 that we do
// support that double encryption path as a fallback.
//
// But ACE adds its own TLS layer (ACE is always CONNECT over
// https). If we don't permit port 80 here as a target, we'd
// have three layers of encryption (TLS + TLS + Noise) which is
// even more silly than two.
//
// So we permit port 80 such that we can only have two layers of
// encryption, varying by the request type:
//
// 1. TLS from client to ACE proxy (CONNECT)
// 2a. TLS from ACE proxy to https://controlplane.tailscale.com/key (port 443)
// 2b. ts2021 Noise from ACE proxy to http://controlplane.tailscale.com/ts2021 (port 80)
//
// But nothing's stopping the client from doing its ts2021
// upgrade over https anyway and having three layers of
// encryption. But we can at least permit the client to do a
// "CONNECT controlplane.tailscale.com:80 HTTP/1.1" if it wants.
return fmt.Errorf("only ports 443 and 80 are allowed")
}
// TODO(bradfitz): make policy configurable from flags and/or come
// from local tailscaled nodeAttrs
if !strings.HasSuffix(host, ".tailscale.com") || strings.Contains(host, "derp") {
return errors.New("bad host")
}
return nil
},
}
ch.ServeHTTP(w, r)
}