diff --git a/.github/workflows/run_cts_tests.yaml b/.github/workflows/run_cts_tests.yaml index c93e4dd88..c8c642af7 100644 --- a/.github/workflows/run_cts_tests.yaml +++ b/.github/workflows/run_cts_tests.yaml @@ -89,7 +89,7 @@ jobs: if: matrix.connect_type == 'tls' run: | cat <<-EOF >> "$GITHUB_ENV" - EMQX_AUTH__MONGO__SSL=on + EMQX_AUTH__MONGO__SSL__ENABLE=on EMQX_AUTH__MONGO__SSL__CACERTFILE=/emqx/apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE_data/ca.pem EMQX_AUTH__MONGO__SSL__CERTFILE=/emqx/apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE_data/client-cert.pem EMQX_AUTH__MONGO__SSL__KEYFILE=/emqx/apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE_data/client-key.pem @@ -101,7 +101,7 @@ jobs: MONGO_TAG: ${{ matrix.mongo_tag }} if: matrix.connect_type == 'tcp' run: | - echo EMQX_AUTH__MONGO__SSL=off >> "$GITHUB_ENV" + echo EMQX_AUTH__MONGO__SSL__ENABLE=off >> "$GITHUB_ENV" - name: setup if: matrix.network_type == 'ipv4' run: | @@ -163,10 +163,10 @@ jobs: if: matrix.connect_type == 'tls' run: | cat <<-EOF >> "$GITHUB_ENV" + EMQX_AUTH__MYSQL__SSL__ENABLE=on EMQX_AUTH__MYSQL__USERNAME=ssluser EMQX_AUTH__MYSQL__PASSWORD=public EMQX_AUTH__MYSQL__DATABASE=mqtt - EMQX_AUTH__MYSQL__SSL=on EMQX_AUTH__MYSQL__SSL__CACERTFILE=/emqx/apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data/ca.pem EMQX_AUTH__MYSQL__SSL__CERTFILE=/emqx/apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data/client-cert.pem EMQX_AUTH__MYSQL__SSL__KEYFILE=/emqx/apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data/client-key.pem @@ -182,7 +182,7 @@ jobs: EMQX_AUTH__MYSQL__USERNAME=root EMQX_AUTH__MYSQL__PASSWORD=public EMQX_AUTH__MYSQL__DATABASE=mqtt - EMQX_AUTH__MYSQL__SSL=off + EMQX_AUTH__MYSQL__SSL__ENABLE=off EOF - name: setup if: matrix.network_type == 'ipv4' @@ -242,7 +242,7 @@ jobs: if: matrix.connect_type == 'tls' run: | cat <<-EOF >> "$GITHUB_ENV" - EMQX_AUTH__PGSQL__SSL=on + EMQX_AUTH__PGSQL__SSL__ENABLE=on EMQX_AUTH__PGSQL__SSL__CACERTFILE=/emqx/apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/ca.pem EMQX_AUTH__PGSQL__SSL__CERTFILE=/emqx/apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/client-cert.pem EMQX_AUTH__PGSQL__SSL__KEYFILE=/emqx/apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/client-key.pem @@ -254,7 +254,7 @@ jobs: PGSQL_TAG: ${{ matrix.pgsql_tag }} if: matrix.connect_type == 'tcp' run: | - echo EMQX_AUTH__PGSQL__SSL=off >> "$GITHUB_ENV" + echo EMQX_AUTH__PGSQL__SSL__ENABLE=off >> "$GITHUB_ENV" - name: setup if: matrix.network_type == 'ipv4' run: | @@ -321,7 +321,7 @@ jobs: if: matrix.connect_type == 'tls' run: | cat <<-EOF >> "$GITHUB_ENV" - EMQX_AUTH__REDIS__SSL=on + EMQX_AUTH__REDIS__SSL__ENABLE=on EMQX_AUTH__REDIS__SSL__CACERTFILE=/emqx/apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs/ca.crt EMQX_AUTH__REDIS__SSL__CERTFILE=/emqx/apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs/redis.crt EMQX_AUTH__REDIS__SSL__KEYFILE=/emqx/apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs/redis.key @@ -333,7 +333,7 @@ jobs: REDIS_TAG: ${{ matrix.redis_tag }} if: matrix.connect_type == 'tcp' run: | - echo EMQX_AUTH__REDIS__SSL=off >> "$GITHUB_ENV" + echo EMQX_AUTH__REDIS__SSL__ENABLE=off >> "$GITHUB_ENV" - name: get server address run: | ipv4_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' redis) diff --git a/apps/emqx_auth_http/etc/emqx_auth_http.conf b/apps/emqx_auth_http/etc/emqx_auth_http.conf index 058c86dbf..56e2055c0 100644 --- a/apps/emqx_auth_http/etc/emqx_auth_http.conf +++ b/apps/emqx_auth_http/etc/emqx_auth_http.conf @@ -7,7 +7,7 @@ ## Value: URL ## ## Examples: http://127.0.0.1:80/mqtt/auth, https://[::1]:80/mqtt/auth -auth.http.auth_req.url = http://127.0.0.1:80/mqtt/auth +auth.http.auth_req.url = "http://127.0.0.1:80/mqtt/auth" ## HTTP Request Method for Auth Request ## @@ -18,7 +18,8 @@ auth.http.auth_req.method = post ## The possible values of the Content-Type header: application/x-www-form-urlencoded, application/json ## ## Examples: auth.http.auth_req.headers.accept = */* -auth.http.auth_req.headers.content_type = application/x-www-form-urlencoded + +auth.http.auth_req.headers.content_type = "application/x-www-form-urlencoded" ## Parameters used to construct the request body or query string parameters ## When the request method is GET, these parameters will be converted into query string parameters @@ -35,14 +36,14 @@ auth.http.auth_req.headers.content_type = application/x-www-form-urlencoded ## - %d: subject of client TLS cert ## ## Value: =,=,... -auth.http.auth_req.params = clientid=%c,username=%u,password=%P +auth.http.auth_req.params = "clientid=%c,username=%u,password=%P" ## HTTP URL API path for SuperUser Request ## ## Value: URL ## ## Examples: http://127.0.0.1:80/mqtt/superuser, https://[::1]:80/mqtt/superuser -auth.http.super_req.url = http://127.0.0.1:80/mqtt/superuser +auth.http.super_req.url = "http://127.0.0.1:80/mqtt/superuser" ## HTTP Request Method for SuperUser Request ## @@ -53,7 +54,7 @@ auth.http.super_req.method = post ## The possible values of the Content-Type header: application/x-www-form-urlencoded, application/json ## ## Examples: auth.http.super_req.headers.accept = */* -auth.http.super_req.headers.content-type = application/x-www-form-urlencoded +auth.http.super_req.headers.content-type = "application/x-www-form-urlencoded" ## Parameters used to construct the request body or query string parameters ## When the request method is GET, these parameters will be converted into query string parameters @@ -70,7 +71,7 @@ auth.http.super_req.headers.content-type = application/x-www-form-urlencoded ## - %d: subject of client TLS cert ## ## Value: =,=,... -auth.http.super_req.params = clientid=%c,username=%u +auth.http.super_req.params = "clientid=%c,username=%u" ## HTTP URL API path for ACL Request ## Comment out this config to disable ACL checks @@ -78,7 +79,7 @@ auth.http.super_req.params = clientid=%c,username=%u ## Value: URL ## ## Examples: http://127.0.0.1:80/mqtt/acl, https://[::1]:80/mqtt/acl -auth.http.acl_req.url = http://127.0.0.1:80/mqtt/acl +auth.http.acl_req.url = "http://127.0.0.1:80/mqtt/acl" ## HTTP Request Method for ACL Request ## @@ -89,7 +90,7 @@ auth.http.acl_req.method = post ## The possible values of the Content-Type header: application/x-www-form-urlencoded, application/json ## ## Examples: auth.http.acl_req.headers.accept = */* -auth.http.acl_req.headers.content-type = application/x-www-form-urlencoded +auth.http.acl_req.headers.content-type = "application/x-www-form-urlencoded" ## Parameters used to construct the request body or query string parameters ## When the request method is GET, these parameters will be converted into query string parameters @@ -108,7 +109,7 @@ auth.http.acl_req.headers.content-type = application/x-www-form-urlencoded ## - %t: topic ## ## Value: =,=,... -auth.http.acl_req.params = access=%A,username=%u,clientid=%c,ipaddr=%a,topic=%t,mountpoint=%m +auth.http.acl_req.params = "access=%A,username=%u,clientid=%c,ipaddr=%a,topic=%t,mountpoint=%m" ## Time-out time for the request. ## @@ -143,17 +144,17 @@ auth.http.pool_size = 32 ## are used during server authentication and when building the client certificate chain. ## ## Value: File -## auth.http.ssl.cacertfile = {{ platform_etc_dir }}/certs/ca.pem +## auth.http.ssl.cacertfile = "{{ platform_etc_dir }}/certs/ca.pem" ## The path to a file containing the client's certificate. ## ## Value: File -## auth.http.ssl.certfile = {{ platform_etc_dir }}/certs/client-cert.pem +## auth.http.ssl.certfile = "{{ platform_etc_dir }}/certs/client-cert.pem" ## Path to a file containing the client's private PEM-encoded key. ## ## Value: File -## auth.http.ssl.keyfile = {{ platform_etc_dir }}/certs/client-key.pem +## auth.http.ssl.keyfile = "{{ platform_etc_dir }}/certs/client-key.pem" ## In mode verify_none the default behavior is to allow all x509-path ## validation errors. diff --git a/apps/emqx_auth_http/rebar.config b/apps/emqx_auth_http/rebar.config index 142b7b429..01c0f4209 100644 --- a/apps/emqx_auth_http/rebar.config +++ b/apps/emqx_auth_http/rebar.config @@ -19,7 +19,7 @@ {profiles, [{test, [{deps, - [{emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "1.2.2"}}}, + [ {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "v1.2.2"}}} ]} ]} diff --git a/apps/emqx_auth_jwt/etc/emqx_auth_jwt.conf b/apps/emqx_auth_jwt/etc/emqx_auth_jwt.conf index 748902f9f..e0e6d48dd 100644 --- a/apps/emqx_auth_jwt/etc/emqx_auth_jwt.conf +++ b/apps/emqx_auth_jwt/etc/emqx_auth_jwt.conf @@ -10,13 +10,13 @@ auth.jwt.secret = emqxsecret ## RSA or ECDSA public key file. ## ## Value: File -#auth.jwt.pubkey = etc/certs/jwt_public_key.pem +#auth.jwt.pubkey = "etc/certs/jwt_public_key.pem" ## The JWKs server address ## ## see: http://self-issued.info/docs/draft-ietf-jose-json-web-key.html ## -#auth.jwt.jwks = https://127.0.0.1:8080/jwks +#auth.jwt.jwks.endpoint = "https://127.0.0.1:8080/jwks" ## The JWKs refresh interval ## @@ -32,7 +32,7 @@ auth.jwt.from = password ## Enable to verify claims fields ## ## Value: on | off -auth.jwt.verify_claims = off +auth.jwt.verify_claims.enable = off ## The checklist of claims to validate ## @@ -46,4 +46,4 @@ auth.jwt.verify_claims = off ## ## For example, to verify that the username in the JWT payload is the same ## as the client (MQTT protocol) username -#auth.jwt.verify_claims.username = %u +#auth.jwt.verify_claims.username = "%u" \ No newline at end of file diff --git a/apps/emqx_auth_jwt/priv/emqx_auth_jwt.schema b/apps/emqx_auth_jwt/priv/emqx_auth_jwt.schema index 3d8de3678..10b2daa5e 100644 --- a/apps/emqx_auth_jwt/priv/emqx_auth_jwt.schema +++ b/apps/emqx_auth_jwt/priv/emqx_auth_jwt.schema @@ -4,7 +4,7 @@ {datatype, string} ]}. -{mapping, "auth.jwt.jwks", "emqx_auth_jwt.jwks", [ +{mapping, "auth.jwt.jwks.endpoint", "emqx_auth_jwt.jwks", [ {datatype, string} ]}. @@ -26,7 +26,7 @@ {datatype, {enum, [raw, der]}} ]}. -{mapping, "auth.jwt.verify_claims", "emqx_auth_jwt.verify_claims", [ +{mapping, "auth.jwt.verify_claims.enable", "emqx_auth_jwt.verify_claims", [ {default, off}, {datatype, flag} ]}. @@ -36,7 +36,7 @@ ]}. {translation, "emqx_auth_jwt.verify_claims", fun(Conf) -> - case cuttlefish:conf_get("auth.jwt.verify_claims", Conf) of + case cuttlefish:conf_get("auth.jwt.verify_claims.enable", Conf) of false -> cuttlefish:unset(); true -> lists:foldr( diff --git a/apps/emqx_auth_jwt/rebar.config b/apps/emqx_auth_jwt/rebar.config index 5e7575881..3ec554950 100644 --- a/apps/emqx_auth_jwt/rebar.config +++ b/apps/emqx_auth_jwt/rebar.config @@ -20,6 +20,6 @@ {profiles, [{test, - [{deps, [{emqx_ct_helpers, {git, "http://github.com/emqx/emqx-ct-helpers", {tag, "1.2.2"}}}]} + [{deps, []} ]} ]}. diff --git a/apps/emqx_auth_ldap/etc/emqx_auth_ldap.conf b/apps/emqx_auth_ldap/etc/emqx_auth_ldap.conf index 8eebefe97..b457229e3 100644 --- a/apps/emqx_auth_ldap/etc/emqx_auth_ldap.conf +++ b/apps/emqx_auth_ldap/etc/emqx_auth_ldap.conf @@ -5,7 +5,7 @@ ## LDAP server list, seperated by ','. ## ## Value: String -auth.ldap.servers = 127.0.0.1 +auth.ldap.servers = "127.0.0.1" ## LDAP server port. ## @@ -20,7 +20,7 @@ auth.ldap.pool = 8 ## LDAP Bind DN. ## ## Value: DN -auth.ldap.bind_dn = cn=root,dc=emqx,dc=io +auth.ldap.bind_dn = "cn=root,dc=emqx,dc=io" ## LDAP Bind Password. ## @@ -37,7 +37,7 @@ auth.ldap.timeout = 30s ## Variables: ## ## Value: DN -auth.ldap.device_dn = ou=device,dc=emqx,dc=io +auth.ldap.device_dn = "ou=device,dc=emqx,dc=io" ## Specified ObjectClass ## @@ -63,14 +63,14 @@ auth.ldap.password.attributetype = userPassword ## Whether to enable SSL. ## ## Value: true | false -auth.ldap.ssl = false +auth.ldap.ssl.enable = false -#auth.ldap.ssl.certfile = etc/certs/cert.pem +#auth.ldap.ssl.certfile = "etc/certs/cert.pem" -#auth.ldap.ssl.keyfile = etc/certs/key.pem +#auth.ldap.ssl.keyfile = "etc/certs/key.pem" -#auth.ldap.ssl.cacertfile = etc/certs/cacert.pem +#auth.ldap.ssl.cacertfile = "etc/certs/cacert.pem" -#auth.ldap.ssl.verify = verify_peer +#auth.ldap.ssl.verify = "verify_peer" #auth.ldap.ssl.server_name_indication = your_server_name diff --git a/apps/emqx_auth_ldap/priv/emqx_auth_ldap.schema b/apps/emqx_auth_ldap/priv/emqx_auth_ldap.schema index b3d3de1a2..f9c3bf16b 100644 --- a/apps/emqx_auth_ldap/priv/emqx_auth_ldap.schema +++ b/apps/emqx_auth_ldap/priv/emqx_auth_ldap.schema @@ -31,7 +31,7 @@ {datatype, {duration, ms}} ]}. -{mapping, "auth.ldap.ssl", "emqx_auth_ldap.ldap", [ +{mapping, "auth.ldap.ssl.enable", "emqx_auth_ldap.ldap", [ {default, false}, {datatype, {enum, [true, false]}} ]}. @@ -83,7 +83,7 @@ {bind_password, BindPassword}, {pool, Pool}, {auto_reconnect, 2}], - case cuttlefish:conf_get("auth.ldap.ssl", Conf) of + case cuttlefish:conf_get("auth.ldap.ssl.enable", Conf) of true -> [{ssl, true}, {sslopts, Filter(SslOpts())}|Opts]; false -> [{ssl, false}|Opts] end diff --git a/apps/emqx_auth_ldap/rebar.config b/apps/emqx_auth_ldap/rebar.config index 48eaf812f..811468a7b 100644 --- a/apps/emqx_auth_ldap/rebar.config +++ b/apps/emqx_auth_ldap/rebar.config @@ -4,7 +4,7 @@ {profiles, [{test, - [{deps, [{emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "1.2.2"}}}]} + [{deps, []} ]} ]}. diff --git a/apps/emqx_auth_mnesia/etc/emqx_auth_mnesia.conf b/apps/emqx_auth_mnesia/etc/emqx_auth_mnesia.conf index ff74656cb..758df1a9c 100644 --- a/apps/emqx_auth_mnesia/etc/emqx_auth_mnesia.conf +++ b/apps/emqx_auth_mnesia/etc/emqx_auth_mnesia.conf @@ -10,12 +10,12 @@ auth.mnesia.password_hash = sha256 ## Examples ##auth.client.1.clientid = id ##auth.client.1.password = passwd -##auth.client.2.clientid = dev:devid +##auth.client.2.clientid = "dev:devid" ##auth.client.2.password = passwd2 -##auth.client.3.clientid = app:appid +##auth.client.3.clientid = "app:appid" ##auth.client.3.password = passwd3 -##auth.client.4.clientid = client~!@#$%^&*()_+ -##auth.client.4.password = passwd~!@#$%^&*()_+ +##auth.client.4.clientid = "client~!@#$%^&*()_+" +##auth.client.4.password = "passwd~!@#$%^&*()_+" ##-------------------------------------------------------------------- ## Username Authentication @@ -26,5 +26,5 @@ auth.mnesia.password_hash = sha256 ##auth.user.1.password = public ##auth.user.2.username = feng@emqtt.io ##auth.user.2.password = public -##auth.user.3.username = name~!@#$%^&*()_+ -##auth.user.3.password = pwsswd~!@#$%^&*()_+ +##auth.user.3.username = "name~!@#$%^&*()_+" +##auth.user.3.password = "pwsswd~!@#$%^&*()_+" diff --git a/apps/emqx_auth_mongo/etc/emqx_auth_mongo.conf b/apps/emqx_auth_mongo/etc/emqx_auth_mongo.conf index 2a3d038f0..c59c80643 100644 --- a/apps/emqx_auth_mongo/etc/emqx_auth_mongo.conf +++ b/apps/emqx_auth_mongo/etc/emqx_auth_mongo.conf @@ -16,8 +16,8 @@ auth.mongo.type = single ## ## Value: String ## -## Examples: 127.0.0.1:27017,127.0.0.2:27017... -auth.mongo.server = 127.0.0.1:27017 +## Examples: "127.0.0.1:27017,127.0.0.2:27017,..." +auth.mongo.server = "127.0.0.1:27017" ## MongoDB pool size ## @@ -53,7 +53,7 @@ auth.mongo.database = mqtt ## Whether to enable SSL connection. ## ## Value: on | off -## auth.mongo.ssl = off +## auth.mongo.ssl.enable = off ## SSL keyfile. ## @@ -117,17 +117,17 @@ auth.mongo.topology.max_overflow = 0 auth.mongo.auth_query.password_hash = sha256 ## sha256 with salt suffix -## auth.mongo.auth_query.password_hash = sha256,salt +## auth.mongo.auth_query.password_hash = "sha256,salt" ## sha256 with salt prefix -## auth.mongo.auth_query.password_hash = salt,sha256 +## auth.mongo.auth_query.password_hash = "salt,sha256" ## bcrypt with salt prefix -## auth.mongo.auth_query.password_hash = salt,bcrypt +## auth.mongo.auth_query.password_hash = "salt,bcrypt" ## pbkdf2 with macfun iterations dklen ## macfun: md4, md5, ripemd160, sha, sha224, sha256, sha384, sha512 -## auth.mongo.auth_query.password_hash = pbkdf2,sha256,1000,20 +## auth.mongo.auth_query.password_hash = "pbkdf2,sha256,1000,20" ## Authentication query. auth.mongo.auth_query.collection = mqtt_user @@ -146,15 +146,15 @@ auth.mongo.auth_query.password_field = password ## - %d: subject of client TLS cert ## ## auth.mongo.auth_query.selector = {Field}={Placeholder} -auth.mongo.auth_query.selector = username=%u +auth.mongo.auth_query.selector = "username=%u" ## ------------------------------------------------- ## Super User Query ## ------------------------------------------------- auth.mongo.super_query.collection = mqtt_user auth.mongo.super_query.super_field = is_superuser -#auth.mongo.super_query.selector = username=%u, clientid=%c -auth.mongo.super_query.selector = username=%u +#auth.mongo.super_query.selector.1 = username=%u, clientid=%c +auth.mongo.super_query.selector = "username=%u" ## ACL Selector. ## @@ -165,8 +165,8 @@ auth.mongo.super_query.selector = username=%u ## ## With following 2 selectors configured: ## -## auth.mongo.acl_query.selector.1 = username=%u -## auth.mongo.acl_query.selector.2 = username=$all +## auth.mongo.acl_query.selector.1 = "username=%u" +## auth.mongo.acl_query.selector.2 = "username=$all" ## ## And if a client connected using username 'ilyas', ## then the following mongo command will be used to @@ -180,8 +180,8 @@ auth.mongo.super_query.selector = username=%u ## ## Examples: ## -## auth.mongo.acl_query.selector.1 = username=%u,clientid=%c -## auth.mongo.acl_query.selector.2 = username=$all -## auth.mongo.acl_query.selector.3 = clientid=$all +## auth.mongo.acl_query.selector.1 = "username=%u,clientid=%c" +## auth.mongo.acl_query.selector.2 = "username=$all" +## auth.mongo.acl_query.selector.3 = "clientid=$all" auth.mongo.acl_query.collection = mqtt_acl -auth.mongo.acl_query.selector = username=%u +auth.mongo.acl_query.selector = "username=%u" diff --git a/apps/emqx_auth_mongo/priv/emqx_auth_mongo.schema b/apps/emqx_auth_mongo/priv/emqx_auth_mongo.schema index 8a2ff98b3..cd8c03015 100644 --- a/apps/emqx_auth_mongo/priv/emqx_auth_mongo.schema +++ b/apps/emqx_auth_mongo/priv/emqx_auth_mongo.schema @@ -45,7 +45,7 @@ {datatype, string} ]}. -{mapping, "auth.mongo.ssl", "emqx_auth_mongo.server", [ +{mapping, "auth.mongo.ssl.enable", "emqx_auth_mongo.server", [ {default, off}, {datatype, {enum, [on, off, true, false]}} %% FIXME: ture/false is compatible with 4.0-4.2 version format, plan to delete in 5.0 ]}. @@ -130,8 +130,6 @@ true -> []; false -> [{r_mode, R}] end, - - Filter = fun(Opts) -> [{K, V} || {K, V} <- Opts, V =/= undefined] end, SslOpts = fun(Prefix) -> Verify = case cuttlefish:conf_get(Prefix ++ ".verify", Conf, false) of @@ -149,8 +147,14 @@ end, %% FIXME: compatible with 4.0-4.2 version format, plan to delete in 5.0 - Ssl = case cuttlefish:conf_get("auth.mongo.ssl", Conf) of - on -> [{ssl, true}, {ssl_opts, SslOpts("auth.mongo.ssl")}]; + GenSsl = case cuttlefish:conf_get("auth.mongo.ssl.cacertfile", Conf, undefined) of + undefined -> [{ssl, true}, {ssl_opts, SslOpts("auth.mongo.ssl_opts")}]; + _ -> [{ssl, true}, {ssl_opts, SslOpts("auth.mongo.ssl")}] + end, + + %% FIXME: compatible with 4.0-4.2 version format, plan to delete in 5.0 + Ssl = case cuttlefish:conf_get("auth.mongo.ssl.enable", Conf) of + on -> GenSsl; off -> []; true -> [{ssl, true}, {ssl_opts, SslOpts("auth.mongo.ssl_opts")}]; false -> [] diff --git a/apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf b/apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf index 6014329b3..1c3d40059 100644 --- a/apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf +++ b/apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf @@ -7,7 +7,7 @@ ## Value: Port | IP:Port ## ## Examples: 3306, 127.0.0.1:3306, localhost:3306 -auth.mysql.server = 127.0.0.1:3306 +auth.mysql.server = "127.0.0.1:3306" ## MySQL pool size. ## @@ -50,7 +50,7 @@ auth.mysql.database = mqtt ## - %C: common name of client TLS cert ## - %d: subject of client TLS cert ## -auth.mysql.auth_query = select password from mqtt_user where username = '%u' limit 1 +auth.mysql.auth_query = "select password from mqtt_user where username = '%u' limit 1" ## auth.mysql.auth_query = select password_hash as password from mqtt_user where username = '%u' limit 1 ## Password hash. @@ -59,17 +59,17 @@ auth.mysql.auth_query = select password from mqtt_user where username = '%u' lim auth.mysql.password_hash = sha256 ## sha256 with salt prefix -## auth.mysql.password_hash = salt,sha256 +## auth.mysql.password_hash = "salt,sha256" ## bcrypt with salt only prefix -## auth.mysql.password_hash = salt,bcrypt +## auth.mysql.password_hash = "salt,bcrypt" ## sha256 with salt suffix -## auth.mysql.password_hash = sha256,salt +## auth.mysql.password_hash = "sha256,salt" ## pbkdf2 with macfun iterations dklen ## macfun: md4, md5, ripemd160, sha, sha224, sha256, sha384, sha512 -## auth.mysql.password_hash = pbkdf2,sha256,1000,20 +## auth.mysql.password_hash = "pbkdf2,sha256,1000,20" ## Superuser query. ## @@ -81,7 +81,7 @@ auth.mysql.password_hash = sha256 ## - %C: common name of client TLS cert ## - %d: subject of client TLS cert ## -auth.mysql.super_query = select is_superuser from mqtt_user where username = '%u' limit 1 +auth.mysql.super_query = "select is_superuser from mqtt_user where username = '%u' limit 1" ## ACL query. ## @@ -93,12 +93,12 @@ auth.mysql.super_query = select is_superuser from mqtt_user where username = '%u ## - %c: clientid ## ## Note: You can add the 'ORDER BY' statement to control the rules match order -auth.mysql.acl_query = select allow, ipaddr, username, clientid, access, topic from mqtt_acl where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c' +auth.mysql.acl_query = "select allow, ipaddr, username, clientid, access, topic from mqtt_acl where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c'" ## Mysql ssl configuration. ## ## Value: on | off -#auth.mysql.ssl = off +## auth.mysql.ssl.enable = off ## CA certificate. ## diff --git a/apps/emqx_auth_mysql/priv/emqx_auth_mysql.schema b/apps/emqx_auth_mysql/priv/emqx_auth_mysql.schema index 5e4619301..fba25f41f 100644 --- a/apps/emqx_auth_mysql/priv/emqx_auth_mysql.schema +++ b/apps/emqx_auth_mysql/priv/emqx_auth_mysql.schema @@ -30,7 +30,7 @@ {datatype, string} ]}. -{mapping, "auth.mysql.ssl", "emqx_auth_mysql.server", [ +{mapping, "auth.mysql.ssl.enable", "emqx_auth_mysql.server", [ {default, off}, {datatype, flag} ]}. @@ -94,7 +94,7 @@ {keep_alive, true}], Filter = fun(Opts) -> [{K, V} || {K, V} <- Opts, V =/= undefined] end, Options1 = - case cuttlefish:conf_get("auth.mysql.ssl", Conf) of + case cuttlefish:conf_get("auth.mysql.ssl.enable", Conf) of true -> %% FIXME: compatible with 4.0-4.2 version format, plan to delete in 5.0 CA = cuttlefish:conf_get( diff --git a/apps/emqx_auth_pgsql/README.md b/apps/emqx_auth_pgsql/README.md index 2dccd6f53..a8f5d723f 100644 --- a/apps/emqx_auth_pgsql/README.md +++ b/apps/emqx_auth_pgsql/README.md @@ -49,7 +49,7 @@ auth.pgsql.encoding = utf8 ## Whether to enable SSL connection. ## ## Value: true | false -auth.pgsql.ssl = false +auth.pgsql.ssl.enable = false ## SSL keyfile. ## diff --git a/apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf b/apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf index e39d0c78a..6f7018210 100644 --- a/apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf +++ b/apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf @@ -6,8 +6,8 @@ ## ## Value: Port | IP:Port ## -## Examples: 5432, 127.0.0.1:5432, localhost:5432 -auth.pgsql.server = 127.0.0.1:5432 +## Examples: 5432, "127.0.0.1:5432", "localhost:5432" +auth.pgsql.server = "127.0.0.1:5432" ## PostgreSQL pool size. ## @@ -37,7 +37,7 @@ auth.pgsql.encoding = utf8 ## Whether to enable SSL connection. ## ## Value: on | off -auth.pgsql.ssl = off +auth.pgsql.ssl.enable = off ## TLS version. ## @@ -87,7 +87,7 @@ auth.pgsql.ssl = off ## - %C: common name of client TLS cert ## - %d: subject of client TLS cert ## -auth.pgsql.auth_query = select password from mqtt_user where username = '%u' limit 1 +auth.pgsql.auth_query = "select password from mqtt_user where username = '%u' limit 1" ## Password hash. ## @@ -95,17 +95,17 @@ auth.pgsql.auth_query = select password from mqtt_user where username = '%u' lim auth.pgsql.password_hash = sha256 ## sha256 with salt prefix -## auth.pgsql.password_hash = salt,sha256 +## auth.pgsql.password_hash = "salt,sha256" ## sha256 with salt suffix -## auth.pgsql.password_hash = sha256,salt +## auth.pgsql.password_hash = "sha256,salt" ## bcrypt with salt prefix -## auth.pgsql.password_hash = salt,bcrypt +## auth.pgsql.password_hash = "salt,bcrypt" ## pbkdf2 with macfun iterations dklen ## macfun: md4, md5, ripemd160, sha, sha224, sha256, sha384, sha512 -## auth.pgsql.password_hash = pbkdf2,sha256,1000,20 +## auth.pgsql.password_hash = "pbkdf2,sha256,1000,20" ## Superuser query. ## @@ -117,7 +117,7 @@ auth.pgsql.password_hash = sha256 ## - %C: common name of client TLS cert ## - %d: subject of client TLS cert ## -auth.pgsql.super_query = select is_superuser from mqtt_user where username = '%u' limit 1 +auth.pgsql.super_query = "select is_superuser from mqtt_user where username = '%u' limit 1" ## ACL query. Comment this query, the ACL will be disabled. ## @@ -129,4 +129,4 @@ auth.pgsql.super_query = select is_superuser from mqtt_user where username = '%u ## - %c: clientid ## ## Note: You can add the 'ORDER BY' statement to control the rules match order -auth.pgsql.acl_query = select allow, ipaddr, username, clientid, access, topic from mqtt_acl where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c' +auth.pgsql.acl_query = "select allow, ipaddr, username, clientid, access, topic from mqtt_acl where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c'" diff --git a/apps/emqx_auth_pgsql/priv/emqx_auth_pgsql.schema b/apps/emqx_auth_pgsql/priv/emqx_auth_pgsql.schema index f78bcc597..2be9b0670 100644 --- a/apps/emqx_auth_pgsql/priv/emqx_auth_pgsql.schema +++ b/apps/emqx_auth_pgsql/priv/emqx_auth_pgsql.schema @@ -30,7 +30,7 @@ {datatype, atom} ]}. -{mapping, "auth.pgsql.ssl", "emqx_auth_pgsql.server", [ +{mapping, "auth.pgsql.ssl.enable", "emqx_auth_pgsql.server", [ {default, off}, {datatype, {enum, [on, off, true, false]}} %% FIXME: true/fasle is compatible with 4.0-4.2 version format, plan to delete in 5.0 ]}. @@ -116,8 +116,14 @@ end, %% FIXME: compatible with 4.0-4.2 version format, plan to delete in 5.0 - Ssl = case cuttlefish:conf_get("auth.pgsql.ssl", Conf) of - on -> [{ssl, true}, {ssl_opts, SslOpts("auth.pgsql.ssl")}]; + GenSsl = case cuttlefish:conf_get("auth.pgsql.ssl.cacertfile", Conf, undefined) of + undefined -> [{ssl, true}, {ssl_opts, SslOpts("auth.pgsql.ssl_opts")}]; + _ -> [{ssl, true}, {ssl_opts, SslOpts("auth.pgsql.ssl")}] + end, + + %% FIXME: compatible with 4.0-4.2 version format, plan to delete in 5.0 + Ssl = case cuttlefish:conf_get("auth.pgsql.ssl.enable", Conf) of + on -> GenSsl; off -> []; true -> [{ssl, true}, {ssl_opts, SslOpts("auth.pgsql.ssl_opts")}]; false -> [] diff --git a/apps/emqx_auth_redis/etc/emqx_auth_redis.conf b/apps/emqx_auth_redis/etc/emqx_auth_redis.conf index 5a56c5dce..62a6e4fe1 100644 --- a/apps/emqx_auth_redis/etc/emqx_auth_redis.conf +++ b/apps/emqx_auth_redis/etc/emqx_auth_redis.conf @@ -12,9 +12,9 @@ auth.redis.type = single ## Value: Port | IP:Port ## ## Single Redis Server: 127.0.0.1:6379, localhost:6379 -## Redis Sentinel: 127.0.0.1:26379,127.0.0.2:26379,127.0.0.3:26379 -## Redis Cluster: 127.0.0.1:6379,127.0.0.2:6379,127.0.0.3:6379 -auth.redis.server = 127.0.0.1:6379 +## Redis Sentinel: "127.0.0.1:26379,127.0.0.2:26379,127.0.0.3:26379" +## Redis Cluster: "127.0.0.1:6379,127.0.0.2:6379,127.0.0.3:6379" +auth.redis.server = "127.0.0.1:6379" ## Redis sentinel cluster name. ## @@ -52,10 +52,10 @@ auth.redis.database = 0 ## - %d: subject of client TLS cert ## ## Examples: -## - HGET mqtt_user:%u password -## - HMGET mqtt_user:%u password -## - HMGET mqtt_user:%u password salt -auth.redis.auth_cmd = HMGET mqtt_user:%u password +## - "HGET mqtt_user:%u password" +## - "HMGET mqtt_user:%u password" +## - "HMGET mqtt_user:%u password salt" +auth.redis.auth_cmd = "HMGET mqtt_user:%u password" ## Password hash. ## @@ -63,17 +63,17 @@ auth.redis.auth_cmd = HMGET mqtt_user:%u password auth.redis.password_hash = plain ## sha256 with salt prefix -## auth.redis.password_hash = salt,sha256 +## auth.redis.password_hash = "salt,sha256" ## sha256 with salt suffix -## auth.redis.password_hash = sha256,salt +## auth.redis.password_hash = "sha256,salt" ## bcrypt with salt prefix -## auth.redis.password_hash = salt,bcrypt +## auth.redis.password_hash = "salt,bcrypt" ## pbkdf2 with macfun iterations dklen ## macfun: md4, md5, ripemd160, sha, sha224, sha256, sha384, sha512 -## auth.redis.password_hash = pbkdf2,sha256,1000,20 +## auth.redis.password_hash = "pbkdf2,sha256,1000,20" ## Superuser query command. ## @@ -84,7 +84,7 @@ auth.redis.password_hash = plain ## - %c: clientid ## - %C: common name of client TLS cert ## - %d: subject of client TLS cert -auth.redis.super_cmd = HGET mqtt_user:%u is_superuser +auth.redis.super_cmd = "HGET mqtt_user:%u is_superuser" ## ACL query command. ## @@ -93,12 +93,12 @@ auth.redis.super_cmd = HGET mqtt_user:%u is_superuser ## Variables: ## - %u: username ## - %c: clientid -auth.redis.acl_cmd = HGETALL mqtt_acl:%u +auth.redis.acl_cmd = "HGETALL mqtt_acl:%u" ## Redis ssl configuration. ## ## Value: on | off -#auth.redis.ssl = off +# auth.redis.ssl.enable = off ## CA certificate. ## @@ -108,12 +108,12 @@ auth.redis.acl_cmd = HGETALL mqtt_acl:%u ## Client ssl certificate. ## ## Value: File -#auth.redis.ssl.certfile = path/to/your/certfile +# auth.redis.ssl.certfile = path/to/your/certfile ## Client ssl keyfile. ## ## Value: File -#auth.redis.ssl.keyfile = path/to/your/keyfile +# auth.redis.ssl.keyfile = path/to/your/keyfile ## In mode verify_none the default behavior is to allow all x509-path ## validation errors. diff --git a/apps/emqx_auth_redis/priv/emqx_auth_redis.schema b/apps/emqx_auth_redis/priv/emqx_auth_redis.schema index 3ce34d597..db758d8c0 100644 --- a/apps/emqx_auth_redis/priv/emqx_auth_redis.schema +++ b/apps/emqx_auth_redis/priv/emqx_auth_redis.schema @@ -33,7 +33,7 @@ hidden ]}. -{mapping, "auth.redis.ssl", "emqx_auth_redis.options", [ +{mapping, "auth.redis.ssl.enable", "emqx_auth_redis.options", [ {default, off}, {datatype, flag} ]}. @@ -75,7 +75,7 @@ ]}. {translation, "emqx_auth_redis.options", fun(Conf) -> - Ssl = cuttlefish:conf_get("auth.redis.ssl", Conf, false), + Ssl = cuttlefish:conf_get("auth.redis.ssl.enable", Conf, false), Filter = fun(Opts) -> [{K, V} || {K, V} <- Opts, V =/= undefined] end, case Ssl of true -> diff --git a/apps/emqx_bridge_mqtt/README.md b/apps/emqx_bridge_mqtt/README.md index 456fae584..812645627 100644 --- a/apps/emqx_bridge_mqtt/README.md +++ b/apps/emqx_bridge_mqtt/README.md @@ -53,13 +53,13 @@ The following is the basic configuration of RPC bridging. A simplest RPC bridgin ``` ## Bridge Address: Use node name (nodename@host) for rpc bridging, and host:port for mqtt connection -bridge.mqtt.emqx2.address = emqx2@192.168.1.2 +bridge.mqtt.emqx2.address = "emqx2@192.168.1.2" ## Forwarding topics of the message -bridge.mqtt.emqx2.forwards = sensor1/#,sensor2/# +bridge.mqtt.emqx2.forwards = "sensor1/#,sensor2/#" ## bridged mountpoint -bridge.mqtt.emqx2.mountpoint = bridge/emqx2/${node}/ +bridge.mqtt.emqx2.mountpoint = "bridge/emqx2/${node}/" ``` If the messages received by the local node emqx1 matches the topic `sersor1/#` or `sensor2/#`, these messages will be forwarded to the `sensor1/#` or `sensor2/#` topic of the remote node emqx2. @@ -82,66 +82,66 @@ EMQ X MQTT bridging principle: Create an MQTT client on the EMQ X broker, and co ``` ## Bridge Address: Use node name for rpc bridging, use host:port for mqtt connection -bridge.mqtt.emqx2.address = 192.168.1.2:1883 +bridge.mqtt.emqx2.address = "192.168.1.2:1883" ## Bridged Protocol Version ## Enumeration value: mqttv3 | mqttv4 | mqttv5 -bridge.mqtt.emqx2.proto_ver = mqttv4 +bridge.mqtt.emqx2.proto_ver = "mqttv4" ## mqtt client's clientid -bridge.mqtt.emqx2.clientid = bridge_emq +bridge.mqtt.emqx2.clientid = "bridge_emq" ## mqtt client's clean_start field ## Note: Some MQTT Brokers need to set the clean_start value as `true` bridge.mqtt.emqx2.clean_start = true ## mqtt client's username field -bridge.mqtt.emqx2.username = user +bridge.mqtt.emqx2.username = "user" ## mqtt client's password field -bridge.mqtt.emqx2.password = passwd +bridge.mqtt.emqx2.password = "passwd" ## Whether the mqtt client uses ssl to connect to a remote serve or not bridge.mqtt.emqx2.ssl = off ## CA Certificate of Client SSL Connection (PEM format) -bridge.mqtt.emqx2.cacertfile = etc/certs/cacert.pem +bridge.mqtt.emqx2.cacertfile = "etc/certs/cacert.pem" ## SSL certificate of Client SSL connection -bridge.mqtt.emqx2.certfile = etc/certs/client-cert.pem +bridge.mqtt.emqx2.certfile = "etc/certs/client-cert.pem" ## Key file of Client SSL connection -bridge.mqtt.emqx2.keyfile = etc/certs/client-key.pem +bridge.mqtt.emqx2.keyfile = "etc/certs/client-key.pem" ## SSL encryption -bridge.mqtt.emqx2.ciphers = ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384 +bridge.mqtt.emqx2.ciphers = "ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384" ## TTLS PSK password ## Note 'listener.ssl.external.ciphers' and 'listener.ssl.external.psk_ciphers' cannot be configured at the same time ## ## See 'https://tools.ietf.org/html/rfc4279#section-2'. -## bridge.mqtt.emqx2.psk_ciphers = PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA +## bridge.mqtt.emqx2.psk_ciphers = "PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA" ## Client's heartbeat interval bridge.mqtt.emqx2.keepalive = 60s ## Supported TLS version -bridge.mqtt.emqx2.tls_versions = tlsv1.3,tlsv1.2,tlsv1.1,tlsv1 +bridge.mqtt.emqx2.tls_versions = "tlsv1.3,tlsv1.2,tlsv1.1,tlsv1" ## Forwarding topics of the message -bridge.mqtt.emqx2.forwards = sensor1/#,sensor2/# +bridge.mqtt.emqx2.forwards = "sensor1/#,sensor2/#" ## Bridged mountpoint -bridge.mqtt.emqx2.mountpoint = bridge/emqx2/${node}/ +bridge.mqtt.emqx2.mountpoint = "bridge/emqx2/${node}/" ## Subscription topic for bridging -bridge.mqtt.emqx2.subscription.1.topic = cmd/topic1 +bridge.mqtt.emqx2.subscription.1.topic = "cmd/topic1" ## Subscription qos for bridging bridge.mqtt.emqx2.subscription.1.qos = 1 ## Subscription topic for bridging -bridge.mqtt.emqx2.subscription.2.topic = cmd/topic2 +bridge.mqtt.emqx2.subscription.2.topic = "cmd/topic2" ## Subscription qos for bridging bridge.mqtt.emqx2.subscription.2.qos = 1 diff --git a/apps/emqx_bridge_mqtt/docs/guide.rst b/apps/emqx_bridge_mqtt/docs/guide.rst index 47235aa7d..a1c2a9126 100644 --- a/apps/emqx_bridge_mqtt/docs/guide.rst +++ b/apps/emqx_bridge_mqtt/docs/guide.rst @@ -39,7 +39,7 @@ In EMQ X, bridge is configured by modifying ``etc/emqx.conf``. EMQ X distinguish .. code-block:: ## Bridge address: node name for local bridge, host:port for remote. - bridge.mqtt.aws.address = 127.0.0.1:1883 + bridge.mqtt.aws.address = "127.0.0.1:1883" This configuration declares a bridge named ``aws`` and specifies that it is bridged to the MQTT broker of 127.0.0.1:1883 by MQTT mode. @@ -69,13 +69,13 @@ The following is the basic configuration of RPC bridging. A simplest RPC bridgin .. code-block:: ## Bridge Address: Use node name (nodename@host) for rpc bridging, and host:port for mqtt connection - bridge.mqtt.emqx2.address = emqx2@192.168.1.2 + bridge.mqtt.emqx2.address = "emqx2@192.168.1.2" ## Forwarding topics of the message - bridge.mqtt.emqx2.forwards = sensor1/#,sensor2/# + bridge.mqtt.emqx2.forwards = "sensor1/#,sensor2/#" ## bridged mountpoint - bridge.mqtt.emqx2.mountpoint = bridge/emqx2/${node}/ + bridge.mqtt.emqx2.mountpoint = "bridge/emqx2/${node}/" If the messages received by the local node emqx1 matches the topic ``sersor1/#`` or ``sensor2/#``\ , these messages will be forwarded to the ``sensor1/#`` or ``sensor2/#`` topic of the remote node emqx2. @@ -86,10 +86,10 @@ If the messages received by the local node emqx1 matches the topic ``sersor1/#`` Limitations of RPC bridging: -#. +#. The RPC bridge of emqx can only forward local messages to the remote node, and cannot synchronize the messages of the remote node to the local node; -#. +#. RPC bridge can only bridge two EMQ X broker together and cannot bridge EMQ X broker to other MQTT brokers. EMQ X MQTT Bridge Configuration @@ -102,66 +102,66 @@ EMQ X MQTT bridging principle: Create an MQTT client on the EMQ X broker, and co .. code-block:: ## Bridge Address: Use node name for rpc bridging, use host:port for mqtt connection - bridge.mqtt.emqx2.address = 192.168.1.2:1883 + bridge.mqtt.emqx2.address = "192.168.1.2:1883" ## Bridged Protocol Version ## Enumeration value: mqttv3 | mqttv4 | mqttv5 - bridge.mqtt.emqx2.proto_ver = mqttv4 + bridge.mqtt.emqx2.proto_ver = "mqttv4" ## mqtt client's clientid - bridge.mqtt.emqx2.clientid = bridge_emq + bridge.mqtt.emqx2.clientid = "bridge_emq" ## mqtt client's clean_start field ## Note: Some MQTT Brokers need to set the clean_start value as `true` bridge.mqtt.emqx2.clean_start = true ## mqtt client's username field - bridge.mqtt.emqx2.username = user + bridge.mqtt.emqx2.username = "user" ## mqtt client's password field - bridge.mqtt.emqx2.password = passwd + bridge.mqtt.emqx2.password = "passwd" ## Whether the mqtt client uses ssl to connect to a remote serve or not bridge.mqtt.emqx2.ssl = off ## CA Certificate of Client SSL Connection (PEM format) - bridge.mqtt.emqx2.cacertfile = etc/certs/cacert.pem + bridge.mqtt.emqx2.cacertfile = "etc/certs/cacert.pem" ## SSL certificate of Client SSL connection - bridge.mqtt.emqx2.certfile = etc/certs/client-cert.pem + bridge.mqtt.emqx2.certfile = "etc/certs/client-cert.pem" ## Key file of Client SSL connection - bridge.mqtt.emqx2.keyfile = etc/certs/client-key.pem + bridge.mqtt.emqx2.keyfile = "etc/certs/client-key.pem" ## TTLS PSK password ## Note 'listener.ssl.external.ciphers' and 'listener.ssl.external.psk_ciphers' cannot be configured at the same time ## ## See 'https://tools.ietf.org/html/rfc4279#section-2'. - ## bridge.mqtt.emqx2.psk_ciphers = PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA + ## bridge.mqtt.emqx2.psk_ciphers = "PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA" ## Client's heartbeat interval bridge.mqtt.emqx2.keepalive = 60s ## Supported TLS version - bridge.mqtt.emqx2.tls_versions = tlsv1.2 + bridge.mqtt.emqx2.tls_versions = "tlsv1.2" ## SSL encryption - bridge.mqtt.emqx2.ciphers = ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384 + bridge.mqtt.emqx2.ciphers = "ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384" ## Forwarding topics of the message - bridge.mqtt.emqx2.forwards = sensor1/#,sensor2/# + bridge.mqtt.emqx2.forwards = "sensor1/#,sensor2/#" ## Bridged mountpoint - bridge.mqtt.emqx2.mountpoint = bridge/emqx2/${node}/ + bridge.mqtt.emqx2.mountpoint = "bridge/emqx2/${node}/" ## Subscription topic for bridging - bridge.mqtt.emqx2.subscription.1.topic = cmd/topic1 + bridge.mqtt.emqx2.subscription.1.topic = "cmd/topic1" ## Subscription qos for bridging bridge.mqtt.emqx2.subscription.1.qos = 1 ## Subscription topic for bridging - bridge.mqtt.emqx2.subscription.2.topic = cmd/topic2 + bridge.mqtt.emqx2.subscription.2.topic = "cmd/topic2" ## Subscription qos for bridging bridge.mqtt.emqx2.subscription.2.qos = 1 @@ -190,7 +190,7 @@ The bridge of EMQ X has a message caching mechanism. The caching mechanism is ap bridge.mqtt.emqx2.queue.batch_bytes_limit = 1000MB ## The path for placing replayq queue. If it is not specified, then replayq will run in `mem-only` mode and messages will not be cached on disk. - bridge.mqtt.emqx2.queue.replayq_dir = data/emqx_emqx2_bridge/ + bridge.mqtt.emqx2.queue.replayq_dir = "data/emqx_emqx2_bridge/" ## Replayq data segment size bridge.mqtt.emqx2.queue.replayq_seg_bytes = 10MB diff --git a/apps/emqx_bridge_mqtt/etc/emqx_bridge_mqtt.conf b/apps/emqx_bridge_mqtt/etc/emqx_bridge_mqtt.conf index db59270c0..3cc719e40 100644 --- a/apps/emqx_bridge_mqtt/etc/emqx_bridge_mqtt.conf +++ b/apps/emqx_bridge_mqtt/etc/emqx_bridge_mqtt.conf @@ -9,8 +9,8 @@ ## Bridge address: node name for local bridge, host:port for remote. ## ## Value: String -## Example: emqx@127.0.0.1, 127.0.0.1:1883 -bridge.mqtt.aws.address = 127.0.0.1:1883 +## Example: emqx@127.0.0.1, "127.0.0.1:1883" +bridge.mqtt.aws.address = "127.0.0.1:1883" ## Protocol version of the bridge. ## @@ -65,18 +65,18 @@ bridge.mqtt.aws.password = passwd ## Topics that need to be forward to AWS IoTHUB ## ## Value: String -## Example: topic1/#,topic2/# -bridge.mqtt.aws.forwards = topic1/#,topic2/# +## Example: "topic1/#,topic2/#" +bridge.mqtt.aws.forwards = "topic1/#,topic2/#" ## Forward messages to the mountpoint of an AWS IoTHUB ## ## Value: String -bridge.mqtt.aws.forward_mountpoint = bridge/aws/${node}/ +bridge.mqtt.aws.forward_mountpoint = "bridge/aws/${node}/" ## Need to subscribe to AWS topics ## ## Value: String -## bridge.mqtt.aws.subscription.1.topic = cmd/topic1 +## bridge.mqtt.aws.subscription.1.topic = "cmd/topic1" ## Need to subscribe to AWS topics QoS. ## @@ -86,7 +86,7 @@ bridge.mqtt.aws.forward_mountpoint = bridge/aws/${node}/ ## A mountpoint that receives messages from AWS IoTHUB ## ## Value: String -## bridge.mqtt.aws.receive_mountpoint = receive/aws/ +## bridge.mqtt.aws.receive_mountpoint = "receive/aws/" ## Bribge to remote server via SSL. @@ -97,28 +97,28 @@ bridge.mqtt.aws.ssl = off ## PEM-encoded CA certificates of the bridge. ## ## Value: File -bridge.mqtt.aws.cacertfile = {{ platform_etc_dir }}/certs/cacert.pem +bridge.mqtt.aws.cacertfile = "{{ platform_etc_dir }}/certs/cacert.pem" ## Client SSL Certfile of the bridge. ## ## Value: File -bridge.mqtt.aws.certfile = {{ platform_etc_dir }}/certs/client-cert.pem +bridge.mqtt.aws.certfile = "{{ platform_etc_dir }}/certs/client-cert.pem" ## Client SSL Keyfile of the bridge. ## ## Value: File -bridge.mqtt.aws.keyfile = {{ platform_etc_dir }}/certs/client-key.pem +bridge.mqtt.aws.keyfile = "{{ platform_etc_dir }}/certs/client-key.pem" ## SSL Ciphers used by the bridge. ## ## Value: String -bridge.mqtt.aws.ciphers = TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA +bridge.mqtt.aws.ciphers = "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA" ## Ciphers for TLS PSK. ## Note that 'bridge.${BridgeName}.ciphers' and 'bridge.${BridgeName}.psk_ciphers' cannot ## be configured at the same time. ## See 'https://tools.ietf.org/html/rfc4279#section-2'. -#bridge.mqtt.aws.psk_ciphers = PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA +#bridge.mqtt.aws.psk_ciphers = "PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA" ## Ping interval of a down bridge. ## @@ -130,7 +130,7 @@ bridge.mqtt.aws.keepalive = 60s ## ## NOTE: Do not use tlsv1.3 if emqx is running on OTP-22 or earlier ## Value: String -bridge.mqtt.aws.tls_versions = tlsv1.3,tlsv1.2,tlsv1.1,tlsv1 +bridge.mqtt.aws.tls_versions = "tlsv1.3,tlsv1.2,tlsv1.1,tlsv1" ## Bridge reconnect time. ## @@ -160,7 +160,7 @@ bridge.mqtt.aws.max_inflight_size = 32 ## replayq works in a mem-only manner. ## ## Value: String -bridge.mqtt.aws.queue.replayq_dir = {{ platform_data_dir }}/replayq/emqx_aws_bridge/ +bridge.mqtt.aws.queue.replayq_dir = "{{ platform_data_dir }}/replayq/emqx_aws_bridge/" ## Replayq segment size ## diff --git a/apps/emqx_coap/etc/emqx_coap.conf b/apps/emqx_coap/etc/emqx_coap.conf index 0590a348e..d6dfa1a6f 100644 --- a/apps/emqx_coap/etc/emqx_coap.conf +++ b/apps/emqx_coap/etc/emqx_coap.conf @@ -4,13 +4,13 @@ ## The IP and UDP port that CoAP bind with. ## -## Default: 0.0.0.0:5683 +## Default: "0.0.0.0:5683" ## ## Examples: -## coap.bind.udp.x = 0.0.0.0:5683 | :::5683 | 127.0.0.1:5683 | ::1:5683 +## coap.bind.udp.x = "0.0.0.0:5683" | ":::5683" | "127.0.0.1:5683" | "::1:5683" ## -coap.bind.udp.1 = 0.0.0.0:5683 -##coap.bind.udp.2 = 0.0.0.0:6683 +coap.bind.udp.1 = "0.0.0.0:5683" +##coap.bind.udp.2 = "0.0.0.0:6683" ## Whether to enable statistics for CoAP clients. ## @@ -23,13 +23,13 @@ coap.enable_stats = off ## The DTLS port that CoAP is listening on. ## -## Default: 0.0.0.0:5684 +## Default: "0.0.0.0:5684" ## ## Examples: -## coap.bind.dtls.x = 0.0.0.0:5684 | :::5684 | 127.0.0.1:5684 | ::1:5684 +## coap.bind.dtls.x = "0.0.0.0:5684" | ":::5684" | "127.0.0.1:5684" | "::1:5684" ## -coap.bind.dtls.1 = 0.0.0.0:5684 -##coap.bind.dtls.2 = 0.0.0.0:6684 +coap.bind.dtls.1 = "0.0.0.0:5684" +##coap.bind.dtls.2 = "0.0.0.0:6684" ## A server only does x509-path validation in mode verify_peer, ## as it then sends a certificate request to the client (this @@ -43,17 +43,17 @@ coap.bind.dtls.1 = 0.0.0.0:5684 ## Private key file for DTLS ## ## Value: File -coap.dtls.keyfile = {{ platform_etc_dir }}/certs/key.pem +coap.dtls.keyfile = "{{ platform_etc_dir }}/certs/key.pem" ## Server certificate for DTLS. ## ## Value: File -coap.dtls.certfile = {{ platform_etc_dir }}/certs/cert.pem +coap.dtls.certfile = "{{ platform_etc_dir }}/certs/cert.pem" ## PEM-encoded CA certificates for DTLS ## ## Value: File -## coap.dtls.cacertfile = {{ platform_etc_dir }}/certs/cacert.pem +## coap.dtls.cacertfile = "{{ platform_etc_dir }}/certs/cacert.pem" ## Used together with {verify, verify_peer} by an SSL server. If set to true, ## the server fails if the client does not have a certificate to send, that is, @@ -79,4 +79,4 @@ coap.dtls.certfile = {{ platform_etc_dir }}/certs/cert.pem ## Most of it was copied from Mozilla’s Server Side TLS article ## ## Value: Ciphers -coap.dtls.ciphers = ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA +coap.dtls.ciphers = "ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA" diff --git a/apps/emqx_exhook/etc/emqx_exhook.conf b/apps/emqx_exhook/etc/emqx_exhook.conf index f6f5213f7..b2758e705 100644 --- a/apps/emqx_exhook/etc/emqx_exhook.conf +++ b/apps/emqx_exhook/etc/emqx_exhook.conf @@ -8,8 +8,8 @@ ## The gRPC server url ## ## exhook.server.$name.url = url() -exhook.server.default.url = http://127.0.0.1:9000 +exhook.server.default.url = "http://127.0.0.1:9000" -#exhook.server.default.ssl.cacertfile = {{ platform_etc_dir }}/certs/cacert.pem -#exhook.server.default.ssl.certfile = {{ platform_etc_dir }}/certs/cert.pem -#exhook.server.default.ssl.keyfile = {{ platform_etc_dir }}/certs/key.pem +#exhook.server.default.ssl.cacertfile = "{{ platform_etc_dir }}/certs/cacert.pem" +#exhook.server.default.ssl.certfile = "{{ platform_etc_dir }}/certs/cert.pem" +#exhook.server.default.ssl.keyfile = "{{ platform_etc_dir }}/certs/key.pem" diff --git a/apps/emqx_exhook/rebar.config b/apps/emqx_exhook/rebar.config index 883aad9bd..eafa20d85 100644 --- a/apps/emqx_exhook/rebar.config +++ b/apps/emqx_exhook/rebar.config @@ -43,7 +43,6 @@ {profiles, [{test, [{deps, - [{emqx_ct_helper, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "v1.3.1"}}} - ]} + []} ]} ]}. diff --git a/apps/emqx_exproto/etc/emqx_exproto.conf b/apps/emqx_exproto/etc/emqx_exproto.conf index 713685734..7a7667271 100644 --- a/apps/emqx_exproto/etc/emqx_exproto.conf +++ b/apps/emqx_exproto/etc/emqx_exproto.conf @@ -5,9 +5,9 @@ exproto.server.http.port = 9100 exproto.server.https.port = 9101 -exproto.server.https.cacertfile = {{ platform_etc_dir }}/certs/cacert.pem -exproto.server.https.certfile = {{ platform_etc_dir }}/certs/cert.pem -exproto.server.https.keyfile = {{ platform_etc_dir }}/certs/key.pem +exproto.server.https.cacertfile = "{{ platform_etc_dir }}/certs/cacert.pem" +exproto.server.https.certfile = "{{ platform_etc_dir }}/certs/cert.pem" +exproto.server.https.keyfile = "{{ platform_etc_dir }}/certs/key.pem" ##-------------------------------------------------------------------- ## Listeners @@ -20,12 +20,12 @@ exproto.server.https.keyfile = {{ platform_etc_dir }}/certs/key.pem ## ## Value: ://: ## -## Examples: tcp://0.0.0.0:7993 | ssl://127.0.0.1:7994 -exproto.listener.protoname = tcp://0.0.0.0:7993 +## Examples: "tcp://0.0.0.0:7993" | "ssl://127.0.0.1:7994" +exproto.listener.protoname.endpoint = "tcp://0.0.0.0:7993" ## The ConnectionHandler server address ## -exproto.listener.protoname.connection_handler_url = http://127.0.0.1:9001 +exproto.listener.protoname.connection_handler_url = "http://127.0.0.1:9001" #exproto.listener.protoname.connection_handler_certfile = #exproto.listener.protoname.connection_handler_cacertfile = @@ -62,8 +62,8 @@ exproto.listener.protoname.idle_timeout = 30s ## ## Value: ACL Rule ## -## Example: allow 192.168.0.0/24 -exproto.listener.protoname.access.1 = allow all +## Example: "allow 192.168.0.0/24" +exproto.listener.protoname.access.1 = "allow all" ## Enable the Proxy Protocol V1/2 if the EMQ X cluster is deployed ## behind HAProxy or Nginx. @@ -146,27 +146,27 @@ exproto.listener.protoname.reuseaddr = true ## See: http://erlang.org/doc/man/ssl.html ## ## Value: String, seperated by ',' -#exproto.listener.protoname.tls_versions = tlsv1.2,tlsv1.1,tlsv1 +#exproto.listener.protoname.tls_versions = "tlsv1.2,tlsv1.1,tlsv1" ## Path to the file containing the user's private PEM-encoded key. ## ## See: http://erlang.org/doc/man/ssl.html ## ## Value: File -#exproto.listener.protoname.keyfile = {{ platform_etc_dir }}/certs/key.pem +#exproto.listener.protoname.keyfile = "{{ platform_etc_dir }}/certs/key.pem" ## Path to a file containing the user certificate. ## ## See: http://erlang.org/doc/man/ssl.html ## ## Value: File -#exproto.listener.protoname.certfile = {{ platform_etc_dir }}/certs/cert.pem +#exproto.listener.protoname.certfile = "{{ platform_etc_dir }}/certs/cert.pem" ## Path to the file containing PEM-encoded CA certificates. The CA certificates ## are used during server authentication and when building the client certificate chain. ## ## Value: File -#exproto.listener.protoname.cacertfile = {{ platform_etc_dir }}/certs/cacert.pem +#exproto.listener.protoname.cacertfile = "{{ platform_etc_dir }}/certs/cacert.pem" ## The Ephemeral Diffie-Helman key exchange is a very effective way of ## ensuring Forward Secrecy by exchanging a set of keys that never hit @@ -183,7 +183,7 @@ exproto.listener.protoname.reuseaddr = true ## openssl dhparam -out dh-params.pem 2048 ## ## Value: File -#exproto.listener.protoname.dhfile = {{ platform_etc_dir }}/certs/dh-params.pem +#exproto.listener.protoname.dhfile = "{{ platform_etc_dir }}/certs/dh-params.pem" ## A server only does x509-path validation in mode verify_peer, ## as it then sends a certificate request to the client (this @@ -218,13 +218,13 @@ exproto.listener.protoname.reuseaddr = true ## Most of it was copied from Mozilla’s Server Side TLS article ## ## Value: Ciphers -#exproto.listener.protoname.ciphers = TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA +#exproto.listener.protoname.ciphers = "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA" ## Ciphers for TLS PSK. ## Note that 'listener.ssl.external.ciphers' and 'listener.ssl.external.psk_ciphers' cannot ## be configured at the same time. ## See 'https://tools.ietf.org/html/rfc4279#section-2'. -#exproto.listener.protoname.psk_ciphers = PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA +#exproto.listener.protoname.psk_ciphers = "PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA" ## SSL parameter renegotiation is a feature that allows a client and a server ## to renegotiate the parameters of the SSL connection on the fly. diff --git a/apps/emqx_exproto/priv/emqx_exproto.schema b/apps/emqx_exproto/priv/emqx_exproto.schema index fb114dc77..4bd215847 100644 --- a/apps/emqx_exproto/priv/emqx_exproto.schema +++ b/apps/emqx_exproto/priv/emqx_exproto.schema @@ -44,7 +44,7 @@ end}. %%-------------------------------------------------------------------- %% Listeners -{mapping, "exproto.listener.$proto", "emqx_exproto.listeners", [ +{mapping, "exproto.listener.$proto.endpoint", "emqx_exproto.listeners", [ {datatype, string} ]}. @@ -340,7 +340,7 @@ end}. Listeners = fun(Proto) -> Prefix = string:join(["exproto","listener", Proto], "."), Opts = HandlerOpts(Prefix) ++ ConnOpts(Prefix) ++ LisOpts(Prefix), - case cuttlefish:conf_get(Prefix, Conf, undefined) of + case cuttlefish:conf_get(Prefix ++ ".endpoint", Conf, undefined) of undefined -> []; ListenOn0 -> case ParseListenOn(ListenOn0) of @@ -359,6 +359,6 @@ end}. end end end, - lists:flatten([Listeners(Proto) || {[_, "listener", Proto], ListenOn} + lists:flatten([Listeners(Proto) || {[_, "listener", Proto, "endpoint"], ListenOn} <- cuttlefish_variable:filter_by_prefix("exproto.listener", Conf)]) end}. diff --git a/apps/emqx_exproto/rebar.config b/apps/emqx_exproto/rebar.config index 61677f0e3..3fa9f6f8a 100644 --- a/apps/emqx_exproto/rebar.config +++ b/apps/emqx_exproto/rebar.config @@ -46,7 +46,6 @@ {profiles, [{test, [{deps, - [{emqx_ct_helper, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "v1.3.0"}}} - ]} + []} ]} ]}. diff --git a/apps/emqx_lwm2m/etc/emqx_lwm2m.conf b/apps/emqx_lwm2m/etc/emqx_lwm2m.conf index 968b8fd19..b244fa385 100644 --- a/apps/emqx_lwm2m/etc/emqx_lwm2m.conf +++ b/apps/emqx_lwm2m/etc/emqx_lwm2m.conf @@ -25,25 +25,25 @@ lwm2m.lifetime_max = 86400s # Placeholders supported: # '%e': Endpoint Name # '%a': IP Address -lwm2m.mountpoint = lwm2m/%e/ +lwm2m.mountpoint = "lwm2m/%e/" # The topic subscribed by the lwm2m client after it is connected # Placeholders supported: # '%e': Endpoint Name # '%a': IP Address -lwm2m.topics.command = dn/# +lwm2m.topics.command = "dn/#" # The topic to which the lwm2m client's response is published -lwm2m.topics.response = up/resp +lwm2m.topics.response = "up/resp" # The topic to which the lwm2m client's notify message is published -lwm2m.topics.notify = up/notify +lwm2m.topics.notify = "up/notify" # The topic to which the lwm2m client's register message is published -lwm2m.topics.register = up/resp +lwm2m.topics.register = "up/resp" # The topic to which the lwm2m client's update message is published -lwm2m.topics.update = up/resp +lwm2m.topics.update = "up/resp" # When publish the update message. # @@ -55,18 +55,18 @@ lwm2m.topics.update = up/resp #lwm2m.update_msg_publish_condition = contains_object_list # Dir where the object definition files can be found -lwm2m.xml_dir = {{ platform_etc_dir }}/lwm2m_xml +lwm2m.xml_dir = "{{ platform_etc_dir }}/lwm2m_xml" ##-------------------------------------------------------------------- ## UDP Listener options ## The IP and port of the LwM2M Gateway ## -## Default: 0.0.0.0:5683 +## Default: "0.0.0.0:5683" ## Examples: -## lwm2m.bind.udp.x = 0.0.0.0:5683 | :::5683 | 127.0.0.1:5683 | ::1:5683 -lwm2m.bind.udp.1 = 0.0.0.0:5683 -#lwm2m.bind.udp.2 = 0.0.0.0:6683 +## lwm2m.bind.udp.x = "0.0.0.0:5683" | ":::5683" | "127.0.0.1:5683" | "::1:5683" +lwm2m.bind.udp.1 = "0.0.0.0:5683" +#lwm2m.bind.udp.2 = "0.0.0.0:6683" ## Socket options, used for performance tuning ## @@ -83,13 +83,13 @@ lwm2m.opts.read_packets = 20 ## The DTLS port that LwM2M is listening on. ## -## Default: 0.0.0.0:5684 +## Default: "0.0.0.0:5684" ## ## Examples: -## lwm2m.bind.dtls.x = 0.0.0.0:5684 | :::5684 | 127.0.0.1:5684 | ::1:5684 +## lwm2m.bind.dtls.x = "0.0.0.0:5684" | ":::5684" | "127.0.0.1:5684" | "::1:5684" ## -lwm2m.bind.dtls.1 = 0.0.0.0:5684 -#lwm2m.bind.dtls.2 = 0.0.0.0:6684 +lwm2m.bind.dtls.1 = "0.0.0.0:5684" +#lwm2m.bind.dtls.2 = "0.0.0.0:6684" ## A server only does x509-path validation in mode verify_peer, ## as it then sends a certificate request to the client (this @@ -103,17 +103,17 @@ lwm2m.bind.dtls.1 = 0.0.0.0:5684 ## Private key file for DTLS ## ## Value: File -lwm2m.dtls.keyfile = {{ platform_etc_dir }}/certs/key.pem +lwm2m.dtls.keyfile = "{{ platform_etc_dir }}/certs/key.pem" ## Server certificate for DTLS. ## ## Value: File -lwm2m.dtls.certfile = {{ platform_etc_dir }}/certs/cert.pem +lwm2m.dtls.certfile = "{{ platform_etc_dir }}/certs/cert.pem" ## PEM-encoded CA certificates for DTLS ## ## Value: File -#lwm2m.dtls.cacertfile = {{ platform_etc_dir }}/certs/cacert.pem +#lwm2m.dtls.cacertfile = "{{ platform_etc_dir }}/certs/cacert.pem" ## Used together with {verify, verify_peer} by an SSL server. If set to true, ## the server fails if the client does not have a certificate to send, that is, @@ -139,11 +139,11 @@ lwm2m.dtls.certfile = {{ platform_etc_dir }}/certs/cert.pem ## Most of it was copied from Mozilla’s Server Side TLS article ## ## Value: Ciphers -lwm2m.dtls.ciphers = ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA +lwm2m.dtls.ciphers = "ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA" ## Ciphers for TLS PSK. ## ## Note that 'lwm2m.dtls.ciphers' and 'lwm2m.dtls.psk_ciphers' cannot ## be configured at the same time. ## See 'https://tools.ietf.org/html/rfc4279#section-2'. -#lwm2m.dtls.psk_ciphers = PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA +#lwm2m.dtls.psk_ciphers = "PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA" diff --git a/apps/emqx_lwm2m/rebar.config b/apps/emqx_lwm2m/rebar.config index b9dea9bb8..fbfcdc02f 100644 --- a/apps/emqx_lwm2m/rebar.config +++ b/apps/emqx_lwm2m/rebar.config @@ -5,7 +5,6 @@ {profiles, [{test, [{deps, [{er_coap_client, {git, "https://github.com/emqx/er_coap_client", {tag, "v1.0"}}}, - {emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "1.2.2"}}}, {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.2.0"}}} ]} ]} diff --git a/apps/emqx_management/etc/emqx_management.conf b/apps/emqx_management/etc/emqx_management.conf index aa737add9..05a3008fa 100644 --- a/apps/emqx_management/etc/emqx_management.conf +++ b/apps/emqx_management/etc/emqx_management.conf @@ -23,7 +23,7 @@ management.default_application.secret = public ##-------------------------------------------------------------------- ## HTTP Listener -management.listener.http = 8081 +management.listener.http.port = 8081 management.listener.http.acceptors = 2 management.listener.http.max_clients = 512 management.listener.http.backlog = 512 @@ -35,19 +35,19 @@ management.listener.http.ipv6_v6only = false ##-------------------------------------------------------------------- ## HTTPS Listener -## management.listener.https = 8081 +## management.listener.https.port = 8081 ## management.listener.https.acceptors = 2 ## management.listener.https.max_clients = 512 ## management.listener.https.backlog = 512 ## management.listener.https.send_timeout = 15s ## management.listener.https.send_timeout_close = on -## management.listener.https.certfile = etc/certs/cert.pem -## management.listener.https.keyfile = etc/certs/key.pem -## management.listener.https.cacertfile = etc/certs/cacert.pem +## management.listener.https.certfile = "etc/certs/cert.pem" +## management.listener.https.keyfile = "etc/certs/key.pem" +## management.listener.https.cacertfile = "etc/certs/cacert.pem" ## management.listener.https.verify = verify_peer ## NOTE: Do not use tlsv1.3 if emqx is running on OTP-22 or earlier -## management.listener.https.tls_versions = tlsv1.3,tlsv1.2,tlsv1.1,tlsv1 -## management.listener.https.ciphers = TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA +## management.listener.https.tls_versions = "tlsv1.3,tlsv1.2,tlsv1.1,tlsv1" +## management.listener.https.ciphers = "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA" ## management.listener.https.fail_if_no_peer_cert = true ## management.listener.https.inet6 = false ## management.listener.https.ipv6_v6only = false diff --git a/apps/emqx_management/priv/emqx_management.schema b/apps/emqx_management/priv/emqx_management.schema index 343a70de6..4e887809e 100644 --- a/apps/emqx_management/priv/emqx_management.schema +++ b/apps/emqx_management/priv/emqx_management.schema @@ -21,7 +21,7 @@ {datatype, string} ]}. -{mapping, "management.listener.http", "emqx_management.listeners", [ +{mapping, "management.listener.http.port", "emqx_management.listeners", [ {datatype, [integer, ip]} ]}. @@ -85,7 +85,7 @@ {datatype, {enum, [true, false]}} ]}. -{mapping, "management.listener.https", "emqx_management.listeners", [ +{mapping, "management.listener.https.port", "emqx_management.listeners", [ {datatype, [integer, ip]} ]}. @@ -225,7 +225,7 @@ end}. lists:foldl( fun(Proto, Acc) -> Prefix = "management.listener." ++ atom_to_list(Proto), - case cuttlefish:conf_get(Prefix, Conf, undefined) of + case cuttlefish:conf_get(Prefix ++ ".port", Conf, undefined) of undefined -> Acc; Port -> [{Proto, Port, TcpOpts(Prefix) ++ Opts(Prefix) diff --git a/apps/emqx_management/test/emqx_bridge_mqtt_data_export_import_SUITE.erl b/apps/emqx_management/test/emqx_bridge_mqtt_data_export_import_SUITE.erl index bae633045..5667cb73f 100644 --- a/apps/emqx_management/test/emqx_bridge_mqtt_data_export_import_SUITE.erl +++ b/apps/emqx_management/test/emqx_bridge_mqtt_data_export_import_SUITE.erl @@ -181,4 +181,4 @@ remove_resources() -> lists:foreach(fun(#resource{id = Id}) -> emqx_rule_engine:delete_resource(Id) end, emqx_rule_registry:get_resources()), - timer:sleep(500). \ No newline at end of file + timer:sleep(500). diff --git a/apps/emqx_prometheus/etc/emqx_prometheus.conf b/apps/emqx_prometheus/etc/emqx_prometheus.conf index 7bfa22095..92e0d850d 100644 --- a/apps/emqx_prometheus/etc/emqx_prometheus.conf +++ b/apps/emqx_prometheus/etc/emqx_prometheus.conf @@ -5,7 +5,7 @@ ## The Prometheus Push Gateway URL address ## ## Note: You can comment out this line to disable it -prometheus.push.gateway.server = http://127.0.0.1:9091 +prometheus.push.gateway.server = "http://127.0.0.1:9091" ## The metrics data push interval (millisecond) ## diff --git a/apps/emqx_psk_file/etc/emqx_psk_file.conf b/apps/emqx_psk_file/etc/emqx_psk_file.conf index 3cee1c926..88c5bbdb1 100644 --- a/apps/emqx_psk_file/etc/emqx_psk_file.conf +++ b/apps/emqx_psk_file/etc/emqx_psk_file.conf @@ -1,2 +1,2 @@ -psk.file.path = {{ platform_etc_dir }}/psk.txt -psk.file.delimiter = : \ No newline at end of file +psk.file.path = "{{ platform_etc_dir }}/psk.txt" +psk.file.delimiter = ":" diff --git a/apps/emqx_resource/Makefile b/apps/emqx_resource/Makefile new file mode 100644 index 000000000..596b9b2a1 --- /dev/null +++ b/apps/emqx_resource/Makefile @@ -0,0 +1,43 @@ +REBAR := rebar3 + +.PHONY: all +all: es + +.PHONY: compile +compile: + $(REBAR) compile + +.PHONY: clean +clean: distclean + +.PHONY: distclean +distclean: + @rm -rf _build erl_crash.dump rebar3.crashdump + +.PHONY: xref +xref: + $(REBAR) xref + +.PHONY: eunit +eunit: compile + $(REBAR) eunit -v -c + $(REBAR) cover + +.PHONY: ct +ct: compile + $(REBAR) as test ct -v + +cover: + $(REBAR) cover + +.PHONY: dialyzer +dialyzer: + $(REBAR) dialyzer + +.PHONY: es +es: compile + $(REBAR) escriptize + +.PHONY: elvis +elvis: + ./scripts/elvis-check.sh diff --git a/apps/emqx_resource/README.md b/apps/emqx_resource/README.md new file mode 100644 index 000000000..9e91b633c --- /dev/null +++ b/apps/emqx_resource/README.md @@ -0,0 +1,53 @@ +# emqx_resource + +The `emqx_resource` is an application that manages configuration specs and runtime states +for components that need to be configured and manipulated from the emqx-dashboard. + +It is intended to be used by resources, actions, acl, auth, backend_logics and more. + +It reads the configuration spec from *.spec (in HOCON format) and provide APIs for +creating, updating and destroying resource instances among all nodes in the cluster. + +It handles the problem like storing the configs and runtime states for both resource +and resource instances, and how porting them between different emqx_resource versions. + +It may maintain the config and data in JSON or HOCON files in data/ dir. + +After restarting the emqx_resource, it re-creates all the resource instances. + +There can be foreign references between resource instances via resource-id. +So they may find each other via this Id. + +## Try it out + + $ ./demo.sh + Eshell V11.1.8 (abort with ^G) + 1> == the demo log tracer <<"log_tracer_clientid_shawn">> started. + config: #{<<"config">> => + #{<<"bulk">> => <<"10KB">>,<<"cache_log_dir">> => <<"/tmp">>, + <<"condition">> => #{<<"clientid">> => <<"abc">>}, + <<"level">> => <<"debug">>}, + <<"id">> => <<"log_tracer_clientid_shawn">>, + <<"resource_type">> => <<"log_tracer">>} + 1> emqx_resource_instance:health_check(<<"log_tracer_clientid_shawn">>). + == the demo log tracer <<"log_tracer_clientid_shawn">> is working well + state: #{health_checked => 1,logger_handler_id => abc} + ok + + 2> emqx_resource_instance:health_check(<<"log_tracer_clientid_shawn">>). + == the demo log tracer <<"log_tracer_clientid_shawn">> is working well + state: #{health_checked => 2,logger_handler_id => abc} + ok + + 3> emqx_resource_instance:query(<<"log_tracer_clientid_shawn">>, get_log). + == the demo log tracer <<"log_tracer_clientid_shawn">> received request: get_log + state: #{health_checked => 2,logger_handler_id => abc} + "this is a demo log messages..." + + 4> emqx_resource_instance:remove(<<"log_tracer_clientid_shawn">>). + == the demo log tracer <<"log_tracer_clientid_shawn">> stopped. + state: #{health_checked => 0,logger_handler_id => abc} + ok + + 5> emqx_resource_instance:query(<<"log_tracer_clientid_shawn">>, get_log). + ** exception error: {get_instance,{<<"log_tracer_clientid_shawn">>,not_found}} diff --git a/apps/emqx_resource/demo.sh b/apps/emqx_resource/demo.sh new file mode 100755 index 000000000..19cbab809 --- /dev/null +++ b/apps/emqx_resource/demo.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -e + +rebar3 compile + +erl -sname abc -pa _build/default/lib/*/ebin _build/default/lib/emqx_resource/examples -s demo diff --git a/apps/emqx_resource/elvis.config b/apps/emqx_resource/elvis.config new file mode 100644 index 000000000..59aa13fbe --- /dev/null +++ b/apps/emqx_resource/elvis.config @@ -0,0 +1,14 @@ +[{elvis, [{config, [ + +#{dirs => ["src"], + filter => "*.erl", + %ignore => [], + ruleset => erl_files, + rules => [{elvis_style, operator_spaces, #{ + rules => [{right, ","}, + {right, "|"}, + {left, "|"}, + {right, "||"}, + {left, "||"}]}}, + {elvis_style, god_modules, #{limit => 100}}]} +]}]}]. diff --git a/apps/emqx_resource/etc/emqx_resource.conf b/apps/emqx_resource/etc/emqx_resource.conf new file mode 100644 index 000000000..1f038ef8b --- /dev/null +++ b/apps/emqx_resource/etc/emqx_resource.conf @@ -0,0 +1,3 @@ +##-------------------------------------------------------------------- +## EMQ X Resource Plugin +##-------------------------------------------------------------------- diff --git a/apps/emqx_resource/examples/demo.erl b/apps/emqx_resource/examples/demo.erl new file mode 100644 index 000000000..171a80b61 --- /dev/null +++ b/apps/emqx_resource/examples/demo.erl @@ -0,0 +1,13 @@ +-module(demo). + +-export([start/0]). + +start() -> + code:load_file(log_tracer), + code:load_file(log_tracer_schema), + {ok, _} = application:ensure_all_started(minirest), + {ok, _} = application:ensure_all_started(emqx_resource), + emqx_resource:load_instances("./_build/default/lib/emqx_resource/examples"), + Handlers = [{"/", minirest:handler(#{modules => [log_tracer]})}], + Dispatch = [{"/[...]", minirest, Handlers}], + minirest:start_http(?MODULE, #{socket_opts => [inet, {port, 9900}]}, Dispatch). diff --git a/apps/emqx_resource/examples/demo.md b/apps/emqx_resource/examples/demo.md new file mode 100644 index 000000000..d7ff7f059 --- /dev/null +++ b/apps/emqx_resource/examples/demo.md @@ -0,0 +1,147 @@ +--- +theme: gaia +color: #000 +colorSecondary: #333 +backgroundColor: #fff +backgroundImage: url('https://marp.app/assets/hero-background.jpg') +paginate: true +marp: true +--- + + + +# EMQX Resource + +--- + +## What is it for + +The [emqx_resource](https://github.com/terry-xiaoyu/emqx_resource) for managing configurations and runtime states for dashboard components . + +![bg right](https://docs.emqx.cn/assets/img/rule_action_1@2x.73766093.png) + +--- + + + +# The Demo + +The little log tracer + +--- + +- The hocon schema file (log_tracer_schema.erl): + +https://github.com/terry-xiaoyu/emqx_resource/blob/main/examples/log_tracer_schema.erl + +- The callback file (log_tracer.erl): + +https://github.com/terry-xiaoyu/emqx_resource/blob/main/examples/log_tracer.erl + +--- + +Start the demo log tracer + +``` +./demo.sh +``` + +Load instance from config files (auto loaded) + +``` +## This will load all of the "*.conf" file under that directory: + +emqx_resource:load_instances("./_build/default/lib/emqx_resource/examples"). +``` + +The config file is validated against the schema (`*_schema.erl`) before loaded. + +--- + +# List Types and Instances + +- To list all the available resource types: + +``` +emqx_resource:list_types(). +emqx_resource:list_instances(). +``` + +- And there's `*_verbose` versions for these `list_*` APIs: + +``` +emqx_resource:list_types_verbose(). +emqx_resource:list_instances_verbose(). +``` + +--- +# Instance management + +- To get a resource types and instances: + +``` +emqx_resource:get_type(log_tracer). +emqx_resource:get_instance("log_tracer_clientid_shawn"). +``` + +- To create a resource instances: + +``` +emqx_resource:create("log_tracer2", log_tracer, +#{bulk => <<"1KB">>,cache_log_dir => <<"/tmp">>, + cache_logs_in => <<"memory">>,chars_limit => 1024, + condition => #{<<"app">> => <<"emqx">>}, + enable_cache => true,level => debug}). +``` + +--- + +- To update a resource: + +``` +emqx_resource:update("log_tracer2", log_tracer, #{bulk => <<"100KB">>}, []). +``` + +- To delete a resource: + +``` +emqx_resource:remove("log_tracer2"). +``` + +--- + + + +# HTTP APIs Demo + +--- + +# Get a log tracer + +To list current log tracers: + +``` +curl -s -XGET 'http://localhost:9900/log_tracer' | jq . +``` + +--- + +## Update or Create + +To update an existing log tracer or create a new one: + +``` +INST='{ + "resource_type": "log_tracer", + "config": { + "condition": { + "app": "emqx" + }, + "level": "debug", + "cache_log_dir": "/tmp", + "bulk": "10KB", + "chars_limit": 1024 + } +}' +curl -sv -XPUT 'http://localhost:9900/log_tracer/log_tracer2' -d $INST | jq . +``` diff --git a/apps/emqx_resource/examples/log_tracer.conf b/apps/emqx_resource/examples/log_tracer.conf new file mode 100644 index 000000000..7b438ec1f --- /dev/null +++ b/apps/emqx_resource/examples/log_tracer.conf @@ -0,0 +1,11 @@ +{ + "id": "log_tracer_clientid_shawn" + "resource_type": "log_tracer" + "config": { + "condition": {"app": "emqx"} + "level": "debug" + "cache_log_dir": "/tmp" + "bulk": "10KB" + "chars_limit": 1024 + } +} \ No newline at end of file diff --git a/apps/emqx_resource/examples/log_tracer.erl b/apps/emqx_resource/examples/log_tracer.erl new file mode 100644 index 000000000..99fc5bd3b --- /dev/null +++ b/apps/emqx_resource/examples/log_tracer.erl @@ -0,0 +1,45 @@ +-module(log_tracer). + +-include_lib("emqx_resource/include/emqx_resource_behaviour.hrl"). + +-emqx_resource_api_path("/log_tracer"). + +%% callbacks of behaviour emqx_resource +-export([ on_start/2 + , on_stop/2 + , on_query/4 + , on_health_check/2 + , on_api_reply_format/1 + , on_config_merge/3 + ]). + +%% callbacks for emqx_resource config schema +-export([fields/1]). + +fields(ConfPath) -> + log_tracer_schema:fields(ConfPath). + +on_start(InstId, Config) -> + io:format("== the demo log tracer ~p started.~nconfig: ~p~n", [InstId, Config]), + {ok, #{logger_handler_id => abc, health_checked => 0}}. + +on_stop(InstId, State) -> + io:format("== the demo log tracer ~p stopped.~nstate: ~p~n", [InstId, State]), + ok. + +on_query(InstId, Request, AfterQuery, State) -> + io:format("== the demo log tracer ~p received request: ~p~nstate: ~p~n", + [InstId, Request, State]), + emqx_resource:query_success(AfterQuery), + "this is a demo log messages...". + +on_health_check(InstId, State = #{health_checked := Checked}) -> + NState = State#{health_checked => Checked + 1}, + io:format("== the demo log tracer ~p is working well~nstate: ~p~n", [InstId, NState]), + {ok, NState}. + +on_api_reply_format(#{id := Id, status := Status, state := #{health_checked := NChecked}}) -> + #{id => Id, status => Status, checked_count => NChecked}. + +on_config_merge(OldConfig, NewConfig, _Params) -> + maps:merge(OldConfig, NewConfig). diff --git a/apps/emqx_resource/examples/log_tracer_schema.erl b/apps/emqx_resource/examples/log_tracer_schema.erl new file mode 100644 index 000000000..2b49aab7f --- /dev/null +++ b/apps/emqx_resource/examples/log_tracer_schema.erl @@ -0,0 +1,45 @@ +-module(log_tracer_schema). + +-include_lib("typerefl/include/types.hrl"). + +-export([fields/1]). + +-reflect_type([t_level/0, t_cache_logs_in/0]). + +-type t_level() :: debug | info | notice | warning | error | critical | alert | emergency. + +-type t_cache_logs_in() :: memory | file. + +fields("config") -> + [ {condition, fun condition/1} + , {level, fun level/1} + , {enable_cache, fun enable_cache/1} + , {cache_logs_in, fun cache_logs_in/1} + , {cache_log_dir, fun cache_log_dir/1} + , {bulk, fun bulk/1} + ]; +fields(_) -> []. + +condition(mapping) -> "config.condition"; +condition(type) -> map(); +condition(_) -> undefined. + +level(mapping) -> "config.level"; +level(type) -> t_level(); +level(_) -> undefined. + +enable_cache(mapping) -> "config.enable_cache"; +enable_cache(type) -> boolean(); +enable_cache(_) -> undefined. + +cache_logs_in(mapping) -> "config.cache_logs_in"; +cache_logs_in(type) -> t_cache_logs_in(); +cache_logs_in(_) -> undefined. + +cache_log_dir(mapping) -> "config.cache_log_dir"; +cache_log_dir(type) -> typerefl:regexp_string("^(.*)$"); +cache_log_dir(_) -> undefined. + +bulk(mapping) -> "config.bulk"; +bulk(type) -> typerefl:regexp_string("^[. 0-9]+(B|KB|MB|GB)$"); +bulk(_) -> undefined. diff --git a/apps/emqx_resource/include/emqx_resource.hrl b/apps/emqx_resource/include/emqx_resource.hrl new file mode 100644 index 000000000..1f75b453e --- /dev/null +++ b/apps/emqx_resource/include/emqx_resource.hrl @@ -0,0 +1,34 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-type resource_type() :: module(). +-type instance_id() :: binary(). +-type resource_config() :: jsx:json_term(). +-type resource_spec() :: map(). +-type resource_state() :: term(). +-type resource_data() :: #{ + id => instance_id(), + mod => module(), + config => resource_config(), + state => resource_state(), + status => started | stopped +}. + +-type after_query() :: {OnSuccess :: after_query_fun(), OnFailed :: after_query_fun()} | + undefined. + +%% the `after_query_fun()` is mainly for callbacks that increment counters or do some fallback +%% actions upon query failure +-type after_query_fun() :: {fun((...) -> ok), Args :: [term()]}. diff --git a/apps/emqx_resource/include/emqx_resource_behaviour.hrl b/apps/emqx_resource/include/emqx_resource_behaviour.hrl new file mode 100644 index 000000000..bb4f18b55 --- /dev/null +++ b/apps/emqx_resource/include/emqx_resource_behaviour.hrl @@ -0,0 +1,18 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-include_lib("emqx_resource/include/emqx_resource.hrl"). +-behaviour(emqx_resource). +-compile({parse_transform, emqx_resource_transform}). diff --git a/apps/emqx_resource/include/emqx_resource_utils.hrl b/apps/emqx_resource/include/emqx_resource_utils.hrl new file mode 100644 index 000000000..a20a17e89 --- /dev/null +++ b/apps/emqx_resource/include/emqx_resource_utils.hrl @@ -0,0 +1,53 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-define(CLUSTER_CALL(Func, Args), ?CLUSTER_CALL(Func, Args, ok)). + +-define(CLUSTER_CALL(Func, Args, ResParttern), +%% ekka_mnesia:running_nodes() + fun() -> + case LocalResult = erlang:apply(?MODULE, Func, Args) of + ResParttern -> + case rpc:multicall(nodes(), ?MODULE, Func, Args, 5000) of + {ResL, []} -> + Filter = fun + (ResParttern) -> false; + ({badrpc, {'EXIT', {undef, [{?MODULE, Func0, _, []}]}}}) + when Func0 =:= Func -> false; + (_) -> true + end, + case lists:filter(Filter, ResL) of + [] -> LocalResult; + ErrL -> {error, ErrL} + end; + {ResL, BadNodes} -> + {error, {failed_on_nodes, BadNodes, ResL}} + end; + ErrorResult -> + {error, ErrorResult} + end + end()). + +-define(SAFE_CALL(_EXP_), + ?SAFE_CALL(_EXP_, _ = do_nothing)). + +-define(SAFE_CALL(_EXP_, _EXP_ON_FAIL_), + fun() -> + try (_EXP_) + catch _EXCLASS_:_EXCPTION_:_ST_ -> + _EXP_ON_FAIL_, + {error, {_EXCLASS_, _EXCPTION_, _ST_}} + end + end()). \ No newline at end of file diff --git a/apps/emqx_resource/priv/emqx_resource.schema b/apps/emqx_resource/priv/emqx_resource.schema new file mode 100644 index 000000000..8246dc6a7 --- /dev/null +++ b/apps/emqx_resource/priv/emqx_resource.schema @@ -0,0 +1,2 @@ +%%-*- mode: erlang -*- +%% emqx-resource config mapping diff --git a/apps/emqx_resource/rebar.config b/apps/emqx_resource/rebar.config new file mode 100644 index 000000000..148af92bd --- /dev/null +++ b/apps/emqx_resource/rebar.config @@ -0,0 +1,18 @@ +{erl_opts, [ debug_info + , nowarn_unused_import + %, {d, 'RESOURCE_DEBUG'} + ]}. + +{erl_first_files, ["src/emqx_resource_transform.erl"]}. + +{extra_src_dirs, ["examples"]}. + +%% try to override the dialyzer 'race_conditions' defined in the top-level dir, +%% but it doesn't work +{dialyzer, [{warnings, [unmatched_returns, error_handling]} + ]}. + +{deps, [ {hocon, {git, "https://github.com/emqx/hocon", {branch, "master"}}} + , {jsx, {git, "https://github.com/talentdeficit/jsx", {tag, "v3.1.0"}}} + ]}. + diff --git a/apps/emqx_resource/scripts/elvis-check.sh b/apps/emqx_resource/scripts/elvis-check.sh new file mode 100755 index 000000000..3fae0f191 --- /dev/null +++ b/apps/emqx_resource/scripts/elvis-check.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -euo pipefail + +ELVIS_VERSION='1.0.0-emqx-2' + +elvis_version="${2:-$ELVIS_VERSION}" + +echo "elvis -v: $elvis_version" + +if [ ! -f ./elvis ] || [ "$(./elvis -v | grep -oE '[1-9]+\.[0-9]+\.[0-9]+\-emqx-[0-9]+')" != "$elvis_version" ]; then + curl -fLO "https://github.com/emqx/elvis/releases/download/$elvis_version/elvis" + chmod +x ./elvis +fi + +./elvis rock --config elvis.config + diff --git a/apps/emqx_resource/src/emqx_resource.app.src b/apps/emqx_resource/src/emqx_resource.app.src new file mode 100644 index 000000000..af9f48cc6 --- /dev/null +++ b/apps/emqx_resource/src/emqx_resource.app.src @@ -0,0 +1,17 @@ +{application, emqx_resource, + [{description, "An OTP application"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {emqx_resource_app, []}}, + {applications, + [kernel, + stdlib, + gproc, + hocon + ]}, + {env,[]}, + {modules, []}, + + {licenses, ["Apache 2.0"]}, + {links, []} + ]}. diff --git a/apps/emqx_resource/src/emqx_resource.erl b/apps/emqx_resource/src/emqx_resource.erl new file mode 100644 index 000000000..b9107d328 --- /dev/null +++ b/apps/emqx_resource/src/emqx_resource.erl @@ -0,0 +1,274 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_resource). + +-include("emqx_resource.hrl"). +-include("emqx_resource_utils.hrl"). + +%% APIs for resource types + +-export([ get_type/1 + , list_types/0 + , list_types_verbose/0 + ]). + +-export([ discover_resource_mods/0 + , is_resource_mod/1 + , call_instance/2 + ]). + +-export([ query_success/1 + , query_failed/1 + ]). + +%% APIs for instances + +-export([ parse_config/2 + , resource_type_from_str/1 + ]). + +%% Sync resource instances and files +%% provisional solution: rpc:multical to all the nodes for creating/updating/removing +%% todo: replicate operations +-export([ create/3 %% store the config and start the instance + , create_dry_run/3 %% run start/2, health_check/2 and stop/1 sequentially + , update/4 %% update the config, stop the old instance and start the new one + %% it will create a new resource when the id does not exist + , remove/1 %% remove the config and stop the instance + ]). + +%% Calls to the callback module with current resource state +%% They also save the state after the call finished (except query/2,3). +-export([ restart/1 %% restart the instance. + , health_check/1 %% verify if the resource is working normally + , stop/1 %% stop the instance + , query/2 %% query the instance + , query/3 %% query the instance with after_query() + ]). + +%% Direct calls to the callback module +-export([ call_start/3 %% start the instance + , call_health_check/3 %% verify if the resource is working normally + , call_stop/3 %% stop the instance + , call_config_merge/4 %% merge the config when updating + ]). + +-export([ list_instances/0 %% list all the instances, id only. + , list_instances_verbose/0 %% list all the instances + , get_instance/1 %% return the data of the instance + , get_instance_by_type/1 %% return all the instances of the same resource type + , load_instances/1 %% load instances from config files + % , dependents/1 + % , inc_counter/2 %% increment the counter of the instance + % , inc_counter/3 %% increment the counter by a given integer + ]). + +-define(EXT, "*.spec"). + +-optional_callbacks([ on_query/4 + , on_health_check/2 + , on_api_reply_format/1 + , on_config_merge/3 + ]). + +-callback on_api_reply_format(resource_data()) -> map(). + +-callback on_config_merge(resource_config(), resource_config(), term()) -> resource_config(). + +%% when calling emqx_resource:start/1 +-callback on_start(instance_id(), resource_config()) -> + {ok, resource_state()} | {error, Reason :: term()}. + +%% when calling emqx_resource:stop/1 +-callback on_stop(instance_id(), resource_state()) -> term(). + +%% when calling emqx_resource:query/3 +-callback on_query(instance_id(), Request :: term(), after_query(), resource_state()) -> term(). + +%% when calling emqx_resource:health_check/2 +-callback on_health_check(instance_id(), resource_state()) -> + {ok, resource_state()} | {error, Reason:: term(), resource_state()}. + +%% load specs and return the loaded resources this time. +-spec list_types_verbose() -> [resource_spec()]. +list_types_verbose() -> + [get_spec(Mod) || Mod <- list_types()]. + +-spec list_types() -> [module()]. +list_types() -> + discover_resource_mods(). + +-spec get_type(module()) -> {ok, resource_spec()} | {error, not_found}. +get_type(Mod) -> + case is_resource_mod(Mod) of + true -> {ok, get_spec(Mod)}; + false -> {error, not_found} + end. + +-spec get_spec(module()) -> resource_spec(). +get_spec(Mod) -> + maps:put(<<"resource_type">>, Mod, Mod:emqx_resource_schema()). + +-spec discover_resource_mods() -> [module()]. +discover_resource_mods() -> + [Mod || {Mod, _} <- code:all_loaded(), is_resource_mod(Mod)]. + +-spec is_resource_mod(module()) -> boolean(). +is_resource_mod(Mod) -> + erlang:function_exported(Mod, emqx_resource_schema, 0). + +-spec query_success(after_query()) -> ok. +query_success(undefined) -> ok; +query_success({{OnSucc, Args}, _}) -> + safe_apply(OnSucc, Args). + +-spec query_failed(after_query()) -> ok. +query_failed(undefined) -> ok; +query_failed({_, {OnFailed, Args}}) -> + safe_apply(OnFailed, Args). + +%% ================================================================================= +%% APIs for resource instances +%% ================================================================================= +-spec create(instance_id(), resource_type(), resource_config()) -> + {ok, resource_data()} | {error, Reason :: term()}. +create(InstId, ResourceType, Config) -> + ?CLUSTER_CALL(call_instance, [InstId, {create, InstId, ResourceType, Config}], {ok, _}). + +-spec create_dry_run(instance_id(), resource_type(), resource_config()) -> + ok | {error, Reason :: term()}. +create_dry_run(InstId, ResourceType, Config) -> + ?CLUSTER_CALL(call_instance, [InstId, {create_dry_run, InstId, ResourceType, Config}]). + +-spec update(instance_id(), resource_type(), resource_config(), term()) -> + {ok, resource_data()} | {error, Reason :: term()}. +update(InstId, ResourceType, Config, Params) -> + ?CLUSTER_CALL(call_instance, [InstId, {update, InstId, ResourceType, Config, Params}], {ok, _}). + +-spec remove(instance_id()) -> ok | {error, Reason :: term()}. +remove(InstId) -> + ?CLUSTER_CALL(call_instance, [InstId, {remove, InstId}]). + +-spec query(instance_id(), Request :: term()) -> Result :: term(). +query(InstId, Request) -> + query(InstId, Request, undefined). + +%% same to above, also defines what to do when the Module:on_query success or failed +%% it is the duty of the Moudle to apply the `after_query()` functions. +-spec query(instance_id(), Request :: term(), after_query()) -> Result :: term(). +query(InstId, Request, AfterQuery) -> + case get_instance(InstId) of + {ok, #{mod := Mod, state := ResourceState}} -> + %% the resource state is readonly to Moudle:on_query/4 + %% and the `after_query()` functions should be thread safe + Mod:on_query(InstId, Request, AfterQuery, ResourceState); + {error, Reason} -> + error({get_instance, {InstId, Reason}}) + end. + +-spec restart(instance_id()) -> ok | {error, Reason :: term()}. +restart(InstId) -> + call_instance(InstId, {restart, InstId}). + +-spec stop(instance_id()) -> ok | {error, Reason :: term()}. +stop(InstId) -> + call_instance(InstId, {stop, InstId}). + +-spec health_check(instance_id()) -> ok | {error, Reason :: term()}. +health_check(InstId) -> + call_instance(InstId, {health_check, InstId}). + +-spec get_instance(instance_id()) -> {ok, resource_data()} | {error, Reason :: term()}. +get_instance(InstId) -> + emqx_resource_instance:lookup(InstId). + +-spec list_instances() -> [instance_id()]. +list_instances() -> + [Id || #{id := Id} <- list_instances_verbose()]. + +-spec list_instances_verbose() -> [resource_data()]. +list_instances_verbose() -> + emqx_resource_instance:list_all(). + +-spec get_instance_by_type(module()) -> [resource_data()]. +get_instance_by_type(ResourceType) -> + emqx_resource_instance:lookup_by_type(ResourceType). + +-spec load_instances(Dir :: string()) -> ok. +load_instances(Dir) -> + emqx_resource_instance:load(Dir). + +-spec call_start(instance_id(), module(), resource_config()) -> + {ok, resource_state()} | {error, Reason :: term()}. +call_start(InstId, Mod, Config) -> + ?SAFE_CALL(Mod:on_start(InstId, Config)). + +-spec call_health_check(instance_id(), module(), resource_state()) -> + {ok, resource_state()} | {error, Reason:: term(), resource_state()}. +call_health_check(InstId, Mod, ResourceState) -> + ?SAFE_CALL(Mod:on_health_check(InstId, ResourceState)). + +-spec call_stop(instance_id(), module(), resource_state()) -> term(). +call_stop(InstId, Mod, ResourceState) -> + ?SAFE_CALL(Mod:on_stop(InstId, ResourceState)). + +-spec call_config_merge(module(), resource_config(), resource_config(), term()) -> + resource_config(). +call_config_merge(Mod, OldConfig, NewConfig, Params) -> + ?SAFE_CALL(Mod:on_config_merge(OldConfig, NewConfig, Params)). + +-spec parse_config(resource_type(), binary() | term()) -> + {ok, resource_config()} | {error, term()}. +parse_config(ResourceType, RawConfig) when is_binary(RawConfig) -> + case hocon:binary(RawConfig, #{format => richmap}) of + {ok, MapConfig} -> + do_parse_config(ResourceType, MapConfig); + Error -> Error + end; +parse_config(ResourceType, RawConfigTerm) -> + parse_config(ResourceType, jsx:encode(#{<<"config">> => RawConfigTerm})). + +-spec do_parse_config(resource_type(), map()) -> {ok, resource_config()} | {error, term()}. +do_parse_config(ResourceType, MapConfig) -> + case ?SAFE_CALL(hocon_schema:generate(ResourceType, MapConfig)) of + {error, Reason} -> {error, Reason}; + Config -> + InstConf = maps:from_list(proplists:get_value(config, Config)), + {ok, InstConf} + end. + +%% ================================================================================= + +-spec resource_type_from_str(string()) -> {ok, resource_type()} | {error, term()}. +resource_type_from_str(ResourceType) -> + try Mod = list_to_existing_atom(str(ResourceType)), + case emqx_resource:is_resource_mod(Mod) of + true -> {ok, Mod}; + false -> {error, {invalid_resource, Mod}} + end + catch error:badarg -> + {error, {not_found, ResourceType}} + end. + +call_instance(InstId, Query) -> + emqx_resource_instance:hash_call(InstId, Query). + +safe_apply(Func, Args) -> + ?SAFE_CALL(erlang:apply(Func, Args)). + +str(S) when is_binary(S) -> binary_to_list(S); +str(S) when is_list(S) -> S. diff --git a/apps/emqx_resource/src/emqx_resource_api.erl b/apps/emqx_resource/src/emqx_resource_api.erl new file mode 100644 index 000000000..cc32d8a11 --- /dev/null +++ b/apps/emqx_resource/src/emqx_resource_api.erl @@ -0,0 +1,79 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-module(emqx_resource_api). + +-export([ get_all/3 + , get/3 + , put/3 + , delete/3 + ]). +get_all(Mod, _Binding, _Params) -> + {200, #{code => 0, data => + [format_data(Mod, Data) || Data <- emqx_resource:list_instances_verbose()]}}. + +get(Mod, #{id := Id}, _Params) -> + case emqx_resource:get_instance(stringnify(Id)) of + {ok, Data} -> + {200, #{code => 0, data => format_data(Mod, Data)}}; + {error, not_found} -> + {404, #{code => 102, message => {resource_instance_not_found, stringnify(Id)}}} + end. + +put(Mod, #{id := Id}, Params) -> + ConfigParams = proplists:get_value(<<"config">>, Params), + ResourceTypeStr = proplists:get_value(<<"resource_type">>, Params), + case emqx_resource:resource_type_from_str(ResourceTypeStr) of + {ok, ResourceType} -> + do_put(Mod, stringnify(Id), ConfigParams, ResourceType, Params); + {error, Reason} -> + {404, #{code => 102, message => stringnify(Reason)}} + end. + +do_put(Mod, Id, ConfigParams, ResourceType, Params) -> + case emqx_resource:parse_config(ResourceType, ConfigParams) of + {ok, Config} -> + case emqx_resource:update(Id, ResourceType, Config, Params) of + {ok, Data} -> + {200, #{code => 0, data => format_data(Mod, Data)}}; + {error, Reason} -> + {500, #{code => 102, message => stringnify(Reason)}} + end; + {error, Reason} -> + {400, #{code => 108, message => stringnify(Reason)}} + end. + +delete(_Mod, #{id := Id}, _Params) -> + case emqx_resource:remove(stringnify(Id)) of + ok -> {200, #{code => 0, data => #{}}}; + {error, Reason} -> + {500, #{code => 102, message => stringnify(Reason)}} + end. + +format_data(Mod, Data) -> + case erlang:function_exported(Mod, on_api_reply_format, 1) of + false -> + default_api_reply_format(Data); + true -> + Mod:on_api_reply_format(Data) + end. + +default_api_reply_format(#{id := Id, status := Status, config := Config}) -> + #{node => node(), id => Id, status => Status, config => Config}. + +stringnify(Bin) when is_binary(Bin) -> Bin; +stringnify(Str) when is_list(Str) -> list_to_binary(Str); +stringnify(Reason) -> + iolist_to_binary(io_lib:format("~p", [Reason])). diff --git a/apps/emqx_resource/src/emqx_resource_app.erl b/apps/emqx_resource/src/emqx_resource_app.erl new file mode 100644 index 000000000..d2c499490 --- /dev/null +++ b/apps/emqx_resource/src/emqx_resource_app.erl @@ -0,0 +1,33 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_resource_app). + +-behaviour(application). + +-include("emqx_resource.hrl"). + +-emqx_plugin(?MODULE). + +-export([start/2, stop/1]). + +start(_StartType, _StartArgs) -> + emqx_resource_sup:start_link(). + +stop(_State) -> + ok. + +%% internal functions diff --git a/apps/emqx_resource/src/emqx_resource_instance.erl b/apps/emqx_resource/src/emqx_resource_instance.erl new file mode 100644 index 000000000..ca5e4829e --- /dev/null +++ b/apps/emqx_resource/src/emqx_resource_instance.erl @@ -0,0 +1,295 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-module(emqx_resource_instance). + +-behaviour(gen_server). + +-include("emqx_resource.hrl"). +-include("emqx_resource_utils.hrl"). + +-export([start_link/2]). + +%% load resource instances from *.conf files +-export([ load/1 + , lookup/1 + , list_all/0 + , lookup_by_type/1 + ]). + +-export([ hash_call/2 + , hash_call/3 + ]). + +%% gen_server Callbacks +-export([ init/1 + , handle_call/3 + , handle_cast/2 + , handle_info/2 + , terminate/2 + , code_change/3 + ]). + +-record(state, {worker_pool, worker_id}). + +-type state() :: #state{}. + +%%------------------------------------------------------------------------------ +%% Start the registry +%%------------------------------------------------------------------------------ + +start_link(Pool, Id) -> + gen_server:start_link({local, proc_name(?MODULE, Id)}, + ?MODULE, {Pool, Id}, []). + +%% call the worker by the hash of resource-instance-id, to make sure we always handle +%% operations on the same instance in the same worker. +hash_call(InstId, Request) -> + hash_call(InstId, Request, infinity). + +hash_call(InstId, Request, Timeout) -> + gen_server:call(pick(InstId), Request, Timeout). + +-spec lookup(instance_id()) -> {ok, resource_data()} | {error, Reason :: term()}. +lookup(InstId) -> + case ets:lookup(emqx_resource_instance, InstId) of + [] -> {error, not_found}; + [{_, Data}] -> {ok, Data#{id => InstId}} + end. + +force_lookup(InstId) -> + {ok, Data} = lookup(InstId), + Data. + +-spec list_all() -> [resource_data()]. +list_all() -> + [Data#{id => Id} || {Id, Data} <- ets:tab2list(emqx_resource_instance)]. + +-spec lookup_by_type(module()) -> [resource_data()]. +lookup_by_type(ResourceType) -> + [Data || #{mod := Mod} = Data <- list_all() + , Mod =:= ResourceType]. + +-spec load(Dir :: string()) -> ok. +load(Dir) -> + lists:foreach(fun load_file/1, filelib:wildcard(filename:join([Dir, "*.conf"]))). + +load_file(File) -> + case ?SAFE_CALL(hocon_token:read(File)) of + {error, Reason} -> + logger:error("load resource from ~p failed: ~p", [File, Reason]); + RawConfig -> + case hocon:binary(RawConfig, #{format => map}) of + {ok, #{<<"id">> := Id, <<"resource_type">> := ResourceTypeStr, + <<"config">> := MapConfig}} -> + case emqx_resource:resource_type_from_str(ResourceTypeStr) of + {ok, ResourceType} -> + parse_and_load_config(Id, ResourceType, MapConfig); + {error, Reason} -> + logger:error("no such resource type: ~s, ~p", + [ResourceTypeStr, Reason]) + end; + {error, Reason} -> + logger:error("load resource from ~p failed: ~p", [File, Reason]) + end + end. + +parse_and_load_config(InstId, ResourceType, MapConfig) -> + case emqx_resource:parse_config(ResourceType, MapConfig) of + {error, Reason} -> + logger:error("parse config for resource ~p of type ~p failed: ~p", + [InstId, ResourceType, Reason]); + {ok, InstConf} -> + create_instance_local(InstId, ResourceType, InstConf) + end. + +create_instance_local(InstId, ResourceType, InstConf) -> + case do_create(InstId, ResourceType, InstConf) of + {ok, Data} -> + logger:debug("created ~p resource instance: ~p from config: ~p, Data: ~p", + [ResourceType, InstId, InstConf, Data]); + {error, Reason} -> + logger:error("create ~p resource instance: ~p failed: ~p, config: ~p", + [ResourceType, InstId, Reason, InstConf]) + end. + +%%------------------------------------------------------------------------------ +%% gen_server callbacks +%%------------------------------------------------------------------------------ + +-spec init({atom(), integer()}) -> + {ok, State :: state()} | {ok, State :: state(), timeout() | hibernate | {continue, term()}} | + {stop, Reason :: term()} | ignore. +init({Pool, Id}) -> + true = gproc_pool:connect_worker(Pool, {Pool, Id}), + {ok, #state{worker_pool = Pool, worker_id = Id}}. + +handle_call({create, InstId, ResourceType, Config}, _From, State) -> + {reply, do_create(InstId, ResourceType, Config), State}; + +handle_call({create_dry_run, InstId, ResourceType, Config}, _From, State) -> + {reply, do_create_dry_run(InstId, ResourceType, Config), State}; + +handle_call({update, InstId, ResourceType, Config, Params}, _From, State) -> + {reply, do_update(InstId, ResourceType, Config, Params), State}; + +handle_call({remove, InstId}, _From, State) -> + {reply, do_remove(InstId), State}; + +handle_call({restart, InstId}, _From, State) -> + {reply, do_restart(InstId), State}; + +handle_call({stop, InstId}, _From, State) -> + {reply, do_stop(InstId), State}; + +handle_call({health_check, InstId}, _From, State) -> + {reply, do_health_check(InstId), State}; + +handle_call(Req, _From, State) -> + logger:error("Received unexpected call: ~p", [Req]), + {reply, ignored, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, #state{worker_pool = Pool, worker_id = Id}) -> + gproc_pool:disconnect_worker(Pool, {Pool, Id}). + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%------------------------------------------------------------------------------ + +%% suppress the race condition check, as these functions are protected in gproc workers +-dialyzer({nowarn_function, [do_update/4, do_create/3, do_restart/1, do_stop/1, do_health_check/1]}). +do_update(InstId, ResourceType, NewConfig, Params) -> + case lookup(InstId) of + {ok, #{mod := ResourceType, state := ResourceState, config := OldConfig}} -> + Config = emqx_resource:call_config_merge(ResourceType, OldConfig, + NewConfig, Params), + case do_create_dry_run(InstId, ResourceType, Config) of + ok -> + do_remove(ResourceType, InstId, ResourceState), + do_create(InstId, ResourceType, Config); + Error -> + Error + end; + {ok, #{mod := Mod}} when Mod =/= ResourceType -> + {error, updating_to_incorrect_resource_type}; + {error, not_found} -> + do_create(InstId, ResourceType, NewConfig) + end. + +do_create(InstId, ResourceType, Config) -> + case lookup(InstId) of + {ok, _} -> {error, already_created}; + _ -> + case emqx_resource:call_start(InstId, ResourceType, Config) of + {ok, ResourceState} -> + ets:insert(emqx_resource_instance, {InstId, + #{mod => ResourceType, config => Config, + state => ResourceState, status => stopped}}), + _ = do_health_check(InstId), + {ok, force_lookup(InstId)}; + {error, Reason} -> + logger:error("start ~s resource ~s failed: ~p", [ResourceType, InstId, Reason]), + {error, Reason} + end + end. + +do_create_dry_run(InstId, ResourceType, Config) -> + case emqx_resource:call_start(InstId, ResourceType, Config) of + {ok, ResourceState0} -> + Return = case emqx_resource:call_health_check(InstId, ResourceType, ResourceState0) of + {ok, ResourceState1} -> ok; + {error, Reason, ResourceState1} -> + {error, Reason} + end, + _ = emqx_resource:call_stop(InstId, ResourceType, ResourceState1), + Return; + {error, Reason} -> + {error, Reason} + end. + +do_remove(InstId) -> + case lookup(InstId) of + {ok, #{mod := Mod, state := ResourceState}} -> + do_remove(Mod, InstId, ResourceState); + Error -> + Error + end. + +do_remove(Mod, InstId, ResourceState) -> + _ = emqx_resource:call_stop(InstId, Mod, ResourceState), + ets:delete(emqx_resource_instance, InstId), + ok. + +do_restart(InstId) -> + case lookup(InstId) of + {ok, #{mod := Mod, state := ResourceState, config := Config} = Data} -> + _ = emqx_resource:call_stop(InstId, Mod, ResourceState), + case emqx_resource:call_start(InstId, Mod, Config) of + {ok, ResourceState} -> + ets:insert(emqx_resource_instance, + {InstId, Data#{state => ResourceState, status => started}}), + ok; + {error, Reason} -> + ets:insert(emqx_resource_instance, {InstId, Data#{status => stopped}}), + {error, Reason} + end; + Error -> + Error + end. + +do_stop(InstId) -> + case lookup(InstId) of + {ok, #{mod := Mod, state := ResourceState} = Data} -> + _ = emqx_resource:call_stop(InstId, Mod, ResourceState), + ets:insert(emqx_resource_instance, {InstId, Data#{status => stopped}}), + ok; + Error -> + Error + end. + +do_health_check(InstId) -> + case lookup(InstId) of + {ok, #{mod := Mod, state := ResourceState0} = Data} -> + case emqx_resource:call_health_check(InstId, Mod, ResourceState0) of + {ok, ResourceState1} -> + ets:insert(emqx_resource_instance, + {InstId, Data#{status => started, state => ResourceState1}}), + ok; + {error, Reason, ResourceState1} -> + logger:error("health check for ~p failed: ~p", [InstId, Reason]), + ets:insert(emqx_resource_instance, + {InstId, Data#{status => stopped, state => ResourceState1}}), + {error, Reason} + end; + Error -> + Error + end. + +%%------------------------------------------------------------------------------ +%% internal functions +%%------------------------------------------------------------------------------ + +proc_name(Mod, Id) -> + list_to_atom(lists:concat([Mod, "_", Id])). + +pick(InstId) -> + gproc_pool:pick_worker(emqx_resource_instance, InstId). diff --git a/apps/emqx_resource/src/emqx_resource_sup.erl b/apps/emqx_resource/src/emqx_resource_sup.erl new file mode 100644 index 000000000..22984b940 --- /dev/null +++ b/apps/emqx_resource/src/emqx_resource_sup.erl @@ -0,0 +1,58 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-module(emqx_resource_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(RESOURCE_INST_MOD, emqx_resource_instance). +-define(POOL_SIZE, 64). %% set a very large pool size in case all the workers busy + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +init([]) -> + TabOpts = [named_table, set, public, {read_concurrency, true}], + _ = ets:new(emqx_resource_instance, TabOpts), + + SupFlags = #{strategy => one_for_one, intensity => 10, period => 10}, + Pool = ?RESOURCE_INST_MOD, + Mod = ?RESOURCE_INST_MOD, + ensure_pool(Pool, hash, [{size, ?POOL_SIZE}]), + {ok, {SupFlags, [ + begin + ensure_pool_worker(Pool, {Pool, Idx}, Idx), + #{id => {Mod, Idx}, + start => {Mod, start_link, [Pool, Idx]}, + restart => transient, + shutdown => 5000, type => worker, modules => [Mod]} + end || Idx <- lists:seq(1, ?POOL_SIZE)]}}. + +%% internal functions +ensure_pool(Pool, Type, Opts) -> + try gproc_pool:new(Pool, Type, Opts) + catch + error:exists -> ok + end. + +ensure_pool_worker(Pool, Name, Slot) -> + try gproc_pool:add_worker(Pool, Name, Slot) + catch + error:exists -> ok + end. \ No newline at end of file diff --git a/apps/emqx_resource/src/emqx_resource_transform.erl b/apps/emqx_resource/src/emqx_resource_transform.erl new file mode 100644 index 000000000..23e14013c --- /dev/null +++ b/apps/emqx_resource/src/emqx_resource_transform.erl @@ -0,0 +1,114 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-module(emqx_resource_transform). + +-include_lib("syntax_tools/include/merl.hrl"). + +-export([parse_transform/2]). + +parse_transform(Forms, _Opts) -> + Mod = hd([M || {attribute, _, module, M} <- Forms]), + AST = trans(Mod, proplists:delete(eof, Forms)), + debug_print(Mod, AST), + AST. + +-ifdef(RESOURCE_DEBUG). + +debug_print(Mod, Ts) -> + {ok, Io} = file:open("./" ++ atom_to_list(Mod) ++ ".trans.erl", [write]), + do_debug_print(Io, Ts), + file:close(Io). + +do_debug_print(Io, Ts) when is_list(Ts) -> + lists:foreach(fun(T) -> do_debug_print(Io, T) end, Ts); +do_debug_print(Io, T) -> + io:put_chars(Io, erl_prettypr:format(merl:tree(T))), + io:nl(Io). +-else. +debug_print(_Mod, _AST) -> + ok. +-endif. + +trans(Mod, Forms) -> + forms(Mod, Forms) ++ [erl_syntax:revert(erl_syntax:eof_marker())]. + +forms(Mod, [F0 | Fs0]) -> + case form(Mod, F0) of + {CurrForm, AppendedForms} -> + CurrForm ++ forms(Mod, Fs0) ++ AppendedForms; + {AHeadForms, CurrForm, AppendedForms} -> + AHeadForms ++ CurrForm ++ forms(Mod, Fs0) ++ AppendedForms + end; +forms(_, []) -> []. + +form(Mod, Form) -> + case Form of + ?Q("-emqx_resource_api_path('@Path').") -> + {fix_spec_attrs() ++ fix_api_attrs(erl_syntax:concrete(Path)) ++ fix_api_exports(), + [], + fix_spec_funcs(Mod) ++ fix_api_funcs(Mod)}; + _ -> + %io:format("---other form: ~p~n", [Form]), + {[], [Form], []} + end. + +fix_spec_attrs() -> + [ ?Q("-export([emqx_resource_schema/0]).") + , ?Q("-export([structs/0]).") + , ?Q("-behaviour(hocon_schema).") + ]. +fix_spec_funcs(_Mod) -> + [ (?Q("emqx_resource_schema() -> <<\"demo_swagger_schema\">>.")) + , ?Q("structs() -> [\"config\"].") + ]. + +fix_api_attrs(Path0) -> + BaseName = filename:basename(Path0), + Path = "/" ++ BaseName, + [erl_syntax:revert( + erl_syntax:attribute(?Q("rest_api"), [ + erl_syntax:abstract(#{ + name => list_to_atom(Name ++ "_log_tracers"), + method => Method, + path => mk_path(Path, WithId), + func => Func, + descr => Name ++ " the " ++ BaseName})])) + || {Name, Method, WithId, Func} <- [ + {"list", 'GET', noid, api_get_all}, + {"get", 'GET', id, api_get}, + {"update", 'PUT', id, api_put}, + {"delete", 'DELETE', id, api_delete}]]. + +fix_api_exports() -> + [?Q("-export([api_get_all/2, api_get/2, api_put/2, api_delete/2]).")]. + +fix_api_funcs(Mod) -> + [erl_syntax:revert(?Q( + "api_get_all(Binding, Params) -> + emqx_resource_api:get_all('@Mod@', Binding, Params).")), + erl_syntax:revert(?Q( + "api_get(Binding, Params) -> + emqx_resource_api:get('@Mod@', Binding, Params).")), + erl_syntax:revert(?Q( + "api_put(Binding, Params) -> + emqx_resource_api:put('@Mod@', Binding, Params).")), + erl_syntax:revert(?Q( + "api_delete(Binding, Params) -> + emqx_resource_api:delete('@Mod@', Binding, Params).")) + ]. + +mk_path(Path, id) -> Path ++ "/:bin:id"; +mk_path(Path, noid) -> Path. diff --git a/apps/emqx_resource/src/emqx_resource_uitils.erl b/apps/emqx_resource/src/emqx_resource_uitils.erl new file mode 100644 index 000000000..ab3f6dd1e --- /dev/null +++ b/apps/emqx_resource/src/emqx_resource_uitils.erl @@ -0,0 +1,16 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +-module(emqx_resource_uitils). \ No newline at end of file diff --git a/apps/emqx_resource/src/emqx_resource_validator.erl b/apps/emqx_resource/src/emqx_resource_validator.erl new file mode 100644 index 000000000..e9517f160 --- /dev/null +++ b/apps/emqx_resource/src/emqx_resource_validator.erl @@ -0,0 +1,63 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_resource_validator). + +-export([ min/2 + , max/2 + , equals/2 + , enum/1 + , required/1 + ]). + +max(Type, Max) -> + limit(Type, '=<', Max). + +min(Type, Min) -> + limit(Type, '>=', Min). + +equals(Type, Expected) -> + limit(Type, '==', Expected). + +enum(Items) -> + fun(Value) -> + return(lists:member(Value, Items), + err_limit({enum, {is_member_of, Items}, {got, Value}})) + end. + +required(ErrMsg) -> + fun(undefined) -> {error, ErrMsg}; + (_) -> ok + end. + +limit(Type, Op, Expected) -> + L = len(Type), + fun(Value) -> + Got = L(Value), + return(erlang:Op(Got, Expected), + err_limit({Type, {Op, Expected}, {got, Got}})) + end. + +len(array) -> fun erlang:length/1; +len(string) -> fun string:length/1; +len(_Type) -> fun(Val) -> Val end. + +err_limit({Type, {Op, Expected}, {got, Got}}) -> + io_lib:format("Expect the ~s value ~s ~p but got: ~p", [Type, Op, Expected, Got]). + +return(true, _) -> ok; +return(false, Error) -> + {error, Error}. diff --git a/apps/emqx_retainer/etc/emqx_retainer.conf b/apps/emqx_retainer/etc/emqx_retainer.conf index 0a883cee5..4db438a98 100644 --- a/apps/emqx_retainer/etc/emqx_retainer.conf +++ b/apps/emqx_retainer/etc/emqx_retainer.conf @@ -37,5 +37,5 @@ retainer.max_payload_size = 1MB ## - 30m: 30 minutes ## - 20s: 20 seconds ## -## Defaut: 0 +## Default: 0 retainer.expiry_interval = 0 diff --git a/apps/emqx_retainer/rebar.config b/apps/emqx_retainer/rebar.config index 9557780e8..7e762cb72 100644 --- a/apps/emqx_retainer/rebar.config +++ b/apps/emqx_retainer/rebar.config @@ -18,7 +18,7 @@ {profiles, [{test, [{deps, - [{emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "1.2.2"}}}, + [ {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.2.3"}}}]} ]} ]}. diff --git a/apps/emqx_rule_engine/etc/emqx_rule_engine.conf b/apps/emqx_rule_engine/etc/emqx_rule_engine.conf index 2fe946779..556c59970 100644 --- a/apps/emqx_rule_engine/etc/emqx_rule_engine.conf +++ b/apps/emqx_rule_engine/etc/emqx_rule_engine.conf @@ -32,7 +32,7 @@ rule_engine.ignore_sys_message = on ## ## QoS-Level: qos0/qos1/qos2 -#rule_engine.events.client_connected = on, qos1 +#rule_engine.events.client_connected = "on, qos1" rule_engine.events.client_connected = off rule_engine.events.client_disconnected = off rule_engine.events.session_subscribed = off diff --git a/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl b/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl index d424c9f8a..2e82ef0cd 100644 --- a/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl +++ b/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl @@ -2554,7 +2554,7 @@ start_apps() -> [start_apps(App, SchemaFile, ConfigFile) || {App, SchemaFile, ConfigFile} <- [{emqx, deps_path(emqx, "priv/emqx.schema"), - deps_path(emqx, "etc/emqx.conf")}, + deps_path(emqx, "etc/emqx.conf.rendered")}, {emqx_rule_engine, local_path("priv/emqx_rule_engine.schema"), local_path("etc/emqx_rule_engine.conf")}]]. @@ -2566,7 +2566,7 @@ start_apps(App, SchemaFile, ConfigFile) -> read_schema_configs(App, SchemaFile, ConfigFile) -> ct:pal("Read configs - SchemaFile: ~p, ConfigFile: ~p", [SchemaFile, ConfigFile]), Schema = cuttlefish_schema:files([SchemaFile]), - Conf = conf_parse:file(ConfigFile), + {ok, Conf} = hocon:load(ConfigFile, #{format => proplists}), NewConfig = cuttlefish_generator:map(Schema, Conf), Vals = proplists:get_value(App, NewConfig, []), [application:set_env(App, Par, Value) || {Par, Value} <- Vals]. diff --git a/apps/emqx_sn/etc/emqx_sn.conf b/apps/emqx_sn/etc/emqx_sn.conf index 6572812c1..e05f1e7be 100644 --- a/apps/emqx_sn/etc/emqx_sn.conf +++ b/apps/emqx_sn/etc/emqx_sn.conf @@ -6,7 +6,7 @@ ## ## Value: IP:Port | Port ## -## Examples: 1884, 127.0.0.1:1884, ::1:1884 +## Examples: 1884, "127.0.0.1:1884", "::1:1884" mqtt.sn.port = 1884 ## The duration that emqx-sn broadcast ADVERTISE message through. @@ -37,8 +37,8 @@ mqtt.sn.idle_timeout = 30s ## The pre-defined topic name corresponding to the pre-defined topic id of N. ## Note that the pre-defined topic id of 0 is reserved. mqtt.sn.predefined.topic.0 = reserved -mqtt.sn.predefined.topic.1 = /predefined/topic/name/hello -mqtt.sn.predefined.topic.2 = /predefined/topic/name/nice +mqtt.sn.predefined.topic.1 = "/predefined/topic/name/hello" +mqtt.sn.predefined.topic.2 = "/predefined/topic/name/nice" ## Default username for MQTT-SN. This parameter is optional. If specified, ## emq-sn will connect EMQ core with this username. It is useful if any auth diff --git a/apps/emqx_sn/priv/emqx_sn.schema b/apps/emqx_sn/priv/emqx_sn.schema index a585c1037..edc76db37 100644 --- a/apps/emqx_sn/priv/emqx_sn.schema +++ b/apps/emqx_sn/priv/emqx_sn.schema @@ -1,23 +1,19 @@ %%-*- mode: erlang -*- %% emqx_sn config mapping {mapping, "mqtt.sn.port", "emqx_sn.port", [ - {default, "1884"}, - {datatype, string} + {default, 1884}, + {datatype, [integer, ip]} ]}. {translation, "emqx_sn.port", fun(Conf) -> - case re:split(cuttlefish:conf_get("mqtt.sn.port", Conf, ""), ":", [{return, list}]) of - [Port] -> - {{0,0,0,0}, list_to_integer(Port)}; - Tokens -> - Port = lists:last(Tokens), - IP = case inet:parse_address(lists:flatten(lists:join(":", Tokens -- [Port]))) of - {error, Reason} -> - throw({invalid_ip_address, Reason}); - {ok, X} -> X - end, - Port1 = list_to_integer(Port), - {IP, Port1} + case cuttlefish:conf_get("mqtt.sn.port", Conf, undefined) of + Port when is_integer(Port) -> + {{0,0,0,0}, Port}; + {Ip, Port} -> + case inet:parse_address(Ip) of + {ok ,R} -> {R, Port}; + _ -> {Ip, Port} + end end end}. diff --git a/apps/emqx_sn/rebar.config b/apps/emqx_sn/rebar.config index 5fecbb815..cbdac78f6 100644 --- a/apps/emqx_sn/rebar.config +++ b/apps/emqx_sn/rebar.config @@ -2,8 +2,7 @@ {plugins, [rebar3_proper]}. {deps, - [{esockd, {git, "https://github.com/emqx/esockd", {tag, "5.7.4"}}}, - {cuttlefish, {git, "https://github.com/emqx/cuttlefish", {tag, "v3.0.0"}}} + [{esockd, {git, "https://github.com/emqx/esockd", {tag, "5.7.4"}}} ]}. {edoc_opts, [{preprocess, true}]}. diff --git a/apps/emqx_stomp/etc/emqx_stomp.conf b/apps/emqx_stomp/etc/emqx_stomp.conf index b404e99ee..832b50655 100644 --- a/apps/emqx_stomp/etc/emqx_stomp.conf +++ b/apps/emqx_stomp/etc/emqx_stomp.conf @@ -8,7 +8,7 @@ ## The Port that stomp listener will bind. ## ## Value: Port -stomp.listener = 61613 +stomp.listener.port = 61613 ## The acceptor pool for stomp listener. ## @@ -28,22 +28,22 @@ stomp.listener.max_connections = 512 ## Path to the file containing the user's private PEM-encoded key. ## ## Value: File -## stomp.listener.keyfile = etc/certs/key.pem +## stomp.listener.keyfile = "etc/certs/key.pem" ## Path to a file containing the user certificate. ## ## Value: File -## stomp.listener.certfile = etc/certs/cert.pem +## stomp.listener.certfile = "etc/certs/cert.pem" ## Path to the file containing PEM-encoded CA certificates. ## ## Value: File -## stomp.listener.cacertfile = etc/certs/cacert.pem +## stomp.listener.cacertfile = "etc/certs/cacert.pem" ## See: 'listener.ssl..dhfile' in emq.conf ## ## Value: File -## stomp.listener.dhfile = etc/certs/dh-params.pem +## stomp.listener.dhfile = "etc/certs/dh-params.pem" ## See: 'listener.ssl..verify' in emq.conf ## @@ -59,7 +59,7 @@ stomp.listener.max_connections = 512 ## ## Value: String, seperated by ',' ## NOTE: Do not use tlsv1.3 if emqx is running on OTP-22 or earlier -## stomp.listener.tls_versions = tlsv1.3,tlsv1.2,tlsv1.1,tlsv1 +## stomp.listener.tls_versions = "tlsv1.3,tlsv1.2,tlsv1.1,tlsv1" ## SSL Handshake timeout. ## @@ -69,7 +69,7 @@ stomp.listener.max_connections = 512 ## See: 'listener.ssl..ciphers' in emq.conf ## ## Value: Ciphers -## stomp.listener.ciphers = TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA +## stomp.listener.ciphers = "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA" ## See: 'listener.ssl..secure_renegotiate' in emq.conf ## diff --git a/apps/emqx_stomp/priv/emqx_stomp.schema b/apps/emqx_stomp/priv/emqx_stomp.schema index c77cc297e..32a3c272b 100644 --- a/apps/emqx_stomp/priv/emqx_stomp.schema +++ b/apps/emqx_stomp/priv/emqx_stomp.schema @@ -1,7 +1,7 @@ %%-*- mode: erlang -*- %% emqx_stomp config mapping -{mapping, "stomp.listener", "emqx_stomp.listener", [ +{mapping, "stomp.listener.port", "emqx_stomp.listener", [ {default, 61613}, {datatype, [integer, ip]} ]}. @@ -72,7 +72,7 @@ ]}. {translation, "emqx_stomp.listener", fun(Conf) -> - Port = cuttlefish:conf_get("stomp.listener", Conf), + Port = cuttlefish:conf_get("stomp.listener.port", Conf), Acceptors = cuttlefish:conf_get("stomp.listener.acceptors", Conf), MaxConnections = cuttlefish:conf_get("stomp.listener.max_connections", Conf), Filter = fun(Opts) -> [{K, V} || {K, V} <- Opts, V =/= undefined] end, diff --git a/apps/emqx_web_hook/etc/emqx_web_hook.conf b/apps/emqx_web_hook/etc/emqx_web_hook.conf index 7b9d32dfb..6707e4673 100644 --- a/apps/emqx_web_hook/etc/emqx_web_hook.conf +++ b/apps/emqx_web_hook/etc/emqx_web_hook.conf @@ -5,16 +5,16 @@ ## Webhook URL ## ## Value: String -web.hook.url = http://127.0.0.1:80 +web.hook.url = "http://127.0.0.1:80" ## HTTP Headers ## ## Example: -## 1. web.hook.headers.content-type = application/json -## 2. web.hook.headers.accept = * +## 1. web.hook.headers.content-type = "application/json" +## 2. web.hook.headers.accept = "*" ## ## Value: String -web.hook.headers.content-type = application/json +web.hook.headers.content-type = "application/json" ## The encoding format of the payload field in the HTTP body ## The payload field only appears in the on_message_publish and on_message_delivered actions @@ -63,15 +63,15 @@ web.hook.pool_size = 32 ## ## Format: ## web.hook.rule.. = -#web.hook.rule.client.connect.1 = {"action": "on_client_connect"} -#web.hook.rule.client.connack.1 = {"action": "on_client_connack"} -#web.hook.rule.client.connected.1 = {"action": "on_client_connected"} -#web.hook.rule.client.disconnected.1 = {"action": "on_client_disconnected"} -#web.hook.rule.client.subscribe.1 = {"action": "on_client_subscribe"} -#web.hook.rule.client.unsubscribe.1 = {"action": "on_client_unsubscribe"} -#web.hook.rule.session.subscribed.1 = {"action": "on_session_subscribed"} -#web.hook.rule.session.unsubscribed.1 = {"action": "on_session_unsubscribed"} -#web.hook.rule.session.terminated.1 = {"action": "on_session_terminated"} -#web.hook.rule.message.publish.1 = {"action": "on_message_publish"} -#web.hook.rule.message.delivered.1 = {"action": "on_message_delivered"} -#web.hook.rule.message.acked.1 = {"action": "on_message_acked"} +#web.hook.rule.client.connect.1 = "{"action": "on_client_connect"}" +#web.hook.rule.client.connack.1 = "{"action": "on_client_connack"}" +#web.hook.rule.client.connected.1 = "{"action": "on_client_connected"}" +#web.hook.rule.client.disconnected.1 = "{"action": "on_client_disconnected"}" +#web.hook.rule.client.subscribe.1 = "{"action": "on_client_subscribe"}" +#web.hook.rule.client.unsubscribe.1 = "{"action": "on_client_unsubscribe"}" +#web.hook.rule.session.subscribed.1 = "{"action": "on_session_subscribed"}" +#web.hook.rule.session.unsubscribed.1 = "{"action": "on_session_unsubscribed"}" +#web.hook.rule.session.terminated.1 = "{"action": "on_session_terminated"}" +#web.hook.rule.message.publish.1 = "{"action": "on_message_publish"}" +#web.hook.rule.message.delivered.1 = "{"action": "on_message_delivered"}" +#web.hook.rule.message.acked.1 = ""{"action": "on_message_acked"}" diff --git a/apps/emqx_web_hook/rebar.config b/apps/emqx_web_hook/rebar.config index 5a1c77868..387972c9f 100644 --- a/apps/emqx_web_hook/rebar.config +++ b/apps/emqx_web_hook/rebar.config @@ -15,4 +15,4 @@ warnings_as_errors, deprecated_functions]}. {cover_enabled, true}. {cover_opts, [verbose]}. -{cover_export_enabled, true}. +{cover_export_enabled, true}. \ No newline at end of file diff --git a/data/loaded_plugins.tmpl b/data/loaded_plugins.tmpl index d0dac7fe1..236eeaa95 100644 --- a/data/loaded_plugins.tmpl +++ b/data/loaded_plugins.tmpl @@ -5,4 +5,5 @@ {emqx_retainer, {{enable_plugin_emqx_retainer}}}. {emqx_telemetry, {{enable_plugin_emqx_telemetry}}}. {emqx_rule_engine, {{enable_plugin_emqx_rule_engine}}}. +{emqx_resource, {{enable_plugin_emqx_resource}}}. {emqx_bridge_mqtt, {{enable_plugin_emqx_bridge_mqtt}}}. diff --git a/etc/emqx.conf b/etc/emqx.conf index bfd9674e8..65d8ec58b 100644 --- a/etc/emqx.conf +++ b/etc/emqx.conf @@ -56,7 +56,7 @@ cluster.autoclean = 5m ## Node list of the cluster. ## ## Value: String -## cluster.static.seeds = emqx1@127.0.0.1,emqx2@127.0.0.1 +## cluster.static.seeds = "emqx1@127.0.0.1,emqx2@127.0.0.1" ##-------------------------------------------------------------------- ## Cluster using IP Multicast. @@ -64,19 +64,19 @@ cluster.autoclean = 5m ## IP Multicast Address. ## ## Value: IP Address -## cluster.mcast.addr = 239.192.0.1 +## cluster.mcast.addr = "239.192.0.1" ## Multicast Ports. ## ## Value: Port List -## cluster.mcast.ports = 4369,4370 +## cluster.mcast.ports = "4369,4370" ## Multicast Iface. ## ## Value: Iface Address ## -## Default: 0.0.0.0 -## cluster.mcast.iface = 0.0.0.0 +## Default: "0.0.0.0" +## cluster.mcast.iface = "0.0.0.0" ## Multicast Ttl. ## @@ -107,7 +107,14 @@ cluster.autoclean = 5m ## Etcd server list, seperated by ','. ## ## Value: String -## cluster.etcd.server = http://127.0.0.1:2379 +## cluster.etcd.server = "http://127.0.0.1:2379" + +## Etcd api version +## +## Value: Enum +## - v2 +## - v3 +## cluster.etcd.version = v3 ## The prefix helps build nodes path in etcd. Each node in the cluster ## will create a path in etcd: v2/keys/// @@ -125,18 +132,18 @@ cluster.autoclean = 5m ## Path to a file containing the client's private PEM-encoded key. ## ## Value: File -## cluster.etcd.ssl.keyfile = {{ platform_etc_dir }}/certs/client-key.pem +## cluster.etcd.ssl.keyfile = "{{ platform_etc_dir }}/certs/client-key.pem" ## The path to a file containing the client's certificate. ## ## Value: File -## cluster.etcd.ssl.certfile = {{ platform_etc_dir }}/certs/client.pem +## cluster.etcd.ssl.certfile = "{{ platform_etc_dir }}/certs/client.pem" ## Path to the file containing PEM-encoded CA certificates. The CA certificates ## are used during server authentication and when building the client certificate chain. ## ## Value: File -## cluster.etcd.ssl.cacertfile = {{ platform_etc_dir }}/certs/ca.pem +## cluster.etcd.ssl.cacertfile = "{{ platform_etc_dir }}/certs/ca.pem" ##-------------------------------------------------------------------- ## Cluster using Kubernetes @@ -144,7 +151,7 @@ cluster.autoclean = 5m ## Kubernetes API server list, seperated by ','. ## ## Value: String -## cluster.k8s.apiserver = http://10.110.111.204:8080 +## cluster.k8s.apiserver = "http://10.110.111.204:8080" ## The service name helps lookup EMQ nodes in the cluster. ## @@ -184,17 +191,17 @@ cluster.autoclean = 5m ## Value: @ ## ## Default: emqx@127.0.0.1 -node.name = emqx@127.0.0.1 +node.name = "emqx@127.0.0.1" ## Cookie for distributed node communication. ## ## Value: String -node.cookie = emqxsecretcookie +node.cookie = "emqxsecretcookie" ## Data dir for the node ## ## Value: Folder -node.data_dir = {{ platform_data_dir }} +node.data_dir = "{{ platform_data_dir }}" ## Heartbeat monitoring of an Erlang runtime system. Comment the line to disable ## heartbeat, or set the value as 'on' @@ -271,14 +278,14 @@ node.global_gc_interval = 15m ## Crash dump log file. ## ## Value: Log file -node.crash_dump = {{ platform_log_dir }}/crash.dump +node.crash_dump = "{{ platform_log_dir }}/crash.dump" ## Specify SSL Options in the file if using SSL for Erlang Distribution. ## ## Value: File ## ## vm.args: -ssl_dist_optfile -## node.ssl_dist_optfile = {{ platform_etc_dir }}/ssl_dist.conf +## node.ssl_dist_optfile = "{{ platform_etc_dir }}/ssl_dist.conf" ## Sets the net_kernel tick time. TickTime is specified in seconds. ## Notice that all communicating nodes are to have the same TickTime @@ -419,10 +426,17 @@ log.to = file ## Default: warning log.level = warning +## Timezone offset to display in logs +## Value: +## - "system" use system zone +## - "utc" for Universal Coordinated Time (UTC) +## - "+hh:mm" or "-hh:mm" for a specified offset +log.time_offset = system + ## The dir for log files. ## ## Value: Folder -log.dir = {{ platform_log_dir }} +log.dir = "{{ platform_log_dir }}" ## The log filename for logs of level specified in "log.level". ## @@ -453,7 +467,7 @@ log.file = emqx.log ## ## Value: on | off ## Default: on -log.rotation = on +log.rotation.enable = on ## Maximum size of each log file. ## @@ -572,7 +586,7 @@ log.rotation.count = 5 ## Value: MaxBurstCount,TimeWindow ## Default: disabled ## -#log.burst_limit = 20000, 1s +#log.burst_limit = "20000, 1s" ## CONFIG_SECTION_END=logger =================================================== @@ -594,7 +608,7 @@ acl_nomatch = allow ## Default ACL File. ## ## Value: File Name -acl_file = {{ platform_etc_dir }}/acl.conf +acl_file = "{{ platform_etc_dir }}/acl.conf" ## Whether to enable ACL cache. ## @@ -628,7 +642,7 @@ acl_deny_action = ignore ## 3. banned interval: the banned interval if a flapping is detected. ## ## Value: Integer,Duration,Duration -flapping_detect_policy = 30, 1m, 5m +flapping_detect_policy = "30, 1m, 5m" ##-------------------------------------------------------------------- ## MQTT Protocol @@ -725,7 +739,7 @@ zone.external.acl_deny_action = ignore ## messages | bytes passed through. ## ## Numbers delimited by `|'. Zero or negative is to disable. -zone.external.force_gc_policy = 16000|16MB +zone.external.force_gc_policy = "16000|16MB" ## Max message queue length and total heap size to force shutdown ## connection/session process. @@ -735,9 +749,9 @@ zone.external.force_gc_policy = 16000|16MB ## Numbers delimited by `|'. Zero or negative is to disable. ## ## Default: -## - 10000|64MB on ARCH_64 system -## - 1000|32MB on ARCH_32 sytem -#zone.external.force_shutdown_policy = 10000|64MB +## - "10000|64MB" on ARCH_64 system +## - "1000|32MB" on ARCH_32 sytem +#zone.external.force_shutdown_policy = "10000|64MB" ## Maximum MQTT packet size allowed. ## @@ -843,7 +857,7 @@ zone.external.max_mqueue_len = 1000 ## are treated equal ## ## Priority number [1-255] -## Example: topic/1=10,topic/2=8 +## Example: "topic/1=10,topic/2=8" ## NOTE: comma and equal signs are not allowed for priority topic names ## NOTE: messages for topics not in the priority table are treated as ## either highest or lowest priority depending on the configured @@ -870,13 +884,13 @@ zone.external.enable_flapping_detect = off ## ## Value: Number,Duration ## Example: 100 messages per 10 seconds. -#zone.external.rate_limit.conn_messages_in = 100,10s +#zone.external.rate_limit.conn_messages_in = "100,10s" ## Bytes limit for a external MQTT connections. ## ## Value: Number,Duration ## Example: 100KB incoming per 10 seconds. -#zone.external.rate_limit.conn_bytes_in = 100KB,10s +#zone.external.rate_limit.conn_bytes_in = "100KB,10s" ## Whether to alarm the congested connections. ## @@ -907,16 +921,16 @@ zone.external.enable_flapping_detect = off ## ## Value: Number, Duration ## -## Example: 100 messaegs per 1s -#zone.external.quota.conn_messages_routing = 100,1s +## Example: 100 messages per 1s +#zone.external.quota.conn_messages_routing = "100,1s" ## Messages quota for the all of external MQTT connections. ## This value consumed by the number of recipient on a message. ## ## Value: Number, Duration ## -## Example: 200000 messaegs per 1s -#zone.external.quota.overall_messages_routing = 200000,1s +## Example: 200000 messages per 1s +#zone.external.quota.overall_messages_routing = "200000,1s" ## All the topics will be prefixed with the mountpoint path if this option is enabled. ## @@ -925,7 +939,7 @@ zone.external.enable_flapping_detect = off ## - %u: username ## ## Value: String -## zone.external.mountpoint = devicebound/ +## zone.external.mountpoint = "devicebound/" ## Whether use username replace client id ## @@ -970,7 +984,7 @@ zone.internal.enable_acl = off zone.internal.acl_deny_action = ignore ## See zone.$name.force_gc_policy -## zone.internal.force_gc_policy = 128000|128MB +## zone.internal.force_gc_policy = "128000|128MB" ## See zone.$name.wildcard_subscription. ## @@ -1015,8 +1029,8 @@ zone.internal.enable_flapping_detect = off ## See zone.$name.force_shutdown_policy ## ## Default: -## - 10000|64MB on ARCH_64 system -## - 1000|32MB on ARCH_32 sytem +## - "10000|64MB" on ARCH_64 system +## - "1000|32MB" on ARCH_32 sytem #zone.internal.force_shutdown_policy = 10000|64MB ## All the topics will be prefixed with the mountpoint path if this option is enabled. @@ -1026,7 +1040,7 @@ zone.internal.enable_flapping_detect = off ## - %u: username ## ## Value: String -## zone.internal.mountpoint = cloudbound/ +## zone.internal.mountpoint = "cloudbound/" ## Whether to ignore loop delivery of messages.(for mqtt v3.1.1) ## @@ -1060,8 +1074,8 @@ zone.internal.bypass_auth_plugins = true ## ## Value: IP:Port | Port ## -## Examples: 1883, 127.0.0.1:1883, ::1:1883 -listener.tcp.external = 0.0.0.0:1883 +## Examples: 1883, "127.0.0.1:1883", "::1:1883" +listener.tcp.external.endpoint = "0.0.0.0:1883" ## The acceptor pool for external MQTT/TCP listener. ## @@ -1096,8 +1110,8 @@ listener.tcp.external.zone = external ## ## Value: ACL Rule ## -## Example: allow 192.168.0.0/24 -listener.tcp.external.access.1 = allow all +## Example: "allow 192.168.0.0/24" +listener.tcp.external.access.1 = "allow all" ## Enable the Proxy Protocol V1/2 if the EMQ X cluster is deployed ## behind HAProxy or Nginx. @@ -1200,8 +1214,8 @@ listener.tcp.external.reuseaddr = true ## ## Value: IP:Port, Port ## -## Examples: 11883, 127.0.0.1:11883, ::1:11883 -listener.tcp.internal = 127.0.0.1:11883 +## Examples: 11883, "127.0.0.1:11883", "::1:11883" +listener.tcp.internal.endpoint = "127.0.0.1:11883" ## The acceptor pool for internal MQTT/TCP listener. ## @@ -1297,8 +1311,8 @@ listener.tcp.internal.reuseaddr = true ## ## Value: IP:Port | Port ## -## Examples: 8883, 127.0.0.1:8883, ::1:8883 -listener.ssl.external = 8883 +## Examples: 8883, "127.0.0.1:8883", "::1:8883" +listener.ssl.external.endpoint = 8883 ## The acceptor pool for external MQTT/SSL listener. ## @@ -1330,7 +1344,7 @@ listener.ssl.external.zone = external ## See: listener.tcp.$name.access ## ## Value: ACL Rule -listener.ssl.external.access.1 = allow all +listener.ssl.external.access.1 = "allow all" ## Enable the Proxy Protocol V1/2 if the EMQ cluster is deployed behind ## HAProxy or Nginx. @@ -1353,7 +1367,7 @@ listener.ssl.external.access.1 = allow all ## ## Value: String, seperated by ',' ## NOTE: Do not use tlsv1.3 if emqx is running on OTP-22 or earlier -## listener.ssl.external.tls_versions = tlsv1.3,tlsv1.2,tlsv1.1,tlsv1 +## listener.ssl.external.tls_versions = "tlsv1.3,tlsv1.2,tlsv1.1,tlsv1" ## TLS Handshake timeout. ## @@ -1377,20 +1391,20 @@ listener.ssl.external.handshake_timeout = 15s ## See: http://erlang.org/doc/man/ssl.html ## ## Value: File -listener.ssl.external.keyfile = {{ platform_etc_dir }}/certs/key.pem +listener.ssl.external.keyfile = "{{ platform_etc_dir }}/certs/key.pem" ## Path to a file containing the user certificate. ## ## See: http://erlang.org/doc/man/ssl.html ## ## Value: File -listener.ssl.external.certfile = {{ platform_etc_dir }}/certs/cert.pem +listener.ssl.external.certfile = "{{ platform_etc_dir }}/certs/cert.pem" ## Path to the file containing PEM-encoded CA certificates. The CA certificates ## are used during server authentication and when building the client certificate chain. ## ## Value: File -## listener.ssl.external.cacertfile = {{ platform_etc_dir }}/certs/cacert.pem +## listener.ssl.external.cacertfile = "{{ platform_etc_dir }}/certs/cacert.pem" ## The Ephemeral Diffie-Helman key exchange is a very effective way of ## ensuring Forward Secrecy by exchanging a set of keys that never hit @@ -1407,7 +1421,7 @@ listener.ssl.external.certfile = {{ platform_etc_dir }}/certs/cert.pem ## openssl dhparam -out dh-params.pem 2048 ## ## Value: File -## listener.ssl.external.dhfile = {{ platform_etc_dir }}/certs/dh-params.pem +## listener.ssl.external.dhfile = "{{ platform_etc_dir }}/certs/dh-params.pem" ## A server only does x509-path validation in mode verify_peer, ## as it then sends a certificate request to the client (this @@ -1442,14 +1456,13 @@ listener.ssl.external.certfile = {{ platform_etc_dir }}/certs/cert.pem ## Most of it was copied from Mozilla’s Server Side TLS article ## ## Value: Ciphers -listener.ssl.external.ciphers = TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA - +listener.ssl.external.ciphers = "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA" ## Ciphers for TLS PSK. ## Note that 'listener.ssl.external.ciphers' and 'listener.ssl.external.psk_ciphers' cannot ## be configured at the same time. ## See 'https://tools.ietf.org/html/rfc4279#section-2'. -#listener.ssl.external.psk_ciphers = PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA +#listener.ssl.external.psk_ciphers = "PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA" ## SSL parameter renegotiation is a feature that allows a client and a server ## to renegotiate the parameters of the SSL connection on the fly. @@ -1559,13 +1572,13 @@ listener.ssl.external.reuseaddr = true ## ## Value: IP:Port | Port ## -## Examples: 8083, 127.0.0.1:8083, ::1:8083 -listener.ws.external = 8083 +## Examples: 8083, "127.0.0.1:8083", "::1:8083" +listener.ws.external.endpoint = 8083 ## The path of WebSocket MQTT endpoint ## ## Value: URL Path -listener.ws.external.mqtt_path = /mqtt +listener.ws.external.mqtt_path = "/mqtt" ## The acceptor pool for external MQTT/WebSocket listener. ## @@ -1597,7 +1610,7 @@ listener.ws.external.zone = external ## See: listener.ws.$name.access ## ## Value: ACL Rule -listener.ws.external.access.1 = allow all +listener.ws.external.access.1 = "allow all" ## If set to true, the server fails if the client does not have a Sec-WebSocket-Protocol to send. ## Set to false for WeChat MiniApp. @@ -1608,7 +1621,7 @@ listener.ws.external.access.1 = allow all ## Supported subprotocols ## ## Default: mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5 -## listener.ws.external.supported_subprotocols = mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5 +## listener.ws.external.supported_subprotocols = "mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5" ## Specify which HTTP header for real source IP if the EMQ X cluster is ## deployed behind NGINX or HAProxy. @@ -1816,7 +1829,7 @@ listener.ws.external.allow_origin_absence = true ## Comma separated list of allowed origin in header for websocket connection ## ## Value: http://url eg. local http dashboard url - http://localhost:18083, http://127.0.0.1:18083 -listener.ws.external.check_origins = http://localhost:18083, http://127.0.0.1:18083 +listener.ws.external.check_origins = "http://localhost:18083, http://127.0.0.1:18083" ##-------------------------------------------------------------------- ## External WebSocket/SSL listener for MQTT Protocol @@ -1826,13 +1839,13 @@ listener.ws.external.check_origins = http://localhost:18083, http://127.0.0.1:18 ## ## Value: IP:Port | Port ## -## Examples: 8084, 127.0.0.1:8084, ::1:8084 -listener.wss.external = 8084 +## Examples: 8084, "127.0.0.1:8084", "::1:8084" +listener.wss.external.endpoint = 8084 ## The path of WebSocket MQTT endpoint ## ## Value: URL Path -listener.wss.external.mqtt_path = /mqtt +listener.wss.external.mqtt_path = "/mqtt" ## The acceptor pool for external MQTT/WebSocket/SSL listener. ## @@ -1866,7 +1879,7 @@ listener.wss.external.zone = external ## See: listener.tcp.$name.access. ## ## Value: ACL Rule -listener.wss.external.access.1 = allow all +listener.wss.external.access.1 = "allow all" ## If set to true, the server fails if the client does not have a Sec-WebSocket-Protocol to send. ## Set to false for WeChat MiniApp. @@ -1877,7 +1890,7 @@ listener.wss.external.access.1 = allow all ## Supported subprotocols ## ## Default: mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5 -## listener.wss.external.supported_subprotocols = mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5 +## listener.wss.external.supported_subprotocols = "mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5" ## Specify which HTTP header for real source IP if the EMQ X cluster is ## deployed behind NGINX or HAProxy. @@ -1911,28 +1924,28 @@ listener.wss.external.access.1 = allow all ## ## Value: String, seperated by ',' ## NOTE: Do not use tlsv1.3 if emqx is running on OTP-22 or earlier -## listener.wss.external.tls_versions = tlsv1.3,tlsv1.2,tlsv1.1,tlsv1 +## listener.wss.external.tls_versions = "tlsv1.3,tlsv1.2,tlsv1.1,tlsv1" ## Path to the file containing the user's private PEM-encoded key. ## ## See: listener.ssl.$name.keyfile ## ## Value: File -listener.wss.external.keyfile = {{ platform_etc_dir }}/certs/key.pem +listener.wss.external.keyfile = "{{ platform_etc_dir }}/certs/key.pem" ## Path to a file containing the user certificate. ## ## See: listener.ssl.$name.certfile ## ## Value: File -listener.wss.external.certfile = {{ platform_etc_dir }}/certs/cert.pem +listener.wss.external.certfile = "{{ platform_etc_dir }}/certs/cert.pem" ## Path to the file containing PEM-encoded CA certificates. ## ## See: listener.ssl.$name.cacert ## ## Value: File -## listener.wss.external.cacertfile = {{ platform_etc_dir }}/certs/cacert.pem +## listener.wss.external.cacertfile = "{{ platform_etc_dir }}/certs/cacert.pem" ## Maximum number of non-self-issued intermediate certificates that ## can follow the peer certificate in a valid certification path. @@ -1953,7 +1966,7 @@ listener.wss.external.certfile = {{ platform_etc_dir }}/certs/cert.pem ## See: listener.ssl.$name.dhfile ## ## Value: File -## listener.ssl.external.dhfile = {{ platform_etc_dir }}/certs/dh-params.pem +## listener.ssl.external.dhfile = "{{ platform_etc_dir }}/certs/dh-params.pem" ## See: listener.ssl.$name.verify ## @@ -1968,13 +1981,13 @@ listener.wss.external.certfile = {{ platform_etc_dir }}/certs/cert.pem ## See: listener.ssl.$name.ciphers ## ## Value: Ciphers -listener.wss.external.ciphers = TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA +listener.wss.external.ciphers = "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA" ## Ciphers for TLS PSK. ## Note that 'listener.wss.external.ciphers' and 'listener.wss.external.psk_ciphers' cannot ## be configured at the same time. ## See 'https://tools.ietf.org/html/rfc4279#section-2'. -## listener.wss.external.psk_ciphers = PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA +## listener.wss.external.psk_ciphers = "PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA" ## See: listener.ssl.$name.secure_renegotiate ## @@ -2133,7 +2146,7 @@ listener.wss.external.allow_origin_absence = true ## Comma separated list of allowed origin in header for secure websocket connection ## ## Value: http://url eg. https://localhost:8084, https://127.0.0.1:8084 -listener.wss.external.check_origins = https://localhost:8084, https://127.0.0.1:8084 +listener.wss.external.check_origins = "https://localhost:8084, https://127.0.0.1:8084" ## CONFIG_SECTION_END=listeners ================================================ @@ -2142,7 +2155,7 @@ listener.wss.external.check_origins = https://localhost:8084, https://127.0.0.1: ## The file to store loaded module names. ## ## Value: File -modules.loaded_file = {{ platform_data_dir }}/loaded_modules +modules.loaded_file = "{{ platform_data_dir }}/loaded_modules" ##-------------------------------------------------------------------- ## Presence Module @@ -2158,7 +2171,7 @@ module.presence.qos = 1 ## Subscribe the Topics automatically when client connected. ## ## Value: String -## module.subscription.1.topic = connected/%c/%u +## module.subscription.1.topic = "connected/%c/%u" ## Qos of the proxy subscription. ## @@ -2191,8 +2204,8 @@ module.presence.qos = 1 ## Rewrite Module ## {rewrite, Topic, Re, Dest} -## module.rewrite.pub.rule.1 = x/# ^x/y/(.+)$ z/y/$1 -## module.rewrite.sub.rule.1 = y/+/z/# ^y/(.+)/z/(.+)$ y/z/$2 +## module.rewrite.pub.rule.1 = "x/# ^x/y/(.+)$ z/y/$1" +## module.rewrite.sub.rule.1 = "y/+/z/# ^y/(.+)/z/(.+)$ y/z/$2" ## CONFIG_SECTION_END=modules ================================================== @@ -2203,17 +2216,17 @@ module.presence.qos = 1 ## The etc dir for plugins' config. ## ## Value: Folder -plugins.etc_dir = {{ platform_etc_dir }}/plugins/ +plugins.etc_dir = "{{ platform_etc_dir }}/plugins/" ## The file to store loaded plugin names. ## ## Value: File -plugins.loaded_file = {{ platform_data_dir }}/loaded_plugins +plugins.loaded_file = "{{ platform_data_dir }}/loaded_plugins" ## The directory of extension plugins. ## ## Value: File -plugins.expand_plugins_dir = {{ platform_plugins_dir }}/ +plugins.expand_plugins_dir = "{{ platform_plugins_dir }}/" ##-------------------------------------------------------------------- ## Broker @@ -2327,7 +2340,6 @@ sysmon.long_gc = 0 ## Examples: ## - 2h: 2 hours ## - 30m: 30 minutes -## - 0.1s: 0.1 seconds ## - 100ms: 100 milliseconds ## ## Default: 0ms @@ -2419,8 +2431,8 @@ vm_mon.process_low_watermark = 60% ## - log ## - publish ## -## Default: log,publish -alarm.actions = log,publish +## Default: "log,publish" +alarm.actions = "log,publish" ## The maximum number of deactivated alarms ## diff --git a/include/emqx_release.hrl b/include/emqx_release.hrl index 333ab0d50..e69b07558 100644 --- a/include/emqx_release.hrl +++ b/include/emqx_release.hrl @@ -29,7 +29,7 @@ -ifndef(EMQX_ENTERPRISE). --define(EMQX_RELEASE, {opensource, "4.3.2"}). +-define(EMQX_RELEASE, {opensource, "5.0-pre"}). -else. diff --git a/lib-ce/emqx_dashboard/etc/emqx_dashboard.conf b/lib-ce/emqx_dashboard/etc/emqx_dashboard.conf index 933b9885d..4f28ec84e 100644 --- a/lib-ce/emqx_dashboard/etc/emqx_dashboard.conf +++ b/lib-ce/emqx_dashboard/etc/emqx_dashboard.conf @@ -20,7 +20,7 @@ dashboard.default_user.password = public ## Value: Port ## ## Examples: 18083 -dashboard.listener.http = 18083 +dashboard.listener.http.port = 18083 ## The acceptor pool for external Dashboard HTTP listener. ## @@ -50,7 +50,7 @@ dashboard.listener.http.ipv6_v6only = false ## Value: Port ## ## Examples: 18084 -## dashboard.listener.https = 18084 +## dashboard.listener.https.port = 18084 ## The acceptor pool for external Dashboard HTTPS listener. ## @@ -75,22 +75,22 @@ dashboard.listener.http.ipv6_v6only = false ## Path to the file containing the user's private PEM-encoded key. ## ## Value: File -## dashboard.listener.https.keyfile = etc/certs/key.pem +## dashboard.listener.https.keyfile = "etc/certs/key.pem" ## Path to a file containing the user certificate. ## ## Value: File -## dashboard.listener.https.certfile = etc/certs/cert.pem +## dashboard.listener.https.certfile = "etc/certs/cert.pem" ## Path to the file containing PEM-encoded CA certificates. ## ## Value: File -## dashboard.listener.https.cacertfile = etc/certs/cacert.pem +## dashboard.listener.https.cacertfile = "etc/certs/cacert.pem" ## See: 'listener.ssl..dhfile' in emq.conf ## ## Value: File -## dashboard.listener.https.dhfile = {{ platform_etc_dir }}/certs/dh-params.pem +## dashboard.listener.https.dhfile = "{{ platform_etc_dir }}/certs/dh-params.pem" ## See: 'listener.ssl..verify' in emq.conf ## @@ -106,12 +106,12 @@ dashboard.listener.http.ipv6_v6only = false ## ## Value: String, seperated by ',' ## NOTE: Do not use tlsv1.3 if emqx is running on OTP-22 or earlier -## dashboard.listener.https.tls_versions = tlsv1.3,tlsv1.2,tlsv1.1,tlsv1 +## dashboard.listener.https.tls_versions = "tlsv1.3,tlsv1.2,tlsv1.1,tlsv1" ## See: 'listener.ssl..ciphers' in emq.conf ## ## Value: Ciphers -## dashboard.listener.https.ciphers = TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA +## dashboard.listener.https.ciphers = "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256,ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA" ## See: 'listener.ssl..secure_renegotiate' in emq.conf ## diff --git a/lib-ce/emqx_dashboard/priv/emqx_dashboard.schema b/lib-ce/emqx_dashboard/priv/emqx_dashboard.schema index d517a6e97..d485ecdf9 100644 --- a/lib-ce/emqx_dashboard/priv/emqx_dashboard.schema +++ b/lib-ce/emqx_dashboard/priv/emqx_dashboard.schema @@ -10,7 +10,7 @@ {override_env, "ADMIN_PASSWORD"} ]}. -{mapping, "dashboard.listener.http", "emqx_dashboard.listeners", [ +{mapping, "dashboard.listener.http.port", "emqx_dashboard.listeners", [ {datatype, integer} ]}. @@ -38,7 +38,7 @@ {datatype, {enum, [true, false]}} ]}. -{mapping, "dashboard.listener.https", "emqx_dashboard.listeners", [ +{mapping, "dashboard.listener.https.port", "emqx_dashboard.listeners", [ {datatype, integer} ]}. @@ -139,7 +139,7 @@ lists:map( fun(Proto) -> Prefix = "dashboard.listener." ++ atom_to_list(Proto), - case cuttlefish:conf_get(Prefix, Conf, undefined) of + case cuttlefish:conf_get(Prefix ++ ".port", Conf, undefined) of undefined -> []; Port -> [{Proto, Port, case Proto of diff --git a/lib-ce/emqx_telemetry/etc/emqx_telemetry.conf b/lib-ce/emqx_telemetry/etc/emqx_telemetry.conf index 041b54f60..326ef0508 100644 --- a/lib-ce/emqx_telemetry/etc/emqx_telemetry.conf +++ b/lib-ce/emqx_telemetry/etc/emqx_telemetry.conf @@ -13,8 +13,8 @@ telemetry.enabled = true ## ## Value: String ## -## Default: https://telemetry.emqx.io/api/telemetry -telemetry.url = https://telemetry.emqx.io/api/telemetry +## Default: "https://telemetry.emqx.io/api/telemetry" +telemetry.url = "https://telemetry.emqx.io/api/telemetry" ## Interval for reporting telemetry data ## @@ -25,4 +25,4 @@ telemetry.url = https://telemetry.emqx.io/api/telemetry ## -s: second ## ## Default: 7d -telemetry.report_interval = 7d \ No newline at end of file +telemetry.report_interval = 7d diff --git a/priv/emqx.schema b/priv/emqx.schema index 7c6bf0298..04970a150 100644 --- a/priv/emqx.schema +++ b/priv/emqx.schema @@ -103,6 +103,10 @@ {datatype, string} ]}. +{mapping, "cluster.etcd.version", "ekka.cluster_discovery", [ + {datatype, {enum, [v2, v3]}} +]}. + {mapping, "cluster.etcd.prefix", "ekka.cluster_discovery", [ {datatype, string} ]}. @@ -180,6 +184,7 @@ end, Options) end, [{server, string:tokens(cuttlefish:conf_get("cluster.etcd.server", Conf), ",")}, + {version, cuttlefish:conf_get("cluster.etcd.version", Conf, v3)}, {prefix, cuttlefish:conf_get("cluster.etcd.prefix", Conf, "emqcl")}, {node_ttl, cuttlefish:conf_get("cluster.etcd.node_ttl", Conf, 60)}, {ssl_options, SslOpts(Conf)}]; @@ -467,6 +472,15 @@ end}. {datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, all]}} ]}. +%% @doc Timezone offset to display in logs, +%% "system" use system time zone +%% "utc" for Universal Coordinated Time (UTC) +%% "+hh:mm" or "-hh:mm" for a specified offset +{mapping, "log.time_offset", "kernel.logger", [ + {default, "system"}, + {datatype, string} +]}. + {mapping, "log.primary_log_level", "kernel.logger_level", [ {default, warning}, {datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, all]}} @@ -512,7 +526,7 @@ end}. {datatype, {enum, [true, false]}} ]}. -{mapping, "log.rotation", "kernel.logger", [ +{mapping, "log.rotation.enable", "kernel.logger", [ {default, on}, {datatype, flag} ]}. @@ -600,7 +614,27 @@ end}. {translation, "kernel.logger", fun(Conf) -> LogTo = cuttlefish:conf_get("log.to", Conf), LogLevel = cuttlefish:conf_get("log.level", Conf), - LogType = case cuttlefish:conf_get("log.rotation", Conf) of + LogTimeoffset = + case cuttlefish:conf_get("log.time_offset", Conf) of + "system" -> ""; + "utc" -> "0"; + [S, H1, H2, $:, M1, M2] = HHMM -> + (S =:= $+ orelse S =:= $-) andalso + try + begin + H = list_to_integer([H1, H2]), + M = list_to_integer([M1, M2]), + H >=0 andalso H =< 14 andalso + M >= 0 andalso M =< 59 + end + catch + _ : _ -> + error({"invalid_log_time_offset", HHMM}) + end andalso HHMM; + Other -> + error({"invalid_log_time_offset", Other}) + end, + LogType = case cuttlefish:conf_get("log.rotation.enable", Conf) of true -> wrap; false -> halt end, @@ -1229,7 +1263,7 @@ end}. %%-------------------------------------------------------------------- %% TCP Listeners -{mapping, "listener.tcp.$name", "emqx.listeners", [ +{mapping, "listener.tcp.$name.endpoint", "emqx.listeners", [ {datatype, [integer, ip]} ]}. @@ -1336,7 +1370,7 @@ end}. %%-------------------------------------------------------------------- %% SSL Listeners -{mapping, "listener.ssl.$name", "emqx.listeners", [ +{mapping, "listener.ssl.$name.endpoint", "emqx.listeners", [ {datatype, [integer, ip]} ]}. @@ -1504,7 +1538,7 @@ end}. %%-------------------------------------------------------------------- %% MQTT/WebSocket Listeners -{mapping, "listener.ws.$name", "emqx.listeners", [ +{mapping, "listener.ws.$name.endpoint", "emqx.listeners", [ {datatype, [integer, ip]} ]}. @@ -1698,7 +1732,7 @@ end}. %%-------------------------------------------------------------------- %% MQTT/WebSocket/SSL Listeners -{mapping, "listener.wss.$name", "emqx.listeners", [ +{mapping, "listener.wss.$name.endpoint", "emqx.listeners", [ {datatype, [integer, ip]} ]}. @@ -1954,7 +1988,6 @@ end}. ]}. {translation, "emqx.listeners", fun(Conf) -> - Filter = fun(Opts) -> [{K, V} || {K, V} <- Opts, V =/= undefined] end, Atom = fun(undefined) -> undefined; (S) -> list_to_atom(S) end, @@ -2103,7 +2136,7 @@ end}. TcpListeners = fun(Type, Name) -> Prefix = string:join(["listener", Type, Name], "."), - ListenOnN = case cuttlefish:conf_get(Prefix, Conf, undefined) of + ListenOnN = case cuttlefish:conf_get(Prefix ++ ".endpoint", Conf, undefined) of undefined -> []; ListenOn -> Listen_fix(ListenOn) end, @@ -2119,7 +2152,7 @@ end}. end, SslListeners = fun(Type, Name) -> Prefix = string:join(["listener", Type, Name], "."), - case cuttlefish:conf_get(Prefix, Conf, undefined) of + case cuttlefish:conf_get(Prefix ++ ".endpoint", Conf, undefined) of undefined -> []; ListenOn -> @@ -2135,12 +2168,11 @@ end}. ] end end, - - lists:flatten([TcpListeners(Type, Name) || {["listener", Type, Name], ListenOn} + lists:flatten([TcpListeners(Type, Name) || {["listener", Type, Name, "endpoint"], ListenOn} <- cuttlefish_variable:filter_by_prefix("listener.tcp", Conf) ++ cuttlefish_variable:filter_by_prefix("listener.ws", Conf)] ++ - [SslListeners(Type, Name) || {["listener", Type, Name], ListenOn} + [SslListeners(Type, Name) || {["listener", Type, Name, "endpoint"], ListenOn} <- cuttlefish_variable:filter_by_prefix("listener.ssl", Conf) ++ cuttlefish_variable:filter_by_prefix("listener.wss", Conf)]) end}. diff --git a/rebar.config b/rebar.config index b62b8055b..ec78316bc 100644 --- a/rebar.config +++ b/rebar.config @@ -43,9 +43,9 @@ , {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}} , {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.8.2"}}} , {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.0"}}} - , {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.8.1"}}} + , {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.9.0"}}} , {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}} - , {cuttlefish, {git, "https://github.com/emqx/cuttlefish", {tag, "v3.3.5"}}} + , {cuttlefish, {git, "https://github.com/emqx/cuttlefish", {tag, "v4.0.0"}}} , {minirest, {git, "https://github.com/emqx/minirest", {tag, "0.3.5"}}} , {ecpool, {git, "https://github.com/emqx/ecpool", {tag, "0.5.1"}}} , {replayq, {git, "https://github.com/emqx/replayq", {tag, "0.3.2"}}} diff --git a/rebar.config.erl b/rebar.config.erl index 5901ab706..8ac16ebbb 100644 --- a/rebar.config.erl +++ b/rebar.config.erl @@ -106,7 +106,7 @@ test_plugins() -> test_deps() -> [ {bbmustache, "1.10.0"} - , {emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "1.3.9"}}} + , {emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {branch, "hocon"}}} , meck ]. @@ -188,6 +188,7 @@ overlay_vars_rel(RelType) -> end, [ {enable_plugin_emqx_rule_engine, RelType =:= cloud} , {enable_plugin_emqx_bridge_mqtt, RelType =:= edge} + , {enable_plugin_emqx_resource, true} , {enable_plugin_emqx_modules, false} %% modules is not a plugin in ce , {enable_plugin_emqx_recon, true} , {enable_plugin_emqx_retainer, true} @@ -281,6 +282,7 @@ relx_plugin_apps(ReleaseType) -> , emqx_auth_mnesia , emqx_web_hook , emqx_recon + , emqx_resource , emqx_rule_engine , emqx_sasl ] diff --git a/test/emqx_listeners_SUITE.erl b/test/emqx_listeners_SUITE.erl index f49d33004..673cc80e9 100644 --- a/test/emqx_listeners_SUITE.erl +++ b/test/emqx_listeners_SUITE.erl @@ -67,14 +67,16 @@ mustache_vars() -> generate_config() -> Schema = cuttlefish_schema:files([local_path(["priv", "emqx.schema"])]), ConfFile = render_config_file(), - Conf = conf_parse:file(ConfFile), + {ok, Conf} = hocon:load(ConfFile, #{format => proplists}), cuttlefish_generator:map(Schema, Conf). set_app_env({App, Lists}) -> lists:foreach(fun({acl_file, _Var}) -> application:set_env(App, acl_file, local_path(["etc", "acl.conf"])); ({plugins_loaded_file, _Var}) -> - application:set_env(App, plugins_loaded_file, local_path(["test", "emqx_SUITE_data","loaded_plugins"])); + application:set_env(App, + plugins_loaded_file, + local_path(["test", "emqx_SUITE_data","loaded_plugins"])); ({Par, Var}) -> application:set_env(App, Par, Var) end, Lists). @@ -91,4 +93,4 @@ get_base_dir(Module) -> get_base_dir() -> get_base_dir(?MODULE). - +