Merge branch 'dev/v4.3.0' into resove-master-to-4.3-conflict
This commit is contained in:
commit
02fb66f4ce
|
@ -0,0 +1,5 @@
|
|||
MYSQL_TAG=8
|
||||
REDIS_TAG=6
|
||||
MONGO_TAG=4
|
||||
PGSQL_TAG=13
|
||||
LDAP_TAG=2.4.50
|
|
@ -0,0 +1,105 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
erlang:
|
||||
container_name: erlang
|
||||
image: emqx/build-env:erl23.2.2-ubuntu20.04
|
||||
depends_on:
|
||||
- mysql_server
|
||||
- redis_server
|
||||
- mongo_server
|
||||
- pgsql_server
|
||||
- ldap_server
|
||||
networks:
|
||||
- emqx_bridge
|
||||
volumes:
|
||||
- ../../.:/emqx
|
||||
working_dir: /emqx
|
||||
tty: true
|
||||
|
||||
mysql_server:
|
||||
container_name: mysql
|
||||
image: mysql:${MYSQL_TAG}
|
||||
restart: always
|
||||
ports:
|
||||
- 3306:3306
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: public
|
||||
MYSQL_DATABASE: mqtt
|
||||
command:
|
||||
--bind-address 0.0.0.0
|
||||
--default-authentication-plugin=mysql_native_password
|
||||
--character-set-server=utf8mb4
|
||||
--collation-server=utf8mb4_general_ci
|
||||
--explicit_defaults_for_timestamp=true
|
||||
--lower_case_table_names=1
|
||||
--max_allowed_packet=128M
|
||||
--skip-symbolic-links
|
||||
networks:
|
||||
- emqx_bridge
|
||||
|
||||
redis_server:
|
||||
container_name: redis
|
||||
image: redis:${REDIS_TAG}
|
||||
ports:
|
||||
- 6379:6379
|
||||
command:
|
||||
- redis-server
|
||||
- "--bind 0.0.0.0 ::"
|
||||
restart: always
|
||||
networks:
|
||||
- emqx_bridge
|
||||
|
||||
mongo_server:
|
||||
container_name: mongo
|
||||
image: mongo:${MONGO_TAG}
|
||||
ports:
|
||||
- 27017:27017
|
||||
restart: always
|
||||
environment:
|
||||
MONGO_INITDB_DATABASE: mqtt
|
||||
command:
|
||||
--ipv6
|
||||
--bind_ip_all
|
||||
networks:
|
||||
- emqx_bridge
|
||||
|
||||
pgsql_server:
|
||||
container_name: pgsql
|
||||
image: postgres:${PGSQL_TAG}
|
||||
ports:
|
||||
- 5432:5432
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: public
|
||||
POSTGRES_USER: root
|
||||
POSTGRES_DB: mqtt
|
||||
networks:
|
||||
- emqx_bridge
|
||||
|
||||
ldap_server:
|
||||
container_name: openldap
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: .ci/apps_tests/openldap/Dockerfile
|
||||
args:
|
||||
LDAP_TAG: ${LDAP_TAG}
|
||||
image: emqx-ldap:1.0
|
||||
ports:
|
||||
- 389:389
|
||||
restart: always
|
||||
networks:
|
||||
- emqx_bridge
|
||||
|
||||
networks:
|
||||
emqx_bridge:
|
||||
driver: bridge
|
||||
name: emqx_bridge
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.100.239.0/24
|
||||
gateway: 172.100.239.1
|
||||
- subnet: 2001:3200:3200::/64
|
||||
gateway: 2001:3200:3200::1
|
|
@ -0,0 +1,26 @@
|
|||
FROM buildpack-deps:stretch
|
||||
|
||||
ARG LDAP_TAG=2.4.50
|
||||
|
||||
RUN apt-get update && apt-get install -y groff groff-base
|
||||
RUN wget ftp://ftp.openldap.org/pub/OpenLDAP/openldap-release/openldap-${LDAP_TAG}.tgz \
|
||||
&& gunzip -c openldap-${LDAP_TAG}.tgz | tar xvfB - \
|
||||
&& cd openldap-${LDAP_TAG} \
|
||||
&& ./configure && make depend && make && make install \
|
||||
&& cd .. && rm -rf openldap-${LDAP_TAG}
|
||||
|
||||
COPY .ci/apps_tests/openldap/slapd.conf /usr/local/etc/openldap/slapd.conf
|
||||
COPY apps/emqx_auth_ldap/emqx.io.ldif /usr/local/etc/openldap/schema/emqx.io.ldif
|
||||
COPY apps/emqx_auth_ldap/emqx.schema /usr/local/etc/openldap/schema/emqx.schema
|
||||
COPY apps/emqx_auth_ldap/test/certs/*.pem /usr/local/etc/openldap/
|
||||
|
||||
RUN mkdir -p /usr/local/etc/openldap/data \
|
||||
&& slapadd -l /usr/local/etc/openldap/schema/emqx.io.ldif -f /usr/local/etc/openldap/slapd.conf
|
||||
|
||||
WORKDIR /usr/local/etc/openldap
|
||||
|
||||
EXPOSE 389 636
|
||||
|
||||
ENTRYPOINT ["/usr/local/libexec/slapd", "-h", "ldap:/// ldaps:///", "-d", "3", "-f", "/usr/local/etc/openldap/slapd.conf"]
|
||||
|
||||
CMD []
|
|
@ -0,0 +1,16 @@
|
|||
include /usr/local/etc/openldap/schema/core.schema
|
||||
include /usr/local/etc/openldap/schema/cosine.schema
|
||||
include /usr/local/etc/openldap/schema/inetorgperson.schema
|
||||
include /usr/local/etc/openldap/schema/ppolicy.schema
|
||||
include /usr/local/etc/openldap/schema/emqx.schema
|
||||
|
||||
TLSCACertificateFile /usr/local/etc/openldap/cacert.pem
|
||||
TLSCertificateFile /usr/local/etc/openldap/cert.pem
|
||||
TLSCertificateKeyFile /usr/local/etc/openldap/key.pem
|
||||
|
||||
database bdb
|
||||
suffix "dc=emqx,dc=io"
|
||||
rootdn "cn=root,dc=emqx,dc=io"
|
||||
rootpw {SSHA}eoF7NhNrejVYYyGHqnt+MdKNBh4r1w3W
|
||||
|
||||
directory /usr/local/etc/openldap/data
|
|
@ -0,0 +1,12 @@
|
|||
ARG BUILD_FROM=emqx/build-env:erl23.2.2-ubuntu20.04
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
ARG EMQX_NAME=emqx
|
||||
|
||||
COPY . /emqx
|
||||
|
||||
WORKDIR /emqx
|
||||
|
||||
RUN make ${EMQX_NAME}-pkg || cat rebar3.crashdump
|
||||
|
||||
RUN /emqx/.ci/build_packages/tests.sh
|
|
@ -0,0 +1,173 @@
|
|||
#!/bin/sh
|
||||
set -x -e -u
|
||||
export EMQX_NAME=${EMQX_NAME:-"emqx"}
|
||||
export PACKAGE_PATH="/emqx/_packages/${EMQX_NAME}"
|
||||
export RELUP_PACKAGE_PATH="/emqx/relup_packages/${EMQX_NAME}"
|
||||
# export EMQX_NODE_NAME="emqx-on-$(uname -m)@127.0.0.1"
|
||||
# export EMQX_NODE_COOKIE=$(date +%s%N)
|
||||
|
||||
emqx_prepare(){
|
||||
mkdir -p ${PACKAGE_PATH}
|
||||
|
||||
if [ ! -d "/paho-mqtt-testing" ]; then
|
||||
git clone -b develop-4.0 https://github.com/emqx/paho.mqtt.testing.git /paho-mqtt-testing
|
||||
fi
|
||||
pip3 install pytest
|
||||
}
|
||||
|
||||
emqx_test(){
|
||||
cd ${PACKAGE_PATH}
|
||||
|
||||
for var in $(ls $PACKAGE_PATH/${EMQX_NAME}-*);do
|
||||
case ${var##*.} in
|
||||
"zip")
|
||||
packagename=`basename ${PACKAGE_PATH}/${EMQX_NAME}-*.zip`
|
||||
unzip -q ${PACKAGE_PATH}/$packagename
|
||||
sed -i "/zone.external.server_keepalive/c zone.external.server_keepalive = 60" ${PACKAGE_PATH}/emqx/etc/emqx.conf
|
||||
sed -i "/mqtt.max_topic_alias/c mqtt.max_topic_alias = 10" ${PACKAGE_PATH}/emqx/etc/emqx.conf
|
||||
sed -i '/emqx_telemetry/d' ${PACKAGE_PATH}/emqx/data/loaded_plugins
|
||||
|
||||
if [ ! -z $(echo ${EMQX_DEPS_DEFAULT_VSN#v} | grep -oE "[0-9]+\.[0-9]+(\.[0-9]+)?-(alpha|beta|rc)\.[0-9]") ]; then
|
||||
if [ ! -d ${PACKAGE_PATH}/emqx/lib/emqx-${EMQX_DEPS_DEFAULT_VSN#v} ] || [ ! -d ${PACKAGE_PATH}/emqx/releases/${EMQX_DEPS_DEFAULT_VSN#v} ] ;then
|
||||
echo "emqx zip version error"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "running ${packagename} start"
|
||||
${PACKAGE_PATH}/emqx/bin/emqx start || tail ${PACKAGE_PATH}/emqx/log/erlang.log.1
|
||||
IDLE_TIME=0
|
||||
while [ -z "$(${PACKAGE_PATH}/emqx/bin/emqx_ctl status |grep 'is running'|awk '{print $1}')" ]
|
||||
do
|
||||
if [ $IDLE_TIME -gt 10 ]
|
||||
then
|
||||
echo "emqx running error"
|
||||
exit 1
|
||||
fi
|
||||
sleep 10
|
||||
IDLE_TIME=$((IDLE_TIME+1))
|
||||
done
|
||||
pytest -v /paho-mqtt-testing/interoperability/test_client/V5/test_connect.py::test_basic
|
||||
${PACKAGE_PATH}/emqx/bin/emqx stop
|
||||
echo "running ${packagename} stop"
|
||||
rm -rf ${PACKAGE_PATH}/emqx
|
||||
;;
|
||||
"deb")
|
||||
packagename=`basename ${PACKAGE_PATH}/${EMQX_NAME}-*.deb`
|
||||
dpkg -i ${PACKAGE_PATH}/$packagename
|
||||
if [ $(dpkg -l |grep emqx |awk '{print $1}') != "ii" ]
|
||||
then
|
||||
echo "package install error"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "running ${packagename} start"
|
||||
running_test
|
||||
echo "running ${packagename} stop"
|
||||
|
||||
dpkg -r ${EMQX_NAME}
|
||||
if [ $(dpkg -l |grep emqx |awk '{print $1}') != "rc" ]
|
||||
then
|
||||
echo "package remove error"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dpkg -P ${EMQX_NAME}
|
||||
if [ ! -z "$(dpkg -l |grep emqx)" ]
|
||||
then
|
||||
echo "package uninstall error"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
"rpm")
|
||||
packagename=`basename ${PACKAGE_PATH}/${EMQX_NAME}-*.rpm`
|
||||
rpm -ivh ${PACKAGE_PATH}/$packagename
|
||||
if [ -z $(rpm -q emqx | grep -o emqx) ];then
|
||||
echo "package install error"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "running ${packagename} start"
|
||||
running_test
|
||||
echo "running ${packagename} stop"
|
||||
|
||||
rpm -e ${EMQX_NAME}
|
||||
if [ "$(rpm -q emqx)" != "package emqx is not installed" ];then
|
||||
echo "package uninstall error"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
running_test(){
|
||||
if [ ! -z $(echo ${EMQX_DEPS_DEFAULT_VSN#v} | grep -oE "[0-9]+\.[0-9]+(\.[0-9]+)?-(alpha|beta|rc)\.[0-9]") ]; then
|
||||
if [ ! -d /usr/lib/emqx/lib/emqx-${EMQX_DEPS_DEFAULT_VSN#v} ] || [ ! -d /usr/lib/emqx/releases/${EMQX_DEPS_DEFAULT_VSN#v} ];then
|
||||
echo "emqx package version error"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
sed -i "/zone.external.server_keepalive/c zone.external.server_keepalive = 60" /etc/emqx/emqx.conf
|
||||
sed -i "/mqtt.max_topic_alias/c mqtt.max_topic_alias = 10" /etc/emqx/emqx.conf
|
||||
sed -i '/emqx_telemetry/d' /var/lib/emqx/loaded_plugins
|
||||
|
||||
emqx start || tail /var/log/emqx/erlang.log.1
|
||||
IDLE_TIME=0
|
||||
while [ -z "$(emqx_ctl status |grep 'is running'|awk '{print $1}')" ]
|
||||
do
|
||||
if [ $IDLE_TIME -gt 10 ]
|
||||
then
|
||||
echo "emqx running error"
|
||||
exit 1
|
||||
fi
|
||||
sleep 10
|
||||
IDLE_TIME=$((IDLE_TIME+1))
|
||||
done
|
||||
pytest -v /paho-mqtt-testing/interoperability/test_client/V5/test_connect.py::test_basic
|
||||
emqx stop || kill $(ps -ef | grep -E '\-progname\s.+emqx\s' |awk '{print $2}')
|
||||
|
||||
if [ $(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g') = ubuntu ] \
|
||||
|| [ $(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g') = debian ] \
|
||||
|| [ $(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g') = raspbian ];then
|
||||
service emqx start || tail /var/log/emqx/erlang.log.1
|
||||
IDLE_TIME=0
|
||||
while [ -z "$(emqx_ctl status |grep 'is running'|awk '{print $1}')" ]
|
||||
do
|
||||
if [ $IDLE_TIME -gt 10 ]
|
||||
then
|
||||
echo "emqx service error"
|
||||
exit 1
|
||||
fi
|
||||
sleep 10
|
||||
IDLE_TIME=$((IDLE_TIME+1))
|
||||
done
|
||||
service emqx stop
|
||||
fi
|
||||
}
|
||||
|
||||
relup_test(){
|
||||
if [ -d ${RELUP_PACKAGE_PATH} ];then
|
||||
cd ${RELUP_PACKAGE_PATH }
|
||||
|
||||
for var in $(ls ${EMQX_NAME}-*-$(uname -m).zip);do
|
||||
packagename=`basename ${var}`
|
||||
unzip $packagename
|
||||
./emqx/bin/emqx start
|
||||
./emqx/bin/emqx_ctl status
|
||||
./emqx/bin/emqx versions
|
||||
cp ${PACKAGE_PATH}/${EMQX_NAME}-*-${EMQX_DEPS_DEFAULT_VSN#v}-$(uname -m).zip ./emqx/releases
|
||||
./emqx/bin/emqx install ${EMQX_DEPS_DEFAULT_VSN#v}
|
||||
[ $(./emqx/bin/emqx versions |grep permanent | grep -oE "[0-9].[0-9].[0-9]") = ${EMQX_DEPS_DEFAULT_VSN#v} ] || exit 1
|
||||
./emqx/bin/emqx_ctl status
|
||||
./emqx/bin/emqx stop
|
||||
rm -rf emqx
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
emqx_prepare
|
||||
emqx_test
|
||||
relup_test
|
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Author: Stefan Buck
|
||||
# License: MIT
|
||||
# https://gist.github.com/stefanbuck/ce788fee19ab6eb0b4447a85fc99f447
|
||||
#
|
||||
#
|
||||
# This script accepts the following parameters:
|
||||
#
|
||||
# * owner
|
||||
# * repo
|
||||
# * tag
|
||||
# * filename
|
||||
# * github_api_token
|
||||
#
|
||||
# Script to upload a release asset using the GitHub API v3.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# upload-github-release-asset.sh github_api_token=TOKEN owner=stefanbuck repo=playground tag=v0.1.0 filename=./build.zip
|
||||
#
|
||||
|
||||
# Check dependencies.
|
||||
set -e
|
||||
xargs=$(which gxargs || which xargs)
|
||||
|
||||
# Validate settings.
|
||||
[ "$TRACE" ] && set -x
|
||||
|
||||
CONFIG=$@
|
||||
|
||||
for line in $CONFIG; do
|
||||
eval "$line"
|
||||
done
|
||||
|
||||
# Define variables.
|
||||
GH_API="https://api.github.com"
|
||||
GH_REPO="$GH_API/repos/$owner/$repo"
|
||||
GH_TAGS="$GH_REPO/releases/tags/$tag"
|
||||
AUTH="Authorization: token $github_api_token"
|
||||
WGET_ARGS="--content-disposition --auth-no-challenge --no-cookie"
|
||||
CURL_ARGS="-LJO#"
|
||||
|
||||
if [[ "$tag" == 'LATEST' ]]; then
|
||||
GH_TAGS="$GH_REPO/releases/latest"
|
||||
fi
|
||||
|
||||
# Validate token.
|
||||
curl -o /dev/null -sH "$AUTH" $GH_REPO || { echo "Error: Invalid repo, token or network issue!"; exit 1; }
|
||||
|
||||
# Read asset tags.
|
||||
response=$(curl -sH "$AUTH" $GH_TAGS)
|
||||
|
||||
# Get ID of the asset based on given filename.
|
||||
eval $(echo "$response" | grep -m 1 "id.:" | grep -w id | tr : = | tr -cd '[[:alnum:]]=')
|
||||
[ "$id" ] || { echo "Error: Failed to get release id for tag: $tag"; echo "$response" | awk 'length($0)<100' >&2; exit 1; }
|
||||
|
||||
# Upload asset
|
||||
# Construct url
|
||||
GH_ASSET="https://uploads.github.com/repos/$owner/$repo/releases/$id/assets?name=$(basename $filename)"
|
||||
|
||||
curl "$GITHUB_OAUTH_BASIC" --data-binary @"$filename" -H "Authorization: token $github_api_token" -H "Content-Type: application/octet-stream" $GH_ASSET
|
|
@ -0,0 +1,5 @@
|
|||
MYSQL_TAG=5.7
|
||||
REDIS_TAG=6
|
||||
MONGO_TAG=4.1
|
||||
PGSQL_TAG=11
|
||||
LDAP_TAG=2.4.50
|
|
@ -0,0 +1,41 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
erlang:
|
||||
container_name: erlang
|
||||
image: emqx/build-env:erl23.2.2-ubuntu20.04
|
||||
depends_on:
|
||||
- ldap_server
|
||||
networks:
|
||||
- emqx_bridge
|
||||
volumes:
|
||||
- ../../.:/emqx
|
||||
working_dir: /emqx
|
||||
tty: true
|
||||
|
||||
ldap_server:
|
||||
container_name: ldap
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: .ci/compatibility_tests/openldap/Dockerfile
|
||||
args:
|
||||
LDAP_TAG: ${LDAP_TAG}
|
||||
image: openldap
|
||||
ports:
|
||||
- 389:389
|
||||
restart: always
|
||||
networks:
|
||||
- emqx_bridge
|
||||
|
||||
networks:
|
||||
emqx_bridge:
|
||||
driver: bridge
|
||||
name: emqx_bridge
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.100.239.0/24
|
||||
gateway: 172.100.239.1
|
||||
- subnet: 2001:3200:3200::/64
|
||||
gateway: 2001:3200:3200::1
|
|
@ -0,0 +1,43 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
erlang:
|
||||
container_name: erlang
|
||||
image: emqx/build-env:erl23.2.2-ubuntu20.04
|
||||
volumes:
|
||||
- ../../:/emqx
|
||||
working_dir: /emqx
|
||||
networks:
|
||||
- emqx_bridge
|
||||
depends_on:
|
||||
- mongo_server
|
||||
tty: true
|
||||
|
||||
mongo_server:
|
||||
container_name: mongo
|
||||
image: mongo:${MONGO_TAG}
|
||||
restart: always
|
||||
environment:
|
||||
MONGO_INITDB_DATABASE: mqtt
|
||||
volumes:
|
||||
- ../../apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE_data/mongodb.pem/:/etc/certs/mongodb.pem
|
||||
networks:
|
||||
- emqx_bridge
|
||||
command:
|
||||
--ipv6
|
||||
--bind_ip_all
|
||||
--sslMode requireSSL
|
||||
--sslPEMKeyFile /etc/certs/mongodb.pem
|
||||
|
||||
networks:
|
||||
emqx_bridge:
|
||||
driver: bridge
|
||||
name: emqx_bridge
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.100.100.0/24
|
||||
gateway: 172.100.100.1
|
||||
- subnet: 2001:3200:3200::/64
|
||||
gateway: 2001:3200:3200::1
|
|
@ -0,0 +1,39 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
erlang:
|
||||
container_name: erlang
|
||||
image: emqx/build-env:erl23.2.2-ubuntu20.04
|
||||
volumes:
|
||||
- ../..:/emqx
|
||||
working_dir: /emqx
|
||||
networks:
|
||||
- emqx_bridge
|
||||
depends_on:
|
||||
- mongo_server
|
||||
tty: true
|
||||
|
||||
mongo_server:
|
||||
container_name: mongo
|
||||
image: mongo:${MONGO_TAG}
|
||||
restart: always
|
||||
environment:
|
||||
MONGO_INITDB_DATABASE: mqtt
|
||||
networks:
|
||||
- emqx_bridge
|
||||
command:
|
||||
--ipv6
|
||||
--bind_ip_all
|
||||
|
||||
networks:
|
||||
emqx_bridge:
|
||||
driver: bridge
|
||||
name: emqx_bridge
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.100.100.0/24
|
||||
gateway: 172.100.100.1
|
||||
- subnet: 2001:3200:3200::/64
|
||||
gateway: 2001:3200:3200::1
|
|
@ -0,0 +1,53 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
erlang:
|
||||
container_name: erlang
|
||||
image: emqx/build-env:erl23.2.2-ubuntu20.04
|
||||
volumes:
|
||||
- ../../:/emqx
|
||||
working_dir: /emqx
|
||||
networks:
|
||||
- emqx_bridge
|
||||
depends_on:
|
||||
- mysql_server
|
||||
tty: true
|
||||
|
||||
mysql_server:
|
||||
container_name: mysql
|
||||
image: mysql:${MYSQL_TAG}
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: public
|
||||
MYSQL_DATABASE: mqtt
|
||||
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
|
||||
networks:
|
||||
- emqx_bridge
|
||||
command:
|
||||
--bind-address "::"
|
||||
--default-authentication-plugin=mysql_native_password
|
||||
--character-set-server=utf8mb4
|
||||
--collation-server=utf8mb4_general_ci
|
||||
--explicit_defaults_for_timestamp=true
|
||||
--lower_case_table_names=1
|
||||
--max_allowed_packet=128M
|
||||
--skip-symbolic-links
|
||||
--ssl-ca=/etc/certs/ca.pem
|
||||
--ssl-cert=/etc/certs/server-cert.pem
|
||||
--ssl-key=/etc/certs/server-key.pem
|
||||
|
||||
networks:
|
||||
emqx_bridge:
|
||||
driver: bridge
|
||||
name: emqx_bridge
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.100.100.0/24
|
||||
gateway: 172.100.100.1
|
||||
- subnet: 2001:3200:3200::/64
|
||||
gateway: 2001:3200:3200::1
|
|
@ -0,0 +1,46 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
erlang:
|
||||
container_name: erlang
|
||||
image: emqx/build-env:erl23.2.2-ubuntu20.04
|
||||
volumes:
|
||||
- ../../:/emqx
|
||||
working_dir: /emqx
|
||||
networks:
|
||||
- emqx_bridge
|
||||
depends_on:
|
||||
- mysql_server
|
||||
tty: true
|
||||
|
||||
mysql_server:
|
||||
container_name: mysql
|
||||
image: mysql:${MYSQL_TAG}
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: public
|
||||
MYSQL_DATABASE: mqtt
|
||||
networks:
|
||||
- emqx_bridge
|
||||
command:
|
||||
--bind-address "::"
|
||||
--default-authentication-plugin=mysql_native_password
|
||||
--character-set-server=utf8mb4
|
||||
--collation-server=utf8mb4_general_ci
|
||||
--explicit_defaults_for_timestamp=true
|
||||
--lower_case_table_names=1
|
||||
--max_allowed_packet=128M
|
||||
--skip-symbolic-links
|
||||
|
||||
networks:
|
||||
emqx_bridge:
|
||||
driver: bridge
|
||||
name: emqx_bridge
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.100.100.0/24
|
||||
gateway: 172.100.100.1
|
||||
- subnet: 2001:3200:3200::/64
|
||||
gateway: 2001:3200:3200::1
|
|
@ -0,0 +1,57 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
erlang:
|
||||
container_name: erlang
|
||||
image: emqx/build-env:erl23.2.2-ubuntu20.04
|
||||
volumes:
|
||||
- ../../:/emqx
|
||||
working_dir: /emqx
|
||||
networks:
|
||||
- emqx_bridge
|
||||
depends_on:
|
||||
- pgsql_server
|
||||
tty: true
|
||||
|
||||
pgsql_server:
|
||||
container_name: pgsql
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: .ci/compatibility_tests/pgsql/Dockerfile
|
||||
args:
|
||||
POSTGRES_USER: postgres
|
||||
BUILD_FROM: postgres:${PGSQL_TAG}
|
||||
image: emqx_pgsql:${PGSQL_TAG}
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_DB: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
command:
|
||||
- -c
|
||||
- ssl=on
|
||||
- -c
|
||||
- ssl_cert_file=/var/lib/postgresql/server.crt
|
||||
- -c
|
||||
- ssl_key_file=/var/lib/postgresql/server.key
|
||||
- -c
|
||||
- ssl_ca_file=/var/lib/postgresql/root.crt
|
||||
- -c
|
||||
- hba_file=/var/lib/postgresql/pg_hba.conf
|
||||
networks:
|
||||
- emqx_bridge
|
||||
|
||||
networks:
|
||||
emqx_bridge:
|
||||
driver: bridge
|
||||
name: emqx_bridge
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.100.100.0/24
|
||||
gateway: 172.100.100.1
|
||||
- subnet: 2001:3200:3200::/64
|
||||
gateway: 2001:3200:3200::1
|
|
@ -0,0 +1,38 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
erlang:
|
||||
container_name: erlang
|
||||
image: emqx/build-env:erl23.2.2-ubuntu20.04
|
||||
volumes:
|
||||
- ../../:/emqx
|
||||
working_dir: /emqx
|
||||
networks:
|
||||
- emqx_bridge
|
||||
depends_on:
|
||||
- pgsql_server
|
||||
tty: true
|
||||
|
||||
pgsql_server:
|
||||
container_name: pgsql
|
||||
image: postgres:${PGSQL_TAG}
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: public
|
||||
POSTGRES_USER: root
|
||||
POSTGRES_DB: mqtt
|
||||
networks:
|
||||
- emqx_bridge
|
||||
|
||||
networks:
|
||||
emqx_bridge:
|
||||
driver: bridge
|
||||
name: emqx_bridge
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.100.100.0/24
|
||||
gateway: 172.100.100.1
|
||||
- subnet: 2001:3200:3200::/64
|
||||
gateway: 2001:3200:3200::1
|
|
@ -0,0 +1,41 @@
|
|||
version: '2.4'
|
||||
# network configuration is limited in version 3
|
||||
# https://github.com/docker/compose/issues/4958
|
||||
|
||||
services:
|
||||
erlang:
|
||||
container_name: erlang
|
||||
image: emqx/build-env:erl23.2.2-ubuntu20.04
|
||||
volumes:
|
||||
- ../..:/emqx
|
||||
networks:
|
||||
- app_net
|
||||
depends_on:
|
||||
- redis_cluster
|
||||
working_dir: /emqx
|
||||
tty: true
|
||||
|
||||
redis_cluster:
|
||||
container_name: redis
|
||||
image: redis:${REDIS_TAG}
|
||||
volumes:
|
||||
- ../../apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs:/tls
|
||||
- ./redis/:/data/conf
|
||||
command: bash -c "/bin/bash /data/conf/redis.sh --node cluster --tls-enabled && while true; do echo 1; sleep 1; done"
|
||||
networks:
|
||||
app_net:
|
||||
# Assign a public address. Erlang container cannot find cluster nodes by network-scoped alias (redis_cluster).
|
||||
ipv4_address: 172.16.239.10
|
||||
ipv6_address: 2001:3200:3200::20
|
||||
|
||||
networks:
|
||||
app_net:
|
||||
driver: bridge
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.16.239.0/24
|
||||
gateway: 172.16.239.1
|
||||
- subnet: 2001:3200:3200::/64
|
||||
gateway: 2001:3200:3200::1
|
|
@ -0,0 +1,40 @@
|
|||
version: '2.4'
|
||||
# network configuration is limited in version 3
|
||||
# https://github.com/docker/compose/issues/4958
|
||||
|
||||
services:
|
||||
erlang:
|
||||
container_name: erlang
|
||||
image: emqx/build-env:erl23.2.2-ubuntu20.04
|
||||
volumes:
|
||||
- ../..:/emqx
|
||||
networks:
|
||||
- app_net
|
||||
depends_on:
|
||||
- redis_cluster
|
||||
working_dir: /emqx
|
||||
tty: true
|
||||
|
||||
redis_cluster:
|
||||
image: redis:${REDIS_TAG}
|
||||
container_name: redis
|
||||
volumes:
|
||||
- ./redis/:/data/conf
|
||||
command: bash -c "/bin/bash /data/conf/redis.sh --node cluster && while true; do echo 1; sleep 1; done"
|
||||
networks:
|
||||
app_net:
|
||||
# Assign a public address. Erlang container cannot find cluster nodes by network-scoped alias (redis_cluster).
|
||||
ipv4_address: 172.16.239.10
|
||||
ipv6_address: 2001:3200:3200::20
|
||||
|
||||
networks:
|
||||
app_net:
|
||||
driver: bridge
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.16.239.0/24
|
||||
gateway: 172.16.239.1
|
||||
- subnet: 2001:3200:3200::/64
|
||||
gateway: 2001:3200:3200::1
|
|
@ -0,0 +1,40 @@
|
|||
version: '2.4'
|
||||
# network configuration is limited in version 3
|
||||
# https://github.com/docker/compose/issues/4958
|
||||
|
||||
services:
|
||||
erlang:
|
||||
container_name: erlang
|
||||
image: emqx/build-env:erl23.2.2-ubuntu20.04
|
||||
volumes:
|
||||
- ../..:/emqx
|
||||
networks:
|
||||
- app_net
|
||||
depends_on:
|
||||
- redis_cluster
|
||||
working_dir: /emqx
|
||||
tty: true
|
||||
|
||||
redis_cluster:
|
||||
container_name: redis
|
||||
image: redis:${REDIS_TAG}
|
||||
volumes:
|
||||
- ./redis/:/data/conf
|
||||
command: bash -c "/bin/bash /data/conf/redis.sh --node sentinel && while true; do echo 1; sleep 1; done"
|
||||
networks:
|
||||
app_net:
|
||||
# Assign a public address. Erlang container cannot find cluster nodes by network-scoped alias (redis_cluster).
|
||||
ipv4_address: 172.16.239.10
|
||||
ipv6_address: 2001:3200:3200::20
|
||||
|
||||
networks:
|
||||
app_net:
|
||||
driver: bridge
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.16.239.0/24
|
||||
gateway: 172.16.239.1
|
||||
- subnet: 2001:3200:3200::/64
|
||||
gateway: 2001:3200:3200::1
|
|
@ -0,0 +1,43 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
erlang:
|
||||
container_name: erlang
|
||||
image: emqx/build-env:erl23.2.2-ubuntu20.04
|
||||
volumes:
|
||||
- ../..:/emqx
|
||||
networks:
|
||||
- emqx_bridge
|
||||
depends_on:
|
||||
- redis_server
|
||||
working_dir: /emqx
|
||||
tty: true
|
||||
|
||||
redis_server:
|
||||
container_name: redis
|
||||
image: redis:${REDIS_TAG}
|
||||
volumes:
|
||||
- ../../apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs:/tls
|
||||
command:
|
||||
- redis-server
|
||||
- "--bind 0.0.0.0 ::"
|
||||
- --tls-port 6380
|
||||
- --tls-cert-file /tls/redis.crt
|
||||
- --tls-key-file /tls/redis.key
|
||||
- --tls-ca-cert-file /tls/ca.crt
|
||||
restart: always
|
||||
networks:
|
||||
- emqx_bridge
|
||||
|
||||
networks:
|
||||
emqx_bridge:
|
||||
driver: bridge
|
||||
name: emqx_bridge
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.100.100.0/24
|
||||
gateway: 172.100.100.1
|
||||
- subnet: 2001:3200:3200::/64
|
||||
gateway: 2001:3200:3200::1
|
|
@ -0,0 +1,37 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
erlang:
|
||||
container_name: erlang
|
||||
image: emqx/build-env:erl23.2.2-ubuntu20.04
|
||||
volumes:
|
||||
- ../..:/emqx
|
||||
networks:
|
||||
- emqx_bridge
|
||||
depends_on:
|
||||
- redis_server
|
||||
working_dir: /emqx
|
||||
tty: true
|
||||
|
||||
redis_server:
|
||||
container_name: redis
|
||||
image: redis:${REDIS_TAG}
|
||||
command:
|
||||
- redis-server
|
||||
- "--bind 0.0.0.0 ::"
|
||||
restart: always
|
||||
networks:
|
||||
- emqx_bridge
|
||||
|
||||
networks:
|
||||
emqx_bridge:
|
||||
driver: bridge
|
||||
name: emqx_bridge
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.100.100.0/24
|
||||
gateway: 172.100.100.1
|
||||
- subnet: 2001:3200:3200::/64
|
||||
gateway: 2001:3200:3200::1
|
|
@ -0,0 +1,26 @@
|
|||
FROM buildpack-deps:stretch
|
||||
|
||||
ARG LDAP_TAG=2.4.50
|
||||
|
||||
RUN apt-get update && apt-get install -y groff groff-base
|
||||
RUN wget ftp://ftp.openldap.org/pub/OpenLDAP/openldap-release/openldap-${LDAP_TAG}.tgz \
|
||||
&& gunzip -c openldap-${LDAP_TAG}.tgz | tar xvfB - \
|
||||
&& cd openldap-${LDAP_TAG} \
|
||||
&& ./configure && make depend && make && make install \
|
||||
&& cd .. && rm -rf openldap-${LDAP_TAG}
|
||||
|
||||
COPY .ci/compatibility_tests/openldap/slapd.conf /usr/local/etc/openldap/slapd.conf
|
||||
COPY apps/emqx_auth_ldap/emqx.io.ldif /usr/local/etc/openldap/schema/emqx.io.ldif
|
||||
COPY apps/emqx_auth_ldap/emqx.schema /usr/local/etc/openldap/schema/emqx.schema
|
||||
COPY apps/emqx_auth_ldap/test/certs/*.pem /usr/local/etc/openldap/
|
||||
|
||||
RUN mkdir -p /usr/local/etc/openldap/data \
|
||||
&& slapadd -l /usr/local/etc/openldap/schema/emqx.io.ldif -f /usr/local/etc/openldap/slapd.conf
|
||||
|
||||
WORKDIR /usr/local/etc/openldap
|
||||
|
||||
EXPOSE 389 636
|
||||
|
||||
ENTRYPOINT ["/usr/local/libexec/slapd", "-h", "ldap:/// ldaps:///", "-d", "3", "-f", "/usr/local/etc/openldap/slapd.conf"]
|
||||
|
||||
CMD []
|
|
@ -0,0 +1,16 @@
|
|||
include /usr/local/etc/openldap/schema/core.schema
|
||||
include /usr/local/etc/openldap/schema/cosine.schema
|
||||
include /usr/local/etc/openldap/schema/inetorgperson.schema
|
||||
include /usr/local/etc/openldap/schema/ppolicy.schema
|
||||
include /usr/local/etc/openldap/schema/emqx.schema
|
||||
|
||||
TLSCACertificateFile /usr/local/etc/openldap/cacert.pem
|
||||
TLSCertificateFile /usr/local/etc/openldap/cert.pem
|
||||
TLSCertificateKeyFile /usr/local/etc/openldap/key.pem
|
||||
|
||||
database bdb
|
||||
suffix "dc=emqx,dc=io"
|
||||
rootdn "cn=root,dc=emqx,dc=io"
|
||||
rootpw {SSHA}eoF7NhNrejVYYyGHqnt+MdKNBh4r1w3W
|
||||
|
||||
directory /usr/local/etc/openldap/data
|
|
@ -0,0 +1,12 @@
|
|||
ARG BUILD_FROM=postgres:11
|
||||
FROM ${BUILD_FROM}
|
||||
ARG POSTGRES_USER=postgres
|
||||
COPY --chown=$POSTGRES_USER .ci/compatibility_tests/pgsql/pg_hba.conf /var/lib/postgresql/pg_hba.conf
|
||||
COPY --chown=$POSTGRES_USER apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server.key /var/lib/postgresql/server.key
|
||||
COPY --chown=$POSTGRES_USER apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/server.crt /var/lib/postgresql/server.crt
|
||||
COPY --chown=$POSTGRES_USER apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/root.crt /var/lib/postgresql/root.crt
|
||||
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
|
||||
RUN chmod 600 /var/lib/postgresql/root.crt
|
||||
EXPOSE 5432
|
|
@ -0,0 +1,9 @@
|
|||
# TYPE DATABASE USER CIDR-ADDRESS METHOD
|
||||
local all all trust
|
||||
host all all 0.0.0.0/0 trust
|
||||
host all all ::/0 trust
|
||||
hostssl all all 0.0.0.0/0 cert
|
||||
hostssl all all ::/0 cert
|
||||
|
||||
hostssl all www-data 0.0.0.0/0 cert clientcert=1
|
||||
hostssl all postgres 0.0.0.0/0 cert clientcert=1
|
|
@ -0,0 +1,5 @@
|
|||
daemonize yes
|
||||
bind 0.0.0.0 ::
|
||||
tls-cert-file /tls/redis.crt
|
||||
tls-key-file /tls/redis.key
|
||||
tls-ca-cert-file /tls/ca.crt
|
|
@ -0,0 +1,2 @@
|
|||
daemonize yes
|
||||
bind 0.0.0.0 ::
|
|
@ -0,0 +1,87 @@
|
|||
#!/bin/bash
|
||||
|
||||
node=single
|
||||
tls=false
|
||||
while [[ $# -gt 0 ]]
|
||||
do
|
||||
key="$1"
|
||||
|
||||
case $key in
|
||||
-n|--node)
|
||||
node="$2"
|
||||
shift # past argument
|
||||
shift # past value
|
||||
;;
|
||||
-t|--tls-enabled)
|
||||
tls="$2"
|
||||
shift # past argument
|
||||
shift # past value
|
||||
;;
|
||||
*)
|
||||
shift # past argument
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
rm -f \
|
||||
/data/conf/r7000i.log \
|
||||
/data/conf/r7001i.log \
|
||||
/data/conf/r7002i.log \
|
||||
/data/conf/nodes.7000.conf \
|
||||
/data/conf/nodes.7001.conf \
|
||||
/data/conf/nodes.7002.conf ;
|
||||
|
||||
if [ ${node} = "cluster" ] ; then
|
||||
if $tls ; then
|
||||
redis-server /data/conf/redis-tls.conf --port 7000 --cluster-config-file /data/conf/nodes.7000.conf \
|
||||
--tls-port 8000 --cluster-enabled yes ;
|
||||
redis-server /data/conf/redis-tls.conf --port 7001 --cluster-config-file /data/conf/nodes.7001.conf \
|
||||
--tls-port 8001 --cluster-enabled yes;
|
||||
redis-server /data/conf/redis-tls.conf --port 7002 --cluster-config-file /data/conf/nodes.7002.conf \
|
||||
--tls-port 8002 --cluster-enabled yes;
|
||||
else
|
||||
redis-server /data/conf/redis.conf --port 7000 --cluster-config-file /data/conf/nodes.7000.conf --cluster-enabled yes;
|
||||
redis-server /data/conf/redis.conf --port 7001 --cluster-config-file /data/conf/nodes.7001.conf --cluster-enabled yes;
|
||||
redis-server /data/conf/redis.conf --port 7002 --cluster-config-file /data/conf/nodes.7002.conf --cluster-enabled yes;
|
||||
fi
|
||||
elif [ ${node} = "sentinel" ] ; then
|
||||
redis-server /data/conf/redis.conf --port 7000 --cluster-config-file /data/conf/nodes.7000.conf \
|
||||
--cluster-enabled no;
|
||||
redis-server /data/conf/redis.conf --port 7001 --cluster-config-file /data/conf/nodes.7001.conf \
|
||||
--cluster-enabled no --slaveof 172.16.239.10 7000;
|
||||
redis-server /data/conf/redis.conf --port 7002 --cluster-config-file /data/conf/nodes.7002.conf \
|
||||
--cluster-enabled no --slaveof 172.16.239.10 7000;
|
||||
fi
|
||||
REDIS_LOAD_FLG=true;
|
||||
|
||||
while $REDIS_LOAD_FLG;
|
||||
do
|
||||
sleep 1;
|
||||
redis-cli -p 7000 info 1> /data/conf/r7000i.log 2> /dev/null;
|
||||
if [ -s /data/conf/r7000i.log ]; then
|
||||
:
|
||||
else
|
||||
continue;
|
||||
fi
|
||||
redis-cli -p 7001 info 1> /data/conf/r7001i.log 2> /dev/null;
|
||||
if [ -s /data/conf/r7001i.log ]; then
|
||||
:
|
||||
else
|
||||
continue;
|
||||
fi
|
||||
redis-cli -p 7002 info 1> /data/conf/r7002i.log 2> /dev/null;
|
||||
if [ -s /data/conf/r7002i.log ]; then
|
||||
:
|
||||
else
|
||||
continue;
|
||||
fi
|
||||
if [ ${node} = "cluster" ] ; then
|
||||
yes "yes" | redis-cli --cluster create 172.16.239.10:7000 172.16.239.10:7001 172.16.239.10:7002;
|
||||
elif [ ${node} = "sentinel" ] ; then
|
||||
cp /data/conf/sentinel.conf /_sentinel.conf
|
||||
redis-server /_sentinel.conf --sentinel;
|
||||
fi
|
||||
REDIS_LOAD_FLG=false;
|
||||
done
|
||||
|
||||
exit 0;
|
|
@ -0,0 +1,3 @@
|
|||
port 26379
|
||||
dir /tmp
|
||||
sentinel monitor mymaster 172.16.239.10 7000 1
|
|
@ -0,0 +1,70 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
emqx1:
|
||||
container_name: node1.emqx.io
|
||||
image: emqx/emqx:build-alpine-amd64
|
||||
environment:
|
||||
- "EMQX_NAME=emqx"
|
||||
- "EMQX_HOST=node1.emqx.io"
|
||||
- "EMQX_CLUSTER__DISCOVERY=static"
|
||||
- "EMQX_CLUSTER__STATIC__SEEDS=emqx@node1.emqx.io, emqx@node2.emqx.io"
|
||||
- "EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s"
|
||||
- "EMQX_MQTT__MAX_TOPIC_ALIAS=10"
|
||||
command:
|
||||
- /bin/sh
|
||||
- -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
|
||||
/opt/emqx/bin/emqx foreground
|
||||
healthcheck:
|
||||
test: ["CMD", "/opt/emqx/bin/emqx_ctl", "status"]
|
||||
interval: 5s
|
||||
timeout: 25s
|
||||
retries: 5
|
||||
networks:
|
||||
emqx-bridge:
|
||||
aliases:
|
||||
- node1.emqx.io
|
||||
|
||||
emqx2:
|
||||
container_name: node2.emqx.io
|
||||
image: emqx/emqx:build-alpine-amd64
|
||||
environment:
|
||||
- "EMQX_NAME=emqx"
|
||||
- "EMQX_HOST=node2.emqx.io"
|
||||
- "EMQX_CLUSTER__DISCOVERY=static"
|
||||
- "EMQX_CLUSTER__STATIC__SEEDS=emqx@node1.emqx.io, emqx@node2.emqx.io"
|
||||
- "EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s"
|
||||
- "EMQX_MQTT__MAX_TOPIC_ALIAS=10"
|
||||
command:
|
||||
- /bin/sh
|
||||
- -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
|
||||
/opt/emqx/bin/emqx foreground
|
||||
healthcheck:
|
||||
test: ["CMD", "/opt/emqx/bin/emqx_ctl", "status"]
|
||||
interval: 5s
|
||||
timeout: 25s
|
||||
retries: 5
|
||||
networks:
|
||||
emqx-bridge:
|
||||
aliases:
|
||||
- node2.emqx.io
|
||||
|
||||
client:
|
||||
container_name: paho_client
|
||||
image: python:3.7.2-alpine3.9
|
||||
depends_on:
|
||||
- emqx1
|
||||
- emqx2
|
||||
tty: true
|
||||
networks:
|
||||
emqx-bridge:
|
||||
|
||||
networks:
|
||||
emqx-bridge:
|
||||
driver: bridge
|
|
@ -0,0 +1,30 @@
|
|||
## http_server
|
||||
|
||||
|
||||
The http server for emqx functional validation testing
|
||||
|
||||
### Build
|
||||
|
||||
|
||||
$ rebar3 compile
|
||||
|
||||
### Getting Started
|
||||
|
||||
```
|
||||
1> http_server:start().
|
||||
Start http_server listener on 8080 successfully.
|
||||
ok
|
||||
2> http_server:stop().
|
||||
ok
|
||||
```
|
||||
|
||||
### APIS
|
||||
|
||||
+ GET `/counter`
|
||||
|
||||
返回计数器的值
|
||||
|
||||
+ POST `/counter`
|
||||
|
||||
计数器加一
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{erl_opts, [debug_info]}.
|
||||
{deps,
|
||||
[
|
||||
{minirest, {git, "https://github.com/emqx/minirest.git", {tag, "0.3.1"}}}
|
||||
]}.
|
||||
|
||||
{shell, [
|
||||
% {config, "config/sys.config"},
|
||||
{apps, [http_server]}
|
||||
]}.
|
|
@ -0,0 +1,17 @@
|
|||
{application, http_server,
|
||||
[{description, "An OTP application"},
|
||||
{vsn, "0.1.0"},
|
||||
{registered, []},
|
||||
% {mod, {http_server_app, []}},
|
||||
{modules, []},
|
||||
{applications,
|
||||
[kernel,
|
||||
stdlib,
|
||||
minirest
|
||||
]},
|
||||
{env,[]},
|
||||
{modules, []},
|
||||
|
||||
{licenses, ["Apache 2.0"]},
|
||||
{links, []}
|
||||
]}.
|
|
@ -0,0 +1,50 @@
|
|||
-module(http_server).
|
||||
|
||||
-import(minirest, [ return/0
|
||||
, return/1
|
||||
]).
|
||||
|
||||
-export([ start/0
|
||||
, stop/0
|
||||
]).
|
||||
|
||||
-rest_api(#{ name => get_counter
|
||||
, method => 'GET'
|
||||
, path => "/counter"
|
||||
, func => get_counter
|
||||
, descr => "Check counter"
|
||||
}).
|
||||
-rest_api(#{ name => add_counter
|
||||
, method => 'POST'
|
||||
, path => "/counter"
|
||||
, func => add_counter
|
||||
, descr => "Counter plus one"
|
||||
}).
|
||||
|
||||
-export([ get_counter/2
|
||||
, add_counter/2
|
||||
]).
|
||||
|
||||
start() ->
|
||||
application:ensure_all_started(minirest),
|
||||
ets:new(relup_test_message, [named_table, public]),
|
||||
Handlers = [{"/", minirest:handler(#{modules => [?MODULE]})}],
|
||||
Dispatch = [{"/[...]", minirest, Handlers}],
|
||||
minirest:start_http(?MODULE, #{socket_opts => [inet, {port, 8080}]}, Dispatch).
|
||||
|
||||
stop() ->
|
||||
ets:delete(relup_test_message),
|
||||
minirest:stop_http(?MODULE).
|
||||
|
||||
get_counter(_Binding, _Params) ->
|
||||
return({ok, ets:info(relup_test_message, size)}).
|
||||
|
||||
add_counter(_Binding, Params) ->
|
||||
case lists:keymember(<<"payload">>, 1, Params) of
|
||||
true ->
|
||||
{value, {<<"id">>, ID}, Params1} = lists:keytake(<<"id">>, 1, Params),
|
||||
ets:insert(relup_test_message, {ID, Params1});
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
return().
|
|
@ -0,0 +1,160 @@
|
|||
[config var=PACKAGE_PATH]
|
||||
[config var=BENCH_PATH]
|
||||
[config var=ONE_MORE_EMQX_PATH]
|
||||
[config var=VSN]
|
||||
[config var=OLD_VSNS]
|
||||
|
||||
[config shell_cmd=/bin/bash]
|
||||
[config timeout=600000]
|
||||
|
||||
[loop old_vsn $OLD_VSNS]
|
||||
|
||||
[shell http_server]
|
||||
!cd http_server
|
||||
!rebar3 shell
|
||||
???Eshell
|
||||
???>
|
||||
!http_server:start().
|
||||
?Start http_server listener on 8080 successfully.
|
||||
?ok
|
||||
?>
|
||||
|
||||
[shell emqx]
|
||||
!cd $PACKAGE_PATH
|
||||
!unzip -q -o emqx-ubuntu20.04-$old_vsn-x86_64.zip
|
||||
?SH-PROMPT
|
||||
|
||||
!cd emqx
|
||||
!sed -i 's|listener.wss.external[ \t]*=.*|listener.wss.external = 8085|g' etc/emqx.conf
|
||||
!sed -i '/emqx_telemetry/d' data/loaded_plugins
|
||||
!./bin/emqx start
|
||||
?EMQ X Broker $old_vsn is started successfully!
|
||||
|
||||
!./bin/emqx_ctl status
|
||||
"""?
|
||||
Node 'emqx@127.0.0.1' is started
|
||||
emqx $old_vsn is running
|
||||
"""
|
||||
|
||||
[shell emqx2]
|
||||
!cd $PACKAGE_PATH
|
||||
!cp -f $ONE_MORE_EMQX_PATH/one_more_emqx.sh .
|
||||
!./one_more_emqx.sh emqx2
|
||||
?SH-PROMPT
|
||||
!cd emqx2
|
||||
|
||||
!sed -i '/emqx_telemetry/d' data/loaded_plugins
|
||||
!./bin/emqx start
|
||||
?EMQ X Broker $old_vsn is started successfully!
|
||||
|
||||
!./bin/emqx_ctl status
|
||||
"""?
|
||||
Node 'emqx2@127.0.0.1' is started
|
||||
emqx $old_vsn is running
|
||||
"""
|
||||
?SH-PROMPT
|
||||
|
||||
!./bin/emqx_ctl cluster join emqx@127.0.0.1
|
||||
???Join the cluster successfully.
|
||||
?SH-PROMPT
|
||||
|
||||
!./bin/emqx_ctl cluster status
|
||||
"""???
|
||||
Cluster status: #{running_nodes => ['emqx2@127.0.0.1','emqx@127.0.0.1'],
|
||||
stopped_nodes => []}
|
||||
"""
|
||||
?SH-PROMPT
|
||||
|
||||
!./bin/emqx_ctl resources create 'web_hook' -i 'resource:691c29ba' -c '{"url": "http://127.0.0.1:8080/counter", "method": "POST"}'
|
||||
?created
|
||||
?SH-PROMPT
|
||||
!./bin/emqx_ctl rules create 'SELECT * FROM "t/#"' '[{"name":"data_to_webserver", "params": {"$$resource": "resource:691c29ba"}}]'
|
||||
?created
|
||||
?SH-PROMPT
|
||||
|
||||
[shell emqx]
|
||||
!./bin/emqx_ctl resources list
|
||||
?691c29ba
|
||||
?SH-PROMPT
|
||||
!./bin/emqx_ctl rules list
|
||||
?691c29ba
|
||||
?SH-PROMPT
|
||||
|
||||
[shell bench]
|
||||
!cd $BENCH_PATH
|
||||
!./emqtt_bench pub -c 10 -I 1000 -t t/%i -s 64 -L 600
|
||||
???sent
|
||||
|
||||
[shell emqx]
|
||||
!cp -f ../emqx-ubuntu20.04-$VSN-x86_64.zip releases/
|
||||
!./bin/emqx install $VSN
|
||||
?SH-PROMPT
|
||||
!./bin/emqx versions |grep permanent | grep -oE "[0-9].[0-9].[0-9]"
|
||||
?$VSN
|
||||
?SH-PROMPT
|
||||
|
||||
!./bin/emqx_ctl cluster status
|
||||
"""???
|
||||
Cluster status: #{running_nodes => ['emqx2@127.0.0.1','emqx@127.0.0.1'],
|
||||
stopped_nodes => []}
|
||||
"""
|
||||
?SH-PROMPT
|
||||
|
||||
[shell emqx2]
|
||||
!cp -f ../emqx-ubuntu20.04-$VSN-x86_64.zip releases/
|
||||
!./bin/emqx install $VSN
|
||||
?SH-PROMPT
|
||||
!./bin/emqx versions |grep permanent | grep -oE "[0-9].[0-9].[0-9]"
|
||||
?$VSN
|
||||
?SH-PROMPT
|
||||
|
||||
!./bin/emqx_ctl cluster status
|
||||
"""???
|
||||
Cluster status: #{running_nodes => ['emqx2@127.0.0.1','emqx@127.0.0.1'],
|
||||
stopped_nodes => []}
|
||||
"""
|
||||
?SH-PROMPT
|
||||
|
||||
[shell bench]
|
||||
???publish complete
|
||||
??SH-PROMPT:
|
||||
!curl http://127.0.0.1:8080/counter
|
||||
???{"data":600,"code":0}
|
||||
?SH-PROMPT
|
||||
|
||||
[shell http_server]
|
||||
!http_server:stop().
|
||||
?ok
|
||||
?>
|
||||
!halt(3).
|
||||
?SH-PROMPT:
|
||||
|
||||
[shell emqx2]
|
||||
!cat log/emqx.log.1 |grep -v 691c29ba |tail -n 100
|
||||
-error
|
||||
??SH-PROMPT:
|
||||
|
||||
!./bin/emqx stop
|
||||
?ok
|
||||
?SH-PROMPT:
|
||||
|
||||
!rm -rf $PACKAGE_PATH/emqx2
|
||||
?SH-PROMPT:
|
||||
|
||||
[shell emqx]
|
||||
!cat log/emqx.log.1 |grep -v 691c29ba |tail -n 100
|
||||
-error
|
||||
??SH-PROMPT:
|
||||
|
||||
!./bin/emqx stop
|
||||
?ok
|
||||
?SH-PROMPT:
|
||||
|
||||
!rm -rf $PACKAGE_PATH/emqx
|
||||
?SH-PROMPT:
|
||||
|
||||
[endloop]
|
||||
|
||||
[cleanup]
|
||||
!echo ==$$?==
|
||||
?==0==
|
|
@ -58,7 +58,7 @@ ignore=title-trailing-punctuation, T1, T2, T3, T4, T5, T6, T8, B1, B2, B3, B4, B
|
|||
# python-style regex that the commit-msg title must match
|
||||
# Note that the regex can contradict with other rules if not used correctly
|
||||
# (e.g. title-must-not-contain-word).
|
||||
regex=^(feat|fix|docs|style|refactor|test|chore|perf)\(.+\): .+
|
||||
regex=^(feat|feature|fix|docs|style|refactor|test|chore|build|perf|improve)\(.+\): .+
|
||||
|
||||
# [body-max-line-length]
|
||||
# line-length=72
|
||||
|
|
|
@ -0,0 +1,394 @@
|
|||
name: Cross build packages
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
repository_dispatch:
|
||||
types: [run_actions]
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
runs-on: windows-2019
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
- uses: gleam-lang/setup-erlang@v1.1.0
|
||||
id: install_erlang
|
||||
with:
|
||||
otp-version: 23.2
|
||||
- name: build
|
||||
run: |
|
||||
# set-executionpolicy remotesigned -s cu
|
||||
# iex (new-object net.webclient).downloadstring('https://get.scoop.sh')
|
||||
# # $env:path + ";" + $env:USERPROFILE + "\scoop\shims" + ';C:\Program Files\erl10.4\bin'
|
||||
# [environment]::SetEnvironmentvariable("Path", ";" + $env:USERPROFILE + "\scoop\shims")
|
||||
# [environment]::SetEnvironmentvariable("Path", ';C:\Program Files\erl10.4\bin')
|
||||
# scoop bucket add extras https://github.com/lukesampson/scoop-extras.git
|
||||
# scoop update
|
||||
# scoop install sudo curl vcredist2013
|
||||
|
||||
$env:PATH = "${{ steps.install_erlang.outputs.erlpath }}\bin;$env:PATH"
|
||||
|
||||
$version = $( "${{ github.ref }}" -replace "^(.*)/(.*)/" )
|
||||
if ($version -match "^v[0-9]+\.[0-9]+(\.[0-9]+)?") {
|
||||
$regex = "[0-9]+\.[0-9]+(-alpha|-beta|-rc)?\.[0-9]"
|
||||
$pkg_name = "emqx-windows-$([regex]::matches($version, $regex).value).zip"
|
||||
}
|
||||
else {
|
||||
$pkg_name = "emqx-windows-$($version -replace '/').zip"
|
||||
}
|
||||
|
||||
make deps-emqx || cat rebar3.crashdump
|
||||
$rebar3 = $env:USERPROFILE + "\rebar3"
|
||||
(New-Object System.Net.WebClient).DownloadFile('https://s3.amazonaws.com/rebar3/rebar3', $rebar3)
|
||||
cd _build/emqx/lib/jiffy/
|
||||
escript $rebar3 compile
|
||||
cd ../../../../
|
||||
|
||||
make emqx
|
||||
mkdir -p _packages/emqx
|
||||
Compress-Archive -Path _build/emqx/rel/emqx -DestinationPath _build/emqx/rel/$pkg_name
|
||||
mv _build/emqx/rel/$pkg_name _packages/emqx
|
||||
Get-FileHash -Path "_packages/emqx/$pkg_name" | Format-List | grep 'Hash' | awk '{print $3}' > _packages/emqx/$pkg_name.sha256
|
||||
- name: run emqx
|
||||
run: |
|
||||
./_build/emqx/rel/emqx/bin/emqx start
|
||||
./_build/emqx/rel/emqx/bin/emqx stop
|
||||
./_build/emqx/rel/emqx/bin/emqx install
|
||||
./_build/emqx/rel/emqx/bin/emqx uninstall
|
||||
- uses: actions/upload-artifact@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
name: emqx
|
||||
path: ./_packages/emqx/.
|
||||
|
||||
mac:
|
||||
runs-on: macos-10.15
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: prepare
|
||||
run: |
|
||||
brew install curl zip unzip gnu-sed kerl unixodbc freetds
|
||||
echo "/usr/local/bin" >> $GITHUB_PATH
|
||||
git config --global credential.helper store
|
||||
- name: build erlang
|
||||
run: |
|
||||
kerl build 23.2.2
|
||||
kerl install 23.2.2 $HOME/.kerl/23.2.2
|
||||
- name: build
|
||||
run: |
|
||||
. $HOME/.kerl/23.2.2/activate
|
||||
make emqx-pkg
|
||||
- name: test
|
||||
run: |
|
||||
pkg_name=$(basename _packages/emqx/emqx-macos-*.zip)
|
||||
unzip _packages/emqx/$pkg_name
|
||||
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
|
||||
ready='yes'
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
if [ "$ready" != "yes" ]; then
|
||||
echo "Timed out waiting for emqx to be ready"
|
||||
cat emqx/log/erlang.log.1
|
||||
exit 1
|
||||
fi
|
||||
./emqx/bin/emqx_ctl status
|
||||
./emqx/bin/emqx stop
|
||||
rm -rf emqx
|
||||
openssl dgst -sha256 ./_packages/emqx/$pkg_name | awk '{print $2}' > ./_packages/emqx/$pkg_name.sha256
|
||||
- uses: actions/upload-artifact@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
name: emqx
|
||||
path: ./_packages/emqx/.
|
||||
|
||||
linux:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
arch:
|
||||
- amd64
|
||||
- arm64
|
||||
emqx:
|
||||
- emqx
|
||||
- emqx-edge
|
||||
os:
|
||||
- ubuntu20.04
|
||||
- ubuntu18.04
|
||||
- ubuntu16.04
|
||||
- debian10
|
||||
- debian9
|
||||
- opensuse
|
||||
- centos8
|
||||
- centos7
|
||||
- centos6
|
||||
- raspbian10
|
||||
- raspbian9
|
||||
exclude:
|
||||
- os: raspbian9
|
||||
arch: amd64
|
||||
- os: raspbian9
|
||||
emqx: emqx
|
||||
- os: raspbian10
|
||||
arch: amd64
|
||||
- os: raspbian10
|
||||
emqx: emqx
|
||||
- os: centos6
|
||||
arch: arm64
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
steps:
|
||||
- name: prepare docker
|
||||
run: |
|
||||
mkdir -p $HOME/.docker
|
||||
echo '{ "experimental": "enabled" }' | tee $HOME/.docker/config.json
|
||||
echo '{ "experimental": true, "storage-driver": "overlay2", "max-concurrent-downloads": 50, "max-concurrent-uploads": 50}' | sudo tee /etc/docker/daemon.json
|
||||
sudo systemctl restart docker
|
||||
docker info
|
||||
docker buildx create --use --name mybuild
|
||||
docker run --rm --privileged tonistiigi/binfmt --install all
|
||||
- uses: actions/checkout@v1
|
||||
- name: get deps
|
||||
env:
|
||||
ERL_OTP: erl23.2.2
|
||||
run: |
|
||||
docker run -i --rm \
|
||||
-e GITHUB_RUN_ID=$GITHUB_RUN_ID \
|
||||
-e GITHUB_REF=$GITHUB_REF \
|
||||
-v $(pwd):/emqx \
|
||||
-w /emqx \
|
||||
emqx/build-env:${ERL_OTP}-debian10 \
|
||||
bash -c "make deps-all"
|
||||
- name: downloads emqx zip packages
|
||||
env:
|
||||
EMQX: ${{ matrix.emqx }}
|
||||
ARCH: ${{ matrix.arch }}
|
||||
SYSTEM: ${{ matrix.os }}
|
||||
run: |
|
||||
set -e -u -x
|
||||
if [ $EMQX = "emqx-edge" ];then broker="emqx-edge"; else broker="emqx-ce"; fi
|
||||
if [ $ARCH = "arm64" ];then arch="aarch64"; else arch="x86_64"; fi
|
||||
|
||||
vsn="$(grep -oE '\{vsn, (.*)\}' src/emqx.app.src | sed -r 's/\{vsn, (.*)\}/\1/g' | sed 's/\"//g')"
|
||||
pre_vsn="$(echo $vsn | grep -oE '^[0-9]+.[0-9]')"
|
||||
old_vsns=($(git tag -l "$pre_vsn.[0-9]" | sed "s/$vsn//"))
|
||||
|
||||
mkdir -p tmp/relup_packages/$EMQX
|
||||
cd tmp/relup_packages/$EMQX
|
||||
for tag in ${old_vsns[@]};do
|
||||
if [ ! -z "$(echo $(curl -I -m 10 -o /dev/null -s -w %{http_code} https://s3-us-west-2.amazonaws.com/packages.emqx/$broker/v${tag#[e|v]}/$EMQX-$SYSTEM-${tag#[e|v]}-$arch.zip) | grep -oE "^[23]+")" ];then
|
||||
wget https://s3-us-west-2.amazonaws.com/packages.emqx/$broker/v${tag#[e|v]}/$EMQX-$SYSTEM-${tag#[e|v]}-$arch.zip
|
||||
wget https://s3-us-west-2.amazonaws.com/packages.emqx/$broker/v${tag#[e|v]}/$EMQX-$SYSTEM-${tag#[e|v]}-$arch.zip.sha256
|
||||
echo "$(cat $EMQX-$SYSTEM-${tag#[e|v]}-$arch.zip.sha256) $EMQX-$SYSTEM-${tag#[e|v]}-$arch.zip" | sha256sum -c || exit 1
|
||||
fi
|
||||
done
|
||||
cd -
|
||||
- name: build emqx packages
|
||||
if: (matrix.arch == 'amd64' && matrix.emqx == 'emqx') || startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
ERL_OTP: erl23.2.2
|
||||
EMQX: ${{ matrix.emqx }}
|
||||
ARCH: ${{ matrix.arch }}
|
||||
SYSTEM: ${{ matrix.os }}
|
||||
run: |
|
||||
set -e -u -x
|
||||
docker buildx build --no-cache \
|
||||
--platform=linux/$ARCH \
|
||||
-t cross_build_emqx_for_$SYSTEM \
|
||||
-f .ci/build_packages/Dockerfile \
|
||||
--build-arg BUILD_FROM=emqx/build-env:$ERL_OTP-$SYSTEM \
|
||||
--build-arg EMQX_NAME=$EMQX \
|
||||
--output type=tar,dest=/tmp/cross-build-$EMQX-for-$SYSTEM.tar .
|
||||
|
||||
mkdir -p /tmp/packages/$EMQX
|
||||
tar -xvf /tmp/cross-build-$EMQX-for-$SYSTEM.tar --wildcards emqx/_packages/$EMQX/*
|
||||
mv emqx/_packages/$EMQX/* /tmp/packages/$EMQX/
|
||||
rm -rf /tmp/cross-build-$EMQX-for-$SYSTEM.tar
|
||||
|
||||
docker rm -f $(docker ps -a -q)
|
||||
docker volume prune -f
|
||||
- name: create sha256
|
||||
env:
|
||||
EMQX: ${{ matrix.emqx }}
|
||||
run: |
|
||||
if [ -d /tmp/packages/$EMQX ]; then
|
||||
cd /tmp/packages/$EMQX
|
||||
for var in $(ls emqx-* ); do
|
||||
bash -c "echo $(sha256sum $var | awk '{print $1}') > $var.sha256"
|
||||
done
|
||||
cd -
|
||||
fi
|
||||
- uses: actions/upload-artifact@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
name: ${{ matrix.emqx }}
|
||||
path: /tmp/packages/${{ matrix.emqx }}/.
|
||||
|
||||
docker:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
arch:
|
||||
- [amd64, x86_64]
|
||||
- [arm64v8, aarch64]
|
||||
- [arm32v7, arm]
|
||||
- [i386, i386]
|
||||
- [s390x, s390x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: get deps
|
||||
env:
|
||||
ERL_OTP: erl23.2.2
|
||||
run: |
|
||||
docker run -i --rm \
|
||||
-e GITHUB_RUN_ID=$GITHUB_RUN_ID \
|
||||
-e GITHUB_REF=$GITHUB_REF \
|
||||
-v $(pwd):/emqx \
|
||||
-w /emqx \
|
||||
emqx/build-env:${ERL_OTP}-alpine-amd64 \
|
||||
sh -c "make deps-emqx"
|
||||
- name: build emqx docker image
|
||||
env:
|
||||
ARCH: ${{ matrix.arch[0] }}
|
||||
QEMU_ARCH: ${{ matrix.arch[1] }}
|
||||
run: |
|
||||
sudo docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
|
||||
sudo TARGET=emqx/emqx ARCH=$ARCH QEMU_ARCH=$QEMU_ARCH make docker
|
||||
cd _packages/emqx && for var in $(ls emqx-docker-* ); do sudo bash -c "echo $(sha256sum $var | awk '{print $1}') > $var.sha256"; done && cd -
|
||||
|
||||
sudo TARGET=emqx/emqx-edge ARCH=$ARCH QEMU_ARCH=$QEMU_ARCH make docker
|
||||
cd _packages/emqx-edge && for var in $(ls emqx-edge-docker-* ); do sudo bash -c "echo $(sha256sum $var | awk '{print $1}') > $var.sha256"; done && cd -
|
||||
- uses: actions/upload-artifact@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
name: emqx
|
||||
path: ./_packages/emqx/.
|
||||
- uses: actions/upload-artifact@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
name: emqx-edge
|
||||
path: ./_packages/emqx-edge/.
|
||||
|
||||
upload:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
needs: [windows, mac, linux, docker]
|
||||
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: emqx
|
||||
path: ./_packages/emqx
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: emqx-edge
|
||||
path: ./_packages/emqx-edge
|
||||
- name: install dos2unix
|
||||
run: sudo apt-get update && sudo apt install -y dos2unix
|
||||
- name: get packages
|
||||
run: |
|
||||
set -e -x -u
|
||||
for EMQX in emqx emqx-edge; do
|
||||
cd _packages/$EMQX
|
||||
for var in $( ls |grep emqx |grep -v sha256); do
|
||||
dos2unix $var.sha256
|
||||
echo "$(cat $var.sha256) $var" | sha256sum -c || exit 1
|
||||
done
|
||||
cd -
|
||||
done
|
||||
- name: upload aws s3
|
||||
run: |
|
||||
set -e -x -u
|
||||
version=$(echo ${{ github.ref }} | sed -r "s ^refs/heads/|^refs/tags/(.*) \1 g")
|
||||
curl "https://d1vvhvl2y92vvt.cloudfront.net/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
||||
unzip awscliv2.zip
|
||||
sudo ./aws/install
|
||||
aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws configure set default.region us-west-2
|
||||
|
||||
aws s3 cp --recursive _packages/emqx s3://packages.emqx/emqx-ce/$version
|
||||
aws s3 cp --recursive _packages/emqx-edge s3://packages.emqx/emqx-edge/$version
|
||||
aws cloudfront create-invalidation --distribution-id E170YEULGLT8XB --paths "/emqx-ce/$version/*,/emqx-edge/$version/*"
|
||||
|
||||
mkdir packages
|
||||
mv _packages/emqx/* packages
|
||||
mv _packages/emqx-edge/* packages
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: emqx
|
||||
- name: update to github and emqx.io
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
set -e -x -u
|
||||
version=$(echo ${{ github.ref }} | sed -r "s ^refs/heads/|^refs/tags/(.*) \1 g")
|
||||
cd packages
|
||||
for var in $(ls); do
|
||||
../emqx/.ci/build_packages/upload_github_release_asset.sh owner=emqx repo=emqx tag=$version filename=$var github_api_token=$(echo ${{ secrets.AccessToken }})
|
||||
sleep 1
|
||||
done
|
||||
curl -w %{http_code} \
|
||||
--insecure \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "token: ${{ secrets.EMQX_IO_TOKEN }}" \
|
||||
-X POST \
|
||||
-d "{\"repo\":\"emqx/emqx\", \"tag\": \"${version}\" }" \
|
||||
${{ secrets.EMQX_IO_RELEASE_API }}
|
||||
- name: push docker image to docker hub
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
set -e -x -u
|
||||
version=$(echo ${{ github.ref }} | sed -r "s ^refs/heads/|^refs/tags/(.*) \1 g")
|
||||
sudo make -C emqx docker-prepare
|
||||
cd packages && for var in $(ls |grep docker |grep -v sha256); do unzip $var; sudo docker load < ${var%.*}; rm -f ${var%.*}; done && cd -
|
||||
echo ${{ secrets.DOCKER_HUB_TOKEN }} |sudo docker login -u ${{ secrets.DOCKER_HUB_USER }} --password-stdin
|
||||
sudo TARGET=emqx/emqx make -C emqx docker-push
|
||||
sudo TARGET=emqx/emqx make -C emqx docker-manifest-list
|
||||
sudo TARGET=emqx/emqx-edge make -C emqx docker-push
|
||||
sudo TARGET=emqx/emqx-edge make -C emqx docker-manifest-list
|
||||
- name: update repo.emqx.io
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
set -e -x -u
|
||||
version=$(echo ${{ github.ref }} | sed -r "s ^refs/heads/|^refs/tags/(.*) \1 g")
|
||||
curl \
|
||||
-H "Authorization: token ${{ secrets.AccessToken }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-X POST \
|
||||
-d "{\"ref\":\"v1.0.0\",\"inputs\":{\"version\": \"${version}\", \"emqx_ce\": \"true\"}}" \
|
||||
https://api.github.com/repos/emqx/emqx-ci-helper/actions/workflows/update_repos.yaml/dispatches
|
||||
- uses: geekyeggo/delete-artifact@v1
|
||||
with:
|
||||
name: emqx
|
||||
- uses: geekyeggo/delete-artifact@v1
|
||||
with:
|
||||
name: emqx-edge
|
||||
# - name: update homebrew packages
|
||||
# run: |
|
||||
# version=$(echo ${{ github.ref }} | sed -r "s .*/.*/(.*) \1 g")
|
||||
# if [ ! -z $(echo $version | grep -oE "v[0-9]+\.[0-9]+(\.[0-9]+)?") ] && [ -z $(echo $version | grep -oE "(alpha|beta|rc)\.[0-9]") ]; then
|
||||
# curl -H "Authorization: token ${{ secrets.AccessToken }}" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" -X POST -d "{\"event_type\":\"update_homebrew\",\"client_payload\":{\"version\": \"$version\"}}" https://api.github.com/repos/emqx/emqx-packages-docker/dispatches
|
||||
# fi
|
|
@ -4,8 +4,8 @@ on: [pull_request]
|
|||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- run: |
|
||||
./elvis-check.sh $GITHUB_BASE_REF
|
||||
./scripts/elvis-check.sh $GITHUB_BASE_REF
|
||||
|
|
|
@ -0,0 +1,315 @@
|
|||
name: Compatibility Test Suite
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
repository_dispatch:
|
||||
types: [run_actions]
|
||||
|
||||
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: setup
|
||||
env:
|
||||
LDAP_TAG: ${{ matrix.ldap_tag }}
|
||||
run: |
|
||||
docker-compose -f .ci/apps_tests/docker-compose.yaml build --no-cache
|
||||
docker-compose -f .ci/compatibility_tests/docker-compose-ldap.yaml up -d
|
||||
- name: setup
|
||||
if: matrix.network_type == 'ipv4'
|
||||
run: |
|
||||
server_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ldap)
|
||||
sed -i "s|^[#[:space:]]*auth.ldap.servers[[:space:]]*=.*|auth.ldap.servers = $server_address|g" apps/emqx_auth_ldap/etc/emqx_auth_ldap.conf
|
||||
- name: setup
|
||||
if: matrix.network_type == 'ipv6'
|
||||
run: |
|
||||
server_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.GlobalIPv6Address}}{{end}}' ldap)
|
||||
sed -i "s|^[#[:space:]]*auth.ldap.servers[[:space:]]*=.*|auth.ldap.servers = $server_address|g" apps/emqx_auth_ldap/etc/emqx_auth_ldap.conf
|
||||
- name: run test cases
|
||||
run: |
|
||||
docker exec -i erlang sh -c "make ensure-rebar3"
|
||||
docker exec -i erlang sh -c "./rebar3 eunit --dir apps/emqx_auth_ldap"
|
||||
docker exec -i erlang sh -c "./rebar3 ct --dir apps/emqx_auth_ldap"
|
||||
- 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: setup
|
||||
env:
|
||||
MONGO_TAG: ${{ matrix.mongo_tag }}
|
||||
if: matrix.connect_type == 'tls'
|
||||
run: |
|
||||
docker-compose -f .ci/compatibility_tests/docker-compose-mongo-tls.yaml up -d
|
||||
sed -i 's|^[#[:space:]]*auth.mongo.ssl[[:space:]]*=.*|auth.mongo.ssl = on|g' apps/emqx_auth_mongo/etc/emqx_auth_mongo.conf
|
||||
sed -i 's|^[#[:space:]]*auth.mongo.cacertfile[[:space:]]*=.*|auth.mongo.cacertfile = /emqx/apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE_data/ca.pem|g' apps/emqx_auth_mongo/etc/emqx_auth_mongo.conf
|
||||
sed -i 's|^[#[:space:]]*auth.mongo.certfile[[:space:]]*=.*|auth.mongo.certfile = /emqx/apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE_data/client-cert.pem|g' apps/emqx_auth_mongo/etc/emqx_auth_mongo.conf
|
||||
sed -i 's|^[#[:space:]]*auth.mongo.keyfile[[:space:]]*=.*|auth.mongo.keyfile = /emqx/apps/emqx_auth_mongo/test/emqx_auth_mongo_SUITE_data/client-key.pem|g' apps/emqx_auth_mongo/etc/emqx_auth_mongo.conf
|
||||
- name: setup
|
||||
env:
|
||||
MONGO_TAG: ${{ matrix.mongo_tag }}
|
||||
if: matrix.connect_type == 'tcp'
|
||||
run: docker-compose -f .ci/compatibility_tests/docker-compose-mongo.yaml up -d
|
||||
- name: setup
|
||||
if: matrix.network_type == 'ipv4'
|
||||
run: |
|
||||
server_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mongo)
|
||||
sed -i "s|^[#[:space:]]*auth.mongo.server[[:space:]]*=.*|auth.mongo.server = $server_address:27017|g" apps/emqx_auth_mongo/etc/emqx_auth_mongo.conf
|
||||
- name: setup
|
||||
if: matrix.network_type == 'ipv6'
|
||||
run: |
|
||||
server_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.GlobalIPv6Address}}{{end}}' mongo)
|
||||
sed -i "s|^[#[:space:]]*auth.mongo.server[[:space:]]*=.*|auth.mongo.server = $server_address:27017|g" apps/emqx_auth_mongo/etc/emqx_auth_mongo.conf
|
||||
- name: run test cases
|
||||
run: |
|
||||
docker exec -i erlang sh -c "make ensure-rebar3"
|
||||
docker exec -i erlang sh -c "./rebar3 eunit --dir apps/emqx_auth_mongo"
|
||||
docker exec -i erlang sh -c "./rebar3 ct --dir apps/emqx_auth_mongo"
|
||||
- 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: setup
|
||||
env:
|
||||
MYSQL_TAG: ${{ matrix.mysql_tag }}
|
||||
if: matrix.connect_type == 'tls'
|
||||
run: |
|
||||
docker-compose -f .ci/compatibility_tests/docker-compose-mysql-tls.yaml up -d
|
||||
sed -i 's|^[#[:space:]]*auth.mysql.ssl[[:space:]]*=.*|auth.mysql.ssl = on|g' apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.mysql.ssl.cacertfile[[:space:]]*=.*|auth.mysql.ssl.cacertfile = /emqx/apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data/ca.pem|g' apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.mysql.ssl.certfile[[:space:]]*=.*|auth.mysql.ssl.certfile = /emqx/apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data/client-cert.pem|g' apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.mysql.ssl.keyfile[[:space:]]*=.*|auth.mysql.ssl.keyfile = /emqx/apps/emqx_auth_mysql/test/emqx_auth_mysql_SUITE_data/client-key.pem|g' apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf
|
||||
- name: setup
|
||||
env:
|
||||
MYSQL_TAG: ${{ matrix.mysql_tag }}
|
||||
if: matrix.connect_type == 'tcp'
|
||||
run: docker-compose -f .ci/compatibility_tests/docker-compose-mysql.yaml up -d
|
||||
- name: setup
|
||||
if: matrix.network_type == 'ipv4'
|
||||
run: |
|
||||
server_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mysql)
|
||||
sed -i "/auth.mysql.server/c auth.mysql.server = $server_address:3306" apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf
|
||||
- name: setup
|
||||
if: matrix.network_type == 'ipv6'
|
||||
run: |
|
||||
server_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.GlobalIPv6Address}}{{end}}' mysql)
|
||||
sed -i "/auth.mysql.server/c auth.mysql.server = $server_address:3306" apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf
|
||||
- name: setup
|
||||
run: |
|
||||
sed -i 's|^[#[:space:]]*auth.mysql.username[[:space:]]*=.*|auth.mysql.username = root|g' apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.mysql.password[[:space:]]*=.*|auth.mysql.password = public|g' apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.mysql.database[[:space:]]*=.*|auth.mysql.database = mqtt|g' apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf
|
||||
- name: run test cases
|
||||
run: |
|
||||
docker exec -i erlang sh -c "make ensure-rebar3"
|
||||
docker exec -i erlang sh -c "./rebar3 eunit --dir apps/emqx_auth_mysql"
|
||||
docker exec -i erlang sh -c "./rebar3 ct --dir apps/emqx_auth_mysql"
|
||||
- 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: setup
|
||||
env:
|
||||
PGSQL_TAG: ${{ matrix.pgsql_tag }}
|
||||
if: matrix.connect_type == 'tls'
|
||||
run: |
|
||||
docker-compose -f .ci/compatibility_tests/docker-compose-pgsql-tls.yaml build --no-cache
|
||||
docker-compose -f .ci/compatibility_tests/docker-compose-pgsql-tls.yaml up -d
|
||||
if [ "$PGSQL_TAG" = "12" ] || [ "$PGSQL_TAG" = "13" ]; then
|
||||
sed -i 's|^[#[:space:]]*auth.pgsql.ssl.tls_versions[ \t]*=.*|auth.pgsql.ssl.tls_versions = tlsv1.3,tlsv1.2|g' apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
|
||||
else
|
||||
sed -i 's|^[#[:space:]]*auth.pgsql.ssl.tls_versions[ \t]*=.*|auth.pgsql.ssl.tls_versions = tlsv1.2,tlsv1.1|g' apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
|
||||
fi
|
||||
|
||||
sed -i 's|^[#[:space:]]*auth.pgsql.username[ \t]*=.*|auth.pgsql.username = postgres|g' apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.pgsql.password[ \t]*=.*|auth.pgsql.password = postgres|g' apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.pgsql.database[ \t]*=.*|auth.pgsql.database = postgres|g' apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.pgsql.ssl[ \t]*=.*|auth.pgsql.ssl = on|g' apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.pgsql.cacertfile[ \t]*=.*|auth.pgsql.cacertfile = /emqx/apps/emqx_auth_pgsql/test/emqx_auth_pgsql_SUITE_data/root.crt|g' apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
|
||||
- name: setup
|
||||
env:
|
||||
PGSQL_TAG: ${{ matrix.pgsql_tag }}
|
||||
if: matrix.connect_type == 'tcp'
|
||||
run: |
|
||||
docker-compose -f .ci/compatibility_tests/docker-compose-pgsql.yaml up -d
|
||||
sed -i 's|^[#[:space:]]*auth.pgsql.username[ \t]*=.*|auth.pgsql.username = root|g' apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.pgsql.password[ \t]*=.*|auth.pgsql.password = public|g' apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.pgsql.database[ \t]*=.*|auth.pgsql.database = mqtt|g' apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
|
||||
- name: setup
|
||||
if: matrix.network_type == 'ipv4'
|
||||
run: |
|
||||
server_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' pgsql)
|
||||
sed -i "s|^[#[:space:]]*auth.pgsql.server[[:space:]]*=.*|auth.pgsql.server = $server_address:5432|g" apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
|
||||
- name: setup
|
||||
if: matrix.network_type == 'ipv6'
|
||||
run: |
|
||||
server_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.GlobalIPv6Address}}{{end}}' pgsql)
|
||||
sed -i "s|^[#[:space:]]*auth.pgsql.server[[:space:]]*=.*|auth.pgsql.server = $server_address:5432|g" apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
|
||||
- name: run test cases
|
||||
run: |
|
||||
docker exec -i erlang sh -c "make ensure-rebar3"
|
||||
docker exec -i erlang sh -c "./rebar3 eunit --dir apps/emqx_auth_pgsql"
|
||||
docker exec -i erlang sh -c "./rebar3 ct --dir apps/emqx_auth_pgsql"
|
||||
- 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
|
||||
- cluster
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: setup
|
||||
env:
|
||||
REDIS_TAG: ${{ matrix.redis_tag }}
|
||||
if: matrix.connect_type == 'tls' && matrix.redis_tag != '5'
|
||||
run: |
|
||||
set -exu
|
||||
docker-compose -f .ci/compatibility_tests/docker-compose-redis-${{ matrix.node_type }}-tls.yaml up -d
|
||||
sed -i 's|^[#[:space:]]*auth.redis.ssl[[:space:]]*=.*|auth.redis.ssl = on|g' apps/emqx_auth_redis/etc/emqx_auth_redis.conf
|
||||
sed -i 's|^[#[:space:]]*auth.redis.ssl.cacertfile[[:space:]]*=.*|auth.redis.ssl.cacertfile = /emqx/apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs/ca.crt|g' apps/emqx_auth_redis/etc/emqx_auth_redis.conf
|
||||
sed -i 's|^[#[:space:]]*auth.redis.ssl.certfile[[:space:]]*=.*|auth.redis.ssl.certfile = /emqx/apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs/redis.crt|g' apps/emqx_auth_redis/etc/emqx_auth_redis.conf
|
||||
sed -i 's|^[#[:space:]]*auth.redis.ssl.keyfile[[:space:]]*=.*|auth.redis.ssl.keyfile = /emqx/apps/emqx_auth_redis/test/emqx_auth_redis_SUITE_data/certs/redis.key|g' apps/emqx_auth_redis/etc/emqx_auth_redis.conf
|
||||
- name: setup
|
||||
env:
|
||||
REDIS_TAG: ${{ matrix.redis_tag }}
|
||||
if: matrix.connect_type == 'tcp'
|
||||
run: docker-compose -f .ci/compatibility_tests/docker-compose-redis-${{ matrix.node_type }}.yaml up -d
|
||||
- name: get server address
|
||||
if: matrix.connect_type == 'tcp' || (matrix.connect_type == 'tls' && matrix.redis_tag != '5')
|
||||
run: |
|
||||
set -exu
|
||||
ipv4_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' redis)
|
||||
ipv6_address=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.GlobalIPv6Address}}{{end}}' redis)
|
||||
echo "redis_ipv4_address=$ipv4_address" >> $GITHUB_ENV
|
||||
echo "redis_ipv6_address=$ipv6_address" >> $GITHUB_ENV
|
||||
- name: setup
|
||||
if: matrix.node_type == 'single' && matrix.connect_type == 'tcp'
|
||||
run: |
|
||||
set -exu
|
||||
sed -i "s|^[#[:space:]]*auth.redis.server[[:space:]]*=.*|auth.redis.server = ${redis_${{ matrix.network_type }}_address}:6379|g" apps/emqx_auth_redis/etc/emqx_auth_redis.conf
|
||||
- name: setup
|
||||
if: matrix.node_type == 'single' && matrix.connect_type == 'tls' && matrix.redis_tag != '5'
|
||||
run: |
|
||||
set -exu
|
||||
sed -i "s|^[#[:space:]]*auth.redis.server[[:space:]]*=.*|auth.redis.server = ${redis_${{ matrix.network_type }}_address}:6380|g" apps/emqx_auth_redis/etc/emqx_auth_redis.conf
|
||||
- name: setup
|
||||
if: matrix.node_type == 'cluster' && matrix.connect_type == 'tcp'
|
||||
run: |
|
||||
set -exu
|
||||
sed -i 's|^[#[:space:]]*auth.redis.type[[:space:]]*=.*|auth.redis.type = cluster|g' apps/emqx_auth_redis/etc/emqx_auth_redis.conf
|
||||
sed -i "s|^[#[:space:]]*auth.redis.server[[:space:]]*=.*|auth.redis.server = ${redis_${{ matrix.network_type }}_address}:7000, ${redis_${{ matrix.network_type }}_address}:7001, ${redis_${{ matrix.network_type }}_address}:7002|g" apps/emqx_auth_redis/etc/emqx_auth_redis.conf
|
||||
- name: setup
|
||||
if: matrix.node_type == 'cluster' && matrix.connect_type == 'tls' && matrix.redis_tag != '5'
|
||||
run: |
|
||||
set -exu
|
||||
sed -i 's|^[#[:space:]]*auth.redis.type[[:space:]]*=.*|auth.redis.type = cluster|g' apps/emqx_auth_redis/etc/emqx_auth_redis.conf
|
||||
sed -i "s|^[#[:space:]]*auth.redis.server[[:space:]]*=.*|auth.redis.server = ${redis_${{ matrix.network_type }}_address}:8000, ${redis_${{ matrix.network_type }}_address}:8001, ${redis_${{ matrix.network_type }}_address}:8002|g" apps/emqx_auth_redis/etc/emqx_auth_redis.conf
|
||||
- name: run test cases
|
||||
if: matrix.connect_type == 'tcp' || (matrix.connect_type == 'tls' && matrix.redis_tag != '5')
|
||||
run: |
|
||||
docker exec -i erlang sh -c "make ensure-rebar3"
|
||||
docker exec -i erlang sh -c "./rebar3 eunit --dir apps/emqx_auth_redis"
|
||||
docker exec -i erlang sh -c "./rebar3 ct --dir apps/emqx_auth_redis"
|
||||
- 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
|
|
@ -0,0 +1,206 @@
|
|||
name: Functional Verification Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
repository_dispatch:
|
||||
types: [run_actions]
|
||||
|
||||
jobs:
|
||||
docker_test:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make emqx image
|
||||
run: TARGET=emqx/emqx make docker
|
||||
- name: run emqx
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
set -e -u -x
|
||||
docker-compose -f .ci/fvt_tests/docker-compose.yaml up -d
|
||||
while [ "$(docker inspect -f '{{ .State.Health.Status}}' node1.emqx.io)" != "healthy" ] || [ "$(docker inspect -f '{{ .State.Health.Status}}' node2.emqx.io)" != "healthy" ]; do
|
||||
if [ $(docker ps -a -f name=fvt_tests_emqx -f status=exited -q | wc -l) -ne 0 ]; then
|
||||
echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:emqx stop";
|
||||
exit;
|
||||
else
|
||||
echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:waiting emqx";
|
||||
sleep 5;
|
||||
fi;
|
||||
done
|
||||
- name: make paho tests
|
||||
run: |
|
||||
docker exec -i paho_client sh -c "apk update && apk add git curl \
|
||||
&& git clone -b develop-4.0 https://github.com/emqx/paho.mqtt.testing.git /paho.mqtt.testing \
|
||||
&& pip install pytest \
|
||||
&& pytest -v /paho.mqtt.testing/interoperability/test_client/V5/test_connect.py -k test_basic --host node1.emqx.io \
|
||||
&& pytest -v /paho.mqtt.testing/interoperability/test_cluster --host1 node1.emqx.io --host2 node2.emqx.io \
|
||||
&& pytest -v /paho.mqtt.testing/interoperability/test_client --host node1.emqx.io"
|
||||
|
||||
helm_test:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make emqx image
|
||||
run: TARGET=emqx/emqx make docker
|
||||
- name: install k3s
|
||||
env:
|
||||
KUBECONFIG: "/etc/rancher/k3s/k3s.yaml"
|
||||
run: |
|
||||
sudo sh -c "echo \"127.0.0.1 $(hostname)\" >> /etc/hosts"
|
||||
curl -sfL https://get.k3s.io | sh -
|
||||
sudo chmod 644 /etc/rancher/k3s/k3s.yaml
|
||||
kubectl cluster-info
|
||||
- name: install helm
|
||||
env:
|
||||
KUBECONFIG: "/etc/rancher/k3s/k3s.yaml"
|
||||
run: |
|
||||
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3
|
||||
sudo chmod 700 get_helm.sh
|
||||
sudo ./get_helm.sh
|
||||
helm version
|
||||
- name: run emqx on chart
|
||||
env:
|
||||
KUBECONFIG: "/etc/rancher/k3s/k3s.yaml"
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
version=$(./pkg-vsn.sh)
|
||||
sudo docker save emqx/emqx:$version -o emqx.tar.gz
|
||||
sudo k3s ctr image import emqx.tar.gz
|
||||
|
||||
sed -i -r "s/^appVersion: .*$/appVersion: \"${version}\"/g" deploy/charts/emqx/Chart.yaml
|
||||
sed -i -r 's/ pullPolicy: .*$/ pullPolicy: Never/g' deploy/charts/emqx/values.yaml
|
||||
sed -i '/emqx_telemetry/d' deploy/charts/emqx/values.yaml
|
||||
|
||||
helm install emqx --set emqxAclConfig="" --set emqxConfig.EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s --set emqxConfig.EMQX_MQTT__MAX_TOPIC_ALIAS=10 deploy/charts/emqx --debug --dry-run
|
||||
helm install emqx --set emqxAclConfig="" --set emqxConfig.EMQX_ZONE__EXTERNAL__RETRY_INTERVAL=2s --set emqxConfig.EMQX_MQTT__MAX_TOPIC_ALIAS=10 deploy/charts/emqx
|
||||
|
||||
while [ "$(kubectl get StatefulSet -l app.kubernetes.io/name=emqx -o jsonpath='{.items[0].status.replicas}')" \
|
||||
!= "$(kubectl get StatefulSet -l app.kubernetes.io/name=emqx -o jsonpath='{.items[0].status.readyReplicas}')" ]; do
|
||||
echo "==============================";
|
||||
kubectl get pods;
|
||||
echo "==============================";
|
||||
echo "waiting emqx started";
|
||||
sleep 10;
|
||||
done
|
||||
- name: get pods log
|
||||
if: failure()
|
||||
env:
|
||||
KUBECONFIG: "/etc/rancher/k3s/k3s.yaml"
|
||||
run: kubectl describe pods emqx-0
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: emqx/paho.mqtt.testing
|
||||
ref: develop-4.0
|
||||
path: paho.mqtt.testing
|
||||
- name: install pytest
|
||||
run: |
|
||||
pip install pytest
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
- name: run paho test
|
||||
env:
|
||||
KUBECONFIG: "/etc/rancher/k3s/k3s.yaml"
|
||||
run: |
|
||||
emqx_svc=$(kubectl get svc --namespace default emqx -o jsonpath="{.spec.clusterIP}")
|
||||
emqx1=$(kubectl get pods emqx-1 -o jsonpath='{.status.podIP}')
|
||||
emqx2=$(kubectl get pods emqx-2 -o jsonpath='{.status.podIP}')
|
||||
|
||||
pytest -v paho.mqtt.testing/interoperability/test_client/V5/test_connect.py -k test_basic --host $emqx_svc
|
||||
pytest -v paho.mqtt.testing/interoperability/test_cluster --host1 $emqx1 --host2 $emqx2
|
||||
|
||||
relup_test:
|
||||
runs-on: ubuntu-20.04
|
||||
container: emqx/build-env:erl23.2.2-ubuntu20.04
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.8'
|
||||
architecture: 'x64'
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: emqx/paho.mqtt.testing
|
||||
ref: develop-4.0
|
||||
path: paho.mqtt.testing
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: terry-xiaoyu/one_more_emqx
|
||||
ref: master
|
||||
path: one_more_emqx
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: emqx/emqtt-bench
|
||||
ref: master
|
||||
path: emqtt-bench
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: hawk/lux
|
||||
ref: lux-2.4
|
||||
path: lux
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ${{ github.repository }}
|
||||
path: emqx
|
||||
fetch-depth: 0
|
||||
- name: get version
|
||||
run: |
|
||||
set -e -x -u
|
||||
cd emqx
|
||||
vsn="$(erl -eval '{ok, [{application,emqx, L} | _]} = file:consult("src/emqx.app.src"), {vsn, VSN} = lists:keyfind(vsn,1,L), io:fwrite(VSN), halt().' -noshell)"
|
||||
echo "VSN=$vsn" >> $GITHUB_ENV
|
||||
pre_tag="$(echo $vsn | grep -oE '^[0-9]+.[0-9]')"
|
||||
old_vsns="$(git tag -l "$pre_tag.[0-9]" | tr "\n" " " | sed "s/$vsn//")"
|
||||
echo "OLD_VSNS=$old_vsns" >> $GITHUB_ENV
|
||||
- name: download emqx
|
||||
run: |
|
||||
set -e -x -u
|
||||
cd emqx
|
||||
old_vsns=($(echo $OLD_VSNS | tr ' ' ' '))
|
||||
for old_vsn in ${old_vsns[@]}; do
|
||||
wget https://s3-us-west-2.amazonaws.com/packages.emqx/emqx-ce/v$old_vsn/emqx-ubuntu20.04-${old_vsn}-x86_64.zip
|
||||
done
|
||||
- name: build emqx
|
||||
run: make -C emqx emqx-zip
|
||||
- name: build emqtt-bench
|
||||
run: make -C emqtt-bench
|
||||
- name: build lux
|
||||
run: |
|
||||
set -e -u -x
|
||||
cd lux
|
||||
autoconf
|
||||
./configure
|
||||
make
|
||||
make install
|
||||
- name: run relup test
|
||||
run: |
|
||||
set -e -x -u
|
||||
if [ -n "$OLD_VSNS" ]; then
|
||||
mkdir -p packages
|
||||
cp emqx/_packages/emqx/*.zip packages
|
||||
cp emqx/*.zip packages
|
||||
lux -v \
|
||||
--timeout 600000 \
|
||||
--var PACKAGE_PATH=$(pwd)/packages \
|
||||
--var BENCH_PATH=$(pwd)/emqtt-bench \
|
||||
--var ONE_MORE_EMQX_PATH=$(pwd)/one_more_emqx \
|
||||
--var VSN="$VSN" \
|
||||
--var OLD_VSNS="$OLD_VSNS" \
|
||||
emqx/.ci/fvt_tests/relup.lux
|
||||
fi
|
||||
- uses: actions/upload-artifact@v1
|
||||
if: failure()
|
||||
with:
|
||||
name: lux_logs
|
||||
path: lux_logs
|
||||
|
||||
|
||||
|
|
@ -4,7 +4,7 @@ on: [pull_request]
|
|||
|
||||
jobs:
|
||||
run_gitlint:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@master
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
name: Run test case
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
|
||||
run_test_case:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
container:
|
||||
image: erlang:22.1
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Code dialyzer
|
||||
run: |
|
||||
make xref
|
||||
make dialyzer
|
||||
rm -f rebar.lock
|
||||
- name: Run tests
|
||||
run: |
|
||||
make eunit
|
||||
rm -f rebar.lock
|
||||
make ct
|
||||
rm -f rebar.lock
|
||||
make cover
|
||||
- name: Coveralls
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
make coveralls
|
||||
- uses: actions/upload-artifact@v1
|
||||
if: always()
|
||||
with:
|
||||
name: logs
|
||||
path: _build/test/logs
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: cover
|
||||
path: _build/test/cover
|
|
@ -0,0 +1,59 @@
|
|||
name: Run test case
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
repository_dispatch:
|
||||
types: [run_actions]
|
||||
|
||||
jobs:
|
||||
run_test_case:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: set up
|
||||
env:
|
||||
MYSQL_TAG: 8
|
||||
REDIS_TAG: 6
|
||||
MONGO_TAG: 4
|
||||
PGSQL_TAG: 13
|
||||
LDAP_TAG: 2.4.50
|
||||
run: |
|
||||
docker-compose -f .ci/apps_tests/docker-compose.yaml build --no-cache
|
||||
docker-compose -f .ci/apps_tests/docker-compose.yaml up -d
|
||||
- name: set config files
|
||||
run: |
|
||||
sed -i 's|^[#[:space:]]*auth.ldap.servers[[:space:]]*=.*|auth.ldap.servers = ldap_server|g' apps/emqx_auth_ldap/etc/emqx_auth_ldap.conf
|
||||
sed -i 's|^[#[:space:]]*auth.mongo.server[[:space:]]*=.*|auth.mongo.server = mongo_server:27017|g' apps/emqx_auth_mongo/etc/emqx_auth_mongo.conf
|
||||
sed -i 's|^[#[:space:]]*auth.redis.server[[:space:]]*=.*|auth.redis.server = redis_server:6379|g' apps/emqx_auth_redis/etc/emqx_auth_redis.conf
|
||||
|
||||
sed -i 's|^[#[:space:]]*auth.mysql.server[[:space:]]*=.*|auth.mysql.server = mysql_server:3306|g' apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.mysql.username[[:space:]]*=.*|auth.mysql.username = root|g' apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.mysql.password[[:space:]]*=.*|auth.mysql.password = public|g' apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.mysql.database[[:space:]]*=.*|auth.mysql.database = mqtt|g' apps/emqx_auth_mysql/etc/emqx_auth_mysql.conf
|
||||
|
||||
sed -i 's|^[#[:space:]]*auth.pgsql.server[[:space:]]*=.*|auth.pgsql.server = pgsql_server:5432|g' apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.pgsql.username[[:space:]]*=.*|auth.pgsql.username = root|g' apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.pgsql.password[[:space:]]*=.*|auth.pgsql.password = public|g' apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
|
||||
sed -i 's|^[#[:space:]]*auth.pgsql.database[[:space:]]*=.*|auth.pgsql.database = mqtt|g' apps/emqx_auth_pgsql/etc/emqx_auth_pgsql.conf
|
||||
- name: run tests
|
||||
run: |
|
||||
docker exec -i erlang bash -c "make xref"
|
||||
docker exec -i erlang bash -c "make ct"
|
||||
docker exec -i erlang bash -c "make cover"
|
||||
- uses: actions/upload-artifact@v1
|
||||
if: failure()
|
||||
with:
|
||||
name: logs
|
||||
path: _build/test/logs
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: cover
|
||||
path: _build/test/cover
|
|
@ -12,34 +12,34 @@ ebin
|
|||
test/ebin/*.beam
|
||||
.exrc
|
||||
plugins/*/ebin
|
||||
log/
|
||||
*.swp
|
||||
*.so
|
||||
.erlang.mk/
|
||||
cover/
|
||||
emqx.d
|
||||
eunit.coverdata
|
||||
test/ct.cover.spec
|
||||
logs
|
||||
ct.coverdata
|
||||
.idea/
|
||||
emqx.iml
|
||||
_rel/
|
||||
data/
|
||||
_build
|
||||
.rebar3
|
||||
rebar3.crashdump
|
||||
.DS_Store
|
||||
emqx.iml
|
||||
bbmustache/
|
||||
etc/gen.emqx.conf
|
||||
compile_commands.json
|
||||
cuttlefish
|
||||
rebar.lock
|
||||
xrefr
|
||||
erlang.mk
|
||||
*.coverdata
|
||||
etc/emqx.conf.rendered
|
||||
Mnesia.*/
|
||||
elvis
|
||||
*.DS_Store
|
||||
_checkouts
|
||||
rebar.config.rendered
|
||||
/rebar3
|
||||
rebar.lock
|
||||
.stamp
|
||||
tmp/
|
||||
_packages
|
||||
elvis
|
||||
emqx_dialyzer_*_plt
|
||||
apps/emqx_dashboard/priv/www
|
||||
dist.zip
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
erlang 23.2.2
|
207
Makefile
207
Makefile
|
@ -1,139 +1,106 @@
|
|||
## shallow clone for speed
|
||||
REBAR_VERSION = 3.14.3-emqx-4
|
||||
DASHBOARD_VERSION = v4.3.0
|
||||
REBAR = $(CURDIR)/rebar3
|
||||
BUILD = $(CURDIR)/build
|
||||
export PKG_VSN ?= $(shell $(CURDIR)/pkg-vsn.sh)
|
||||
|
||||
REBAR_GIT_CLONE_OPTIONS += --depth 1
|
||||
export REBAR_GIT_CLONE_OPTIONS
|
||||
PROFILE ?= emqx
|
||||
REL_PROFILES := emqx emqx-edge
|
||||
PKG_PROFILES := emqx-pkg emqx-edge-pkg
|
||||
PROFILES := $(REL_PROFILES) $(PKG_PROFILES)
|
||||
|
||||
SUITES_FILES := $(shell find test -name '*_SUITE.erl' | sort)
|
||||
export REBAR_GIT_CLONE_OPTIONS += --depth=1
|
||||
|
||||
CT_SUITES := $(foreach value,$(SUITES_FILES),$(shell val=$$(basename $(value) .erl); echo $${val%_*}))
|
||||
|
||||
CT_NODE_NAME = emqxct@127.0.0.1
|
||||
|
||||
RUN_NODE_NAME = emqxdebug@127.0.0.1
|
||||
.PHONY: default
|
||||
default: $(REBAR) $(PROFILE)
|
||||
|
||||
.PHONY: all
|
||||
all: compile
|
||||
all: $(REBAR) $(PROFILES)
|
||||
|
||||
.PHONY: tests
|
||||
tests: eunit ct
|
||||
.PHONY: ensure-rebar3
|
||||
ensure-rebar3:
|
||||
$(CURDIR)/ensure-rebar3.sh $(REBAR_VERSION)
|
||||
|
||||
.PHONY: run
|
||||
run: run_setup unlock
|
||||
@rebar3 as test get-deps
|
||||
@rebar3 as test auto --name $(RUN_NODE_NAME) --script scripts/run_emqx.escript
|
||||
$(REBAR): ensure-rebar3
|
||||
|
||||
.PHONY: run_setup
|
||||
run_setup:
|
||||
@erl -noshell -eval \
|
||||
"{ok, [[HOME]]} = init:get_argument(home), \
|
||||
FilePath = HOME ++ \"/.config/rebar3/rebar.config\", \
|
||||
case file:consult(FilePath) of \
|
||||
{ok, Term} -> \
|
||||
NewTerm = case lists:keyfind(plugins, 1, Term) of \
|
||||
false -> [{plugins, [rebar3_auto]} | Term]; \
|
||||
{plugins, OldPlugins} -> \
|
||||
NewPlugins0 = OldPlugins -- [rebar3_auto], \
|
||||
NewPlugins = [rebar3_auto | NewPlugins0], \
|
||||
lists:keyreplace(plugins, 1, Term, {plugins, NewPlugins}) \
|
||||
end, \
|
||||
ok = file:write_file(FilePath, [io_lib:format(\"~p.\n\", [I]) || I <- NewTerm]); \
|
||||
_Enoent -> \
|
||||
os:cmd(\"mkdir -p ~/.config/rebar3/ \"), \
|
||||
NewTerm=[{plugins, [rebar3_auto]}], \
|
||||
ok = file:write_file(FilePath, [io_lib:format(\"~p.\n\", [I]) || I <- NewTerm]) \
|
||||
end, \
|
||||
halt(0)."
|
||||
|
||||
.PHONY: shell
|
||||
shell:
|
||||
@rebar3 as test auto
|
||||
|
||||
compile: unlock
|
||||
@rebar3 compile
|
||||
|
||||
unlock:
|
||||
@rebar3 unlock
|
||||
|
||||
clean: distclean
|
||||
|
||||
## Cuttlefish escript is built by default when cuttlefish app (as dependency) was built
|
||||
CUTTLEFISH_SCRIPT := _build/default/lib/cuttlefish/cuttlefish
|
||||
|
||||
.PHONY: cover
|
||||
cover:
|
||||
@rebar3 cover
|
||||
|
||||
.PHONY: coveralls
|
||||
coveralls:
|
||||
@rebar3 as test coveralls send
|
||||
|
||||
.PHONY: xref
|
||||
xref:
|
||||
@rebar3 xref
|
||||
|
||||
.PHONY: dialyzer
|
||||
dialyzer:
|
||||
@rebar3 dialyzer
|
||||
|
||||
.PHONY: proper
|
||||
proper:
|
||||
@rebar3 proper -d test/props -c
|
||||
|
||||
.PHONY: deps
|
||||
deps:
|
||||
@rebar3 get-deps
|
||||
.PHONY: get-dashboard
|
||||
get-dashboard:
|
||||
$(CURDIR)/get-dashboard.sh $(DASHBOARD_VERSION)
|
||||
|
||||
.PHONY: eunit
|
||||
eunit:
|
||||
@rebar3 eunit -v
|
||||
|
||||
.PHONY: ct_setup
|
||||
ct_setup:
|
||||
rebar3 as test compile
|
||||
@mkdir -p data
|
||||
@if [ ! -f data/loaded_plugins ]; then touch data/loaded_plugins; fi
|
||||
@ln -s -f '../../../../etc' _build/test/lib/emqx/
|
||||
@ln -s -f '../../../../data' _build/test/lib/emqx/
|
||||
eunit: $(REBAR)
|
||||
$(REBAR) eunit
|
||||
|
||||
.PHONY: ct
|
||||
ct: ct_setup
|
||||
@rebar3 ct -v --name $(CT_NODE_NAME) --suite=$(shell echo $(foreach var,$(CT_SUITES),test/$(var)_SUITE) | tr ' ' ',')
|
||||
ct: $(REBAR)
|
||||
$(REBAR) ct --name 'test@127.0.0.1' -c -v
|
||||
|
||||
## Run one single CT with rebar3
|
||||
## e.g. make ct-one-suite suite=emqx_bridge
|
||||
.PHONY: $(SUITES:%=ct-%)
|
||||
$(CT_SUITES:%=ct-%): ct_setup
|
||||
@rebar3 ct -v --readable=false --name $(CT_NODE_NAME) --suite=$(@:ct-%=%)_SUITE --cover
|
||||
.PHONY: cover
|
||||
cover: $(REBAR)
|
||||
$(REBAR) cover
|
||||
|
||||
.PHONY: app.config
|
||||
app.config: $(CUTTLEFISH_SCRIPT) etc/gen.emqx.conf
|
||||
$(CUTTLEFISH_SCRIPT) -l info -e etc/ -c etc/gen.emqx.conf -i priv/emqx.schema -d data/
|
||||
.PHONY: $(REL_PROFILES)
|
||||
$(REL_PROFILES:%=%): $(REBAR) get-dashboard
|
||||
ifneq ($(shell echo $(@) |grep edge),)
|
||||
export EMQX_DESC="EMQ X Edge"
|
||||
else
|
||||
export EMQX_DESC="EMQ X Broker"
|
||||
endif
|
||||
$(REBAR) as $(@) release
|
||||
|
||||
$(CUTTLEFISH_SCRIPT):
|
||||
@rebar3 get-deps
|
||||
@if [ ! -f cuttlefish ]; then make -C _build/default/lib/cuttlefish; fi
|
||||
# rebar clean
|
||||
.PHONY: clean $(PROFILES:%=clean-%)
|
||||
clean: $(PROFILES:%=clean-%)
|
||||
$(PROFILES:%=clean-%): $(REBAR)
|
||||
$(REBAR) as $(@:clean-%=%) clean
|
||||
rm -rf apps/emqx_dashboard/priv/www
|
||||
|
||||
bbmustache:
|
||||
@git clone https://github.com/soranoba/bbmustache.git && cd bbmustache && ./rebar3 compile && cd ..
|
||||
.PHONY: deps-all
|
||||
deps-all: $(REBAR) $(PROFILES:%=deps-%)
|
||||
|
||||
# This hack is to generate a conf file for testing
|
||||
# relx overlay is used for release
|
||||
etc/gen.emqx.conf: bbmustache etc/emqx.conf
|
||||
@erl -noshell -pa bbmustache/_build/default/lib/bbmustache/ebin -eval \
|
||||
"{ok, Temp} = file:read_file('etc/emqx.conf'), \
|
||||
{ok, Vars0} = file:consult('vars'), \
|
||||
Vars = [{atom_to_list(N), list_to_binary(V)} || {N, V} <- Vars0], \
|
||||
Targ = bbmustache:render(Temp, Vars), \
|
||||
ok = file:write_file('etc/gen.emqx.conf', Targ), \
|
||||
halt(0)."
|
||||
.PHONY: $(PROFILES:%=deps-%)
|
||||
$(PROFILES:%=deps-%): $(REBAR) get-dashboard
|
||||
ifneq ($(shell echo $(@) |grep edge),)
|
||||
export EMQX_DESC="EMQ X Edge"
|
||||
else
|
||||
export EMQX_DESC="EMQ X Broker"
|
||||
endif
|
||||
$(REBAR) as $(@:deps-%=%) get-deps
|
||||
|
||||
.PHONY: gen-clean
|
||||
gen-clean:
|
||||
@rm -rf bbmustache
|
||||
@rm -f etc/gen.emqx.conf etc/emqx.conf.rendered
|
||||
.PHONY: xref
|
||||
xref: $(REBAR)
|
||||
$(REBAR) as check xref
|
||||
|
||||
.PHONY: distclean
|
||||
distclean: gen-clean
|
||||
@rm -rf Mnesia.*
|
||||
@rm -rf _build cover deps logs log data
|
||||
@rm -f rebar.lock compile_commands.json cuttlefish erl_crash.dump
|
||||
.PHONY: dialyzer
|
||||
dialyzer: $(REBAR)
|
||||
$(REBAR) as check dialyzer
|
||||
|
||||
.PHONY: $(REL_PROFILES:%=relup-%)
|
||||
$(REL_PROFILES:%=relup-%): $(REBAR)
|
||||
ifneq ($(OS),Windows_NT)
|
||||
$(BUILD) $(@:relup-%=%) relup
|
||||
endif
|
||||
|
||||
.PHONY: $(REL_PROFILES:%=%-tar) $(PKG_PROFILES:%=%-tar)
|
||||
$(REL_PROFILES:%=%-tar) $(PKG_PROFILES:%=%-tar): $(REBAR) get-dashboard
|
||||
$(BUILD) $(subst -tar,,$(@)) tar
|
||||
|
||||
## zip targets depend on the corresponding relup and tar artifacts
|
||||
.PHONY: $(REL_PROFILES:%=%-zip)
|
||||
define gen-zip-target
|
||||
$1-zip: relup-$1 $1-tar
|
||||
$(BUILD) $1 zip
|
||||
endef
|
||||
ALL_ZIPS = $(REL_PROFILES) $(PKG_PROFILES)
|
||||
$(foreach zt,$(ALL_ZIPS),$(eval $(call gen-zip-target,$(zt))))
|
||||
|
||||
## A pkg target depend on a regular release profile zip to include relup,
|
||||
## and also a -pkg suffixed profile tar (without relup) for making deb/rpm package
|
||||
.PHONY: $(PKG_PROFILES)
|
||||
define gen-pkg-target
|
||||
$1: $(subst -pkg,,$1)-zip $1-tar
|
||||
$(BUILD) $1 pkg
|
||||
endef
|
||||
$(foreach pt,$(PKG_PROFILES),$(eval $(call gen-pkg-target,$(pt))))
|
||||
|
||||
include docker.mk
|
||||
|
|
11
README-CN.md
11
README-CN.md
|
@ -62,6 +62,17 @@ cd _rel/emqx && ./bin/emqx console
|
|||
|
||||
*EMQ X* 启动,可以使用浏览器访问 http://localhost:18083 来查看 Dashboard。
|
||||
|
||||
### 静态分析(Dialyzer)
|
||||
##### 分析所有应用程序
|
||||
```
|
||||
make dialyzer
|
||||
```
|
||||
|
||||
##### 要分析特定的应用程序,(用逗号分隔的应用程序列表)
|
||||
```
|
||||
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
访问 [EMQ X FAQ](https://docs.emqx.io/broker/latest/cn/faq/faq.html) 以获取常见问题的帮助。
|
||||
|
|
11
README-JP.md
11
README-JP.md
|
@ -62,6 +62,17 @@ cd _rel/emqx && ./bin/emqx console
|
|||
|
||||
*EMQ X* ブローカーを起動したら、ブラウザで http://localhost:18083 にアクセスしてダッシュボードを表示できます。
|
||||
|
||||
### Dialyzer
|
||||
##### アプリケーションの型情報を解析する
|
||||
```
|
||||
make dialyzer
|
||||
```
|
||||
|
||||
##### 特定のアプリケーションのみ解析する(アプリケーション名をコンマ区切りで入力)
|
||||
```
|
||||
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
よくある質問については、[EMQ X FAQ](https://docs.emqx.io/broker/latest/en/faq/faq.html)にアクセスしてください。
|
||||
|
|
54
README.md
54
README.md
|
@ -41,18 +41,30 @@ Get the binary package of the corresponding OS from [EMQ X Download](https://www
|
|||
|
||||
The *EMQ X* broker requires Erlang/OTP R21+ to build since 3.0 release.
|
||||
|
||||
For 4.3 and later versions.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/emqx/emqx.git
|
||||
cd emqx
|
||||
make
|
||||
_build/emqx/rel/emqx/bin console
|
||||
```
|
||||
git clone -b v4.0.0 https://github.com/emqx/emqx-rel.git
|
||||
|
||||
cd emqx-rel && make
|
||||
|
||||
cd _build/emqx/rel/emqx && ./bin/emqx console
|
||||
For earlier versions, release has to be built from another repo.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/emqx/emqx-rel.git
|
||||
cd emqx-rel
|
||||
make
|
||||
_build/emqx/rel/emqx/bin/emqx console
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```
|
||||
If emqx is built from source, `cd _buid/emqx/rel/emqx`.
|
||||
Or change to the installation root directory if emqx is installed from a release package.
|
||||
|
||||
```bash
|
||||
# Start emqx
|
||||
./bin/emqx start
|
||||
|
||||
|
@ -65,6 +77,38 @@ cd _build/emqx/rel/emqx && ./bin/emqx console
|
|||
|
||||
To view the dashboard after running, use your browser to open: http://localhost:18083
|
||||
|
||||
## Test
|
||||
|
||||
### To test everything in one go
|
||||
|
||||
```
|
||||
make eunit ct
|
||||
```
|
||||
|
||||
### To run subset of the common tests
|
||||
|
||||
examples
|
||||
|
||||
```bash
|
||||
./rebar3 ct --name 'test@127.0.0.1' -c -v --dir test,apps/emqx_sn,apps/emqx_coap
|
||||
./rebar3 ct --name 'test@127.0.0.1' -c -v --dir apps/emqx_auth_mnesi --suite emqx_acl_mnesia_SUITE
|
||||
./rebar3 ct --name 'test@127.0.0.1' -c -v --dir apps/emqx_auth_mnesi --suite emqx_acl_mnesia_SUITE --case t_rest_api
|
||||
```
|
||||
|
||||
NOTE: Do *NOT* use full (relative) path to SUITE files like this `--suite apps/emqx_auth_mnesia/test/emqx_acl_mnesia_SUITE.erl`,
|
||||
because it will lead to a full copy of `apps` dir into `_buid/test/lib/emqx`.
|
||||
|
||||
### Dialyzer
|
||||
##### To Analyze all the apps
|
||||
```
|
||||
make dialyzer
|
||||
```
|
||||
|
||||
##### To Analyse specific apps, (list of comma separated apps)
|
||||
```
|
||||
DIALYZER_ANALYSE_APP=emqx_lwm2m,emqx_auth_jwt,emqx_auth_ldap make dialyzer
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
Visiting [EMQ X FAQ](https://docs.emqx.io/broker/latest/en/faq/faq.html) to get help of common problems.
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
.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
|
|
@ -0,0 +1,100 @@
|
|||
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.
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
##--------------------------------------------------------------------
|
||||
## 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
|
||||
##
|
||||
## 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:
|
||||
## - %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.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
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
-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)).
|
|
@ -0,0 +1,118 @@
|
|||
%%-*- 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}.
|
||||
|
||||
{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}
|
||||
]}.
|
|
@ -0,0 +1,28 @@
|
|||
{deps,
|
||||
[{ehttpc, {git, "https://github.com/emqx/ehttpc", {tag, "0.1.2"}}}
|
||||
]}.
|
||||
|
||||
{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,
|
||||
[{emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "1.2.2"}}},
|
||||
{emqtt, {git, "https://github.com/emqx/emqtt", {tag, "v1.2.2"}}}
|
||||
]}
|
||||
]}
|
||||
]}.
|
|
@ -0,0 +1,88 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020 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.
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{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"}
|
||||
]}
|
||||
]}.
|
|
@ -0,0 +1,112 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020 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.
|
|
@ -0,0 +1,175 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020 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),
|
||||
#{host := Host0,
|
||||
path := Path0,
|
||||
scheme := Scheme} = URIMap = uri_string:parse(add_default_scheme(uri_string:normalize(URL))),
|
||||
Port = maps:get(port, URIMap, case Scheme of
|
||||
"https" -> 443;
|
||||
"http" -> 80
|
||||
end),
|
||||
Path = path(Path0),
|
||||
{Inet, Host} = parse_host(Host0),
|
||||
MoreOpts = case Scheme of
|
||||
"http" ->
|
||||
[{transport_opts, [Inet]}];
|
||||
"https" ->
|
||||
CACertFile = application:get_env(?APP, cacertfile, undefined),
|
||||
CertFile = application:get_env(?APP, certfile, undefined),
|
||||
KeyFile = application:get_env(?APP, keyfile, undefined),
|
||||
TLSOpts = lists:filter(fun({_K, V}) when V =:= <<>> ->
|
||||
false;
|
||||
(_) ->
|
||||
true
|
||||
end, [{keyfile, KeyFile}, {certfile, CertFile}, {cacertfile, CACertFile}]),
|
||||
TlsVers = ['tlsv1.2','tlsv1.1',tlsv1],
|
||||
NTLSOpts = [{versions, TlsVers},
|
||||
{ciphers, lists:foldl(fun(TlsVer, Ciphers) ->
|
||||
Ciphers ++ ssl:cipher_suites(all, TlsVer)
|
||||
end, [], TlsVers)} | TLSOpts],
|
||||
[{transport, ssl}, {transport_opts, [Inet | 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, to_lower(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),
|
||||
ehttpc_sup:start_pool(PoolName, PoolOpts),
|
||||
case application:get_env(?APP, super_req) of
|
||||
undefined ->
|
||||
emqx:hook('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),
|
||||
ehttpc_sup:start_pool(PoolName1, PoolOpts1),
|
||||
emqx:hook('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),
|
||||
ehttpc_sup:start_pool(PoolName2, PoolOpts2),
|
||||
emqx:hook('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.
|
||||
|
||||
parse_host(Host) ->
|
||||
case inet:parse_address(Host) of
|
||||
{ok, Addr} when size(Addr) =:= 4 -> {inet, Addr};
|
||||
{ok, Addr} when size(Addr) =:= 8 -> {inet6, Addr};
|
||||
{error, einval} ->
|
||||
case inet:getaddr(Host, inet6) of
|
||||
{ok, _} -> {inet6, Host};
|
||||
{error, _} -> {inet, Host}
|
||||
end
|
||||
end.
|
||||
|
||||
to_lower(Headers) ->
|
||||
[{string:to_lower(K), V} || {K, V} <- Headers].
|
||||
|
||||
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).
|
||||
|
||||
add_default_scheme(URL) when is_list(URL) ->
|
||||
binary_to_list(add_default_scheme(list_to_binary(URL)));
|
||||
add_default_scheme(<<"http://", _/binary>> = URL) ->
|
||||
URL;
|
||||
add_default_scheme(<<"https://", _/binary>> = URL) ->
|
||||
URL;
|
||||
add_default_scheme(URL) ->
|
||||
<<"http://", URL/binary>>.
|
||||
|
||||
path("") ->
|
||||
"/";
|
||||
path(Path) ->
|
||||
Path.
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020 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).
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020 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_sup).
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
-export([start_link/0]).
|
||||
|
||||
-export([init/1]).
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
init([]) ->
|
||||
{ok, {{one_for_all, 0, 1}, []}}.
|
|
@ -0,0 +1,173 @@
|
|||
%% Copyright (c) 2020 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}].
|
||||
|
||||
groups() ->
|
||||
Cases = emqx_ct:all(?MODULE),
|
||||
[{Name, Cases} || Name <- [http_inet, http_inet6, https_inet, https_inet6]].
|
||||
|
||||
init_per_group(GrpName, Cfg) ->
|
||||
[Schema, Inet] = [list_to_atom(X) || X <- string:tokens(atom_to_list(GrpName), "_")],
|
||||
http_auth_server:start(Schema, Inet),
|
||||
Fun = fun(App) -> set_special_configs(App, Schema, Inet) end,
|
||||
emqx_ct_helpers:start_apps([emqx_auth_http], Fun),
|
||||
Cfg.
|
||||
|
||||
end_per_group(_GrpName, _Cfg) ->
|
||||
http_auth_server:stop(),
|
||||
emqx_ct_helpers:stop_apps([emqx_auth_http, emqx]).
|
||||
|
||||
set_special_configs(emqx, _Schmea, _Inet) ->
|
||||
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, Schema, Inet) ->
|
||||
ServerAddr = http_server(Schema, 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"}]},
|
||||
|
||||
Schema =:= 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)),
|
||||
application:set_env(emqx_auth_http, acl_req, maps:to_list(AclReq)).
|
||||
|
||||
%% @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)).
|
||||
|
||||
%% @private
|
||||
http_server(http, inet) -> "http://127.0.0.1:8991";
|
||||
http_server(http, inet6) -> "http://[::1]:8991";
|
||||
http_server(https, inet) -> "https://127.0.0.1:8991";
|
||||
http_server(https, inet6) -> "https://[::1]:8991".
|
||||
|
||||
%%------------------------------------------------------------------------------
|
||||
%% Testcases
|
||||
%%------------------------------------------------------------------------------
|
||||
|
||||
t_check_acl(_) ->
|
||||
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(_) ->
|
||||
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">>}).
|
||||
|
||||
t_sub_pub(_) ->
|
||||
ct:pal("start client"),
|
||||
{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_comment_config(_) ->
|
||||
AuthCount = length(emqx_hooks:lookup('client.authenticate')),
|
||||
AclCount = length(emqx_hooks:lookup('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')),
|
||||
?assertEqual(AuthCount - 1, length(emqx_hooks:lookup('client.authenticate'))),
|
||||
?assertEqual(AclCount - 1, length(emqx_hooks:lookup('client.check_acl'))).
|
|
@ -0,0 +1,152 @@
|
|||
-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.
|
|
@ -0,0 +1,28 @@
|
|||
.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/
|
|
@ -0,0 +1,90 @@
|
|||
|
||||
# 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.
|
|
@ -0,0 +1,2 @@
|
|||
1. Notice for the [Critical vulnerabilities in JSON Web Token](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
https://crypto.stackexchange.com/questions/30657/hmac-vs-ecdsa-for-jwt
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
##--------------------------------------------------------------------
|
||||
## 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 = 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 = 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
|
|
@ -0,0 +1,49 @@
|
|||
%%-*- mode: erlang -*-
|
||||
|
||||
{mapping, "auth.jwt.secret", "emqx_auth_jwt.secret", [
|
||||
{datatype, string}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.jwt.jwks", "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", "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", 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}.
|
|
@ -0,0 +1,25 @@
|
|||
{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, [{emqx_ct_helpers, {git, "http://github.com/emqx/emqx-ct-helpers", {tag, "1.2.2"}}}]}
|
||||
]}
|
||||
]}.
|
|
@ -0,0 +1,14 @@
|
|||
{application, emqx_auth_jwt,
|
||||
[{description, "EMQ X Authentication with JWT"},
|
||||
{vsn, "4.3.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"}
|
||||
]}
|
||||
]}.
|
|
@ -0,0 +1,99 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020 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).
|
|
@ -0,0 +1,81 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020 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).
|
|
@ -0,0 +1,222 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020 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} ->
|
||||
[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
|
||||
_:_ ->
|
||||
{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 ->
|
||||
{error, invalid_signature};
|
||||
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
|
||||
end
|
||||
end.
|
|
@ -0,0 +1,142 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020 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
|
||||
]}
|
||||
].
|
||||
|
||||
init_per_suite(Config) ->
|
||||
emqx_ct_helpers:start_apps([emqx, emqx_auth_jwt], fun set_special_configs/1),
|
||||
Config.
|
||||
|
||||
end_per_suite(_Config) ->
|
||||
emqx_ct_helpers:stop_apps([emqx_auth_jwt, emqx]).
|
||||
|
||||
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, 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">>}]),
|
||||
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">>}]),
|
||||
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">>),
|
||||
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">>}]),
|
||||
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).
|
|
@ -0,0 +1,25 @@
|
|||
.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
|
|
@ -0,0 +1,96 @@
|
|||
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.
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
## 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=
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
#
|
||||
# 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 ) )
|
|
@ -0,0 +1,78 @@
|
|||
##--------------------------------------------------------------------
|
||||
## 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 = 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.fail_if_no_peer_cert = true
|
||||
|
||||
#auth.ldap.ssl.server_name_indication = your_server_name
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
-define(APP, emqx_auth_ldap).
|
||||
|
||||
-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)).
|
|
@ -0,0 +1,176 @@
|
|||
%%-*- mode: erlang -*-
|
||||
%% emqx_auth_ldap config mapping
|
||||
|
||||
{mapping, "auth.ldap.servers", "emqx_auth_ldap.ldap", [
|
||||
{default, "127.0.0.1"},
|
||||
{datatype, string}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.port", "emqx_auth_ldap.ldap", [
|
||||
{default, 389},
|
||||
{datatype, integer}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.pool", "emqx_auth_ldap.ldap", [
|
||||
{default, 8},
|
||||
{datatype, integer}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.bind_dn", "emqx_auth_ldap.ldap", [
|
||||
{datatype, string},
|
||||
{default, "cn=root,dc=emqx,dc=io"}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.bind_password", "emqx_auth_ldap.ldap", [
|
||||
{datatype, string},
|
||||
{default, "public"}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.timeout", "emqx_auth_ldap.ldap", [
|
||||
{default, "30s"},
|
||||
{datatype, {duration, ms}}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.ssl", "emqx_auth_ldap.ldap", [
|
||||
{default, false},
|
||||
{datatype, {enum, [true, false]}}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.ssl.certfile", "emqx_auth_ldap.ldap", [
|
||||
{datatype, string}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.ssl.keyfile", "emqx_auth_ldap.ldap", [
|
||||
{datatype, string}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.ssl.cacertfile", "emqx_auth_ldap.ldap", [
|
||||
{datatype, string}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.ssl.verify", "emqx_auth_ldap.ldap", [
|
||||
{default, verify_none},
|
||||
{datatype, {enum, [verify_none, verify_peer]}}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.ssl.fail_if_no_peer_cert", "emqx_auth_ldap.ldap", [
|
||||
{datatype, {enum, [true, false]}}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.ssl.server_name_indication", "emqx_auth_ldap.ldap", [
|
||||
{datatype, string}
|
||||
]}.
|
||||
|
||||
{translation, "emqx_auth_ldap.ldap", fun(Conf) ->
|
||||
A2N = fun(A) -> case inet:parse_address(A) of {ok, N} -> N; _ -> A end end,
|
||||
Servers = [A2N(A) || A <- string:tokens(cuttlefish:conf_get("auth.ldap.servers", Conf), ",")],
|
||||
Port = cuttlefish:conf_get("auth.ldap.port", Conf),
|
||||
Pool = cuttlefish:conf_get("auth.ldap.pool", Conf),
|
||||
BindDN = cuttlefish:conf_get("auth.ldap.bind_dn", Conf),
|
||||
BindPassword = cuttlefish:conf_get("auth.ldap.bind_password", Conf),
|
||||
Timeout = cuttlefish:conf_get("auth.ldap.timeout", Conf),
|
||||
Filter = fun(Ls) -> [E || E = {_, V} <- Ls, V /= undefined]end,
|
||||
SslOpts = fun() ->
|
||||
[{certfile, cuttlefish:conf_get("auth.ldap.ssl.certfile", Conf)},
|
||||
{keyfile, cuttlefish:conf_get("auth.ldap.ssl.keyfile", Conf)},
|
||||
{cacertfile, cuttlefish:conf_get("auth.ldap.ssl.cacertfile", Conf, undefined)},
|
||||
{verify, cuttlefish:conf_get("auth.ldap.ssl.verify", Conf, undefined)},
|
||||
{server_name_indication, cuttlefish:conf_get("auth.ldap.ssl.server_name_indication", Conf, disable)},
|
||||
{fail_if_no_peer_cert, cuttlefish:conf_get("auth.ldap.ssl.fail_if_no_peer_cert", Conf, undefined)}]
|
||||
end,
|
||||
Opts = [{servers, Servers},
|
||||
{port, Port},
|
||||
{timeout, Timeout},
|
||||
{bind_dn, BindDN},
|
||||
{bind_password, BindPassword},
|
||||
{pool, Pool},
|
||||
{auto_reconnect, 2}],
|
||||
case cuttlefish:conf_get("auth.ldap.ssl", Conf) of
|
||||
true -> [{ssl, true}, {sslopts, Filter(SslOpts())}|Opts];
|
||||
false -> [{ssl, false}|Opts]
|
||||
end
|
||||
end}.
|
||||
|
||||
{mapping, "auth.ldap.device_dn", "emqx_auth_ldap.device_dn", [
|
||||
{default, "ou=device,dc=emqx,dc=io"},
|
||||
{datatype, string}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.match_objectclass", "emqx_auth_ldap.match_objectclass", [
|
||||
{default, "mqttUser"},
|
||||
{datatype, string}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.custom_base_dn", "emqx_auth_ldap.custom_base_dn", [
|
||||
{default, "${username_attr}=${user},${device_dn}"},
|
||||
{datatype, string}
|
||||
]}.
|
||||
|
||||
%% auth.ldap.filters.1.key = "objectClass"
|
||||
%% auth.ldap.filters.1.value = "mqttUser"
|
||||
%% auth.ldap.filters.1.op = "and"
|
||||
%% auth.ldap.filters.2.key = "uiAttr"
|
||||
%% auth.ldap.filters.2.value "someAttr"
|
||||
%% auth.ldap.filters.2.op = "or"
|
||||
%% auth.ldap.filters.3.key = "someKey"
|
||||
%% auth.ldap.filters.3.value = "someValue"
|
||||
%% The configuratation structure sent to the application:
|
||||
%% [{"objectClass","mqttUser"},"and",{"uiAttr","someAttr"},"or",{"someKey","someAttr"}]
|
||||
%% The resulting LDAP filter would look like this:
|
||||
%% ==> "|(&(objectClass=Class)(uiAttr=someAttr)(someKey=someValue))"
|
||||
{translation, "emqx_auth_ldap.filters",
|
||||
fun(Conf) ->
|
||||
Settings = cuttlefish_variable:filter_by_prefix("auth.ldap.filters", Conf),
|
||||
Keys = [{Num, {key, V}} || {["auth","ldap","filters", Num, "key"], V} <- Settings],
|
||||
Values = [{Num, {value, V}} || {["auth","ldap","filters", Num, "value"], V} <- Settings],
|
||||
Ops = [{Num, {op, V}} || {["auth","ldap","filters", Num, "op"], V} <- Settings],
|
||||
RawFilters = Keys ++ Values ++ Ops,
|
||||
Filters =
|
||||
lists:foldl(
|
||||
fun({Num,{T,V}}, Acc)->
|
||||
maps:update_with(Num,
|
||||
fun(F)->
|
||||
maps:put(T,V,F)
|
||||
end,
|
||||
#{T=>V}, Acc)
|
||||
end, #{}, RawFilters),
|
||||
Order=lists:usort(maps:keys(Filters)),
|
||||
lists:reverse(
|
||||
lists:foldl(
|
||||
fun(F,Acc)->
|
||||
case F of
|
||||
#{key:=K, op:=Op, value:=V} -> [Op,{K,V}|Acc];
|
||||
#{key:=K, value:=V} -> [{K,V}|Acc]
|
||||
end
|
||||
end,
|
||||
[],
|
||||
lists:map(fun(K) -> maps:get(K, Filters) end, Order)))
|
||||
end}.
|
||||
|
||||
{mapping, "auth.ldap.filters.$num.key", "emqx_auth_ldap.filters", [
|
||||
{datatype, string}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.filters.$num.value", "emqx_auth_ldap.filters", [
|
||||
{datatype, string}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.filters.$num.op", "emqx_auth_ldap.filters", [
|
||||
{datatype, {enum, [ "or", "and" ] } }
|
||||
]}.
|
||||
|
||||
|
||||
{mapping, "auth.ldap.bind_as_user", "emqx_auth_ldap.bind_as_user", [
|
||||
{default, false},
|
||||
{datatype, {enum, [true, false]}}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.username.attributetype", "emqx_auth_ldap.username_attr", [
|
||||
{default, "uid"},
|
||||
{datatype, string}
|
||||
]}.
|
||||
|
||||
{mapping, "auth.ldap.password.attributetype", "emqx_auth_ldap.password_attr", [
|
||||
{default, "userPassword"},
|
||||
{datatype, string}
|
||||
]}.
|
|
@ -0,0 +1,25 @@
|
|||
{deps,
|
||||
[{eldap2, {git, "https://github.com/emqx/eldap2", {tag, "v0.2.2"}}}
|
||||
]}.
|
||||
|
||||
{profiles,
|
||||
[{test,
|
||||
[{deps, [{emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "1.2.2"}}}]}
|
||||
]}
|
||||
]}.
|
||||
|
||||
{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}.
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020 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_ldap).
|
||||
|
||||
-include("emqx_auth_ldap.hrl").
|
||||
|
||||
-include_lib("emqx/include/emqx.hrl").
|
||||
-include_lib("eldap/include/eldap.hrl").
|
||||
-include_lib("emqx/include/logger.hrl").
|
||||
|
||||
-export([ register_metrics/0
|
||||
, check_acl/5
|
||||
, description/0
|
||||
]).
|
||||
|
||||
-import(proplists, [get_value/2]).
|
||||
|
||||
-import(emqx_auth_ldap_cli, [search/4]).
|
||||
|
||||
-spec(register_metrics() -> ok).
|
||||
register_metrics() ->
|
||||
lists:foreach(fun emqx_metrics:ensure/1, ?ACL_METRICS).
|
||||
|
||||
check_acl(ClientInfo, PubSub, Topic, NoMatchAction, State) ->
|
||||
case do_check_acl(ClientInfo, PubSub, Topic, NoMatchAction, State) of
|
||||
ok -> emqx_metrics:inc(?ACL_METRICS(ignore)), ok;
|
||||
{stop, allow} -> emqx_metrics:inc(?ACL_METRICS(allow)), {stop, allow};
|
||||
{stop, deny} -> emqx_metrics:inc(?ACL_METRICS(deny)), {stop, deny}
|
||||
end.
|
||||
|
||||
do_check_acl(#{username := <<$$, _/binary>>}, _PubSub, _Topic, _NoMatchAction, _State) ->
|
||||
ok;
|
||||
|
||||
do_check_acl(#{username := Username}, PubSub, Topic, _NoMatchAction,
|
||||
#{device_dn := DeviceDn,
|
||||
match_objectclass := ObjectClass,
|
||||
username_attr := UidAttr,
|
||||
custom_base_dn := CustomBaseDN,
|
||||
pool := Pool} = Config) ->
|
||||
|
||||
Filters = maps:get(filters, Config, []),
|
||||
|
||||
ReplaceRules = [{"${username_attr}", UidAttr},
|
||||
{"${user}", binary_to_list(Username)},
|
||||
{"${device_dn}", DeviceDn}],
|
||||
|
||||
Filter = emqx_auth_ldap:prepare_filter(Filters, UidAttr, ObjectClass, ReplaceRules),
|
||||
|
||||
Attribute = case PubSub of
|
||||
publish -> "mqttPublishTopic";
|
||||
subscribe -> "mqttSubscriptionTopic"
|
||||
end,
|
||||
Attribute1 = "mqttPubSubTopic",
|
||||
?LOG(debug, "[LDAP] search dn:~p filter:~p, attribute:~p",
|
||||
[DeviceDn, Filter, Attribute]),
|
||||
|
||||
BaseDN = emqx_auth_ldap:replace_vars(CustomBaseDN, ReplaceRules),
|
||||
|
||||
case search(Pool, BaseDN, Filter, [Attribute, Attribute1]) of
|
||||
{error, noSuchObject} ->
|
||||
ok;
|
||||
{ok, #eldap_search_result{entries = []}} ->
|
||||
ok;
|
||||
{ok, #eldap_search_result{entries = [Entry]}} ->
|
||||
Topics = get_value(Attribute, Entry#eldap_entry.attributes)
|
||||
++ get_value(Attribute1, Entry#eldap_entry.attributes),
|
||||
match(Topic, Topics);
|
||||
Error ->
|
||||
?LOG(error, "[LDAP] search error:~p", [Error]),
|
||||
{stop, deny}
|
||||
end.
|
||||
|
||||
match(_Topic, []) ->
|
||||
ok;
|
||||
|
||||
match(Topic, [Filter | Topics]) ->
|
||||
case emqx_topic:match(Topic, list_to_binary(Filter)) of
|
||||
true -> {stop, allow};
|
||||
false -> match(Topic, Topics)
|
||||
end.
|
||||
|
||||
description() ->
|
||||
"ACL with LDAP".
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{application, emqx_auth_ldap,
|
||||
[{description, "EMQ X Authentication/ACL with LDAP"},
|
||||
{vsn, "4.3.0"}, % strict semver, bump manually!
|
||||
{modules, []},
|
||||
{registered, [emqx_auth_ldap_sup]},
|
||||
{applications, [kernel,stdlib,eldap2,ecpool]},
|
||||
{mod, {emqx_auth_ldap_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-ldap"}
|
||||
]}
|
||||
]}.
|
|
@ -0,0 +1,210 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020 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_ldap).
|
||||
|
||||
-include("emqx_auth_ldap.hrl").
|
||||
|
||||
-include_lib("emqx/include/emqx.hrl").
|
||||
-include_lib("eldap/include/eldap.hrl").
|
||||
-include_lib("emqx/include/logger.hrl").
|
||||
|
||||
-import(proplists, [get_value/2]).
|
||||
|
||||
-import(emqx_auth_ldap_cli, [search/3]).
|
||||
|
||||
-export([ register_metrics/0
|
||||
, check/3
|
||||
, description/0
|
||||
, prepare_filter/4
|
||||
, replace_vars/2
|
||||
]).
|
||||
|
||||
-spec(register_metrics() -> ok).
|
||||
register_metrics() ->
|
||||
lists:foreach(fun emqx_metrics:ensure/1, ?AUTH_METRICS).
|
||||
|
||||
check(ClientInfo = #{username := Username, password := Password}, AuthResult,
|
||||
State = #{password_attr := PasswdAttr, bind_as_user := BindAsUserRequired, pool := Pool}) ->
|
||||
CheckResult =
|
||||
case lookup_user(Username, State) of
|
||||
undefined -> {error, not_found};
|
||||
{error, Error} -> {error, Error};
|
||||
Entry ->
|
||||
PasswordString = binary_to_list(Password),
|
||||
ObjectName = Entry#eldap_entry.object_name,
|
||||
Attributes = Entry#eldap_entry.attributes,
|
||||
case BindAsUserRequired of
|
||||
true ->
|
||||
emqx_auth_ldap_cli:post_bind(Pool, ObjectName, PasswordString);
|
||||
false ->
|
||||
case get_value(PasswdAttr, Attributes) of
|
||||
undefined ->
|
||||
logger:error("LDAP Search State: ~p, uid: ~p, result:~p",
|
||||
[State, Username, Attributes]),
|
||||
{error, not_found};
|
||||
[Passhash1] ->
|
||||
format_password(Passhash1, Password, ClientInfo)
|
||||
end
|
||||
end
|
||||
end,
|
||||
case CheckResult of
|
||||
ok ->
|
||||
ok = emqx_metrics:inc(?AUTH_METRICS(success)),
|
||||
{stop, AuthResult#{auth_result => success, anonymous => false}};
|
||||
{error, not_found} ->
|
||||
emqx_metrics:inc(?AUTH_METRICS(ignore));
|
||||
{error, ResultCode} ->
|
||||
ok = emqx_metrics:inc(?AUTH_METRICS(failure)),
|
||||
?LOG(error, "[LDAP] Auth from ldap failed: ~p", [ResultCode]),
|
||||
{stop, AuthResult#{auth_result => ResultCode, anonymous => false}}
|
||||
end.
|
||||
|
||||
lookup_user(Username, #{username_attr := UidAttr,
|
||||
match_objectclass := ObjectClass,
|
||||
device_dn := DeviceDn,
|
||||
custom_base_dn := CustomBaseDN, pool := Pool} = Config) ->
|
||||
|
||||
Filters = maps:get(filters, Config, []),
|
||||
|
||||
ReplaceRules = [{"${username_attr}", UidAttr},
|
||||
{"${user}", binary_to_list(Username)},
|
||||
{"${device_dn}", DeviceDn}],
|
||||
|
||||
Filter = prepare_filter(Filters, UidAttr, ObjectClass, ReplaceRules),
|
||||
|
||||
%% auth.ldap.custom_base_dn = "${username_attr}=${user},${device_dn}"
|
||||
BaseDN = replace_vars(CustomBaseDN, ReplaceRules),
|
||||
|
||||
case search(Pool, BaseDN, Filter) of
|
||||
%% This clause seems to be impossible to match. `eldap2:search/2` does
|
||||
%% not validates the result, so if it returns "successfully" from the
|
||||
%% LDAP server, it always returns `{ok, #eldap_search_result{}}`.
|
||||
{error, noSuchObject} ->
|
||||
undefined;
|
||||
%% In case no user was found by the search, but the search was completed
|
||||
%% without error we get an empty `entries` list.
|
||||
{ok, #eldap_search_result{entries = []}} ->
|
||||
undefined;
|
||||
{ok, #eldap_search_result{entries = [Entry]}} ->
|
||||
Attributes = Entry#eldap_entry.attributes,
|
||||
case get_value("isEnabled", Attributes) of
|
||||
undefined ->
|
||||
Entry;
|
||||
[Val] ->
|
||||
case list_to_atom(string:to_lower(Val)) of
|
||||
true -> Entry;
|
||||
false -> {error, username_disabled}
|
||||
end
|
||||
end;
|
||||
{error, Error} ->
|
||||
?LOG(error, "[LDAP] Search dn: ~p, filter: ~p, fail:~p", [DeviceDn, Filter, Error]),
|
||||
{error, username_or_password_error}
|
||||
end.
|
||||
|
||||
check_pass(Password, Password, _ClientInfo) -> ok;
|
||||
check_pass(_, _, _) -> {error, bad_username_or_password}.
|
||||
|
||||
format_password(Passhash, Password, ClientInfo) ->
|
||||
case do_format_password(Passhash, Password) of
|
||||
{error, Error2} ->
|
||||
{error, Error2};
|
||||
{Passhash1, Password1} ->
|
||||
check_pass(Passhash1, Password1, ClientInfo)
|
||||
end.
|
||||
|
||||
do_format_password(Passhash, Password) ->
|
||||
Base64PasshashHandler =
|
||||
handle_passhash(fun(HashType, Passhash1, Password1) ->
|
||||
Passhash2 = binary_to_list(base64:decode(Passhash1)),
|
||||
resolve_passhash(HashType, Passhash2, Password1)
|
||||
end,
|
||||
fun(_Passhash, _Password) ->
|
||||
{error, password_error}
|
||||
end),
|
||||
PasshashHandler = handle_passhash(fun resolve_passhash/3, Base64PasshashHandler),
|
||||
PasshashHandler(Passhash, Password).
|
||||
|
||||
resolve_passhash(HashType, Passhash, Password) ->
|
||||
[_, Passhash1] = string:tokens(Passhash, "}"),
|
||||
do_resolve(HashType, Passhash1, Password).
|
||||
|
||||
handle_passhash(HandleMatch, HandleNoMatch) ->
|
||||
fun(Passhash, Password) ->
|
||||
case re:run(Passhash, "(?<={)[^{}]+(?=})", [{capture, all, list}, global]) of
|
||||
{match, [[HashType]]} ->
|
||||
HandleMatch(list_to_atom(string:to_lower(HashType)), Passhash, Password);
|
||||
_ ->
|
||||
HandleNoMatch(Passhash, Password)
|
||||
end
|
||||
end.
|
||||
|
||||
do_resolve(ssha, Passhash, Password) ->
|
||||
D64 = base64:decode(Passhash),
|
||||
{HashedData, Salt} = lists:split(20, binary_to_list(D64)),
|
||||
NewHash = crypto:hash(sha, <<Password/binary, (list_to_binary(Salt))/binary>>),
|
||||
{list_to_binary(HashedData), NewHash};
|
||||
do_resolve(HashType, Passhash, Password) ->
|
||||
Password1 = base64:encode(crypto:hash(HashType, Password)),
|
||||
{list_to_binary(Passhash), Password1}.
|
||||
|
||||
description() -> "LDAP Authentication Plugin".
|
||||
|
||||
prepare_filter(Filters, _UidAttr, ObjectClass, ReplaceRules) ->
|
||||
SubFilters =
|
||||
lists:map(fun({K, V}) ->
|
||||
{replace_vars(K, ReplaceRules), replace_vars(V, ReplaceRules)};
|
||||
(Op) ->
|
||||
Op
|
||||
end, Filters),
|
||||
case SubFilters of
|
||||
[] -> eldap2:equalityMatch("objectClass", ObjectClass);
|
||||
_List -> compile_filters(SubFilters, [])
|
||||
end.
|
||||
|
||||
|
||||
compile_filters([{Key, Value}], []) ->
|
||||
compile_equal(Key, Value);
|
||||
compile_filters([{K1, V1}, "and", {K2, V2} | Rest], []) ->
|
||||
compile_filters(
|
||||
Rest,
|
||||
eldap2:'and'([compile_equal(K1, V1),
|
||||
compile_equal(K2, V2)]));
|
||||
compile_filters([{K1, V1}, "or", {K2, V2} | Rest], []) ->
|
||||
compile_filters(
|
||||
Rest,
|
||||
eldap2:'or'([compile_equal(K1, V1),
|
||||
compile_equal(K2, V2)]));
|
||||
compile_filters(["and", {K, V} | Rest], PartialFilter) ->
|
||||
compile_filters(
|
||||
Rest,
|
||||
eldap2:'and'([PartialFilter,
|
||||
compile_equal(K, V)]));
|
||||
compile_filters(["or", {K, V} | Rest], PartialFilter) ->
|
||||
compile_filters(
|
||||
Rest,
|
||||
eldap2:'or'([PartialFilter,
|
||||
compile_equal(K, V)]));
|
||||
compile_filters([], Filter) ->
|
||||
Filter.
|
||||
|
||||
compile_equal(Key, Value) ->
|
||||
eldap2:equalityMatch(Key, Value).
|
||||
|
||||
replace_vars(CustomBaseDN, ReplaceRules) ->
|
||||
lists:foldl(fun({Pattern, Substitute}, DN) ->
|
||||
lists:flatten(string:replace(DN, Pattern, Substitute))
|
||||
end, CustomBaseDN, ReplaceRules).
|
|
@ -0,0 +1,78 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020 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_ldap_app).
|
||||
|
||||
-behaviour(application).
|
||||
|
||||
-emqx_plugin(auth).
|
||||
|
||||
-include("emqx_auth_ldap.hrl").
|
||||
|
||||
%% Application callbacks
|
||||
-export([ start/2
|
||||
, prep_stop/1
|
||||
, stop/1
|
||||
]).
|
||||
|
||||
start(_StartType, _StartArgs) ->
|
||||
{ok, Sup} = emqx_auth_ldap_sup:start_link(),
|
||||
_ = if_enabled([device_dn, match_objectclass,
|
||||
username_attr, password_attr,
|
||||
filters, custom_base_dn, bind_as_user],
|
||||
fun load_auth_hook/1),
|
||||
_ = if_enabled([device_dn, match_objectclass,
|
||||
username_attr, password_attr,
|
||||
filters, custom_base_dn, bind_as_user],
|
||||
fun load_acl_hook/1),
|
||||
{ok, Sup}.
|
||||
|
||||
prep_stop(State) ->
|
||||
emqx:unhook('client.authenticate', fun emqx_auth_ldap:check/3),
|
||||
emqx:unhook('client.check_acl', fun emqx_acl_ldap:check_acl/5),
|
||||
State.
|
||||
|
||||
stop(_State) ->
|
||||
ok.
|
||||
|
||||
load_auth_hook(DeviceDn) ->
|
||||
ok = emqx_auth_ldap:register_metrics(),
|
||||
Params = maps:from_list(DeviceDn),
|
||||
emqx:hook('client.authenticate', fun emqx_auth_ldap:check/3, [Params#{pool => ?APP}]).
|
||||
|
||||
load_acl_hook(DeviceDn) ->
|
||||
ok = emqx_acl_ldap:register_metrics(),
|
||||
Params = maps:from_list(DeviceDn),
|
||||
emqx:hook('client.check_acl', fun emqx_acl_ldap:check_acl/5 , [Params#{pool => ?APP}]).
|
||||
|
||||
if_enabled(Cfgs, Fun) ->
|
||||
case get_env(Cfgs) of
|
||||
{ok, []} -> ok;
|
||||
{ok, InitArgs} -> Fun(InitArgs)
|
||||
end.
|
||||
|
||||
get_env(Cfgs) ->
|
||||
get_env(Cfgs, []).
|
||||
|
||||
get_env([Cfg | LeftCfgs], ENVS) ->
|
||||
case application:get_env(?APP, Cfg) of
|
||||
{ok, ENV} ->
|
||||
get_env(LeftCfgs, [{Cfg, ENV} | ENVS]);
|
||||
undefined ->
|
||||
get_env(LeftCfgs, ENVS)
|
||||
end;
|
||||
get_env([], ENVS) ->
|
||||
{ok, ENVS}.
|
|
@ -0,0 +1,150 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020 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_ldap_cli).
|
||||
|
||||
-behaviour(ecpool_worker).
|
||||
|
||||
-include("emqx_auth_ldap.hrl").
|
||||
|
||||
-include_lib("emqx/include/emqx.hrl").
|
||||
-include_lib("emqx/include/logger.hrl").
|
||||
|
||||
%% ecpool callback
|
||||
-export([connect/1]).
|
||||
|
||||
-export([ search/3
|
||||
, search/4
|
||||
, post_bind/3
|
||||
, init_args/1
|
||||
]).
|
||||
|
||||
-import(proplists,
|
||||
[ get_value/2
|
||||
, get_value/3
|
||||
]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% LDAP Connect/Search
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
connect(Opts) ->
|
||||
Servers = get_value(servers, Opts, ["localhost"]),
|
||||
Port = get_value(port, Opts, 389),
|
||||
Timeout = get_value(timeout, Opts, 30),
|
||||
BindDn = get_value(bind_dn, Opts),
|
||||
BindPassword = get_value(bind_password, Opts),
|
||||
LdapOpts = case get_value(ssl, Opts, false)of
|
||||
true ->
|
||||
SslOpts = get_value(sslopts, Opts),
|
||||
[{port, Port}, {timeout, Timeout}, {sslopts, SslOpts}];
|
||||
false ->
|
||||
[{port, Port}, {timeout, Timeout}]
|
||||
end,
|
||||
?LOG(debug, "[LDAP] Connecting to OpenLDAP server: ~p, Opts:~p ...", [Servers, LdapOpts]),
|
||||
|
||||
case eldap2:open(Servers, LdapOpts) of
|
||||
{ok, LDAP} ->
|
||||
try eldap2:simple_bind(LDAP, BindDn, BindPassword) of
|
||||
ok -> {ok, LDAP};
|
||||
{error, Error} ->
|
||||
?LOG(error, "[LDAP] Can't authenticated to OpenLDAP server: ~p", [Error]),
|
||||
{error, Error}
|
||||
catch
|
||||
error:Reason ->
|
||||
?LOG(error, "[LDAP] Can't authenticated to OpenLDAP server: ~p", [Reason]),
|
||||
{error, Reason}
|
||||
end;
|
||||
{error, Reason} ->
|
||||
?LOG(error, "[LDAP] Can't connect to OpenLDAP server: ~p", [Reason]),
|
||||
{error, Reason}
|
||||
end.
|
||||
|
||||
search(Pool, Base, Filter) ->
|
||||
ecpool:with_client(Pool,
|
||||
fun(C) ->
|
||||
case application:get_env(?APP, bind_as_user) of
|
||||
{ok, true} ->
|
||||
{ok, Opts} = application:get_env(?APP, ldap),
|
||||
BindDn = get_value(bind_dn, Opts),
|
||||
BindPassword = get_value(bind_password, Opts),
|
||||
try eldap2:simple_bind(C, BindDn, BindPassword) of
|
||||
ok ->
|
||||
eldap2:search(C, [{base, Base},
|
||||
{filter, Filter},
|
||||
{deref, eldap2:derefFindingBaseObj()}]);
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
catch
|
||||
error:Reason -> {error, Reason}
|
||||
end;
|
||||
{ok, false} ->
|
||||
eldap2:search(C, [{base, Base},
|
||||
{filter, Filter},
|
||||
{deref, eldap2:derefFindingBaseObj()}])
|
||||
end
|
||||
end).
|
||||
|
||||
search(Pool, Base, Filter, Attributes) ->
|
||||
ecpool:with_client(Pool,
|
||||
fun(C) ->
|
||||
case application:get_env(?APP, bind_as_user) of
|
||||
{ok, true} ->
|
||||
{ok, Opts} = application:get_env(?APP, ldap),
|
||||
BindDn = get_value(bind_dn, Opts),
|
||||
BindPassword = get_value(bind_password, Opts),
|
||||
try eldap2:simple_bind(C, BindDn, BindPassword) of
|
||||
ok ->
|
||||
eldap2:search(C, [{base, Base},
|
||||
{filter, Filter},
|
||||
{attributes, Attributes},
|
||||
{deref, eldap2:derefFindingBaseObj()}]);
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
catch
|
||||
error:Reason -> {error, Reason}
|
||||
end;
|
||||
{ok, false} ->
|
||||
eldap2:search(C, [{base, Base},
|
||||
{filter, Filter},
|
||||
{attributes, Attributes},
|
||||
{deref, eldap2:derefFindingBaseObj()}])
|
||||
end
|
||||
end).
|
||||
|
||||
post_bind(Pool, BindDn, BindPassword) ->
|
||||
ecpool:with_client(Pool,
|
||||
fun(C) ->
|
||||
try eldap2:simple_bind(C, BindDn, BindPassword) of
|
||||
ok -> ok;
|
||||
{error, Error} ->
|
||||
{error, Error}
|
||||
catch
|
||||
error:Reason -> {error, Reason}
|
||||
end
|
||||
end).
|
||||
|
||||
|
||||
init_args(ENVS) ->
|
||||
DeviceDn = get_value(device_dn, ENVS),
|
||||
ObjectClass = get_value(match_objectclass, ENVS),
|
||||
UidAttr = get_value(username_attr, ENVS),
|
||||
PasswdAttr = get_value(password_attr, ENVS),
|
||||
{ok, #{device_dn => DeviceDn,
|
||||
match_objectclass => ObjectClass,
|
||||
username_attr => UidAttr,
|
||||
password_attr => PasswdAttr}}.
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020 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_ldap_sup).
|
||||
|
||||
-behaviour(supervisor).
|
||||
|
||||
-include("emqx_auth_ldap.hrl").
|
||||
|
||||
-export([start_link/0]).
|
||||
|
||||
-export([init/1]).
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
init([]) ->
|
||||
%% LDAP Connection Pool.
|
||||
{ok, Server} = application:get_env(?APP, ldap),
|
||||
PoolSpec = ecpool:pool_spec(?APP, ?APP, emqx_auth_ldap_cli, Server),
|
||||
{ok, {{one_for_one, 10, 100}, [PoolSpec]}}.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDUTCCAjmgAwIBAgIJAPPYCjTmxdt/MA0GCSqGSIb3DQEBCwUAMD8xCzAJBgNV
|
||||
BAYTAkNOMREwDwYDVQQIDAhoYW5nemhvdTEMMAoGA1UECgwDRU1RMQ8wDQYDVQQD
|
||||
DAZSb290Q0EwHhcNMjAwNTA4MDgwNjUyWhcNMzAwNTA2MDgwNjUyWjA/MQswCQYD
|
||||
VQQGEwJDTjERMA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UE
|
||||
AwwGUm9vdENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzcgVLex1
|
||||
EZ9ON64EX8v+wcSjzOZpiEOsAOuSXOEN3wb8FKUxCdsGrsJYB7a5VM/Jot25Mod2
|
||||
juS3OBMg6r85k2TWjdxUoUs+HiUB/pP/ARaaW6VntpAEokpij/przWMPgJnBF3Ur
|
||||
MjtbLayH9hGmpQrI5c2vmHQ2reRZnSFbY+2b8SXZ+3lZZgz9+BaQYWdQWfaUWEHZ
|
||||
uDaNiViVO0OT8DRjCuiDp3yYDj3iLWbTA/gDL6Tf5XuHuEwcOQUrd+h0hyIphO8D
|
||||
tsrsHZ14j4AWYLk1CPA6pq1HIUvEl2rANx2lVUNv+nt64K/Mr3RnVQd9s8bK+TXQ
|
||||
KGHd2Lv/PALYuwIDAQABo1AwTjAdBgNVHQ4EFgQUGBmW+iDzxctWAWxmhgdlE8Pj
|
||||
EbQwHwYDVR0jBBgwFoAUGBmW+iDzxctWAWxmhgdlE8PjEbQwDAYDVR0TBAUwAwEB
|
||||
/zANBgkqhkiG9w0BAQsFAAOCAQEAGbhRUjpIred4cFAFJ7bbYD9hKu/yzWPWkMRa
|
||||
ErlCKHmuYsYk+5d16JQhJaFy6MGXfLgo3KV2itl0d+OWNH0U9ULXcglTxy6+njo5
|
||||
CFqdUBPwN1jxhzo9yteDMKF4+AHIxbvCAJa17qcwUKR5MKNvv09C6pvQDJLzid7y
|
||||
E2dkgSuggik3oa0427KvctFf8uhOV94RvEDyqvT5+pgNYZ2Yfga9pD/jjpoHEUlo
|
||||
88IGU8/wJCx3Ds2yc8+oBg/ynxG8f/HmCC1ET6EHHoe2jlo8FpU/SgGtghS1YL30
|
||||
IWxNsPrUP+XsZpBJy/mvOhE5QXo6Y35zDqqj8tI7AGmAWu22jg==
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,19 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDEzCCAfugAwIBAgIBAjANBgkqhkiG9w0BAQsFADA/MQswCQYDVQQGEwJDTjER
|
||||
MA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UEAwwGUm9vdENB
|
||||
MB4XDTIwMDUwODA4MDcwNVoXDTMwMDUwNjA4MDcwNVowPzELMAkGA1UEBhMCQ04x
|
||||
ETAPBgNVBAgMCGhhbmd6aG91MQwwCgYDVQQKDANFTVExDzANBgNVBAMMBlNlcnZl
|
||||
cjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALNeWT3pE+QFfiRJzKmn
|
||||
AMUrWo3K2j/Tm3+Xnl6WLz67/0rcYrJbbKvS3uyRP/stXyXEKw9CepyQ1ViBVFkW
|
||||
Aoy8qQEOWFDsZc/5UzhXUnb6LXr3qTkFEjNmhj+7uzv/lbBxlUG1NlYzSeOB6/RT
|
||||
8zH/lhOeKhLnWYPXdXKsa1FL6ij4X8DeDO1kY7fvAGmBn/THh1uTpDizM4YmeI+7
|
||||
4dmayA5xXvARte5h4Vu5SIze7iC057N+vymToMk2Jgk+ZZFpyXrnq+yo6RaD3ANc
|
||||
lrc4FbeUQZ5a5s5Sxgs9a0Y3WMG+7c5VnVXcbjBRz/aq2NtOnQQjikKKQA8GF080
|
||||
BQkCAwEAAaMaMBgwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQEL
|
||||
BQADggEBAJefnMZpaRDHQSNUIEL3iwGXE9c6PmIsQVE2ustr+CakBp3TZ4l0enLt
|
||||
iGMfEVFju69cO4oyokWv+hl5eCMkHBf14Kv51vj448jowYnF1zmzn7SEzm5Uzlsa
|
||||
sqjtAprnLyof69WtLU1j5rYWBuFX86yOTwRAFNjm9fvhAcrEONBsQtqipBWkMROp
|
||||
iUYMkRqbKcQMdwxov+lHBYKq9zbWRoqLROAn54SRqgQk6c15JdEfgOOjShbsOkIH
|
||||
UhqcwRkQic7n1zwHVGVDgNIZVgmJ2IdIWBlPEC7oLrRrBD/X1iEEXtKab6p5o22n
|
||||
KB5mN+iQaE+Oe2cpGKZJiJRdM+IqDDQ=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,19 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDEzCCAfugAwIBAgIBATANBgkqhkiG9w0BAQsFADA/MQswCQYDVQQGEwJDTjER
|
||||
MA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UEAwwGUm9vdENB
|
||||
MB4XDTIwMDUwODA4MDY1N1oXDTMwMDUwNjA4MDY1N1owPzELMAkGA1UEBhMCQ04x
|
||||
ETAPBgNVBAgMCGhhbmd6aG91MQwwCgYDVQQKDANFTVExDzANBgNVBAMMBkNsaWVu
|
||||
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMy4hoksKcZBDbY680u6
|
||||
TS25U51nuB1FBcGMlF9B/t057wPOlxF/OcmbxY5MwepS41JDGPgulE1V7fpsXkiW
|
||||
1LUimYV/tsqBfymIe0mlY7oORahKji7zKQ2UBIVFhdlvQxunlIDnw6F9popUgyHt
|
||||
dMhtlgZK8oqRwHxO5dbfoukYd6J/r+etS5q26sgVkf3C6dt0Td7B25H9qW+f7oLV
|
||||
PbcHYCa+i73u9670nrpXsC+Qc7Mygwa2Kq/jwU+ftyLQnOeW07DuzOwsziC/fQZa
|
||||
nbxR+8U9FNftgRcC3uP/JMKYUqsiRAuaDokARZxVTV5hUElfpO6z6/NItSDvvh3i
|
||||
eikCAwEAAaMaMBgwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQEL
|
||||
BQADggEBABchYxKo0YMma7g1qDswJXsR5s56Czx/I+B41YcpMBMTrRqpUC0nHtLk
|
||||
M7/tZp592u/tT8gzEnQjZLKBAhFeZaR3aaKyknLqwiPqJIgg0pgsBGITrAK3Pv4z
|
||||
5/YvAJJKgTe5UdeTz6U4lvNEux/4juZ4pmqH4qSFJTOzQS7LmgSmNIdd072rwXBd
|
||||
UzcSHzsJgEMb88u/LDLjj1pQ7AtZ4Tta8JZTvcgBFmjB0QUi6fgkHY6oGat/W4kR
|
||||
jSRUBlMUbM/drr2PVzRc2dwbFIl3X+ZE6n5Sl3ZwRAC/s92JU6CPMRW02muVu6xl
|
||||
goraNgPISnrbpR6KjxLZkVembXzjNNc=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAzLiGiSwpxkENtjrzS7pNLblTnWe4HUUFwYyUX0H+3TnvA86X
|
||||
EX85yZvFjkzB6lLjUkMY+C6UTVXt+mxeSJbUtSKZhX+2yoF/KYh7SaVjug5FqEqO
|
||||
LvMpDZQEhUWF2W9DG6eUgOfDoX2milSDIe10yG2WBkryipHAfE7l1t+i6Rh3on+v
|
||||
561LmrbqyBWR/cLp23RN3sHbkf2pb5/ugtU9twdgJr6Lve73rvSeulewL5BzszKD
|
||||
BrYqr+PBT5+3ItCc55bTsO7M7CzOIL99BlqdvFH7xT0U1+2BFwLe4/8kwphSqyJE
|
||||
C5oOiQBFnFVNXmFQSV+k7rPr80i1IO++HeJ6KQIDAQABAoIBAGWgvPjfuaU3qizq
|
||||
uti/FY07USz0zkuJdkANH6LiSjlchzDmn8wJ0pApCjuIE0PV/g9aS8z4opp5q/gD
|
||||
UBLM/a8mC/xf2EhTXOMrY7i9p/I3H5FZ4ZehEqIw9sWKK9YzC6dw26HabB2BGOnW
|
||||
5nozPSQ6cp2RGzJ7BIkxSZwPzPnVTgy3OAuPOiJytvK+hGLhsNaT+Y9bNDvplVT2
|
||||
ZwYTV8GlHZC+4b2wNROILm0O86v96O+Qd8nn3fXjGHbMsAnONBq10bZS16L4fvkH
|
||||
5G+W/1PeSXmtZFppdRRDxIW+DWcXK0D48WRliuxcV4eOOxI+a9N2ZJZZiNLQZGwg
|
||||
w3A8+mECgYEA8HuJFrlRvdoBe2U/EwUtG74dcyy30L4yEBnN5QscXmEEikhaQCfX
|
||||
Wm6EieMcIB/5I5TQmSw0cmBMeZjSXYoFdoI16/X6yMMuATdxpvhOZGdUGXxhAH+x
|
||||
xoTUavWZnEqW3fkUU71kT5E2f2i+0zoatFESXHeslJyz85aAYpP92H0CgYEA2e5A
|
||||
Yozt5eaA1Gyhd8SeptkEU4xPirNUnVQHStpMWUb1kzTNXrPmNWccQ7JpfpG6DcYl
|
||||
zUF6p6mlzY+zkMiyPQjwEJlhiHM2NlL1QS7td0R8ewgsFoyn8WsBI4RejWrEG9td
|
||||
EDniuIw+pBFkcWthnTLHwECHdzgquToyTMjrBB0CgYEA28tdGbrZXhcyAZEhHAZA
|
||||
Gzog+pKlkpEzeonLKIuGKzCrEKRecIK5jrqyQsCjhS0T7ZRnL4g6i0s+umiV5M5w
|
||||
fcc292pEA1h45L3DD6OlKplSQVTv55/OYS4oY3YEJtf5mfm8vWi9lQeY8sxOlQpn
|
||||
O+VZTdBHmTC8PGeTAgZXHZUCgYA6Tyv88lYowB7SN2qQgBQu8jvdGtqhcs/99GCr
|
||||
H3N0I69LPsKAR0QeH8OJPXBKhDUywESXAaEOwS5yrLNP1tMRz5Vj65YUCzeDG3kx
|
||||
gpvY4IMp7ArX0bSRvJ6mYSFnVxy3k174G3TVCfksrtagHioVBGQ7xUg5ltafjrms
|
||||
n8l55QKBgQDVzU8tQvBVqY8/1lnw11Vj4fkE/drZHJ5UkdC1eenOfSWhlSLfUJ8j
|
||||
ds7vEWpRPPoVuPZYeR1y78cyxKe1GBx6Wa2lF5c7xjmiu0xbRnrxYeLolce9/ntp
|
||||
asClqpnHT8/VJYTD7Kqj0fouTTZf0zkig/y+2XERppd8k+pSKjUCPQ==
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAs15ZPekT5AV+JEnMqacAxStajcraP9Obf5eeXpYvPrv/Stxi
|
||||
sltsq9Le7JE/+y1fJcQrD0J6nJDVWIFUWRYCjLypAQ5YUOxlz/lTOFdSdvotevep
|
||||
OQUSM2aGP7u7O/+VsHGVQbU2VjNJ44Hr9FPzMf+WE54qEudZg9d1cqxrUUvqKPhf
|
||||
wN4M7WRjt+8AaYGf9MeHW5OkOLMzhiZ4j7vh2ZrIDnFe8BG17mHhW7lIjN7uILTn
|
||||
s36/KZOgyTYmCT5lkWnJeuer7KjpFoPcA1yWtzgVt5RBnlrmzlLGCz1rRjdYwb7t
|
||||
zlWdVdxuMFHP9qrY206dBCOKQopADwYXTzQFCQIDAQABAoIBAQCuvCbr7Pd3lvI/
|
||||
n7VFQG+7pHRe1VKwAxDkx2t8cYos7y/QWcm8Ptwqtw58HzPZGWYrgGMCRpzzkRSF
|
||||
V9g3wP1S5Scu5C6dBu5YIGc157tqNGXB+SpdZddJQ4Nc6yGHXYERllT04ffBGc3N
|
||||
WG/oYS/1cSteiSIrsDy/91FvGRCi7FPxH3wIgHssY/tw69s1Cfvaq5lr2NTFzxIG
|
||||
xCvpJKEdSfVfS9I7LYiymVjst3IOR/w76/ZFY9cRa8ZtmQSWWsm0TUpRC1jdcbkm
|
||||
ZoJptYWlP+gSwx/fpMYftrkJFGOJhHJHQhwxT5X/ajAISeqjjwkWSEJLwnHQd11C
|
||||
Zy2+29lBAoGBANlEAIK4VxCqyPXNKfoOOi5dS64NfvyH4A1v2+KaHWc7lqaqPN49
|
||||
ezfN2n3X+KWx4cviDD914Yc2JQ1vVJjSaHci7yivocDo2OfZDmjBqzaMp/y+rX1R
|
||||
/f3MmiTqMa468rjaxI9RRZu7vDgpTR+za1+OBCgMzjvAng8dJuN/5gjlAoGBANNY
|
||||
uYPKtearBmkqdrSV7eTUe49Nhr0XotLaVBH37TCW0Xv9wjO2xmbm5Ga/DCtPIsBb
|
||||
yPeYwX9FjoasuadUD7hRvbFu6dBa0HGLmkXRJZTcD7MEX2Lhu4BuC72yDLLFd0r+
|
||||
Ep9WP7F5iJyagYqIZtz+4uf7gBvUDdmvXz3sGr1VAoGAdXTD6eeKeiI6PlhKBztF
|
||||
zOb3EQOO0SsLv3fnodu7ZaHbUgLaoTMPuB17r2jgrYM7FKQCBxTNdfGZmmfDjlLB
|
||||
0xZ5wL8ibU30ZXL8zTlWPElST9sto4B+FYVVF/vcG9sWeUUb2ncPcJ/Po3UAktDG
|
||||
jYQTTyuNGtSJHpad/YOZctkCgYBtWRaC7bq3of0rJGFOhdQT9SwItN/lrfj8hyHA
|
||||
OjpqTV4NfPmhsAtu6j96OZaeQc+FHvgXwt06cE6Rt4RG4uNPRluTFgO7XYFDfitP
|
||||
vCppnoIw6S5BBvHwPP+uIhUX2bsi/dm8vu8tb+gSvo4PkwtFhEr6I9HglBKmcmog
|
||||
q6waEQKBgHyecFBeM6Ls11Cd64vborwJPAuxIW7HBAFj/BS99oeG4TjBx4Sz2dFd
|
||||
rzUibJt4ndnHIvCN8JQkjNG14i9hJln+H3mRss8fbZ9vQdqG+2vOWADYSzzsNI55
|
||||
RFY7JjluKcVkp/zCDeUxTU3O6sS+v6/3VE11Cob6OYQx3lN5wrZ3
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,153 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020 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_ldap_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(PID, emqx_auth_ldap).
|
||||
|
||||
-define(APP, emqx_auth_ldap).
|
||||
|
||||
-define(DeviceDN, "ou=test_device,dc=emqx,dc=io").
|
||||
|
||||
-define(AuthDN, "ou=test_auth,dc=emqx,dc=io").
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Setups
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
all() ->
|
||||
[{group, nossl}, {group, ssl}].
|
||||
|
||||
groups() ->
|
||||
Cases = emqx_ct:all(?MODULE),
|
||||
[{nossl, Cases}, {ssl, Cases}].
|
||||
|
||||
init_per_group(GrpName, Cfg) ->
|
||||
Fun = fun(App) -> set_special_configs(GrpName, App) end,
|
||||
emqx_ct_helpers:start_apps([emqx_auth_ldap], Fun),
|
||||
emqx_mod_acl_internal:unload([]),
|
||||
Cfg.
|
||||
|
||||
end_per_group(_GrpName, _Cfg) ->
|
||||
emqx_ct_helpers:stop_apps([emqx_auth_ldap]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Cases
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
t_check_auth(_) ->
|
||||
MqttUser1 = #{clientid => <<"mqttuser1">>,
|
||||
username => <<"mqttuser0001">>,
|
||||
password => <<"mqttuser0001">>,
|
||||
zone => external},
|
||||
MqttUser2 = #{clientid => <<"mqttuser2">>,
|
||||
username => <<"mqttuser0002">>,
|
||||
password => <<"mqttuser0002">>,
|
||||
zone => external},
|
||||
MqttUser3 = #{clientid => <<"mqttuser3">>,
|
||||
username => <<"mqttuser0003">>,
|
||||
password => <<"mqttuser0003">>,
|
||||
zone => external},
|
||||
MqttUser4 = #{clientid => <<"mqttuser4">>,
|
||||
username => <<"mqttuser0004">>,
|
||||
password => <<"mqttuser0004">>,
|
||||
zone => external},
|
||||
MqttUser5 = #{clientid => <<"mqttuser5">>,
|
||||
username => <<"mqttuser0005">>,
|
||||
password => <<"mqttuser0005">>,
|
||||
zone => external},
|
||||
NonExistUser1 = #{clientid => <<"mqttuser6">>,
|
||||
username => <<"mqttuser0006">>,
|
||||
password => <<"mqttuser0006">>,
|
||||
zone => external},
|
||||
NonExistUser2 = #{clientid => <<"mqttuser7">>,
|
||||
username => <<"mqttuser0005">>,
|
||||
password => <<"mqttuser0006">>,
|
||||
zone => external},
|
||||
ct:log("MqttUser: ~p", [emqx_access_control:authenticate(MqttUser1)]),
|
||||
?assertMatch({ok, #{auth_result := success}}, emqx_access_control:authenticate(MqttUser1)),
|
||||
?assertMatch({ok, #{auth_result := success}}, emqx_access_control:authenticate(MqttUser2)),
|
||||
?assertMatch({ok, #{auth_result := success}}, emqx_access_control:authenticate(MqttUser3)),
|
||||
?assertMatch({ok, #{auth_result := success}}, emqx_access_control:authenticate(MqttUser4)),
|
||||
?assertMatch({ok, #{auth_result := success}}, emqx_access_control:authenticate(MqttUser5)),
|
||||
?assertEqual({error, not_authorized}, emqx_access_control:authenticate(NonExistUser1)),
|
||||
?assertEqual({error, bad_username_or_password}, emqx_access_control:authenticate(NonExistUser2)).
|
||||
|
||||
t_check_acl(_) ->
|
||||
MqttUser = #{clientid => <<"mqttuser1">>, username => <<"mqttuser0001">>, zone => external},
|
||||
NoMqttUser = #{clientid => <<"mqttuser2">>, username => <<"mqttuser0007">>, zone => external},
|
||||
allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pub/1">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pub/+">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pub/#">>),
|
||||
|
||||
allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/sub/1">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/sub/+">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/sub/#">>),
|
||||
|
||||
allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pubsub/1">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pubsub/+">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pubsub/#">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/pubsub/1">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/pubsub/+">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/pubsub/#">>),
|
||||
|
||||
deny = emqx_access_control:check_acl(NoMqttUser, publish, <<"mqttuser0001/req/mqttuser0001/+">>),
|
||||
deny = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/req/mqttuser0002/+">>),
|
||||
deny = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/req/+/mqttuser0002">>),
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% Helpers
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
set_special_configs(_, emqx) ->
|
||||
application:set_env(emqx, allow_anonymous, false),
|
||||
application:set_env(emqx, enable_acl_cache, false),
|
||||
application:set_env(emqx, acl_nomatch, deny),
|
||||
AclFilePath = filename:join(["test", "emqx_SUITE_data", "acl.conf"]),
|
||||
application:set_env(emqx, acl_file,
|
||||
emqx_ct_helpers:deps_path(emqx, AclFilePath)),
|
||||
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(Ssl, emqx_auth_ldap) ->
|
||||
case Ssl == ssl of
|
||||
true ->
|
||||
LdapOpts = application:get_env(emqx_auth_ldap, ldap, []),
|
||||
Path = emqx_ct_helpers:deps_path(emqx_auth_ldap, "test/certs/"),
|
||||
SslOpts = [{verify, verify_peer},
|
||||
{fail_if_no_peer_cert, true},
|
||||
{server_name_indication, disable},
|
||||
{keyfile, Path ++ "/client-key.pem"},
|
||||
{certfile, Path ++ "/client-cert.pem"},
|
||||
{cacertfile, Path ++ "/cacert.pem"}],
|
||||
LdapOpts1 = lists:keystore(ssl, 1, LdapOpts, {ssl, true}),
|
||||
LdapOpts2 = lists:keystore(sslopts, 1, LdapOpts1, {sslopts, SslOpts}),
|
||||
LdapOpts3 = lists:keystore(port, 1, LdapOpts2, {port, 636}),
|
||||
application:set_env(emqx_auth_ldap, ldap, LdapOpts3);
|
||||
_ ->
|
||||
ok
|
||||
end,
|
||||
application:set_env(emqx_auth_ldap, device_dn, "ou=testdevice, dc=emqx, dc=io").
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
%%--------------------------------------------------------------------
|
||||
%% Copyright (c) 2020 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_ldap_bind_as_user_SUITE).
|
||||
|
||||
-compile(nowarn_export_all).
|
||||
-compile(export_all).
|
||||
|
||||
-include_lib("emqx/include/emqx.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
-define(PID, emqx_auth_ldap).
|
||||
|
||||
-define(APP, emqx_auth_ldap).
|
||||
|
||||
-define(DeviceDN, "ou=test_device,dc=emqx,dc=io").
|
||||
|
||||
-define(AuthDN, "ou=test_auth,dc=emqx,dc=io").
|
||||
|
||||
all() ->
|
||||
[check_auth,
|
||||
check_acl].
|
||||
|
||||
init_per_suite(Config) ->
|
||||
emqx_ct_helpers:start_apps([emqx, emqx_auth_ldap], fun set_special_configs/1),
|
||||
emqx_mod_acl_internal:unload([]),
|
||||
Config.
|
||||
|
||||
end_per_suite(_Config) ->
|
||||
emqx_ct_helpers:stop_apps([emqx_auth_ldap, emqx]).
|
||||
|
||||
check_auth(_) ->
|
||||
MqttUser1 = #{clientid => <<"mqttuser1">>,
|
||||
username => <<"user1">>,
|
||||
password => <<"mqttuser0001">>,
|
||||
zone => external},
|
||||
MqttUser2 = #{clientid => <<"mqttuser2">>,
|
||||
username => <<"user2">>,
|
||||
password => <<"mqttuser0002">>,
|
||||
zone => external},
|
||||
NonExistUser1 = #{clientid => <<"mqttuser3">>,
|
||||
username => <<"user3">>,
|
||||
password => <<"mqttuser0003">>,
|
||||
zone => external},
|
||||
ct:log("MqttUser: ~p", [emqx_access_control:authenticate(MqttUser1)]),
|
||||
?assertMatch({ok, #{auth_result := success}}, emqx_access_control:authenticate(MqttUser1)),
|
||||
?assertMatch({ok, #{auth_result := success}}, emqx_access_control:authenticate(MqttUser2)),
|
||||
?assertEqual({error, not_authorized}, emqx_access_control:authenticate(NonExistUser1)).
|
||||
|
||||
check_acl(_) ->
|
||||
% emqx_modules:load_module(emqx_mod_acl_internal, false),
|
||||
MqttUser = #{clientid => <<"mqttuser1">>, username => <<"user1">>, zone => external},
|
||||
NoMqttUser = #{clientid => <<"mqttuser2">>, username => <<"user7">>, zone => external},
|
||||
allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pub/1">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pub/+">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pub/#">>),
|
||||
|
||||
allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/sub/1">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/sub/+">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/sub/#">>),
|
||||
|
||||
allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pubsub/1">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pubsub/+">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/pubsub/#">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/pubsub/1">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/pubsub/+">>),
|
||||
allow = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/pubsub/#">>),
|
||||
|
||||
deny = emqx_access_control:check_acl(NoMqttUser, publish, <<"mqttuser0001/req/mqttuser0001/+">>),
|
||||
deny = emqx_access_control:check_acl(MqttUser, publish, <<"mqttuser0001/req/mqttuser0002/+">>),
|
||||
deny = emqx_access_control:check_acl(MqttUser, subscribe, <<"mqttuser0001/req/+/mqttuser0002">>),
|
||||
ok.
|
||||
|
||||
set_special_configs(emqx) ->
|
||||
application:set_env(emqx, allow_anonymous, false),
|
||||
application:set_env(emqx, enable_acl_cache, false),
|
||||
application:set_env(emqx, acl_nomatch, deny),
|
||||
AclFilePath = filename:join(["test", "emqx_SUITE_data", "acl.conf"]),
|
||||
application:set_env(emqx, acl_file,
|
||||
emqx_ct_helpers:deps_path(emqx, AclFilePath)),
|
||||
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_ldap) ->
|
||||
application:set_env(emqx_auth_ldap, bind_as_user, true),
|
||||
application:set_env(emqx_auth_ldap, device_dn, "ou=testdevice, dc=emqx, dc=io"),
|
||||
application:set_env(emqx_auth_ldap, custom_base_dn, "${device_dn}"),
|
||||
%% auth.ldap.filters.1.key = mqttAccountName
|
||||
%% auth.ldap.filters.1.value = ${user}
|
||||
%% auth.ldap.filters.1.op = and
|
||||
%% auth.ldap.filters.2.key = objectClass
|
||||
%% auth.ldap.filters.1.value = mqttUser
|
||||
application:set_env(emqx_auth_ldap, filters, [{"mqttAccountName", "${user}"},
|
||||
"and",
|
||||
{"objectClass", "mqttUser"}]);
|
||||
|
||||
set_special_configs(_App) ->
|
||||
ok.
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
.eunit
|
||||
deps
|
||||
*.o
|
||||
*.beam
|
||||
*.plt
|
||||
erl_crash.dump
|
||||
ebin
|
||||
rel/example_project
|
||||
.concrete/DEV_MODE
|
||||
.rebar
|
||||
.erlang.mk/
|
||||
emqx_auth_mnesia.d
|
||||
data/
|
||||
_build/
|
||||
.DS_Store
|
||||
cover/
|
||||
ct.coverdata
|
||||
eunit.coverdata
|
||||
logs/
|
||||
test/ct.cover.spec
|
||||
rebar.lock
|
||||
rebar3.crashdump
|
||||
erlang.mk
|
||||
.*.swp
|
||||
.rebar3/
|
||||
etc/emqx_auth_mnesia.conf.rendered
|
|
@ -0,0 +1,2 @@
|
|||
emqx_auth_mnesia
|
||||
===============
|
|
@ -0,0 +1,30 @@
|
|||
## Password hash.
|
||||
##
|
||||
## Value: plain | md5 | sha | sha256 | sha512
|
||||
auth.mnesia.password_hash = sha256
|
||||
|
||||
##--------------------------------------------------------------------
|
||||
## ClientId Authentication
|
||||
##--------------------------------------------------------------------
|
||||
|
||||
## Examples
|
||||
##auth.client.1.clientid = id
|
||||
##auth.client.1.password = passwd
|
||||
##auth.client.2.clientid = dev:devid
|
||||
##auth.client.2.password = passwd2
|
||||
##auth.client.3.clientid = app:appid
|
||||
##auth.client.3.password = passwd3
|
||||
##auth.client.4.clientid = client~!@#$%^&*()_+
|
||||
##auth.client.4.password = passwd~!@#$%^&*()_+
|
||||
|
||||
##--------------------------------------------------------------------
|
||||
## Username Authentication
|
||||
##--------------------------------------------------------------------
|
||||
|
||||
## Examples:
|
||||
##auth.user.1.username = admin
|
||||
##auth.user.1.password = public
|
||||
##auth.user.2.username = feng@emqtt.io
|
||||
##auth.user.2.password = public
|
||||
##auth.user.3.username = name~!@#$%^&*()_+
|
||||
##auth.user.3.password = pwsswd~!@#$%^&*()_+
|
|
@ -0,0 +1,38 @@
|
|||
-define(APP, emqx_auth_mnesia).
|
||||
|
||||
-type(login():: {clientid, binary()}
|
||||
| {username, binary()}).
|
||||
|
||||
-record(emqx_user, {
|
||||
login :: login(),
|
||||
password :: binary(),
|
||||
created_at :: integer()
|
||||
}).
|
||||
|
||||
-record(emqx_acl, {
|
||||
filter:: {login() | all, emqx_topic:topic()},
|
||||
action :: pub | sub | pubsub,
|
||||
access :: allow | deny,
|
||||
created_at :: integer()
|
||||
}).
|
||||
|
||||
-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)).
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue