chore(project): merge code from master
This commit is contained in:
commit
477097c062
|
@ -39,7 +39,7 @@ emqx_test(){
|
||||||
unzip -q "${PACKAGE_PATH}/${packagename}"
|
unzip -q "${PACKAGE_PATH}/${packagename}"
|
||||||
export EMQX_ZONE__EXTERNAL__SERVER_KEEPALIVE=60 \
|
export EMQX_ZONE__EXTERNAL__SERVER_KEEPALIVE=60 \
|
||||||
EMQX_MQTT__MAX_TOPIC_ALIAS=10
|
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"
|
echo "running ${packagename} start"
|
||||||
if ! "${PACKAGE_PATH}"/emqx/bin/emqx start; then
|
if ! "${PACKAGE_PATH}"/emqx/bin/emqx start; then
|
||||||
|
@ -115,7 +115,7 @@ emqx_test(){
|
||||||
running_test(){
|
running_test(){
|
||||||
export EMQX_ZONE__EXTERNAL__SERVER_KEEPALIVE=60 \
|
export EMQX_ZONE__EXTERNAL__SERVER_KEEPALIVE=60 \
|
||||||
EMQX_MQTT__MAX_TOPIC_ALIAS=10
|
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
|
if ! emqx start; then
|
||||||
cat /var/log/emqx/erlang.log.1 || true
|
cat /var/log/emqx/erlang.log.1 || true
|
||||||
|
|
|
@ -38,7 +38,7 @@ services:
|
||||||
- -c
|
- -c
|
||||||
- |
|
- |
|
||||||
sed -i "s 127.0.0.1 $$(ip route show |grep "link" |awk '{print $$1}') g" /opt/emqx/etc/acl.conf
|
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
|
/opt/emqx/bin/emqx foreground
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "/opt/emqx/bin/emqx_ctl", "status"]
|
test: ["CMD", "/opt/emqx/bin/emqx_ctl", "status"]
|
||||||
|
@ -62,7 +62,7 @@ services:
|
||||||
- -c
|
- -c
|
||||||
- |
|
- |
|
||||||
sed -i "s 127.0.0.1 $$(ip route show |grep "link" |awk '{print $$1}') g" /opt/emqx/etc/acl.conf
|
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
|
/opt/emqx/bin/emqx foreground
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "/opt/emqx/bin/emqx", "ping"]
|
test: ["CMD", "/opt/emqx/bin/emqx", "ping"]
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -9,6 +9,8 @@ services:
|
||||||
MONGO_INITDB_DATABASE: mqtt
|
MONGO_INITDB_DATABASE: mqtt
|
||||||
networks:
|
networks:
|
||||||
- emqx_bridge
|
- emqx_bridge
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
command:
|
command:
|
||||||
--ipv6
|
--ipv6
|
||||||
--bind_ip_all
|
--bind_ip_all
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -11,9 +11,11 @@ services:
|
||||||
MYSQL_USER: ssluser
|
MYSQL_USER: ssluser
|
||||||
MYSQL_PASSWORD: public
|
MYSQL_PASSWORD: public
|
||||||
volumes:
|
volumes:
|
||||||
- ../../apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data/ca.pem:/etc/certs/ca-cert.pem
|
- ../../apps/emqx/etc/certs/cacert.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/etc/certs/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/key.pem:/etc/certs/server-key.pem
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
networks:
|
networks:
|
||||||
- emqx_bridge
|
- emqx_bridge
|
||||||
command:
|
command:
|
||||||
|
|
|
@ -5,7 +5,9 @@ services:
|
||||||
container_name: redis
|
container_name: redis
|
||||||
image: redis:${REDIS_TAG}
|
image: redis:${REDIS_TAG}
|
||||||
volumes:
|
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
|
- ./redis/:/data/conf
|
||||||
command: bash -c "/bin/bash /data/conf/redis.sh --node cluster --tls-enabled && tail -f /var/log/redis-server.log"
|
command: bash -c "/bin/bash /data/conf/redis.sh --node cluster --tls-enabled && tail -f /var/log/redis-server.log"
|
||||||
networks:
|
networks:
|
||||||
|
|
|
@ -5,7 +5,9 @@ services:
|
||||||
container_name: redis
|
container_name: redis
|
||||||
image: redis:${REDIS_TAG}
|
image: redis:${REDIS_TAG}
|
||||||
volumes:
|
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
|
- ./redis/:/data/conf
|
||||||
command: bash -c "/bin/bash /data/conf/redis.sh --node sentinel --tls-enabled && tail -f /var/log/redis-server.log"
|
command: bash -c "/bin/bash /data/conf/redis.sh --node sentinel --tls-enabled && tail -f /var/log/redis-server.log"
|
||||||
networks:
|
networks:
|
||||||
|
|
|
@ -5,15 +5,17 @@ services:
|
||||||
container_name: redis
|
container_name: redis
|
||||||
image: redis:${REDIS_TAG}
|
image: redis:${REDIS_TAG}
|
||||||
volumes:
|
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:
|
command:
|
||||||
- redis-server
|
- redis-server
|
||||||
- "--bind 0.0.0.0 ::"
|
- "--bind 0.0.0.0 ::"
|
||||||
- --requirepass public
|
- --requirepass public
|
||||||
- --tls-port 6380
|
- --tls-port 6380
|
||||||
- --tls-cert-file /tls/redis.crt
|
- --tls-cert-file /etc/certs/redis.crt
|
||||||
- --tls-key-file /tls/redis.key
|
- --tls-key-file /etc/certs/redis.key
|
||||||
- --tls-ca-cert-file /tls/ca.crt
|
- --tls-ca-cert-file /etc/certs/ca.crt
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
- emqx_bridge
|
- emqx_bridge
|
||||||
|
|
|
@ -2,9 +2,9 @@ ARG BUILD_FROM=postgres:11
|
||||||
FROM ${BUILD_FROM}
|
FROM ${BUILD_FROM}
|
||||||
ARG POSTGRES_USER=postgres
|
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 .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/etc/certs/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/etc/certs/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/cacert.pem /var/lib/postgresql/root.crt
|
||||||
RUN chmod 600 /var/lib/postgresql/pg_hba.conf
|
RUN chmod 600 /var/lib/postgresql/pg_hba.conf
|
||||||
RUN chmod 600 /var/lib/postgresql/server.key
|
RUN chmod 600 /var/lib/postgresql/server.key
|
||||||
RUN chmod 600 /var/lib/postgresql/server.crt
|
RUN chmod 600 /var/lib/postgresql/server.crt
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
daemonize yes
|
daemonize yes
|
||||||
bind 0.0.0.0 ::
|
bind 0.0.0.0 ::
|
||||||
logfile /var/log/redis-server.log
|
logfile /var/log/redis-server.log
|
||||||
tls-cert-file /tls/redis.crt
|
tls-cert-file /etc/certs/redis.crt
|
||||||
tls-key-file /tls/redis.key
|
tls-key-file /etc/certs/redis.key
|
||||||
tls-ca-cert-file /tls/ca.crt
|
tls-ca-cert-file /etc/certs/ca.crt
|
||||||
tls-replication yes
|
tls-replication yes
|
||||||
tls-cluster yes
|
tls-cluster yes
|
||||||
protected-mode no
|
protected-mode no
|
||||||
|
|
|
@ -91,7 +91,7 @@ do
|
||||||
fi
|
fi
|
||||||
if [ "${node}" = "cluster" ] ; then
|
if [ "${node}" = "cluster" ] ; then
|
||||||
if $tls ; 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
|
else
|
||||||
yes "yes" | redis-cli --cluster create "$LOCAL_IP:7000" "$LOCAL_IP:7001" "$LOCAL_IP:7002" --pass public --no-auth-warning;
|
yes "yes" | redis-cli --cluster create "$LOCAL_IP:7000" "$LOCAL_IP:7001" "$LOCAL_IP:7002" --pass public --no-auth-warning;
|
||||||
fi
|
fi
|
||||||
|
@ -107,9 +107,9 @@ EOF
|
||||||
cat >>/_sentinel.conf<<EOF
|
cat >>/_sentinel.conf<<EOF
|
||||||
tls-port 26380
|
tls-port 26380
|
||||||
tls-replication yes
|
tls-replication yes
|
||||||
tls-cert-file /tls/redis.crt
|
tls-cert-file /etc/certs/redis.crt
|
||||||
tls-key-file /tls/redis.key
|
tls-key-file /etc/certs/redis.key
|
||||||
tls-ca-cert-file /tls/ca.crt
|
tls-ca-cert-file /etc/certs/ca.crt
|
||||||
sentinel monitor mymaster $LOCAL_IP 8000 1
|
sentinel monitor mymaster $LOCAL_IP 8000 1
|
||||||
EOF
|
EOF
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{erl_opts, [debug_info]}.
|
{erl_opts, [debug_info]}.
|
||||||
{deps,
|
{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, [
|
{shell, [
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
!sed -i '/emqx_telemetry/d' data/loaded_plugins
|
!sed -i '/emqx_telemetry/d' data/loaded_plugins
|
||||||
|
|
||||||
!./bin/emqx start
|
!./bin/emqx start
|
||||||
?EMQ X (.*) is started successfully!
|
?EMQ X .* is started successfully!
|
||||||
?SH-PROMPT
|
?SH-PROMPT
|
||||||
|
|
||||||
!./bin/emqx_ctl cluster join emqx@127.0.0.1
|
!./bin/emqx_ctl cluster join emqx@127.0.0.1
|
||||||
|
@ -99,6 +99,10 @@
|
||||||
"""
|
"""
|
||||||
?SH-PROMPT
|
?SH-PROMPT
|
||||||
|
|
||||||
|
!./bin/emqx_ctl plugins list | grep emqx_management
|
||||||
|
?Plugin\(emqx_management.*active=true\)
|
||||||
|
?SH-PROMPT
|
||||||
|
|
||||||
[shell emqx2]
|
[shell emqx2]
|
||||||
!echo "" > log/emqx.log.1
|
!echo "" > log/emqx.log.1
|
||||||
?SH-PROMPT
|
?SH-PROMPT
|
||||||
|
@ -120,6 +124,10 @@
|
||||||
"""
|
"""
|
||||||
?SH-PROMPT
|
?SH-PROMPT
|
||||||
|
|
||||||
|
!./bin/emqx_ctl plugins list | grep emqx_management
|
||||||
|
?Plugin\(emqx_management.*active=true\)
|
||||||
|
?SH-PROMPT
|
||||||
|
|
||||||
[shell bench]
|
[shell bench]
|
||||||
???publish complete
|
???publish complete
|
||||||
??SH-PROMPT:
|
??SH-PROMPT:
|
||||||
|
|
|
@ -3,7 +3,6 @@ name: Bug Report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: ''
|
title: ''
|
||||||
labels: Support
|
labels: Support
|
||||||
assignees: tigercl
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ name: Feature Request
|
||||||
about: Suggest an idea for this project
|
about: Suggest an idea for this project
|
||||||
title: ''
|
title: ''
|
||||||
labels: Feature
|
labels: Feature
|
||||||
assignees: tigercl
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ name: Support Needed
|
||||||
about: Asking a question about usages, docs or anything you're insterested in
|
about: Asking a question about usages, docs or anything you're insterested in
|
||||||
title: ''
|
title: ''
|
||||||
labels: Support
|
labels: Support
|
||||||
assignees: tigercl
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,7 @@ jobs:
|
||||||
- name: build
|
- name: build
|
||||||
env:
|
env:
|
||||||
PYTHON: python
|
PYTHON: python
|
||||||
|
DIAGNOSTIC: 1
|
||||||
run: |
|
run: |
|
||||||
$env:PATH = "${{ steps.install_erlang.outputs.erlpath }}\bin;$env:PATH"
|
$env:PATH = "${{ steps.install_erlang.outputs.erlpath }}\bin;$env:PATH"
|
||||||
|
|
||||||
|
@ -168,19 +169,21 @@ jobs:
|
||||||
- name: build
|
- name: build
|
||||||
run: |
|
run: |
|
||||||
. $HOME/.kerl/${{ matrix.erl_otp }}/activate
|
. $HOME/.kerl/${{ matrix.erl_otp }}/activate
|
||||||
make -C source ensure-rebar3
|
cd source
|
||||||
sudo cp source/rebar3 /usr/local/bin/rebar3
|
make ensure-rebar3
|
||||||
make -C source ${{ matrix.profile }}-zip
|
sudo cp rebar3 /usr/local/bin/rebar3
|
||||||
|
rm -rf _build/${{ matrix.profile }}/lib
|
||||||
|
make ${{ matrix.profile }}-zip
|
||||||
- name: test
|
- name: test
|
||||||
run: |
|
run: |
|
||||||
cd source
|
cd source
|
||||||
pkg_name=$(basename _packages/${{ matrix.profile }}/${{ matrix.profile }}-*.zip)
|
pkg_name=$(basename _packages/${{ matrix.profile }}/${{ matrix.profile }}-*.zip)
|
||||||
unzip -q _packages/${{ matrix.profile }}/$pkg_name
|
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
|
./emqx/bin/emqx start || cat emqx/log/erlang.log.1
|
||||||
ready='no'
|
ready='no'
|
||||||
for i in {1..10}; do
|
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'
|
ready='yes'
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
|
@ -339,13 +342,6 @@ jobs:
|
||||||
- [amd64, x86_64]
|
- [amd64, x86_64]
|
||||||
- [arm64v8, aarch64]
|
- [arm64v8, aarch64]
|
||||||
- [arm32v7, arm]
|
- [arm32v7, arm]
|
||||||
- [i386, i386]
|
|
||||||
- [s390x, s390x]
|
|
||||||
exclude:
|
|
||||||
- profile: emqx-ee
|
|
||||||
arch: [i386, i386]
|
|
||||||
- profile: emqx-ee
|
|
||||||
arch: [s390x, s390x]
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v2
|
- uses: actions/download-artifact@v2
|
||||||
|
|
|
@ -38,6 +38,11 @@ jobs:
|
||||||
run: make ${EMQX_NAME}-zip
|
run: make ${EMQX_NAME}-zip
|
||||||
- name: build deb/rpm packages
|
- name: build deb/rpm packages
|
||||||
run: make ${EMQX_NAME}-pkg
|
run: make ${EMQX_NAME}-pkg
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
if: failure()
|
||||||
|
with:
|
||||||
|
name: rebar3.crashdump
|
||||||
|
path: ./rebar3.crashdump
|
||||||
- name: pakcages test
|
- name: pakcages test
|
||||||
run: |
|
run: |
|
||||||
export CODE_PATH=$GITHUB_WORKSPACE
|
export CODE_PATH=$GITHUB_WORKSPACE
|
||||||
|
@ -94,15 +99,20 @@ jobs:
|
||||||
make ensure-rebar3
|
make ensure-rebar3
|
||||||
sudo cp rebar3 /usr/local/bin/rebar3
|
sudo cp rebar3 /usr/local/bin/rebar3
|
||||||
make ${EMQX_NAME}-zip
|
make ${EMQX_NAME}-zip
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
if: failure()
|
||||||
|
with:
|
||||||
|
name: rebar3.crashdump
|
||||||
|
path: ./rebar3.crashdump
|
||||||
- name: test
|
- name: test
|
||||||
run: |
|
run: |
|
||||||
pkg_name=$(basename _packages/${EMQX_NAME}/emqx-*.zip)
|
pkg_name=$(basename _packages/${EMQX_NAME}/emqx-*.zip)
|
||||||
unzip -q _packages/${EMQX_NAME}/$pkg_name
|
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
|
./emqx/bin/emqx start || cat emqx/log/erlang.log.1
|
||||||
ready='no'
|
ready='no'
|
||||||
for i in {1..10}; do
|
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'
|
ready='yes'
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -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
|
|
|
@ -48,13 +48,13 @@ jobs:
|
||||||
echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:waiting emqx";
|
echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:waiting emqx";
|
||||||
sleep 5;
|
sleep 5;
|
||||||
done
|
done
|
||||||
- name: verify EMQX_LOADED_PLUGINS override working
|
# - name: verify EMQX_LOADED_PLUGINS override working
|
||||||
run: |
|
# run: |
|
||||||
expected="{emqx_sn, true}."
|
# expected="{emqx_sn, true}."
|
||||||
output=$(docker exec -i node1.emqx.io bash -c "cat data/loaded_plugins" | tail -n1)
|
# output=$(docker exec -i node1.emqx.io bash -c "cat data/loaded_plugins" | tail -n1)
|
||||||
if [ "$expected" != "$output" ]; then
|
# if [ "$expected" != "$output" ]; then
|
||||||
exit 1
|
# exit 1
|
||||||
fi
|
# fi
|
||||||
- name: make paho tests
|
- name: make paho tests
|
||||||
run: |
|
run: |
|
||||||
if ! docker exec -i python /scripts/pytest.sh; then
|
if ! docker exec -i python /scripts/pytest.sh; then
|
||||||
|
@ -131,11 +131,27 @@ jobs:
|
||||||
echo "waiting emqx started";
|
echo "waiting emqx started";
|
||||||
sleep 10;
|
sleep 10;
|
||||||
done
|
done
|
||||||
- name: get pods log
|
- name: get emqx-0 pods log
|
||||||
if: failure()
|
if: failure()
|
||||||
env:
|
env:
|
||||||
KUBECONFIG: "/etc/rancher/k3s/k3s.yaml"
|
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
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
repository: emqx/paho.mqtt.testing
|
repository: emqx/paho.mqtt.testing
|
||||||
|
|
|
@ -56,64 +56,21 @@ jobs:
|
||||||
- name: docker compose up
|
- name: docker compose up
|
||||||
if: env.EDITION == 'opensource'
|
if: env.EDITION == 'opensource'
|
||||||
env:
|
env:
|
||||||
MYSQL_TAG: 8
|
|
||||||
REDIS_TAG: 6
|
|
||||||
MONGO_TAG: 4
|
|
||||||
PGSQL_TAG: 13
|
|
||||||
LDAP_TAG: 2.4.50
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
docker-compose \
|
docker-compose \
|
||||||
-f .ci/docker-compose-file/docker-compose.yaml \
|
-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
|
up -d --build
|
||||||
- name: docker compose up
|
- name: docker compose up
|
||||||
if: env.EDITION == 'enterprise'
|
if: env.EDITION == 'enterprise'
|
||||||
env:
|
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 }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
run: |
|
run: |
|
||||||
docker-compose \
|
docker-compose \
|
||||||
-f .ci/docker-compose-file/docker-compose.yaml \
|
-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.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
|
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
|
- name: run eunit
|
||||||
run: |
|
run: |
|
||||||
docker exec -i erlang bash -c "make eunit"
|
docker exec -i erlang bash -c "make eunit"
|
||||||
|
|
|
@ -45,6 +45,6 @@ emqx_dialyzer_*_plt
|
||||||
*/emqx_dashboard/priv/www
|
*/emqx_dashboard/priv/www
|
||||||
dist.zip
|
dist.zip
|
||||||
scripts/git-token
|
scripts/git-token
|
||||||
etc/*.seg
|
apps/*/etc/*.all
|
||||||
_upgrade_base/
|
_upgrade_base/
|
||||||
TAGS
|
TAGS
|
||||||
|
|
8
Makefile
8
Makefile
|
@ -73,7 +73,8 @@ coveralls: $(REBAR)
|
||||||
@ENABLE_COVER_COMPILE=1 $(REBAR) as test coveralls send
|
@ENABLE_COVER_COMPILE=1 $(REBAR) as test coveralls send
|
||||||
|
|
||||||
.PHONY: $(REL_PROFILES)
|
.PHONY: $(REL_PROFILES)
|
||||||
$(REL_PROFILES:%=%): $(REBAR) get-dashboard
|
|
||||||
|
$(REL_PROFILES:%=%): $(REBAR) get-dashboard conf-segs
|
||||||
@$(REBAR) as $(@) do compile,release
|
@$(REBAR) as $(@) do compile,release
|
||||||
|
|
||||||
## Not calling rebar3 clean because
|
## Not calling rebar3 clean because
|
||||||
|
@ -111,7 +112,7 @@ xref: $(REBAR)
|
||||||
dialyzer: $(REBAR)
|
dialyzer: $(REBAR)
|
||||||
@$(REBAR) as check dialyzer
|
@$(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
|
## rel target is to create release package without relup
|
||||||
.PHONY: $(REL_PROFILES:%=%-rel) $(PKG_PROFILES:%=%-rel)
|
.PHONY: $(REL_PROFILES:%=%-rel) $(PKG_PROFILES:%=%-rel)
|
||||||
|
@ -152,3 +153,6 @@ quickrun:
|
||||||
./_build/$(PROFILE)/rel/emqx/bin/emqx console
|
./_build/$(PROFILE)/rel/emqx/bin/emqx console
|
||||||
|
|
||||||
include docker.mk
|
include docker.mk
|
||||||
|
|
||||||
|
conf-segs:
|
||||||
|
@scripts/merge-config.escript
|
||||||
|
|
|
@ -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 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)
|
||||||
|
|
||||||
## 开源许可
|
## 开源许可
|
||||||
|
|
||||||
|
|
|
@ -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 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
|
## License
|
||||||
|
|
||||||
|
|
|
@ -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 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)
|
||||||
|
|
||||||
## Лицензия
|
## Лицензия
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ make dialyzer
|
||||||
|
|
||||||
##### To Analyse specific apps, (list of comma separated apps)
|
##### 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
|
## 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 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
|
## License
|
||||||
|
|
||||||
|
|
|
@ -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}.
|
|
||||||
|
|
|
@ -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}.
|
|
||||||
|
|
|
@ -307,6 +307,14 @@ cluster {
|
||||||
## Default: default
|
## Default: default
|
||||||
namespace: default
|
namespace: default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db_backend: mnesia
|
||||||
|
|
||||||
|
rlog: {
|
||||||
|
# role: core
|
||||||
|
# core_nodes: []
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
##==================================================================
|
##==================================================================
|
||||||
|
@ -1259,8 +1267,9 @@ zones.default {
|
||||||
##
|
##
|
||||||
## @doc zones.<name>.listeners.<name>.type
|
## @doc zones.<name>.listeners.<name>.type
|
||||||
## ValueType: tcp | ws
|
## ValueType: tcp | ws
|
||||||
## - tcp: MQTT over TCP
|
## - tcp: MQTT over TCP
|
||||||
## - ws: MQTT over Websocket
|
## - ws: MQTT over Websocket
|
||||||
|
## - quic: MQTT over QUIC
|
||||||
## Required: true
|
## Required: true
|
||||||
type: tcp
|
type: tcp
|
||||||
|
|
||||||
|
@ -1390,8 +1399,9 @@ zones.default {
|
||||||
##
|
##
|
||||||
## @doc zones.<name>.listeners.<name>.type
|
## @doc zones.<name>.listeners.<name>.type
|
||||||
## ValueType: tcp | ws
|
## ValueType: tcp | ws
|
||||||
## - tcp: MQTT over TCP
|
## - tcp: MQTT over TCP
|
||||||
## - ws: MQTT over Websocket
|
## - ws: MQTT over Websocket
|
||||||
|
## - quic: MQTT over QUIC
|
||||||
## Required: true
|
## Required: true
|
||||||
type: tcp
|
type: tcp
|
||||||
|
|
||||||
|
@ -1520,6 +1530,51 @@ zones.default {
|
||||||
tcp.buffer: 4KB
|
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:
|
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.
|
#${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
|
## @doc zones.<name>.listeners.<name>.type
|
||||||
## ValueType: tcp | ws
|
## ValueType: tcp | ws
|
||||||
## - tcp: MQTT over TCP
|
## - tcp: MQTT over TCP
|
||||||
## - ws: MQTT over Websocket
|
## - ws: MQTT over Websocket
|
||||||
|
## - quic: MQTT over QUIC
|
||||||
## Required: true
|
## Required: true
|
||||||
type: ws
|
type: ws
|
||||||
|
|
||||||
|
@ -1662,8 +1718,9 @@ zones.default {
|
||||||
##
|
##
|
||||||
## @doc zones.<name>.listeners.<name>.type
|
## @doc zones.<name>.listeners.<name>.type
|
||||||
## ValueType: tcp | ws
|
## ValueType: tcp | ws
|
||||||
## - tcp: MQTT over TCP
|
## - tcp: MQTT over TCP
|
||||||
## - ws: MQTT over Websocket
|
## - ws: MQTT over Websocket
|
||||||
|
## - quic: MQTT over QUIC
|
||||||
## Required: true
|
## Required: true
|
||||||
type: ws
|
type: ws
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,10 @@
|
||||||
|
|
||||||
-define(Otherwise, true).
|
-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
|
%% Banner
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -86,6 +90,9 @@
|
||||||
|
|
||||||
-define(ROUTE_SHARD, route_shard).
|
-define(ROUTE_SHARD, route_shard).
|
||||||
|
|
||||||
|
|
||||||
|
-define(RULE_ENGINE_SHARD, emqx_rule_engine_shard).
|
||||||
|
|
||||||
-record(route, {
|
-record(route, {
|
||||||
topic :: binary(),
|
topic :: binary(),
|
||||||
dest :: node() | {binary(), node()}
|
dest :: node() | {binary(), node()}
|
||||||
|
@ -101,8 +108,7 @@
|
||||||
descr :: string(),
|
descr :: string(),
|
||||||
vendor :: string() | undefined,
|
vendor :: string() | undefined,
|
||||||
active = false :: boolean(),
|
active = false :: boolean(),
|
||||||
info = #{} :: map(),
|
info = #{} :: map()
|
||||||
type :: atom()
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -134,4 +140,3 @@
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
|
|
|
@ -30,11 +30,13 @@
|
||||||
%% MQTT Protocol Version and Names
|
%% MQTT Protocol Version and Names
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-define(MQTT_SN_PROTO_V1, 1).
|
||||||
-define(MQTT_PROTO_V3, 3).
|
-define(MQTT_PROTO_V3, 3).
|
||||||
-define(MQTT_PROTO_V4, 4).
|
-define(MQTT_PROTO_V4, 4).
|
||||||
-define(MQTT_PROTO_V5, 5).
|
-define(MQTT_PROTO_V5, 5).
|
||||||
|
|
||||||
-define(PROTOCOL_NAMES, [
|
-define(PROTOCOL_NAMES, [
|
||||||
|
{?MQTT_SN_PROTO_V1, <<"MQTT-SN">>}, %% XXX:Compatible with emqx-sn plug-in
|
||||||
{?MQTT_PROTO_V3, <<"MQIsdp">>},
|
{?MQTT_PROTO_V3, <<"MQIsdp">>},
|
||||||
{?MQTT_PROTO_V4, <<"MQTT">>},
|
{?MQTT_PROTO_V4, <<"MQTT">>},
|
||||||
{?MQTT_PROTO_V5, <<"MQTT">>}]).
|
{?MQTT_PROTO_V5, <<"MQTT">>}]).
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
-ifndef(EMQX_ENTERPRISE).
|
-ifndef(EMQX_ENTERPRISE).
|
||||||
|
|
||||||
-define(EMQX_RELEASE, {opensource, "5.0-pre"}).
|
-define(EMQX_RELEASE, {opensource, "5.0-alpha.1"}).
|
||||||
|
|
||||||
-else.
|
-else.
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {branch, "2.0.4"}}}
|
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {branch, "2.0.4"}}}
|
||||||
, {recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}}
|
, {recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}}
|
||||||
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.13.0"}}}
|
, {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]}.
|
{plugins, [rebar3_proper]}.
|
||||||
|
@ -30,7 +31,7 @@
|
||||||
[ meck
|
[ meck
|
||||||
, {bbmustache,"1.10.0"}
|
, {bbmustache,"1.10.0"}
|
||||||
, {emqx_ct_helpers, {git,"https://github.com/emqx/emqx-ct-helpers", {branch,"hocon"}}}
|
, {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]}]}
|
{extra_src_dirs, [{"test",[recursive]}]}
|
||||||
]}
|
]}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
{vsn, "5.0.0"}, % strict semver, bump manually!
|
{vsn, "5.0.0"}, % strict semver, bump manually!
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, []},
|
{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,[]}},
|
{mod, {emqx_app,[]}},
|
||||||
{env, []},
|
{env, []},
|
||||||
{licenses, ["Apache-2.0"]},
|
{licenses, ["Apache-2.0"]},
|
||||||
|
|
|
@ -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,[]}]},
|
|
||||||
{<<".*">>,[]}]}.
|
|
|
@ -227,7 +227,6 @@ shutdown() ->
|
||||||
shutdown(Reason) ->
|
shutdown(Reason) ->
|
||||||
?LOG(critical, "emqx shutdown for ~s", [Reason]),
|
?LOG(critical, "emqx shutdown for ~s", [Reason]),
|
||||||
_ = emqx_alarm_handler:unload(),
|
_ = emqx_alarm_handler:unload(),
|
||||||
_ = emqx_plugins:unload(),
|
|
||||||
lists:foreach(fun application:stop/1
|
lists:foreach(fun application:stop/1
|
||||||
, lists:reverse(default_started_applications())
|
, lists:reverse(default_started_applications())
|
||||||
).
|
).
|
||||||
|
@ -237,10 +236,10 @@ reboot() ->
|
||||||
|
|
||||||
-ifdef(EMQX_ENTERPRISE).
|
-ifdef(EMQX_ENTERPRISE).
|
||||||
default_started_applications() ->
|
default_started_applications() ->
|
||||||
[gproc, esockd, ranch, cowboy, ekka, emqx].
|
[gproc, esockd, ranch, cowboy, ekka, quicer, emqx].
|
||||||
-else.
|
-else.
|
||||||
default_started_applications() ->
|
default_started_applications() ->
|
||||||
[gproc, esockd, ranch, cowboy, ekka, emqx, emqx_modules].
|
[gproc, esockd, ranch, cowboy, ekka, quicer, emqx] ++ emqx_feature().
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -253,3 +252,16 @@ reload_config(ConfFile) ->
|
||||||
[application:set_env(App, Par, Val) || {Par, Val} <- Vals]
|
[application:set_env(App, Par, Val) || {Par, Val} <- Vals]
|
||||||
end, Conf).
|
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].
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
-export([authenticate/1]).
|
-export([authenticate/1]).
|
||||||
|
|
||||||
-export([ check_acl/3
|
-export([ authorize/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-type(result() :: #{auth_result := emqx_types:auth_result(),
|
-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)).
|
return_auth_result(run_hooks('client.authenticate', [ClientInfo], AuthResult)).
|
||||||
|
|
||||||
%% @doc Check ACL
|
%% @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).
|
-> 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
|
case emqx_acl_cache:is_enabled(Zone, Listener) of
|
||||||
true -> check_acl_cache(ClientInfo, PubSub, Topic);
|
true -> check_authorization_cache(ClientInfo, PubSub, Topic);
|
||||||
false -> do_check_acl(ClientInfo, PubSub, Topic)
|
false -> do_authorize(ClientInfo, PubSub, Topic)
|
||||||
end.
|
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
|
case emqx_acl_cache:get_acl_cache(Zone, Listener, PubSub, Topic) of
|
||||||
not_found ->
|
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),
|
emqx_acl_cache:put_acl_cache(Zone, Listener, PubSub, Topic, AclResult),
|
||||||
AclResult;
|
AclResult;
|
||||||
AclResult -> AclResult
|
AclResult -> AclResult
|
||||||
end.
|
end.
|
||||||
|
|
||||||
do_check_acl(ClientInfo, PubSub, Topic) ->
|
do_authorize(ClientInfo, PubSub, Topic) ->
|
||||||
case run_hooks('client.check_acl', [ClientInfo, PubSub, Topic], allow) of
|
case run_hooks('client.authorize', [ClientInfo, PubSub, Topic], allow) of
|
||||||
allow -> allow;
|
allow -> allow;
|
||||||
_Other -> deny
|
_Other -> deny
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -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]).
|
|
||||||
|
|
|
@ -82,6 +82,9 @@
|
||||||
|
|
||||||
-define(DEACTIVATED_ALARM, emqx_deactivated_alarm).
|
-define(DEACTIVATED_ALARM, emqx_deactivated_alarm).
|
||||||
|
|
||||||
|
-rlog_shard({?COMMON_SHARD, ?ACTIVATED_ALARM}).
|
||||||
|
-rlog_shard({?COMMON_SHARD, ?DEACTIVATED_ALARM}).
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
-compile(export_all).
|
-compile(export_all).
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
|
@ -167,7 +170,7 @@ handle_call({activate_alarm, Name, Details}, _From, State) ->
|
||||||
details = Details,
|
details = Details,
|
||||||
message = normalize_message(Name, Details),
|
message = normalize_message(Name, Details),
|
||||||
activate_at = erlang:system_time(microsecond)},
|
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])),
|
do_actions(activate, Alarm, emqx_config:get([alarm, actions])),
|
||||||
{reply, ok, State}
|
{reply, ok, State}
|
||||||
end;
|
end;
|
||||||
|
@ -186,9 +189,14 @@ handle_call(delete_all_deactivated_alarms, _From, State) ->
|
||||||
{reply, ok, State};
|
{reply, ok, State};
|
||||||
|
|
||||||
handle_call({get_alarms, all}, _From, State) ->
|
handle_call({get_alarms, all}, _From, State) ->
|
||||||
Alarms = [normalize(Alarm) ||
|
{atomic, Alarms} =
|
||||||
Alarm <- ets:tab2list(?ACTIVATED_ALARM)
|
ekka_mnesia:ro_transaction(
|
||||||
++ ets:tab2list(?DEACTIVATED_ALARM)],
|
?COMMON_SHARD,
|
||||||
|
fun() ->
|
||||||
|
[normalize(Alarm) ||
|
||||||
|
Alarm <- ets:tab2list(?ACTIVATED_ALARM)
|
||||||
|
++ ets:tab2list(?DEACTIVATED_ALARM)]
|
||||||
|
end),
|
||||||
{reply, Alarms, State};
|
{reply, Alarms, State};
|
||||||
|
|
||||||
handle_call({get_alarms, activated}, _From, 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
|
case mnesia:dirty_first(?DEACTIVATED_ALARM) of
|
||||||
'$end_of_table' -> ok;
|
'$end_of_table' -> ok;
|
||||||
ActivateAt2 ->
|
ActivateAt2 ->
|
||||||
mnesia:dirty_delete(?DEACTIVATED_ALARM, ActivateAt2)
|
ekka_mnesia:dirty_delete(?DEACTIVATED_ALARM, ActivateAt2)
|
||||||
end;
|
end;
|
||||||
false -> ok
|
false -> ok
|
||||||
end,
|
end,
|
||||||
|
@ -244,8 +252,8 @@ deactivate_alarm(Details, #activated_alarm{activate_at = ActivateAt, name = Name
|
||||||
DeActAlarm = make_deactivated_alarm(ActivateAt, Name, Details,
|
DeActAlarm = make_deactivated_alarm(ActivateAt, Name, Details,
|
||||||
normalize_message(Name, Details),
|
normalize_message(Name, Details),
|
||||||
erlang:system_time(microsecond)),
|
erlang:system_time(microsecond)),
|
||||||
mnesia:dirty_write(?DEACTIVATED_ALARM, HistoryAlarm),
|
ekka_mnesia:dirty_write(?DEACTIVATED_ALARM, HistoryAlarm),
|
||||||
mnesia:dirty_delete(?ACTIVATED_ALARM, Name),
|
ekka_mnesia:dirty_delete(?ACTIVATED_ALARM, Name),
|
||||||
do_actions(deactivate, DeActAlarm, emqx_config:get([alarm, actions])).
|
do_actions(deactivate, DeActAlarm, emqx_config:get([alarm, actions])).
|
||||||
|
|
||||||
make_deactivated_alarm(ActivateAt, Name, Details, Message, DeActivateAt) ->
|
make_deactivated_alarm(ActivateAt, Name, Details, Message, DeActivateAt) ->
|
||||||
|
@ -262,7 +270,7 @@ deactivate_all_alarms() ->
|
||||||
details = Details,
|
details = Details,
|
||||||
message = Message,
|
message = Message,
|
||||||
activate_at = ActivateAt}) ->
|
activate_at = ActivateAt}) ->
|
||||||
mnesia:dirty_write(?DEACTIVATED_ALARM,
|
ekka_mnesia:dirty_write(?DEACTIVATED_ALARM,
|
||||||
#deactivated_alarm{
|
#deactivated_alarm{
|
||||||
activate_at = ActivateAt,
|
activate_at = ActivateAt,
|
||||||
name = Name,
|
name = Name,
|
||||||
|
@ -274,7 +282,7 @@ deactivate_all_alarms() ->
|
||||||
|
|
||||||
%% Delete all records from the given table, ignore result.
|
%% Delete all records from the given table, ignore result.
|
||||||
clear_table(TableName) ->
|
clear_table(TableName) ->
|
||||||
case mnesia:clear_table(TableName) of
|
case ekka_mnesia:clear_table(TableName) of
|
||||||
{aborted, Reason} ->
|
{aborted, Reason} ->
|
||||||
?LOG(warning, "Faile to clear table ~p reason: ~p",
|
?LOG(warning, "Faile to clear table ~p reason: ~p",
|
||||||
[TableName, Reason]);
|
[TableName, Reason]);
|
||||||
|
@ -294,7 +302,7 @@ delete_expired_deactivated_alarms('$end_of_table', _Checkpoint) ->
|
||||||
delete_expired_deactivated_alarms(ActivatedAt, Checkpoint) ->
|
delete_expired_deactivated_alarms(ActivatedAt, Checkpoint) ->
|
||||||
case ActivatedAt =< Checkpoint of
|
case ActivatedAt =< Checkpoint of
|
||||||
true ->
|
true ->
|
||||||
mnesia:dirty_delete(?DEACTIVATED_ALARM, ActivatedAt),
|
ekka_mnesia:dirty_delete(?DEACTIVATED_ALARM, ActivatedAt),
|
||||||
NActivatedAt = mnesia:dirty_next(?DEACTIVATED_ALARM, ActivatedAt),
|
NActivatedAt = mnesia:dirty_next(?DEACTIVATED_ALARM, ActivatedAt),
|
||||||
delete_expired_deactivated_alarms(NActivatedAt, Checkpoint);
|
delete_expired_deactivated_alarms(NActivatedAt, Checkpoint);
|
||||||
false ->
|
false ->
|
||||||
|
|
|
@ -28,7 +28,12 @@
|
||||||
|
|
||||||
-define(APP, emqx).
|
-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").
|
-include("emqx_release.hrl").
|
||||||
|
|
||||||
|
@ -46,7 +51,7 @@ start(_Type, _Args) ->
|
||||||
ok = ekka_rlog:wait_for_shards(?EMQX_SHARDS, infinity),
|
ok = ekka_rlog:wait_for_shards(?EMQX_SHARDS, infinity),
|
||||||
{ok, Sup} = emqx_sup:start_link(),
|
{ok, Sup} = emqx_sup:start_link(),
|
||||||
ok = start_autocluster(),
|
ok = start_autocluster(),
|
||||||
ok = emqx_plugins:init(),
|
% ok = emqx_plugins:init(),
|
||||||
_ = emqx_plugins:load(),
|
_ = emqx_plugins:load(),
|
||||||
_ = start_ce_modules(),
|
_ = start_ce_modules(),
|
||||||
emqx_boot:is_enabled(listeners) andalso (ok = emqx_listeners:start()),
|
emqx_boot:is_enabled(listeners) andalso (ok = emqx_listeners:start()),
|
||||||
|
|
|
@ -51,6 +51,8 @@
|
||||||
|
|
||||||
-define(BANNED_TAB, ?MODULE).
|
-define(BANNED_TAB, ?MODULE).
|
||||||
|
|
||||||
|
-rlog_shard({?COMMON_SHARD, ?BANNED_TAB}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Mnesia bootstrap
|
%% Mnesia bootstrap
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
@ -96,19 +98,19 @@ create(#{who := Who,
|
||||||
reason := Reason,
|
reason := Reason,
|
||||||
at := At,
|
at := At,
|
||||||
until := Until}) ->
|
until := Until}) ->
|
||||||
mnesia:dirty_write(?BANNED_TAB, #banned{who = Who,
|
ekka_mnesia:dirty_write(?BANNED_TAB, #banned{who = Who,
|
||||||
by = By,
|
by = By,
|
||||||
reason = Reason,
|
reason = Reason,
|
||||||
at = At,
|
at = At,
|
||||||
until = Until});
|
until = Until});
|
||||||
create(Banned) when is_record(Banned, banned) ->
|
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()}
|
-spec(delete({clientid, emqx_types:clientid()}
|
||||||
| {username, emqx_types:username()}
|
| {username, emqx_types:username()}
|
||||||
| {peerhost, emqx_types:peerhost()}) -> ok).
|
| {peerhost, emqx_types:peerhost()}) -> ok).
|
||||||
delete(Who) ->
|
delete(Who) ->
|
||||||
mnesia:dirty_delete(?BANNED_TAB, Who).
|
ekka_mnesia:dirty_delete(?BANNED_TAB, Who).
|
||||||
|
|
||||||
info(InfoKey) ->
|
info(InfoKey) ->
|
||||||
mnesia:table_info(?BANNED_TAB, InfoKey).
|
mnesia:table_info(?BANNED_TAB, InfoKey).
|
||||||
|
@ -129,7 +131,7 @@ handle_cast(Msg, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
handle_info({timeout, TRef, expire}, State = #{expiry_timer := TRef}) ->
|
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};
|
{noreply, ensure_expiry_timer(State), hibernate};
|
||||||
|
|
||||||
handle_info(Info, State) ->
|
handle_info(Info, State) ->
|
||||||
|
@ -160,4 +162,3 @@ expire_banned_items(Now) ->
|
||||||
mnesia:delete_object(?BANNED_TAB, B, sticky_write);
|
mnesia:delete_object(?BANNED_TAB, B, sticky_write);
|
||||||
(_, _Acc) -> ok
|
(_, _Acc) -> ok
|
||||||
end, ok, ?BANNED_TAB).
|
end, ok, ?BANNED_TAB).
|
||||||
|
|
||||||
|
|
|
@ -1420,7 +1420,7 @@ check_pub_alias(_Packet, _Channel) -> ok.
|
||||||
check_pub_acl(#mqtt_packet{variable = #mqtt_packet_publish{topic_name = Topic}},
|
check_pub_acl(#mqtt_packet{variable = #mqtt_packet_publish{topic_name = Topic}},
|
||||||
#channel{clientinfo = ClientInfo}) ->
|
#channel{clientinfo = ClientInfo}) ->
|
||||||
case is_acl_enabled(ClientInfo) andalso
|
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;
|
false -> ok;
|
||||||
allow -> ok;
|
allow -> ok;
|
||||||
deny -> {error, ?RC_NOT_AUTHORIZED}
|
deny -> {error, ?RC_NOT_AUTHORIZED}
|
||||||
|
@ -1454,7 +1454,7 @@ check_sub_acls([], _Channel, Acc) ->
|
||||||
|
|
||||||
check_sub_acl(TopicFilter, #channel{clientinfo = ClientInfo}) ->
|
check_sub_acl(TopicFilter, #channel{clientinfo = ClientInfo}) ->
|
||||||
case is_acl_enabled(ClientInfo) andalso
|
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;
|
false -> allow;
|
||||||
Result -> Result
|
Result -> Result
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -294,6 +294,9 @@ do_discard_session(ClientId, Pid) ->
|
||||||
_ : {noproc, _} -> % emqx_connection: gen_server:call
|
_ : {noproc, _} -> % emqx_connection: gen_server:call
|
||||||
?tp(debug, "session_already_gone", #{pid => Pid}),
|
?tp(debug, "session_already_gone", #{pid => Pid}),
|
||||||
ok;
|
ok;
|
||||||
|
_ : {'EXIT', {noproc, _}} -> % rpc_call/3
|
||||||
|
?tp(debug, "session_already_gone", #{pid => Pid}),
|
||||||
|
ok;
|
||||||
_ : {{shutdown, _}, _} ->
|
_ : {{shutdown, _}, _} ->
|
||||||
?tp(debug, "session_already_shutdown", #{pid => Pid}),
|
?tp(debug, "session_already_shutdown", #{pid => Pid}),
|
||||||
ok;
|
ok;
|
||||||
|
|
|
@ -48,7 +48,9 @@
|
||||||
-define(TAB, emqx_channel_registry).
|
-define(TAB, emqx_channel_registry).
|
||||||
-define(LOCK, {?MODULE, cleanup_down}).
|
-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}).
|
-record(channel, {chid, pid}).
|
||||||
|
|
||||||
|
@ -111,6 +113,7 @@ init([]) ->
|
||||||
{storage_properties, [{ets, [{read_concurrency, true},
|
{storage_properties, [{ets, [{read_concurrency, true},
|
||||||
{write_concurrency, true}]}]}]),
|
{write_concurrency, true}]}]}]),
|
||||||
ok = ekka_mnesia:copy_table(?TAB, ram_copies),
|
ok = ekka_mnesia:copy_table(?TAB, ram_copies),
|
||||||
|
ok = ekka_rlog:wait_for_shards([?CM_SHARD], infinity),
|
||||||
ok = ekka:monitor(membership),
|
ok = ekka:monitor(membership),
|
||||||
{ok, #{}}.
|
{ok, #{}}.
|
||||||
|
|
||||||
|
@ -125,7 +128,7 @@ handle_cast(Msg, State) ->
|
||||||
handle_info({membership, {mnesia, down, Node}}, State) ->
|
handle_info({membership, {mnesia, down, Node}}, State) ->
|
||||||
global:trans({?LOCK, self()},
|
global:trans({?LOCK, self()},
|
||||||
fun() ->
|
fun() ->
|
||||||
ekka_mnesia:transaction(?ROUTE_SHARD, fun cleanup_channels/1, [Node])
|
ekka_mnesia:transaction(?CM_SHARD, fun cleanup_channels/1, [Node])
|
||||||
end),
|
end),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
|
||||||
|
|
|
@ -120,4 +120,3 @@ put_raw(Config) ->
|
||||||
-spec put_raw(emqx_map_lib:config_key_path(), term()) -> ok.
|
-spec put_raw(emqx_map_lib:config_key_path(), term()) -> ok.
|
||||||
put_raw(KeyPath, Config) ->
|
put_raw(KeyPath, Config) ->
|
||||||
put_raw(emqx_map_lib:deep_put(KeyPath, get_raw(), Config)).
|
put_raw(emqx_map_lib:deep_put(KeyPath, get_raw(), Config)).
|
||||||
|
|
||||||
|
|
|
@ -422,6 +422,13 @@ handle_msg({Inet, _Sock, Data}, State) when Inet == tcp; Inet == ssl ->
|
||||||
ok = emqx_metrics:inc('bytes.received', Oct),
|
ok = emqx_metrics:inc('bytes.received', Oct),
|
||||||
parse_incoming(Data, State);
|
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)},
|
handle_msg({incoming, Packet = ?CONNECT_PACKET(ConnPkt)},
|
||||||
State = #state{idle_timer = IdleTimer}) ->
|
State = #state{idle_timer = IdleTimer}) ->
|
||||||
ok = emqx_misc:cancel_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_info({sock_closed, Closed}, close_socket(State));
|
||||||
|
|
||||||
handle_msg({Passive, _Sock}, 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
|
%% In Stats
|
||||||
Pubs = emqx_pd:reset_counter(incoming_pubs),
|
Pubs = emqx_pd:reset_counter(incoming_pubs),
|
||||||
Bytes = emqx_pd:reset_counter(incoming_bytes),
|
Bytes = emqx_pd:reset_counter(incoming_bytes),
|
||||||
|
@ -739,6 +746,15 @@ handle_info({sock_error, Reason}, State) ->
|
||||||
end,
|
end,
|
||||||
handle_info({sock_closed, Reason}, close_socket(State));
|
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) ->
|
handle_info(Info, State) ->
|
||||||
with_channel(handle_info, [Info], State).
|
with_channel(handle_info, [Info], State).
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,28 @@ do_start_listener(ZoneName, ListenerName, #{type := ws, bind := ListenOn} = Opts
|
||||||
cowboy:start_clear(Id, RanchOpts, WsOpts);
|
cowboy:start_clear(Id, RanchOpts, WsOpts);
|
||||||
true ->
|
true ->
|
||||||
cowboy:start_tls(Id, RanchOpts, WsOpts)
|
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) ->
|
esockd_opts(Opts0) ->
|
||||||
Opts1 = maps:with([acceptors, max_connections, proxy_protocol, proxy_protocol_timeout], Opts0),
|
Opts1 = maps:with([acceptors, max_connections, proxy_protocol, proxy_protocol_timeout], Opts0),
|
||||||
|
|
|
@ -172,7 +172,7 @@
|
||||||
{counter, 'client.connected'},
|
{counter, 'client.connected'},
|
||||||
{counter, 'client.authenticate'},
|
{counter, 'client.authenticate'},
|
||||||
{counter, 'client.auth.anonymous'},
|
{counter, 'client.auth.anonymous'},
|
||||||
{counter, 'client.check_acl'},
|
{counter, 'client.authorize'},
|
||||||
{counter, 'client.subscribe'},
|
{counter, 'client.subscribe'},
|
||||||
{counter, 'client.unsubscribe'},
|
{counter, 'client.unsubscribe'},
|
||||||
{counter, 'client.disconnected'}
|
{counter, 'client.disconnected'}
|
||||||
|
@ -563,7 +563,7 @@ reserved_idx('client.connected') -> 202;
|
||||||
reserved_idx('client.authenticate') -> 203;
|
reserved_idx('client.authenticate') -> 203;
|
||||||
reserved_idx('client.enhanced_authenticate') -> 204;
|
reserved_idx('client.enhanced_authenticate') -> 204;
|
||||||
reserved_idx('client.auth.anonymous') -> 205;
|
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.subscribe') -> 207;
|
||||||
reserved_idx('client.unsubscribe') -> 208;
|
reserved_idx('client.unsubscribe') -> 208;
|
||||||
reserved_idx('client.disconnected') -> 209;
|
reserved_idx('client.disconnected') -> 209;
|
||||||
|
|
|
@ -21,8 +21,6 @@
|
||||||
|
|
||||||
-logger_header("[Plugins]").
|
-logger_header("[Plugins]").
|
||||||
|
|
||||||
-export([init/0]).
|
|
||||||
|
|
||||||
-export([ load/0
|
-export([ load/0
|
||||||
, load/1
|
, load/1
|
||||||
, unload/0
|
, unload/0
|
||||||
|
@ -39,35 +37,14 @@
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
-dialyzer({no_match, [ plugin_loaded/2
|
|
||||||
, plugin_unloaded/2
|
|
||||||
]}).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% APIs
|
%% 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.
|
%% @doc Load all plugins when the broker started.
|
||||||
-spec(load() -> ok | ignore | {error, term()}).
|
-spec(load() -> ok | ignore | {error, term()}).
|
||||||
load() ->
|
load() ->
|
||||||
ok = load_ext_plugins(emqx:get_env(expand_plugins_dir)),
|
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.
|
|
||||||
|
|
||||||
%% @doc Load a Plugin
|
%% @doc Load a Plugin
|
||||||
-spec(load(atom()) -> ok | {error, term()}).
|
-spec(load(atom()) -> ok | {error, term()}).
|
||||||
|
@ -80,17 +57,13 @@ load(PluginName) when is_atom(PluginName) ->
|
||||||
?LOG(notice, "Plugin ~s is already started", [PluginName]),
|
?LOG(notice, "Plugin ~s is already started", [PluginName]),
|
||||||
{error, already_started};
|
{error, already_started};
|
||||||
{_, false} ->
|
{_, false} ->
|
||||||
load_plugin(PluginName, true)
|
load_plugin(PluginName)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc Unload all plugins before broker stopped.
|
%% @doc Unload all plugins before broker stopped.
|
||||||
-spec(unload() -> list() | {error, term()}).
|
-spec(unload() -> ok).
|
||||||
unload() ->
|
unload() ->
|
||||||
case emqx:get_env(plugins_loaded_file) of
|
stop_plugins(list()).
|
||||||
undefined -> ignore;
|
|
||||||
File ->
|
|
||||||
with_loaded_file(File, fun stop_plugins/1)
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% @doc UnLoad a Plugin
|
%% @doc UnLoad a Plugin
|
||||||
-spec(unload(atom()) -> ok | {error, term()}).
|
-spec(unload(atom()) -> ok | {error, term()}).
|
||||||
|
@ -103,7 +76,7 @@ unload(PluginName) when is_atom(PluginName) ->
|
||||||
?LOG(error, "Plugin ~s is not started", [PluginName]),
|
?LOG(error, "Plugin ~s is not started", [PluginName]),
|
||||||
{error, not_started};
|
{error, not_started};
|
||||||
{_, _} ->
|
{_, _} ->
|
||||||
unload_plugin(PluginName, true)
|
unload_plugin(PluginName)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
reload(PluginName) when is_atom(PluginName)->
|
reload(PluginName) when is_atom(PluginName)->
|
||||||
|
@ -124,8 +97,8 @@ reload(PluginName) when is_atom(PluginName)->
|
||||||
-spec(list() -> [emqx_types:plugin()]).
|
-spec(list() -> [emqx_types:plugin()]).
|
||||||
list() ->
|
list() ->
|
||||||
StartedApps = names(started_app),
|
StartedApps = names(started_app),
|
||||||
lists:map(fun({Name, _, [Type| _]}) ->
|
lists:map(fun({Name, _, _}) ->
|
||||||
Plugin = plugin(Name, Type),
|
Plugin = plugin(Name),
|
||||||
case lists:member(Name, StartedApps) of
|
case lists:member(Name, StartedApps) of
|
||||||
true -> Plugin#plugin{active = true};
|
true -> Plugin#plugin{active = true};
|
||||||
false -> Plugin
|
false -> Plugin
|
||||||
|
@ -142,12 +115,6 @@ find_plugin(Name, Plugins) ->
|
||||||
%% Internal functions
|
%% 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 external plugins which are placed in etc/plugins dir
|
||||||
load_ext_plugins(undefined) -> ok;
|
load_ext_plugins(undefined) -> ok;
|
||||||
load_ext_plugins(Dir) ->
|
load_ext_plugins(Dir) ->
|
||||||
|
@ -171,7 +138,15 @@ load_ext_plugin(PluginDir) ->
|
||||||
?LOG(alert, "plugin_app_file_not_found: ~s", [AppFile]),
|
?LOG(alert, "plugin_app_file_not_found: ~s", [AppFile]),
|
||||||
error({plugin_app_file_not_found, AppFile})
|
error({plugin_app_file_not_found, AppFile})
|
||||||
end,
|
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) ->
|
load_plugin_app(AppName, Ebin) ->
|
||||||
_ = code:add_patha(Ebin),
|
_ = code:add_patha(Ebin),
|
||||||
|
@ -189,56 +164,24 @@ load_plugin_app(AppName, Ebin) ->
|
||||||
{error, {already_loaded, _}} -> ok
|
{error, {already_loaded, _}} -> ok
|
||||||
end.
|
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
|
||||||
stop_plugins(Names) ->
|
stop_plugins(Plugins) ->
|
||||||
_ = [stop_app(App) || App <- Names],
|
_ = [stop_app(Plugin#plugin.name) || Plugin <- Plugins],
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
plugin(AppName, Type) ->
|
plugin(AppName) ->
|
||||||
case application:get_all_key(AppName) of
|
case application:get_all_key(AppName) of
|
||||||
{ok, Attrs} ->
|
{ok, Attrs} ->
|
||||||
Descr = proplists:get_value(description, 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})
|
undefined -> error({plugin_not_found, AppName})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
load_plugin(Name, Persistent) ->
|
load_plugin(Name) ->
|
||||||
try
|
try
|
||||||
case load_app(Name) of
|
case load_app(Name) of
|
||||||
ok ->
|
ok ->
|
||||||
start_app(Name, fun(App) -> plugin_loaded(App, Persistent) end);
|
start_app(Name);
|
||||||
{error, Error0} ->
|
{error, Error0} ->
|
||||||
{error, Error0}
|
{error, Error0}
|
||||||
end
|
end
|
||||||
|
@ -257,22 +200,21 @@ load_app(App) ->
|
||||||
{error, Error}
|
{error, Error}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
start_app(App, SuccFun) ->
|
start_app(App) ->
|
||||||
case application:ensure_all_started(App) of
|
case application:ensure_all_started(App) of
|
||||||
{ok, Started} ->
|
{ok, Started} ->
|
||||||
?LOG(info, "Started plugins: ~p", [Started]),
|
?LOG(info, "Started plugins: ~p", [Started]),
|
||||||
?LOG(info, "Load plugin ~s successfully", [App]),
|
?LOG(info, "Load plugin ~s successfully", [App]),
|
||||||
_ = SuccFun(App),
|
|
||||||
ok;
|
ok;
|
||||||
{error, {ErrApp, Reason}} ->
|
{error, {ErrApp, Reason}} ->
|
||||||
?LOG(error, "Load plugin ~s failed, cannot start plugin ~s for ~0p", [App, ErrApp, Reason]),
|
?LOG(error, "Load plugin ~s failed, cannot start plugin ~s for ~0p", [App, ErrApp, Reason]),
|
||||||
{error, {ErrApp, Reason}}
|
{error, {ErrApp, Reason}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
unload_plugin(App, Persistent) ->
|
unload_plugin(App) ->
|
||||||
case stop_app(App) of
|
case stop_app(App) of
|
||||||
ok ->
|
ok ->
|
||||||
_ = plugin_unloaded(App, Persistent), ok;
|
ok;
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
{error, Reason}
|
{error, Reason}
|
||||||
end.
|
end.
|
||||||
|
@ -296,60 +238,5 @@ names(started_app) ->
|
||||||
names(Plugins) ->
|
names(Plugins) ->
|
||||||
[Name || #plugin{name = Name} <- 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) ->
|
funlog(Key, Value) ->
|
||||||
?LOG(info, "~s = ~p", [string:join(Key, "."), Value]).
|
?LOG(info, "~s = ~p", [string:join(Key, "."), Value]).
|
||||||
|
|
|
@ -14,21 +14,13 @@
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqx_authentication_app).
|
-module(emqx_quic_connection).
|
||||||
|
|
||||||
-behaviour(application).
|
%% Callbacks
|
||||||
|
-export([ new_conn/2
|
||||||
-emqx_plugin(?MODULE).
|
|
||||||
|
|
||||||
%% Application callbacks
|
|
||||||
-export([ start/2
|
|
||||||
, stop/1
|
|
||||||
]).
|
]).
|
||||||
|
|
||||||
start(_StartType, _StartArgs) ->
|
new_conn(Conn, {_L, COpts, _S}) when is_map(COpts) ->
|
||||||
{ok, Sup} = emqx_authentication_sup:start_link(),
|
new_conn(Conn, maps:to_list(COpts));
|
||||||
ok = emqx_authentication:register_service_types(),
|
new_conn(Conn, COpts) ->
|
||||||
{ok, Sup}.
|
emqx_connection:start_link(emqx_quic_stream, Conn, COpts).
|
||||||
|
|
||||||
stop(_State) ->
|
|
||||||
ok.
|
|
|
@ -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.
|
|
@ -59,8 +59,8 @@
|
||||||
-export([includes/0]).
|
-export([includes/0]).
|
||||||
|
|
||||||
structs() -> ["cluster", "node", "rpc", "log", "lager",
|
structs() -> ["cluster", "node", "rpc", "log", "lager",
|
||||||
"zones", "listeners", "module", "broker",
|
"zones", "listeners", "broker",
|
||||||
"plugins", "sysmon", "alarm", "telemetry"]
|
"plugins", "sysmon", "alarm"]
|
||||||
++ includes().
|
++ includes().
|
||||||
|
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
|
@ -69,6 +69,14 @@ includes() ->[].
|
||||||
includes() ->
|
includes() ->
|
||||||
[ "emqx_data_bridge"
|
[ "emqx_data_bridge"
|
||||||
, "emqx_telemetry"
|
, "emqx_telemetry"
|
||||||
|
, "emqx_retainer"
|
||||||
|
, "emqx_statsd"
|
||||||
|
, "emqx_authn"
|
||||||
|
, "emqx_authz"
|
||||||
|
, "emqx_bridge_mqtt"
|
||||||
|
, "emqx_modules"
|
||||||
|
, "emqx_management"
|
||||||
|
, "emqx_gateway"
|
||||||
].
|
].
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
|
@ -345,6 +353,7 @@ fields("listeners") ->
|
||||||
[ {"$name", hoconsc:union(
|
[ {"$name", hoconsc:union(
|
||||||
[ hoconsc:ref("mqtt_tcp_listener")
|
[ hoconsc:ref("mqtt_tcp_listener")
|
||||||
, hoconsc:ref("mqtt_ws_listener")
|
, hoconsc:ref("mqtt_ws_listener")
|
||||||
|
, hoconsc:ref("mqtt_quic_listener")
|
||||||
])}
|
])}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -361,6 +370,10 @@ fields("mqtt_ws_listener") ->
|
||||||
, {"websocket", ref("ws_opts")}
|
, {"websocket", ref("ws_opts")}
|
||||||
] ++ mqtt_listener();
|
] ++ mqtt_listener();
|
||||||
|
|
||||||
|
fields("mqtt_quic_listener") ->
|
||||||
|
[ {"type", t(quic)}
|
||||||
|
] ++ base_listener();
|
||||||
|
|
||||||
fields("ws_opts") ->
|
fields("ws_opts") ->
|
||||||
[ {"mqtt_path", t(string(), undefined, "/mqtt")}
|
[ {"mqtt_path", t(string(), undefined, "/mqtt")}
|
||||||
, {"mqtt_piggyback", t(union(single, multiple), undefined, multiple)}
|
, {"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)}
|
, {"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") ->
|
fields("presence") ->
|
||||||
[ {"qos", t(range(0, 2), undefined, 1)}];
|
[ {"qos", t(range(0, 2), undefined, 1)}];
|
||||||
|
@ -440,9 +447,7 @@ fields("rule") ->
|
||||||
[ {"$id", t(string())}];
|
[ {"$id", t(string())}];
|
||||||
|
|
||||||
fields("plugins") ->
|
fields("plugins") ->
|
||||||
[ {"etc_dir", t(string(), "emqx.plugins_etc_dir", undefined)}
|
[ {"expand_plugins_dir", t(string(), "emqx.expand_plugins_dir", undefined)}
|
||||||
, {"loaded_file", t(string(), "emqx.plugins_loaded_file", undefined)}
|
|
||||||
, {"expand_plugins_dir", t(string(), "emqx.expand_plugins_dir", undefined)}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
fields("broker") ->
|
fields("broker") ->
|
||||||
|
@ -492,24 +497,22 @@ fields("alarm") ->
|
||||||
, {"validity_period", t(duration_s(), undefined, "24h")}
|
, {"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) ->
|
fields(ExtraField) ->
|
||||||
Mod = list_to_atom(ExtraField++"_schema"),
|
Mod = list_to_atom(ExtraField++"_schema"),
|
||||||
Mod:fields(ExtraField).
|
Mod:fields(ExtraField).
|
||||||
|
|
||||||
mqtt_listener() ->
|
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()))}
|
[ {"bind", t(union(ip_port(), integer()))}
|
||||||
, {"acceptors", t(integer(), undefined, 16)}
|
, {"acceptors", t(integer(), undefined, 16)}
|
||||||
, {"max_connections", maybe_infinity(integer(), infinity)}
|
, {"max_connections", maybe_infinity(integer(), infinity)}
|
||||||
, {"rate_limit", ref("rate_limit")}
|
, {"rate_limit", ref("rate_limit")}
|
||||||
, {"access_rules", t(hoconsc:array(string()))}
|
|
||||||
, {"proxy_protocol", t(boolean(), undefined, false)}
|
|
||||||
, {"proxy_protocol_timeout", t(duration())}
|
|
||||||
].
|
].
|
||||||
|
|
||||||
translations() -> ["kernel"].
|
translations() -> ["kernel"].
|
||||||
|
|
|
@ -77,6 +77,8 @@
|
||||||
-define(NACK(Reason), {shared_sub_nack, Reason}).
|
-define(NACK(Reason), {shared_sub_nack, Reason}).
|
||||||
-define(NO_ACK, no_ack).
|
-define(NO_ACK, no_ack).
|
||||||
|
|
||||||
|
-rlog_shard({?SHARED_SUB_SHARD, ?TAB}).
|
||||||
|
|
||||||
-record(state, {pmon}).
|
-record(state, {pmon}).
|
||||||
|
|
||||||
-record(emqx_shared_subscription, {group, topic, subpid}).
|
-record(emqx_shared_subscription, {group, topic, subpid}).
|
||||||
|
@ -297,7 +299,7 @@ subscribers(Group, Topic) ->
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
{ok, _} = mnesia:subscribe({table, ?TAB, simple}),
|
{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(?SHARED_SUBS, [protected, bag]),
|
||||||
ok = emqx_tables:new(?ALIVE_SUBS, [protected, set, {read_concurrency, true}]),
|
ok = emqx_tables:new(?ALIVE_SUBS, [protected, set, {read_concurrency, true}]),
|
||||||
{ok, update_stats(#state{pmon = PMon})}.
|
{ok, update_stats(#state{pmon = PMon})}.
|
||||||
|
@ -309,7 +311,7 @@ init_monitors() ->
|
||||||
end, emqx_pmon:new(), ?TAB).
|
end, emqx_pmon:new(), ?TAB).
|
||||||
|
|
||||||
handle_call({subscribe, Group, Topic, SubPid}, _From, State = #state{pmon = PMon}) ->
|
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
|
case ets:member(?SHARED_SUBS, {Group, Topic}) of
|
||||||
true -> ok;
|
true -> ok;
|
||||||
false -> ok = emqx_router:do_add_route(Topic, {Group, node()})
|
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)})};
|
{reply, ok, update_stats(State#state{pmon = emqx_pmon:monitor(SubPid, PMon)})};
|
||||||
|
|
||||||
handle_call({unsubscribe, Group, Topic, SubPid}, _From, State) ->
|
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}),
|
true = ets:delete_object(?SHARED_SUBS, {{Group, Topic}, SubPid}),
|
||||||
delete_route_if_needed({Group, Topic}),
|
delete_route_if_needed({Group, Topic}),
|
||||||
{reply, ok, State};
|
{reply, ok, State};
|
||||||
|
@ -336,9 +338,13 @@ handle_info({mnesia_table_event, {write, NewRecord, _}}, State = #state{pmon = P
|
||||||
#emqx_shared_subscription{subpid = SubPid} = NewRecord,
|
#emqx_shared_subscription{subpid = SubPid} = NewRecord,
|
||||||
{noreply, update_stats(State#state{pmon = emqx_pmon:monitor(SubPid, PMon)})};
|
{noreply, update_stats(State#state{pmon = emqx_pmon:monitor(SubPid, PMon)})};
|
||||||
|
|
||||||
handle_info({mnesia_table_event, {delete_object, OldRecord, _}}, State = #state{pmon = PMon}) ->
|
%% The subscriber may have subscribed multiple topics, so we need to keep monitoring the PID until
|
||||||
#emqx_shared_subscription{subpid = SubPid} = OldRecord,
|
%% it `unsubscribed` the last topic.
|
||||||
{noreply, update_stats(State#state{pmon = emqx_pmon:demonitor(SubPid, PMon)})};
|
%% 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) ->
|
handle_info({mnesia_table_event, _Event}, State) ->
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
@ -348,8 +354,7 @@ handle_info({'DOWN', _MRef, process, SubPid, _Reason}, State = #state{pmon = PMo
|
||||||
cleanup_down(SubPid),
|
cleanup_down(SubPid),
|
||||||
{noreply, update_stats(State#state{pmon = emqx_pmon:erase(SubPid, PMon)})};
|
{noreply, update_stats(State#state{pmon = emqx_pmon:erase(SubPid, PMon)})};
|
||||||
|
|
||||||
handle_info(Info, State) ->
|
handle_info(_Info, State) ->
|
||||||
?LOG(error, "Unexpected info: ~p", [Info]),
|
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
terminate(_Reason, _State) ->
|
terminate(_Reason, _State) ->
|
||||||
|
@ -370,7 +375,7 @@ cleanup_down(SubPid) ->
|
||||||
?IS_LOCAL_PID(SubPid) orelse ets:delete(?ALIVE_SUBS, SubPid),
|
?IS_LOCAL_PID(SubPid) orelse ets:delete(?ALIVE_SUBS, SubPid),
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
fun(Record = #emqx_shared_subscription{topic = Topic, group = Group}) ->
|
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}),
|
true = ets:delete_object(?SHARED_SUBS, {{Group, Topic}, SubPid}),
|
||||||
delete_route_if_needed({Group, Topic})
|
delete_route_if_needed({Group, Topic})
|
||||||
end, mnesia:dirty_match_object(#emqx_shared_subscription{_ = '_', subpid = SubPid})).
|
end, mnesia:dirty_match_object(#emqx_shared_subscription{_ = '_', subpid = SubPid})).
|
||||||
|
|
|
@ -403,7 +403,10 @@ websocket_close(Reason, State) ->
|
||||||
|
|
||||||
terminate(Reason, _Req, #state{channel = Channel}) ->
|
terminate(Reason, _Req, #state{channel = Channel}) ->
|
||||||
?LOG(debug, "Terminated due to ~p", [Reason]),
|
?LOG(debug, "Terminated due to ~p", [Reason]),
|
||||||
emqx_channel:terminate(Reason, Channel).
|
emqx_channel:terminate(Reason, Channel);
|
||||||
|
|
||||||
|
terminate(_Reason, _Req, _UnExpectedState) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Handle call
|
%% Handle call
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
{emqx_mod_acl_internal, true}.
|
{emqx_mod_presence, true}.
|
||||||
{emqx_mod_presence, true}.
|
|
||||||
|
|
|
@ -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}.
|
|
|
@ -1,3 +0,0 @@
|
||||||
{deny, {user, "emqx"}, pubsub, ["acl_deny_action"]}.
|
|
||||||
{deny, {user, "pub_deny"}, publish, ["pub_deny"]}.
|
|
||||||
{allow, all}.
|
|
|
@ -38,16 +38,9 @@ t_authenticate(_) ->
|
||||||
emqx_zone:set_env(zone, allow_anonymous, true),
|
emqx_zone:set_env(zone, allow_anonymous, true),
|
||||||
?assertMatch({ok, _}, emqx_access_control:authenticate(clientinfo())).
|
?assertMatch({ok, _}, emqx_access_control:authenticate(clientinfo())).
|
||||||
|
|
||||||
t_check_acl(_) ->
|
t_authorize(_) ->
|
||||||
emqx_zone:set_env(zone, acl_nomatch, deny),
|
|
||||||
application:set_env(emqx, enable_acl_cache, false),
|
|
||||||
Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>),
|
Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>),
|
||||||
?assertEqual(deny, emqx_access_control:check_acl(clientinfo(), Publish, <<"t">>)),
|
?assertEqual(allow, emqx_access_control:authorize(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">>)).
|
|
||||||
|
|
||||||
t_bypass_auth_plugins(_) ->
|
t_bypass_auth_plugins(_) ->
|
||||||
ClientInfo = clientinfo(),
|
ClientInfo = clientinfo(),
|
||||||
|
|
|
@ -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"]}))).
|
|
|
@ -18,14 +18,14 @@
|
||||||
|
|
||||||
%% ACL callbacks
|
%% ACL callbacks
|
||||||
-export([ init/1
|
-export([ init/1
|
||||||
, check_acl/2
|
, authorize/2
|
||||||
, description/0
|
, description/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
init(AclOpts) ->
|
init(AclOpts) ->
|
||||||
{ok, AclOpts}.
|
{ok, AclOpts}.
|
||||||
|
|
||||||
check_acl({_User, _PubSub, _Topic}, _State) ->
|
authorize({_User, _PubSub, _Topic}, _State) ->
|
||||||
allow.
|
allow.
|
||||||
|
|
||||||
description() ->
|
description() ->
|
||||||
|
|
|
@ -37,7 +37,7 @@ init_per_suite(Config) ->
|
||||||
ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]),
|
ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]),
|
||||||
ok = meck:expect(emqx_access_control, authenticate,
|
ok = meck:expect(emqx_access_control, authenticate,
|
||||||
fun(_) -> {ok, #{auth_result => success}} end),
|
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
|
%% Broker Meck
|
||||||
ok = meck:new(emqx_broker, [passthrough, no_history, no_link]),
|
ok = meck:new(emqx_broker, [passthrough, no_history, no_link]),
|
||||||
%% Hooks Meck
|
%% Hooks Meck
|
||||||
|
|
|
@ -28,6 +28,7 @@ all() -> emqx_ct:all(?MODULE).
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
NewConfig = generate_config(),
|
NewConfig = generate_config(),
|
||||||
application:ensure_all_started(esockd),
|
application:ensure_all_started(esockd),
|
||||||
|
application:ensure_all_started(quicer),
|
||||||
application:ensure_all_started(cowboy),
|
application:ensure_all_started(cowboy),
|
||||||
lists:foreach(fun set_app_env/1, NewConfig),
|
lists:foreach(fun set_app_env/1, NewConfig),
|
||||||
Config.
|
Config.
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
-include_lib("emqx/include/emqx_mqtt.hrl").
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||||
|
-include_lib("common_test/include/ct.hrl").
|
||||||
|
|
||||||
-import(lists, [nth/2]).
|
-import(lists, [nth/2]).
|
||||||
|
|
||||||
|
@ -32,18 +33,37 @@
|
||||||
-define(WILD_TOPICS, [<<"TopicA/+">>, <<"+/C">>, <<"#">>, <<"/#">>, <<"/+">>,
|
-define(WILD_TOPICS, [<<"TopicA/+">>, <<"+/C">>, <<"#">>, <<"/#">>, <<"/+">>,
|
||||||
<<"+/+">>, <<"TopicA/#">>]).
|
<<"+/+">>, <<"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) ->
|
init_per_suite(Config) ->
|
||||||
%% Meck emqtt
|
|
||||||
ok = meck:new(emqtt, [non_strict, passthrough, no_history, no_link]),
|
|
||||||
%% Start Apps
|
%% Start Apps
|
||||||
emqx_ct_helpers:boot_modules(all),
|
emqx_ct_helpers:boot_modules(all),
|
||||||
emqx_ct_helpers:start_apps([]),
|
emqx_ct_helpers:start_apps([]),
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
ok = meck:unload(emqtt),
|
|
||||||
emqx_ct_helpers:stop_apps([]).
|
emqx_ct_helpers:stop_apps([]).
|
||||||
|
|
||||||
init_per_testcase(TestCase, Config) ->
|
init_per_testcase(TestCase, Config) ->
|
||||||
|
@ -97,9 +117,10 @@ waiting_client_process_exit(C) ->
|
||||||
1000 -> error({waiting_timeout, C})
|
1000 -> error({waiting_timeout, C})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
clean_retained(Topic) ->
|
clean_retained(Topic, Config) ->
|
||||||
{ok, Clean} = emqtt:start_link([{clean_start, true}]),
|
ConnFun = ?config(conn_fun, Config),
|
||||||
{ok, _} = emqtt:connect(Clean),
|
{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:publish(Clean, Topic, #{}, <<"">>, [{qos, ?QOS_1}, {retain, true}]),
|
||||||
ok = emqtt:disconnect(Clean).
|
ok = emqtt:disconnect(Clean).
|
||||||
|
|
||||||
|
@ -107,11 +128,12 @@ clean_retained(Topic) ->
|
||||||
%% Test Cases
|
%% Test Cases
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
t_basic_test(_) ->
|
t_basic_test(Config) ->
|
||||||
|
ConnFun = ?config(conn_fun, Config),
|
||||||
Topic = nth(1, ?TOPICS),
|
Topic = nth(1, ?TOPICS),
|
||||||
ct:print("Basic test starting"),
|
ct:print("Basic test starting"),
|
||||||
{ok, C} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, C} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(C),
|
{ok, _} = emqtt:ConnFun(C),
|
||||||
{ok, _, [1]} = emqtt:subscribe(C, Topic, qos1),
|
{ok, _, [1]} = emqtt:subscribe(C, Topic, qos1),
|
||||||
{ok, _, [2]} = emqtt:subscribe(C, Topic, qos2),
|
{ok, _, [2]} = emqtt:subscribe(C, Topic, qos2),
|
||||||
{ok, _} = emqtt:publish(C, Topic, <<"qos 2">>, 2),
|
{ok, _} = emqtt:publish(C, Topic, <<"qos 2">>, 2),
|
||||||
|
@ -124,16 +146,17 @@ t_basic_test(_) ->
|
||||||
%% Connection
|
%% Connection
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
t_connect_clean_start(_) ->
|
t_connect_clean_start(Config) ->
|
||||||
|
ConnFun = ?config(conn_fun, Config),
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
{ok, Client1} = emqtt:start_link([{clientid, <<"t_connect_clean_start">>},
|
{ok, Client1} = emqtt:start_link([{clientid, <<"t_connect_clean_start">>},
|
||||||
{proto_ver, v5},{clean_start, true}]),
|
{proto_ver, v5},{clean_start, true} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
?assertEqual(0, client_info(session_present, Client1)), %% [MQTT-3.1.2-4]
|
?assertEqual(0, client_info(session_present, Client1)), %% [MQTT-3.1.2-4]
|
||||||
ok = emqtt:pause(Client1),
|
ok = emqtt:pause(Client1),
|
||||||
{ok, Client2} = emqtt:start_link([{clientid, <<"t_connect_clean_start">>},
|
{ok, Client2} = emqtt:start_link([{clientid, <<"t_connect_clean_start">>},
|
||||||
{proto_ver, v5},{clean_start, false}]),
|
{proto_ver, v5},{clean_start, false} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client2),
|
{ok, _} = emqtt:ConnFun(Client2),
|
||||||
?assertEqual(1, client_info(session_present, Client2)), %% [MQTT-3.1.2-5]
|
?assertEqual(1, client_info(session_present, Client2)), %% [MQTT-3.1.2-5]
|
||||||
?assertEqual(142, receive_disconnect_reasoncode()),
|
?assertEqual(142, receive_disconnect_reasoncode()),
|
||||||
waiting_client_process_exit(Client1),
|
waiting_client_process_exit(Client1),
|
||||||
|
@ -142,32 +165,32 @@ t_connect_clean_start(_) ->
|
||||||
waiting_client_process_exit(Client2),
|
waiting_client_process_exit(Client2),
|
||||||
|
|
||||||
{ok, Client3} = emqtt:start_link([{clientid, <<"new_client">>},
|
{ok, Client3} = emqtt:start_link([{clientid, <<"new_client">>},
|
||||||
{proto_ver, v5},{clean_start, false}]),
|
{proto_ver, v5},{clean_start, false} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client3),
|
{ok, _} = emqtt:ConnFun(Client3),
|
||||||
?assertEqual(0, client_info(session_present, Client3)), %% [MQTT-3.1.2-6]
|
?assertEqual(0, client_info(session_present, Client3)), %% [MQTT-3.1.2-6]
|
||||||
ok = emqtt:disconnect(Client3),
|
ok = emqtt:disconnect(Client3),
|
||||||
waiting_client_process_exit(Client3),
|
waiting_client_process_exit(Client3),
|
||||||
|
|
||||||
process_flag(trap_exit, false).
|
process_flag(trap_exit, false).
|
||||||
|
|
||||||
t_connect_will_message(_) ->
|
t_connect_will_message(Config) ->
|
||||||
|
ConnFun = ?config(conn_fun, Config),
|
||||||
Topic = nth(1, ?TOPICS),
|
Topic = nth(1, ?TOPICS),
|
||||||
Payload = "will message",
|
Payload = "will message",
|
||||||
|
|
||||||
{ok, Client1} = emqtt:start_link([
|
{ok, Client1} = emqtt:start_link([ {proto_ver, v5},
|
||||||
{proto_ver, v5},
|
{clean_start, true},
|
||||||
{clean_start, true},
|
{will_flag, true},
|
||||||
{will_flag, true},
|
{will_topic, Topic},
|
||||||
{will_topic, Topic},
|
{will_payload, Payload} | Config
|
||||||
{will_payload, Payload}
|
]),
|
||||||
]),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
|
||||||
[ClientPid] = emqx_cm:lookup_channels(client_info(clientid, Client1)),
|
[ClientPid] = emqx_cm:lookup_channels(client_info(clientid, Client1)),
|
||||||
Info = emqx_connection:info(sys:get_state(ClientPid)),
|
Info = emqx_connection:info(sys:get_state(ClientPid)),
|
||||||
?assertNotEqual(undefined, maps:find(will_msg, Info)), %% [MQTT-3.1.2-7]
|
?assertNotEqual(undefined, maps:find(will_msg, Info)), %% [MQTT-3.1.2-7]
|
||||||
|
|
||||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client2),
|
{ok, _} = emqtt:ConnFun(Client2),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Client2, Topic, qos2),
|
{ok, _, [2]} = emqtt:subscribe(Client2, Topic, qos2),
|
||||||
|
|
||||||
ok = emqtt:disconnect(Client1, 4), %% [MQTT-3.14.2-1]
|
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)),
|
?assertEqual({ok, 0}, maps:find(qos, Msg)),
|
||||||
ok = emqtt:disconnect(Client2),
|
ok = emqtt:disconnect(Client2),
|
||||||
|
|
||||||
{ok, Client3} = emqtt:start_link([
|
{ok, Client3} = emqtt:start_link([ {proto_ver, v5},
|
||||||
{proto_ver, v5},
|
{clean_start, true},
|
||||||
{clean_start, true},
|
{will_flag, true},
|
||||||
{will_flag, true},
|
{will_topic, Topic},
|
||||||
{will_topic, Topic},
|
{will_payload, Payload} | Config
|
||||||
{will_payload, Payload}
|
]),
|
||||||
]),
|
{ok, _} = emqtt:ConnFun(Client3),
|
||||||
{ok, _} = emqtt:connect(Client3),
|
|
||||||
|
|
||||||
{ok, Client4} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client4} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client4),
|
{ok, _} = emqtt:ConnFun(Client4),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Client4, Topic, qos2),
|
{ok, _, [2]} = emqtt:subscribe(Client4, Topic, qos2),
|
||||||
ok = emqtt:disconnect(Client3),
|
ok = emqtt:disconnect(Client3),
|
||||||
?assertEqual(0, length(receive_messages(1))), %% [MQTT-3.1.2-10]
|
?assertEqual(0, length(receive_messages(1))), %% [MQTT-3.1.2-10]
|
||||||
ok = emqtt:disconnect(Client4).
|
ok = emqtt:disconnect(Client4).
|
||||||
|
|
||||||
t_batch_subscribe(_) ->
|
t_batch_subscribe(init, Config) ->
|
||||||
{ok, Client} = emqtt:start_link([{proto_ver, v5}, {clientid, <<"batch_test">>}]),
|
ok = meck:new(emqx_access_control, [non_strict, passthrough, no_history, no_link]),
|
||||||
{ok, _} = emqtt:connect(Client),
|
meck:expect(emqx_access_control, authorize, fun(_, _, _) -> deny end),
|
||||||
application:set_env(emqx, enable_acl_cache, false),
|
Config;
|
||||||
application:set_env(emqx, acl_nomatch, deny),
|
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,
|
{ok, _, [?RC_NOT_AUTHORIZED,
|
||||||
?RC_NOT_AUTHORIZED,
|
?RC_NOT_AUTHORIZED,
|
||||||
?RC_NOT_AUTHORIZED]} = emqtt:subscribe(Client, [{<<"t1">>, qos1},
|
?RC_NOT_AUTHORIZED]} = emqtt:subscribe(Client, [{<<"t1">>, qos1},
|
||||||
|
@ -209,25 +237,25 @@ t_batch_subscribe(_) ->
|
||||||
?RC_NO_SUBSCRIPTION_EXISTED]} = emqtt:unsubscribe(Client, [<<"t1">>,
|
?RC_NO_SUBSCRIPTION_EXISTED]} = emqtt:unsubscribe(Client, [<<"t1">>,
|
||||||
<<"t2">>,
|
<<"t2">>,
|
||||||
<<"t3">>]),
|
<<"t3">>]),
|
||||||
application:set_env(emqx, acl_nomatch, allow),
|
|
||||||
emqtt:disconnect(Client).
|
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),
|
Topic = nth(1, ?TOPICS),
|
||||||
Payload = "will message",
|
Payload = "will message",
|
||||||
|
|
||||||
{ok, Client1} = emqtt:start_link([
|
{ok, Client1} = emqtt:start_link([ {proto_ver, v5},
|
||||||
{proto_ver, v5},
|
{clean_start, true},
|
||||||
{clean_start, true},
|
{will_flag, true},
|
||||||
{will_flag, true},
|
{will_topic, Topic},
|
||||||
{will_topic, Topic},
|
{will_payload, Payload},
|
||||||
{will_payload, Payload},
|
{will_retain, false} | Config
|
||||||
{will_retain, false}
|
]),
|
||||||
]),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
|
||||||
|
|
||||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client2),
|
{ok, _} = emqtt:ConnFun(Client2),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Client2, #{}, [{Topic, [{rap, true}, {qos, 2}]}]),
|
{ok, _, [2]} = emqtt:subscribe(Client2, #{}, [{Topic, [{rap, true}, {qos, 2}]}]),
|
||||||
|
|
||||||
ok = emqtt:disconnect(Client1, 4),
|
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]
|
?assertEqual({ok, false}, maps:find(retain, Msg1)), %% [MQTT-3.1.2-14]
|
||||||
ok = emqtt:disconnect(Client2),
|
ok = emqtt:disconnect(Client2),
|
||||||
|
|
||||||
{ok, Client3} = emqtt:start_link([
|
{ok, Client3} = emqtt:start_link([ {proto_ver, v5},
|
||||||
{proto_ver, v5},
|
{clean_start, true},
|
||||||
{clean_start, true},
|
{will_flag, true},
|
||||||
{will_flag, true},
|
{will_topic, Topic},
|
||||||
{will_topic, Topic},
|
{will_payload, Payload},
|
||||||
{will_payload, Payload},
|
{will_retain, true} | Config
|
||||||
{will_retain, true}
|
]),
|
||||||
]),
|
{ok, _} = emqtt:ConnFun(Client3),
|
||||||
{ok, _} = emqtt:connect(Client3),
|
|
||||||
|
|
||||||
{ok, Client4} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client4} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client4),
|
{ok, _} = emqtt:ConnFun(Client4),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Client4, #{}, [{Topic, [{rap, true}, {qos, 2}]}]),
|
{ok, _, [2]} = emqtt:subscribe(Client4, #{}, [{Topic, [{rap, true}, {qos, 2}]}]),
|
||||||
|
|
||||||
ok = emqtt:disconnect(Client3, 4),
|
ok = emqtt:disconnect(Client3, 4),
|
||||||
[Msg2 | _ ] = receive_messages(1),
|
[Msg2 | _ ] = receive_messages(1),
|
||||||
?assertEqual({ok, true}, maps:find(retain, Msg2)), %% [MQTT-3.1.2-15]
|
?assertEqual({ok, true}, maps:find(retain, Msg2)), %% [MQTT-3.1.2-15]
|
||||||
ok = emqtt:disconnect(Client4),
|
ok = emqtt:disconnect(Client4),
|
||||||
clean_retained(Topic).
|
clean_retained(Topic, Config).
|
||||||
|
|
||||||
t_connect_idle_timeout(_) ->
|
t_connect_idle_timeout(_Config) ->
|
||||||
IdleTimeout = 2000,
|
IdleTimeout = 2000,
|
||||||
emqx_zone:set_env(external, idle_timeout, IdleTimeout),
|
emqx_zone:set_env(external, idle_timeout, IdleTimeout),
|
||||||
|
|
||||||
|
@ -263,25 +290,30 @@ t_connect_idle_timeout(_) ->
|
||||||
timer:sleep(IdleTimeout),
|
timer:sleep(IdleTimeout),
|
||||||
?assertMatch({error, closed}, emqtt_sock:recv(Sock,1024)).
|
?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]),
|
ok = meck:new(proplists, [non_strict, passthrough, no_history, no_link, unstick]),
|
||||||
meck:expect(proplists, get_value, fun(active_n, _Options, _Default) -> 1;
|
meck:expect(proplists, get_value, fun(active_n, _Options, _Default) -> 1;
|
||||||
(Arg1, ARg2, Arg3) -> meck:passthrough([Arg1, ARg2, Arg3])
|
(Arg1, ARg2, Arg3) -> meck:passthrough([Arg1, ARg2, Arg3])
|
||||||
end),
|
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),
|
Topic = nth(1, ?TOPICS),
|
||||||
emqx_zone:set_env(external, publish_limit, {3, 5}),
|
emqx_zone:set_env(external, publish_limit, {3, 5}),
|
||||||
|
|
||||||
{ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60}]),
|
{ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client),
|
{ok, _} = emqtt:ConnFun(Client),
|
||||||
[ClientPid] = emqx_cm:lookup_channels(client_info(clientid, Client)),
|
[ClientPid] = emqx_cm:lookup_channels(client_info(clientid, Client)),
|
||||||
|
|
||||||
?assertEqual(undefined, emqx_connection:info(limit_timer, sys:get_state(ClientPid))),
|
?assertEqual(undefined, emqx_connection:info(limit_timer, sys:get_state(ClientPid))),
|
||||||
Payload = <<"t_shared_subscriptions_client_terminates_when_qos_eq_2">>,
|
Payload = <<"t_shared_subscriptions_client_terminates_when_qos_eq_2">>,
|
||||||
ok = emqtt:publish(Client, Topic, Payload, 0),
|
{ok, 2} = emqtt:publish(Client, Topic, Payload, 1),
|
||||||
ok = emqtt:publish(Client, Topic, Payload, 0),
|
{ok, 3} = emqtt:publish(Client, Topic, Payload, 1),
|
||||||
ok = emqtt:publish(Client, Topic, Payload, 0),
|
{ok, 4} = emqtt:publish(Client, Topic, Payload, 1),
|
||||||
timer:sleep(200),
|
timer:sleep(250),
|
||||||
?assert(is_reference(emqx_connection:info(limit_timer, sys:get_state(ClientPid)))),
|
?assert(is_reference(emqx_connection:info(limit_timer, sys:get_state(ClientPid)))),
|
||||||
|
|
||||||
ok = emqtt:disconnect(Client),
|
ok = emqtt:disconnect(Client),
|
||||||
|
@ -301,9 +333,10 @@ t_connect_emit_stats_timeout('end', Config) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_connect_emit_stats_timeout(Config) ->
|
t_connect_emit_stats_timeout(Config) ->
|
||||||
|
ConnFun = ?config(conn_fun, Config),
|
||||||
{_, IdleTimeout} = lists:keyfind(idle_timeout, 1, Config),
|
{_, IdleTimeout} = lists:keyfind(idle_timeout, 1, Config),
|
||||||
{ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60}]),
|
{ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client),
|
{ok, _} = emqtt:ConnFun(Client),
|
||||||
[ClientPid] = emqx_cm:lookup_channels(client_info(clientid, Client)),
|
[ClientPid] = emqx_cm:lookup_channels(client_info(clientid, Client)),
|
||||||
?assert(is_reference(emqx_connection:info(stats_timer, sys:get_state(ClientPid)))),
|
?assert(is_reference(emqx_connection:info(stats_timer, sys:get_state(ClientPid)))),
|
||||||
?block_until(#{?snk_kind := cancel_stats_timer}, IdleTimeout * 2, _BackInTime = 0),
|
?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).
|
ok = emqtt:disconnect(Client).
|
||||||
|
|
||||||
%% [MQTT-3.1.2-22]
|
%% [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.
|
%% Prevent the emqtt client bringing us down on the disconnect.
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
|
|
||||||
Keepalive = 2,
|
Keepalive = 2,
|
||||||
|
|
||||||
{ok, Client} = emqtt:start_link([{proto_ver, v5},
|
{ok, Client} = emqtt:start_link([{proto_ver, v5},
|
||||||
{keepalive, Keepalive}]),
|
{keepalive, Keepalive} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client),
|
{ok, _} = emqtt:ConnFun(Client),
|
||||||
emqtt:pause(Client),
|
emqtt:pause(Client),
|
||||||
receive
|
receive
|
||||||
{disconnected, ReasonCode, _Channel} -> ?assertEqual(141, ReasonCode)
|
{disconnected, ReasonCode, _Channel} -> ?assertEqual(141, ReasonCode)
|
||||||
|
@ -328,30 +362,30 @@ t_connect_keepalive_timeout(_) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% [MQTT-3.1.2-23]
|
%% [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),
|
Topic = nth(1, ?TOPICS),
|
||||||
Payload = "test message",
|
Payload = "test message",
|
||||||
|
|
||||||
{ok, Client1} = emqtt:start_link([
|
{ok, Client1} = emqtt:start_link([ {clientid, <<"t_connect_session_expiry_interval">>},
|
||||||
{clientid, <<"t_connect_session_expiry_interval">>},
|
{proto_ver, v5},
|
||||||
{proto_ver, v5},
|
{properties, #{'Session-Expiry-Interval' => 7200}}
|
||||||
{properties, #{'Session-Expiry-Interval' => 7200}}
|
| Config
|
||||||
]),
|
]),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Client1, Topic, qos2),
|
{ok, _, [2]} = emqtt:subscribe(Client1, Topic, qos2),
|
||||||
ok = emqtt:disconnect(Client1),
|
ok = emqtt:disconnect(Client1),
|
||||||
|
|
||||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client2),
|
{ok, _} = emqtt:ConnFun(Client2),
|
||||||
{ok, 2} = emqtt:publish(Client2, Topic, Payload, 2),
|
{ok, 2} = emqtt:publish(Client2, Topic, Payload, 2),
|
||||||
ok = emqtt:disconnect(Client2),
|
ok = emqtt:disconnect(Client2),
|
||||||
|
|
||||||
{ok, Client3} = emqtt:start_link([
|
{ok, Client3} = emqtt:start_link([ {clientid, <<"t_connect_session_expiry_interval">>},
|
||||||
{clientid, <<"t_connect_session_expiry_interval">>},
|
{proto_ver, v5},
|
||||||
{proto_ver, v5},
|
{clean_start, false} | Config
|
||||||
{clean_start, false}
|
|
||||||
]),
|
]),
|
||||||
{ok, _} = emqtt:connect(Client3),
|
{ok, _} = emqtt:ConnFun(Client3),
|
||||||
[Msg | _ ] = receive_messages(1),
|
[Msg | _ ] = receive_messages(1),
|
||||||
?assertEqual({ok, iolist_to_binary(Topic)}, maps:find(topic, Msg)),
|
?assertEqual({ok, iolist_to_binary(Topic)}, maps:find(topic, Msg)),
|
||||||
?assertEqual({ok, iolist_to_binary(Payload)}, maps:find(payload, 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]
|
%% [MQTT-3.1.3-9]
|
||||||
%% !!!REFACTOR NEED:
|
%% !!!REFACTOR NEED:
|
||||||
%t_connect_will_delay_interval(_) ->
|
%t_connect_will_delay_interval(Config) ->
|
||||||
% process_flag(trap_exit, true),
|
% process_flag(trap_exit, true),
|
||||||
% Topic = nth(1, ?TOPICS),
|
% Topic = nth(1, ?TOPICS),
|
||||||
% Payload = "will message",
|
% Payload = "will message",
|
||||||
%
|
%
|
||||||
% {ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
% {ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
% {ok, _} = emqtt:connect(Client1),
|
% {ok, _} = emqtt:ConnFun(Client1),
|
||||||
% {ok, _, [2]} = emqtt:subscribe(Client1, Topic, qos2),
|
% {ok, _, [2]} = emqtt:subscribe(Client1, Topic, qos2),
|
||||||
%
|
%
|
||||||
% {ok, Client2} = emqtt:start_link([
|
% {ok, Client2} = emqtt:start_link([
|
||||||
|
@ -379,9 +413,9 @@ t_connect_session_expiry_interval(_) ->
|
||||||
% {will_payload, Payload},
|
% {will_payload, Payload},
|
||||||
% {will_props, #{'Will-Delay-Interval' => 3}},
|
% {will_props, #{'Will-Delay-Interval' => 3}},
|
||||||
% {properties, #{'Session-Expiry-Interval' => 7200}},
|
% {properties, #{'Session-Expiry-Interval' => 7200}},
|
||||||
% {keepalive, 2}
|
% {keepalive, 2} | Config
|
||||||
% ]),
|
% ]),
|
||||||
% {ok, _} = emqtt:connect(Client2),
|
% {ok, _} = emqtt:ConnFun(Client2),
|
||||||
% timer:sleep(50),
|
% timer:sleep(50),
|
||||||
% erlang:exit(Client2, kill),
|
% erlang:exit(Client2, kill),
|
||||||
% timer:sleep(2000),
|
% timer:sleep(2000),
|
||||||
|
@ -399,9 +433,9 @@ t_connect_session_expiry_interval(_) ->
|
||||||
% {will_payload, Payload},
|
% {will_payload, Payload},
|
||||||
% {will_props, #{'Will-Delay-Interval' => 7200}},
|
% {will_props, #{'Will-Delay-Interval' => 7200}},
|
||||||
% {properties, #{'Session-Expiry-Interval' => 3}},
|
% {properties, #{'Session-Expiry-Interval' => 3}},
|
||||||
% {keepalive, 2}
|
% {keepalive, 2} | Config
|
||||||
% ]),
|
% ]),
|
||||||
% {ok, _} = emqtt:connect(Client3),
|
% {ok, _} = emqtt:ConnFun(Client3),
|
||||||
% timer:sleep(50),
|
% timer:sleep(50),
|
||||||
% erlang:exit(Client3, kill),
|
% erlang:exit(Client3, kill),
|
||||||
%
|
%
|
||||||
|
@ -418,18 +452,17 @@ t_connect_session_expiry_interval(_) ->
|
||||||
% process_flag(trap_exit, false).
|
% process_flag(trap_exit, false).
|
||||||
|
|
||||||
%% [MQTT-3.1.4-3]
|
%% [MQTT-3.1.4-3]
|
||||||
t_connect_duplicate_clientid(_) ->
|
t_connect_duplicate_clientid(Config) ->
|
||||||
|
ConnFun = ?config(conn_fun, Config),
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
{ok, Client1} = emqtt:start_link([
|
{ok, Client1} = emqtt:start_link([ {clientid, <<"t_connect_duplicate_clientid">>},
|
||||||
{clientid, <<"t_connect_duplicate_clientid">>},
|
{proto_ver, v5} | Config
|
||||||
{proto_ver, v5}
|
]),
|
||||||
]),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
{ok, Client2} = emqtt:start_link([ {clientid, <<"t_connect_duplicate_clientid">>},
|
||||||
{ok, Client2} = emqtt:start_link([
|
{proto_ver, v5} | Config
|
||||||
{clientid, <<"t_connect_duplicate_clientid">>},
|
]),
|
||||||
{proto_ver, v5}
|
{ok, _} = emqtt:ConnFun(Client2),
|
||||||
]),
|
|
||||||
{ok, _} = emqtt:connect(Client2),
|
|
||||||
?assertEqual(142, receive_disconnect_reasoncode()),
|
?assertEqual(142, receive_disconnect_reasoncode()),
|
||||||
waiting_client_process_exit(Client1),
|
waiting_client_process_exit(Client1),
|
||||||
|
|
||||||
|
@ -441,28 +474,33 @@ t_connect_duplicate_clientid(_) ->
|
||||||
%% Connack
|
%% Connack
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
t_connack_session_present(_) ->
|
t_connack_session_present(Config) ->
|
||||||
{ok, Client1} = emqtt:start_link([
|
ConnFun = ?config(conn_fun, Config),
|
||||||
{clientid, <<"t_connect_duplicate_clientid">>},
|
{ok, Client1} = emqtt:start_link([ {clientid, <<"t_connect_duplicate_clientid">>},
|
||||||
{proto_ver, v5},
|
{proto_ver, v5},
|
||||||
{properties, #{'Session-Expiry-Interval' => 7200}},
|
{properties, #{'Session-Expiry-Interval' => 7200}},
|
||||||
{clean_start, true}
|
{clean_start, true} | Config
|
||||||
]),
|
]),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
?assertEqual(0, client_info(session_present, Client1)), %% [MQTT-3.2.2-2]
|
?assertEqual(0, client_info(session_present, Client1)), %% [MQTT-3.2.2-2]
|
||||||
ok = emqtt:disconnect(Client1),
|
ok = emqtt:disconnect(Client1),
|
||||||
|
|
||||||
{ok, Client2} = emqtt:start_link([
|
{ok, Client2} = emqtt:start_link([ {clientid, <<"t_connect_duplicate_clientid">>},
|
||||||
{clientid, <<"t_connect_duplicate_clientid">>},
|
{proto_ver, v5},
|
||||||
{proto_ver, v5},
|
{properties, #{'Session-Expiry-Interval' => 7200}},
|
||||||
{properties, #{'Session-Expiry-Interval' => 7200}},
|
{clean_start, false} | Config
|
||||||
{clean_start, false}
|
]),
|
||||||
]),
|
{ok, _} = emqtt:ConnFun(Client2),
|
||||||
{ok, _} = emqtt:connect(Client2),
|
|
||||||
?assertEqual(1, client_info(session_present, Client2)), %% [[MQTT-3.2.2-3]]
|
?assertEqual(1, client_info(session_present, Client2)), %% [[MQTT-3.2.2-3]]
|
||||||
ok = emqtt:disconnect(Client2).
|
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),
|
process_flag(trap_exit, true),
|
||||||
Topic = nth(1, ?TOPICS),
|
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_caps'}),
|
||||||
persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}),
|
persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}),
|
||||||
|
|
||||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, Connack1} = emqtt:connect(Client1),
|
{ok, Connack1} = emqtt:ConnFun(Client1),
|
||||||
?assertEqual(0, maps:get('Maximum-QoS', Connack1)), %% [MQTT-3.2.2-9]
|
?assertEqual(0, maps:get('Maximum-QoS', Connack1)), %% [MQTT-3.2.2-9]
|
||||||
|
|
||||||
{ok, _, [0]} = emqtt:subscribe(Client1, Topic, 0), %% [MQTT-3.2.2-10]
|
{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]
|
?assertEqual(155, receive_disconnect_reasoncode()), %% [MQTT-3.2.2-11]
|
||||||
waiting_client_process_exit(Client1),
|
waiting_client_process_exit(Client1),
|
||||||
|
|
||||||
{ok, Client2} = emqtt:start_link([
|
{ok, Client2} = emqtt:start_link([ {proto_ver, v5},
|
||||||
{proto_ver, v5},
|
{will_flag, true},
|
||||||
{will_flag, true},
|
{will_topic, Topic},
|
||||||
{will_topic, Topic},
|
{will_payload, <<"Unsupported Qos">>},
|
||||||
{will_payload, <<"Unsupported Qos">>},
|
{will_qos, 2} | Config
|
||||||
{will_qos, 2}
|
]),
|
||||||
]),
|
{error, Connack2} = emqtt:ConnFun(Client2),
|
||||||
{error, Connack2} = emqtt:connect(Client2),
|
|
||||||
?assertMatch({qos_not_supported, _}, Connack2), %% [MQTT-3.2.2-12]
|
?assertMatch({qos_not_supported, _}, Connack2), %% [MQTT-3.2.2-12]
|
||||||
waiting_client_process_exit(Client2),
|
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_caps'}),
|
||||||
persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}),
|
persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}),
|
||||||
|
|
||||||
{ok, Client3} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client3} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, Connack3} = emqtt:connect(Client3),
|
{ok, Connack3} = emqtt:ConnFun(Client3),
|
||||||
?assertEqual(1, maps:get('Maximum-QoS', Connack3)), %% [MQTT-3.2.2-9]
|
?assertEqual(1, maps:get('Maximum-QoS', Connack3)), %% [MQTT-3.2.2-9]
|
||||||
|
|
||||||
{ok, _, [0]} = emqtt:subscribe(Client3, Topic, 0), %% [MQTT-3.2.2-10]
|
{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]
|
?assertEqual(155, receive_disconnect_reasoncode()), %% [MQTT-3.2.2-11]
|
||||||
waiting_client_process_exit(Client3),
|
waiting_client_process_exit(Client3),
|
||||||
|
|
||||||
{ok, Client4} = emqtt:start_link([
|
{ok, Client4} = emqtt:start_link([ {proto_ver, v5},
|
||||||
{proto_ver, v5},
|
{will_flag, true},
|
||||||
{will_flag, true},
|
{will_topic, Topic},
|
||||||
{will_topic, Topic},
|
{will_payload, <<"Unsupported Qos">>},
|
||||||
{will_payload, <<"Unsupported Qos">>},
|
{will_qos, 2} | Config
|
||||||
{will_qos, 2}
|
]),
|
||||||
]),
|
{error, Connack4} = emqtt:ConnFun(Client4),
|
||||||
{error, Connack4} = emqtt:connect(Client4),
|
|
||||||
?assertMatch({qos_not_supported, _}, Connack4), %% [MQTT-3.2.2-12]
|
?assertMatch({qos_not_supported, _}, Connack4), %% [MQTT-3.2.2-12]
|
||||||
waiting_client_process_exit(Client4),
|
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_caps'}),
|
||||||
persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}),
|
persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}),
|
||||||
|
|
||||||
{ok, Client5} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client5} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, Connack5} = emqtt:connect(Client5),
|
{ok, Connack5} = emqtt:ConnFun(Client5),
|
||||||
?assertEqual(undefined, maps:get('Maximum-QoS', Connack5, undefined)), %% [MQTT-3.2.2-9]
|
?assertEqual(undefined, maps:get('Maximum-QoS', Connack5, undefined)), %% [MQTT-3.2.2-9]
|
||||||
ok = emqtt:disconnect(Client5),
|
ok = emqtt:disconnect(Client5),
|
||||||
waiting_client_process_exit(Client5),
|
waiting_client_process_exit(Client5),
|
||||||
|
|
||||||
process_flag(trap_exit, false).
|
process_flag(trap_exit, false).
|
||||||
|
|
||||||
t_connack_assigned_clienid(_) ->
|
t_connack_assigned_clienid(Config) ->
|
||||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
ConnFun = ?config(conn_fun, Config),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
{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]
|
?assert(is_binary(client_info(clientid, Client1))), %% [MQTT-3.2.2-16]
|
||||||
ok = emqtt:disconnect(Client1).
|
ok = emqtt:disconnect(Client1).
|
||||||
|
|
||||||
|
@ -545,11 +582,12 @@ t_connack_assigned_clienid(_) ->
|
||||||
%% Publish
|
%% Publish
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
t_publish_rap(_) ->
|
t_publish_rap(Config) ->
|
||||||
|
ConnFun = ?config(conn_fun, Config),
|
||||||
Topic = nth(1, ?TOPICS),
|
Topic = nth(1, ?TOPICS),
|
||||||
|
|
||||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Client1, #{}, [{Topic, [{rap, true}, {qos, 2}]}]),
|
{ok, _, [2]} = emqtt:subscribe(Client1, #{}, [{Topic, [{rap, true}, {qos, 2}]}]),
|
||||||
{ok, _} = emqtt:publish(Client1, Topic, #{}, <<"retained message">>,
|
{ok, _} = emqtt:publish(Client1, Topic, #{}, <<"retained message">>,
|
||||||
[{qos, ?QOS_1}, {retain, true}]),
|
[{qos, ?QOS_1}, {retain, true}]),
|
||||||
|
@ -557,8 +595,8 @@ t_publish_rap(_) ->
|
||||||
?assertEqual(true, maps:get(retain, Msg1)), %% [MQTT-3.3.1-12]
|
?assertEqual(true, maps:get(retain, Msg1)), %% [MQTT-3.3.1-12]
|
||||||
ok = emqtt:disconnect(Client1),
|
ok = emqtt:disconnect(Client1),
|
||||||
|
|
||||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client2),
|
{ok, _} = emqtt:ConnFun(Client2),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Client2, #{}, [{Topic, [{rap, false}, {qos, 2}]}]),
|
{ok, _, [2]} = emqtt:subscribe(Client2, #{}, [{Topic, [{rap, false}, {qos, 2}]}]),
|
||||||
{ok, _} = emqtt:publish(Client2, Topic, #{}, <<"retained message">>,
|
{ok, _} = emqtt:publish(Client2, Topic, #{}, <<"retained message">>,
|
||||||
[{qos, ?QOS_1}, {retain, true}]),
|
[{qos, ?QOS_1}, {retain, true}]),
|
||||||
|
@ -566,44 +604,47 @@ t_publish_rap(_) ->
|
||||||
?assertEqual(false, maps:get(retain, Msg2)), %% [MQTT-3.3.1-13]
|
?assertEqual(false, maps:get(retain, Msg2)), %% [MQTT-3.3.1-13]
|
||||||
ok = emqtt:disconnect(Client2),
|
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),
|
process_flag(trap_exit, true),
|
||||||
Topic = nth(1, ?WILD_TOPICS),
|
Topic = nth(1, ?WILD_TOPICS),
|
||||||
|
|
||||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
ok = emqtt:publish(Client1, Topic, <<"error topic">>),
|
ok = emqtt:publish(Client1, Topic, <<"error topic">>),
|
||||||
?assertEqual(144, receive_disconnect_reasoncode()),
|
?assertEqual(144, receive_disconnect_reasoncode()),
|
||||||
waiting_client_process_exit(Client1),
|
waiting_client_process_exit(Client1),
|
||||||
|
|
||||||
process_flag(trap_exit, false).
|
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),
|
Topic = nth(1, ?TOPICS),
|
||||||
Properties = #{'Payload-Format-Indicator' => 233},
|
Properties = #{'Payload-Format-Indicator' => 233},
|
||||||
|
|
||||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Client1, Topic, qos2),
|
{ok, _, [2]} = emqtt:subscribe(Client1, Topic, qos2),
|
||||||
ok = emqtt:publish(Client1, Topic, Properties, <<"Payload Format Indicator">>, [{qos, ?QOS_0}]),
|
ok = emqtt:publish(Client1, Topic, Properties, <<"Payload Format Indicator">>, [{qos, ?QOS_0}]),
|
||||||
[Msg1 | _] = receive_messages(1),
|
[Msg1 | _] = receive_messages(1),
|
||||||
?assertEqual(Properties, maps:get(properties, Msg1)), %% [MQTT-3.3.2-6]
|
?assertEqual(Properties, maps:get(properties, Msg1)), %% [MQTT-3.3.2-6]
|
||||||
ok = emqtt:disconnect(Client1).
|
ok = emqtt:disconnect(Client1).
|
||||||
|
|
||||||
t_publish_topic_alias(_) ->
|
t_publish_topic_alias(Config) ->
|
||||||
|
ConnFun = ?config(conn_fun, Config),
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
Topic = nth(1, ?TOPICS),
|
Topic = nth(1, ?TOPICS),
|
||||||
|
|
||||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
ok = emqtt:publish(Client1, Topic, #{'Topic-Alias' => 0}, <<"Topic-Alias">>, [{qos, ?QOS_0}]),
|
ok = emqtt:publish(Client1, Topic, #{'Topic-Alias' => 0}, <<"Topic-Alias">>, [{qos, ?QOS_0}]),
|
||||||
?assertEqual(148, receive_disconnect_reasoncode()), %% [MQTT-3.3.2-8]
|
?assertEqual(148, receive_disconnect_reasoncode()), %% [MQTT-3.3.2-8]
|
||||||
waiting_client_process_exit(Client1),
|
waiting_client_process_exit(Client1),
|
||||||
|
|
||||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client2),
|
{ok, _} = emqtt:ConnFun(Client2),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Client2, Topic, qos2),
|
{ok, _, [2]} = emqtt:subscribe(Client2, Topic, qos2),
|
||||||
ok = emqtt:publish(Client2, Topic, #{'Topic-Alias' => 233},
|
ok = emqtt:publish(Client2, Topic, #{'Topic-Alias' => 233},
|
||||||
<<"Topic-Alias">>, [{qos, ?QOS_0}]),
|
<<"Topic-Alias">>, [{qos, ?QOS_0}]),
|
||||||
|
@ -615,12 +656,13 @@ t_publish_topic_alias(_) ->
|
||||||
|
|
||||||
process_flag(trap_exit, false).
|
process_flag(trap_exit, false).
|
||||||
|
|
||||||
t_publish_response_topic(_) ->
|
t_publish_response_topic(Config) ->
|
||||||
|
ConnFun = ?config(conn_fun, Config),
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
Topic = nth(1, ?TOPICS),
|
Topic = nth(1, ?TOPICS),
|
||||||
|
|
||||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
ok = emqtt:publish(Client1, Topic, #{'Response-Topic' => nth(1, ?WILD_TOPICS)},
|
ok = emqtt:publish(Client1, Topic, #{'Response-Topic' => nth(1, ?WILD_TOPICS)},
|
||||||
<<"Response-Topic">>, [{qos, ?QOS_0}]),
|
<<"Response-Topic">>, [{qos, ?QOS_0}]),
|
||||||
?assertEqual(130, receive_disconnect_reasoncode()), %% [MQTT-3.3.2-14]
|
?assertEqual(130, receive_disconnect_reasoncode()), %% [MQTT-3.3.2-14]
|
||||||
|
@ -628,7 +670,8 @@ t_publish_response_topic(_) ->
|
||||||
|
|
||||||
process_flag(trap_exit, false).
|
process_flag(trap_exit, false).
|
||||||
|
|
||||||
t_publish_properties(_) ->
|
t_publish_properties(Config) ->
|
||||||
|
ConnFun = ?config(conn_fun, Config),
|
||||||
Topic = nth(1, ?TOPICS),
|
Topic = nth(1, ?TOPICS),
|
||||||
Properties = #{
|
Properties = #{
|
||||||
'Response-Topic' => Topic, %% [MQTT-3.3.2-15]
|
'Response-Topic' => Topic, %% [MQTT-3.3.2-15]
|
||||||
|
@ -637,20 +680,21 @@ t_publish_properties(_) ->
|
||||||
'Content-Type' => <<"2333">> %% [MQTT-3.3.2-20]
|
'Content-Type' => <<"2333">> %% [MQTT-3.3.2-20]
|
||||||
},
|
},
|
||||||
|
|
||||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Client1, Topic, qos2),
|
{ok, _, [2]} = emqtt:subscribe(Client1, Topic, qos2),
|
||||||
ok = emqtt:publish(Client1, Topic, Properties, <<"Publish Properties">>, [{qos, ?QOS_0}]),
|
ok = emqtt:publish(Client1, Topic, Properties, <<"Publish Properties">>, [{qos, ?QOS_0}]),
|
||||||
[Msg1 | _] = receive_messages(1),
|
[Msg1 | _] = receive_messages(1),
|
||||||
?assertEqual(Properties, maps:get(properties, Msg1)), %% [MQTT-3.3.2-16]
|
?assertEqual(Properties, maps:get(properties, Msg1)), %% [MQTT-3.3.2-16]
|
||||||
ok = emqtt:disconnect(Client1).
|
ok = emqtt:disconnect(Client1).
|
||||||
|
|
||||||
t_publish_overlapping_subscriptions(_) ->
|
t_publish_overlapping_subscriptions(Config) ->
|
||||||
|
ConnFun = ?config(conn_fun, Config),
|
||||||
Topic = nth(1, ?TOPICS),
|
Topic = nth(1, ?TOPICS),
|
||||||
Properties = #{'Subscription-Identifier' => 2333},
|
Properties = #{'Subscription-Identifier' => 2333},
|
||||||
|
|
||||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
{ok, _, [1]} = emqtt:subscribe(Client1, Properties, nth(1, ?WILD_TOPICS), qos1),
|
{ok, _, [1]} = emqtt:subscribe(Client1, Properties, nth(1, ?WILD_TOPICS), qos1),
|
||||||
{ok, _, [0]} = emqtt:subscribe(Client1, Properties, nth(3, ?WILD_TOPICS), qos0),
|
{ok, _, [0]} = emqtt:subscribe(Client1, Properties, nth(3, ?WILD_TOPICS), qos0),
|
||||||
{ok, _} = emqtt:publish(Client1, Topic, #{},
|
{ok, _} = emqtt:publish(Client1, Topic, #{},
|
||||||
|
@ -665,13 +709,15 @@ t_publish_overlapping_subscriptions(_) ->
|
||||||
%% Subsctibe
|
%% Subsctibe
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
t_subscribe_topic_alias(_) ->
|
t_subscribe_topic_alias(Config) ->
|
||||||
|
ConnFun = ?config(conn_fun, Config),
|
||||||
Topic1 = nth(1, ?TOPICS),
|
Topic1 = nth(1, ?TOPICS),
|
||||||
Topic2 = nth(2, ?TOPICS),
|
Topic2 = nth(2, ?TOPICS),
|
||||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5},
|
{ok, Client1} = emqtt:start_link([ {proto_ver, v5},
|
||||||
{properties, #{'Topic-Alias-Maximum' => 1}}
|
{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, Topic1, qos2),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Client1, Topic2, qos2),
|
{ok, _, [2]} = emqtt:subscribe(Client1, Topic2, qos2),
|
||||||
|
|
||||||
|
@ -692,27 +738,29 @@ t_subscribe_topic_alias(_) ->
|
||||||
|
|
||||||
ok = emqtt:disconnect(Client1).
|
ok = emqtt:disconnect(Client1).
|
||||||
|
|
||||||
t_subscribe_no_local(_) ->
|
t_subscribe_no_local(Config) ->
|
||||||
|
ConnFun = ?config(conn_fun, Config),
|
||||||
Topic = nth(1, ?TOPICS),
|
Topic = nth(1, ?TOPICS),
|
||||||
|
|
||||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Client1, #{}, [{Topic, [{nl, true}, {qos, 2}]}]),
|
{ok, _, [2]} = emqtt:subscribe(Client1, #{}, [{Topic, [{nl, true}, {qos, 2}]}]),
|
||||||
|
|
||||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client2),
|
{ok, _} = emqtt:ConnFun(Client2),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Client2, #{}, [{Topic, [{nl, true}, {qos, 2}]}]),
|
{ok, _, [2]} = emqtt:subscribe(Client2, #{}, [{Topic, [{nl, true}, {qos, 2}]}]),
|
||||||
|
|
||||||
ok = emqtt:publish(Client1, Topic, <<"t_subscribe_no_local">>, 0),
|
ok = emqtt:publish(Client1, Topic, <<"t_subscribe_no_local">>, 0),
|
||||||
?assertEqual(1, length(receive_messages(2))), %% [MQTT-3.8.3-3]
|
?assertEqual(1, length(receive_messages(2))), %% [MQTT-3.8.3-3]
|
||||||
ok = emqtt:disconnect(Client1).
|
ok = emqtt:disconnect(Client1).
|
||||||
|
|
||||||
t_subscribe_actions(_) ->
|
t_subscribe_actions(Config) ->
|
||||||
|
ConnFun = ?config(conn_fun, Config),
|
||||||
Topic = nth(1, ?TOPICS),
|
Topic = nth(1, ?TOPICS),
|
||||||
Properties = #{'Subscription-Identifier' => 2333},
|
Properties = #{'Subscription-Identifier' => 2333},
|
||||||
|
|
||||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Client1, Properties, Topic, qos2),
|
{ok, _, [2]} = emqtt:subscribe(Client1, Properties, Topic, qos2),
|
||||||
{ok, _, [1]} = emqtt:subscribe(Client1, Properties, Topic, qos1),
|
{ok, _, [1]} = emqtt:subscribe(Client1, Properties, Topic, qos1),
|
||||||
{ok, _} = emqtt:publish(Client1, Topic, <<"t_subscribe_actions">>, 2),
|
{ok, _} = emqtt:publish(Client1, Topic, <<"t_subscribe_actions">>, 2),
|
||||||
|
@ -726,12 +774,13 @@ t_subscribe_actions(_) ->
|
||||||
%% Unsubsctibe Unsuback
|
%% Unsubsctibe Unsuback
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
t_unscbsctibe(_) ->
|
t_unscbsctibe(Config) ->
|
||||||
|
ConnFun = ?config(conn_fun, Config),
|
||||||
Topic1 = nth(1, ?TOPICS),
|
Topic1 = nth(1, ?TOPICS),
|
||||||
Topic2 = nth(2, ?TOPICS),
|
Topic2 = nth(2, ?TOPICS),
|
||||||
|
|
||||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Client1, Topic1, qos2),
|
{ok, _, [2]} = emqtt:subscribe(Client1, Topic1, qos2),
|
||||||
{ok, _, [0]} = emqtt:unsubscribe(Client1, Topic1), %% [MQTT-3.10.4-4]
|
{ok, _, [0]} = emqtt:unsubscribe(Client1, Topic1), %% [MQTT-3.10.4-4]
|
||||||
{ok, _, [17]} = emqtt:unsubscribe(Client1, <<"noExistTopic">>), %% [MQTT-3.10.4-5]
|
{ok, _, [17]} = emqtt:unsubscribe(Client1, <<"noExistTopic">>), %% [MQTT-3.10.4-5]
|
||||||
|
@ -745,9 +794,10 @@ t_unscbsctibe(_) ->
|
||||||
%% Pingreq
|
%% Pingreq
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
t_pingreq(_) ->
|
t_pingreq(Config) ->
|
||||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
ConnFun = ?config(conn_fun, Config),
|
||||||
{ok, _} = emqtt:connect(Client1),
|
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||||
|
{ok, _} = emqtt:ConnFun(Client1),
|
||||||
pong = emqtt:ping(Client1), %% [MQTT-3.12.4-1]
|
pong = emqtt:ping(Client1), %% [MQTT-3.12.4-1]
|
||||||
ok = emqtt:disconnect(Client1).
|
ok = emqtt:disconnect(Client1).
|
||||||
|
|
||||||
|
@ -755,7 +805,14 @@ t_pingreq(_) ->
|
||||||
%% Shared Subscriptions
|
%% 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),
|
process_flag(trap_exit, true),
|
||||||
application:set_env(emqx, shared_dispatch_ack_enabled, 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,
|
meck:expect(emqtt, connected,
|
||||||
fun(cast, ?PUBLISH_PACKET(?QOS_2, _PacketId), _State) ->
|
fun(cast, ?PUBLISH_PACKET(?QOS_2, _PacketId), _State) ->
|
||||||
ok = counters:add(CRef, 1, 1),
|
ok = counters:add(CRef, 1, 1),
|
||||||
{stop, {shutdown, for_testiong}};
|
{stop, {shutdown, for_testing}};
|
||||||
(Arg1, ARg2, Arg3) -> meck:passthrough([Arg1, ARg2, Arg3])
|
(Arg1, ARg2, Arg3) -> meck:passthrough([Arg1, ARg2, Arg3])
|
||||||
end),
|
end),
|
||||||
|
|
||||||
{ok, Sub1} = emqtt:start_link([{proto_ver, v5},
|
{ok, Sub1} = emqtt:start_link([ {proto_ver, v5},
|
||||||
{clientid, <<"sub_client_1">>},
|
{clientid, <<"sub_client_1">>},
|
||||||
{keepalive, 5}]),
|
{keepalive, 5} | Config
|
||||||
{ok, _} = emqtt:connect(Sub1),
|
]),
|
||||||
|
{ok, _} = emqtt:ConnFun(Sub1),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Sub1, SharedTopic, qos2),
|
{ok, _, [2]} = emqtt:subscribe(Sub1, SharedTopic, qos2),
|
||||||
|
|
||||||
{ok, Sub2} = emqtt:start_link([{proto_ver, v5},
|
{ok, Sub2} = emqtt:start_link([{proto_ver, v5},
|
||||||
{clientid, <<"sub_client_2">>},
|
{clientid, <<"sub_client_2">>},
|
||||||
{keepalive, 5}]),
|
{keepalive, 5} | Config]),
|
||||||
{ok, _} = emqtt:connect(Sub2),
|
{ok, _} = emqtt:ConnFun(Sub2),
|
||||||
{ok, _, [2]} = emqtt:subscribe(Sub2, SharedTopic, qos2),
|
{ok, _, [2]} = emqtt:subscribe(Sub2, SharedTopic, qos2),
|
||||||
|
|
||||||
{ok, Pub} = emqtt:start_link([{proto_ver, v5}, {clientid, <<"pub_client">>}]),
|
{ok, Pub} = emqtt:start_link([{proto_ver, v5}, {clientid, <<"pub_client">>} | Config]),
|
||||||
{ok, _} = emqtt:connect(Pub),
|
{ok, _} = emqtt:ConnFun(Pub),
|
||||||
{ok, _} = emqtt:publish(Pub, Topic,
|
{ok, _} = emqtt:publish(Pub, Topic,
|
||||||
<<"t_shared_subscriptions_client_terminates_when_qos_eq_2">>, 2),
|
<<"t_shared_subscriptions_client_terminates_when_qos_eq_2">>, 2),
|
||||||
|
|
||||||
receive
|
receive
|
||||||
{'EXIT', _,{shutdown, for_testiong}} ->
|
{'EXIT', _,{shutdown, for_testing}} ->
|
||||||
ok
|
ok
|
||||||
after 1000 ->
|
after 1000 ->
|
||||||
error("disconnected timeout")
|
ct:fail("disconnected timeout")
|
||||||
end,
|
end,
|
||||||
|
|
||||||
?assertEqual(1, counters:get(CRef, 1)),
|
?assertEqual(1, counters:get(CRef, 1)),
|
||||||
|
|
|
@ -63,64 +63,28 @@ t_load(_) ->
|
||||||
?assertEqual({error, not_started}, emqx_plugins:unload(emqx_hocon_plugin)),
|
?assertEqual({error, not_started}, emqx_plugins:unload(emqx_hocon_plugin)),
|
||||||
|
|
||||||
application:set_env(emqx, expand_plugins_dir, undefined),
|
application:set_env(emqx, expand_plugins_dir, undefined),
|
||||||
application:set_env(emqx, plugins_loaded_file, 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)).
|
|
||||||
|
|
||||||
t_load_ext_plugin(_) ->
|
t_load_ext_plugin(_) ->
|
||||||
?assertError({plugin_app_file_not_found, _},
|
?assertError({plugin_app_file_not_found, _},
|
||||||
emqx_plugins:load_ext_plugin("./not_existed_path/")).
|
emqx_plugins:load_ext_plugin("./not_existed_path/")).
|
||||||
|
|
||||||
t_list(_) ->
|
t_list(_) ->
|
||||||
?assertMatch([{plugin, _, _, _, _, _, _, _} | _ ], emqx_plugins:list()).
|
?assertMatch([{plugin, _, _, _, _, _, _} | _ ], emqx_plugins:list()).
|
||||||
|
|
||||||
t_find_plugin(_) ->
|
t_find_plugin(_) ->
|
||||||
?assertMatch({plugin, emqx_mini_plugin, _, _, _, _, _, _}, emqx_plugins:find_plugin(emqx_mini_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)).
|
?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)).
|
|
||||||
|
|
||||||
t_plugin(_) ->
|
t_plugin(_) ->
|
||||||
try
|
try
|
||||||
emqx_plugins:plugin(not_existed_plugin, undefined)
|
emqx_plugins:plugin(not_existed_plugin)
|
||||||
catch
|
catch
|
||||||
_Error:Reason:_Stacktrace ->
|
_Error:Reason:_Stacktrace ->
|
||||||
?assertEqual({plugin_not_found,not_existed_plugin}, Reason)
|
?assertEqual({plugin_not_found,not_existed_plugin}, Reason)
|
||||||
end,
|
end,
|
||||||
?assertMatch({plugin, emqx_mini_plugin, _, _, _, _, _, _}, emqx_plugins:plugin(emqx_mini_plugin, undefined)),
|
?assertMatch({plugin, emqx_mini_plugin, _, _, _, _, _}, emqx_plugins:plugin(emqx_mini_plugin)),
|
||||||
?assertMatch({plugin, emqx_hocon_plugin, _, _, _, _, _, _}, emqx_plugins:plugin(emqx_hocon_plugin, undefined)).
|
?assertMatch({plugin, emqx_hocon_plugin, _, _, _, _, _}, emqx_plugins:plugin(emqx_hocon_plugin)).
|
||||||
|
|
||||||
t_filter_plugins(_) ->
|
|
||||||
?assertEqual([name1, name2], emqx_plugins:filter_plugins([name1, {name2,true}, {name3, false}])).
|
|
||||||
|
|
||||||
t_load_plugin(_) ->
|
t_load_plugin(_) ->
|
||||||
ok = meck:new(application, [unstick, non_strict, passthrough, no_history]),
|
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:new(emqx_plugins, [unstick, non_strict, passthrough, no_history]),
|
||||||
ok = meck:expect(emqx_plugins, generate_configs, fun(_) -> ok end),
|
ok = meck:expect(emqx_plugins, generate_configs, fun(_) -> ok end),
|
||||||
ok = meck:expect(emqx_plugins, apply_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({error, _}, emqx_plugins:load_plugin(already_loaded_app)),
|
||||||
?assertMatch(ok, emqx_plugins:load_plugin(normal, true)),
|
?assertMatch(ok, emqx_plugins:load_plugin(normal)),
|
||||||
?assertMatch({error,_}, emqx_plugins:load_plugin(error_app, true)),
|
?assertMatch({error,_}, emqx_plugins:load_plugin(error_app)),
|
||||||
|
|
||||||
ok = meck:unload(emqx_plugins),
|
ok = meck:unload(emqx_plugins),
|
||||||
ok = meck:unload(application).
|
ok = meck:unload(application).
|
||||||
|
@ -146,8 +110,8 @@ t_unload_plugin(_) ->
|
||||||
(error_app) -> {error, error};
|
(error_app) -> {error, error};
|
||||||
(_) -> ok end),
|
(_) -> ok end),
|
||||||
|
|
||||||
?assertEqual(ok, emqx_plugins:unload_plugin(not_started_app, true)),
|
?assertEqual(ok, emqx_plugins:unload_plugin(not_started_app)),
|
||||||
?assertEqual(ok, emqx_plugins:unload_plugin(normal, true)),
|
?assertEqual(ok, emqx_plugins:unload_plugin(normal)),
|
||||||
?assertEqual({error,error}, emqx_plugins:unload_plugin(error_app, true)),
|
?assertEqual({error,error}, emqx_plugins:unload_plugin(error_app)),
|
||||||
|
|
||||||
ok = meck:unload(application).
|
ok = meck:unload(application).
|
||||||
|
|
|
@ -64,7 +64,7 @@ init_per_testcase(TestCase, Config) when
|
||||||
end),
|
end),
|
||||||
%% Mock emqx_access_control
|
%% Mock emqx_access_control
|
||||||
ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]),
|
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
|
%% Mock emqx_hooks
|
||||||
ok = meck:new(emqx_hooks, [passthrough, no_history, no_link]),
|
ok = meck:new(emqx_hooks, [passthrough, no_history, no_link]),
|
||||||
ok = meck:expect(emqx_hooks, run, fun(_Hook, _Args) -> ok end),
|
ok = meck:expect(emqx_hooks, run, fun(_Hook, _Args) -> ok end),
|
||||||
|
|
|
@ -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
|
|
|
@ -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.
|
|
||||||
|
|
|
@ -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
|
|
|
@ -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)).
|
|
|
@ -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}
|
|
||||||
]}.
|
|
|
@ -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"}}}
|
|
||||||
]}
|
|
||||||
]}
|
|
||||||
]}.
|
|
|
@ -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.
|
|
||||||
|
|
|
@ -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"}
|
|
||||||
]}
|
|
||||||
]}.
|
|
|
@ -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.
|
|
|
@ -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.
|
|
||||||
|
|
|
@ -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).
|
|
||||||
|
|
|
@ -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.
|
|
|
@ -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.
|
|
|
@ -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/
|
|
|
@ -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.
|
|
|
@ -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/)
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
|
|
||||||
https://crypto.stackexchange.com/questions/30657/hmac-vs-ecdsa-for-jwt
|
|
||||||
|
|
|
@ -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"
|
|
|
@ -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}.
|
|
|
@ -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, []}
|
|
||||||
]}
|
|
||||||
]}.
|
|
|
@ -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"}
|
|
||||||
]}
|
|
||||||
]}.
|
|
|
@ -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, []}
|
|
||||||
]},
|
|
||||||
{<<".*">>, []}
|
|
||||||
]
|
|
||||||
}.
|
|
|
@ -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).
|
|
|
@ -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).
|
|
|
@ -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.
|
|
|
@ -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).
|
|
|
@ -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
|
|
|
@ -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.
|
|
||||||
|
|
|
@ -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=
|
|
||||||
|
|
|
@ -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 ) )
|
|
|
@ -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
Loading…
Reference in New Issue