{cmd/tailscale/cli,ipn}: add http support to tailscale serve (#8358)
Updates #8357 Signed-off-by: Shayne Sweeney <shayne@tailscale.com>pull/8120/head
parent
a2153afeeb
commit
6697690b55
|
@ -30,10 +30,10 @@ func newFunnelCommand(e *serveEnv) *ffcli.Command {
|
||||||
return &ffcli.Command{
|
return &ffcli.Command{
|
||||||
Name: "funnel",
|
Name: "funnel",
|
||||||
ShortHelp: "Turn on/off Funnel service",
|
ShortHelp: "Turn on/off Funnel service",
|
||||||
ShortUsage: strings.TrimSpace(`
|
ShortUsage: strings.Join([]string{
|
||||||
funnel <serve-port> {on|off}
|
"funnel <serve-port> {on|off}",
|
||||||
funnel status [--json]
|
"funnel status [--json]",
|
||||||
`),
|
}, "\n "),
|
||||||
LongHelp: strings.Join([]string{
|
LongHelp: strings.Join([]string{
|
||||||
"Funnel allows you to publish a 'tailscale serve'",
|
"Funnel allows you to publish a 'tailscale serve'",
|
||||||
"server publicly, open to the entire internet.",
|
"server publicly, open to the entire internet.",
|
||||||
|
|
|
@ -35,13 +35,14 @@ func newServeCommand(e *serveEnv) *ffcli.Command {
|
||||||
return &ffcli.Command{
|
return &ffcli.Command{
|
||||||
Name: "serve",
|
Name: "serve",
|
||||||
ShortHelp: "Serve content and local servers",
|
ShortHelp: "Serve content and local servers",
|
||||||
ShortUsage: strings.TrimSpace(`
|
ShortUsage: strings.Join([]string{
|
||||||
serve https:<port> <mount-point> <source> [off]
|
"serve http:<port> <mount-point> <source> [off]",
|
||||||
serve tcp:<port> tcp://localhost:<local-port> [off]
|
"serve https:<port> <mount-point> <source> [off]",
|
||||||
serve tls-terminated-tcp:<port> tcp://localhost:<local-port> [off]
|
"serve tcp:<port> tcp://localhost:<local-port> [off]",
|
||||||
serve status [--json]
|
"serve tls-terminated-tcp:<port> tcp://localhost:<local-port> [off]",
|
||||||
serve reset
|
"serve status [--json]",
|
||||||
`),
|
"serve reset",
|
||||||
|
}, "\n "),
|
||||||
LongHelp: strings.TrimSpace(`
|
LongHelp: strings.TrimSpace(`
|
||||||
*** BETA; all of this is subject to change ***
|
*** BETA; all of this is subject to change ***
|
||||||
|
|
||||||
|
@ -58,8 +59,8 @@ EXAMPLES
|
||||||
- To proxy requests to a web server at 127.0.0.1:3000:
|
- To proxy requests to a web server at 127.0.0.1:3000:
|
||||||
$ tailscale serve https:443 / http://127.0.0.1:3000
|
$ tailscale serve https:443 / http://127.0.0.1:3000
|
||||||
|
|
||||||
Or, using the default port:
|
Or, using the default port (443):
|
||||||
$ tailscale serve https / http://127.0.0.1:3000
|
$ tailscale serve https / http://127.0.0.1:3000
|
||||||
|
|
||||||
- To serve a single file or a directory of files:
|
- To serve a single file or a directory of files:
|
||||||
$ tailscale serve https / /home/alice/blog/index.html
|
$ tailscale serve https / /home/alice/blog/index.html
|
||||||
|
@ -68,6 +69,12 @@ EXAMPLES
|
||||||
- To serve simple static text:
|
- To serve simple static text:
|
||||||
$ tailscale serve https:8080 / text:"Hello, world!"
|
$ tailscale serve https:8080 / text:"Hello, world!"
|
||||||
|
|
||||||
|
- To serve over HTTP (tailnet only):
|
||||||
|
$ tailscale serve http:80 / http://127.0.0.1:3000
|
||||||
|
|
||||||
|
Or, using the default port (80):
|
||||||
|
$ tailscale serve http / http://127.0.0.1:3000
|
||||||
|
|
||||||
- To forward incoming TCP connections on port 2222 to a local TCP server on
|
- To forward incoming TCP connections on port 2222 to a local TCP server on
|
||||||
port 22 (e.g. to run OpenSSH in parallel with Tailscale SSH):
|
port 22 (e.g. to run OpenSSH in parallel with Tailscale SSH):
|
||||||
$ tailscale serve tcp:2222 tcp://localhost:22
|
$ tailscale serve tcp:2222 tcp://localhost:22
|
||||||
|
@ -175,6 +182,7 @@ func (e *serveEnv) getLocalClientStatus(ctx context.Context) (*ipnstate.Status,
|
||||||
// serve config types like proxy, path, and text.
|
// serve config types like proxy, path, and text.
|
||||||
//
|
//
|
||||||
// Examples:
|
// Examples:
|
||||||
|
// - tailscale serve http / http://localhost:3000
|
||||||
// - tailscale serve https / http://localhost:3000
|
// - tailscale serve https / http://localhost:3000
|
||||||
// - tailscale serve https /images/ /var/www/images/
|
// - tailscale serve https /images/ /var/www/images/
|
||||||
// - tailscale serve https:10000 /motd.txt text:"Hello, world!"
|
// - tailscale serve https:10000 /motd.txt text:"Hello, world!"
|
||||||
|
@ -199,19 +207,14 @@ func (e *serveEnv) runServe(ctx context.Context, args []string) error {
|
||||||
return e.lc.SetServeConfig(ctx, sc)
|
return e.lc.SetServeConfig(ctx, sc)
|
||||||
}
|
}
|
||||||
|
|
||||||
parsePort := func(portStr string) (uint16, error) {
|
|
||||||
port64, err := strconv.ParseUint(portStr, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return uint16(port64), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
srcType, srcPortStr, found := strings.Cut(args[0], ":")
|
srcType, srcPortStr, found := strings.Cut(args[0], ":")
|
||||||
if !found {
|
if !found {
|
||||||
if srcType == "https" && srcPortStr == "" {
|
if srcType == "https" && srcPortStr == "" {
|
||||||
// Default https port to 443.
|
// Default https port to 443.
|
||||||
srcPortStr = "443"
|
srcPortStr = "443"
|
||||||
|
} else if srcType == "http" && srcPortStr == "" {
|
||||||
|
// Default http port to 80.
|
||||||
|
srcPortStr = "80"
|
||||||
} else {
|
} else {
|
||||||
return flag.ErrHelp
|
return flag.ErrHelp
|
||||||
}
|
}
|
||||||
|
@ -219,18 +222,18 @@ func (e *serveEnv) runServe(ctx context.Context, args []string) error {
|
||||||
|
|
||||||
turnOff := "off" == args[len(args)-1]
|
turnOff := "off" == args[len(args)-1]
|
||||||
|
|
||||||
if len(args) < 2 || (srcType == "https" && !turnOff && len(args) < 3) {
|
if len(args) < 2 || ((srcType == "https" || srcType == "http") && !turnOff && len(args) < 3) {
|
||||||
fmt.Fprintf(os.Stderr, "error: invalid number of arguments\n\n")
|
fmt.Fprintf(os.Stderr, "error: invalid number of arguments\n\n")
|
||||||
return flag.ErrHelp
|
return flag.ErrHelp
|
||||||
}
|
}
|
||||||
|
|
||||||
srcPort, err := parsePort(srcPortStr)
|
srcPort, err := parseServePort(srcPortStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("invalid port %q: %w", srcPortStr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch srcType {
|
switch srcType {
|
||||||
case "https":
|
case "https", "http":
|
||||||
mount, err := cleanMountPoint(args[1])
|
mount, err := cleanMountPoint(args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -238,7 +241,8 @@ func (e *serveEnv) runServe(ctx context.Context, args []string) error {
|
||||||
if turnOff {
|
if turnOff {
|
||||||
return e.handleWebServeRemove(ctx, srcPort, mount)
|
return e.handleWebServeRemove(ctx, srcPort, mount)
|
||||||
}
|
}
|
||||||
return e.handleWebServe(ctx, srcPort, mount, args[2])
|
useTLS := srcType == "https"
|
||||||
|
return e.handleWebServe(ctx, srcPort, useTLS, mount, args[2])
|
||||||
case "tcp", "tls-terminated-tcp":
|
case "tcp", "tls-terminated-tcp":
|
||||||
if turnOff {
|
if turnOff {
|
||||||
return e.handleTCPServeRemove(ctx, srcPort)
|
return e.handleTCPServeRemove(ctx, srcPort)
|
||||||
|
@ -246,20 +250,20 @@ func (e *serveEnv) runServe(ctx context.Context, args []string) error {
|
||||||
return e.handleTCPServe(ctx, srcType, srcPort, args[1])
|
return e.handleTCPServe(ctx, srcType, srcPort, args[1])
|
||||||
default:
|
default:
|
||||||
fmt.Fprintf(os.Stderr, "error: invalid serve type %q\n", srcType)
|
fmt.Fprintf(os.Stderr, "error: invalid serve type %q\n", srcType)
|
||||||
fmt.Fprint(os.Stderr, "must be one of: https:<port>, tcp:<port> or tls-terminated-tcp:<port>\n\n", srcType)
|
fmt.Fprint(os.Stderr, "must be one of: http:<port>, https:<port>, tcp:<port> or tls-terminated-tcp:<port>\n\n", srcType)
|
||||||
return flag.ErrHelp
|
return flag.ErrHelp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleWebServe handles the "tailscale serve https:..." subcommand.
|
// handleWebServe handles the "tailscale serve (http/https):..." subcommand. It
|
||||||
// It configures the serve config to forward HTTPS connections to the
|
// configures the serve config to forward HTTPS connections to the given source.
|
||||||
// given source.
|
|
||||||
//
|
//
|
||||||
// Examples:
|
// Examples:
|
||||||
|
// - tailscale serve http / http://localhost:3000
|
||||||
// - tailscale serve https / http://localhost:3000
|
// - tailscale serve https / http://localhost:3000
|
||||||
// - tailscale serve https:8443 /files/ /home/alice/shared-files/
|
// - tailscale serve https:8443 /files/ /home/alice/shared-files/
|
||||||
// - tailscale serve https:10000 /motd.txt text:"Hello, world!"
|
// - tailscale serve https:10000 /motd.txt text:"Hello, world!"
|
||||||
func (e *serveEnv) handleWebServe(ctx context.Context, srvPort uint16, mount, source string) error {
|
func (e *serveEnv) handleWebServe(ctx context.Context, srvPort uint16, useTLS bool, mount, source string) error {
|
||||||
h := new(ipn.HTTPHandler)
|
h := new(ipn.HTTPHandler)
|
||||||
|
|
||||||
ts, _, _ := strings.Cut(source, ":")
|
ts, _, _ := strings.Cut(source, ":")
|
||||||
|
@ -318,7 +322,7 @@ func (e *serveEnv) handleWebServe(ctx context.Context, srvPort uint16, mount, so
|
||||||
return flag.ErrHelp
|
return flag.ErrHelp
|
||||||
}
|
}
|
||||||
|
|
||||||
mak.Set(&sc.TCP, srvPort, &ipn.TCPPortHandler{HTTPS: true})
|
mak.Set(&sc.TCP, srvPort, &ipn.TCPPortHandler{HTTPS: useTLS, HTTP: !useTLS})
|
||||||
|
|
||||||
if _, ok := sc.Web[hp]; !ok {
|
if _, ok := sc.Web[hp]; !ok {
|
||||||
mak.Set(&sc.Web, hp, new(ipn.WebServerConfig))
|
mak.Set(&sc.Web, hp, new(ipn.WebServerConfig))
|
||||||
|
@ -626,7 +630,10 @@ func (e *serveEnv) runServeStatus(ctx context.Context, args []string) error {
|
||||||
printf("\n")
|
printf("\n")
|
||||||
}
|
}
|
||||||
for hp := range sc.Web {
|
for hp := range sc.Web {
|
||||||
printWebStatusTree(sc, hp)
|
err := e.printWebStatusTree(sc, hp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
printf("\n")
|
printf("\n")
|
||||||
}
|
}
|
||||||
printFunnelWarning(sc)
|
printFunnelWarning(sc)
|
||||||
|
@ -665,20 +672,37 @@ func printTCPStatusTree(ctx context.Context, sc *ipn.ServeConfig, st *ipnstate.S
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func printWebStatusTree(sc *ipn.ServeConfig, hp ipn.HostPort) {
|
func (e *serveEnv) printWebStatusTree(sc *ipn.ServeConfig, hp ipn.HostPort) error {
|
||||||
|
// No-op if no serve config
|
||||||
if sc == nil {
|
if sc == nil {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
fStatus := "tailnet only"
|
fStatus := "tailnet only"
|
||||||
if sc.AllowFunnel[hp] {
|
if sc.AllowFunnel[hp] {
|
||||||
fStatus = "Funnel on"
|
fStatus = "Funnel on"
|
||||||
}
|
}
|
||||||
host, portStr, _ := net.SplitHostPort(string(hp))
|
host, portStr, _ := net.SplitHostPort(string(hp))
|
||||||
if portStr == "443" {
|
|
||||||
printf("https://%s (%s)\n", host, fStatus)
|
port, err := parseServePort(portStr)
|
||||||
} else {
|
if err != nil {
|
||||||
printf("https://%s:%s (%s)\n", host, portStr, fStatus)
|
return fmt.Errorf("invalid port %q: %w", portStr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scheme := "https"
|
||||||
|
if sc.IsServingHTTP(port) {
|
||||||
|
scheme = "http"
|
||||||
|
}
|
||||||
|
|
||||||
|
portPart := ":" + portStr
|
||||||
|
if scheme == "http" && portStr == "80" ||
|
||||||
|
scheme == "https" && portStr == "443" {
|
||||||
|
portPart = ""
|
||||||
|
}
|
||||||
|
if scheme == "http" {
|
||||||
|
hostname, _, _ := strings.Cut("host", ".")
|
||||||
|
printf("%s://%s%s (%s)\n", scheme, hostname, portPart, fStatus)
|
||||||
|
}
|
||||||
|
printf("%s://%s%s (%s)\n", scheme, host, portPart, fStatus)
|
||||||
srvTypeAndDesc := func(h *ipn.HTTPHandler) (string, string) {
|
srvTypeAndDesc := func(h *ipn.HTTPHandler) (string, string) {
|
||||||
switch {
|
switch {
|
||||||
case h.Path != "":
|
case h.Path != "":
|
||||||
|
@ -705,6 +729,8 @@ func printWebStatusTree(sc *ipn.ServeConfig, hp ipn.HostPort) {
|
||||||
t, d := srvTypeAndDesc(h)
|
t, d := srvTypeAndDesc(h)
|
||||||
printf("%s %s%s %-5s %s\n", "|--", m, strings.Repeat(" ", maxLen-len(m)), t, d)
|
printf("%s %s%s %-5s %s\n", "|--", m, strings.Repeat(" ", maxLen-len(m)), t, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func elipticallyTruncate(s string, max int) string {
|
func elipticallyTruncate(s string, max int) string {
|
||||||
|
@ -725,3 +751,16 @@ func (e *serveEnv) runServeReset(ctx context.Context, args []string) error {
|
||||||
sc := new(ipn.ServeConfig)
|
sc := new(ipn.ServeConfig)
|
||||||
return e.lc.SetServeConfig(ctx, sc)
|
return e.lc.SetServeConfig(ctx, sc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseServePort parses a port number from a string and returns it as a
|
||||||
|
// uint16. It returns an error if the port number is invalid or zero.
|
||||||
|
func parseServePort(s string) (uint16, error) {
|
||||||
|
p, err := strconv.ParseUint(s, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if p == 0 {
|
||||||
|
return 0, errors.New("port number must be non-zero")
|
||||||
|
}
|
||||||
|
return uint16(p), nil
|
||||||
|
}
|
||||||
|
|
|
@ -89,6 +89,59 @@ func TestServeConfigMutations(t *testing.T) {
|
||||||
wantErr: exactErr(flag.ErrHelp, "flag.ErrHelp"),
|
wantErr: exactErr(flag.ErrHelp, "flag.ErrHelp"),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// https
|
||||||
|
add(step{reset: true})
|
||||||
|
add(step{ // allow omitting port (default to 80)
|
||||||
|
command: cmd("http / http://localhost:3000"),
|
||||||
|
want: &ipn.ServeConfig{
|
||||||
|
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}},
|
||||||
|
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
||||||
|
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
|
||||||
|
"/": {Proxy: "http://127.0.0.1:3000"},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
add(step{ // support non Funnel port
|
||||||
|
command: cmd("http:9999 /abc http://localhost:3001"),
|
||||||
|
want: &ipn.ServeConfig{
|
||||||
|
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}, 9999: {HTTP: true}},
|
||||||
|
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
||||||
|
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
|
||||||
|
"/": {Proxy: "http://127.0.0.1:3000"},
|
||||||
|
}},
|
||||||
|
"foo.test.ts.net:9999": {Handlers: map[string]*ipn.HTTPHandler{
|
||||||
|
"/abc": {Proxy: "http://127.0.0.1:3001"},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
add(step{
|
||||||
|
command: cmd("http:9999 /abc off"),
|
||||||
|
want: &ipn.ServeConfig{
|
||||||
|
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}},
|
||||||
|
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
||||||
|
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
|
||||||
|
"/": {Proxy: "http://127.0.0.1:3000"},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
add(step{
|
||||||
|
command: cmd("http:8080 /abc http://127.0.0.1:3001"),
|
||||||
|
want: &ipn.ServeConfig{
|
||||||
|
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}, 8080: {HTTP: true}},
|
||||||
|
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
||||||
|
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
|
||||||
|
"/": {Proxy: "http://127.0.0.1:3000"},
|
||||||
|
}},
|
||||||
|
"foo.test.ts.net:8080": {Handlers: map[string]*ipn.HTTPHandler{
|
||||||
|
"/abc": {Proxy: "http://127.0.0.1:3001"},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// https
|
// https
|
||||||
add(step{reset: true})
|
add(step{reset: true})
|
||||||
add(step{
|
add(step{
|
||||||
|
|
|
@ -103,6 +103,7 @@ func (src *TCPPortHandler) Clone() *TCPPortHandler {
|
||||||
// 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 _TCPPortHandlerCloneNeedsRegeneration = TCPPortHandler(struct {
|
var _TCPPortHandlerCloneNeedsRegeneration = TCPPortHandler(struct {
|
||||||
HTTPS bool
|
HTTPS bool
|
||||||
|
HTTP bool
|
||||||
TCPForward string
|
TCPForward string
|
||||||
TerminateTLS string
|
TerminateTLS string
|
||||||
}{})
|
}{})
|
||||||
|
|
|
@ -228,12 +228,14 @@ func (v *TCPPortHandlerView) UnmarshalJSON(b []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v TCPPortHandlerView) HTTPS() bool { return v.ж.HTTPS }
|
func (v TCPPortHandlerView) HTTPS() bool { return v.ж.HTTPS }
|
||||||
|
func (v TCPPortHandlerView) HTTP() bool { return v.ж.HTTP }
|
||||||
func (v TCPPortHandlerView) TCPForward() string { return v.ж.TCPForward }
|
func (v TCPPortHandlerView) TCPForward() string { return v.ж.TCPForward }
|
||||||
func (v TCPPortHandlerView) TerminateTLS() string { return v.ж.TerminateTLS }
|
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.
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
var _TCPPortHandlerViewNeedsRegeneration = TCPPortHandler(struct {
|
var _TCPPortHandlerViewNeedsRegeneration = TCPPortHandler(struct {
|
||||||
HTTPS bool
|
HTTPS bool
|
||||||
|
HTTP bool
|
||||||
TCPForward string
|
TCPForward string
|
||||||
TerminateTLS string
|
TerminateTLS string
|
||||||
}{})
|
}{})
|
||||||
|
|
|
@ -4129,6 +4129,10 @@ func (b *LocalBackend) setServeProxyHandlersLocked() {
|
||||||
b.serveConfig.Web().Range(func(_ ipn.HostPort, conf ipn.WebServerConfigView) (cont bool) {
|
b.serveConfig.Web().Range(func(_ ipn.HostPort, conf ipn.WebServerConfigView) (cont bool) {
|
||||||
conf.Handlers().Range(func(_ string, h ipn.HTTPHandlerView) (cont bool) {
|
conf.Handlers().Range(func(_ string, h ipn.HTTPHandlerView) (cont bool) {
|
||||||
backend := h.Proxy()
|
backend := h.Proxy()
|
||||||
|
if backend == "" {
|
||||||
|
// Only create proxy handlers for servers with a proxy backend.
|
||||||
|
return true
|
||||||
|
}
|
||||||
mak.Set(&backends, backend, true)
|
mak.Set(&backends, backend, true)
|
||||||
if _, ok := b.serveProxyHandlers.Load(backend); ok {
|
if _, ok := b.serveProxyHandlers.Load(backend); ok {
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -332,11 +332,8 @@ func (b *LocalBackend) tcpHandlerForServe(dport uint16, srcAddr netip.AddrPort)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if tcph.HTTPS() {
|
if tcph.HTTPS() || tcph.HTTP() {
|
||||||
hs := &http.Server{
|
hs := &http.Server{
|
||||||
TLSConfig: &tls.Config{
|
|
||||||
GetCertificate: b.getTLSServeCertForPort(dport),
|
|
||||||
},
|
|
||||||
Handler: http.HandlerFunc(b.serveWebHandler),
|
Handler: http.HandlerFunc(b.serveWebHandler),
|
||||||
BaseContext: func(_ net.Listener) context.Context {
|
BaseContext: func(_ net.Listener) context.Context {
|
||||||
return context.WithValue(context.Background(), serveHTTPContextKey{}, &serveHTTPContext{
|
return context.WithValue(context.Background(), serveHTTPContextKey{}, &serveHTTPContext{
|
||||||
|
@ -345,8 +342,17 @@ func (b *LocalBackend) tcpHandlerForServe(dport uint16, srcAddr netip.AddrPort)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
if tcph.HTTPS() {
|
||||||
|
hs.TLSConfig = &tls.Config{
|
||||||
|
GetCertificate: b.getTLSServeCertForPort(dport),
|
||||||
|
}
|
||||||
|
return func(c net.Conn) error {
|
||||||
|
return hs.ServeTLS(netutil.NewOneConnListener(c, nil), "", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return func(c net.Conn) error {
|
return func(c net.Conn) error {
|
||||||
return hs.ServeTLS(netutil.NewOneConnListener(c, nil), "", "")
|
return hs.Serve(netutil.NewOneConnListener(c, nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,8 +412,14 @@ func getServeHTTPContext(r *http.Request) (c *serveHTTPContext, ok bool) {
|
||||||
func (b *LocalBackend) getServeHandler(r *http.Request) (_ ipn.HTTPHandlerView, at string, ok bool) {
|
func (b *LocalBackend) getServeHandler(r *http.Request) (_ ipn.HTTPHandlerView, at string, ok bool) {
|
||||||
var z ipn.HTTPHandlerView // zero value
|
var z ipn.HTTPHandlerView // zero value
|
||||||
|
|
||||||
|
hostname := r.Host
|
||||||
if r.TLS == nil {
|
if r.TLS == nil {
|
||||||
return z, "", false
|
tcd := "." + b.Status().CurrentTailnet.MagicDNSSuffix
|
||||||
|
if !strings.HasSuffix(hostname, tcd) {
|
||||||
|
hostname += tcd
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hostname = r.TLS.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
sctx, ok := getServeHTTPContext(r)
|
sctx, ok := getServeHTTPContext(r)
|
||||||
|
@ -415,7 +427,7 @@ func (b *LocalBackend) getServeHandler(r *http.Request) (_ ipn.HTTPHandlerView,
|
||||||
b.logf("[unexpected] localbackend: no serveHTTPContext in request")
|
b.logf("[unexpected] localbackend: no serveHTTPContext in request")
|
||||||
return z, "", false
|
return z, "", false
|
||||||
}
|
}
|
||||||
wsc, ok := b.webServerConfig(r.TLS.ServerName, sctx.DestPort)
|
wsc, ok := b.webServerConfig(hostname, sctx.DestPort)
|
||||||
if !ok {
|
if !ok {
|
||||||
return z, "", false
|
return z, "", false
|
||||||
}
|
}
|
||||||
|
@ -472,7 +484,9 @@ func (b *LocalBackend) proxyHandlerForBackend(backend string) (*httputil.Reverse
|
||||||
|
|
||||||
func addProxyForwardedHeaders(r *httputil.ProxyRequest) {
|
func addProxyForwardedHeaders(r *httputil.ProxyRequest) {
|
||||||
r.Out.Header.Set("X-Forwarded-Host", r.In.Host)
|
r.Out.Header.Set("X-Forwarded-Host", r.In.Host)
|
||||||
r.Out.Header.Set("X-Forwarded-Proto", "https")
|
if r.In.TLS != nil {
|
||||||
|
r.Out.Header.Set("X-Forwarded-Proto", "https")
|
||||||
|
}
|
||||||
if c, ok := getServeHTTPContext(r.Out); ok {
|
if c, ok := getServeHTTPContext(r.Out); ok {
|
||||||
r.Out.Header.Set("X-Forwarded-For", c.SrcAddr.Addr().String())
|
r.Out.Header.Set("X-Forwarded-For", c.SrcAddr.Addr().String())
|
||||||
}
|
}
|
||||||
|
@ -634,8 +648,8 @@ func allNumeric(s string) bool {
|
||||||
return s != ""
|
return s != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) webServerConfig(sniName string, port uint16) (c ipn.WebServerConfigView, ok bool) {
|
func (b *LocalBackend) webServerConfig(hostname string, port uint16) (c ipn.WebServerConfigView, ok bool) {
|
||||||
key := ipn.HostPort(fmt.Sprintf("%s:%v", sniName, port))
|
key := ipn.HostPort(fmt.Sprintf("%s:%v", hostname, port))
|
||||||
|
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
48
ipn/serve.go
48
ipn/serve.go
|
@ -76,6 +76,12 @@ type TCPPortHandler struct {
|
||||||
// It is mutually exclusive with TCPForward.
|
// It is mutually exclusive with TCPForward.
|
||||||
HTTPS bool `json:",omitempty"`
|
HTTPS bool `json:",omitempty"`
|
||||||
|
|
||||||
|
// HTTP, if true, means that tailscaled should handle this connection as an
|
||||||
|
// HTTP request as configured by ServeConfig.Web.
|
||||||
|
//
|
||||||
|
// It is mutually exclusive with TCPForward.
|
||||||
|
HTTP bool `json:",omitempty"`
|
||||||
|
|
||||||
// TCPForward is the IP:port to forward TCP connections to.
|
// TCPForward is the IP:port to forward TCP connections to.
|
||||||
// Whether or not TLS is terminated by tailscaled depends on
|
// Whether or not TLS is terminated by tailscaled depends on
|
||||||
// TerminateTLS.
|
// TerminateTLS.
|
||||||
|
@ -103,7 +109,7 @@ type HTTPHandler struct {
|
||||||
// temporary ones? Error codes? Redirects?
|
// temporary ones? Error codes? Redirects?
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebHandlerExists checks if the ServeConfig Web handler exists for
|
// WebHandlerExists reports whether if the ServeConfig Web handler exists for
|
||||||
// the given host:port and mount point.
|
// the given host:port and mount point.
|
||||||
func (sc *ServeConfig) WebHandlerExists(hp HostPort, mount string) bool {
|
func (sc *ServeConfig) WebHandlerExists(hp HostPort, mount string) bool {
|
||||||
h := sc.GetWebHandler(hp, mount)
|
h := sc.GetWebHandler(hp, mount)
|
||||||
|
@ -128,9 +134,8 @@ func (sc *ServeConfig) GetTCPPortHandler(port uint16) *TCPPortHandler {
|
||||||
return sc.TCP[port]
|
return sc.TCP[port]
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTCPForwardingAny checks if ServeConfig is currently forwarding
|
// IsTCPForwardingAny reports whether ServeConfig is currently forwarding in
|
||||||
// in TCPForward mode on any port.
|
// TCPForward mode on any port. This is exclusive of Web/HTTPS serving.
|
||||||
// This is exclusive of Web/HTTPS serving.
|
|
||||||
func (sc *ServeConfig) IsTCPForwardingAny() bool {
|
func (sc *ServeConfig) IsTCPForwardingAny() bool {
|
||||||
if sc == nil || len(sc.TCP) == 0 {
|
if sc == nil || len(sc.TCP) == 0 {
|
||||||
return false
|
return false
|
||||||
|
@ -143,34 +148,47 @@ func (sc *ServeConfig) IsTCPForwardingAny() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTCPForwardingOnPort checks if ServeConfig is currently forwarding
|
// IsTCPForwardingOnPort reports whether if ServeConfig is currently forwarding
|
||||||
// in TCPForward mode on the given port.
|
// in TCPForward mode on the given port. This is exclusive of Web/HTTPS serving.
|
||||||
// This is exclusive of Web/HTTPS serving.
|
|
||||||
func (sc *ServeConfig) IsTCPForwardingOnPort(port uint16) bool {
|
func (sc *ServeConfig) IsTCPForwardingOnPort(port uint16) bool {
|
||||||
if sc == nil || sc.TCP[port] == nil {
|
if sc == nil || sc.TCP[port] == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return !sc.TCP[port].HTTPS
|
return !sc.IsServingWeb(port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsServingWeb checks if ServeConfig is currently serving
|
// IsServingWeb reports whether if ServeConfig is currently serving Web
|
||||||
// Web/HTTPS on the given port.
|
// (HTTP/HTTPS) on the given port. This is exclusive of TCPForwarding.
|
||||||
// This is exclusive of TCPForwarding.
|
|
||||||
func (sc *ServeConfig) IsServingWeb(port uint16) bool {
|
func (sc *ServeConfig) IsServingWeb(port uint16) bool {
|
||||||
|
return sc.IsServingHTTP(port) || sc.IsServingHTTPS(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsServingHTTPS reports whether if ServeConfig is currently serving HTTPS on
|
||||||
|
// the given port. This is exclusive of HTTP and TCPForwarding.
|
||||||
|
func (sc *ServeConfig) IsServingHTTPS(port uint16) bool {
|
||||||
if sc == nil || sc.TCP[port] == nil {
|
if sc == nil || sc.TCP[port] == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return sc.TCP[port].HTTPS
|
return sc.TCP[port].HTTPS
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsFunnelOn checks if ServeConfig is currently allowing
|
// IsServingHTTP reports whether if ServeConfig is currently serving HTTP on the
|
||||||
// funnel traffic for any host:port.
|
// given port. This is exclusive of HTTPS and TCPForwarding.
|
||||||
|
func (sc *ServeConfig) IsServingHTTP(port uint16) bool {
|
||||||
|
if sc == nil || sc.TCP[port] == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return sc.TCP[port].HTTP
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFunnelOn reports whether if ServeConfig is currently allowing funnel
|
||||||
|
// traffic for any host:port.
|
||||||
//
|
//
|
||||||
// View version of ServeConfig.IsFunnelOn.
|
// View version of ServeConfig.IsFunnelOn.
|
||||||
func (v ServeConfigView) IsFunnelOn() bool { return v.ж.IsFunnelOn() }
|
func (v ServeConfigView) IsFunnelOn() bool { return v.ж.IsFunnelOn() }
|
||||||
|
|
||||||
// IsFunnelOn checks if ServeConfig is currently allowing
|
// IsFunnelOn reports whether if ServeConfig is currently allowing funnel
|
||||||
// funnel traffic for any host:port.
|
// traffic for any host:port.
|
||||||
func (sc *ServeConfig) IsFunnelOn() bool {
|
func (sc *ServeConfig) IsFunnelOn() bool {
|
||||||
if sc == nil {
|
if sc == nil {
|
||||||
return false
|
return false
|
||||||
|
|
Loading…
Reference in New Issue