Skip to content
File

Blob: tun/client/config_test.go

go311 lines
1package client
2 
3import (
4 "os"
5 "runtime"
6 "testing"
7 "time"
8 
9 "github.com/stretchr/testify/require"
10)
11 
12const v1Cfg = `apex: dev.specter.dev:1234
13tunnels:
14 - target: tcp://127.0.0.1:1234
15 hostname: tcp.dev.specter.dev
16`
17 
18const bare = `version: 2
19apex: dev.specter.dev:1234
20tunnels:
21 - target: tcp://127.0.0.1:1234
22 hostname: tcp.dev.specter.dev
23 proxyHeaderTimeout: 60s
24 proxyHeaderHost: blah.com
25`
26 
27const bareHeader = `version: 2
28apex: dev.specter.dev:1234
29tunnels:
30 - target: tcp://127.0.0.1:1234
31 hostname: tcp.dev.specter.dev
32 headerTimeout: 45s
33 headerHost: header.com
34 headerMode: hostname
35`
36 
37const bareMixedHeaders = `version: 2
38apex: dev.specter.dev:1234
39tunnels:
40 - target: tcp://127.0.0.1:1234
41 hostname: tcp.dev.specter.dev
42 proxyHeaderTimeout: 60s
43 proxyHeaderHost: old.com
44 proxyHeaderMode: target
45 headerTimeout: 30s
46 headerHost: new.com
47 headerMode: hostname
48`
49 
50const testPrivateKey = `MC4CAQAwBQYDK2VwBCIEIFXA98L8HvJQxzyqYosZxyaX/G1vfJ4TeSP0E+N0FIfj`
51const registered = `version: 2
52apex: dev.specter.dev:1234
53privKey: |
54 -----BEGIN PRIVATE KEY-----
55 ` + testPrivateKey + `
56 -----END PRIVATE KEY-----
57tunnels:
58 - target: tcp://127.0.0.1:1234
59 hostname: tcp.dev.specter.dev
60 - target: http://127.0.0.1:2813
61 hostname: http.dev.specter.dev
62`
63 
64const namedPipe = `version: 2
65apex: dev.specter.dev:1234
66tunnels:
67 - target: \\.\pipe\something
68 hostname: pipe
69`
70 
71const unixSocket = `version: 2
72apex: dev.specter.dev:1234
73tunnels:
74 - target: unix:///tmp/nginx.sock
75 hostname: unix
76`
77 
78func TestConfig(t *testing.T) {
79 as := require.New(t)
80 
81 bareFile, err := os.CreateTemp("", "client")
82 as.NoError(err)
83 defer os.Remove(bareFile.Name())
84 
85 _, err = bareFile.WriteString(bare)
86 as.NoError(err)
87 as.NoError(bareFile.Close())
88 
89 bareCfg, err := NewConfig(bareFile.Name())
90 as.NoError(err)
91 as.Equal("dev.specter.dev:1234", bareCfg.Apex)
92 as.NotEmpty(bareCfg.PrivKey)
93 bareCfg.buildRouter()
94 as.Equal(1, bareCfg.router.Len())
95 route, ok := bareCfg.router.Load("tcp.dev.specter.dev")
96 as.True(ok)
97 as.Equal(time.Second*60, route.proxyHeaderReadTimeout)
98 as.Equal("blah.com", route.proxyHeaderHost)
99 
100 // new header* names only
101 headerFile, err := os.CreateTemp("", "client")
102 as.NoError(err)
103 defer os.Remove(headerFile.Name())
104 
105 _, err = headerFile.WriteString(bareHeader)
106 as.NoError(err)
107 as.NoError(headerFile.Close())
108 
109 headerCfg, err := NewConfig(headerFile.Name())
110 as.NoError(err)
111 headerCfg.buildRouter()
112 as.Equal(1, headerCfg.router.Len())
113 route, ok = headerCfg.router.Load("tcp.dev.specter.dev")
114 as.True(ok)
115 as.Equal(time.Second*45, route.proxyHeaderReadTimeout)
116 as.Equal("header.com", route.proxyHeaderHost)
117 
118 // mixed legacy and new names: new should win
119 mixedFile, err := os.CreateTemp("", "client")
120 as.NoError(err)
121 defer os.Remove(mixedFile.Name())
122 
123 _, err = mixedFile.WriteString(bareMixedHeaders)
124 as.NoError(err)
125 as.NoError(mixedFile.Close())
126 
127 mixedCfg, err := NewConfig(mixedFile.Name())
128 as.NoError(err)
129 mixedCfg.buildRouter()
130 as.Equal(1, mixedCfg.router.Len())
131 route, ok = mixedCfg.router.Load("tcp.dev.specter.dev")
132 as.True(ok)
133 // headerTimeout 30s should override proxyHeaderTimeout 60s
134 as.Equal(time.Second*30, route.proxyHeaderReadTimeout)
135 // headerHost new.com should override proxyHeaderHost old.com
136 as.Equal("new.com", route.proxyHeaderHost)
137 
138 regFile, err := os.CreateTemp("", "client")
139 as.NoError(err)
140 defer os.Remove(regFile.Name())
141 
142 _, err = regFile.WriteString(registered)
143 as.NoError(err)
144 as.NoError(regFile.Close())
145 
146 regCfg, err := NewConfig(regFile.Name())
147 as.NoError(err)
148 as.Equal("dev.specter.dev:1234", regCfg.Apex)
149 as.Contains(regCfg.PrivKey, testPrivateKey)
150 regCfg.buildRouter()
151 as.Equal(2, regCfg.router.Len())
152 
153 as.NoError(err)
154 regCfg.Tunnels = append(regCfg.Tunnels, Tunnel{
155 Hostname: "https.dev.specter.dev",
156 Target: "https://127.0.0.1",
157 })
158 as.NoError(regCfg.writeFile())
159 
160 regCfg, err = NewConfig(regFile.Name())
161 as.NoError(err)
162 regCfg.buildRouter()
163 as.Equal(3, regCfg.router.Len())
164}
165 
166func TestPipeOrSocket(t *testing.T) {
167 as := require.New(t)
168 
169 pipeFile, err := os.CreateTemp("", "client")
170 as.NoError(err)
171 defer os.Remove(pipeFile.Name())
172 
173 _, err = pipeFile.WriteString(namedPipe)
174 as.NoError(err)
175 as.NoError(pipeFile.Close())
176 
177 pipeCfg, err := NewConfig(pipeFile.Name())
178 if runtime.GOOS == "windows" {
179 as.NoError(err)
180 pipeCfg.buildRouter()
181 as.Equal(1, pipeCfg.router.Len())
182 r, ok := pipeCfg.router.Load("pipe")
183 as.True(ok)
184 u := r.parsed
185 as.Equal("winio", u.Scheme)
186 as.Equal("\\\\.\\pipe\\something", u.Path)
187 } else {
188 as.Error(err)
189 }
190 
191 sockFile, err := os.CreateTemp("", "client")
192 as.NoError(err)
193 defer os.Remove(sockFile.Name())
194 
195 _, err = sockFile.WriteString(unixSocket)
196 as.NoError(err)
197 as.NoError(sockFile.Close())
198 
199 sockCfg, err := NewConfig(sockFile.Name())
200 if runtime.GOOS == "windows" {
201 as.Error(err)
202 } else {
203 as.NoError(err)
204 sockCfg.buildRouter()
205 as.Equal(1, sockCfg.router.Len())
206 r, ok := sockCfg.router.Load("unix")
207 as.True(ok)
208 u := r.parsed
209 as.Equal("unix", u.Scheme)
210 as.Equal("/tmp/nginx.sock", u.Path)
211 }
212}
213 
214func TestRebuild(t *testing.T) {
215 as := require.New(t)
216 
217 regFile, err := os.CreateTemp("", "client")
218 as.NoError(err)
219 defer os.Remove(regFile.Name())
220 
221 _, err = regFile.WriteString(registered)
222 as.NoError(err)
223 as.NoError(regFile.Close())
224 
225 regCfg, err := NewConfig(regFile.Name())
226 as.NoError(err)
227 as.NoError(regCfg.validate())
228 regCfg.buildRouter()
229 as.Equal(2, regCfg.router.Len())
230 
231 drop := 1
232 dropTunnel := regCfg.Tunnels[drop]
233 
234 // drop
235 _, ok := regCfg.router.Load(dropTunnel.Hostname)
236 as.True(ok)
237 regCfg.Tunnels = append(regCfg.Tunnels[:drop], regCfg.Tunnels[drop+1:]...)
238 as.NoError(regCfg.validate())
239 regCfg.buildRouter(dropTunnel)
240 as.Equal(1, regCfg.router.Len())
241 _, ok = regCfg.router.Load(dropTunnel.Hostname)
242 as.False(ok)
243 
244 // update
245 prev := regCfg.Tunnels[0]
246 r, ok := regCfg.router.Load(prev.Hostname)
247 as.True(ok)
248 as.Equal(prev.Target, r.parsed.String())
249 newTarget := "http://127.0.0.1:8080"
250 regCfg.Tunnels[0].Target = newTarget
251 as.NoError(regCfg.validate())
252 regCfg.buildRouter(prev)
253 as.Equal(1, regCfg.router.Len())
254 r, ok = regCfg.router.Load(prev.Hostname)
255 as.True(ok)
256 as.Equal(newTarget, r.parsed.String())
257}
258 
259func TestV1Config(t *testing.T) {
260 as := require.New(t)
261 
262 v1File, err := os.CreateTemp("", "client")
263 as.NoError(err)
264 defer os.Remove(v1File.Name())
265 
266 _, err = v1File.WriteString(v1Cfg)
267 as.NoError(err)
268 as.NoError(v1File.Close())
269 
270 _, err = NewConfig(v1File.Name())
271 as.Error(err)
272}
273 
274func TestProxyHeaderModeValidation(t *testing.T) {
275 as := require.New(t)
276 
277 // invalid mode
278 cfg := &Config{
279 Tunnels: []Tunnel{{
280 Target: "http://127.0.0.1:8080",
281 ProxyHeaderMode: "invalid",
282 }},
283 }
284 err := cfg.validate()
285 as.Error(err)
286 
287 // custom mode without host should fail
288 cfg = &Config{
289 Tunnels: []Tunnel{{
290 Target: "http://127.0.0.1:8080",
291 ProxyHeaderMode: "custom",
292 }},
293 }
294 err = cfg.validate()
295 as.Error(err)
296 
297 // target mode with pipe/unix targets should fail on all platforms
298 pipeTarget := "unix:///tmp/nginx.sock"
299 if runtime.GOOS == "windows" {
300 pipeTarget = `\\.\\pipe\\something`
301 }
302 cfg = &Config{
303 Tunnels: []Tunnel{{
304 Target: pipeTarget,
305 ProxyHeaderMode: "target",
306 }},
307 }
308 err = cfg.validate()
309 as.Error(err)
310}