JianBo He 4 лет назад
Родитель
Сommit
cf1d98adc6

+ 76 - 6
apps/emqx_gateway/src/emqx_gateway_conf.erl

@@ -17,6 +17,8 @@
 %% @doc The gateway configuration management module
 -module(emqx_gateway_conf).
 
+-include_lib("emqx/include/logger.hrl").
+
 %% Load/Unload
 -export([ load/0
         , unload/0
@@ -107,6 +109,8 @@ update_gateway(GwName, Conf0) ->
                          <<"listeners">>, <<"authentication">>], Conf0),
     update({?FUNCTION_NAME, bin(GwName), Conf}).
 
+%% FIXME: delete cert files ??
+
 -spec unload_gateway(atom_or_bin()) -> ok_or_err().
 unload_gateway(GwName) ->
     update({?FUNCTION_NAME, bin(GwName)}).
@@ -247,7 +251,8 @@ bin(B) when is_binary(B) ->
 pre_config_update({load_gateway, GwName, Conf}, RawConf) ->
     case maps:get(GwName, RawConf, undefined) of
         undefined ->
-            {ok, emqx_map_lib:deep_merge(RawConf, #{GwName => Conf})};
+            NConf = tune_gw_certs(fun convert_certs/2, GwName, Conf),
+            {ok, emqx_map_lib:deep_merge(RawConf, #{GwName => NConf})};
         _ ->
             {error, already_exist}
     end;
@@ -261,13 +266,18 @@ pre_config_update({update_gateway, GwName, Conf}, RawConf) ->
             {ok, emqx_map_lib:deep_merge(RawConf, #{GwName => NConf})}
     end;
 pre_config_update({unload_gateway, GwName}, RawConf) ->
+    _ = tune_gw_certs(fun clear_certs/2,
+                      GwName,
+                      maps:get(GwName, RawConf, #{})
+                     ),
     {ok, maps:remove(GwName, RawConf)};
 
 pre_config_update({add_listener, GwName, {LType, LName}, Conf}, RawConf) ->
     case emqx_map_lib:deep_get(
            [GwName, <<"listeners">>, LType, LName], RawConf, undefined) of
         undefined ->
-            NListener = #{LType => #{LName => Conf}},
+            NConf = convert_certs(certs_dir(GwName), Conf),
+            NListener = #{LType => #{LName => NConf}},
             {ok, emqx_map_lib:deep_merge(
                    RawConf,
                    #{GwName => #{<<"listeners">> => NListener}})};
@@ -279,16 +289,23 @@ pre_config_update({update_listener, GwName, {LType, LName}, Conf}, RawConf) ->
            [GwName, <<"listeners">>, LType, LName], RawConf, undefined) of
         undefined ->
             {error, not_found};
-        _OldConf ->
-            NListener = #{LType => #{LName => Conf}},
+        OldConf ->
+            NConf = convert_certs(certs_dir(GwName), Conf, OldConf),
+            NListener = #{LType => #{LName => NConf}},
             {ok, emqx_map_lib:deep_merge(
                    RawConf,
                    #{GwName => #{<<"listeners">> => NListener}})}
 
     end;
 pre_config_update({remove_listener, GwName, {LType, LName}}, RawConf) ->
-    {ok, emqx_map_lib:deep_remove(
-           [GwName, <<"listeners">>, LType, LName], RawConf)};
+    Path = [GwName, <<"listeners">>, LType, LName],
+    case emqx_map_lib:deep_get(Path, RawConf, undefined) of
+         undefined ->
+            {ok, RawConf};
+        OldConf ->
+            clear_certs(certs_dir(GwName), OldConf),
+            {ok, emqx_map_lib:deep_remove(Path, RawConf)}
+    end;
 
 pre_config_update({add_authn, GwName, Conf}, RawConf) ->
     case emqx_map_lib:deep_get(
@@ -382,3 +399,56 @@ post_config_update(Req, NewConfig, OldConfig, _AppEnvs) when is_tuple(Req) ->
     end;
 post_config_update(_Req, _NewConfig, _OldConfig, _AppEnvs) ->
     ok.
+
+%%--------------------------------------------------------------------
+%% Internal funcs
+%%--------------------------------------------------------------------
+
+
+tune_gw_certs(Fun, GwName, Conf) ->
+    SubDir = certs_dir(GwName),
+    case maps:get(<<"listeners">>, Conf, undefined) of
+        undefined -> Conf;
+        Liss ->
+            maps:put(<<"listeners">>,
+                maps:map(fun(_, Lis) ->
+                    maps:map(fun(_, LisConf) ->
+                        erlang:apply(Fun, [SubDir, LisConf])
+                    end, Lis)
+                end, Liss),
+                Conf)
+   end.
+
+certs_dir(GwName) when is_binary(GwName) ->
+    GwName.
+
+convert_certs(SubDir, Conf) ->
+    case emqx_tls_lib:ensure_ssl_files(
+           SubDir,
+           maps:get(<<"ssl">>, Conf, undefined)
+          ) of
+        {ok, SSL} ->
+            new_ssl_config(Conf, SSL);
+        {error, Reason} ->
+            ?SLOG(error, Reason#{msg => bad_ssl_config}),
+            throw({bad_ssl_config, Reason})
+    end.
+
+convert_certs(SubDir, NConf, OConf) ->
+    OSSL = maps:get(<<"ssl">>, OConf, undefined),
+    NSSL = maps:get(<<"ssl">>, NConf, undefined),
+    case emqx_tls_lib:ensure_ssl_files(SubDir, NSSL) of
+        {ok, NSSL1} ->
+            ok = emqx_tls_lib:delete_ssl_files(SubDir, NSSL1, OSSL),
+            new_ssl_config(NConf, NSSL1);
+        {error, Reason} ->
+            ?SLOG(error, Reason#{msg => bad_ssl_config}),
+            throw({bad_ssl_config, Reason})
+    end.
+
+new_ssl_config(Conf, undefined) -> Conf;
+new_ssl_config(Conf, SSL) -> Conf#{<<"ssl">> => SSL}.
+
+clear_certs(SubDir, Conf) ->
+    SSL = maps:get(<<"ssl">>, Conf, undefined),
+    ok = emqx_tls_lib:delete_ssl_files(SubDir, undefined, SSL).

+ 4 - 4
apps/emqx_gateway/src/emqx_gateway_schema.erl

@@ -340,7 +340,7 @@ After succeed observe a resource of LwM2M client, Gateway will send the notifyev
 
 fields(translator) ->
     [ {topic, sc(binary())}
-    , {qos, sc(range(0, 2), 0)}
+    , {qos, sc(range(0, 2), #{default => 0})}
     ];
 
 fields(udp_listeners) ->
@@ -362,7 +362,7 @@ fields(udp_tcp_listeners) ->
 
 fields(tcp_listener) ->
     [ %% some special confs for tcp listener
-      {acceptors, sc(integer(), 16)}
+      {acceptors, sc(integer(), #{default => 16})}
     ] ++
     tcp_opts() ++
     proxy_protocol_opts() ++
@@ -390,11 +390,11 @@ fields(dtls_listener) ->
     [{dtls, sc(ref(dtls_opts), #{desc => "DTLS listener options"})}];
 
 fields(udp_opts) ->
-    [ {active_n, sc(integer(), 100)}
+    [ {active_n, sc(integer(), #{default => 100})}
     , {recbuf, sc(bytesize())}
     , {sndbuf, sc(bytesize())}
     , {buffer, sc(bytesize())}
-    , {reuseaddr, sc(boolean(), true)}
+    , {reuseaddr, sc(boolean(), #{default => true})}
     ];
 
 fields(dtls_opts) ->

+ 240 - 1
apps/emqx_gateway/test/emqx_gateway_conf_SUITE.erl

@@ -49,6 +49,133 @@ init_per_testcase(_CaseName, Conf) ->
 %% Cases
 %%--------------------------------------------------------------------
 
+-define(SVR_CA,
+<<"-----BEGIN CERTIFICATE-----
+MIIDUTCCAjmgAwIBAgIJAPPYCjTmxdt/MA0GCSqGSIb3DQEBCwUAMD8xCzAJBgNV
+BAYTAkNOMREwDwYDVQQIDAhoYW5nemhvdTEMMAoGA1UECgwDRU1RMQ8wDQYDVQQD
+DAZSb290Q0EwHhcNMjAwNTA4MDgwNjUyWhcNMzAwNTA2MDgwNjUyWjA/MQswCQYD
+VQQGEwJDTjERMA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UE
+AwwGUm9vdENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzcgVLex1
+EZ9ON64EX8v+wcSjzOZpiEOsAOuSXOEN3wb8FKUxCdsGrsJYB7a5VM/Jot25Mod2
+juS3OBMg6r85k2TWjdxUoUs+HiUB/pP/ARaaW6VntpAEokpij/przWMPgJnBF3Ur
+MjtbLayH9hGmpQrI5c2vmHQ2reRZnSFbY+2b8SXZ+3lZZgz9+BaQYWdQWfaUWEHZ
+uDaNiViVO0OT8DRjCuiDp3yYDj3iLWbTA/gDL6Tf5XuHuEwcOQUrd+h0hyIphO8D
+tsrsHZ14j4AWYLk1CPA6pq1HIUvEl2rANx2lVUNv+nt64K/Mr3RnVQd9s8bK+TXQ
+KGHd2Lv/PALYuwIDAQABo1AwTjAdBgNVHQ4EFgQUGBmW+iDzxctWAWxmhgdlE8Pj
+EbQwHwYDVR0jBBgwFoAUGBmW+iDzxctWAWxmhgdlE8PjEbQwDAYDVR0TBAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAQEAGbhRUjpIred4cFAFJ7bbYD9hKu/yzWPWkMRa
+ErlCKHmuYsYk+5d16JQhJaFy6MGXfLgo3KV2itl0d+OWNH0U9ULXcglTxy6+njo5
+CFqdUBPwN1jxhzo9yteDMKF4+AHIxbvCAJa17qcwUKR5MKNvv09C6pvQDJLzid7y
+E2dkgSuggik3oa0427KvctFf8uhOV94RvEDyqvT5+pgNYZ2Yfga9pD/jjpoHEUlo
+88IGU8/wJCx3Ds2yc8+oBg/ynxG8f/HmCC1ET6EHHoe2jlo8FpU/SgGtghS1YL30
+IWxNsPrUP+XsZpBJy/mvOhE5QXo6Y35zDqqj8tI7AGmAWu22jg==
+-----END CERTIFICATE-----
+">>).
+
+-define(SVR_CERT,
+<<"-----BEGIN CERTIFICATE-----
+MIIDEzCCAfugAwIBAgIBAjANBgkqhkiG9w0BAQsFADA/MQswCQYDVQQGEwJDTjER
+MA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UEAwwGUm9vdENB
+MB4XDTIwMDUwODA4MDcwNVoXDTMwMDUwNjA4MDcwNVowPzELMAkGA1UEBhMCQ04x
+ETAPBgNVBAgMCGhhbmd6aG91MQwwCgYDVQQKDANFTVExDzANBgNVBAMMBlNlcnZl
+cjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALNeWT3pE+QFfiRJzKmn
+AMUrWo3K2j/Tm3+Xnl6WLz67/0rcYrJbbKvS3uyRP/stXyXEKw9CepyQ1ViBVFkW
+Aoy8qQEOWFDsZc/5UzhXUnb6LXr3qTkFEjNmhj+7uzv/lbBxlUG1NlYzSeOB6/RT
+8zH/lhOeKhLnWYPXdXKsa1FL6ij4X8DeDO1kY7fvAGmBn/THh1uTpDizM4YmeI+7
+4dmayA5xXvARte5h4Vu5SIze7iC057N+vymToMk2Jgk+ZZFpyXrnq+yo6RaD3ANc
+lrc4FbeUQZ5a5s5Sxgs9a0Y3WMG+7c5VnVXcbjBRz/aq2NtOnQQjikKKQA8GF080
+BQkCAwEAAaMaMBgwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQEL
+BQADggEBAJefnMZpaRDHQSNUIEL3iwGXE9c6PmIsQVE2ustr+CakBp3TZ4l0enLt
+iGMfEVFju69cO4oyokWv+hl5eCMkHBf14Kv51vj448jowYnF1zmzn7SEzm5Uzlsa
+sqjtAprnLyof69WtLU1j5rYWBuFX86yOTwRAFNjm9fvhAcrEONBsQtqipBWkMROp
+iUYMkRqbKcQMdwxov+lHBYKq9zbWRoqLROAn54SRqgQk6c15JdEfgOOjShbsOkIH
+UhqcwRkQic7n1zwHVGVDgNIZVgmJ2IdIWBlPEC7oLrRrBD/X1iEEXtKab6p5o22n
+KB5mN+iQaE+Oe2cpGKZJiJRdM+IqDDQ=
+-----END CERTIFICATE-----
+">>).
+
+-define(SVR_KEY,
+<<"-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAs15ZPekT5AV+JEnMqacAxStajcraP9Obf5eeXpYvPrv/Stxi
+sltsq9Le7JE/+y1fJcQrD0J6nJDVWIFUWRYCjLypAQ5YUOxlz/lTOFdSdvotevep
+OQUSM2aGP7u7O/+VsHGVQbU2VjNJ44Hr9FPzMf+WE54qEudZg9d1cqxrUUvqKPhf
+wN4M7WRjt+8AaYGf9MeHW5OkOLMzhiZ4j7vh2ZrIDnFe8BG17mHhW7lIjN7uILTn
+s36/KZOgyTYmCT5lkWnJeuer7KjpFoPcA1yWtzgVt5RBnlrmzlLGCz1rRjdYwb7t
+zlWdVdxuMFHP9qrY206dBCOKQopADwYXTzQFCQIDAQABAoIBAQCuvCbr7Pd3lvI/
+n7VFQG+7pHRe1VKwAxDkx2t8cYos7y/QWcm8Ptwqtw58HzPZGWYrgGMCRpzzkRSF
+V9g3wP1S5Scu5C6dBu5YIGc157tqNGXB+SpdZddJQ4Nc6yGHXYERllT04ffBGc3N
+WG/oYS/1cSteiSIrsDy/91FvGRCi7FPxH3wIgHssY/tw69s1Cfvaq5lr2NTFzxIG
+xCvpJKEdSfVfS9I7LYiymVjst3IOR/w76/ZFY9cRa8ZtmQSWWsm0TUpRC1jdcbkm
+ZoJptYWlP+gSwx/fpMYftrkJFGOJhHJHQhwxT5X/ajAISeqjjwkWSEJLwnHQd11C
+Zy2+29lBAoGBANlEAIK4VxCqyPXNKfoOOi5dS64NfvyH4A1v2+KaHWc7lqaqPN49
+ezfN2n3X+KWx4cviDD914Yc2JQ1vVJjSaHci7yivocDo2OfZDmjBqzaMp/y+rX1R
+/f3MmiTqMa468rjaxI9RRZu7vDgpTR+za1+OBCgMzjvAng8dJuN/5gjlAoGBANNY
+uYPKtearBmkqdrSV7eTUe49Nhr0XotLaVBH37TCW0Xv9wjO2xmbm5Ga/DCtPIsBb
+yPeYwX9FjoasuadUD7hRvbFu6dBa0HGLmkXRJZTcD7MEX2Lhu4BuC72yDLLFd0r+
+Ep9WP7F5iJyagYqIZtz+4uf7gBvUDdmvXz3sGr1VAoGAdXTD6eeKeiI6PlhKBztF
+zOb3EQOO0SsLv3fnodu7ZaHbUgLaoTMPuB17r2jgrYM7FKQCBxTNdfGZmmfDjlLB
+0xZ5wL8ibU30ZXL8zTlWPElST9sto4B+FYVVF/vcG9sWeUUb2ncPcJ/Po3UAktDG
+jYQTTyuNGtSJHpad/YOZctkCgYBtWRaC7bq3of0rJGFOhdQT9SwItN/lrfj8hyHA
+OjpqTV4NfPmhsAtu6j96OZaeQc+FHvgXwt06cE6Rt4RG4uNPRluTFgO7XYFDfitP
+vCppnoIw6S5BBvHwPP+uIhUX2bsi/dm8vu8tb+gSvo4PkwtFhEr6I9HglBKmcmog
+q6waEQKBgHyecFBeM6Ls11Cd64vborwJPAuxIW7HBAFj/BS99oeG4TjBx4Sz2dFd
+rzUibJt4ndnHIvCN8JQkjNG14i9hJln+H3mRss8fbZ9vQdqG+2vOWADYSzzsNI55
+RFY7JjluKcVkp/zCDeUxTU3O6sS+v6/3VE11Cob6OYQx3lN5wrZ3
+-----END RSA PRIVATE KEY-----
+">>).
+
+-define(SVR_CERT2,
+<<"-----BEGIN CERTIFICATE-----
+MIIDEzCCAfugAwIBAgIBATANBgkqhkiG9w0BAQsFADA/MQswCQYDVQQGEwJDTjER
+MA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UEAwwGUm9vdENB
+MB4XDTIwMDUwODA4MDY1N1oXDTMwMDUwNjA4MDY1N1owPzELMAkGA1UEBhMCQ04x
+ETAPBgNVBAgMCGhhbmd6aG91MQwwCgYDVQQKDANFTVExDzANBgNVBAMMBkNsaWVu
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMy4hoksKcZBDbY680u6
+TS25U51nuB1FBcGMlF9B/t057wPOlxF/OcmbxY5MwepS41JDGPgulE1V7fpsXkiW
+1LUimYV/tsqBfymIe0mlY7oORahKji7zKQ2UBIVFhdlvQxunlIDnw6F9popUgyHt
+dMhtlgZK8oqRwHxO5dbfoukYd6J/r+etS5q26sgVkf3C6dt0Td7B25H9qW+f7oLV
+PbcHYCa+i73u9670nrpXsC+Qc7Mygwa2Kq/jwU+ftyLQnOeW07DuzOwsziC/fQZa
+nbxR+8U9FNftgRcC3uP/JMKYUqsiRAuaDokARZxVTV5hUElfpO6z6/NItSDvvh3i
+eikCAwEAAaMaMBgwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQEL
+BQADggEBABchYxKo0YMma7g1qDswJXsR5s56Czx/I+B41YcpMBMTrRqpUC0nHtLk
+M7/tZp592u/tT8gzEnQjZLKBAhFeZaR3aaKyknLqwiPqJIgg0pgsBGITrAK3Pv4z
+5/YvAJJKgTe5UdeTz6U4lvNEux/4juZ4pmqH4qSFJTOzQS7LmgSmNIdd072rwXBd
+UzcSHzsJgEMb88u/LDLjj1pQ7AtZ4Tta8JZTvcgBFmjB0QUi6fgkHY6oGat/W4kR
+jSRUBlMUbM/drr2PVzRc2dwbFIl3X+ZE6n5Sl3ZwRAC/s92JU6CPMRW02muVu6xl
+goraNgPISnrbpR6KjxLZkVembXzjNNc=
+-----END CERTIFICATE-----
+">>).
+
+-define(SVR_KEY2,
+<<"-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAzLiGiSwpxkENtjrzS7pNLblTnWe4HUUFwYyUX0H+3TnvA86X
+EX85yZvFjkzB6lLjUkMY+C6UTVXt+mxeSJbUtSKZhX+2yoF/KYh7SaVjug5FqEqO
+LvMpDZQEhUWF2W9DG6eUgOfDoX2milSDIe10yG2WBkryipHAfE7l1t+i6Rh3on+v
+561LmrbqyBWR/cLp23RN3sHbkf2pb5/ugtU9twdgJr6Lve73rvSeulewL5BzszKD
+BrYqr+PBT5+3ItCc55bTsO7M7CzOIL99BlqdvFH7xT0U1+2BFwLe4/8kwphSqyJE
+C5oOiQBFnFVNXmFQSV+k7rPr80i1IO++HeJ6KQIDAQABAoIBAGWgvPjfuaU3qizq
+uti/FY07USz0zkuJdkANH6LiSjlchzDmn8wJ0pApCjuIE0PV/g9aS8z4opp5q/gD
+UBLM/a8mC/xf2EhTXOMrY7i9p/I3H5FZ4ZehEqIw9sWKK9YzC6dw26HabB2BGOnW
+5nozPSQ6cp2RGzJ7BIkxSZwPzPnVTgy3OAuPOiJytvK+hGLhsNaT+Y9bNDvplVT2
+ZwYTV8GlHZC+4b2wNROILm0O86v96O+Qd8nn3fXjGHbMsAnONBq10bZS16L4fvkH
+5G+W/1PeSXmtZFppdRRDxIW+DWcXK0D48WRliuxcV4eOOxI+a9N2ZJZZiNLQZGwg
+w3A8+mECgYEA8HuJFrlRvdoBe2U/EwUtG74dcyy30L4yEBnN5QscXmEEikhaQCfX
+Wm6EieMcIB/5I5TQmSw0cmBMeZjSXYoFdoI16/X6yMMuATdxpvhOZGdUGXxhAH+x
+xoTUavWZnEqW3fkUU71kT5E2f2i+0zoatFESXHeslJyz85aAYpP92H0CgYEA2e5A
+Yozt5eaA1Gyhd8SeptkEU4xPirNUnVQHStpMWUb1kzTNXrPmNWccQ7JpfpG6DcYl
+zUF6p6mlzY+zkMiyPQjwEJlhiHM2NlL1QS7td0R8ewgsFoyn8WsBI4RejWrEG9td
+EDniuIw+pBFkcWthnTLHwECHdzgquToyTMjrBB0CgYEA28tdGbrZXhcyAZEhHAZA
+Gzog+pKlkpEzeonLKIuGKzCrEKRecIK5jrqyQsCjhS0T7ZRnL4g6i0s+umiV5M5w
+fcc292pEA1h45L3DD6OlKplSQVTv55/OYS4oY3YEJtf5mfm8vWi9lQeY8sxOlQpn
+O+VZTdBHmTC8PGeTAgZXHZUCgYA6Tyv88lYowB7SN2qQgBQu8jvdGtqhcs/99GCr
+H3N0I69LPsKAR0QeH8OJPXBKhDUywESXAaEOwS5yrLNP1tMRz5Vj65YUCzeDG3kx
+gpvY4IMp7ArX0bSRvJ6mYSFnVxy3k174G3TVCfksrtagHioVBGQ7xUg5ltafjrms
+n8l55QKBgQDVzU8tQvBVqY8/1lnw11Vj4fkE/drZHJ5UkdC1eenOfSWhlSLfUJ8j
+ds7vEWpRPPoVuPZYeR1y78cyxKe1GBx6Wa2lF5c7xjmiu0xbRnrxYeLolce9/ntp
+asClqpnHT8/VJYTD7Kqj0fouTTZf0zkig/y+2XERppd8k+pSKjUCPQ==
+-----END RSA PRIVATE KEY-----
+">>).
+
 -define(CONF_STOMP_BAISC_1,
         #{ <<"idle_timeout">> => <<"10s">>,
            <<"mountpoint">> => <<"t/">>,
@@ -73,6 +200,31 @@ init_per_testcase(_CaseName, Conf) ->
 -define(CONF_STOMP_LISTENER_2,
         #{ <<"bind">> => <<"61614">>
          }).
+-define(CONF_STOMP_LISTENER_SSL,
+        #{ <<"bind">> => <<"61614">>,
+           <<"ssl">> =>
+           #{ <<"cacertfile">> => ?SVR_CA,
+              <<"certfile">> => ?SVR_CERT,
+              <<"keyfile">> => ?SVR_KEY
+            }
+         }).
+-define(CONF_STOMP_LISTENER_SSL_2,
+        #{ <<"bind">> => <<"61614">>,
+           <<"ssl">> =>
+           #{ <<"cacertfile">> => ?SVR_CA,
+              <<"certfile">> => ?SVR_CERT2,
+              <<"keyfile">> => ?SVR_KEY2
+            }
+         }).
+-define(CERTS_PATH(CertName), filename:join(["../../lib/emqx/etc/certs/", CertName])).
+-define(CONF_STOMP_LISTENER_SSL_PATH,
+        #{ <<"bind">> => <<"61614">>,
+           <<"ssl">> =>
+           #{ <<"cacertfile">> => ?CERTS_PATH("cacert.pem"),
+              <<"certfile">> => ?CERTS_PATH("cert.pem"),
+              <<"keyfile">> => ?CERTS_PATH("key.pem")
+            }
+         }).
 -define(CONF_STOMP_AUTHN_1,
         #{ <<"mechanism">> => <<"password-based">>,
            <<"backend">> => <<"built-in-database">>,
@@ -92,7 +244,6 @@ t_load_unload_gateway(_) ->
     StompConf2 = compose(?CONF_STOMP_BAISC_2,
                          ?CONF_STOMP_AUTHN_1,
                          ?CONF_STOMP_LISTENER_1),
-
     ok = emqx_gateway_conf:load_gateway(stomp, StompConf1),
     {error, already_exist} =
         emqx_gateway_conf:load_gateway(stomp, StompConf1),
@@ -210,6 +361,87 @@ t_load_remove_listener_authn(_) ->
       ),
     ok.
 
+t_load_gateway_with_certs_content(_) ->
+    StompConf = compose_ssl_listener(
+                  ?CONF_STOMP_BAISC_1,
+                  ?CONF_STOMP_LISTENER_SSL
+                 ),
+    ok = emqx_gateway_conf:load_gateway(<<"stomp">>, StompConf),
+    assert_confs(StompConf, emqx:get_raw_config([gateway, stomp])),
+    SslConf = emqx_map_lib:deep_get(
+                [<<"listeners">>, <<"ssl">>, <<"default">>, <<"ssl">>],
+                emqx:get_raw_config([gateway, stomp])
+               ),
+    ok = emqx_gateway_conf:unload_gateway(<<"stomp">>),
+    assert_ssl_confs_files_deleted(SslConf),
+    ?assertException(error, {config_not_found, [gateway, stomp]},
+                     emqx:get_raw_config([gateway, stomp])),
+    ok.
+
+%% TODO: Comment out this test case for now, because emqx_tls_lib
+%% will delete the configured certificate file.
+
+%t_load_gateway_with_certs_path(_) ->
+%    StompConf = compose_ssl_listener(
+%                  ?CONF_STOMP_BAISC_1,
+%                  ?CONF_STOMP_LISTENER_SSL_PATH
+%                 ),
+%    ok = emqx_gateway_conf:load_gateway(<<"stomp">>, StompConf),
+%    assert_confs(StompConf, emqx:get_raw_config([gateway, stomp])),
+%    SslConf = emqx_map_lib:deep_get(
+%                [<<"listeners">>, <<"ssl">>, <<"default">>, <<"ssl">>],
+%                emqx:get_raw_config([gateway, stomp])
+%               ),
+%    ok = emqx_gateway_conf:unload_gateway(<<"stomp">>),
+%    assert_ssl_confs_files_deleted(SslConf),
+%    ?assertException(error, {config_not_found, [gateway, stomp]},
+%                     emqx:get_raw_config([gateway, stomp])),
+%    ok.
+
+t_add_listener_with_certs_content(_) ->
+    StompConf = ?CONF_STOMP_BAISC_1,
+    StompConf1 = compose_ssl_listener(
+                   ?CONF_STOMP_BAISC_1,
+                   ?CONF_STOMP_LISTENER_SSL
+                  ),
+    ok = emqx_gateway_conf:load_gateway(<<"stomp">>, StompConf),
+    assert_confs(StompConf, emqx:get_raw_config([gateway, stomp])),
+
+    ok = emqx_gateway_conf:add_listener(
+           <<"stomp">>, {<<"ssl">>, <<"default">>}, ?CONF_STOMP_LISTENER_SSL),
+    assert_confs(
+      maps:merge(StompConf, ssl_listener(?CONF_STOMP_LISTENER_SSL)),
+      emqx:get_raw_config([gateway, stomp])),
+
+    ok = emqx_gateway_conf:update_listener(
+           <<"stomp">>, {<<"ssl">>, <<"default">>}, ?CONF_STOMP_LISTENER_SSL_2),
+    assert_confs(
+      maps:merge(StompConf, ssl_listener(?CONF_STOMP_LISTENER_SSL_2)),
+      emqx:get_raw_config([gateway, stomp])),
+
+    SslConf = emqx_map_lib:deep_get(
+                [<<"listeners">>, <<"ssl">>, <<"default">>, <<"ssl">>],
+                emqx:get_raw_config([gateway, stomp])
+               ),
+    ok = emqx_gateway_conf:remove_listener(
+           <<"stomp">>, {<<"ssl">>, <<"default">>}),
+    assert_ssl_confs_files_deleted(SslConf),
+    {error, not_found} =
+        emqx_gateway_conf:update_listener(
+          <<"stomp">>, {<<"ssl">>, <<"default">>}, ?CONF_STOMP_LISTENER_SSL_2),
+    ?assertException(
+       error, {config_not_found, [gateway, stomp, listeners, ssl, default]},
+       emqx:get_raw_config([gateway, stomp, listeners, ssl, default])
+      ),
+    ok.
+
+assert_ssl_confs_files_deleted(SslConf) when is_map(SslConf) ->
+    Ks = [<<"cacertfile">>, <<"certfile">>, <<"keyfile">>],
+    lists:foreach(fun(K) ->
+        Path = maps:get(K, SslConf),
+        {error, enoent} = file:read_file(Path)
+    end, Ks).
+
 %%--------------------------------------------------------------------
 %% Utils
 
@@ -221,6 +453,9 @@ compose(Basic, Authn, Listener) ->
 compose_listener(Basic, Listener) ->
     maps:merge(Basic, listener(Listener)).
 
+compose_ssl_listener(Basic, Listener) ->
+    maps:merge(Basic, ssl_listener(Listener)).
+
 compose_authn(Basic, Authn) ->
     maps:merge(Basic, #{<<"authentication">> => Authn}).
 
@@ -232,3 +467,7 @@ compose_listener_authn(Basic, Listener, Authn) ->
 listener(L) ->
     #{<<"listeners">> => [L#{<<"type">> => <<"tcp">>,
                              <<"name">> => <<"default">>}]}.
+
+ssl_listener(L) ->
+    #{<<"listeners">> => [L#{<<"type">> => <<"ssl">>,
+                             <<"name">> => <<"default">>}]}.

+ 23 - 10
apps/emqx_gateway/test/emqx_gateway_test_utils.erl

@@ -21,7 +21,7 @@
 
 assert_confs(Expected0, Effected) ->
     Expected = maybe_unconvert_listeners(Expected0),
-    case do_assert_confs(Expected, Effected) of
+    case do_assert_confs(root, Expected, Effected) of
         false ->
             io:format(standard_error, "Expected config: ~p,\n"
                                       "Effected config: ~p",
@@ -31,23 +31,36 @@ assert_confs(Expected0, Effected) ->
             ok
     end.
 
-do_assert_confs(Expected, Effected) when is_map(Expected),
-                                         is_map(Effected) ->
+do_assert_confs(_Key, Expected, Effected) when is_map(Expected),
+                                               is_map(Effected) ->
     Ks1 = maps:keys(Expected),
     lists:all(fun(K) ->
-        do_assert_confs(maps:get(K, Expected),
+        do_assert_confs(K,
+                        maps:get(K, Expected),
                         maps:get(K, Effected, undefined))
     end, Ks1);
 
-do_assert_confs([Expected|More1], [Effected|More2]) ->
-    do_assert_confs(Expected, Effected) andalso do_assert_confs(More1, More2);
-do_assert_confs([], []) ->
+do_assert_confs(Key, Expected, Effected) when Key == <<"cacertfile">>;
+                                              Key == <<"certfile">>;
+                                              Key == <<"keyfile">> ->
+    case Expected == Effected of
+        true -> true;
+        false ->
+            case file:read_file(Effected) of
+                {ok, Content} -> Expected == Content;
+                _ -> false
+            end
+    end;
+do_assert_confs(Key, [Expected|More1], [Effected|More2]) ->
+    do_assert_confs(Key, Expected, Effected)
+    andalso do_assert_confs(Key, More1, More2);
+do_assert_confs(_Key, [], []) ->
     true;
-do_assert_confs(Expected, Effected) ->
+do_assert_confs(Key, Expected, Effected) ->
     Res = Expected =:= Effected,
     Res == false andalso
-    ct:pal("Errors: conf not match, "
-           "expected: ~p, got: ~p~n", [Expected, Effected]),
+    ct:pal("Errors: ~p value not match, "
+           "expected: ~p, got: ~p~n", [Key, Expected, Effected]),
     Res.
 
 maybe_unconvert_listeners(Conf) when is_map(Conf) ->