config_test.go 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098
  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: true,
  635. errContains: "invalid TOC listeners \" , , \": empty entry",
  636. },
  637. {
  638. name: "TOC listeners with trailing comma",
  639. config: Config{
  640. TOCListeners: []string{"0.0.0.0:9898", ""},
  641. APIListener: "127.0.0.1:8080",
  642. WebAPIListeners: []string{"0.0.0.0:8081"},
  643. },
  644. wantErr: true,
  645. errContains: "invalid TOC listeners \"0.0.0.0:9898,\": empty entry. Remove the trailing or doubled comma from TOC_LISTENERS",
  646. },
  647. {
  648. name: "TOC listeners with doubled comma",
  649. config: Config{
  650. TOCListeners: []string{"0.0.0.0:9898", "", "192.168.1.10:9899"},
  651. APIListener: "127.0.0.1:8080",
  652. WebAPIListeners: []string{"0.0.0.0:8081"},
  653. },
  654. wantErr: true,
  655. errContains: "invalid TOC listeners \"0.0.0.0:9898,,192.168.1.10:9899\": empty entry",
  656. },
  657. {
  658. name: "whitespace-only API listener",
  659. config: Config{
  660. TOCListeners: []string{"0.0.0.0:9898"},
  661. APIListener: " ",
  662. WebAPIListeners: []string{"0.0.0.0:8081"},
  663. },
  664. wantErr: true,
  665. errContains: "APIListener is required and cannot be empty",
  666. },
  667. {
  668. name: "valid multiple web API listeners",
  669. config: Config{
  670. TOCListeners: []string{"0.0.0.0:9898"},
  671. APIListener: "127.0.0.1:8080",
  672. WebAPIListeners: []string{"0.0.0.0:8081", "192.168.1.10:8082"},
  673. },
  674. wantErr: false,
  675. },
  676. {
  677. name: "whitespace-only web API listeners",
  678. config: Config{
  679. TOCListeners: []string{"0.0.0.0:9898"},
  680. APIListener: "127.0.0.1:8080",
  681. WebAPIListeners: []string{" ", " "},
  682. },
  683. wantErr: true,
  684. errContains: "invalid web API listeners \" , \": empty entry",
  685. },
  686. {
  687. name: "web API listeners with trailing comma",
  688. config: Config{
  689. TOCListeners: []string{"0.0.0.0:9898"},
  690. APIListener: "127.0.0.1:8080",
  691. WebAPIListeners: []string{"0.0.0.0:8081", ""},
  692. },
  693. wantErr: true,
  694. errContains: "invalid web API listeners \"0.0.0.0:8081,\": empty entry. Remove the trailing or doubled comma from WEBAPI_LISTENERS",
  695. },
  696. {
  697. name: "web API listeners with doubled comma",
  698. config: Config{
  699. TOCListeners: []string{"0.0.0.0:9898"},
  700. APIListener: "127.0.0.1:8080",
  701. WebAPIListeners: []string{"0.0.0.0:8081", "", "192.168.1.10:8082"},
  702. },
  703. wantErr: true,
  704. errContains: "invalid web API listeners \"0.0.0.0:8081,,192.168.1.10:8082\": empty entry",
  705. },
  706. {
  707. name: "no web API listeners",
  708. config: Config{
  709. TOCListeners: []string{"0.0.0.0:9898"},
  710. APIListener: "127.0.0.1:8080",
  711. },
  712. wantErr: false,
  713. },
  714. {
  715. name: "invalid web API listener - missing port",
  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"},
  720. },
  721. wantErr: true,
  722. errContains: "invalid web API listener \"0.0.0.0\": address 0.0.0.0: missing port in address",
  723. },
  724. {
  725. name: "invalid web API listener - missing host",
  726. config: Config{
  727. TOCListeners: []string{"0.0.0.0:9898"},
  728. APIListener: "127.0.0.1:8080",
  729. WebAPIListeners: []string{":8081"},
  730. },
  731. wantErr: true,
  732. errContains: "invalid web API listener \":8081\": missing host",
  733. },
  734. {
  735. name: "invalid web API listener - malformed",
  736. config: Config{
  737. TOCListeners: []string{"0.0.0.0:9898"},
  738. APIListener: "127.0.0.1:8080",
  739. WebAPIListeners: []string{"invalid-format"},
  740. },
  741. wantErr: true,
  742. errContains: "invalid web API listener \"invalid-format\": address invalid-format: missing port in address",
  743. },
  744. {
  745. name: "invalid web API listener in comma-separated list",
  746. config: Config{
  747. TOCListeners: []string{"0.0.0.0:9898"},
  748. APIListener: "127.0.0.1:8080",
  749. WebAPIListeners: []string{"0.0.0.0:8081", "invalid-format", "192.168.1.10:8082"},
  750. },
  751. wantErr: true,
  752. errContains: "invalid web API listener \"invalid-format\": address invalid-format: missing port in address",
  753. },
  754. {
  755. name: "valid multiple web API allowed origins",
  756. config: Config{
  757. TOCListeners: []string{"0.0.0.0:9898"},
  758. APIListener: "127.0.0.1:8080",
  759. WebAPIListeners: []string{"0.0.0.0:8081"},
  760. WebAPIAllowedOrigins: []string{"https://aim.example.com", "http://localhost:8000", " http://ras.dev "},
  761. },
  762. wantErr: false,
  763. },
  764. {
  765. name: "wildcard web API allowed origin",
  766. config: Config{
  767. TOCListeners: []string{"0.0.0.0:9898"},
  768. APIListener: "127.0.0.1:8080",
  769. WebAPIListeners: []string{"0.0.0.0:8081"},
  770. WebAPIAllowedOrigins: []string{"*"},
  771. },
  772. wantErr: false,
  773. },
  774. {
  775. // An empty list is a valid setting meaning "allow any origin", not a
  776. // misconfiguration. There is no way to express "deny all".
  777. name: "empty web API allowed origins",
  778. config: Config{
  779. TOCListeners: []string{"0.0.0.0:9898"},
  780. APIListener: "127.0.0.1:8080",
  781. WebAPIListeners: []string{"0.0.0.0:8081"},
  782. },
  783. wantErr: false,
  784. },
  785. {
  786. name: "whitespace-only web API allowed origins",
  787. config: Config{
  788. TOCListeners: []string{"0.0.0.0:9898"},
  789. APIListener: "127.0.0.1:8080",
  790. WebAPIListeners: []string{"0.0.0.0:8081"},
  791. WebAPIAllowedOrigins: []string{" ", ""},
  792. },
  793. wantErr: false,
  794. },
  795. {
  796. name: "invalid web API allowed origin - trailing slash",
  797. config: Config{
  798. TOCListeners: []string{"0.0.0.0:9898"},
  799. APIListener: "127.0.0.1:8080",
  800. WebAPIListeners: []string{"0.0.0.0:8081"},
  801. WebAPIAllowedOrigins: []string{"http://localhost:8000/"},
  802. },
  803. wantErr: true,
  804. errContains: "invalid web API allowed origin \"http://localhost:8000/\": must not have a trailing slash, path, query, fragment or userinfo",
  805. },
  806. {
  807. name: "invalid web API allowed origin - has a path",
  808. config: Config{
  809. TOCListeners: []string{"0.0.0.0:9898"},
  810. APIListener: "127.0.0.1:8080",
  811. WebAPIListeners: []string{"0.0.0.0:8081"},
  812. WebAPIAllowedOrigins: []string{"https://aim.example.com/client"},
  813. },
  814. wantErr: true,
  815. errContains: "must not have a trailing slash, path, query, fragment or userinfo",
  816. },
  817. {
  818. name: "invalid web API allowed origin - missing scheme",
  819. config: Config{
  820. TOCListeners: []string{"0.0.0.0:9898"},
  821. APIListener: "127.0.0.1:8080",
  822. WebAPIListeners: []string{"0.0.0.0:8081"},
  823. WebAPIAllowedOrigins: []string{"aim.example.com"},
  824. },
  825. wantErr: true,
  826. errContains: "scheme must be http or https",
  827. },
  828. {
  829. name: "invalid web API allowed origin - unsupported scheme",
  830. config: Config{
  831. TOCListeners: []string{"0.0.0.0:9898"},
  832. APIListener: "127.0.0.1:8080",
  833. WebAPIListeners: []string{"0.0.0.0:8081"},
  834. WebAPIAllowedOrigins: []string{"ftp://aim.example.com"},
  835. },
  836. wantErr: true,
  837. errContains: "scheme must be http or https",
  838. },
  839. {
  840. name: "valid web API allowed origin - port wildcard",
  841. config: Config{
  842. TOCListeners: []string{"0.0.0.0:9898"},
  843. APIListener: "127.0.0.1:8080",
  844. WebAPIListeners: []string{"0.0.0.0:8081"},
  845. WebAPIAllowedOrigins: []string{"http://localhost:*"},
  846. },
  847. wantErr: false,
  848. },
  849. {
  850. name: "valid web API allowed origin - host wildcard with port",
  851. config: Config{
  852. TOCListeners: []string{"0.0.0.0:9898"},
  853. APIListener: "127.0.0.1:8080",
  854. WebAPIListeners: []string{"0.0.0.0:8081"},
  855. WebAPIAllowedOrigins: []string{"http://*:8000", "https://*"},
  856. },
  857. wantErr: false,
  858. },
  859. {
  860. // Underscores are legal in a host and browsers send them verbatim.
  861. name: "valid web API allowed origin - underscore host",
  862. config: Config{
  863. TOCListeners: []string{"0.0.0.0:9898"},
  864. APIListener: "127.0.0.1:8080",
  865. WebAPIListeners: []string{"0.0.0.0:8081"},
  866. WebAPIAllowedOrigins: []string{"http://my_host:8080"},
  867. },
  868. wantErr: false,
  869. },
  870. {
  871. name: "valid web API allowed origin - IPv6 literal",
  872. config: Config{
  873. TOCListeners: []string{"0.0.0.0:9898"},
  874. APIListener: "127.0.0.1:8080",
  875. WebAPIListeners: []string{"0.0.0.0:8081"},
  876. WebAPIAllowedOrigins: []string{"http://[::1]:8000"},
  877. },
  878. wantErr: false,
  879. },
  880. {
  881. name: "invalid web API allowed origin - two wildcards",
  882. config: Config{
  883. TOCListeners: []string{"0.0.0.0:9898"},
  884. APIListener: "127.0.0.1:8080",
  885. WebAPIListeners: []string{"0.0.0.0:8081"},
  886. WebAPIAllowedOrigins: []string{"http://*.example.*"},
  887. },
  888. wantErr: true,
  889. errContains: "only one wildcard is allowed per origin",
  890. },
  891. {
  892. name: "invalid web API allowed origin - wildcard scheme",
  893. config: Config{
  894. TOCListeners: []string{"0.0.0.0:9898"},
  895. APIListener: "127.0.0.1:8080",
  896. WebAPIListeners: []string{"0.0.0.0:8081"},
  897. WebAPIAllowedOrigins: []string{"*://example.com"},
  898. },
  899. wantErr: true,
  900. errContains: "cannot itself be a wildcard",
  901. },
  902. {
  903. name: "invalid web API allowed origin - wildcard with default port",
  904. config: Config{
  905. TOCListeners: []string{"0.0.0.0:9898"},
  906. APIListener: "127.0.0.1:8080",
  907. WebAPIListeners: []string{"0.0.0.0:8081"},
  908. WebAPIAllowedOrigins: []string{"https://*.example.com:443"},
  909. },
  910. wantErr: true,
  911. errContains: "a browser omits the default port, so this would never match. Use \"https://*.example.com\"",
  912. },
  913. {
  914. name: "invalid web API allowed origin - wildcard with path",
  915. config: Config{
  916. TOCListeners: []string{"0.0.0.0:9898"},
  917. APIListener: "127.0.0.1:8080",
  918. WebAPIListeners: []string{"0.0.0.0:8081"},
  919. WebAPIAllowedOrigins: []string{"http://localhost:*/x"},
  920. },
  921. wantErr: true,
  922. errContains: "must not have a trailing slash, path, query, fragment or userinfo",
  923. },
  924. {
  925. name: "invalid web API allowed origin - explicit https default port",
  926. config: Config{
  927. TOCListeners: []string{"0.0.0.0:9898"},
  928. APIListener: "127.0.0.1:8080",
  929. WebAPIListeners: []string{"0.0.0.0:8081"},
  930. WebAPIAllowedOrigins: []string{"https://ras.dev:443"},
  931. },
  932. wantErr: true,
  933. errContains: "a browser omits the default port, so this would never match. Use \"https://ras.dev\"",
  934. },
  935. {
  936. name: "invalid web API allowed origin - explicit http default port",
  937. config: Config{
  938. TOCListeners: []string{"0.0.0.0:9898"},
  939. APIListener: "127.0.0.1:8080",
  940. WebAPIListeners: []string{"0.0.0.0:8081"},
  941. WebAPIAllowedOrigins: []string{"http://localhost:80"},
  942. },
  943. wantErr: true,
  944. errContains: "a browser omits the default port, so this would never match. Use \"http://localhost\"",
  945. },
  946. {
  947. name: "invalid web API allowed origin - userinfo",
  948. config: Config{
  949. TOCListeners: []string{"0.0.0.0:9898"},
  950. APIListener: "127.0.0.1:8080",
  951. WebAPIListeners: []string{"0.0.0.0:8081"},
  952. WebAPIAllowedOrigins: []string{"http://user@example.com"},
  953. },
  954. wantErr: true,
  955. errContains: "must not have a trailing slash, path, query, fragment or userinfo",
  956. },
  957. {
  958. name: "invalid web API allowed origin - forced empty query",
  959. config: Config{
  960. TOCListeners: []string{"0.0.0.0:9898"},
  961. APIListener: "127.0.0.1:8080",
  962. WebAPIListeners: []string{"0.0.0.0:8081"},
  963. WebAPIAllowedOrigins: []string{"https://example.com?"},
  964. },
  965. wantErr: true,
  966. errContains: "must not have a trailing slash, path, query, fragment or userinfo",
  967. },
  968. {
  969. // A non-default port is part of the origin and must be kept.
  970. name: "valid web API allowed origin - non-default port",
  971. config: Config{
  972. TOCListeners: []string{"0.0.0.0:9898"},
  973. APIListener: "127.0.0.1:8080",
  974. WebAPIListeners: []string{"0.0.0.0:8081"},
  975. WebAPIAllowedOrigins: []string{"http://localhost:8000", "https://ras.dev:8443"},
  976. },
  977. wantErr: false,
  978. },
  979. {
  980. // rs/cors matches case-insensitively, so this does work at runtime.
  981. name: "valid web API allowed origin - uppercase",
  982. config: Config{
  983. TOCListeners: []string{"0.0.0.0:9898"},
  984. APIListener: "127.0.0.1:8080",
  985. WebAPIListeners: []string{"0.0.0.0:8081"},
  986. WebAPIAllowedOrigins: []string{"HTTPS://RAS.DEV"},
  987. },
  988. wantErr: false,
  989. },
  990. {
  991. // rs/cors supports one wildcard per entry.
  992. name: "valid web API allowed origin - subdomain wildcard",
  993. config: Config{
  994. TOCListeners: []string{"0.0.0.0:9898"},
  995. APIListener: "127.0.0.1:8080",
  996. WebAPIListeners: []string{"0.0.0.0:8081"},
  997. WebAPIAllowedOrigins: []string{"https://*.example.com"},
  998. },
  999. wantErr: false,
  1000. },
  1001. {
  1002. name: "invalid web API allowed origin - missing host",
  1003. config: Config{
  1004. TOCListeners: []string{"0.0.0.0:9898"},
  1005. APIListener: "127.0.0.1:8080",
  1006. WebAPIListeners: []string{"0.0.0.0:8081"},
  1007. WebAPIAllowedOrigins: []string{"https://"},
  1008. },
  1009. wantErr: true,
  1010. errContains: "missing host",
  1011. },
  1012. }
  1013. for _, tt := range tests {
  1014. t.Run(tt.name, func(t *testing.T) {
  1015. err := tt.config.Validate()
  1016. if tt.wantErr {
  1017. if err == nil {
  1018. t.Errorf("Config.Validate() expected error but got none")
  1019. return
  1020. }
  1021. if tt.errContains != "" && !contains(err.Error(), tt.errContains) {
  1022. t.Errorf("Config.Validate() error = %v, want error containing %q", err, tt.errContains)
  1023. }
  1024. return
  1025. }
  1026. if err != nil {
  1027. t.Errorf("Config.Validate() unexpected error = %v", err)
  1028. }
  1029. })
  1030. }
  1031. }
  1032. func TestParseICBMLegacyCharset(t *testing.T) {
  1033. tests := []struct {
  1034. name string
  1035. charset string
  1036. want encoding.Encoding
  1037. errContains string
  1038. }{
  1039. {name: "Windows1251", charset: "Windows1251", want: charmap.Windows1251},
  1040. {name: "KOI8U", charset: "KOI8U", want: charmap.KOI8U},
  1041. {name: "case-insensitive", charset: "koi8r", want: charmap.KOI8R},
  1042. {name: "surrounding whitespace", charset: " Windows1252 ", want: charmap.Windows1252},
  1043. {name: "unsupported charset", charset: "UTF8", errContains: `unsupported ICBM_LEGACY_CHARSET "UTF8"`},
  1044. {name: "unsupported charmap", charset: "Windows874", errContains: "Possible values: Windows1250"},
  1045. {name: "empty", charset: "", errContains: "unsupported ICBM_LEGACY_CHARSET"},
  1046. }
  1047. for _, tt := range tests {
  1048. t.Run(tt.name, func(t *testing.T) {
  1049. cfg := Config{ICBMLegacyCharset: tt.charset}
  1050. got, err := cfg.ParseICBMLegacyCharset()
  1051. if tt.errContains != "" {
  1052. if err == nil {
  1053. t.Fatalf("ParseICBMLegacyCharset() expected error but got none")
  1054. }
  1055. if !strings.Contains(err.Error(), tt.errContains) {
  1056. t.Errorf("ParseICBMLegacyCharset() error = %v, want error containing %q", err, tt.errContains)
  1057. }
  1058. return
  1059. }
  1060. if err != nil {
  1061. t.Fatalf("ParseICBMLegacyCharset() unexpected error = %v", err)
  1062. }
  1063. if got != tt.want {
  1064. t.Errorf("ParseICBMLegacyCharset() = %v, want %v", got, tt.want)
  1065. }
  1066. })
  1067. }
  1068. }
  1069. // Helper function to check if a string contains a substring
  1070. func contains(s, substr string) bool {
  1071. return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
  1072. (len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr ||
  1073. func() bool {
  1074. for i := 1; i <= len(s)-len(substr); i++ {
  1075. if s[i:i+len(substr)] == substr {
  1076. return true
  1077. }
  1078. }
  1079. return false
  1080. }())))
  1081. }