Skip to content

Commit ac74d28

Browse files
authored
ipn/ipnlocal: add validations when setting serve config (tailscale#17950)
These validations were previously performed in the CLI frontend. There are two motivations for moving these to the local backend: 1. The backend controls synchronization around the relevant state, so only the backend can guarantee many of these validations. 2. Doing these validations in the back-end avoids the need to repeat them across every frontend (e.g. the CLI and tsnet). Updates tailscale/corp#27200 Signed-off-by: Harry Harpham <[email protected]>
1 parent 42a5262 commit ac74d28

File tree

5 files changed

+483
-314
lines changed

5 files changed

+483
-314
lines changed

cmd/tailscale/cli/serve_v2.go

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -478,11 +478,6 @@ func (e *serveEnv) runServeCombined(subcmd serveMode) execFunc {
478478
}
479479
wantFg := !e.bg.Value && !turnOff
480480
if wantFg {
481-
// validate the config before creating a WatchIPNBus session
482-
if err := e.validateConfig(parentSC, srvPort, srvType, svcName); err != nil {
483-
return err
484-
}
485-
486481
// if foreground mode, create a WatchIPNBus session
487482
// and use the nested config for all following operations
488483
// TODO(marwan-at-work): nested-config validations should happen here or previous to this point.
@@ -508,9 +503,6 @@ func (e *serveEnv) runServeCombined(subcmd serveMode) execFunc {
508503
// only unset serve when trying to unset with type and port flags.
509504
err = e.unsetServe(sc, dnsName, srvType, srvPort, mount, magicDNSSuffix)
510505
} else {
511-
if err := e.validateConfig(parentSC, srvPort, srvType, svcName); err != nil {
512-
return err
513-
}
514506
if forService {
515507
e.addServiceToPrefs(ctx, svcName)
516508
}
@@ -907,66 +899,6 @@ func (e *serveEnv) runServeSetConfig(ctx context.Context, args []string) (err er
907899
return e.lc.SetServeConfig(ctx, sc)
908900
}
909901

910-
const backgroundExistsMsg = "background configuration already exists, use `tailscale %s --%s=%d off` to remove the existing configuration"
911-
912-
// validateConfig checks if the serve config is valid to serve the type wanted on the port.
913-
// dnsName is a FQDN or a serviceName (with `svc:` prefix).
914-
func (e *serveEnv) validateConfig(sc *ipn.ServeConfig, port uint16, wantServe serveType, svcName tailcfg.ServiceName) error {
915-
var tcpHandlerForPort *ipn.TCPPortHandler
916-
if svcName != noService {
917-
svc := sc.Services[svcName]
918-
if svc == nil {
919-
return nil
920-
}
921-
if wantServe == serveTypeTUN && (svc.TCP != nil || svc.Web != nil) {
922-
return errors.New("service already has a TCP or Web handler, cannot serve in TUN mode")
923-
}
924-
if svc.Tun && wantServe != serveTypeTUN {
925-
return errors.New("service is already being served in TUN mode")
926-
}
927-
if svc.TCP[port] == nil {
928-
return nil
929-
}
930-
tcpHandlerForPort = svc.TCP[port]
931-
} else {
932-
sc, isFg := sc.FindConfig(port)
933-
if sc == nil {
934-
return nil
935-
}
936-
if isFg {
937-
return errors.New("foreground already exists under this port")
938-
}
939-
if !e.bg.Value {
940-
return fmt.Errorf(backgroundExistsMsg, infoMap[e.subcmd].Name, wantServe.String(), port)
941-
}
942-
tcpHandlerForPort = sc.TCP[port]
943-
}
944-
existingServe := serveFromPortHandler(tcpHandlerForPort)
945-
if wantServe != existingServe {
946-
target := svcName
947-
if target == noService {
948-
target = "machine"
949-
}
950-
return fmt.Errorf("want to serve %q but port is already serving %q for %q", wantServe, existingServe, target)
951-
}
952-
return nil
953-
}
954-
955-
func serveFromPortHandler(tcp *ipn.TCPPortHandler) serveType {
956-
switch {
957-
case tcp.HTTP:
958-
return serveTypeHTTP
959-
case tcp.HTTPS:
960-
return serveTypeHTTPS
961-
case tcp.TerminateTLS != "":
962-
return serveTypeTLSTerminatedTCP
963-
case tcp.TCPForward != "":
964-
return serveTypeTCP
965-
default:
966-
return -1
967-
}
968-
}
969-
970902
func (e *serveEnv) setServe(sc *ipn.ServeConfig, dnsName string, srvType serveType, srvPort uint16, mount string, target string, allowFunnel bool, mds string, caps []tailcfg.PeerCapability, proxyProtocol int) error {
971903
// update serve config based on the type
972904
switch srvType {

cmd/tailscale/cli/serve_v2_test.go

Lines changed: 0 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -819,26 +819,6 @@ func TestServeDevConfigMutations(t *testing.T) {
819819
},
820820
},
821821
},
822-
{
823-
name: "forground_with_bg_conflict",
824-
steps: []step{
825-
{
826-
command: cmd("serve --bg --http=3000 localhost:3000"),
827-
want: &ipn.ServeConfig{
828-
TCP: map[uint16]*ipn.TCPPortHandler{3000: {HTTP: true}},
829-
Web: map[ipn.HostPort]*ipn.WebServerConfig{
830-
"foo.test.ts.net:3000": {Handlers: map[string]*ipn.HTTPHandler{
831-
"/": {Proxy: "http://localhost:3000"},
832-
}},
833-
},
834-
},
835-
},
836-
{
837-
command: cmd("serve --http=3000 localhost:3000"),
838-
wantErr: exactErrMsg(fmt.Errorf(backgroundExistsMsg, "serve", "http", 3000)),
839-
},
840-
},
841-
},
842822
{
843823
name: "advertise_service",
844824
initialState: fakeLocalServeClient{
@@ -1067,190 +1047,6 @@ func TestServeDevConfigMutations(t *testing.T) {
10671047
}
10681048
}
10691049

1070-
func TestValidateConfig(t *testing.T) {
1071-
tests := [...]struct {
1072-
name string
1073-
desc string
1074-
cfg *ipn.ServeConfig
1075-
svc tailcfg.ServiceName
1076-
servePort uint16
1077-
serveType serveType
1078-
bg bgBoolFlag
1079-
wantErr bool
1080-
}{
1081-
{
1082-
name: "nil_config",
1083-
desc: "when config is nil, all requests valid",
1084-
cfg: nil,
1085-
servePort: 3000,
1086-
serveType: serveTypeHTTPS,
1087-
},
1088-
{
1089-
name: "new_bg_tcp",
1090-
desc: "no error when config exists but we're adding a new bg tcp port",
1091-
cfg: &ipn.ServeConfig{
1092-
TCP: map[uint16]*ipn.TCPPortHandler{
1093-
443: {HTTPS: true},
1094-
},
1095-
},
1096-
bg: bgBoolFlag{true, false},
1097-
servePort: 10000,
1098-
serveType: serveTypeHTTPS,
1099-
},
1100-
{
1101-
name: "override_bg_tcp",
1102-
desc: "no error when overwriting previous port under the same serve type",
1103-
cfg: &ipn.ServeConfig{
1104-
TCP: map[uint16]*ipn.TCPPortHandler{
1105-
443: {TCPForward: "http://localhost:4545"},
1106-
},
1107-
},
1108-
bg: bgBoolFlag{true, false},
1109-
servePort: 443,
1110-
serveType: serveTypeTCP,
1111-
},
1112-
{
1113-
name: "override_bg_tcp",
1114-
desc: "error when overwriting previous port under a different serve type",
1115-
cfg: &ipn.ServeConfig{
1116-
TCP: map[uint16]*ipn.TCPPortHandler{
1117-
443: {HTTPS: true},
1118-
},
1119-
},
1120-
bg: bgBoolFlag{true, false},
1121-
servePort: 443,
1122-
serveType: serveTypeHTTP,
1123-
wantErr: true,
1124-
},
1125-
{
1126-
name: "new_fg_port",
1127-
desc: "no error when serving a new foreground port",
1128-
cfg: &ipn.ServeConfig{
1129-
TCP: map[uint16]*ipn.TCPPortHandler{
1130-
443: {HTTPS: true},
1131-
},
1132-
Foreground: map[string]*ipn.ServeConfig{
1133-
"abc123": {
1134-
TCP: map[uint16]*ipn.TCPPortHandler{
1135-
3000: {HTTPS: true},
1136-
},
1137-
},
1138-
},
1139-
},
1140-
servePort: 4040,
1141-
serveType: serveTypeTCP,
1142-
},
1143-
{
1144-
name: "same_fg_port",
1145-
desc: "error when overwriting a previous fg port",
1146-
cfg: &ipn.ServeConfig{
1147-
Foreground: map[string]*ipn.ServeConfig{
1148-
"abc123": {
1149-
TCP: map[uint16]*ipn.TCPPortHandler{
1150-
3000: {HTTPS: true},
1151-
},
1152-
},
1153-
},
1154-
},
1155-
servePort: 3000,
1156-
serveType: serveTypeTCP,
1157-
wantErr: true,
1158-
},
1159-
{
1160-
name: "new_service_tcp",
1161-
desc: "no error when adding a new service port",
1162-
cfg: &ipn.ServeConfig{
1163-
Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{
1164-
"svc:foo": {
1165-
TCP: map[uint16]*ipn.TCPPortHandler{80: {HTTP: true}},
1166-
},
1167-
},
1168-
},
1169-
svc: "svc:foo",
1170-
servePort: 8080,
1171-
serveType: serveTypeTCP,
1172-
},
1173-
{
1174-
name: "override_service_tcp",
1175-
desc: "no error when overwriting a previous service port",
1176-
cfg: &ipn.ServeConfig{
1177-
Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{
1178-
"svc:foo": {
1179-
TCP: map[uint16]*ipn.TCPPortHandler{
1180-
443: {TCPForward: "http://localhost:4545"},
1181-
},
1182-
},
1183-
},
1184-
},
1185-
svc: "svc:foo",
1186-
servePort: 443,
1187-
serveType: serveTypeTCP,
1188-
},
1189-
{
1190-
name: "override_service_tcp",
1191-
desc: "error when overwriting a previous service port with a different serve type",
1192-
cfg: &ipn.ServeConfig{
1193-
Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{
1194-
"svc:foo": {
1195-
TCP: map[uint16]*ipn.TCPPortHandler{
1196-
443: {HTTPS: true},
1197-
},
1198-
},
1199-
},
1200-
},
1201-
svc: "svc:foo",
1202-
servePort: 443,
1203-
serveType: serveTypeHTTP,
1204-
wantErr: true,
1205-
},
1206-
{
1207-
name: "override_service_tcp",
1208-
desc: "error when setting previous tcp service to tun mode",
1209-
cfg: &ipn.ServeConfig{
1210-
Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{
1211-
"svc:foo": {
1212-
TCP: map[uint16]*ipn.TCPPortHandler{
1213-
443: {TCPForward: "http://localhost:4545"},
1214-
},
1215-
},
1216-
},
1217-
},
1218-
svc: "svc:foo",
1219-
serveType: serveTypeTUN,
1220-
wantErr: true,
1221-
},
1222-
{
1223-
name: "override_service_tun",
1224-
desc: "error when setting previous tun service to tcp forwarder",
1225-
cfg: &ipn.ServeConfig{
1226-
Services: map[tailcfg.ServiceName]*ipn.ServiceConfig{
1227-
"svc:foo": {
1228-
Tun: true,
1229-
},
1230-
},
1231-
},
1232-
svc: "svc:foo",
1233-
serveType: serveTypeTCP,
1234-
servePort: 443,
1235-
wantErr: true,
1236-
},
1237-
}
1238-
1239-
for _, tc := range tests {
1240-
t.Run(tc.name, func(t *testing.T) {
1241-
se := serveEnv{bg: tc.bg}
1242-
err := se.validateConfig(tc.cfg, tc.servePort, tc.serveType, tc.svc)
1243-
if err == nil && tc.wantErr {
1244-
t.Fatal("expected an error but got nil")
1245-
}
1246-
if err != nil && !tc.wantErr {
1247-
t.Fatalf("expected no error but got: %v", err)
1248-
}
1249-
})
1250-
}
1251-
1252-
}
1253-
12541050
func TestSrcTypeFromFlags(t *testing.T) {
12551051
tests := []struct {
12561052
name string

0 commit comments

Comments
 (0)