Signed-off-by: Gesa Stupperich <gesa@tailscale.com>
knyar/serve-grants-headers
Gesa Stupperich 3 months ago committed by Anton Tolchanov
parent 312582bdbf
commit 03f5511342

@ -1,6 +1,6 @@
module gokrazy/build/tsapp
go 1.23.1
go 1.25.1
replace tailscale.com => ../../../..

@ -1,6 +1,6 @@
module gokrazy/build/tsapp
go 1.23.1
go 1.25.1
replace tailscale.com => ../../../..

@ -806,6 +806,7 @@ func (rp *reverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.Out.Host = r.In.Host
addProxyForwardedHeaders(r)
rp.lb.addTailscaleIdentityHeaders(r)
rp.lb.addCustomGrantHeaders(r)
}}
// There is no way to autodetect h2c as per RFC 9113
@ -912,6 +913,31 @@ func (b *LocalBackend) addTailscaleIdentityHeaders(r *httputil.ProxyRequest) {
r.Out.Header.Set("Tailscale-Headers-Info", "https://tailscale.com/s/serve-headers")
}
func (b *LocalBackend) addCustomGrantHeaders(r *httputil.ProxyRequest) {
c, ok := serveHTTPContextKey.ValueOk(r.Out.Context())
if !ok {
return
}
peerCaps := b.PeerCaps(c.SrcAddr.Addr())
// TODO: make configurable
capType := tailcfg.PeerCapability("neinkeinkaffee.com/cap/grafana")
type capStruct struct {
Role string `json:"role,omitempty"`
}
unmarshalled, err := tailcfg.UnmarshalCapJSON[capStruct](peerCaps, capType)
if err != nil {
b.logf("couldn't parse capability %s: %v", capType, err)
return
}
if len(unmarshalled) > 0 {
// TODO: make configurable
value := unmarshalled[0].Role
r.Out.Header.Set("Tailscale-User-Role", encTailscaleHeaderValue(value))
}
}
// encTailscaleHeaderValue cleans or encodes as necessary v, to be suitable in
// an HTTP header value. See
// https://github.com/tailscale/tailscale/issues/11603.

@ -23,6 +23,7 @@ import (
"path/filepath"
"reflect"
"strings"
"tailscale.com/types/views"
"testing"
"time"
@ -699,6 +700,9 @@ func TestServeHTTPProxyHeaders(t *testing.T) {
want string
}
peercaps := b.PeerCaps(netip.MustParsePrefix("100.150.151.152/32").Addr())
_ = peercaps
tests := []struct {
name string
srcIP string
@ -714,6 +718,7 @@ func TestServeHTTPProxyHeaders(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-Role", "Admin"},
},
},
{
@ -925,6 +930,9 @@ func newTestBackend(t *testing.T, opts ...any) *LocalBackend {
b.currentNode().SetNetMap(&netmap.NetworkMap{
SelfNode: (&tailcfg.Node{
Name: "example.ts.net",
Addresses: []netip.Prefix{
netip.MustParsePrefix("100.150.151.151/32"),
},
}).View(),
UserProfiles: map[tailcfg.UserID]tailcfg.UserProfileView{
tailcfg.UserID(1): (&tailcfg.UserProfile{
@ -933,6 +941,19 @@ func newTestBackend(t *testing.T, opts ...any) *LocalBackend {
ProfilePicURL: "https://example.com/photo.jpg",
}).View(),
},
PacketFilterRules: views.SliceOf([]tailcfg.FilterRule{{
SrcIPs: []string{"100.150.151.152"}, // first peer in the list below, the one without tags
CapGrant: []tailcfg.CapGrant{{
Dsts: []netip.Prefix{
netip.MustParsePrefix("100.150.151.151/32"), // TODO: try anywhere instead?
},
CapMap: tailcfg.PeerCapMap{
"neinkeinkaffee.com/cap/grafana": []tailcfg.RawMessage{
"{\"role\":[\"Admin\"]}",
},
},
}},
}}),
Peers: []tailcfg.NodeView{
(&tailcfg.Node{
ID: 152,
@ -942,6 +963,9 @@ func newTestBackend(t *testing.T, opts ...any) *LocalBackend {
Addresses: []netip.Prefix{
netip.MustParsePrefix("100.150.151.152/32"),
},
CapMap: map[tailcfg.NodeCapability][]tailcfg.RawMessage{
"neinkeinkaffee.com/cap/grafana": {"{\"role\":[\"Admin\"]}"},
},
}).View(),
(&tailcfg.Node{
ID: 153,

Loading…
Cancel
Save