mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-06 01:41:28 +01:00
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 <gesa@tailscale.com>
This commit is contained in:
parent
576aacd459
commit
d6fa899eba
@ -173,7 +173,7 @@ type serveEnv struct {
|
|||||||
service tailcfg.ServiceName // service name
|
service tailcfg.ServiceName // service name
|
||||||
tun bool // redirect traffic to OS for service
|
tun bool // redirect traffic to OS for service
|
||||||
allServices bool // apply config file to all services
|
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
|
lc localServeClient // localClient interface, specific to serve
|
||||||
// optional stuff for tests:
|
// optional stuff for tests:
|
||||||
|
|||||||
@ -96,12 +96,12 @@ func (b *bgBoolFlag) String() string {
|
|||||||
return strconv.FormatBool(b.Value)
|
return strconv.FormatBool(b.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
type userCapsFlag struct {
|
type acceptAppCapsFlag struct {
|
||||||
Value *[]tailcfg.PeerCapability
|
Value *[]tailcfg.PeerCapability
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set appends s to the list of userCaps.
|
// Set appends s to the list of appCaps to accept.
|
||||||
func (u *userCapsFlag) Set(s string) error {
|
func (u *acceptAppCapsFlag) Set(s string) error {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -109,8 +109,8 @@ func (u *userCapsFlag) Set(s string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the string representation of the userCaps slice.
|
// String returns the string representation of the slice of appCaps to accept.
|
||||||
func (u *userCapsFlag) String() string {
|
func (u *acceptAppCapsFlag) String() string {
|
||||||
s := make([]string, len(*u.Value))
|
s := make([]string, len(*u.Value))
|
||||||
for i, v := range *u.Value {
|
for i, v := range *u.Value {
|
||||||
s[i] = string(v)
|
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)")
|
fs.UintVar(&e.https, "https", 0, "Expose an HTTPS server at the specified port (default mode)")
|
||||||
if subcmd == serve {
|
if subcmd == serve {
|
||||||
fs.UintVar(&e.http, "http", 0, "Expose an HTTP server at the specified port")
|
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.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")
|
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 {
|
if len(args) > 0 {
|
||||||
target = 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)
|
msg = e.messageForPort(sc, st, dnsName, srvType, srvPort)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1141,7 +1141,7 @@ func (e *serveEnv) applyWebServe(sc *ipn.ServeConfig, dnsName string, srvPort ui
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
h.Proxy = t
|
h.Proxy = t
|
||||||
h.UserCaps = caps
|
h.AcceptAppCaps = caps
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: validation needs to check nested foreground configs
|
// TODO: validation needs to check nested foreground configs
|
||||||
|
|||||||
@ -861,42 +861,42 @@ func TestServeDevConfigMutations(t *testing.T) {
|
|||||||
name: "forward_grant_header",
|
name: "forward_grant_header",
|
||||||
steps: []step{
|
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{
|
want: &ipn.ServeConfig{
|
||||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
|
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
|
||||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
||||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
|
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
|
||||||
"/": {
|
"/": {
|
||||||
Proxy: "http://127.0.0.1:3000",
|
Proxy: "http://127.0.0.1:3000",
|
||||||
UserCaps: []tailcfg.PeerCapability{"example.com/cap/foo"},
|
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{
|
want: &ipn.ServeConfig{
|
||||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
|
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
|
||||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
||||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
|
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
|
||||||
"/": {
|
"/": {
|
||||||
Proxy: "http://127.0.0.1:3000",
|
Proxy: "http://127.0.0.1:3000",
|
||||||
UserCaps: []tailcfg.PeerCapability{"example.com/cap/foo", "example.com/cap/bar"},
|
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{
|
want: &ipn.ServeConfig{
|
||||||
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
|
TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}},
|
||||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
||||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
|
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
|
||||||
"/": {
|
"/": {
|
||||||
Proxy: "http://127.0.0.1:3000",
|
Proxy: "http://127.0.0.1:3000",
|
||||||
UserCaps: []tailcfg.PeerCapability{"example.com/cap/bar"},
|
AcceptAppCaps: []tailcfg.PeerCapability{"example.com/cap/bar"},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -232,16 +232,16 @@ func (src *HTTPHandler) Clone() *HTTPHandler {
|
|||||||
}
|
}
|
||||||
dst := new(HTTPHandler)
|
dst := new(HTTPHandler)
|
||||||
*dst = *src
|
*dst = *src
|
||||||
dst.UserCaps = append(src.UserCaps[:0:0], src.UserCaps...)
|
dst.AcceptAppCaps = append(src.AcceptAppCaps[:0:0], src.AcceptAppCaps...)
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 _HTTPHandlerCloneNeedsRegeneration = HTTPHandler(struct {
|
var _HTTPHandlerCloneNeedsRegeneration = HTTPHandler(struct {
|
||||||
Path string
|
Path string
|
||||||
Proxy string
|
Proxy string
|
||||||
Text string
|
Text string
|
||||||
UserCaps []tailcfg.PeerCapability
|
AcceptAppCaps []tailcfg.PeerCapability
|
||||||
}{})
|
}{})
|
||||||
|
|
||||||
// Clone makes a deep copy of WebServerConfig.
|
// Clone makes a deep copy of WebServerConfig.
|
||||||
|
|||||||
@ -892,16 +892,16 @@ func (v HTTPHandlerView) Proxy() string { return v.ж.Proxy }
|
|||||||
func (v HTTPHandlerView) Text() string { return v.ж.Text }
|
func (v HTTPHandlerView) Text() string { return v.ж.Text }
|
||||||
|
|
||||||
// peer capabilities to forward in grant header, e.g. example.com/cap/mon
|
// peer capabilities to forward in grant header, e.g. example.com/cap/mon
|
||||||
func (v HTTPHandlerView) UserCaps() views.Slice[tailcfg.PeerCapability] {
|
func (v HTTPHandlerView) AcceptAppCaps() views.Slice[tailcfg.PeerCapability] {
|
||||||
return views.SliceOf(v.ж.UserCaps)
|
return views.SliceOf(v.ж.AcceptAppCaps)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
Text string
|
||||||
UserCaps []tailcfg.PeerCapability
|
AcceptAppCaps []tailcfg.PeerCapability
|
||||||
}{})
|
}{})
|
||||||
|
|
||||||
// View returns a read-only view of WebServerConfig.
|
// View returns a read-only view of WebServerConfig.
|
||||||
|
|||||||
@ -65,7 +65,6 @@ func init() {
|
|||||||
const (
|
const (
|
||||||
contentTypeHeader = "Content-Type"
|
contentTypeHeader = "Content-Type"
|
||||||
grpcBaseContentType = "application/grpc"
|
grpcBaseContentType = "application/grpc"
|
||||||
grantHeaderMaxSize = 15360 // 15 KiB
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrETagMismatch signals that the given
|
// ErrETagMismatch signals that the given
|
||||||
@ -932,7 +931,7 @@ func encTailscaleHeaderValue(v string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) addTailscaleGrantHeader(r *httputil.ProxyRequest) {
|
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())
|
c, ok := serveHTTPContextKey.ValueOk(r.Out.Context())
|
||||||
if !ok || c.Funnel != nil {
|
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 {
|
if err != nil {
|
||||||
b.logf("serve: failed to serialize PeerCapMap: %v", err)
|
b.logf("serve: failed to serialize filtered PeerCapMap: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if truncated {
|
|
||||||
b.logf("serve: serialized PeerCapMap exceeds %d bytes, forwarding truncated PeerCapMap", grantHeaderMaxSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Out.Header.Set("Tailscale-User-Capabilities", encTailscaleHeaderValue(serialized))
|
r.Out.Header.Set("Tailscale-App-Capabilities", encTailscaleHeaderValue(string(peerCapsSerialized)))
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveWebHandler is an http.HandlerFunc that maps incoming requests to the
|
// 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)
|
http.Error(w, "unknown proxy destination", http.StatusInternalServerError)
|
||||||
return
|
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())
|
c, ok := serveHTTPContextKey.ValueOk(r.Context())
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.PeerCapsFilter = h.UserCaps()
|
c.PeerCapsFilter = h.AcceptAppCaps()
|
||||||
h := p.(http.Handler)
|
h := p.(http.Handler)
|
||||||
// Trim the mount point from the URL path before proxying. (#6571)
|
// Trim the mount point from the URL path before proxying. (#6571)
|
||||||
if r.URL.Path != "/" {
|
if r.URL.Path != "/" {
|
||||||
|
|||||||
@ -828,8 +828,8 @@ func TestServeHTTPProxyGrantHeader(t *testing.T) {
|
|||||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
||||||
"example.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
|
"example.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
|
||||||
"/": {
|
"/": {
|
||||||
Proxy: testServ.URL,
|
Proxy: testServ.URL,
|
||||||
UserCaps: []tailcfg.PeerCapability{"example.com/cap/interesting", "example.com/cap/boring"},
|
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-Name", "Some One"},
|
||||||
{"Tailscale-User-Profile-Pic", "https://example.com/photo.jpg"},
|
{"Tailscale-User-Profile-Pic", "https://example.com/photo.jpg"},
|
||||||
{"Tailscale-Headers-Info", "https://tailscale.com/s/serve-headers"},
|
{"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-Name", ""},
|
||||||
{"Tailscale-User-Profile-Pic", ""},
|
{"Tailscale-User-Profile-Pic", ""},
|
||||||
{"Tailscale-Headers-Info", ""},
|
{"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-Name", ""},
|
||||||
{"Tailscale-User-Profile-Pic", ""},
|
{"Tailscale-User-Profile-Pic", ""},
|
||||||
{"Tailscale-Headers-Info", ""},
|
{"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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -160,7 +160,7 @@ type HTTPHandler struct {
|
|||||||
|
|
||||||
Text string `json:",omitempty"` // plaintext to serve (primarily for testing)
|
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
|
// 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