mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-01 09:32:08 +00:00
cmd/tailscale/cli: error when advertising a Service from an untagged node (#17577)
Service hosts must be tagged nodes, meaning it is only valid to advertise a Service from a machine which has at least one ACL tag. Fixes tailscale/corp#33197 Signed-off-by: Harry Harpham <harry@tailscale.com>
This commit is contained in:
@@ -860,6 +860,7 @@ type fakeLocalServeClient struct {
|
||||
setCount int // counts calls to SetServeConfig
|
||||
queryFeatureResponse *mockQueryFeatureResponse // mock response to QueryFeature calls
|
||||
prefs *ipn.Prefs // fake preferences, used to test GetPrefs and SetPrefs
|
||||
statusWithoutPeers *ipnstate.Status // nil for fakeStatus
|
||||
}
|
||||
|
||||
// fakeStatus is a fake ipnstate.Status value for tests.
|
||||
@@ -880,7 +881,10 @@ var fakeStatus = &ipnstate.Status{
|
||||
}
|
||||
|
||||
func (lc *fakeLocalServeClient) StatusWithoutPeers(ctx context.Context) (*ipnstate.Status, error) {
|
||||
return fakeStatus, nil
|
||||
if lc.statusWithoutPeers == nil {
|
||||
return fakeStatus, nil
|
||||
}
|
||||
return lc.statusWithoutPeers, nil
|
||||
}
|
||||
|
||||
func (lc *fakeLocalServeClient) GetServeConfig(ctx context.Context) (*ipn.ServeConfig, error) {
|
||||
|
||||
@@ -420,6 +420,10 @@ func (e *serveEnv) runServeCombined(subcmd serveMode) execFunc {
|
||||
svcName = e.service
|
||||
dnsName = e.service.String()
|
||||
}
|
||||
tagged := st.Self.Tags != nil && st.Self.Tags.Len() > 0
|
||||
if forService && !tagged && !turnOff {
|
||||
return errors.New("service hosts must be tagged nodes")
|
||||
}
|
||||
if !forService && srvType == serveTypeTUN {
|
||||
return errors.New("tun mode is only supported for services")
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/views"
|
||||
)
|
||||
|
||||
func TestServeDevConfigMutations(t *testing.T) {
|
||||
@@ -33,10 +34,11 @@ func TestServeDevConfigMutations(t *testing.T) {
|
||||
}
|
||||
|
||||
// group is a group of steps that share the same
|
||||
// config mutation, but always starts from an empty config
|
||||
// config mutation
|
||||
type group struct {
|
||||
name string
|
||||
steps []step
|
||||
name string
|
||||
steps []step
|
||||
initialState fakeLocalServeClient // use the zero value for empty config
|
||||
}
|
||||
|
||||
// creaet a temporary directory for path-based destinations
|
||||
@@ -814,17 +816,58 @@ func TestServeDevConfigMutations(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "advertise_service",
|
||||
initialState: fakeLocalServeClient{
|
||||
statusWithoutPeers: &ipnstate.Status{
|
||||
BackendState: ipn.Running.String(),
|
||||
Self: &ipnstate.PeerStatus{
|
||||
DNSName: "foo.test.ts.net",
|
||||
CapMap: tailcfg.NodeCapMap{
|
||||
tailcfg.NodeAttrFunnel: nil,
|
||||
tailcfg.CapabilityFunnelPorts + "?ports=443,8443": nil,
|
||||
},
|
||||
Tags: ptrToReadOnlySlice([]string{"some-tag"}),
|
||||
},
|
||||
CurrentTailnet: &ipnstate.TailnetStatus{MagicDNSSuffix: "test.ts.net"},
|
||||
},
|
||||
},
|
||||
steps: []step{{
|
||||
command: cmd("serve --service=svc:foo --http=80 text:foo"),
|
||||
want: &ipn.ServeConfig{
|
||||
Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{
|
||||
"svc:foo": {
|
||||
TCP: map[uint16]*ipn.TCPPortHandler{
|
||||
80: {HTTP: true},
|
||||
},
|
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
||||
"foo.test.ts.net:80": {Handlers: map[string]*ipn.HTTPHandler{
|
||||
"/": {Text: "foo"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "advertise_service_from_untagged_node",
|
||||
steps: []step{{
|
||||
command: cmd("serve --service=svc:foo --http=80 text:foo"),
|
||||
wantErr: anyErr(),
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
t.Run(group.name, func(t *testing.T) {
|
||||
lc := &fakeLocalServeClient{}
|
||||
lc := group.initialState
|
||||
for i, st := range group.steps {
|
||||
var stderr bytes.Buffer
|
||||
var stdout bytes.Buffer
|
||||
var flagOut bytes.Buffer
|
||||
e := &serveEnv{
|
||||
lc: lc,
|
||||
lc: &lc,
|
||||
testFlagOut: &flagOut,
|
||||
testStdout: &stdout,
|
||||
testStderr: &stderr,
|
||||
@@ -2249,3 +2292,8 @@ func exactErrMsg(want error) func(error) string {
|
||||
return fmt.Sprintf("\ngot: %v\nwant: %v\n", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func ptrToReadOnlySlice[T any](s []T) *views.Slice[T] {
|
||||
vs := views.SliceOf(s)
|
||||
return &vs
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user