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
|
setCount int // counts calls to SetServeConfig
|
||||||
queryFeatureResponse *mockQueryFeatureResponse // mock response to QueryFeature calls
|
queryFeatureResponse *mockQueryFeatureResponse // mock response to QueryFeature calls
|
||||||
prefs *ipn.Prefs // fake preferences, used to test GetPrefs and SetPrefs
|
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.
|
// 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) {
|
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) {
|
func (lc *fakeLocalServeClient) GetServeConfig(ctx context.Context) (*ipn.ServeConfig, error) {
|
||||||
|
|||||||
@@ -420,6 +420,10 @@ func (e *serveEnv) runServeCombined(subcmd serveMode) execFunc {
|
|||||||
svcName = e.service
|
svcName = e.service
|
||||||
dnsName = e.service.String()
|
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 {
|
if !forService && srvType == serveTypeTUN {
|
||||||
return errors.New("tun mode is only supported for services")
|
return errors.New("tun mode is only supported for services")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
|
"tailscale.com/types/views"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServeDevConfigMutations(t *testing.T) {
|
func TestServeDevConfigMutations(t *testing.T) {
|
||||||
@@ -33,10 +34,11 @@ func TestServeDevConfigMutations(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// group is a group of steps that share the same
|
// group is a group of steps that share the same
|
||||||
// config mutation, but always starts from an empty config
|
// config mutation
|
||||||
type group struct {
|
type group struct {
|
||||||
name string
|
name string
|
||||||
steps []step
|
steps []step
|
||||||
|
initialState fakeLocalServeClient // use the zero value for empty config
|
||||||
}
|
}
|
||||||
|
|
||||||
// creaet a temporary directory for path-based destinations
|
// 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 {
|
for _, group := range groups {
|
||||||
t.Run(group.name, func(t *testing.T) {
|
t.Run(group.name, func(t *testing.T) {
|
||||||
lc := &fakeLocalServeClient{}
|
lc := group.initialState
|
||||||
for i, st := range group.steps {
|
for i, st := range group.steps {
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
var flagOut bytes.Buffer
|
var flagOut bytes.Buffer
|
||||||
e := &serveEnv{
|
e := &serveEnv{
|
||||||
lc: lc,
|
lc: &lc,
|
||||||
testFlagOut: &flagOut,
|
testFlagOut: &flagOut,
|
||||||
testStdout: &stdout,
|
testStdout: &stdout,
|
||||||
testStderr: &stderr,
|
testStderr: &stderr,
|
||||||
@@ -2249,3 +2292,8 @@ func exactErrMsg(want error) func(error) string {
|
|||||||
return fmt.Sprintf("\ngot: %v\nwant: %v\n", got, want)
|
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