Browse Source

ipn/ipnlocal/serve: remove grant header truncation logic

Given that we filter based on the usercaps argument now, truncation
should not be necessary anymore.

Updates tailscale/corp/#28372

Signed-off-by: Gesa Stupperich <[email protected]>
Gesa Stupperich 4 months ago
parent
commit
d6fa899eba

+ 1 - 1
cmd/tailscale/cli/serve_legacy.go

@@ -173,7 +173,7 @@ type serveEnv struct {
 	service          tailcfg.ServiceName      // service name
 	tun              bool                     // redirect traffic to OS for service
 	allServices      bool                     // apply config file to all services
-	userCaps         []tailcfg.PeerCapability // user capabilities to forward
+	acceptAppCaps    []tailcfg.PeerCapability // app capabilities to forward
 
 	lc localServeClient // localClient interface, specific to serve
 	// optional stuff for tests:

+ 8 - 8
cmd/tailscale/cli/serve_v2.go

@@ -96,12 +96,12 @@ func (b *bgBoolFlag) String() string {
 	return strconv.FormatBool(b.Value)
 }
 
-type userCapsFlag struct {
+type acceptAppCapsFlag struct {
 	Value *[]tailcfg.PeerCapability
 }
 
-// Set appends s to the list of userCaps.
-func (u *userCapsFlag) Set(s string) error {
+// Set appends s to the list of appCaps to accept.
+func (u *acceptAppCapsFlag) Set(s string) error {
 	if s == "" {
 		return nil
 	}
@@ -109,8 +109,8 @@ func (u *userCapsFlag) Set(s string) error {
 	return nil
 }
 
-// String returns the string representation of the userCaps slice.
-func (u *userCapsFlag) String() string {
+// String returns the string representation of the slice of appCaps to accept.
+func (u *acceptAppCapsFlag) String() string {
 	s := make([]string, len(*u.Value))
 	for i, v := range *u.Value {
 		s[i] = string(v)
@@ -221,7 +221,7 @@ func newServeV2Command(e *serveEnv, subcmd serveMode) *ffcli.Command {
 			fs.UintVar(&e.https, "https", 0, "Expose an HTTPS server at the specified port (default mode)")
 			if subcmd == serve {
 				fs.UintVar(&e.http, "http", 0, "Expose an HTTP server at the specified port")
-				fs.Var(&userCapsFlag{Value: &e.userCaps}, "usercaps", "User capability to forward to the server (can be specified multiple times)")
+				fs.Var(&acceptAppCapsFlag{Value: &e.acceptAppCaps}, "accept-app-caps", "App capability to forward to the server (can be specified multiple times)")
 			}
 			fs.UintVar(&e.tcp, "tcp", 0, "Expose a TCP forwarder to forward raw TCP packets at the specified port")
 			fs.UintVar(&e.tlsTerminatedTCP, "tls-terminated-tcp", 0, "Expose a TCP forwarder to forward TLS-terminated TCP packets at the specified port")
@@ -492,7 +492,7 @@ func (e *serveEnv) runServeCombined(subcmd serveMode) execFunc {
 			if len(args) > 0 {
 				target = args[0]
 			}
-			err = e.setServe(sc, dnsName, srvType, srvPort, mount, target, funnel, magicDNSSuffix, e.userCaps)
+			err = e.setServe(sc, dnsName, srvType, srvPort, mount, target, funnel, magicDNSSuffix, e.acceptAppCaps)
 			msg = e.messageForPort(sc, st, dnsName, srvType, srvPort)
 		}
 		if err != nil {
@@ -1141,7 +1141,7 @@ func (e *serveEnv) applyWebServe(sc *ipn.ServeConfig, dnsName string, srvPort ui
 			return err
 		}
 		h.Proxy = t
-		h.UserCaps = caps
+		h.AcceptAppCaps = caps
 	}
 
 	// TODO: validation needs to check nested foreground configs

+ 9 - 9
cmd/tailscale/cli/serve_v2_test.go

@@ -861,42 +861,42 @@ func TestServeDevConfigMutations(t *testing.T) {
 			name: "forward_grant_header",
 			steps: []step{
 				{
-					command: cmd("serve --bg --usercaps=example.com/cap/foo 3000"),
+					command: cmd("serve --bg --accept-app-caps=example.com/cap/foo 3000"),
 					want: &ipn.ServeConfig{
 						TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
 						Web: map[ipn.HostPort]*ipn.WebServerConfig{
 							"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
 								"/": {
-									Proxy:    "http://127.0.0.1:3000",
-									UserCaps: []tailcfg.PeerCapability{"example.com/cap/foo"},
+									Proxy:         "http://127.0.0.1:3000",
+									AcceptAppCaps: []tailcfg.PeerCapability{"example.com/cap/foo"},
 								},
 							}},
 						},
 					},
 				},
 				{
-					command: cmd("serve --bg --usercaps=example.com/cap/foo --usercaps=example.com/cap/bar 3000"),
+					command: cmd("serve --bg --accept-app-caps=example.com/cap/foo --accept-app-caps=example.com/cap/bar 3000"),
 					want: &ipn.ServeConfig{
 						TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
 						Web: map[ipn.HostPort]*ipn.WebServerConfig{
 							"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
 								"/": {
-									Proxy:    "http://127.0.0.1:3000",
-									UserCaps: []tailcfg.PeerCapability{"example.com/cap/foo", "example.com/cap/bar"},
+									Proxy:         "http://127.0.0.1:3000",
+									AcceptAppCaps: []tailcfg.PeerCapability{"example.com/cap/foo", "example.com/cap/bar"},
 								},
 							}},
 						},
 					},
 				},
 				{
-					command: cmd("serve --bg --usercaps=example.com/cap/bar 3000"),
+					command: cmd("serve --bg --accept-app-caps=example.com/cap/bar 3000"),
 					want: &ipn.ServeConfig{
 						TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
 						Web: map[ipn.HostPort]*ipn.WebServerConfig{
 							"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
 								"/": {
-									Proxy:    "http://127.0.0.1:3000",
-									UserCaps: []tailcfg.PeerCapability{"example.com/cap/bar"},
+									Proxy:         "http://127.0.0.1:3000",
+									AcceptAppCaps: []tailcfg.PeerCapability{"example.com/cap/bar"},
 								},
 							}},
 						},

+ 5 - 5
ipn/ipn_clone.go

@@ -232,16 +232,16 @@ func (src *HTTPHandler) Clone() *HTTPHandler {
 	}
 	dst := new(HTTPHandler)
 	*dst = *src
-	dst.UserCaps = append(src.UserCaps[:0:0], src.UserCaps...)
+	dst.AcceptAppCaps = append(src.AcceptAppCaps[:0:0], src.AcceptAppCaps...)
 	return dst
 }
 
 // A compilation failure here means this code must be regenerated, with the command at the top of this file.
 var _HTTPHandlerCloneNeedsRegeneration = HTTPHandler(struct {
-	Path     string
-	Proxy    string
-	Text     string
-	UserCaps []tailcfg.PeerCapability
+	Path          string
+	Proxy         string
+	Text          string
+	AcceptAppCaps []tailcfg.PeerCapability
 }{})
 
 // Clone makes a deep copy of WebServerConfig.

+ 6 - 6
ipn/ipn_view.go

@@ -892,16 +892,16 @@ func (v HTTPHandlerView) Proxy() string { return v.ж.Proxy }
 func (v HTTPHandlerView) Text() string { return v.ж.Text }
 
 // peer capabilities to forward in grant header, e.g. example.com/cap/mon
-func (v HTTPHandlerView) UserCaps() views.Slice[tailcfg.PeerCapability] {
-	return views.SliceOf(v.ж.UserCaps)
+func (v HTTPHandlerView) AcceptAppCaps() views.Slice[tailcfg.PeerCapability] {
+	return views.SliceOf(v.ж.AcceptAppCaps)
 }
 
 // A compilation failure here means this code must be regenerated, with the command at the top of this file.
 var _HTTPHandlerViewNeedsRegeneration = HTTPHandler(struct {
-	Path     string
-	Proxy    string
-	Text     string
-	UserCaps []tailcfg.PeerCapability
+	Path          string
+	Proxy         string
+	Text          string
+	AcceptAppCaps []tailcfg.PeerCapability
 }{})
 
 // View returns a read-only view of WebServerConfig.

+ 6 - 31
ipn/ipnlocal/serve.go

@@ -65,7 +65,6 @@ func init() {
 const (
 	contentTypeHeader   = "Content-Type"
 	grpcBaseContentType = "application/grpc"
-	grantHeaderMaxSize  = 15360 // 15 KiB
 )
 
 // ErrETagMismatch signals that the given
@@ -932,7 +931,7 @@ func encTailscaleHeaderValue(v string) string {
 }
 
 func (b *LocalBackend) addTailscaleGrantHeader(r *httputil.ProxyRequest) {
-	r.Out.Header.Del("Tailscale-User-Capabilities")
+	r.Out.Header.Del("Tailscale-App-Capabilities")
 
 	c, ok := serveHTTPContextKey.ValueOk(r.Out.Context())
 	if !ok || c.Funnel != nil {
@@ -954,37 +953,13 @@ func (b *LocalBackend) addTailscaleGrantHeader(r *httputil.ProxyRequest) {
 		}
 	}
 
-	serialized, truncated, err := serializeUpToNBytes(peerCapsFiltered, grantHeaderMaxSize)
+	peerCapsSerialized, err := json.Marshal(peerCapsFiltered)
 	if err != nil {
-		b.logf("serve: failed to serialize PeerCapMap: %v", err)
+		b.logf("serve: failed to serialize filtered PeerCapMap: %v", err)
 		return
 	}
-	if truncated {
-		b.logf("serve: serialized PeerCapMap exceeds %d bytes, forwarding truncated PeerCapMap", grantHeaderMaxSize)
-	}
-
-	r.Out.Header.Set("Tailscale-User-Capabilities", encTailscaleHeaderValue(serialized))
-}
 
-// serializeUpToNBytes serializes capMap. It arbitrarily truncates entries from the capMap
-// if the size of the serialized capMap would exceed N bytes.
-func serializeUpToNBytes(capMap tailcfg.PeerCapMap, N int) (string, bool, error) {
-	numBytes := 0
-	capped := false
-	result := tailcfg.PeerCapMap{}
-	for k, v := range capMap {
-		numBytes += len(k) + len(v)
-		if numBytes > N {
-			capped = true
-			break
-		}
-		result[k] = v
-	}
-	marshalled, err := json.Marshal(result)
-	if err != nil {
-		return "", false, err
-	}
-	return string(marshalled), capped, nil
+	r.Out.Header.Set("Tailscale-App-Capabilities", encTailscaleHeaderValue(string(peerCapsSerialized)))
 }
 
 // serveWebHandler is an http.HandlerFunc that maps incoming requests to the
@@ -1010,12 +985,12 @@ func (b *LocalBackend) serveWebHandler(w http.ResponseWriter, r *http.Request) {
 			http.Error(w, "unknown proxy destination", http.StatusInternalServerError)
 			return
 		}
-		// Inject user capabilities to forward into the request context
+		// Inject app capabilities to forward into the request context
 		c, ok := serveHTTPContextKey.ValueOk(r.Context())
 		if !ok {
 			return
 		}
-		c.PeerCapsFilter = h.UserCaps()
+		c.PeerCapsFilter = h.AcceptAppCaps()
 		h := p.(http.Handler)
 		// Trim the mount point from the URL path before proxying. (#6571)
 		if r.URL.Path != "/" {

+ 5 - 91
ipn/ipnlocal/serve_test.go

@@ -828,8 +828,8 @@ func TestServeHTTPProxyGrantHeader(t *testing.T) {
 		Web: map[ipn.HostPort]*ipn.WebServerConfig{
 			"example.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
 				"/": {
-					Proxy:    testServ.URL,
-					UserCaps: []tailcfg.PeerCapability{"example.com/cap/interesting", "example.com/cap/boring"},
+					Proxy:         testServ.URL,
+					AcceptAppCaps: []tailcfg.PeerCapability{"example.com/cap/interesting", "example.com/cap/boring"},
 				},
 			}},
 		},
@@ -858,7 +858,7 @@ func TestServeHTTPProxyGrantHeader(t *testing.T) {
 				{"Tailscale-User-Name", "Some One"},
 				{"Tailscale-User-Profile-Pic", "https://example.com/photo.jpg"},
 				{"Tailscale-Headers-Info", "https://tailscale.com/s/serve-headers"},
-				{"Tailscale-User-Capabilities", `{"example.com/cap/interesting":[{"role":"🐿"}]}`},
+				{"Tailscale-App-Capabilities", `{"example.com/cap/interesting":[{"role":"🐿"}]}`},
 			},
 		},
 		{
@@ -871,7 +871,7 @@ func TestServeHTTPProxyGrantHeader(t *testing.T) {
 				{"Tailscale-User-Name", ""},
 				{"Tailscale-User-Profile-Pic", ""},
 				{"Tailscale-Headers-Info", ""},
-				{"Tailscale-User-Capabilities", `{"example.com/cap/boring":[{"role":"Viewer"}]}`},
+				{"Tailscale-App-Capabilities", `{"example.com/cap/boring":[{"role":"Viewer"}]}`},
 			},
 		},
 		{
@@ -884,7 +884,7 @@ func TestServeHTTPProxyGrantHeader(t *testing.T) {
 				{"Tailscale-User-Name", ""},
 				{"Tailscale-User-Profile-Pic", ""},
 				{"Tailscale-Headers-Info", ""},
-				{"Tailscale-User-Capabilities", ""},
+				{"Tailscale-App-Capabilities", ""},
 			},
 		},
 	}
@@ -1327,89 +1327,3 @@ func TestServeGRPCProxy(t *testing.T) {
 		})
 	}
 }
-
-func TestSerialisePeerCapMap(t *testing.T) {
-	var tests = []struct {
-		name                string
-		capMap              tailcfg.PeerCapMap
-		maxNumBytes         int
-		wantOneOfSerialized []string
-		wantTruncated       bool
-	}{
-		{
-			name:                "empty cap map",
-			capMap:              tailcfg.PeerCapMap{},
-			maxNumBytes:         50,
-			wantOneOfSerialized: []string{"{}"},
-			wantTruncated:       false,
-		},
-		{
-			name: "cap map with one capability",
-			capMap: tailcfg.PeerCapMap{
-				"tailscale.com/cap/kubernetes": []tailcfg.RawMessage{
-					`{"impersonate": {"groups": ["tailnet-readers"]}}`,
-				},
-			},
-			maxNumBytes: 50,
-			wantOneOfSerialized: []string{
-				`{"tailscale.com/cap/kubernetes":[{"impersonate":{"groups":["tailnet-readers"]}}]}`,
-			},
-			wantTruncated: false,
-		},
-		{
-			name: "cap map with two capabilities",
-			capMap: tailcfg.PeerCapMap{
-				"foo.com/cap/something": []tailcfg.RawMessage{
-					`{"role": "Admin"}`,
-				},
-				"bar.com/cap/other-thing": []tailcfg.RawMessage{
-					`{"role": "Viewer"}`,
-				},
-			},
-			maxNumBytes: 50,
-			// Both cap map entries will be included, but they could appear in any order.
-			wantOneOfSerialized: []string{
-				`{"foo.com/cap/something":[{"role":"Admin"}],"bar.com/cap/other-thing":[{"role":"Viewer"}]}`,
-				`{"bar.com/cap/other-thing":[{"role":"Viewer"}],"foo.com/cap/something":[{"role":"Admin"}]}`,
-			},
-			wantTruncated: false,
-		},
-		{
-			name: "cap map that should be truncated to stay within size limits",
-			capMap: tailcfg.PeerCapMap{
-				"foo.com/cap/something": []tailcfg.RawMessage{
-					`{"role": "Admin"}`,
-				},
-				"bar.com/cap/other-thing": []tailcfg.RawMessage{
-					`{"role": "Viewer"}`,
-				},
-			},
-			maxNumBytes: 40,
-			// Only one cap map entry will be included, but we don't know which one.
-			wantOneOfSerialized: []string{
-				`{"foo.com/cap/something":[{"role":"Admin"}]}`,
-				`{"bar.com/cap/other-thing":[{"role":"Viewer"}]}`,
-			},
-			wantTruncated: true,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			gotSerialized, gotCapped, err := serializeUpToNBytes(tt.capMap, tt.maxNumBytes)
-
-			if err != nil {
-				t.Fatal(err)
-			}
-			if gotCapped != tt.wantTruncated {
-				t.Errorf("got %t, want %t", gotCapped, tt.wantTruncated)
-			}
-			for _, wantSerialized := range tt.wantOneOfSerialized {
-				if gotSerialized == wantSerialized {
-					return
-				}
-			}
-			t.Errorf("want one of %v, got %q", tt.wantOneOfSerialized, gotSerialized)
-		})
-	}
-}

+ 1 - 1
ipn/serve.go

@@ -160,7 +160,7 @@ type HTTPHandler struct {
 
 	Text string `json:",omitempty"` // plaintext to serve (primarily for testing)
 
-	UserCaps []tailcfg.PeerCapability `json:",omitempty"` // peer capabilities to forward in grant header, e.g. example.com/cap/mon
+	AcceptAppCaps []tailcfg.PeerCapability `json:",omitempty"` // peer capabilities to forward in grant header, e.g. example.com/cap/mon
 
 	// TODO(bradfitz): bool to not enumerate directories? TTL on mapping for
 	// temporary ones? Error codes? Redirects?