chore(project): merge code from master

This commit is contained in:
Shawn 2021-07-07 19:15:35 +08:00
commit 477097c062
554 changed files with 12398 additions and 28097 deletions

View File

@ -39,7 +39,7 @@ emqx_test(){
unzip -q "${PACKAGE_PATH}/${packagename}"
export EMQX_ZONE__EXTERNAL__SERVER_KEEPALIVE=60 \
EMQX_MQTT__MAX_TOPIC_ALIAS=10
sed -i '/emqx_telemetry/d' "${PACKAGE_PATH}"/emqx/data/loaded_plugins
# sed -i '/emqx_telemetry/d' "${PACKAGE_PATH}"/emqx/data/loaded_plugins
echo "running ${packagename} start"
if ! "${PACKAGE_PATH}"/emqx/bin/emqx start; then
@ -115,7 +115,7 @@ emqx_test(){
running_test(){
export EMQX_ZONE__EXTERNAL__SERVER_KEEPALIVE=60 \
EMQX_MQTT__MAX_TOPIC_ALIAS=10
sed -i '/emqx_telemetry/d' /var/lib/emqx/loaded_plugins
# sed -i '/emqx_telemetry/d' /var/lib/emqx/loaded_plugins
if ! emqx start; then
cat /var/log/emqx/erlang.log.1 || true

View File

@ -38,7 +38,7 @@ services:
- -c
- |
sed -i "s 127.0.0.1 $$(ip route show |grep "link" |awk '{print $$1}') g" /opt/emqx/etc/acl.conf
sed -i '/emqx_telemetry/d' /opt/emqx/data/loaded_plugins
# sed -i '/emqx_telemetry/d' /opt/emqx/data/loaded_plugins
/opt/emqx/bin/emqx foreground
healthcheck:
test: ["CMD", "/opt/emqx/bin/emqx_ctl", "status"]
@ -62,7 +62,7 @@ services:
- -c
- |
sed -i "s 127.0.0.1 $$(ip route show |grep "link" |awk '{print $$1}') g" /opt/emqx/etc/acl.conf
sed -i '/emqx_telemetry/d' /opt/emqx/data/loaded_plugins
# sed -i '/emqx_telemetry/d' /opt/emqx/data/loaded_plugins
/opt/emqx/bin/emqx foreground
healthcheck:
test: ["CMD", "/opt/emqx/bin/emqx", "ping"]

View File

@ -0,0 +1,81 @@
version: "3"
services:
mongo1:
hostname: mongo1
container_name: mongo1
image: mongo:${MONGO_TAG}
environment:
MONGO_INITDB_DATABASE: mqtt
networks:
- emqx_bridge
expose:
- 27017
ports:
- 27011:27017
restart: always
command:
--ipv6
--bind_ip_all
--replSet rs0
mongo2:
hostname: mongo2
container_name: mongo2
image: mongo:${MONGO_TAG}
environment:
MONGO_INITDB_DATABASE: mqtt
networks:
- emqx_bridge
expose:
- 27017
ports:
- 27012:27017
restart: always
command:
--ipv6
--bind_ip_all
--replSet rs0
mongo3:
hostname: mongo3
container_name: mongo3
image: mongo:${MONGO_TAG}
environment:
MONGO_INITDB_DATABASE: mqtt
networks:
- emqx_bridge
expose:
- 27017
ports:
- 27013:27017
restart: always
command:
--ipv6
--bind_ip_all
--replSet rs0
mongo_client:
image: mongo:${MONGO_TAG}
container_name: mongo_client
networks:
- emqx_bridge
depends_on:
- mongo1
- mongo2
- mongo3
command:
- /bin/bash
- -c
- |
while ! mongo --host mongo1 --eval 'db.runCommand("ping").ok' --quiet > /dev/null 2>&1; do
sleep 1
done
while ! mongo --host mongo2 --eval 'db.runCommand("ping").ok' --quiet > /dev/null 2>&1; do
sleep 1
done
while ! mongo --host mongo3 --eval 'db.runCommand("ping").ok' --quiet > /dev/null 2>&1; do
sleep 1
done
mongo --host mongo1 --eval "rs.initiate( { _id : 'rs0', members: [ { _id : 0, host : 'mongo1:27017' }, { _id : 1, host : 'mongo2:27017' }, { _id : 2, host : 'mongo3:27017' } ] })" --quiet
mongo --host mongo1 --eval "rs.status()" --quiet

View File

@ -0,0 +1,98 @@
version: "3"
services:
mongo1:
hostname: mongo1
container_name: mongo1
image: mongo:${MONGO_TAG}
environment:
MONGO_INITDB_DATABASE: mqtt
networks:
- emqx_bridge
expose:
- 27017
ports:
- 27011:27017
restart: always
volumes:
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/cert.pem
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/key.pem
command:
- /bin/bash
- -c
- |
cat /etc/certs/key.pem /etc/certs/cert.pem > /etc/certs/mongodb.pem
mongod --ipv6 --bind_ip_all --tlsMode requireTLS --tlsCertificateKeyFile /etc/certs/mongodb.pem --replSet rs0
mongo2:
hostname: mongo2
container_name: mongo2
image: mongo:${MONGO_TAG}
environment:
MONGO_INITDB_DATABASE: mqtt
networks:
- emqx_bridge
expose:
- 27017
ports:
- 27012:27017
restart: always
volumes:
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/cert.pem
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/key.pem
command:
- /bin/bash
- -c
- |
cat /etc/certs/key.pem /etc/certs/cert.pem > /etc/certs/mongodb.pem
mongod --ipv6 --bind_ip_all --tlsMode requireTLS --tlsCertificateKeyFile /etc/certs/mongodb.pem --replSet rs0
mongo3:
hostname: mongo3
container_name: mongo3
image: mongo:${MONGO_TAG}
environment:
MONGO_INITDB_DATABASE: mqtt
networks:
- emqx_bridge
expose:
- 27017
ports:
- 27013:27017
restart: always
volumes:
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/cert.pem
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/key.pem
command:
- /bin/bash
- -c
- |
cat /etc/certs/key.pem /etc/certs/cert.pem > /etc/certs/mongodb.pem
mongod --ipv6 --bind_ip_all --tlsMode requireTLS --tlsCertificateKeyFile /etc/certs/mongodb.pem --replSet rs0
mongo_client:
image: mongo:${MONGO_TAG}
container_name: mongo_client
networks:
- emqx_bridge
depends_on:
- mongo1
- mongo2
- mongo3
volumes:
- ../../apps/emqx/etc/certs/cacert.pem:/etc/certs/cacert.pem
command:
- /bin/bash
- -c
- |
while ! mongo --host mongo1 --tls --tlsCAFile /etc/certs/cacert.pem --tlsAllowInvalidHostnames --eval 'db.runCommand("ping").ok' --quiet > /dev/null 2>&1; do
sleep 1
done
while ! mongo --host mongo2 --tls --tlsCAFile /etc/certs/cacert.pem --tlsAllowInvalidHostnames --eval 'db.runCommand("ping").ok' --quiet > /dev/null 2>&1; do
sleep 1
done
while ! mongo --host mongo3 --tls --tlsCAFile /etc/certs/cacert.pem --tlsAllowInvalidHostnames --eval 'db.runCommand("ping").ok' --quiet > /dev/null 2>&1; do
sleep 1
done
mongo --host mongo1 --tls --tlsCAFile /etc/certs/cacert.pem --tlsAllowInvalidHostnames --eval "rs.initiate( { _id : 'rs0', members: [ { _id : 0, host : 'mongo1:27017' }, { _id : 1, host : 'mongo2:27017' }, { _id : 2, host : 'mongo3:27017' } ] })" --quiet
mongo --host mongo1 --tls --tlsCAFile /etc/certs/cacert.pem --tlsAllowInvalidHostnames --eval "rs.status()" --quiet

View File

@ -9,6 +9,8 @@ services:
MONGO_INITDB_DATABASE: mqtt
networks:
- emqx_bridge
ports:
- "27017:27017"
command:
--ipv6
--bind_ip_all

View File

@ -0,0 +1,23 @@
version: '3.9'
services:
mongo_server:
container_name: mongo
image: mongo:${MONGO_TAG}
restart: always
environment:
MONGO_INITDB_DATABASE: mqtt
volumes:
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/cert.pem
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/key.pem
networks:
- emqx_bridge
ports:
- "27017:27017"
command:
- /bin/bash
- -c
- |
cat /etc/certs/key.pem /etc/certs/cert.pem > /etc/certs/mongodb.pem
mongod --ipv6 --bind_ip_all --sslMode requireSSL --sslPEMKeyFile /etc/certs/mongodb.pem

View File

@ -1,18 +0,0 @@
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

View File

@ -11,9 +11,11 @@ services:
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
- ../../apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data/server-key.pem:/etc/certs/server-key.pem
- ../../apps/emqx/etc/certs/cacert.pem:/etc/certs/ca-cert.pem
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/server-cert.pem
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/server-key.pem
ports:
- "3306:3306"
networks:
- emqx_bridge
command:

View File

@ -5,7 +5,9 @@ services:
container_name: redis
image: redis:${REDIS_TAG}
volumes:
- ../../apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs:/tls
- ../../apps/emqx/etc/certs/cacert.pem:/etc/certs/ca.crt
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/redis.crt
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/redis.key
- ./redis/:/data/conf
command: bash -c "/bin/bash /data/conf/redis.sh --node cluster --tls-enabled && tail -f /var/log/redis-server.log"
networks:

View File

@ -5,7 +5,9 @@ services:
container_name: redis
image: redis:${REDIS_TAG}
volumes:
- ../../apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs:/tls
- ../../apps/emqx/etc/certs/cacert.pem:/etc/certs/ca.crt
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/redis.crt
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/redis.key
- ./redis/:/data/conf
command: bash -c "/bin/bash /data/conf/redis.sh --node sentinel --tls-enabled && tail -f /var/log/redis-server.log"
networks:

View File

@ -5,15 +5,17 @@ services:
container_name: redis
image: redis:${REDIS_TAG}
volumes:
- ../../apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs:/tls
- ../../apps/emqx/etc/certs/cacert.pem:/etc/certs/ca.crt
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/redis.crt
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/redis.key
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
- --tls-cert-file /etc/certs/redis.crt
- --tls-key-file /etc/certs/redis.key
- --tls-ca-cert-file /etc/certs/ca.crt
restart: always
networks:
- emqx_bridge

View File

@ -2,9 +2,9 @@ ARG BUILD_FROM=postgres:11
FROM ${BUILD_FROM}
ARG POSTGRES_USER=postgres
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
COPY --chown=$POSTGRES_USER apps/emqx/etc/certs/key.pem /var/lib/postgresql/server.key
COPY --chown=$POSTGRES_USER apps/emqx/etc/certs/cert.pem /var/lib/postgresql/server.crt
COPY --chown=$POSTGRES_USER apps/emqx/etc/certs/cacert.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

View File

@ -1,9 +1,9 @@
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-cert-file /etc/certs/redis.crt
tls-key-file /etc/certs/redis.key
tls-ca-cert-file /etc/certs/ca.crt
tls-replication yes
tls-cluster yes
protected-mode no

View File

@ -91,7 +91,7 @@ do
fi
if [ "${node}" = "cluster" ] ; then
if $tls ; then
yes "yes" | redis-cli --cluster create "$LOCAL_IP:8000" "$LOCAL_IP:8001" "$LOCAL_IP:8002" --pass public --no-auth-warning --tls true --cacert /tls/ca.crt --cert /tls/redis.crt --key /tls/redis.key;
yes "yes" | redis-cli --cluster create "$LOCAL_IP:8000" "$LOCAL_IP:8001" "$LOCAL_IP:8002" --pass public --no-auth-warning --tls true --cacert /etc/certs/ca.crt --cert /etc/certs/redis.crt --key /etc/certs/redis.key;
else
yes "yes" | redis-cli --cluster create "$LOCAL_IP:7000" "$LOCAL_IP:7001" "$LOCAL_IP:7002" --pass public --no-auth-warning;
fi
@ -107,9 +107,9 @@ EOF
cat >>/_sentinel.conf<<EOF
tls-port 26380
tls-replication yes
tls-cert-file /tls/redis.crt
tls-key-file /tls/redis.key
tls-ca-cert-file /tls/ca.crt
tls-cert-file /etc/certs/redis.crt
tls-key-file /etc/certs/redis.key
tls-ca-cert-file /etc/certs/ca.crt
sentinel monitor mymaster $LOCAL_IP 8000 1
EOF
else

View File

@ -1,7 +1,7 @@
{erl_opts, [debug_info]}.
{deps,
[
{minirest, {git, "https://github.com/emqx/minirest.git", {tag, "0.3.5"}}}
{minirest, {git, "https://github.com/emqx/minirest.git", {tag, "0.3.6"}}}
]}.
{shell, [

View File

@ -43,7 +43,7 @@
!sed -i '/emqx_telemetry/d' data/loaded_plugins
!./bin/emqx start
?EMQ X (.*) is started successfully!
?EMQ X .* is started successfully!
?SH-PROMPT
!./bin/emqx_ctl cluster join emqx@127.0.0.1
@ -99,6 +99,10 @@
"""
?SH-PROMPT
!./bin/emqx_ctl plugins list | grep emqx_management
?Plugin\(emqx_management.*active=true\)
?SH-PROMPT
[shell emqx2]
!echo "" > log/emqx.log.1
?SH-PROMPT
@ -120,6 +124,10 @@
"""
?SH-PROMPT
!./bin/emqx_ctl plugins list | grep emqx_management
?Plugin\(emqx_management.*active=true\)
?SH-PROMPT
[shell bench]
???publish complete
??SH-PROMPT:

View File

@ -3,7 +3,6 @@ name: Bug Report
about: Create a report to help us improve
title: ''
labels: Support
assignees: tigercl
---

View File

@ -3,7 +3,6 @@ name: Feature Request
about: Suggest an idea for this project
title: ''
labels: Feature
assignees: tigercl
---

View File

@ -3,7 +3,6 @@ name: Support Needed
about: Asking a question about usages, docs or anything you're insterested in
title: ''
labels: Support
assignees: tigercl
---

View File

@ -83,6 +83,7 @@ jobs:
- name: build
env:
PYTHON: python
DIAGNOSTIC: 1
run: |
$env:PATH = "${{ steps.install_erlang.outputs.erlpath }}\bin;$env:PATH"
@ -168,19 +169,21 @@ jobs:
- name: build
run: |
. $HOME/.kerl/${{ matrix.erl_otp }}/activate
make -C source ensure-rebar3
sudo cp source/rebar3 /usr/local/bin/rebar3
make -C source ${{ matrix.profile }}-zip
cd source
make ensure-rebar3
sudo cp rebar3 /usr/local/bin/rebar3
rm -rf _build/${{ matrix.profile }}/lib
make ${{ matrix.profile }}-zip
- name: test
run: |
cd source
pkg_name=$(basename _packages/${{ matrix.profile }}/${{ matrix.profile }}-*.zip)
unzip -q _packages/${{ matrix.profile }}/$pkg_name
gsed -i '/emqx_telemetry/d' ./emqx/data/loaded_plugins
# gsed -i '/emqx_telemetry/d' ./emqx/data/loaded_plugins
./emqx/bin/emqx start || cat emqx/log/erlang.log.1
ready='no'
for i in {1..10}; do
if curl -fs 127.0.0.1:18083 > /dev/null; then
if curl -fs 127.0.0.1:8081/status > /dev/null; then
ready='yes'
break
fi
@ -339,13 +342,6 @@ jobs:
- [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/download-artifact@v2

View File

@ -38,6 +38,11 @@ jobs:
run: make ${EMQX_NAME}-zip
- name: build deb/rpm packages
run: make ${EMQX_NAME}-pkg
- uses: actions/upload-artifact@v1
if: failure()
with:
name: rebar3.crashdump
path: ./rebar3.crashdump
- name: pakcages test
run: |
export CODE_PATH=$GITHUB_WORKSPACE
@ -94,15 +99,20 @@ jobs:
make ensure-rebar3
sudo cp rebar3 /usr/local/bin/rebar3
make ${EMQX_NAME}-zip
- uses: actions/upload-artifact@v1
if: failure()
with:
name: rebar3.crashdump
path: ./rebar3.crashdump
- name: test
run: |
pkg_name=$(basename _packages/${EMQX_NAME}/emqx-*.zip)
unzip -q _packages/${EMQX_NAME}/$pkg_name
gsed -i '/emqx_telemetry/d' ./emqx/data/loaded_plugins
# gsed -i '/emqx_telemetry/d' ./emqx/data/loaded_plugins
./emqx/bin/emqx start || cat emqx/log/erlang.log.1
ready='no'
for i in {1..10}; do
if curl -fs 127.0.0.1:18083 > /dev/null; then
if curl -fs 127.0.0.1:8081/status > /dev/null; then
ready='yes'
break
fi

View File

@ -1,407 +0,0 @@
name: Compatibility Test Suite
on:
push:
tags:
- v*
- e*
pull_request:
jobs:
ldap:
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
ldap_tag:
- 2.4.50
network_type:
- ipv4
- ipv6
steps:
- uses: actions/checkout@v1
- name: docker compose up
env:
LDAP_TAG: ${{ matrix.ldap_tag }}
run: |
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: |
echo EMQX_AUTH__LDAP__SERVERS=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ldap) >> "$GITHUB_ENV"
- name: setup
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_
export HOCON_ENV_OVERRIDE_PREFIX=EMQX_
printenv > .env
docker exec -i erlang sh -c "make ensure-rebar3"
docker exec -i erlang sh -c "./rebar3 eunit --application=emqx_auth_ldap"
docker exec --env-file .env -i erlang sh -c "make apps/emqx_auth_ldap-ct"
- uses: actions/upload-artifact@v1
if: failure()
with:
name: logs_ldap${{ matrix.ldap_tag }}_${{ matrix.network_type }}
path: _build/test/logs
mongo:
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
mongo_tag:
- 3
- 4
network_type:
- ipv4
- ipv6
connect_type:
- tls
- tcp
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: |
cat <<-EOF >> "$GITHUB_ENV"
EMQX_AUTH__MONGO__SSL__ENABLE=on
EMQX_AUTH__MONGO__SSL__CACERTFILE=/emqx/apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE_data/ca.pem
EMQX_AUTH__MONGO__SSL__CERTFILE=/emqx/apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE_data/client-cert.pem
EMQX_AUTH__MONGO__SSL__KEYFILE=/emqx/apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE_data/client-key.pem
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: |
echo EMQX_AUTH__MONGO__SSL__ENABLE=off >> "$GITHUB_ENV"
- name: setup
if: matrix.network_type == 'ipv4'
run: |
echo "EMQX_AUTH__MONGO__SERVER=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mongo):27017" >> "$GITHUB_ENV"
- name: setup
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_
export HOCON_ENV_OVERRIDE_PREFIX=EMQX_
printenv > .env
docker exec -i erlang sh -c "make ensure-rebar3"
docker exec -i erlang sh -c "./rebar3 eunit --application=emqx_auth_mongo"
docker exec --env-file .env -i erlang sh -c "make apps/emqx_auth_mongo-ct"
- uses: actions/upload-artifact@v1
if: failure()
with:
name: logs_mongo${{ matrix.mongo_tag }}_${{ matrix.network_type }}_${{ matrix.connect_type }}
path: _build/test/logs
mysql:
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
mysql_tag:
- 5.7
- 8
network_type:
- ipv4
- ipv6
connect_type:
- tls
- tcp
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: |
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: |
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: |
echo "EMQX_AUTH__MYSQL__SERVER=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mysql):3306" >> "$GITHUB_ENV"
- name: setup
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 CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_
export HOCON_ENV_OVERRIDE_PREFIX=EMQX_
printenv > .env
docker exec -i erlang sh -c "make ensure-rebar3"
docker exec -i erlang sh -c "./rebar3 eunit --application=emqx_auth_mysql"
docker exec --env-file .env -i erlang sh -c "make apps/emqx_auth_mysql-ct"
- uses: actions/upload-artifact@v1
if: failure()
with:
name: logs_mysql${{ matrix.mysql_tag }}_${{ matrix.network_type }}_${{ matrix.connect_type }}
path: _build/test/logs
pgsql:
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
pgsql_tag:
- 9
- 10
- 11
- 12
- 13
network_type:
- ipv4
- ipv6
connect_type:
- tls
- 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: |
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/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: |
echo EMQX_AUTH__PGSQL__SSL__ENABLE=off >> "$GITHUB_ENV"
- name: setup
if: matrix.network_type == 'ipv4'
run: |
echo "EMQX_AUTH__PGSQL__SERVER=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' pgsql):5432" >> "$GITHUB_ENV"
- name: setup
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 \
EMQX_AUTH__PGSQL__PASSWORD=public \
EMQX_AUTH__PGSQL__DATABASE=mqtt \
CUTTLEFISH_ENV_OVERRIDE_PREFIX=EMQX_ \
HOCON_ENV_OVERRIDE_PREFIX=EMQX_
printenv > .env
docker exec -i erlang sh -c "make ensure-rebar3"
docker exec -i erlang sh -c "./rebar3 eunit --application=emqx_auth_pgsql"
docker exec --env-file .env -i erlang sh -c "make apps/emqx_auth_pgsql-ct"
- uses: actions/upload-artifact@v1
if: failure()
with:
name: logs_pgsql${{ matrix.pgsql_tag }}_${{ matrix.network_type }}_${{ matrix.connect_type }}
path: _build/test/logs
redis:
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
redis_tag:
- 5
- 6
network_type:
- ipv4
- ipv6
connect_type:
- tls
- tcp
node_type:
- single
- sentinel
- 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'
run: |
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: |
echo EMQX_AUTH__REDIS__SSL__ENABLE=off >> "$GITHUB_ENV"
- name: get server address
run: |
ipv4_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' redis)
ipv6_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.GlobalIPv6Address}}{{end}}' redis)
cat <<-EOF >> "$GITHUB_ENV"
redis_ipv4_address=$ipv4_address
redis_ipv6_address=$ipv6_address
EOF
- name: setup
if: matrix.node_type == 'single' && matrix.connect_type == 'tcp'
run: |
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'
run: |
cat <<-EOF >> "$GITHUB_ENV"
EMQX_AUTH__REDIS__TYPE=single
EMQX_AUTH__REDIS__SERVER=${redis_${{ matrix.network_type }}_address}:6380
EOF
- name: setup
if: matrix.node_type == 'sentinel' && matrix.connect_type == 'tcp'
run: |
cat <<-EOF >> "$GITHUB_ENV"
EMQX_AUTH__REDIS__TYPE=sentinel
EMQX_AUTH__REDIS__SERVER=${redis_${{ matrix.network_type }}_address}:26379
EMQX_AUTH__REDIS__SENTINEL=mymaster
EOF
- name: setup
if: matrix.node_type == 'sentinel' && matrix.connect_type == 'tls'
run: |
cat <<-EOF >> "$GITHUB_ENV"
EMQX_AUTH__REDIS__TYPE=sentinel
EMQX_AUTH__REDIS__SERVER=${redis_${{ matrix.network_type }}_address}:26380
EMQX_AUTH__REDIS__SENTINEL=mymaster
EOF
- name: setup
if: matrix.node_type == 'cluster' && matrix.connect_type == 'tcp'
run: |
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'
run: |
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
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 --application=emqx_auth_redis"
docker exec --env-file .env -i erlang sh -c "make apps/emqx_auth_redis-ct"
- uses: actions/upload-artifact@v1
if: failure()
with:
name: logs_redis${{ matrix.redis_tag }}_${{ matrix.node_type }}_${{ matrix.network_type }}_${{ matrix.connect_type }}
path: _build/test/logs

View File

@ -48,13 +48,13 @@ jobs:
echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:waiting emqx";
sleep 5;
done
- name: verify EMQX_LOADED_PLUGINS override working
run: |
expected="{emqx_sn, true}."
output=$(docker exec -i node1.emqx.io bash -c "cat data/loaded_plugins" | tail -n1)
if [ "$expected" != "$output" ]; then
exit 1
fi
# - name: verify EMQX_LOADED_PLUGINS override working
# run: |
# expected="{emqx_sn, true}."
# output=$(docker exec -i node1.emqx.io bash -c "cat data/loaded_plugins" | tail -n1)
# if [ "$expected" != "$output" ]; then
# exit 1
# fi
- name: make paho tests
run: |
if ! docker exec -i python /scripts/pytest.sh; then
@ -131,11 +131,27 @@ jobs:
echo "waiting emqx started";
sleep 10;
done
- name: get pods log
- name: get emqx-0 pods log
if: failure()
env:
KUBECONFIG: "/etc/rancher/k3s/k3s.yaml"
run: kubectl describe pods emqx-0
run: |
kubectl describe pods emqx-0
kubectl logs emqx-0
- name: get emqx-1 pods log
if: failure()
env:
KUBECONFIG: "/etc/rancher/k3s/k3s.yaml"
run: |
kubectl describe pods emqx-1
kubectl logs emqx-1
- name: get emqx-2 pods log
if: failure()
env:
KUBECONFIG: "/etc/rancher/k3s/k3s.yaml"
run: |
kubectl describe pods emqx-2
kubectl logs emqx-2
- uses: actions/checkout@v2
with:
repository: emqx/paho.mqtt.testing

View File

@ -56,64 +56,21 @@ jobs:
- 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/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"

2
.gitignore vendored
View File

@ -45,6 +45,6 @@ emqx_dialyzer_*_plt
*/emqx_dashboard/priv/www
dist.zip
scripts/git-token
etc/*.seg
apps/*/etc/*.all
_upgrade_base/
TAGS

View File

@ -73,7 +73,8 @@ coveralls: $(REBAR)
@ENABLE_COVER_COMPILE=1 $(REBAR) as test coveralls send
.PHONY: $(REL_PROFILES)
$(REL_PROFILES:%=%): $(REBAR) get-dashboard
$(REL_PROFILES:%=%): $(REBAR) get-dashboard conf-segs
@$(REBAR) as $(@) do compile,release
## Not calling rebar3 clean because
@ -111,7 +112,7 @@ xref: $(REBAR)
dialyzer: $(REBAR)
@$(REBAR) as check dialyzer
COMMON_DEPS := $(REBAR) get-dashboard $(CONF_SEGS)
COMMON_DEPS := $(REBAR) get-dashboard conf-segs
## rel target is to create release package without relup
.PHONY: $(REL_PROFILES:%=%-rel) $(PKG_PROFILES:%=%-rel)
@ -152,3 +153,6 @@ quickrun:
./_build/$(PROFILE)/rel/emqx/bin/emqx console
include docker.mk
conf-segs:
@scripts/merge-config.escript

View File

@ -101,7 +101,7 @@ make dialyzer
##### 要分析特定的应用程序,(用逗号分隔的应用程序列表)
```
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_authz make dialyzer
```
## 社区
@ -145,7 +145,7 @@ DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
[MQTT Version 5.0](https://docs.oasis-open.org/mqtt/mqtt/v5.0/cs02/mqtt-v5.0-cs02.html)
[MQTT SN](http://mqtt.org/new/wp-content/uploads/2009/06/MQTT-SN_spec_v1.2.pdf)
[MQTT SN](https://www.oasis-open.org/committees/download.php/66091/MQTT-SN_spec_v1.2.pdf)
## 开源许可

View File

@ -95,7 +95,7 @@ make dialyzer
##### 特定のアプリケーションのみ解析する(アプリケーション名をコンマ区切りで入力)
```
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_authz make dialyzer
```
## コミュニティ
@ -125,7 +125,7 @@ DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
[MQTT Version 5.0](https://docs.oasis-open.org/mqtt/mqtt/v5.0/cs02/mqtt-v5.0-cs02.html)
[MQTT SN](http://mqtt.org/new/wp-content/uploads/2009/06/MQTT-SN_spec_v1.2.pdf)
[MQTT SN](https://www.oasis-open.org/committees/download.php/66091/MQTT-SN_spec_v1.2.pdf)
## License

View File

@ -104,7 +104,7 @@ make dialyzer
##### Статический анализ части приложений (список через запятую)
```
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_authz make dialyzer
```
## Сообщество
@ -135,7 +135,7 @@ DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
[MQTT Version 5.0](https://docs.oasis-open.org/mqtt/mqtt/v5.0/cs02/mqtt-v5.0-cs02.html)
[MQTT SN](http://mqtt.org/new/wp-content/uploads/2009/06/MQTT-SN_spec_v1.2.pdf)
[MQTT SN](https://www.oasis-open.org/committees/download.php/66091/MQTT-SN_spec_v1.2.pdf)
## Лицензия

View File

@ -103,7 +103,7 @@ make dialyzer
##### To Analyse specific apps, (list of comma separated apps)
```
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_authz make dialyzer
```
## Community
@ -134,7 +134,7 @@ You can read the mqtt protocol via the following links:
[MQTT Version 5.0](https://docs.oasis-open.org/mqtt/mqtt/v5.0/cs02/mqtt-v5.0-cs02.html)
[MQTT SN](http://mqtt.org/new/wp-content/uploads/2009/06/MQTT-SN_spec_v1.2.pdf)
[MQTT SN](https://www.oasis-open.org/committees/download.php/66091/MQTT-SN_spec_v1.2.pdf)
## License

View File

@ -1,26 +0,0 @@
%%--------------------------------------------------------------------
%% [ACL](https://docs.emqx.io/broker/v3/en/config.html)
%%
%% -type(who() :: all | binary() |
%% {ipaddr, esockd_access:cidr()} |
%% {client, binary()} |
%% {user, binary()}).
%%
%% -type(access() :: subscribe | publish | pubsub).
%%
%% -type(topic() :: binary()).
%%
%% -type(rule() :: {allow, all} |
%% {allow, who(), access(), list(topic())} |
%% {deny, all} |
%% {deny, who(), access(), list(topic())}).
%%--------------------------------------------------------------------
{allow, {user, "dashboard"}, subscribe, ["$SYS/#"]}.
{allow, {ipaddr, "127.0.0.1"}, pubsub, ["$SYS/#", "#"]}.
{deny, all, subscribe, ["$SYS/#", {eq, "#"}]}.
{allow, all}.

View File

@ -1,14 +0,0 @@
%%--------------------------------------------------------------------
%% For paho interoperability test cases
%%--------------------------------------------------------------------
{deny, {client, "myclientid"}, subscribe, ["test/nosubscribe"]}.
{allow, {user, "dashboard"}, subscribe, ["$SYS/#"]}.
{allow, {ipaddr, "127.0.0.1"}, pubsub, ["$SYS/#", "#"]}.
{deny, all, subscribe, ["$SYS/#", {eq, "#"}]}.
{allow, all}.

View File

@ -307,6 +307,14 @@ cluster {
## Default: default
namespace: default
}
db_backend: mnesia
rlog: {
# role: core
# core_nodes: []
}
}
##==================================================================
@ -1259,8 +1267,9 @@ zones.default {
##
## @doc zones.<name>.listeners.<name>.type
## ValueType: tcp | ws
## - tcp: MQTT over TCP
## - ws: MQTT over Websocket
## - tcp: MQTT over TCP
## - ws: MQTT over Websocket
## - quic: MQTT over QUIC
## Required: true
type: tcp
@ -1390,8 +1399,9 @@ zones.default {
##
## @doc zones.<name>.listeners.<name>.type
## ValueType: tcp | ws
## - tcp: MQTT over TCP
## - ws: MQTT over Websocket
## - tcp: MQTT over TCP
## - ws: MQTT over Websocket
## - quic: MQTT over QUIC
## Required: true
type: tcp
@ -1520,6 +1530,51 @@ zones.default {
tcp.buffer: 4KB
}
listeners.mqtt_quic:
#${example_common_ssl_options} # common options can be written in a separate config entry and reference it from here.
{
## The type of the listener.
##
## @doc zones.<name>.listeners.<name>.type
## ValueType: tcp | ws
## - tcp: MQTT over TCP
## - ws: MQTT over Websocket
## - quic: MQTT over QUIC
## Required: true
type: quic
## The IP address and port that the listener will bind.
##
## @doc zones.<name>.listeners.<name>.bind
## ValueType: IPAddress | Port | IPAddrPort
## Required: true
## Examples: 14567, 127.0.0.1:14567, ::1:14567
bind: "0.0.0.0:14567"
## The size of the acceptor pool for this listener.
##
## @doc zones.<name>.listeners.<name>.acceptors
## ValueType: Number
## Default: 16
acceptors: 16
## Maximum number of concurrent connections.
##
## @doc zones.<name>.listeners.<name>.max_connections
## ValueType: Number | infinity
## Default: infinity
max_connections: 1024000
## SSL options
## See ${example_common_ssl_options} for more information
ssl.enable: false
#ssl.versions: ["tlsv1.3", "tlsv1.2", "tlsv1.1", "tlsv1"]
#ssl.keyfile: "{{ platform_etc_dir }}/certs/key.pem"
#ssl.certfile: "{{ platform_etc_dir }}/certs/cert.pem"
#ssl.cacertfile: "{{ platform_etc_dir }}/certs/cacert.pem"
}
listeners.mqtt_ws:
#${example_common_tcp_options} ${example_common_websocket_options} # common options can be written in a separate config entry and reference it from here.
{
@ -1528,8 +1583,9 @@ zones.default {
##
## @doc zones.<name>.listeners.<name>.type
## ValueType: tcp | ws
## - tcp: MQTT over TCP
## - ws: MQTT over Websocket
## - tcp: MQTT over TCP
## - ws: MQTT over Websocket
## - quic: MQTT over QUIC
## Required: true
type: ws
@ -1662,8 +1718,9 @@ zones.default {
##
## @doc zones.<name>.listeners.<name>.type
## ValueType: tcp | ws
## - tcp: MQTT over TCP
## - ws: MQTT over Websocket
## - tcp: MQTT over TCP
## - ws: MQTT over Websocket
## - quic: MQTT over QUIC
## Required: true
type: ws

View File

@ -23,6 +23,10 @@
-define(Otherwise, true).
-define(COMMON_SHARD, emqx_common_shard).
-define(SHARED_SUB_SHARD, emqx_shared_sub_shard).
-define(MOD_DELAYED_SHARD, emqx_delayed_shard).
%%--------------------------------------------------------------------
%% Banner
%%--------------------------------------------------------------------
@ -86,6 +90,9 @@
-define(ROUTE_SHARD, route_shard).
-define(RULE_ENGINE_SHARD, emqx_rule_engine_shard).
-record(route, {
topic :: binary(),
dest :: node() | {binary(), node()}
@ -101,8 +108,7 @@
descr :: string(),
vendor :: string() | undefined,
active = false :: boolean(),
info = #{} :: map(),
type :: atom()
info = #{} :: map()
}).
%%--------------------------------------------------------------------
@ -134,4 +140,3 @@
}).
-endif.

View File

@ -30,11 +30,13 @@
%% MQTT Protocol Version and Names
%%--------------------------------------------------------------------
-define(MQTT_SN_PROTO_V1, 1).
-define(MQTT_PROTO_V3, 3).
-define(MQTT_PROTO_V4, 4).
-define(MQTT_PROTO_V5, 5).
-define(PROTOCOL_NAMES, [
{?MQTT_SN_PROTO_V1, <<"MQTT-SN">>}, %% XXX:Compatible with emqx-sn plug-in
{?MQTT_PROTO_V3, <<"MQIsdp">>},
{?MQTT_PROTO_V4, <<"MQTT">>},
{?MQTT_PROTO_V5, <<"MQTT">>}]).

View File

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

View File

@ -20,6 +20,7 @@
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {branch, "2.0.4"}}}
, {recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}}
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.13.0"}}}
, {quicer, {git, "https://github.com/emqx/quic.git", {tag, "0.0.5"}}}
]}.
{plugins, [rebar3_proper]}.
@ -30,7 +31,7 @@
[ meck
, {bbmustache,"1.10.0"}
, {emqx_ct_helpers, {git,"https://github.com/emqx/emqx-ct-helpers", {branch,"hocon"}}}
, {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.2.3.1"}}}
, {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.4.1"}}}
]},
{extra_src_dirs, [{"test",[recursive]}]}
]}

View File

@ -4,7 +4,7 @@
{vsn, "5.0.0"}, % strict semver, bump manually!
{modules, []},
{registered, []},
{applications, [kernel,stdlib,gproc,gen_rpc,esockd,cowboy,sasl,os_mon]},
{applications, [kernel,stdlib,gproc,gen_rpc,esockd,cowboy,sasl,os_mon,quicer,jiffy]},
{mod, {emqx_app,[]}},
{env, []},
{licenses, ["Apache-2.0"]},

View File

@ -1,69 +0,0 @@
%% -*- mode: erlang -*-
{VSN,
[{"4.3.2",
[{load_module,emqx_http_lib,brutal_purge,soft_purge,[]},
{load_module,emqx_channel,brutal_purge,soft_purge,[]},
{load_module,emqx_app,brutal_purge,soft_purge,[]},
{load_module,emqx_connection,brutal_purge,soft_purge,[]}]},
{"4.3.1",
[{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
{load_module,emqx_connection,brutal_purge,soft_purge,[]},
{load_module,emqx_frame,brutal_purge,soft_purge,[]},
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
{load_module,emqx_congestion,brutal_purge,soft_purge,[]},
{load_module,emqx_node_dump,brutal_purge,soft_purge,[]},
{load_module,emqx_channel,brutal_purge,soft_purge,[]},
{load_module,emqx_app,brutal_purge,soft_purge,[]},
{load_module,emqx_plugins,brutal_purge,soft_purge,[]},
{load_module,emqx_logger_textfmt,brutal_purge,soft_purge,[]},
{load_module,emqx_http_lib,brutal_purge,soft_purge,[]}]},
{"4.3.0",
[{load_module,emqx_logger_jsonfmt,brutal_purge,soft_purge,[]},
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
{load_module,emqx_congestion,brutal_purge,soft_purge,[]},
{load_module,emqx_connection,brutal_purge,soft_purge,[]},
{load_module,emqx_frame,brutal_purge,soft_purge,[]},
{load_module,emqx_trie,brutal_purge,soft_purge,[]},
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
{load_module,emqx_node_dump,brutal_purge,soft_purge,[]},
{load_module,emqx_channel,brutal_purge,soft_purge,[]},
{load_module,emqx_app,brutal_purge,soft_purge,[]},
{load_module,emqx_plugins,brutal_purge,soft_purge,[]},
{load_module,emqx_logger_textfmt,brutal_purge,soft_purge,[]},
{load_module,emqx_metrics,brutal_purge,soft_purge,[]},
{apply,{emqx_metrics,upgrade_retained_delayed_counter_type,[]}},
{load_module,emqx_http_lib,brutal_purge,soft_purge,[]}]},
{<<".*">>,[]}],
[{"4.3.2",
[{load_module,emqx_http_lib,brutal_purge,soft_purge,[]},
{load_module,emqx_channel,brutal_purge,soft_purge,[]},
{load_module,emqx_app,brutal_purge,soft_purge,[]},
{load_module,emqx_connection,brutal_purge,soft_purge,[]}]},
{"4.3.1",
[{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
{load_module,emqx_connection,brutal_purge,soft_purge,[]},
{load_module,emqx_frame,brutal_purge,soft_purge,[]},
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
{load_module,emqx_congestion,brutal_purge,soft_purge,[]},
{load_module,emqx_node_dump,brutal_purge,soft_purge,[]},
{load_module,emqx_channel,brutal_purge,soft_purge,[]},
{load_module,emqx_app,brutal_purge,soft_purge,[]},
{load_module,emqx_plugins,brutal_purge,soft_purge,[]},
{load_module,emqx_logger_textfmt,brutal_purge,soft_purge,[]},
{load_module,emqx_http_lib,brutal_purge,soft_purge,[]}]},
{"4.3.0",
[{load_module,emqx_logger_jsonfmt,brutal_purge,soft_purge,[]},
{load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
{load_module,emqx_connection,brutal_purge,soft_purge,[]},
{load_module,emqx_congestion,brutal_purge,soft_purge,[]},
{load_module,emqx_frame,brutal_purge,soft_purge,[]},
{load_module,emqx_trie,brutal_purge,soft_purge,[]},
{load_module,emqx_cm,brutal_purge,soft_purge,[]},
{load_module,emqx_node_dump,brutal_purge,soft_purge,[]},
{load_module,emqx_channel,brutal_purge,soft_purge,[]},
{load_module,emqx_app,brutal_purge,soft_purge,[]},
{load_module,emqx_plugins,brutal_purge,soft_purge,[]},
{load_module,emqx_logger_textfmt,brutal_purge,soft_purge,[]},
{load_module,emqx_metrics,brutal_purge,soft_purge,[]},
{load_module,emqx_http_lib,brutal_purge,soft_purge,[]}]},
{<<".*">>,[]}]}.

View File

@ -227,7 +227,6 @@ shutdown() ->
shutdown(Reason) ->
?LOG(critical, "emqx shutdown for ~s", [Reason]),
_ = emqx_alarm_handler:unload(),
_ = emqx_plugins:unload(),
lists:foreach(fun application:stop/1
, lists:reverse(default_started_applications())
).
@ -237,10 +236,10 @@ reboot() ->
-ifdef(EMQX_ENTERPRISE).
default_started_applications() ->
[gproc, esockd, ranch, cowboy, ekka, emqx].
[gproc, esockd, ranch, cowboy, ekka, quicer, emqx].
-else.
default_started_applications() ->
[gproc, esockd, ranch, cowboy, ekka, emqx, emqx_modules].
[gproc, esockd, ranch, cowboy, ekka, quicer, emqx] ++ emqx_feature().
-endif.
%%--------------------------------------------------------------------
@ -253,3 +252,16 @@ reload_config(ConfFile) ->
[application:set_env(App, Par, Val) || {Par, Val} <- Vals]
end, Conf).
emqx_feature() ->
[ emqx_resource
, emqx_authn
, emqx_authz
, emqx_gateway
, emqx_data_bridge
, emqx_rule_engine
, emqx_bridge_mqtt
, emqx_modules
, emqx_management
, emqx_retainer
, emqx_statsd].

View File

@ -20,7 +20,7 @@
-export([authenticate/1]).
-export([ check_acl/3
-export([ authorize/3
]).
-type(result() :: #{auth_result := emqx_types:auth_result(),
@ -37,25 +37,25 @@ authenticate(ClientInfo = #{zone := Zone, listener := Listener}) ->
return_auth_result(run_hooks('client.authenticate', [ClientInfo], AuthResult)).
%% @doc Check ACL
-spec(check_acl(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic())
-spec(authorize(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic())
-> allow | deny).
check_acl(ClientInfo = #{zone := Zone, listener := Listener}, PubSub, Topic) ->
authorize(ClientInfo = #{zone := Zone, listener := Listener}, PubSub, Topic) ->
case emqx_acl_cache:is_enabled(Zone, Listener) of
true -> check_acl_cache(ClientInfo, PubSub, Topic);
false -> do_check_acl(ClientInfo, PubSub, Topic)
true -> check_authorization_cache(ClientInfo, PubSub, Topic);
false -> do_authorize(ClientInfo, PubSub, Topic)
end.
check_acl_cache(ClientInfo = #{zone := Zone, listener := Listener}, PubSub, Topic) ->
check_authorization_cache(ClientInfo = #{zone := Zone, listener := Listener}, PubSub, Topic) ->
case emqx_acl_cache:get_acl_cache(Zone, Listener, PubSub, Topic) of
not_found ->
AclResult = do_check_acl(ClientInfo, PubSub, Topic),
AclResult = do_authorize(ClientInfo, PubSub, Topic),
emqx_acl_cache:put_acl_cache(Zone, Listener, PubSub, Topic, AclResult),
AclResult;
AclResult -> AclResult
end.
do_check_acl(ClientInfo, PubSub, Topic) ->
case run_hooks('client.check_acl', [ClientInfo, PubSub, Topic], allow) of
do_authorize(ClientInfo, PubSub, Topic) ->
case run_hooks('client.authorize', [ClientInfo, PubSub, Topic], allow) of
allow -> allow;
_Other -> deny
end.

View File

@ -1,152 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2017-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_access_rule).
%% APIs
-export([ match/3
, compile/1
]).
-export_type([rule/0]).
-type(acl_result() :: allow | deny).
-type(who() :: all | binary() |
{client, binary()} |
{user, binary()} |
{ipaddr, esockd_cidr:cidr_string()}).
-type(access() :: subscribe | publish | pubsub).
-type(rule() :: {acl_result(), all} |
{acl_result(), who(), access(), list(emqx_topic:topic())}).
-define(ALLOW_DENY(A), ((A =:= allow) orelse (A =:= deny))).
-define(PUBSUB(A), ((A =:= subscribe) orelse (A =:= publish) orelse (A =:= pubsub))).
%% @doc Compile Access Rule.
compile({A, all}) when ?ALLOW_DENY(A) ->
{A, all};
compile({A, Who, Access, Topic}) when ?ALLOW_DENY(A), ?PUBSUB(Access), is_binary(Topic) ->
{A, compile(who, Who), Access, [compile(topic, Topic)]};
compile({A, Who, Access, TopicFilters}) when ?ALLOW_DENY(A), ?PUBSUB(Access) ->
{A, compile(who, Who), Access, [compile(topic, Topic) || Topic <- TopicFilters]}.
compile(who, all) ->
all;
compile(who, {ipaddr, CIDR}) ->
{ipaddr, esockd_cidr:parse(CIDR, true)};
compile(who, {client, all}) ->
{client, all};
compile(who, {client, ClientId}) ->
{client, bin(ClientId)};
compile(who, {user, all}) ->
{user, all};
compile(who, {user, Username}) ->
{user, bin(Username)};
compile(who, {'and', Conds}) when is_list(Conds) ->
{'and', [compile(who, Cond) || Cond <- Conds]};
compile(who, {'or', Conds}) when is_list(Conds) ->
{'or', [compile(who, Cond) || Cond <- Conds]};
compile(topic, {eq, Topic}) ->
{eq, emqx_topic:words(bin(Topic))};
compile(topic, Topic) ->
Words = emqx_topic:words(bin(Topic)),
case pattern(Words) of
true -> {pattern, Words};
false -> Words
end.
pattern(Words) ->
lists:member(<<"%u">>, Words) orelse lists:member(<<"%c">>, Words).
bin(L) when is_list(L) ->
list_to_binary(L);
bin(B) when is_binary(B) ->
B.
%% @doc Match access rule
-spec(match(emqx_types:clientinfo(), emqx_types:topic(), rule())
-> {matched, allow} | {matched, deny} | nomatch).
match(_ClientInfo, _Topic, {AllowDeny, all}) when ?ALLOW_DENY(AllowDeny) ->
{matched, AllowDeny};
match(ClientInfo, Topic, {AllowDeny, Who, _PubSub, TopicFilters})
when ?ALLOW_DENY(AllowDeny) ->
case match_who(ClientInfo, Who)
andalso match_topics(ClientInfo, Topic, TopicFilters) of
true -> {matched, AllowDeny};
false -> nomatch
end.
match_who(_ClientInfo, all) ->
true;
match_who(_ClientInfo, {user, all}) ->
true;
match_who(_ClientInfo, {client, all}) ->
true;
match_who(#{clientid := ClientId}, {client, ClientId}) ->
true;
match_who(#{username := Username}, {user, Username}) ->
true;
match_who(#{peerhost := undefined}, {ipaddr, _Tup}) ->
false;
match_who(#{peerhost := IP}, {ipaddr, CIDR}) ->
esockd_cidr:match(IP, CIDR);
match_who(ClientInfo, {'and', Conds}) when is_list(Conds) ->
lists:foldl(fun(Who, Allow) ->
match_who(ClientInfo, Who) andalso Allow
end, true, Conds);
match_who(ClientInfo, {'or', Conds}) when is_list(Conds) ->
lists:foldl(fun(Who, Allow) ->
match_who(ClientInfo, Who) orelse Allow
end, false, Conds);
match_who(_ClientInfo, _Who) ->
false.
match_topics(_ClientInfo, _Topic, []) ->
false;
match_topics(ClientInfo, Topic, [{pattern, PatternFilter}|Filters]) ->
TopicFilter = feed_var(ClientInfo, PatternFilter),
match_topic(emqx_topic:words(Topic), TopicFilter)
orelse match_topics(ClientInfo, Topic, Filters);
match_topics(ClientInfo, Topic, [TopicFilter|Filters]) ->
match_topic(emqx_topic:words(Topic), TopicFilter)
orelse match_topics(ClientInfo, Topic, Filters).
match_topic(Topic, {eq, TopicFilter}) ->
Topic == TopicFilter;
match_topic(Topic, TopicFilter) ->
emqx_topic:match(Topic, TopicFilter).
feed_var(ClientInfo, Pattern) ->
feed_var(ClientInfo, Pattern, []).
feed_var(_ClientInfo, [], Acc) ->
lists:reverse(Acc);
feed_var(ClientInfo = #{clientid := undefined}, [<<"%c">>|Words], Acc) ->
feed_var(ClientInfo, Words, [<<"%c">>|Acc]);
feed_var(ClientInfo = #{clientid := ClientId}, [<<"%c">>|Words], Acc) ->
feed_var(ClientInfo, Words, [ClientId |Acc]);
feed_var(ClientInfo = #{username := undefined}, [<<"%u">>|Words], Acc) ->
feed_var(ClientInfo, Words, [<<"%u">>|Acc]);
feed_var(ClientInfo = #{username := Username}, [<<"%u">>|Words], Acc) ->
feed_var(ClientInfo, Words, [Username|Acc]);
feed_var(ClientInfo, [W|Words], Acc) ->
feed_var(ClientInfo, Words, [W|Acc]).

View File

@ -82,6 +82,9 @@
-define(DEACTIVATED_ALARM, emqx_deactivated_alarm).
-rlog_shard({?COMMON_SHARD, ?ACTIVATED_ALARM}).
-rlog_shard({?COMMON_SHARD, ?DEACTIVATED_ALARM}).
-ifdef(TEST).
-compile(export_all).
-compile(nowarn_export_all).
@ -167,7 +170,7 @@ handle_call({activate_alarm, Name, Details}, _From, State) ->
details = Details,
message = normalize_message(Name, Details),
activate_at = erlang:system_time(microsecond)},
mnesia:dirty_write(?ACTIVATED_ALARM, Alarm),
ekka_mnesia:dirty_write(?ACTIVATED_ALARM, Alarm),
do_actions(activate, Alarm, emqx_config:get([alarm, actions])),
{reply, ok, State}
end;
@ -186,9 +189,14 @@ handle_call(delete_all_deactivated_alarms, _From, State) ->
{reply, ok, State};
handle_call({get_alarms, all}, _From, State) ->
Alarms = [normalize(Alarm) ||
Alarm <- ets:tab2list(?ACTIVATED_ALARM)
++ ets:tab2list(?DEACTIVATED_ALARM)],
{atomic, Alarms} =
ekka_mnesia:ro_transaction(
?COMMON_SHARD,
fun() ->
[normalize(Alarm) ||
Alarm <- ets:tab2list(?ACTIVATED_ALARM)
++ ets:tab2list(?DEACTIVATED_ALARM)]
end),
{reply, Alarms, State};
handle_call({get_alarms, activated}, _From, State) ->
@ -235,7 +243,7 @@ deactivate_alarm(Details, #activated_alarm{activate_at = ActivateAt, name = Name
case mnesia:dirty_first(?DEACTIVATED_ALARM) of
'$end_of_table' -> ok;
ActivateAt2 ->
mnesia:dirty_delete(?DEACTIVATED_ALARM, ActivateAt2)
ekka_mnesia:dirty_delete(?DEACTIVATED_ALARM, ActivateAt2)
end;
false -> ok
end,
@ -244,8 +252,8 @@ deactivate_alarm(Details, #activated_alarm{activate_at = ActivateAt, name = Name
DeActAlarm = make_deactivated_alarm(ActivateAt, Name, Details,
normalize_message(Name, Details),
erlang:system_time(microsecond)),
mnesia:dirty_write(?DEACTIVATED_ALARM, HistoryAlarm),
mnesia:dirty_delete(?ACTIVATED_ALARM, Name),
ekka_mnesia:dirty_write(?DEACTIVATED_ALARM, HistoryAlarm),
ekka_mnesia:dirty_delete(?ACTIVATED_ALARM, Name),
do_actions(deactivate, DeActAlarm, emqx_config:get([alarm, actions])).
make_deactivated_alarm(ActivateAt, Name, Details, Message, DeActivateAt) ->
@ -262,7 +270,7 @@ deactivate_all_alarms() ->
details = Details,
message = Message,
activate_at = ActivateAt}) ->
mnesia:dirty_write(?DEACTIVATED_ALARM,
ekka_mnesia:dirty_write(?DEACTIVATED_ALARM,
#deactivated_alarm{
activate_at = ActivateAt,
name = Name,
@ -274,7 +282,7 @@ deactivate_all_alarms() ->
%% Delete all records from the given table, ignore result.
clear_table(TableName) ->
case mnesia:clear_table(TableName) of
case ekka_mnesia:clear_table(TableName) of
{aborted, Reason} ->
?LOG(warning, "Faile to clear table ~p reason: ~p",
[TableName, Reason]);
@ -294,7 +302,7 @@ delete_expired_deactivated_alarms('$end_of_table', _Checkpoint) ->
delete_expired_deactivated_alarms(ActivatedAt, Checkpoint) ->
case ActivatedAt =< Checkpoint of
true ->
mnesia:dirty_delete(?DEACTIVATED_ALARM, ActivatedAt),
ekka_mnesia:dirty_delete(?DEACTIVATED_ALARM, ActivatedAt),
NActivatedAt = mnesia:dirty_next(?DEACTIVATED_ALARM, ActivatedAt),
delete_expired_deactivated_alarms(NActivatedAt, Checkpoint);
false ->

View File

@ -28,7 +28,12 @@
-define(APP, emqx).
-define(EMQX_SHARDS, [route_shard]).
-define(EMQX_SHARDS, [ ?ROUTE_SHARD
, ?COMMON_SHARD
, ?SHARED_SUB_SHARD
, ?RULE_ENGINE_SHARD
, ?MOD_DELAYED_SHARD
]).
-include("emqx_release.hrl").
@ -46,7 +51,7 @@ start(_Type, _Args) ->
ok = ekka_rlog:wait_for_shards(?EMQX_SHARDS, infinity),
{ok, Sup} = emqx_sup:start_link(),
ok = start_autocluster(),
ok = emqx_plugins:init(),
% ok = emqx_plugins:init(),
_ = emqx_plugins:load(),
_ = start_ce_modules(),
emqx_boot:is_enabled(listeners) andalso (ok = emqx_listeners:start()),

View File

@ -51,6 +51,8 @@
-define(BANNED_TAB, ?MODULE).
-rlog_shard({?COMMON_SHARD, ?BANNED_TAB}).
%%--------------------------------------------------------------------
%% Mnesia bootstrap
%%--------------------------------------------------------------------
@ -96,19 +98,19 @@ create(#{who := Who,
reason := Reason,
at := At,
until := Until}) ->
mnesia:dirty_write(?BANNED_TAB, #banned{who = Who,
by = By,
reason = Reason,
at = At,
until = Until});
ekka_mnesia:dirty_write(?BANNED_TAB, #banned{who = Who,
by = By,
reason = Reason,
at = At,
until = Until});
create(Banned) when is_record(Banned, banned) ->
mnesia:dirty_write(?BANNED_TAB, Banned).
ekka_mnesia:dirty_write(?BANNED_TAB, Banned).
-spec(delete({clientid, emqx_types:clientid()}
| {username, emqx_types:username()}
| {peerhost, emqx_types:peerhost()}) -> ok).
delete(Who) ->
mnesia:dirty_delete(?BANNED_TAB, Who).
ekka_mnesia:dirty_delete(?BANNED_TAB, Who).
info(InfoKey) ->
mnesia:table_info(?BANNED_TAB, InfoKey).
@ -129,7 +131,7 @@ handle_cast(Msg, State) ->
{noreply, State}.
handle_info({timeout, TRef, expire}, State = #{expiry_timer := TRef}) ->
mnesia:async_dirty(fun expire_banned_items/1, [erlang:system_time(second)]),
ekka_mnesia:transaction(?COMMON_SHARD, fun expire_banned_items/1, [erlang:system_time(second)]),
{noreply, ensure_expiry_timer(State), hibernate};
handle_info(Info, State) ->
@ -160,4 +162,3 @@ expire_banned_items(Now) ->
mnesia:delete_object(?BANNED_TAB, B, sticky_write);
(_, _Acc) -> ok
end, ok, ?BANNED_TAB).

View File

@ -1420,7 +1420,7 @@ check_pub_alias(_Packet, _Channel) -> ok.
check_pub_acl(#mqtt_packet{variable = #mqtt_packet_publish{topic_name = Topic}},
#channel{clientinfo = ClientInfo}) ->
case is_acl_enabled(ClientInfo) andalso
emqx_access_control:check_acl(ClientInfo, publish, Topic) of
emqx_access_control:authorize(ClientInfo, publish, Topic) of
false -> ok;
allow -> ok;
deny -> {error, ?RC_NOT_AUTHORIZED}
@ -1454,7 +1454,7 @@ check_sub_acls([], _Channel, Acc) ->
check_sub_acl(TopicFilter, #channel{clientinfo = ClientInfo}) ->
case is_acl_enabled(ClientInfo) andalso
emqx_access_control:check_acl(ClientInfo, subscribe, TopicFilter) of
emqx_access_control:authorize(ClientInfo, subscribe, TopicFilter) of
false -> allow;
Result -> Result
end.

View File

@ -294,6 +294,9 @@ do_discard_session(ClientId, Pid) ->
_ : {noproc, _} -> % emqx_connection: gen_server:call
?tp(debug, "session_already_gone", #{pid => Pid}),
ok;
_ : {'EXIT', {noproc, _}} -> % rpc_call/3
?tp(debug, "session_already_gone", #{pid => Pid}),
ok;
_ : {{shutdown, _}, _} ->
?tp(debug, "session_already_shutdown", #{pid => Pid}),
ok;

View File

@ -48,7 +48,9 @@
-define(TAB, emqx_channel_registry).
-define(LOCK, {?MODULE, cleanup_down}).
-rlog_shard({?ROUTE_SHARD, ?TAB}).
-define(CM_SHARD, emqx_cm_shard).
-rlog_shard({?CM_SHARD, ?TAB}).
-record(channel, {chid, pid}).
@ -111,6 +113,7 @@ init([]) ->
{storage_properties, [{ets, [{read_concurrency, true},
{write_concurrency, true}]}]}]),
ok = ekka_mnesia:copy_table(?TAB, ram_copies),
ok = ekka_rlog:wait_for_shards([?CM_SHARD], infinity),
ok = ekka:monitor(membership),
{ok, #{}}.
@ -125,7 +128,7 @@ handle_cast(Msg, State) ->
handle_info({membership, {mnesia, down, Node}}, State) ->
global:trans({?LOCK, self()},
fun() ->
ekka_mnesia:transaction(?ROUTE_SHARD, fun cleanup_channels/1, [Node])
ekka_mnesia:transaction(?CM_SHARD, fun cleanup_channels/1, [Node])
end),
{noreply, State};

View File

@ -120,4 +120,3 @@ put_raw(Config) ->
-spec put_raw(emqx_map_lib:config_key_path(), term()) -> ok.
put_raw(KeyPath, Config) ->
put_raw(emqx_map_lib:deep_put(KeyPath, get_raw(), Config)).

View File

@ -422,6 +422,13 @@ handle_msg({Inet, _Sock, Data}, State) when Inet == tcp; Inet == ssl ->
ok = emqx_metrics:inc('bytes.received', Oct),
parse_incoming(Data, State);
handle_msg({quic, Data, _Sock, _, _, _}, State) ->
?LOG(debug, "RECV ~0p", [Data]),
Oct = iolist_size(Data),
inc_counter(incoming_bytes, Oct),
ok = emqx_metrics:inc('bytes.received', Oct),
parse_incoming(Data, State);
handle_msg({incoming, Packet = ?CONNECT_PACKET(ConnPkt)},
State = #state{idle_timer = IdleTimer}) ->
ok = emqx_misc:cancel_timer(IdleTimer),
@ -446,7 +453,7 @@ handle_msg({Closed, _Sock}, State)
handle_info({sock_closed, Closed}, close_socket(State));
handle_msg({Passive, _Sock}, State)
when Passive == tcp_passive; Passive == ssl_passive ->
when Passive == tcp_passive; Passive == ssl_passive; Passive =:= quic_passive ->
%% In Stats
Pubs = emqx_pd:reset_counter(incoming_pubs),
Bytes = emqx_pd:reset_counter(incoming_bytes),
@ -739,6 +746,15 @@ handle_info({sock_error, Reason}, State) ->
end,
handle_info({sock_closed, Reason}, close_socket(State));
handle_info({quic, peer_send_shutdown, _Stream}, State) ->
handle_info({sock_closed, force}, close_socket(State));
handle_info({quic, closed, _Channel, ReasonFlag}, State) ->
handle_info({sock_closed, ReasonFlag}, State);
handle_info({quic, closed, _Stream}, State) ->
handle_info({sock_closed, force}, State);
handle_info(Info, State) ->
with_channel(handle_info, [Info], State).

View File

@ -79,7 +79,28 @@ do_start_listener(ZoneName, ListenerName, #{type := ws, bind := ListenOn} = Opts
cowboy:start_clear(Id, RanchOpts, WsOpts);
true ->
cowboy:start_tls(Id, RanchOpts, WsOpts)
end.
end;
%% Start MQTT/QUIC listener
do_start_listener(ZoneName, ListenerName, #{type := quic, bind := ListenOn} = Opts) ->
%% @fixme unsure why we need reopen lib and reopen config.
quicer_nif:open_lib(),
quicer_nif:reg_open(),
SSLOpts = ssl_opts(Opts),
DefAcceptors = erlang:system_info(schedulers_online) * 8,
ListenOpts = [ {cert, maps:get(certfile, SSLOpts, undefined)}
, {key, maps:get(keyfile, SSLOpts, undefined)}
, {alpn, ["mqtt"]}
, {conn_acceptors, maps:get(acceptors, Opts, DefAcceptors)}
, {idle_timeout_ms, emqx_config:get_listener_conf(ZoneName, ListenerName,
[mqtt, idle_timeout])}
],
ConnectionOpts = #{conn_callback => emqx_quic_connection
, peer_unidi_stream_count => 1
, peer_bidi_stream_count => 10
},
StreamOpts = [],
quicer:start_listener('mqtt:quic', ListenOn, {ListenOpts, ConnectionOpts, StreamOpts}).
esockd_opts(Opts0) ->
Opts1 = maps:with([acceptors, max_connections, proxy_protocol, proxy_protocol_timeout], Opts0),

View File

@ -172,7 +172,7 @@
{counter, 'client.connected'},
{counter, 'client.authenticate'},
{counter, 'client.auth.anonymous'},
{counter, 'client.check_acl'},
{counter, 'client.authorize'},
{counter, 'client.subscribe'},
{counter, 'client.unsubscribe'},
{counter, 'client.disconnected'}
@ -563,7 +563,7 @@ reserved_idx('client.connected') -> 202;
reserved_idx('client.authenticate') -> 203;
reserved_idx('client.enhanced_authenticate') -> 204;
reserved_idx('client.auth.anonymous') -> 205;
reserved_idx('client.check_acl') -> 206;
reserved_idx('client.authorize') -> 206;
reserved_idx('client.subscribe') -> 207;
reserved_idx('client.unsubscribe') -> 208;
reserved_idx('client.disconnected') -> 209;

View File

@ -21,8 +21,6 @@
-logger_header("[Plugins]").
-export([init/0]).
-export([ load/0
, load/1
, unload/0
@ -39,35 +37,14 @@
-compile(nowarn_export_all).
-endif.
-dialyzer({no_match, [ plugin_loaded/2
, plugin_unloaded/2
]}).
%%--------------------------------------------------------------------
%% APIs
%%--------------------------------------------------------------------
%% @doc Init plugins' config
-spec(init() -> ok).
init() ->
case emqx:get_env(plugins_etc_dir) of
undefined -> ok;
PluginsEtc ->
CfgFiles = [filename:join(PluginsEtc, File) ||
File <- filelib:wildcard("*.config", PluginsEtc)],
lists:foreach(fun init_config/1, CfgFiles)
end.
%% @doc Load all plugins when the broker started.
-spec(load() -> ok | ignore | {error, term()}).
load() ->
ok = load_ext_plugins(emqx:get_env(expand_plugins_dir)),
case emqx:get_env(plugins_loaded_file) of
undefined -> ignore; %% No plugins available
File ->
_ = ensure_file(File),
with_loaded_file(File, fun(Names) -> load_plugins(Names, false) end)
end.
ok = load_ext_plugins(emqx:get_env(expand_plugins_dir)).
%% @doc Load a Plugin
-spec(load(atom()) -> ok | {error, term()}).
@ -80,17 +57,13 @@ load(PluginName) when is_atom(PluginName) ->
?LOG(notice, "Plugin ~s is already started", [PluginName]),
{error, already_started};
{_, false} ->
load_plugin(PluginName, true)
load_plugin(PluginName)
end.
%% @doc Unload all plugins before broker stopped.
-spec(unload() -> list() | {error, term()}).
-spec(unload() -> ok).
unload() ->
case emqx:get_env(plugins_loaded_file) of
undefined -> ignore;
File ->
with_loaded_file(File, fun stop_plugins/1)
end.
stop_plugins(list()).
%% @doc UnLoad a Plugin
-spec(unload(atom()) -> ok | {error, term()}).
@ -103,7 +76,7 @@ unload(PluginName) when is_atom(PluginName) ->
?LOG(error, "Plugin ~s is not started", [PluginName]),
{error, not_started};
{_, _} ->
unload_plugin(PluginName, true)
unload_plugin(PluginName)
end.
reload(PluginName) when is_atom(PluginName)->
@ -124,8 +97,8 @@ reload(PluginName) when is_atom(PluginName)->
-spec(list() -> [emqx_types:plugin()]).
list() ->
StartedApps = names(started_app),
lists:map(fun({Name, _, [Type| _]}) ->
Plugin = plugin(Name, Type),
lists:map(fun({Name, _, _}) ->
Plugin = plugin(Name),
case lists:member(Name, StartedApps) of
true -> Plugin#plugin{active = true};
false -> Plugin
@ -142,12 +115,6 @@ find_plugin(Name, Plugins) ->
%% Internal functions
%%--------------------------------------------------------------------
init_config(CfgFile) ->
{ok, [AppsEnv]} = file:consult(CfgFile),
lists:foreach(fun({App, Envs}) ->
[application:set_env(App, Par, Val) || {Par, Val} <- Envs]
end, AppsEnv).
%% load external plugins which are placed in etc/plugins dir
load_ext_plugins(undefined) -> ok;
load_ext_plugins(Dir) ->
@ -171,7 +138,15 @@ load_ext_plugin(PluginDir) ->
?LOG(alert, "plugin_app_file_not_found: ~s", [AppFile]),
error({plugin_app_file_not_found, AppFile})
end,
load_plugin_app(AppName, Ebin).
ok = load_plugin_app(AppName, Ebin).
% try
% ok = generate_configs(AppName, PluginDir)
% catch
% throw : {conf_file_not_found, ConfFile} ->
% %% this is maybe a dependency of an external plugin
% ?LOG(debug, "config_load_error_ignored for app=~p, path=~s", [AppName, ConfFile]),
% ok
% end.
load_plugin_app(AppName, Ebin) ->
_ = code:add_patha(Ebin),
@ -189,56 +164,24 @@ load_plugin_app(AppName, Ebin) ->
{error, {already_loaded, _}} -> ok
end.
ensure_file(File) ->
case filelib:is_file(File) of false -> write_loaded([]); true -> ok end.
with_loaded_file(File, SuccFun) ->
case read_loaded(File) of
{ok, Names0} ->
Names = filter_plugins(Names0),
SuccFun(Names);
{error, Error} ->
?LOG(alert, "Failed to read: ~p, error: ~p", [File, Error]),
{error, Error}
end.
filter_plugins(Names) ->
lists:filtermap(fun(Name1) when is_atom(Name1) -> {true, Name1};
({Name1, true}) -> {true, Name1};
({_Name1, false}) -> false
end, Names).
load_plugins(Names, Persistent) ->
Plugins = list(),
NotFound = Names -- names(Plugins),
case NotFound of
[] -> ok;
NotFound -> ?LOG(alert, "cannot_find_plugins: ~p", [NotFound])
end,
NeedToLoad = Names -- NotFound -- names(started_app),
lists:foreach(fun(Name) ->
Plugin = find_plugin(Name, Plugins),
load_plugin(Plugin#plugin.name, Persistent)
end, NeedToLoad).
%% Stop plugins
stop_plugins(Names) ->
_ = [stop_app(App) || App <- Names],
stop_plugins(Plugins) ->
_ = [stop_app(Plugin#plugin.name) || Plugin <- Plugins],
ok.
plugin(AppName, Type) ->
plugin(AppName) ->
case application:get_all_key(AppName) of
{ok, Attrs} ->
Descr = proplists:get_value(description, Attrs, ""),
#plugin{name = AppName, descr = Descr, type = plugin_type(Type)};
#plugin{name = AppName, descr = Descr};
undefined -> error({plugin_not_found, AppName})
end.
load_plugin(Name, Persistent) ->
load_plugin(Name) ->
try
case load_app(Name) of
ok ->
start_app(Name, fun(App) -> plugin_loaded(App, Persistent) end);
start_app(Name);
{error, Error0} ->
{error, Error0}
end
@ -257,22 +200,21 @@ load_app(App) ->
{error, Error}
end.
start_app(App, SuccFun) ->
start_app(App) ->
case application:ensure_all_started(App) of
{ok, Started} ->
?LOG(info, "Started plugins: ~p", [Started]),
?LOG(info, "Load plugin ~s successfully", [App]),
_ = SuccFun(App),
ok;
{error, {ErrApp, Reason}} ->
?LOG(error, "Load plugin ~s failed, cannot start plugin ~s for ~0p", [App, ErrApp, Reason]),
{error, {ErrApp, Reason}}
end.
unload_plugin(App, Persistent) ->
unload_plugin(App) ->
case stop_app(App) of
ok ->
_ = plugin_unloaded(App, Persistent), ok;
ok;
{error, Reason} ->
{error, Reason}
end.
@ -296,60 +238,5 @@ names(started_app) ->
names(Plugins) ->
[Name || #plugin{name = Name} <- Plugins].
plugin_loaded(_Name, false) ->
ok;
plugin_loaded(Name, true) ->
case read_loaded() of
{ok, Names} ->
case lists:member(Name, Names) of
false ->
%% write file if plugin is loaded
write_loaded(lists:append(Names, [{Name, true}]));
true ->
ignore
end;
{error, Error} ->
?LOG(error, "Cannot read loaded plugins: ~p", [Error])
end.
plugin_unloaded(_Name, false) ->
ok;
plugin_unloaded(Name, true) ->
case read_loaded() of
{ok, Names0} ->
Names = filter_plugins(Names0),
case lists:member(Name, Names) of
true ->
write_loaded(lists:delete(Name, Names));
false ->
?LOG(error, "Cannot find ~s in loaded_file", [Name])
end;
{error, Error} ->
?LOG(error, "Cannot read loaded_plugins: ~p", [Error])
end.
read_loaded() ->
case emqx:get_env(plugins_loaded_file) of
undefined -> {error, not_found};
File -> read_loaded(File)
end.
read_loaded(File) -> file:consult(File).
write_loaded(AppNames) ->
FilePath = emqx:get_env(plugins_loaded_file),
case file:write_file(FilePath, [io_lib:format("~p.~n", [Name]) || Name <- AppNames]) of
ok -> ok;
{error, Error} ->
?LOG(error, "Write File ~p Error: ~p", [FilePath, Error]),
{error, Error}
end.
plugin_type(auth) -> auth;
plugin_type(protocol) -> protocol;
plugin_type(backend) -> backend;
plugin_type(bridge) -> bridge;
plugin_type(_) -> feature.
funlog(Key, Value) ->
?LOG(info, "~s = ~p", [string:join(Key, "."), Value]).

View File

@ -14,21 +14,13 @@
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_authentication_app).
-module(emqx_quic_connection).
-behaviour(application).
-emqx_plugin(?MODULE).
%% Application callbacks
-export([ start/2
, stop/1
%% Callbacks
-export([ new_conn/2
]).
start(_StartType, _StartArgs) ->
{ok, Sup} = emqx_authentication_sup:start_link(),
ok = emqx_authentication:register_service_types(),
{ok, Sup}.
stop(_State) ->
ok.
new_conn(Conn, {_L, COpts, _S}) when is_map(COpts) ->
new_conn(Conn, maps:to_list(COpts));
new_conn(Conn, COpts) ->
emqx_connection:start_link(emqx_quic_stream, Conn, COpts).

View File

@ -0,0 +1,92 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
%% MQTT/QUIC Stream
-module(emqx_quic_stream).
%% emqx transport Callbacks
-export([ type/1
, wait/1
, getstat/2
, fast_close/1
, ensure_ok_or_exit/2
, async_send/3
, setopts/2
, getopts/2
, peername/1
, sockname/1
, peercert/1
]).
wait(Conn) ->
quicer:accept_stream(Conn, []).
type(_) ->
quic.
peername(S) ->
quicer:peername(S).
sockname(S) ->
quicer:sockname(S).
peercert(_S) ->
nossl.
getstat(Socket, Stats) ->
case quicer:getstat(Socket, Stats) of
{error, _} -> {error, closed};
Res -> Res
end.
setopts(Socket, Opts) ->
lists:foreach(fun({Opt, V}) when is_atom(Opt) ->
quicer:setopt(Socket, Opt, V);
(Opt) when is_atom(Opt) ->
quicer:setopt(Socket, Opt, true)
end, Opts),
ok.
getopts(_Socket, _Opts) ->
%% @todo
{ok, [{high_watermark, 0},
{high_msgq_watermark, 0},
{sndbuf, 0},
{recbuf, 0},
{buffer,80000}]}.
fast_close(Stream) ->
%% Stream might be closed already.
_ = quicer:async_close_stream(Stream),
ok.
-spec(ensure_ok_or_exit(atom(), list(term())) -> term()).
ensure_ok_or_exit(Fun, Args = [Sock|_]) when is_atom(Fun), is_list(Args) ->
case erlang:apply(?MODULE, Fun, Args) of
{error, Reason} when Reason =:= enotconn; Reason =:= closed ->
fast_close(Sock),
exit(normal);
{error, Reason} ->
fast_close(Sock),
exit({shutdown, Reason});
Result -> Result
end.
async_send(Stream, Data, Options) when is_list(Data) ->
async_send(Stream, iolist_to_binary(Data), Options);
async_send(Stream, Data, _Options) when is_binary(Data) ->
{ok, _Len} = quicer:send(Stream, Data),
ok.

View File

@ -59,8 +59,8 @@
-export([includes/0]).
structs() -> ["cluster", "node", "rpc", "log", "lager",
"zones", "listeners", "module", "broker",
"plugins", "sysmon", "alarm", "telemetry"]
"zones", "listeners", "broker",
"plugins", "sysmon", "alarm"]
++ includes().
-ifdef(TEST).
@ -69,6 +69,14 @@ includes() ->[].
includes() ->
[ "emqx_data_bridge"
, "emqx_telemetry"
, "emqx_retainer"
, "emqx_statsd"
, "emqx_authn"
, "emqx_authz"
, "emqx_bridge_mqtt"
, "emqx_modules"
, "emqx_management"
, "emqx_gateway"
].
-endif.
@ -345,6 +353,7 @@ fields("listeners") ->
[ {"$name", hoconsc:union(
[ hoconsc:ref("mqtt_tcp_listener")
, hoconsc:ref("mqtt_ws_listener")
, hoconsc:ref("mqtt_quic_listener")
])}
];
@ -361,6 +370,10 @@ fields("mqtt_ws_listener") ->
, {"websocket", ref("ws_opts")}
] ++ mqtt_listener();
fields("mqtt_quic_listener") ->
[ {"type", t(quic)}
] ++ base_listener();
fields("ws_opts") ->
[ {"mqtt_path", t(string(), undefined, "/mqtt")}
, {"mqtt_piggyback", t(union(single, multiple), undefined, multiple)}
@ -409,12 +422,6 @@ fields("deflate_opts") ->
, {"client_max_window_bits", t(range(8, 15), undefined, 15)}
];
fields("module") ->
[ {"loaded_file", t(string(), "emqx.modules_loaded_file", undefined)}
, {"presence", ref("presence")}
, {"subscription", ref("subscription")}
, {"rewrite", ref("rewrite")}
];
fields("presence") ->
[ {"qos", t(range(0, 2), undefined, 1)}];
@ -440,9 +447,7 @@ fields("rule") ->
[ {"$id", t(string())}];
fields("plugins") ->
[ {"etc_dir", t(string(), "emqx.plugins_etc_dir", undefined)}
, {"loaded_file", t(string(), "emqx.plugins_loaded_file", undefined)}
, {"expand_plugins_dir", t(string(), "emqx.expand_plugins_dir", undefined)}
[ {"expand_plugins_dir", t(string(), "emqx.expand_plugins_dir", undefined)}
];
fields("broker") ->
@ -492,24 +497,22 @@ fields("alarm") ->
, {"validity_period", t(duration_s(), undefined, "24h")}
];
fields("telemetry") ->
[ {"enabled", t(boolean(), undefined, false)}
, {"url", t(string(), undefined, "https://telemetry-emqx-io.bigpar.vercel.app/api/telemetry")}
, {"report_interval", t(duration_s(), undefined, "7d")}
];
fields(ExtraField) ->
Mod = list_to_atom(ExtraField++"_schema"),
Mod:fields(ExtraField).
mqtt_listener() ->
base_listener() ++
[ {"access_rules", t(hoconsc:array(string()))}
, {"proxy_protocol", t(boolean(), undefined, false)}
, {"proxy_protocol_timeout", t(duration())}
].
base_listener() ->
[ {"bind", t(union(ip_port(), integer()))}
, {"acceptors", t(integer(), undefined, 16)}
, {"max_connections", maybe_infinity(integer(), infinity)}
, {"rate_limit", ref("rate_limit")}
, {"access_rules", t(hoconsc:array(string()))}
, {"proxy_protocol", t(boolean(), undefined, false)}
, {"proxy_protocol_timeout", t(duration())}
].
translations() -> ["kernel"].

View File

@ -77,6 +77,8 @@
-define(NACK(Reason), {shared_sub_nack, Reason}).
-define(NO_ACK, no_ack).
-rlog_shard({?SHARED_SUB_SHARD, ?TAB}).
-record(state, {pmon}).
-record(emqx_shared_subscription, {group, topic, subpid}).
@ -297,7 +299,7 @@ subscribers(Group, Topic) ->
init([]) ->
{ok, _} = mnesia:subscribe({table, ?TAB, simple}),
{atomic, PMon} = mnesia:transaction(fun init_monitors/0),
{atomic, PMon} = ekka_mnesia:transaction(?SHARED_SUB_SHARD, fun init_monitors/0),
ok = emqx_tables:new(?SHARED_SUBS, [protected, bag]),
ok = emqx_tables:new(?ALIVE_SUBS, [protected, set, {read_concurrency, true}]),
{ok, update_stats(#state{pmon = PMon})}.
@ -309,7 +311,7 @@ init_monitors() ->
end, emqx_pmon:new(), ?TAB).
handle_call({subscribe, Group, Topic, SubPid}, _From, State = #state{pmon = PMon}) ->
mnesia:dirty_write(?TAB, record(Group, Topic, SubPid)),
ekka_mnesia:dirty_write(?TAB, record(Group, Topic, SubPid)),
case ets:member(?SHARED_SUBS, {Group, Topic}) of
true -> ok;
false -> ok = emqx_router:do_add_route(Topic, {Group, node()})
@ -319,7 +321,7 @@ handle_call({subscribe, Group, Topic, SubPid}, _From, State = #state{pmon = PMon
{reply, ok, update_stats(State#state{pmon = emqx_pmon:monitor(SubPid, PMon)})};
handle_call({unsubscribe, Group, Topic, SubPid}, _From, State) ->
mnesia:dirty_delete_object(?TAB, record(Group, Topic, SubPid)),
ekka_mnesia:dirty_delete_object(?TAB, record(Group, Topic, SubPid)),
true = ets:delete_object(?SHARED_SUBS, {{Group, Topic}, SubPid}),
delete_route_if_needed({Group, Topic}),
{reply, ok, State};
@ -336,9 +338,13 @@ handle_info({mnesia_table_event, {write, NewRecord, _}}, State = #state{pmon = P
#emqx_shared_subscription{subpid = SubPid} = NewRecord,
{noreply, update_stats(State#state{pmon = emqx_pmon:monitor(SubPid, PMon)})};
handle_info({mnesia_table_event, {delete_object, OldRecord, _}}, State = #state{pmon = PMon}) ->
#emqx_shared_subscription{subpid = SubPid} = OldRecord,
{noreply, update_stats(State#state{pmon = emqx_pmon:demonitor(SubPid, PMon)})};
%% The subscriber may have subscribed multiple topics, so we need to keep monitoring the PID until
%% it `unsubscribed` the last topic.
%% The trick is we don't demonitor the subscriber here, and (after a long time) it will eventually
%% be disconnected.
% handle_info({mnesia_table_event, {delete_object, OldRecord, _}}, State = #state{pmon = PMon}) ->
% #emqx_shared_subscription{subpid = SubPid} = OldRecord,
% {noreply, update_stats(State#state{pmon = emqx_pmon:demonitor(SubPid, PMon)})};
handle_info({mnesia_table_event, _Event}, State) ->
{noreply, State};
@ -348,8 +354,7 @@ handle_info({'DOWN', _MRef, process, SubPid, _Reason}, State = #state{pmon = PMo
cleanup_down(SubPid),
{noreply, update_stats(State#state{pmon = emqx_pmon:erase(SubPid, PMon)})};
handle_info(Info, State) ->
?LOG(error, "Unexpected info: ~p", [Info]),
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
@ -370,7 +375,7 @@ cleanup_down(SubPid) ->
?IS_LOCAL_PID(SubPid) orelse ets:delete(?ALIVE_SUBS, SubPid),
lists:foreach(
fun(Record = #emqx_shared_subscription{topic = Topic, group = Group}) ->
ok = mnesia:dirty_delete_object(?TAB, Record),
ok = ekka_mnesia:dirty_delete_object(?TAB, Record),
true = ets:delete_object(?SHARED_SUBS, {{Group, Topic}, SubPid}),
delete_route_if_needed({Group, Topic})
end, mnesia:dirty_match_object(#emqx_shared_subscription{_ = '_', subpid = SubPid})).

View File

@ -403,7 +403,10 @@ websocket_close(Reason, State) ->
terminate(Reason, _Req, #state{channel = Channel}) ->
?LOG(debug, "Terminated due to ~p", [Reason]),
emqx_channel:terminate(Reason, Channel).
emqx_channel:terminate(Reason, Channel);
terminate(_Reason, _Req, _UnExpectedState) ->
ok.
%%--------------------------------------------------------------------
%% Handle call

View File

@ -1,2 +1 @@
{emqx_mod_acl_internal, true}.
{emqx_mod_presence, true}.
{emqx_mod_presence, true}.

View File

@ -1,15 +0,0 @@
{allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]}.
{allow, {user, "testuser"}, subscribe, ["a/b/c", "d/e/f/#"]}.
{allow, {user, "admin"}, pubsub, ["a/b/c", "d/e/f/#"]}.
{allow, {client, "testClient"}, subscribe, ["testTopics/testClient"]}.
{allow, all, subscribe, ["clients/%c"]}.
{allow, all, pubsub, ["users/%u/#"]}.
{deny, all, subscribe, ["$SYS/#", "#"]}.
{deny, all}.

View File

@ -1,3 +0,0 @@
{deny, {user, "emqx"}, pubsub, ["acl_deny_action"]}.
{deny, {user, "pub_deny"}, publish, ["pub_deny"]}.
{allow, all}.

View File

@ -38,16 +38,9 @@ t_authenticate(_) ->
emqx_zone:set_env(zone, allow_anonymous, true),
?assertMatch({ok, _}, emqx_access_control:authenticate(clientinfo())).
t_check_acl(_) ->
emqx_zone:set_env(zone, acl_nomatch, deny),
application:set_env(emqx, enable_acl_cache, false),
t_authorize(_) ->
Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>),
?assertEqual(deny, emqx_access_control:check_acl(clientinfo(), Publish, <<"t">>)),
emqx_zone:set_env(zone, acl_nomatch, allow),
application:set_env(emqx, enable_acl_cache, true),
Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>),
?assertEqual(allow, emqx_access_control:check_acl(clientinfo(), Publish, <<"t">>)).
?assertEqual(allow, emqx_access_control:authorize(clientinfo(), Publish, <<"t">>)).
t_bypass_auth_plugins(_) ->
ClientInfo = clientinfo(),

View File

@ -1,97 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_access_rule_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("eunit/include/eunit.hrl").
all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) ->
emqx_ct_helpers:boot_modules([router, broker]),
emqx_ct_helpers:start_apps([]),
Config.
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
t_compile(_) ->
Rule1 = {allow, all, pubsub, <<"%u">>},
Compile1 = {allow, all, pubsub, [{pattern,[<<"%u">>]}]},
Rule2 = {allow, {ipaddr, "127.0.0.1"}, pubsub, <<"%c">>},
Compile2 = {allow, {ipaddr, {{127,0,0,1}, {127,0,0,1}, 32}}, pubsub, [{pattern,[<<"%c">>]}]},
Rule3 = {allow, {'and', [{client, <<"testClient">>}, {user, <<"testUser">>}]}, pubsub, [<<"testTopics1">>, <<"testTopics2">>]},
Compile3 = {allow, {'and', [{client, <<"testClient">>}, {user, <<"testUser">>}]}, pubsub, [[<<"testTopics1">>], [<<"testTopics2">>]]},
Rule4 = {allow, {'or', [{client, all}, {user, all}]}, pubsub, [ <<"testTopics1">>, <<"testTopics2">>]},
Compile4 = {allow, {'or', [{client, all}, {user, all}]}, pubsub, [[<<"testTopics1">>], [<<"testTopics2">>]]},
?assertEqual(Compile1, emqx_access_rule:compile(Rule1)),
?assertEqual(Compile2, emqx_access_rule:compile(Rule2)),
?assertEqual(Compile3, emqx_access_rule:compile(Rule3)),
?assertEqual(Compile4, emqx_access_rule:compile(Rule4)).
t_match(_) ->
ClientInfo1 = #{zone => external,
clientid => <<"testClient">>,
username => <<"TestUser">>,
peerhost => {127,0,0,1}
},
ClientInfo2 = #{zone => external,
clientid => <<"testClient">>,
username => <<"TestUser">>,
peerhost => {192,168,0,10}
},
ClientInfo3 = #{zone => external,
clientid => <<"testClient">>,
username => <<"TestUser">>,
peerhost => undefined
},
?assertEqual({matched, deny}, emqx_access_rule:match([], [], {deny, all})),
?assertEqual({matched, allow}, emqx_access_rule:match([], [], {allow, all})),
?assertEqual(nomatch, emqx_access_rule:match(ClientInfo1, <<"Test/Topic">>,
emqx_access_rule:compile({allow, {user, all}, pubsub, []}))),
?assertEqual({matched, allow}, emqx_access_rule:match(ClientInfo1, <<"Test/Topic">>,
emqx_access_rule:compile({allow, {client, all}, pubsub, ["$SYS/#", "#"]}))),
?assertEqual(nomatch, emqx_access_rule:match(ClientInfo3, <<"Test/Topic">>,
emqx_access_rule:compile({allow, {ipaddr, "127.0.0.1"}, pubsub, ["$SYS/#", "#"]}))),
?assertEqual({matched, allow}, emqx_access_rule:match(ClientInfo1, <<"Test/Topic">>,
emqx_access_rule:compile({allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]}))),
?assertEqual({matched, allow}, emqx_access_rule:match(ClientInfo2, <<"Test/Topic">>,
emqx_access_rule:compile({allow, {ipaddr, "192.168.0.1/24"}, subscribe, ["$SYS/#", "#"]}))),
?assertEqual({matched, allow}, emqx_access_rule:match(ClientInfo1, <<"d/e/f/x">>,
emqx_access_rule:compile({allow, {user, "TestUser"}, subscribe, ["a/b/c", "d/e/f/#"]}))),
?assertEqual(nomatch, emqx_access_rule:match(ClientInfo1, <<"d/e/f/x">>,
emqx_access_rule:compile({allow, {user, "admin"}, pubsub, ["d/e/f/#"]}))),
?assertEqual({matched, allow}, emqx_access_rule:match(ClientInfo1, <<"testTopics/testClient">>,
emqx_access_rule:compile({allow, {client, "testClient"}, publish, ["testTopics/testClient"]}))),
?assertEqual({matched, allow}, emqx_access_rule:match(ClientInfo1, <<"clients/testClient">>,
emqx_access_rule:compile({allow, all, pubsub, ["clients/%c"]}))),
?assertEqual({matched, allow}, emqx_access_rule:match(#{username => <<"user2">>}, <<"users/user2/abc/def">>,
emqx_access_rule:compile({allow, all, subscribe, ["users/%u/#"]}))),
?assertEqual({matched, deny}, emqx_access_rule:match(ClientInfo1, <<"d/e/f">>,
emqx_access_rule:compile({deny, all, subscribe, ["$SYS/#", "#"]}))),
?assertEqual(nomatch, emqx_access_rule:match(ClientInfo1, <<"Topic">>,
emqx_access_rule:compile({allow, {'and', [{ipaddr, "127.0.0.1"}, {user, <<"WrongUser">>}]}, publish, <<"Topic">>}))),
?assertEqual({matched, allow}, emqx_access_rule:match(ClientInfo1, <<"Topic">>,
emqx_access_rule:compile({allow, {'and', [{ipaddr, "127.0.0.1"}, {user, <<"TestUser">>}]}, publish, <<"Topic">>}))),
?assertEqual({matched, allow}, emqx_access_rule:match(ClientInfo1, <<"Topic">>,
emqx_access_rule:compile({allow, {'or', [{ipaddr, "127.0.0.1"}, {user, <<"WrongUser">>}]}, publish, ["Topic"]}))).

View File

@ -18,14 +18,14 @@
%% ACL callbacks
-export([ init/1
, check_acl/2
, authorize/2
, description/0
]).
init(AclOpts) ->
{ok, AclOpts}.
check_acl({_User, _PubSub, _Topic}, _State) ->
authorize({_User, _PubSub, _Topic}, _State) ->
allow.
description() ->

View File

@ -37,7 +37,7 @@ init_per_suite(Config) ->
ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]),
ok = meck:expect(emqx_access_control, authenticate,
fun(_) -> {ok, #{auth_result => success}} end),
ok = meck:expect(emqx_access_control, check_acl, fun(_, _, _) -> allow end),
ok = meck:expect(emqx_access_control, authorize, fun(_, _, _) -> allow end),
%% Broker Meck
ok = meck:new(emqx_broker, [passthrough, no_history, no_link]),
%% Hooks Meck

View File

@ -28,6 +28,7 @@ all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) ->
NewConfig = generate_config(),
application:ensure_all_started(esockd),
application:ensure_all_started(quicer),
application:ensure_all_started(cowboy),
lists:foreach(fun set_app_env/1, NewConfig),
Config.

View File

@ -23,6 +23,7 @@
-include_lib("emqx/include/emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
-include_lib("common_test/include/ct.hrl").
-import(lists, [nth/2]).
@ -32,18 +33,37 @@
-define(WILD_TOPICS, [<<"TopicA/+">>, <<"+/C">>, <<"#">>, <<"/#">>, <<"/+">>,
<<"+/+">>, <<"TopicA/#">>]).
all() -> emqx_ct:all(?MODULE).
all() ->
[ {group, tcp}
, {group, quic}
].
groups() ->
TCs = emqx_ct:all(?MODULE),
[ {tcp, [], TCs}
, {quic, [], TCs}
].
init_per_group(tcp, Config) ->
emqx_ct_helpers:start_apps([]),
[ {port, 1883}, {conn_fun, connect} | Config];
init_per_group(quic, Config) ->
emqx_ct_helpers:start_apps([]),
[ {port, 14567}, {conn_fun, quic_connect} | Config];
init_per_group(_, Config) ->
emqx_ct_helpers:stop_apps([]),
Config.
end_per_group(_Group, _Config) ->
ok.
init_per_suite(Config) ->
%% Meck emqtt
ok = meck:new(emqtt, [non_strict, passthrough, no_history, no_link]),
%% Start Apps
emqx_ct_helpers:boot_modules(all),
emqx_ct_helpers:start_apps([]),
Config.
end_per_suite(_Config) ->
ok = meck:unload(emqtt),
emqx_ct_helpers:stop_apps([]).
init_per_testcase(TestCase, Config) ->
@ -97,9 +117,10 @@ waiting_client_process_exit(C) ->
1000 -> error({waiting_timeout, C})
end.
clean_retained(Topic) ->
{ok, Clean} = emqtt:start_link([{clean_start, true}]),
{ok, _} = emqtt:connect(Clean),
clean_retained(Topic, Config) ->
ConnFun = ?config(conn_fun, Config),
{ok, Clean} = emqtt:start_link([{clean_start, true} | Config]),
{ok, _} = emqtt:ConnFun(Clean),
{ok, _} = emqtt:publish(Clean, Topic, #{}, <<"">>, [{qos, ?QOS_1}, {retain, true}]),
ok = emqtt:disconnect(Clean).
@ -107,11 +128,12 @@ clean_retained(Topic) ->
%% Test Cases
%%--------------------------------------------------------------------
t_basic_test(_) ->
t_basic_test(Config) ->
ConnFun = ?config(conn_fun, Config),
Topic = nth(1, ?TOPICS),
ct:print("Basic test starting"),
{ok, C} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(C),
{ok, C} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(C),
{ok, _, [1]} = emqtt:subscribe(C, Topic, qos1),
{ok, _, [2]} = emqtt:subscribe(C, Topic, qos2),
{ok, _} = emqtt:publish(C, Topic, <<"qos 2">>, 2),
@ -124,16 +146,17 @@ t_basic_test(_) ->
%% Connection
%%--------------------------------------------------------------------
t_connect_clean_start(_) ->
t_connect_clean_start(Config) ->
ConnFun = ?config(conn_fun, Config),
process_flag(trap_exit, true),
{ok, Client1} = emqtt:start_link([{clientid, <<"t_connect_clean_start">>},
{proto_ver, v5},{clean_start, true}]),
{ok, _} = emqtt:connect(Client1),
{proto_ver, v5},{clean_start, true} | Config]),
{ok, _} = emqtt:ConnFun(Client1),
?assertEqual(0, client_info(session_present, Client1)), %% [MQTT-3.1.2-4]
ok = emqtt:pause(Client1),
{ok, Client2} = emqtt:start_link([{clientid, <<"t_connect_clean_start">>},
{proto_ver, v5},{clean_start, false}]),
{ok, _} = emqtt:connect(Client2),
{proto_ver, v5},{clean_start, false} | Config]),
{ok, _} = emqtt:ConnFun(Client2),
?assertEqual(1, client_info(session_present, Client2)), %% [MQTT-3.1.2-5]
?assertEqual(142, receive_disconnect_reasoncode()),
waiting_client_process_exit(Client1),
@ -142,32 +165,32 @@ t_connect_clean_start(_) ->
waiting_client_process_exit(Client2),
{ok, Client3} = emqtt:start_link([{clientid, <<"new_client">>},
{proto_ver, v5},{clean_start, false}]),
{ok, _} = emqtt:connect(Client3),
{proto_ver, v5},{clean_start, false} | Config]),
{ok, _} = emqtt:ConnFun(Client3),
?assertEqual(0, client_info(session_present, Client3)), %% [MQTT-3.1.2-6]
ok = emqtt:disconnect(Client3),
waiting_client_process_exit(Client3),
process_flag(trap_exit, false).
t_connect_will_message(_) ->
t_connect_will_message(Config) ->
ConnFun = ?config(conn_fun, Config),
Topic = nth(1, ?TOPICS),
Payload = "will message",
{ok, Client1} = emqtt:start_link([
{proto_ver, v5},
{clean_start, true},
{will_flag, true},
{will_topic, Topic},
{will_payload, Payload}
]),
{ok, _} = emqtt:connect(Client1),
{ok, Client1} = emqtt:start_link([ {proto_ver, v5},
{clean_start, true},
{will_flag, true},
{will_topic, Topic},
{will_payload, Payload} | Config
]),
{ok, _} = emqtt:ConnFun(Client1),
[ClientPid] = emqx_cm:lookup_channels(client_info(clientid, Client1)),
Info = emqx_connection:info(sys:get_state(ClientPid)),
?assertNotEqual(undefined, maps:find(will_msg, Info)), %% [MQTT-3.1.2-7]
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client2),
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client2),
{ok, _, [2]} = emqtt:subscribe(Client2, Topic, qos2),
ok = emqtt:disconnect(Client1, 4), %% [MQTT-3.14.2-1]
@ -178,27 +201,32 @@ t_connect_will_message(_) ->
?assertEqual({ok, 0}, maps:find(qos, Msg)),
ok = emqtt:disconnect(Client2),
{ok, Client3} = emqtt:start_link([
{proto_ver, v5},
{clean_start, true},
{will_flag, true},
{will_topic, Topic},
{will_payload, Payload}
]),
{ok, _} = emqtt:connect(Client3),
{ok, Client3} = emqtt:start_link([ {proto_ver, v5},
{clean_start, true},
{will_flag, true},
{will_topic, Topic},
{will_payload, Payload} | Config
]),
{ok, _} = emqtt:ConnFun(Client3),
{ok, Client4} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client4),
{ok, Client4} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client4),
{ok, _, [2]} = emqtt:subscribe(Client4, Topic, qos2),
ok = emqtt:disconnect(Client3),
?assertEqual(0, length(receive_messages(1))), %% [MQTT-3.1.2-10]
ok = emqtt:disconnect(Client4).
t_batch_subscribe(_) ->
{ok, Client} = emqtt:start_link([{proto_ver, v5}, {clientid, <<"batch_test">>}]),
{ok, _} = emqtt:connect(Client),
application:set_env(emqx, enable_acl_cache, false),
application:set_env(emqx, acl_nomatch, deny),
t_batch_subscribe(init, Config) ->
ok = meck:new(emqx_access_control, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_access_control, authorize, fun(_, _, _) -> deny end),
Config;
t_batch_subscribe('end', _Config) ->
meck:unload(emqx_access_control).
t_batch_subscribe(Config) ->
ConnFun = ?config(conn_fun, Config),
{ok, Client} = emqtt:start_link([{proto_ver, v5}, {clientid, <<"batch_test">>} | Config]),
{ok, _} = emqtt:ConnFun(Client),
{ok, _, [?RC_NOT_AUTHORIZED,
?RC_NOT_AUTHORIZED,
?RC_NOT_AUTHORIZED]} = emqtt:subscribe(Client, [{<<"t1">>, qos1},
@ -209,25 +237,25 @@ t_batch_subscribe(_) ->
?RC_NO_SUBSCRIPTION_EXISTED]} = emqtt:unsubscribe(Client, [<<"t1">>,
<<"t2">>,
<<"t3">>]),
application:set_env(emqx, acl_nomatch, allow),
emqtt:disconnect(Client).
t_connect_will_retain(_) ->
t_connect_will_retain(Config) ->
ConnFun = ?config(conn_fun, Config),
process_flag(trap_exit, true),
Topic = nth(1, ?TOPICS),
Payload = "will message",
{ok, Client1} = emqtt:start_link([
{proto_ver, v5},
{clean_start, true},
{will_flag, true},
{will_topic, Topic},
{will_payload, Payload},
{will_retain, false}
]),
{ok, _} = emqtt:connect(Client1),
{ok, Client1} = emqtt:start_link([ {proto_ver, v5},
{clean_start, true},
{will_flag, true},
{will_topic, Topic},
{will_payload, Payload},
{will_retain, false} | Config
]),
{ok, _} = emqtt:ConnFun(Client1),
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client2),
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client2),
{ok, _, [2]} = emqtt:subscribe(Client2, #{}, [{Topic, [{rap, true}, {qos, 2}]}]),
ok = emqtt:disconnect(Client1, 4),
@ -235,27 +263,26 @@ t_connect_will_retain(_) ->
?assertEqual({ok, false}, maps:find(retain, Msg1)), %% [MQTT-3.1.2-14]
ok = emqtt:disconnect(Client2),
{ok, Client3} = emqtt:start_link([
{proto_ver, v5},
{clean_start, true},
{will_flag, true},
{will_topic, Topic},
{will_payload, Payload},
{will_retain, true}
]),
{ok, _} = emqtt:connect(Client3),
{ok, Client3} = emqtt:start_link([ {proto_ver, v5},
{clean_start, true},
{will_flag, true},
{will_topic, Topic},
{will_payload, Payload},
{will_retain, true} | Config
]),
{ok, _} = emqtt:ConnFun(Client3),
{ok, Client4} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client4),
{ok, Client4} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client4),
{ok, _, [2]} = emqtt:subscribe(Client4, #{}, [{Topic, [{rap, true}, {qos, 2}]}]),
ok = emqtt:disconnect(Client3, 4),
[Msg2 | _ ] = receive_messages(1),
?assertEqual({ok, true}, maps:find(retain, Msg2)), %% [MQTT-3.1.2-15]
ok = emqtt:disconnect(Client4),
clean_retained(Topic).
clean_retained(Topic, Config).
t_connect_idle_timeout(_) ->
t_connect_idle_timeout(_Config) ->
IdleTimeout = 2000,
emqx_zone:set_env(external, idle_timeout, IdleTimeout),
@ -263,25 +290,30 @@ t_connect_idle_timeout(_) ->
timer:sleep(IdleTimeout),
?assertMatch({error, closed}, emqtt_sock:recv(Sock,1024)).
t_connect_limit_timeout(_) ->
t_connect_limit_timeout(init, Config) ->
ok = meck:new(proplists, [non_strict, passthrough, no_history, no_link, unstick]),
meck:expect(proplists, get_value, fun(active_n, _Options, _Default) -> 1;
(Arg1, ARg2, Arg3) -> meck:passthrough([Arg1, ARg2, Arg3])
end),
Config;
t_connect_limit_timeout('end', _Config) ->
catch meck:unload(proplists).
t_connect_limit_timeout(Config) ->
ConnFun = ?config(conn_fun, Config),
Topic = nth(1, ?TOPICS),
emqx_zone:set_env(external, publish_limit, {3, 5}),
{ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60}]),
{ok, _} = emqtt:connect(Client),
{ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60} | Config]),
{ok, _} = emqtt:ConnFun(Client),
[ClientPid] = emqx_cm:lookup_channels(client_info(clientid, Client)),
?assertEqual(undefined, emqx_connection:info(limit_timer, sys:get_state(ClientPid))),
Payload = <<"t_shared_subscriptions_client_terminates_when_qos_eq_2">>,
ok = emqtt:publish(Client, Topic, Payload, 0),
ok = emqtt:publish(Client, Topic, Payload, 0),
ok = emqtt:publish(Client, Topic, Payload, 0),
timer:sleep(200),
{ok, 2} = emqtt:publish(Client, Topic, Payload, 1),
{ok, 3} = emqtt:publish(Client, Topic, Payload, 1),
{ok, 4} = emqtt:publish(Client, Topic, Payload, 1),
timer:sleep(250),
?assert(is_reference(emqx_connection:info(limit_timer, sys:get_state(ClientPid)))),
ok = emqtt:disconnect(Client),
@ -301,9 +333,10 @@ t_connect_emit_stats_timeout('end', Config) ->
ok.
t_connect_emit_stats_timeout(Config) ->
ConnFun = ?config(conn_fun, Config),
{_, IdleTimeout} = lists:keyfind(idle_timeout, 1, Config),
{ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60}]),
{ok, _} = emqtt:connect(Client),
{ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60} | Config]),
{ok, _} = emqtt:ConnFun(Client),
[ClientPid] = emqx_cm:lookup_channels(client_info(clientid, Client)),
?assert(is_reference(emqx_connection:info(stats_timer, sys:get_state(ClientPid)))),
?block_until(#{?snk_kind := cancel_stats_timer}, IdleTimeout * 2, _BackInTime = 0),
@ -311,15 +344,16 @@ t_connect_emit_stats_timeout(Config) ->
ok = emqtt:disconnect(Client).
%% [MQTT-3.1.2-22]
t_connect_keepalive_timeout(_) ->
t_connect_keepalive_timeout(Config) ->
ConnFun = ?config(conn_fun, Config),
%% Prevent the emqtt client bringing us down on the disconnect.
process_flag(trap_exit, true),
Keepalive = 2,
{ok, Client} = emqtt:start_link([{proto_ver, v5},
{keepalive, Keepalive}]),
{ok, _} = emqtt:connect(Client),
{keepalive, Keepalive} | Config]),
{ok, _} = emqtt:ConnFun(Client),
emqtt:pause(Client),
receive
{disconnected, ReasonCode, _Channel} -> ?assertEqual(141, ReasonCode)
@ -328,30 +362,30 @@ t_connect_keepalive_timeout(_) ->
end.
%% [MQTT-3.1.2-23]
t_connect_session_expiry_interval(_) ->
t_connect_session_expiry_interval(Config) ->
ConnFun = ?config(conn_fun, Config),
Topic = nth(1, ?TOPICS),
Payload = "test message",
{ok, Client1} = emqtt:start_link([
{clientid, <<"t_connect_session_expiry_interval">>},
{proto_ver, v5},
{properties, #{'Session-Expiry-Interval' => 7200}}
{ok, Client1} = emqtt:start_link([ {clientid, <<"t_connect_session_expiry_interval">>},
{proto_ver, v5},
{properties, #{'Session-Expiry-Interval' => 7200}}
| Config
]),
{ok, _} = emqtt:connect(Client1),
{ok, _} = emqtt:ConnFun(Client1),
{ok, _, [2]} = emqtt:subscribe(Client1, Topic, qos2),
ok = emqtt:disconnect(Client1),
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client2),
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client2),
{ok, 2} = emqtt:publish(Client2, Topic, Payload, 2),
ok = emqtt:disconnect(Client2),
{ok, Client3} = emqtt:start_link([
{clientid, <<"t_connect_session_expiry_interval">>},
{proto_ver, v5},
{clean_start, false}
{ok, Client3} = emqtt:start_link([ {clientid, <<"t_connect_session_expiry_interval">>},
{proto_ver, v5},
{clean_start, false} | Config
]),
{ok, _} = emqtt:connect(Client3),
{ok, _} = emqtt:ConnFun(Client3),
[Msg | _ ] = receive_messages(1),
?assertEqual({ok, iolist_to_binary(Topic)}, maps:find(topic, Msg)),
?assertEqual({ok, iolist_to_binary(Payload)}, maps:find(payload, Msg)),
@ -360,13 +394,13 @@ t_connect_session_expiry_interval(_) ->
%% [MQTT-3.1.3-9]
%% !!!REFACTOR NEED:
%t_connect_will_delay_interval(_) ->
%t_connect_will_delay_interval(Config) ->
% process_flag(trap_exit, true),
% Topic = nth(1, ?TOPICS),
% Payload = "will message",
%
% {ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
% {ok, _} = emqtt:connect(Client1),
% {ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
% {ok, _} = emqtt:ConnFun(Client1),
% {ok, _, [2]} = emqtt:subscribe(Client1, Topic, qos2),
%
% {ok, Client2} = emqtt:start_link([
@ -379,9 +413,9 @@ t_connect_session_expiry_interval(_) ->
% {will_payload, Payload},
% {will_props, #{'Will-Delay-Interval' => 3}},
% {properties, #{'Session-Expiry-Interval' => 7200}},
% {keepalive, 2}
% {keepalive, 2} | Config
% ]),
% {ok, _} = emqtt:connect(Client2),
% {ok, _} = emqtt:ConnFun(Client2),
% timer:sleep(50),
% erlang:exit(Client2, kill),
% timer:sleep(2000),
@ -399,9 +433,9 @@ t_connect_session_expiry_interval(_) ->
% {will_payload, Payload},
% {will_props, #{'Will-Delay-Interval' => 7200}},
% {properties, #{'Session-Expiry-Interval' => 3}},
% {keepalive, 2}
% {keepalive, 2} | Config
% ]),
% {ok, _} = emqtt:connect(Client3),
% {ok, _} = emqtt:ConnFun(Client3),
% timer:sleep(50),
% erlang:exit(Client3, kill),
%
@ -418,18 +452,17 @@ t_connect_session_expiry_interval(_) ->
% process_flag(trap_exit, false).
%% [MQTT-3.1.4-3]
t_connect_duplicate_clientid(_) ->
t_connect_duplicate_clientid(Config) ->
ConnFun = ?config(conn_fun, Config),
process_flag(trap_exit, true),
{ok, Client1} = emqtt:start_link([
{clientid, <<"t_connect_duplicate_clientid">>},
{proto_ver, v5}
]),
{ok, _} = emqtt:connect(Client1),
{ok, Client2} = emqtt:start_link([
{clientid, <<"t_connect_duplicate_clientid">>},
{proto_ver, v5}
]),
{ok, _} = emqtt:connect(Client2),
{ok, Client1} = emqtt:start_link([ {clientid, <<"t_connect_duplicate_clientid">>},
{proto_ver, v5} | Config
]),
{ok, _} = emqtt:ConnFun(Client1),
{ok, Client2} = emqtt:start_link([ {clientid, <<"t_connect_duplicate_clientid">>},
{proto_ver, v5} | Config
]),
{ok, _} = emqtt:ConnFun(Client2),
?assertEqual(142, receive_disconnect_reasoncode()),
waiting_client_process_exit(Client1),
@ -441,28 +474,33 @@ t_connect_duplicate_clientid(_) ->
%% Connack
%%--------------------------------------------------------------------
t_connack_session_present(_) ->
{ok, Client1} = emqtt:start_link([
{clientid, <<"t_connect_duplicate_clientid">>},
{proto_ver, v5},
{properties, #{'Session-Expiry-Interval' => 7200}},
{clean_start, true}
]),
{ok, _} = emqtt:connect(Client1),
t_connack_session_present(Config) ->
ConnFun = ?config(conn_fun, Config),
{ok, Client1} = emqtt:start_link([ {clientid, <<"t_connect_duplicate_clientid">>},
{proto_ver, v5},
{properties, #{'Session-Expiry-Interval' => 7200}},
{clean_start, true} | Config
]),
{ok, _} = emqtt:ConnFun(Client1),
?assertEqual(0, client_info(session_present, Client1)), %% [MQTT-3.2.2-2]
ok = emqtt:disconnect(Client1),
{ok, Client2} = emqtt:start_link([
{clientid, <<"t_connect_duplicate_clientid">>},
{proto_ver, v5},
{properties, #{'Session-Expiry-Interval' => 7200}},
{clean_start, false}
]),
{ok, _} = emqtt:connect(Client2),
{ok, Client2} = emqtt:start_link([ {clientid, <<"t_connect_duplicate_clientid">>},
{proto_ver, v5},
{properties, #{'Session-Expiry-Interval' => 7200}},
{clean_start, false} | Config
]),
{ok, _} = emqtt:ConnFun(Client2),
?assertEqual(1, client_info(session_present, Client2)), %% [[MQTT-3.2.2-3]]
ok = emqtt:disconnect(Client2).
t_connack_max_qos_allowed(_) ->
t_connack_max_qos_allowed(init, Config) ->
Config;
t_connack_max_qos_allowed('end', _Config) ->
emqx_zone:set_env(external, max_qos_allowed, 2),
ok.
t_connack_max_qos_allowed(Config) ->
ConnFun = ?config(conn_fun, Config),
process_flag(trap_exit, true),
Topic = nth(1, ?TOPICS),
@ -471,8 +509,8 @@ t_connack_max_qos_allowed(_) ->
persistent_term:erase({emqx_zone, external, '$mqtt_caps'}),
persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}),
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
{ok, Connack1} = emqtt:connect(Client1),
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, Connack1} = emqtt:ConnFun(Client1),
?assertEqual(0, maps:get('Maximum-QoS', Connack1)), %% [MQTT-3.2.2-9]
{ok, _, [0]} = emqtt:subscribe(Client1, Topic, 0), %% [MQTT-3.2.2-10]
@ -483,14 +521,13 @@ t_connack_max_qos_allowed(_) ->
?assertEqual(155, receive_disconnect_reasoncode()), %% [MQTT-3.2.2-11]
waiting_client_process_exit(Client1),
{ok, Client2} = emqtt:start_link([
{proto_ver, v5},
{will_flag, true},
{will_topic, Topic},
{will_payload, <<"Unsupported Qos">>},
{will_qos, 2}
]),
{error, Connack2} = emqtt:connect(Client2),
{ok, Client2} = emqtt:start_link([ {proto_ver, v5},
{will_flag, true},
{will_topic, Topic},
{will_payload, <<"Unsupported Qos">>},
{will_qos, 2} | Config
]),
{error, Connack2} = emqtt:ConnFun(Client2),
?assertMatch({qos_not_supported, _}, Connack2), %% [MQTT-3.2.2-12]
waiting_client_process_exit(Client2),
@ -499,8 +536,8 @@ t_connack_max_qos_allowed(_) ->
persistent_term:erase({emqx_zone, external, '$mqtt_caps'}),
persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}),
{ok, Client3} = emqtt:start_link([{proto_ver, v5}]),
{ok, Connack3} = emqtt:connect(Client3),
{ok, Client3} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, Connack3} = emqtt:ConnFun(Client3),
?assertEqual(1, maps:get('Maximum-QoS', Connack3)), %% [MQTT-3.2.2-9]
{ok, _, [0]} = emqtt:subscribe(Client3, Topic, 0), %% [MQTT-3.2.2-10]
@ -511,14 +548,13 @@ t_connack_max_qos_allowed(_) ->
?assertEqual(155, receive_disconnect_reasoncode()), %% [MQTT-3.2.2-11]
waiting_client_process_exit(Client3),
{ok, Client4} = emqtt:start_link([
{proto_ver, v5},
{will_flag, true},
{will_topic, Topic},
{will_payload, <<"Unsupported Qos">>},
{will_qos, 2}
]),
{error, Connack4} = emqtt:connect(Client4),
{ok, Client4} = emqtt:start_link([ {proto_ver, v5},
{will_flag, true},
{will_topic, Topic},
{will_payload, <<"Unsupported Qos">>},
{will_qos, 2} | Config
]),
{error, Connack4} = emqtt:ConnFun(Client4),
?assertMatch({qos_not_supported, _}, Connack4), %% [MQTT-3.2.2-12]
waiting_client_process_exit(Client4),
@ -527,17 +563,18 @@ t_connack_max_qos_allowed(_) ->
persistent_term:erase({emqx_zone, external, '$mqtt_caps'}),
persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}),
{ok, Client5} = emqtt:start_link([{proto_ver, v5}]),
{ok, Connack5} = emqtt:connect(Client5),
{ok, Client5} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, Connack5} = emqtt:ConnFun(Client5),
?assertEqual(undefined, maps:get('Maximum-QoS', Connack5, undefined)), %% [MQTT-3.2.2-9]
ok = emqtt:disconnect(Client5),
waiting_client_process_exit(Client5),
process_flag(trap_exit, false).
t_connack_assigned_clienid(_) ->
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client1),
t_connack_assigned_clienid(Config) ->
ConnFun = ?config(conn_fun, Config),
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client1),
?assert(is_binary(client_info(clientid, Client1))), %% [MQTT-3.2.2-16]
ok = emqtt:disconnect(Client1).
@ -545,11 +582,12 @@ t_connack_assigned_clienid(_) ->
%% Publish
%%--------------------------------------------------------------------
t_publish_rap(_) ->
t_publish_rap(Config) ->
ConnFun = ?config(conn_fun, Config),
Topic = nth(1, ?TOPICS),
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client1),
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client1),
{ok, _, [2]} = emqtt:subscribe(Client1, #{}, [{Topic, [{rap, true}, {qos, 2}]}]),
{ok, _} = emqtt:publish(Client1, Topic, #{}, <<"retained message">>,
[{qos, ?QOS_1}, {retain, true}]),
@ -557,8 +595,8 @@ t_publish_rap(_) ->
?assertEqual(true, maps:get(retain, Msg1)), %% [MQTT-3.3.1-12]
ok = emqtt:disconnect(Client1),
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client2),
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client2),
{ok, _, [2]} = emqtt:subscribe(Client2, #{}, [{Topic, [{rap, false}, {qos, 2}]}]),
{ok, _} = emqtt:publish(Client2, Topic, #{}, <<"retained message">>,
[{qos, ?QOS_1}, {retain, true}]),
@ -566,44 +604,47 @@ t_publish_rap(_) ->
?assertEqual(false, maps:get(retain, Msg2)), %% [MQTT-3.3.1-13]
ok = emqtt:disconnect(Client2),
clean_retained(Topic).
clean_retained(Topic, Config).
t_publish_wildtopic(_) ->
t_publish_wildtopic(Config) ->
ConnFun = ?config(conn_fun, Config),
process_flag(trap_exit, true),
Topic = nth(1, ?WILD_TOPICS),
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client1),
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client1),
ok = emqtt:publish(Client1, Topic, <<"error topic">>),
?assertEqual(144, receive_disconnect_reasoncode()),
waiting_client_process_exit(Client1),
process_flag(trap_exit, false).
t_publish_payload_format_indicator(_) ->
t_publish_payload_format_indicator(Config) ->
ConnFun = ?config(conn_fun, Config),
Topic = nth(1, ?TOPICS),
Properties = #{'Payload-Format-Indicator' => 233},
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client1),
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client1),
{ok, _, [2]} = emqtt:subscribe(Client1, Topic, qos2),
ok = emqtt:publish(Client1, Topic, Properties, <<"Payload Format Indicator">>, [{qos, ?QOS_0}]),
[Msg1 | _] = receive_messages(1),
?assertEqual(Properties, maps:get(properties, Msg1)), %% [MQTT-3.3.2-6]
ok = emqtt:disconnect(Client1).
t_publish_topic_alias(_) ->
t_publish_topic_alias(Config) ->
ConnFun = ?config(conn_fun, Config),
process_flag(trap_exit, true),
Topic = nth(1, ?TOPICS),
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client1),
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client1),
ok = emqtt:publish(Client1, Topic, #{'Topic-Alias' => 0}, <<"Topic-Alias">>, [{qos, ?QOS_0}]),
?assertEqual(148, receive_disconnect_reasoncode()), %% [MQTT-3.3.2-8]
waiting_client_process_exit(Client1),
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client2),
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client2),
{ok, _, [2]} = emqtt:subscribe(Client2, Topic, qos2),
ok = emqtt:publish(Client2, Topic, #{'Topic-Alias' => 233},
<<"Topic-Alias">>, [{qos, ?QOS_0}]),
@ -615,12 +656,13 @@ t_publish_topic_alias(_) ->
process_flag(trap_exit, false).
t_publish_response_topic(_) ->
t_publish_response_topic(Config) ->
ConnFun = ?config(conn_fun, Config),
process_flag(trap_exit, true),
Topic = nth(1, ?TOPICS),
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client1),
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client1),
ok = emqtt:publish(Client1, Topic, #{'Response-Topic' => nth(1, ?WILD_TOPICS)},
<<"Response-Topic">>, [{qos, ?QOS_0}]),
?assertEqual(130, receive_disconnect_reasoncode()), %% [MQTT-3.3.2-14]
@ -628,7 +670,8 @@ t_publish_response_topic(_) ->
process_flag(trap_exit, false).
t_publish_properties(_) ->
t_publish_properties(Config) ->
ConnFun = ?config(conn_fun, Config),
Topic = nth(1, ?TOPICS),
Properties = #{
'Response-Topic' => Topic, %% [MQTT-3.3.2-15]
@ -637,20 +680,21 @@ t_publish_properties(_) ->
'Content-Type' => <<"2333">> %% [MQTT-3.3.2-20]
},
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client1),
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client1),
{ok, _, [2]} = emqtt:subscribe(Client1, Topic, qos2),
ok = emqtt:publish(Client1, Topic, Properties, <<"Publish Properties">>, [{qos, ?QOS_0}]),
[Msg1 | _] = receive_messages(1),
?assertEqual(Properties, maps:get(properties, Msg1)), %% [MQTT-3.3.2-16]
ok = emqtt:disconnect(Client1).
t_publish_overlapping_subscriptions(_) ->
t_publish_overlapping_subscriptions(Config) ->
ConnFun = ?config(conn_fun, Config),
Topic = nth(1, ?TOPICS),
Properties = #{'Subscription-Identifier' => 2333},
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client1),
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client1),
{ok, _, [1]} = emqtt:subscribe(Client1, Properties, nth(1, ?WILD_TOPICS), qos1),
{ok, _, [0]} = emqtt:subscribe(Client1, Properties, nth(3, ?WILD_TOPICS), qos0),
{ok, _} = emqtt:publish(Client1, Topic, #{},
@ -665,13 +709,15 @@ t_publish_overlapping_subscriptions(_) ->
%% Subsctibe
%%--------------------------------------------------------------------
t_subscribe_topic_alias(_) ->
t_subscribe_topic_alias(Config) ->
ConnFun = ?config(conn_fun, Config),
Topic1 = nth(1, ?TOPICS),
Topic2 = nth(2, ?TOPICS),
{ok, Client1} = emqtt:start_link([{proto_ver, v5},
{properties, #{'Topic-Alias-Maximum' => 1}}
{ok, Client1} = emqtt:start_link([ {proto_ver, v5},
{properties, #{'Topic-Alias-Maximum' => 1}}
| Config
]),
{ok, _} = emqtt:connect(Client1),
{ok, _} = emqtt:ConnFun(Client1),
{ok, _, [2]} = emqtt:subscribe(Client1, Topic1, qos2),
{ok, _, [2]} = emqtt:subscribe(Client1, Topic2, qos2),
@ -692,27 +738,29 @@ t_subscribe_topic_alias(_) ->
ok = emqtt:disconnect(Client1).
t_subscribe_no_local(_) ->
t_subscribe_no_local(Config) ->
ConnFun = ?config(conn_fun, Config),
Topic = nth(1, ?TOPICS),
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client1),
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client1),
{ok, _, [2]} = emqtt:subscribe(Client1, #{}, [{Topic, [{nl, true}, {qos, 2}]}]),
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client2),
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client2),
{ok, _, [2]} = emqtt:subscribe(Client2, #{}, [{Topic, [{nl, true}, {qos, 2}]}]),
ok = emqtt:publish(Client1, Topic, <<"t_subscribe_no_local">>, 0),
?assertEqual(1, length(receive_messages(2))), %% [MQTT-3.8.3-3]
ok = emqtt:disconnect(Client1).
t_subscribe_actions(_) ->
t_subscribe_actions(Config) ->
ConnFun = ?config(conn_fun, Config),
Topic = nth(1, ?TOPICS),
Properties = #{'Subscription-Identifier' => 2333},
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client1),
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client1),
{ok, _, [2]} = emqtt:subscribe(Client1, Properties, Topic, qos2),
{ok, _, [1]} = emqtt:subscribe(Client1, Properties, Topic, qos1),
{ok, _} = emqtt:publish(Client1, Topic, <<"t_subscribe_actions">>, 2),
@ -726,12 +774,13 @@ t_subscribe_actions(_) ->
%% Unsubsctibe Unsuback
%%--------------------------------------------------------------------
t_unscbsctibe(_) ->
t_unscbsctibe(Config) ->
ConnFun = ?config(conn_fun, Config),
Topic1 = nth(1, ?TOPICS),
Topic2 = nth(2, ?TOPICS),
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client1),
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client1),
{ok, _, [2]} = emqtt:subscribe(Client1, Topic1, qos2),
{ok, _, [0]} = emqtt:unsubscribe(Client1, Topic1), %% [MQTT-3.10.4-4]
{ok, _, [17]} = emqtt:unsubscribe(Client1, <<"noExistTopic">>), %% [MQTT-3.10.4-5]
@ -745,9 +794,10 @@ t_unscbsctibe(_) ->
%% Pingreq
%%--------------------------------------------------------------------
t_pingreq(_) ->
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
{ok, _} = emqtt:connect(Client1),
t_pingreq(Config) ->
ConnFun = ?config(conn_fun, Config),
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
{ok, _} = emqtt:ConnFun(Client1),
pong = emqtt:ping(Client1), %% [MQTT-3.12.4-1]
ok = emqtt:disconnect(Client1).
@ -755,7 +805,14 @@ t_pingreq(_) ->
%% Shared Subscriptions
%%--------------------------------------------------------------------
t_shared_subscriptions_client_terminates_when_qos_eq_2(_) ->
t_shared_subscriptions_client_terminates_when_qos_eq_2(init, Config) ->
ok = meck:new(emqtt, [non_strict, passthrough, no_history, no_link]),
Config;
t_shared_subscriptions_client_terminates_when_qos_eq_2('end', _Config) ->
catch meck:unload(emqtt).
t_shared_subscriptions_client_terminates_when_qos_eq_2(Config) ->
ConnFun = ?config(conn_fun, Config),
process_flag(trap_exit, true),
application:set_env(emqx, shared_dispatch_ack_enabled, true),
@ -766,32 +823,33 @@ t_shared_subscriptions_client_terminates_when_qos_eq_2(_) ->
meck:expect(emqtt, connected,
fun(cast, ?PUBLISH_PACKET(?QOS_2, _PacketId), _State) ->
ok = counters:add(CRef, 1, 1),
{stop, {shutdown, for_testiong}};
{stop, {shutdown, for_testing}};
(Arg1, ARg2, Arg3) -> meck:passthrough([Arg1, ARg2, Arg3])
end),
{ok, Sub1} = emqtt:start_link([{proto_ver, v5},
{ok, Sub1} = emqtt:start_link([ {proto_ver, v5},
{clientid, <<"sub_client_1">>},
{keepalive, 5}]),
{ok, _} = emqtt:connect(Sub1),
{keepalive, 5} | Config
]),
{ok, _} = emqtt:ConnFun(Sub1),
{ok, _, [2]} = emqtt:subscribe(Sub1, SharedTopic, qos2),
{ok, Sub2} = emqtt:start_link([{proto_ver, v5},
{clientid, <<"sub_client_2">>},
{keepalive, 5}]),
{ok, _} = emqtt:connect(Sub2),
{keepalive, 5} | Config]),
{ok, _} = emqtt:ConnFun(Sub2),
{ok, _, [2]} = emqtt:subscribe(Sub2, SharedTopic, qos2),
{ok, Pub} = emqtt:start_link([{proto_ver, v5}, {clientid, <<"pub_client">>}]),
{ok, _} = emqtt:connect(Pub),
{ok, Pub} = emqtt:start_link([{proto_ver, v5}, {clientid, <<"pub_client">>} | Config]),
{ok, _} = emqtt:ConnFun(Pub),
{ok, _} = emqtt:publish(Pub, Topic,
<<"t_shared_subscriptions_client_terminates_when_qos_eq_2">>, 2),
receive
{'EXIT', _,{shutdown, for_testiong}} ->
{'EXIT', _,{shutdown, for_testing}} ->
ok
after 1000 ->
error("disconnected timeout")
ct:fail("disconnected timeout")
end,
?assertEqual(1, counters:get(CRef, 1)),

View File

@ -63,64 +63,28 @@ t_load(_) ->
?assertEqual({error, not_started}, emqx_plugins:unload(emqx_hocon_plugin)),
application:set_env(emqx, expand_plugins_dir, undefined),
application:set_env(emqx, plugins_loaded_file, undefined),
?assertEqual(ignore, emqx_plugins:load()),
?assertEqual(ignore, emqx_plugins:unload()).
t_init_config(_) ->
ConfFile = "emqx_mini_plugin.config",
Data = "[{emqx_mini_plugin,[{mininame ,test}]}].",
file:write_file(ConfFile, list_to_binary(Data)),
?assertEqual(ok, emqx_plugins:init_config(ConfFile)),
file:delete(ConfFile),
?assertEqual({ok,test}, application:get_env(emqx_mini_plugin, mininame)).
application:set_env(emqx, plugins_loaded_file, undefined).
t_load_ext_plugin(_) ->
?assertError({plugin_app_file_not_found, _},
emqx_plugins:load_ext_plugin("./not_existed_path/")).
t_list(_) ->
?assertMatch([{plugin, _, _, _, _, _, _, _} | _ ], emqx_plugins:list()).
?assertMatch([{plugin, _, _, _, _, _, _} | _ ], emqx_plugins:list()).
t_find_plugin(_) ->
?assertMatch({plugin, emqx_mini_plugin, _, _, _, _, _, _}, emqx_plugins:find_plugin(emqx_mini_plugin)),
?assertMatch({plugin, emqx_hocon_plugin, _, _, _, _, _, _}, emqx_plugins:find_plugin(emqx_hocon_plugin)).
t_plugin_type(_) ->
?assertEqual(auth, emqx_plugins:plugin_type(auth)),
?assertEqual(protocol, emqx_plugins:plugin_type(protocol)),
?assertEqual(backend, emqx_plugins:plugin_type(backend)),
?assertEqual(bridge, emqx_plugins:plugin_type(bridge)),
?assertEqual(feature, emqx_plugins:plugin_type(undefined)).
t_with_loaded_file(_) ->
?assertMatch({error, _}, emqx_plugins:with_loaded_file("./not_existed_path/", fun(_) -> ok end)).
t_plugin_loaded(_) ->
?assertEqual(ok, emqx_plugins:plugin_loaded(emqx_mini_plugin, false)),
?assertEqual(ok, emqx_plugins:plugin_loaded(emqx_mini_plugin, true)),
?assertEqual(ok, emqx_plugins:plugin_loaded(emqx_hocon_plugin, false)),
?assertEqual(ok, emqx_plugins:plugin_loaded(emqx_hocon_plugin, true)).
t_plugin_unloaded(_) ->
?assertEqual(ok, emqx_plugins:plugin_unloaded(emqx_mini_plugin, false)),
?assertEqual(ok, emqx_plugins:plugin_unloaded(emqx_mini_plugin, true)),
?assertEqual(ok, emqx_plugins:plugin_unloaded(emqx_hocon_plugin, false)),
?assertEqual(ok, emqx_plugins:plugin_unloaded(emqx_hocon_plugin, true)).
?assertMatch({plugin, emqx_mini_plugin, _, _, _, _, _}, emqx_plugins:find_plugin(emqx_mini_plugin)),
?assertMatch({plugin, emqx_hocon_plugin, _, _, _, _, _}, emqx_plugins:find_plugin(emqx_hocon_plugin)).
t_plugin(_) ->
try
emqx_plugins:plugin(not_existed_plugin, undefined)
emqx_plugins:plugin(not_existed_plugin)
catch
_Error:Reason:_Stacktrace ->
?assertEqual({plugin_not_found,not_existed_plugin}, Reason)
end,
?assertMatch({plugin, emqx_mini_plugin, _, _, _, _, _, _}, emqx_plugins:plugin(emqx_mini_plugin, undefined)),
?assertMatch({plugin, emqx_hocon_plugin, _, _, _, _, _, _}, emqx_plugins:plugin(emqx_hocon_plugin, undefined)).
t_filter_plugins(_) ->
?assertEqual([name1, name2], emqx_plugins:filter_plugins([name1, {name2,true}, {name3, false}])).
?assertMatch({plugin, emqx_mini_plugin, _, _, _, _, _}, emqx_plugins:plugin(emqx_mini_plugin)),
?assertMatch({plugin, emqx_hocon_plugin, _, _, _, _, _}, emqx_plugins:plugin(emqx_hocon_plugin)).
t_load_plugin(_) ->
ok = meck:new(application, [unstick, non_strict, passthrough, no_history]),
@ -133,9 +97,9 @@ t_load_plugin(_) ->
ok = meck:new(emqx_plugins, [unstick, non_strict, passthrough, no_history]),
ok = meck:expect(emqx_plugins, generate_configs, fun(_) -> ok end),
ok = meck:expect(emqx_plugins, apply_configs, fun(_) -> ok end),
?assertMatch({error, _}, emqx_plugins:load_plugin(already_loaded_app, true)),
?assertMatch(ok, emqx_plugins:load_plugin(normal, true)),
?assertMatch({error,_}, emqx_plugins:load_plugin(error_app, true)),
?assertMatch({error, _}, emqx_plugins:load_plugin(already_loaded_app)),
?assertMatch(ok, emqx_plugins:load_plugin(normal)),
?assertMatch({error,_}, emqx_plugins:load_plugin(error_app)),
ok = meck:unload(emqx_plugins),
ok = meck:unload(application).
@ -146,8 +110,8 @@ t_unload_plugin(_) ->
(error_app) -> {error, error};
(_) -> ok end),
?assertEqual(ok, emqx_plugins:unload_plugin(not_started_app, true)),
?assertEqual(ok, emqx_plugins:unload_plugin(normal, true)),
?assertEqual({error,error}, emqx_plugins:unload_plugin(error_app, true)),
?assertEqual(ok, emqx_plugins:unload_plugin(not_started_app)),
?assertEqual(ok, emqx_plugins:unload_plugin(normal)),
?assertEqual({error,error}, emqx_plugins:unload_plugin(error_app)),
ok = meck:unload(application).

View File

@ -64,7 +64,7 @@ init_per_testcase(TestCase, Config) when
end),
%% Mock emqx_access_control
ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]),
ok = meck:expect(emqx_access_control, check_acl, fun(_, _, _) -> allow end),
ok = meck:expect(emqx_access_control, authorize, fun(_, _, _) -> allow end),
%% Mock emqx_hooks
ok = meck:new(emqx_hooks, [passthrough, no_history, no_link]),
ok = meck:expect(emqx_hooks, run, fun(_Hook, _Args) -> ok end),

View File

@ -1,25 +0,0 @@
.eunit
deps
*.o
*.beam
*.plt
erl_crash.dump
ebin
rel/example_project
.concrete/DEV_MODE
.rebar
.erlang.mk/
emqx_auth_http.d
data
ct.cover.spec
cover/
ct.coverdata
eunit.coverdata
logs/
erlang.mk
_build/
rebar.lock
rebar3.crashdump
etc/emqx_auth_http.conf.rendered
.rebar3/
*.swp

View File

@ -1,100 +0,0 @@
emqx_auth_http
==============
EMQ X HTTP Auth/ACL Plugin
Build
-----
```
make && make tests
```
Configure the Plugin
--------------------
File: etc/emqx_auth_http.conf
```
##--------------------------------------------------------------------
## Authentication request.
##
## Variables:
## - %u: username
## - %c: clientid
## - %a: ipaddress
## - %r: protocol
## - %P: password
## - %C: common name of client TLS cert
## - %d: subject of client TLS cert
##
## Value: URL
auth.http.auth_req = http://127.0.0.1:8080/mqtt/auth
## Value: post | get | put
auth.http.auth_req.method = post
## Value: Params
auth.http.auth_req.params = clientid=%c,username=%u,password=%P
##--------------------------------------------------------------------
## Superuser request.
##
## Variables:
## - %u: username
## - %c: clientid
## - %a: ipaddress
## - %r: protocol
## - %P: password
## - %C: common name of client TLS cert
## - %d: subject of client TLS cert
##
## Value: URL
auth.http.super_req = http://127.0.0.1:8080/mqtt/superuser
## Value: post | get | put
auth.http.super_req.method = post
## Value: Params
auth.http.super_req.params = clientid=%c,username=%u
##--------------------------------------------------------------------
## ACL request.
##
## Variables:
## - %A: 1 | 2, 1 = sub, 2 = pub
## - %u: username
## - %c: clientid
## - %a: ipaddress
## - %r: protocol
## - %m: mountpoint
## - %t: topic
##
## Value: URL
auth.http.acl_req = http://127.0.0.1:8080/mqtt/acl
## Value: post | get | put
auth.http.acl_req.method = get
## Value: Params
auth.http.acl_req.params = access=%A,username=%u,clientid=%c,ipaddr=%a,topic=%t
```
Load the Plugin
---------------
```
./bin/emqx_ctl plugins load emqx_auth_http
```
HTTP API
--------
200 if ok
4xx if unauthorized
License
-------
Apache License Version 2.0
Author
------
EMQ X Team.

View File

@ -1,172 +0,0 @@
##--------------------------------------------------------------------
## HTTP Auth/ACL Plugin
##--------------------------------------------------------------------
## HTTP URL API path for Auth Request
##
## Value: URL
##
## Examples: http://127.0.0.1:80/mqtt/auth, https://[::1]:80/mqtt/auth
auth.http.auth_req.url = "http://127.0.0.1:80/mqtt/auth"
## HTTP Request Method for Auth Request
##
## Value: post | get
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
## - %a: ipaddress
## - %r: protocol
## - %P: password
## - %p: sockport of server accepted
## - %C: common name of client TLS cert
## - %d: subject of client TLS cert
##
## Value: <K1>=<V1>,<K2>=<V2>,...
auth.http.auth_req.params = "clientid=%c,username=%u,password=%P"
## HTTP URL API path for SuperUser Request
##
## Value: URL
##
## Examples: http://127.0.0.1:80/mqtt/superuser, https://[::1]:80/mqtt/superuser
auth.http.super_req.url = "http://127.0.0.1:80/mqtt/superuser"
## HTTP Request Method for SuperUser Request
##
## Value: post | get
auth.http.super_req.method = post
## HTTP Request Headers for SuperUser 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.super_req.headers.accept = */*
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
## - %a: ipaddress
## - %r: protocol
## - %P: password
## - %p: sockport of server accepted
## - %C: common name of client TLS cert
## - %d: subject of client TLS cert
##
## Value: <K1>=<V1>,<K2>=<V2>,...
auth.http.super_req.params = "clientid=%c,username=%u"
## HTTP URL API path for ACL Request
## Comment out this config to disable ACL checks
##
## Value: URL
##
## Examples: http://127.0.0.1:80/mqtt/acl, https://[::1]:80/mqtt/acl
auth.http.acl_req.url = "http://127.0.0.1:80/mqtt/acl"
## HTTP Request Method for ACL Request
##
## Value: post | get
auth.http.acl_req.method = post
## HTTP Request Headers for ACL 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.acl_req.headers.accept = */*
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:
## - %A: access (1 - subscribe, 2 - publish)
## - %u: username
## - %c: clientid
## - %a: ipaddress
## - %r: protocol
## - %P: password
## - %p: sockport of server accepted
## - %C: common name of client TLS cert
## - %d: subject of client TLS cert
## - %t: topic
##
## Value: <K1>=<V1>,<K2>=<V2>,...
auth.http.acl_req.params = "access=%A,username=%u,clientid=%c,ipaddr=%a,topic=%t,mountpoint=%m"
## Time-out time for the request.
##
## Value: Duration
## -h: hour, e.g. '2h' for 2 hours
## -m: minute, e.g. '5m' for 5 minutes
## -s: second, e.g. '30s' for 30 seconds
##
## Default: 5s
auth.http.timeout = 5s
## Connection time-out time, used during the initial request,
## when the client is connecting to the server.
##
## Value: Duration
## -h: hour, e.g. '2h' for 2 hours
## -m: minute, e.g. '5m' for 5 minutes
## -s: second, e.g. '30s' for 30 seconds
##
## Default: 5s
auth.http.connect_timeout = 5s
## Connection process pool size
##
## Value: Number
auth.http.pool_size = 32
##------------------------------------------------------------------------------
## SSL options
## Path to the file containing PEM-encoded CA certificates. The CA certificates
## are used during server authentication and when building the client certificate chain.
##
## Value: File
## auth.http.ssl.cacertfile = "{{ platform_etc_dir }}/certs/ca.pem"
## The path to a file containing the client's certificate.
##
## Value: File
## auth.http.ssl.certfile = "{{ platform_etc_dir }}/certs/client-cert.pem"
## Path to a file containing the client's private PEM-encoded key.
##
## Value: File
## auth.http.ssl.keyfile = "{{ platform_etc_dir }}/certs/client-key.pem"
## 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

View File

@ -1,23 +0,0 @@
-define(APP, emqx_auth_http).
-record(auth_metrics, {
success = 'client.auth.success',
failure = 'client.auth.failure',
ignore = 'client.auth.ignore'
}).
-record(acl_metrics, {
allow = 'client.acl.allow',
deny = 'client.acl.deny',
ignore = 'client.acl.ignore'
}).
-define(METRICS(Type), tl(tuple_to_list(#Type{}))).
-define(METRICS(Type, K), #Type{}#Type.K).
-define(AUTH_METRICS, ?METRICS(auth_metrics)).
-define(AUTH_METRICS(K), ?METRICS(auth_metrics, K)).
-define(ACL_METRICS, ?METRICS(acl_metrics)).
-define(ACL_METRICS(K), ?METRICS(acl_metrics, K)).

View File

@ -1,131 +0,0 @@
%%-*- mode: erlang -*-
%% emqx_auth_http config mapping
{mapping, "auth.http.auth_req.url", "emqx_auth_http.auth_req", [
{datatype, string}
]}.
{mapping, "auth.http.auth_req.method", "emqx_auth_http.auth_req", [
{default, post},
{datatype, {enum, [post, get]}}
]}.
{mapping, "auth.http.auth_req.headers.$field", "emqx_auth_http.auth_req", [
{datatype, string}
]}.
{mapping, "auth.http.auth_req.params", "emqx_auth_http.auth_req", [
{datatype, string}
]}.
{translation, "emqx_auth_http.auth_req", fun(Conf) ->
case cuttlefish:conf_get("auth.http.auth_req.url", Conf, undefined) of
undefined -> cuttlefish:unset();
Url ->
Headers = cuttlefish_variable:filter_by_prefix("auth.http.auth_req.headers", Conf),
Params = cuttlefish:conf_get("auth.http.auth_req.params", Conf),
[{url, Url},
{method, cuttlefish:conf_get("auth.http.auth_req.method", Conf)},
{headers, [{K, V} || {[_, _, _, _, K], V} <- Headers]},
{params, [list_to_tuple(string:tokens(S, "=")) || S <- string:tokens(Params, ",")]}]
end
end}.
{mapping, "auth.http.super_req.url", "emqx_auth_http.super_req", [
{datatype, string}
]}.
{mapping, "auth.http.super_req.method", "emqx_auth_http.super_req", [
{default, post},
{datatype, {enum, [post, get]}}
]}.
{mapping, "auth.http.super_req.headers.$field", "emqx_auth_http.super_req", [
{datatype, string}
]}.
{mapping, "auth.http.super_req.params", "emqx_auth_http.super_req", [
{datatype, string}
]}.
{translation, "emqx_auth_http.super_req", fun(Conf) ->
case cuttlefish:conf_get("auth.http.super_req.url", Conf, undefined) of
undefined -> cuttlefish:unset();
Url ->
Headers = cuttlefish_variable:filter_by_prefix("auth.http.super_req.headers", Conf),
Params = cuttlefish:conf_get("auth.http.super_req.params", Conf),
[{url, Url},
{method, cuttlefish:conf_get("auth.http.super_req.method", Conf)},
{headers, [{K, V} || {[_, _, _, _, K], V} <- Headers]},
{params, [list_to_tuple(string:tokens(S, "=")) || S <- string:tokens(Params, ",")]}]
end
end}.
%% @doc URL for ACL checks. Example: http://127.0.0.1:80/mqtt/acl
%% ACL checks are disabled for this plugin if this config is
%% commented out from the config file, or when the overriding
%% environment variable is set to empty string.
{mapping, "auth.http.acl_req.url", "emqx_auth_http.acl_req", [
{datatype, string}
]}.
{mapping, "auth.http.acl_req.method", "emqx_auth_http.acl_req", [
{default, post},
{datatype, {enum, [post, get]}}
]}.
{mapping, "auth.http.acl_req.headers.$field", "emqx_auth_http.acl_req", [
{datatype, string}
]}.
{mapping, "auth.http.acl_req.params", "emqx_auth_http.acl_req", [
{datatype, string}
]}.
{translation, "emqx_auth_http.acl_req", fun(Conf) ->
case cuttlefish:conf_get("auth.http.acl_req.url", Conf, undefined) of
undefined -> cuttlefish:unset();
Url ->
Headers = cuttlefish_variable:filter_by_prefix("auth.http.acl_req.headers", Conf),
Params = cuttlefish:conf_get("auth.http.acl_req.params", Conf),
[{url, Url},
{method, cuttlefish:conf_get("auth.http.acl_req.method", Conf)},
{headers, [{K, V} || {[_, _, _, _, K], V} <- Headers]},
{params, [list_to_tuple(string:tokens(S, "=")) || S <- string:tokens(Params, ",")]}]
end
end}.
{mapping, "auth.http.timeout", "emqx_auth_http.timeout", [
{default, "5s"},
{datatype, [integer, {duration, ms}]}
]}.
{mapping, "auth.http.connect_timeout", "emqx_auth_http.connect_timeout", [
{default, "5s"},
{datatype, [integer, {duration, ms}]}
]}.
{mapping, "auth.http.pool_size", "emqx_auth_http.pool_size", [
{default, 8},
{datatype, integer}
]}.
{mapping, "auth.http.ssl.cacertfile", "emqx_auth_http.cacertfile", [
{datatype, string}
]}.
{mapping, "auth.http.ssl.certfile", "emqx_auth_http.certfile", [
{datatype, string}
]}.
{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}
]}.

View File

@ -1,26 +0,0 @@
{deps, []}.
{edoc_opts, [{preprocess, true}]}.
{erl_opts, [warn_unused_vars,
warn_shadow_vars,
warn_unused_import,
warn_obsolete_guard,
debug_info,
{parse_transform}]}.
{xref_checks, [undefined_function_calls, undefined_functions,
locals_not_used, deprecated_function_calls,
warnings_as_errors, deprecated_functions]}.
{cover_enabled, true}.
{cover_opts, [verbose]}.
{cover_export_enabled, true}.
{profiles,
[{test,
[{deps,
[
{emqtt, {git, "https://github.com/emqx/emqtt", {tag, "v1.2.2"}}}
]}
]}
]}.

View File

@ -1,88 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_acl_http).
-include("emqx_auth_http.hrl").
-include_lib("emqx/include/emqx.hrl").
-include_lib("emqx/include/logger.hrl").
-logger_header("[ACL http]").
-import(emqx_auth_http_cli,
[ request/6
, feedvar/2
]).
%% ACL callbacks
-export([ register_metrics/0
, check_acl/5
, description/0
]).
-spec(register_metrics() -> ok).
register_metrics() ->
lists:foreach(fun emqx_metrics:ensure/1, ?ACL_METRICS).
%%--------------------------------------------------------------------
%% ACL callbacks
%%--------------------------------------------------------------------
check_acl(ClientInfo, PubSub, Topic, AclResult, Params) ->
return_with(fun inc_metrics/1,
do_check_acl(ClientInfo, PubSub, Topic, AclResult, Params)).
do_check_acl(#{username := <<$$, _/binary>>}, _PubSub, _Topic, _AclResult, _Params) ->
ok;
do_check_acl(ClientInfo, PubSub, Topic, _AclResult, #{acl := ACLParams = #{path := Path}}) ->
ClientInfo1 = ClientInfo#{access => access(PubSub), topic => Topic},
case check_acl_request(ACLParams, ClientInfo1) of
{ok, 200, <<"ignore">>} -> ok;
{ok, 200, _Body} -> {stop, allow};
{ok, _Code, _Body} -> {stop, deny};
{error, Error} ->
?LOG(error, "Request ACL path ~s, error: ~p", [Path, Error]),
ok
end.
description() -> "ACL with HTTP API".
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
inc_metrics(ok) ->
emqx_metrics:inc(?ACL_METRICS(ignore));
inc_metrics({stop, allow}) ->
emqx_metrics:inc(?ACL_METRICS(allow));
inc_metrics({stop, deny}) ->
emqx_metrics:inc(?ACL_METRICS(deny)).
return_with(Fun, Result) ->
Fun(Result), Result.
check_acl_request(#{pool_name := PoolName,
path := Path,
method := Method,
headers := Headers,
params := Params,
timeout := Timeout}, ClientInfo) ->
request(PoolName, Method, Path, Headers, feedvar(Params, ClientInfo), Timeout).
access(subscribe) -> 1;
access(publish) -> 2.

View File

@ -1,14 +0,0 @@
{application, emqx_auth_http,
[{description, "EMQ X Authentication/ACL with HTTP API"},
{vsn, "4.3.0"}, % strict semver, bump manually!
{modules, []},
{registered, [emqx_auth_http_sup]},
{applications, [kernel,stdlib,ehttpc]},
{mod, {emqx_auth_http_app, []}},
{env, []},
{licenses, ["Apache-2.0"]},
{maintainers, ["EMQ X Team <contact@emqx.io>"]},
{links, [{"Homepage", "https://emqx.io/"},
{"Github", "https://github.com/emqx/emqx-auth-http"}
]}
]}.

View File

@ -1,112 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_auth_http).
-include("emqx_auth_http.hrl").
-include_lib("emqx/include/emqx.hrl").
-include_lib("emqx/include/logger.hrl").
-include_lib("emqx/include/types.hrl").
-logger_header("[Auth http]").
-import(emqx_auth_http_cli,
[ request/6
, feedvar/2
]).
%% Callbacks
-export([ register_metrics/0
, check/3
, description/0
]).
-spec(register_metrics() -> ok).
register_metrics() ->
lists:foreach(fun emqx_metrics:ensure/1, ?AUTH_METRICS).
check(ClientInfo, AuthResult, #{auth := AuthParms = #{path := Path},
super := SuperParams}) ->
case authenticate(AuthParms, ClientInfo) of
{ok, 200, <<"ignore">>} ->
emqx_metrics:inc(?AUTH_METRICS(ignore)), ok;
{ok, 200, Body} ->
emqx_metrics:inc(?AUTH_METRICS(success)),
IsSuperuser = is_superuser(SuperParams, ClientInfo),
{stop, AuthResult#{is_superuser => IsSuperuser,
auth_result => success,
anonymous => false,
mountpoint => mountpoint(Body, ClientInfo)}};
{ok, Code, _Body} ->
?LOG(error, "Deny connection from path: ~s, response http code: ~p",
[Path, Code]),
emqx_metrics:inc(?AUTH_METRICS(failure)),
{stop, AuthResult#{auth_result => http_to_connack_error(Code),
anonymous => false}};
{error, Error} ->
?LOG(error, "Request auth path: ~s, error: ~p", [Path, Error]),
emqx_metrics:inc(?AUTH_METRICS(failure)),
%%FIXME later: server_unavailable is not right.
{stop, AuthResult#{auth_result => server_unavailable,
anonymous => false}}
end.
description() -> "Authentication by HTTP API".
%%--------------------------------------------------------------------
%% Requests
%%--------------------------------------------------------------------
authenticate(#{pool_name := PoolName,
path := Path,
method := Method,
headers := Headers,
params := Params,
timeout := Timeout}, ClientInfo) ->
request(PoolName, Method, Path, Headers, feedvar(Params, ClientInfo), Timeout).
-spec(is_superuser(maybe(map()), emqx_types:client()) -> boolean()).
is_superuser(undefined, _ClientInfo) ->
false;
is_superuser(#{pool_name := PoolName,
path := Path,
method := Method,
headers := Headers,
params := Params,
timeout := Timeout}, ClientInfo) ->
case request(PoolName, Method, Path, Headers, feedvar(Params, ClientInfo), Timeout) of
{ok, 200, _Body} -> true;
{ok, _Code, _Body} -> false;
{error, Error} -> ?LOG(error, "Request superuser path ~s, error: ~p", [Path, Error]),
false
end.
mountpoint(Body, #{mountpoint := Mountpoint}) ->
case emqx_json:safe_decode(Body, [return_maps]) of
{error, _} -> Mountpoint;
{ok, Json} when is_map(Json) ->
maps:get(<<"mountpoint">>, Json, Mountpoint);
{ok, _NotMap} -> Mountpoint
end.
http_to_connack_error(400) -> bad_username_or_password;
http_to_connack_error(401) -> bad_username_or_password;
http_to_connack_error(403) -> not_authorized;
http_to_connack_error(429) -> banned;
http_to_connack_error(503) -> server_unavailable;
http_to_connack_error(504) -> server_busy;
http_to_connack_error(_) -> server_unavailable.

View File

@ -1,158 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_auth_http_app).
-behaviour(application).
-emqx_plugin(auth).
-include("emqx_auth_http.hrl").
-export([ start/2
, stop/1
]).
%%--------------------------------------------------------------------
%% Application Callbacks
%%--------------------------------------------------------------------
start(_StartType, _StartArgs) ->
{ok, Sup} = emqx_auth_http_sup:start_link(),
translate_env(),
load_hooks(),
{ok, Sup}.
stop(_State) ->
unload_hooks().
%%--------------------------------------------------------------------
%% Internel functions
%%--------------------------------------------------------------------
translate_env() ->
lists:foreach(fun translate_env/1, [auth_req, super_req, acl_req]).
translate_env(EnvName) ->
case application:get_env(?APP, EnvName) of
undefined -> ok;
{ok, Req} ->
{ok, PoolSize} = application:get_env(?APP, pool_size),
{ok, ConnectTimeout} = application:get_env(?APP, connect_timeout),
URL = proplists:get_value(url, Req),
{ok, #{host := Host,
path := Path0,
port := Port,
scheme := Scheme}} = emqx_http_lib:uri_parse(URL),
Path = path(Path0),
MoreOpts = case Scheme of
http ->
[{transport_opts, emqx_misc:ipv6_probe([])}];
https ->
CACertFile = application:get_env(?APP, cacertfile, undefined),
CertFile = application:get_env(?APP, certfile, undefined),
KeyFile = application:get_env(?APP, keyfile, undefined),
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
],
[{transport, ssl}, {transport_opts, emqx_misc:ipv6_probe(NTLSOpts)}]
end,
PoolOpts = [{host, Host},
{port, Port},
{pool_size, PoolSize},
{pool_type, random},
{connect_timeout, ConnectTimeout},
{retry, 5},
{retry_timeout, 1000}] ++ MoreOpts,
Method = proplists:get_value(method, Req),
Headers = proplists:get_value(headers, Req),
NHeaders = ensure_content_type_header(Method, emqx_http_lib:normalise_headers(Headers)),
NReq = lists:keydelete(headers, 1, Req),
{ok, Timeout} = application:get_env(?APP, timeout),
application:set_env(?APP, EnvName, [{path, Path},
{headers, NHeaders},
{timeout, Timeout},
{pool_name, list_to_atom("emqx_auth_http/" ++ atom_to_list(EnvName))},
{pool_opts, PoolOpts} | NReq])
end.
load_hooks() ->
case application:get_env(?APP, auth_req) of
undefined -> ok;
{ok, AuthReq} ->
ok = emqx_auth_http:register_metrics(),
PoolOpts = proplists:get_value(pool_opts, AuthReq),
PoolName = proplists:get_value(pool_name, AuthReq),
{ok, _} = ehttpc_sup:start_pool(PoolName, PoolOpts),
case application:get_env(?APP, super_req) of
undefined ->
emqx_hooks:put('client.authenticate', {emqx_auth_http, check, [#{auth => maps:from_list(AuthReq),
super => undefined}]});
{ok, SuperReq} ->
PoolOpts1 = proplists:get_value(pool_opts, SuperReq),
PoolName1 = proplists:get_value(pool_name, SuperReq),
{ok, _} = ehttpc_sup:start_pool(PoolName1, PoolOpts1),
emqx_hooks:put('client.authenticate', {emqx_auth_http, check, [#{auth => maps:from_list(AuthReq),
super => maps:from_list(SuperReq)}]})
end
end,
case application:get_env(?APP, acl_req) of
undefined -> ok;
{ok, ACLReq} ->
ok = emqx_acl_http:register_metrics(),
PoolOpts2 = proplists:get_value(pool_opts, ACLReq),
PoolName2 = proplists:get_value(pool_name, ACLReq),
{ok, _} = ehttpc_sup:start_pool(PoolName2, PoolOpts2),
emqx_hooks:put('client.check_acl', {emqx_acl_http, check_acl, [#{acl => maps:from_list(ACLReq)}]})
end,
ok.
unload_hooks() ->
emqx:unhook('client.authenticate', {emqx_auth_http, check}),
emqx:unhook('client.check_acl', {emqx_acl_http, check_acl}),
_ = ehttpc_sup:stop_pool('emqx_auth_http/auth_req'),
_ = ehttpc_sup:stop_pool('emqx_auth_http/super_req'),
_ = ehttpc_sup:stop_pool('emqx_auth_http/acl_req'),
ok.
ensure_content_type_header(Method, Headers)
when Method =:= post orelse Method =:= put ->
Headers;
ensure_content_type_header(_Method, Headers) ->
lists:keydelete("content-type", 1, Headers).
path("") ->
"/";
path(Path) ->
Path.

View File

@ -1,92 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_auth_http_cli).
-include("emqx_auth_http.hrl").
-export([ request/6
, feedvar/2
, feedvar/3
]).
%%--------------------------------------------------------------------
%% HTTP Request
%%--------------------------------------------------------------------
request(PoolName, get, Path, Headers, Params, Timeout) ->
NewPath = Path ++ "?" ++ binary_to_list(cow_qs:qs(bin_kw(Params))),
reply(ehttpc:request(ehttpc_pool:pick_worker(PoolName), get, {NewPath, Headers}, Timeout));
request(PoolName, post, Path, Headers, Params, Timeout) ->
Body = case proplists:get_value("content-type", Headers) of
"application/x-www-form-urlencoded" ->
cow_qs:qs(bin_kw(Params));
"application/json" ->
emqx_json:encode(bin_kw(Params))
end,
reply(ehttpc:request(ehttpc_pool:pick_worker(PoolName), post, {Path, Headers, Body}, Timeout)).
reply({ok, StatusCode, _Headers}) ->
{ok, StatusCode, <<>>};
reply({ok, StatusCode, _Headers, Body}) ->
{ok, StatusCode, Body};
reply({error, Reason}) ->
{error, Reason}.
%% TODO: move this conversion to cuttlefish config and schema
bin_kw(KeywordList) when is_list(KeywordList) ->
[{bin(K), bin(V)} || {K, V} <- KeywordList].
bin(Atom) when is_atom(Atom) ->
list_to_binary(atom_to_list(Atom));
bin(Int) when is_integer(Int) ->
integer_to_binary(Int);
bin(Float) when is_float(Float) ->
float_to_binary(Float, [{decimals, 12}, compact]);
bin(List) when is_list(List)->
list_to_binary(List);
bin(Binary) when is_binary(Binary) ->
Binary.
%%--------------------------------------------------------------------
%% Feed Variables
%%--------------------------------------------------------------------
feedvar(Params, ClientInfo = #{clientid := ClientId,
protocol := Protocol,
peerhost := Peerhost}) ->
lists:map(fun({Param, "%u"}) -> {Param, maps:get(username, ClientInfo, null)};
({Param, "%c"}) -> {Param, ClientId};
({Param, "%r"}) -> {Param, Protocol};
({Param, "%a"}) -> {Param, inet:ntoa(Peerhost)};
({Param, "%P"}) -> {Param, maps:get(password, ClientInfo, null)};
({Param, "%p"}) -> {Param, maps:get(sockport, ClientInfo, null)};
({Param, "%C"}) -> {Param, maps:get(cn, ClientInfo, null)};
({Param, "%d"}) -> {Param, maps:get(dn, ClientInfo, null)};
({Param, "%A"}) -> {Param, maps:get(access, ClientInfo, null)};
({Param, "%t"}) -> {Param, maps:get(topic, ClientInfo, null)};
({Param, "%m"}) -> {Param, maps:get(mountpoint, ClientInfo, null)};
({Param, Var}) -> {Param, Var}
end, Params).
feedvar(Params, Var, Val) ->
lists:map(fun({Param, Var0}) when Var0 == Var ->
{Param, Val};
({Param, Var0}) ->
{Param, Var0}
end, Params).

View File

@ -1,257 +0,0 @@
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-module(emqx_auth_http_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("emqx/include/emqx.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-define(APP, emqx_auth_http).
-define(USER(ClientId, Username, Protocol, Peerhost, Zone),
#{clientid => ClientId, username => Username, protocol => Protocol,
peerhost => Peerhost, zone => Zone}).
-define(USER(ClientId, Username, Protocol, Peerhost, Zone, Mountpoint),
#{clientid => ClientId, username => Username, protocol => Protocol,
peerhost => Peerhost, zone => Zone, mountpoint => Mountpoint}).
%%--------------------------------------------------------------------
%% Setups
%%--------------------------------------------------------------------
all() ->
[
{group, http_inet},
{group, http_inet6},
{group, https_inet},
{group, https_inet6},
pub_sub_no_acl,
no_hook_if_config_unset
].
groups() ->
Cases = emqx_ct:all(?MODULE),
[{Name, Cases} || Name <- [http_inet, http_inet6, https_inet, https_inet6]].
init_per_group(GrpName, Cfg) ->
[Scheme, Inet] = [list_to_atom(X) || X <- string:tokens(atom_to_list(GrpName), "_")],
ok = setup(Scheme, Inet),
Cfg.
end_per_group(_GrpName, _Cfg) ->
teardown().
init_per_testcase(pub_sub_no_acl, Cfg) ->
Scheme = http,
Inet = inet,
http_auth_server:start(Scheme, Inet),
Fun = fun(App) -> set_special_configs(App, Scheme, Inet, no_acl) end,
emqx_ct_helpers:start_apps([emqx_auth_http], Fun),
?assert(is_hooked('client.authenticate')),
?assertNot(is_hooked('client.check_acl')),
Cfg;
init_per_testcase(no_hook_if_config_unset, Cfg) ->
setup(http, inet),
Cfg;
init_per_testcase(_, Cfg) ->
%% init per group
Cfg.
end_per_testcase(pub_sub_no_acl, _Cfg) ->
teardown();
end_per_testcase(no_hook_if_config_unset, _Cfg) ->
teardown();
end_per_testcase(_, _Cfg) ->
%% teardown per group
ok.
setup(Scheme, Inet) ->
http_auth_server:start(Scheme, Inet),
Fun = fun(App) -> set_special_configs(App, Scheme, Inet, normal) end,
emqx_ct_helpers:start_apps([emqx_auth_http], Fun),
?assert(is_hooked('client.authenticate')),
?assert(is_hooked('client.check_acl')).
teardown() ->
http_auth_server:stop(),
application:stop(emqx_auth_http),
?assertNot(is_hooked('client.authenticate')),
?assertNot(is_hooked('client.check_acl')),
emqx_ct_helpers:stop_apps([emqx]).
set_special_configs(emqx, _Scheme, _Inet, _AuthConfig) ->
application:set_env(emqx, allow_anonymous, true),
application:set_env(emqx, enable_acl_cache, false),
LoadedPluginPath = filename:join(["test", "emqx_SUITE_data", "loaded_plugins"]),
application:set_env(emqx, plugins_loaded_file,
emqx_ct_helpers:deps_path(emqx, LoadedPluginPath));
set_special_configs(emqx_auth_http, Scheme, Inet, PluginConfig) ->
[application:unset_env(?APP, Par) || Par <- [acl_req, auth_req]],
ServerAddr = http_server(Scheme, Inet),
AuthReq = #{method => get,
url => ServerAddr ++ "/mqtt/auth",
headers => [{"content-type", "application/json"}],
params => [{"clientid", "%c"}, {"username", "%u"}, {"password", "%P"}]},
SuperReq = #{method => post,
url => ServerAddr ++ "/mqtt/superuser",
headers => [{"content-type", "application/json"}],
params => [{"clientid", "%c"}, {"username", "%u"}]},
AclReq = #{method => post,
url => ServerAddr ++ "/mqtt/acl",
headers => [{"content-type", "application/json"}],
params => [{"access", "%A"}, {"username", "%u"}, {"clientid", "%c"}, {"ipaddr", "%a"}, {"topic", "%t"}, {"mountpoint", "%m"}]},
Scheme =:= https andalso set_https_client_opts(),
application:set_env(emqx_auth_http, auth_req, maps:to_list(AuthReq)),
application:set_env(emqx_auth_http, super_req, maps:to_list(SuperReq)),
case PluginConfig of
normal -> ok = application:set_env(emqx_auth_http, acl_req, maps:to_list(AclReq));
no_acl -> ok
end.
%% @private
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, verify, true),
application:set_env(emqx_auth_http, server_name_indication, "disable").
%% @private
http_server(http, inet) -> "http://127.0.0.1:8991"; % ipv4
http_server(http, inet6) -> "http://localhost:8991"; % test hostname resolution
http_server(https, inet) -> "https://localhost:8991"; % test hostname resolution
http_server(https, inet6) -> "https://[::1]:8991". % ipv6
%%------------------------------------------------------------------------------
%% Testcases
%%------------------------------------------------------------------------------
t_check_acl(Cfg) when is_list(Cfg) ->
SuperUser = ?USER(<<"superclient">>, <<"superuser">>, mqtt, {127,0,0,1}, external),
deny = emqx_access_control:check_acl(SuperUser, subscribe, <<"users/testuser/1">>),
deny = emqx_access_control:check_acl(SuperUser, publish, <<"anytopic">>),
User1 = ?USER(<<"client1">>, <<"testuser">>, mqtt, {127,0,0,1}, external),
UnIpUser1 = ?USER(<<"client1">>, <<"testuser">>, mqtt, {192,168,0,4}, external),
UnClientIdUser1 = ?USER(<<"unkonwc">>, <<"testuser">>, mqtt, {127,0,0,1}, external),
UnnameUser1= ?USER(<<"client1">>, <<"unuser">>, mqtt, {127,0,0,1}, external),
allow = emqx_access_control:check_acl(User1, subscribe, <<"users/testuser/1">>),
deny = emqx_access_control:check_acl(User1, publish, <<"users/testuser/1">>),
deny = emqx_access_control:check_acl(UnIpUser1, subscribe, <<"users/testuser/1">>),
deny = emqx_access_control:check_acl(UnClientIdUser1, subscribe, <<"users/testuser/1">>),
deny = emqx_access_control:check_acl(UnnameUser1, subscribe, <<"$SYS/testuser/1">>),
User2 = ?USER(<<"client2">>, <<"xyz">>, mqtt, {127,0,0,1}, external),
UserC = ?USER(<<"client2">>, <<"xyz">>, mqtt, {192,168,1,3}, external),
allow = emqx_access_control:check_acl(UserC, publish, <<"a/b/c">>),
deny = emqx_access_control:check_acl(User2, publish, <<"a/b/c">>),
deny = emqx_access_control:check_acl(User2, subscribe, <<"$SYS/testuser/1">>).
t_check_auth(Cfg) when is_list(Cfg) ->
User1 = ?USER(<<"client1">>, <<"testuser1">>, mqtt, {127,0,0,1}, external, undefined),
User2 = ?USER(<<"client2">>, <<"testuser2">>, mqtt, {127,0,0,1}, exteneral, undefined),
User3 = ?USER(<<"client3">>, undefined, mqtt, {127,0,0,1}, exteneral, undefined),
{ok, #{auth_result := success,
anonymous := false,
is_superuser := false}} = emqx_access_control:authenticate(User1#{password => <<"pass1">>}),
{error, bad_username_or_password} = emqx_access_control:authenticate(User1#{password => <<"pass">>}),
{error, bad_username_or_password} = emqx_access_control:authenticate(User1#{password => <<>>}),
{ok, #{is_superuser := false}} = emqx_access_control:authenticate(User2#{password => <<"pass2">>}),
{error, bad_username_or_password} = emqx_access_control:authenticate(User2#{password => <<>>}),
{error, bad_username_or_password} = emqx_access_control:authenticate(User2#{password => <<"errorpwd">>}),
{error, bad_username_or_password} = emqx_access_control:authenticate(User3#{password => <<"pwd">>}).
pub_sub_no_acl(Cfg) when is_list(Cfg) ->
{ok, T1} = emqtt:start_link([{host, "localhost"},
{clientid, <<"client1">>},
{username, <<"testuser1">>},
{password, <<"pass1">>}]),
{ok, _} = emqtt:connect(T1),
emqtt:publish(T1, <<"topic">>, <<"body">>, [{qos, 0}, {retain, true}]),
timer:sleep(1000),
{ok, T2} = emqtt:start_link([{host, "localhost"},
{clientid, <<"client2">>},
{username, <<"testuser2">>},
{password, <<"pass2">>}]),
{ok, _} = emqtt:connect(T2),
emqtt:subscribe(T2, <<"topic">>),
receive
{publish, _Topic, Payload} ->
?assertEqual(<<"body">>, Payload)
after 1000 -> false end,
emqtt:disconnect(T1),
emqtt:disconnect(T2).
t_pub_sub(Cfg) when is_list(Cfg) ->
{ok, T1} = emqtt:start_link([{host, "localhost"},
{clientid, <<"client1">>},
{username, <<"testuser1">>},
{password, <<"pass1">>}]),
{ok, _} = emqtt:connect(T1),
emqtt:publish(T1, <<"topic">>, <<"body">>, [{qos, 0}, {retain, true}]),
timer:sleep(1000),
{ok, T2} = emqtt:start_link([{host, "localhost"},
{clientid, <<"client2">>},
{username, <<"testuser2">>},
{password, <<"pass2">>}]),
{ok, _} = emqtt:connect(T2),
emqtt:subscribe(T2, <<"topic">>),
receive
{publish, _Topic, Payload} ->
?assertEqual(<<"body">>, Payload)
after 1000 -> false end,
emqtt:disconnect(T1),
emqtt:disconnect(T2).
no_hook_if_config_unset(Cfg) when is_list(Cfg) ->
?assert(is_hooked('client.authenticate')),
?assert(is_hooked('client.check_acl')),
application:stop(?APP),
[application:unset_env(?APP, Par) || Par <- [acl_req, auth_req]],
application:start(?APP),
?assertEqual([], emqx_hooks:lookup('client.authenticate')),
?assertNot(is_hooked('client.authenticate')),
?assertNot(is_hooked('client.check_acl')).
is_hooked(HookName) ->
Callbacks = emqx_hooks:lookup(HookName),
F = fun(Callback) ->
case emqx_hooks:callback_action(Callback) of
{emqx_auth_http, check, _} ->
'client.authenticate' = HookName, % assert
true;
{emqx_acl_http, check_acl, _} ->
'client.check_acl' = HookName, % assert
true;
_ ->
false
end
end,
case lists:filter(F, Callbacks) of
[_] -> true;
[] -> false
end.

View File

@ -1,152 +0,0 @@
-module(http_auth_server).
-export([ start/2
, stop/0
]).
-define(SUPERUSER, [[{"username", "superuser"}, {"clientid", "superclient"}]]).
-define(ACL, [[{<<"username">>, <<"testuser">>},
{<<"clientid">>, <<"client1">>},
{<<"access">>, <<"1">>},
{<<"topic">>, <<"users/testuser/1">>},
{<<"ipaddr">>, <<"127.0.0.1">>},
{<<"mountpoint">>, <<"null">>}],
[{<<"username">>, <<"xyz">>},
{<<"clientid">>, <<"client2">>},
{<<"access">>, <<"2">>},
{<<"topic">>, <<"a/b/c">>},
{<<"ipaddr">>, <<"192.168.1.3">>},
{<<"mountpoint">>, <<"null">>}],
[{<<"username">>, <<"testuser1">>},
{<<"clientid">>, <<"client1">>},
{<<"access">>, <<"2">>},
{<<"topic">>, <<"topic">>},
{<<"ipaddr">>, <<"127.0.0.1">>},
{<<"mountpoint">>, <<"null">>}],
[{<<"username">>, <<"testuser2">>},
{<<"clientid">>, <<"client2">>},
{<<"access">>, <<"1">>},
{<<"topic">>, <<"topic">>},
{<<"ipaddr">>, <<"127.0.0.1">>},
{<<"mountpoint">>, <<"null">>}]]).
-define(AUTH, [[{<<"clientid">>, <<"client1">>},
{<<"username">>, <<"testuser1">>},
{<<"password">>, <<"pass1">>}],
[{<<"clientid">>, <<"client2">>},
{<<"username">>, <<"testuser2">>},
{<<"password">>, <<"pass2">>}]]).
%%------------------------------------------------------------------------------
%% REST Interface
%%------------------------------------------------------------------------------
-rest_api(#{ name => auth
, method => 'GET'
, path => "/mqtt/auth"
, func => authenticate
, descr => "Authenticate user access permission"
}).
-rest_api(#{ name => is_superuser
, method => 'GET'
, path => "/mqtt/superuser"
, func => is_superuser
, descr => "Is super user"
}).
-rest_api(#{ name => acl
, method => 'GET'
, path => "/mqtt/acl"
, func => check_acl
, descr => "Check acl"
}).
-rest_api(#{ name => auth
, method => 'POST'
, path => "/mqtt/auth"
, func => authenticate
, descr => "Authenticate user access permission"
}).
-rest_api(#{ name => is_superuser
, method => 'POST'
, path => "/mqtt/superuser"
, func => is_superuser
, descr => "Is super user"
}).
-rest_api(#{ name => acl
, method => 'POST'
, path => "/mqtt/acl"
, func => check_acl
, descr => "Check acl"
}).
-export([ authenticate/2
, is_superuser/2
, check_acl/2
]).
authenticate(_Binding, Params) ->
return(check(Params, ?AUTH)).
is_superuser(_Binding, Params) ->
return(check(Params, ?SUPERUSER)).
check_acl(_Binding, Params) ->
return(check(Params, ?ACL)).
return(allow) -> {200, <<"allow">>};
return(deny) -> {400, <<"deny">>}.
start(http, Inet) ->
application:ensure_all_started(minirest),
Handlers = [{"/", minirest:handler(#{modules => [?MODULE]})}],
Dispatch = [{"/[...]", minirest, Handlers}],
minirest:start_http(http_auth_server, #{socket_opts => [Inet, {port, 8991}]}, Dispatch);
start(https, Inet) ->
application:ensure_all_started(minirest),
Handlers = [{"/", minirest:handler(#{modules => [?MODULE]})}],
Dispatch = [{"/[...]", minirest, Handlers}],
minirest:start_https(http_auth_server, #{socket_opts => [Inet, {port, 8991} | certopts()]}, Dispatch).
%% @private
certopts() ->
Certfile = filename:join(["etc", "certs", "cert.pem"]),
Keyfile = filename:join(["etc", "certs", "key.pem"]),
CaCert = filename:join(["etc", "certs", "cacert.pem"]),
[{verify, verify_peer},
{certfile, emqx_ct_helpers:deps_path(emqx, Certfile)},
{keyfile, emqx_ct_helpers:deps_path(emqx, Keyfile)},
{cacertfile, emqx_ct_helpers:deps_path(emqx, CaCert)}] ++ emqx_ct_helpers:client_ssl().
stop() ->
minirest:stop_http(http_auth_server).
-spec check(HttpReqParams :: list(), DefinedConf :: list()) -> allow | deny.
check(_Params, []) ->
%ct:pal("check auth_result: deny~n"),
deny;
check(Params, [ConfRecord|T]) ->
% ct:pal("Params: ~p, ConfRecord:~p ~n", [Params, ConfRecord]),
case match_config(Params, ConfRecord) of
not_match ->
check(Params, T);
matched -> allow
end.
match_config([], _ConfigColumn) ->
%ct:pal("match_config auth_result: matched~n"),
matched;
match_config([Param|T], ConfigColumn) ->
%ct:pal("Param: ~p, ConfigColumn:~p ~n", [Param, ConfigColumn]),
case lists:member(Param, ConfigColumn) of
true ->
match_config(T, ConfigColumn);
false ->
not_match
end.

View File

@ -1,28 +0,0 @@
.eunit
deps
*.o
*.beam
*.plt
erl_crash.dump
ebin
rel/example_project
.concrete/DEV_MODE
.rebar
.erlang.mk/
emqx_auth_jwt.d
data/
.DS_Store
cover/
ct.coverdata
eunit.coverdata
logs/
test/ct.cover.spec
emq_auth_jwt.d
erlang.mk
_build/
rebar.lock
rebar3.crashdump
etc/emqx_auth_jwt.conf.rendered
.rebar3/
*.swp
Mnesia.nonode@nohost/

View File

@ -1,90 +0,0 @@
# emqx-auth-jwt
EMQ X JWT Authentication Plugin
Build
-----
```
make && make tests
```
Configure the Plugin
--------------------
File: etc/plugins/emqx_auth_jwt.conf
```
## HMAC Hash Secret.
##
## Value: String
auth.jwt.secret = emqxsecret
## From where the JWT string can be got
##
## Value: username | password
## Default: password
auth.jwt.from = password
## RSA or ECDSA public key file.
##
## Value: File
## auth.jwt.pubkey = etc/certs/jwt_public_key.pem
## Enable to verify claims fields
##
## Value: on | off
auth.jwt.verify_claims = off
## The checklist of claims to validate
##
## Value: String
## auth.jwt.verify_claims.$name = expected
##
## Variables:
## - %u: username
## - %c: clientid
# auth.jwt.verify_claims.username = %u
```
Load the Plugin
---------------
```
./bin/emqx_ctl plugins load emqx_auth_jwt
```
Example
-------
```
mosquitto_pub -t 'pub' -m 'hello' -i test -u test -P eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiYm9iIiwiYWdlIjoyOX0.bIV_ZQ8D5nQi0LT8AVkpM4Pd6wmlbpR9S8nOLJAsA8o
```
Algorithms
----------
The JWT spec supports several algorithms for cryptographic signing. This plugin currently supports:
* HS256 - HMAC using SHA-256 hash algorithm
* HS384 - HMAC using SHA-384 hash algorithm
* HS512 - HMAC using SHA-512 hash algorithm
* RS256 - RSA with the SHA-256 hash algorithm
* RS384 - RSA with the SHA-384 hash algorithm
* RS512 - RSA with the SHA-512 hash algorithm
* ES256 - ECDSA using the P-256 curve
* ES384 - ECDSA using the P-384 curve
* ES512 - ECDSA using the P-512 curve
License
-------
Apache License Version 2.0
Author
------
EMQ X Team.

View File

@ -1,2 +0,0 @@
1. Notice for the [Critical vulnerabilities in JSON Web Token](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/)

View File

@ -1,3 +0,0 @@
https://crypto.stackexchange.com/questions/30657/hmac-vs-ecdsa-for-jwt

View File

@ -1,49 +0,0 @@
##--------------------------------------------------------------------
## JWT Auth Plugin
##--------------------------------------------------------------------
## HMAC Hash Secret.
##
## Value: String
auth.jwt.secret = emqxsecret
## RSA or ECDSA public key file.
##
## Value: File
#auth.jwt.pubkey = "etc/certs/jwt_public_key.pem"
## The JWKs server address
##
## see: http://self-issued.info/docs/draft-ietf-jose-json-web-key.html
##
#auth.jwt.jwks.endpoint = "https://127.0.0.1:8080/jwks"
## The JWKs refresh interval
##
## Value: Duration
#auth.jwt.jwks.refresh_interval = 5m
## From where the JWT string can be got
##
## Value: username | password
## Default: password
auth.jwt.from = password
## Enable to verify claims fields
##
## Value: on | off
auth.jwt.verify_claims.enable = off
## The checklist of claims to validate
##
## Configuration format: auth.jwt.verify_claims.$name = $expected
## - $name: the name of the field in the JWT payload to be verified
## - $expected: the expected value
##
## The available placeholders for $expected:
## - %u: username
## - %c: clientid
##
## For example, to verify that the username in the JWT payload is the same
## as the client (MQTT protocol) username
#auth.jwt.verify_claims.username = "%u"

View File

@ -1,49 +0,0 @@
%%-*- mode: erlang -*-
{mapping, "auth.jwt.secret", "emqx_auth_jwt.secret", [
{datatype, string}
]}.
{mapping, "auth.jwt.jwks.endpoint", "emqx_auth_jwt.jwks", [
{datatype, string}
]}.
{mapping, "auth.jwt.jwks.refresh_interval", "emqx_auth_jwt.refresh_interval", [
{datatype, {duration, ms}}
]}.
{mapping, "auth.jwt.from", "emqx_auth_jwt.from", [
{default, password},
{datatype, atom}
]}.
{mapping, "auth.jwt.pubkey", "emqx_auth_jwt.pubkey", [
{datatype, string}
]}.
{mapping, "auth.jwt.signature_format", "emqx_auth_jwt.jwerl_opts", [
{default, "der"},
{datatype, {enum, [raw, der]}}
]}.
{mapping, "auth.jwt.verify_claims.enable", "emqx_auth_jwt.verify_claims", [
{default, off},
{datatype, flag}
]}.
{mapping, "auth.jwt.verify_claims.$name", "emqx_auth_jwt.verify_claims", [
{datatype, string}
]}.
{translation, "emqx_auth_jwt.verify_claims", fun(Conf) ->
case cuttlefish:conf_get("auth.jwt.verify_claims.enable", Conf) of
false -> cuttlefish:unset();
true ->
lists:foldr(
fun({["auth","jwt","verify_claims", Name], Value}, Acc) ->
[{list_to_atom(Name), list_to_binary(Value)} | Acc];
({["auth","jwt","verify_claims"], _Value}, Acc) ->
Acc
end, [], cuttlefish_variable:filter_by_prefix("auth.jwt.verify_claims", Conf))
end
end}.

View File

@ -1,25 +0,0 @@
{deps,
[
{jose, {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.11.1"}}}
]}.
{edoc_opts, [{preprocess, true}]}.
{erl_opts, [warn_unused_vars,
warn_shadow_vars,
warn_unused_import,
warn_obsolete_guard,
debug_info,
{parse_transform}]}.
{xref_checks, [undefined_function_calls, undefined_functions,
locals_not_used, deprecated_function_calls,
warnings_as_errors, deprecated_functions]}.
{cover_enabled, true}.
{cover_opts, [verbose]}.
{cover_export_enabled, true}.
{profiles,
[{test,
[{deps, []}
]}
]}.

View File

@ -1,14 +0,0 @@
{application, emqx_auth_jwt,
[{description, "EMQ X Authentication with JWT"},
{vsn, "4.4.0"}, % strict semver, bump manually!
{modules, []},
{registered, [emqx_auth_jwt_sup]},
{applications, [kernel,stdlib,jose]},
{mod, {emqx_auth_jwt_app, []}},
{env, []},
{licenses, ["Apache-2.0"]},
{maintainers, ["EMQ X Team <contact@emqx.io>"]},
{links, [{"Homepage", "https://emqx.io/"},
{"Github", "https://github.com/emqx/emqx-auth-jwt"}
]}
]}.

View File

@ -1,15 +0,0 @@
%% -*-: erlang -*-
{VSN,
[
{"4.3.0", [
{load_module, emqx_auth_jwt_svr, brutal_purge, soft_purge, []}
]},
{<<".*">>, []}
],
[
{"4.3.0", [
{load_module, emqx_auth_jwt_svr, brutal_purge, soft_purge, []}
]},
{<<".*">>, []}
]
}.

View File

@ -1,99 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_auth_jwt).
-include_lib("emqx/include/emqx.hrl").
-include_lib("emqx/include/logger.hrl").
-logger_header("[JWT]").
-export([ register_metrics/0
, check/3
, description/0
]).
-record(auth_metrics, {
success = 'client.auth.success',
failure = 'client.auth.failure',
ignore = 'client.auth.ignore'
}).
-define(METRICS(Type), tl(tuple_to_list(#Type{}))).
-define(METRICS(Type, K), #Type{}#Type.K).
-define(AUTH_METRICS, ?METRICS(auth_metrics)).
-define(AUTH_METRICS(K), ?METRICS(auth_metrics, K)).
-spec(register_metrics() -> ok).
register_metrics() ->
lists:foreach(fun emqx_metrics:ensure/1, ?AUTH_METRICS).
%%--------------------------------------------------------------------
%% Authentication callbacks
%%--------------------------------------------------------------------
check(ClientInfo, AuthResult, #{pid := Pid,
from := From,
checklists := Checklists}) ->
case maps:find(From, ClientInfo) of
error ->
ok = emqx_metrics:inc(?AUTH_METRICS(ignore));
{ok, undefined} ->
ok = emqx_metrics:inc(?AUTH_METRICS(ignore));
{ok, Token} ->
case emqx_auth_jwt_svr:verify(Pid, Token) of
{error, not_found} ->
ok = emqx_metrics:inc(?AUTH_METRICS(ignore));
{error, not_token} ->
ok = emqx_metrics:inc(?AUTH_METRICS(ignore));
{error, Reason} ->
ok = emqx_metrics:inc(?AUTH_METRICS(failure)),
{stop, AuthResult#{auth_result => Reason, anonymous => false}};
{ok, Claims} ->
{stop, maps:merge(AuthResult, verify_claims(Checklists, Claims, ClientInfo))}
end
end.
description() -> "Authentication with JWT".
%%------------------------------------------------------------------------------
%% Verify Claims
%%--------------------------------------------------------------------
verify_claims(Checklists, Claims, ClientInfo) ->
case do_verify_claims(feedvar(Checklists, ClientInfo), Claims) of
{error, Reason} ->
ok = emqx_metrics:inc(?AUTH_METRICS(failure)),
#{auth_result => Reason, anonymous => false};
ok ->
ok = emqx_metrics:inc(?AUTH_METRICS(success)),
#{auth_result => success, anonymous => false, jwt_claims => Claims}
end.
do_verify_claims([], _Claims) ->
ok;
do_verify_claims([{Key, Expected} | L], Claims) ->
case maps:get(Key, Claims, undefined) =:= Expected of
true -> do_verify_claims(L, Claims);
false -> {error, {verify_claim_failed, Key}}
end.
feedvar(Checklists, #{username := Username, clientid := ClientId}) ->
lists:map(fun({K, <<"%u">>}) -> {K, Username};
({K, <<"%c">>}) -> {K, ClientId};
({K, Expected}) -> {K, Expected}
end, Checklists).

View File

@ -1,81 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_auth_jwt_app).
-behaviour(application).
-behaviour(supervisor).
-emqx_plugin(auth).
-export([start/2, stop/1]).
-export([init/1]).
-define(APP, emqx_auth_jwt).
start(_Type, _Args) ->
{ok, Sup} = supervisor:start_link({local, ?MODULE}, ?MODULE, []),
{ok, Pid} = start_auth_server(jwks_svr_options()),
ok = emqx_auth_jwt:register_metrics(),
AuthEnv0 = auth_env(),
AuthEnv1 = AuthEnv0#{pid => Pid},
_ = emqx:hook('client.authenticate', {emqx_auth_jwt, check, [AuthEnv1]}),
{ok, Sup, AuthEnv1}.
stop(AuthEnv) ->
emqx:unhook('client.authenticate', {emqx_auth_jwt, check, [AuthEnv]}).
%%--------------------------------------------------------------------
%% Dummy supervisor
%%--------------------------------------------------------------------
init([]) ->
{ok, {{one_for_all, 1, 10}, []}}.
start_auth_server(Options) ->
Spec = #{id => jwt_svr,
start => {emqx_auth_jwt_svr, start_link, [Options]},
restart => permanent,
shutdown => brutal_kill,
type => worker,
modules => [emqx_auth_jwt_svr]},
supervisor:start_child(?MODULE, Spec).
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
auth_env() ->
Checklists = [{atom_to_binary(K, utf8), V}
|| {K, V} <- env(verify_claims, [])],
#{ from => env(from, password)
, checklists => Checklists
}.
jwks_svr_options() ->
[{K, V} || {K, V}
<- [{secret, env(secret, undefined)},
{pubkey, env(pubkey, undefined)},
{jwks_addr, env(jwks, undefined)},
{interval, env(refresh_interval, undefined)}],
V /= undefined].
env(Key, Default) ->
application:get_env(?APP, Key, Default).

View File

@ -1,224 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_auth_jwt_svr).
-behaviour(gen_server).
-include_lib("emqx/include/logger.hrl").
-include_lib("jose/include/jose_jwk.hrl").
-logger_header("[JWT-SVR]").
%% APIs
-export([start_link/1]).
-export([verify/2]).
%% gen_server callbacks
-export([ init/1
, handle_call/3
, handle_cast/2
, handle_info/2
, terminate/2
, code_change/3
]).
-type options() :: [option()].
-type option() :: {secret, list()}
| {pubkey, list()}
| {jwks_addr, list()}
| {interval, pos_integer()}.
-define(INTERVAL, 300000).
-record(state, {static, remote, addr, tref, intv}).
%%--------------------------------------------------------------------
%% APIs
%%--------------------------------------------------------------------
-spec start_link(options()) -> gen_server:start_ret().
start_link(Options) ->
gen_server:start_link(?MODULE, [Options], []).
-spec verify(pid(), binary())
-> {error, term()}
| {ok, Payload :: map()}.
verify(S, JwsCompacted) when is_binary(JwsCompacted) ->
case catch jose_jws:peek(JwsCompacted) of
{'EXIT', _} -> {error, not_token};
_ -> gen_server:call(S, {verify, JwsCompacted})
end.
%%--------------------------------------------------------------------
%% gen_server callbacks
%%--------------------------------------------------------------------
init([Options]) ->
ok = jose:json_module(jiffy),
{Static, Remote} = do_init_jwks(Options),
Intv = proplists:get_value(interval, Options, ?INTERVAL),
{ok, reset_timer(
#state{
static = Static,
remote = Remote,
addr = proplists:get_value(jwks_addr, Options),
intv = Intv})}.
%% @private
do_init_jwks(Options) ->
K2J = fun(K, F) ->
case proplists:get_value(K, Options) of
undefined -> undefined;
V ->
try F(V) of
{error, Reason} ->
?LOG(warning, "Build ~p JWK ~p failed: {error, ~p}~n",
[K, V, Reason]),
undefined;
J -> J
catch T:R:_ ->
?LOG(warning, "Build ~p JWK ~p failed: {~p, ~p}~n",
[K, V, T, R]),
undefined
end
end
end,
OctJwk = K2J(secret, fun(V) ->
jose_jwk:from_oct(list_to_binary(V))
end),
PemJwk = K2J(pubkey, fun jose_jwk:from_pem_file/1),
Remote = K2J(jwks_addr, fun request_jwks/1),
{[J ||J <- [OctJwk, PemJwk], J /= undefined], Remote}.
handle_call({verify, JwsCompacted}, _From, State) ->
handle_verify(JwsCompacted, State);
handle_call(_Req, _From, State) ->
{reply, ok, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({timeout, _TRef, refresh}, State = #state{addr = Addr}) ->
NState = try
State#state{remote = request_jwks(Addr)}
catch _:_ ->
State
end,
{noreply, reset_timer(NState)};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, State) ->
_ = cancel_timer(State),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%--------------------------------------------------------------------
%% Internal funcs
%%--------------------------------------------------------------------
handle_verify(JwsCompacted,
State = #state{static = Static, remote = Remote}) ->
try
Jwks = case emqx_json:decode(jose_jws:peek_protected(JwsCompacted), [return_maps]) of
#{<<"kid">> := Kid} when Remote /= undefined ->
[J || J <- Remote, maps:get(<<"kid">>, J#jose_jwk.fields, undefined) =:= Kid];
_ -> Static
end,
case Jwks of
[] -> {reply, {error, not_found}, State};
_ ->
{reply, do_verify(JwsCompacted, Jwks), State}
end
catch
Class : Reason : Stk ->
?LOG(error, "Handle JWK crashed: ~p, ~p, stacktrace: ~p~n",
[Class, Reason, Stk]),
{reply, {error, invalid_signature}, State}
end.
request_jwks(Addr) ->
case httpc:request(get, {Addr, []}, [], [{body_format, binary}]) of
{error, Reason} ->
error(Reason);
{ok, {_Code, _Headers, Body}} ->
try
JwkSet = jose_jwk:from(emqx_json:decode(Body, [return_maps])),
{_, Jwks} = JwkSet#jose_jwk.keys, Jwks
catch _:_ ->
?LOG(error, "Invalid jwks server response: ~p~n", [Body]),
error(badarg)
end
end.
reset_timer(State = #state{addr = undefined}) ->
State;
reset_timer(State = #state{intv = Intv}) ->
State#state{tref = erlang:start_timer(Intv, self(), refresh)}.
cancel_timer(State = #state{tref = undefined}) ->
State;
cancel_timer(State = #state{tref = TRef}) ->
_ = erlang:cancel_timer(TRef),
State#state{tref = undefined}.
do_verify(_JwsCompated, []) ->
{error, invalid_signature};
do_verify(JwsCompacted, [Jwk|More]) ->
case jose_jws:verify(Jwk, JwsCompacted) of
{true, Payload, _Jws} ->
Claims = emqx_json:decode(Payload, [return_maps]),
case check_claims(Claims) of
{false, <<"exp">>} ->
{error, {invalid_signature, expired}};
NClaims ->
{ok, NClaims}
end;
{false, _, _} ->
do_verify(JwsCompacted, More)
end.
check_claims(Claims) ->
Now = os:system_time(seconds),
Checker = [{<<"exp">>, fun(ExpireTime) ->
Now < ExpireTime
end},
{<<"iat">>, fun(IssueAt) ->
IssueAt =< Now
end},
{<<"nbf">>, fun(NotBefore) ->
NotBefore =< Now
end}
],
do_check_claim(Checker, Claims).
do_check_claim([], Claims) ->
Claims;
do_check_claim([{K, F}|More], Claims) ->
case maps:take(K, Claims) of
error -> do_check_claim(More, Claims);
{V, NClaims} ->
case F(V) of
true -> do_check_claim(More, NClaims);
_ -> {false, K}
end
end.

View File

@ -1,166 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_auth_jwt_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("emqx/include/emqx.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-define(APP, emqx_auth_jwt).
all() ->
[{group, emqx_auth_jwt}].
groups() ->
[{emqx_auth_jwt, [sequence], [ t_check_auth
, t_check_claims
, t_check_claims_clientid
, t_check_claims_username
, t_check_claims_kid_in_header
]}
].
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([emqx_auth_jwt], fun set_special_configs/1),
Config.
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([emqx_auth_jwt]).
set_special_configs(emqx) ->
application:set_env(emqx, allow_anonymous, false),
application:set_env(emqx, acl_nomatch, deny),
application:set_env(emqx, enable_acl_cache, false),
LoadedPluginPath = filename:join(["test", "emqx_SUITE_data", "loaded_plugins"]),
AclFilePath = filename:join(["test", "emqx_SUITE_data", "acl.conf"]),
application:set_env(emqx, plugins_loaded_file,
emqx_ct_helpers:deps_path(emqx, LoadedPluginPath)),
application:set_env(emqx, acl_file,
emqx_ct_helpers:deps_path(emqx, AclFilePath));
set_special_configs(emqx_auth_jwt) ->
application:set_env(emqx_auth_jwt, secret, "emqxsecret"),
application:set_env(emqx_auth_jwt, from, password);
set_special_configs(_) ->
ok.
sign(Payload, Header, Key) when is_map(Header) ->
Jwk = jose_jwk:from_oct(Key),
Jwt = emqx_json:encode(Payload),
{_, Token} = jose_jws:compact(jose_jwt:sign(Jwk, Header, Jwt)),
Token;
sign(Payload, Alg, Key) ->
Jwk = jose_jwk:from_oct(Key),
Jwt = emqx_json:encode(Payload),
{_, Token} = jose_jws:compact(jose_jwt:sign(Jwk, #{<<"alg">> => Alg}, Jwt)),
Token.
%%------------------------------------------------------------------------------
%% Testcases
%%------------------------------------------------------------------------------
t_check_auth(_) ->
Plain = #{clientid => <<"client1">>, username => <<"plain">>, zone => external},
Jwt = sign([{clientid, <<"client1">>},
{username, <<"plain">>},
{exp, os:system_time(seconds) + 3}], <<"HS256">>, <<"emqxsecret">>),
ct:pal("Jwt: ~p~n", [Jwt]),
Result0 = emqx_access_control:authenticate(Plain#{password => Jwt}),
ct:pal("Auth result: ~p~n", [Result0]),
?assertMatch({ok, #{auth_result := success, jwt_claims := #{<<"clientid">> := <<"client1">>}}}, Result0),
ct:sleep(3100),
Result1 = emqx_access_control:authenticate(Plain#{password => Jwt}),
ct:pal("Auth result after 1000ms: ~p~n", [Result1]),
?assertMatch({error, _}, Result1),
Jwt_Error = sign([{client_id, <<"client1">>},
{username, <<"plain">>}], <<"HS256">>, <<"secret">>),
ct:pal("invalid jwt: ~p~n", [Jwt_Error]),
Result2 = emqx_access_control:authenticate(Plain#{password => Jwt_Error}),
ct:pal("Auth result for the invalid jwt: ~p~n", [Result2]),
?assertEqual({error, invalid_signature}, Result2),
?assertMatch({error, _}, emqx_access_control:authenticate(Plain#{password => <<"asd">>})).
t_check_claims(_) ->
application:set_env(emqx_auth_jwt, verify_claims, [{sub, <<"value">>}]),
application:stop(emqx_auth_jwt), application:start(emqx_auth_jwt),
Plain = #{clientid => <<"client1">>, username => <<"plain">>, zone => external},
Jwt = sign([{client_id, <<"client1">>},
{username, <<"plain">>},
{sub, value},
{exp, os:system_time(seconds) + 3}], <<"HS256">>, <<"emqxsecret">>),
Result0 = emqx_access_control:authenticate(Plain#{password => Jwt}),
ct:pal("Auth result: ~p~n", [Result0]),
?assertMatch({ok, #{auth_result := success, jwt_claims := _}}, Result0),
Jwt_Error = sign([{clientid, <<"client1">>},
{username, <<"plain">>}], <<"HS256">>, <<"secret">>),
Result2 = emqx_access_control:authenticate(Plain#{password => Jwt_Error}),
ct:pal("Auth result for the invalid jwt: ~p~n", [Result2]),
?assertEqual({error, invalid_signature}, Result2).
t_check_claims_clientid(_) ->
application:set_env(emqx_auth_jwt, verify_claims, [{clientid, <<"%c">>}]),
application:stop(emqx_auth_jwt), application:start(emqx_auth_jwt),
Plain = #{clientid => <<"client23">>, username => <<"plain">>, zone => external},
Jwt = sign([{clientid, <<"client23">>},
{username, <<"plain">>},
{exp, os:system_time(seconds) + 3}], <<"HS256">>, <<"emqxsecret">>),
Result0 = emqx_access_control:authenticate(Plain#{password => Jwt}),
ct:pal("Auth result: ~p~n", [Result0]),
?assertMatch({ok, #{auth_result := success, jwt_claims := _}}, Result0),
Jwt_Error = sign([{clientid, <<"client1">>},
{username, <<"plain">>}], <<"HS256">>, <<"secret">>),
Result2 = emqx_access_control:authenticate(Plain#{password => Jwt_Error}),
ct:pal("Auth result for the invalid jwt: ~p~n", [Result2]),
?assertEqual({error, invalid_signature}, Result2).
t_check_claims_username(_) ->
application:set_env(emqx_auth_jwt, verify_claims, [{username, <<"%u">>}]),
application:stop(emqx_auth_jwt), application:start(emqx_auth_jwt),
Plain = #{clientid => <<"client23">>, username => <<"plain">>, zone => external},
Jwt = sign([{client_id, <<"client23">>},
{username, <<"plain">>},
{exp, os:system_time(seconds) + 3}], <<"HS256">>, <<"emqxsecret">>),
Result0 = emqx_access_control:authenticate(Plain#{password => Jwt}),
ct:pal("Auth result: ~p~n", [Result0]),
?assertMatch({ok, #{auth_result := success, jwt_claims := _}}, Result0),
Jwt_Error = sign([{clientid, <<"client1">>},
{username, <<"plain">>}], <<"HS256">>, <<"secret">>),
Result3 = emqx_access_control:authenticate(Plain#{password => Jwt_Error}),
ct:pal("Auth result for the invalid jwt: ~p~n", [Result3]),
?assertEqual({error, invalid_signature}, Result3).
t_check_claims_kid_in_header(_) ->
application:set_env(emqx_auth_jwt, verify_claims, []),
Plain = #{clientid => <<"client23">>, username => <<"plain">>, zone => external},
Jwt = sign([{clientid, <<"client23">>},
{username, <<"plain">>},
{exp, os:system_time(seconds) + 3}],
#{<<"alg">> => <<"HS256">>,
<<"kid">> => <<"a_kid_str">>}, <<"emqxsecret">>),
Result0 = emqx_access_control:authenticate(Plain#{password => Jwt}),
ct:pal("Auth result: ~p~n", [Result0]),
?assertMatch({ok, #{auth_result := success, jwt_claims := _}}, Result0).

View File

@ -1,25 +0,0 @@
.eunit
deps
*.o
*.beam
*.plt
erl_crash.dump
ebin
rel/example_project
.concrete/DEV_MODE
.rebar
.erlang.mk/
emqx_auth_ldap.d
data/
cover/
ct.coverdata
eunit.coverdata
logs/
test/ct.cover.spec
.DS_Store
_build/
rebar.lock
erlang.mk
rebar3.crashdump
.rebar3/
etc/emqx_auth_ldap.conf.rendered

View File

@ -1,96 +0,0 @@
emqx_auth_ldap
==============
EMQ X LDAP Authentication Plugin
Build
-----
```
make
```
Load the Plugin
---------------
```
# ./bin/emqx_ctl plugins load emqx_auth_ldap
```
Generate Password
---------------
```
slappasswd -h '{ssha}' -s public
```
Configuration Open LDAP
-----------------------
vim /etc/openldap/slapd.conf
```
include /etc/openldap/schema/core.schema
include /etc/openldap/schema/cosine.schema
include /etc/openldap/schema/inetorgperson.schema
include /etc/openldap/schema/ppolicy.schema
include /etc/openldap/schema/emqx.schema
database bdb
suffix "dc=emqx,dc=io"
rootdn "cn=root,dc=emqx,dc=io"
rootpw {SSHA}eoF7NhNrejVYYyGHqnt+MdKNBh4r1w3W
directory /etc/openldap/data
```
If the ldap launched with error below:
```
Unrecognized database type (bdb)
5c4a72b9 slapd.conf: line 7: <database> failed init (bdb)
slapadd: bad configuration file!
```
Insert lines to the slapd.conf
```
modulepath /usr/lib/ldap
moduleload back_bdb.la
```
Import EMQX User Data
----------------------
Use ldapadd
```
# ldapadd -x -D "cn=root,dc=emqx,dc=io" -w public -f emqx.com.ldif
```
Use slapadd
```
# sudo slapadd -l schema/emqx.io.ldif -f slapd.conf
```
Launch slapd
```
# sudo slapd -d 3
```
Test
-----
After configure slapd correctly and launch slapd successfully.
You could execute
``` bash
# make tests
```
License
-------
Apache License Version 2.0
Author
------
EMQ X Team.

View File

@ -1,135 +0,0 @@
## create emqx.io
dn:dc=emqx,dc=io
objectclass: top
objectclass: dcobject
objectclass: organization
dc:emqx
o:emqx,Inc.
# create testdevice.emqx.io
dn:ou=testdevice,dc=emqx,dc=io
objectClass: top
objectclass:organizationalUnit
ou:testdevice
# create user admin
dn:uid=admin,ou=testdevice,dc=emqx,dc=io
objectClass: top
objectClass: simpleSecurityObject
objectClass: account
userPassword:: e1NIQX1XNnBoNU1tNVB6OEdnaVVMYlBnekczN21qOWc9
uid: admin
## create user=mqttuser0001,
# password=mqttuser0001,
# passhash={SHA}mlb3fat40MKBTXUVZwCKmL73R/0=
# base64passhash=e1NIQX1tbGIzZmF0NDBNS0JUWFVWWndDS21MNzNSLzA9
dn:uid=mqttuser0001,ou=testdevice,dc=emqx,dc=io
objectClass: top
objectClass: mqttUser
objectClass: mqttDevice
objectClass: mqttSecurity
uid: mqttuser0001
isEnabled: TRUE
mqttAccountName: user1
mqttPublishTopic: mqttuser0001/pub/1
mqttPublishTopic: mqttuser0001/pub/+
mqttPublishTopic: mqttuser0001/pub/#
mqttSubscriptionTopic: mqttuser0001/sub/1
mqttSubscriptionTopic: mqttuser0001/sub/+
mqttSubscriptionTopic: mqttuser0001/sub/#
mqttPubSubTopic: mqttuser0001/pubsub/1
mqttPubSubTopic: mqttuser0001/pubsub/+
mqttPubSubTopic: mqttuser0001/pubsub/#
userPassword:: e1NIQX1tbGIzZmF0NDBNS0JUWFVWWndDS21MNzNSLzA9
## create user=mqttuser0002
# password=mqttuser0002,
# passhash={SSHA}n9XdtoG4Q/TQ3TQF4Y+khJbMBH4qXj4M
# base64passhash=e1NTSEF9bjlYZHRvRzRRL1RRM1RRRjRZK2toSmJNQkg0cVhqNE0=
dn:uid=mqttuser0002,ou=testdevice,dc=emqx,dc=io
objectClass: top
objectClass: mqttUser
objectClass: mqttDevice
objectClass: mqttSecurity
uid: mqttuser0002
isEnabled: TRUE
mqttAccountName: user2
mqttPublishTopic: mqttuser0002/pub/1
mqttPublishTopic: mqttuser0002/pub/+
mqttPublishTopic: mqttuser0002/pub/#
mqttSubscriptionTopic: mqttuser0002/sub/1
mqttSubscriptionTopic: mqttuser0002/sub/+
mqttSubscriptionTopic: mqttuser0002/sub/#
mqttPubSubTopic: mqttuser0002/pubsub/1
mqttPubSubTopic: mqttuser0002/pubsub/+
mqttPubSubTopic: mqttuser0002/pubsub/#
userPassword:: e1NTSEF9bjlYZHRvRzRRL1RRM1RRRjRZK2toSmJNQkg0cVhqNE0=
## create user mqttuser0003
# password=mqttuser0003,
# passhash={MD5}ybsPGoaK3nDyiQvveiCOIw==
# base64passhash=e01ENX15YnNQR29hSzNuRHlpUXZ2ZWlDT0l3PT0=
dn:uid=mqttuser0003,ou=testdevice,dc=emqx,dc=io
objectClass: top
objectClass: mqttUser
objectClass: mqttDevice
objectClass: mqttSecurity
uid: mqttuser0003
isEnabled: TRUE
mqttPublishTopic: mqttuser0003/pub/1
mqttPublishTopic: mqttuser0003/pub/+
mqttPublishTopic: mqttuser0003/pub/#
mqttSubscriptionTopic: mqttuser0003/sub/1
mqttSubscriptionTopic: mqttuser0003/sub/+
mqttSubscriptionTopic: mqttuser0003/sub/#
mqttPubSubTopic: mqttuser0003/pubsub/1
mqttPubSubTopic: mqttuser0003/pubsub/+
mqttPubSubTopic: mqttuser0003/pubsub/#
userPassword:: e01ENX15YnNQR29hSzNuRHlpUXZ2ZWlDT0l3PT0=
## create user mqttuser0004
# password=mqttuser0004,
# passhash={MD5}2Br6pPDSEDIEvUlu9+s+MA==
# base64passhash=e01ENX0yQnI2cFBEU0VESUV2VWx1OStzK01BPT0=
dn:uid=mqttuser0004,ou=testdevice,dc=emqx,dc=io
objectClass: top
objectClass: mqttUser
objectClass: mqttDevice
objectClass: mqttSecurity
uid: mqttuser0004
isEnabled: TRUE
mqttPublishTopic: mqttuser0004/pub/1
mqttPublishTopic: mqttuser0004/pub/+
mqttPublishTopic: mqttuser0004/pub/#
mqttSubscriptionTopic: mqttuser0004/sub/1
mqttSubscriptionTopic: mqttuser0004/sub/+
mqttSubscriptionTopic: mqttuser0004/sub/#
mqttPubSubTopic: mqttuser0004/pubsub/1
mqttPubSubTopic: mqttuser0004/pubsub/+
mqttPubSubTopic: mqttuser0004/pubsub/#
userPassword: {MD5}2Br6pPDSEDIEvUlu9+s+MA==
## create user mqttuser0005
# password=mqttuser0005,
# passhash={SHA}jKnxeEDGR14kE8AR7yuVFOelhz4=
# base64passhash=e1NIQX1qS254ZUVER1IxNGtFOEFSN3l1VkZPZWxoejQ9
objectClass: top
dn:uid=mqttuser0005,ou=testdevice,dc=emqx,dc=io
objectClass: mqttUser
objectClass: mqttDevice
objectClass: mqttSecurity
uid: mqttuser0005
isEnabled: TRUE
mqttPublishTopic: mqttuser0005/pub/1
mqttPublishTopic: mqttuser0005/pub/+
mqttPublishTopic: mqttuser0005/pub/#
mqttSubscriptionTopic: mqttuser0005/sub/1
mqttSubscriptionTopic: mqttuser0005/sub/+
mqttSubscriptionTopic: mqttuser0005/sub/#
mqttPubSubTopic: mqttuser0005/pubsub/1
mqttPubSubTopic: mqttuser0005/pubsub/+
mqttPubSubTopic: mqttuser0005/pubsub/#
userPassword: {SHA}jKnxeEDGR14kE8AR7yuVFOelhz4=

View File

@ -1,46 +0,0 @@
#
# Preliminary Apple OS X Native LDAP Schema
# This file is subject to change.
#
attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.1.3 NAME 'isEnabled'
EQUALITY booleanMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
SINGLE-VALUE
USAGE userApplications )
attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4.1 NAME ( 'mqttPublishTopic' 'mpt' )
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
USAGE userApplications )
attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4.2 NAME ( 'mqttSubscriptionTopic' 'mst' )
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
USAGE userApplications )
attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4.3 NAME ( 'mqttPubSubTopic' 'mpst' )
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
USAGE userApplications )
attributetype ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4.4 NAME ( 'mqttAccountName' 'man' )
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
USAGE userApplications )
objectclass ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.4 NAME 'mqttUser'
AUXILIARY
MAY ( mqttPublishTopic $ mqttSubscriptionTopic $ mqttPubSubTopic $ mqttAccountName) )
objectclass ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.2 NAME 'mqttDevice'
SUP top
STRUCTURAL
MUST ( uid )
MAY ( isEnabled ) )
objectclass ( 1.3.6.1.4.1.11.2.53.2.2.3.1.2.3.3 NAME 'mqttSecurity'
SUP top
AUXILIARY
MAY ( userPassword $ userPKCS12 $ pwdAttribute $ pwdLockout ) )

View File

@ -1,76 +0,0 @@
##--------------------------------------------------------------------
## LDAP Auth Plugin
##--------------------------------------------------------------------
## LDAP server list, seperated by ','.
##
## Value: String
auth.ldap.servers = "127.0.0.1"
## LDAP server port.
##
## Value: Port
auth.ldap.port = 389
## LDAP pool size
##
## Value: String
auth.ldap.pool = 8
## LDAP Bind DN.
##
## Value: DN
auth.ldap.bind_dn = "cn=root,dc=emqx,dc=io"
## LDAP Bind Password.
##
## Value: String
auth.ldap.bind_password = public
## LDAP query timeout.
##
## Value: Number
auth.ldap.timeout = 30s
## Device DN.
##
## Variables:
##
## Value: DN
auth.ldap.device_dn = "ou=device,dc=emqx,dc=io"
## Specified ObjectClass
##
## Variables:
##
## Value: string
auth.ldap.match_objectclass = mqttUser
## attributetype for username
##
## Variables:
##
## Value: string
auth.ldap.username.attributetype = uid
## attributetype for password
##
## Variables:
##
## Value: string
auth.ldap.password.attributetype = userPassword
## Whether to enable SSL.
##
## Value: true | false
auth.ldap.ssl.enable = false
#auth.ldap.ssl.certfile = "etc/certs/cert.pem"
#auth.ldap.ssl.keyfile = "etc/certs/key.pem"
#auth.ldap.ssl.cacertfile = "etc/certs/cacert.pem"
#auth.ldap.ssl.verify = "verify_peer"
#auth.ldap.ssl.server_name_indication = your_server_name

Some files were not shown because too many files have changed in this diff Show More