Просмотр исходного кода

Merge branch 'master' of github.com:emqx/emqx into merge-master-to-v5

z8674558 5 лет назад
Родитель
Сommit
971e6ca90e
100 измененных файлов с 1496 добавлено и 1106 удалено
  1. 0 115
      .ci/apps_tests/docker-compose.yaml
  2. 0 26
      .ci/apps_tests/openldap/Dockerfile
  3. 1 1
      .ci/build_packages/Dockerfile
  4. 6 7
      .ci/build_packages/tests.sh
  5. 0 5
      .ci/compatibility_tests/.env
  6. 0 43
      .ci/compatibility_tests/docker-compose-mongo-tls.yaml
  7. 0 39
      .ci/compatibility_tests/docker-compose-mongo.yaml
  8. 0 46
      .ci/compatibility_tests/docker-compose-mysql.yaml
  9. 0 38
      .ci/compatibility_tests/docker-compose-pgsql.yaml
  10. 0 41
      .ci/compatibility_tests/docker-compose-redis-cluster-tls.yaml
  11. 0 40
      .ci/compatibility_tests/docker-compose-redis-cluster.yaml
  12. 0 40
      .ci/compatibility_tests/docker-compose-redis-sentinel.yaml
  13. 0 43
      .ci/compatibility_tests/docker-compose-redis-single-tls.yaml
  14. 0 37
      .ci/compatibility_tests/docker-compose-redis-single.yaml
  15. 0 16
      .ci/compatibility_tests/openldap/slapd.conf
  16. 0 2
      .ci/compatibility_tests/redis/redis.conf
  17. 0 0
      .ci/docker-compose-file/.env
  18. 2 1
      .ci/apps_tests/conf.env
  19. 16 0
      .ci/docker-compose-file/docker-compose-ldap-tcp.yaml
  20. 14 0
      .ci/docker-compose-file/docker-compose-mongo-tcp.yaml
  21. 18 0
      .ci/docker-compose-file/docker-compose-mongo-tls.yaml
  22. 20 0
      .ci/docker-compose-file/docker-compose-mysql-tcp.yaml
  23. 18 25
      .ci/compatibility_tests/docker-compose-mysql-tls.yaml
  24. 15 0
      .ci/docker-compose-file/docker-compose-pgsql-tcp.yaml
  25. 2 27
      .ci/compatibility_tests/docker-compose-pgsql-tls.yaml
  26. 11 0
      .ci/docker-compose-file/docker-compose-redis-cluster-tcp.yaml
  27. 12 0
      .ci/docker-compose-file/docker-compose-redis-cluster-tls.yaml
  28. 11 0
      .ci/docker-compose-file/docker-compose-redis-sentinel-tcp.yaml
  29. 13 0
      .ci/docker-compose-file/docker-compose-redis-single-tcp.yaml
  30. 19 0
      .ci/docker-compose-file/docker-compose-redis-single-tls.yaml
  31. 13 19
      .ci/compatibility_tests/docker-compose-ldap.yaml
  32. 1 1
      .ci/compatibility_tests/openldap/Dockerfile
  33. 0 0
      .ci/docker-compose-file/openldap/slapd.conf
  34. 4 4
      .ci/compatibility_tests/pgsql/Dockerfile
  35. 0 0
      .ci/docker-compose-file/pgsql/pg_hba.conf
  36. 3 1
      .ci/compatibility_tests/redis/redis-tls.conf
  37. 4 0
      .ci/docker-compose-file/redis/redis.conf
  38. 7 3
      .ci/compatibility_tests/redis/redis.sh
  39. 1 0
      .ci/compatibility_tests/redis/sentinel.conf
  40. 1 0
      .ci/fvt_tests/.env
  41. 7 3
      .ci/fvt_tests/docker-compose.yaml
  42. 22 0
      .ci/fvt_tests/scripts/pytest.sh
  43. 5 0
      .gitattributes
  44. 241 156
      .github/workflows/build_packages.yaml
  45. 22 6
      .github/workflows/build_slim_packages.yaml
  46. 1 1
      .github/workflows/check_deps_integrity.yaml
  47. 6 1
      .github/workflows/elvis_lint.yaml
  48. 15 2
      .github/workflows/git_sync.yaml
  49. 90 32
      .github/workflows/run_cts_tests.yaml
  50. 75 18
      .github/workflows/run_fvt_tests.yaml
  51. 12 2
      .github/workflows/run_gitlint.yaml
  52. 68 6
      .github/workflows/run_test_cases.yaml
  53. 28 8
      Makefile
  54. 10 9
      README-CN.md
  55. 20 17
      README.md
  56. 127 0
      Windows.md
  57. 20 5
      apps/emqx_auth_http/etc/emqx_auth_http.conf
  58. 9 0
      apps/emqx_auth_http/priv/emqx_auth_http.schema
  59. 16 5
      apps/emqx_auth_http/src/emqx_auth_http_app.erl
  60. 3 1
      apps/emqx_auth_http/test/emqx_auth_http_SUITE.erl
  61. 0 2
      apps/emqx_auth_ldap/etc/emqx_auth_ldap.conf
  62. 4 6
      apps/emqx_auth_ldap/priv/emqx_auth_ldap.schema
  63. 1 3
      apps/emqx_auth_ldap/test/emqx_auth_ldap_SUITE.erl
  64. 2 4
      apps/emqx_auth_ldap/test/emqx_auth_ldap_bind_as_user_SUITE.erl
  65. 1 0
      apps/emqx_auth_mnesia/src/emqx_acl_mnesia.erl
  66. 54 10
      apps/emqx_auth_mnesia/src/emqx_acl_mnesia_cli.erl
  67. 8 2
      apps/emqx_auth_mnesia/src/emqx_auth_mnesia_cli.erl
  68. 62 10
      apps/emqx_auth_mnesia/test/emqx_acl_mnesia_SUITE.erl
  69. 2 2
      apps/emqx_auth_mnesia/test/emqx_auth_mnesia_SUITE.erl
  70. 15 0
      apps/emqx_auth_mongo/etc/emqx_auth_mongo.conf
  71. 20 2
      apps/emqx_auth_mongo/priv/emqx_auth_mongo.schema
  72. 4 9
      apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE.erl
  73. 15 0
      apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf
  74. 21 2
      apps/emqx_auth_mysql/priv/emqx_auth_mysql.schema
  75. 15 0
      apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
  76. 18 0
      apps/emqx_auth_pgsql/priv/emqx_auth_pgsql.schema
  77. 4 5
      apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE.erl
  78. 27 0
      apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/ca-key.pem
  79. 19 0
      apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/ca.pem
  80. 19 0
      apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/client-cert.pem
  81. 27 0
      apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/client-key.pem
  82. 0 21
      apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/postgresql.crt
  83. 0 17
      apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/postgresql.csr
  84. 0 27
      apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/postgresql.key
  85. 27 0
      apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/private_key.pem
  86. 9 0
      apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/public_key.pem
  87. 0 21
      apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/root.crt
  88. 0 1
      apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/root.srl
  89. 19 0
      apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server-cert.pem
  90. 27 0
      apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server-key.pem
  91. 0 21
      apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server.crt
  92. 0 27
      apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server.key
  93. 14 0
      apps/emqx_auth_redis/etc/emqx_auth_redis.conf
  94. 37 2
      apps/emqx_auth_redis/priv/emqx_auth_redis.schema
  95. 4 9
      apps/emqx_auth_redis/test/emqx_auth_redis_SUITE.erl
  96. 1 2
      apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_actions.erl
  97. 7 5
      apps/emqx_coap/README.md
  98. 1 1
      apps/emqx_coap/TODO
  99. 3 3
      apps/emqx_coap/src/emqx_coap_app.erl
  100. 0 0
      apps/emqx_coap/src/emqx_coap_mqtt_adapter.erl

+ 0 - 115
.ci/apps_tests/docker-compose.yaml

@@ -1,115 +0,0 @@
-version: '3'
-
-services:
-  erlang:
-    container_name: erlang
-    image: emqx/build-env:erl23.2.2-ubuntu20.04
-    depends_on:
-      - mysql_server
-      - redis_server
-      - mongo_server
-      - pgsql_server
-      - ldap_server
-    networks:
-      - emqx_bridge
-    env_file:
-      - conf.env
-    environment:
-      GITHUB_ACTIONS: ${GITHUB_ACTIONS}
-      GITHUB_TOKEN: ${GITHUB_TOKEN}
-      GITHUB_RUN_ID: ${GITHUB_RUN_ID}
-      GITHUB_SHA: ${GITHUB_SHA}
-      GITHUB_RUN_NUMBER: ${GITHUB_RUN_NUMBER}
-      GITHUB_EVENT_NAME: ${GITHUB_EVENT_NAME}
-      GITHUB_REF: ${GITHUB_REF}
-    volumes:
-      - ../../.:/emqx
-    working_dir: /emqx
-    tty: true
-
-  mysql_server:
-    container_name: mysql
-    image: mysql:${MYSQL_TAG}
-    restart: always
-    ports:
-      - 3306:3306
-    environment:
-      MYSQL_ROOT_PASSWORD: public
-      MYSQL_DATABASE: mqtt
-    command:
-      --bind-address 0.0.0.0
-      --default-authentication-plugin=mysql_native_password
-      --character-set-server=utf8mb4
-      --collation-server=utf8mb4_general_ci
-      --explicit_defaults_for_timestamp=true
-      --lower_case_table_names=1
-      --max_allowed_packet=128M
-      --skip-symbolic-links
-    networks:
-      - emqx_bridge
-
-  redis_server:
-    container_name: redis
-    image: redis:${REDIS_TAG}
-    ports:
-      - 6379:6379
-    command:
-      - redis-server
-      - "--bind 0.0.0.0 ::"
-    restart: always
-    networks:
-      - emqx_bridge
-
-  mongo_server:
-    container_name: mongo
-    image: mongo:${MONGO_TAG}
-    ports:
-      - 27017:27017
-    restart: always
-    environment:
-      MONGO_INITDB_DATABASE: mqtt
-    command:
-      --ipv6
-      --bind_ip_all
-    networks:
-      - emqx_bridge
-
-  pgsql_server:
-    container_name: pgsql
-    image: postgres:${PGSQL_TAG}
-    ports:
-      - 5432:5432
-    restart: always
-    environment:
-      POSTGRES_PASSWORD: public
-      POSTGRES_USER: root
-      POSTGRES_DB: mqtt
-    networks:
-      - emqx_bridge
-
-  ldap_server:
-    container_name: openldap
-    build:
-      context: ../..
-      dockerfile: .ci/apps_tests/openldap/Dockerfile
-      args:
-        LDAP_TAG: ${LDAP_TAG}
-    image: emqx-ldap:1.0
-    ports:
-      - 389:389
-    restart: always
-    networks:
-      - emqx_bridge
-
-networks:
-  emqx_bridge:
-    driver: bridge
-    name: emqx_bridge
-    enable_ipv6: true
-    ipam:
-      driver: default
-      config:
-        - subnet: 172.100.239.0/24
-          gateway: 172.100.239.1
-        - subnet: 2001:3200:3200::/64
-          gateway: 2001:3200:3200::1

+ 0 - 26
.ci/apps_tests/openldap/Dockerfile

@@ -1,26 +0,0 @@
-FROM buildpack-deps:stretch
-
-ARG LDAP_TAG=2.4.50
-
-RUN apt-get update && apt-get install -y groff groff-base
-RUN wget ftp://ftp.openldap.org/pub/OpenLDAP/openldap-release/openldap-${LDAP_TAG}.tgz \
-    && gunzip -c openldap-${LDAP_TAG}.tgz | tar xvfB - \
-    && cd openldap-${LDAP_TAG} \
-    && ./configure && make depend && make && make install \
-    && cd .. && rm -rf  openldap-${LDAP_TAG}
-
-COPY .ci/apps_tests/openldap/slapd.conf /usr/local/etc/openldap/slapd.conf
-COPY apps/emqx_auth_ldap/emqx.io.ldif /usr/local/etc/openldap/schema/emqx.io.ldif
-COPY apps/emqx_auth_ldap/emqx.schema /usr/local/etc/openldap/schema/emqx.schema
-COPY apps/emqx_auth_ldap/test/certs/*.pem /usr/local/etc/openldap/
-
-RUN mkdir -p /usr/local/etc/openldap/data \
-    && slapadd -l /usr/local/etc/openldap/schema/emqx.io.ldif -f /usr/local/etc/openldap/slapd.conf
-
-WORKDIR /usr/local/etc/openldap
-
-EXPOSE 389 636
-
-ENTRYPOINT ["/usr/local/libexec/slapd", "-h", "ldap:/// ldaps:///", "-d", "3", "-f", "/usr/local/etc/openldap/slapd.conf"]
-
-CMD []

+ 1 - 1
.ci/build_packages/Dockerfile

@@ -1,4 +1,4 @@
-ARG BUILD_FROM=emqx/build-env:erl23.2.2-ubuntu20.04
+ARG BUILD_FROM=emqx/build-env:erl23.2.7-ubuntu20.04
 FROM ${BUILD_FROM}
 
 ARG EMQX_NAME=emqx

+ 6 - 7
.ci/build_packages/tests.sh

@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
 set -x -e -u
 export CODE_PATH=${CODE_PATH:-"/emqx"}
 export EMQX_NAME=${EMQX_NAME:-"emqx"}
@@ -29,7 +29,7 @@ emqx_test(){
                 sed -i '/emqx_telemetry/d' "${PACKAGE_PATH}"/emqx/data/loaded_plugins
 
                 echo "running ${packagename} start"
-                "${PACKAGE_PATH}"/emqx/bin/emqx start || tail "${PACKAGE_PATH}"/emqx/log/erlang.log.1
+                "${PACKAGE_PATH}"/emqx/bin/emqx start || ( tail "${PACKAGE_PATH}"/emqx/log/emqx.log.1 && exit 1 )
                 IDLE_TIME=0
                 while [ -z "$("${PACKAGE_PATH}"/emqx/bin/emqx_ctl status |grep 'is running'|awk '{print $1}')" ]
                 do
@@ -101,7 +101,7 @@ running_test(){
            EMQX_MQTT__MAX_TOPIC_ALIAS=10
     sed -i '/emqx_telemetry/d' /var/lib/emqx/loaded_plugins
 
-    emqx start || tail /var/log/emqx/erlang.log.1
+    emqx start || ( tail /var/log/emqx/emqx.log.1 && exit 1 )
     IDLE_TIME=0
     while [ -z "$(emqx_ctl status |grep 'is running'|awk '{print $1}')" ]
     do
@@ -118,9 +118,8 @@ running_test(){
     emqx stop || kill "$(ps -ef | grep -E '\-progname\s.+emqx\s' |awk '{print $2}')"
 
     if [ "$(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g')" = ubuntu ] \
-    || [ "$(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g')" = debian ] \
-    || [ "$(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g')" = raspbian ];then
-        service emqx start || tail /var/log/emqx/erlang.log.1
+    || [ "$(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g')" = debian ] ;then
+        service emqx start || ( tail /var/log/emqx/emqx.log.1 && exit 1 )
         IDLE_TIME=0
         while [ -z "$(emqx_ctl status |grep 'is running'|awk '{print $1}')" ]
         do
@@ -144,7 +143,7 @@ relup_test(){
         for var in "${EMQX_NAME}"-*-"$(uname -m)".zip;do
             packagename=$(basename "${var}")
             unzip "$packagename"
-            ./emqx/bin/emqx start
+            ./emqx/bin/emqx start || ( tail emqx/log/emqx.log.1 && exit 1 )
             ./emqx/bin/emqx_ctl status
             ./emqx/bin/emqx versions
             cp "${PACKAGE_PATH}/${EMQX_NAME}"-*-"${TARGET_VERSION}-$(uname -m)".zip ./emqx/releases

+ 0 - 5
.ci/compatibility_tests/.env

@@ -1,5 +0,0 @@
-MYSQL_TAG=5.7
-REDIS_TAG=6
-MONGO_TAG=4.1
-PGSQL_TAG=11
-LDAP_TAG=2.4.50

+ 0 - 43
.ci/compatibility_tests/docker-compose-mongo-tls.yaml

@@ -1,43 +0,0 @@
-version: '3'
-
-services:
-  erlang:
-    container_name: erlang
-    image: emqx/build-env:erl23.2.2-ubuntu20.04
-    volumes:
-      - ../../:/emqx
-    working_dir: /emqx
-    networks:
-      - emqx_bridge
-    depends_on:
-      - mongo_server
-    tty: true
-
-  mongo_server:
-    container_name: mongo
-    image: mongo:${MONGO_TAG}
-    restart: always
-    environment:
-      MONGO_INITDB_DATABASE: mqtt
-    volumes:
-        - ../../apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE_data/mongodb.pem/:/etc/certs/mongodb.pem
-    networks:
-      - emqx_bridge
-    command:
-      --ipv6
-      --bind_ip_all
-      --sslMode requireSSL
-      --sslPEMKeyFile /etc/certs/mongodb.pem
-
-networks:
-  emqx_bridge:
-    driver: bridge
-    name: emqx_bridge
-    enable_ipv6: true
-    ipam:
-      driver: default
-      config:
-        - subnet: 172.100.100.0/24
-          gateway: 172.100.100.1
-        - subnet: 2001:3200:3200::/64
-          gateway: 2001:3200:3200::1

+ 0 - 39
.ci/compatibility_tests/docker-compose-mongo.yaml

@@ -1,39 +0,0 @@
-version: '3'
-
-services:
-  erlang:
-    container_name: erlang
-    image: emqx/build-env:erl23.2.2-ubuntu20.04
-    volumes:
-      - ../..:/emqx
-    working_dir: /emqx
-    networks:
-      - emqx_bridge
-    depends_on:
-      - mongo_server
-    tty: true
-
-  mongo_server:
-    container_name: mongo 
-    image: mongo:${MONGO_TAG}
-    restart: always
-    environment:
-      MONGO_INITDB_DATABASE: mqtt
-    networks:
-      - emqx_bridge
-    command:
-      --ipv6
-      --bind_ip_all
-
-networks:
-  emqx_bridge:
-    driver: bridge
-    name: emqx_bridge
-    enable_ipv6: true
-    ipam:
-      driver: default
-      config:
-        - subnet: 172.100.100.0/24
-          gateway: 172.100.100.1
-        - subnet: 2001:3200:3200::/64
-          gateway: 2001:3200:3200::1

+ 0 - 46
.ci/compatibility_tests/docker-compose-mysql.yaml

@@ -1,46 +0,0 @@
-version: '3'
-
-services:
-  erlang:
-    container_name: erlang
-    image: emqx/build-env:erl23.2.2-ubuntu20.04
-    volumes:
-      - ../../:/emqx
-    working_dir: /emqx
-    networks:
-      - emqx_bridge
-    depends_on:
-      - mysql_server
-    tty: true
-
-  mysql_server:
-    container_name: mysql
-    image: mysql:${MYSQL_TAG}
-    restart: always
-    environment:
-      MYSQL_ROOT_PASSWORD: public
-      MYSQL_DATABASE: mqtt
-    networks:
-      - emqx_bridge
-    command:
-      --bind-address "::"
-      --default-authentication-plugin=mysql_native_password
-      --character-set-server=utf8mb4
-      --collation-server=utf8mb4_general_ci
-      --explicit_defaults_for_timestamp=true
-      --lower_case_table_names=1
-      --max_allowed_packet=128M
-      --skip-symbolic-links
-
-networks:
-  emqx_bridge:
-    driver: bridge
-    name: emqx_bridge
-    enable_ipv6: true
-    ipam:
-      driver: default
-      config:
-        - subnet: 172.100.100.0/24
-          gateway: 172.100.100.1
-        - subnet: 2001:3200:3200::/64
-          gateway: 2001:3200:3200::1

+ 0 - 38
.ci/compatibility_tests/docker-compose-pgsql.yaml

@@ -1,38 +0,0 @@
-version: '3'
-
-services:
-  erlang:
-    container_name: erlang
-    image: emqx/build-env:erl23.2.2-ubuntu20.04
-    volumes:
-      - ../../:/emqx
-    working_dir: /emqx
-    networks:
-      - emqx_bridge
-    depends_on:
-      - pgsql_server
-    tty: true
-
-  pgsql_server:
-    container_name: pgsql
-    image: postgres:${PGSQL_TAG}
-    restart: always
-    environment:
-      POSTGRES_PASSWORD: public
-      POSTGRES_USER: root
-      POSTGRES_DB: mqtt
-    networks:
-      - emqx_bridge
-
-networks:
-  emqx_bridge:
-    driver: bridge
-    name: emqx_bridge
-    enable_ipv6: true
-    ipam:
-      driver: default
-      config:
-        - subnet: 172.100.100.0/24
-          gateway: 172.100.100.1
-        - subnet: 2001:3200:3200::/64
-          gateway: 2001:3200:3200::1

+ 0 - 41
.ci/compatibility_tests/docker-compose-redis-cluster-tls.yaml

@@ -1,41 +0,0 @@
-version: '2.4'
-# network configuration is limited in version 3
-# https://github.com/docker/compose/issues/4958
-
-services:
-  erlang:
-    container_name: erlang
-    image: emqx/build-env:erl23.2.2-ubuntu20.04
-    volumes:
-      - ../..:/emqx
-    networks:
-      - app_net
-    depends_on:
-      - redis_cluster
-    working_dir: /emqx
-    tty: true
-
-  redis_cluster:
-    container_name: redis
-    image: redis:${REDIS_TAG}
-    volumes:
-      - ../../apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs:/tls
-      - ./redis/:/data/conf
-    command: bash -c "/bin/bash /data/conf/redis.sh --node cluster --tls-enabled && while true; do echo 1; sleep 1; done"
-    networks:
-      app_net:
-        # Assign a public address. Erlang container cannot find cluster nodes by network-scoped alias (redis_cluster).
-        ipv4_address: 172.16.239.10
-        ipv6_address: 2001:3200:3200::20
-
-networks:
-  app_net:
-    driver: bridge
-    enable_ipv6: true
-    ipam:
-      driver: default
-      config:
-        - subnet: 172.16.239.0/24
-          gateway: 172.16.239.1
-        - subnet: 2001:3200:3200::/64
-          gateway: 2001:3200:3200::1

+ 0 - 40
.ci/compatibility_tests/docker-compose-redis-cluster.yaml

@@ -1,40 +0,0 @@
-version: '2.4'
-# network configuration is limited in version 3
-# https://github.com/docker/compose/issues/4958
-
-services:
-  erlang:
-    container_name: erlang
-    image: emqx/build-env:erl23.2.2-ubuntu20.04
-    volumes:
-      - ../..:/emqx
-    networks:
-      - app_net
-    depends_on:
-      - redis_cluster
-    working_dir: /emqx
-    tty: true
-
-  redis_cluster:
-    image: redis:${REDIS_TAG}
-    container_name: redis
-    volumes:
-      - ./redis/:/data/conf
-    command: bash -c "/bin/bash /data/conf/redis.sh --node cluster && while true; do echo 1; sleep 1; done"
-    networks:
-      app_net:
-        # Assign a public address. Erlang container cannot find cluster nodes by network-scoped alias (redis_cluster).
-        ipv4_address: 172.16.239.10
-        ipv6_address: 2001:3200:3200::20
-
-networks:
-  app_net:
-    driver: bridge
-    enable_ipv6: true
-    ipam:
-      driver: default
-      config:
-        - subnet: 172.16.239.0/24
-          gateway: 172.16.239.1
-        - subnet: 2001:3200:3200::/64
-          gateway: 2001:3200:3200::1

+ 0 - 40
.ci/compatibility_tests/docker-compose-redis-sentinel.yaml

@@ -1,40 +0,0 @@
-version: '2.4'
-# network configuration is limited in version 3
-# https://github.com/docker/compose/issues/4958
-
-services:
-  erlang:
-    container_name: erlang
-    image: emqx/build-env:erl23.2.2-ubuntu20.04
-    volumes:
-      - ../..:/emqx
-    networks:
-      - app_net
-    depends_on:
-      - redis_cluster
-    working_dir: /emqx
-    tty: true
-
-  redis_cluster:
-    container_name: redis
-    image: redis:${REDIS_TAG}
-    volumes:
-      - ./redis/:/data/conf
-    command: bash -c "/bin/bash /data/conf/redis.sh --node sentinel && while true; do echo 1; sleep 1; done"
-    networks:
-      app_net:
-        # Assign a public address. Erlang container cannot find cluster nodes by network-scoped alias (redis_cluster).
-        ipv4_address: 172.16.239.10
-        ipv6_address: 2001:3200:3200::20
-
-networks:
-  app_net:
-    driver: bridge
-    enable_ipv6: true
-    ipam:
-      driver: default
-      config:
-        - subnet: 172.16.239.0/24
-          gateway: 172.16.239.1
-        - subnet: 2001:3200:3200::/64
-          gateway: 2001:3200:3200::1

+ 0 - 43
.ci/compatibility_tests/docker-compose-redis-single-tls.yaml

@@ -1,43 +0,0 @@
-version: '3'
-
-services:
-  erlang:
-    container_name: erlang
-    image: emqx/build-env:erl23.2.2-ubuntu20.04
-    volumes:
-      - ../..:/emqx
-    networks:
-      - emqx_bridge
-    depends_on:
-      - redis_server
-    working_dir: /emqx
-    tty: true
-
-  redis_server:
-    container_name: redis
-    image: redis:${REDIS_TAG}
-    volumes:
-      - ../../apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs:/tls
-    command:
-      - redis-server
-      - "--bind 0.0.0.0 ::"
-      - --tls-port 6380
-      - --tls-cert-file /tls/redis.crt
-      - --tls-key-file /tls/redis.key
-      - --tls-ca-cert-file /tls/ca.crt
-    restart: always
-    networks:
-      - emqx_bridge
-
-networks:
-  emqx_bridge:
-    driver: bridge
-    name: emqx_bridge
-    enable_ipv6: true
-    ipam:
-      driver: default
-      config:
-        - subnet: 172.100.100.0/24
-          gateway: 172.100.100.1
-        - subnet: 2001:3200:3200::/64
-          gateway: 2001:3200:3200::1

+ 0 - 37
.ci/compatibility_tests/docker-compose-redis-single.yaml

@@ -1,37 +0,0 @@
-version: '3'
-
-services:
-  erlang:
-    container_name: erlang
-    image: emqx/build-env:erl23.2.2-ubuntu20.04
-    volumes:
-      - ../..:/emqx
-    networks:
-      - emqx_bridge
-    depends_on:
-      - redis_server
-    working_dir: /emqx
-    tty: true
-
-  redis_server:
-    container_name: redis 
-    image: redis:${REDIS_TAG}
-    command:
-        - redis-server
-        - "--bind 0.0.0.0 ::"
-    restart: always
-    networks:
-      - emqx_bridge
-
-networks:
-  emqx_bridge:
-    driver: bridge
-    name: emqx_bridge
-    enable_ipv6: true
-    ipam:
-      driver: default
-      config:
-        - subnet: 172.100.100.0/24
-          gateway: 172.100.100.1
-        - subnet: 2001:3200:3200::/64
-          gateway: 2001:3200:3200::1

+ 0 - 16
.ci/compatibility_tests/openldap/slapd.conf

@@ -1,16 +0,0 @@
-include         /usr/local/etc/openldap/schema/core.schema
-include         /usr/local/etc/openldap/schema/cosine.schema
-include         /usr/local/etc/openldap/schema/inetorgperson.schema
-include         /usr/local/etc/openldap/schema/ppolicy.schema
-include         /usr/local/etc/openldap/schema/emqx.schema
-
-TLSCACertificateFile  /usr/local/etc/openldap/cacert.pem
-TLSCertificateFile    /usr/local/etc/openldap/cert.pem
-TLSCertificateKeyFile /usr/local/etc/openldap/key.pem
-
-database bdb
-suffix "dc=emqx,dc=io"
-rootdn "cn=root,dc=emqx,dc=io"
-rootpw {SSHA}eoF7NhNrejVYYyGHqnt+MdKNBh4r1w3W
-
-directory       /usr/local/etc/openldap/data

+ 0 - 2
.ci/compatibility_tests/redis/redis.conf

@@ -1,2 +0,0 @@
-daemonize yes
-bind 0.0.0.0 ::

.ci/apps_tests/.env → .ci/docker-compose-file/.env


+ 2 - 1
.ci/apps_tests/conf.env

@@ -1,6 +1,5 @@
 EMQX_AUTH__LDAP__SERVERS=ldap_server
 EMQX_AUTH__MONGO__SERVER=mongo_server:27017
-EMQX_AUTH__REDIS__SERVER=redis_server:6379
 EMQX_AUTH__MYSQL__SERVER=mysql_server:3306
 EMQX_AUTH__MYSQL__USERNAME=root
 EMQX_AUTH__MYSQL__PASSWORD=public
@@ -9,4 +8,6 @@ EMQX_AUTH__PGSQL__SERVER=pgsql_server:5432
 EMQX_AUTH__PGSQL__USERNAME=root
 EMQX_AUTH__PGSQL__PASSWORD=public
 EMQX_AUTH__PGSQL__DATABASE=mqtt
+EMQX_AUTH__REDIS__SERVER=redis_server:6379
+EMQX_AUTH__REDIS__PASSWORD=public
 CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_

+ 16 - 0
.ci/docker-compose-file/docker-compose-ldap-tcp.yaml

@@ -0,0 +1,16 @@
+version: '3.9'
+
+services:
+  ldap_server:
+    container_name: ldap
+    build:
+      context: ../..
+      dockerfile: .ci/docker-compose-file/openldap/Dockerfile
+      args: 
+        LDAP_TAG: ${LDAP_TAG}
+    image: openldap 
+    ports:
+      - 389:389
+    restart: always
+    networks:
+      - emqx_bridge

+ 14 - 0
.ci/docker-compose-file/docker-compose-mongo-tcp.yaml

@@ -0,0 +1,14 @@
+version: '3.9'
+
+services:
+  mongo_server:
+    container_name: mongo 
+    image: mongo:${MONGO_TAG}
+    restart: always
+    environment:
+      MONGO_INITDB_DATABASE: mqtt
+    networks:
+      - emqx_bridge
+    command:
+      --ipv6
+      --bind_ip_all

+ 18 - 0
.ci/docker-compose-file/docker-compose-mongo-tls.yaml

@@ -0,0 +1,18 @@
+version: '3.9'
+
+services:
+  mongo_server:
+    container_name: mongo
+    image: mongo:${MONGO_TAG}
+    restart: always
+    environment:
+      MONGO_INITDB_DATABASE: mqtt
+    volumes:
+        - ../../apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE_data/mongodb.pem/:/etc/certs/mongodb.pem
+    networks:
+      - emqx_bridge
+    command:
+      --ipv6
+      --bind_ip_all
+      --sslMode requireSSL
+      --sslPEMKeyFile /etc/certs/mongodb.pem

+ 20 - 0
.ci/docker-compose-file/docker-compose-mysql-tcp.yaml

@@ -0,0 +1,20 @@
+version: '3.9'
+
+services:
+  mysql_server:
+    container_name: mysql
+    image: mysql:${MYSQL_TAG}
+    restart: always
+    environment:
+      MYSQL_ROOT_PASSWORD: public
+      MYSQL_DATABASE: mqtt
+    networks:
+      - emqx_bridge
+    command:
+      --bind-address "::"
+      --character-set-server=utf8mb4
+      --collation-server=utf8mb4_general_ci
+      --explicit_defaults_for_timestamp=true
+      --lower_case_table_names=1
+      --max_allowed_packet=128M
+      --skip-symbolic-links

+ 18 - 25
.ci/compatibility_tests/docker-compose-mysql-tls.yaml

@@ -1,18 +1,6 @@
-version: '3'
+version: '3.9'
 
 services:
-  erlang:
-    container_name: erlang
-    image: emqx/build-env:erl23.2.2-ubuntu20.04
-    volumes:
-      - ../../:/emqx
-    working_dir: /emqx
-    networks:
-      - emqx_bridge
-    depends_on:
-      - mysql_server
-    tty: true
-
   mysql_server:
     container_name: mysql
     image: mysql:${MYSQL_TAG}
@@ -20,6 +8,8 @@ services:
     environment:
       MYSQL_ROOT_PASSWORD: public
       MYSQL_DATABASE: mqtt
+      MYSQL_USER: ssluser
+      MYSQL_PASSWORD: public
     volumes:
       - ../../apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data/ca.pem:/etc/certs/ca-cert.pem
       - ../../apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data/server-cert.pem:/etc/certs/server-cert.pem
@@ -38,15 +28,18 @@ services:
       --ssl-cert=/etc/certs/server-cert.pem
       --ssl-key=/etc/certs/server-key.pem
 
-networks:
-  emqx_bridge:
-    driver: bridge
-    name: emqx_bridge
-    enable_ipv6: true
-    ipam:
-      driver: default
-      config:
-        - subnet: 172.100.100.0/24
-          gateway: 172.100.100.1
-        - subnet: 2001:3200:3200::/64
-          gateway: 2001:3200:3200::1
+  mysql_client:
+    container_name: mysql_client
+    image: mysql:${MYSQL_TAG}
+    networks:
+      - emqx_bridge
+    depends_on:
+      - mysql_server
+    command:
+      - /bin/bash
+      - -c
+      - |
+        service mysql start
+        echo "show tables;" | mysql -h mysql_server -u root -ppublic mqtt mqtt
+        while [[ $$? -ne 0 ]];do echo "show tables;" | mysql -h mysql_server -u root -ppublic mqtt; done
+        echo "ALTER USER 'ssluser'@'%' REQUIRE X509;" | mysql -h mysql_server -u root -ppublic mqtt

+ 15 - 0
.ci/docker-compose-file/docker-compose-pgsql-tcp.yaml

@@ -0,0 +1,15 @@
+version: '3.9'
+
+services:
+  pgsql_server:
+    container_name: pgsql
+    image: postgres:${PGSQL_TAG}
+    restart: always
+    environment:
+      POSTGRES_PASSWORD: public
+      POSTGRES_USER: root
+      POSTGRES_DB: mqtt
+    ports:
+      - "5432:5432"
+    networks:
+      - emqx_bridge

+ 2 - 27
.ci/compatibility_tests/docker-compose-pgsql-tls.yaml

@@ -1,23 +1,11 @@
-version: '3'
+version: '3.9'
 
 services:
-  erlang:
-    container_name: erlang
-    image: emqx/build-env:erl23.2.2-ubuntu20.04
-    volumes:
-      - ../../:/emqx
-    working_dir: /emqx
-    networks:
-      - emqx_bridge
-    depends_on:
-      - pgsql_server
-    tty: true
-
   pgsql_server:
     container_name: pgsql
     build:
       context: ../..
-      dockerfile: .ci/compatibility_tests/pgsql/Dockerfile
+      dockerfile: .ci/docker-compose-file/pgsql/Dockerfile
       args:
         POSTGRES_USER: postgres
         BUILD_FROM: postgres:${PGSQL_TAG}
@@ -42,16 +30,3 @@ services:
       - hba_file=/var/lib/postgresql/pg_hba.conf
     networks:
       - emqx_bridge
-
-networks:
-  emqx_bridge:
-    driver: bridge
-    name: emqx_bridge
-    enable_ipv6: true
-    ipam:
-      driver: default
-      config:
-        - subnet: 172.100.100.0/24
-          gateway: 172.100.100.1
-        - subnet: 2001:3200:3200::/64
-          gateway: 2001:3200:3200::1

+ 11 - 0
.ci/docker-compose-file/docker-compose-redis-cluster-tcp.yaml

@@ -0,0 +1,11 @@
+version: '3.9'
+
+services:
+  redis_cluster:
+    image: redis:${REDIS_TAG}
+    container_name: redis
+    volumes:
+      - ./redis/:/data/conf
+    command: bash -c "/bin/bash /data/conf/redis.sh --node cluster && tail -f /var/log/redis-server.log"
+    networks:
+      - emqx_bridge

+ 12 - 0
.ci/docker-compose-file/docker-compose-redis-cluster-tls.yaml

@@ -0,0 +1,12 @@
+version: '3.9'
+
+services:
+  redis_cluster:
+    container_name: redis
+    image: redis:${REDIS_TAG}
+    volumes:
+      - ../../apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs:/tls
+      - ./redis/:/data/conf
+    command: bash -c "/bin/bash /data/conf/redis.sh --node cluster --tls-enabled && tail -f /var/log/redis-server.log"
+    networks:
+      - emqx_bridge

+ 11 - 0
.ci/docker-compose-file/docker-compose-redis-sentinel-tcp.yaml

@@ -0,0 +1,11 @@
+version: '3.9'
+
+services:
+  redis_cluster:
+    container_name: redis
+    image: redis:${REDIS_TAG}
+    volumes:
+      - ./redis/:/data/conf
+    command: bash -c "/bin/bash /data/conf/redis.sh --node sentinel && tail -f /var/log/redis-server.log"
+    networks:
+      - emqx_bridge

+ 13 - 0
.ci/docker-compose-file/docker-compose-redis-single-tcp.yaml

@@ -0,0 +1,13 @@
+version: '3.9'
+
+services:
+  redis_server:
+    container_name: redis 
+    image: redis:${REDIS_TAG}
+    command:
+      - redis-server
+      - "--bind 0.0.0.0 ::"
+      - --requirepass public
+    restart: always
+    networks:
+      - emqx_bridge

+ 19 - 0
.ci/docker-compose-file/docker-compose-redis-single-tls.yaml

@@ -0,0 +1,19 @@
+version: '3.9'
+
+services:
+  redis_server:
+    container_name: redis
+    image: redis:${REDIS_TAG}
+    volumes:
+      - ../../apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs:/tls
+    command:
+      - redis-server
+      - "--bind 0.0.0.0 ::"
+      - --requirepass public
+      - --tls-port 6380
+      - --tls-cert-file /tls/redis.crt
+      - --tls-key-file /tls/redis.key
+      - --tls-ca-cert-file /tls/ca.crt
+    restart: always
+    networks:
+      - emqx_bridge

+ 13 - 19
.ci/compatibility_tests/docker-compose-ldap.yaml

@@ -1,32 +1,26 @@
-version: '3'
+version: '3.9'
 
 services:
   erlang:
     container_name: erlang
-    image: emqx/build-env:erl23.2.2-ubuntu20.04
-    depends_on:
-      - ldap_server
+    image: emqx/build-env:erl23.2.7-ubuntu20.04
+    env_file:
+      - conf.env
+    environment:
+      GITHUB_ACTIONS: ${GITHUB_ACTIONS}
+      GITHUB_TOKEN: ${GITHUB_TOKEN}
+      GITHUB_RUN_ID: ${GITHUB_RUN_ID}
+      GITHUB_SHA: ${GITHUB_SHA}
+      GITHUB_RUN_NUMBER: ${GITHUB_RUN_NUMBER}
+      GITHUB_EVENT_NAME: ${GITHUB_EVENT_NAME}
+      GITHUB_REF: ${GITHUB_REF}
     networks:
       - emqx_bridge
     volumes:
-      - ../../.:/emqx
+      - ../..:/emqx
     working_dir: /emqx
     tty: true
 
-  ldap_server:
-    container_name: ldap
-    build:
-      context: ../..
-      dockerfile: .ci/compatibility_tests/openldap/Dockerfile
-      args: 
-        LDAP_TAG: ${LDAP_TAG}
-    image: openldap 
-    ports:
-      - 389:389
-    restart: always
-    networks:
-      - emqx_bridge
-
 networks:
   emqx_bridge:
     driver: bridge

+ 1 - 1
.ci/compatibility_tests/openldap/Dockerfile

@@ -9,7 +9,7 @@ RUN wget ftp://ftp.openldap.org/pub/OpenLDAP/openldap-release/openldap-${LDAP_TA
     && ./configure && make depend && make && make install \
     && cd .. && rm -rf  openldap-${LDAP_TAG}
 
-COPY .ci/compatibility_tests/openldap/slapd.conf /usr/local/etc/openldap/slapd.conf
+COPY .ci/docker-compose-file/openldap/slapd.conf /usr/local/etc/openldap/slapd.conf
 COPY apps/emqx_auth_ldap/emqx.io.ldif /usr/local/etc/openldap/schema/emqx.io.ldif
 COPY apps/emqx_auth_ldap/emqx.schema /usr/local/etc/openldap/schema/emqx.schema
 COPY apps/emqx_auth_ldap/test/certs/*.pem /usr/local/etc/openldap/

.ci/apps_tests/openldap/slapd.conf → .ci/docker-compose-file/openldap/slapd.conf


+ 4 - 4
.ci/compatibility_tests/pgsql/Dockerfile

@@ -1,10 +1,10 @@
 ARG BUILD_FROM=postgres:11
 FROM ${BUILD_FROM}
 ARG POSTGRES_USER=postgres
-COPY --chown=$POSTGRES_USER .ci/compatibility_tests/pgsql/pg_hba.conf /var/lib/postgresql/pg_hba.conf
-COPY --chown=$POSTGRES_USER apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server.key /var/lib/postgresql/server.key
-COPY --chown=$POSTGRES_USER apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server.crt /var/lib/postgresql/server.crt
-COPY --chown=$POSTGRES_USER apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/root.crt /var/lib/postgresql/root.crt
+COPY --chown=$POSTGRES_USER .ci/docker-compose-file/pgsql/pg_hba.conf /var/lib/postgresql/pg_hba.conf
+COPY --chown=$POSTGRES_USER apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server-key.pem /var/lib/postgresql/server.key
+COPY --chown=$POSTGRES_USER apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server-cert.pem /var/lib/postgresql/server.crt
+COPY --chown=$POSTGRES_USER apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/ca.pem /var/lib/postgresql/root.crt
 RUN chmod 600 /var/lib/postgresql/pg_hba.conf
 RUN chmod 600 /var/lib/postgresql/server.key
 RUN chmod 600 /var/lib/postgresql/server.crt

.ci/compatibility_tests/pgsql/pg_hba.conf → .ci/docker-compose-file/pgsql/pg_hba.conf


+ 3 - 1
.ci/compatibility_tests/redis/redis-tls.conf

@@ -1,5 +1,7 @@
 daemonize yes
 bind 0.0.0.0 ::
+logfile /var/log/redis-server.log
 tls-cert-file /tls/redis.crt
 tls-key-file /tls/redis.key
-tls-ca-cert-file /tls/ca.crt
+tls-ca-cert-file /tls/ca.crt
+requirepass public

+ 4 - 0
.ci/docker-compose-file/redis/redis.conf

@@ -0,0 +1,4 @@
+daemonize yes
+bind 0.0.0.0 ::
+logfile /var/log/redis-server.log
+requirepass public

+ 7 - 3
.ci/compatibility_tests/redis/redis.sh

@@ -1,5 +1,9 @@
 #!/bin/bash
 
+set -x
+
+LOCAL_IP=$(hostname -i | grep -oE '((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])\.){3}(25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])' | head -n 1)
+
 node=single
 tls=false
 while [[ $# -gt 0 ]]
@@ -48,9 +52,9 @@ elif [ "${node}" = "sentinel" ] ; then
     redis-server /data/conf/redis.conf --port 7000 --cluster-config-file /data/conf/nodes.7000.conf \
                                        --cluster-enabled no;
     redis-server /data/conf/redis.conf --port 7001 --cluster-config-file /data/conf/nodes.7001.conf \
-                                       --cluster-enabled no --slaveof 172.16.239.10 7000;
+                                       --cluster-enabled no --slaveof "$LOCAL_IP" 7000;
     redis-server /data/conf/redis.conf --port 7002 --cluster-config-file /data/conf/nodes.7002.conf \
-                                       --cluster-enabled no --slaveof 172.16.239.10 7000;
+                                       --cluster-enabled no --slaveof "$LOCAL_IP" 7000;
 fi
 REDIS_LOAD_FLG=true;
 
@@ -76,7 +80,7 @@ do
         continue;
     fi
     if [ "${node}" = "cluster" ] ; then
-      yes "yes" | redis-cli --cluster create 172.16.239.10:7000 172.16.239.10:7001 172.16.239.10:7002;
+      yes "yes" | redis-cli --cluster create "$LOCAL_IP:7000" "$LOCAL_IP:7001" "$LOCAL_IP:7002" --pass public --no-auth-warning;
     elif [ "${node}" = "sentinel" ] ; then
       cp /data/conf/sentinel.conf /_sentinel.conf
       redis-server /_sentinel.conf --sentinel;

+ 1 - 0
.ci/compatibility_tests/redis/sentinel.conf

@@ -1,3 +1,4 @@
 port 26379
 dir /tmp
 sentinel monitor mymaster 172.16.239.10 7000 1
+logfile /var/log/redis-server.log

+ 1 - 0
.ci/fvt_tests/.env

@@ -0,0 +1 @@
+TARGET=emqx/emqx

+ 7 - 3
.ci/fvt_tests/docker-compose.yaml

@@ -3,7 +3,7 @@ version: '3'
 services:
   emqx1:
     container_name: node1.emqx.io
-    image: emqx/emqx:build-alpine-amd64
+    image: ${TARGET}:build-alpine-amd64
     environment:
     - "EMQX_NAME=emqx"
     - "EMQX_HOST=node1.emqx.io"
@@ -11,6 +11,7 @@ services:
     - "EMQX_CLUSTER__STATIC__SEEDS=emqx@node1.emqx.io, emqx@node2.emqx.io"
     - "EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s"
     - "EMQX_MQTT__MAX_TOPIC_ALIAS=10"
+    - "EMQX_LOG__LEVEL=debug"
     command:
         - /bin/sh
         - -c
@@ -30,7 +31,7 @@ services:
 
   emqx2:
     container_name: node2.emqx.io
-    image: emqx/emqx:build-alpine-amd64
+    image: ${TARGET}:build-alpine-amd64
     environment:
     - "EMQX_NAME=emqx"
     - "EMQX_HOST=node2.emqx.io"
@@ -38,6 +39,7 @@ services:
     - "EMQX_CLUSTER__STATIC__SEEDS=emqx@node1.emqx.io, emqx@node2.emqx.io"
     - "EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s"
     - "EMQX_MQTT__MAX_TOPIC_ALIAS=10"
+    - "EMQX_LOG__LEVEL=debug"
     command:
       - /bin/sh
       - -c
@@ -46,7 +48,7 @@ services:
         sed -i '/emqx_telemetry/d' /opt/emqx/data/loaded_plugins
         /opt/emqx/bin/emqx foreground
     healthcheck:
-      test: ["CMD", "/opt/emqx/bin/emqx_ctl", "status"]
+      test: ["CMD", "/opt/emqx/bin/emqx", "ping"]
       interval: 5s
       timeout: 25s
       retries: 5
@@ -64,6 +66,8 @@ services:
     tty: true
     networks:
         emqx-bridge:
+    volumes:
+      - ./scripts:/scripts
 
 networks:
   emqx-bridge:

+ 22 - 0
.ci/fvt_tests/scripts/pytest.sh

@@ -0,0 +1,22 @@
+#!/bin/sh
+
+## This script is to run emqx cluster smoke tests (fvt) in github action
+## This script is executed in pacho_client
+
+set -x
+set +e
+
+NODE1="node1.emqx.io"
+NODE2="node2.emqx.io"
+
+apk update && apk add git curl
+git clone -b develop-4.0 https://github.com/emqx/paho.mqtt.testing.git /paho.mqtt.testing
+pip install pytest
+pytest -v /paho.mqtt.testing/interoperability/test_client/V5/test_connect.py -k test_basic --host "$NODE1"
+RESULT=$?
+pytest -v /paho.mqtt.testing/interoperability/test_cluster --host1 "$NODE1" --host2 "$NODE2"
+RESULT=$(( RESULT + $? ))
+pytest -v /paho.mqtt.testing/interoperability/test_client --host "$NODE1"
+RESULT=$(( RESULT + $? ))
+
+exit $RESULT

+ 5 - 0
.gitattributes

@@ -0,0 +1,5 @@
+* text=auto
+*.* text eol=lf
+*.jpg -text
+*.png -text
+*.pdf -text

+ 241 - 156
.github/workflows/build_packages.yaml

@@ -6,71 +6,133 @@ on:
   push:
     tags:
       - v*
+      - e*
   release:
     types:
       - published
+  workflow_dispatch:
 
 jobs:
+  prepare:
+    runs-on: ubuntu-20.04
+    container: emqx/build-env:erl23.2.7-ubuntu20.04
+
+    outputs:
+      profiles: ${{ steps.set_profile.outputs.profiles}}
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          path: source
+      - name: set profile
+        id: set_profile
+        shell: bash
+        run: |
+          if make -C source emqx-ee --dry-run > /dev/null 2>&1; then
+            echo "::set-output name=profiles::[\"emqx-ee\"]"
+          else
+            echo "::set-output name=profiles::[\"emqx\", \"emqx-edge\"]"
+          fi
+      - name: get_all_deps
+        if: endsWith(github.repository, 'emqx')
+        run: |
+          make -C source deps-all
+          zip -ryq source.zip source
+      - name: get_all_deps
+        if: endsWith(github.repository, 'enterprise')
+        run: |
+          echo "https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com" > $HOME/.git-credentials
+          git config --global credential.helper store
+          echo "${{ secrets.CI_GIT_TOKEN }}" >> source/scripts/git-token
+          make -C source deps-all
+          zip -ryq source.zip source
+      - uses: actions/upload-artifact@v2
+        with:
+          name: source
+          path: source.zip
+
   windows:
     runs-on: windows-2019
 
+    needs: prepare
+    if: endsWith(github.repository, 'emqx')
+
+    strategy:
+      matrix:
+        profile: ${{fromJSON(needs.prepare.outputs.profiles)}}
+        exclude:
+          - profile: emqx-edge
+
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/download-artifact@v2
+      with:
+        name: source
+        path: .
+    - name: unzip source code
+      run: Expand-Archive -Path source.zip -DestinationPath ./
     - uses: ilammy/msvc-dev-cmd@v1
     - uses: gleam-lang/setup-erlang@v1.1.0
       id: install_erlang
       with:
         otp-version: 23.2
     - name: build
+      env:
+        PYTHON: python
       run: |
-        #     set-executionpolicy remotesigned -s cu
-        #     iex (new-object net.webclient).downloadstring('https://get.scoop.sh')
-        #     # $env:path + ";" + $env:USERPROFILE + "\scoop\shims" + ';C:\Program Files\erl10.4\bin'
-        #     [environment]::SetEnvironmentvariable("Path", ";" + $env:USERPROFILE + "\scoop\shims")
-        #     [environment]::SetEnvironmentvariable("Path", ';C:\Program Files\erl10.4\bin')
-        #     scoop bucket add extras https://github.com/lukesampson/scoop-extras.git
-        #     scoop update
-        #     scoop install sudo curl vcredist2013
-
         $env:PATH = "${{ steps.install_erlang.outputs.erlpath }}\bin;$env:PATH"
 
         $version = $( "${{ github.ref }}" -replace "^(.*)/(.*)/" )
         if ($version -match "^v[0-9]+\.[0-9]+(\.[0-9]+)?") {
           $regex = "[0-9]+\.[0-9]+(-alpha|-beta|-rc)?\.[0-9]"
-          $pkg_name = "emqx-windows-$([regex]::matches($version, $regex).value).zip"
+          $pkg_name = "${{ matrix.profile }}-windows-$([regex]::matches($version, $regex).value).zip"
           }
         else {
-          $pkg_name = "emqx-windows-$($version -replace '/').zip"
+          $pkg_name = "${{ matrix.profile }}-windows-$($version -replace '/').zip"
           }
-
-        make deps-emqx || cat rebar3.crashdump
-        $rebar3 = $env:USERPROFILE + "\rebar3"
-        (New-Object System.Net.WebClient).DownloadFile('https://s3.amazonaws.com/rebar3/rebar3', $rebar3)
-        cd _build/emqx/lib/jiffy/
-        escript $rebar3 compile
-        cd ../../../../
-
-        make emqx
-        mkdir -p _packages/emqx
-        Compress-Archive -Path _build/emqx/rel/emqx -DestinationPath _build/emqx/rel/$pkg_name
-        mv _build/emqx/rel/$pkg_name _packages/emqx
-        Get-FileHash -Path "_packages/emqx/$pkg_name" | Format-List | grep 'Hash' | awk '{print $3}'  > _packages/emqx/$pkg_name.sha256
+        cd source
+        ## We do not build/release bcrypt for windows package
+        Remove-Item -Recurse -Force -Path _build/default/lib/bcrypt/
+        if (Test-Path rebar.lock) {
+            Remove-Item -Force -Path rebar.lock
+        }
+        make ${{ matrix.profile }}
+        mkdir -p _packages/${{ matrix.profile }}
+        Compress-Archive -Path _build/${{ matrix.profile }}/rel/emqx -DestinationPath _build/${{ matrix.profile }}/rel/$pkg_name
+        mv _build/${{ matrix.profile }}/rel/$pkg_name _packages/${{ matrix.profile }}
+        Get-FileHash -Path "_packages/${{ matrix.profile }}/$pkg_name" | Format-List | grep 'Hash' | awk '{print $3}'  > _packages/${{ matrix.profile }}/$pkg_name.sha256
     - name: run emqx
+      timeout-minutes: 1
       run: |
-        ./_build/emqx/rel/emqx/bin/emqx start
-        ./_build/emqx/rel/emqx/bin/emqx stop
-        ./_build/emqx/rel/emqx/bin/emqx install
-        ./_build/emqx/rel/emqx/bin/emqx uninstall
+        cd source
+        ./_build/${{ matrix.profile }}/rel/emqx/bin/emqx start
+        Start-Sleep -s 5
+        ./_build/${{ matrix.profile }}/rel/emqx/bin/emqx stop
+        ./_build/${{ matrix.profile }}/rel/emqx/bin/emqx install
+        ./_build/${{ matrix.profile }}/rel/emqx/bin/emqx uninstall
     - uses: actions/upload-artifact@v1
+      if: startsWith(github.ref, 'refs/tags/')
       with:
-        name: emqx
-        path: ./_packages/emqx/.
+        name: ${{ matrix.profile }}
+        path: source/_packages/${{ matrix.profile }}/.
 
   mac:
     runs-on: macos-10.15
 
+    needs: prepare
+
+    strategy:
+      matrix:
+        profile: ${{fromJSON(needs.prepare.outputs.profiles)}}
+        exclude:
+          - profile: emqx-edge
+
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/download-artifact@v2
+      with:
+        name: source
+        path: .
+    - name: unzip source code
+      run: unzip -q source.zip
     - name: prepare
       run: |
         brew install curl zip unzip gnu-sed kerl unixodbc freetds
@@ -79,16 +141,17 @@ jobs:
     - name: build erlang
       timeout-minutes: 60
       run: |
-        kerl build 23.2.2
-        kerl install 23.2.2 $HOME/.kerl/23.2.2
+        kerl build 23.2.7
+        kerl install 23.2.7 $HOME/.kerl/23.2.7
     - name: build
       run: |
-        . $HOME/.kerl/23.2.2/activate
-        make emqx-pkg
+        . $HOME/.kerl/23.2.7/activate
+        make -C source ${{ matrix.profile }}-pkg
     - name: test
       run: |
-        pkg_name=$(basename _packages/emqx/emqx-macos-*.zip)
-        unzip _packages/emqx/$pkg_name
+        cd source
+        pkg_name=$(basename _packages/${{ matrix.profile }}/${{ matrix.profile }}-*.zip)
+        unzip _packages/${{ matrix.profile }}/$pkg_name
         gsed -i '/emqx_telemetry/d' ./emqx/data/loaded_plugins
         ./emqx/bin/emqx start || cat emqx/log/erlang.log.1
         ready='no'
@@ -107,46 +170,51 @@ jobs:
         ./emqx/bin/emqx_ctl status
         ./emqx/bin/emqx stop
         rm -rf emqx
-        openssl dgst -sha256 ./_packages/emqx/$pkg_name | awk '{print $2}'  > ./_packages/emqx/$pkg_name.sha256
+        openssl dgst -sha256 ./_packages/${{ matrix.profile }}/$pkg_name | awk '{print $2}'  > ./_packages/${{ matrix.profile }}/$pkg_name.sha256
     - uses: actions/upload-artifact@v1
+      if: startsWith(github.ref, 'refs/tags/')
       with:
-        name: emqx
-        path: ./_packages/emqx/.
+        name: ${{ matrix.profile }}
+        path: source/_packages/${{ matrix.profile }}/.
 
   linux:
     runs-on: ubuntu-20.04
 
+    needs: prepare
+
     strategy:
       matrix:
+        profile: ${{fromJSON(needs.prepare.outputs.profiles)}}
         arch:
-        - amd64
-        - arm64
-        emqx:
-        - emqx
-        - emqx-edge
+          - amd64
+          - arm64
         os:
-        - ubuntu20.04
-        - ubuntu18.04
-        - ubuntu16.04
-        - debian10
-        - debian9
-        - opensuse
-        - centos8
-        - centos7
-        - centos6
-        - raspbian10
-        - raspbian9
+          - ubuntu20.04
+          - ubuntu18.04
+          - ubuntu16.04
+          - debian10
+          - debian9
+          # - opensuse
+          - centos8
+          - centos7
+          - centos6
+          - raspbian10
+          - raspbian9
         exclude:
+        - os: centos6
+          arch: arm64
         - os: raspbian9
           arch: amd64
-        - os: raspbian9
-          emqx: emqx
         - os: raspbian10
           arch: amd64
+        - os: raspbian9
+          profile: emqx
         - os: raspbian10
-          emqx: emqx
-        - os: centos6
-          arch: arm64
+          profile: emqx
+        - os: raspbian9
+          profile: emqx-ee
+        - os: raspbian10
+          profile: emqx-ee
 
     defaults:
       run:
@@ -162,71 +230,71 @@ jobs:
         docker info
         docker buildx create --use --name mybuild
         docker run --rm --privileged tonistiigi/binfmt --install all
-    - uses: actions/checkout@v1
-    - name: get deps
-      env:
-        ERL_OTP: erl23.2.2
-      run: |
-        docker run -i --rm \
-            -e GITHUB_RUN_ID=$GITHUB_RUN_ID \
-            -e GITHUB_REF=$GITHUB_REF \
-            -v $(pwd):/emqx \
-            -w /emqx \
-            emqx/build-env:${ERL_OTP}-debian10 \
-            bash -c "make deps-all"
+    - uses: actions/download-artifact@v2
+      with:
+        name: source
+        path: .
+    - name: unzip source code
+      run: unzip -q source.zip
     - name: downloads emqx zip packages
       env:
-        EMQX: ${{ matrix.emqx }}
+        PROFILE: ${{ matrix.profile }}
         ARCH: ${{ matrix.arch }}
         SYSTEM: ${{ matrix.os }}
       run: |
         set -e -u -x
-        if [ $EMQX = "emqx-edge" ];then broker="emqx-edge"; else broker="emqx-ce"; fi
-        if [ $ARCH = "arm64" ];then arch="aarch64"; else arch="x86_64"; fi
+        cd source
+        if [ $PROFILE = "emqx" ];then broker="emqx-ce"; else broker="$PROFILE"; fi
+        if [ $PROFILE = "emqx-ee" ];then edition='enterprise'; else edition='opensource'; fi
 
-        vsn="$(grep -oE '\{vsn, (.*)\}' src/emqx.app.src | sed -r 's/\{vsn, (.*)\}/\1/g' | sed 's/\"//g')"
+        vsn="$(grep -E "define.+EMQX_RELEASE.+${edition}" include/emqx_release.hrl | cut -d '"' -f2)"
         pre_vsn="$(echo $vsn | grep -oE '^[0-9]+.[0-9]')"
-        old_vsns=($(git tag -l "$pre_vsn.[0-9]" | sed "s/$vsn//"))
+        if  [ $PROFILE = "emqx-ee" ]; then
+            old_vsns=($(git tag -l "e$pre_vsn.[0-9]" | sed "s/e$vsn//"))
+        else
+            old_vsns=($(git tag -l "v$pre_vsn.[0-9]" | sed "s/v$vsn//"))
+        fi
 
-        mkdir -p tmp/relup_packages/$EMQX
-        cd tmp/relup_packages/$EMQX
+        mkdir -p tmp/relup_packages/$PROFILE
+        cd tmp/relup_packages/$PROFILE
         for tag in ${old_vsns[@]};do
-          if [ ! -z "$(echo  $(curl -I -m 10 -o /dev/null -s -w %{http_code} https://s3-us-west-2.amazonaws.com/packages.emqx/$broker/v${tag#[e|v]}/$EMQX-$SYSTEM-${tag#[e|v]}-$arch.zip) | grep -oE "^[23]+")" ];then
-            wget https://s3-us-west-2.amazonaws.com/packages.emqx/$broker/v${tag#[e|v]}/$EMQX-$SYSTEM-${tag#[e|v]}-$arch.zip
-            wget https://s3-us-west-2.amazonaws.com/packages.emqx/$broker/v${tag#[e|v]}/$EMQX-$SYSTEM-${tag#[e|v]}-$arch.zip.sha256
-            echo "$(cat $EMQX-$SYSTEM-${tag#[e|v]}-$arch.zip.sha256) $EMQX-$SYSTEM-${tag#[e|v]}-$arch.zip" | sha256sum -c || exit 1
+          if [ ! -z "$(echo  $(curl -I -m 10 -o /dev/null -s -w %{http_code} https://s3-${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com/${{ secrets.AWS_S3_BUCKET }}/$broker/$tag/$PROFILE-$SYSTEM-${tag#[e|v]}-$ARCH.zip) | grep -oE "^[23]+")" ];then
+            wget https://s3-${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com/${{ secrets.AWS_S3_BUCKET }}/$broker/$tag/$PROFILE-$SYSTEM-${tag#[e|v]}-$ARCH.zip
+            wget https://s3-${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com/${{ secrets.AWS_S3_BUCKET }}/$broker/$tag/$PROFILE-$SYSTEM-${tag#[e|v]}-$ARCH.zip.sha256
+            echo "$(cat $PROFILE-$SYSTEM-${tag#[e|v]}-$ARCH.zip.sha256) $PROFILE-$SYSTEM-${tag#[e|v]}-$ARCH.zip" | sha256sum -c || exit 1
           fi
         done
         cd -
     - name: build emqx packages
       env:
-        ERL_OTP: erl23.2.2
-        EMQX: ${{ matrix.emqx }}
+        ERL_OTP: erl23.2.7
+        PROFILE: ${{ matrix.profile }}
         ARCH: ${{ matrix.arch }}
         SYSTEM: ${{ matrix.os }}
       run: |
-        set -e -u -x
+        set -e -u
+        cd source
         docker buildx build --no-cache \
           --platform=linux/$ARCH \
           -t cross_build_emqx_for_$SYSTEM \
           -f .ci/build_packages/Dockerfile \
           --build-arg BUILD_FROM=emqx/build-env:$ERL_OTP-$SYSTEM \
-          --build-arg EMQX_NAME=$EMQX \
-          --output type=tar,dest=/tmp/cross-build-$EMQX-for-$SYSTEM.tar .
+          --build-arg EMQX_NAME=$PROFILE \
+          --output type=tar,dest=/tmp/cross-build-$PROFILE-for-$SYSTEM.tar .
 
-        mkdir -p /tmp/packages/$EMQX
-        tar -xvf /tmp/cross-build-$EMQX-for-$SYSTEM.tar --wildcards emqx/_packages/$EMQX/*
-        mv emqx/_packages/$EMQX/* /tmp/packages/$EMQX/
-        rm -rf /tmp/cross-build-$EMQX-for-$SYSTEM.tar
+        mkdir -p /tmp/packages/$PROFILE
+        tar -xvf /tmp/cross-build-$PROFILE-for-$SYSTEM.tar --wildcards emqx/_packages/$PROFILE/*
+        mv emqx/_packages/$PROFILE/* /tmp/packages/$PROFILE/
+        rm -rf /tmp/cross-build-$PROFILE-for-$SYSTEM.tar
 
         docker rm -f $(docker ps -a -q)
         docker volume prune -f
     - name: create sha256
       env:
-        EMQX: ${{ matrix.emqx }}
+        PROFILE: ${{ matrix.profile}}
       run: |
-        if [ -d /tmp/packages/$EMQX ]; then
-          cd /tmp/packages/$EMQX
+        if [ -d /tmp/packages/$PROFILE ]; then
+          cd /tmp/packages/$PROFILE
             for var in $(ls emqx-* ); do
               bash -c "echo $(sha256sum $var | awk '{print $1}') > $var.sha256"
             done
@@ -235,52 +303,74 @@ jobs:
     - uses: actions/upload-artifact@v1
       if: startsWith(github.ref, 'refs/tags/')
       with:
-        name: ${{ matrix.emqx }}
-        path: /tmp/packages/${{ matrix.emqx }}/.
+        name: ${{ matrix.profile }}
+        path: /tmp/packages/${{ matrix.profile }}/.
 
   docker:
     runs-on: ubuntu-20.04
 
+    needs: prepare
+
     strategy:
       matrix:
+        profile: ${{fromJSON(needs.prepare.outputs.profiles)}}
         arch:
-        - [amd64, x86_64]
-        - [arm64v8, aarch64]
-        - [arm32v7, arm]
-        - [i386, i386]
-        - [s390x, s390x]
+          - [amd64, x86_64]
+          - [arm64v8, aarch64]
+          - [arm32v7, arm]
+          - [i386, i386]
+          - [s390x, s390x]
+        exclude:
+          - profile: emqx-ee
+            arch: [i386, i386]
+          - profile: emqx-ee
+            arch: [s390x, s390x]
 
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/download-artifact@v2
+      with:
+        name: source
+        path: .
+    - name: unzip source code
+      run: unzip -q source.zip
     - name: build emqx docker image
       env:
+        PROFILE: ${{ matrix.profile }}
         ARCH: ${{ matrix.arch[0] }}
         QEMU_ARCH: ${{ matrix.arch[1] }}
       run: |
         sudo docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
 
-        sudo TARGET=emqx/emqx ARCH=$ARCH QEMU_ARCH=$QEMU_ARCH  make docker
-        cd _packages/emqx && for var in $(ls emqx-docker-* ); do sudo bash -c "echo $(sha256sum $var | awk '{print $1}') > $var.sha256"; done && cd -
-
-        sudo TARGET=emqx/emqx-edge ARCH=$ARCH QEMU_ARCH=$QEMU_ARCH  make docker
-        cd _packages/emqx-edge && for var in $(ls emqx-edge-docker-* ); do sudo bash -c "echo $(sha256sum $var | awk '{print $1}') > $var.sha256"; done && cd -
+        cd source
+        sudo TARGET=emqx/$PROFILE ARCH=$ARCH QEMU_ARCH=$QEMU_ARCH  make docker
+        cd _packages/$PROFILE && for var in $(ls ${PROFILE}-docker-* ); do sudo bash -c "echo $(sha256sum $var | awk '{print $1}') > $var.sha256"; done && cd -
     - uses: actions/upload-artifact@v1
+      if: startsWith(github.ref, 'refs/tags/')
       with:
-        name: emqx
-        path: ./_packages/emqx/.
-    - uses: actions/upload-artifact@v1
+        name: ${{ matrix.profile }}
+        path: source/_packages/${{ matrix.profile }}/.
+
+  delete-artifact:
+    runs-on: ubuntu-20.04
+    needs: [prepare, mac, linux, docker]
+    steps:
+    - uses: geekyeggo/delete-artifact@v1
       with:
-        name: emqx-edge
-        path: ./_packages/emqx-edge/.
+        name: source
 
   upload:
     runs-on: ubuntu-20.04
 
-    needs: [windows, mac, linux, docker]
-
     if: startsWith(github.ref, 'refs/tags/')
 
+    needs: [prepare, mac, linux, docker]
+
+    strategy:
+      matrix:
+        profile: ${{fromJSON(needs.prepare.outputs.profiles)}}
+
     steps:
+    - uses: actions/checkout@v2
     - name: get_version
       run: |
         echo 'version<<EOF' >> $GITHUB_ENV
@@ -288,47 +378,38 @@ jobs:
         echo 'EOF' >> $GITHUB_ENV
     - uses: actions/download-artifact@v2
       with:
-        name: emqx
-        path: ./_packages/emqx
-    - uses: actions/download-artifact@v2
-      with:
-        name: emqx-edge
-        path: ./_packages/emqx-edge
+        name: ${{ matrix.profile }}
+        path: ./_packages/${{ matrix.profile }}
     - name: install dos2unix
       run: sudo apt-get update && sudo apt install -y dos2unix
     - name: get packages
       run: |
-        set -e -x -u
-        for EMQX in emqx emqx-edge; do
-          cd _packages/$EMQX
-          for var in $( ls |grep emqx |grep -v sha256); do
-            dos2unix $var.sha256
-            echo "$(cat $var.sha256) $var" | sha256sum -c || exit 1
-          done
-          cd -
+        set -e -u
+        cd _packages/${{ matrix.profile }}
+        for var in $( ls |grep emqx |grep -v sha256); do
+          dos2unix $var.sha256
+          echo "$(cat $var.sha256) $var" | sha256sum -c || exit 1
         done
+        cd -
     - name: upload aws s3
       run: |
-        set -e -x -u
+        set -e -u
+        if [ "${{ matrix.profile }}"  == "emqx" ];then
+            broker="emqx-ce"
+        else
+            broker=${{ matrix.profile }}
+        fi
         aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }}
         aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_ACCESS_KEY }}
-        aws configure set default.region us-west-2
+        aws configure set default.region ${{ secrets.AWS_DEFAULT_REGION }}
 
-        aws s3 cp --recursive _packages/emqx s3://packages.emqx/emqx-ce/${{ env.version }}
-        aws s3 cp --recursive _packages/emqx-edge s3://packages.emqx/emqx-edge/${{ env.version }}
-        aws cloudfront create-invalidation --distribution-id E170YEULGLT8XB --paths "/emqx-ce/${{ env.version }}/*,/emqx-edge/${{ env.version }}/*"
-
-        mkdir packages
-        mv _packages/emqx/* packages
-        mv _packages/emqx-edge/* packages
-    - uses: actions/checkout@v2
-      with:
-        path: emqx
+        aws s3 cp --recursive _packages/${{ matrix.profile }} s3://${{ secrets.AWS_S3_BUCKET }}/$broker/${{ env.version }}
+        aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CLOUDFRONT_ID }} --paths "/$broker/${{ env.version }}/*"
     - uses: Rory-Z/upload-release-asset@v1
       if: github.event_name == 'release'
       with:
         repo: emqx
-        path: "packages/emqx-*"
+        path: "_packages/${{ matrix.profile }}/emqx-*"
         token: ${{ github.token }}
     - name: update to emqx.io
       if: github.event_name == 'release'
@@ -345,15 +426,22 @@ jobs:
       if: github.event_name == 'release'
       run: |
         set -e -x -u
-        sudo make -C emqx docker-prepare
-        cd packages && for var in $(ls |grep docker |grep -v sha256); do unzip $var; sudo docker load < ${var%.*}; rm -f ${var%.*}; done && cd -
+        sudo make docker-prepare
+        cd _packages/${{ matrix.profile }} && for var in $(ls |grep docker |grep -v sha256); do unzip $var; sudo docker load < ${var%.*}; rm -f ${var%.*}; done && cd -
         echo ${{ secrets.DOCKER_HUB_TOKEN }} |sudo docker login -u ${{ secrets.DOCKER_HUB_USER }} --password-stdin
-        sudo TARGET=emqx/emqx make -C emqx docker-push
-        sudo TARGET=emqx/emqx make -C emqx docker-manifest-list
-        sudo TARGET=emqx/emqx-edge make -C emqx docker-push
-        sudo TARGET=emqx/emqx-edge make -C emqx docker-manifest-list
+        sudo TARGET=emqx/${{ matrix.profile }} make docker-push
+        sudo TARGET=emqx/${{ matrix.profile }} make docker-manifest-list
     - name: update repo.emqx.io
-      if: github.event_name == 'release'
+      if: github.event_name == 'release' && endsWith(github.repository, 'enterprise') && matrix.profile == 'emqx-ee'
+      run: |
+        curl --silent --show-error \
+          -H "Authorization: token ${{ secrets.CI_GIT_TOKEN }}" \
+          -H "Accept: application/vnd.github.v3+json" \
+          -X POST \
+          -d "{\"ref\":\"v1.0.1\",\"inputs\":{\"version\": \"${{ env.version }}\", \"emqx_ee\": \"true\"}}" \
+          "https://api.github.com/repos/emqx/emqx-ci-helper/actions/workflows/update_emqx_repos.yaml/dispatches"
+    - name: update repo.emqx.io
+      if: github.event_name == 'release' && endsWith(github.repository, 'emqx') && matrix.profile == 'emqx'
       run: |
         curl --silent --show-error \
           -H "Authorization: token ${{ secrets.CI_GIT_TOKEN }}" \
@@ -362,7 +450,7 @@ jobs:
           -d "{\"ref\":\"v1.0.1\",\"inputs\":{\"version\": \"${{ env.version }}\", \"emqx_ce\": \"true\"}}" \
           "https://api.github.com/repos/emqx/emqx-ci-helper/actions/workflows/update_emqx_repos.yaml/dispatches"
     - name: update homebrew packages
-      if: github.event_name == 'release'
+      if: github.event_name == 'release' && endsWith(github.repository, 'emqx') && matrix.profile == 'emqx'
       run: |
         if [ -z $(echo $version | grep -oE "(alpha|beta|rc)\.[0-9]") ]; then
             curl --silent --show-error \
@@ -374,7 +462,4 @@ jobs:
         fi
     - uses: geekyeggo/delete-artifact@v1
       with:
-        name: emqx
-    - uses: geekyeggo/delete-artifact@v1
-      with:
-        name: emqx-edge
+        name: ${{ matrix.profile }}

+ 22 - 6
.github/workflows/build_slim_packages.yaml

@@ -1,26 +1,42 @@
 name: Build slim packages
 
-on: [pull_request]
+on:
+  - pull_request
+  - workflow_dispatch
 
 jobs:
   build:
     runs-on: ubuntu-20.04
-    
+
     strategy:
       matrix:
         erl_otp:
-        - erl23.2.2
+        - erl23.2.7
         os:
         - ubuntu20.04
-        - centos8
+        - centos7
 
     container: emqx/build-env:${{ matrix.erl_otp }}-${{ matrix.os }}
-    
+
     steps:
     - uses: actions/checkout@v1
+    - name: prepare
+      run: |
+        if make emqx-ee --dry-run > /dev/null 2>&1; then
+          echo "https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com" > $HOME/.git-credentials
+          git config --global credential.helper store
+          echo "${{ secrets.CI_GIT_TOKEN }}" >> ./scripts/git-token
+          echo "EMQX_NAME=emqx-ee" >> $GITHUB_ENV
+        else
+          echo "EMQX_NAME=emqx" >> $GITHUB_ENV
+        fi
     - name: build packages
-      run: make emqx-pkg
+      run: make ${EMQX_NAME}-pkg
     - name: pakcages test
       run: |
         export CODE_PATH=$GITHUB_WORKSPACE
         .ci/build_packages/tests.sh
+    - uses: actions/upload-artifact@v2
+      with:
+        name: ${{ matrix.os }}
+        path: _packages/**/*.zip

+ 1 - 1
.github/workflows/check_deps_integrity.yaml

@@ -5,7 +5,7 @@ on: [pull_request]
 jobs:
   check_deps_integrity:
     runs-on: ubuntu-20.04
-    container: emqx/build-env:erl23.2.2-ubuntu20.04
+    container: emqx/build-env:erl23.2.7-ubuntu20.04
 
     steps:
       - uses: actions/checkout@v2

+ 6 - 1
.github/workflows/elvis_lint.yaml

@@ -6,6 +6,11 @@ jobs:
   build:
     runs-on: ubuntu-20.04
     steps:
-      - uses: actions/checkout@v1
+      - uses: actions/checkout@v2
+      - name: Set git token
+        if: endsWith(github.repository, 'enterprise')
+        run: |
+          echo "https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com" > $HOME/.git-credentials
+          git config --global credential.helper store
       - run: |
           ./scripts/elvis-check.sh $GITHUB_BASE_REF

+ 15 - 2
.github/workflows/git_sync.yaml

@@ -19,11 +19,24 @@ jobs:
           destination_branch: ${{ github.ref }}
           destination_ssh_private_key: "${{ secrets.CI_SSH_PRIVATE_KEY }}"
       - name: create pull request
+        id: create_pull_request
+        run: |
+          set -euo pipefail
+          R=$(curl --silent --show-error \
+            -H "Accept: application/vnd.github.v3+json" \
+            -H "Authorization: token ${{ secrets.CI_GIT_TOKEN }}" \
+            -X POST \
+            -d '{"title": "Sync code into enterprise from opensource", "head": "master", "base":"enterprise"}' \
+            https://api.github.com/repos/${{ github.repository_owner }}/emqx-enterprise/pulls)
+          echo $R | jq
+          echo "::set-output name=url::$(echo $R | jq '.url')"
+      - name: request reviewers for a pull request
+        if: steps.create_pull_request.outputs.url != 'null'
         run: |
           set -euo pipefail
           curl --silent --show-error \
           -H "Accept: application/vnd.github.v3+json" \
           -H "Authorization: token ${{ secrets.CI_GIT_TOKEN }}" \
           -X POST \
-          -d '{"title": "Sync code into enterprise from opensource", "head": "master", "base":"enterprise"}' \
-          https://api.github.com/repos/${{ github.repository_owner }}/emqx-enterprise/pulls
+          -d '{"team_reviewers":["emqx-devs"]}' \
+          ${{ steps.create_pull_request.outputs.url }}/requested_reviewers

+ 90 - 32
.github/workflows/run_cts_tests.yaml

@@ -24,12 +24,14 @@ jobs:
 
     steps:
       - uses: actions/checkout@v1
-      - name: setup
+      - name: docker compose up
         env:
           LDAP_TAG: ${{ matrix.ldap_tag }}
         run: |
-          docker-compose -f .ci/apps_tests/docker-compose.yaml build --no-cache
-          docker-compose -f .ci/compatibility_tests/docker-compose-ldap.yaml up -d
+          docker-compose \
+            -f .ci/docker-compose-file/docker-compose-ldap-tcp.yaml \
+            -f .ci/docker-compose-file/docker-compose.yaml \
+            up -d --build
       - name: setup
         if: matrix.network_type == 'ipv4'
         run: |
@@ -38,6 +40,11 @@ jobs:
         if: matrix.network_type == 'ipv6'
         run: |
           echo EMQX_AUTH__LDAP__SERVERS=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.GlobalIPv6Address}}{{end}}' ldap) >> "$GITHUB_ENV"
+      - name: set git token
+        run: |
+          if make emqx-ee --dry-run > /dev/null 2>&1; then
+            docker exec -i erlang bash -c "echo \"https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com\" > /root/.git-credentials && git config --global credential.helper store"
+          fi
       - name: run test cases
         run: |
           export CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_
@@ -69,24 +76,30 @@ jobs:
 
     steps:
       - uses: actions/checkout@v1
+      - name: docker-compose up
+        run: |
+          docker-compose \
+            -f .ci/docker-compose-file/docker-compose-mongo-${{ matrix.connect_type }}.yaml \
+            -f .ci/docker-compose-file/docker-compose.yaml \
+            up -d --build
       - name: setup
         env:
           MONGO_TAG: ${{ matrix.mongo_tag }}
         if: matrix.connect_type == 'tls'
         run: |
-          docker-compose -f .ci/compatibility_tests/docker-compose-mongo-tls.yaml up -d
           cat <<-EOF >> "$GITHUB_ENV"
           EMQX_AUTH__MONGO__SSL__ENABLE=on
-          EMQX_AUTH__MONGO__CACERTFILE=/emqx/apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE_data/ca.pem
-          EMQX_AUTH__MONGO__CERTFILE=/emqx/apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE_data/client-cert.pem
-          EMQX_AUTH__MONGO__KEYFILE=/emqx/apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE_data/client-key.pem
+          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
+          EMQX_AUTH__MONGO__SSL__VERIFY=true
+          EMQX_AUTH__MONGO__SSL__SERVER_NAME_INDICATION=disable
           EOF
       - name: setup
         env:
           MONGO_TAG: ${{ matrix.mongo_tag }}
         if: matrix.connect_type == 'tcp'
         run: |
-          docker-compose -f .ci/compatibility_tests/docker-compose-mongo.yaml up -d
           echo EMQX_AUTH__MONGO__SSL__ENABLE=off >> "$GITHUB_ENV"
       - name: setup
         if: matrix.network_type == 'ipv4'
@@ -96,6 +109,11 @@ jobs:
         if: matrix.network_type == 'ipv6'
         run: |
           echo "EMQX_AUTH__MONGO__SERVER=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.GlobalIPv6Address}}{{end}}' mongo):27017" >> "$GITHUB_ENV"
+      - name: set git token
+        run: |
+          if make emqx-ee --dry-run > /dev/null 2>&1; then
+            docker exec -i erlang bash -c "echo \"https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com\" > /root/.git-credentials && git config --global credential.helper store"
+          fi
       - name: run test cases
         run: |
           export CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_
@@ -127,25 +145,44 @@ jobs:
 
     steps:
       - uses: actions/checkout@v1
+      - name: docker-compose up
+        timeout-minutes: 5
+        run: |
+          docker-compose \
+            -f .ci/docker-compose-file/docker-compose-mysql-${{ matrix.connect_type }}.yaml \
+            -f .ci/docker-compose-file/docker-compose.yaml \
+            up -d --build
+          while [ $(docker ps -a --filter name=client --filter exited=0 | wc -l) \
+                 != $(docker ps -a --filter name=client | wc -l) ]; do
+              sleep 5
+          done
       - name: setup
         env:
           MYSQL_TAG: ${{ matrix.mysql_tag }}
         if: matrix.connect_type == 'tls'
         run: |
-          docker-compose -f .ci/compatibility_tests/docker-compose-mysql-tls.yaml up -d
           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__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
+            EMQX_AUTH__MYSQL__SSL__VERIFY=true
+            EMQX_AUTH__MYSQL__SSL__SERVER_NAME_INDICATION=disable
           EOF
       - name: setup
         env:
           MYSQL_TAG: ${{ matrix.mysql_tag }}
         if: matrix.connect_type == 'tcp'
         run: |
-          docker-compose -f .ci/compatibility_tests/docker-compose-mysql.yaml up -d
-          echo EMQX_AUTH__MYSQL__SSL__ENABLE=off >> "$GITHUB_ENV"
+          cat <<-EOF >> "$GITHUB_ENV"
+            EMQX_AUTH__MYSQL__USERNAME=root
+            EMQX_AUTH__MYSQL__PASSWORD=public
+            EMQX_AUTH__MYSQL__DATABASE=mqtt
+            EMQX_AUTH__MYSQL__SSL__ENABLE=off
+          EOF
       - name: setup
         if: matrix.network_type == 'ipv4'
         run: |
@@ -154,12 +191,14 @@ jobs:
         if: matrix.network_type == 'ipv6'
         run: |
           echo "EMQX_AUTH__MYSQL__SERVER=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.GlobalIPv6Address}}{{end}}' mysql):3306" >> "$GITHUB_ENV"
+      - name: set git token
+        run: |
+          if make emqx-ee --dry-run > /dev/null 2>&1; then
+            docker exec -i erlang bash -c "echo \"https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com\" > /root/.git-credentials && git config --global credential.helper store"
+          fi
       - name: run test cases
         run: |
-          export EMQX_AUTH__MYSQL__USERNAME=root \
-                 EMQX_AUTH__MYSQL__PASSWORD=public \
-                 EMQX_AUTH__MYSQL__DATABASE=mqtt \
-                 CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_
+          export CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_
           printenv > .env
           docker exec -i erlang sh -c "make ensure-rebar3"
           docker exec -i erlang sh -c "./rebar3 eunit --dir apps/emqx_auth_mysql"
@@ -190,23 +229,30 @@ jobs:
         - tcp
     steps:
       - uses: actions/checkout@v1
+      - name: docker-compose up
+        run: |
+          docker-compose \
+            -f .ci/docker-compose-file/docker-compose-pgsql-${{ matrix.connect_type }}.yaml \
+            -f .ci/docker-compose-file/docker-compose.yaml \
+            up -d --build
       - name: setup
         env:
           PGSQL_TAG: ${{ matrix.pgsql_tag }}
         if: matrix.connect_type == 'tls'
         run: |
-          docker-compose -f .ci/compatibility_tests/docker-compose-pgsql-tls.yaml build --no-cache
-          docker-compose -f .ci/compatibility_tests/docker-compose-pgsql-tls.yaml up -d
           cat <<-EOF >> "$GITHUB_ENV"
           EMQX_AUTH__PGSQL__SSL__ENABLE=on
-          EMQX_AUTH__PGSQL__SSL__CACERTFILE=/emqx/apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/root.crt
+          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
+          EMQX_AUTH__PGSQL__SSL__VERIFY=true
+          EMQX_AUTH__PGSQL__SSL__SERVER_NAME_INDICATION=disable
           EOF
       - name: setup
         env:
           PGSQL_TAG: ${{ matrix.pgsql_tag }}
         if: matrix.connect_type == 'tcp'
         run: |
-          docker-compose -f .ci/compatibility_tests/docker-compose-pgsql.yaml up -d
           echo EMQX_AUTH__PGSQL__SSL__ENABLE=off >> "$GITHUB_ENV"
       - name: setup
         if: matrix.network_type == 'ipv4'
@@ -216,6 +262,11 @@ jobs:
         if: matrix.network_type == 'ipv6'
         run: |
           echo "EMQX_AUTH__PGSQL__SERVER=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.GlobalIPv6Address}}{{end}}' pgsql):5432" >> "$GITHUB_ENV"
+      - name: set git token
+        run: |
+          if make emqx-ee --dry-run > /dev/null 2>&1; then
+            docker exec -i erlang bash -c "echo \"https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com\" > /root/.git-credentials && git config --global credential.helper store"
+          fi
       - name: run test cases
         run: |
           export EMQX_AUTH__PGSQL__USERNAME=root \
@@ -250,33 +301,39 @@ jobs:
         node_type:
         - single
         - cluster
+        exclude:
+        - redis_tag: 5
+          connect_type: tls
 
     steps:
       - uses: actions/checkout@v1
+      - name: docker-compose up
+        run: |
+          docker-compose \
+            -f .ci/docker-compose-file/docker-compose-redis-${{ matrix.node_type }}-${{ matrix.connect_type }}.yaml \
+            -f .ci/docker-compose-file/docker-compose.yaml \
+            up -d --build
       - name: setup
         env:
           REDIS_TAG: ${{ matrix.redis_tag }}
-        if: matrix.connect_type == 'tls' && matrix.redis_tag != '5'
+        if: matrix.connect_type == 'tls'
         run: |
-          set -exu
-          docker-compose -f .ci/compatibility_tests/docker-compose-redis-${{ matrix.node_type }}-tls.yaml up -d
           cat <<-EOF >> "$GITHUB_ENV"
           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
+          EMQX_AUTH__REDIS__SSL__VERIFY=true
+          EMQX_AUTH__REDIS__SSL__SERVER_NAME_INDICATION=disable
           EOF
       - name: setup
         env:
           REDIS_TAG: ${{ matrix.redis_tag }}
         if: matrix.connect_type == 'tcp'
         run: |
-          docker-compose -f .ci/compatibility_tests/docker-compose-redis-${{ matrix.node_type }}.yaml up -d
           echo EMQX_AUTH__REDIS__SSL__ENABLE=off >> "$GITHUB_ENV"
       - name: get server address
-        if: matrix.connect_type == 'tcp' || (matrix.connect_type == 'tls' && matrix.redis_tag != '5')
         run: |
-          set -exu
           ipv4_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' redis)
           ipv6_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.GlobalIPv6Address}}{{end}}' redis)
           cat <<-EOF >> "$GITHUB_ENV"
@@ -286,15 +343,13 @@ jobs:
       - name: setup
         if: matrix.node_type == 'single' && matrix.connect_type == 'tcp'
         run: |
-          set -exu
           cat <<-EOF >> "$GITHUB_ENV"
           EMQX_AUTH__REDIS__TYPE=single
           EMQX_AUTH__REDIS__SERVER=${redis_${{ matrix.network_type }}_address}:6379
           EOF
       - name: setup
-        if: matrix.node_type == 'single' && matrix.connect_type == 'tls' && matrix.redis_tag != '5'
+        if: matrix.node_type == 'single' && matrix.connect_type == 'tls'
         run: |
-          set -exu
           cat <<-EOF >> "$GITHUB_ENV"
           EMQX_AUTH__REDIS__TYPE=single
           EMQX_AUTH__REDIS__SERVER=${redis_${{ matrix.network_type }}_address}:6380
@@ -302,23 +357,26 @@ jobs:
       - name: setup
         if: matrix.node_type == 'cluster' && matrix.connect_type == 'tcp'
         run: |
-          set -exu
           cat <<-EOF >> "$GITHUB_ENV"
           EMQX_AUTH__REDIS__TYPE=cluster
           EMQX_AUTH__REDIS__SERVER=${redis_${{ matrix.network_type }}_address}:7000
           EOF
       - name: setup
-        if: matrix.node_type == 'cluster' && matrix.connect_type == 'tls' && matrix.redis_tag != '5'
+        if: matrix.node_type == 'cluster' && matrix.connect_type == 'tls'
         run: |
-          set -exu
           cat <<-EOF >> "$GITHUB_ENV"
           EMQX_AUTH__REDIS__TYPE=cluster
           EMQX_AUTH__REDIS__SERVER=${redis_${{ matrix.network_type }}_address}:8000
           EOF
+      - name: set git token
+        run: |
+          if make emqx-ee --dry-run > /dev/null 2>&1; then
+            docker exec -i erlang bash -c "echo \"https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com\" > /root/.git-credentials && git config --global credential.helper store"
+          fi
       - name: run test cases
-        if: matrix.connect_type == 'tcp' || (matrix.connect_type == 'tls' && matrix.redis_tag != '5')
         run: |
           export CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_
+          export EMQX_AUTH__REIDS__PASSWORD=public
           printenv > .env
           docker exec -i erlang sh -c "make ensure-rebar3"
           docker exec -i erlang sh -c "./rebar3 eunit --dir apps/emqx_auth_redis"

+ 75 - 18
.github/workflows/run_fvt_tests.yaml

@@ -15,8 +15,19 @@ jobs:
 
         steps:
         - uses: actions/checkout@v1
+        - name: prepare
+          run: |
+            if make emqx-ee --dry-run > /dev/null 2>&1; then
+              echo "https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com" > $HOME/.git-credentials
+              git config --global credential.helper store
+              echo "${{ secrets.CI_GIT_TOKEN }}" >> scripts/git-token
+              make deps-emqx-ee
+              echo "TARGET=emqx/emqx-ee" >> $GITHUB_ENV
+            else
+              echo "TARGET=emqx/emqx" >> $GITHUB_ENV
+            fi
         - name: make emqx image
-          run: TARGET=emqx/emqx make docker
+          run: make docker
         - name: run emqx
           timeout-minutes: 5
           run: |
@@ -33,20 +44,30 @@ jobs:
             done
         - name: make paho tests
           run: |
-            docker exec -i paho_client sh -c "apk update && apk add git curl \
-              && git clone -b develop-4.0 https://github.com/emqx/paho.mqtt.testing.git /paho.mqtt.testing \
-              && pip install pytest \
-              && pytest -v /paho.mqtt.testing/interoperability/test_client/V5/test_connect.py -k test_basic --host node1.emqx.io \
-              && pytest -v /paho.mqtt.testing/interoperability/test_cluster --host1 node1.emqx.io --host2 node2.emqx.io \
-              && pytest -v /paho.mqtt.testing/interoperability/test_client --host node1.emqx.io"
+            if ! docker exec -i paho_client /scripts/pytest.sh; then
+              docker logs node1.emqx.io
+              docker logs node2.emqx.io
+              exit 1
+            fi
 
     helm_test:
         runs-on: ubuntu-20.04
 
         steps:
         - uses: actions/checkout@v1
+        - name: prepare
+          run: |
+            if make emqx-ee --dry-run > /dev/null 2>&1; then
+              echo "https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com" > $HOME/.git-credentials
+              git config --global credential.helper store
+              echo "${{ secrets.CI_GIT_TOKEN }}" >> scripts/git-token
+              make deps-emqx-ee
+              echo "TARGET=emqx/emqx-ee" >> $GITHUB_ENV
+            else
+              echo "TARGET=emqx/emqx" >> $GITHUB_ENV
+            fi
         - name: make emqx image
-          run: TARGET=emqx/emqx make docker
+          run: make docker
         - name: install k3s
           env:
             KUBECONFIG: "/etc/rancher/k3s/k3s.yaml"
@@ -69,15 +90,21 @@ jobs:
           timeout-minutes: 5
           run: |
             version=$(./pkg-vsn.sh)
-            sudo docker save emqx/emqx:$version -o emqx.tar.gz
+            sudo docker save ${TARGET}:$version -o emqx.tar.gz
             sudo k3s ctr image import emqx.tar.gz
 
             sed -i -r "s/^appVersion: .*$/appVersion: \"${version}\"/g" deploy/charts/emqx/Chart.yaml
-            sed -i -r 's/  pullPolicy: .*$/  pullPolicy: Never/g' deploy/charts/emqx/values.yaml
             sed -i '/emqx_telemetry/d' deploy/charts/emqx/values.yaml
 
-            helm install emqx --set emqxAclConfig="" --set emqxConfig.EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s --set emqxConfig.EMQX_MQTT__MAX_TOPIC_ALIAS=10 deploy/charts/emqx --debug --dry-run
-            helm install emqx --set emqxAclConfig="" --set emqxConfig.EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s --set emqxConfig.EMQX_MQTT__MAX_TOPIC_ALIAS=10 deploy/charts/emqx
+            helm install emqx \
+                --set image.repository=${TARGET} \
+                --set image.pullPolicy=Never \
+                --set emqxAclConfig="" \
+                --set image.pullPolicy=Never \
+                --set emqxConfig.EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s \
+                --set emqxConfig.EMQX_MQTT__MAX_TOPIC_ALIAS=10 \
+                deploy/charts/emqx \
+                --debug
 
             while [ "$(kubectl get StatefulSet -l app.kubernetes.io/name=emqx -o jsonpath='{.items[0].status.replicas}')" \
               != "$(kubectl get StatefulSet -l app.kubernetes.io/name=emqx -o jsonpath='{.items[0].status.readyReplicas}')" ]; do
@@ -110,11 +137,18 @@ jobs:
             emqx2=$(kubectl get pods emqx-2 -o jsonpath='{.status.podIP}')
 
             pytest -v paho.mqtt.testing/interoperability/test_client/V5/test_connect.py -k test_basic --host $emqx_svc
+            RESULT=$?
             pytest -v paho.mqtt.testing/interoperability/test_cluster --host1 $emqx1 --host2 $emqx2
+            RESULT=$((RESULT + $?))
+            if [ 0 -ne $RESULT ]; then
+                kubectl logs emqx-1
+                kubectl logs emqx-2
+            fi
+            exit $RESULT
 
     relup_test:
         runs-on: ubuntu-20.04
-        container: emqx/build-env:erl23.2.2-ubuntu20.04
+        container: emqx/build-env:erl23.2.7-ubuntu20.04
         defaults:
           run:
             shell: bash
@@ -148,14 +182,37 @@ jobs:
             repository: ${{ github.repository }}
             path: emqx
             fetch-depth: 0
+        - name: prepare
+          run: |
+            if make -C emqx emqx-ee --dry-run > /dev/null 2>&1; then
+              echo "https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com" > $HOME/.git-credentials
+              git config --global credential.helper store
+              echo "${{ secrets.CI_GIT_TOKEN }}" >> emqx/scripts/git-token
+              echo "PROFILE=emqx-ee" >> $GITHUB_ENV
+            else
+              echo "PROFILE=emqx" >> $GITHUB_ENV
+            fi
         - name: get version
           run: |
             set -e -x -u
             cd emqx
-            vsn="$(erl -eval '{ok, [{application,emqx, L} | _]} = file:consult("src/emqx.app.src"), {vsn, VSN} = lists:keyfind(vsn,1,L), io:fwrite(VSN), halt().' -noshell)"
+            if [ $PROFILE = "emqx" ];then
+                broker="emqx-ce"
+                edition='opensource'
+            else
+                broker="emqx-ee"
+                edition='enterprise'
+            fi
+
+            vsn="$(grep -E "define.+EMQX_RELEASE.+${edition}" include/emqx_release.hrl | cut -d '"' -f2)"
             echo "VSN=$vsn" >> $GITHUB_ENV
-            pre_tag="$(echo $vsn | grep -oE '^[0-9]+.[0-9]')"
-            old_vsns="$(git tag -l "$pre_tag.[0-9]" | tr "\n" " " | sed "s/$vsn//")"
+
+            pre_vsn="$(echo $vsn | grep -oE '^[0-9]+.[0-9]')"
+            if  [ $PROFILE = "emqx" ]; then
+                old_vsns="$(git tag -l "v$pre_vsn.[0-9]" | tr "\n" " " | sed "s/v$vsn//")"
+            else
+                old_vsns="$(git tag -l "e$pre_vsn.[0-9]" | tr "\n" " " | sed "s/v$vsn//")"
+            fi
             echo "OLD_VSNS=$old_vsns" >> $GITHUB_ENV
         - name: download emqx
           run: |
@@ -163,10 +220,10 @@ jobs:
             cd emqx
             old_vsns=($(echo $OLD_VSNS | tr ' ' ' '))
             for old_vsn in ${old_vsns[@]}; do
-              wget https://s3-us-west-2.amazonaws.com/packages.emqx/emqx-ce/v$old_vsn/emqx-ubuntu20.04-${old_vsn}-x86_64.zip
+              wget https://s3-${{ secrets.AWS_DEFAULT_REGION }}.amazonaws.com/${{ secrets.AWS_S3_BUCKET }}/$broker/$old_vsn/$PROFILE-ubuntu20.04-${old_vsn#[e|v]}-x86_64.zip
             done
         - name: build emqx
-          run: make -C emqx emqx-zip
+          run: make -C emqx ${PROFILE}-zip
         - name: build emqtt-bench
           run: make -C emqtt-bench
         - name: build lux

+ 12 - 2
.github/workflows/run_gitlint.yaml

@@ -12,15 +12,25 @@ jobs:
         run: |
           sudo apt-get update
           sudo apt install gitlint
+      - name: Set auth header
+        if: endsWith(github.repository, 'enterprise')
+        run: |
+          echo 'AUTH_HEADER<<EOF' >> $GITHUB_ENV
+          echo "Authorization: token ${{ secrets.CI_GIT_TOKEN }}" >> $GITHUB_ENV
+          echo 'EOF' >> $GITHUB_ENV
       - name: Run gitlint
+        shell: bash
         run: |
           pr_number=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }')
-          messages=$(curl "https://api.github.com/repos/${GITHUB_REPOSITORY}/pulls/${pr_number}/commits")
+          messages="$(curl --silent --show-error \
+                      --header "${{ env.AUTH_HEADER }}" \
+                      --header "Accept: application/vnd.github.v3+json" \
+                      "https://api.github.com/repos/${GITHUB_REPOSITORY}/pulls/${pr_number}/commits")"
           len=$(echo $messages | jq length)
           result=true
           for i in $( seq 0 $(($len - 1)) ); do
             message=$(echo $messages | jq -r .[$i].commit.message)
-            echo commit message: $message
+            echo "commit message: $message"
             status=0
             echo $message | gitlint -C ./.github/workflows/.gitlint || status=$?
             if [ $status -ne 0 ]; then

+ 68 - 6
.github/workflows/run_test_cases.yaml

@@ -12,10 +12,16 @@ on:
 jobs:
     run_static_analysis:
         runs-on: ubuntu-20.04
-        container: emqx/build-env:erl23.2.2-ubuntu20.04
+        container: emqx/build-env:erl23.2.7-ubuntu20.04
 
         steps:
         - uses: actions/checkout@v2
+        - name: set git credentials
+          run: |
+            if make emqx-ee --dry-run > /dev/null 2>&1; then
+              echo "https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com" > $HOME/.git-credentials
+              git config --global credential.helper store
+            fi
         - name: xref
           run: make xref
         - name: dialyzer
@@ -26,25 +32,81 @@ jobs:
 
         steps:
         - uses: actions/checkout@v2
-        - name: set up
+        - name: set edition
+          id: set_edition
+          run: |
+            if make emqx-ee --dry-run > /dev/null 2>&1; then
+                echo "EDITION=enterprise" >> $GITHUB_ENV
+            else
+                echo "EDITION=opensource" >> $GITHUB_ENV
+            fi
+        - name: docker compose up
+          if: env.EDITION == 'opensource'
+          env:
+            MYSQL_TAG: 8
+            REDIS_TAG: 6
+            MONGO_TAG: 4
+            PGSQL_TAG: 13
+            LDAP_TAG: 2.4.50
+            GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          run: |
+            docker-compose \
+                -f .ci/docker-compose-file/docker-compose.yaml \
+                -f .ci/docker-compose-file/docker-compose-ldap-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-mongo-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-mysql-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-pgsql-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-redis-single-tcp.yaml \
+                up -d --build
+        - name: docker compose up
+          if: env.EDITION == 'enterprise'
           env:
             MYSQL_TAG: 8
             REDIS_TAG: 6
             MONGO_TAG: 4
             PGSQL_TAG: 13
             LDAP_TAG: 2.4.50
+            OPENTSDB_TAG: latest
+            INFLUXDB_TAG: 1.7.6
+            DYNAMODB_TAG: 1.11.477
+            TIMESCALE_TAG: latest-pg11
+            CASSANDRA_TAG: 3.11.6
+            RABBITMQ_TAG: 3.7
+            KAFKA_TAG: 2.5.0
+            PULSAR_TAG: 2.3.2
             GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          timeout-minutes: 20
           run: |
-            docker-compose -f .ci/apps_tests/docker-compose.yaml build --no-cache
-            docker-compose -f .ci/apps_tests/docker-compose.yaml up -d
+            docker-compose \
+                -f .ci/docker-compose-file/docker-compose.yaml \
+                -f .ci/docker-compose-file/docker-compose-ldap-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-mongo-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-mysql-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-pgsql-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-redis-single-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-enterprise.yaml \
+                -f .ci/docker-compose-file/docker-compose-enterprise-cassandra-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-enterprise-dynamodb-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-enterprise-influxdb-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-enterprise-kafka-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-enterprise-opentsdb-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-enterprise-pulsar-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-enterprise-rabbit-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-enterprise-timescale-tcp.yaml \
+                -f .ci/docker-compose-file/docker-compose-enterprise-mysql-client.yaml \
+                -f .ci/docker-compose-file/docker-compose-enterprise-pgsql-and-timescale-client.yaml \
+                up -d --build
+            docker exec -i erlang bash -c "echo \"https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com\" > /root/.git-credentials && git config --global credential.helper store"
+            while [ $(docker ps -a --filter name=client --filter exited=0 | wc -l) \
+                 != $(docker ps -a --filter name=client | wc -l) ]; do
+              sleep 5
+            done
         - name: run eunit
           run: |
             docker exec -i erlang bash -c "make eunit"
-            docker exec --env EMQX_EXTRA_PLUGINS=all -i erlang bash -c "./rebar3 eunit --dir $(find lib-extra/ -mindepth 1 -maxdepth 2 -type l | tr '\n' ',')"
         - name: run common test
           run: |
             docker exec -i erlang bash -c "make ct"
-            docker exec --env EMQX_EXTRA_PLUGINS=all -i erlang bash -c "./rebar3 ct --dir $(find lib-extra/ -mindepth 1 -maxdepth 2 -type l | tr '\n' ',')"
         - name: run cover
           run: |
             docker exec -i erlang bash -c "make cover"

+ 28 - 8
Makefile

@@ -1,4 +1,4 @@
-$(shell scripts/git-hooks-init.sh)
+$(shell $(CURDIR)/scripts/git-hooks-init.sh)
 REBAR_VERSION = 3.14.3-emqx-5
 REBAR = $(CURDIR)/rebar3
 BUILD = $(CURDIR)/build
@@ -6,6 +6,9 @@ SCRIPTS = $(CURDIR)/scripts
 export PKG_VSN ?= $(shell $(CURDIR)/pkg-vsn.sh)
 export EMQX_DESC ?= EMQ X
 export EMQX_CE_DASHBOARD_VERSION ?= v4.3.0-beta.1
+ifeq ($(OS),Windows_NT)
+	export REBAR_COLOR=none
+endif
 
 PROFILE ?= emqx
 REL_PROFILES := emqx emqx-edge
@@ -33,27 +36,37 @@ get-dashboard:
 
 .PHONY: eunit
 eunit: $(REBAR)
-	@$(REBAR) eunit -v -c
+	@ENABLE_COVER_COMPILE=1 $(REBAR) eunit -v -c
 
 .PHONY: proper
 proper: $(REBAR)
-	@$(REBAR) as test proper -d test/props -c
+	@ENABLE_COVER_COMPILE=1 $(REBAR) as test proper -d test/props -c
 
 .PHONY: ct
 ct: $(REBAR)
-	@$(REBAR) ct --name 'test@127.0.0.1' -c -v
+	@ENABLE_COVER_COMPILE=1 $(REBAR) ct --name 'test@127.0.0.1' -c -v
+
+APPS=$(shell $(CURDIR)/scripts/find-apps.sh)
+
+## app/name-ct targets are intended for local tests hence cover is not enabled
+.PHONY: $(APPS:%=%-ct)
+define gen-app-ct-target
+$1-ct:
+	$(REBAR) ct --name 'test@127.0.0.1' -v --suite $(shell $(CURDIR)/scripts/find-suites.sh $1)
+endef
+$(foreach app,$(APPS),$(eval $(call gen-app-ct-target,$(app))))
 
 .PHONY: cover
 cover: $(REBAR)
-	@$(REBAR) cover
+	@ENABLE_COVER_COMPILE=1 $(REBAR) cover
 
 .PHONY: coveralls
 coveralls: $(REBAR)
-	@$(REBAR) as test coveralls send
+	@ENABLE_COVER_COMPILE=1 $(REBAR) as test coveralls send
 
 .PHONY: $(REL_PROFILES)
 $(REL_PROFILES:%=%): $(REBAR) get-dashboard
-	@$(REBAR) as $(@) release
+	@$(REBAR) as $(@) do compile,release
 
 ## Not calling rebar3 clean because
 ## 1. rebar3 clean relies on rebar3, meaning it reads config, fetches dependencies etc.
@@ -97,7 +110,7 @@ ifneq ($(OS),Windows_NT)
 endif
 
 .PHONY: $(REL_PROFILES:%=%-tar) $(PKG_PROFILES:%=%-tar)
-$(REL_PROFILES:%=%-tar) $(PKG_PROFILES:%=%-tar): $(REBAR) get-dashboard
+$(REL_PROFILES:%=%-tar) $(PKG_PROFILES:%=%-tar): $(REBAR) get-dashboard $(CONF_SEGS)
 	@$(BUILD) $(subst -tar,,$(@)) tar
 
 ## zip targets depend on the corresponding relup and tar artifacts
@@ -118,4 +131,11 @@ $1: $(subst -pkg,,$1)-zip $1-tar
 endef
 $(foreach pt,$(PKG_PROFILES),$(eval $(call gen-pkg-target,$(pt))))
 
+.PHONY: run
+run: $(PROFILE) quickrun
+
+.PHONY: quickrun
+quickrun:
+	./_build/$(PROFILE)/rel/emqx/bin/emqx console
+
 include docker.mk

+ 10 - 9
README-CN.md

@@ -6,8 +6,9 @@
 [![Docker Pulls](https://img.shields.io/docker/pulls/emqx/emqx)](https://hub.docker.com/r/emqx/emqx)
 [![Slack Invite](<https://slack-invite.emqx.io/badge.svg>)](https://slack-invite.emqx.io)
 [![Twitter](https://img.shields.io/badge/Twitter-EMQ%20X-1DA1F2?logo=twitter)](https://twitter.com/emqtt)
+[![Community](https://img.shields.io/badge/Community-EMQ%20X-yellow)](https://askemq.com)
 
-[![最棒的物联网 MQTT 开源团队期待您的加入](https://www.emqx.io/static/img/github_readme_cn_bg.png)](https://www.emqx.io/cn/careers)
+[![最棒的物联网 MQTT 开源团队期待您的加入](https://www.emqx.io/static/img/github_readme_cn_bg.png)](https://careers.emqx.cn/)
 
 [English](./README.md) | 简体中文 | [日本語](./README-JP.md)
 
@@ -16,7 +17,7 @@
 从 3.0 版本开始,*EMQ X* 完整支持 MQTT V5.0 协议规范,向下兼容 MQTT V3.1 和 V3.1.1,并支持 MQTT-SN、CoAP、LwM2M、WebSocket 和 STOMP 等通信协议。EMQ X 3.0 单集群可支持千万级别的 MQTT 并发连接。
 
 - 新功能的完整列表,请参阅 [EMQ X Release Notes](https://github.com/emqx/emqx/releases)。
-- 获取更多信息,请访问 [EMQ X 官网](https://www.emqx.io/cn/)。
+- 获取更多信息,请访问 [EMQ X 官网](https://www.emqx.cn/)。
 
 ## 安装
 
@@ -25,15 +26,15 @@
 #### EMQ X Docker 镜像安装
 
 ```
-docker run -d --name emqx -p 1883:1883 -p 8083:8083 -p 8883:8883 -p 8084:8084 -p 18083:18083 emqx/emqx
+docker run -d --name emqx -p 1883:1883 -p 8081:8081 -p 8083:8083 -p 8883:8883 -p 8084:8084 -p 18083:18083 emqx/emqx
 ```
 
 #### 二进制软件包安装
 
-需从 [EMQ X 下载](https://www.emqx.io/cn/downloads) 页面获取相应操作系统的二进制软件包。
+需从 [EMQ X 下载](https://www.emqx.cn/downloads) 页面获取相应操作系统的二进制软件包。
 
-- [单节点安装文档](https://docs.emqx.io/broker/latest/cn/getting-started/install.html)
-- [集群配置文档](https://docs.emqx.io/broker/latest/cn/advanced/cluster.html)
+- [单节点安装文档](https://docs.emqx.cn/broker/latest/getting-started/install.html)
+- [集群配置文档](https://docs.emqx.cn/broker/latest/advanced/cluster.html)
 
 ## 从源码构建
 
@@ -75,7 +76,7 @@ DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
 
 ## FAQ
 
-访问 [EMQ X FAQ](https://docs.emqx.io/broker/latest/cn/faq/faq.html) 以获取常见问题的帮助。
+访问 [EMQ X FAQ](https://docs.emqx.cn/broker/latest/faq/faq.html) 以获取常见问题的帮助。
 
 ## 产品路线
 
@@ -89,9 +90,9 @@ DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
 - [Twitter](https://twitter.com/emqtt)
 - [Facebook](https://www.facebook.com/emqxmqtt)
 - [Reddit](https://www.reddit.com/r/emqx/)
-- [Forum](https://groups.google.com/d/forum/emqtt)
+- [Forum](https://askemq.com)
 - [Weibo](https://weibo.com/emqtt)
-- [Blog](https://www.emqx.io/cn/blog)
+- [Blog](https://www.emqx.cn/blog)
 
 欢迎你将任何 bug、问题和功能请求提交到 [emqx/emqx](https://github.com/emqx/emqx/issues)。
 

+ 20 - 17
README.md

@@ -23,18 +23,20 @@ Starting from 3.0 release, *EMQ X* broker fully supports MQTT V5.0 protocol spec
 
 The *EMQ X* broker is cross-platform, which supports Linux, Unix, macOS and Windows. It means *EMQ X* can be deployed on x86_64 architecture servers and ARM devices like Raspberry Pi.
 
+See more details for building and running *EMQ X* on Windows in [Windows.md](./Windows.md)
+
 #### Installing via EMQ X Docker Image
 
 ```
-docker run -d --name emqx -p 1883:1883 -p 8083:8083 -p 8883:8883 -p 8084:8084 -p 18083:18083 emqx/emqx
+docker run -d --name emqx -p 1883:1883 -p 8081:8081 -p 8083:8083 -p 8883:8883 -p 8084:8084 -p 18083:18083 emqx/emqx
 ```
 
 #### Installing via Binary Package
 
 Get the binary package of the corresponding OS from [EMQ X Download](https://www.emqx.io/downloads) page.
 
-- [Single Node Install](https://docs.emqx.io/broker/latest/en/getting-started/install.html)
-- [Multi Node Install](https://docs.emqx.io/broker/latest/en/advanced/cluster.html)
+- [Single Node Install](https://docs.emqx.io/en/broker/latest/getting-started/install.html)
+- [Multi Node Install](https://docs.emqx.io/en/broker/latest/advanced/cluster.html)
 
 
 ## Build From Source
@@ -87,17 +89,12 @@ make eunit ct
 
 ### To run subset of the common tests
 
-examples
+Examples
 
 ```bash
-./rebar3 ct --name 'test@127.0.0.1' -c -v --dir test,apps/emqx_sn,apps/emqx_coap
-./rebar3 ct --name 'test@127.0.0.1' -c -v --dir apps/emqx_auth_mnesi --suite emqx_acl_mnesia_SUITE
-./rebar3 ct --name 'test@127.0.0.1' -c -v --dir apps/emqx_auth_mnesi --suite emqx_acl_mnesia_SUITE --case t_rest_api
+make apps/emqx_bridge_mqtt-ct
 ```
 
-NOTE: Do *NOT* use full (relative) path to SUITE files like this `--suite apps/emqx_auth_mnesia/test/emqx_acl_mnesia_SUITE.erl`,
-because it will lead to a full copy of `apps` dir into `_buid/test/lib/emqx`.
-
 ### Dialyzer
 ##### To Analyze all the apps
 ```
@@ -109,19 +106,25 @@ make dialyzer
 DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
 ```
 
-## FAQ
+## Community
 
-Visiting [EMQ X FAQ](https://docs.emqx.io/broker/latest/en/faq/faq.html) to get help of common problems.
+### FAQ
 
-## Roadmap
+Visiting [EMQ X FAQ](https://docs.emqx.io/en/broker/latest/faq/faq.html) to get help of common problems.
 
-The [EMQ X Roadmap uses Github milestones](https://github.com/emqx/emqx/milestones) to track the progress of the project.
 
-## Community
+### Questions
+
+[GitHub Discussions](https://github.com/emqx/emqx/discussions) is where you can ask questions, and share ideas.
+
+### Proposals
+
+For more organised improvement proposals, you can send pull requests to [EIP](https://github.com/emqx/eip).
+
+### Plugin development
 
-The EMQ X community can be found on [GitHub Discussions](https://github.com/emqx/emqx/discussions), where you can ask questions, voice ideas, and share your projects.
+To develop your own plugins, see [lib-extra/README.md](./lib-extra/README.md)
 
-To chat with other community members you can join the [EMQ X Slack](https://slack-invite.emqx.io).
 
 ## MQTT Specifications
 

+ 127 - 0
Windows.md

@@ -0,0 +1,127 @@
+# Build and run EMQ X on Windows
+
+NOTE: The instructions and examples are based on Windows 10.
+
+## Build Environment
+
+### Visual studio for C/C++ compile and link
+
+EMQ X includes Erlang NIF (Native Implmented Function) components, implemented
+in C/C++. To compile and link C/C++ libraries, the easiest way is perhaps to
+install Visual Studio.
+
+Visual Studio 2019 is used in our tests.
+If you are like me (@zmstone), do not know where to start,
+please follow this OTP guide:
+https://github.com/erlang/otp/blob/master/HOWTO/INSTALL-WIN32.md
+
+NOTE: To avoid surprises, you may need to add below two paths to `Path` environment variable
+and order them before other paths.
+
+```
+C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29910\bin\Hostx64\x64
+C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build
+```
+
+Depending on your visual studio version and OS, the paths may differ.
+The first path is for rebar3 port compiler to find `cl.exe` and `link.exe`
+The second path is for Powershell or CMD to setup environment variables.
+
+### Erlang/OTP
+
+Install Erlang/OTP 23.2 from https://www.erlang.org/downloads
+You may need to edit the `Path` environment variable to allow running
+Erlang commands such as `erl` from powershell.
+
+To validate Erlang installation in CMD or powershell:
+
+* Start (or restart) CMD or powershell
+
+* Execute `erl` command to enter Erlang shell
+
+* Evaluate Erlang expression `halt().` to exit Erlang shell.
+
+e.g.
+
+```
+PS C:\Users\zmsto> erl
+Eshell V11.1.4  (abort with ^G)
+1> halt().
+```
+
+### bash
+
+All EMQ X build/run scripts are either in `bash` or `escript`.
+`escript` is installed as a part of Erlang. To install a `bash`
+environment in Windows, there are quite a few options.
+
+Cygwin is what we tested with.
+
+* Add `cygwin\bin` dir to `Path` environment variable
+  To do so, search for Edit environment variable in control pannel and
+  add `C:\tools\cygwin\bin` (depending on the location where it was installed)
+  to `Path` list.
+
+* Validate installation.
+  Start (restart) CMD or powershell console and execute `which bash`, it should
+  print out `/usr/bin/bash`
+
+### Other tools
+
+Some of the unix world tools are required to build EMQ X.  Including:
+
+* git
+* curl
+* make
+* jq
+* zip / unzip
+
+We recommend using [scoop](https://scoop.sh/), or [Chocolatey](https://chocolatey.org/install) to install the tools.
+
+When using scoop:
+
+```
+scoop install git curl make jq zip unzip
+```
+
+## Build EMQ X source code
+
+* Clone the repo: `git clone https://github.com/emqx/emqx.git`
+
+* Start CMD or Powershell
+
+* Execute `vcvarsall.bat x86_amd64` to load environment variables
+
+* Change to emqx directory and execute `make`
+
+### Possible errors
+
+* `'cl.exe' is not recognized as an internal or external command`
+  This error is likely due to Visual Studio executables are not set in `Path` environment variable.
+  To fix it, either add path like `C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.28.29910\bin\Hostx64\x64`
+  to `Paht`. Or make sure `vcvarsall.bat x86_amd64` is executed prior to the `make` command
+
+* `fatal error C1083: Cannot open include file: 'assert.h': No such file or directory`
+  If Visual Studio is installed correctly, this is likely `LIB` and `LIB_PATH` environment
+  variables are not set. Make sure `vcvarsall.bat x86_amd64` is executed prior to the `make` command
+
+* `link: extra operand 'some.obj'`
+  This is likely due ot the usage of GNU `lnik.exe` but not the one from Visual Studio.
+  Exeucte `link.exe --version` to inspect which one is in use. The one installed from
+  Visual Studio should print out `Microsoft (R) Incremental Linker`.
+  To fix it, Visual Studio's bin paths should be ordered prior to Cygwin's (or similar installation's)
+  bin paths in `Path` environment variable.
+
+## Run EMQ X
+
+To start EMQ X broker.
+
+Execute `_build\emqx\rel\emqx>.\bin\emqx console` or `_build\emqx\rel\emqx>.\bin\emqx start` to start EMQ X.
+
+Then execute `_build\emqx\rel\emqx>.\bin\emqx_ctl status` to check status.
+If everything works fine, it should print out
+
+```
+Node 'emqx@127.0.0.1' 4.3-beta.1 is started
+Application emqx 4.3.0 is running
+```

+ 20 - 5
apps/emqx_auth_http/etc/emqx_auth_http.conf

@@ -16,14 +16,14 @@ auth.http.auth_req.method = post
 
 ## HTTP Request Headers for Auth Request, Content-Type header is configured by default.
 ## 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"
 
 ## 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
 ## When the request method is POST, the final format is determined by content-type
-## 
+##
 ## Available Variables:
 ##  - %u: username
 ##  - %c: clientid
@@ -58,7 +58,7 @@ 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
 ## When the request method is POST, the final format is determined by content-type
-## 
+##
 ## Available Variables:
 ##  - %u: username
 ##  - %c: clientid
@@ -93,7 +93,7 @@ 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
 ## When the request method is POST, the final format is determined by content-type
-## 
+##
 ## Available Variables:
 ##  - %u: username
 ##  - %c: clientid
@@ -117,7 +117,7 @@ auth.http.acl_req.params = "access=%A,username=%u,clientid=%c,ipaddr=%a,topic=%t
 ## Default: 5s
 auth.http.timeout = 5s
 
-## Connection time-out time, used during the initial request, 
+## Connection time-out time, used during the initial request,
 ## when the client is connecting to the server.
 ##
 ## Value: Duration
@@ -151,3 +151,18 @@ auth.http.pool_size = 32
 ##
 ## Value: File
 ## 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.
+##
+## Value: true | false
+## auth.http.ssl.verify = false
+
+## If not specified, the server's names returned in server's certificate is validated against
+## what's provided `auth.http.auth_req.url` config's host part.
+## Setting to 'disable' will make EMQ X ignore unmatched server names.
+## If set with a host name, the server's names returned in server's certificate is validated
+## against this value.
+##
+## Value: String | disable
+## auth.http.ssl.server_name_indication = disable

+ 9 - 0
apps/emqx_auth_http/priv/emqx_auth_http.schema

@@ -116,3 +116,12 @@ end}.
 {mapping, "auth.http.ssl.keyfile", "emqx_auth_http.keyfile", [
   {datatype, string}
 ]}.
+
+{mapping, "auth.http.ssl.verify", "emqx_auth_http.verify", [
+  {default, false},
+  {datatype, {enum, [true, false]}}
+]}.
+
+{mapping, "auth.http.ssl.server_name_indication", "emqx_auth_http.server_name_indication", [
+  {datatype, string}
+]}.

+ 16 - 5
apps/emqx_auth_http/src/emqx_auth_http_app.erl

@@ -66,11 +66,22 @@ translate_env(EnvName) ->
                             CACertFile = application:get_env(?APP, cacertfile, undefined),
                             CertFile = application:get_env(?APP, certfile, undefined),
                             KeyFile = application:get_env(?APP, keyfile, undefined),
-                            TLSOpts = lists:filter(fun({_K, V}) when V =:= <<>> ->
-                                                        false;
-                                                        (_) ->
-                                                        true
-                                                    end, [{keyfile, KeyFile}, {certfile, CertFile}, {cacertfile, CACertFile}]),
+                            Verify = case application:get_env(?APP, verify, fasle) of
+                                         true -> verify_peer;
+                                         false -> verify_none
+                                     end,
+                            SNI = case application:get_env(?APP, server_name_indication, undefined) of
+                                    "disable" -> disable;
+                                    SNI0 -> SNI0
+                                  end,
+                            TLSOpts = lists:filter(
+                                        fun({_, V}) ->
+                                            V =/= <<>> andalso V =/= undefined
+                                        end, [{keyfile, KeyFile},
+                                              {certfile, CertFile},
+                                              {cacertfile, CACertFile},
+                                              {verify, Verify},
+                                              {server_name_indication, SNI}]),
                             NTLSOpts = [ {versions, emqx_tls_lib:default_versions()}
                                        , {ciphers, emqx_tls_lib:default_ciphers()}
                                        | TLSOpts

+ 3 - 1
apps/emqx_auth_http/test/emqx_auth_http_SUITE.erl

@@ -90,7 +90,9 @@ set_https_client_opts() ->
     SSLOpt = emqx_ct_helpers:client_ssl_twoway(),
     application:set_env(emqx_auth_http, cacertfile, proplists:get_value(cacertfile, SSLOpt, undefined)),
     application:set_env(emqx_auth_http, certfile, proplists:get_value(certfile, SSLOpt, undefined)),
-    application:set_env(emqx_auth_http, keyfile, proplists:get_value(keyfile, SSLOpt, undefined)).
+    application:set_env(emqx_auth_http, keyfile, proplists:get_value(keyfile, SSLOpt, undefined)),
+    application:set_env(emqx_auth_http, verify, true),
+    application:set_env(emqx_auth_http, server_name_indication, "disable").
 
 %% @private
 http_server(http, inet) -> "http://127.0.0.1:8991";

+ 0 - 2
apps/emqx_auth_ldap/etc/emqx_auth_ldap.conf

@@ -73,6 +73,4 @@ auth.ldap.ssl.enable = false
 
 #auth.ldap.ssl.verify = "verify_peer"
 
-#auth.ldap.ssl.fail_if_no_peer_cert = true
-
 #auth.ldap.ssl.server_name_indication = your_server_name

+ 4 - 6
apps/emqx_auth_ldap/priv/emqx_auth_ldap.schema

@@ -53,10 +53,6 @@
   {datatype, {enum, [verify_none, verify_peer]}}
 ]}.
 
-{mapping, "auth.ldap.ssl.fail_if_no_peer_cert", "emqx_auth_ldap.ldap", [
-  {datatype, {enum, [true, false]}}
-]}.
-
 {mapping, "auth.ldap.ssl.server_name_indication", "emqx_auth_ldap.ldap", [
   {datatype, string}
 ]}.
@@ -75,8 +71,10 @@
                  {keyfile, cuttlefish:conf_get("auth.ldap.ssl.keyfile", Conf)},
                  {cacertfile, cuttlefish:conf_get("auth.ldap.ssl.cacertfile", Conf, undefined)},
                  {verify, cuttlefish:conf_get("auth.ldap.ssl.verify", Conf, undefined)},
-                 {server_name_indication, cuttlefish:conf_get("auth.ldap.ssl.server_name_indication", Conf, disable)},
-                 {fail_if_no_peer_cert, cuttlefish:conf_get("auth.ldap.ssl.fail_if_no_peer_cert", Conf, undefined)}]
+                 {server_name_indication, case cuttlefish:conf_get("auth.ldap.ssl.server_name_indication", Conf, undefined) of
+                                            "disable" -> disable;
+                                            SNI -> SNI
+                                          end}]
               end,
     Opts = [{servers, Servers},
             {port, Port},

+ 1 - 3
apps/emqx_auth_ldap/test/emqx_auth_ldap_SUITE.erl

@@ -44,13 +44,11 @@ groups() ->
 
 init_per_group(GrpName, Cfg) ->
     Fun = fun(App) -> set_special_configs(GrpName, App) end,
-    emqx_ct_helpers:start_apps([emqx_modules]),
     emqx_ct_helpers:start_apps([emqx_auth_ldap], Fun),
-    emqx_mod_acl_internal:unload([]),
     Cfg.
 
 end_per_group(_GrpName, _Cfg) ->
-    emqx_ct_helpers:stop_apps([emqx_auth_ldap, emqx_modules]).
+    emqx_ct_helpers:stop_apps([emqx_auth_ldap]).
 
 %%--------------------------------------------------------------------
 %% Cases

+ 2 - 4
apps/emqx_auth_ldap/test/emqx_auth_ldap_bind_as_user_SUITE.erl

@@ -36,12 +36,11 @@ all() ->
      check_acl].
 
 init_per_suite(Config) ->
-    emqx_ct_helpers:start_apps([emqx_modules, emqx_auth_ldap], fun set_special_configs/1),
-    emqx_mod_acl_internal:unload([]),
+    emqx_ct_helpers:start_apps([emqx_auth_ldap], fun set_special_configs/1),
     Config.
 
 end_per_suite(_Config) ->
-    emqx_ct_helpers:stop_apps([emqx_auth_ldap, emqx_modules]).
+    emqx_ct_helpers:stop_apps([emqx_auth_ldap]).
 
 check_auth(_) ->
     MqttUser1 = #{clientid => <<"mqttuser1">>,
@@ -62,7 +61,6 @@ check_auth(_) ->
     ?assertEqual({error, not_authorized}, emqx_access_control:authenticate(NonExistUser1)).
 
 check_acl(_) ->
-    % emqx_modules:load_module(emqx_mod_acl_internal, false),
     MqttUser = #{clientid => <<"mqttuser1">>, username => <<"user1">>, zone => external},
     NoMqttUser = #{clientid => <<"mqttuser2">>, username => <<"user7">>, zone => external},
     allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pub/1">>),

+ 1 - 0
apps/emqx_auth_mnesia/src/emqx_acl_mnesia.erl

@@ -31,6 +31,7 @@
 
 init() ->
     ok = ekka_mnesia:create_table(emqx_acl, [
+            {type, bag},
             {disc_copies, [node()]},
             {attributes, record_info(fields, emqx_acl)},
             {storage_properties, [{ets, [{read_concurrency, true}]}]}]),

+ 54 - 10
apps/emqx_auth_mnesia/src/emqx_acl_mnesia_cli.erl

@@ -39,13 +39,24 @@
 -spec(add_acl(login() | all, emqx_topic:topic(), pub | sub | pubsub, allow | deny) ->
         ok | {error, any()}).
 add_acl(Login, Topic, Action, Access) ->
-    Acls = #?TABLE{
-              filter = {Login, Topic},
-              action = Action,
-              access = Access,
-              created_at = erlang:system_time(millisecond)
-             },
-    ret(mnesia:transaction(fun mnesia:write/1, [Acls])).
+    Filter = {Login, Topic},
+    Acl = #?TABLE{
+             filter = Filter,
+             action = Action,
+             access = Access,
+             created_at = erlang:system_time(millisecond)
+            },
+    ret(mnesia:transaction(
+          fun() ->
+                  OldRecords = mnesia:wread({?TABLE, Filter}),
+                  case Action of
+                      pubsub ->
+                          update_permission(pub, Acl, OldRecords),
+                          update_permission(sub, Acl, OldRecords);
+                      _ ->
+                          update_permission(Action, Acl, OldRecords)
+                  end
+          end)).
 
 %% @doc Lookup acl by login
 -spec(lookup_acl(login() | all) -> list()).
@@ -160,18 +171,27 @@ cli(["show", "username", Username]) ->
     [print_acl(Acl) || Acl <- lookup_acl({username, iolist_to_binary(Username)})];
 
 cli(["del", "clientid", Clientid, Topic])->
+    cli(["delete", "clientid", Clientid, Topic]);
+
+cli(["delete", "clientid", Clientid, Topic])->
     case remove_acl({clientid, iolist_to_binary(Clientid)}, iolist_to_binary(Topic)) of
          ok -> emqx_ctl:print("ok~n");
         {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
     end;
 
 cli(["del", "username", Username, Topic])->
+    cli(["delete", "username", Username, Topic]);
+
+cli(["delete", "username", Username, Topic])->
     case remove_acl({username, iolist_to_binary(Username)}, iolist_to_binary(Topic)) of
          ok -> emqx_ctl:print("ok~n");
         {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
     end;
 
 cli(["del", "_all", Topic])->
+    cli(["delete", "_all", Topic]);
+
+cli(["delete", "_all", Topic])->
     case remove_acl(all, iolist_to_binary(Topic)) of
          ok -> emqx_ctl:print("ok~n");
         {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
@@ -186,9 +206,9 @@ cli(_) ->
                    , {"acl aad clientid <Clientid> <Topic> <Action> <Access>", "Add clientid acl"}
                    , {"acl add Username <Username> <Topic> <Action> <Access>", "Add username acl"}
                    , {"acl add _all <Topic> <Action> <Access>", "Add $all acl"}
-                   , {"acl del clientid <Clientid> <Topic>", "Delete clientid acl"}
-                   , {"acl del username <Username> <Topic>", "Delete username acl"}
-                   , {"acl del _all <Topic>", "Delete $all acl"}
+                   , {"acl delete clientid <Clientid> <Topic>", "Delete clientid acl"}
+                   , {"acl delete username <Username> <Topic>", "Delete username acl"}
+                   , {"acl delete _all <Topic>", "Delete $all acl"}
                    ]).
 
 %%--------------------------------------------------------------------
@@ -224,3 +244,27 @@ print_acl({all, Topic, Action, Access, _}) ->
         "Acl($all topic = ~p action = ~p access = ~p)~n",
         [Topic, Action, Access]
      ).
+
+update_permission(Action, Acl0, OldRecords) ->
+    Acl = Acl0 #?TABLE{action = Action},
+    maybe_delete_shadowed_records(Action, OldRecords),
+    mnesia:write(Acl).
+
+maybe_delete_shadowed_records(_, []) ->
+    ok;
+maybe_delete_shadowed_records(Action1, [Rec = #emqx_acl{action = Action2} | Rest]) ->
+    if Action1 =:= Action2 ->
+            ok = mnesia:delete_object(Rec);
+       Action2 =:= pubsub ->
+            %% Perform migration from the old data format on the
+            %% fly. This is needed only for the enterprise version,
+            %% delete this branch on 5.0
+            mnesia:delete_object(Rec),
+            mnesia:write(Rec#?TABLE{action = other_action(Action1)});
+       true ->
+            ok
+    end,
+    maybe_delete_shadowed_records(Action1, Rest).
+
+other_action(pub) -> sub;
+other_action(sub) -> pub.

+ 8 - 2
apps/emqx_auth_mnesia/src/emqx_auth_mnesia_cli.erl

@@ -144,6 +144,9 @@ auth_clientid_cli(["update", ClientId, NewPassword]) ->
     end;
 
 auth_clientid_cli(["del", ClientId]) ->
+    auth_clientid_cli(["delete", ClientId]);
+
+auth_clientid_cli(["delete", ClientId]) ->
     case  remove_user({clientid, iolist_to_binary(ClientId)}) of
         ok -> emqx_ctl:print("ok~n");
         {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
@@ -153,7 +156,7 @@ auth_clientid_cli(_) ->
     emqx_ctl:usage([{"clientid list", "List clientid auth rules"},
                     {"clientid add <Username> <Password>", "Add clientid auth rule"},
                     {"clientid update <Username> <NewPassword>", "Update clientid auth rule"},
-                    {"clientid del <Username>", "Delete clientid auth rule"}]).
+                    {"clientid delete <Username>", "Delete clientid auth rule"}]).
 
 %%--------------------------------------------------------------------
 %% Auth Username Cli
@@ -176,6 +179,9 @@ auth_username_cli(["update", Username, NewPassword]) ->
         {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
     end;
 auth_username_cli(["del", Username]) ->
+    auth_username_cli(["delete", Username]);
+
+auth_username_cli(["delete", Username]) ->
     case  remove_user({username, iolist_to_binary(Username)}) of
         ok -> emqx_ctl:print("ok~n");
         {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason])
@@ -185,4 +191,4 @@ auth_username_cli(_) ->
     emqx_ctl:usage([{"user list", "List username auth rules"},
                     {"user add <Username> <Password>", "Add username auth rule"},
                     {"user update <Username> <NewPassword>", "Update username auth rule"},
-                    {"user del <Username>", "Delete username auth rule"}]).
+                    {"user delete <Username>", "Delete username auth rule"}]).

+ 62 - 10
apps/emqx_auth_mnesia/test/emqx_acl_mnesia_SUITE.erl

@@ -42,13 +42,13 @@ groups() ->
     [].
 
 init_per_suite(Config) ->
-    emqx_ct_helpers:start_apps([emqx_management, emqx_auth_mnesia], fun set_special_configs/1),
+    emqx_ct_helpers:start_apps([emqx_modules, emqx_management, emqx_auth_mnesia], fun set_special_configs/1),
     create_default_app(),
     Config.
 
 end_per_suite(_Config) ->
     delete_default_app(),
-    emqx_ct_helpers:stop_apps([emqx_management, emqx_auth_mnesia]).
+    emqx_ct_helpers:stop_apps([emqx_modules, emqx_management, emqx_auth_mnesia]).
 
 init_per_testcase(t_check_acl_as_clientid, Config) ->
     emqx:hook('client.check_acl', fun emqx_acl_mnesia:check_acl/5, [#{key_as => clientid}]),
@@ -86,11 +86,15 @@ t_management(_Config) ->
     ok = emqx_acl_mnesia_cli:add_acl({username, <<"test_username">>}, <<"topic/%u">>, sub, deny),
     ok = emqx_acl_mnesia_cli:add_acl({username, <<"test_username">>}, <<"topic/+">>, pub, allow),
     ok = emqx_acl_mnesia_cli:add_acl(all, <<"#">>, pubsub, deny),
+    %% Sleeps below are needed to hide the race condition between
+    %% mnesia and ets dirty select in check_acl, that make this test
+    %% flaky
+    timer:sleep(100),
 
     ?assertEqual(2, length(emqx_acl_mnesia_cli:lookup_acl({clientid, <<"test_clientid">>}))),
     ?assertEqual(2, length(emqx_acl_mnesia_cli:lookup_acl({username, <<"test_username">>}))),
-    ?assertEqual(1, length(emqx_acl_mnesia_cli:lookup_acl(all))),
-    ?assertEqual(5, length(emqx_acl_mnesia_cli:all_acls())),
+    ?assertEqual(2, length(emqx_acl_mnesia_cli:lookup_acl(all))),
+    ?assertEqual(6, length(emqx_acl_mnesia_cli:all_acls())),
 
     User1 = #{zone => external, clientid => <<"test_clientid">>},
     User2 = #{zone => external, clientid => <<"no_exist">>, username => <<"test_username">>},
@@ -105,11 +109,55 @@ t_management(_Config) ->
     deny  = emqx_access_control:check_acl(User3, subscribe, <<"topic/A/B">>),
     deny  = emqx_access_control:check_acl(User3, publish,   <<"topic/A/B">>),
 
+    %% Test merging of pubsub capability:
+    ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pubsub, deny),
+    timer:sleep(100),
+    deny  = emqx_access_control:check_acl(User1, subscribe,   <<"topic/mix">>),
+    deny  = emqx_access_control:check_acl(User1, publish,     <<"topic/mix">>),
+    ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pub, allow),
+    timer:sleep(100),
+    deny  = emqx_access_control:check_acl(User1, subscribe,   <<"topic/mix">>),
+    allow = emqx_access_control:check_acl(User1, publish,     <<"topic/mix">>),
+    ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pubsub, allow),
+    timer:sleep(100),
+    allow = emqx_access_control:check_acl(User1, subscribe,   <<"topic/mix">>),
+    allow = emqx_access_control:check_acl(User1, publish,     <<"topic/mix">>),
+    ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, sub, deny),
+    timer:sleep(100),
+    deny  = emqx_access_control:check_acl(User1, subscribe,   <<"topic/mix">>),
+    allow = emqx_access_control:check_acl(User1, publish,     <<"topic/mix">>),
+    ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pub, deny),
+    timer:sleep(100),
+    deny  = emqx_access_control:check_acl(User1, subscribe,   <<"topic/mix">>),
+    deny  = emqx_access_control:check_acl(User1, publish,     <<"topic/mix">>),
+
+    %% Test implicit migration of pubsub to pub and sub:
+    ok = emqx_acl_mnesia_cli:remove_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>),
+    ok = mnesia:dirty_write(#emqx_acl{
+                               filter = {{clientid, <<"test_clientid">>}, <<"topic/mix">>},
+                               action = pubsub,
+                               access = allow,
+                               created_at = erlang:system_time(millisecond)
+                              }),
+    timer:sleep(100),
+    allow = emqx_access_control:check_acl(User1, subscribe,   <<"topic/mix">>),
+    allow = emqx_access_control:check_acl(User1, publish,     <<"topic/mix">>),
+    ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pub, deny),
+    timer:sleep(100),
+    allow = emqx_access_control:check_acl(User1, subscribe,   <<"topic/mix">>),
+    deny  = emqx_access_control:check_acl(User1, publish,     <<"topic/mix">>),
+    ok = emqx_acl_mnesia_cli:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, sub, deny),
+    timer:sleep(100),
+    deny  = emqx_access_control:check_acl(User1, subscribe,   <<"topic/mix">>),
+    deny  = emqx_access_control:check_acl(User1, publish,     <<"topic/mix">>),
+
     ok = emqx_acl_mnesia_cli:remove_acl({clientid, <<"test_clientid">>}, <<"topic/%c">>),
     ok = emqx_acl_mnesia_cli:remove_acl({clientid, <<"test_clientid">>}, <<"topic/+">>),
+    ok = emqx_acl_mnesia_cli:remove_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>),
     ok = emqx_acl_mnesia_cli:remove_acl({username, <<"test_username">>}, <<"topic/%u">>),
     ok = emqx_acl_mnesia_cli:remove_acl({username, <<"test_username">>}, <<"topic/+">>),
     ok = emqx_acl_mnesia_cli:remove_acl(all, <<"#">>),
+    timer:sleep(100),
 
     ?assertEqual([], emqx_acl_mnesia_cli:all_acls()).
 
@@ -124,6 +172,7 @@ t_acl_cli(_Config) ->
 
     ?assertEqual(0, length(emqx_acl_mnesia_cli:cli(["list"]))),
 
+    emqx_acl_mnesia_cli:cli(["add", "clientid", "test_clientid", "topic/A", "pub", "deny"]),
     emqx_acl_mnesia_cli:cli(["add", "clientid", "test_clientid", "topic/A", "pub", "allow"]),
     R1 = emqx_ctl:format("Acl(clientid = ~p topic = ~p action = ~p access = ~p)~n",
                          [<<"test_clientid">>, <<"topic/A">>, pub, allow]),
@@ -136,11 +185,14 @@ t_acl_cli(_Config) ->
     ?assertEqual([R2], emqx_acl_mnesia_cli:cli(["show", "username", "test_username"])),
     ?assertEqual([R2], emqx_acl_mnesia_cli:cli(["list", "username"])),
 
+    emqx_acl_mnesia_cli:cli(["add", "_all", "#", "pub", "allow"]),
     emqx_acl_mnesia_cli:cli(["add", "_all", "#", "pubsub", "deny"]),
-    ?assertMatch(["Acl($all topic = <<\"#\">> action = pubsub access = deny)\n"],
-                 emqx_acl_mnesia_cli:cli(["list", "_all"])
+    ?assertMatch(["",
+                  "Acl($all topic = <<\"#\">> action = pub access = deny)",
+                  "Acl($all topic = <<\"#\">> action = sub access = deny)"],
+                 lists:sort(string:split(emqx_acl_mnesia_cli:cli(["list", "_all"]), "\n", all))
                 ),
-    ?assertEqual(3, length(emqx_acl_mnesia_cli:cli(["list"]))),
+    ?assertEqual(4, length(emqx_acl_mnesia_cli:cli(["list"]))),
 
     emqx_acl_mnesia_cli:cli(["del", "clientid", "test_clientid", "topic/A"]),
     emqx_acl_mnesia_cli:cli(["del", "username", "test_username", "topic/B"]),
@@ -169,7 +221,7 @@ t_rest_api(_Config) ->
                 }],
     {ok, _} = request_http_rest_add([], Params1),
     {ok, Re1} = request_http_rest_list(["clientid", "test_clientid"]),
-    ?assertMatch(3, length(get_http_data(Re1))),
+    ?assertMatch(4, length(get_http_data(Re1))),
     {ok, _} = request_http_rest_delete(["clientid", "test_clientid", "topic", "topic/A"]),
     {ok, _} = request_http_rest_delete(["clientid", "test_clientid", "topic", "topic/B"]),
     {ok, _} = request_http_rest_delete(["clientid", "test_clientid", "topic", "topic/C"]),
@@ -193,7 +245,7 @@ t_rest_api(_Config) ->
                 }],
     {ok, _} = request_http_rest_add([], Params2),
     {ok, Re2} = request_http_rest_list(["username", "test_username"]),
-    ?assertMatch(3, length(get_http_data(Re2))),
+    ?assertMatch(4, length(get_http_data(Re2))),
     {ok, _} = request_http_rest_delete(["username", "test_username", "topic", "topic/A"]),
     {ok, _} = request_http_rest_delete(["username", "test_username", "topic", "topic/B"]),
     {ok, _} = request_http_rest_delete(["username", "test_username", "topic", "topic/C"]),
@@ -214,7 +266,7 @@ t_rest_api(_Config) ->
                 }],
     {ok, _} = request_http_rest_add([], Params3),
     {ok, Re3} = request_http_rest_list(["$all"]),
-    ?assertMatch(3, length(get_http_data(Re3))),
+    ?assertMatch(4, length(get_http_data(Re3))),
     {ok, _} = request_http_rest_delete(["$all", "topic", "topic/A"]),
     {ok, _} = request_http_rest_delete(["$all", "topic", "topic/B"]),
     {ok, _} = request_http_rest_delete(["$all", "topic", "topic/C"]),

+ 2 - 2
apps/emqx_auth_mnesia/test/emqx_auth_mnesia_SUITE.erl

@@ -47,13 +47,13 @@ groups() ->
     [].
 
 init_per_suite(Config) ->
-    ok = emqx_ct_helpers:start_apps([emqx_management, emqx_auth_mnesia], fun set_special_configs/1),
+    ok = emqx_ct_helpers:start_apps([emqx_modules, emqx_management, emqx_auth_mnesia], fun set_special_configs/1),
     create_default_app(),
     Config.
 
 end_per_suite(_Config) ->
     delete_default_app(),
-    emqx_ct_helpers:stop_apps([emqx_management, emqx_auth_mnesia]).
+    emqx_ct_helpers:stop_apps([emqx_modules, emqx_management, emqx_auth_mnesia]).
 
 init_per_testcase(t_check_as_clientid, Config) ->
     Params = #{

+ 15 - 0
apps/emqx_auth_mongo/etc/emqx_auth_mongo.conf

@@ -70,6 +70,21 @@ auth.mongo.database = mqtt
 ## Value: File
 ## auth.mongo.ssl.cacertfile =
 
+## In mode verify_none the default behavior is to allow all x509-path
+## validation errors.
+##
+## Value: true | false
+## auth.mongo.ssl.verify = false
+
+## If not specified, the server's names returned in server's certificate is validated against
+## what's provided `auth.mongo.server` config's host part.
+## Setting to 'disable' will make EMQ X ignore unmatched server names.
+## If set with a host name, the server's names returned in server's certificate is validated
+## against this value.
+##
+## Value: String | disable
+## auth.mongo.ssl.server_name_indication = disable
+
 ## MongoDB write mode.
 ##
 ## Value: unsafe | safe

+ 20 - 2
apps/emqx_auth_mongo/priv/emqx_auth_mongo.schema

@@ -62,6 +62,15 @@
   {datatype, string}
 ]}.
 
+{mapping, "auth.mongo.ssl.verify", "emqx_auth_mongo.server", [
+  {default, false},
+  {datatype, {enum, [true, false]}}
+]}.
+
+{mapping, "auth.mongo.ssl.server_name_indication", "emqx_auth_mongo.server", [
+  {datatype, string}
+]}.
+
 %% FIXME: compatible with 4.0-4.2 version format, plan to delete in 5.0
 {mapping, "auth.mongo.ssl_opts.keyfile", "emqx_auth_mongo.server", [
   {datatype, string}
@@ -123,8 +132,17 @@
   end,
   Filter  = fun(Opts) -> [{K, V} || {K, V} <- Opts, V =/= undefined] end,
   SslOpts = fun(Prefix) ->
-                Filter([{keyfile,    cuttlefish:conf_get(Prefix ++ ".keyfile", Conf, undefined)},
-                        {certfile,   cuttlefish:conf_get(Prefix ++ ".certfile", Conf, undefined)},
+                Verify = case cuttlefish:conf_get(Prefix ++ ".verify", Conf, false) of
+                             true -> verify_peer;
+                             flase -> verify_none
+                         end,
+                Filter([{verify, Verify},
+                        {server_name_indication, case cuttlefish:conf_get(Prefix ++ ".server_name_indication", Conf, undefined) of
+                                                   "disable" -> disable;
+                                                   SNI -> SNI
+                                                 end},
+                        {keyfile, cuttlefish:conf_get(Prefix ++ ".keyfile", Conf, undefined)},
+                        {certfile, cuttlefish:conf_get(Prefix ++ ".certfile", Conf, undefined)},
                         {cacertfile, cuttlefish:conf_get(Prefix ++ ".cacertfile", Conf, undefined)}])
             end,
 

+ 4 - 9
apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE.erl

@@ -50,23 +50,18 @@ all() ->
     emqx_ct:all(?MODULE).
 
 init_per_suite(Cfg) ->
-    emqx_ct_helpers:start_apps([emqx_modules, emqx_auth_mongo], fun set_special_confs/1),
-    emqx_modules:load_module(emqx_mod_acl_internal, false),
+    emqx_ct_helpers:start_apps([emqx_auth_mongo], fun set_special_confs/1),
     init_mongo_data(),
     Cfg.
 
 end_per_suite(_Cfg) ->
     deinit_mongo_data(),
-    emqx_ct_helpers:stop_apps([emqx_auth_mongo, emqx_modules]).
+    emqx_ct_helpers:stop_apps([emqx_auth_mongo]).
 
 set_special_confs(emqx) ->
     application:set_env(emqx, acl_nomatch, deny),
-    application:set_env(emqx, acl_file,
-                        emqx_ct_helpers:deps_path(emqx, "test/emqx_SUITE_data/acl.conf")),
     application:set_env(emqx, allow_anonymous, false),
-    application:set_env(emqx, enable_acl_cache, false),
-    application:set_env(emqx, plugins_loaded_file,
-                        emqx_ct_helpers:deps_path(emqx, "test/emqx_SUITE_data/loaded_plugins"));
+    application:set_env(emqx, enable_acl_cache, false);
 set_special_confs(_App) ->
     ok.
 
@@ -133,7 +128,7 @@ t_check_acl(_) ->
     allow = emqx_access_control:check_acl(User2, subscribe, <<"$SYS/testuser/1">>),
     allow = emqx_access_control:check_acl(User3, publish, <<"a/b/c">>),
     deny = emqx_access_control:check_acl(User3, publish, <<"c">>),
-    allow = emqx_access_control:check_acl(User4, publish, <<"a/b/c">>).
+    deny = emqx_access_control:check_acl(User4, publish, <<"a/b/c">>).
 
 t_acl_super(_) ->
     reload({auth_query, [{password_hash, plain}, {password_field, [<<"password">>]}]}),

+ 15 - 0
apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf

@@ -114,3 +114,18 @@ auth.mysql.acl_query = "select allow, ipaddr, username, clientid, access, topic
 ##
 ## Value: File
 #auth.mysql.ssl.keyfile = /path/to/your/clientkey.pem
+
+## In mode verify_none the default behavior is to allow all x509-path
+## validation errors.
+##
+## Value: true | false
+#auth.mysql.ssl.verify = false
+
+## If not specified, the server's names returned in server's certificate is validated against
+## what's provided `auth.mysql.server` config's host part.
+## Setting to 'disable' will make EMQ X ignore unmatched server names.
+## If set with a host name, the server's names returned in server's certificate is validated
+## against this value.
+##
+## Value: String | disable
+## auth.mysql.ssl.server_name_indication = disable

+ 21 - 2
apps/emqx_auth_mysql/priv/emqx_auth_mysql.schema

@@ -52,6 +52,15 @@
   {datatype, string}
 ]}.
 
+{mapping, "auth.mysql.ssl.verify", "emqx_auth_mysql.server", [
+  {default, false},
+  {datatype, {enum, [true, false]}}
+]}.
+
+{mapping, "auth.mysql.ssl.server_name_indication", "emqx_auth_mysql.server", [
+  {datatype, string}
+]}.
+
 {translation, "emqx_auth_mysql.server", fun(Conf) ->
   {MyHost, MyPort} =
   case cuttlefish:conf_get("auth.mysql.server", Conf) of
@@ -94,10 +103,20 @@
                      ),
                 Cert = cuttlefish:conf_get("auth.mysql.ssl.certfile", Conf, undefined),
                 Key = cuttlefish:conf_get("auth.mysql.ssl.keyfile", Conf, undefined),
-                Options ++ [{ssl, Filter([{server_name_indication, disable},
+                Verify = case cuttlefish:conf_get("auth.mysql.ssl.verify", Conf, false) of
+                             true -> verify_peer;
+                             flase -> verify_none
+                         end,
+                SNI = case cuttlefish:conf_get("auth.mysql.ssl.server_name_indication", Conf, undefined) of
+                        "disable" -> disable;
+                        SNI0 -> SNI0
+                      end,
+                Options ++ [{ssl, Filter([{server_name_indication, SNI},
                                           {cacertfile, CA},
                                           {certfile, Cert},
-                                          {keyfile, Key}])
+                                          {keyfile, Key},
+                                          {verify, Verify}
+                                         ])
                             }];
             _ ->
                 Options

+ 15 - 0
apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf

@@ -62,6 +62,21 @@ auth.pgsql.ssl.enable = off
 ## Value: File
 #auth.pgsql.ssl.cacertfile =
 
+## In mode verify_none the default behavior is to allow all x509-path
+## validation errors.
+##
+## Value: true | false
+#auth.pgsql.ssl.verify = false
+
+## If not specified, the server's names returned in server's certificate is validated against
+## what's provided `auth.pgsql.server` config's host part.
+## Setting to 'disable' will make EMQ X ignore unmatched server names.
+## If set with a host name, the server's names returned in server's certificate is validated
+## against this value.
+##
+## Value: String | disable
+## auth.pgsql.ssl.server_name_indication = disable
+
 ## Authentication query.
 ##
 ## Value: SQL

+ 18 - 0
apps/emqx_auth_pgsql/priv/emqx_auth_pgsql.schema

@@ -52,6 +52,15 @@
   {datatype, string}
 ]}.
 
+{mapping, "auth.pgsql.ssl.verify", "emqx_auth_pgsql.server", [
+  {default, false},
+  {datatype, {enum, [true, false]}}
+]}.
+
+{mapping, "auth.pgsql.ssl.server_name_indication", "emqx_auth_pgsql.server", [
+  {datatype, string}
+]}.
+
 %% FIXME: compatible with 4.0-4.2 version format, plan to delete in 5.0
 {mapping, "auth.pgsql.ssl_opts.keyfile", "emqx_auth_pgsql.server", [
   {datatype, string}
@@ -90,9 +99,18 @@
 
   Filter  = fun(Opts) -> [{K, V} || {K, V} <- Opts, V =/= undefined] end,
   SslOpts = fun(Prefix) ->
+                Verify = case cuttlefish:conf_get(Prefix ++ ".verify", Conf, false) of
+                             true -> verify_peer;
+                             flase -> verify_none
+                         end,
                 Filter([{keyfile,    cuttlefish:conf_get(Prefix ++ ".keyfile", Conf, undefined)},
                         {certfile,   cuttlefish:conf_get(Prefix ++ ".certfile", Conf, undefined)},
                         {cacertfile, cuttlefish:conf_get(Prefix ++ ".cacertfile", Conf, undefined)},
+                        {verify,     Verify},
+                        {server_name_indication, case cuttlefish:conf_get(Prefix ++ ".server_name_indication", Conf, undefined) of
+                                                   "disable" -> disable;
+                                                   SNI -> SNI
+                                                 end},
                         {versions, [list_to_existing_atom(Value)
                                     || Value <- string:tokens(cuttlefish:conf_get(Prefix ++ ".tls_versions", Conf), " ,")]}])
             end,

+ 4 - 5
apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE.erl

@@ -70,7 +70,7 @@ all() ->
     emqx_ct:all(?MODULE).
 
 init_per_suite(Config) ->
-    emqx_ct_helpers:start_apps([emqx_modules, emqx_auth_pgsql]),
+    emqx_ct_helpers:start_apps([emqx_auth_pgsql]),
     drop_acl(),
     drop_auth(),
     init_auth(),
@@ -79,7 +79,7 @@ init_per_suite(Config) ->
     Config.
 
 end_per_suite(Config) ->
-    emqx_ct_helpers:stop_apps([emqx_auth_pgsql, emqx_modules]),
+    emqx_ct_helpers:stop_apps([emqx_auth_pgsql]),
     Config.
 
 set_special_configs() ->
@@ -161,7 +161,6 @@ t_check_auth(_) ->
     {error, not_authorized} = emqx_access_control:authenticate(Bcrypt#{password => <<"password">>}).
 
 t_check_acl(_) ->
-    emqx_modules:load_module(emqx_mod_acl_internal, false),
     User1 = #{zone => external, peerhost => {127,0,0,1}, clientid => <<"c1">>, username => <<"u1">>},
     User2 = #{zone => external, peerhost => {127,0,0,1}, clientid => <<"c2">>, username => <<"u2">>},
     allow = emqx_access_control:check_acl(User1, subscribe, <<"t1">>),
@@ -170,8 +169,8 @@ t_check_acl(_) ->
     User4 = #{zone => external, peerhost => {10,10,10,110}, clientid => <<"c1">>, username => <<"u1">>},
     allow = emqx_access_control:check_acl(User3, subscribe, <<"t1">>),
     allow = emqx_access_control:check_acl(User3, subscribe, <<"t1">>),
-    allow = emqx_access_control:check_acl(User3, subscribe, <<"t2">>),%% nomatch -> ignore -> emqttd acl
-    allow = emqx_access_control:check_acl(User4, subscribe, <<"t1">>),%% nomatch -> ignore -> emqttd acl
+    deny = emqx_access_control:check_acl(User3, subscribe, <<"t2">>),%% nomatch -> ignore -> emqx acl
+    deny = emqx_access_control:check_acl(User4, subscribe, <<"t1">>),%% nomatch -> ignore -> emqx acl
     User5 = #{zone => external, peerhost => {127,0,0,1}, clientid => <<"c3">>, username => <<"u3">>},
     allow = emqx_access_control:check_acl(User5, subscribe, <<"t1">>),
     allow = emqx_access_control:check_acl(User5, publish, <<"t1">>).

+ 27 - 0
apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/ca-key.pem

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA0kGUBi9NDp65jgdxKfizIfuSr2wpwb44yM9SuP4oUQSULOA2
+4iFpLR/c5FAYHU81y9Vx91dQjdZfffaBZuv2zVvteXUkol8Nez7boKbo2E41MTew
+8edtNKZAQVvnaHAC2NCZxjchCzUCDEoUUcl+cIERZ8R48FBqK5iTVcMRIx1akwus
++dhBqP0ykA5TGOWZkJrLM9aUXSPQha9+wXlOpkvu0Ur2nkX8PPJnifWao9UShSar
+ll1IqPZNCSlZMwcFYcQNBCpdvITUUYlHvMRQV64bUpOxUGDuJkQL3dLKBlNuBRlJ
+BcjBAKw7rFnwwHZcMmQ9tan/dZzpzwjo/T0XjwIDAQABAoIBAQCSHvUqnzDkWjcG
+l/Fzg92qXlYBCCC0/ugj1sHcwvVt6Mq5rVE3MpUPwTcYjPlVVTlD4aEEjm/zQuq2
+ddxUlOS+r4aIhHrjRT/vSS4FpjnoKeIZxGR6maVxk6DQS3i1QjMYT1CvSpzyVvKH
+a+xXMrtmoKxh+085ZAmFJtIuJhUA2yEa4zggCxWnvz8ecLClUPfVDPhdLBHc3KmL
+CRpHEC6L/wanvDPRdkkzfKyaJuIJlTDaCg63AY5sDkTW2I57iI/nJ3haSeidfQKz
+39EfbnM1A/YprIakafjAu3frBIsjBVcxwGihZmL/YriTHjOggJF841kT5zFkkv2L
+/530Wk6xAoGBAOqZLZ4DIi/zLndEOz1mRbUfjc7GQUdYplBnBwJ22VdS0P4TOXnd
+UbJth2MA92NM7ocTYVFl4TVIZY/Y+Prxk7KQdHWzR7JPpKfx9OEVgtSqV0vF9eGI
+rKp79Y1T4Mvc3UcQCXX6TP7nHLihEzpS8odm2LW4txrOiLsn4Fq/IWrLAoGBAOVv
+6U4tm3lImotUupKLZPKEBYwruo9qRysoug9FiorP4TjaBVOfltiiHbAQD6aGfVtN
+SZpZZtrs17wL7Xl4db5asgMcZd+8Hkfo5siR7AuGW9FZloOjDcXb5wCh9EvjJ74J
+Cjw7RqyVymq9t7IP6wnVwj5Ck48YhlOZCz/mzlnNAoGAWq7NYFgLvgc9feLFF23S
+IjpJQZWHJEITP98jaYNxbfzYRm49+GphqxwFinKULjFNvq7yHlnIXSVYBOu1CqOZ
+GRwXuGuNmlKI7lZr9xmukfAqgGLMMdr4C4qRF4lFyufcLRz42z7exmWlx4ST/yaT
+E13hBRWayeTuG5JFei6Jh1MCgYEAqmX4LyC+JFBgvvQZcLboLRkSCa18bADxhENG
+FAuAvmFvksqRRC71WETmqZj0Fqgxt7pp3KFjO1rFSprNLvbg85PmO1s+6fCLyLpX
+lESTu2d5D71qhK93jigooxalGitFm+SY3mzjq0/AOpBWOn+J/w7rqVPGxXLgaHv0
+l+vx+00CgYBOvo9/ImjwYii2jFl+sHEoCzlvpITi2temRlT2j6ulSjCLJgjwEFw9
+8e+vvfQumQOsutakUVyURrkMGNDiNlIv8kv5YLCCkrwN22E6Ghyi69MJUvHQXkc/
+QZhjn/luyfpB5f/BeHFS2bkkxAXo+cfG45ApY3Qfz6/7o+H+vDa6/A==
+-----END RSA PRIVATE KEY-----

+ 19 - 0
apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/ca.pem

@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDAzCCAeugAwIBAgIBATANBgkqhkiG9w0BAQsFADA8MTowOAYDVQQDDDFNeVNR
+TF9TZXJ2ZXJfOC4wLjE5X0F1dG9fR2VuZXJhdGVkX0NBX0NlcnRpZmljYXRlMB4X
+DTIwMDYxMTAzMzg0NloXDTMwMDYwOTAzMzg0NlowPDE6MDgGA1UEAwwxTXlTUUxf
+U2VydmVyXzguMC4xOV9BdXRvX0dlbmVyYXRlZF9DQV9DZXJ0aWZpY2F0ZTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANJBlAYvTQ6euY4HcSn4syH7kq9s
+KcG+OMjPUrj+KFEElCzgNuIhaS0f3ORQGB1PNcvVcfdXUI3WX332gWbr9s1b7Xl1
+JKJfDXs+26Cm6NhONTE3sPHnbTSmQEFb52hwAtjQmcY3IQs1AgxKFFHJfnCBEWfE
+ePBQaiuYk1XDESMdWpMLrPnYQaj9MpAOUxjlmZCayzPWlF0j0IWvfsF5TqZL7tFK
+9p5F/DzyZ4n1mqPVEoUmq5ZdSKj2TQkpWTMHBWHEDQQqXbyE1FGJR7zEUFeuG1KT
+sVBg7iZEC93SygZTbgUZSQXIwQCsO6xZ8MB2XDJkPbWp/3Wc6c8I6P09F48CAwEA
+AaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEADKz6bIpP5anp
+GgLB0jkclRWuMlS4qqIt4itSsMXPJ/ezpHwECixmgW2TIQl6S1woRkUeMxhT2/Ay
+Sn/7aKxuzRagyE5NEGOvrOuAP5RO2ZdNJ/X3/Rh533fK1sOTEEbSsWUvW6iSkZef
+rsfZBVP32xBhRWkKRdLeLB4W99ADMa0IrTmZPCXHSSE2V4e1o6zWLXcOZeH1Qh8N
+SkelBweR+8r1Fbvy1r3s7eH7DCbYoGEDVLQGOLvzHKBisQHmoDnnF5E9g1eeNRdg
+o+vhOKfYCOzeNREJIqS42PHcGhdNRk90ycigPmfUJclz1mDHoMjKR2S5oosTpr65
+tNPx3CL7GA==
+-----END CERTIFICATE-----

+ 19 - 0
apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/client-cert.pem

@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDBDCCAeygAwIBAgIBAzANBgkqhkiG9w0BAQsFADA8MTowOAYDVQQDDDFNeVNR
+TF9TZXJ2ZXJfOC4wLjE5X0F1dG9fR2VuZXJhdGVkX0NBX0NlcnRpZmljYXRlMB4X
+DTIwMDYxMTAzMzg0N1oXDTMwMDYwOTAzMzg0N1owQDE+MDwGA1UEAww1TXlTUUxf
+U2VydmVyXzguMC4xOV9BdXRvX0dlbmVyYXRlZF9DbGllbnRfQ2VydGlmaWNhdGUw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVYSWpOvCTupz82fc85Opv
+EQ7rkB8X2oOMyBCpkyHKBIr1ZQgRDWBp9UVOASq3GnSElm6+T3Kb1QbOffa8GIlw
+sjAueKdq5L2eSkmPIEQ7eoO5kEW+4V866hE1LeL/PmHg2lGP0iqZiJYtElhHNQO8
+3y9I7cm3xWMAA3SSWikVtpJRn3qIp2QSrH+tK+/HHbE5QwtPxdir4ULSCSOaM5Yh
+Wi5Oto88TZqe1v7SXC864JVvO4LuS7TuSreCdWZyPXTJFBFeCEWSAxonKZrqHbBe
+CwKML6/0NuzjaQ51c2tzmVI6xpHj3nnu4cSRx6Jf9WBm+35vm0wk4pohX3ptdzeV
+AgMBAAGjDTALMAkGA1UdEwQCMAAwDQYJKoZIhvcNAQELBQADggEBAByQ5zSNeFUH
+Aw7JlpZHtHaSEeiiyBHke20ziQ07BK1yi/ms2HAWwQkpZv149sjNuIRH8pkTmkZn
+g8PDzSefjLbC9AsWpWV0XNV22T/cdobqLqMBDDZ2+5bsV+jTrOigWd9/AHVZ93PP
+IJN8HJn6rtvo2l1bh/CdsX14uVSdofXnuWGabNTydqtMvmCerZsdf6qKqLL+PYwm
+RDpgWiRUY7KPBSSlKm/9lJzA+bOe4dHeJzxWFVCJcbpoiTFs1je1V8kKQaHtuW39
+ifX6LTKUMlwEECCbDKM8Yq2tm8NjkjCcnFDtKg8zKGPUu+jrFMN5otiC3wnKcP7r
+O9EkaPcgYH8=
+-----END CERTIFICATE-----

+ 27 - 0
apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/client-key.pem

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEA1WElqTrwk7qc/Nn3POTqbxEO65AfF9qDjMgQqZMhygSK9WUI
+EQ1gafVFTgEqtxp0hJZuvk9ym9UGzn32vBiJcLIwLninauS9nkpJjyBEO3qDuZBF
+vuFfOuoRNS3i/z5h4NpRj9IqmYiWLRJYRzUDvN8vSO3Jt8VjAAN0klopFbaSUZ96
+iKdkEqx/rSvvxx2xOUMLT8XYq+FC0gkjmjOWIVouTraPPE2antb+0lwvOuCVbzuC
+7ku07kq3gnVmcj10yRQRXghFkgMaJyma6h2wXgsCjC+v9Dbs42kOdXNrc5lSOsaR
+49557uHEkceiX/VgZvt+b5tMJOKaIV96bXc3lQIDAQABAoIBAF7yjXmSOn7h6P0y
+WCuGiTLG2mbDiLJqj2LTm2Z5i+2Cu/qZ7E76Ls63TxF4v3MemH5vGfQhEhR5ZD/6
+GRJ1sKKvB3WGRqjwA9gtojHH39S/nWGy6vYW/vMOOH37XyjIr3EIdIaUtFQBTSHd
+Kd71niYrAbVn6fyWHolhADwnVmTMOl5OOAhCdEF4GN3b5aIhIu8BJ7EUzTtHBJIj
+CAEfjZFjDs1y1cIgGFJkuIQxMfCpq5recU2qwip7YO6fk//WEjOPu7kSf5IEswL8
+jg1dea9rGBV6KaD2xsgsC6Ll6Sb4BbsrHMfflG3K2Lk3RdVqqTFp1Fn1PTLQE/1S
+S/SZPYECgYEA9qYcHKHd0+Q5Ty5wgpxKGa4UCWkpwvfvyv4bh8qlmxueB+l2AIdo
+ZvkM8gTPagPQ3WypAyC2b9iQu70uOJo1NizTtKnpjDdN1YpDjISJuS/P0x73gZwy
+gmoM5AzMtN4D6IbxXtXnPaYICvwLKU80ouEN5ZPM4/ODLUu6gsp0v2UCgYEA3Xgi
+zMC4JF0vEKEaK0H6QstaoXUmw/lToZGH3TEojBIkb/2LrHUclygtONh9kJSFb89/
+jbmRRLAOrx3HZKCNGUmF4H9k5OQyAIv6OGBinvLGqcbqnyNlI+Le8zxySYwKMlEj
+EMrBCLmSyi0CGFrbZ3mlj/oCET/ql9rNvcK+DHECgYAEx5dH3sMjtgp+RFId1dWB
+xePRgt4yTwewkVgLO5wV82UOljGZNQaK6Eyd7AXw8f38LHzh+KJQbIvxd2sL4cEi
+OaAoohpKg0/Y0YMZl//rPMf0OWdmdZZs/I0fZjgZUSwWN3c59T8z7KG/RL8an9RP
+S7kvN7wCttdV61/D5RR6GQKBgDxCe/WKWpBKaovzydMLWLTj7/0Oi0W3iXHkzzr4
+LTgvl4qBSofaNbVLUUKuZTv5rXUG2IYPf99YqCYtzBstNDc1MiAriaBeFtzfOW4t
+i6gEFtoLLbuvPc3N5Sv5vn8Ug5G9UfU3td5R4AbyyCcoUZqOFuZd+EIJSiOXfXOs
+kVmBAoGBAIU9aPAqhU5LX902oq8KsrpdySONqv5mtoStvl3wo95WIqXNEsFY60wO
+q02jKQmJJ2MqhkJm2EoF2Mq8+40EZ5sz8LdgeQ/M0yQ9lAhPi4rftwhpe55Ma9dk
+SE9X1c/DMCBEaIjJqVXdy0/EeArwpb8sHkguVVAZUWxzD+phm1gs
+-----END RSA PRIVATE KEY-----

+ 0 - 21
apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/postgresql.crt

@@ -1,21 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDYzCCAksCCQC7J1oPkDz7vTANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMC
-Q0ExGTAXBgNVBAgMEEJyaXRpc2ggQ29sdW1iaWExDjAMBgNVBAcMBUNvbW94MRQw
-EgYDVQQKDAtUaGVCcmFpbi5jYTEUMBIGA1UEAwwLdGhlYnJhaW4uY2ExHzAdBgkq
-hkiG9w0BCQEWEGluZm9AdGhlYnJhaW4uY2EwHhcNMjEwMTEzMDkwNzM2WhcNMjEw
-MjEyMDkwNzM2WjBhMQswCQYDVQQGEwJDQTEZMBcGA1UECAwQQnJpdGlzaCBDb2x1
-bWJpYTEOMAwGA1UEBwwFQ29tb3gxFDASBgNVBAoMC1RoZUJyYWluLmNhMREwDwYD
-VQQDDAh3d3ctZGF0YTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJv9
-yO5JGKBl+7w0HGkRDIPZ5Ku3lIAzB4ThszRHBqll7VjlTz+q16OQOONqeHBuxPjj
-11WMXD2KnfYZW2ZWd0U8FKzuIGOCStGbSUi2hC0owp+KkJcDujfIafXQnAa0fUiS
-FBB5iG98vm3QI4gv9135LgnO5oHopH6oZ/t0Id1LzFhp2sdhebdtczmImpo+nt7v
-fduapptuIJ20ThdAvo3MlYoAhivsvJKntlWPAwPMQdyezww/q7T5Y8DCyJJTydr5
-PrMz9S/WQTkj/G0y4dZgQonG5r0d1Nf+rwkn78DdXGktVDMBBP41+VWnEDBCTlgS
-FjQEY6Izaof8s8q8K2UCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAdlAQkumOAKbQ
-SW5gtkHgKyIQyfwk9maKqKccK04WlNk1t1jsvk7kaOEHr3t7YG28yKqicGHAcfFf
-i/RU51v2GJVzWCbzkAAH/zNgDcYnYk6sn54YcuBzrPliVH1xxmZy/52+huTxy8Vd
-3nmCjdYR/I764rd8gkRK+aHaUTLyitzX1kW90LtXonKY72CNZVXHEBom3XM/a6ff
-ilybDloNVTfHstnfsnHHyNYn0SfapqXxPCO+FL9hQjlztUBZryRdS0nq66hB2GSB
-CEst/vtNGo/2aa1Vw4bKl2oGepjKNzxp0ZTTVuIcwGzV6oKIsx1ZnWE3gQLEH/TX
-dzMzesBayA==
------END CERTIFICATE-----

+ 0 - 17
apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/postgresql.csr

@@ -1,17 +0,0 @@
------BEGIN CERTIFICATE REQUEST-----
-MIICpjCCAY4CAQAwYTELMAkGA1UEBhMCQ0ExGTAXBgNVBAgMEEJyaXRpc2ggQ29s
-dW1iaWExDjAMBgNVBAcMBUNvbW94MRQwEgYDVQQKDAtUaGVCcmFpbi5jYTERMA8G
-A1UEAwwId3d3LWRhdGEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCb
-/cjuSRigZfu8NBxpEQyD2eSrt5SAMweE4bM0RwapZe1Y5U8/qtejkDjjanhwbsT4
-49dVjFw9ip32GVtmVndFPBSs7iBjgkrRm0lItoQtKMKfipCXA7o3yGn10JwGtH1I
-khQQeYhvfL5t0COIL/dd+S4JzuaB6KR+qGf7dCHdS8xYadrHYXm3bXM5iJqaPp7e
-733bmqabbiCdtE4XQL6NzJWKAIYr7LySp7ZVjwMDzEHcns8MP6u0+WPAwsiSU8na
-+T6zM/Uv1kE5I/xtMuHWYEKJxua9HdTX/q8JJ+/A3VxpLVQzAQT+NflVpxAwQk5Y
-EhY0BGOiM2qH/LPKvCtlAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAN6Q8MEDx
-g5xlpYB/fFmagpe15+G2QbqVf2mH1a4aBcBns4jMMqNidi4gyjGfzvNxX77R6KcI
-AfcxENRVDYJbhAgEQ96jv4jv5pEMuyvQ8VLhn9AOXCaK/VHxbYlOiM7tfFtEDrrB
-wTn8FvoEwjehfsSX2dWiwcUK4SPPeuklE/EGjRgoVCwg8EqWzf1fn+tzME8OpnRQ
-I8coyALF6ANehvP7ADV3m5iOOaNhfnqmqGBEwjB3TTvE1gZ4UvAyl75bi+Zh3Osn
-qemyxocp/ML4o6d/F+nKIZOe6309V2nyrY6RSd2fBCrhYj2rKTbrGTZrpKXeAhtI
-jMivnjCK+WNHpQ==
------END CERTIFICATE REQUEST-----

+ 0 - 27
apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/postgresql.key

@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEowIBAAKCAQEAm/3I7kkYoGX7vDQcaREMg9nkq7eUgDMHhOGzNEcGqWXtWOVP
-P6rXo5A442p4cG7E+OPXVYxcPYqd9hlbZlZ3RTwUrO4gY4JK0ZtJSLaELSjCn4qQ
-lwO6N8hp9dCcBrR9SJIUEHmIb3y+bdAjiC/3XfkuCc7mgeikfqhn+3Qh3UvMWGna
-x2F5t21zOYiamj6e3u9925qmm24gnbROF0C+jcyVigCGK+y8kqe2VY8DA8xB3J7P
-DD+rtPljwMLIklPJ2vk+szP1L9ZBOSP8bTLh1mBCicbmvR3U1/6vCSfvwN1caS1U
-MwEE/jX5VacQMEJOWBIWNARjojNqh/yzyrwrZQIDAQABAoIBAAOicycSLu+10Jq/
-ABZ2njsIPaq+mUgvaDJxa9KBASe7Rz92AFW0blfSSXELDwlXm2FNNbw5jACnFS0h
-xB5rT1Yeo0CwP7Lx2zptCtUV45iFxZsgCGRsYs9f7RAcLzZ8yBqDxNHpcwNd/bXj
-TqCitXnMD4WM+5P1TrfgxqN2Pj/Atg8w/4dP7KcFcTzcZzIz5rr3NTyjsrLdiFis
-sR+7m7Qu4PyEfrDpR9Np111nQqVJ1bpt9qt/hv318FaBnpNY6MMBaSni99mvMXSd
-SwHn3gnfHREWcNSLGA9gjEQmyIPHpV9T6SJ/zyr++6y8QCq4DiSP36A9zeA1XThP
-YEIsWxUCgYEAyLppQerpOT2CnbTbKO/9rGwlbf8FT2GWFcPBtUm0lp21/C32BX+H
-jNCmQsE1pZ6+sqv2mb1onr6Xl9cSEt6KsI1EJtFFR9Lnvqqu+JKo31U94z2yTqgv
-sc+qMl7shy1kja8T5NaRc++UkCVzVNsnFB9torIaqQwY9IRdRwmYjisCgYEAxvHR
-MwvWpOg25zz75OfupIOQhj9W6yphpY5/yoYBms/4OeabJhMrOV142s9souCHmuGU
-EtzOQC5jbEc+3MUjx1ZlboHY7UuoEu87kykFEs9mnaD+T34PEAJcQjSzqzS5KMJE
-Ro275xf+V/e3hS/Z3hQXmDQNQDNRYMcAZfTW9K8CgYBkHITOuYikYcc5PLBplHhi
-fHWWjLBrTPJ73GxKLH6C+BmBsrKXP2mtk4q4lIBbH/dgSV/ugYciVVBqDHwZKSDm
-uS4aZhk1nzyx3ZLyqsLK0ErTgTvi+wL+neH2yV0SdlNGTuGPKmzU89KWqfcBhWPS
-J3KYyFd/pGb13OZgvap2jQKBgBXCXR84LEHdJCQmh2aB95gGy8fjJZ6TBBsXeuKr
-xYEpPf0XO+DuN8wObSmBhmBKLorCIW/utqBOcpFlOXrsFP24dV+g1BkgLUHk6J8v
-3V4xUQfsk+Qd5YfaujyDhyMyoQ3UMaOF3QdpmGgGsAvhL/MaP3pmNwzOkBgFrAV6
-wggBAoGBAMflqy2pfqGhaj9S6qZ3K95h7NdCUikdQzqmgbNtOHaZ2kHByyYtOPLB
-1VnuDRQiacmum+fTZa6wNmvp2FWg+uxI/aspfF6SdPfGpyPrG5D+ITtqKF2xieK+
-XpzehKTrTuYQRAVhmWbhpuyahYnQyd/MrsCMGzUfAJtM7l5vKa2O
------END RSA PRIVATE KEY-----

+ 27 - 0
apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/private_key.pem

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA1zVmMhPqpSPMmYkKh5wwlRD5XuS8YWJKEM6tjFx61VK8qxHE
+YngkC2KnL5EuKAjQZIF3tJskwt0hAat047CCCZxrkNEpbVvSnvnk+A/8bg/Ww1n3
+qxzfifhsWfpUKlDnwrtH+ftt+5rZeEkf37XAPy7ZjzecAF9SDV6WSiPeAxUX2+hN
+dId42Pf45woo4LFGUlQeagCFkD/R0dpNIMGwcnkKCUikiBqr2ijSIgvRtBfZ9fBG
+jFGER2uE/Eay4AgcQsHue8skRwDCng8OnqtPnBtTytmqTy9V/BRgsVKUoksm6wsx
+kUYwgHeaq7UCvlCm25SZ7yRyd4k8t0BKDf2h+wIDAQABAoIBAEQcrHmRACTADdNS
+IjkFYALt2l8EOfMAbryfDSJtapr1kqz59JPNvmq0EIHnixo0n/APYdmReLML1ZR3
+tYkSpjVwgkLVUC1CcIjMQoGYXaZf8PLnGJHZk45RR8m6hsTV0mQ5bfBaeVa2jbma
+OzJMjcnxg/3l9cPQZ2G/3AUfEPccMxOXp1KRz3mUQcGnKJGtDbN/kfmntcwYoxaE
+Zg4RoeKAoMpK1SSHAiJKe7TnztINJ7uygR9XSzNd6auY8A3vomSIjpYO7XL+lh7L
+izm4Ir3Gb/eCYBvWgQyQa2KCJgK/sQyEs3a09ngofSEUhQJQYhgZDwUj+fDDOGqj
+hCZOA8ECgYEA+ZWuHdcUQ3ygYhLds2QcogUlIsx7C8n/Gk/FUrqqXJrTkuO0Eqqa
+B47lCITvmn2zm0ODfSFIARgKEUEDLS/biZYv7SUTrFqBLcet+aGI7Dpv91CgB75R
+tNzcIf8VxoiP0jPqdbh9mLbbxGi5Uc4p9TVXRljC4hkswaouebWee0sCgYEA3L2E
+YB3kiHrhPI9LHS5Px9C1w+NOu5wP5snxrDGEgaFCvL6zgY6PflacppgnmTXl8D1x
+im0IDKSw5dP3FFonSVXReq3CXDql7UnhfTCiLDahV7bLxTH42FofcBpDN3ERdOal
+58RwQh6VrLkzQRVoObo+hbGlFiwwSAfQC509FhECgYBsRSBpVXo25IN2yBRg09cP
++gdoFyhxrsj5kw1YnB13WrrZh+oABv4WtUhp77E5ZbpaamlKCPwBbXpAjeFg4tfr
+0bksuN7V79UGFQ9FsWuCfr8/nDwv38H2IbFlFhFONMOfPmJBey0Q6JJhm8R41mSh
+OOiJXcv85UrjIH5U0hLUDQKBgQDVLOU5WcUJlPoOXSgiT0ZW5xWSzuOLRUUKEf6l
+19BqzAzCcLy0orOrRAPW01xylt2v6/bJw1Ahva7k1ZZo/kOwjANYoZPxM+ZoSZBN
+MXl8j2mzZuJVV1RFxItV3NcLJNPB/Lk+IbRz9kt/2f9InF7iWR3mSU/wIM6j0X+2
+p6yFsQKBgQCM/ldWb511lA+SNkqXB2P6WXAgAM/7+jwsNHX2ia2Ikufm4SUEKMSv
+mti/nZkHDHsrHU4wb/2cOAywMELzv9EHzdcoenjBQP65OAc/1qWJs+LnBcCXfqKk
+aHjEZW6+brkHdRGLLY3YAHlt/AUL+RsKPJfN72i/FSpmu+52G36eeQ==
+-----END RSA PRIVATE KEY-----

+ 9 - 0
apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/public_key.pem

@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1zVmMhPqpSPMmYkKh5ww
+lRD5XuS8YWJKEM6tjFx61VK8qxHEYngkC2KnL5EuKAjQZIF3tJskwt0hAat047CC
+CZxrkNEpbVvSnvnk+A/8bg/Ww1n3qxzfifhsWfpUKlDnwrtH+ftt+5rZeEkf37XA
+Py7ZjzecAF9SDV6WSiPeAxUX2+hNdId42Pf45woo4LFGUlQeagCFkD/R0dpNIMGw
+cnkKCUikiBqr2ijSIgvRtBfZ9fBGjFGER2uE/Eay4AgcQsHue8skRwDCng8OnqtP
+nBtTytmqTy9V/BRgsVKUoksm6wsxkUYwgHeaq7UCvlCm25SZ7yRyd4k8t0BKDf2h
++wIDAQAB
+-----END PUBLIC KEY-----

+ 0 - 21
apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/root.crt

@@ -1,21 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDiDCCAnACCQCCsPcIlZO4TDANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
-Q0ExGTAXBgNVBAgMEEJyaXRpc2ggQ29sdW1iaWExDjAMBgNVBAcMBUNvbW94MRQw
-EgYDVQQKDAtUaGVCcmFpbi5jYTEUMBIGA1UEAwwLdGhlYnJhaW4uY2ExHzAdBgkq
-hkiG9w0BCQEWEGluZm9AdGhlYnJhaW4uY2EwHhcNMjEwMTEzMDkwNDIyWhcNMzEw
-MTExMDkwNDIyWjCBhTELMAkGA1UEBhMCQ0ExGTAXBgNVBAgMEEJyaXRpc2ggQ29s
-dW1iaWExDjAMBgNVBAcMBUNvbW94MRQwEgYDVQQKDAtUaGVCcmFpbi5jYTEUMBIG
-A1UEAwwLdGhlYnJhaW4uY2ExHzAdBgkqhkiG9w0BCQEWEGluZm9AdGhlYnJhaW4u
-Y2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2YWuwplM2Hc5tzBMu
-covW9nwZ8iNEFo5pbDc8710pmnkF+wsDztLy4afJe6OeVHyCgQxmE+rTZcoWbvoh
-pxW3Zy/8es4My07RKHqI3NYadThUvDsmI10cF3tJbhOZaIrMaExLGookZYKwbNAy
-7yJ1+MLyNCuFFsaOiNNxHOjH/InKSzEuGSLV68tdC7Pe+uanBcC7RKhOrjUC6Occ
-naHPC+a/YMyRYx29T8CfkCBB7N6WanWylFN/1RBmAgq++kDflSaF9k+Zdl6I4jiF
-mCPGS0k+AMre4PuAKOZOZOwhF0sWlXIxH6zPm9w0bSYdTLBupL846RTO72NtNP+X
-KX5DAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACXXFws+h+Zo9HsxW3BWpl2JU5u6
-KyfbLQt4kSN/gqltd4s84Q8c4z2jNdI0t8Oh5dXTjbLCpFjzuF2tdMtOWeYBCdsQ
-4NJ69RrwkFdsSPxDPhSE0WGXPaOBaA92wJjTkVf+UYIek1ozeyWwFm1LPiZVei00
-mwDVgbAbIEb8cf6OqJrl2r5PMBCLWBwwg5aca3fe6TopJhyPA//DZDRPA5xzKb9e
-PHUgF3apbcWxuxm8Mts4bAq8BcKoEvLHYWJ4fEWQvXPP7q1jYC3TkpSt5n3FQZTe
-nLyQ+RNzsEHzmyOtTSa0Q+5KVluO1TE3ifpv8737pTLdY8t2waBamoboCu8=
------END CERTIFICATE-----

+ 0 - 1
apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/root.srl

@@ -1 +0,0 @@
-BB275A0F903CFBBD

+ 19 - 0
apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server-cert.pem

@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDBDCCAeygAwIBAgIBAjANBgkqhkiG9w0BAQsFADA8MTowOAYDVQQDDDFNeVNR
+TF9TZXJ2ZXJfOC4wLjE5X0F1dG9fR2VuZXJhdGVkX0NBX0NlcnRpZmljYXRlMB4X
+DTIwMDYxMTAzMzg0NloXDTMwMDYwOTAzMzg0NlowQDE+MDwGA1UEAww1TXlTUUxf
+U2VydmVyXzguMC4xOV9BdXRvX0dlbmVyYXRlZF9TZXJ2ZXJfQ2VydGlmaWNhdGUw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCcEnEm5hqP1EbEJycOz8Ua
+NWp29QdpFUzTWhkKGhVXk+0msmNTw4NBAFB42moY44OU8wvDideOlJNhPRWveD8z
+G2lxzJA91p0UK4et8ia9MmeuCGhdC9jxJ8X69WNlUiPyy0hI/ZsqRq9Z0C2eW0iL
+JPXsy4X8Xpw3SFwoXf5pR9RFY5Pb2tuyxqmSestu2VXT/NQjJg4CVDR3mFcHPXZB
+4elRzH0WshExEGkgy0bg20MJeRc2Qdb5Xx+EakbmwroDWaCn3NSGqQ7jv6Vw0doy
+TGvS6h6RHBxnyqRfRgKGlCoOMG9/5+rFJC00QpCUG2vHXHWGoWlMlJ3foN7rj5v9
+AgMBAAGjDTALMAkGA1UdEwQCMAAwDQYJKoZIhvcNAQELBQADggEBAJ5zt2rj4Ag6
+zpN59AWC1Fur8g8l41ksHkSpKPp+PtyO/ngvbMqBpfmK1e7JCKZv/68QXfMyWWAI
+hwalqZkXXWHKjuz3wE7dE25PXFXtGJtcZAaj10xt98fzdqt8lQSwh2kbfNwZIz1F
+sgAStgE7+ZTcqTgvNB76Os1UK0to+/P0VBWktaVFdyub4Nc2SdPVnZNvrRBXBwOD
+3V8ViwywDOFoE7DvCvwx/SVsvoC0Z4j3AMMovO6oHicP7uU83qsQgm1Qru3YeoLR
++DoVi7IPHbWvN7MqFYn3YjNlByO2geblY7MR0BlqbFlmFrqLsUfjsh2ys7/U/knC
+dN/klu446fI=
+-----END CERTIFICATE-----

+ 27 - 0
apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server-key.pem

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAnBJxJuYaj9RGxCcnDs/FGjVqdvUHaRVM01oZChoVV5PtJrJj
+U8ODQQBQeNpqGOODlPMLw4nXjpSTYT0Vr3g/MxtpccyQPdadFCuHrfImvTJnrgho
+XQvY8SfF+vVjZVIj8stISP2bKkavWdAtnltIiyT17MuF/F6cN0hcKF3+aUfURWOT
+29rbssapknrLbtlV0/zUIyYOAlQ0d5hXBz12QeHpUcx9FrIRMRBpIMtG4NtDCXkX
+NkHW+V8fhGpG5sK6A1mgp9zUhqkO47+lcNHaMkxr0uoekRwcZ8qkX0YChpQqDjBv
+f+fqxSQtNEKQlBtrx1x1hqFpTJSd36De64+b/QIDAQABAoIBAFiah66Dt9SruLkn
+WR8piUaFyLlcBib8Nq9OWSTJBhDAJERxxb4KIvvGB+l0ZgNXNp5bFPSfzsZdRwZP
+PX5uj8Kd71Dxx3mz211WESMJdEC42u+MSmN4lGLkJ5t/sDwXU91E1vbJM0ve8THV
+4/Ag9qA4DX2vVZOeyqT/6YHpSsPNZplqzrbAiwrfHwkctHfgqwOf3QLfhmVQgfCS
+VwidBldEUv2whSIiIxh4Rv5St4kA68IBCbJxdpOpyuQBkk6CkxZ7VN9FqOuSd4Pk
+Wm7iWyBMZsCmELZh5XAXld4BEt87C5R4CvbPBDZxAv3THk1DNNvpy3PFQfwARRFb
+SAToYMECgYEAyL7U8yxpzHDYWd3oCx6vTi9p9N/z0FfAkWrRF6dm4UcSklNiT1Aq
+EOnTA+SaW8tV3E64gCWcY23gNP8so/ZseWj6L+peHwtchaP9+KB7yGw2A+05+lOx
+VetLTjAOmfpiUXFe5w1q4C1RGhLjZjjzW+GvwdAuchQgUEFaomrV+PUCgYEAxwfH
+cmVGFbAktcjU4HSRjKSfawCrut+3YUOLybyku3Q/hP9amG8qkVTFe95CTLjLe2D0
+ccaTTpofFEJ32COeck0g0Ujn/qQ+KXRoauOYs4FB1DtqMpqB78wufWEUpDpbd9/h
+J+gJdC/IADd4tJW9zA92g8IA7ZtFmqDtiSpQ0ekCgYAQGkaorvJZpN+l7cf0RGTZ
+h7IfI2vCVZer0n6tQA9fmLzjoe6r4AlPzAHSOR8sp9XeUy43kUzHKQQoHCPvjw/K
+eWJAP7OHF/k2+x2fOPhU7mEy1W+mJdp+wt4Kio5RSaVjVQ3AyPG+w8PSrJszEvRq
+dWMMz+851WV2KpfjmWBKlQKBgQC++4j4DZQV5aMkSKV1CIZOBf3vaIJhXKEUFQPD
+PmB4fBEjpwCg+zNGp6iktt65zi17o8qMjrb1mtCt2SY04eD932LZUHNFlwcLMmes
+Ad+aiDLJ24WJL1f16eDGcOyktlblDZB5gZ/ovJzXEGOkLXglosTfo77OQculmDy2
+/UL2WQKBgGeKasmGNfiYAcWio+KXgFkHXWtAXB9B91B1OFnCa40wx+qnl71MIWQH
+PQ/CZFNWOfGiNEJIZjrHsfNJoeXkhq48oKcT0AVCDYyLV0VxDO4ejT95mGW6njNd
+JpvmhwwAjOvuWVr0tn4iXlSK8irjlJHmwcRjLTJq97vE9fsA2MjI
+-----END RSA PRIVATE KEY-----

+ 0 - 21
apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server.crt

@@ -1,21 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDiDCCAnACCQCCsPcIlZO4TDANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
-Q0ExGTAXBgNVBAgMEEJyaXRpc2ggQ29sdW1iaWExDjAMBgNVBAcMBUNvbW94MRQw
-EgYDVQQKDAtUaGVCcmFpbi5jYTEUMBIGA1UEAwwLdGhlYnJhaW4uY2ExHzAdBgkq
-hkiG9w0BCQEWEGluZm9AdGhlYnJhaW4uY2EwHhcNMjEwMTEzMDkwNDIyWhcNMzEw
-MTExMDkwNDIyWjCBhTELMAkGA1UEBhMCQ0ExGTAXBgNVBAgMEEJyaXRpc2ggQ29s
-dW1iaWExDjAMBgNVBAcMBUNvbW94MRQwEgYDVQQKDAtUaGVCcmFpbi5jYTEUMBIG
-A1UEAwwLdGhlYnJhaW4uY2ExHzAdBgkqhkiG9w0BCQEWEGluZm9AdGhlYnJhaW4u
-Y2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2YWuwplM2Hc5tzBMu
-covW9nwZ8iNEFo5pbDc8710pmnkF+wsDztLy4afJe6OeVHyCgQxmE+rTZcoWbvoh
-pxW3Zy/8es4My07RKHqI3NYadThUvDsmI10cF3tJbhOZaIrMaExLGookZYKwbNAy
-7yJ1+MLyNCuFFsaOiNNxHOjH/InKSzEuGSLV68tdC7Pe+uanBcC7RKhOrjUC6Occ
-naHPC+a/YMyRYx29T8CfkCBB7N6WanWylFN/1RBmAgq++kDflSaF9k+Zdl6I4jiF
-mCPGS0k+AMre4PuAKOZOZOwhF0sWlXIxH6zPm9w0bSYdTLBupL846RTO72NtNP+X
-KX5DAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACXXFws+h+Zo9HsxW3BWpl2JU5u6
-KyfbLQt4kSN/gqltd4s84Q8c4z2jNdI0t8Oh5dXTjbLCpFjzuF2tdMtOWeYBCdsQ
-4NJ69RrwkFdsSPxDPhSE0WGXPaOBaA92wJjTkVf+UYIek1ozeyWwFm1LPiZVei00
-mwDVgbAbIEb8cf6OqJrl2r5PMBCLWBwwg5aca3fe6TopJhyPA//DZDRPA5xzKb9e
-PHUgF3apbcWxuxm8Mts4bAq8BcKoEvLHYWJ4fEWQvXPP7q1jYC3TkpSt5n3FQZTe
-nLyQ+RNzsEHzmyOtTSa0Q+5KVluO1TE3ifpv8737pTLdY8t2waBamoboCu8=
------END CERTIFICATE-----

+ 0 - 27
apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server.key

@@ -1,27 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEowIBAAKCAQEAtmFrsKZTNh3ObcwTLnKL1vZ8GfIjRBaOaWw3PO9dKZp5BfsL
-A87S8uGnyXujnlR8goEMZhPq02XKFm76IacVt2cv/HrODMtO0Sh6iNzWGnU4VLw7
-JiNdHBd7SW4TmWiKzGhMSxqKJGWCsGzQMu8idfjC8jQrhRbGjojTcRzox/yJyksx
-Lhki1evLXQuz3vrmpwXAu0SoTq41AujnHJ2hzwvmv2DMkWMdvU/An5AgQezelmp1
-spRTf9UQZgIKvvpA35UmhfZPmXZeiOI4hZgjxktJPgDK3uD7gCjmTmTsIRdLFpVy
-MR+sz5vcNG0mHUywbqS/OOkUzu9jbTT/lyl+QwIDAQABAoIBAA6UVR6G/UnrMhBW
-6wWghItHov4T/Du6LeJBk1zcqa7kuV4ABo5kXzqpTVdu+dJzYIyyMkKKvw/tKC2I
-65f7GmJR7mUZkBU3v3I68Si1tqvgyQMFFRlkZFIVknZ5RTnTQJ08jTTHx1lHgB4I
-ZNBdi3ywySzBfOUjv/Wu/HAjZnxuEh2guBpRMZdwQwZLXr2koDa5inL3IwJrA4Ir
-QzpZ0y6ql3A0tw7jAw36G1AKyyz74aFwJ0I8U8w+2Uk4iX5hcKGA8mFq4lyO4/3+
-7W2Z4V8cQzwMq2SMixI0Omxlc2BJUi9j17Ey//5dAXyPaG8QI1kzeL/3Gbs8YBMq
-ekN8AZECgYEA5YxcFIVv3yO+ARNWUHovrsMuf9ElhyRuZd0I2+vjrq1b9zQsSy2d
-PsyYWD17lO/GDmpTzZOdVsYtZHi+EiXmQnkzLJ4m2nlc7W4annWlbzlQMEn6vAji
-l9bSHJXXiiIB7X/oHpDUdsnJp/uyAJppmnVLbSBboNCrG4Mf5cJqOnsCgYEAy2We
-scp19h4UEKAU0Yh+5jh8W4VVtlISkH64vMgz/JZWXMPt1bM5C/5j+3UVUL5VmFqF
-J1g0gXYkTGTL0+entb3SUiL42zrp3rZ3GgMU6V+aktq3dmri5bOifzihuLHLgjO5
-u/MJPBzvFxIiJxnNBybNLijIZfPm+9roUfpcBNkCgYBGE3Zc0WuYnEm5/FRCVzrN
-SEqevJOPUSDeuf6lXLryLXxA2E2ZWcCCVmU/su1SR2yYI/+XZ7QFtJRQ8sdbtPQ5
-YNStj05fLeOfnBhGPbYWYVHInB0OYEwEfJFCJsBZLA6YmY6cHiyuYuXMAXuS0ZDh
-lWNEWjd+vZUu3fXT52kUlwKBgDgq/eH3GRA4Si41JsqeOPz2iFD1xy+sBnhkpjtr
-xf9wvLStXpZvAcfwHkgokxRTG2wRQ0gUMZu2tltqUmdYR5YGr3gDNFnGMSNRnB5Q
-z4uK3TLEt3k6FyJ7stoTF4Xbg2mXQylF+jzheJ0UYt4NX/MjofGnTX/qFNVkJFfP
-HW4xAoGBAMBb9cXTpzOMiMcSdQRlaLttV1p05pqxTgQNEQD8HB+lkx4AGnnHvtxW
-XQJvPumtqdCEpfe4kaqLip8T+67sGfcDVQMogJc/tpvZ0AN4FuViFsf/YDuTPXEp
-whMldPHtusbRP2fk/JFq4Ak0Xz2wAI1iMD3qfBeW6eJpvRllUo69
------END RSA PRIVATE KEY-----

+ 14 - 0
apps/emqx_auth_redis/etc/emqx_auth_redis.conf

@@ -115,3 +115,17 @@ auth.redis.acl_cmd = "HGETALL mqtt_acl:%u"
 ## Value: File
 # auth.redis.ssl.keyfile = path/to/your/keyfile
 
+## In mode verify_none the default behavior is to allow all x509-path
+## validation errors.
+##
+## Value: true | false
+#auth.redis.ssl.verify = false
+
+## If not specified, the server's names returned in server's certificate is validated against
+## what's provided `auth.redis.server` config's host part.
+## Setting to 'disable' will make EMQ X ignore unmatched server names.
+## If set with a host name, the server's names returned in server's certificate is validated
+## against this value.
+##
+## Value: String | disable
+## auth.redis.ssl.server_name_indication = disable

+ 37 - 2
apps/emqx_auth_redis/priv/emqx_auth_redis.schema

@@ -50,6 +50,30 @@
   {datatype, string}
 ]}.
 
+{mapping, "auth.redis.ssl.verify", "emqx_auth_redis.options", [
+  {default, false},
+  {datatype, {enum, [true, false]}}
+]}.
+
+{mapping, "auth.redis.ssl.server_name_indication", "emqx_auth_redis.options", [
+  {datatype, string}
+]}.
+
+%% FIXME: compatible with 4.0-4.2 version format, plan to delete in 5.0
+{mapping, "auth.redis.cafile", "emqx_auth_redis.options", [
+  {datatype, string}
+]}.
+
+%% FIXME: compatible with 4.0-4.2 version format, plan to delete in 5.0
+{mapping, "auth.redis.certfile", "emqx_auth_redis.options", [
+  {datatype, string}
+]}.
+
+%% FIXME: compatible with 4.0-4.2 version format, plan to delete in 5.0
+{mapping, "auth.redis.keyfile", "emqx_auth_redis.options", [
+  {datatype, string}
+]}.
+
 {translation, "emqx_auth_redis.options", fun(Conf) ->
    Ssl = cuttlefish:conf_get("auth.redis.ssl.enable", Conf, false),
    Filter  = fun(Opts) -> [{K, V} || {K, V} <- Opts, V =/= undefined] end,
@@ -58,7 +82,7 @@
            %% FIXME: compatible with 4.0-4.2 version format, plan to delete in 5.0
            CA = cuttlefish:conf_get(
                     "auth.redis.ssl.cacertfile", Conf,
-                    cuttlefish:conf_get("auth.redis.cacertfile", Conf, undefined)
+                    cuttlefish:conf_get("auth.redis.cafile", Conf, undefined)
                 ),
            Cert = cuttlefish:conf_get(
                     "auth.redis.ssl.certfile", Conf,
@@ -68,10 +92,21 @@
                     "auth.redis.ssl.keyfile", Conf,
                     cuttlefish:conf_get("auth.redis.keyfile", Conf, undefined)
                  ),
+           Verify = case cuttlefish:conf_get("auth.redis.ssl.verify", Conf, false) of
+                         true -> verify_peer;
+                         flase -> verify_none
+                     end,
+           SNI = case cuttlefish:conf_get("auth.redis.ssl.server_name_indication", Conf, undefined) of
+                   "disable" -> disable;
+                   SNI0 -> SNI0
+                 end,
            [{options, [{ssl_options,
                         Filter([{cacertfile, CA},
                                 {certfile, Cert},
-                                {keyfile, Key}])
+                                {keyfile, Key},
+                                {verify, Verify},
+                                {server_name_indication, SNI}
+                               ])
                        }]}];
        _ -> [{options, []}]
    end

+ 4 - 9
apps/emqx_auth_redis/test/emqx_auth_redis_SUITE.erl

@@ -49,22 +49,18 @@ all() ->
     emqx_ct:all(?MODULE).
 
 init_per_suite(Cfg) ->
-    emqx_ct_helpers:start_apps([emqx_modules, emqx_auth_redis], fun set_special_configs/1),
+    emqx_ct_helpers:start_apps([emqx_auth_redis], fun set_special_configs/1),
     init_redis_rows(),
     Cfg.
 
 end_per_suite(_Cfg) ->
     deinit_redis_rows(),
-    emqx_ct_helpers:stop_apps([emqx_auth_redis, emqx_modules]).
+    emqx_ct_helpers:stop_apps([emqx_auth_redis]).
 
 set_special_configs(emqx) ->
     application:set_env(emqx, allow_anonymous, false),
     application:set_env(emqx, acl_nomatch, deny),
-    application:set_env(emqx, acl_file,
-                        emqx_ct_helpers:deps_path(emqx, "test/emqx_SUITE_data/acl.conf")),
-    application:set_env(emqx, enable_acl_cache, false),
-    application:set_env(emqx, plugins_loaded_file,
-                        emqx_ct_helpers:deps_path(emqx, "test/emqx_SUITE_data/loaded_plugins"));
+    application:set_env(emqx, enable_acl_cache, false);
 set_special_configs(_App) ->
     ok.
 
@@ -72,7 +68,6 @@ init_redis_rows() ->
     %% Users
     [q(["HMSET", Key|FiledValue]) || {Key, FiledValue} <- ?INIT_AUTH],
     %% ACLs
-    emqx_modules:load_module(emqx_mod_acl_internal, false),
     Result = [q(["HSET", Key, Filed, Value]) || {Key, Filed, Value} <- ?INIT_ACL],
     ct:pal("redis init result: ~p~n", [Result]).
 
@@ -136,7 +131,7 @@ t_check_acl(_) ->
     allow = emqx_access_control:check_acl(User2, subscribe, <<"topic2">>),
     allow = emqx_access_control:check_acl(User3, publish, <<"topic3">>),
     allow = emqx_access_control:check_acl(User3, subscribe, <<"topic3">>),
-    allow = emqx_access_control:check_acl(User4, publish, <<"a/b/c">>).
+    deny = emqx_access_control:check_acl(User4, publish, <<"a/b/c">>).
 
 t_acl_super(_) ->
     reload([{password_hash, plain}]),

+ 1 - 2
apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt_actions.erl

@@ -567,8 +567,7 @@ options(Options, PoolName, ResId) ->
 maybe_ssl(_Options, false, _ResId) ->
     [];
 maybe_ssl(Options, true, ResId) ->
-    Dir = filename:join([emqx:get_env(data_dir), "rule", ResId]),
-    [{ssl, true}, {ssl_opts, emqx_plugin_libs_ssl:save_files_return_opts(Options, Dir)}].
+    [{ssl, true}, {ssl_opts, emqx_plugin_libs_ssl:save_files_return_opts(Options, "rules", ResId)}].
 
 mqtt_ver(ProtoVer) ->
     case ProtoVer of

+ 7 - 5
apps/emqx_coap/README.md

@@ -151,8 +151,9 @@ To subscribe any topic, issue following command:
 - if clientid is absent, a "bad_request" will be returned.
 - {topicname} in URI should be percent-encoded to prevent special characters, such as + and #.
 - {username} and {password} are optional.
-- if {username} and {password} are not correct, an uauthorized error will be returned.
+- if {username} or {password} is incorrect, the error code `uauthorized` will be returned.
 - topic is subscribed with qos1.
+- if the subscription failed due to ACL deny, the error code `forbidden` will be returned.
 
 CoAP Client Unobserve Operation (unsubscribe topic)
 ---------------------------------------------------
@@ -168,7 +169,7 @@ To cancel observation, issue following command:
 - if clientid is absent, a "bad_request" will be returned.
 - {topicname} in URI should be percent-encoded to prevent special characters, such as + and #.
 - {username} and {password} are optional.
-- if {username} and {password} are not correct, an uauthorized error will be returned.
+- if {username} or {password} is incorrect, the error code `uauthorized` will be returned.
 
 CoAP Client Notification Operation (subscribed Message)
 -------------------------------------------------------
@@ -179,7 +180,7 @@ Server will issue an observe-notification as a subscribed message.
 
 CoAP Client Publish Operation
 -----------------------------
-Issue a coap put command to do publishment. For example:
+Issue a coap put command to publish messages. For example:
 
 ```
   PUT  coap://localhost/mqtt/{topicname}?c={clientid}&u={username}&p={password}
@@ -191,10 +192,11 @@ Issue a coap put command to do publishment. For example:
 - if clientid is absent, a "bad_request" will be returned.
 - {topicname} in URI should be percent-encoded to prevent special characters, such as + and #.
 - {username} and {password} are optional.
-- if {username} and {password} are not correct, an uauthorized error will be returned.
+- if {username} or {password} is incorrect, the error code `uauthorized` will be returned.
 - payload could be any binary data.
 - payload data type is "application/octet-stream".
 - publish message will be sent with qos0.
+- if the publishing failed due to ACL deny, the error code `forbidden` will be returned.
 
 CoAP Client Keep Alive
 ----------------------
@@ -209,7 +211,7 @@ Device should issue a get command periodically, serve as a ping to keep mqtt ses
 - {any_topicname} is optional, and should be percent-encoded to prevent special characters.
 - {clientid} is mandatory. If clientid is absent, a "bad_request" will be returned.
 - {username} and {password} are optional.
-- if {username} and {password} are not correct, an uauthorized error will be returned.
+- if {username} or {password} is incorrect, the error code `uauthorized` will be returned.
 - coap client should do keepalive work periodically to keep mqtt session online, especially those devices in a NAT network.
 
 

+ 1 - 1
apps/emqx_coap/TODO

@@ -2,7 +2,7 @@
     - Enhance all test case
 
 2. Remove the mqtt adaptor
-3. Remove the emqx_coap_ps_topics.erl
+3. Remove the emqx_coap_pubsub_topics.erl
 
 
 ### Problems

+ 3 - 3
apps/emqx_coap/src/emqx_coap_app.erl

@@ -29,12 +29,12 @@
 start(_Type, _Args) ->
     {ok, Sup} = emqx_coap_sup:start_link(),
     coap_server_registry:add_handler([<<"mqtt">>], emqx_coap_resource, undefined),
-    coap_server_registry:add_handler([<<"ps">>], emqx_coap_ps_resource, undefined),
-    _ = emqx_coap_ps_topics:start_link(),
+    coap_server_registry:add_handler([<<"ps">>], emqx_coap_pubsub_resource, undefined),
+    _ = emqx_coap_pubsub_topics:start_link(),
     emqx_coap_server:start(application:get_all_env(?APP)),
     {ok,Sup}.
 
 stop(_State) ->
     coap_server_registry:remove_handler([<<"mqtt">>], emqx_coap_resource, undefined),
-    coap_server_registry:remove_handler([<<"ps">>], emqx_coap_ps_resource, undefined),
+    coap_server_registry:remove_handler([<<"ps">>], emqx_coap_pubsub_resource, undefined),
     emqx_coap_server:stop(application:get_all_env(?APP)).

+ 0 - 0
apps/emqx_coap/src/emqx_coap_mqtt_adapter.erl


Некоторые файлы не были показаны из-за большого количества измененных файлов