mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-27 10:47:35 +00:00
ipn/ipnlocal: start implementing web server bits of serve
Updates tailscale/corp#7515 Change-Id: I96f4016161ba3c370492da941274c6d9a234c2bb Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
c35dcd427f
commit
25e26c16ee
@ -564,7 +564,8 @@ var devStoreSetArgs struct {
|
|||||||
func runDevStoreSet(ctx context.Context, args []string) error {
|
func runDevStoreSet(ctx context.Context, args []string) error {
|
||||||
// TODO(bradfitz): remove this temporary (2022-11-09) hack once
|
// TODO(bradfitz): remove this temporary (2022-11-09) hack once
|
||||||
// profile stuff and serving CLI commands are more fleshed out.
|
// profile stuff and serving CLI commands are more fleshed out.
|
||||||
if len(args) >= 1 && strings.HasPrefix(args[0], "_serve/") {
|
isServe := len(args) >= 1 && strings.HasPrefix(args[0], "_serve/")
|
||||||
|
if isServe {
|
||||||
st, err := localClient.StatusWithoutPeers(ctx)
|
st, err := localClient.StatusWithoutPeers(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -584,6 +585,11 @@ func runDevStoreSet(ctx context.Context, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if isServe {
|
||||||
|
if err := json.Unmarshal(valb, new(ipn.ServeConfig)); err != nil {
|
||||||
|
return fmt.Errorf("invalid JSON: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
val = string(valb)
|
val = string(valb)
|
||||||
}
|
}
|
||||||
return localClient.SetDevStoreKeyValue(ctx, key, val)
|
return localClient.SetDevStoreKeyValue(ctx, key, val)
|
||||||
|
@ -118,6 +118,7 @@ func (src *HTTPHandler) Clone() *HTTPHandler {
|
|||||||
var _HTTPHandlerCloneNeedsRegeneration = HTTPHandler(struct {
|
var _HTTPHandlerCloneNeedsRegeneration = HTTPHandler(struct {
|
||||||
Path string
|
Path string
|
||||||
Proxy string
|
Proxy string
|
||||||
|
Text string
|
||||||
}{})
|
}{})
|
||||||
|
|
||||||
// Clone makes a deep copy of WebServerConfig.
|
// Clone makes a deep copy of WebServerConfig.
|
||||||
|
@ -285,11 +285,13 @@ func (v *HTTPHandlerView) UnmarshalJSON(b []byte) error {
|
|||||||
|
|
||||||
func (v HTTPHandlerView) Path() string { return v.ж.Path }
|
func (v HTTPHandlerView) Path() string { return v.ж.Path }
|
||||||
func (v HTTPHandlerView) Proxy() string { return v.ж.Proxy }
|
func (v HTTPHandlerView) Proxy() string { return v.ж.Proxy }
|
||||||
|
func (v HTTPHandlerView) Text() string { return v.ж.Text }
|
||||||
|
|
||||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
var _HTTPHandlerViewNeedsRegeneration = HTTPHandler(struct {
|
var _HTTPHandlerViewNeedsRegeneration = HTTPHandler(struct {
|
||||||
Path string
|
Path string
|
||||||
Proxy string
|
Proxy string
|
||||||
|
Text string
|
||||||
}{})
|
}{})
|
||||||
|
|
||||||
// View returns a readonly view of WebServerConfig.
|
// View returns a readonly view of WebServerConfig.
|
||||||
|
@ -6,7 +6,6 @@ package ipnlocal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@ -4086,41 +4085,3 @@ func (b *LocalBackend) SetDevStateStore(key, value string) error {
|
|||||||
func (b *LocalBackend) ShouldInterceptTCPPort(port uint16) bool {
|
func (b *LocalBackend) ShouldInterceptTCPPort(port uint16) bool {
|
||||||
return b.shouldInterceptTCPPortAtomic.Load()(port)
|
return b.shouldInterceptTCPPortAtomic.Load()(port)
|
||||||
}
|
}
|
||||||
|
|
||||||
var runDevWebServer = envknob.RegisterBool("TS_DEV_WEBSERVER")
|
|
||||||
|
|
||||||
func (b *LocalBackend) HandleInterceptedTCPConn(c net.Conn) {
|
|
||||||
if !runDevWebServer() {
|
|
||||||
b.logf("localbackend: closing TCP conn from %v to %v", c.RemoteAddr(), c.LocalAddr())
|
|
||||||
c.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(bradfitz): look up how; sniff SNI if ambiguous
|
|
||||||
hs := &http.Server{
|
|
||||||
TLSConfig: &tls.Config{
|
|
||||||
GetCertificate: b.getTLSServeCert,
|
|
||||||
},
|
|
||||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
io.WriteString(w, "<h1>hello world</h1>this is tailscaled")
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
hs.ServeTLS(netutil.NewOneConnListener(c, nil), "", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *LocalBackend) getTLSServeCert(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
||||||
if hi == nil || hi.ServerName == "" {
|
|
||||||
return nil, errors.New("no SNI ServerName")
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
pair, err := b.GetCertPEM(ctx, hi.ServerName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cert, err := tls.X509KeyPair(pair.CertPEM, pair.KeyPEM)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &cert, nil
|
|
||||||
}
|
|
||||||
|
107
ipn/ipnlocal/serve.go
Normal file
107
ipn/ipnlocal/serve.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package ipnlocal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
pathpkg "path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tailscale.com/envknob"
|
||||||
|
"tailscale.com/ipn"
|
||||||
|
"tailscale.com/net/netutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var runDevWebServer = envknob.RegisterBool("TS_DEV_WEBSERVER")
|
||||||
|
|
||||||
|
func (b *LocalBackend) HandleInterceptedTCPConn(c net.Conn) {
|
||||||
|
if !runDevWebServer() {
|
||||||
|
b.logf("localbackend: closing TCP conn from %v to %v", c.RemoteAddr(), c.LocalAddr())
|
||||||
|
c.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(bradfitz): look up how; sniff SNI if ambiguous
|
||||||
|
hs := &http.Server{
|
||||||
|
TLSConfig: &tls.Config{
|
||||||
|
GetCertificate: b.getTLSServeCert,
|
||||||
|
},
|
||||||
|
Handler: http.HandlerFunc(b.serveWebHandler),
|
||||||
|
}
|
||||||
|
hs.ServeTLS(netutil.NewOneConnListener(c, nil), "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LocalBackend) getServeHandler(r *http.Request) (_ *ipn.HTTPHandler, ok bool) {
|
||||||
|
if r.TLS == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
sni := r.TLS.ServerName
|
||||||
|
port := "443" // TODO(bradfitz): fix
|
||||||
|
key := ipn.HostPort(net.JoinHostPort(sni, port))
|
||||||
|
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
wsc, ok := b.serveConfig.Web[key]
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
path := r.URL.Path
|
||||||
|
for {
|
||||||
|
if h, ok := wsc.Handlers[path]; ok {
|
||||||
|
return h, true
|
||||||
|
}
|
||||||
|
if path == "/" {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
path = pathpkg.Dir(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LocalBackend) serveWebHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h, ok := b.getServeHandler(r)
|
||||||
|
if !ok {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s := h.Text; s != "" {
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
io.WriteString(w, s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v := h.Path; v != "" {
|
||||||
|
io.WriteString(w, "TODO(bradfitz): serve file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v := h.Proxy; v != "" {
|
||||||
|
io.WriteString(w, "TODO(bradfitz): proxy")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Error(w, "empty handler", 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *LocalBackend) getTLSServeCert(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
|
if hi == nil || hi.ServerName == "" {
|
||||||
|
return nil, errors.New("no SNI ServerName")
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
pair, err := b.GetCertPEM(ctx, hi.ServerName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cert, err := tls.X509KeyPair(pair.CertPEM, pair.KeyPEM)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &cert, nil
|
||||||
|
}
|
@ -121,6 +121,8 @@ type HTTPHandler struct {
|
|||||||
Path string `json:",omitempty"` // absolute path to directory or file to serve
|
Path string `json:",omitempty"` // absolute path to directory or file to serve
|
||||||
Proxy string `json:",omitempty"` // http://localhost:3000/, localhost:3030, 3030
|
Proxy string `json:",omitempty"` // http://localhost:3000/, localhost:3030, 3030
|
||||||
|
|
||||||
|
Text string `json:",omitempty"` // plaintext to serve (primarily for testing)
|
||||||
|
|
||||||
// TODO(bradfitz): bool to not enumerate directories? TTL on mapping for
|
// TODO(bradfitz): bool to not enumerate directories? TTL on mapping for
|
||||||
// temporary ones? Error codes? Redirects?
|
// temporary ones? Error codes? Redirects?
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user