From d4416dc58130f6223d38d72560b8bc3f849c2bc0 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Tue, 13 Sep 2022 09:37:01 -0300 Subject: [PATCH] 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. --- bin/emqx | 24 +- bin/nodetool | 4 +- lib-ee/emqx_license/etc/emqx_license.conf | 1 - .../i18n/emqx_license_http_api.conf | 11 - .../i18n/emqx_license_schema_i18n.conf | 28 -- lib-ee/emqx_license/src/emqx_license.app.src | 2 +- lib-ee/emqx_license/src/emqx_license.erl | 94 +----- lib-ee/emqx_license/src/emqx_license_cli.erl | 17 - .../src/emqx_license_http_api.erl | 65 +--- .../emqx_license/src/emqx_license_parser.erl | 3 +- .../src/emqx_license_parser_legacy.erl | 265 --------------- .../emqx_license/src/emqx_license_schema.erl | 62 +--- .../src/proto/emqx_license_proto_v2.erl | 9 +- .../emqx_license/test/emqx_license_SUITE.erl | 308 +----------------- .../test/emqx_license_checker_SUITE.erl | 2 +- .../test/emqx_license_cli_SUITE.erl | 11 +- .../test/emqx_license_http_api_SUITE.erl | 86 +---- .../test/emqx_license_installer_SUITE.erl | 2 +- .../test/emqx_license_parser_SUITE.erl | 2 +- .../test/emqx_license_parser_legacy_SUITE.erl | 114 ------- .../test/emqx_license_resources_SUITE.erl | 2 +- .../test/emqx_license_test_lib.erl | 12 +- 22 files changed, 53 insertions(+), 1071 deletions(-) delete mode 100644 lib-ee/emqx_license/src/emqx_license_parser_legacy.erl delete mode 100644 lib-ee/emqx_license/test/emqx_license_parser_legacy_SUITE.erl diff --git a/bin/emqx b/bin/emqx index 20394b96f..63aae6e70 100755 --- a/bin/emqx +++ b/bin/emqx @@ -417,7 +417,7 @@ call_hocon() { ## 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' ) if [ "$IS_ENTERPRISE" = 'yes' ]; then - CONF_KEYS+=( 'license.type' 'license.file' 'license.key' ) + CONF_KEYS+=( 'license.key' ) fi if [ "$IS_BOOT_COMMAND" = 'yes' ]; then @@ -499,22 +499,16 @@ check_license() { return 0 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 - call_nodetool check_license_file "$file_license" + if [[ -n "$key_license" && ("$key_license" != "undefined") ]]; then + call_nodetool check_license_key "$key_license" else - key_license="${EMQX_LICENSE__KEY:-$(get_boot_config 'license.key')}" - - if [[ -n "$key_license" && ("$key_license" != "undefined") ]]; then - call_nodetool check_license_key "$key_license" - else - 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 + set +x + echoerr "License not found." + echoerr "Please specify one via the EMQX_LICENSE__KEY variable" + echoerr "or via license.key in emqx-enterprise.conf." + return 1 fi } diff --git a/bin/nodetool b/bin/nodetool index 0711f32d6..4af7aae02 100755 --- a/bin/nodetool +++ b/bin/nodetool @@ -25,9 +25,7 @@ main(Args) -> %% forward the call to hocon_cli hocon_cli:main(Rest); ["check_license_key", Key] -> - check_license(#{type => key, key => list_to_binary(Key)}); - ["check_license_file", File] -> - check_license(#{type => file, file => list_to_binary(File)}); + check_license(#{key => list_to_binary(Key)}); _ -> do(Args) end. diff --git a/lib-ee/emqx_license/etc/emqx_license.conf b/lib-ee/emqx_license/etc/emqx_license.conf index b5684b740..476444ea0 100644 --- a/lib-ee/emqx_license/etc/emqx_license.conf +++ b/lib-ee/emqx_license/etc/emqx_license.conf @@ -1,5 +1,4 @@ license { - type = key # The default license has 1000 connections limit, it is issued on 20220419 and valid for 5 years (1825 days) key = "MjIwMTExCjAKMTAKRXZhbHVhdGlvbgpjb250YWN0QGVtcXguaW8KZGVmYXVsdAoyMDIyMDQxOQoxODI1CjEwMDAK.MEQCICbgRVijCQov2hrvZXR1mk9Oa+tyV1F5oJ6iOZeSHjnQAiB9dUiVeaZekDOjztk+NCWjhk4PG8tWfw2uFZWruSzD6g==" connection_low_watermark = 75%, diff --git a/lib-ee/emqx_license/i18n/emqx_license_http_api.conf b/lib-ee/emqx_license/i18n/emqx_license_http_api.conf index 59f76b7d6..40a18bbf3 100644 --- a/lib-ee/emqx_license/i18n/emqx_license_http_api.conf +++ b/lib-ee/emqx_license/i18n/emqx_license_http_api.conf @@ -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 { en: "Update a license key" diff --git a/lib-ee/emqx_license/i18n/emqx_license_schema_i18n.conf b/lib-ee/emqx_license/i18n/emqx_license_schema_i18n.conf index edf6ba89a..1ee1ed35e 100644 --- a/lib-ee/emqx_license/i18n/emqx_license_schema_i18n.conf +++ b/lib-ee/emqx_license/i18n/emqx_license_schema_i18n.conf @@ -2,15 +2,9 @@ emqx_license_schema { license_root { desc { 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" "visit https://www.emqx.com/apply-licenses/emqx to apply." zh: "EMQX企业许可证。\n" - "许可证是一个 `key` 或一个 `file`。\n" - "当 `key` 和 `file` 同时被配置时,优先使用 `key`。\n" - "\n" "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 { desc { 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 { desc { en: "Low watermark limit below which license connection quota usage alarms are deactivated" diff --git a/lib-ee/emqx_license/src/emqx_license.app.src b/lib-ee/emqx_license/src/emqx_license.app.src index d39661bd3..2ef4ba405 100644 --- a/lib-ee/emqx_license/src/emqx_license.app.src +++ b/lib-ee/emqx_license/src/emqx_license.app.src @@ -1,6 +1,6 @@ {application, emqx_license, [ {description, "EMQX License"}, - {vsn, "5.0.2"}, + {vsn, "5.0.3"}, {modules, []}, {registered, [emqx_license_sup]}, {applications, [kernel, stdlib]}, diff --git a/lib-ee/emqx_license/src/emqx_license.erl b/lib-ee/emqx_license/src/emqx_license.erl index d37d40dd3..776a0f239 100644 --- a/lib-ee/emqx_license/src/emqx_license.erl +++ b/lib-ee/emqx_license/src/emqx_license.erl @@ -21,11 +21,7 @@ unload/0, read_license/0, read_license/1, - update_file/1, - update_file_contents/1, - update_key/1, - license_dir/0, - save_and_backup_license/1 + update_key/1 ]). -define(CONF_KEY_PATH, [license]). @@ -57,35 +53,6 @@ unload() -> emqx_conf:remove_handler(?CONF_KEY_PATH), 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()) -> {ok, emqx_config:update_result()} | {error, emqx_config:update_error()}. update_key(Value) when is_binary(Value); is_list(Value) -> @@ -147,18 +114,10 @@ del_license_hook() -> _ = emqx_hooks:del('client.connect', {?MODULE, check, []}), 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) -> case emqx_license_parser:parse(Content) of {ok, _License} -> - maps:remove(<<"file">>, Conf#{<<"type">> => key, <<"key">> => Content}); + Conf#{<<"key">> => Content}; {error, Reason} -> erlang:throw(Reason) end; @@ -166,57 +125,10 @@ do_update({key, Content}, Conf) when is_binary(Content); is_list(Content) -> do_update(_Other, 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) -> emqx_license_resources:connection_count() > MaxClients * 1.1. -read_license(#{type := file, file := Filename}) -> - 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}) -> +read_license(#{key := Content}) -> emqx_license_parser:parse(Content). handle_config_update_result({error, {post_config_update, ?MODULE, Error}}) -> diff --git a/lib-ee/emqx_license/src/emqx_license_cli.erl b/lib-ee/emqx_license/src/emqx_license_cli.erl index 9d618db67..87f2ac702 100644 --- a/lib-ee/emqx_license/src/emqx_license_cli.erl +++ b/lib-ee/emqx_license/src/emqx_license_cli.erl @@ -19,21 +19,6 @@ load() -> 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]) -> case emqx_license:update_key(EncodedLicense) of {ok, Warnings} -> @@ -56,8 +41,6 @@ license(_) -> emqx_ctl:usage( [ {"license info", "Show license info"}, - {"license reload []", - "Reload license from a file specified with an absolute path"}, {"license update License", "Update license given as a string"} ] ). diff --git a/lib-ee/emqx_license/src/emqx_license_http_api.erl b/lib-ee/emqx_license/src/emqx_license_http_api.erl index e758bbf6b..17e85b88a 100644 --- a/lib-ee/emqx_license/src/emqx_license_http_api.erl +++ b/lib-ee/emqx_license/src/emqx_license_http_api.erl @@ -17,9 +17,7 @@ ]). -export([ - '/license'/2, - '/license/key'/2, - '/license/file'/2 + '/license'/2 ]). -define(BAD_REQUEST, 'BAD_REQUEST'). @@ -31,9 +29,7 @@ api_spec() -> paths() -> [ - "/license", - "/license/key", - "/license/file" + "/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 => #{ tags => [<<"license">>], 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) -> #{code => Code, message => emqx_misc:readable_error_msg(Msg)}. +%% read license info '/license'(get, _Params) -> License = maps:from_list(emqx_license_checker:dump()), - {200, License}. - -'/license/file'(post, #{body := #{<<"filename">> := #{type := _} = File}}) -> - [{_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}}) -> + {200, License}; +%% set/update license +'/license'(post, #{body := #{<<"key">> := Key}}) -> case emqx_license:update_key(Key) of {error, Error} -> ?SLOG(error, #{ @@ -162,5 +115,5 @@ error_msg(Code, Msg) -> License = maps:from_list(emqx_license_checker:dump()), {200, License} end; -'/license/key'(post, _Params) -> +'/license'(post, _Params) -> {400, error_msg(?BAD_REQUEST, <<"Invalid request params">>)}. diff --git a/lib-ee/emqx_license/src/emqx_license_parser.erl b/lib-ee/emqx_license/src/emqx_license_parser.erl index e3abf1301..727ef2760 100644 --- a/lib-ee/emqx_license/src/emqx_license_parser.erl +++ b/lib-ee/emqx_license/src/emqx_license_parser.erl @@ -20,8 +20,7 @@ >>). -define(LICENSE_PARSE_MODULES, [ - emqx_license_parser_v20220101, - emqx_license_parser_legacy + emqx_license_parser_v20220101 ]). -type license_data() :: term(). diff --git a/lib-ee/emqx_license/src/emqx_license_parser_legacy.erl b/lib-ee/emqx_license/src/emqx_license_parser_legacy.erl deleted file mode 100644 index a5fa4ab64..000000000 --- a/lib-ee/emqx_license/src/emqx_license_parser_legacy.erl +++ /dev/null @@ -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(<>), b2l(<>), b2l(<>)}, { - b2l(<>), b2l(<>), b2l(<>) - }}; -local_time([Y1, Y2, M1, M2, D1, D2, H1, H2, Min1, Min2, S1, S2, $Z]) -> - {{b2l(<<"20", Y1, Y2>>), b2l(<>), b2l(<>)}, { - b2l(<>), b2l(<>), b2l(<>) - }}. - -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). diff --git a/lib-ee/emqx_license/src/emqx_license_schema.erl b/lib-ee/emqx_license/src/emqx_license_schema.erl index 2ce768425..bddcc0819 100644 --- a/lib-ee/emqx_license/src/emqx_license_schema.erl +++ b/lib-ee/emqx_license/src/emqx_license_schema.erl @@ -16,16 +16,14 @@ -export([roots/0, fields/1, validations/0, desc/1]). -export([ - license_type/0, - key_license/0, - file_license/0 + key_license/0 ]). roots() -> [ {license, hoconsc:mk( - license_type(), + key_license(), #{ desc => ?DESC(license_root) } @@ -34,11 +32,6 @@ roots() -> fields(key_license) -> [ - {type, #{ - type => key, - required => true, - desc => ?DESC(license_type_field) - }}, {key, #{ type => string(), %% so it's not logged @@ -46,43 +39,6 @@ fields(key_license) -> required => true, 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, #{ type => emqx_schema:percent(), default => "75%", @@ -95,21 +51,17 @@ common_fields() -> }} ]. +desc(key_license) -> + "License provisioned as a string."; +desc(_) -> + undefined. + validations() -> [{check_license_watermark, fun check_license_watermark/1}]. -license_type() -> - hoconsc:union([ - key_license(), - file_license() - ]). - key_license() -> hoconsc:ref(?MODULE, key_license). -file_license() -> - hoconsc:ref(?MODULE, file_license). - check_license_watermark(Conf) -> case hocon_maps:get("license.connection_low_watermark", Conf) of undefined -> diff --git a/lib-ee/emqx_license/src/proto/emqx_license_proto_v2.erl b/lib-ee/emqx_license/src/proto/emqx_license_proto_v2.erl index 6af1cea77..b2d99d5d4 100644 --- a/lib-ee/emqx_license/src/proto/emqx_license_proto_v2.erl +++ b/lib-ee/emqx_license/src/proto/emqx_license_proto_v2.erl @@ -11,20 +11,15 @@ -export([introduced_in/0]). -export([ - remote_connection_counts/1, - save_and_backup_license/2 + remote_connection_counts/1 ]). -define(TIMEOUT, 500). -define(BACKUP_TIMEOUT, 15_000). introduced_in() -> - "5.0.5". + "e5.0.0". -spec remote_connection_counts(list(node())) -> list({atom(), term()}). remote_connection_counts(Nodes) -> 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). diff --git a/lib-ee/emqx_license/test/emqx_license_SUITE.erl b/lib-ee/emqx_license/test/emqx_license_SUITE.erl index 851ef30ef..f1cc7dbf8 100644 --- a/lib-ee/emqx_license/test/emqx_license_SUITE.erl +++ b/lib-ee/emqx_license/test/emqx_license_SUITE.erl @@ -27,168 +27,31 @@ end_per_suite(_) -> init_per_testcase(Case, Config) -> {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), - set_invalid_license_file(Case), Paths = set_override_paths(Case), Config0 = setup_test(Case, Config), Paths ++ Config0 ++ Config. end_per_testcase(Case, Config) -> - restore_valid_license_file(Case), clean_overrides(Case, Config), teardown_test(Case, Config), 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) -> []. -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) -> 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) -> []. -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) -> 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) -> - Config = #{type => file, file => emqx_license_test_lib:default_license()}, + Config = #{key => emqx_license_test_lib:default_license()}, 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); set_special_configs(_) -> ok. @@ -201,151 +64,19 @@ assert_on_nodes(Nodes, RunFun, CheckFun) -> %% 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) -> ?assertMatch( {error, [_ | _]}, 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( {ok, #{}}, emqx_license:update_key(LicenseValue) ). -t_read_license_from_invalid_file(_Config) -> - ?assertMatch( - {error, enoent}, - emqx_license:read_license() - ). - t_check_exceeded(_Config) -> {_, License} = mk_license( [ @@ -435,39 +166,6 @@ t_check_not_loaded(_Config) -> 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 %%------------------------------------------------------------------------------ diff --git a/lib-ee/emqx_license/test/emqx_license_checker_SUITE.erl b/lib-ee/emqx_license/test/emqx_license_checker_SUITE.erl index 0e10b684d..d3e585d30 100644 --- a/lib-ee/emqx_license/test/emqx_license_checker_SUITE.erl +++ b/lib-ee/emqx_license/test/emqx_license_checker_SUITE.erl @@ -35,7 +35,7 @@ end_per_testcase(_Case, _Config) -> ok. 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); set_special_configs(_) -> ok. diff --git a/lib-ee/emqx_license/test/emqx_license_cli_SUITE.erl b/lib-ee/emqx_license/test/emqx_license_cli_SUITE.erl index 5cf11adda..84d694ca5 100644 --- a/lib-ee/emqx_license/test/emqx_license_cli_SUITE.erl +++ b/lib-ee/emqx_license/test/emqx_license_cli_SUITE.erl @@ -31,9 +31,9 @@ end_per_testcase(_Case, _Config) -> ok. 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), - RawConfig = #{<<"type">> => file, <<"file">> => emqx_license_test_lib:default_license()}, + RawConfig = #{<<"key">> => emqx_license_test_lib:default_license()}, emqx_config:put_raw([<<"license">>], RawConfig); set_special_configs(_) -> ok. @@ -48,13 +48,8 @@ t_help(_Config) -> t_info(_Config) -> _ = 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) -> - {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(["reload"]), _ = emqx_license_cli:license(["update", "Invalid License Value"]). diff --git a/lib-ee/emqx_license/test/emqx_license_http_api_SUITE.erl b/lib-ee/emqx_license/test/emqx_license_http_api_SUITE.erl index afcb85059..082a236d8 100644 --- a/lib-ee/emqx_license/test/emqx_license_http_api_SUITE.erl +++ b/lib-ee/emqx_license/test/emqx_license_http_api_SUITE.erl @@ -26,9 +26,10 @@ init_per_suite(Config) -> end_per_suite(_) -> 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), - RawConfig = #{<<"type">> => file, <<"file">> => emqx_license_test_lib:default_license()}, + RawConfig = #{<<"key">> => LicenseKey}, emqx_config:put_raw([<<"license">>], RawConfig), persistent_term:erase(emqx_license_test_pubkey), ok. @@ -37,9 +38,9 @@ set_special_configs(emqx_dashboard) -> emqx_dashboard_api_test_helpers:set_default_config(<<"license_admin">>); set_special_configs(emqx_license) -> LicenseKey = emqx_license_test_lib:make_license(#{max_connections => "100"}), - Config = #{type => key, key => LicenseKey}, + Config = #{key => LicenseKey}, emqx_config:put([license], Config), - RawConfig = #{<<"type">> => key, <<"key">> => LicenseKey}, + RawConfig = #{<<"key">> => LicenseKey}, emqx_config:put_raw([<<"license">>], RawConfig), ok = persistent_term:put( emqx_license_test_pubkey, @@ -82,14 +83,6 @@ assert_untouched_license() -> get_license() ). -multipart_formdata_request(Uri, File) -> - emqx_dashboard_api_test_helpers:multipart_formdata_request( - Uri, - _Username = <<"license_admin">>, - _Fields = [], - [File] - ). - %%------------------------------------------------------------------------------ %% Testcases %%------------------------------------------------------------------------------ @@ -114,74 +107,11 @@ t_license_info(_Config) -> ), 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) -> NewKey = emqx_license_test_lib:make_license(#{max_connections => "999"}), Res = request( post, - uri(["license", "key"]), + uri(["license"]), #{key => NewKey} ), ?assertMatch({ok, 200, _}, Res), @@ -210,7 +140,7 @@ t_license_upload_key_bad_key(_Config) -> BadKey = <<"bad key">>, Res = request( post, - uri(["license", "key"]), + uri(["license"]), #{key => BadKey} ), ?assertMatch({ok, 400, _}, Res), @@ -228,7 +158,7 @@ t_license_upload_key_bad_key(_Config) -> t_license_upload_key_not_json(_Config) -> Res = request( post, - uri(["license", "key"]), + uri(["license"]), <<"">> ), ?assertMatch({ok, 400, _}, Res), diff --git a/lib-ee/emqx_license/test/emqx_license_installer_SUITE.erl b/lib-ee/emqx_license/test/emqx_license_installer_SUITE.erl index e62c4d814..dfc64672c 100644 --- a/lib-ee/emqx_license/test/emqx_license_installer_SUITE.erl +++ b/lib-ee/emqx_license/test/emqx_license_installer_SUITE.erl @@ -31,7 +31,7 @@ end_per_testcase(_Case, _Config) -> ok. 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); set_special_configs(_) -> ok. diff --git a/lib-ee/emqx_license/test/emqx_license_parser_SUITE.erl b/lib-ee/emqx_license/test/emqx_license_parser_SUITE.erl index e9868cdc1..44c83d27b 100644 --- a/lib-ee/emqx_license/test/emqx_license_parser_SUITE.erl +++ b/lib-ee/emqx_license/test/emqx_license_parser_SUITE.erl @@ -30,7 +30,7 @@ end_per_testcase(_Case, _Config) -> ok. 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); set_special_configs(_) -> ok. diff --git a/lib-ee/emqx_license/test/emqx_license_parser_legacy_SUITE.erl b/lib-ee/emqx_license/test/emqx_license_parser_legacy_SUITE.erl deleted file mode 100644 index 61f3c4cd8..000000000 --- a/lib-ee/emqx_license/test/emqx_license_parser_legacy_SUITE.erl +++ /dev/null @@ -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}]). diff --git a/lib-ee/emqx_license/test/emqx_license_resources_SUITE.erl b/lib-ee/emqx_license/test/emqx_license_resources_SUITE.erl index 84a63e611..529eedf24 100644 --- a/lib-ee/emqx_license/test/emqx_license_resources_SUITE.erl +++ b/lib-ee/emqx_license/test/emqx_license_resources_SUITE.erl @@ -31,7 +31,7 @@ end_per_testcase(_Case, _Config) -> ok. 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); set_special_configs(_) -> ok. diff --git a/lib-ee/emqx_license/test/emqx_license_test_lib.erl b/lib-ee/emqx_license/test/emqx_license_test_lib.erl index af3912f75..5310921ee 100644 --- a/lib-ee/emqx_license/test/emqx_license_test_lib.erl +++ b/lib-ee/emqx_license/test/emqx_license_test_lib.erl @@ -18,8 +18,6 @@ "10" ]). --define(DEFAULT_LICENSE_FILE, "emqx.lic"). - private_key() -> test_key("pvt.key"). @@ -32,9 +30,6 @@ public_key_pem() -> test_key(Filename) -> test_key(Filename, decoded). -legacy_license() -> - test_key("emqx.lic", pem). - test_key(Filename, Format) -> Dir = code:lib_dir(emqx_license, test), Path = filename:join([Dir, "data", Filename]), @@ -83,8 +78,5 @@ make_license(Values) -> default_license() -> %% keep it the same as in etc/emqx_license.conf - License = - "MjIwMTExCjAKMTAKRXZhbHVhdGlvbgpjb250YWN0QGVtcXguaW8KZGVmYXVsdAoyMDIyMDQxOQoxODI1CjEwMDAK." - "MEQCICbgRVijCQov2hrvZXR1mk9Oa+tyV1F5oJ6iOZeSHjnQAiB9dUiVeaZekDOjztk+NCWjhk4PG8tWfw2uFZWruSzD6g==", - ok = file:write_file(?DEFAULT_LICENSE_FILE, License), - ?DEFAULT_LICENSE_FILE. + "MjIwMTExCjAKMTAKRXZhbHVhdGlvbgpjb250YWN0QGVtcXguaW8KZGVmYXVsdAoyMDIyMDQxOQoxODI1CjEwMDAK." + "MEQCICbgRVijCQov2hrvZXR1mk9Oa+tyV1F5oJ6iOZeSHjnQAiB9dUiVeaZekDOjztk+NCWjhk4PG8tWfw2uFZWruSzD6g==".