chore(project): merge code from master
This commit is contained in:
commit
477097c062
|
@ -39,7 +39,7 @@ emqx_test(){
|
|||
unzip -q "${PACKAGE_PATH}/${packagename}"
|
||||
export EMQX_ZONE__EXTERNAL__SERVER_KEEPALIVE=60 \
|
||||
EMQX_MQTT__MAX_TOPIC_ALIAS=10
|
||||
sed -i '/emqx_telemetry/d' "${PACKAGE_PATH}"/emqx/data/loaded_plugins
|
||||
# sed -i '/emqx_telemetry/d' "${PACKAGE_PATH}"/emqx/data/loaded_plugins
|
||||
|
||||
echo "running ${packagename} start"
|
||||
if ! "${PACKAGE_PATH}"/emqx/bin/emqx start; then
|
||||
|
@ -115,7 +115,7 @@ emqx_test(){
|
|||
running_test(){
|
||||
export EMQX_ZONE__EXTERNAL__SERVER_KEEPALIVE=60 \
|
||||
EMQX_MQTT__MAX_TOPIC_ALIAS=10
|
||||
sed -i '/emqx_telemetry/d' /var/lib/emqx/loaded_plugins
|
||||
# sed -i '/emqx_telemetry/d' /var/lib/emqx/loaded_plugins
|
||||
|
||||
if ! emqx start; then
|
||||
cat /var/log/emqx/erlang.log.1 || true
|
||||
|
|
|
@ -38,7 +38,7 @@ services:
|
|||
- -c
|
||||
- |
|
||||
sed -i "s 127.0.0.1 $$(ip route show |grep "link" |awk '{print $$1}') g" /opt/emqx/etc/acl.conf
|
||||
sed -i '/emqx_telemetry/d' /opt/emqx/data/loaded_plugins
|
||||
# sed -i '/emqx_telemetry/d' /opt/emqx/data/loaded_plugins
|
||||
/opt/emqx/bin/emqx foreground
|
||||
healthcheck:
|
||||
test: ["CMD", "/opt/emqx/bin/emqx_ctl", "status"]
|
||||
|
@ -62,7 +62,7 @@ services:
|
|||
- -c
|
||||
- |
|
||||
sed -i "s 127.0.0.1 $$(ip route show |grep "link" |awk '{print $$1}') g" /opt/emqx/etc/acl.conf
|
||||
sed -i '/emqx_telemetry/d' /opt/emqx/data/loaded_plugins
|
||||
# sed -i '/emqx_telemetry/d' /opt/emqx/data/loaded_plugins
|
||||
/opt/emqx/bin/emqx foreground
|
||||
healthcheck:
|
||||
test: ["CMD", "/opt/emqx/bin/emqx", "ping"]
|
||||
|
|
|
@ -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
|
||||
networks:
|
||||
- emqx_bridge
|
||||
ports:
|
||||
- "27017:27017"
|
||||
command:
|
||||
--ipv6
|
||||
--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_PASSWORD: public
|
||||
volumes:
|
||||
- ../../apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data/ca.pem:/etc/certs/ca-cert.pem
|
||||
- ../../apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data/server-cert.pem:/etc/certs/server-cert.pem
|
||||
- ../../apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data/server-key.pem:/etc/certs/server-key.pem
|
||||
- ../../apps/emqx/etc/certs/cacert.pem:/etc/certs/ca-cert.pem
|
||||
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/server-cert.pem
|
||||
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/server-key.pem
|
||||
ports:
|
||||
- "3306:3306"
|
||||
networks:
|
||||
- emqx_bridge
|
||||
command:
|
||||
|
|
|
@ -5,7 +5,9 @@ services:
|
|||
container_name: redis
|
||||
image: redis:${REDIS_TAG}
|
||||
volumes:
|
||||
- ../../apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs:/tls
|
||||
- ../../apps/emqx/etc/certs/cacert.pem:/etc/certs/ca.crt
|
||||
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/redis.crt
|
||||
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/redis.key
|
||||
- ./redis/:/data/conf
|
||||
command: bash -c "/bin/bash /data/conf/redis.sh --node cluster --tls-enabled && tail -f /var/log/redis-server.log"
|
||||
networks:
|
||||
|
|
|
@ -5,7 +5,9 @@ services:
|
|||
container_name: redis
|
||||
image: redis:${REDIS_TAG}
|
||||
volumes:
|
||||
- ../../apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs:/tls
|
||||
- ../../apps/emqx/etc/certs/cacert.pem:/etc/certs/ca.crt
|
||||
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/redis.crt
|
||||
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/redis.key
|
||||
- ./redis/:/data/conf
|
||||
command: bash -c "/bin/bash /data/conf/redis.sh --node sentinel --tls-enabled && tail -f /var/log/redis-server.log"
|
||||
networks:
|
||||
|
|
|
@ -5,15 +5,17 @@ services:
|
|||
container_name: redis
|
||||
image: redis:${REDIS_TAG}
|
||||
volumes:
|
||||
- ../../apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs:/tls
|
||||
- ../../apps/emqx/etc/certs/cacert.pem:/etc/certs/ca.crt
|
||||
- ../../apps/emqx/etc/certs/cert.pem:/etc/certs/redis.crt
|
||||
- ../../apps/emqx/etc/certs/key.pem:/etc/certs/redis.key
|
||||
command:
|
||||
- redis-server
|
||||
- "--bind 0.0.0.0 ::"
|
||||
- --requirepass public
|
||||
- --tls-port 6380
|
||||
- --tls-cert-file /tls/redis.crt
|
||||
- --tls-key-file /tls/redis.key
|
||||
- --tls-ca-cert-file /tls/ca.crt
|
||||
- --tls-cert-file /etc/certs/redis.crt
|
||||
- --tls-key-file /etc/certs/redis.key
|
||||
- --tls-ca-cert-file /etc/certs/ca.crt
|
||||
restart: always
|
||||
networks:
|
||||
- emqx_bridge
|
||||
|
|
|
@ -2,9 +2,9 @@ ARG BUILD_FROM=postgres:11
|
|||
FROM ${BUILD_FROM}
|
||||
ARG POSTGRES_USER=postgres
|
||||
COPY --chown=$POSTGRES_USER .ci/docker-compose-file/pgsql/pg_hba.conf /var/lib/postgresql/pg_hba.conf
|
||||
COPY --chown=$POSTGRES_USER apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server-key.pem /var/lib/postgresql/server.key
|
||||
COPY --chown=$POSTGRES_USER apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server-cert.pem /var/lib/postgresql/server.crt
|
||||
COPY --chown=$POSTGRES_USER apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/ca.pem /var/lib/postgresql/root.crt
|
||||
COPY --chown=$POSTGRES_USER apps/emqx/etc/certs/key.pem /var/lib/postgresql/server.key
|
||||
COPY --chown=$POSTGRES_USER apps/emqx/etc/certs/cert.pem /var/lib/postgresql/server.crt
|
||||
COPY --chown=$POSTGRES_USER apps/emqx/etc/certs/cacert.pem /var/lib/postgresql/root.crt
|
||||
RUN chmod 600 /var/lib/postgresql/pg_hba.conf
|
||||
RUN chmod 600 /var/lib/postgresql/server.key
|
||||
RUN chmod 600 /var/lib/postgresql/server.crt
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
daemonize yes
|
||||
bind 0.0.0.0 ::
|
||||
logfile /var/log/redis-server.log
|
||||
tls-cert-file /tls/redis.crt
|
||||
tls-key-file /tls/redis.key
|
||||
tls-ca-cert-file /tls/ca.crt
|
||||
tls-cert-file /etc/certs/redis.crt
|
||||
tls-key-file /etc/certs/redis.key
|
||||
tls-ca-cert-file /etc/certs/ca.crt
|
||||
tls-replication yes
|
||||
tls-cluster yes
|
||||
protected-mode no
|
||||
|
|
|
@ -91,7 +91,7 @@ do
|
|||
fi
|
||||
if [ "${node}" = "cluster" ] ; then
|
||||
if $tls ; then
|
||||
yes "yes" | redis-cli --cluster create "$LOCAL_IP:8000" "$LOCAL_IP:8001" "$LOCAL_IP:8002" --pass public --no-auth-warning --tls true --cacert /tls/ca.crt --cert /tls/redis.crt --key /tls/redis.key;
|
||||
yes "yes" | redis-cli --cluster create "$LOCAL_IP:8000" "$LOCAL_IP:8001" "$LOCAL_IP:8002" --pass public --no-auth-warning --tls true --cacert /etc/certs/ca.crt --cert /etc/certs/redis.crt --key /etc/certs/redis.key;
|
||||
else
|
||||
yes "yes" | redis-cli --cluster create "$LOCAL_IP:7000" "$LOCAL_IP:7001" "$LOCAL_IP:7002" --pass public --no-auth-warning;
|
||||
fi
|
||||
|
@ -107,9 +107,9 @@ EOF
|
|||
cat >>/_sentinel.conf<<EOF
|
||||
tls-port 26380
|
||||
tls-replication yes
|
||||
tls-cert-file /tls/redis.crt
|
||||
tls-key-file /tls/redis.key
|
||||
tls-ca-cert-file /tls/ca.crt
|
||||
tls-cert-file /etc/certs/redis.crt
|
||||
tls-key-file /etc/certs/redis.key
|
||||
tls-ca-cert-file /etc/certs/ca.crt
|
||||
sentinel monitor mymaster $LOCAL_IP 8000 1
|
||||
EOF
|
||||
else
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{erl_opts, [debug_info]}.
|
||||
{deps,
|
||||
[
|
||||
{minirest, {git, "https://github.com/emqx/minirest.git", {tag, "0.3.5"}}}
|
||||
{minirest, {git, "https://github.com/emqx/minirest.git", {tag, "0.3.6"}}}
|
||||
]}.
|
||||
|
||||
{shell, [
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
!sed -i '/emqx_telemetry/d' data/loaded_plugins
|
||||
|
||||
!./bin/emqx start
|
||||
?EMQ X (.*) is started successfully!
|
||||
?EMQ X .* is started successfully!
|
||||
?SH-PROMPT
|
||||
|
||||
!./bin/emqx_ctl cluster join emqx@127.0.0.1
|
||||
|
@ -99,6 +99,10 @@
|
|||
"""
|
||||
?SH-PROMPT
|
||||
|
||||
!./bin/emqx_ctl plugins list | grep emqx_management
|
||||
?Plugin\(emqx_management.*active=true\)
|
||||
?SH-PROMPT
|
||||
|
||||
[shell emqx2]
|
||||
!echo "" > log/emqx.log.1
|
||||
?SH-PROMPT
|
||||
|
@ -120,6 +124,10 @@
|
|||
"""
|
||||
?SH-PROMPT
|
||||
|
||||
!./bin/emqx_ctl plugins list | grep emqx_management
|
||||
?Plugin\(emqx_management.*active=true\)
|
||||
?SH-PROMPT
|
||||
|
||||
[shell bench]
|
||||
???publish complete
|
||||
??SH-PROMPT:
|
||||
|
|
|
@ -3,7 +3,6 @@ name: Bug Report
|
|||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: Support
|
||||
assignees: tigercl
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ name: Feature Request
|
|||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: Feature
|
||||
assignees: tigercl
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ name: Support Needed
|
|||
about: Asking a question about usages, docs or anything you're insterested in
|
||||
title: ''
|
||||
labels: Support
|
||||
assignees: tigercl
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -83,6 +83,7 @@ jobs:
|
|||
- name: build
|
||||
env:
|
||||
PYTHON: python
|
||||
DIAGNOSTIC: 1
|
||||
run: |
|
||||
$env:PATH = "${{ steps.install_erlang.outputs.erlpath }}\bin;$env:PATH"
|
||||
|
||||
|
@ -168,19 +169,21 @@ jobs:
|
|||
- name: build
|
||||
run: |
|
||||
. $HOME/.kerl/${{ matrix.erl_otp }}/activate
|
||||
make -C source ensure-rebar3
|
||||
sudo cp source/rebar3 /usr/local/bin/rebar3
|
||||
make -C source ${{ matrix.profile }}-zip
|
||||
cd source
|
||||
make ensure-rebar3
|
||||
sudo cp rebar3 /usr/local/bin/rebar3
|
||||
rm -rf _build/${{ matrix.profile }}/lib
|
||||
make ${{ matrix.profile }}-zip
|
||||
- name: test
|
||||
run: |
|
||||
cd source
|
||||
pkg_name=$(basename _packages/${{ matrix.profile }}/${{ matrix.profile }}-*.zip)
|
||||
unzip -q _packages/${{ matrix.profile }}/$pkg_name
|
||||
gsed -i '/emqx_telemetry/d' ./emqx/data/loaded_plugins
|
||||
# gsed -i '/emqx_telemetry/d' ./emqx/data/loaded_plugins
|
||||
./emqx/bin/emqx start || cat emqx/log/erlang.log.1
|
||||
ready='no'
|
||||
for i in {1..10}; do
|
||||
if curl -fs 127.0.0.1:18083 > /dev/null; then
|
||||
if curl -fs 127.0.0.1:8081/status > /dev/null; then
|
||||
ready='yes'
|
||||
break
|
||||
fi
|
||||
|
@ -339,13 +342,6 @@ jobs:
|
|||
- [amd64, x86_64]
|
||||
- [arm64v8, aarch64]
|
||||
- [arm32v7, arm]
|
||||
- [i386, i386]
|
||||
- [s390x, s390x]
|
||||
exclude:
|
||||
- profile: emqx-ee
|
||||
arch: [i386, i386]
|
||||
- profile: emqx-ee
|
||||
arch: [s390x, s390x]
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v2
|
||||
|
|
|
@ -38,6 +38,11 @@ jobs:
|
|||
run: make ${EMQX_NAME}-zip
|
||||
- name: build deb/rpm packages
|
||||
run: make ${EMQX_NAME}-pkg
|
||||
- uses: actions/upload-artifact@v1
|
||||
if: failure()
|
||||
with:
|
||||
name: rebar3.crashdump
|
||||
path: ./rebar3.crashdump
|
||||
- name: pakcages test
|
||||
run: |
|
||||
export CODE_PATH=$GITHUB_WORKSPACE
|
||||
|
@ -94,15 +99,20 @@ jobs:
|
|||
make ensure-rebar3
|
||||
sudo cp rebar3 /usr/local/bin/rebar3
|
||||
make ${EMQX_NAME}-zip
|
||||
- uses: actions/upload-artifact@v1
|
||||
if: failure()
|
||||
with:
|
||||
name: rebar3.crashdump
|
||||
path: ./rebar3.crashdump
|
||||
- name: test
|
||||
run: |
|
||||
pkg_name=$(basename _packages/${EMQX_NAME}/emqx-*.zip)
|
||||
unzip -q _packages/${EMQX_NAME}/$pkg_name
|
||||
gsed -i '/emqx_telemetry/d' ./emqx/data/loaded_plugins
|
||||
# gsed -i '/emqx_telemetry/d' ./emqx/data/loaded_plugins
|
||||
./emqx/bin/emqx start || cat emqx/log/erlang.log.1
|
||||
ready='no'
|
||||
for i in {1..10}; do
|
||||
if curl -fs 127.0.0.1:18083 > /dev/null; then
|
||||
if curl -fs 127.0.0.1:8081/status > /dev/null; then
|
||||
ready='yes'
|
||||
break
|
||||
fi
|
||||
|
|
|
@ -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";
|
||||
sleep 5;
|
||||
done
|
||||
- name: verify EMQX_LOADED_PLUGINS override working
|
||||
run: |
|
||||
expected="{emqx_sn, true}."
|
||||
output=$(docker exec -i node1.emqx.io bash -c "cat data/loaded_plugins" | tail -n1)
|
||||
if [ "$expected" != "$output" ]; then
|
||||
exit 1
|
||||
fi
|
||||
# - name: verify EMQX_LOADED_PLUGINS override working
|
||||
# run: |
|
||||
# expected="{emqx_sn, true}."
|
||||
# output=$(docker exec -i node1.emqx.io bash -c "cat data/loaded_plugins" | tail -n1)
|
||||
# if [ "$expected" != "$output" ]; then
|
||||
# exit 1
|
||||
# fi
|
||||
- name: make paho tests
|
||||
run: |
|
||||
if ! docker exec -i python /scripts/pytest.sh; then
|
||||
|
@ -131,11 +131,27 @@ jobs:
|
|||
echo "waiting emqx started";
|
||||
sleep 10;
|
||||
done
|
||||
- name: get pods log
|
||||
- name: get emqx-0 pods log
|
||||
if: failure()
|
||||
env:
|
||||
KUBECONFIG: "/etc/rancher/k3s/k3s.yaml"
|
||||
run: kubectl describe pods emqx-0
|
||||
run: |
|
||||
kubectl describe pods emqx-0
|
||||
kubectl logs emqx-0
|
||||
- name: get emqx-1 pods log
|
||||
if: failure()
|
||||
env:
|
||||
KUBECONFIG: "/etc/rancher/k3s/k3s.yaml"
|
||||
run: |
|
||||
kubectl describe pods emqx-1
|
||||
kubectl logs emqx-1
|
||||
- name: get emqx-2 pods log
|
||||
if: failure()
|
||||
env:
|
||||
KUBECONFIG: "/etc/rancher/k3s/k3s.yaml"
|
||||
run: |
|
||||
kubectl describe pods emqx-2
|
||||
kubectl logs emqx-2
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: emqx/paho.mqtt.testing
|
||||
|
|
|
@ -56,64 +56,21 @@ jobs:
|
|||
- name: docker compose up
|
||||
if: env.EDITION == 'opensource'
|
||||
env:
|
||||
MYSQL_TAG: 8
|
||||
REDIS_TAG: 6
|
||||
MONGO_TAG: 4
|
||||
PGSQL_TAG: 13
|
||||
LDAP_TAG: 2.4.50
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
docker-compose \
|
||||
-f .ci/docker-compose-file/docker-compose.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-ldap-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-mongo-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-mysql-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-pgsql-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-redis-single-tcp.yaml \
|
||||
up -d --build
|
||||
- name: docker compose up
|
||||
if: env.EDITION == 'enterprise'
|
||||
env:
|
||||
MYSQL_TAG: 8
|
||||
REDIS_TAG: 6
|
||||
MONGO_TAG: 4
|
||||
PGSQL_TAG: 13
|
||||
LDAP_TAG: 2.4.50
|
||||
OPENTSDB_TAG: latest
|
||||
INFLUXDB_TAG: 1.7.6
|
||||
DYNAMODB_TAG: 1.11.477
|
||||
TIMESCALE_TAG: latest-pg11
|
||||
CASSANDRA_TAG: 3.11.6
|
||||
RABBITMQ_TAG: 3.7
|
||||
KAFKA_TAG: 2.5.0
|
||||
PULSAR_TAG: 2.3.2
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
docker-compose \
|
||||
-f .ci/docker-compose-file/docker-compose.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-ldap-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-mongo-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-mysql-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-pgsql-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-redis-single-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-enterprise.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-enterprise-cassandra-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-enterprise-dynamodb-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-enterprise-influxdb-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-enterprise-kafka-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-enterprise-opentsdb-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-enterprise-pulsar-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-enterprise-rabbit-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-enterprise-timescale-tcp.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-enterprise-mysql-client.yaml \
|
||||
-f .ci/docker-compose-file/docker-compose-enterprise-pgsql-and-timescale-client.yaml \
|
||||
up -d --build
|
||||
docker exec -i erlang bash -c "echo \"https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com\" > /root/.git-credentials && git config --global credential.helper store"
|
||||
while [ $(docker ps -a --filter name=client --filter exited=0 | wc -l) \
|
||||
!= $(docker ps -a --filter name=client | wc -l) ]; do
|
||||
sleep 5
|
||||
done
|
||||
- name: run eunit
|
||||
run: |
|
||||
docker exec -i erlang bash -c "make eunit"
|
||||
|
|
|
@ -45,6 +45,6 @@ emqx_dialyzer_*_plt
|
|||
*/emqx_dashboard/priv/www
|
||||
dist.zip
|
||||
scripts/git-token
|
||||
etc/*.seg
|
||||
apps/*/etc/*.all
|
||||
_upgrade_base/
|
||||
TAGS
|
||||
|
|
8
Makefile
8
Makefile
|
@ -73,7 +73,8 @@ coveralls: $(REBAR)
|
|||
@ENABLE_COVER_COMPILE=1 $(REBAR) as test coveralls send
|
||||
|
||||
.PHONY: $(REL_PROFILES)
|
||||
$(REL_PROFILES:%=%): $(REBAR) get-dashboard
|
||||
|
||||
$(REL_PROFILES:%=%): $(REBAR) get-dashboard conf-segs
|
||||
@$(REBAR) as $(@) do compile,release
|
||||
|
||||
## Not calling rebar3 clean because
|
||||
|
@ -111,7 +112,7 @@ xref: $(REBAR)
|
|||
dialyzer: $(REBAR)
|
||||
@$(REBAR) as check dialyzer
|
||||
|
||||
COMMON_DEPS := $(REBAR) get-dashboard $(CONF_SEGS)
|
||||
COMMON_DEPS := $(REBAR) get-dashboard conf-segs
|
||||
|
||||
## rel target is to create release package without relup
|
||||
.PHONY: $(REL_PROFILES:%=%-rel) $(PKG_PROFILES:%=%-rel)
|
||||
|
@ -152,3 +153,6 @@ quickrun:
|
|||
./_build/$(PROFILE)/rel/emqx/bin/emqx console
|
||||
|
||||
include docker.mk
|
||||
|
||||
conf-segs:
|
||||
@scripts/merge-config.escript
|
||||
|
|
|
@ -101,7 +101,7 @@ make dialyzer
|
|||
|
||||
##### 要分析特定的应用程序,(用逗号分隔的应用程序列表)
|
||||
```
|
||||
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
|
||||
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_authz make dialyzer
|
||||
```
|
||||
|
||||
## 社区
|
||||
|
@ -145,7 +145,7 @@ DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
|
|||
|
||||
[MQTT Version 5.0](https://docs.oasis-open.org/mqtt/mqtt/v5.0/cs02/mqtt-v5.0-cs02.html)
|
||||
|
||||
[MQTT SN](http://mqtt.org/new/wp-content/uploads/2009/06/MQTT-SN_spec_v1.2.pdf)
|
||||
[MQTT SN](https://www.oasis-open.org/committees/download.php/66091/MQTT-SN_spec_v1.2.pdf)
|
||||
|
||||
## 开源许可
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ make dialyzer
|
|||
|
||||
##### 特定のアプリケーションのみ解析する(アプリケーション名をコンマ区切りで入力)
|
||||
```
|
||||
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
|
||||
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_authz make dialyzer
|
||||
```
|
||||
|
||||
## コミュニティ
|
||||
|
@ -125,7 +125,7 @@ DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
|
|||
|
||||
[MQTT Version 5.0](https://docs.oasis-open.org/mqtt/mqtt/v5.0/cs02/mqtt-v5.0-cs02.html)
|
||||
|
||||
[MQTT SN](http://mqtt.org/new/wp-content/uploads/2009/06/MQTT-SN_spec_v1.2.pdf)
|
||||
[MQTT SN](https://www.oasis-open.org/committees/download.php/66091/MQTT-SN_spec_v1.2.pdf)
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ make dialyzer
|
|||
|
||||
##### Статический анализ части приложений (список через запятую)
|
||||
```
|
||||
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
|
||||
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_authz make dialyzer
|
||||
```
|
||||
|
||||
## Сообщество
|
||||
|
@ -135,7 +135,7 @@ DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
|
|||
|
||||
[MQTT Version 5.0](https://docs.oasis-open.org/mqtt/mqtt/v5.0/cs02/mqtt-v5.0-cs02.html)
|
||||
|
||||
[MQTT SN](http://mqtt.org/new/wp-content/uploads/2009/06/MQTT-SN_spec_v1.2.pdf)
|
||||
[MQTT SN](https://www.oasis-open.org/committees/download.php/66091/MQTT-SN_spec_v1.2.pdf)
|
||||
|
||||
## Лицензия
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ make dialyzer
|
|||
|
||||
##### To Analyse specific apps, (list of comma separated apps)
|
||||
```
|
||||
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
|
||||
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_authz make dialyzer
|
||||
```
|
||||
|
||||
## Community
|
||||
|
@ -134,7 +134,7 @@ You can read the mqtt protocol via the following links:
|
|||
|
||||
[MQTT Version 5.0](https://docs.oasis-open.org/mqtt/mqtt/v5.0/cs02/mqtt-v5.0-cs02.html)
|
||||
|
||||
[MQTT SN](http://mqtt.org/new/wp-content/uploads/2009/06/MQTT-SN_spec_v1.2.pdf)
|
||||
[MQTT SN](https://www.oasis-open.org/committees/download.php/66091/MQTT-SN_spec_v1.2.pdf)
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -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
|
||||
namespace: default
|
||||
}
|
||||
|
||||
db_backend: mnesia
|
||||
|
||||
rlog: {
|
||||
# role: core
|
||||
# core_nodes: []
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
##==================================================================
|
||||
|
@ -1259,8 +1267,9 @@ zones.default {
|
|||
##
|
||||
## @doc zones.<name>.listeners.<name>.type
|
||||
## ValueType: tcp | ws
|
||||
## - tcp: MQTT over TCP
|
||||
## - ws: MQTT over Websocket
|
||||
## - tcp: MQTT over TCP
|
||||
## - ws: MQTT over Websocket
|
||||
## - quic: MQTT over QUIC
|
||||
## Required: true
|
||||
type: tcp
|
||||
|
||||
|
@ -1390,8 +1399,9 @@ zones.default {
|
|||
##
|
||||
## @doc zones.<name>.listeners.<name>.type
|
||||
## ValueType: tcp | ws
|
||||
## - tcp: MQTT over TCP
|
||||
## - ws: MQTT over Websocket
|
||||
## - tcp: MQTT over TCP
|
||||
## - ws: MQTT over Websocket
|
||||
## - quic: MQTT over QUIC
|
||||
## Required: true
|
||||
type: tcp
|
||||
|
||||
|
@ -1520,6 +1530,51 @@ zones.default {
|
|||
tcp.buffer: 4KB
|
||||
}
|
||||
|
||||
listeners.mqtt_quic:
|
||||
#${example_common_ssl_options} # common options can be written in a separate config entry and reference it from here.
|
||||
{
|
||||
|
||||
## The type of the listener.
|
||||
##
|
||||
## @doc zones.<name>.listeners.<name>.type
|
||||
## ValueType: tcp | ws
|
||||
## - tcp: MQTT over TCP
|
||||
## - ws: MQTT over Websocket
|
||||
## - quic: MQTT over QUIC
|
||||
## Required: true
|
||||
type: quic
|
||||
|
||||
## The IP address and port that the listener will bind.
|
||||
##
|
||||
## @doc zones.<name>.listeners.<name>.bind
|
||||
## ValueType: IPAddress | Port | IPAddrPort
|
||||
## Required: true
|
||||
## Examples: 14567, 127.0.0.1:14567, ::1:14567
|
||||
bind: "0.0.0.0:14567"
|
||||
|
||||
## The size of the acceptor pool for this listener.
|
||||
##
|
||||
## @doc zones.<name>.listeners.<name>.acceptors
|
||||
## ValueType: Number
|
||||
## Default: 16
|
||||
acceptors: 16
|
||||
|
||||
## Maximum number of concurrent connections.
|
||||
##
|
||||
## @doc zones.<name>.listeners.<name>.max_connections
|
||||
## ValueType: Number | infinity
|
||||
## Default: infinity
|
||||
max_connections: 1024000
|
||||
|
||||
## SSL options
|
||||
## See ${example_common_ssl_options} for more information
|
||||
ssl.enable: false
|
||||
#ssl.versions: ["tlsv1.3", "tlsv1.2", "tlsv1.1", "tlsv1"]
|
||||
#ssl.keyfile: "{{ platform_etc_dir }}/certs/key.pem"
|
||||
#ssl.certfile: "{{ platform_etc_dir }}/certs/cert.pem"
|
||||
#ssl.cacertfile: "{{ platform_etc_dir }}/certs/cacert.pem"
|
||||
}
|
||||
|
||||
listeners.mqtt_ws:
|
||||
#${example_common_tcp_options} ${example_common_websocket_options} # common options can be written in a separate config entry and reference it from here.
|
||||
{
|
||||
|
@ -1528,8 +1583,9 @@ zones.default {
|
|||
##
|
||||
## @doc zones.<name>.listeners.<name>.type
|
||||
## ValueType: tcp | ws
|
||||
## - tcp: MQTT over TCP
|
||||
## - ws: MQTT over Websocket
|
||||
## - tcp: MQTT over TCP
|
||||
## - ws: MQTT over Websocket
|
||||
## - quic: MQTT over QUIC
|
||||
## Required: true
|
||||
type: ws
|
||||
|
||||
|
@ -1662,8 +1718,9 @@ zones.default {
|
|||
##
|
||||
## @doc zones.<name>.listeners.<name>.type
|
||||
## ValueType: tcp | ws
|
||||
## - tcp: MQTT over TCP
|
||||
## - ws: MQTT over Websocket
|
||||
## - tcp: MQTT over TCP
|
||||
## - ws: MQTT over Websocket
|
||||
## - quic: MQTT over QUIC
|
||||
## Required: true
|
||||
type: ws
|
||||
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
|
||||
-define(Otherwise, true).
|
||||
|
||||
-define(COMMON_SHARD, emqx_common_shard).
|
||||
-define(SHARED_SUB_SHARD, emqx_shared_sub_shard).
|
||||
-define(MOD_DELAYED_SHARD, emqx_delayed_shard).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Banner
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -86,6 +90,9 @@
|
|||
|
||||
-define(ROUTE_SHARD, route_shard).
|
||||
|
||||
|
||||
-define(RULE_ENGINE_SHARD, emqx_rule_engine_shard).
|
||||
|
||||
-record(route, {
|
||||
topic :: binary(),
|
||||
dest :: node() | {binary(), node()}
|
||||
|
@ -101,8 +108,7 @@
|
|||
descr :: string(),
|
||||
vendor :: string() | undefined,
|
||||
active = false :: boolean(),
|
||||
info = #{} :: map(),
|
||||
type :: atom()
|
||||
info = #{} :: map()
|
||||
}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -134,4 +140,3 @@
|
|||
}).
|
||||
|
||||
-endif.
|
||||
|
||||
|
|
|
@ -30,11 +30,13 @@
|
|||
%% MQTT Protocol Version and Names
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-define(MQTT_SN_PROTO_V1, 1).
|
||||
-define(MQTT_PROTO_V3, 3).
|
||||
-define(MQTT_PROTO_V4, 4).
|
||||
-define(MQTT_PROTO_V5, 5).
|
||||
|
||||
-define(PROTOCOL_NAMES, [
|
||||
{?MQTT_SN_PROTO_V1, <<"MQTT-SN">>}, %% XXX:Compatible with emqx-sn plug-in
|
||||
{?MQTT_PROTO_V3, <<"MQIsdp">>},
|
||||
{?MQTT_PROTO_V4, <<"MQTT">>},
|
||||
{?MQTT_PROTO_V5, <<"MQTT">>}]).
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
-ifndef(EMQX_ENTERPRISE).
|
||||
|
||||
-define(EMQX_RELEASE, {opensource, "5.0-pre"}).
|
||||
-define(EMQX_RELEASE, {opensource, "5.0-alpha.1"}).
|
||||
|
||||
-else.
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {branch, "2.0.4"}}}
|
||||
, {recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}}
|
||||
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.13.0"}}}
|
||||
, {quicer, {git, "https://github.com/emqx/quic.git", {tag, "0.0.5"}}}
|
||||
]}.
|
||||
|
||||
{plugins, [rebar3_proper]}.
|
||||
|
@ -30,7 +31,7 @@
|
|||
[ meck
|
||||
, {bbmustache,"1.10.0"}
|
||||
, {emqx_ct_helpers, {git,"https://github.com/emqx/emqx-ct-helpers", {branch,"hocon"}}}
|
||||
, {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.2.3.1"}}}
|
||||
, {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.4.1"}}}
|
||||
]},
|
||||
{extra_src_dirs, [{"test",[recursive]}]}
|
||||
]}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
{vsn, "5.0.0"}, % strict semver, bump manually!
|
||||
{modules, []},
|
||||
{registered, []},
|
||||
{applications, [kernel,stdlib,gproc,gen_rpc,esockd,cowboy,sasl,os_mon]},
|
||||
{applications, [kernel,stdlib,gproc,gen_rpc,esockd,cowboy,sasl,os_mon,quicer,jiffy]},
|
||||
{mod, {emqx_app,[]}},
|
||||
{env, []},
|
||||
{licenses, ["Apache-2.0"]},
|
||||
|
|
|
@ -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) ->
|
||||
?LOG(critical, "emqx shutdown for ~s", [Reason]),
|
||||
_ = emqx_alarm_handler:unload(),
|
||||
_ = emqx_plugins:unload(),
|
||||
lists:foreach(fun application:stop/1
|
||||
, lists:reverse(default_started_applications())
|
||||
).
|
||||
|
@ -237,10 +236,10 @@ reboot() ->
|
|||
|
||||
-ifdef(EMQX_ENTERPRISE).
|
||||
default_started_applications() ->
|
||||
[gproc, esockd, ranch, cowboy, ekka, emqx].
|
||||
[gproc, esockd, ranch, cowboy, ekka, quicer, emqx].
|
||||
-else.
|
||||
default_started_applications() ->
|
||||
[gproc, esockd, ranch, cowboy, ekka, emqx, emqx_modules].
|
||||
[gproc, esockd, ranch, cowboy, ekka, quicer, emqx] ++ emqx_feature().
|
||||
-endif.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -253,3 +252,16 @@ reload_config(ConfFile) ->
|
|||
[application:set_env(App, Par, Val) || {Par, Val} <- Vals]
|
||||
end, Conf).
|
||||
|
||||
|
||||
emqx_feature() ->
|
||||
[ emqx_resource
|
||||
, emqx_authn
|
||||
, emqx_authz
|
||||
, emqx_gateway
|
||||
, emqx_data_bridge
|
||||
, emqx_rule_engine
|
||||
, emqx_bridge_mqtt
|
||||
, emqx_modules
|
||||
, emqx_management
|
||||
, emqx_retainer
|
||||
, emqx_statsd].
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
-export([authenticate/1]).
|
||||
|
||||
-export([ check_acl/3
|
||||
-export([ authorize/3
|
||||
]).
|
||||
|
||||
-type(result() :: #{auth_result := emqx_types:auth_result(),
|
||||
|
@ -37,25 +37,25 @@ authenticate(ClientInfo = #{zone := Zone, listener := Listener}) ->
|
|||
return_auth_result(run_hooks('client.authenticate', [ClientInfo], AuthResult)).
|
||||
|
||||
%% @doc Check ACL
|
||||
-spec(check_acl(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic())
|
||||
-spec(authorize(emqx_types:clientinfo(), emqx_types:pubsub(), emqx_types:topic())
|
||||
-> allow | deny).
|
||||
check_acl(ClientInfo = #{zone := Zone, listener := Listener}, PubSub, Topic) ->
|
||||
authorize(ClientInfo = #{zone := Zone, listener := Listener}, PubSub, Topic) ->
|
||||
case emqx_acl_cache:is_enabled(Zone, Listener) of
|
||||
true -> check_acl_cache(ClientInfo, PubSub, Topic);
|
||||
false -> do_check_acl(ClientInfo, PubSub, Topic)
|
||||
true -> check_authorization_cache(ClientInfo, PubSub, Topic);
|
||||
false -> do_authorize(ClientInfo, PubSub, Topic)
|
||||
end.
|
||||
|
||||
check_acl_cache(ClientInfo = #{zone := Zone, listener := Listener}, PubSub, Topic) ->
|
||||
check_authorization_cache(ClientInfo = #{zone := Zone, listener := Listener}, PubSub, Topic) ->
|
||||
case emqx_acl_cache:get_acl_cache(Zone, Listener, PubSub, Topic) of
|
||||
not_found ->
|
||||
AclResult = do_check_acl(ClientInfo, PubSub, Topic),
|
||||
AclResult = do_authorize(ClientInfo, PubSub, Topic),
|
||||
emqx_acl_cache:put_acl_cache(Zone, Listener, PubSub, Topic, AclResult),
|
||||
AclResult;
|
||||
AclResult -> AclResult
|
||||
end.
|
||||
|
||||
do_check_acl(ClientInfo, PubSub, Topic) ->
|
||||
case run_hooks('client.check_acl', [ClientInfo, PubSub, Topic], allow) of
|
||||
do_authorize(ClientInfo, PubSub, Topic) ->
|
||||
case run_hooks('client.authorize', [ClientInfo, PubSub, Topic], allow) of
|
||||
allow -> allow;
|
||||
_Other -> deny
|
||||
end.
|
||||
|
|
|
@ -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).
|
||||
|
||||
-rlog_shard({?COMMON_SHARD, ?ACTIVATED_ALARM}).
|
||||
-rlog_shard({?COMMON_SHARD, ?DEACTIVATED_ALARM}).
|
||||
|
||||
-ifdef(TEST).
|
||||
-compile(export_all).
|
||||
-compile(nowarn_export_all).
|
||||
|
@ -167,7 +170,7 @@ handle_call({activate_alarm, Name, Details}, _From, State) ->
|
|||
details = Details,
|
||||
message = normalize_message(Name, Details),
|
||||
activate_at = erlang:system_time(microsecond)},
|
||||
mnesia:dirty_write(?ACTIVATED_ALARM, Alarm),
|
||||
ekka_mnesia:dirty_write(?ACTIVATED_ALARM, Alarm),
|
||||
do_actions(activate, Alarm, emqx_config:get([alarm, actions])),
|
||||
{reply, ok, State}
|
||||
end;
|
||||
|
@ -186,9 +189,14 @@ handle_call(delete_all_deactivated_alarms, _From, State) ->
|
|||
{reply, ok, State};
|
||||
|
||||
handle_call({get_alarms, all}, _From, State) ->
|
||||
Alarms = [normalize(Alarm) ||
|
||||
Alarm <- ets:tab2list(?ACTIVATED_ALARM)
|
||||
++ ets:tab2list(?DEACTIVATED_ALARM)],
|
||||
{atomic, Alarms} =
|
||||
ekka_mnesia:ro_transaction(
|
||||
?COMMON_SHARD,
|
||||
fun() ->
|
||||
[normalize(Alarm) ||
|
||||
Alarm <- ets:tab2list(?ACTIVATED_ALARM)
|
||||
++ ets:tab2list(?DEACTIVATED_ALARM)]
|
||||
end),
|
||||
{reply, Alarms, State};
|
||||
|
||||
handle_call({get_alarms, activated}, _From, State) ->
|
||||
|
@ -235,7 +243,7 @@ deactivate_alarm(Details, #activated_alarm{activate_at = ActivateAt, name = Name
|
|||
case mnesia:dirty_first(?DEACTIVATED_ALARM) of
|
||||
'$end_of_table' -> ok;
|
||||
ActivateAt2 ->
|
||||
mnesia:dirty_delete(?DEACTIVATED_ALARM, ActivateAt2)
|
||||
ekka_mnesia:dirty_delete(?DEACTIVATED_ALARM, ActivateAt2)
|
||||
end;
|
||||
false -> ok
|
||||
end,
|
||||
|
@ -244,8 +252,8 @@ deactivate_alarm(Details, #activated_alarm{activate_at = ActivateAt, name = Name
|
|||
DeActAlarm = make_deactivated_alarm(ActivateAt, Name, Details,
|
||||
normalize_message(Name, Details),
|
||||
erlang:system_time(microsecond)),
|
||||
mnesia:dirty_write(?DEACTIVATED_ALARM, HistoryAlarm),
|
||||
mnesia:dirty_delete(?ACTIVATED_ALARM, Name),
|
||||
ekka_mnesia:dirty_write(?DEACTIVATED_ALARM, HistoryAlarm),
|
||||
ekka_mnesia:dirty_delete(?ACTIVATED_ALARM, Name),
|
||||
do_actions(deactivate, DeActAlarm, emqx_config:get([alarm, actions])).
|
||||
|
||||
make_deactivated_alarm(ActivateAt, Name, Details, Message, DeActivateAt) ->
|
||||
|
@ -262,7 +270,7 @@ deactivate_all_alarms() ->
|
|||
details = Details,
|
||||
message = Message,
|
||||
activate_at = ActivateAt}) ->
|
||||
mnesia:dirty_write(?DEACTIVATED_ALARM,
|
||||
ekka_mnesia:dirty_write(?DEACTIVATED_ALARM,
|
||||
#deactivated_alarm{
|
||||
activate_at = ActivateAt,
|
||||
name = Name,
|
||||
|
@ -274,7 +282,7 @@ deactivate_all_alarms() ->
|
|||
|
||||
%% Delete all records from the given table, ignore result.
|
||||
clear_table(TableName) ->
|
||||
case mnesia:clear_table(TableName) of
|
||||
case ekka_mnesia:clear_table(TableName) of
|
||||
{aborted, Reason} ->
|
||||
?LOG(warning, "Faile to clear table ~p reason: ~p",
|
||||
[TableName, Reason]);
|
||||
|
@ -294,7 +302,7 @@ delete_expired_deactivated_alarms('$end_of_table', _Checkpoint) ->
|
|||
delete_expired_deactivated_alarms(ActivatedAt, Checkpoint) ->
|
||||
case ActivatedAt =< Checkpoint of
|
||||
true ->
|
||||
mnesia:dirty_delete(?DEACTIVATED_ALARM, ActivatedAt),
|
||||
ekka_mnesia:dirty_delete(?DEACTIVATED_ALARM, ActivatedAt),
|
||||
NActivatedAt = mnesia:dirty_next(?DEACTIVATED_ALARM, ActivatedAt),
|
||||
delete_expired_deactivated_alarms(NActivatedAt, Checkpoint);
|
||||
false ->
|
||||
|
|
|
@ -28,7 +28,12 @@
|
|||
|
||||
-define(APP, emqx).
|
||||
|
||||
-define(EMQX_SHARDS, [route_shard]).
|
||||
-define(EMQX_SHARDS, [ ?ROUTE_SHARD
|
||||
, ?COMMON_SHARD
|
||||
, ?SHARED_SUB_SHARD
|
||||
, ?RULE_ENGINE_SHARD
|
||||
, ?MOD_DELAYED_SHARD
|
||||
]).
|
||||
|
||||
-include("emqx_release.hrl").
|
||||
|
||||
|
@ -46,7 +51,7 @@ start(_Type, _Args) ->
|
|||
ok = ekka_rlog:wait_for_shards(?EMQX_SHARDS, infinity),
|
||||
{ok, Sup} = emqx_sup:start_link(),
|
||||
ok = start_autocluster(),
|
||||
ok = emqx_plugins:init(),
|
||||
% ok = emqx_plugins:init(),
|
||||
_ = emqx_plugins:load(),
|
||||
_ = start_ce_modules(),
|
||||
emqx_boot:is_enabled(listeners) andalso (ok = emqx_listeners:start()),
|
||||
|
|
|
@ -51,6 +51,8 @@
|
|||
|
||||
-define(BANNED_TAB, ?MODULE).
|
||||
|
||||
-rlog_shard({?COMMON_SHARD, ?BANNED_TAB}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Mnesia bootstrap
|
||||
%%--------------------------------------------------------------------
|
||||
|
@ -96,19 +98,19 @@ create(#{who := Who,
|
|||
reason := Reason,
|
||||
at := At,
|
||||
until := Until}) ->
|
||||
mnesia:dirty_write(?BANNED_TAB, #banned{who = Who,
|
||||
by = By,
|
||||
reason = Reason,
|
||||
at = At,
|
||||
until = Until});
|
||||
ekka_mnesia:dirty_write(?BANNED_TAB, #banned{who = Who,
|
||||
by = By,
|
||||
reason = Reason,
|
||||
at = At,
|
||||
until = Until});
|
||||
create(Banned) when is_record(Banned, banned) ->
|
||||
mnesia:dirty_write(?BANNED_TAB, Banned).
|
||||
ekka_mnesia:dirty_write(?BANNED_TAB, Banned).
|
||||
|
||||
-spec(delete({clientid, emqx_types:clientid()}
|
||||
| {username, emqx_types:username()}
|
||||
| {peerhost, emqx_types:peerhost()}) -> ok).
|
||||
delete(Who) ->
|
||||
mnesia:dirty_delete(?BANNED_TAB, Who).
|
||||
ekka_mnesia:dirty_delete(?BANNED_TAB, Who).
|
||||
|
||||
info(InfoKey) ->
|
||||
mnesia:table_info(?BANNED_TAB, InfoKey).
|
||||
|
@ -129,7 +131,7 @@ handle_cast(Msg, State) ->
|
|||
{noreply, State}.
|
||||
|
||||
handle_info({timeout, TRef, expire}, State = #{expiry_timer := TRef}) ->
|
||||
mnesia:async_dirty(fun expire_banned_items/1, [erlang:system_time(second)]),
|
||||
ekka_mnesia:transaction(?COMMON_SHARD, fun expire_banned_items/1, [erlang:system_time(second)]),
|
||||
{noreply, ensure_expiry_timer(State), hibernate};
|
||||
|
||||
handle_info(Info, State) ->
|
||||
|
@ -160,4 +162,3 @@ expire_banned_items(Now) ->
|
|||
mnesia:delete_object(?BANNED_TAB, B, sticky_write);
|
||||
(_, _Acc) -> ok
|
||||
end, ok, ?BANNED_TAB).
|
||||
|
||||
|
|
|
@ -1420,7 +1420,7 @@ check_pub_alias(_Packet, _Channel) -> ok.
|
|||
check_pub_acl(#mqtt_packet{variable = #mqtt_packet_publish{topic_name = Topic}},
|
||||
#channel{clientinfo = ClientInfo}) ->
|
||||
case is_acl_enabled(ClientInfo) andalso
|
||||
emqx_access_control:check_acl(ClientInfo, publish, Topic) of
|
||||
emqx_access_control:authorize(ClientInfo, publish, Topic) of
|
||||
false -> ok;
|
||||
allow -> ok;
|
||||
deny -> {error, ?RC_NOT_AUTHORIZED}
|
||||
|
@ -1454,7 +1454,7 @@ check_sub_acls([], _Channel, Acc) ->
|
|||
|
||||
check_sub_acl(TopicFilter, #channel{clientinfo = ClientInfo}) ->
|
||||
case is_acl_enabled(ClientInfo) andalso
|
||||
emqx_access_control:check_acl(ClientInfo, subscribe, TopicFilter) of
|
||||
emqx_access_control:authorize(ClientInfo, subscribe, TopicFilter) of
|
||||
false -> allow;
|
||||
Result -> Result
|
||||
end.
|
||||
|
|
|
@ -294,6 +294,9 @@ do_discard_session(ClientId, Pid) ->
|
|||
_ : {noproc, _} -> % emqx_connection: gen_server:call
|
||||
?tp(debug, "session_already_gone", #{pid => Pid}),
|
||||
ok;
|
||||
_ : {'EXIT', {noproc, _}} -> % rpc_call/3
|
||||
?tp(debug, "session_already_gone", #{pid => Pid}),
|
||||
ok;
|
||||
_ : {{shutdown, _}, _} ->
|
||||
?tp(debug, "session_already_shutdown", #{pid => Pid}),
|
||||
ok;
|
||||
|
|
|
@ -48,7 +48,9 @@
|
|||
-define(TAB, emqx_channel_registry).
|
||||
-define(LOCK, {?MODULE, cleanup_down}).
|
||||
|
||||
-rlog_shard({?ROUTE_SHARD, ?TAB}).
|
||||
-define(CM_SHARD, emqx_cm_shard).
|
||||
|
||||
-rlog_shard({?CM_SHARD, ?TAB}).
|
||||
|
||||
-record(channel, {chid, pid}).
|
||||
|
||||
|
@ -111,6 +113,7 @@ init([]) ->
|
|||
{storage_properties, [{ets, [{read_concurrency, true},
|
||||
{write_concurrency, true}]}]}]),
|
||||
ok = ekka_mnesia:copy_table(?TAB, ram_copies),
|
||||
ok = ekka_rlog:wait_for_shards([?CM_SHARD], infinity),
|
||||
ok = ekka:monitor(membership),
|
||||
{ok, #{}}.
|
||||
|
||||
|
@ -125,7 +128,7 @@ handle_cast(Msg, State) ->
|
|||
handle_info({membership, {mnesia, down, Node}}, State) ->
|
||||
global:trans({?LOCK, self()},
|
||||
fun() ->
|
||||
ekka_mnesia:transaction(?ROUTE_SHARD, fun cleanup_channels/1, [Node])
|
||||
ekka_mnesia:transaction(?CM_SHARD, fun cleanup_channels/1, [Node])
|
||||
end),
|
||||
{noreply, State};
|
||||
|
||||
|
|
|
@ -120,4 +120,3 @@ put_raw(Config) ->
|
|||
-spec put_raw(emqx_map_lib:config_key_path(), term()) -> ok.
|
||||
put_raw(KeyPath, Config) ->
|
||||
put_raw(emqx_map_lib:deep_put(KeyPath, get_raw(), Config)).
|
||||
|
||||
|
|
|
@ -422,6 +422,13 @@ handle_msg({Inet, _Sock, Data}, State) when Inet == tcp; Inet == ssl ->
|
|||
ok = emqx_metrics:inc('bytes.received', Oct),
|
||||
parse_incoming(Data, State);
|
||||
|
||||
handle_msg({quic, Data, _Sock, _, _, _}, State) ->
|
||||
?LOG(debug, "RECV ~0p", [Data]),
|
||||
Oct = iolist_size(Data),
|
||||
inc_counter(incoming_bytes, Oct),
|
||||
ok = emqx_metrics:inc('bytes.received', Oct),
|
||||
parse_incoming(Data, State);
|
||||
|
||||
handle_msg({incoming, Packet = ?CONNECT_PACKET(ConnPkt)},
|
||||
State = #state{idle_timer = IdleTimer}) ->
|
||||
ok = emqx_misc:cancel_timer(IdleTimer),
|
||||
|
@ -446,7 +453,7 @@ handle_msg({Closed, _Sock}, State)
|
|||
handle_info({sock_closed, Closed}, close_socket(State));
|
||||
|
||||
handle_msg({Passive, _Sock}, State)
|
||||
when Passive == tcp_passive; Passive == ssl_passive ->
|
||||
when Passive == tcp_passive; Passive == ssl_passive; Passive =:= quic_passive ->
|
||||
%% In Stats
|
||||
Pubs = emqx_pd:reset_counter(incoming_pubs),
|
||||
Bytes = emqx_pd:reset_counter(incoming_bytes),
|
||||
|
@ -739,6 +746,15 @@ handle_info({sock_error, Reason}, State) ->
|
|||
end,
|
||||
handle_info({sock_closed, Reason}, close_socket(State));
|
||||
|
||||
handle_info({quic, peer_send_shutdown, _Stream}, State) ->
|
||||
handle_info({sock_closed, force}, close_socket(State));
|
||||
|
||||
handle_info({quic, closed, _Channel, ReasonFlag}, State) ->
|
||||
handle_info({sock_closed, ReasonFlag}, State);
|
||||
|
||||
handle_info({quic, closed, _Stream}, State) ->
|
||||
handle_info({sock_closed, force}, State);
|
||||
|
||||
handle_info(Info, State) ->
|
||||
with_channel(handle_info, [Info], State).
|
||||
|
||||
|
|
|
@ -79,7 +79,28 @@ do_start_listener(ZoneName, ListenerName, #{type := ws, bind := ListenOn} = Opts
|
|||
cowboy:start_clear(Id, RanchOpts, WsOpts);
|
||||
true ->
|
||||
cowboy:start_tls(Id, RanchOpts, WsOpts)
|
||||
end.
|
||||
end;
|
||||
|
||||
%% Start MQTT/QUIC listener
|
||||
do_start_listener(ZoneName, ListenerName, #{type := quic, bind := ListenOn} = Opts) ->
|
||||
%% @fixme unsure why we need reopen lib and reopen config.
|
||||
quicer_nif:open_lib(),
|
||||
quicer_nif:reg_open(),
|
||||
SSLOpts = ssl_opts(Opts),
|
||||
DefAcceptors = erlang:system_info(schedulers_online) * 8,
|
||||
ListenOpts = [ {cert, maps:get(certfile, SSLOpts, undefined)}
|
||||
, {key, maps:get(keyfile, SSLOpts, undefined)}
|
||||
, {alpn, ["mqtt"]}
|
||||
, {conn_acceptors, maps:get(acceptors, Opts, DefAcceptors)}
|
||||
, {idle_timeout_ms, emqx_config:get_listener_conf(ZoneName, ListenerName,
|
||||
[mqtt, idle_timeout])}
|
||||
],
|
||||
ConnectionOpts = #{conn_callback => emqx_quic_connection
|
||||
, peer_unidi_stream_count => 1
|
||||
, peer_bidi_stream_count => 10
|
||||
},
|
||||
StreamOpts = [],
|
||||
quicer:start_listener('mqtt:quic', ListenOn, {ListenOpts, ConnectionOpts, StreamOpts}).
|
||||
|
||||
esockd_opts(Opts0) ->
|
||||
Opts1 = maps:with([acceptors, max_connections, proxy_protocol, proxy_protocol_timeout], Opts0),
|
||||
|
|
|
@ -172,7 +172,7 @@
|
|||
{counter, 'client.connected'},
|
||||
{counter, 'client.authenticate'},
|
||||
{counter, 'client.auth.anonymous'},
|
||||
{counter, 'client.check_acl'},
|
||||
{counter, 'client.authorize'},
|
||||
{counter, 'client.subscribe'},
|
||||
{counter, 'client.unsubscribe'},
|
||||
{counter, 'client.disconnected'}
|
||||
|
@ -563,7 +563,7 @@ reserved_idx('client.connected') -> 202;
|
|||
reserved_idx('client.authenticate') -> 203;
|
||||
reserved_idx('client.enhanced_authenticate') -> 204;
|
||||
reserved_idx('client.auth.anonymous') -> 205;
|
||||
reserved_idx('client.check_acl') -> 206;
|
||||
reserved_idx('client.authorize') -> 206;
|
||||
reserved_idx('client.subscribe') -> 207;
|
||||
reserved_idx('client.unsubscribe') -> 208;
|
||||
reserved_idx('client.disconnected') -> 209;
|
||||
|
|
|
@ -21,8 +21,6 @@
|
|||
|
||||
-logger_header("[Plugins]").
|
||||
|
||||
-export([init/0]).
|
||||
|
||||
-export([ load/0
|
||||
, load/1
|
||||
, unload/0
|
||||
|
@ -39,35 +37,14 @@
|
|||
-compile(nowarn_export_all).
|
||||
-endif.
|
||||
|
||||
-dialyzer({no_match, [ plugin_loaded/2
|
||||
, plugin_unloaded/2
|
||||
]}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% APIs
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
%% @doc Init plugins' config
|
||||
-spec(init() -> ok).
|
||||
init() ->
|
||||
case emqx:get_env(plugins_etc_dir) of
|
||||
undefined -> ok;
|
||||
PluginsEtc ->
|
||||
CfgFiles = [filename:join(PluginsEtc, File) ||
|
||||
File <- filelib:wildcard("*.config", PluginsEtc)],
|
||||
lists:foreach(fun init_config/1, CfgFiles)
|
||||
end.
|
||||
|
||||
%% @doc Load all plugins when the broker started.
|
||||
-spec(load() -> ok | ignore | {error, term()}).
|
||||
load() ->
|
||||
ok = load_ext_plugins(emqx:get_env(expand_plugins_dir)),
|
||||
case emqx:get_env(plugins_loaded_file) of
|
||||
undefined -> ignore; %% No plugins available
|
||||
File ->
|
||||
_ = ensure_file(File),
|
||||
with_loaded_file(File, fun(Names) -> load_plugins(Names, false) end)
|
||||
end.
|
||||
ok = load_ext_plugins(emqx:get_env(expand_plugins_dir)).
|
||||
|
||||
%% @doc Load a Plugin
|
||||
-spec(load(atom()) -> ok | {error, term()}).
|
||||
|
@ -80,17 +57,13 @@ load(PluginName) when is_atom(PluginName) ->
|
|||
?LOG(notice, "Plugin ~s is already started", [PluginName]),
|
||||
{error, already_started};
|
||||
{_, false} ->
|
||||
load_plugin(PluginName, true)
|
||||
load_plugin(PluginName)
|
||||
end.
|
||||
|
||||
%% @doc Unload all plugins before broker stopped.
|
||||
-spec(unload() -> list() | {error, term()}).
|
||||
-spec(unload() -> ok).
|
||||
unload() ->
|
||||
case emqx:get_env(plugins_loaded_file) of
|
||||
undefined -> ignore;
|
||||
File ->
|
||||
with_loaded_file(File, fun stop_plugins/1)
|
||||
end.
|
||||
stop_plugins(list()).
|
||||
|
||||
%% @doc UnLoad a Plugin
|
||||
-spec(unload(atom()) -> ok | {error, term()}).
|
||||
|
@ -103,7 +76,7 @@ unload(PluginName) when is_atom(PluginName) ->
|
|||
?LOG(error, "Plugin ~s is not started", [PluginName]),
|
||||
{error, not_started};
|
||||
{_, _} ->
|
||||
unload_plugin(PluginName, true)
|
||||
unload_plugin(PluginName)
|
||||
end.
|
||||
|
||||
reload(PluginName) when is_atom(PluginName)->
|
||||
|
@ -124,8 +97,8 @@ reload(PluginName) when is_atom(PluginName)->
|
|||
-spec(list() -> [emqx_types:plugin()]).
|
||||
list() ->
|
||||
StartedApps = names(started_app),
|
||||
lists:map(fun({Name, _, [Type| _]}) ->
|
||||
Plugin = plugin(Name, Type),
|
||||
lists:map(fun({Name, _, _}) ->
|
||||
Plugin = plugin(Name),
|
||||
case lists:member(Name, StartedApps) of
|
||||
true -> Plugin#plugin{active = true};
|
||||
false -> Plugin
|
||||
|
@ -142,12 +115,6 @@ find_plugin(Name, Plugins) ->
|
|||
%% Internal functions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
init_config(CfgFile) ->
|
||||
{ok, [AppsEnv]} = file:consult(CfgFile),
|
||||
lists:foreach(fun({App, Envs}) ->
|
||||
[application:set_env(App, Par, Val) || {Par, Val} <- Envs]
|
||||
end, AppsEnv).
|
||||
|
||||
%% load external plugins which are placed in etc/plugins dir
|
||||
load_ext_plugins(undefined) -> ok;
|
||||
load_ext_plugins(Dir) ->
|
||||
|
@ -171,7 +138,15 @@ load_ext_plugin(PluginDir) ->
|
|||
?LOG(alert, "plugin_app_file_not_found: ~s", [AppFile]),
|
||||
error({plugin_app_file_not_found, AppFile})
|
||||
end,
|
||||
load_plugin_app(AppName, Ebin).
|
||||
ok = load_plugin_app(AppName, Ebin).
|
||||
% try
|
||||
% ok = generate_configs(AppName, PluginDir)
|
||||
% catch
|
||||
% throw : {conf_file_not_found, ConfFile} ->
|
||||
% %% this is maybe a dependency of an external plugin
|
||||
% ?LOG(debug, "config_load_error_ignored for app=~p, path=~s", [AppName, ConfFile]),
|
||||
% ok
|
||||
% end.
|
||||
|
||||
load_plugin_app(AppName, Ebin) ->
|
||||
_ = code:add_patha(Ebin),
|
||||
|
@ -189,56 +164,24 @@ load_plugin_app(AppName, Ebin) ->
|
|||
{error, {already_loaded, _}} -> ok
|
||||
end.
|
||||
|
||||
ensure_file(File) ->
|
||||
case filelib:is_file(File) of false -> write_loaded([]); true -> ok end.
|
||||
|
||||
with_loaded_file(File, SuccFun) ->
|
||||
case read_loaded(File) of
|
||||
{ok, Names0} ->
|
||||
Names = filter_plugins(Names0),
|
||||
SuccFun(Names);
|
||||
{error, Error} ->
|
||||
?LOG(alert, "Failed to read: ~p, error: ~p", [File, Error]),
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
filter_plugins(Names) ->
|
||||
lists:filtermap(fun(Name1) when is_atom(Name1) -> {true, Name1};
|
||||
({Name1, true}) -> {true, Name1};
|
||||
({_Name1, false}) -> false
|
||||
end, Names).
|
||||
|
||||
load_plugins(Names, Persistent) ->
|
||||
Plugins = list(),
|
||||
NotFound = Names -- names(Plugins),
|
||||
case NotFound of
|
||||
[] -> ok;
|
||||
NotFound -> ?LOG(alert, "cannot_find_plugins: ~p", [NotFound])
|
||||
end,
|
||||
NeedToLoad = Names -- NotFound -- names(started_app),
|
||||
lists:foreach(fun(Name) ->
|
||||
Plugin = find_plugin(Name, Plugins),
|
||||
load_plugin(Plugin#plugin.name, Persistent)
|
||||
end, NeedToLoad).
|
||||
|
||||
%% Stop plugins
|
||||
stop_plugins(Names) ->
|
||||
_ = [stop_app(App) || App <- Names],
|
||||
stop_plugins(Plugins) ->
|
||||
_ = [stop_app(Plugin#plugin.name) || Plugin <- Plugins],
|
||||
ok.
|
||||
|
||||
plugin(AppName, Type) ->
|
||||
plugin(AppName) ->
|
||||
case application:get_all_key(AppName) of
|
||||
{ok, Attrs} ->
|
||||
Descr = proplists:get_value(description, Attrs, ""),
|
||||
#plugin{name = AppName, descr = Descr, type = plugin_type(Type)};
|
||||
#plugin{name = AppName, descr = Descr};
|
||||
undefined -> error({plugin_not_found, AppName})
|
||||
end.
|
||||
|
||||
load_plugin(Name, Persistent) ->
|
||||
load_plugin(Name) ->
|
||||
try
|
||||
case load_app(Name) of
|
||||
ok ->
|
||||
start_app(Name, fun(App) -> plugin_loaded(App, Persistent) end);
|
||||
start_app(Name);
|
||||
{error, Error0} ->
|
||||
{error, Error0}
|
||||
end
|
||||
|
@ -257,22 +200,21 @@ load_app(App) ->
|
|||
{error, Error}
|
||||
end.
|
||||
|
||||
start_app(App, SuccFun) ->
|
||||
start_app(App) ->
|
||||
case application:ensure_all_started(App) of
|
||||
{ok, Started} ->
|
||||
?LOG(info, "Started plugins: ~p", [Started]),
|
||||
?LOG(info, "Load plugin ~s successfully", [App]),
|
||||
_ = SuccFun(App),
|
||||
ok;
|
||||
{error, {ErrApp, Reason}} ->
|
||||
?LOG(error, "Load plugin ~s failed, cannot start plugin ~s for ~0p", [App, ErrApp, Reason]),
|
||||
{error, {ErrApp, Reason}}
|
||||
end.
|
||||
|
||||
unload_plugin(App, Persistent) ->
|
||||
unload_plugin(App) ->
|
||||
case stop_app(App) of
|
||||
ok ->
|
||||
_ = plugin_unloaded(App, Persistent), ok;
|
||||
ok;
|
||||
{error, Reason} ->
|
||||
{error, Reason}
|
||||
end.
|
||||
|
@ -296,60 +238,5 @@ names(started_app) ->
|
|||
names(Plugins) ->
|
||||
[Name || #plugin{name = Name} <- Plugins].
|
||||
|
||||
plugin_loaded(_Name, false) ->
|
||||
ok;
|
||||
plugin_loaded(Name, true) ->
|
||||
case read_loaded() of
|
||||
{ok, Names} ->
|
||||
case lists:member(Name, Names) of
|
||||
false ->
|
||||
%% write file if plugin is loaded
|
||||
write_loaded(lists:append(Names, [{Name, true}]));
|
||||
true ->
|
||||
ignore
|
||||
end;
|
||||
{error, Error} ->
|
||||
?LOG(error, "Cannot read loaded plugins: ~p", [Error])
|
||||
end.
|
||||
|
||||
plugin_unloaded(_Name, false) ->
|
||||
ok;
|
||||
plugin_unloaded(Name, true) ->
|
||||
case read_loaded() of
|
||||
{ok, Names0} ->
|
||||
Names = filter_plugins(Names0),
|
||||
case lists:member(Name, Names) of
|
||||
true ->
|
||||
write_loaded(lists:delete(Name, Names));
|
||||
false ->
|
||||
?LOG(error, "Cannot find ~s in loaded_file", [Name])
|
||||
end;
|
||||
{error, Error} ->
|
||||
?LOG(error, "Cannot read loaded_plugins: ~p", [Error])
|
||||
end.
|
||||
|
||||
read_loaded() ->
|
||||
case emqx:get_env(plugins_loaded_file) of
|
||||
undefined -> {error, not_found};
|
||||
File -> read_loaded(File)
|
||||
end.
|
||||
|
||||
read_loaded(File) -> file:consult(File).
|
||||
|
||||
write_loaded(AppNames) ->
|
||||
FilePath = emqx:get_env(plugins_loaded_file),
|
||||
case file:write_file(FilePath, [io_lib:format("~p.~n", [Name]) || Name <- AppNames]) of
|
||||
ok -> ok;
|
||||
{error, Error} ->
|
||||
?LOG(error, "Write File ~p Error: ~p", [FilePath, Error]),
|
||||
{error, Error}
|
||||
end.
|
||||
|
||||
plugin_type(auth) -> auth;
|
||||
plugin_type(protocol) -> protocol;
|
||||
plugin_type(backend) -> backend;
|
||||
plugin_type(bridge) -> bridge;
|
||||
plugin_type(_) -> feature.
|
||||
|
||||
funlog(Key, Value) ->
|
||||
?LOG(info, "~s = ~p", [string:join(Key, "."), Value]).
|
||||
|
|
|
@ -14,21 +14,13 @@
|
|||
%% limitations under the License.
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
-module(emqx_authentication_app).
|
||||
-module(emqx_quic_connection).
|
||||
|
||||
-behaviour(application).
|
||||
|
||||
-emqx_plugin(?MODULE).
|
||||
|
||||
%% Application callbacks
|
||||
-export([ start/2
|
||||
, stop/1
|
||||
%% Callbacks
|
||||
-export([ new_conn/2
|
||||
]).
|
||||
|
||||
start(_StartType, _StartArgs) ->
|
||||
{ok, Sup} = emqx_authentication_sup:start_link(),
|
||||
ok = emqx_authentication:register_service_types(),
|
||||
{ok, Sup}.
|
||||
|
||||
stop(_State) ->
|
||||
ok.
|
||||
new_conn(Conn, {_L, COpts, _S}) when is_map(COpts) ->
|
||||
new_conn(Conn, maps:to_list(COpts));
|
||||
new_conn(Conn, COpts) ->
|
||||
emqx_connection:start_link(emqx_quic_stream, Conn, COpts).
|
|
@ -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]).
|
||||
|
||||
structs() -> ["cluster", "node", "rpc", "log", "lager",
|
||||
"zones", "listeners", "module", "broker",
|
||||
"plugins", "sysmon", "alarm", "telemetry"]
|
||||
"zones", "listeners", "broker",
|
||||
"plugins", "sysmon", "alarm"]
|
||||
++ includes().
|
||||
|
||||
-ifdef(TEST).
|
||||
|
@ -69,6 +69,14 @@ includes() ->[].
|
|||
includes() ->
|
||||
[ "emqx_data_bridge"
|
||||
, "emqx_telemetry"
|
||||
, "emqx_retainer"
|
||||
, "emqx_statsd"
|
||||
, "emqx_authn"
|
||||
, "emqx_authz"
|
||||
, "emqx_bridge_mqtt"
|
||||
, "emqx_modules"
|
||||
, "emqx_management"
|
||||
, "emqx_gateway"
|
||||
].
|
||||
-endif.
|
||||
|
||||
|
@ -345,6 +353,7 @@ fields("listeners") ->
|
|||
[ {"$name", hoconsc:union(
|
||||
[ hoconsc:ref("mqtt_tcp_listener")
|
||||
, hoconsc:ref("mqtt_ws_listener")
|
||||
, hoconsc:ref("mqtt_quic_listener")
|
||||
])}
|
||||
];
|
||||
|
||||
|
@ -361,6 +370,10 @@ fields("mqtt_ws_listener") ->
|
|||
, {"websocket", ref("ws_opts")}
|
||||
] ++ mqtt_listener();
|
||||
|
||||
fields("mqtt_quic_listener") ->
|
||||
[ {"type", t(quic)}
|
||||
] ++ base_listener();
|
||||
|
||||
fields("ws_opts") ->
|
||||
[ {"mqtt_path", t(string(), undefined, "/mqtt")}
|
||||
, {"mqtt_piggyback", t(union(single, multiple), undefined, multiple)}
|
||||
|
@ -409,12 +422,6 @@ fields("deflate_opts") ->
|
|||
, {"client_max_window_bits", t(range(8, 15), undefined, 15)}
|
||||
];
|
||||
|
||||
fields("module") ->
|
||||
[ {"loaded_file", t(string(), "emqx.modules_loaded_file", undefined)}
|
||||
, {"presence", ref("presence")}
|
||||
, {"subscription", ref("subscription")}
|
||||
, {"rewrite", ref("rewrite")}
|
||||
];
|
||||
|
||||
fields("presence") ->
|
||||
[ {"qos", t(range(0, 2), undefined, 1)}];
|
||||
|
@ -440,9 +447,7 @@ fields("rule") ->
|
|||
[ {"$id", t(string())}];
|
||||
|
||||
fields("plugins") ->
|
||||
[ {"etc_dir", t(string(), "emqx.plugins_etc_dir", undefined)}
|
||||
, {"loaded_file", t(string(), "emqx.plugins_loaded_file", undefined)}
|
||||
, {"expand_plugins_dir", t(string(), "emqx.expand_plugins_dir", undefined)}
|
||||
[ {"expand_plugins_dir", t(string(), "emqx.expand_plugins_dir", undefined)}
|
||||
];
|
||||
|
||||
fields("broker") ->
|
||||
|
@ -492,24 +497,22 @@ fields("alarm") ->
|
|||
, {"validity_period", t(duration_s(), undefined, "24h")}
|
||||
];
|
||||
|
||||
fields("telemetry") ->
|
||||
[ {"enabled", t(boolean(), undefined, false)}
|
||||
, {"url", t(string(), undefined, "https://telemetry-emqx-io.bigpar.vercel.app/api/telemetry")}
|
||||
, {"report_interval", t(duration_s(), undefined, "7d")}
|
||||
];
|
||||
|
||||
fields(ExtraField) ->
|
||||
Mod = list_to_atom(ExtraField++"_schema"),
|
||||
Mod:fields(ExtraField).
|
||||
|
||||
mqtt_listener() ->
|
||||
base_listener() ++
|
||||
[ {"access_rules", t(hoconsc:array(string()))}
|
||||
, {"proxy_protocol", t(boolean(), undefined, false)}
|
||||
, {"proxy_protocol_timeout", t(duration())}
|
||||
].
|
||||
|
||||
base_listener() ->
|
||||
[ {"bind", t(union(ip_port(), integer()))}
|
||||
, {"acceptors", t(integer(), undefined, 16)}
|
||||
, {"max_connections", maybe_infinity(integer(), infinity)}
|
||||
, {"rate_limit", ref("rate_limit")}
|
||||
, {"access_rules", t(hoconsc:array(string()))}
|
||||
, {"proxy_protocol", t(boolean(), undefined, false)}
|
||||
, {"proxy_protocol_timeout", t(duration())}
|
||||
].
|
||||
|
||||
translations() -> ["kernel"].
|
||||
|
|
|
@ -77,6 +77,8 @@
|
|||
-define(NACK(Reason), {shared_sub_nack, Reason}).
|
||||
-define(NO_ACK, no_ack).
|
||||
|
||||
-rlog_shard({?SHARED_SUB_SHARD, ?TAB}).
|
||||
|
||||
-record(state, {pmon}).
|
||||
|
||||
-record(emqx_shared_subscription, {group, topic, subpid}).
|
||||
|
@ -297,7 +299,7 @@ subscribers(Group, Topic) ->
|
|||
|
||||
init([]) ->
|
||||
{ok, _} = mnesia:subscribe({table, ?TAB, simple}),
|
||||
{atomic, PMon} = mnesia:transaction(fun init_monitors/0),
|
||||
{atomic, PMon} = ekka_mnesia:transaction(?SHARED_SUB_SHARD, fun init_monitors/0),
|
||||
ok = emqx_tables:new(?SHARED_SUBS, [protected, bag]),
|
||||
ok = emqx_tables:new(?ALIVE_SUBS, [protected, set, {read_concurrency, true}]),
|
||||
{ok, update_stats(#state{pmon = PMon})}.
|
||||
|
@ -309,7 +311,7 @@ init_monitors() ->
|
|||
end, emqx_pmon:new(), ?TAB).
|
||||
|
||||
handle_call({subscribe, Group, Topic, SubPid}, _From, State = #state{pmon = PMon}) ->
|
||||
mnesia:dirty_write(?TAB, record(Group, Topic, SubPid)),
|
||||
ekka_mnesia:dirty_write(?TAB, record(Group, Topic, SubPid)),
|
||||
case ets:member(?SHARED_SUBS, {Group, Topic}) of
|
||||
true -> ok;
|
||||
false -> ok = emqx_router:do_add_route(Topic, {Group, node()})
|
||||
|
@ -319,7 +321,7 @@ handle_call({subscribe, Group, Topic, SubPid}, _From, State = #state{pmon = PMon
|
|||
{reply, ok, update_stats(State#state{pmon = emqx_pmon:monitor(SubPid, PMon)})};
|
||||
|
||||
handle_call({unsubscribe, Group, Topic, SubPid}, _From, State) ->
|
||||
mnesia:dirty_delete_object(?TAB, record(Group, Topic, SubPid)),
|
||||
ekka_mnesia:dirty_delete_object(?TAB, record(Group, Topic, SubPid)),
|
||||
true = ets:delete_object(?SHARED_SUBS, {{Group, Topic}, SubPid}),
|
||||
delete_route_if_needed({Group, Topic}),
|
||||
{reply, ok, State};
|
||||
|
@ -336,9 +338,13 @@ handle_info({mnesia_table_event, {write, NewRecord, _}}, State = #state{pmon = P
|
|||
#emqx_shared_subscription{subpid = SubPid} = NewRecord,
|
||||
{noreply, update_stats(State#state{pmon = emqx_pmon:monitor(SubPid, PMon)})};
|
||||
|
||||
handle_info({mnesia_table_event, {delete_object, OldRecord, _}}, State = #state{pmon = PMon}) ->
|
||||
#emqx_shared_subscription{subpid = SubPid} = OldRecord,
|
||||
{noreply, update_stats(State#state{pmon = emqx_pmon:demonitor(SubPid, PMon)})};
|
||||
%% The subscriber may have subscribed multiple topics, so we need to keep monitoring the PID until
|
||||
%% it `unsubscribed` the last topic.
|
||||
%% The trick is we don't demonitor the subscriber here, and (after a long time) it will eventually
|
||||
%% be disconnected.
|
||||
% handle_info({mnesia_table_event, {delete_object, OldRecord, _}}, State = #state{pmon = PMon}) ->
|
||||
% #emqx_shared_subscription{subpid = SubPid} = OldRecord,
|
||||
% {noreply, update_stats(State#state{pmon = emqx_pmon:demonitor(SubPid, PMon)})};
|
||||
|
||||
handle_info({mnesia_table_event, _Event}, State) ->
|
||||
{noreply, State};
|
||||
|
@ -348,8 +354,7 @@ handle_info({'DOWN', _MRef, process, SubPid, _Reason}, State = #state{pmon = PMo
|
|||
cleanup_down(SubPid),
|
||||
{noreply, update_stats(State#state{pmon = emqx_pmon:erase(SubPid, PMon)})};
|
||||
|
||||
handle_info(Info, State) ->
|
||||
?LOG(error, "Unexpected info: ~p", [Info]),
|
||||
handle_info(_Info, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
|
@ -370,7 +375,7 @@ cleanup_down(SubPid) ->
|
|||
?IS_LOCAL_PID(SubPid) orelse ets:delete(?ALIVE_SUBS, SubPid),
|
||||
lists:foreach(
|
||||
fun(Record = #emqx_shared_subscription{topic = Topic, group = Group}) ->
|
||||
ok = mnesia:dirty_delete_object(?TAB, Record),
|
||||
ok = ekka_mnesia:dirty_delete_object(?TAB, Record),
|
||||
true = ets:delete_object(?SHARED_SUBS, {{Group, Topic}, SubPid}),
|
||||
delete_route_if_needed({Group, Topic})
|
||||
end, mnesia:dirty_match_object(#emqx_shared_subscription{_ = '_', subpid = SubPid})).
|
||||
|
|
|
@ -403,7 +403,10 @@ websocket_close(Reason, State) ->
|
|||
|
||||
terminate(Reason, _Req, #state{channel = Channel}) ->
|
||||
?LOG(debug, "Terminated due to ~p", [Reason]),
|
||||
emqx_channel:terminate(Reason, Channel).
|
||||
emqx_channel:terminate(Reason, Channel);
|
||||
|
||||
terminate(_Reason, _Req, _UnExpectedState) ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Handle call
|
||||
|
|
|
@ -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),
|
||||
?assertMatch({ok, _}, emqx_access_control:authenticate(clientinfo())).
|
||||
|
||||
t_check_acl(_) ->
|
||||
emqx_zone:set_env(zone, acl_nomatch, deny),
|
||||
application:set_env(emqx, enable_acl_cache, false),
|
||||
t_authorize(_) ->
|
||||
Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>),
|
||||
?assertEqual(deny, emqx_access_control:check_acl(clientinfo(), Publish, <<"t">>)),
|
||||
|
||||
emqx_zone:set_env(zone, acl_nomatch, allow),
|
||||
application:set_env(emqx, enable_acl_cache, true),
|
||||
Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>),
|
||||
?assertEqual(allow, emqx_access_control:check_acl(clientinfo(), Publish, <<"t">>)).
|
||||
?assertEqual(allow, emqx_access_control:authorize(clientinfo(), Publish, <<"t">>)).
|
||||
|
||||
t_bypass_auth_plugins(_) ->
|
||||
ClientInfo = clientinfo(),
|
||||
|
|
|
@ -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
|
||||
-export([ init/1
|
||||
, check_acl/2
|
||||
, authorize/2
|
||||
, description/0
|
||||
]).
|
||||
|
||||
init(AclOpts) ->
|
||||
{ok, AclOpts}.
|
||||
|
||||
check_acl({_User, _PubSub, _Topic}, _State) ->
|
||||
authorize({_User, _PubSub, _Topic}, _State) ->
|
||||
allow.
|
||||
|
||||
description() ->
|
||||
|
|
|
@ -37,7 +37,7 @@ init_per_suite(Config) ->
|
|||
ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]),
|
||||
ok = meck:expect(emqx_access_control, authenticate,
|
||||
fun(_) -> {ok, #{auth_result => success}} end),
|
||||
ok = meck:expect(emqx_access_control, check_acl, fun(_, _, _) -> allow end),
|
||||
ok = meck:expect(emqx_access_control, authorize, fun(_, _, _) -> allow end),
|
||||
%% Broker Meck
|
||||
ok = meck:new(emqx_broker, [passthrough, no_history, no_link]),
|
||||
%% Hooks Meck
|
||||
|
|
|
@ -28,6 +28,7 @@ all() -> emqx_ct:all(?MODULE).
|
|||
init_per_suite(Config) ->
|
||||
NewConfig = generate_config(),
|
||||
application:ensure_all_started(esockd),
|
||||
application:ensure_all_started(quicer),
|
||||
application:ensure_all_started(cowboy),
|
||||
lists:foreach(fun set_app_env/1, NewConfig),
|
||||
Config.
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
-include_lib("emqx/include/emqx_mqtt.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
-import(lists, [nth/2]).
|
||||
|
||||
|
@ -32,18 +33,37 @@
|
|||
-define(WILD_TOPICS, [<<"TopicA/+">>, <<"+/C">>, <<"#">>, <<"/#">>, <<"/+">>,
|
||||
<<"+/+">>, <<"TopicA/#">>]).
|
||||
|
||||
all() -> emqx_ct:all(?MODULE).
|
||||
all() ->
|
||||
[ {group, tcp}
|
||||
, {group, quic}
|
||||
].
|
||||
|
||||
groups() ->
|
||||
TCs = emqx_ct:all(?MODULE),
|
||||
[ {tcp, [], TCs}
|
||||
, {quic, [], TCs}
|
||||
].
|
||||
|
||||
init_per_group(tcp, Config) ->
|
||||
emqx_ct_helpers:start_apps([]),
|
||||
[ {port, 1883}, {conn_fun, connect} | Config];
|
||||
init_per_group(quic, Config) ->
|
||||
emqx_ct_helpers:start_apps([]),
|
||||
[ {port, 14567}, {conn_fun, quic_connect} | Config];
|
||||
init_per_group(_, Config) ->
|
||||
emqx_ct_helpers:stop_apps([]),
|
||||
Config.
|
||||
|
||||
end_per_group(_Group, _Config) ->
|
||||
ok.
|
||||
|
||||
init_per_suite(Config) ->
|
||||
%% Meck emqtt
|
||||
ok = meck:new(emqtt, [non_strict, passthrough, no_history, no_link]),
|
||||
%% Start Apps
|
||||
emqx_ct_helpers:boot_modules(all),
|
||||
emqx_ct_helpers:start_apps([]),
|
||||
Config.
|
||||
|
||||
end_per_suite(_Config) ->
|
||||
ok = meck:unload(emqtt),
|
||||
emqx_ct_helpers:stop_apps([]).
|
||||
|
||||
init_per_testcase(TestCase, Config) ->
|
||||
|
@ -97,9 +117,10 @@ waiting_client_process_exit(C) ->
|
|||
1000 -> error({waiting_timeout, C})
|
||||
end.
|
||||
|
||||
clean_retained(Topic) ->
|
||||
{ok, Clean} = emqtt:start_link([{clean_start, true}]),
|
||||
{ok, _} = emqtt:connect(Clean),
|
||||
clean_retained(Topic, Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
{ok, Clean} = emqtt:start_link([{clean_start, true} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Clean),
|
||||
{ok, _} = emqtt:publish(Clean, Topic, #{}, <<"">>, [{qos, ?QOS_1}, {retain, true}]),
|
||||
ok = emqtt:disconnect(Clean).
|
||||
|
||||
|
@ -107,11 +128,12 @@ clean_retained(Topic) ->
|
|||
%% Test Cases
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
t_basic_test(_) ->
|
||||
t_basic_test(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
Topic = nth(1, ?TOPICS),
|
||||
ct:print("Basic test starting"),
|
||||
{ok, C} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(C),
|
||||
{ok, C} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(C),
|
||||
{ok, _, [1]} = emqtt:subscribe(C, Topic, qos1),
|
||||
{ok, _, [2]} = emqtt:subscribe(C, Topic, qos2),
|
||||
{ok, _} = emqtt:publish(C, Topic, <<"qos 2">>, 2),
|
||||
|
@ -124,16 +146,17 @@ t_basic_test(_) ->
|
|||
%% Connection
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
t_connect_clean_start(_) ->
|
||||
t_connect_clean_start(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
process_flag(trap_exit, true),
|
||||
{ok, Client1} = emqtt:start_link([{clientid, <<"t_connect_clean_start">>},
|
||||
{proto_ver, v5},{clean_start, true}]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
{proto_ver, v5},{clean_start, true} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
?assertEqual(0, client_info(session_present, Client1)), %% [MQTT-3.1.2-4]
|
||||
ok = emqtt:pause(Client1),
|
||||
{ok, Client2} = emqtt:start_link([{clientid, <<"t_connect_clean_start">>},
|
||||
{proto_ver, v5},{clean_start, false}]),
|
||||
{ok, _} = emqtt:connect(Client2),
|
||||
{proto_ver, v5},{clean_start, false} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client2),
|
||||
?assertEqual(1, client_info(session_present, Client2)), %% [MQTT-3.1.2-5]
|
||||
?assertEqual(142, receive_disconnect_reasoncode()),
|
||||
waiting_client_process_exit(Client1),
|
||||
|
@ -142,32 +165,32 @@ t_connect_clean_start(_) ->
|
|||
waiting_client_process_exit(Client2),
|
||||
|
||||
{ok, Client3} = emqtt:start_link([{clientid, <<"new_client">>},
|
||||
{proto_ver, v5},{clean_start, false}]),
|
||||
{ok, _} = emqtt:connect(Client3),
|
||||
{proto_ver, v5},{clean_start, false} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client3),
|
||||
?assertEqual(0, client_info(session_present, Client3)), %% [MQTT-3.1.2-6]
|
||||
ok = emqtt:disconnect(Client3),
|
||||
waiting_client_process_exit(Client3),
|
||||
|
||||
process_flag(trap_exit, false).
|
||||
|
||||
t_connect_will_message(_) ->
|
||||
t_connect_will_message(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
Topic = nth(1, ?TOPICS),
|
||||
Payload = "will message",
|
||||
|
||||
{ok, Client1} = emqtt:start_link([
|
||||
{proto_ver, v5},
|
||||
{clean_start, true},
|
||||
{will_flag, true},
|
||||
{will_topic, Topic},
|
||||
{will_payload, Payload}
|
||||
]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
{ok, Client1} = emqtt:start_link([ {proto_ver, v5},
|
||||
{clean_start, true},
|
||||
{will_flag, true},
|
||||
{will_topic, Topic},
|
||||
{will_payload, Payload} | Config
|
||||
]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
[ClientPid] = emqx_cm:lookup_channels(client_info(clientid, Client1)),
|
||||
Info = emqx_connection:info(sys:get_state(ClientPid)),
|
||||
?assertNotEqual(undefined, maps:find(will_msg, Info)), %% [MQTT-3.1.2-7]
|
||||
|
||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client2),
|
||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client2),
|
||||
{ok, _, [2]} = emqtt:subscribe(Client2, Topic, qos2),
|
||||
|
||||
ok = emqtt:disconnect(Client1, 4), %% [MQTT-3.14.2-1]
|
||||
|
@ -178,27 +201,32 @@ t_connect_will_message(_) ->
|
|||
?assertEqual({ok, 0}, maps:find(qos, Msg)),
|
||||
ok = emqtt:disconnect(Client2),
|
||||
|
||||
{ok, Client3} = emqtt:start_link([
|
||||
{proto_ver, v5},
|
||||
{clean_start, true},
|
||||
{will_flag, true},
|
||||
{will_topic, Topic},
|
||||
{will_payload, Payload}
|
||||
]),
|
||||
{ok, _} = emqtt:connect(Client3),
|
||||
{ok, Client3} = emqtt:start_link([ {proto_ver, v5},
|
||||
{clean_start, true},
|
||||
{will_flag, true},
|
||||
{will_topic, Topic},
|
||||
{will_payload, Payload} | Config
|
||||
]),
|
||||
{ok, _} = emqtt:ConnFun(Client3),
|
||||
|
||||
{ok, Client4} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client4),
|
||||
{ok, Client4} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client4),
|
||||
{ok, _, [2]} = emqtt:subscribe(Client4, Topic, qos2),
|
||||
ok = emqtt:disconnect(Client3),
|
||||
?assertEqual(0, length(receive_messages(1))), %% [MQTT-3.1.2-10]
|
||||
ok = emqtt:disconnect(Client4).
|
||||
|
||||
t_batch_subscribe(_) ->
|
||||
{ok, Client} = emqtt:start_link([{proto_ver, v5}, {clientid, <<"batch_test">>}]),
|
||||
{ok, _} = emqtt:connect(Client),
|
||||
application:set_env(emqx, enable_acl_cache, false),
|
||||
application:set_env(emqx, acl_nomatch, deny),
|
||||
t_batch_subscribe(init, Config) ->
|
||||
ok = meck:new(emqx_access_control, [non_strict, passthrough, no_history, no_link]),
|
||||
meck:expect(emqx_access_control, authorize, fun(_, _, _) -> deny end),
|
||||
Config;
|
||||
t_batch_subscribe('end', _Config) ->
|
||||
meck:unload(emqx_access_control).
|
||||
|
||||
t_batch_subscribe(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
{ok, Client} = emqtt:start_link([{proto_ver, v5}, {clientid, <<"batch_test">>} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client),
|
||||
{ok, _, [?RC_NOT_AUTHORIZED,
|
||||
?RC_NOT_AUTHORIZED,
|
||||
?RC_NOT_AUTHORIZED]} = emqtt:subscribe(Client, [{<<"t1">>, qos1},
|
||||
|
@ -209,25 +237,25 @@ t_batch_subscribe(_) ->
|
|||
?RC_NO_SUBSCRIPTION_EXISTED]} = emqtt:unsubscribe(Client, [<<"t1">>,
|
||||
<<"t2">>,
|
||||
<<"t3">>]),
|
||||
application:set_env(emqx, acl_nomatch, allow),
|
||||
emqtt:disconnect(Client).
|
||||
|
||||
t_connect_will_retain(_) ->
|
||||
t_connect_will_retain(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
process_flag(trap_exit, true),
|
||||
Topic = nth(1, ?TOPICS),
|
||||
Payload = "will message",
|
||||
|
||||
{ok, Client1} = emqtt:start_link([
|
||||
{proto_ver, v5},
|
||||
{clean_start, true},
|
||||
{will_flag, true},
|
||||
{will_topic, Topic},
|
||||
{will_payload, Payload},
|
||||
{will_retain, false}
|
||||
]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
{ok, Client1} = emqtt:start_link([ {proto_ver, v5},
|
||||
{clean_start, true},
|
||||
{will_flag, true},
|
||||
{will_topic, Topic},
|
||||
{will_payload, Payload},
|
||||
{will_retain, false} | Config
|
||||
]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
|
||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client2),
|
||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client2),
|
||||
{ok, _, [2]} = emqtt:subscribe(Client2, #{}, [{Topic, [{rap, true}, {qos, 2}]}]),
|
||||
|
||||
ok = emqtt:disconnect(Client1, 4),
|
||||
|
@ -235,27 +263,26 @@ t_connect_will_retain(_) ->
|
|||
?assertEqual({ok, false}, maps:find(retain, Msg1)), %% [MQTT-3.1.2-14]
|
||||
ok = emqtt:disconnect(Client2),
|
||||
|
||||
{ok, Client3} = emqtt:start_link([
|
||||
{proto_ver, v5},
|
||||
{clean_start, true},
|
||||
{will_flag, true},
|
||||
{will_topic, Topic},
|
||||
{will_payload, Payload},
|
||||
{will_retain, true}
|
||||
]),
|
||||
{ok, _} = emqtt:connect(Client3),
|
||||
{ok, Client3} = emqtt:start_link([ {proto_ver, v5},
|
||||
{clean_start, true},
|
||||
{will_flag, true},
|
||||
{will_topic, Topic},
|
||||
{will_payload, Payload},
|
||||
{will_retain, true} | Config
|
||||
]),
|
||||
{ok, _} = emqtt:ConnFun(Client3),
|
||||
|
||||
{ok, Client4} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client4),
|
||||
{ok, Client4} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client4),
|
||||
{ok, _, [2]} = emqtt:subscribe(Client4, #{}, [{Topic, [{rap, true}, {qos, 2}]}]),
|
||||
|
||||
ok = emqtt:disconnect(Client3, 4),
|
||||
[Msg2 | _ ] = receive_messages(1),
|
||||
?assertEqual({ok, true}, maps:find(retain, Msg2)), %% [MQTT-3.1.2-15]
|
||||
ok = emqtt:disconnect(Client4),
|
||||
clean_retained(Topic).
|
||||
clean_retained(Topic, Config).
|
||||
|
||||
t_connect_idle_timeout(_) ->
|
||||
t_connect_idle_timeout(_Config) ->
|
||||
IdleTimeout = 2000,
|
||||
emqx_zone:set_env(external, idle_timeout, IdleTimeout),
|
||||
|
||||
|
@ -263,25 +290,30 @@ t_connect_idle_timeout(_) ->
|
|||
timer:sleep(IdleTimeout),
|
||||
?assertMatch({error, closed}, emqtt_sock:recv(Sock,1024)).
|
||||
|
||||
t_connect_limit_timeout(_) ->
|
||||
t_connect_limit_timeout(init, Config) ->
|
||||
ok = meck:new(proplists, [non_strict, passthrough, no_history, no_link, unstick]),
|
||||
meck:expect(proplists, get_value, fun(active_n, _Options, _Default) -> 1;
|
||||
(Arg1, ARg2, Arg3) -> meck:passthrough([Arg1, ARg2, Arg3])
|
||||
end),
|
||||
Config;
|
||||
t_connect_limit_timeout('end', _Config) ->
|
||||
catch meck:unload(proplists).
|
||||
|
||||
t_connect_limit_timeout(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
Topic = nth(1, ?TOPICS),
|
||||
emqx_zone:set_env(external, publish_limit, {3, 5}),
|
||||
|
||||
{ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60}]),
|
||||
{ok, _} = emqtt:connect(Client),
|
||||
{ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client),
|
||||
[ClientPid] = emqx_cm:lookup_channels(client_info(clientid, Client)),
|
||||
|
||||
?assertEqual(undefined, emqx_connection:info(limit_timer, sys:get_state(ClientPid))),
|
||||
Payload = <<"t_shared_subscriptions_client_terminates_when_qos_eq_2">>,
|
||||
ok = emqtt:publish(Client, Topic, Payload, 0),
|
||||
ok = emqtt:publish(Client, Topic, Payload, 0),
|
||||
ok = emqtt:publish(Client, Topic, Payload, 0),
|
||||
timer:sleep(200),
|
||||
{ok, 2} = emqtt:publish(Client, Topic, Payload, 1),
|
||||
{ok, 3} = emqtt:publish(Client, Topic, Payload, 1),
|
||||
{ok, 4} = emqtt:publish(Client, Topic, Payload, 1),
|
||||
timer:sleep(250),
|
||||
?assert(is_reference(emqx_connection:info(limit_timer, sys:get_state(ClientPid)))),
|
||||
|
||||
ok = emqtt:disconnect(Client),
|
||||
|
@ -301,9 +333,10 @@ t_connect_emit_stats_timeout('end', Config) ->
|
|||
ok.
|
||||
|
||||
t_connect_emit_stats_timeout(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
{_, IdleTimeout} = lists:keyfind(idle_timeout, 1, Config),
|
||||
{ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60}]),
|
||||
{ok, _} = emqtt:connect(Client),
|
||||
{ok, Client} = emqtt:start_link([{proto_ver, v5},{keepalive, 60} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client),
|
||||
[ClientPid] = emqx_cm:lookup_channels(client_info(clientid, Client)),
|
||||
?assert(is_reference(emqx_connection:info(stats_timer, sys:get_state(ClientPid)))),
|
||||
?block_until(#{?snk_kind := cancel_stats_timer}, IdleTimeout * 2, _BackInTime = 0),
|
||||
|
@ -311,15 +344,16 @@ t_connect_emit_stats_timeout(Config) ->
|
|||
ok = emqtt:disconnect(Client).
|
||||
|
||||
%% [MQTT-3.1.2-22]
|
||||
t_connect_keepalive_timeout(_) ->
|
||||
t_connect_keepalive_timeout(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
%% Prevent the emqtt client bringing us down on the disconnect.
|
||||
process_flag(trap_exit, true),
|
||||
|
||||
Keepalive = 2,
|
||||
|
||||
{ok, Client} = emqtt:start_link([{proto_ver, v5},
|
||||
{keepalive, Keepalive}]),
|
||||
{ok, _} = emqtt:connect(Client),
|
||||
{keepalive, Keepalive} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client),
|
||||
emqtt:pause(Client),
|
||||
receive
|
||||
{disconnected, ReasonCode, _Channel} -> ?assertEqual(141, ReasonCode)
|
||||
|
@ -328,30 +362,30 @@ t_connect_keepalive_timeout(_) ->
|
|||
end.
|
||||
|
||||
%% [MQTT-3.1.2-23]
|
||||
t_connect_session_expiry_interval(_) ->
|
||||
t_connect_session_expiry_interval(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
Topic = nth(1, ?TOPICS),
|
||||
Payload = "test message",
|
||||
|
||||
{ok, Client1} = emqtt:start_link([
|
||||
{clientid, <<"t_connect_session_expiry_interval">>},
|
||||
{proto_ver, v5},
|
||||
{properties, #{'Session-Expiry-Interval' => 7200}}
|
||||
{ok, Client1} = emqtt:start_link([ {clientid, <<"t_connect_session_expiry_interval">>},
|
||||
{proto_ver, v5},
|
||||
{properties, #{'Session-Expiry-Interval' => 7200}}
|
||||
| Config
|
||||
]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
{ok, _, [2]} = emqtt:subscribe(Client1, Topic, qos2),
|
||||
ok = emqtt:disconnect(Client1),
|
||||
|
||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client2),
|
||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client2),
|
||||
{ok, 2} = emqtt:publish(Client2, Topic, Payload, 2),
|
||||
ok = emqtt:disconnect(Client2),
|
||||
|
||||
{ok, Client3} = emqtt:start_link([
|
||||
{clientid, <<"t_connect_session_expiry_interval">>},
|
||||
{proto_ver, v5},
|
||||
{clean_start, false}
|
||||
{ok, Client3} = emqtt:start_link([ {clientid, <<"t_connect_session_expiry_interval">>},
|
||||
{proto_ver, v5},
|
||||
{clean_start, false} | Config
|
||||
]),
|
||||
{ok, _} = emqtt:connect(Client3),
|
||||
{ok, _} = emqtt:ConnFun(Client3),
|
||||
[Msg | _ ] = receive_messages(1),
|
||||
?assertEqual({ok, iolist_to_binary(Topic)}, maps:find(topic, Msg)),
|
||||
?assertEqual({ok, iolist_to_binary(Payload)}, maps:find(payload, Msg)),
|
||||
|
@ -360,13 +394,13 @@ t_connect_session_expiry_interval(_) ->
|
|||
|
||||
%% [MQTT-3.1.3-9]
|
||||
%% !!!REFACTOR NEED:
|
||||
%t_connect_will_delay_interval(_) ->
|
||||
%t_connect_will_delay_interval(Config) ->
|
||||
% process_flag(trap_exit, true),
|
||||
% Topic = nth(1, ?TOPICS),
|
||||
% Payload = "will message",
|
||||
%
|
||||
% {ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
||||
% {ok, _} = emqtt:connect(Client1),
|
||||
% {ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
% {ok, _} = emqtt:ConnFun(Client1),
|
||||
% {ok, _, [2]} = emqtt:subscribe(Client1, Topic, qos2),
|
||||
%
|
||||
% {ok, Client2} = emqtt:start_link([
|
||||
|
@ -379,9 +413,9 @@ t_connect_session_expiry_interval(_) ->
|
|||
% {will_payload, Payload},
|
||||
% {will_props, #{'Will-Delay-Interval' => 3}},
|
||||
% {properties, #{'Session-Expiry-Interval' => 7200}},
|
||||
% {keepalive, 2}
|
||||
% {keepalive, 2} | Config
|
||||
% ]),
|
||||
% {ok, _} = emqtt:connect(Client2),
|
||||
% {ok, _} = emqtt:ConnFun(Client2),
|
||||
% timer:sleep(50),
|
||||
% erlang:exit(Client2, kill),
|
||||
% timer:sleep(2000),
|
||||
|
@ -399,9 +433,9 @@ t_connect_session_expiry_interval(_) ->
|
|||
% {will_payload, Payload},
|
||||
% {will_props, #{'Will-Delay-Interval' => 7200}},
|
||||
% {properties, #{'Session-Expiry-Interval' => 3}},
|
||||
% {keepalive, 2}
|
||||
% {keepalive, 2} | Config
|
||||
% ]),
|
||||
% {ok, _} = emqtt:connect(Client3),
|
||||
% {ok, _} = emqtt:ConnFun(Client3),
|
||||
% timer:sleep(50),
|
||||
% erlang:exit(Client3, kill),
|
||||
%
|
||||
|
@ -418,18 +452,17 @@ t_connect_session_expiry_interval(_) ->
|
|||
% process_flag(trap_exit, false).
|
||||
|
||||
%% [MQTT-3.1.4-3]
|
||||
t_connect_duplicate_clientid(_) ->
|
||||
t_connect_duplicate_clientid(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
process_flag(trap_exit, true),
|
||||
{ok, Client1} = emqtt:start_link([
|
||||
{clientid, <<"t_connect_duplicate_clientid">>},
|
||||
{proto_ver, v5}
|
||||
]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
{ok, Client2} = emqtt:start_link([
|
||||
{clientid, <<"t_connect_duplicate_clientid">>},
|
||||
{proto_ver, v5}
|
||||
]),
|
||||
{ok, _} = emqtt:connect(Client2),
|
||||
{ok, Client1} = emqtt:start_link([ {clientid, <<"t_connect_duplicate_clientid">>},
|
||||
{proto_ver, v5} | Config
|
||||
]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
{ok, Client2} = emqtt:start_link([ {clientid, <<"t_connect_duplicate_clientid">>},
|
||||
{proto_ver, v5} | Config
|
||||
]),
|
||||
{ok, _} = emqtt:ConnFun(Client2),
|
||||
?assertEqual(142, receive_disconnect_reasoncode()),
|
||||
waiting_client_process_exit(Client1),
|
||||
|
||||
|
@ -441,28 +474,33 @@ t_connect_duplicate_clientid(_) ->
|
|||
%% Connack
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
t_connack_session_present(_) ->
|
||||
{ok, Client1} = emqtt:start_link([
|
||||
{clientid, <<"t_connect_duplicate_clientid">>},
|
||||
{proto_ver, v5},
|
||||
{properties, #{'Session-Expiry-Interval' => 7200}},
|
||||
{clean_start, true}
|
||||
]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
t_connack_session_present(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
{ok, Client1} = emqtt:start_link([ {clientid, <<"t_connect_duplicate_clientid">>},
|
||||
{proto_ver, v5},
|
||||
{properties, #{'Session-Expiry-Interval' => 7200}},
|
||||
{clean_start, true} | Config
|
||||
]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
?assertEqual(0, client_info(session_present, Client1)), %% [MQTT-3.2.2-2]
|
||||
ok = emqtt:disconnect(Client1),
|
||||
|
||||
{ok, Client2} = emqtt:start_link([
|
||||
{clientid, <<"t_connect_duplicate_clientid">>},
|
||||
{proto_ver, v5},
|
||||
{properties, #{'Session-Expiry-Interval' => 7200}},
|
||||
{clean_start, false}
|
||||
]),
|
||||
{ok, _} = emqtt:connect(Client2),
|
||||
{ok, Client2} = emqtt:start_link([ {clientid, <<"t_connect_duplicate_clientid">>},
|
||||
{proto_ver, v5},
|
||||
{properties, #{'Session-Expiry-Interval' => 7200}},
|
||||
{clean_start, false} | Config
|
||||
]),
|
||||
{ok, _} = emqtt:ConnFun(Client2),
|
||||
?assertEqual(1, client_info(session_present, Client2)), %% [[MQTT-3.2.2-3]]
|
||||
ok = emqtt:disconnect(Client2).
|
||||
|
||||
t_connack_max_qos_allowed(_) ->
|
||||
t_connack_max_qos_allowed(init, Config) ->
|
||||
Config;
|
||||
t_connack_max_qos_allowed('end', _Config) ->
|
||||
emqx_zone:set_env(external, max_qos_allowed, 2),
|
||||
ok.
|
||||
t_connack_max_qos_allowed(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
process_flag(trap_exit, true),
|
||||
Topic = nth(1, ?TOPICS),
|
||||
|
||||
|
@ -471,8 +509,8 @@ t_connack_max_qos_allowed(_) ->
|
|||
persistent_term:erase({emqx_zone, external, '$mqtt_caps'}),
|
||||
persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}),
|
||||
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, Connack1} = emqtt:connect(Client1),
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, Connack1} = emqtt:ConnFun(Client1),
|
||||
?assertEqual(0, maps:get('Maximum-QoS', Connack1)), %% [MQTT-3.2.2-9]
|
||||
|
||||
{ok, _, [0]} = emqtt:subscribe(Client1, Topic, 0), %% [MQTT-3.2.2-10]
|
||||
|
@ -483,14 +521,13 @@ t_connack_max_qos_allowed(_) ->
|
|||
?assertEqual(155, receive_disconnect_reasoncode()), %% [MQTT-3.2.2-11]
|
||||
waiting_client_process_exit(Client1),
|
||||
|
||||
{ok, Client2} = emqtt:start_link([
|
||||
{proto_ver, v5},
|
||||
{will_flag, true},
|
||||
{will_topic, Topic},
|
||||
{will_payload, <<"Unsupported Qos">>},
|
||||
{will_qos, 2}
|
||||
]),
|
||||
{error, Connack2} = emqtt:connect(Client2),
|
||||
{ok, Client2} = emqtt:start_link([ {proto_ver, v5},
|
||||
{will_flag, true},
|
||||
{will_topic, Topic},
|
||||
{will_payload, <<"Unsupported Qos">>},
|
||||
{will_qos, 2} | Config
|
||||
]),
|
||||
{error, Connack2} = emqtt:ConnFun(Client2),
|
||||
?assertMatch({qos_not_supported, _}, Connack2), %% [MQTT-3.2.2-12]
|
||||
waiting_client_process_exit(Client2),
|
||||
|
||||
|
@ -499,8 +536,8 @@ t_connack_max_qos_allowed(_) ->
|
|||
persistent_term:erase({emqx_zone, external, '$mqtt_caps'}),
|
||||
persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}),
|
||||
|
||||
{ok, Client3} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, Connack3} = emqtt:connect(Client3),
|
||||
{ok, Client3} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, Connack3} = emqtt:ConnFun(Client3),
|
||||
?assertEqual(1, maps:get('Maximum-QoS', Connack3)), %% [MQTT-3.2.2-9]
|
||||
|
||||
{ok, _, [0]} = emqtt:subscribe(Client3, Topic, 0), %% [MQTT-3.2.2-10]
|
||||
|
@ -511,14 +548,13 @@ t_connack_max_qos_allowed(_) ->
|
|||
?assertEqual(155, receive_disconnect_reasoncode()), %% [MQTT-3.2.2-11]
|
||||
waiting_client_process_exit(Client3),
|
||||
|
||||
{ok, Client4} = emqtt:start_link([
|
||||
{proto_ver, v5},
|
||||
{will_flag, true},
|
||||
{will_topic, Topic},
|
||||
{will_payload, <<"Unsupported Qos">>},
|
||||
{will_qos, 2}
|
||||
]),
|
||||
{error, Connack4} = emqtt:connect(Client4),
|
||||
{ok, Client4} = emqtt:start_link([ {proto_ver, v5},
|
||||
{will_flag, true},
|
||||
{will_topic, Topic},
|
||||
{will_payload, <<"Unsupported Qos">>},
|
||||
{will_qos, 2} | Config
|
||||
]),
|
||||
{error, Connack4} = emqtt:ConnFun(Client4),
|
||||
?assertMatch({qos_not_supported, _}, Connack4), %% [MQTT-3.2.2-12]
|
||||
waiting_client_process_exit(Client4),
|
||||
|
||||
|
@ -527,17 +563,18 @@ t_connack_max_qos_allowed(_) ->
|
|||
persistent_term:erase({emqx_zone, external, '$mqtt_caps'}),
|
||||
persistent_term:erase({emqx_zone, external, '$mqtt_pub_caps'}),
|
||||
|
||||
{ok, Client5} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, Connack5} = emqtt:connect(Client5),
|
||||
{ok, Client5} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, Connack5} = emqtt:ConnFun(Client5),
|
||||
?assertEqual(undefined, maps:get('Maximum-QoS', Connack5, undefined)), %% [MQTT-3.2.2-9]
|
||||
ok = emqtt:disconnect(Client5),
|
||||
waiting_client_process_exit(Client5),
|
||||
|
||||
process_flag(trap_exit, false).
|
||||
|
||||
t_connack_assigned_clienid(_) ->
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
t_connack_assigned_clienid(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
?assert(is_binary(client_info(clientid, Client1))), %% [MQTT-3.2.2-16]
|
||||
ok = emqtt:disconnect(Client1).
|
||||
|
||||
|
@ -545,11 +582,12 @@ t_connack_assigned_clienid(_) ->
|
|||
%% Publish
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
t_publish_rap(_) ->
|
||||
t_publish_rap(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
Topic = nth(1, ?TOPICS),
|
||||
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
{ok, _, [2]} = emqtt:subscribe(Client1, #{}, [{Topic, [{rap, true}, {qos, 2}]}]),
|
||||
{ok, _} = emqtt:publish(Client1, Topic, #{}, <<"retained message">>,
|
||||
[{qos, ?QOS_1}, {retain, true}]),
|
||||
|
@ -557,8 +595,8 @@ t_publish_rap(_) ->
|
|||
?assertEqual(true, maps:get(retain, Msg1)), %% [MQTT-3.3.1-12]
|
||||
ok = emqtt:disconnect(Client1),
|
||||
|
||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client2),
|
||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client2),
|
||||
{ok, _, [2]} = emqtt:subscribe(Client2, #{}, [{Topic, [{rap, false}, {qos, 2}]}]),
|
||||
{ok, _} = emqtt:publish(Client2, Topic, #{}, <<"retained message">>,
|
||||
[{qos, ?QOS_1}, {retain, true}]),
|
||||
|
@ -566,44 +604,47 @@ t_publish_rap(_) ->
|
|||
?assertEqual(false, maps:get(retain, Msg2)), %% [MQTT-3.3.1-13]
|
||||
ok = emqtt:disconnect(Client2),
|
||||
|
||||
clean_retained(Topic).
|
||||
clean_retained(Topic, Config).
|
||||
|
||||
t_publish_wildtopic(_) ->
|
||||
t_publish_wildtopic(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
process_flag(trap_exit, true),
|
||||
Topic = nth(1, ?WILD_TOPICS),
|
||||
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
ok = emqtt:publish(Client1, Topic, <<"error topic">>),
|
||||
?assertEqual(144, receive_disconnect_reasoncode()),
|
||||
waiting_client_process_exit(Client1),
|
||||
|
||||
process_flag(trap_exit, false).
|
||||
|
||||
t_publish_payload_format_indicator(_) ->
|
||||
t_publish_payload_format_indicator(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
Topic = nth(1, ?TOPICS),
|
||||
Properties = #{'Payload-Format-Indicator' => 233},
|
||||
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
{ok, _, [2]} = emqtt:subscribe(Client1, Topic, qos2),
|
||||
ok = emqtt:publish(Client1, Topic, Properties, <<"Payload Format Indicator">>, [{qos, ?QOS_0}]),
|
||||
[Msg1 | _] = receive_messages(1),
|
||||
?assertEqual(Properties, maps:get(properties, Msg1)), %% [MQTT-3.3.2-6]
|
||||
ok = emqtt:disconnect(Client1).
|
||||
|
||||
t_publish_topic_alias(_) ->
|
||||
t_publish_topic_alias(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
process_flag(trap_exit, true),
|
||||
Topic = nth(1, ?TOPICS),
|
||||
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
ok = emqtt:publish(Client1, Topic, #{'Topic-Alias' => 0}, <<"Topic-Alias">>, [{qos, ?QOS_0}]),
|
||||
?assertEqual(148, receive_disconnect_reasoncode()), %% [MQTT-3.3.2-8]
|
||||
waiting_client_process_exit(Client1),
|
||||
|
||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client2),
|
||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client2),
|
||||
{ok, _, [2]} = emqtt:subscribe(Client2, Topic, qos2),
|
||||
ok = emqtt:publish(Client2, Topic, #{'Topic-Alias' => 233},
|
||||
<<"Topic-Alias">>, [{qos, ?QOS_0}]),
|
||||
|
@ -615,12 +656,13 @@ t_publish_topic_alias(_) ->
|
|||
|
||||
process_flag(trap_exit, false).
|
||||
|
||||
t_publish_response_topic(_) ->
|
||||
t_publish_response_topic(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
process_flag(trap_exit, true),
|
||||
Topic = nth(1, ?TOPICS),
|
||||
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
ok = emqtt:publish(Client1, Topic, #{'Response-Topic' => nth(1, ?WILD_TOPICS)},
|
||||
<<"Response-Topic">>, [{qos, ?QOS_0}]),
|
||||
?assertEqual(130, receive_disconnect_reasoncode()), %% [MQTT-3.3.2-14]
|
||||
|
@ -628,7 +670,8 @@ t_publish_response_topic(_) ->
|
|||
|
||||
process_flag(trap_exit, false).
|
||||
|
||||
t_publish_properties(_) ->
|
||||
t_publish_properties(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
Topic = nth(1, ?TOPICS),
|
||||
Properties = #{
|
||||
'Response-Topic' => Topic, %% [MQTT-3.3.2-15]
|
||||
|
@ -637,20 +680,21 @@ t_publish_properties(_) ->
|
|||
'Content-Type' => <<"2333">> %% [MQTT-3.3.2-20]
|
||||
},
|
||||
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
{ok, _, [2]} = emqtt:subscribe(Client1, Topic, qos2),
|
||||
ok = emqtt:publish(Client1, Topic, Properties, <<"Publish Properties">>, [{qos, ?QOS_0}]),
|
||||
[Msg1 | _] = receive_messages(1),
|
||||
?assertEqual(Properties, maps:get(properties, Msg1)), %% [MQTT-3.3.2-16]
|
||||
ok = emqtt:disconnect(Client1).
|
||||
|
||||
t_publish_overlapping_subscriptions(_) ->
|
||||
t_publish_overlapping_subscriptions(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
Topic = nth(1, ?TOPICS),
|
||||
Properties = #{'Subscription-Identifier' => 2333},
|
||||
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
{ok, _, [1]} = emqtt:subscribe(Client1, Properties, nth(1, ?WILD_TOPICS), qos1),
|
||||
{ok, _, [0]} = emqtt:subscribe(Client1, Properties, nth(3, ?WILD_TOPICS), qos0),
|
||||
{ok, _} = emqtt:publish(Client1, Topic, #{},
|
||||
|
@ -665,13 +709,15 @@ t_publish_overlapping_subscriptions(_) ->
|
|||
%% Subsctibe
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
t_subscribe_topic_alias(_) ->
|
||||
t_subscribe_topic_alias(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
Topic1 = nth(1, ?TOPICS),
|
||||
Topic2 = nth(2, ?TOPICS),
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5},
|
||||
{properties, #{'Topic-Alias-Maximum' => 1}}
|
||||
{ok, Client1} = emqtt:start_link([ {proto_ver, v5},
|
||||
{properties, #{'Topic-Alias-Maximum' => 1}}
|
||||
| Config
|
||||
]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
{ok, _, [2]} = emqtt:subscribe(Client1, Topic1, qos2),
|
||||
{ok, _, [2]} = emqtt:subscribe(Client1, Topic2, qos2),
|
||||
|
||||
|
@ -692,27 +738,29 @@ t_subscribe_topic_alias(_) ->
|
|||
|
||||
ok = emqtt:disconnect(Client1).
|
||||
|
||||
t_subscribe_no_local(_) ->
|
||||
t_subscribe_no_local(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
Topic = nth(1, ?TOPICS),
|
||||
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
{ok, _, [2]} = emqtt:subscribe(Client1, #{}, [{Topic, [{nl, true}, {qos, 2}]}]),
|
||||
|
||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client2),
|
||||
{ok, Client2} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client2),
|
||||
{ok, _, [2]} = emqtt:subscribe(Client2, #{}, [{Topic, [{nl, true}, {qos, 2}]}]),
|
||||
|
||||
ok = emqtt:publish(Client1, Topic, <<"t_subscribe_no_local">>, 0),
|
||||
?assertEqual(1, length(receive_messages(2))), %% [MQTT-3.8.3-3]
|
||||
ok = emqtt:disconnect(Client1).
|
||||
|
||||
t_subscribe_actions(_) ->
|
||||
t_subscribe_actions(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
Topic = nth(1, ?TOPICS),
|
||||
Properties = #{'Subscription-Identifier' => 2333},
|
||||
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
{ok, _, [2]} = emqtt:subscribe(Client1, Properties, Topic, qos2),
|
||||
{ok, _, [1]} = emqtt:subscribe(Client1, Properties, Topic, qos1),
|
||||
{ok, _} = emqtt:publish(Client1, Topic, <<"t_subscribe_actions">>, 2),
|
||||
|
@ -726,12 +774,13 @@ t_subscribe_actions(_) ->
|
|||
%% Unsubsctibe Unsuback
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
t_unscbsctibe(_) ->
|
||||
t_unscbsctibe(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
Topic1 = nth(1, ?TOPICS),
|
||||
Topic2 = nth(2, ?TOPICS),
|
||||
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
{ok, _, [2]} = emqtt:subscribe(Client1, Topic1, qos2),
|
||||
{ok, _, [0]} = emqtt:unsubscribe(Client1, Topic1), %% [MQTT-3.10.4-4]
|
||||
{ok, _, [17]} = emqtt:unsubscribe(Client1, <<"noExistTopic">>), %% [MQTT-3.10.4-5]
|
||||
|
@ -745,9 +794,10 @@ t_unscbsctibe(_) ->
|
|||
%% Pingreq
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
t_pingreq(_) ->
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5}]),
|
||||
{ok, _} = emqtt:connect(Client1),
|
||||
t_pingreq(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
{ok, Client1} = emqtt:start_link([{proto_ver, v5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Client1),
|
||||
pong = emqtt:ping(Client1), %% [MQTT-3.12.4-1]
|
||||
ok = emqtt:disconnect(Client1).
|
||||
|
||||
|
@ -755,7 +805,14 @@ t_pingreq(_) ->
|
|||
%% Shared Subscriptions
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
t_shared_subscriptions_client_terminates_when_qos_eq_2(_) ->
|
||||
t_shared_subscriptions_client_terminates_when_qos_eq_2(init, Config) ->
|
||||
ok = meck:new(emqtt, [non_strict, passthrough, no_history, no_link]),
|
||||
Config;
|
||||
t_shared_subscriptions_client_terminates_when_qos_eq_2('end', _Config) ->
|
||||
catch meck:unload(emqtt).
|
||||
|
||||
t_shared_subscriptions_client_terminates_when_qos_eq_2(Config) ->
|
||||
ConnFun = ?config(conn_fun, Config),
|
||||
process_flag(trap_exit, true),
|
||||
application:set_env(emqx, shared_dispatch_ack_enabled, true),
|
||||
|
||||
|
@ -766,32 +823,33 @@ t_shared_subscriptions_client_terminates_when_qos_eq_2(_) ->
|
|||
meck:expect(emqtt, connected,
|
||||
fun(cast, ?PUBLISH_PACKET(?QOS_2, _PacketId), _State) ->
|
||||
ok = counters:add(CRef, 1, 1),
|
||||
{stop, {shutdown, for_testiong}};
|
||||
{stop, {shutdown, for_testing}};
|
||||
(Arg1, ARg2, Arg3) -> meck:passthrough([Arg1, ARg2, Arg3])
|
||||
end),
|
||||
|
||||
{ok, Sub1} = emqtt:start_link([{proto_ver, v5},
|
||||
{ok, Sub1} = emqtt:start_link([ {proto_ver, v5},
|
||||
{clientid, <<"sub_client_1">>},
|
||||
{keepalive, 5}]),
|
||||
{ok, _} = emqtt:connect(Sub1),
|
||||
{keepalive, 5} | Config
|
||||
]),
|
||||
{ok, _} = emqtt:ConnFun(Sub1),
|
||||
{ok, _, [2]} = emqtt:subscribe(Sub1, SharedTopic, qos2),
|
||||
|
||||
{ok, Sub2} = emqtt:start_link([{proto_ver, v5},
|
||||
{clientid, <<"sub_client_2">>},
|
||||
{keepalive, 5}]),
|
||||
{ok, _} = emqtt:connect(Sub2),
|
||||
{keepalive, 5} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Sub2),
|
||||
{ok, _, [2]} = emqtt:subscribe(Sub2, SharedTopic, qos2),
|
||||
|
||||
{ok, Pub} = emqtt:start_link([{proto_ver, v5}, {clientid, <<"pub_client">>}]),
|
||||
{ok, _} = emqtt:connect(Pub),
|
||||
{ok, Pub} = emqtt:start_link([{proto_ver, v5}, {clientid, <<"pub_client">>} | Config]),
|
||||
{ok, _} = emqtt:ConnFun(Pub),
|
||||
{ok, _} = emqtt:publish(Pub, Topic,
|
||||
<<"t_shared_subscriptions_client_terminates_when_qos_eq_2">>, 2),
|
||||
|
||||
receive
|
||||
{'EXIT', _,{shutdown, for_testiong}} ->
|
||||
{'EXIT', _,{shutdown, for_testing}} ->
|
||||
ok
|
||||
after 1000 ->
|
||||
error("disconnected timeout")
|
||||
ct:fail("disconnected timeout")
|
||||
end,
|
||||
|
||||
?assertEqual(1, counters:get(CRef, 1)),
|
||||
|
|
|
@ -63,64 +63,28 @@ t_load(_) ->
|
|||
?assertEqual({error, not_started}, emqx_plugins:unload(emqx_hocon_plugin)),
|
||||
|
||||
application:set_env(emqx, expand_plugins_dir, undefined),
|
||||
application:set_env(emqx, plugins_loaded_file, undefined),
|
||||
?assertEqual(ignore, emqx_plugins:load()),
|
||||
?assertEqual(ignore, emqx_plugins:unload()).
|
||||
|
||||
|
||||
t_init_config(_) ->
|
||||
ConfFile = "emqx_mini_plugin.config",
|
||||
Data = "[{emqx_mini_plugin,[{mininame ,test}]}].",
|
||||
file:write_file(ConfFile, list_to_binary(Data)),
|
||||
?assertEqual(ok, emqx_plugins:init_config(ConfFile)),
|
||||
file:delete(ConfFile),
|
||||
?assertEqual({ok,test}, application:get_env(emqx_mini_plugin, mininame)).
|
||||
application:set_env(emqx, plugins_loaded_file, undefined).
|
||||
|
||||
t_load_ext_plugin(_) ->
|
||||
?assertError({plugin_app_file_not_found, _},
|
||||
emqx_plugins:load_ext_plugin("./not_existed_path/")).
|
||||
|
||||
t_list(_) ->
|
||||
?assertMatch([{plugin, _, _, _, _, _, _, _} | _ ], emqx_plugins:list()).
|
||||
?assertMatch([{plugin, _, _, _, _, _, _} | _ ], emqx_plugins:list()).
|
||||
|
||||
t_find_plugin(_) ->
|
||||
?assertMatch({plugin, emqx_mini_plugin, _, _, _, _, _, _}, emqx_plugins:find_plugin(emqx_mini_plugin)),
|
||||
?assertMatch({plugin, emqx_hocon_plugin, _, _, _, _, _, _}, emqx_plugins:find_plugin(emqx_hocon_plugin)).
|
||||
|
||||
t_plugin_type(_) ->
|
||||
?assertEqual(auth, emqx_plugins:plugin_type(auth)),
|
||||
?assertEqual(protocol, emqx_plugins:plugin_type(protocol)),
|
||||
?assertEqual(backend, emqx_plugins:plugin_type(backend)),
|
||||
?assertEqual(bridge, emqx_plugins:plugin_type(bridge)),
|
||||
?assertEqual(feature, emqx_plugins:plugin_type(undefined)).
|
||||
|
||||
t_with_loaded_file(_) ->
|
||||
?assertMatch({error, _}, emqx_plugins:with_loaded_file("./not_existed_path/", fun(_) -> ok end)).
|
||||
|
||||
t_plugin_loaded(_) ->
|
||||
?assertEqual(ok, emqx_plugins:plugin_loaded(emqx_mini_plugin, false)),
|
||||
?assertEqual(ok, emqx_plugins:plugin_loaded(emqx_mini_plugin, true)),
|
||||
?assertEqual(ok, emqx_plugins:plugin_loaded(emqx_hocon_plugin, false)),
|
||||
?assertEqual(ok, emqx_plugins:plugin_loaded(emqx_hocon_plugin, true)).
|
||||
|
||||
t_plugin_unloaded(_) ->
|
||||
?assertEqual(ok, emqx_plugins:plugin_unloaded(emqx_mini_plugin, false)),
|
||||
?assertEqual(ok, emqx_plugins:plugin_unloaded(emqx_mini_plugin, true)),
|
||||
?assertEqual(ok, emqx_plugins:plugin_unloaded(emqx_hocon_plugin, false)),
|
||||
?assertEqual(ok, emqx_plugins:plugin_unloaded(emqx_hocon_plugin, true)).
|
||||
?assertMatch({plugin, emqx_mini_plugin, _, _, _, _, _}, emqx_plugins:find_plugin(emqx_mini_plugin)),
|
||||
?assertMatch({plugin, emqx_hocon_plugin, _, _, _, _, _}, emqx_plugins:find_plugin(emqx_hocon_plugin)).
|
||||
|
||||
t_plugin(_) ->
|
||||
try
|
||||
emqx_plugins:plugin(not_existed_plugin, undefined)
|
||||
emqx_plugins:plugin(not_existed_plugin)
|
||||
catch
|
||||
_Error:Reason:_Stacktrace ->
|
||||
?assertEqual({plugin_not_found,not_existed_plugin}, Reason)
|
||||
end,
|
||||
?assertMatch({plugin, emqx_mini_plugin, _, _, _, _, _, _}, emqx_plugins:plugin(emqx_mini_plugin, undefined)),
|
||||
?assertMatch({plugin, emqx_hocon_plugin, _, _, _, _, _, _}, emqx_plugins:plugin(emqx_hocon_plugin, undefined)).
|
||||
|
||||
t_filter_plugins(_) ->
|
||||
?assertEqual([name1, name2], emqx_plugins:filter_plugins([name1, {name2,true}, {name3, false}])).
|
||||
?assertMatch({plugin, emqx_mini_plugin, _, _, _, _, _}, emqx_plugins:plugin(emqx_mini_plugin)),
|
||||
?assertMatch({plugin, emqx_hocon_plugin, _, _, _, _, _}, emqx_plugins:plugin(emqx_hocon_plugin)).
|
||||
|
||||
t_load_plugin(_) ->
|
||||
ok = meck:new(application, [unstick, non_strict, passthrough, no_history]),
|
||||
|
@ -133,9 +97,9 @@ t_load_plugin(_) ->
|
|||
ok = meck:new(emqx_plugins, [unstick, non_strict, passthrough, no_history]),
|
||||
ok = meck:expect(emqx_plugins, generate_configs, fun(_) -> ok end),
|
||||
ok = meck:expect(emqx_plugins, apply_configs, fun(_) -> ok end),
|
||||
?assertMatch({error, _}, emqx_plugins:load_plugin(already_loaded_app, true)),
|
||||
?assertMatch(ok, emqx_plugins:load_plugin(normal, true)),
|
||||
?assertMatch({error,_}, emqx_plugins:load_plugin(error_app, true)),
|
||||
?assertMatch({error, _}, emqx_plugins:load_plugin(already_loaded_app)),
|
||||
?assertMatch(ok, emqx_plugins:load_plugin(normal)),
|
||||
?assertMatch({error,_}, emqx_plugins:load_plugin(error_app)),
|
||||
|
||||
ok = meck:unload(emqx_plugins),
|
||||
ok = meck:unload(application).
|
||||
|
@ -146,8 +110,8 @@ t_unload_plugin(_) ->
|
|||
(error_app) -> {error, error};
|
||||
(_) -> ok end),
|
||||
|
||||
?assertEqual(ok, emqx_plugins:unload_plugin(not_started_app, true)),
|
||||
?assertEqual(ok, emqx_plugins:unload_plugin(normal, true)),
|
||||
?assertEqual({error,error}, emqx_plugins:unload_plugin(error_app, true)),
|
||||
?assertEqual(ok, emqx_plugins:unload_plugin(not_started_app)),
|
||||
?assertEqual(ok, emqx_plugins:unload_plugin(normal)),
|
||||
?assertEqual({error,error}, emqx_plugins:unload_plugin(error_app)),
|
||||
|
||||
ok = meck:unload(application).
|
||||
|
|
|
@ -64,7 +64,7 @@ init_per_testcase(TestCase, Config) when
|
|||
end),
|
||||
%% Mock emqx_access_control
|
||||
ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]),
|
||||
ok = meck:expect(emqx_access_control, check_acl, fun(_, _, _) -> allow end),
|
||||
ok = meck:expect(emqx_access_control, authorize, fun(_, _, _) -> allow end),
|
||||
%% Mock emqx_hooks
|
||||
ok = meck:new(emqx_hooks, [passthrough, no_history, no_link]),
|
||||
ok = meck:expect(emqx_hooks, run, fun(_Hook, _Args) -> ok end),
|
||||
|
|
|
@ -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