From 3928ea206e1002b957e32797c86c97ddd1137f69 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 8 Mar 2022 14:51:09 -0800 Subject: [PATCH] control/controlhttp: send expected control public key in upgrade request So we can do key rotation later and have small windows of overlapping valid server keys. Updates #3488 Change-Id: Ib5c7f2006a797a069e3f55d37f5d41f533e82f71 Signed-off-by: Brad Fitzpatrick --- control/controlhttp/client.go | 5 +++++ control/controlhttp/server.go | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/control/controlhttp/client.go b/control/controlhttp/client.go index c9db75025..62bdce556 100644 --- a/control/controlhttp/client.go +++ b/control/controlhttp/client.go @@ -50,6 +50,10 @@ const ( // payload, to save an RTT. handshakeHeaderName = "X-Tailscale-Handshake" + // serverPubHeaderName is the HTTP request header that + // says the expected public key of the control plane. + serverPubHeaderName = "X-Tailscale-Control-Public" + // serverUpgradePath is where the server-side HTTP handler to // to do the protocol switch is located. serverUpgradePath = "/ts2021" @@ -194,6 +198,7 @@ func (a *dialParams) tryURL(u *url.URL, init []byte) (net.Conn, error) { "Upgrade": []string{upgradeHeaderValue}, "Connection": []string{"upgrade"}, handshakeHeaderName: []string{base64.StdEncoding.EncodeToString(init)}, + serverPubHeaderName: []string{a.controlKey.String()}, }, } req = req.WithContext(ctx) diff --git a/control/controlhttp/server.go b/control/controlhttp/server.go index 92bd9ec9b..a446a0ebc 100644 --- a/control/controlhttp/server.go +++ b/control/controlhttp/server.go @@ -44,6 +44,18 @@ func AcceptHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request, pri return nil, fmt.Errorf("decoding base64 handshake header: %v", err) } + if wantPub := r.Header.Get(serverPubHeaderName); wantPub != "" { + // If the client declared the public key they expect to speak to, + // check it. + // TODO: replace the 'private' parameter with a func/interface + // that looks up the private key as a function of the public key + // to see if we have a currently in-rotation key that's valid. + if private.Public().String() != wantPub { + http.Error(w, "requested server key unavailable", http.StatusServiceUnavailable) + return nil, errors.New("client requested unavailble server key") + } + } + hijacker, ok := w.(http.Hijacker) if !ok { http.Error(w, "make request over HTTP/1", http.StatusBadRequest)