config_test.go 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048
  1. package config
  2. import (
  3. "reflect"
  4. "slices"
  5. "strings"
  6. "testing"
  7. "golang.org/x/text/encoding"
  8. "golang.org/x/text/encoding/charmap"
  9. )
  10. func TestParseListenersCfg(t *testing.T) {
  11. tests := []struct {
  12. name string
  13. bosListeners []string
  14. bosAdvertisedListeners []string
  15. bosAdvertisedHostsSSL []string
  16. kerberosListeners []string
  17. want []ListenerGroup
  18. wantErr bool
  19. errContains string
  20. bosListenersSSL []string
  21. }{
  22. {
  23. name: "valid single listener with kerberos",
  24. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  25. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  26. bosAdvertisedHostsSSL: []string{},
  27. kerberosListeners: []string{"LOCAL://0.0.0.0:1088"},
  28. want: []ListenerGroup{
  29. {
  30. Name: "local",
  31. BOSListenAddress: "0.0.0.0:5190",
  32. BOSAdvertisedHostPlain: "127.0.0.1:5190",
  33. KerberosListenAddress: "0.0.0.0:1088",
  34. },
  35. },
  36. wantErr: false,
  37. },
  38. {
  39. name: "valid single listener without kerberos",
  40. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  41. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  42. bosAdvertisedHostsSSL: []string{},
  43. kerberosListeners: []string{},
  44. want: []ListenerGroup{
  45. {
  46. Name: "local",
  47. BOSListenAddress: "0.0.0.0:5190",
  48. BOSAdvertisedHostPlain: "127.0.0.1:5190",
  49. KerberosListenAddress: "",
  50. },
  51. },
  52. wantErr: false,
  53. },
  54. {
  55. name: "valid multiple listeners with mixed kerberos",
  56. bosListeners: []string{"LAN://192.168.1.10:5190", "WAN://0.0.0.0:5191"},
  57. bosAdvertisedListeners: []string{"LAN://192.168.1.10:5190", "WAN://example.com:5191"},
  58. bosAdvertisedHostsSSL: []string{},
  59. kerberosListeners: []string{"LAN://192.168.1.10:1088"},
  60. want: []ListenerGroup{
  61. {
  62. Name: "lan",
  63. BOSListenAddress: "192.168.1.10:5190",
  64. BOSAdvertisedHostPlain: "192.168.1.10:5190",
  65. KerberosListenAddress: "192.168.1.10:1088",
  66. },
  67. {
  68. Name: "wan",
  69. BOSListenAddress: "0.0.0.0:5191",
  70. BOSAdvertisedHostPlain: "example.com:5191",
  71. KerberosListenAddress: "",
  72. },
  73. },
  74. wantErr: false,
  75. },
  76. {
  77. name: "missing BOS advertised host",
  78. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  79. bosAdvertisedListeners: []string{},
  80. bosAdvertisedHostsSSL: []string{},
  81. kerberosListeners: []string{},
  82. want: nil,
  83. wantErr: true,
  84. errContains: "missing BOS advertise address",
  85. },
  86. {
  87. name: "missing BOS listen address",
  88. bosListeners: []string{},
  89. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  90. bosAdvertisedHostsSSL: []string{},
  91. kerberosListeners: []string{},
  92. want: nil,
  93. wantErr: true,
  94. errContains: "missing BOS listen address for listener `local://`",
  95. },
  96. {
  97. name: "duplicate listener definition in BOS",
  98. bosListeners: []string{"LOCAL://0.0.0.0:5190", "LOCAL://0.0.0.0:5191"},
  99. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  100. bosAdvertisedHostsSSL: []string{},
  101. kerberosListeners: []string{},
  102. want: nil,
  103. wantErr: true,
  104. errContains: "duplicate listener definition",
  105. },
  106. {
  107. name: "duplicate listener definition in advertised",
  108. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  109. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190", "LOCAL://127.0.0.1:5191"},
  110. bosAdvertisedHostsSSL: []string{},
  111. kerberosListeners: []string{},
  112. want: nil,
  113. wantErr: true,
  114. errContains: "duplicate listener definition",
  115. },
  116. {
  117. name: "duplicate listener definition in kerberos",
  118. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  119. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  120. bosAdvertisedHostsSSL: []string{},
  121. kerberosListeners: []string{"LOCAL://0.0.0.0:1088", "LOCAL://0.0.0.0:1089"},
  122. want: nil,
  123. wantErr: true,
  124. errContains: "duplicate listener definition",
  125. },
  126. {
  127. name: "invalid URI format in BOS",
  128. bosListeners: []string{"invalid-uri"},
  129. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  130. bosAdvertisedHostsSSL: []string{},
  131. kerberosListeners: []string{},
  132. want: nil,
  133. wantErr: true,
  134. errContains: "missing scheme. Valid format",
  135. },
  136. {
  137. name: "invalid URI format in advertised",
  138. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  139. bosAdvertisedListeners: []string{"invalid-uri"},
  140. bosAdvertisedHostsSSL: []string{},
  141. kerberosListeners: []string{},
  142. want: nil,
  143. wantErr: true,
  144. errContains: "missing scheme. Valid format",
  145. },
  146. {
  147. name: "invalid URI format in kerberos",
  148. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  149. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  150. bosAdvertisedHostsSSL: []string{},
  151. kerberosListeners: []string{"invalid-uri"},
  152. want: nil,
  153. wantErr: true,
  154. errContains: "missing scheme. Valid format",
  155. },
  156. {
  157. name: "URI with underscore in scheme",
  158. bosListeners: []string{"LOCAL_://0.0.0.0:5190"},
  159. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  160. bosAdvertisedHostsSSL: []string{},
  161. kerberosListeners: []string{},
  162. want: nil,
  163. wantErr: true,
  164. errContains: "Valid format: SCHEME://HOST:PORT",
  165. },
  166. {
  167. name: "BOS listener missing port",
  168. bosListeners: []string{"LOCAL://0.0.0.0"},
  169. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  170. bosAdvertisedHostsSSL: []string{},
  171. kerberosListeners: []string{},
  172. want: nil,
  173. wantErr: true,
  174. errContains: "missing port",
  175. },
  176. {
  177. name: "BOS listener missing host",
  178. bosListeners: []string{"LOCAL://:5190"},
  179. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  180. bosAdvertisedHostsSSL: []string{},
  181. kerberosListeners: []string{},
  182. want: nil,
  183. wantErr: true,
  184. errContains: "missing host",
  185. },
  186. {
  187. name: "complex multi-listener setup",
  188. bosListeners: []string{"LAN://192.168.1.10:5190", "WAN://0.0.0.0:5191", "DOCKER://172.17.0.1:5192"},
  189. bosAdvertisedListeners: []string{"DOCKER://172.17.0.1:5192", "LAN://192.168.1.10:5190", "WAN://example.com:5191"},
  190. bosAdvertisedHostsSSL: []string{},
  191. kerberosListeners: []string{"WAN://0.0.0.0:1089", "LAN://192.168.1.10:1088"},
  192. want: []ListenerGroup{
  193. {
  194. Name: "docker",
  195. BOSListenAddress: "172.17.0.1:5192",
  196. BOSAdvertisedHostPlain: "172.17.0.1:5192",
  197. BOSAdvertisedHostSSL: "",
  198. KerberosListenAddress: "",
  199. },
  200. {
  201. Name: "lan",
  202. BOSListenAddress: "192.168.1.10:5190",
  203. BOSAdvertisedHostPlain: "192.168.1.10:5190",
  204. BOSAdvertisedHostSSL: "",
  205. KerberosListenAddress: "192.168.1.10:1088",
  206. },
  207. {
  208. Name: "wan",
  209. BOSListenAddress: "0.0.0.0:5191",
  210. BOSAdvertisedHostPlain: "example.com:5191",
  211. BOSAdvertisedHostSSL: "",
  212. KerberosListenAddress: "0.0.0.0:1089",
  213. },
  214. },
  215. wantErr: false,
  216. },
  217. {
  218. name: "empty strings for all inputs",
  219. bosListeners: []string{},
  220. bosAdvertisedListeners: []string{},
  221. bosAdvertisedHostsSSL: []string{},
  222. kerberosListeners: []string{},
  223. want: nil,
  224. wantErr: true,
  225. errContains: "at least one BOS listener is required",
  226. },
  227. {
  228. name: "whitespace-only strings",
  229. bosListeners: []string{" "},
  230. bosAdvertisedListeners: []string{" "},
  231. bosAdvertisedHostsSSL: []string{},
  232. kerberosListeners: []string{" "},
  233. want: nil,
  234. wantErr: true,
  235. errContains: "at least one BOS listener is required",
  236. },
  237. {
  238. name: "only kerberos listeners provided",
  239. bosListeners: []string{},
  240. bosAdvertisedListeners: []string{},
  241. bosAdvertisedHostsSSL: []string{},
  242. kerberosListeners: []string{"LOCAL://0.0.0.0:1088"},
  243. want: nil,
  244. wantErr: true,
  245. errContains: "missing BOS advertise address for listener `local://`",
  246. },
  247. {
  248. name: "valid single listener with SSL",
  249. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  250. bosListenersSSL: []string{"LOCAL://0.0.0.0:5191"},
  251. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  252. bosAdvertisedHostsSSL: []string{"LOCAL://127.0.0.1:5193"},
  253. kerberosListeners: []string{},
  254. want: []ListenerGroup{
  255. {
  256. Name: "local",
  257. BOSListenAddress: "0.0.0.0:5190",
  258. BOSListenAddressSSL: "0.0.0.0:5191",
  259. BOSAdvertisedHostPlain: "127.0.0.1:5190",
  260. BOSAdvertisedHostSSL: "127.0.0.1:5193",
  261. KerberosListenAddress: "",
  262. },
  263. },
  264. wantErr: false,
  265. },
  266. {
  267. name: "valid multiple listeners with mixed SSL",
  268. bosListeners: []string{"LAN://192.168.1.10:5190", "WAN://0.0.0.0:5191"},
  269. bosListenersSSL: []string{"LAN://192.168.1.10:5195"},
  270. bosAdvertisedListeners: []string{"LAN://192.168.1.10:5190", "WAN://example.com:5191"},
  271. bosAdvertisedHostsSSL: []string{"LAN://192.168.1.10:5193"},
  272. kerberosListeners: []string{},
  273. want: []ListenerGroup{
  274. {
  275. Name: "lan",
  276. BOSListenAddress: "192.168.1.10:5190",
  277. BOSListenAddressSSL: "192.168.1.10:5195",
  278. BOSAdvertisedHostPlain: "192.168.1.10:5190",
  279. BOSAdvertisedHostSSL: "192.168.1.10:5193",
  280. KerberosListenAddress: "",
  281. },
  282. {
  283. Name: "wan",
  284. BOSListenAddress: "0.0.0.0:5191",
  285. BOSAdvertisedHostPlain: "example.com:5191",
  286. BOSAdvertisedHostSSL: "",
  287. KerberosListenAddress: "",
  288. },
  289. },
  290. wantErr: false,
  291. },
  292. {
  293. name: "advertised SSL host without an SSL listen address",
  294. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  295. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  296. bosAdvertisedHostsSSL: []string{"LOCAL://127.0.0.1:5193"},
  297. kerberosListeners: []string{},
  298. want: nil,
  299. wantErr: true,
  300. errContains: "missing SSL BOS listen address for listener `local://`",
  301. },
  302. {
  303. name: "SSL listen address without advertised SSL host",
  304. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  305. bosListenersSSL: []string{"LOCAL://0.0.0.0:5191"},
  306. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  307. bosAdvertisedHostsSSL: []string{},
  308. kerberosListeners: []string{},
  309. want: []ListenerGroup{
  310. {
  311. Name: "local",
  312. BOSListenAddress: "0.0.0.0:5190",
  313. BOSListenAddressSSL: "0.0.0.0:5191",
  314. BOSAdvertisedHostPlain: "127.0.0.1:5190",
  315. BOSAdvertisedHostSSL: "",
  316. KerberosListenAddress: "",
  317. },
  318. },
  319. wantErr: false,
  320. },
  321. {
  322. name: "SSL listener with kerberos",
  323. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  324. bosListenersSSL: []string{"LOCAL://0.0.0.0:5191"},
  325. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  326. bosAdvertisedHostsSSL: []string{"LOCAL://127.0.0.1:5193"},
  327. kerberosListeners: []string{"LOCAL://0.0.0.0:1088"},
  328. want: []ListenerGroup{
  329. {
  330. Name: "local",
  331. BOSListenAddress: "0.0.0.0:5190",
  332. BOSListenAddressSSL: "0.0.0.0:5191",
  333. BOSAdvertisedHostPlain: "127.0.0.1:5190",
  334. BOSAdvertisedHostSSL: "127.0.0.1:5193",
  335. KerberosListenAddress: "0.0.0.0:1088",
  336. },
  337. },
  338. wantErr: false,
  339. },
  340. {
  341. name: "duplicate SSL BOS listen address",
  342. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  343. bosListenersSSL: []string{"LOCAL://0.0.0.0:5191", "LOCAL://0.0.0.0:5192"},
  344. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  345. bosAdvertisedHostsSSL: []string{"LOCAL://127.0.0.1:5193"},
  346. kerberosListeners: []string{},
  347. want: nil,
  348. wantErr: true,
  349. errContains: "duplicate listener definition",
  350. },
  351. {
  352. name: "plaintext and SSL BOS listeners share an address",
  353. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  354. bosListenersSSL: []string{"LOCAL://0.0.0.0:5190"},
  355. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  356. bosAdvertisedHostsSSL: []string{"LOCAL://127.0.0.1:5193"},
  357. kerberosListeners: []string{},
  358. want: nil,
  359. wantErr: true,
  360. errContains: "listen address 0.0.0.0:5190 is configured for both OSCAR_LISTENERS `local://` and OSCAR_LISTENERS_SSL `local://`",
  361. },
  362. {
  363. name: "BOS listeners in different groups share an address",
  364. bosListeners: []string{"LAN://0.0.0.0:5190", "WAN://0.0.0.0:5190"},
  365. bosAdvertisedListeners: []string{"LAN://192.168.1.10:5190", "WAN://example.com:5190"},
  366. bosAdvertisedHostsSSL: []string{},
  367. kerberosListeners: []string{},
  368. want: nil,
  369. wantErr: true,
  370. errContains: "listen address 0.0.0.0:5190 is configured for both OSCAR_LISTENERS `lan://` and OSCAR_LISTENERS `wan://`",
  371. },
  372. {
  373. name: "BOS and kerberos listeners share an address",
  374. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  375. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  376. bosAdvertisedHostsSSL: []string{},
  377. kerberosListeners: []string{"LOCAL://0.0.0.0:5190"},
  378. want: nil,
  379. wantErr: true,
  380. errContains: "listen address 0.0.0.0:5190 is configured for both OSCAR_LISTENERS `local://` and KERBEROS_LISTENERS `local://`",
  381. },
  382. {
  383. name: "SSL BOS listener missing port",
  384. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  385. bosListenersSSL: []string{"LOCAL://0.0.0.0"},
  386. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  387. bosAdvertisedHostsSSL: []string{"LOCAL://127.0.0.1:5193"},
  388. kerberosListeners: []string{},
  389. want: nil,
  390. wantErr: true,
  391. errContains: "missing port",
  392. },
  393. {
  394. name: "SSL host without corresponding BOS listener",
  395. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  396. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  397. bosAdvertisedHostsSSL: []string{"WAN://ssl.example.com:5193"},
  398. kerberosListeners: []string{},
  399. want: nil,
  400. wantErr: true,
  401. errContains: "missing BOS advertise address for listener `wan://`",
  402. },
  403. {
  404. name: "duplicate SSL listener definition",
  405. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  406. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  407. bosAdvertisedHostsSSL: []string{"LOCAL://127.0.0.1:5193", "LOCAL://127.0.0.1:5194"},
  408. kerberosListeners: []string{},
  409. want: nil,
  410. wantErr: true,
  411. errContains: "duplicate listener definition",
  412. },
  413. {
  414. name: "invalid URI format in SSL",
  415. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  416. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  417. bosAdvertisedHostsSSL: []string{"invalid-uri"},
  418. kerberosListeners: []string{},
  419. want: nil,
  420. wantErr: true,
  421. errContains: "missing scheme. Valid format",
  422. },
  423. {
  424. name: "SSL listener missing port",
  425. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  426. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  427. bosAdvertisedHostsSSL: []string{"LOCAL://127.0.0.1"},
  428. kerberosListeners: []string{},
  429. want: nil,
  430. wantErr: true,
  431. errContains: "missing port",
  432. },
  433. {
  434. name: "SSL listener missing host",
  435. bosListeners: []string{"LOCAL://0.0.0.0:5190"},
  436. bosAdvertisedListeners: []string{"LOCAL://127.0.0.1:5190"},
  437. bosAdvertisedHostsSSL: []string{"LOCAL://:5193"},
  438. kerberosListeners: []string{},
  439. want: nil,
  440. wantErr: true,
  441. errContains: "missing host",
  442. },
  443. {
  444. name: "complex multi-listener setup with SSL",
  445. bosListeners: []string{"LAN://192.168.1.10:5190", "WAN://0.0.0.0:5191", "DOCKER://172.17.0.1:5192"},
  446. bosListenersSSL: []string{"LAN://192.168.1.10:5195", "WAN://0.0.0.0:5196"},
  447. bosAdvertisedListeners: []string{"DOCKER://172.17.0.1:5192", "LAN://192.168.1.10:5190", "WAN://example.com:5191"},
  448. bosAdvertisedHostsSSL: []string{"LAN://192.168.1.10:5193", "WAN://ssl.example.com:5194"},
  449. kerberosListeners: []string{"WAN://0.0.0.0:1089", "LAN://192.168.1.10:1088"},
  450. want: []ListenerGroup{
  451. {
  452. Name: "docker",
  453. BOSListenAddress: "172.17.0.1:5192",
  454. BOSAdvertisedHostPlain: "172.17.0.1:5192",
  455. BOSAdvertisedHostSSL: "",
  456. KerberosListenAddress: "",
  457. },
  458. {
  459. Name: "lan",
  460. BOSListenAddress: "192.168.1.10:5190",
  461. BOSListenAddressSSL: "192.168.1.10:5195",
  462. BOSAdvertisedHostPlain: "192.168.1.10:5190",
  463. BOSAdvertisedHostSSL: "192.168.1.10:5193",
  464. KerberosListenAddress: "192.168.1.10:1088",
  465. },
  466. {
  467. Name: "wan",
  468. BOSListenAddress: "0.0.0.0:5191",
  469. BOSListenAddressSSL: "0.0.0.0:5196",
  470. BOSAdvertisedHostPlain: "example.com:5191",
  471. BOSAdvertisedHostSSL: "ssl.example.com:5194",
  472. KerberosListenAddress: "0.0.0.0:1089",
  473. },
  474. },
  475. wantErr: false,
  476. },
  477. }
  478. for _, tt := range tests {
  479. t.Run(tt.name, func(t *testing.T) {
  480. config := &Config{
  481. BOSListeners: tt.bosListeners,
  482. BOSListenersSSL: tt.bosListenersSSL,
  483. BOSAdvertisedHostsPlain: tt.bosAdvertisedListeners,
  484. BOSAdvertisedHostsSSL: tt.bosAdvertisedHostsSSL,
  485. KerberosListeners: tt.kerberosListeners,
  486. }
  487. got, err := config.ParseListenersCfg()
  488. if tt.wantErr {
  489. if err == nil {
  490. t.Errorf("ParseListenersCfg() expected error but got none")
  491. return
  492. }
  493. if tt.errContains != "" && !contains(err.Error(), tt.errContains) {
  494. t.Errorf("ParseListenersCfg() error = %v, want error containing %q", err, tt.errContains)
  495. }
  496. return
  497. }
  498. if err != nil {
  499. t.Errorf("ParseListenersCfg() unexpected error = %v", err)
  500. return
  501. }
  502. // groups come back in map order; want is listed by name
  503. slices.SortFunc(got, func(a, b ListenerGroup) int {
  504. return strings.Compare(a.Name, b.Name)
  505. })
  506. if !reflect.DeepEqual(got, tt.want) {
  507. t.Errorf("ParseListenersCfg() = %+v, want %+v", got, tt.want)
  508. }
  509. })
  510. }
  511. }
  512. func TestConfigValidate(t *testing.T) {
  513. tests := []struct {
  514. name string
  515. config Config
  516. wantErr bool
  517. errContains string
  518. }{
  519. {
  520. name: "valid config with all fields",
  521. config: Config{
  522. TOCListeners: []string{"0.0.0.0:9898", "192.168.1.10:9899"},
  523. APIListener: "127.0.0.1:8080",
  524. WebAPIListeners: []string{"0.0.0.0:8081"},
  525. },
  526. wantErr: false,
  527. },
  528. {
  529. name: "valid config with single TOC listener",
  530. config: Config{
  531. TOCListeners: []string{"0.0.0.0:9898"},
  532. APIListener: "127.0.0.1:8080",
  533. WebAPIListeners: []string{"0.0.0.0:8081"},
  534. },
  535. wantErr: false,
  536. },
  537. {
  538. name: "valid config with empty TOC listeners",
  539. config: Config{
  540. TOCListeners: []string{},
  541. APIListener: "127.0.0.1:8080",
  542. WebAPIListeners: []string{"0.0.0.0:8081"},
  543. },
  544. wantErr: false,
  545. },
  546. {
  547. name: "valid config with empty API listener",
  548. config: Config{
  549. TOCListeners: []string{"0.0.0.0:9898"},
  550. APIListener: "",
  551. },
  552. wantErr: true,
  553. errContains: "APIListener is required and cannot be empty",
  554. },
  555. {
  556. name: "valid config with all empty",
  557. config: Config{
  558. TOCListeners: []string{},
  559. APIListener: "",
  560. },
  561. wantErr: true,
  562. errContains: "APIListener is required and cannot be empty",
  563. },
  564. {
  565. name: "invalid TOC listener - missing port",
  566. config: Config{
  567. TOCListeners: []string{"0.0.0.0"},
  568. APIListener: "127.0.0.1:8080",
  569. },
  570. wantErr: true,
  571. errContains: "invalid TOC listener \"0.0.0.0\": address 0.0.0.0: missing port in address",
  572. },
  573. {
  574. name: "invalid TOC listener - missing host",
  575. config: Config{
  576. TOCListeners: []string{":9898"},
  577. APIListener: "127.0.0.1:8080",
  578. },
  579. wantErr: true,
  580. errContains: "invalid TOC listener \":9898\": missing host",
  581. },
  582. {
  583. name: "invalid TOC listener - malformed",
  584. config: Config{
  585. TOCListeners: []string{"invalid-format"},
  586. APIListener: "127.0.0.1:8080",
  587. },
  588. wantErr: true,
  589. errContains: "invalid TOC listener \"invalid-format\": address invalid-format: missing port in address",
  590. },
  591. {
  592. name: "invalid TOC listener in comma-separated list",
  593. config: Config{
  594. TOCListeners: []string{"0.0.0.0:9898", "invalid-format", "192.168.1.10:9899"},
  595. APIListener: "127.0.0.1:8080",
  596. },
  597. wantErr: true,
  598. errContains: "invalid TOC listener \"invalid-format\": address invalid-format: missing port in address",
  599. },
  600. {
  601. name: "invalid API listener - missing port",
  602. config: Config{
  603. TOCListeners: []string{"0.0.0.0:9898"},
  604. APIListener: "127.0.0.1",
  605. },
  606. wantErr: true,
  607. errContains: "invalid API listener \"127.0.0.1\": address 127.0.0.1: missing port in address",
  608. },
  609. {
  610. name: "invalid API listener - missing host",
  611. config: Config{
  612. TOCListeners: []string{"0.0.0.0:9898"},
  613. APIListener: ":8080",
  614. },
  615. wantErr: true,
  616. errContains: "invalid API listener \":8080\": missing host",
  617. },
  618. {
  619. name: "invalid API listener - malformed",
  620. config: Config{
  621. TOCListeners: []string{"0.0.0.0:9898"},
  622. APIListener: "invalid-format",
  623. },
  624. wantErr: true,
  625. errContains: "invalid API listener \"invalid-format\": address invalid-format: missing port in address",
  626. },
  627. {
  628. name: "whitespace-only TOC listeners",
  629. config: Config{
  630. TOCListeners: []string{" ", " ", " "},
  631. APIListener: "127.0.0.1:8080",
  632. WebAPIListeners: []string{"0.0.0.0:8081"},
  633. },
  634. wantErr: false,
  635. },
  636. {
  637. name: "whitespace-only API listener",
  638. config: Config{
  639. TOCListeners: []string{"0.0.0.0:9898"},
  640. APIListener: " ",
  641. WebAPIListeners: []string{"0.0.0.0:8081"},
  642. },
  643. wantErr: true,
  644. errContains: "APIListener is required and cannot be empty",
  645. },
  646. {
  647. name: "valid multiple web API listeners",
  648. config: Config{
  649. TOCListeners: []string{"0.0.0.0:9898"},
  650. APIListener: "127.0.0.1:8080",
  651. WebAPIListeners: []string{"0.0.0.0:8081", "192.168.1.10:8082"},
  652. },
  653. wantErr: false,
  654. },
  655. {
  656. name: "whitespace-only web API listeners",
  657. config: Config{
  658. TOCListeners: []string{"0.0.0.0:9898"},
  659. APIListener: "127.0.0.1:8080",
  660. WebAPIListeners: []string{" ", " "},
  661. },
  662. wantErr: false,
  663. },
  664. {
  665. name: "invalid web API listener - missing port",
  666. config: Config{
  667. TOCListeners: []string{"0.0.0.0:9898"},
  668. APIListener: "127.0.0.1:8080",
  669. WebAPIListeners: []string{"0.0.0.0"},
  670. },
  671. wantErr: true,
  672. errContains: "invalid web API listener \"0.0.0.0\": address 0.0.0.0: missing port in address",
  673. },
  674. {
  675. name: "invalid web API listener - missing host",
  676. config: Config{
  677. TOCListeners: []string{"0.0.0.0:9898"},
  678. APIListener: "127.0.0.1:8080",
  679. WebAPIListeners: []string{":8081"},
  680. },
  681. wantErr: true,
  682. errContains: "invalid web API listener \":8081\": missing host",
  683. },
  684. {
  685. name: "invalid web API listener - malformed",
  686. config: Config{
  687. TOCListeners: []string{"0.0.0.0:9898"},
  688. APIListener: "127.0.0.1:8080",
  689. WebAPIListeners: []string{"invalid-format"},
  690. },
  691. wantErr: true,
  692. errContains: "invalid web API listener \"invalid-format\": address invalid-format: missing port in address",
  693. },
  694. {
  695. name: "invalid web API listener in comma-separated list",
  696. config: Config{
  697. TOCListeners: []string{"0.0.0.0:9898"},
  698. APIListener: "127.0.0.1:8080",
  699. WebAPIListeners: []string{"0.0.0.0:8081", "invalid-format", "192.168.1.10:8082"},
  700. },
  701. wantErr: true,
  702. errContains: "invalid web API listener \"invalid-format\": address invalid-format: missing port in address",
  703. },
  704. {
  705. name: "valid multiple web API allowed origins",
  706. config: Config{
  707. TOCListeners: []string{"0.0.0.0:9898"},
  708. APIListener: "127.0.0.1:8080",
  709. WebAPIListeners: []string{"0.0.0.0:8081"},
  710. WebAPIAllowedOrigins: []string{"https://aim.example.com", "http://localhost:8000", " http://ras.dev "},
  711. },
  712. wantErr: false,
  713. },
  714. {
  715. name: "wildcard web API allowed origin",
  716. config: Config{
  717. TOCListeners: []string{"0.0.0.0:9898"},
  718. APIListener: "127.0.0.1:8080",
  719. WebAPIListeners: []string{"0.0.0.0:8081"},
  720. WebAPIAllowedOrigins: []string{"*"},
  721. },
  722. wantErr: false,
  723. },
  724. {
  725. // An empty list is a valid setting meaning "allow any origin", not a
  726. // misconfiguration. There is no way to express "deny all".
  727. name: "empty web API allowed origins",
  728. config: Config{
  729. TOCListeners: []string{"0.0.0.0:9898"},
  730. APIListener: "127.0.0.1:8080",
  731. WebAPIListeners: []string{"0.0.0.0:8081"},
  732. },
  733. wantErr: false,
  734. },
  735. {
  736. name: "whitespace-only web API allowed origins",
  737. config: Config{
  738. TOCListeners: []string{"0.0.0.0:9898"},
  739. APIListener: "127.0.0.1:8080",
  740. WebAPIListeners: []string{"0.0.0.0:8081"},
  741. WebAPIAllowedOrigins: []string{" ", ""},
  742. },
  743. wantErr: false,
  744. },
  745. {
  746. name: "invalid web API allowed origin - trailing slash",
  747. config: Config{
  748. TOCListeners: []string{"0.0.0.0:9898"},
  749. APIListener: "127.0.0.1:8080",
  750. WebAPIListeners: []string{"0.0.0.0:8081"},
  751. WebAPIAllowedOrigins: []string{"http://localhost:8000/"},
  752. },
  753. wantErr: true,
  754. errContains: "invalid web API allowed origin \"http://localhost:8000/\": must not have a trailing slash, path, query, fragment or userinfo",
  755. },
  756. {
  757. name: "invalid web API allowed origin - has a path",
  758. config: Config{
  759. TOCListeners: []string{"0.0.0.0:9898"},
  760. APIListener: "127.0.0.1:8080",
  761. WebAPIListeners: []string{"0.0.0.0:8081"},
  762. WebAPIAllowedOrigins: []string{"https://aim.example.com/client"},
  763. },
  764. wantErr: true,
  765. errContains: "must not have a trailing slash, path, query, fragment or userinfo",
  766. },
  767. {
  768. name: "invalid web API allowed origin - missing scheme",
  769. config: Config{
  770. TOCListeners: []string{"0.0.0.0:9898"},
  771. APIListener: "127.0.0.1:8080",
  772. WebAPIListeners: []string{"0.0.0.0:8081"},
  773. WebAPIAllowedOrigins: []string{"aim.example.com"},
  774. },
  775. wantErr: true,
  776. errContains: "scheme must be http or https",
  777. },
  778. {
  779. name: "invalid web API allowed origin - unsupported scheme",
  780. config: Config{
  781. TOCListeners: []string{"0.0.0.0:9898"},
  782. APIListener: "127.0.0.1:8080",
  783. WebAPIListeners: []string{"0.0.0.0:8081"},
  784. WebAPIAllowedOrigins: []string{"ftp://aim.example.com"},
  785. },
  786. wantErr: true,
  787. errContains: "scheme must be http or https",
  788. },
  789. {
  790. name: "valid web API allowed origin - port wildcard",
  791. config: Config{
  792. TOCListeners: []string{"0.0.0.0:9898"},
  793. APIListener: "127.0.0.1:8080",
  794. WebAPIListeners: []string{"0.0.0.0:8081"},
  795. WebAPIAllowedOrigins: []string{"http://localhost:*"},
  796. },
  797. wantErr: false,
  798. },
  799. {
  800. name: "valid web API allowed origin - host wildcard with port",
  801. config: Config{
  802. TOCListeners: []string{"0.0.0.0:9898"},
  803. APIListener: "127.0.0.1:8080",
  804. WebAPIListeners: []string{"0.0.0.0:8081"},
  805. WebAPIAllowedOrigins: []string{"http://*:8000", "https://*"},
  806. },
  807. wantErr: false,
  808. },
  809. {
  810. // Underscores are legal in a host and browsers send them verbatim.
  811. name: "valid web API allowed origin - underscore host",
  812. config: Config{
  813. TOCListeners: []string{"0.0.0.0:9898"},
  814. APIListener: "127.0.0.1:8080",
  815. WebAPIListeners: []string{"0.0.0.0:8081"},
  816. WebAPIAllowedOrigins: []string{"http://my_host:8080"},
  817. },
  818. wantErr: false,
  819. },
  820. {
  821. name: "valid web API allowed origin - IPv6 literal",
  822. config: Config{
  823. TOCListeners: []string{"0.0.0.0:9898"},
  824. APIListener: "127.0.0.1:8080",
  825. WebAPIListeners: []string{"0.0.0.0:8081"},
  826. WebAPIAllowedOrigins: []string{"http://[::1]:8000"},
  827. },
  828. wantErr: false,
  829. },
  830. {
  831. name: "invalid web API allowed origin - two wildcards",
  832. config: Config{
  833. TOCListeners: []string{"0.0.0.0:9898"},
  834. APIListener: "127.0.0.1:8080",
  835. WebAPIListeners: []string{"0.0.0.0:8081"},
  836. WebAPIAllowedOrigins: []string{"http://*.example.*"},
  837. },
  838. wantErr: true,
  839. errContains: "only one wildcard is allowed per origin",
  840. },
  841. {
  842. name: "invalid web API allowed origin - wildcard scheme",
  843. config: Config{
  844. TOCListeners: []string{"0.0.0.0:9898"},
  845. APIListener: "127.0.0.1:8080",
  846. WebAPIListeners: []string{"0.0.0.0:8081"},
  847. WebAPIAllowedOrigins: []string{"*://example.com"},
  848. },
  849. wantErr: true,
  850. errContains: "cannot itself be a wildcard",
  851. },
  852. {
  853. name: "invalid web API allowed origin - wildcard with default port",
  854. config: Config{
  855. TOCListeners: []string{"0.0.0.0:9898"},
  856. APIListener: "127.0.0.1:8080",
  857. WebAPIListeners: []string{"0.0.0.0:8081"},
  858. WebAPIAllowedOrigins: []string{"https://*.example.com:443"},
  859. },
  860. wantErr: true,
  861. errContains: "a browser omits the default port, so this would never match. Use \"https://*.example.com\"",
  862. },
  863. {
  864. name: "invalid web API allowed origin - wildcard with path",
  865. config: Config{
  866. TOCListeners: []string{"0.0.0.0:9898"},
  867. APIListener: "127.0.0.1:8080",
  868. WebAPIListeners: []string{"0.0.0.0:8081"},
  869. WebAPIAllowedOrigins: []string{"http://localhost:*/x"},
  870. },
  871. wantErr: true,
  872. errContains: "must not have a trailing slash, path, query, fragment or userinfo",
  873. },
  874. {
  875. name: "invalid web API allowed origin - explicit https default port",
  876. config: Config{
  877. TOCListeners: []string{"0.0.0.0:9898"},
  878. APIListener: "127.0.0.1:8080",
  879. WebAPIListeners: []string{"0.0.0.0:8081"},
  880. WebAPIAllowedOrigins: []string{"https://ras.dev:443"},
  881. },
  882. wantErr: true,
  883. errContains: "a browser omits the default port, so this would never match. Use \"https://ras.dev\"",
  884. },
  885. {
  886. name: "invalid web API allowed origin - explicit http default port",
  887. config: Config{
  888. TOCListeners: []string{"0.0.0.0:9898"},
  889. APIListener: "127.0.0.1:8080",
  890. WebAPIListeners: []string{"0.0.0.0:8081"},
  891. WebAPIAllowedOrigins: []string{"http://localhost:80"},
  892. },
  893. wantErr: true,
  894. errContains: "a browser omits the default port, so this would never match. Use \"http://localhost\"",
  895. },
  896. {
  897. name: "invalid web API allowed origin - userinfo",
  898. config: Config{
  899. TOCListeners: []string{"0.0.0.0:9898"},
  900. APIListener: "127.0.0.1:8080",
  901. WebAPIListeners: []string{"0.0.0.0:8081"},
  902. WebAPIAllowedOrigins: []string{"http://user@example.com"},
  903. },
  904. wantErr: true,
  905. errContains: "must not have a trailing slash, path, query, fragment or userinfo",
  906. },
  907. {
  908. name: "invalid web API allowed origin - forced empty query",
  909. config: Config{
  910. TOCListeners: []string{"0.0.0.0:9898"},
  911. APIListener: "127.0.0.1:8080",
  912. WebAPIListeners: []string{"0.0.0.0:8081"},
  913. WebAPIAllowedOrigins: []string{"https://example.com?"},
  914. },
  915. wantErr: true,
  916. errContains: "must not have a trailing slash, path, query, fragment or userinfo",
  917. },
  918. {
  919. // A non-default port is part of the origin and must be kept.
  920. name: "valid web API allowed origin - non-default port",
  921. config: Config{
  922. TOCListeners: []string{"0.0.0.0:9898"},
  923. APIListener: "127.0.0.1:8080",
  924. WebAPIListeners: []string{"0.0.0.0:8081"},
  925. WebAPIAllowedOrigins: []string{"http://localhost:8000", "https://ras.dev:8443"},
  926. },
  927. wantErr: false,
  928. },
  929. {
  930. // rs/cors matches case-insensitively, so this does work at runtime.
  931. name: "valid web API allowed origin - uppercase",
  932. config: Config{
  933. TOCListeners: []string{"0.0.0.0:9898"},
  934. APIListener: "127.0.0.1:8080",
  935. WebAPIListeners: []string{"0.0.0.0:8081"},
  936. WebAPIAllowedOrigins: []string{"HTTPS://RAS.DEV"},
  937. },
  938. wantErr: false,
  939. },
  940. {
  941. // rs/cors supports one wildcard per entry.
  942. name: "valid web API allowed origin - subdomain wildcard",
  943. config: Config{
  944. TOCListeners: []string{"0.0.0.0:9898"},
  945. APIListener: "127.0.0.1:8080",
  946. WebAPIListeners: []string{"0.0.0.0:8081"},
  947. WebAPIAllowedOrigins: []string{"https://*.example.com"},
  948. },
  949. wantErr: false,
  950. },
  951. {
  952. name: "invalid web API allowed origin - missing host",
  953. config: Config{
  954. TOCListeners: []string{"0.0.0.0:9898"},
  955. APIListener: "127.0.0.1:8080",
  956. WebAPIListeners: []string{"0.0.0.0:8081"},
  957. WebAPIAllowedOrigins: []string{"https://"},
  958. },
  959. wantErr: true,
  960. errContains: "missing host",
  961. },
  962. }
  963. for _, tt := range tests {
  964. t.Run(tt.name, func(t *testing.T) {
  965. err := tt.config.Validate()
  966. if tt.wantErr {
  967. if err == nil {
  968. t.Errorf("Config.Validate() expected error but got none")
  969. return
  970. }
  971. if tt.errContains != "" && !contains(err.Error(), tt.errContains) {
  972. t.Errorf("Config.Validate() error = %v, want error containing %q", err, tt.errContains)
  973. }
  974. return
  975. }
  976. if err != nil {
  977. t.Errorf("Config.Validate() unexpected error = %v", err)
  978. }
  979. })
  980. }
  981. }
  982. func TestParseICBMLegacyCharset(t *testing.T) {
  983. tests := []struct {
  984. name string
  985. charset string
  986. want encoding.Encoding
  987. errContains string
  988. }{
  989. {name: "Windows1251", charset: "Windows1251", want: charmap.Windows1251},
  990. {name: "KOI8U", charset: "KOI8U", want: charmap.KOI8U},
  991. {name: "case-insensitive", charset: "koi8r", want: charmap.KOI8R},
  992. {name: "surrounding whitespace", charset: " Windows1252 ", want: charmap.Windows1252},
  993. {name: "unsupported charset", charset: "UTF8", errContains: `unsupported ICBM_LEGACY_CHARSET "UTF8"`},
  994. {name: "unsupported charmap", charset: "Windows874", errContains: "Possible values: Windows1250"},
  995. {name: "empty", charset: "", errContains: "unsupported ICBM_LEGACY_CHARSET"},
  996. }
  997. for _, tt := range tests {
  998. t.Run(tt.name, func(t *testing.T) {
  999. cfg := Config{ICBMLegacyCharset: tt.charset}
  1000. got, err := cfg.ParseICBMLegacyCharset()
  1001. if tt.errContains != "" {
  1002. if err == nil {
  1003. t.Fatalf("ParseICBMLegacyCharset() expected error but got none")
  1004. }
  1005. if !strings.Contains(err.Error(), tt.errContains) {
  1006. t.Errorf("ParseICBMLegacyCharset() error = %v, want error containing %q", err, tt.errContains)
  1007. }
  1008. return
  1009. }
  1010. if err != nil {
  1011. t.Fatalf("ParseICBMLegacyCharset() unexpected error = %v", err)
  1012. }
  1013. if got != tt.want {
  1014. t.Errorf("ParseICBMLegacyCharset() = %v, want %v", got, tt.want)
  1015. }
  1016. })
  1017. }
  1018. }
  1019. // Helper function to check if a string contains a substring
  1020. func contains(s, substr string) bool {
  1021. return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
  1022. (len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr ||
  1023. func() bool {
  1024. for i := 1; i <= len(s)-len(substr); i++ {
  1025. if s[i:i+len(substr)] == substr {
  1026. return true
  1027. }
  1028. }
  1029. return false
  1030. }())))
  1031. }