chore: drop support for license files (ee5.0)
Related issue: EMQX-7247 EMQX ee5.0 is to drop support for both the old license format (x509) and for files containing the new format.
This commit is contained in:
parent
25b29de821
commit
d4416dc581
24
bin/emqx
24
bin/emqx
|
@ -417,7 +417,7 @@ call_hocon() {
|
||||||
## and parsing HOCON config + environment variables is a non-trivial task
|
## and parsing HOCON config + environment variables is a non-trivial task
|
||||||
CONF_KEYS=( 'node.data_dir' 'node.name' 'node.cookie' 'node.db_backend' 'cluster.proto_dist' )
|
CONF_KEYS=( 'node.data_dir' 'node.name' 'node.cookie' 'node.db_backend' 'cluster.proto_dist' )
|
||||||
if [ "$IS_ENTERPRISE" = 'yes' ]; then
|
if [ "$IS_ENTERPRISE" = 'yes' ]; then
|
||||||
CONF_KEYS+=( 'license.type' 'license.file' 'license.key' )
|
CONF_KEYS+=( 'license.key' )
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
|
if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
|
||||||
|
@ -499,22 +499,16 @@ check_license() {
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
file_license="${EMQX_LICENSE__FILE:-$(get_boot_config 'license.file')}"
|
key_license="${EMQX_LICENSE__KEY:-$(get_boot_config 'license.key')}"
|
||||||
|
|
||||||
if [[ -n "$file_license" && ("$file_license" != "undefined") ]]; then
|
if [[ -n "$key_license" && ("$key_license" != "undefined") ]]; then
|
||||||
call_nodetool check_license_file "$file_license"
|
call_nodetool check_license_key "$key_license"
|
||||||
else
|
else
|
||||||
key_license="${EMQX_LICENSE__KEY:-$(get_boot_config 'license.key')}"
|
set +x
|
||||||
|
echoerr "License not found."
|
||||||
if [[ -n "$key_license" && ("$key_license" != "undefined") ]]; then
|
echoerr "Please specify one via the EMQX_LICENSE__KEY variable"
|
||||||
call_nodetool check_license_key "$key_license"
|
echoerr "or via license.key in emqx-enterprise.conf."
|
||||||
else
|
return 1
|
||||||
set +x
|
|
||||||
echoerr "License not found."
|
|
||||||
echoerr "Please specify one via EMQX_LICENSE__KEY or EMQX_LICENSE__FILE variables"
|
|
||||||
echoerr "or via license.key|file in emqx_enterprise.conf."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,9 +25,7 @@ main(Args) ->
|
||||||
%% forward the call to hocon_cli
|
%% forward the call to hocon_cli
|
||||||
hocon_cli:main(Rest);
|
hocon_cli:main(Rest);
|
||||||
["check_license_key", Key] ->
|
["check_license_key", Key] ->
|
||||||
check_license(#{type => key, key => list_to_binary(Key)});
|
check_license(#{key => list_to_binary(Key)});
|
||||||
["check_license_file", File] ->
|
|
||||||
check_license(#{type => file, file => list_to_binary(File)});
|
|
||||||
_ ->
|
_ ->
|
||||||
do(Args)
|
do(Args)
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
license {
|
license {
|
||||||
type = key
|
|
||||||
# The default license has 1000 connections limit, it is issued on 20220419 and valid for 5 years (1825 days)
|
# The default license has 1000 connections limit, it is issued on 20220419 and valid for 5 years (1825 days)
|
||||||
key = "MjIwMTExCjAKMTAKRXZhbHVhdGlvbgpjb250YWN0QGVtcXguaW8KZGVmYXVsdAoyMDIyMDQxOQoxODI1CjEwMDAK.MEQCICbgRVijCQov2hrvZXR1mk9Oa+tyV1F5oJ6iOZeSHjnQAiB9dUiVeaZekDOjztk+NCWjhk4PG8tWfw2uFZWruSzD6g=="
|
key = "MjIwMTExCjAKMTAKRXZhbHVhdGlvbgpjb250YWN0QGVtcXguaW8KZGVmYXVsdAoyMDIyMDQxOQoxODI1CjEwMDAK.MEQCICbgRVijCQov2hrvZXR1mk9Oa+tyV1F5oJ6iOZeSHjnQAiB9dUiVeaZekDOjztk+NCWjhk4PG8tWfw2uFZWruSzD6g=="
|
||||||
connection_low_watermark = 75%,
|
connection_low_watermark = 75%,
|
||||||
|
|
|
@ -10,17 +10,6 @@ emqx_license_http_api {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
desc_license_file_api {
|
|
||||||
desc {
|
|
||||||
en: "Upload a license file"
|
|
||||||
zh: "上传一个许可证文件"
|
|
||||||
}
|
|
||||||
label: {
|
|
||||||
en: "Update license"
|
|
||||||
zh: "更新许可证"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
desc_license_key_api {
|
desc_license_key_api {
|
||||||
desc {
|
desc {
|
||||||
en: "Update a license key"
|
en: "Update a license key"
|
||||||
|
|
|
@ -2,15 +2,9 @@ emqx_license_schema {
|
||||||
license_root {
|
license_root {
|
||||||
desc {
|
desc {
|
||||||
en: "Defines the EMQX Enterprise license. \n\n"
|
en: "Defines the EMQX Enterprise license. \n\n"
|
||||||
"A license is either a `key` or a `file`.\n"
|
|
||||||
"When `key` and `file` are both configured, `key` is used.\n"
|
|
||||||
"\n"
|
|
||||||
"EMQX comes with a default trial license. For production use, please \n"
|
"EMQX comes with a default trial license. For production use, please \n"
|
||||||
"visit https://www.emqx.com/apply-licenses/emqx to apply."
|
"visit https://www.emqx.com/apply-licenses/emqx to apply."
|
||||||
zh: "EMQX企业许可证。\n"
|
zh: "EMQX企业许可证。\n"
|
||||||
"许可证是一个 `key` 或一个 `file`。\n"
|
|
||||||
"当 `key` 和 `file` 同时被配置时,优先使用 `key`。\n"
|
|
||||||
"\n"
|
|
||||||
"EMQX 自带一个默认的试用许可证,若需要在生产环境部署,\n"
|
"EMQX 自带一个默认的试用许可证,若需要在生产环境部署,\n"
|
||||||
"请访问 https://www.emqx.com/apply-licenses/emqx 来申请。\n"
|
"请访问 https://www.emqx.com/apply-licenses/emqx 来申请。\n"
|
||||||
}
|
}
|
||||||
|
@ -20,17 +14,6 @@ emqx_license_schema {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
license_type_field {
|
|
||||||
desc {
|
|
||||||
en: "License type"
|
|
||||||
zh: "许可证类型"
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
en: "License type"
|
|
||||||
zh: "许可证类型"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
key_field {
|
key_field {
|
||||||
desc {
|
desc {
|
||||||
en: "License string"
|
en: "License string"
|
||||||
|
@ -42,17 +25,6 @@ emqx_license_schema {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
file_field {
|
|
||||||
desc {
|
|
||||||
en: "Path to the license file"
|
|
||||||
zh: "许可证文件的路径"
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
en: "Path to the license file"
|
|
||||||
zh: "许可证文件的路径"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connection_low_watermark_field {
|
connection_low_watermark_field {
|
||||||
desc {
|
desc {
|
||||||
en: "Low watermark limit below which license connection quota usage alarms are deactivated"
|
en: "Low watermark limit below which license connection quota usage alarms are deactivated"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{application, emqx_license, [
|
{application, emqx_license, [
|
||||||
{description, "EMQX License"},
|
{description, "EMQX License"},
|
||||||
{vsn, "5.0.2"},
|
{vsn, "5.0.3"},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, [emqx_license_sup]},
|
{registered, [emqx_license_sup]},
|
||||||
{applications, [kernel, stdlib]},
|
{applications, [kernel, stdlib]},
|
||||||
|
|
|
@ -21,11 +21,7 @@
|
||||||
unload/0,
|
unload/0,
|
||||||
read_license/0,
|
read_license/0,
|
||||||
read_license/1,
|
read_license/1,
|
||||||
update_file/1,
|
update_key/1
|
||||||
update_file_contents/1,
|
|
||||||
update_key/1,
|
|
||||||
license_dir/0,
|
|
||||||
save_and_backup_license/1
|
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(CONF_KEY_PATH, [license]).
|
-define(CONF_KEY_PATH, [license]).
|
||||||
|
@ -57,35 +53,6 @@ unload() ->
|
||||||
emqx_conf:remove_handler(?CONF_KEY_PATH),
|
emqx_conf:remove_handler(?CONF_KEY_PATH),
|
||||||
emqx_license_cli:unload().
|
emqx_license_cli:unload().
|
||||||
|
|
||||||
-spec license_dir() -> file:filename().
|
|
||||||
license_dir() ->
|
|
||||||
filename:join([emqx:data_dir(), licenses]).
|
|
||||||
|
|
||||||
%% Subdirectory relative to data dir.
|
|
||||||
-spec relative_license_path() -> file:filename().
|
|
||||||
relative_license_path() ->
|
|
||||||
filename:join([licenses, "emqx.lic"]).
|
|
||||||
|
|
||||||
-spec update_file(binary() | string()) ->
|
|
||||||
{ok, emqx_config:update_result()} | {error, emqx_config:update_error()}.
|
|
||||||
update_file(Filename) when is_binary(Filename); is_list(Filename) ->
|
|
||||||
case file:read_file(Filename) of
|
|
||||||
{ok, Contents} ->
|
|
||||||
update_file_contents(Contents);
|
|
||||||
{error, Error} ->
|
|
||||||
{error, Error}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec update_file_contents(binary() | string()) ->
|
|
||||||
{ok, emqx_config:update_result()} | {error, emqx_config:update_error()}.
|
|
||||||
update_file_contents(Contents) when is_binary(Contents) ->
|
|
||||||
Result = emqx_conf:update(
|
|
||||||
?CONF_KEY_PATH,
|
|
||||||
{file, Contents},
|
|
||||||
#{rawconf_with_defaults => true, override_to => local}
|
|
||||||
),
|
|
||||||
handle_config_update_result(Result).
|
|
||||||
|
|
||||||
-spec update_key(binary() | string()) ->
|
-spec update_key(binary() | string()) ->
|
||||||
{ok, emqx_config:update_result()} | {error, emqx_config:update_error()}.
|
{ok, emqx_config:update_result()} | {error, emqx_config:update_error()}.
|
||||||
update_key(Value) when is_binary(Value); is_list(Value) ->
|
update_key(Value) when is_binary(Value); is_list(Value) ->
|
||||||
|
@ -147,18 +114,10 @@ del_license_hook() ->
|
||||||
_ = emqx_hooks:del('client.connect', {?MODULE, check, []}),
|
_ = emqx_hooks:del('client.connect', {?MODULE, check, []}),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
do_update({file, NewContents}, Conf) ->
|
|
||||||
Res = emqx_license_proto_v2:save_and_backup_license(mria_mnesia:running_nodes(), NewContents),
|
|
||||||
%% assert
|
|
||||||
true = lists:all(fun(X) -> X =:= {ok, ok} end, Res),
|
|
||||||
%% Must be relative to the data dir, since different nodes might
|
|
||||||
%% have different data directories configured...
|
|
||||||
LicensePath = relative_license_path(),
|
|
||||||
maps:remove(<<"key">>, Conf#{<<"type">> => file, <<"file">> => LicensePath});
|
|
||||||
do_update({key, Content}, Conf) when is_binary(Content); is_list(Content) ->
|
do_update({key, Content}, Conf) when is_binary(Content); is_list(Content) ->
|
||||||
case emqx_license_parser:parse(Content) of
|
case emqx_license_parser:parse(Content) of
|
||||||
{ok, _License} ->
|
{ok, _License} ->
|
||||||
maps:remove(<<"file">>, Conf#{<<"type">> => key, <<"key">> => Content});
|
Conf#{<<"key">> => Content};
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
erlang:throw(Reason)
|
erlang:throw(Reason)
|
||||||
end;
|
end;
|
||||||
|
@ -166,57 +125,10 @@ do_update({key, Content}, Conf) when is_binary(Content); is_list(Content) ->
|
||||||
do_update(_Other, Conf) ->
|
do_update(_Other, Conf) ->
|
||||||
Conf.
|
Conf.
|
||||||
|
|
||||||
save_and_backup_license(NewLicenseKey) ->
|
|
||||||
%% Must be relative to the data dir, since different nodes might
|
|
||||||
%% have different data directories configured...
|
|
||||||
CurrentLicensePath = filename:join(emqx:data_dir(), relative_license_path()),
|
|
||||||
LicenseDir = filename:dirname(CurrentLicensePath),
|
|
||||||
case filelib:ensure_dir(CurrentLicensePath) of
|
|
||||||
ok -> ok;
|
|
||||||
{error, EnsureError} -> throw({error_creating_license_dir, EnsureError})
|
|
||||||
end,
|
|
||||||
case file:read_file(CurrentLicensePath) of
|
|
||||||
{ok, NewLicenseKey} ->
|
|
||||||
%% same contents; nothing to do.
|
|
||||||
ok;
|
|
||||||
{ok, _OldContents} ->
|
|
||||||
Time = calendar:system_time_to_rfc3339(erlang:system_time(second)),
|
|
||||||
BackupPath = filename:join([
|
|
||||||
LicenseDir,
|
|
||||||
"emqx.lic." ++ Time ++ ".backup"
|
|
||||||
]),
|
|
||||||
case file:copy(CurrentLicensePath, BackupPath) of
|
|
||||||
{ok, _} -> ok;
|
|
||||||
{error, CopyError} -> throw({error_backing_up_license, CopyError})
|
|
||||||
end,
|
|
||||||
ok;
|
|
||||||
{error, enoent} ->
|
|
||||||
ok;
|
|
||||||
{error, Error} ->
|
|
||||||
throw({error_reading_existing_license, Error})
|
|
||||||
end,
|
|
||||||
case file:write_file(CurrentLicensePath, NewLicenseKey) of
|
|
||||||
ok -> ok;
|
|
||||||
{error, WriteError} -> throw({error_writing_license, WriteError})
|
|
||||||
end,
|
|
||||||
ok.
|
|
||||||
|
|
||||||
check_max_clients_exceeded(MaxClients) ->
|
check_max_clients_exceeded(MaxClients) ->
|
||||||
emqx_license_resources:connection_count() > MaxClients * 1.1.
|
emqx_license_resources:connection_count() > MaxClients * 1.1.
|
||||||
|
|
||||||
read_license(#{type := file, file := Filename}) ->
|
read_license(#{key := Content}) ->
|
||||||
case file:read_file(Filename) of
|
|
||||||
{ok, Content} ->
|
|
||||||
emqx_license_parser:parse(Content);
|
|
||||||
{error, _} = Error ->
|
|
||||||
%% Could be a relative path in data folder after update.
|
|
||||||
FilenameDataDir = filename:join(emqx:data_dir(), Filename),
|
|
||||||
case file:read_file(FilenameDataDir) of
|
|
||||||
{ok, Content} -> emqx_license_parser:parse(Content);
|
|
||||||
_Error -> Error
|
|
||||||
end
|
|
||||||
end;
|
|
||||||
read_license(#{type := key, key := Content}) ->
|
|
||||||
emqx_license_parser:parse(Content).
|
emqx_license_parser:parse(Content).
|
||||||
|
|
||||||
handle_config_update_result({error, {post_config_update, ?MODULE, Error}}) ->
|
handle_config_update_result({error, {post_config_update, ?MODULE, Error}}) ->
|
||||||
|
|
|
@ -19,21 +19,6 @@
|
||||||
load() ->
|
load() ->
|
||||||
ok = emqx_ctl:register_command(license, {?MODULE, license}, []).
|
ok = emqx_ctl:register_command(license, {?MODULE, license}, []).
|
||||||
|
|
||||||
license(["reload"]) ->
|
|
||||||
case emqx:get_config([license]) of
|
|
||||||
#{file := Filename} ->
|
|
||||||
license(["reload", Filename]);
|
|
||||||
#{key := _Key} ->
|
|
||||||
?PRINT_MSG("License is not configured as a file, please specify file explicitly~n")
|
|
||||||
end;
|
|
||||||
license(["reload", Filename]) ->
|
|
||||||
case emqx_license:update_file(Filename) of
|
|
||||||
{ok, Warnings} ->
|
|
||||||
ok = print_warnings(Warnings),
|
|
||||||
ok = ?PRINT_MSG("ok~n");
|
|
||||||
{error, Reason} ->
|
|
||||||
?PRINT("Error: ~p~n", [Reason])
|
|
||||||
end;
|
|
||||||
license(["update", EncodedLicense]) ->
|
license(["update", EncodedLicense]) ->
|
||||||
case emqx_license:update_key(EncodedLicense) of
|
case emqx_license:update_key(EncodedLicense) of
|
||||||
{ok, Warnings} ->
|
{ok, Warnings} ->
|
||||||
|
@ -56,8 +41,6 @@ license(_) ->
|
||||||
emqx_ctl:usage(
|
emqx_ctl:usage(
|
||||||
[
|
[
|
||||||
{"license info", "Show license info"},
|
{"license info", "Show license info"},
|
||||||
{"license reload [<File>]",
|
|
||||||
"Reload license from a file specified with an absolute path"},
|
|
||||||
{"license update License", "Update license given as a string"}
|
{"license update License", "Update license given as a string"}
|
||||||
]
|
]
|
||||||
).
|
).
|
||||||
|
|
|
@ -17,9 +17,7 @@
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
'/license'/2,
|
'/license'/2
|
||||||
'/license/key'/2,
|
|
||||||
'/license/file'/2
|
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(BAD_REQUEST, 'BAD_REQUEST').
|
-define(BAD_REQUEST, 'BAD_REQUEST').
|
||||||
|
@ -31,9 +29,7 @@ api_spec() ->
|
||||||
|
|
||||||
paths() ->
|
paths() ->
|
||||||
[
|
[
|
||||||
"/license",
|
"/license"
|
||||||
"/license/key",
|
|
||||||
"/license/file"
|
|
||||||
].
|
].
|
||||||
|
|
||||||
schema("/license") ->
|
schema("/license") ->
|
||||||
|
@ -53,32 +49,7 @@ schema("/license") ->
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
|
||||||
schema("/license/file") ->
|
|
||||||
#{
|
|
||||||
'operationId' => '/license/file',
|
|
||||||
post => #{
|
|
||||||
tags => [<<"license">>],
|
|
||||||
summary => <<"Upload license file">>,
|
|
||||||
description => ?DESC("desc_license_file_api"),
|
|
||||||
'requestBody' => emqx_dashboard_swagger:file_schema(filename),
|
|
||||||
responses => #{
|
|
||||||
200 => emqx_dashboard_swagger:schema_with_examples(
|
|
||||||
map(),
|
|
||||||
#{
|
|
||||||
sample_license_info => #{
|
|
||||||
value => sample_license_info_response()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad license file">>)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
schema("/license/key") ->
|
|
||||||
#{
|
|
||||||
'operationId' => '/license/key',
|
|
||||||
post => #{
|
post => #{
|
||||||
tags => [<<"license">>],
|
tags => [<<"license">>],
|
||||||
summary => <<"Update license key">>,
|
summary => <<"Update license key">>,
|
||||||
|
@ -105,7 +76,7 @@ schema("/license/key") ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad license file">>)
|
400 => emqx_dashboard_swagger:error_codes([?BAD_REQUEST], <<"Bad license key">>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.
|
}.
|
||||||
|
@ -126,30 +97,12 @@ sample_license_info_response() ->
|
||||||
error_msg(Code, Msg) ->
|
error_msg(Code, Msg) ->
|
||||||
#{code => Code, message => emqx_misc:readable_error_msg(Msg)}.
|
#{code => Code, message => emqx_misc:readable_error_msg(Msg)}.
|
||||||
|
|
||||||
|
%% read license info
|
||||||
'/license'(get, _Params) ->
|
'/license'(get, _Params) ->
|
||||||
License = maps:from_list(emqx_license_checker:dump()),
|
License = maps:from_list(emqx_license_checker:dump()),
|
||||||
{200, License}.
|
{200, License};
|
||||||
|
%% set/update license
|
||||||
'/license/file'(post, #{body := #{<<"filename">> := #{type := _} = File}}) ->
|
'/license'(post, #{body := #{<<"key">> := Key}}) ->
|
||||||
[{_Filename, Contents}] = maps:to_list(maps:without([type], File)),
|
|
||||||
case emqx_license:update_file_contents(Contents) of
|
|
||||||
{error, Error} ->
|
|
||||||
?SLOG(error, #{
|
|
||||||
msg => "bad_license_file",
|
|
||||||
reason => Error
|
|
||||||
}),
|
|
||||||
{400, error_msg(?BAD_REQUEST, <<"Bad license file">>)};
|
|
||||||
{ok, _} ->
|
|
||||||
?SLOG(info, #{
|
|
||||||
msg => "updated_license_file"
|
|
||||||
}),
|
|
||||||
License = maps:from_list(emqx_license_checker:dump()),
|
|
||||||
{200, License}
|
|
||||||
end;
|
|
||||||
'/license/file'(post, _Params) ->
|
|
||||||
{400, error_msg(?BAD_REQUEST, <<"Invalid request params">>)}.
|
|
||||||
|
|
||||||
'/license/key'(post, #{body := #{<<"key">> := Key}}) ->
|
|
||||||
case emqx_license:update_key(Key) of
|
case emqx_license:update_key(Key) of
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
?SLOG(error, #{
|
?SLOG(error, #{
|
||||||
|
@ -162,5 +115,5 @@ error_msg(Code, Msg) ->
|
||||||
License = maps:from_list(emqx_license_checker:dump()),
|
License = maps:from_list(emqx_license_checker:dump()),
|
||||||
{200, License}
|
{200, License}
|
||||||
end;
|
end;
|
||||||
'/license/key'(post, _Params) ->
|
'/license'(post, _Params) ->
|
||||||
{400, error_msg(?BAD_REQUEST, <<"Invalid request params">>)}.
|
{400, error_msg(?BAD_REQUEST, <<"Invalid request params">>)}.
|
||||||
|
|
|
@ -20,8 +20,7 @@
|
||||||
>>).
|
>>).
|
||||||
|
|
||||||
-define(LICENSE_PARSE_MODULES, [
|
-define(LICENSE_PARSE_MODULES, [
|
||||||
emqx_license_parser_v20220101,
|
emqx_license_parser_v20220101
|
||||||
emqx_license_parser_legacy
|
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-type license_data() :: term().
|
-type license_data() :: term().
|
||||||
|
|
|
@ -1,265 +0,0 @@
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2022 EMQ Technologies Co., Ltd. All Rights Reserved.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
-module(emqx_license_parser_legacy).
|
|
||||||
|
|
||||||
-behaviour(emqx_license_parser).
|
|
||||||
|
|
||||||
-include_lib("public_key/include/public_key.hrl").
|
|
||||||
-include("emqx_license.hrl").
|
|
||||||
|
|
||||||
-elvis([{elvis_style, atom_naming_convention, disable}]).
|
|
||||||
|
|
||||||
-define(CACERT, <<
|
|
||||||
"-----BEGIN CERTIFICATE-----\n"
|
|
||||||
"MIIDVDCCAjwCCQCckt8CVupoRDANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJD\n"
|
|
||||||
"TjERMA8GA1UECAwIWmhlamlhbmcxETAPBgNVBAcMCEhhbmd6aG91MQwwCgYDVQQK\n"
|
|
||||||
"DANFTVExDDAKBgNVBAsMA0VNUTEbMBkGA1UEAwwSRU1RWCBFbnRlcnByaXNlIHY1\n"
|
|
||||||
"MB4XDTIyMDQwODE1MTA1M1oXDTIzMDQwODE1MTA1M1owbDELMAkGA1UEBhMCQ04x\n"
|
|
||||||
"ETAPBgNVBAgMCFpoZWppYW5nMREwDwYDVQQHDAhIYW5nemhvdTEMMAoGA1UECgwD\n"
|
|
||||||
"RU1RMQwwCgYDVQQLDANFTVExGzAZBgNVBAMMEkVNUVggRW50ZXJwcmlzZSB2NTCC\n"
|
|
||||||
"ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMiYB/gbxCSErWL8sNZHkP4s\n"
|
|
||||||
"VTyeBho5T+5Uyp2S95qmcj10FBGi50ZnEN/62vMWED3HzEXsp6pq2Jk+Of3g9rSu\n"
|
|
||||||
"63V082HzlqFNHFzUDGkEu23tWyxeEKwBGyYRLIJI1/az99Jq82Qo0UZ5ELVpouAz\n"
|
|
||||||
"QVOKjpehHvWgEuWmPi+w1uuOieO08nO4AAOLHWcNOChgV50sl88gbz2n/kAcjqzl\n"
|
|
||||||
"1MQXMXoRzfzseNf3bmBV0keNFOpcqePTWCeshFFVkqeKMbK5HIKsnoDSl3VtQ/KK\n"
|
|
||||||
"iV88WpW4f0QfGGJV/gHt++4BAZS3nzxXUhGA0Tf2o7N1CHqnXuottJVcgzyIxHEC\n"
|
|
||||||
"AwEAATANBgkqhkiG9w0BAQsFAAOCAQEANh3ofOa9Aoqb7gUoTb6dNj883aHZ4aHi\n"
|
|
||||||
"kQVo4fVc4IH1MLVNuH/H/aqQ+YtRbbE4YT0icApJFa8qriv8afD9reh5/6ySdsms\n"
|
|
||||||
"RAXSogCuAPk2DwT1fyQa6A45x5EBpgwW10rYhwa5JJi6YKPpWS/Uo1Fgk9YGmeW4\n"
|
|
||||||
"FgGWYvWQHQIXhjfTC0wJPXlsDB2AB7xMINlOSfg/Bz8mhz7iOjM4pkvnTj17JrgR\n"
|
|
||||||
"VQLAj4NFAvdLFFjhZarFtCjPiCE4gb5YZI/Os4iMenD1ZWnYy9Sy7JSNXhWda6e2\n"
|
|
||||||
"WGl1AsyDsVPdvAzcB5ymrLnptCzZYT29PSubmCHS9nFgT6hkWCam4g==\n"
|
|
||||||
"-----END CERTIFICATE-----"
|
|
||||||
>>).
|
|
||||||
|
|
||||||
%% emqx_license_parser callbacks
|
|
||||||
-export([
|
|
||||||
parse/2,
|
|
||||||
dump/1,
|
|
||||||
customer_type/1,
|
|
||||||
license_type/1,
|
|
||||||
expiry_date/1,
|
|
||||||
max_connections/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% emqx_license_parser API
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
%% Sample parsed data:
|
|
||||||
%% #{customer => <<"EMQ X Evaluation">>,
|
|
||||||
%% email => "contact@emqx.io",
|
|
||||||
%% permits =>
|
|
||||||
%% #{customer_type => 10,
|
|
||||||
%% enabled_plugins =>
|
|
||||||
%% [emqx_backend_redis,emqx_backend_mysql,
|
|
||||||
%% emqx_backend_pgsql,emqx_backend_mongo,
|
|
||||||
%% emqx_backend_cassa,emqx_bridge_kafka,
|
|
||||||
%% emqx_bridge_rabbit],
|
|
||||||
%% max_connections => 10,type => 1},
|
|
||||||
%% product => "EMQX Enterprise",
|
|
||||||
%% validity =>
|
|
||||||
%% {{{2020,6,20},{3,2,52}},{{2049,1,1},{3,2,52}}},
|
|
||||||
%% vendor => "EMQ Technologies Co., Ltd.",
|
|
||||||
%% version => "5.0.0-alpha.1-22e2ad1c"}
|
|
||||||
|
|
||||||
parse(Contents, _PublicKey) ->
|
|
||||||
case decode_and_verify_signature(Contents) of
|
|
||||||
{ok, DerCert} ->
|
|
||||||
parse_payload(DerCert);
|
|
||||||
{error, Error} ->
|
|
||||||
{error, Error}
|
|
||||||
end.
|
|
||||||
|
|
||||||
dump(#{
|
|
||||||
customer := Customer,
|
|
||||||
email := Email,
|
|
||||||
permits :=
|
|
||||||
#{
|
|
||||||
customer_type := CustomerType,
|
|
||||||
max_connections := MaxConnections,
|
|
||||||
type := Type
|
|
||||||
},
|
|
||||||
validity := {{StartAtDate, _StartAtTime}, {ExpiryAtDate, _ExpiryAtTime}}
|
|
||||||
}) ->
|
|
||||||
{DateNow, _} = calendar:universal_time(),
|
|
||||||
Expiry = DateNow > ExpiryAtDate,
|
|
||||||
[
|
|
||||||
{customer, Customer},
|
|
||||||
{email, Email},
|
|
||||||
{deployment, "default"},
|
|
||||||
{max_connections, MaxConnections},
|
|
||||||
{start_at, format_date(StartAtDate)},
|
|
||||||
{expiry_at, format_date(ExpiryAtDate)},
|
|
||||||
{type, format_type(Type)},
|
|
||||||
{customer_type, CustomerType},
|
|
||||||
{expiry, Expiry}
|
|
||||||
].
|
|
||||||
|
|
||||||
customer_type(#{permits := Permits}) ->
|
|
||||||
maps:get(customer_type, Permits, ?LARGE_CUSTOMER).
|
|
||||||
|
|
||||||
license_type(#{permits := Permits}) ->
|
|
||||||
maps:get(type, Permits, ?TRIAL).
|
|
||||||
|
|
||||||
expiry_date(#{validity := {_From, {EndDate, _EndTime}}}) ->
|
|
||||||
EndDate.
|
|
||||||
|
|
||||||
max_connections(#{permits := Permits}) ->
|
|
||||||
maps:get(max_connections, Permits, 0).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Internal functions
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
decode_and_verify_signature(Contents) ->
|
|
||||||
try
|
|
||||||
{ok, Cert, DerCert} = decode_license(Contents),
|
|
||||||
[{'Certificate', DerCaCert, _}] = public_key:pem_decode(?CACERT),
|
|
||||||
CaCert = public_key:pkix_decode_cert(DerCaCert, otp),
|
|
||||||
Result = public_key:pkix_path_validation(
|
|
||||||
CaCert,
|
|
||||||
[DerCert],
|
|
||||||
[{verify_fun, {fun verify_fun/3, user_state}}]
|
|
||||||
),
|
|
||||||
case Result of
|
|
||||||
{ok, _Info} ->
|
|
||||||
{ok, Cert};
|
|
||||||
{error, {bad_cert, Reason}} ->
|
|
||||||
{error, Reason}
|
|
||||||
end
|
|
||||||
catch
|
|
||||||
throw:bad_license_format ->
|
|
||||||
{error, bad_license_format};
|
|
||||||
_:_ ->
|
|
||||||
{error, bad_certificate}
|
|
||||||
end.
|
|
||||||
|
|
||||||
decode_license(Contents) ->
|
|
||||||
case public_key:pem_decode(Contents) of
|
|
||||||
[{'Certificate', DerCert, _}] ->
|
|
||||||
Cert = public_key:pkix_decode_cert(DerCert, otp),
|
|
||||||
{ok, Cert, DerCert};
|
|
||||||
_ ->
|
|
||||||
throw(bad_license_format)
|
|
||||||
end.
|
|
||||||
|
|
||||||
parse_payload(DerCert) ->
|
|
||||||
try
|
|
||||||
{Start, End} = read_validity(DerCert),
|
|
||||||
Subject = read_subject(DerCert),
|
|
||||||
Permits = read_permits(DerCert),
|
|
||||||
LicenseData = maps:merge(
|
|
||||||
#{
|
|
||||||
vendor => "EMQ Technologies Co., Ltd.",
|
|
||||||
product => emqx_sys:sysdescr(),
|
|
||||||
version => emqx_sys:version(),
|
|
||||||
validity => {Start, End},
|
|
||||||
permits => Permits
|
|
||||||
},
|
|
||||||
Subject
|
|
||||||
),
|
|
||||||
{ok, LicenseData}
|
|
||||||
catch
|
|
||||||
_:_ ->
|
|
||||||
{error, bad_license}
|
|
||||||
end.
|
|
||||||
|
|
||||||
read_validity(#'OTPCertificate'{tbsCertificate = #'OTPTBSCertificate'{validity = Validity}}) ->
|
|
||||||
case Validity of
|
|
||||||
{'Validity', {utcTime, Start0}, {utcTime, End0}} ->
|
|
||||||
{local_time(Start0), local_time(End0)};
|
|
||||||
{'Validity', {utcTime, Start0}, {generalTime, End0}} ->
|
|
||||||
{local_time(Start0), local_time(End0)}
|
|
||||||
end.
|
|
||||||
|
|
||||||
local_time([Y01, Y0, Y1, Y2, M1, M2, D1, D2, H1, H2, Min1, Min2, S1, S2, $Z]) ->
|
|
||||||
{{b2l(<<Y01, Y0, Y1, Y2>>), b2l(<<M1, M2>>), b2l(<<D1, D2>>)}, {
|
|
||||||
b2l(<<H1, H2>>), b2l(<<Min1, Min2>>), b2l(<<S1, S2>>)
|
|
||||||
}};
|
|
||||||
local_time([Y1, Y2, M1, M2, D1, D2, H1, H2, Min1, Min2, S1, S2, $Z]) ->
|
|
||||||
{{b2l(<<"20", Y1, Y2>>), b2l(<<M1, M2>>), b2l(<<D1, D2>>)}, {
|
|
||||||
b2l(<<H1, H2>>), b2l(<<Min1, Min2>>), b2l(<<S1, S2>>)
|
|
||||||
}}.
|
|
||||||
|
|
||||||
b2l(L) -> binary_to_integer(L).
|
|
||||||
|
|
||||||
read_subject(#'OTPCertificate'{tbsCertificate = TbsCertificate}) ->
|
|
||||||
#'OTPTBSCertificate'{subject = {rdnSequence, RDNs}} = TbsCertificate,
|
|
||||||
read_subject(lists:flatten(RDNs), #{}).
|
|
||||||
|
|
||||||
read_subject([], Subject) ->
|
|
||||||
Subject;
|
|
||||||
read_subject([#'AttributeTypeAndValue'{type = {2, 5, 4, 3}, value = V0} | RDNs], Subject) ->
|
|
||||||
V = unwrap_utf8_string(V0),
|
|
||||||
read_subject(RDNs, maps:put(customer, V, Subject));
|
|
||||||
read_subject([#'AttributeTypeAndValue'{type = {2, 5, 4, 10}, value = V0} | RDNs], Subject) ->
|
|
||||||
V = unwrap_utf8_string(V0),
|
|
||||||
read_subject(RDNs, maps:put(customer, V, Subject));
|
|
||||||
read_subject(
|
|
||||||
[#'AttributeTypeAndValue'{type = {1, 2, 840, 113549, 1, 9, 1}, value = V} | RDNs],
|
|
||||||
Subject
|
|
||||||
) ->
|
|
||||||
read_subject(RDNs, maps:put(email, V, Subject));
|
|
||||||
read_subject([_ | RDNs], Subject) ->
|
|
||||||
read_subject(RDNs, Subject).
|
|
||||||
|
|
||||||
read_permits(#'OTPCertificate'{tbsCertificate = #'OTPTBSCertificate'{extensions = Extensions}}) ->
|
|
||||||
read_permits(Extensions, #{}).
|
|
||||||
|
|
||||||
read_permits([], Permits) ->
|
|
||||||
Permits;
|
|
||||||
read_permits(
|
|
||||||
[#'Extension'{extnID = {1, 3, 6, 1, 4, 1, 52509, 1}, extnValue = Val} | More], Permits
|
|
||||||
) ->
|
|
||||||
MaxConns = list_to_integer(parse_utf8_string(Val)),
|
|
||||||
read_permits(More, maps:put(max_connections, MaxConns, Permits));
|
|
||||||
read_permits(
|
|
||||||
[#'Extension'{extnID = {1, 3, 6, 1, 4, 1, 52509, 2}, extnValue = Val} | More], Permits
|
|
||||||
) ->
|
|
||||||
Plugins = [list_to_atom(Plugin) || Plugin <- string:tokens(parse_utf8_string(Val), ",")],
|
|
||||||
read_permits(More, maps:put(enabled_plugins, Plugins, Permits));
|
|
||||||
read_permits(
|
|
||||||
[#'Extension'{extnID = {1, 3, 6, 1, 4, 1, 52509, 3}, extnValue = Val} | More], Permits
|
|
||||||
) ->
|
|
||||||
Type = list_to_integer(parse_utf8_string(Val)),
|
|
||||||
read_permits(More, maps:put(type, Type, Permits));
|
|
||||||
read_permits(
|
|
||||||
[#'Extension'{extnID = {1, 3, 6, 1, 4, 1, 52509, 4}, extnValue = Val} | More], Permits
|
|
||||||
) ->
|
|
||||||
CustomerType = list_to_integer(parse_utf8_string(Val)),
|
|
||||||
read_permits(More, maps:put(customer_type, CustomerType, Permits));
|
|
||||||
read_permits([_ | More], Permits) ->
|
|
||||||
read_permits(More, Permits).
|
|
||||||
|
|
||||||
unwrap_utf8_string({utf8String, Str}) -> Str;
|
|
||||||
unwrap_utf8_string(Str) -> Str.
|
|
||||||
|
|
||||||
parse_utf8_string(Val) ->
|
|
||||||
{utf8String, Str} = public_key:der_decode('DisplayText', Val),
|
|
||||||
binary_to_list(Str).
|
|
||||||
|
|
||||||
format_date({Year, Month, Day}) ->
|
|
||||||
iolist_to_binary(
|
|
||||||
io_lib:format(
|
|
||||||
"~4..0w-~2..0w-~2..0w",
|
|
||||||
[Year, Month, Day]
|
|
||||||
)
|
|
||||||
).
|
|
||||||
|
|
||||||
format_type(?OFFICIAL) -> <<"official">>;
|
|
||||||
format_type(?TRIAL) -> <<"trial">>.
|
|
||||||
|
|
||||||
%% We want to issue new CA certificates with different issuer and keep
|
|
||||||
%% validating old licenses.
|
|
||||||
verify_fun(_OTPCertificate, {bad_cert, invalid_issuer}, UserState) ->
|
|
||||||
{valid, UserState};
|
|
||||||
%% We want to continue using the same CA certificate even after it
|
|
||||||
%% expires.
|
|
||||||
verify_fun(_OTPCertificate, {bad_cert, cert_expired}, UserState) ->
|
|
||||||
{valid, UserState};
|
|
||||||
verify_fun(OTPCertificate, Event, State) ->
|
|
||||||
DefaultVerifyFun = element(1, ?DEFAULT_VERIFYFUN),
|
|
||||||
DefaultVerifyFun(OTPCertificate, Event, State).
|
|
|
@ -16,16 +16,14 @@
|
||||||
-export([roots/0, fields/1, validations/0, desc/1]).
|
-export([roots/0, fields/1, validations/0, desc/1]).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
license_type/0,
|
key_license/0
|
||||||
key_license/0,
|
|
||||||
file_license/0
|
|
||||||
]).
|
]).
|
||||||
|
|
||||||
roots() ->
|
roots() ->
|
||||||
[
|
[
|
||||||
{license,
|
{license,
|
||||||
hoconsc:mk(
|
hoconsc:mk(
|
||||||
license_type(),
|
key_license(),
|
||||||
#{
|
#{
|
||||||
desc => ?DESC(license_root)
|
desc => ?DESC(license_root)
|
||||||
}
|
}
|
||||||
|
@ -34,11 +32,6 @@ roots() ->
|
||||||
|
|
||||||
fields(key_license) ->
|
fields(key_license) ->
|
||||||
[
|
[
|
||||||
{type, #{
|
|
||||||
type => key,
|
|
||||||
required => true,
|
|
||||||
desc => ?DESC(license_type_field)
|
|
||||||
}},
|
|
||||||
{key, #{
|
{key, #{
|
||||||
type => string(),
|
type => string(),
|
||||||
%% so it's not logged
|
%% so it's not logged
|
||||||
|
@ -46,43 +39,6 @@ fields(key_license) ->
|
||||||
required => true,
|
required => true,
|
||||||
desc => ?DESC(key_field)
|
desc => ?DESC(key_field)
|
||||||
}},
|
}},
|
||||||
{file, #{
|
|
||||||
type => string(),
|
|
||||||
required => false,
|
|
||||||
desc => ?DESC(file_field)
|
|
||||||
}}
|
|
||||||
| common_fields()
|
|
||||||
];
|
|
||||||
fields(file_license) ->
|
|
||||||
[
|
|
||||||
{type, #{
|
|
||||||
type => file,
|
|
||||||
required => true,
|
|
||||||
desc => ?DESC(license_type_field)
|
|
||||||
}},
|
|
||||||
{key, #{
|
|
||||||
type => string(),
|
|
||||||
%% so it's not logged
|
|
||||||
sensitive => true,
|
|
||||||
required => false,
|
|
||||||
desc => ?DESC(key_field)
|
|
||||||
}},
|
|
||||||
{file, #{
|
|
||||||
type => string(),
|
|
||||||
desc => ?DESC(file_field)
|
|
||||||
}}
|
|
||||||
| common_fields()
|
|
||||||
].
|
|
||||||
|
|
||||||
desc(key_license) ->
|
|
||||||
"License provisioned as a string.";
|
|
||||||
desc(file_license) ->
|
|
||||||
"License provisioned as a file.";
|
|
||||||
desc(_) ->
|
|
||||||
undefined.
|
|
||||||
|
|
||||||
common_fields() ->
|
|
||||||
[
|
|
||||||
{connection_low_watermark, #{
|
{connection_low_watermark, #{
|
||||||
type => emqx_schema:percent(),
|
type => emqx_schema:percent(),
|
||||||
default => "75%",
|
default => "75%",
|
||||||
|
@ -95,21 +51,17 @@ common_fields() ->
|
||||||
}}
|
}}
|
||||||
].
|
].
|
||||||
|
|
||||||
|
desc(key_license) ->
|
||||||
|
"License provisioned as a string.";
|
||||||
|
desc(_) ->
|
||||||
|
undefined.
|
||||||
|
|
||||||
validations() ->
|
validations() ->
|
||||||
[{check_license_watermark, fun check_license_watermark/1}].
|
[{check_license_watermark, fun check_license_watermark/1}].
|
||||||
|
|
||||||
license_type() ->
|
|
||||||
hoconsc:union([
|
|
||||||
key_license(),
|
|
||||||
file_license()
|
|
||||||
]).
|
|
||||||
|
|
||||||
key_license() ->
|
key_license() ->
|
||||||
hoconsc:ref(?MODULE, key_license).
|
hoconsc:ref(?MODULE, key_license).
|
||||||
|
|
||||||
file_license() ->
|
|
||||||
hoconsc:ref(?MODULE, file_license).
|
|
||||||
|
|
||||||
check_license_watermark(Conf) ->
|
check_license_watermark(Conf) ->
|
||||||
case hocon_maps:get("license.connection_low_watermark", Conf) of
|
case hocon_maps:get("license.connection_low_watermark", Conf) of
|
||||||
undefined ->
|
undefined ->
|
||||||
|
|
|
@ -11,20 +11,15 @@
|
||||||
-export([introduced_in/0]).
|
-export([introduced_in/0]).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
remote_connection_counts/1,
|
remote_connection_counts/1
|
||||||
save_and_backup_license/2
|
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(TIMEOUT, 500).
|
-define(TIMEOUT, 500).
|
||||||
-define(BACKUP_TIMEOUT, 15_000).
|
-define(BACKUP_TIMEOUT, 15_000).
|
||||||
|
|
||||||
introduced_in() ->
|
introduced_in() ->
|
||||||
"5.0.5".
|
"e5.0.0".
|
||||||
|
|
||||||
-spec remote_connection_counts(list(node())) -> list({atom(), term()}).
|
-spec remote_connection_counts(list(node())) -> list({atom(), term()}).
|
||||||
remote_connection_counts(Nodes) ->
|
remote_connection_counts(Nodes) ->
|
||||||
erpc:multicall(Nodes, emqx_license_resources, local_connection_count, [], ?TIMEOUT).
|
erpc:multicall(Nodes, emqx_license_resources, local_connection_count, [], ?TIMEOUT).
|
||||||
|
|
||||||
-spec save_and_backup_license(list(node()), binary()) -> list({atom(), term()}).
|
|
||||||
save_and_backup_license(Nodes, NewLicenseKey) ->
|
|
||||||
erpc:multicall(Nodes, emqx_license, save_and_backup_license, [NewLicenseKey], ?BACKUP_TIMEOUT).
|
|
||||||
|
|
|
@ -27,168 +27,31 @@ end_per_suite(_) ->
|
||||||
|
|
||||||
init_per_testcase(Case, Config) ->
|
init_per_testcase(Case, Config) ->
|
||||||
{ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000),
|
{ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000),
|
||||||
set_invalid_license_file(Case),
|
|
||||||
Paths = set_override_paths(Case),
|
Paths = set_override_paths(Case),
|
||||||
Config0 = setup_test(Case, Config),
|
Config0 = setup_test(Case, Config),
|
||||||
Paths ++ Config0 ++ Config.
|
Paths ++ Config0 ++ Config.
|
||||||
|
|
||||||
end_per_testcase(Case, Config) ->
|
end_per_testcase(Case, Config) ->
|
||||||
restore_valid_license_file(Case),
|
|
||||||
clean_overrides(Case, Config),
|
clean_overrides(Case, Config),
|
||||||
teardown_test(Case, Config),
|
teardown_test(Case, Config),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
set_override_paths(TestCase) when
|
|
||||||
TestCase =:= t_change_from_file_to_key;
|
|
||||||
TestCase =:= t_change_from_key_to_file
|
|
||||||
->
|
|
||||||
LocalOverridePath = filename:join([
|
|
||||||
"/tmp",
|
|
||||||
"local-" ++ atom_to_list(TestCase) ++ ".conf"
|
|
||||||
]),
|
|
||||||
ClusterOverridePath = filename:join([
|
|
||||||
"/tmp",
|
|
||||||
"local-" ++ atom_to_list(TestCase) ++ ".conf"
|
|
||||||
]),
|
|
||||||
application:set_env(emqx, local_override_conf_file, LocalOverridePath),
|
|
||||||
application:set_env(emqx, cluster_override_conf_file, ClusterOverridePath),
|
|
||||||
[
|
|
||||||
{local_override_path, LocalOverridePath},
|
|
||||||
{cluster_override_path, ClusterOverridePath}
|
|
||||||
];
|
|
||||||
set_override_paths(_TestCase) ->
|
set_override_paths(_TestCase) ->
|
||||||
[].
|
[].
|
||||||
|
|
||||||
clean_overrides(TestCase, Config) when
|
|
||||||
TestCase =:= t_change_from_file_to_key;
|
|
||||||
TestCase =:= t_change_from_key_to_file
|
|
||||||
->
|
|
||||||
LocalOverridePath = ?config(local_override_path, Config),
|
|
||||||
ClusterOverridePath = ?config(cluster_override_path, Config),
|
|
||||||
file:delete(LocalOverridePath),
|
|
||||||
file:delete(ClusterOverridePath),
|
|
||||||
application:unset_env(emqx, local_override_conf_file),
|
|
||||||
application:unset_env(emqx, cluster_override_conf_file),
|
|
||||||
ok;
|
|
||||||
clean_overrides(_TestCase, _Config) ->
|
clean_overrides(_TestCase, _Config) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
setup_test(TestCase, Config) when
|
|
||||||
TestCase =:= t_update_file_cluster_backup
|
|
||||||
->
|
|
||||||
DataDir = ?config(data_dir, Config),
|
|
||||||
{LicenseKey, _License} = mk_license(
|
|
||||||
[
|
|
||||||
%% license format version
|
|
||||||
"220111",
|
|
||||||
%% license type
|
|
||||||
"0",
|
|
||||||
%% customer type
|
|
||||||
"10",
|
|
||||||
%% customer name
|
|
||||||
"Foo",
|
|
||||||
%% customer email
|
|
||||||
"contact@foo.com",
|
|
||||||
%% deplayment name
|
|
||||||
"bar-deployment",
|
|
||||||
%% start date
|
|
||||||
"20220111",
|
|
||||||
%% days
|
|
||||||
"100000",
|
|
||||||
%% max connections
|
|
||||||
"19"
|
|
||||||
]
|
|
||||||
),
|
|
||||||
Cluster = emqx_common_test_helpers:emqx_cluster(
|
|
||||||
[core, core],
|
|
||||||
[
|
|
||||||
{apps, [emqx_conf, emqx_license]},
|
|
||||||
{load_schema, false},
|
|
||||||
{schema_mod, emqx_enterprise_conf_schema},
|
|
||||||
{env_handler, fun
|
|
||||||
(emqx) ->
|
|
||||||
emqx_config:save_schema_mod_and_names(emqx_enterprise_conf_schema),
|
|
||||||
%% emqx_config:save_schema_mod_and_names(emqx_license_schema),
|
|
||||||
application:set_env(emqx, boot_modules, []),
|
|
||||||
application:set_env(
|
|
||||||
emqx,
|
|
||||||
data_dir,
|
|
||||||
filename:join([
|
|
||||||
DataDir,
|
|
||||||
TestCase,
|
|
||||||
node()
|
|
||||||
])
|
|
||||||
),
|
|
||||||
ok;
|
|
||||||
(emqx_conf) ->
|
|
||||||
emqx_config:save_schema_mod_and_names(emqx_enterprise_conf_schema),
|
|
||||||
%% emqx_config:save_schema_mod_and_names(emqx_license_schema),
|
|
||||||
application:set_env(
|
|
||||||
emqx,
|
|
||||||
data_dir,
|
|
||||||
filename:join([
|
|
||||||
DataDir,
|
|
||||||
TestCase,
|
|
||||||
node()
|
|
||||||
])
|
|
||||||
),
|
|
||||||
ok;
|
|
||||||
(emqx_license) ->
|
|
||||||
LicensePath = filename:join(emqx_license:license_dir(), "emqx.lic"),
|
|
||||||
filelib:ensure_dir(LicensePath),
|
|
||||||
ok = file:write_file(LicensePath, LicenseKey),
|
|
||||||
LicConfig = #{type => file, file => LicensePath},
|
|
||||||
emqx_config:put([license], LicConfig),
|
|
||||||
RawConfig = #{<<"type">> => file, <<"file">> => LicensePath},
|
|
||||||
emqx_config:put_raw([<<"license">>], RawConfig),
|
|
||||||
ok = persistent_term:put(
|
|
||||||
emqx_license_test_pubkey,
|
|
||||||
emqx_license_test_lib:public_key_pem()
|
|
||||||
),
|
|
||||||
ok;
|
|
||||||
(_) ->
|
|
||||||
ok
|
|
||||||
end}
|
|
||||||
]
|
|
||||||
),
|
|
||||||
Nodes = [emqx_common_test_helpers:start_slave(Name, Opts) || {Name, Opts} <- Cluster],
|
|
||||||
[{nodes, Nodes}, {cluster, Cluster}, {old_license, LicenseKey}];
|
|
||||||
setup_test(_TestCase, _Config) ->
|
setup_test(_TestCase, _Config) ->
|
||||||
[].
|
[].
|
||||||
|
|
||||||
teardown_test(TestCase, Config) when
|
|
||||||
TestCase =:= t_update_file_cluster_backup
|
|
||||||
->
|
|
||||||
Nodes = ?config(nodes, Config),
|
|
||||||
lists:foreach(
|
|
||||||
fun(N) ->
|
|
||||||
LicenseDir = erpc:call(N, emqx_license, license_dir, []),
|
|
||||||
{ok, _} = emqx_common_test_helpers:stop_slave(N),
|
|
||||||
ok = file:del_dir_r(LicenseDir),
|
|
||||||
ok
|
|
||||||
end,
|
|
||||||
Nodes
|
|
||||||
),
|
|
||||||
ok;
|
|
||||||
teardown_test(_TestCase, _Config) ->
|
teardown_test(_TestCase, _Config) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
set_invalid_license_file(t_read_license_from_invalid_file) ->
|
|
||||||
Config = #{type => file, file => "/invalid/file"},
|
|
||||||
emqx_config:put([license], Config);
|
|
||||||
set_invalid_license_file(_) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
restore_valid_license_file(t_read_license_from_invalid_file) ->
|
|
||||||
Config = #{type => file, file => emqx_license_test_lib:default_license()},
|
|
||||||
emqx_config:put([license], Config);
|
|
||||||
restore_valid_license_file(_) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
set_special_configs(emqx_license) ->
|
set_special_configs(emqx_license) ->
|
||||||
Config = #{type => file, file => emqx_license_test_lib:default_license()},
|
Config = #{key => emqx_license_test_lib:default_license()},
|
||||||
emqx_config:put([license], Config),
|
emqx_config:put([license], Config),
|
||||||
RawConfig = #{<<"type">> => file, <<"file">> => emqx_license_test_lib:default_license()},
|
RawConfig = #{<<"key">> => emqx_license_test_lib:default_license()},
|
||||||
emqx_config:put_raw([<<"license">>], RawConfig);
|
emqx_config:put_raw([<<"license">>], RawConfig);
|
||||||
set_special_configs(_) ->
|
set_special_configs(_) ->
|
||||||
ok.
|
ok.
|
||||||
|
@ -201,151 +64,19 @@ assert_on_nodes(Nodes, RunFun, CheckFun) ->
|
||||||
%% Tests
|
%% Tests
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
t_update_file(_Config) ->
|
|
||||||
?assertMatch(
|
|
||||||
{error, enoent},
|
|
||||||
emqx_license:update_file("/unknown/path")
|
|
||||||
),
|
|
||||||
|
|
||||||
ok = file:write_file("license_with_invalid_content.lic", <<"bad license">>),
|
|
||||||
?assertMatch(
|
|
||||||
{error, [_ | _]},
|
|
||||||
emqx_license:update_file("license_with_invalid_content.lic")
|
|
||||||
),
|
|
||||||
|
|
||||||
?assertMatch(
|
|
||||||
{ok, #{}},
|
|
||||||
emqx_license:update_file(emqx_license_test_lib:default_license())
|
|
||||||
).
|
|
||||||
|
|
||||||
t_update_file_cluster_backup(Config) ->
|
|
||||||
OldLicenseKey = ?config(old_license, Config),
|
|
||||||
Nodes = [N1 | _] = ?config(nodes, Config),
|
|
||||||
|
|
||||||
%% update the license file for the cluster
|
|
||||||
{NewLicenseKey, NewDecodedLicense} = mk_license(
|
|
||||||
[
|
|
||||||
%% license format version
|
|
||||||
"220111",
|
|
||||||
%% license type
|
|
||||||
"0",
|
|
||||||
%% customer type
|
|
||||||
"10",
|
|
||||||
%% customer name
|
|
||||||
"Foo",
|
|
||||||
%% customer email
|
|
||||||
"contact@foo.com",
|
|
||||||
%% deplayment name
|
|
||||||
"bar-deployment",
|
|
||||||
%% start date
|
|
||||||
"20220111",
|
|
||||||
%% days
|
|
||||||
"100000",
|
|
||||||
%% max connections
|
|
||||||
"190"
|
|
||||||
]
|
|
||||||
),
|
|
||||||
NewLicensePath = "tmp_new_license.lic",
|
|
||||||
ok = file:write_file(NewLicensePath, NewLicenseKey),
|
|
||||||
{ok, _} = erpc:call(N1, emqx_license, update_file, [NewLicensePath]),
|
|
||||||
|
|
||||||
assert_on_nodes(
|
|
||||||
Nodes,
|
|
||||||
fun() ->
|
|
||||||
Conf = emqx_conf:get([license]),
|
|
||||||
emqx_license:read_license(Conf)
|
|
||||||
end,
|
|
||||||
fun({N, Res}) ->
|
|
||||||
?assertMatch({ok, _}, Res, #{node => N}),
|
|
||||||
{ok, License} = Res,
|
|
||||||
?assertEqual(NewDecodedLicense, License, #{node => N})
|
|
||||||
end
|
|
||||||
),
|
|
||||||
|
|
||||||
assert_on_nodes(
|
|
||||||
Nodes,
|
|
||||||
fun() ->
|
|
||||||
LicenseDir = emqx_license:license_dir(),
|
|
||||||
file:list_dir(LicenseDir)
|
|
||||||
end,
|
|
||||||
fun({N, Res}) ->
|
|
||||||
?assertMatch({ok, _}, Res, #{node => N}),
|
|
||||||
{ok, DirContents} = Res,
|
|
||||||
%% the now current license
|
|
||||||
?assert(lists:member("emqx.lic", DirContents), #{node => N, dir_contents => DirContents}),
|
|
||||||
%% the backed up old license
|
|
||||||
?assert(
|
|
||||||
lists:any(
|
|
||||||
fun
|
|
||||||
("emqx.lic." ++ Suffix) -> lists:suffix(".backup", Suffix);
|
|
||||||
(_) -> false
|
|
||||||
end,
|
|
||||||
DirContents
|
|
||||||
),
|
|
||||||
#{node => N, dir_contents => DirContents}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
),
|
|
||||||
|
|
||||||
assert_on_nodes(
|
|
||||||
Nodes,
|
|
||||||
fun() ->
|
|
||||||
LicenseDir = emqx_license:license_dir(),
|
|
||||||
{ok, DirContents} = file:list_dir(LicenseDir),
|
|
||||||
[BackupLicensePath0] = [
|
|
||||||
F
|
|
||||||
|| "emqx.lic." ++ F <- DirContents, lists:suffix(".backup", F)
|
|
||||||
],
|
|
||||||
BackupLicensePath = "emqx.lic." ++ BackupLicensePath0,
|
|
||||||
{ok, BackupLicense} = file:read_file(filename:join(LicenseDir, BackupLicensePath)),
|
|
||||||
{ok, NewLicense} = file:read_file(filename:join(LicenseDir, "emqx.lic")),
|
|
||||||
#{
|
|
||||||
backup => BackupLicense,
|
|
||||||
new => NewLicense
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
fun({N, #{backup := BackupLicense, new := NewLicense}}) ->
|
|
||||||
?assertEqual(OldLicenseKey, BackupLicense, #{node => N}),
|
|
||||||
?assertEqual(NewLicenseKey, NewLicense, #{node => N})
|
|
||||||
end
|
|
||||||
),
|
|
||||||
|
|
||||||
%% uploading the same license twice should not generate extra backups.
|
|
||||||
{ok, _} = erpc:call(N1, emqx_license, update_file, [NewLicensePath]),
|
|
||||||
|
|
||||||
assert_on_nodes(
|
|
||||||
Nodes,
|
|
||||||
fun() ->
|
|
||||||
LicenseDir = emqx_license:license_dir(),
|
|
||||||
{ok, DirContents} = file:list_dir(LicenseDir),
|
|
||||||
[F || "emqx.lic." ++ F <- DirContents, lists:suffix(".backup", F)]
|
|
||||||
end,
|
|
||||||
fun({N, Backups}) ->
|
|
||||||
?assertMatch([_], Backups, #{node => N})
|
|
||||||
end
|
|
||||||
),
|
|
||||||
|
|
||||||
ok.
|
|
||||||
|
|
||||||
t_update_value(_Config) ->
|
t_update_value(_Config) ->
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
{error, [_ | _]},
|
{error, [_ | _]},
|
||||||
emqx_license:update_key("invalid.license")
|
emqx_license:update_key("invalid.license")
|
||||||
),
|
),
|
||||||
|
|
||||||
{ok, LicenseValue} = file:read_file(emqx_license_test_lib:default_license()),
|
LicenseValue = emqx_license_test_lib:default_license(),
|
||||||
|
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
{ok, #{}},
|
{ok, #{}},
|
||||||
emqx_license:update_key(LicenseValue)
|
emqx_license:update_key(LicenseValue)
|
||||||
).
|
).
|
||||||
|
|
||||||
t_read_license_from_invalid_file(_Config) ->
|
|
||||||
?assertMatch(
|
|
||||||
{error, enoent},
|
|
||||||
emqx_license:read_license()
|
|
||||||
).
|
|
||||||
|
|
||||||
t_check_exceeded(_Config) ->
|
t_check_exceeded(_Config) ->
|
||||||
{_, License} = mk_license(
|
{_, License} = mk_license(
|
||||||
[
|
[
|
||||||
|
@ -435,39 +166,6 @@ t_check_not_loaded(_Config) ->
|
||||||
emqx_license:check(#{}, #{})
|
emqx_license:check(#{}, #{})
|
||||||
).
|
).
|
||||||
|
|
||||||
t_change_from_file_to_key(_Config) ->
|
|
||||||
%% precondition
|
|
||||||
?assertMatch(#{file := _}, emqx_conf:get([license])),
|
|
||||||
|
|
||||||
OldConf = emqx_conf:get_raw([]),
|
|
||||||
|
|
||||||
%% this saves updated config to `{cluster,local}-overrrides.conf'
|
|
||||||
{ok, LicenseValue} = file:read_file(emqx_license_test_lib:default_license()),
|
|
||||||
{ok, _NewConf} = emqx_license:update_key(LicenseValue),
|
|
||||||
|
|
||||||
%% assert that `{cluster,local}-overrides.conf' merge correctly
|
|
||||||
?assertEqual(ok, emqx_config:init_load(emqx_license_schema, OldConf, #{})),
|
|
||||||
|
|
||||||
ok.
|
|
||||||
|
|
||||||
t_change_from_key_to_file(_Config) ->
|
|
||||||
Config = #{type => key, key => <<"some key">>},
|
|
||||||
emqx_config:put([license], Config),
|
|
||||||
RawConfig = #{<<"type">> => key, <<"key">> => <<"some key">>},
|
|
||||||
emqx_config:put_raw([<<"license">>], RawConfig),
|
|
||||||
|
|
||||||
%% precondition
|
|
||||||
?assertMatch(#{type := key, key := _}, emqx_conf:get([license])),
|
|
||||||
OldConf = emqx_conf:get_raw([]),
|
|
||||||
|
|
||||||
%% this saves updated config to `{cluster,local}-overrrides.conf'
|
|
||||||
{ok, _NewConf} = emqx_license:update_file(emqx_license_test_lib:default_license()),
|
|
||||||
|
|
||||||
%% assert that `{cluster,local}-overrides.conf' merge correctly
|
|
||||||
?assertEqual(ok, emqx_config:init_load(emqx_license_schema, OldConf, #{})),
|
|
||||||
|
|
||||||
ok.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Helpers
|
%% Helpers
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
|
@ -35,7 +35,7 @@ end_per_testcase(_Case, _Config) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
set_special_configs(emqx_license) ->
|
set_special_configs(emqx_license) ->
|
||||||
Config = #{type => file, file => emqx_license_test_lib:default_license()},
|
Config = #{key => emqx_license_test_lib:default_license()},
|
||||||
emqx_config:put([license], Config);
|
emqx_config:put([license], Config);
|
||||||
set_special_configs(_) ->
|
set_special_configs(_) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -31,9 +31,9 @@ end_per_testcase(_Case, _Config) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
set_special_configs(emqx_license) ->
|
set_special_configs(emqx_license) ->
|
||||||
Config = #{type => file, file => emqx_license_test_lib:default_license()},
|
Config = #{key => emqx_license_test_lib:default_license()},
|
||||||
emqx_config:put([license], Config),
|
emqx_config:put([license], Config),
|
||||||
RawConfig = #{<<"type">> => file, <<"file">> => emqx_license_test_lib:default_license()},
|
RawConfig = #{<<"key">> => emqx_license_test_lib:default_license()},
|
||||||
emqx_config:put_raw([<<"license">>], RawConfig);
|
emqx_config:put_raw([<<"license">>], RawConfig);
|
||||||
set_special_configs(_) ->
|
set_special_configs(_) ->
|
||||||
ok.
|
ok.
|
||||||
|
@ -48,13 +48,8 @@ t_help(_Config) ->
|
||||||
t_info(_Config) ->
|
t_info(_Config) ->
|
||||||
_ = emqx_license_cli:license(["info"]).
|
_ = emqx_license_cli:license(["info"]).
|
||||||
|
|
||||||
t_reload(_Config) ->
|
|
||||||
_ = emqx_license_cli:license(["reload", "/invalid/path"]),
|
|
||||||
_ = emqx_license_cli:license(["reload", emqx_license_test_lib:default_license()]),
|
|
||||||
_ = emqx_license_cli:license(["reload"]).
|
|
||||||
|
|
||||||
t_update(_Config) ->
|
t_update(_Config) ->
|
||||||
{ok, LicenseValue} = file:read_file(emqx_license_test_lib:default_license()),
|
LicenseValue = emqx_license_test_lib:default_license(),
|
||||||
_ = emqx_license_cli:license(["update", LicenseValue]),
|
_ = emqx_license_cli:license(["update", LicenseValue]),
|
||||||
_ = emqx_license_cli:license(["reload"]),
|
_ = emqx_license_cli:license(["reload"]),
|
||||||
_ = emqx_license_cli:license(["update", "Invalid License Value"]).
|
_ = emqx_license_cli:license(["update", "Invalid License Value"]).
|
||||||
|
|
|
@ -26,9 +26,10 @@ init_per_suite(Config) ->
|
||||||
|
|
||||||
end_per_suite(_) ->
|
end_per_suite(_) ->
|
||||||
emqx_common_test_helpers:stop_apps([emqx_license, emqx_dashboard]),
|
emqx_common_test_helpers:stop_apps([emqx_license, emqx_dashboard]),
|
||||||
Config = #{type => file, file => emqx_license_test_lib:default_license()},
|
LicenseKey = emqx_license_test_lib:make_license(#{max_connections => "100"}),
|
||||||
|
Config = #{key => LicenseKey},
|
||||||
emqx_config:put([license], Config),
|
emqx_config:put([license], Config),
|
||||||
RawConfig = #{<<"type">> => file, <<"file">> => emqx_license_test_lib:default_license()},
|
RawConfig = #{<<"key">> => LicenseKey},
|
||||||
emqx_config:put_raw([<<"license">>], RawConfig),
|
emqx_config:put_raw([<<"license">>], RawConfig),
|
||||||
persistent_term:erase(emqx_license_test_pubkey),
|
persistent_term:erase(emqx_license_test_pubkey),
|
||||||
ok.
|
ok.
|
||||||
|
@ -37,9 +38,9 @@ set_special_configs(emqx_dashboard) ->
|
||||||
emqx_dashboard_api_test_helpers:set_default_config(<<"license_admin">>);
|
emqx_dashboard_api_test_helpers:set_default_config(<<"license_admin">>);
|
||||||
set_special_configs(emqx_license) ->
|
set_special_configs(emqx_license) ->
|
||||||
LicenseKey = emqx_license_test_lib:make_license(#{max_connections => "100"}),
|
LicenseKey = emqx_license_test_lib:make_license(#{max_connections => "100"}),
|
||||||
Config = #{type => key, key => LicenseKey},
|
Config = #{key => LicenseKey},
|
||||||
emqx_config:put([license], Config),
|
emqx_config:put([license], Config),
|
||||||
RawConfig = #{<<"type">> => key, <<"key">> => LicenseKey},
|
RawConfig = #{<<"key">> => LicenseKey},
|
||||||
emqx_config:put_raw([<<"license">>], RawConfig),
|
emqx_config:put_raw([<<"license">>], RawConfig),
|
||||||
ok = persistent_term:put(
|
ok = persistent_term:put(
|
||||||
emqx_license_test_pubkey,
|
emqx_license_test_pubkey,
|
||||||
|
@ -82,14 +83,6 @@ assert_untouched_license() ->
|
||||||
get_license()
|
get_license()
|
||||||
).
|
).
|
||||||
|
|
||||||
multipart_formdata_request(Uri, File) ->
|
|
||||||
emqx_dashboard_api_test_helpers:multipart_formdata_request(
|
|
||||||
Uri,
|
|
||||||
_Username = <<"license_admin">>,
|
|
||||||
_Fields = [],
|
|
||||||
[File]
|
|
||||||
).
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Testcases
|
%% Testcases
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
@ -114,74 +107,11 @@ t_license_info(_Config) ->
|
||||||
),
|
),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_license_upload_file_success(_Config) ->
|
|
||||||
NewKey = emqx_license_test_lib:make_license(#{max_connections => "999"}),
|
|
||||||
Res = multipart_formdata_request(
|
|
||||||
uri(["license", "file"]),
|
|
||||||
{filename, "emqx.lic", NewKey}
|
|
||||||
),
|
|
||||||
?assertMatch({ok, 200, _}, Res),
|
|
||||||
{ok, 200, Payload} = Res,
|
|
||||||
?assertEqual(
|
|
||||||
#{
|
|
||||||
<<"customer">> => <<"Foo">>,
|
|
||||||
<<"customer_type">> => 10,
|
|
||||||
<<"deployment">> => <<"bar-deployment">>,
|
|
||||||
<<"email">> => <<"contact@foo.com">>,
|
|
||||||
<<"expiry">> => false,
|
|
||||||
<<"expiry_at">> => <<"2295-10-27">>,
|
|
||||||
<<"max_connections">> => 999,
|
|
||||||
<<"start_at">> => <<"2022-01-11">>,
|
|
||||||
<<"type">> => <<"trial">>
|
|
||||||
},
|
|
||||||
emqx_json:decode(Payload, [return_maps])
|
|
||||||
),
|
|
||||||
?assertMatch(
|
|
||||||
#{max_connections := 999},
|
|
||||||
get_license()
|
|
||||||
),
|
|
||||||
ok.
|
|
||||||
|
|
||||||
t_license_upload_file_bad_license(_Config) ->
|
|
||||||
Res = multipart_formdata_request(
|
|
||||||
uri(["license", "file"]),
|
|
||||||
{filename, "bad.lic", <<"bad key">>}
|
|
||||||
),
|
|
||||||
?assertMatch({ok, 400, _}, Res),
|
|
||||||
{ok, 400, Payload} = Res,
|
|
||||||
?assertEqual(
|
|
||||||
#{
|
|
||||||
<<"code">> => <<"BAD_REQUEST">>,
|
|
||||||
<<"message">> => <<"Bad license file">>
|
|
||||||
},
|
|
||||||
emqx_json:decode(Payload, [return_maps])
|
|
||||||
),
|
|
||||||
assert_untouched_license(),
|
|
||||||
ok.
|
|
||||||
|
|
||||||
t_license_upload_file_not_json(_Config) ->
|
|
||||||
Res = request(
|
|
||||||
post,
|
|
||||||
uri(["license", "file"]),
|
|
||||||
<<"">>
|
|
||||||
),
|
|
||||||
?assertMatch({ok, 400, _}, Res),
|
|
||||||
{ok, 400, Payload} = Res,
|
|
||||||
?assertEqual(
|
|
||||||
#{
|
|
||||||
<<"code">> => <<"BAD_REQUEST">>,
|
|
||||||
<<"message">> => <<"Invalid request params">>
|
|
||||||
},
|
|
||||||
emqx_json:decode(Payload, [return_maps])
|
|
||||||
),
|
|
||||||
assert_untouched_license(),
|
|
||||||
ok.
|
|
||||||
|
|
||||||
t_license_upload_key_success(_Config) ->
|
t_license_upload_key_success(_Config) ->
|
||||||
NewKey = emqx_license_test_lib:make_license(#{max_connections => "999"}),
|
NewKey = emqx_license_test_lib:make_license(#{max_connections => "999"}),
|
||||||
Res = request(
|
Res = request(
|
||||||
post,
|
post,
|
||||||
uri(["license", "key"]),
|
uri(["license"]),
|
||||||
#{key => NewKey}
|
#{key => NewKey}
|
||||||
),
|
),
|
||||||
?assertMatch({ok, 200, _}, Res),
|
?assertMatch({ok, 200, _}, Res),
|
||||||
|
@ -210,7 +140,7 @@ t_license_upload_key_bad_key(_Config) ->
|
||||||
BadKey = <<"bad key">>,
|
BadKey = <<"bad key">>,
|
||||||
Res = request(
|
Res = request(
|
||||||
post,
|
post,
|
||||||
uri(["license", "key"]),
|
uri(["license"]),
|
||||||
#{key => BadKey}
|
#{key => BadKey}
|
||||||
),
|
),
|
||||||
?assertMatch({ok, 400, _}, Res),
|
?assertMatch({ok, 400, _}, Res),
|
||||||
|
@ -228,7 +158,7 @@ t_license_upload_key_bad_key(_Config) ->
|
||||||
t_license_upload_key_not_json(_Config) ->
|
t_license_upload_key_not_json(_Config) ->
|
||||||
Res = request(
|
Res = request(
|
||||||
post,
|
post,
|
||||||
uri(["license", "key"]),
|
uri(["license"]),
|
||||||
<<"">>
|
<<"">>
|
||||||
),
|
),
|
||||||
?assertMatch({ok, 400, _}, Res),
|
?assertMatch({ok, 400, _}, Res),
|
||||||
|
|
|
@ -31,7 +31,7 @@ end_per_testcase(_Case, _Config) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
set_special_configs(emqx_license) ->
|
set_special_configs(emqx_license) ->
|
||||||
Config = #{type => file, file => emqx_license_test_lib:default_license()},
|
Config = #{key => emqx_license_test_lib:default_license()},
|
||||||
emqx_config:put([license], Config);
|
emqx_config:put([license], Config);
|
||||||
set_special_configs(_) ->
|
set_special_configs(_) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -30,7 +30,7 @@ end_per_testcase(_Case, _Config) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
set_special_configs(emqx_license) ->
|
set_special_configs(emqx_license) ->
|
||||||
Config = #{type => file, file => emqx_license_test_lib:default_license()},
|
Config = #{key => emqx_license_test_lib:default_license()},
|
||||||
emqx_config:put([license], Config);
|
emqx_config:put([license], Config);
|
||||||
set_special_configs(_) ->
|
set_special_configs(_) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -1,114 +0,0 @@
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2022 EMQ Technologies Co., Ltd. All Rights Reserved.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
-module(emqx_license_parser_legacy_SUITE).
|
|
||||||
|
|
||||||
-compile(nowarn_export_all).
|
|
||||||
-compile(export_all).
|
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
|
||||||
-include_lib("public_key/include/public_key.hrl").
|
|
||||||
|
|
||||||
all() ->
|
|
||||||
emqx_common_test_helpers:all(?MODULE).
|
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
|
||||||
_ = application:load(emqx_conf),
|
|
||||||
emqx_common_test_helpers:start_apps([emqx_license], fun set_special_configs/1),
|
|
||||||
Config.
|
|
||||||
|
|
||||||
end_per_suite(_) ->
|
|
||||||
emqx_common_test_helpers:stop_apps([emqx_license]),
|
|
||||||
ok.
|
|
||||||
|
|
||||||
init_per_testcase(_Case, Config) ->
|
|
||||||
{ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000),
|
|
||||||
Config.
|
|
||||||
|
|
||||||
end_per_testcase(_Case, _Config) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
set_special_configs(emqx_license) ->
|
|
||||||
Config = #{type => file, file => emqx_license_test_lib:default_license()},
|
|
||||||
emqx_config:put([license], Config);
|
|
||||||
set_special_configs(_) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% Tests - emqx_license_parser API
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
t_parse(_Config) ->
|
|
||||||
?assertMatch({ok, _}, emqx_license_parser:parse(sample_license(), public_key_pem())),
|
|
||||||
|
|
||||||
Res1 = emqx_license_parser:parse(tampered_license(), public_key_pem()),
|
|
||||||
?assertMatch({error, _}, Res1),
|
|
||||||
{error, Errors} = Res1,
|
|
||||||
?assertEqual(
|
|
||||||
invalid_signature,
|
|
||||||
proplists:get_value(emqx_license_parser_legacy, Errors)
|
|
||||||
),
|
|
||||||
|
|
||||||
ok.
|
|
||||||
|
|
||||||
t_dump(_Config) ->
|
|
||||||
{ok, License} = emqx_license_parser:parse(sample_license(), public_key_pem()),
|
|
||||||
?assertEqual(
|
|
||||||
[
|
|
||||||
{customer, <<"EMQ X Evaluation">>},
|
|
||||||
{email, "contact@emqx.io"},
|
|
||||||
{deployment, "default"},
|
|
||||||
{max_connections, 10},
|
|
||||||
{start_at, <<"2020-06-20">>},
|
|
||||||
{expiry_at, <<"2049-01-01">>},
|
|
||||||
{type, <<"official">>},
|
|
||||||
{customer_type, 10},
|
|
||||||
{expiry, false}
|
|
||||||
],
|
|
||||||
emqx_license_parser:dump(License)
|
|
||||||
).
|
|
||||||
|
|
||||||
t_customer_type(_Config) ->
|
|
||||||
{ok, License} = emqx_license_parser:parse(sample_license(), public_key_pem()),
|
|
||||||
?assertEqual(10, emqx_license_parser:customer_type(License)).
|
|
||||||
|
|
||||||
t_license_type(_Config) ->
|
|
||||||
{ok, License} = emqx_license_parser:parse(sample_license(), public_key_pem()),
|
|
||||||
?assertEqual(1, emqx_license_parser:license_type(License)).
|
|
||||||
|
|
||||||
t_max_connections(_Config) ->
|
|
||||||
{ok, License} = emqx_license_parser:parse(sample_license(), public_key_pem()),
|
|
||||||
?assertEqual(10, emqx_license_parser:max_connections(License)).
|
|
||||||
|
|
||||||
t_expiry_date(_Config) ->
|
|
||||||
{ok, License} = emqx_license_parser:parse(sample_license(), public_key_pem()),
|
|
||||||
?assertEqual({2049, 1, 1}, emqx_license_parser:expiry_date(License)).
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% Helpers
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
%% not used for this parser, but required for the behaviour.
|
|
||||||
public_key_pem() ->
|
|
||||||
emqx_license_test_lib:public_key_pem().
|
|
||||||
|
|
||||||
sample_license() ->
|
|
||||||
emqx_license_test_lib:legacy_license().
|
|
||||||
|
|
||||||
tampered_license() ->
|
|
||||||
LicenseBin = emqx_license_test_lib:legacy_license(),
|
|
||||||
[{'Certificate', DerCert, _}] = public_key:pem_decode(LicenseBin),
|
|
||||||
Cert = public_key:pkix_decode_cert(DerCert, otp),
|
|
||||||
TbsCert = Cert#'OTPCertificate'.tbsCertificate,
|
|
||||||
Validity0 = TbsCert#'OTPTBSCertificate'.validity,
|
|
||||||
Validity = Validity0#'Validity'{notBefore = {utcTime, "19800620030252Z"}},
|
|
||||||
|
|
||||||
TamperedCert = Cert#'OTPCertificate'{
|
|
||||||
tbsCertificate =
|
|
||||||
TbsCert#'OTPTBSCertificate'{
|
|
||||||
validity = Validity
|
|
||||||
}
|
|
||||||
},
|
|
||||||
TamperedCertDer = public_key:pkix_encode('OTPCertificate', TamperedCert, otp),
|
|
||||||
public_key:pem_encode([{'Certificate', TamperedCertDer, not_encrypted}]).
|
|
|
@ -31,7 +31,7 @@ end_per_testcase(_Case, _Config) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
set_special_configs(emqx_license) ->
|
set_special_configs(emqx_license) ->
|
||||||
Config = #{type => file, file => emqx_license_test_lib:default_license()},
|
Config = #{key => emqx_license_test_lib:default_license()},
|
||||||
emqx_config:put([license], Config);
|
emqx_config:put([license], Config);
|
||||||
set_special_configs(_) ->
|
set_special_configs(_) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
|
@ -18,8 +18,6 @@
|
||||||
"10"
|
"10"
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(DEFAULT_LICENSE_FILE, "emqx.lic").
|
|
||||||
|
|
||||||
private_key() ->
|
private_key() ->
|
||||||
test_key("pvt.key").
|
test_key("pvt.key").
|
||||||
|
|
||||||
|
@ -32,9 +30,6 @@ public_key_pem() ->
|
||||||
test_key(Filename) ->
|
test_key(Filename) ->
|
||||||
test_key(Filename, decoded).
|
test_key(Filename, decoded).
|
||||||
|
|
||||||
legacy_license() ->
|
|
||||||
test_key("emqx.lic", pem).
|
|
||||||
|
|
||||||
test_key(Filename, Format) ->
|
test_key(Filename, Format) ->
|
||||||
Dir = code:lib_dir(emqx_license, test),
|
Dir = code:lib_dir(emqx_license, test),
|
||||||
Path = filename:join([Dir, "data", Filename]),
|
Path = filename:join([Dir, "data", Filename]),
|
||||||
|
@ -83,8 +78,5 @@ make_license(Values) ->
|
||||||
|
|
||||||
default_license() ->
|
default_license() ->
|
||||||
%% keep it the same as in etc/emqx_license.conf
|
%% keep it the same as in etc/emqx_license.conf
|
||||||
License =
|
"MjIwMTExCjAKMTAKRXZhbHVhdGlvbgpjb250YWN0QGVtcXguaW8KZGVmYXVsdAoyMDIyMDQxOQoxODI1CjEwMDAK."
|
||||||
"MjIwMTExCjAKMTAKRXZhbHVhdGlvbgpjb250YWN0QGVtcXguaW8KZGVmYXVsdAoyMDIyMDQxOQoxODI1CjEwMDAK."
|
"MEQCICbgRVijCQov2hrvZXR1mk9Oa+tyV1F5oJ6iOZeSHjnQAiB9dUiVeaZekDOjztk+NCWjhk4PG8tWfw2uFZWruSzD6g==".
|
||||||
"MEQCICbgRVijCQov2hrvZXR1mk9Oa+tyV1F5oJ6iOZeSHjnQAiB9dUiVeaZekDOjztk+NCWjhk4PG8tWfw2uFZWruSzD6g==",
|
|
||||||
ok = file:write_file(?DEFAULT_LICENSE_FILE, License),
|
|
||||||
?DEFAULT_LICENSE_FILE.
|
|
||||||
|
|
Loading…
Reference in New Issue