diff --git a/ipn/ipn_clone.go b/ipn/ipn_clone.go index 4859e3ffc..51d855fe6 100644 --- a/ipn/ipn_clone.go +++ b/ipn/ipn_clone.go @@ -107,7 +107,7 @@ func (src *TCPPortHandler) Clone() *TCPPortHandler { var _TCPPortHandlerCloneNeedsRegeneration = TCPPortHandler(struct { HTTPS bool TCPForward string - TerminateTLS bool + TerminateTLS string }{}) // Clone makes a deep copy of HTTPHandler. diff --git a/ipn/ipn_view.go b/ipn/ipn_view.go index a85da3e3f..44260ad07 100644 --- a/ipn/ipn_view.go +++ b/ipn/ipn_view.go @@ -232,15 +232,15 @@ func (v *TCPPortHandlerView) UnmarshalJSON(b []byte) error { return nil } -func (v TCPPortHandlerView) HTTPS() bool { return v.ж.HTTPS } -func (v TCPPortHandlerView) TCPForward() string { return v.ж.TCPForward } -func (v TCPPortHandlerView) TerminateTLS() bool { return v.ж.TerminateTLS } +func (v TCPPortHandlerView) HTTPS() bool { return v.ж.HTTPS } +func (v TCPPortHandlerView) TCPForward() string { return v.ж.TCPForward } +func (v TCPPortHandlerView) TerminateTLS() string { return v.ж.TerminateTLS } // A compilation failure here means this code must be regenerated, with the command at the top of this file. var _TCPPortHandlerViewNeedsRegeneration = TCPPortHandler(struct { HTTPS bool TCPForward string - TerminateTLS bool + TerminateTLS string }{}) // View returns a readonly view of HTTPHandler. diff --git a/ipn/ipnlocal/serve.go b/ipn/ipnlocal/serve.go index 1137e687a..f7a04aa24 100644 --- a/ipn/ipnlocal/serve.go +++ b/ipn/ipnlocal/serve.go @@ -112,11 +112,6 @@ func (b *LocalBackend) HandleInterceptedTCPConn(dport uint16, srcAddr netip.Addr } if backDst := tcph.TCPForward(); backDst != "" { - if tcph.TerminateTLS() { - b.logf("TODO(bradfitz): finish") - sendRST() - return - } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) backConn, err := b.dialer.SystemDial(ctx, "tcp", backDst) cancel() @@ -134,6 +129,24 @@ func (b *LocalBackend) HandleInterceptedTCPConn(dport uint16, srcAddr netip.Addr defer conn.Close() defer backConn.Close() + if sni := tcph.TerminateTLS(); sni != "" { + conn = tls.Server(conn, &tls.Config{ + GetCertificate: func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + pair, err := b.GetCertPEM(ctx, sni) + if err != nil { + return nil, err + } + cert, err := tls.X509KeyPair(pair.CertPEM, pair.KeyPEM) + if err != nil { + return nil, err + } + return &cert, nil + }, + }) + } + // TODO(bradfitz): do the RegisterIPPortIdentity and // UnregisterIPPortIdentity stuff that netstack does diff --git a/ipn/store.go b/ipn/store.go index 6579fa200..516971742 100644 --- a/ipn/store.go +++ b/ipn/store.go @@ -112,10 +112,11 @@ type TCPPortHandler struct { // It is mutually exclusive with HTTPS. TCPForward string `json:",omitempty"` - // TerminateTLS is whether tailscaled should terminate TLS - // connections before forwarding them to TCPForward. It is only - // used if TCPForward is non-empty. (the HTTPS mode ) - TerminateTLS bool `json:",omitempty"` + // TerminateTLS, if non-empty, means that tailscaled should terminate the + // TLS connections before forwarding them to TCPForward, permitting only the + // SNI name with this value. It is only used if TCPForward is non-empty. + // (the HTTPS mode uses ServeConfig.Web) + TerminateTLS string `json:",omitempty"` } // HTTPHandler is either a path or a proxy to serve.