zhouzb 4 лет назад
Родитель
Сommit
7c61bc18cf

+ 38 - 0
apps/emqx/src/emqx_tls_psk.erl

@@ -0,0 +1,38 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2019-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%%     http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%--------------------------------------------------------------------
+
+-module(emqx_tls_psk).
+
+-include("logger.hrl").
+
+%% SSL PSK Callbacks
+-export([lookup/3]).
+
+-type psk_identity() :: string().
+-type psk_user_state() :: term().
+
+-spec lookup(psk, psk_identity(), psk_user_state()) -> {ok, SharedSecret :: binary()} | error.
+lookup(psk, PSKIdentity, UserState) ->
+    try emqx_hooks:run_fold('tls_handshake.psk_lookup', [PSKIdentity], UserState) of
+        SharedSecret when is_binary(SharedSecret) -> {ok, SharedSecret};
+        Error ->
+            ?LOG(error, "Look PSK for PSKID ~p error: ~p", [PSKIdentity, Error]),
+            error
+    catch
+        Except:Error:Stacktrace ->
+          ?LOG(error, "Lookup PSK failed, ~0p: ~0p", [{Except,Error}, Stacktrace]),
+          error
+    end.

+ 2 - 0
apps/emqx_psk/data/boot.psk

@@ -0,0 +1,2 @@
+myclient1:8c701116e9127c57a99d5563709af3deaca75563e2c4dd0865701ae839fb6d79
+myclient2:d1e617d3b963757bfc21dad3fea169716c3a2f053f23decaea5cdfaabd04bfc4

+ 7 - 0
apps/emqx_psk/etc/emqx_psk.conf

@@ -0,0 +1,7 @@
+psk {
+    enable = true
+
+    # boot_file = {{ platform_data_dir }}/boot.psk
+
+    # delimiter = ":"
+}

+ 18 - 0
apps/emqx_psk/rebar.config

@@ -0,0 +1,18 @@
+{deps, []}.
+
+{edoc_opts, [{preprocess, true}]}.
+{erl_opts, [warn_unused_vars,
+            warn_shadow_vars,
+            warnings_as_errors,
+            warn_unused_import,
+            warn_obsolete_guard,
+            debug_info,
+            {parse_transform}]}.
+
+{xref_checks, [undefined_function_calls, undefined_functions,
+               locals_not_used, deprecated_function_calls,
+               warnings_as_errors, deprecated_functions]}.
+
+{cover_enabled, true}.
+{cover_opts, [verbose]}.
+{cover_export_enabled, true}.

+ 15 - 0
apps/emqx_psk/src/emqx_psk.app.src

@@ -0,0 +1,15 @@
+%% -*- mode: erlang -*-
+{application, emqx_psk,
+ [{description, "EMQ X PSK"},
+  {vsn, "5.0.0"}, % strict semver, bump manually!
+  {modules, []},
+  {registered, [emqx_psk_sup]},
+  {applications, [kernel,stdlib]},
+  {mod, {emqx_psk_app,[]}},
+  {env, []},
+  {licenses, ["Apache-2.0"]},
+  {maintainers, ["EMQ X Team <contact@emqx.io>"]},
+  {links, [{"Homepage", "https://emqx.io/"},
+           {"Github", "https://github.com/emqx/emqx"}
+          ]}
+ ]}.

+ 224 - 0
apps/emqx_psk/src/emqx_psk.erl

@@ -0,0 +1,224 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%%     http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%--------------------------------------------------------------------
+
+-module(emqx_psk).
+
+-behaviour(gen_server).
+
+-include_lib("emqx/include/logger.hrl").
+
+-export([ load/0
+        , unload/0
+        , on_psk_lookup/2
+        , import/1
+        ]).
+
+-export([ start_link/0
+        , stop/0
+        ]).
+
+%% gen_server callbacks
+-export([ init/1
+        , handle_call/3
+        , handle_cast/2
+        , handle_info/2
+        , terminate/2
+        , code_change/3
+        ]).
+
+-record(psk_entry, {psk_id :: binary(),
+                    shared_secret :: binary()}).
+
+-export([mnesia/1]).
+
+-boot_mnesia({mnesia, [boot]}).
+-copy_mnesia({mnesia, [copy]}).
+
+-define(TAB, ?MODULE).
+-define(PSK_SHARD, emqx_psk_shard).
+
+-define(DEFAULT_DELIMITER, <<":">>).
+
+-define(CR, 13).
+-define(LF, 10).
+
+%%------------------------------------------------------------------------------
+%% Mnesia bootstrap
+%%------------------------------------------------------------------------------
+
+%% @doc Create or replicate tables.
+-spec(mnesia(boot | copy) -> ok).
+mnesia(boot) ->
+    ok = ekka_mnesia:create_table(?TAB, [
+                {rlog_shard, ?PSK_SHARD},
+                {disc_copies, [node()]},
+                {record_name, psk_entry},
+                {attributes, record_info(fields, psk_entry)},
+                {storage_properties, [{ets, [{read_concurrency, true}]}]}]);
+
+mnesia(copy) ->
+    ok = ekka_mnesia:copy_table(?TAB, disc_copies).
+
+%%------------------------------------------------------------------------------
+%% APIs
+%%------------------------------------------------------------------------------
+
+load() ->
+    emqx:hook('tls_handshake.psk_lookup', {?MODULE, on_psk_lookup, []}).
+
+unload() ->
+    emqx:unhook('tls_handshake.psk_lookup', {?MODULE, on_psk_lookup, []}).
+
+on_psk_lookup(PSKIdentity, _UserState) ->
+    case mnesia:dirty_read(?TAB, PSKIdentity) of
+        [#psk_entry{shared_secret = SharedSecret}] ->
+            {stop, SharedSecret};
+        _ ->
+            ignore
+    end.
+
+import(SrcFile) ->
+    gen_server:call(?MODULE, {import, SrcFile}).
+
+-spec start_link() -> {ok, pid()} | ignore | {error, term()}.
+start_link() ->
+    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+-spec stop() -> ok.
+stop() ->
+    gen_server:stop(?MODULE).
+
+%%--------------------------------------------------------------------
+%% gen_server callbacks
+%%--------------------------------------------------------------------
+
+init(_Opts) ->
+    case is_enable() of
+        true -> load();
+        false -> ok
+    end,
+    case boot_file() of
+        undefined -> ok;
+        BootFile -> import_psks(BootFile)
+    end,
+    {ok, #{}}.
+
+handle_call({import, SrcFile}, _From, State) ->
+    {reply, import_psks(SrcFile), State};
+
+handle_call(Req, _From, State) ->
+    ?LOG(error, "Unexpected call: ~p", [Req]),
+    {reply, ignored, State}.
+
+handle_cast(Req, State) ->
+    ?LOG(error, "Unexpected case: ~p", [Req]),
+    {noreply, State}.
+
+handle_info(Info, State) ->
+    ?LOG(error, "Unexpected info: ~p", [Info]),
+    {noreply, State}.
+
+terminate(_Reason, _State) ->
+    unload(),
+    ok.
+
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+%%------------------------------------------------------------------------------
+%% Internal functions
+%%------------------------------------------------------------------------------
+
+is_enable() ->
+    emqx_config:get([psk, enable]).
+
+boot_file() ->
+    emqx_config:get([psk, boot_file], undefined).
+
+delimiter() ->
+    emqx_config:get([psk, delimiter], ?DEFAULT_DELIMITER).
+
+import_psks(SrcFile) ->
+    case file:open(SrcFile, [read, raw, binary, read_ahead]) of
+        {error, Reason} ->
+            {error, Reason};
+        {ok, Io} ->
+            case Result = import_psks(Io, delimiter()) of
+                ok -> ignore;
+                {error, Reason} ->
+                    ?LOG("Failed to import psk from ~s due to ~p", [SrcFile, Reason])
+            end,
+            _ = file:close(Io),
+            Result
+    end.
+
+import_psks(Io, Delimiter) ->
+    case get_psks(Io, Delimiter, 50) of
+        {ok, Entries} ->
+            _ = trans(fun insert_psks/1, Entries),
+            import_psks(Io, Delimiter);
+        {eof, Entries} ->
+            _ = trans(fun insert_psks/1, Entries),
+            ok;
+        {error, Reaosn} ->
+            {error, Reaosn}
+    end.
+
+get_psks(Io, Delimiter, Max) ->
+    get_psks(Io, Delimiter, Max, []).
+
+get_psks(_Io, _Delimiter, 0, Acc) ->
+    {ok, Acc};
+get_psks(Io, Delimiter, Remaining, Acc) ->
+    case file:read_line(Io) of
+        {ok, Line} ->
+            case binary:split(Line, Delimiter) of
+                [PSKIdentity, SharedSecret] ->
+                    NSharedSecret = trim_crlf(SharedSecret),
+                    get_psks(Io, Delimiter, Remaining - 1, [{PSKIdentity, NSharedSecret} | Acc]);
+                _ ->
+                    {error, {bad_format, Line}}
+            end;
+        eof ->
+            {eof, Acc};
+        {error, Reason} ->
+            {error, Reason}
+    end.
+
+insert_psks(Entries) ->
+    lists:foreach(fun(Entry) ->
+                      insert_psk(Entry)
+                  end, Entries).
+
+insert_psk({PSKIdentity, SharedSecret}) ->
+    mnesia:write(?TAB, #psk_entry{psk_id = PSKIdentity, shared_secret = SharedSecret}, write).
+
+trim_crlf(Bin) ->
+    Size = byte_size(Bin),
+    case binary:at(Bin, Size - 1) of
+        ?LF ->
+            case binary:at(Bin, Size - 2) of
+                ?CR -> binary:part(Bin, 0, Size - 2);
+                _ -> binary:part(Bin, 0, Size - 1)
+            end;
+        _ -> Bin
+    end.
+
+trans(Fun, Args) ->
+    case ekka_mnesia:transaction(?PSK_SHARD, Fun, Args) of
+        {atomic, Res} -> Res;
+        {aborted, Reason} -> {error, Reason}
+    end.

+ 30 - 0
apps/emqx_psk/src/emqx_psk_app.erl

@@ -0,0 +1,30 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%%     http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%--------------------------------------------------------------------
+
+-module(emqx_psk_app).
+
+-behaviour(application).
+
+-export([ start/2
+        , stop/1
+        ]).
+
+start(_Type, _Args) ->
+    {ok, Sup} = emqx_psk_sup:start_link(),
+    {ok, Sup}.
+
+stop(_State) ->
+    ok.

+ 45 - 0
apps/emqx_psk/src/emqx_psk_schema.erl

@@ -0,0 +1,45 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2021 EMQ Technologies Co., Ltd. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%%     http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%--------------------------------------------------------------------
+
+-module(emqx_psk_schema).
+
+-behaviour(hocon_schema).
+
+-include_lib("typerefl/include/types.hrl").
+
+-export([ roots/0
+        , fields/1
+        ]).
+
+roots() -> [].
+
+fields("psk") ->
+    [ {enable,    fun enable/1}
+    , {boot_file, fun boot_file/1}
+    , {delimiter, fun delimiter/1}
+    ].
+
+enable(type) -> boolean();
+enable(default) -> false;
+enable(_) -> undefined.
+
+boot_file(type) -> binary();
+boot_file(nullable) -> true;
+boot_file(_) -> undefined.
+
+delimiter(type) -> binary();
+delimiter(default) -> <<":">>;
+delimiter(_) -> undefined.

+ 35 - 0
apps/emqx_psk/src/emqx_psk_sup.erl

@@ -0,0 +1,35 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%%     http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%--------------------------------------------------------------------
+
+-module(emqx_psk_sup).
+
+-behaviour(supervisor).
+
+-export([start_link/0]).
+
+-export([init/1]).
+
+start_link() ->
+    supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+init([]) ->
+    {ok, {{one_for_one, 10, 3600},
+          [#{id       => emqx_psk,
+             start    => {emqx_psk, start_link, []},
+             restart  => permanent,
+             shutdown => 5000,
+             type     => worker,
+             modules  => [emqx_psk]}]}}.