Merge branch 'dev/v5.0' into resolve-master-dev/v5.0-conflict-release-version

This commit is contained in:
Zaiming (Stone) Shi 2021-06-05 11:51:02 +02:00 committed by GitHub
commit 5dab6985c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
96 changed files with 3311 additions and 497 deletions

View File

@ -11,3 +11,4 @@ EMQX_AUTH__PGSQL__DATABASE=mqtt
EMQX_AUTH__REDIS__SERVER=redis_server:6379
EMQX_AUTH__REDIS__PASSWORD=public
CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_
HOCON_ENV_OVERRIDE_PREFIX=EMQX_

View File

@ -86,7 +86,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
@ -98,7 +98,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: |
@ -160,10 +160,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
@ -179,7 +179,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'
@ -239,7 +239,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
@ -251,7 +251,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: |
@ -318,7 +318,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
@ -330,7 +330,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)

View File

@ -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: <K1>=<V1>,<K2>=<V2>,...
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: <K1>=<V1>,<K2>=<V2>,...
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: <K1>=<V1>,<K2>=<V2>,...
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.

View File

@ -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"}}}
]}
]}

View File

@ -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"

View File

@ -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(

View File

@ -20,6 +20,6 @@
{profiles,
[{test,
[{deps, [{emqx_ct_helpers, {git, "http://github.com/emqx/emqx-ct-helpers", {tag, "1.2.2"}}}]}
[{deps, []}
]}
]}.

View File

@ -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

View File

@ -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

View File

@ -4,7 +4,7 @@
{profiles,
[{test,
[{deps, [{emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "1.2.2"}}}]}
[{deps, []}
]}
]}.

View File

@ -1,6 +1,6 @@
{application, emqx_auth_ldap,
[{description, "EMQ X Authentication/ACL with LDAP"},
{vsn, "4.3.0"}, % strict semver, bump manually!
{vsn, "4.4.0"}, % strict semver, bump manually!
{modules, []},
{registered, [emqx_auth_ldap_sup]},
{applications, [kernel,stdlib,eldap2,ecpool]},

View File

@ -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~!@#$%^&*()_+"

View File

@ -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"

View File

@ -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 -> []

View File

@ -1,6 +1,6 @@
{application, emqx_auth_mongo,
[{description, "EMQ X Authentication/ACL with MongoDB"},
{vsn, "4.3.0"}, % strict semver, bump manually!
{vsn, "4.4.0"}, % strict semver, bump manually!
{modules, []},
{registered, [emqx_auth_mongo_sup]},
{applications, [kernel,stdlib,mongodb,ecpool]},

View File

@ -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.
##

View File

@ -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(

View File

@ -1,6 +1,6 @@
{application, emqx_auth_mysql,
[{description, "EMQ X Authentication/ACL with MySQL"},
{vsn, "4.3.0"}, % strict semver, bump manually!
{vsn, "4.4.0"}, % strict semver, bump manually!
{modules, []},
{registered, [emqx_auth_mysql_sup]},
{applications, [kernel,stdlib,mysql,ecpool]},

View File

@ -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.
##

View File

@ -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'"

View File

@ -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 -> []

View File

@ -1,6 +1,6 @@
{application, emqx_auth_pgsql,
[{description, "EMQ X Authentication/ACL with PostgreSQL"},
{vsn, "4.3.0"}, % strict semver, bump manually!
{vsn, "4.4.0"}, % strict semver, bump manually!
{modules, []},
{registered, [emqx_auth_pgsql_sup]},
{applications, [kernel,stdlib,epgsql,ecpool]},

View File

@ -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.

View File

@ -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 ->

View File

@ -1,6 +1,6 @@
{application, emqx_auth_redis,
[{description, "EMQ X Authentication/ACL with Redis"},
{vsn, "4.3.0"}, % strict semver, bump manually!
{vsn, "4.4.0"}, % strict semver, bump manually!
{modules, []},
{registered, [emqx_auth_redis_sup]},
{applications, [kernel,stdlib,eredis,eredis_cluster,ecpool]},

View File

@ -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

View File

@ -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

View File

@ -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
##

View File

@ -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 Mozillas 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"

View File

@ -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"

View File

@ -43,7 +43,6 @@
{profiles,
[{test,
[{deps,
[{emqx_ct_helper, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "v1.3.1"}}}
]}
[]}
]}
]}.

View File

@ -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: <tcp|ssl|udp|dtls>://<ip>:<port>
##
## 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 Mozillas 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.

View File

@ -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}.

View File

@ -46,7 +46,6 @@
{profiles,
[{test,
[{deps,
[{emqx_ct_helper, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "v1.3.0"}}}
]}
[]}
]}
]}.

View File

@ -1,6 +1,6 @@
{application, emqx_exproto,
[{description, "EMQ X Extension for Protocol"},
{vsn, "4.3.0"}, %% strict semver
{vsn, "4.4.0"}, %% strict semver
{modules, []},
{registered, []},
{mod, {emqx_exproto_app, []}},

View File

@ -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 Mozillas 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"

View File

@ -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"}}}
]}
]}

View File

@ -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

View File

@ -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)

View File

@ -1,6 +1,6 @@
{application, emqx_management,
[{description, "EMQ X Management API and CLI"},
{vsn, "4.3.3"}, % strict semver, bump manually!
{vsn, "4.4.0"}, % strict semver, bump manually!
{modules, []},
{registered, [emqx_management_sup]},
{applications, [kernel,stdlib,minirest]},

View File

@ -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).
timer:sleep(500).

View File

@ -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)
##

View File

@ -1,2 +1,2 @@
psk.file.path = {{ platform_etc_dir }}/psk.txt
psk.file.delimiter = :
psk.file.path = "{{ platform_etc_dir }}/psk.txt"
psk.file.delimiter = ":"

View File

@ -1,6 +1,6 @@
{application, emqx_recon,
[{description, "EMQ X Recon Plugin"},
{vsn, "4.3.0"}, % strict semver, bump manually!
{vsn, "4.4.0"}, % strict semver, bump manually!
{modules, []},
{registered, []},
{applications, [kernel,stdlib,recon]},

View File

@ -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

View File

@ -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}}

6
apps/emqx_resource/demo.sh Executable file
View File

@ -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

View File

@ -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}}]}
]}]}].

View File

@ -0,0 +1,3 @@
##--------------------------------------------------------------------
## EMQ X Resource Plugin
##--------------------------------------------------------------------

View File

@ -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).

View File

@ -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
---
<!-- _class: lead -->
# 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)
---
<!-- _class: lead -->
# 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").
```
---
<!-- _class: lead -->
# 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 .
```

View File

@ -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
}
}

View File

@ -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).

View File

@ -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.

View File

@ -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() :: 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()]}.

View File

@ -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}).

View File

@ -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()).

View File

@ -0,0 +1,2 @@
%%-*- mode: erlang -*-
%% emqx-resource config mapping

View File

@ -0,0 +1,17 @@
{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, [ {jsx, {git, "https://github.com/talentdeficit/jsx", {tag, "v3.1.0"}}}
]}.

View File

@ -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

View File

@ -0,0 +1,18 @@
{application, emqx_resource,
[{description, "An OTP application"},
{vsn, "0.1.0"},
{registered, []},
{mod, {emqx_resource_app, []}},
{applications,
[kernel,
stdlib,
gproc,
hocon,
jsx
]},
{env,[]},
{modules, []},
{licenses, ["Apache 2.0"]},
{links, []}
]}.

View File

@ -0,0 +1,310 @@
%%--------------------------------------------------------------------
%% 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
, call_jsonify/2
, call_api_reply_format/2
]).
-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_from_dir/1 %% load instances from a directory
, load_instance_from_file/1 %% load an instance from a config file
, load_instance_from_config/1 %% load an instance from a map or json-string config
% , 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_config_merge/3
, on_jsonify/1
, on_api_reply_format/1
]).
-callback on_api_reply_format(resource_data()) -> jsx:json_term().
-callback on_config_merge(resource_config(), resource_config(), term()) -> resource_config().
-callback on_jsonify(resource_config()) -> jsx:json_term().
%% 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_from_dir(Dir :: string()) -> ok.
load_instances_from_dir(Dir) ->
emqx_resource_instance:load_dir(Dir).
-spec load_instance_from_file(File :: string()) -> ok.
load_instance_from_file(File) ->
emqx_resource_instance:load_file(File).
-spec load_instance_from_config(binary() | map()) -> {ok, resource_data()} | {error, term()}.
load_instance_from_config(Config) ->
emqx_resource_instance:load_config(Config).
-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) ->
case erlang:function_exported(Mod, on_jsonify, 1) of
true ->
?SAFE_CALL(Mod:on_config_merge(OldConfig, NewConfig, Params));
false when is_map(OldConfig), is_map(NewConfig) ->
maps:merge(OldConfig, NewConfig);
false ->
NewConfig
end.
-spec call_jsonify(module(), resource_config()) -> jsx:json_term().
call_jsonify(Mod, Config) ->
case erlang:function_exported(Mod, on_jsonify, 1) of
false -> Config;
true -> ?SAFE_CALL(Mod:on_jsonify(Config))
end.
-spec call_api_reply_format(module(), resource_data()) -> jsx:json_term().
call_api_reply_format(Mod, Data) ->
case erlang:function_exported(Mod, on_api_reply_format, 1) of
false -> emqx_resource_api:default_api_reply_format(Data);
true -> ?SAFE_CALL(Mod:on_api_reply_format(Data))
end.
-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, {resource_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.

View File

@ -0,0 +1,78 @@
%%--------------------------------------------------------------------
%% 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
]).
-export([default_api_reply_format/1]).
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) ->
emqx_resource:call_api_reply_format(Mod, Data).
default_api_reply_format(#{id := Id, mod := Mod, status := Status, config := Config}) ->
#{node => node(), id => Id, status => Status, resource_type => Mod,
config => emqx_resource:call_jsonify(Mod, 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])).

View File

@ -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

View File

@ -0,0 +1,315 @@
%%--------------------------------------------------------------------
%% 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_dir/1
, load_file/1
, load_config/1
, lookup/1
, list_all/0
, lookup_by_type/1
, create_local/3
]).
-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(Dir :: string()) -> ok.
load_dir(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 load_config(RawConfig) of
{ok, Data} ->
logger:debug("loaded resource instance from file: ~p, data: ~p",
[File, Data]);
{error, Reason} ->
logger:error("load resource from ~p failed: ~p", [File, Reason])
end
end.
-spec load_config(binary() | map()) -> {ok, resource_data()} | {error, term()}.
load_config(RawConfig) when is_binary(RawConfig) ->
case hocon:binary(RawConfig, #{format => map}) of
{ok, ConfigTerm} -> load_config(ConfigTerm);
Error -> Error
end;
load_config(#{<<"id">> := Id, <<"resource_type">> := ResourceTypeStr} = Config) ->
MapConfig = maps:get(<<"config">>, Config, #{}),
case emqx_resource:resource_type_from_str(ResourceTypeStr) of
{ok, ResourceType} -> parse_and_load_config(Id, ResourceType, MapConfig);
Error -> Error
end.
parse_and_load_config(InstId, ResourceType, MapConfig) ->
case emqx_resource:parse_config(ResourceType, MapConfig) of
{ok, InstConf} -> create_local(InstId, ResourceType, InstConf);
Error -> Error
end.
create_local(InstId, ResourceType, InstConf) ->
case hash_call(InstId, {create, InstId, ResourceType, InstConf}, 15000) of
{ok, Data} -> {ok, Data};
Error -> Error
end.
save_config_to_disk(InstId, ResourceType, Config) ->
%% TODO: send an event to the config handler, and the hander (single process)
%% will dump configs for all instances (from an ETS table) to a file.
file:write_file(filename:join([emqx_data_dir(), binary_to_list(InstId) ++ ".conf"]),
jsx:encode(#{id => InstId, resource_type => ResourceType,
config => emqx_resource:call_jsonify(ResourceType, Config)})).
emqx_data_dir() ->
"data".
%%------------------------------------------------------------------------------
%% 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),
case save_config_to_disk(InstId, ResourceType, Config) of
ok -> {ok, force_lookup(InstId)};
{error, Reason} ->
logger:error("save config for ~p resource ~p to disk failed: ~p",
[ResourceType, InstId, Reason]),
{error, Reason}
end;
{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).

View File

@ -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.

View File

@ -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(Mod, 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(Mod, Path) ->
BaseName = atom_to_list(Mod),
[erl_syntax:revert(
erl_syntax:attribute(?Q("rest_api"), [
erl_syntax:abstract(#{
name => list_to_atom(Act ++ "_" ++ BaseName),
method => Method,
path => mk_path(Path, WithId),
func => Func,
descr => Act ++ " the " ++ BaseName})]))
|| {Act, 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) -> string:trim(Path, trailing, "/") ++ "/:bin:id";
mk_path(Path, noid) -> Path.

View File

@ -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).

View File

@ -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}.

View File

@ -37,5 +37,5 @@ retainer.max_payload_size = 1MB
## - 30m: 30 minutes
## - 20s: 20 seconds
##
## Defaut: 0
## Default: 0
retainer.expiry_interval = 0

View File

@ -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"}}}]}
]}
]}.

View File

@ -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

View File

@ -2554,23 +2554,15 @@ 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")}]].
start_apps(App, SchemaFile, ConfigFile) ->
read_schema_configs(App, SchemaFile, ConfigFile),
emqx_ct_helpers:read_schema_configs(SchemaFile, ConfigFile),
set_special_configs(App),
{ok, _} = application:ensure_all_started(App).
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),
NewConfig = cuttlefish_generator:map(Schema, Conf),
Vals = proplists:get_value(App, NewConfig, []),
[application:set_env(App, Par, Value) || {Par, Value} <- Vals].
deps_path(App, RelativePath) ->
%% Note: not lib_dir because etc dir is not sym-link-ed to _build dir
%% but priv dir is

View File

@ -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

View File

@ -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}.

View File

@ -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}]}.

View File

@ -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.<name>.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.<name>.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.<name>.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.<name>.secure_renegotiate' in emq.conf
##

View File

@ -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,

View File

@ -1,6 +1,6 @@
{application, emqx_stomp,
[{description, "EMQ X Stomp Protocol Plugin"},
{vsn, "4.3.0"}, % strict semver, bump manually!
{vsn, "4.4.0"}, % strict semver, bump manually!
{modules, []},
{registered, [emqx_stomp_sup]},
{applications, [kernel,stdlib]},

View File

@ -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.<HookName>.<No> = <Spec>
#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"}"

View File

@ -15,4 +15,4 @@
warnings_as_errors, deprecated_functions]}.
{cover_enabled, true}.
{cover_opts, [verbose]}.
{cover_export_enabled, true}.
{cover_export_enabled, true}.

View File

@ -20,8 +20,8 @@ mkdir -p "$RUNNER_LOG_DIR"
# Make sure data directory exists
mkdir -p "$RUNNER_DATA_DIR"
# cuttlefish try to read environment variables starting with "EMQX_"
export CUTTLEFISH_ENV_OVERRIDE_PREFIX='EMQX_'
# hocon try to read environment variables starting with "EMQX_"
export HOCON_ENV_OVERRIDE_PREFIX='EMQX_'
relx_usage() {
command="$1"
@ -123,9 +123,6 @@ fi
# Echo to stderr on errors
echoerr() { echo "$@" 1>&2; }
# By default, use cuttlefish to generate app.config and vm.args
CUTTLEFISH="${USE_CUTTLEFISH:-yes}"
SED_REPLACE="sed -i "
case $(sed --help 2>&1) in
*GNU*) SED_REPLACE="sed -i ";;
@ -202,53 +199,45 @@ generate_config() {
## changing the config 'log.rotation.size'
rm -rf "${RUNNER_LOG_DIR}"/*.siz
if [ "$CUTTLEFISH" != "yes" ]; then
# Note: we have added a parameter '-vm_args' to this. It
# appears redundant but it is not! the erlang vm allows us to
# access all arguments to the erl command EXCEPT '-args_file',
# so in order to get access to this file location from within
# the vm, we need to pass it in twice.
CONFIG_ARGS=" -config $RUNNER_ETC_DIR/app.config -args_file $RUNNER_ETC_DIR/vm.args -vm_args $RUNNER_ETC_DIR/vm.args "
else
EMQX_LICENSE_CONF_OPTION=""
if [ "${EMQX_LICENSE_CONF:-}" != "" ]; then
EMQX_LICENSE_CONF_OPTION="-i ${EMQX_LICENSE_CONF}"
fi
## todo: include license conf option to hocon escript
## EMQX_LICENSE_CONF_OPTION=""
## if [ "${EMQX_LICENSE_CONF:-}" != "" ]; then
## EMQX_LICENSE_CONF_OPTION="-i ${EMQX_LICENSE_CONF}"
## fi
set +e
# shellcheck disable=SC2086
CUTTLEFISH_OUTPUT="$("$ERTS_PATH"/escript "$RUNNER_ROOT_DIR"/bin/cuttlefish -v -i "$REL_DIR"/emqx.schema $EMQX_LICENSE_CONF_OPTION -c "$RUNNER_ETC_DIR"/emqx.conf -d "$RUNNER_DATA_DIR"/configs generate)"
# shellcheck disable=SC2181
RESULT=$?
set -e
if [ $RESULT -gt 0 ]; then
echo "$CUTTLEFISH_OUTPUT"
exit $RESULT
fi
# print override from environment variables (EMQX_*)
echo "$CUTTLEFISH_OUTPUT" | sed -e '$d'
CONFIG_ARGS=$(echo "$CUTTLEFISH_OUTPUT" | tail -n 1)
## Merge cuttlefish generated *.args into the vm.args
CUTTLE_GEN_ARG_FILE=$(echo "$CONFIG_ARGS" | sed -n 's/^.*\(vm_args[[:space:]]\)//p' | awk '{print $1}')
TMP_ARG_FILE="$RUNNER_DATA_DIR/configs/vm.args.tmp"
cp "$RUNNER_ETC_DIR/vm.args" "$TMP_ARG_FILE"
echo "" >> "$TMP_ARG_FILE"
echo "-pa ${REL_DIR}/consolidated" >> "$TMP_ARG_FILE"
sed '/^#/d' "$CUTTLE_GEN_ARG_FILE" | sed '/^$/d' | while IFS='' read -r ARG_LINE || [ -n "$ARG_LINE" ]; do
ARG_KEY=$(echo "$ARG_LINE" | awk '{$NF="";print}')
ARG_VALUE=$(echo "$ARG_LINE" | awk '{print $NF}')
TMP_ARG_VALUE=$(grep "^$ARG_KEY" "$TMP_ARG_FILE" | awk '{print $NF}')
if [ "$ARG_VALUE" != "$TMP_ARG_VALUE" ] ; then
if [ -n "$TMP_ARG_VALUE" ]; then
sh -c "$SED_REPLACE 's/^$ARG_KEY.*$/$ARG_LINE/' $TMP_ARG_FILE"
else
echo "$ARG_LINE" >> "$TMP_ARG_FILE"
fi
fi
done
mv -f "$TMP_ARG_FILE" "$CUTTLE_GEN_ARG_FILE"
set +e
# shellcheck disable=SC2086
HOCON_OUTPUT="$("$ERTS_PATH"/escript "$RUNNER_ROOT_DIR"/bin/hocon -s emqx_schema -c "$RUNNER_ETC_DIR"/emqx.conf -d "$RUNNER_DATA_DIR"/configs generate)"
# shellcheck disable=SC2181
RESULT=$?
set -e
if [ $RESULT -gt 0 ]; then
echo "$HOCON_OUTPUT"
exit $RESULT
fi
# print override from environment variables (EMQX_*)
echo "$HOCON_OUTPUT" | sed -e '$d'
CONFIG_ARGS=$(echo "$HOCON_OUTPUT" | tail -n 1)
## Merge hocon generated *.args into the vm.args
HOCON_GEN_ARG_FILE=$(echo "$CONFIG_ARGS" | sed -n 's/^.*\(vm_args[[:space:]]\)//p' | awk '{print $1}')
TMP_ARG_FILE="$RUNNER_DATA_DIR/configs/vm.args.tmp"
cp "$RUNNER_ETC_DIR/vm.args" "$TMP_ARG_FILE"
echo "" >> "$TMP_ARG_FILE"
echo "-pa ${REL_DIR}/consolidated" >> "$TMP_ARG_FILE"
sed '/^#/d' "$HOCON_GEN_ARG_FILE" | sed '/^$/d' | while IFS='' read -r ARG_LINE || [ -n "$ARG_LINE" ]; do
ARG_KEY=$(echo "$ARG_LINE" | awk '{$NF="";print}')
ARG_VALUE=$(echo "$ARG_LINE" | awk '{print $NF}')
TMP_ARG_VALUE=$(grep "^$ARG_KEY" "$TMP_ARG_FILE" | awk '{print $NF}')
if [ "$ARG_VALUE" != "$TMP_ARG_VALUE" ] ; then
if [ -n "$TMP_ARG_VALUE" ]; then
sh -c "$SED_REPLACE 's/^$ARG_KEY.*$/$ARG_LINE/' $TMP_ARG_FILE"
else
echo "$ARG_LINE" >> "$TMP_ARG_FILE"
fi
fi
done
mv -f "$TMP_ARG_FILE" "$HOCON_GEN_ARG_FILE"
# shellcheck disable=SC2086
if ! relx_nodetool chkconfig $CONFIG_ARGS; then
@ -303,7 +292,8 @@ if [ -z "$NAME_ARG" ]; then
NODENAME="$(grep -E '^-name' "$LATEST_VM_ARGS" | awk '{print $2}')"
else
# for boot commands, inspect emqx.conf for node name
NODENAME=$("$ERTS_PATH"/escript "$RUNNER_ROOT_DIR"/bin/cuttlefish -i "$REL_DIR"/emqx.schema -c "$RUNNER_ETC_DIR"/emqx.conf get node.name)
# todo: use get command from hocon escript
NODENAME="$("$ERTS_PATH"/escript "$RUNNER_ROOT_DIR"/bin/hocon -s emqx_schema -c "$RUNNER_ETC_DIR"/emqx.conf get node.name | tr -d \")"
fi
fi
if [ -z "$NODENAME" ]; then
@ -329,7 +319,7 @@ PIPE_DIR="${PIPE_DIR:-/$RUNNER_DATA_DIR/${WHOAMI}_erl_pipes/$NAME/}"
COOKIE="${EMQX_NODE_COOKIE:-}"
if [ -z "$COOKIE" ]; then
if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
COOKIE=$("$ERTS_PATH"/escript "$RUNNER_ROOT_DIR"/bin/cuttlefish -i "$REL_DIR"/emqx.schema -c "$RUNNER_ETC_DIR"/emqx.conf get node.cookie)
COOKIE="$("$ERTS_PATH"/escript "$RUNNER_ROOT_DIR"/bin/hocon -s emqx_schema -c "$RUNNER_ETC_DIR"/emqx.conf get node.cookie | tr -d \")"
else
# shellcheck disable=SC2012,SC2086
LATEST_VM_ARGS="$(ls -t $RUNNER_DATA_DIR/configs/vm.*.args | head -1)"

View File

@ -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}}}.

View File

@ -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/<prefix>/<cluster.name>/<node.name>
@ -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: <name>@<host>
##
## 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 <File>
## 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".
##
@ -460,7 +474,7 @@ log.file = emqx.log
##
## Value: on | off
## Default: on
log.rotation = on
log.rotation.enable = on
## Maximum size of each log file.
##
@ -579,7 +593,7 @@ log.rotation.count = 5
## Value: MaxBurstCount,TimeWindow
## Default: disabled
##
#log.burst_limit = 20000, 1s
#log.burst_limit = "20000, 1s"
## CONFIG_SECTION_END=logger ===================================================
@ -591,42 +605,42 @@ log.rotation.count = 5
## Notice: Disable the option in production deployment!
##
## Value: true | false
allow_anonymous = true
acl.allow_anonymous = true
## Allow or deny if no ACL rules matched.
##
## Value: allow | deny
acl_nomatch = allow
acl.acl_nomatch = allow
## Default ACL File.
##
## Value: File Name
acl_file = {{ platform_etc_dir }}/acl.conf
acl.acl_file = "{{ platform_etc_dir }}/acl.conf"
## Whether to enable ACL cache.
##
## If enabled, ACLs roles for each client will be cached in the memory
##
## Value: on | off
enable_acl_cache = on
acl.enable_acl_cache = on
## The maximum count of ACL entries can be cached for a client.
##
## Value: Integer greater than 0
## Default: 32
acl_cache_max_size = 32
acl.acl_cache_max_size = 32
## The time after which an ACL cache entry will be deleted
##
## Value: Duration
## Default: 1 minute
acl_cache_ttl = 1m
acl.acl_cache_ttl = 1m
## The action when acl check reject current operation
##
## Value: ignore | disconnect
## Default: ignore
acl_deny_action = ignore
acl.acl_deny_action = ignore
## Specify the global flapping detect policy.
## The value is a string composed of flapping threshold, duration and banned interval.
@ -635,7 +649,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
acl.flapping_detect_policy = "30, 1m, 5m"
##--------------------------------------------------------------------
## MQTT Protocol
@ -732,7 +746,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.
@ -742,9 +756,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.
##
@ -850,7 +864,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
@ -877,13 +891,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.
##
@ -914,16 +928,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.
##
@ -932,7 +946,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
##
@ -977,7 +991,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.
##
@ -1022,8 +1036,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.
@ -1033,7 +1047,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)
##
@ -1067,8 +1081,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.
##
@ -1103,8 +1117,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.
@ -1207,8 +1221,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.
##
@ -1304,8 +1318,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.
##
@ -1337,7 +1351,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.
@ -1360,7 +1374,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.
##
@ -1384,20 +1398,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
@ -1414,7 +1428,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
@ -1449,14 +1463,13 @@ listener.ssl.external.certfile = {{ platform_etc_dir }}/certs/cert.pem
## Most of it was copied from Mozillas 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.
@ -1566,13 +1579,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.
##
@ -1604,7 +1617,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.
@ -1615,7 +1628,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.
@ -1823,7 +1836,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
@ -1833,13 +1846,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.
##
@ -1873,7 +1886,7 @@ listener.wss.external.zone = external
## See: listener.tcp.$name.access.<no>
##
## 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.
@ -1884,7 +1897,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.
@ -1918,28 +1931,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.
@ -1960,7 +1973,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
##
@ -1975,13 +1988,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
##
@ -2140,7 +2153,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 ================================================
@ -2149,7 +2162,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
module.loaded_file = "{{ platform_data_dir }}/loaded_modules"
##--------------------------------------------------------------------
## Presence Module
@ -2165,7 +2178,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.
##
@ -2198,8 +2211,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 ==================================================
@ -2210,17 +2223,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
@ -2334,7 +2347,6 @@ sysmon.long_gc = 0
## Examples:
## - 2h: 2 hours
## - 30m: 30 minutes
## - 0.1s: 0.1 seconds
## - 100ms: 100 milliseconds
##
## Default: 0ms
@ -2426,8 +2438,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
##

View File

@ -29,7 +29,7 @@
-ifndef(EMQX_ENTERPRISE).
-define(EMQX_RELEASE, {opensource, "4.3.3"}).
-define(EMQX_RELEASE, {opensource, "5.0-pre"}).
-else.

View File

@ -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.<name>.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.<name>.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.<name>.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.<name>.secure_renegotiate' in emq.conf
##

View File

@ -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

View File

@ -1,6 +1,6 @@
{application, emqx_dashboard,
[{description, "EMQ X Web Dashboard"},
{vsn, "4.3.0"}, % strict semver, bump manually!
{vsn, "4.4.0"}, % strict semver, bump manually!
{modules, []},
{registered, [emqx_dashboard_sup]},
{applications, [kernel,stdlib,mnesia,minirest]},

View File

@ -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
telemetry.report_interval = 7d

View File

@ -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}.

View File

@ -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.1"}}} % TODO: delete when all apps moved to hocon
, {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"}}}
@ -56,6 +56,7 @@
, {observer_cli, "1.6.1"} % NOTE: depends on recon 2.5.1
, {getopt, "1.0.1"}
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.13.0"}}}
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.4.0"}}}
]}.
{xref_ignores,

View File

@ -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
]
@ -334,9 +336,8 @@ relx_overlay(ReleaseType) ->
, {template, "bin/emqx_ctl.cmd", "bin/emqx_ctl.cmd"}
, {copy, "bin/nodetool", "bin/nodetool"}
, {copy, "bin/nodetool", "bin/nodetool-{{release_version}}"}
, {copy, "_build/default/lib/cuttlefish/cuttlefish", "bin/cuttlefish"}
, {copy, "_build/default/lib/cuttlefish/cuttlefish", "bin/cuttlefish-{{release_version}}"}
, {copy, "priv/emqx.schema", "releases/{{release_version}}/"}
, {copy, "_build/default/lib/hocon/hocon", "bin/hocon"}
, {copy, "_build/default/lib/hocon/hocon", "bin/hocon-{{release_version}}"}
] ++ case is_enterprise() of
true -> ee_etc_overlay(ReleaseType);
false -> etc_overlay(ReleaseType)

View File

@ -6,22 +6,45 @@ latest_release=$(git describe --tags "$(git rev-list --tags --max-count=1 --remo
bad_app_count=0
while read -r app; do
if [ "$app" != "emqx" ]; then
app_path="$app"
get_vsn() {
commit="$1"
app_src_file="$2"
if [ "$commit" = 'HEAD' ]; then
if [ -f "$app_src_file" ]; then
grep vsn "$app_src_file" | grep -oE '"[0-9]+.[0-9]+.[0-9]+"' | tr -d '"' || true
fi
else
app_path="."
git show "$commit":"$app_src_file" 2>/dev/null | grep vsn | grep -oE '"[0-9]+.[0-9]+.[0-9]+"' | tr -d '"' || true
fi
src_file="$app_path/src/$(basename "$app").app.src"
old_app_version="$(git show "$latest_release":"$src_file" | grep vsn | grep -oE '"[0-9]+.[0-9]+.[0-9]+"' | tr -d '"')"
now_app_version=$(grep -E 'vsn' "$src_file" | grep -oE '"[0-9]+\.[0-9]+\.[0-9]+"' | tr -d '"')
if [ "$old_app_version" = "$now_app_version" ]; then
changed="$(git diff --name-only "$latest_release"...HEAD \
}
while read -r app_path; do
app=$(basename "$app_path")
src_file="$app_path/src/$app.app.src"
old_app_version="$(get_vsn "$latest_release" "$src_file")"
## TODO: delete it after new version is released with emqx app in apps dir
if [ "$app" = 'emqx' ] && [ "$old_app_version" = '' ]; then
old_app_version="$(get_vsn "$latest_release" 'src/emqx.app.src')"
fi
now_app_version="$(get_vsn 'HEAD' "$src_file")"
## TODO: delete it after new version is released with emqx app in apps dir
if [ "$app" = 'emqx' ] && [ "$now_app_version" = '' ]; then
now_app_version="$(get_vsn 'HEAD' 'src/emqx.app.src')"
fi
if [ -z "$now_app_version" ]; then
echo "failed_to_get_new_app_vsn for $app"
exit 1
fi
if [ -z "${old_app_version:-}" ]; then
echo "skiped checking new app ${app}"
elif [ "$old_app_version" = "$now_app_version" ]; then
lines="$(git diff --name-only "$latest_release"...HEAD \
-- "$app_path/src" \
-- "$app_path/priv" \
-- "$app_path/c_src" | wc -l)"
if [ "$changed" -gt 0 ]; then
echo "$src_file needs a vsn bump"
-- "$app_path/c_src")"
if [ "$lines" != '' ]; then
echo "$src_file needs a vsn bump (old=$old_app_version)"
echo "changed: $lines"
bad_app_count=$(( bad_app_count + 1))
fi
fi

View File

@ -32,6 +32,9 @@
-export([format/2]).
-ifdef(TEST).
-include_lib("proper/include/proper.hrl").
-include_lib("eunit/include/eunit.hrl").
-export([report_cb_1/1, report_cb_2/2, report_cb_crash/2]).
-endif.
@ -220,8 +223,6 @@ json_key(Term) ->
end.
-ifdef(TEST).
-include_lib("proper/include/proper.hrl").
-include_lib("eunit/include/eunit.hrl").
no_crash_test_() ->
Opts = [{numtests, 1000}, {to_file, user}],

1230
src/emqx_schema.erl Normal file

File diff suppressed because it is too large Load Diff

View File

@ -65,16 +65,17 @@ mustache_vars() ->
].
generate_config() ->
Schema = cuttlefish_schema:files([local_path(["priv", "emqx.schema"])]),
ConfFile = render_config_file(),
Conf = conf_parse:file(ConfFile),
cuttlefish_generator:map(Schema, Conf).
{ok, Conf} = hocon:load(ConfFile, #{format => richmap}),
hocon_schema:generate(emqx_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 +92,4 @@ get_base_dir(Module) ->
get_base_dir() ->
get_base_dir(?MODULE).