emqx_auth_pgsql_SUITE.erl 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2020 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. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. -module(emqx_auth_pgsql_SUITE).
  17. -compile(export_all).
  18. -define(POOL, emqx_auth_pgsql).
  19. -define(APP, emqx_auth_pgsql).
  20. -include_lib("emqx/include/emqx.hrl").
  21. -include_lib("eunit/include/eunit.hrl").
  22. -include_lib("common_test/include/ct.hrl").
  23. %%setp1 init table
  24. -define(DROP_ACL_TABLE, "DROP TABLE IF EXISTS mqtt_acl_test").
  25. -define(CREATE_ACL_TABLE, "CREATE TABLE mqtt_acl_test (
  26. id SERIAL primary key,
  27. allow integer,
  28. ipaddr character varying(60),
  29. username character varying(100),
  30. clientid character varying(100),
  31. access integer,
  32. topic character varying(100))").
  33. -define(INIT_ACL, "INSERT INTO mqtt_acl_test (id, allow, ipaddr, username, clientid, access, topic)
  34. VALUES
  35. (1,1,'127.0.0.1','u1','c1',1,'t1'),
  36. (2,0,'127.0.0.1','u2','c2',1,'t1'),
  37. (3,1,'10.10.0.110','u1','c1',1,'t1'),
  38. (4,1,'127.0.0.1','u3','c3',3,'t1')").
  39. -define(DROP_AUTH_TABLE, "DROP TABLE IF EXISTS mqtt_user_test").
  40. -define(CREATE_AUTH_TABLE, "CREATE TABLE mqtt_user_test (
  41. id SERIAL primary key,
  42. is_superuser boolean,
  43. username character varying(100),
  44. password character varying(100),
  45. salt character varying(40))").
  46. -define(INIT_AUTH, "INSERT INTO mqtt_user_test (id, is_superuser, username, password, salt)
  47. VALUES
  48. (1, true, 'plain', 'plain', 'salt'),
  49. (2, false, 'md5', '1bc29b36f623ba82aaf6724fd3b16718', 'salt'),
  50. (3, false, 'sha', 'd8f4590320e1343a915b6394170650a8f35d6926', 'salt'),
  51. (4, false, 'sha256', '5d5b09f6dcb2d53a5fffc60c4ac0d55fabdf556069d6631545f42aa6e3500f2e', 'salt'),
  52. (5, false, 'pbkdf2_password', 'cdedb5281bb2f801565a1122b2563515', 'ATHENA.MIT.EDUraeburn'),
  53. (6, false, 'bcrypt_foo', '$2a$12$sSS8Eg.ovVzaHzi1nUHYK.HbUIOdlQI0iS22Q5rd5z.JVVYH6sfm6', '$2a$12$sSS8Eg.ovVzaHzi1nUHYK.'),
  54. (7, false, 'bcrypt', '$2y$16$rEVsDarhgHYB0TGnDFJzyu5f.T.Ha9iXMTk9J36NCMWWM7O16qyaK', 'salt')").
  55. all() ->
  56. [{group, ssl}, {group, nossl}].
  57. groups() ->
  58. Cases = emqx_ct:all(?MODULE),
  59. [{ssl, [sequence], Cases}, {nossl, [sequence], Cases}].
  60. init_per_group(Name, Config) ->
  61. case Name of
  62. ssl ->
  63. emqx_ct_helpers:start_apps([emqx_auth_pgsql], fun set_special_configs_ssl/1);
  64. nossl ->
  65. emqx_ct_helpers:start_apps([emqx_auth_pgsql], fun set_special_configs/1)
  66. end,
  67. init_auth_(),
  68. init_acl_(),
  69. Config.
  70. end_per_group(_, Config) ->
  71. drop_auth_(),
  72. drop_acl_(),
  73. emqx_ct_helpers:stop_apps([emqx_auth_pgsql]),
  74. Config.
  75. set_special_configs_ssl(Name) ->
  76. Server = application:get_env(?APP, server, []),
  77. Path = emqx_ct_helpers:deps_path(emqx_auth_pgsql, "test/emqx_auth_pgsql_SUITE_data/"),
  78. Sslopts = [{keyfile, Path ++ "/client-key.pem"},
  79. {certfile, Path ++ "/client-cert.pem"},
  80. {cacertfile, Path ++ "/ca.pem"}],
  81. Temp = lists:keyreplace(ssl, 1, Server, {ssl, true}),
  82. application:set_env(?APP, server, Temp),
  83. application:set_env(?APP, server, lists:keyreplace(ssl_opts, 1, Temp, {ssl_opts, Sslopts})),
  84. set_special_configs(Name).
  85. set_special_configs(emqx) ->
  86. application:set_env(emqx, acl_nomatch, deny),
  87. application:set_env(emqx, acl_file,
  88. emqx_ct_helpers:deps_path(emqx, "test/emqx_SUITE_data/acl.conf")),
  89. application:set_env(emqx, allow_anonymous, false),
  90. application:set_env(emqx, enable_acl_cache, false),
  91. application:set_env(emqx, plugins_loaded_file,
  92. emqx_ct_helpers:deps_path(emqx, "test/emqx_SUITE_data/loaded_plugins"));
  93. set_special_configs(emqx_auth_pgsql) ->
  94. Server = application:get_env(?APP, server, []),
  95. application:set_env(?APP, server,
  96. lists:keyreplace(password,
  97. 1,
  98. lists:keyreplace(pool_size, 1, Server, {pool_size, 1}),
  99. {password, "public"})),
  100. application:set_env(?APP, acl_query, "select allow, ipaddr, username, clientid, access, topic from mqtt_acl_test where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c'"),
  101. application:set_env(?APP, super_query, "select is_superuser from mqtt_user_test where username = '%u' limit 1"),
  102. application:set_env(?APP, auth_query, "select password from mqtt_user_test where username = '%u' limit 1");
  103. set_special_configs(_App) ->
  104. ok.
  105. t_comment_config(_) ->
  106. AuthCount = length(emqx_hooks:lookup('client.authenticate')),
  107. AclCount = length(emqx_hooks:lookup('client.check_acl')),
  108. application:stop(?APP),
  109. [application:unset_env(?APP, Par) || Par <- [acl_query, auth_query]],
  110. application:start(?APP),
  111. ?assertEqual([], emqx_hooks:lookup('client.authenticate')),
  112. ?assertEqual(AuthCount - 1, length(emqx_hooks:lookup('client.authenticate'))),
  113. ?assertEqual(AclCount - 1, length(emqx_hooks:lookup('client.check_acl'))).
  114. t_placeholders(_) ->
  115. ClientA = #{username => <<"plain">>, clientid => <<"plain">>, zone => external},
  116. reload([{password_hash, plain},
  117. {auth_query, "select password from mqtt_user_test where username = '%u' and 'a_cn_val' = '%C' limit 1"}]),
  118. {error, not_authorized} =
  119. emqx_access_control:authenticate(ClientA#{password => <<"plain">>}),
  120. {error, not_authorized} =
  121. emqx_access_control:authenticate(ClientA#{password => <<"plain">>, cn => undefined}),
  122. {ok, _} =
  123. emqx_access_control:authenticate(ClientA#{password => <<"plain">>, cn => <<"a_cn_val">>}),
  124. reload([{auth_query, "select password from mqtt_user_test where username = '%c' and 'a_dn_val' = '%d' limit 1"}]),
  125. {error, not_authorized} =
  126. emqx_access_control:authenticate(ClientA#{password => <<"plain">>}),
  127. {error, not_authorized} =
  128. emqx_access_control:authenticate(ClientA#{password => <<"plain">>, dn => undefined}),
  129. {ok, _} =
  130. emqx_access_control:authenticate(ClientA#{password => <<"plain">>, dn => <<"a_dn_val">>}),
  131. reload([{auth_query, "select password from mqtt_user_test where username = '%u' and '192.168.1.5' = '%a' limit 1"}]),
  132. {error, not_authorized} =
  133. emqx_access_control:authenticate(ClientA#{password => <<"plain">>}),
  134. {ok, _} =
  135. emqx_access_control:authenticate(ClientA#{password => <<"plain">>, peerhost => {192,168,1,5}}).
  136. t_check_auth(_) ->
  137. Plain = #{clientid => <<"client1">>, username => <<"plain">>, zone => external},
  138. Md5 = #{clientid => <<"md5">>, username => <<"md5">>, zone => external},
  139. Sha = #{clientid => <<"sha">>, username => <<"sha">>, zone => external},
  140. Sha256 = #{clientid => <<"sha256">>, username => <<"sha256">>, zone => external},
  141. Pbkdf2 = #{clientid => <<"pbkdf2_password">>, username => <<"pbkdf2_password">>, zone => external},
  142. BcryptFoo = #{clientid => <<"bcrypt_foo">>, username => <<"bcrypt_foo">>, zone => external},
  143. User1 = #{clientid => <<"bcrypt_foo">>, username => <<"user">>, zone => external},
  144. Bcrypt = #{clientid => <<"bcrypt">>, username => <<"bcrypt">>, zone => external},
  145. reload([{password_hash, plain},
  146. {auth_query, "select password from mqtt_user_test where username = '%u' limit 1"}]),
  147. {ok, #{is_superuser := true}} = emqx_access_control:authenticate(Plain#{password => <<"plain">>}),
  148. reload([{password_hash, md5}]),
  149. {ok, #{is_superuser := false}} = emqx_access_control:authenticate(Md5#{password => <<"md5">>}),
  150. reload([{password_hash, sha}]),
  151. {ok, #{is_superuser := false}} = emqx_access_control:authenticate(Sha#{password => <<"sha">>}),
  152. reload([{password_hash, sha256}]),
  153. {ok, #{is_superuser := false}} = emqx_access_control:authenticate(Sha256#{password => <<"sha256">>}),
  154. reload([{password_hash, bcrypt}]),
  155. {ok, #{is_superuser := false}} = emqx_access_control:authenticate(Bcrypt#{password => <<"password">>}),
  156. %%pbkdf2 sha
  157. reload([{password_hash, {pbkdf2, sha, 1, 16}}, {auth_query, "select password, salt from mqtt_user_test where username = '%u' limit 1"}]),
  158. {ok, #{is_superuser := false}} = emqx_access_control:authenticate(Pbkdf2#{password => <<"password">>}),
  159. reload([{password_hash, {salt, bcrypt}}]),
  160. {ok, #{is_superuser := false}} = emqx_access_control:authenticate(BcryptFoo#{password => <<"foo">>}),
  161. {error, _} = emqx_access_control:authenticate(User1#{password => <<"foo">>}),
  162. {error, not_authorized} = emqx_access_control:authenticate(Bcrypt#{password => <<"password">>}).
  163. t_check_acl(_) ->
  164. emqx_modules:load_module(emqx_mod_acl_internal, false),
  165. User1 = #{zone => external, peerhost => {127,0,0,1}, clientid => <<"c1">>, username => <<"u1">>},
  166. User2 = #{zone => external, peerhost => {127,0,0,1}, clientid => <<"c2">>, username => <<"u2">>},
  167. reload([{acl_query, "select allow, ipaddr, username, clientid, access, topic from mqtt_acl_test where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c'"}]),
  168. allow = emqx_access_control:check_acl(User1, subscribe, <<"t1">>),
  169. deny = emqx_access_control:check_acl(User2, subscribe, <<"t1">>),
  170. User3 = #{zone => external, peerhost => {10,10,0,110}, clientid => <<"c1">>, username => <<"u1">>},
  171. User4 = #{zone => external, peerhost => {10,10,10,110}, clientid => <<"c1">>, username => <<"u1">>},
  172. allow = emqx_access_control:check_acl(User3, subscribe, <<"t1">>),
  173. allow = emqx_access_control:check_acl(User3, subscribe, <<"t1">>),
  174. allow = emqx_access_control:check_acl(User3, subscribe, <<"t2">>),%% nomatch -> ignore -> emqttd acl
  175. allow = emqx_access_control:check_acl(User4, subscribe, <<"t1">>),%% nomatch -> ignore -> emqttd acl
  176. User5 = #{zone => external, peerhost => {127,0,0,1}, clientid => <<"c3">>, username => <<"u3">>},
  177. allow = emqx_access_control:check_acl(User5, subscribe, <<"t1">>),
  178. allow = emqx_access_control:check_acl(User5, publish, <<"t1">>).
  179. t_acl_super(_) ->
  180. reload([{password_hash, plain}, {auth_query, "select password from mqtt_user_test where username = '%u' limit 1"}]),
  181. {ok, C} = emqtt:start_link([{host, "localhost"}, {clientid, <<"simpleClient">>},
  182. {username, <<"plain">>}, {password, <<"plain">>}]),
  183. {ok, _} = emqtt:connect(C),
  184. timer:sleep(10),
  185. emqtt:subscribe(C, <<"TopicA">>, qos2),
  186. emqtt:publish(C, <<"TopicA">>, <<"Payload">>, qos2),
  187. timer:sleep(1000),
  188. receive
  189. {publish, #{payload := Payload}} ->
  190. ?assertEqual(<<"Payload">>, Payload)
  191. after
  192. 1000 ->
  193. ct:fail({receive_timeout, <<"Payload">>}),
  194. ok
  195. end,
  196. emqtt:disconnect(C).
  197. reload(Config) when is_list(Config) ->
  198. application:stop(?APP),
  199. [application:set_env(?APP, K, V) || {K, V} <- Config],
  200. application:start(?APP).
  201. init_acl_() ->
  202. {ok, Pid} = ecpool_worker:client(gproc_pool:pick_worker({ecpool, ?POOL})),
  203. {ok, [], []} = epgsql:squery(Pid, ?DROP_ACL_TABLE),
  204. {ok, [], []} = epgsql:squery(Pid, ?CREATE_ACL_TABLE),
  205. {ok, _} = epgsql:equery(Pid, ?INIT_ACL).
  206. drop_acl_() ->
  207. {ok, Pid} = ecpool_worker:client(gproc_pool:pick_worker({ecpool, ?POOL})),
  208. {ok, [], []}= epgsql:squery(Pid, ?DROP_ACL_TABLE).
  209. init_auth_() ->
  210. {ok, Pid} = ecpool_worker:client(gproc_pool:pick_worker({ecpool, ?POOL})),
  211. {ok, [], []} = epgsql:squery(Pid, ?DROP_AUTH_TABLE),
  212. {ok, [], []} = epgsql:squery(Pid, ?CREATE_AUTH_TABLE),
  213. {ok, _} = epgsql:equery(Pid, ?INIT_AUTH).
  214. drop_auth_() ->
  215. {ok, Pid} = ecpool_worker:client(gproc_pool:pick_worker({ecpool, ?POOL})),
  216. {ok, [], []} = epgsql:squery(Pid, ?DROP_AUTH_TABLE).