emqx_authz_postgresql_SUITE.erl 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2020-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %% http://www.apache.org/licenses/LICENSE-2.0
  8. %%
  9. %% Unless required by applicable law or agreed to in writing, software
  10. %% distributed under the License is distributed on an "AS IS" BASIS,
  11. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. %% See the License for the specific language governing permissions and
  13. %% limitations under the License.
  14. %%--------------------------------------------------------------------
  15. -module(emqx_authz_postgresql_SUITE).
  16. -compile(nowarn_export_all).
  17. -compile(export_all).
  18. -include_lib("../../emqx_postgresql/include/emqx_postgresql.hrl").
  19. -include_lib("emqx_auth/include/emqx_authz.hrl").
  20. -include_lib("eunit/include/eunit.hrl").
  21. -include_lib("common_test/include/ct.hrl").
  22. -define(PGSQL_HOST, "pgsql").
  23. -define(PGSQL_RESOURCE, <<"emqx_authz_pgsql_SUITE">>).
  24. all() ->
  25. emqx_authz_test_lib:all_with_table_case(?MODULE, t_run_case, cases()).
  26. groups() ->
  27. emqx_authz_test_lib:table_groups(t_run_case, cases()).
  28. init_per_suite(Config) ->
  29. case emqx_common_test_helpers:is_tcp_server_available(?PGSQL_HOST, ?PGSQL_DEFAULT_PORT) of
  30. true ->
  31. Apps = emqx_cth_suite:start(
  32. [
  33. emqx,
  34. {emqx_conf,
  35. "authorization.no_match = deny, authorization.cache.enable = false"},
  36. emqx_auth,
  37. emqx_auth_postgresql
  38. ],
  39. #{work_dir => ?config(priv_dir, Config)}
  40. ),
  41. {ok, _} = create_pgsql_resource(),
  42. [{suite_apps, Apps} | Config];
  43. false ->
  44. {skip, no_pgsql}
  45. end.
  46. end_per_suite(Config) ->
  47. ok = emqx_authz_test_lib:restore_authorizers(),
  48. ok = emqx_resource:remove_local(?PGSQL_RESOURCE),
  49. ok = emqx_cth_suite:stop_apps(?config(suite_apps, Config)).
  50. init_per_group(Group, Config) ->
  51. [{test_case, emqx_authz_test_lib:get_case(Group, cases())} | Config].
  52. end_per_group(_Group, _Config) ->
  53. ok.
  54. init_per_testcase(_TestCase, Config) ->
  55. ok = emqx_authz_test_lib:reset_authorizers(),
  56. Config.
  57. end_per_testcase(_TestCase, _Config) ->
  58. _ = emqx_authz:set_feature_available(rich_actions, true),
  59. ok = drop_table(),
  60. ok.
  61. set_special_configs(emqx_auth) ->
  62. ok = emqx_authz_test_lib:reset_authorizers();
  63. set_special_configs(_) ->
  64. ok.
  65. %%------------------------------------------------------------------------------
  66. %% Testcases
  67. %%------------------------------------------------------------------------------
  68. t_run_case(Config) ->
  69. Case = ?config(test_case, Config),
  70. ok = setup_source_data(Case),
  71. ok = setup_authz_source(Case),
  72. ok = emqx_authz_test_lib:run_checks(Case).
  73. t_create_invalid(_Config) ->
  74. BadConfig = maps:merge(
  75. raw_pgsql_authz_config(),
  76. #{<<"server">> => <<"255.255.255.255:33333">>}
  77. ),
  78. {ok, _} = emqx_authz:update(?CMD_REPLACE, [BadConfig]),
  79. [_] = emqx_authz:lookup().
  80. %%------------------------------------------------------------------------------
  81. %% Cases
  82. %%------------------------------------------------------------------------------
  83. cases() ->
  84. [
  85. #{
  86. name => base_publish,
  87. setup => [
  88. "CREATE TABLE acl(username VARCHAR(255), topic VARCHAR(255), "
  89. "permission VARCHAR(255), action VARCHAR(255))",
  90. "INSERT INTO acl(username, topic, permission, action) VALUES('username', 'a', 'allow', 'publish')",
  91. "INSERT INTO acl(username, topic, permission, action) VALUES('username', 'b', 'allow', 'subscribe')"
  92. ],
  93. query => "SELECT permission, action, topic FROM acl WHERE username = ${username}",
  94. client_info => #{username => <<"username">>},
  95. checks => [
  96. {allow, ?AUTHZ_PUBLISH, <<"a">>},
  97. {deny, ?AUTHZ_PUBLISH, <<"b">>},
  98. {deny, ?AUTHZ_SUBSCRIBE, <<"a">>},
  99. {allow, ?AUTHZ_SUBSCRIBE, <<"b">>}
  100. ]
  101. },
  102. #{
  103. name => rule_by_clientid_cn_dn_peerhost,
  104. setup => [
  105. "CREATE TABLE acl(clientid VARCHAR(255), cn VARCHAR(255), dn VARCHAR(255),"
  106. " peerhost VARCHAR(255), topic VARCHAR(255),"
  107. " permission VARCHAR(255), action VARCHAR(255))",
  108. "INSERT INTO acl(clientid, cn, dn, peerhost, topic, permission, action)"
  109. " VALUES('clientid', 'cn', 'dn', '127.0.0.1', 'a', 'allow', 'publish')"
  110. ],
  111. query =>
  112. "SELECT permission, action, topic FROM acl WHERE"
  113. " clientid = ${clientid} AND cn = ${cert_common_name}"
  114. " AND dn = ${cert_subject} AND peerhost = ${peerhost}",
  115. client_info => #{
  116. clientid => <<"clientid">>,
  117. cn => <<"cn">>,
  118. dn => <<"dn">>,
  119. peerhost => {127, 0, 0, 1}
  120. },
  121. checks => [
  122. {allow, ?AUTHZ_PUBLISH, <<"a">>},
  123. {deny, ?AUTHZ_PUBLISH, <<"b">>}
  124. ]
  125. },
  126. #{
  127. name => topics_literal_wildcard_variable,
  128. setup => [
  129. "CREATE TABLE acl(username VARCHAR(255), topic VARCHAR(255), "
  130. "permission VARCHAR(255), action VARCHAR(255))",
  131. "INSERT INTO acl(username, topic, permission, action) "
  132. "VALUES('username', 't/${username}', 'allow', 'publish')",
  133. "INSERT INTO acl(username, topic, permission, action) "
  134. "VALUES('username', 't/${clientid}', 'allow', 'publish')",
  135. "INSERT INTO acl(username, topic, permission, action) "
  136. "VALUES('username', 'eq t/${username}', 'allow', 'publish')",
  137. "INSERT INTO acl(username, topic, permission, action) "
  138. "VALUES('username', 't/#', 'allow', 'publish')",
  139. "INSERT INTO acl(username, topic, permission, action) "
  140. "VALUES('username', 't1/+', 'allow', 'publish')"
  141. ],
  142. query => "SELECT permission, action, topic FROM acl WHERE username = ${username}",
  143. client_info => #{
  144. username => <<"username">>
  145. },
  146. checks => [
  147. {allow, ?AUTHZ_PUBLISH, <<"t/username">>},
  148. {allow, ?AUTHZ_PUBLISH, <<"t/clientid">>},
  149. {allow, ?AUTHZ_PUBLISH, <<"t/${username}">>},
  150. {allow, ?AUTHZ_PUBLISH, <<"t/1/2">>},
  151. {allow, ?AUTHZ_PUBLISH, <<"t1/1">>},
  152. {deny, ?AUTHZ_PUBLISH, <<"t1/1/2">>},
  153. {deny, ?AUTHZ_PUBLISH, <<"abc">>},
  154. {deny, ?AUTHZ_SUBSCRIBE, <<"t/username">>}
  155. ]
  156. },
  157. #{
  158. name => qos_retain_in_query_result,
  159. features => [rich_actions],
  160. setup => [
  161. "CREATE TABLE acl(username VARCHAR(255), topic VARCHAR(255), "
  162. "permission VARCHAR(255), action VARCHAR(255),"
  163. "qos_s VARCHAR(255), retain_s VARCHAR(255))",
  164. "INSERT INTO acl(username, topic, permission, action, qos_s, retain_s)"
  165. " VALUES('username', 't1', 'allow', 'publish', '1', 'true')",
  166. "INSERT INTO acl(username, topic, permission, action, qos_s, retain_s)"
  167. " VALUES('username', 't2', 'allow', 'publish', '2', 'false')",
  168. "INSERT INTO acl(username, topic, permission, action, qos_s, retain_s)"
  169. " VALUES('username', 't3', 'allow', 'publish', '0,1,2', 'all')",
  170. "INSERT INTO acl(username, topic, permission, action, qos_s, retain_s)"
  171. " VALUES('username', 't4', 'allow', 'subscribe', '1', null)",
  172. "INSERT INTO acl(username, topic, permission, action, qos_s, retain_s)"
  173. " VALUES('username', 't5', 'allow', 'subscribe', '0,1,2', null)"
  174. ],
  175. query =>
  176. "SELECT permission, action, topic, qos_s as qos, retain_s as retain"
  177. " FROM acl WHERE username = ${username}",
  178. client_info => #{
  179. username => <<"username">>
  180. },
  181. checks => [
  182. {allow, ?AUTHZ_PUBLISH(1, true), <<"t1">>},
  183. {deny, ?AUTHZ_PUBLISH(1, false), <<"t1">>},
  184. {deny, ?AUTHZ_PUBLISH(0, true), <<"t1">>},
  185. {allow, ?AUTHZ_PUBLISH(2, false), <<"t2">>},
  186. {deny, ?AUTHZ_PUBLISH(1, false), <<"t2">>},
  187. {deny, ?AUTHZ_PUBLISH(2, true), <<"t2">>},
  188. {allow, ?AUTHZ_PUBLISH(1, true), <<"t3">>},
  189. {allow, ?AUTHZ_PUBLISH(2, false), <<"t3">>},
  190. {allow, ?AUTHZ_PUBLISH(2, true), <<"t3">>},
  191. {allow, ?AUTHZ_PUBLISH(0, false), <<"t3">>},
  192. {allow, ?AUTHZ_SUBSCRIBE(1), <<"t4">>},
  193. {deny, ?AUTHZ_SUBSCRIBE(2), <<"t4">>},
  194. {allow, ?AUTHZ_SUBSCRIBE(1), <<"t5">>},
  195. {allow, ?AUTHZ_SUBSCRIBE(2), <<"t5">>},
  196. {allow, ?AUTHZ_SUBSCRIBE(0), <<"t5">>}
  197. ]
  198. },
  199. #{
  200. name => qos_retain_in_query_result_as_integer,
  201. features => [rich_actions],
  202. setup => [
  203. "CREATE TABLE acl(username VARCHAR(255), topic VARCHAR(255), "
  204. "permission VARCHAR(255), action VARCHAR(255),"
  205. "qos_i VARCHAR(255), retain_i VARCHAR(255))",
  206. "INSERT INTO acl(username, topic, permission, action, qos_i, retain_i)"
  207. " VALUES('username', 't1', 'allow', 'publish', 1, 1)"
  208. ],
  209. query =>
  210. "SELECT permission, action, topic, qos_i as qos, retain_i as retain"
  211. " FROM acl WHERE username = ${username}",
  212. client_info => #{
  213. username => <<"username">>
  214. },
  215. checks => [
  216. {allow, ?AUTHZ_PUBLISH(1, true), <<"t1">>},
  217. {deny, ?AUTHZ_PUBLISH(1, false), <<"t1">>},
  218. {deny, ?AUTHZ_PUBLISH(0, true), <<"t1">>}
  219. ]
  220. },
  221. #{
  222. name => retain_in_query_result_as_boolean,
  223. features => [rich_actions],
  224. setup => [
  225. "CREATE TABLE acl(username VARCHAR(255), topic VARCHAR(255), permission VARCHAR(255),"
  226. " action VARCHAR(255), retain_b BOOLEAN)",
  227. "INSERT INTO acl(username, topic, permission, action, retain_b)"
  228. " VALUES('username', 't1', 'allow', 'publish', true)",
  229. "INSERT INTO acl(username, topic, permission, action, retain_b)"
  230. " VALUES('username', 't2', 'allow', 'publish', false)"
  231. ],
  232. query =>
  233. "SELECT permission, action, topic, retain_b as retain"
  234. " FROM acl WHERE username = ${username}",
  235. client_info => #{
  236. username => <<"username">>
  237. },
  238. checks => [
  239. {allow, ?AUTHZ_PUBLISH(1, true), <<"t1">>},
  240. {deny, ?AUTHZ_PUBLISH(1, false), <<"t1">>},
  241. {allow, ?AUTHZ_PUBLISH(1, false), <<"t2">>},
  242. {deny, ?AUTHZ_PUBLISH(1, true), <<"t2">>}
  243. ]
  244. },
  245. #{
  246. name => nonbin_values_in_client_info,
  247. setup => [
  248. "CREATE TABLE acl(who VARCHAR(255), topic VARCHAR(255), permission VARCHAR(255),"
  249. " action VARCHAR(255))",
  250. "INSERT INTO acl(who, topic, permission, action)"
  251. " VALUES('username', 't/${username}', 'allow', 'publish')",
  252. "INSERT INTO acl(who, topic, permission, action)"
  253. " VALUES('clientid', 't/${clientid}', 'allow', 'publish')"
  254. ],
  255. query =>
  256. "SELECT permission, action, topic"
  257. " FROM acl WHERE who = ${username} OR who = ${clientid}",
  258. client_info => #{
  259. %% string, not a binary
  260. username => "username",
  261. %% atom, not a binary
  262. clientid => clientid
  263. },
  264. checks => [
  265. {allow, ?AUTHZ_PUBLISH, <<"t/username">>},
  266. {allow, ?AUTHZ_PUBLISH, <<"t/clientid">>},
  267. {deny, ?AUTHZ_PUBLISH, <<"t/foo">>}
  268. ]
  269. },
  270. #{
  271. name => array_null_qos,
  272. features => [rich_actions],
  273. setup => [
  274. "CREATE TABLE acl(qos INTEGER[], "
  275. " topic VARCHAR(255), permission VARCHAR(255), action VARCHAR(255))",
  276. "INSERT INTO acl(qos, topic, permission, action)"
  277. " VALUES('{1,2}', 'tp', 'allow', 'publish')",
  278. "INSERT INTO acl(qos, topic, permission, action)"
  279. " VALUES(NULL, 'ts', 'allow', 'subscribe')"
  280. ],
  281. query =>
  282. "SELECT permission, action, topic, qos FROM acl",
  283. checks => [
  284. {allow, ?AUTHZ_PUBLISH(1, false), <<"tp">>},
  285. {allow, ?AUTHZ_PUBLISH(2, false), <<"tp">>},
  286. {deny, ?AUTHZ_PUBLISH(3, false), <<"tp">>},
  287. {allow, ?AUTHZ_SUBSCRIBE(1), <<"ts">>},
  288. {allow, ?AUTHZ_SUBSCRIBE(2), <<"ts">>}
  289. ]
  290. },
  291. #{
  292. name => strip_double_quote,
  293. setup => [
  294. "CREATE TABLE acl(username VARCHAR(255), topic VARCHAR(255), "
  295. "permission VARCHAR(255), action VARCHAR(255))",
  296. "INSERT INTO acl(username, topic, permission, action) VALUES('username', 'a', 'allow', 'publish')"
  297. ],
  298. query => "SELECT permission, action, topic FROM acl WHERE username = \"${username}\"",
  299. checks => [
  300. {allow, ?AUTHZ_PUBLISH, <<"a">>}
  301. ]
  302. },
  303. #{
  304. name => invalid_query,
  305. setup => [],
  306. query => "SELECT permission, action, topic FROM acl WHER",
  307. checks => [
  308. {deny, ?AUTHZ_PUBLISH, <<"a">>}
  309. ]
  310. },
  311. #{
  312. name => pgsql_error,
  313. setup => [],
  314. query =>
  315. "SELECT permission, action, topic FROM table_not_exists WHERE username = ${username}",
  316. checks => [
  317. {deny, ?AUTHZ_PUBLISH, <<"t">>}
  318. ]
  319. },
  320. #{
  321. name => invalid_rule,
  322. setup => [
  323. "CREATE TABLE acl(username VARCHAR(255), topic VARCHAR(255), "
  324. "permission VARCHAR(255), action VARCHAR(255))",
  325. %% 'permit' is invalid value for action
  326. "INSERT INTO acl(username, topic, permission, action) VALUES('username', 'a', 'permit', 'publish')"
  327. ],
  328. query => "SELECT permission, action, topic FROM acl WHERE username = ${username}",
  329. checks => [
  330. {deny, ?AUTHZ_PUBLISH, <<"a">>}
  331. ]
  332. }
  333. %% TODO: add case for unknown variables after fixing EMQX-10400
  334. ].
  335. %%------------------------------------------------------------------------------
  336. %% Helpers
  337. %%------------------------------------------------------------------------------
  338. setup_source_data(#{setup := Queries}) ->
  339. lists:foreach(
  340. fun(Query) ->
  341. _ = q(Query)
  342. end,
  343. Queries
  344. ).
  345. setup_authz_source(#{query := Query}) ->
  346. setup_config(
  347. #{
  348. <<"query">> => Query
  349. }
  350. ).
  351. raw_pgsql_authz_config() ->
  352. #{
  353. <<"enable">> => <<"true">>,
  354. <<"type">> => <<"postgresql">>,
  355. <<"database">> => <<"mqtt">>,
  356. <<"username">> => <<"root">>,
  357. <<"password">> => <<"public">>,
  358. <<"query">> => <<
  359. "SELECT permission, action, topic "
  360. "FROM acl WHERE username = ${username}"
  361. >>,
  362. <<"server">> => <<?PGSQL_HOST>>
  363. }.
  364. q(Sql) ->
  365. emqx_resource:simple_sync_query(
  366. ?PGSQL_RESOURCE,
  367. {query, Sql}
  368. ).
  369. drop_table() ->
  370. {ok, _, _} = q("DROP TABLE IF EXISTS acl"),
  371. ok.
  372. setup_config(SpecialParams) ->
  373. emqx_authz_test_lib:setup_config(
  374. raw_pgsql_authz_config(),
  375. SpecialParams
  376. ).
  377. pgsql_config() ->
  378. #{
  379. auto_reconnect => true,
  380. disable_prepared_statements => false,
  381. database => <<"mqtt">>,
  382. username => <<"root">>,
  383. password => <<"public">>,
  384. pool_size => 8,
  385. server => <<?PGSQL_HOST>>,
  386. ssl => #{enable => false}
  387. }.
  388. create_pgsql_resource() ->
  389. emqx_resource:create_local(
  390. ?PGSQL_RESOURCE,
  391. ?AUTHZ_RESOURCE_GROUP,
  392. emqx_postgresql,
  393. pgsql_config(),
  394. #{}
  395. ).
  396. start_apps(Apps) ->
  397. lists:foreach(fun application:ensure_all_started/1, Apps).
  398. stop_apps(Apps) ->
  399. lists:foreach(fun application:stop/1, Apps).