Compare commits

..

9 Commits

Author SHA1 Message Date
firest 579c28e9ab feat(krb): added test cases for kerberos authentication 2024-08-09 13:23:43 +08:00
firest f3008c74d8 tmp 2024-08-08 23:33:36 +08:00
zmstone 9ba0c33256 refactor: get server FQDN from principal 2024-08-02 10:31:26 +02:00
zmstone af28b52152 chore: upgrade to sasl_auth 2.1.1 2024-08-02 09:39:43 +02:00
zmstone 6364bab0a6 refactor: rename gssapi to kerberos
gssapi is the type kerberos is the backend.
2024-08-02 09:36:28 +02:00
zmstone 30420f0481 fix: add server_fqdn to kerberos auth config 2024-08-02 09:03:46 +02:00
zmstone 319530ddf2 chore(gssapi): refine schema doc 2024-08-02 07:15:55 +02:00
zmstone 7a2d9c6d25 chore: refine build script output 2024-08-02 07:15:55 +02:00
zhongwencool 1270e6a64d feat: gssapi authentication 2024-08-02 07:15:55 +02:00
421 changed files with 3005 additions and 10736 deletions

View File

@ -16,24 +16,6 @@ services:
user: "${DOCKER_USER:-root}" user: "${DOCKER_USER:-root}"
volumes: volumes:
- /tmp/emqx-ci/emqx-shared-secret:/var/lib/secret - /tmp/emqx-ci/emqx-shared-secret:/var/lib/secret
kdc:
hostname: kdc.emqx.net
image: ghcr.io/emqx/emqx-builder/5.3-9:1.15.7-26.2.5-3-ubuntu22.04
container_name: kdc.emqx.net
expose:
- 88 # kdc
- 749 # admin server
# ports:
# - 88:88
# - 749:749
networks:
emqx_bridge:
volumes:
- /tmp/emqx-ci/emqx-shared-secret:/var/lib/secret
- ./kerberos/krb5.conf:/etc/kdc/krb5.conf
- ./kerberos/krb5.conf:/etc/krb5.conf
- ./kerberos/run.sh:/usr/bin/run.sh
command: run.sh
kafka_1: kafka_1:
image: wurstmeister/kafka:2.13-2.8.1 image: wurstmeister/kafka:2.13-2.8.1
# ports: # ports:
@ -76,4 +58,3 @@ services:
- ./kerberos/krb5.conf:/etc/kdc/krb5.conf - ./kerberos/krb5.conf:/etc/kdc/krb5.conf
- ./kerberos/krb5.conf:/etc/krb5.conf - ./kerberos/krb5.conf:/etc/krb5.conf
command: kafka-entrypoint.sh command: kafka-entrypoint.sh

View File

@ -0,0 +1,21 @@
version: '3.9'
services:
kdc:
hostname: kdc.emqx.net
image: ghcr.io/emqx/emqx-builder/5.3-9:1.15.7-26.2.5-3-ubuntu22.04
container_name: kdc.emqx.net
expose:
- 88 # kdc
- 749 # admin server
# ports:
# - 88:88
# - 749:749
networks:
emqx_bridge:
volumes:
- /tmp/emqx-ci/emqx-shared-secret:/var/lib/secret
- ./kerberos/krb5.conf:/etc/kdc/krb5.conf
- ./kerberos/krb5.conf:/etc/krb5.conf
- ./kerberos/run.sh:/usr/bin/run.sh
command: run.sh

View File

@ -10,7 +10,7 @@ services:
nofile: 1024 nofile: 1024
image: openldap image: openldap
#ports: #ports:
# - "389:389" # - 389:389
volumes: volumes:
- ./certs/ca.crt:/etc/certs/ca.crt - ./certs/ca.crt:/etc/certs/ca.crt
restart: always restart: always

View File

@ -2,6 +2,7 @@ version: '3.9'
services: services:
erlang: erlang:
hostname: erlang.emqx.net
container_name: erlang container_name: erlang
image: ${DOCKER_CT_RUNNER_IMAGE:-ghcr.io/emqx/emqx-builder/5.3-9:1.15.7-26.2.5-3-ubuntu22.04} image: ${DOCKER_CT_RUNNER_IMAGE:-ghcr.io/emqx/emqx-builder/5.3-9:1.15.7-26.2.5-3-ubuntu22.04}
env_file: env_file:

View File

@ -6,6 +6,7 @@
rdns = false rdns = false
dns_lookup_kdc = no dns_lookup_kdc = no
dns_lookup_realm = no dns_lookup_realm = no
default_keytab_name = /var/lib/secret/erlang.keytab
[realms] [realms]
KDC.EMQX.NET = { KDC.EMQX.NET = {

View File

@ -6,20 +6,31 @@ echo "Remove old keytabs"
rm -f /var/lib/secret/kafka.keytab > /dev/null 2>&1 rm -f /var/lib/secret/kafka.keytab > /dev/null 2>&1
rm -f /var/lib/secret/rig.keytab > /dev/null 2>&1 rm -f /var/lib/secret/rig.keytab > /dev/null 2>&1
rm -f /var/lib/secret/erlang.keytab > /dev/null 2>&1
rm -f /var/lib/secret/krb_authn_cli.keytab > /dev/null 2>&1
echo "Create realm" echo "Create realm"
kdb5_util -P emqx -r KDC.EMQX.NET create -s kdb5_util -P emqx -r KDC.EMQX.NET create -s
echo "Add principals" echo "Add principals"
kadmin.local -w password -q "add_principal -randkey kafka/kafka-1.emqx.net@KDC.EMQX.NET" kadmin.local -w password -q "add_principal -randkey kafka/kafka-1.emqx.net@KDC.EMQX.NET" > /dev/null
kadmin.local -w password -q "add_principal -randkey rig@KDC.EMQX.NET" > /dev/null kadmin.local -w password -q "add_principal -randkey rig@KDC.EMQX.NET" > /dev/null
# For Kerberos Authn
kadmin.local -w password -q "add_principal -randkey emqx/erlang.emqx.net@KDC.EMQX.NET" > /dev/null
kadmin.local -w password -q "add_principal -randkey krb_authn_cli@KDC.EMQX.NET" > /dev/null
echo "Create keytabs" echo "Create keytabs"
kadmin.local -w password -q "ktadd -k /var/lib/secret/kafka.keytab -norandkey kafka/kafka-1.emqx.net@KDC.EMQX.NET " > /dev/null kadmin.local -w password -q "ktadd -k /var/lib/secret/kafka.keytab -norandkey kafka/kafka-1.emqx.net@KDC.EMQX.NET " > /dev/null
kadmin.local -w password -q "ktadd -k /var/lib/secret/rig.keytab -norandkey rig@KDC.EMQX.NET " > /dev/null kadmin.local -w password -q "ktadd -k /var/lib/secret/rig.keytab -norandkey rig@KDC.EMQX.NET " > /dev/null
# For Kerberos Authn
kadmin.local -w password -q "ktadd -k /var/lib/secret/erlang.keytab -norandkey emqx/erlang.emqx.net@KDC.EMQX.NET " > /dev/null
kadmin.local -w password -q "ktadd -k /var/lib/secret/krb_authn_cli.keytab -norandkey krb_authn_cli@KDC.EMQX.NET " > /dev/null
echo STARTING KDC echo STARTING KDC
/usr/sbin/krb5kdc -n /usr/sbin/krb5kdc -n

View File

@ -1,61 +0,0 @@
# LDAP authentication
To run manual tests with the default docker-compose files.
Expose openldap container port by uncommenting the `ports` config in `docker-compose-ldap.yaml `
To start openldap:
```
docker-compose -f ./.ci/docker-compose-file/docker-compose.yaml -f ./.ci/docker-compose-file/docker-compose-ldap.yaml up -docker
```
## LDAP database
LDAP database is populated from below files:
```
apps/emqx_ldap/test/data/emqx.io.ldif /usr/local/etc/openldap/schema/emqx.io.ldif
apps/emqx_ldap/test/data/emqx.schema /usr/local/etc/openldap/schema/emqx.schema
```
## Minimal EMQX config
```
authentication = [
{
backend = ldap
base_dn = "uid=${username},ou=testdevice,dc=emqx,dc=io"
filter = "(& (objectClass=mqttUser) (uid=${username}))"
mechanism = password_based
method {
is_superuser_attribute = isSuperuser
password_attribute = userPassword
type = hash
}
password = public
pool_size = 8
query_timeout = "5s"
request_timeout = "10s"
server = "localhost:1389"
username = "cn=root,dc=emqx,dc=io"
}
]
```
## Example ldapsearch command
```
ldapsearch -x -H ldap://localhost:389 -D "cn=root,dc=emqx,dc=io" -W -b "uid=mqttuser0007,ou=testdevice,dc=emqx,dc=io" "(&(objectClass=mqttUser)(uid=mqttuser0007))"
```
## Example mqttx command
The client password hashes are generated from their username.
```
# disabled user
mqttx pub -t 't/1' -h localhost -p 1883 -m x -u mqttuser0006 -P mqttuser0006
# enabled super-user
mqttx pub -t 't/1' -h localhost -p 1883 -m x -u mqttuser0007 -P mqttuser0007
```

View File

@ -51,7 +51,7 @@ runs:
echo "SELF_HOSTED=false" >> $GITHUB_OUTPUT echo "SELF_HOSTED=false" >> $GITHUB_OUTPUT
;; ;;
esac esac
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1
id: cache id: cache
if: steps.prepare.outputs.SELF_HOSTED != 'true' if: steps.prepare.outputs.SELF_HOSTED != 'true'
with: with:

View File

@ -152,7 +152,7 @@ jobs:
echo "PROFILE=${PROFILE}" | tee -a .env echo "PROFILE=${PROFILE}" | tee -a .env
echo "PKG_VSN=$(./pkg-vsn.sh ${PROFILE})" | tee -a .env echo "PKG_VSN=$(./pkg-vsn.sh ${PROFILE})" | tee -a .env
zip -ryq -x@.github/workflows/.zipignore $PROFILE.zip . zip -ryq -x@.github/workflows/.zipignore $PROFILE.zip .
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: ${{ matrix.profile }} name: ${{ matrix.profile }}
path: ${{ matrix.profile }}.zip path: ${{ matrix.profile }}.zip

View File

@ -163,7 +163,7 @@ jobs:
echo "PROFILE=${PROFILE}" | tee -a .env echo "PROFILE=${PROFILE}" | tee -a .env
echo "PKG_VSN=$(./pkg-vsn.sh ${PROFILE})" | tee -a .env echo "PKG_VSN=$(./pkg-vsn.sh ${PROFILE})" | tee -a .env
zip -ryq -x@.github/workflows/.zipignore $PROFILE.zip . zip -ryq -x@.github/workflows/.zipignore $PROFILE.zip .
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: ${{ matrix.profile }} name: ${{ matrix.profile }}
path: ${{ matrix.profile }}.zip path: ${{ matrix.profile }}.zip

View File

@ -83,7 +83,7 @@ jobs:
id: build id: build
run: | run: |
make ${{ matrix.profile }}-tgz make ${{ matrix.profile }}-tgz
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: "${{ matrix.profile }}-${{ matrix.arch }}.tar.gz" name: "${{ matrix.profile }}-${{ matrix.arch }}.tar.gz"
path: "_packages/emqx*/emqx-*.tar.gz" path: "_packages/emqx*/emqx-*.tar.gz"
@ -110,7 +110,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with: with:
ref: ${{ github.event.inputs.ref }} ref: ${{ github.event.inputs.ref }}
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
pattern: "${{ matrix.profile[0] }}-*.tar.gz" pattern: "${{ matrix.profile[0] }}-*.tar.gz"
path: _packages path: _packages
@ -122,25 +122,24 @@ jobs:
run: | run: |
ls -lR _packages/$PROFILE ls -lR _packages/$PROFILE
mv _packages/$PROFILE/*.tar.gz ./ mv _packages/$PROFILE/*.tar.gz ./
- name: Enable containerd image store on Docker Engine - name: Enable containerd image store on Docker Engine
run: | run: |
echo "$(sudo cat /etc/docker/daemon.json | jq '. += {"features": {"containerd-snapshotter": true}}')" > daemon.json echo "$(jq '. += {"features": {"containerd-snapshotter": true}}' /etc/docker/daemon.json)" > daemon.json
sudo mv daemon.json /etc/docker/daemon.json sudo mv daemon.json /etc/docker/daemon.json
sudo systemctl restart docker sudo systemctl restart docker
- uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
- uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
- name: Login to hub.docker.com - name: Login to hub.docker.com
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
if: inputs.publish && contains(matrix.profile[1], 'docker.io') if: inputs.publish && contains(matrix.profile[1], 'docker.io')
with: with:
username: ${{ secrets.DOCKER_HUB_USER }} username: ${{ secrets.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_HUB_TOKEN }} password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Login to AWS ECR - name: Login to AWS ECR
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0
if: inputs.publish && contains(matrix.profile[1], 'public.ecr.aws') if: inputs.publish && contains(matrix.profile[1], 'public.ecr.aws')
with: with:
registry: public.ecr.aws registry: public.ecr.aws

View File

@ -51,7 +51,7 @@ jobs:
if: always() if: always()
run: | run: |
docker save $_EMQX_DOCKER_IMAGE_TAG | gzip > $EMQX_NAME-docker-$PKG_VSN.tar.gz docker save $_EMQX_DOCKER_IMAGE_TAG | gzip > $EMQX_NAME-docker-$PKG_VSN.tar.gz
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: "${{ env.EMQX_NAME }}-docker" name: "${{ env.EMQX_NAME }}-docker"
path: "${{ env.EMQX_NAME }}-docker-${{ env.PKG_VSN }}.tar.gz" path: "${{ env.EMQX_NAME }}-docker-${{ env.PKG_VSN }}.tar.gz"

View File

@ -95,7 +95,7 @@ jobs:
apple_developer_identity: ${{ secrets.APPLE_DEVELOPER_IDENTITY }} apple_developer_identity: ${{ secrets.APPLE_DEVELOPER_IDENTITY }}
apple_developer_id_bundle: ${{ secrets.APPLE_DEVELOPER_ID_BUNDLE }} apple_developer_id_bundle: ${{ secrets.APPLE_DEVELOPER_ID_BUNDLE }}
apple_developer_id_bundle_password: ${{ secrets.APPLE_DEVELOPER_ID_BUNDLE_PASSWORD }} apple_developer_id_bundle_password: ${{ secrets.APPLE_DEVELOPER_ID_BUNDLE_PASSWORD }}
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: success() if: success()
with: with:
name: ${{ matrix.profile }}-${{ matrix.os }}-${{ matrix.otp }} name: ${{ matrix.profile }}-${{ matrix.os }}-${{ matrix.otp }}
@ -180,7 +180,7 @@ jobs:
--builder $BUILDER \ --builder $BUILDER \
--elixir $IS_ELIXIR \ --elixir $IS_ELIXIR \
--pkgtype pkg --pkgtype pkg
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: ${{ matrix.profile }}-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.with_elixir == 'yes' && '-elixir' || '' }}-${{ matrix.builder }}-${{ matrix.otp }}-${{ matrix.elixir }} name: ${{ matrix.profile }}-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.with_elixir == 'yes' && '-elixir' || '' }}-${{ matrix.builder }}-${{ matrix.otp }}-${{ matrix.elixir }}
path: _packages/${{ matrix.profile }}/ path: _packages/${{ matrix.profile }}/
@ -198,7 +198,7 @@ jobs:
profile: profile:
- ${{ inputs.profile }} - ${{ inputs.profile }}
steps: steps:
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
pattern: "${{ matrix.profile }}-*" pattern: "${{ matrix.profile }}-*"
path: packages/${{ matrix.profile }} path: packages/${{ matrix.profile }}

View File

@ -23,7 +23,6 @@ jobs:
profile: profile:
- ['emqx', 'master'] - ['emqx', 'master']
- ['emqx', 'release-57'] - ['emqx', 'release-57']
- ['emqx', 'release-58']
os: os:
- ubuntu22.04 - ubuntu22.04
- amzn2023 - amzn2023
@ -54,7 +53,7 @@ jobs:
- name: build pkg - name: build pkg
run: | run: |
./scripts/buildx.sh --profile "$PROFILE" --pkgtype pkg --builder "$BUILDER" ./scripts/buildx.sh --profile "$PROFILE" --pkgtype pkg --builder "$BUILDER"
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: success() if: success()
with: with:
name: ${{ matrix.profile[0] }}-${{ matrix.profile[1] }}-${{ matrix.os }} name: ${{ matrix.profile[0] }}-${{ matrix.profile[1] }}-${{ matrix.os }}
@ -102,7 +101,7 @@ jobs:
apple_developer_identity: ${{ secrets.APPLE_DEVELOPER_IDENTITY }} apple_developer_identity: ${{ secrets.APPLE_DEVELOPER_IDENTITY }}
apple_developer_id_bundle: ${{ secrets.APPLE_DEVELOPER_ID_BUNDLE }} apple_developer_id_bundle: ${{ secrets.APPLE_DEVELOPER_ID_BUNDLE }}
apple_developer_id_bundle_password: ${{ secrets.APPLE_DEVELOPER_ID_BUNDLE_PASSWORD }} apple_developer_id_bundle_password: ${{ secrets.APPLE_DEVELOPER_ID_BUNDLE_PASSWORD }}
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: success() if: success()
with: with:
name: ${{ matrix.profile }}-${{ matrix.os }} name: ${{ matrix.profile }}-${{ matrix.os }}

View File

@ -41,13 +41,13 @@ jobs:
- name: build pkg - name: build pkg
run: | run: |
./scripts/buildx.sh --profile $PROFILE --pkgtype pkg --elixir $ELIXIR --arch $ARCH ./scripts/buildx.sh --profile $PROFILE --pkgtype pkg --elixir $ELIXIR --arch $ARCH
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: "${{ matrix.profile[0] }}-${{ matrix.profile[1] }}-${{ matrix.profile[2] }}" name: "${{ matrix.profile[0] }}-${{ matrix.profile[1] }}-${{ matrix.profile[2] }}"
path: _packages/${{ matrix.profile[0] }}/* path: _packages/${{ matrix.profile[0] }}/*
retention-days: 7 retention-days: 7
compression-level: 0 compression-level: 0
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: "${{ matrix.profile[0] }}-schema-dump-${{ matrix.profile[1] }}-${{ matrix.profile[2] }}" name: "${{ matrix.profile[0] }}-schema-dump-${{ matrix.profile[1] }}-${{ matrix.profile[2] }}"
path: | path: |
@ -84,7 +84,7 @@ jobs:
apple_developer_identity: ${{ secrets.APPLE_DEVELOPER_IDENTITY }} apple_developer_identity: ${{ secrets.APPLE_DEVELOPER_IDENTITY }}
apple_developer_id_bundle: ${{ secrets.APPLE_DEVELOPER_ID_BUNDLE }} apple_developer_id_bundle: ${{ secrets.APPLE_DEVELOPER_ID_BUNDLE }}
apple_developer_id_bundle_password: ${{ secrets.APPLE_DEVELOPER_ID_BUNDLE_PASSWORD }} apple_developer_id_bundle_password: ${{ secrets.APPLE_DEVELOPER_ID_BUNDLE_PASSWORD }}
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: ${{ matrix.os }} name: ${{ matrix.os }}
path: _packages/**/* path: _packages/**/*

View File

@ -37,7 +37,7 @@ jobs:
- run: ./scripts/check-elixir-deps-discrepancies.exs - run: ./scripts/check-elixir-deps-discrepancies.exs
- run: ./scripts/check-elixir-applications.exs - run: ./scripts/check-elixir-applications.exs
- name: Upload produced lock files - name: Upload produced lock files
uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: failure() if: failure()
with: with:
name: ${{ matrix.profile }}_produced_lock_files name: ${{ matrix.profile }}_produced_lock_files

View File

@ -24,7 +24,6 @@ jobs:
branch: branch:
- master - master
- release-57 - release-57
- release-58
language: language:
- cpp - cpp
- python - python

View File

@ -24,7 +24,6 @@ jobs:
ref: ref:
- master - master
- release-57 - release-57
- release-58
steps: steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with: with:

View File

@ -52,7 +52,7 @@ jobs:
id: package_file id: package_file
run: | run: |
echo "PACKAGE_FILE=$(find _packages/emqx -name 'emqx-*.deb' | head -n 1 | xargs basename)" >> $GITHUB_OUTPUT echo "PACKAGE_FILE=$(find _packages/emqx -name 'emqx-*.deb' | head -n 1 | xargs basename)" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: emqx-ubuntu20.04 name: emqx-ubuntu20.04
path: _packages/emqx/${{ steps.package_file.outputs.PACKAGE_FILE }} path: _packages/emqx/${{ steps.package_file.outputs.PACKAGE_FILE }}
@ -77,7 +77,7 @@ jobs:
repository: emqx/tf-emqx-performance-test repository: emqx/tf-emqx-performance-test
path: tf-emqx-performance-test path: tf-emqx-performance-test
ref: v0.2.3 ref: v0.2.3
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: emqx-ubuntu20.04 name: emqx-ubuntu20.04
path: tf-emqx-performance-test/ path: tf-emqx-performance-test/
@ -113,13 +113,13 @@ jobs:
working-directory: ./tf-emqx-performance-test working-directory: ./tf-emqx-performance-test
run: | run: |
terraform destroy -auto-approve terraform destroy -auto-approve
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: success() if: success()
with: with:
name: metrics name: metrics
path: | path: |
"./tf-emqx-performance-test/*.tar.gz" "./tf-emqx-performance-test/*.tar.gz"
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: failure() if: failure()
with: with:
name: terraform name: terraform
@ -148,7 +148,7 @@ jobs:
repository: emqx/tf-emqx-performance-test repository: emqx/tf-emqx-performance-test
path: tf-emqx-performance-test path: tf-emqx-performance-test
ref: v0.2.3 ref: v0.2.3
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: emqx-ubuntu20.04 name: emqx-ubuntu20.04
path: tf-emqx-performance-test/ path: tf-emqx-performance-test/
@ -184,13 +184,13 @@ jobs:
working-directory: ./tf-emqx-performance-test working-directory: ./tf-emqx-performance-test
run: | run: |
terraform destroy -auto-approve terraform destroy -auto-approve
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: success() if: success()
with: with:
name: metrics name: metrics
path: | path: |
"./tf-emqx-performance-test/*.tar.gz" "./tf-emqx-performance-test/*.tar.gz"
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: failure() if: failure()
with: with:
name: terraform name: terraform
@ -220,7 +220,7 @@ jobs:
repository: emqx/tf-emqx-performance-test repository: emqx/tf-emqx-performance-test
path: tf-emqx-performance-test path: tf-emqx-performance-test
ref: v0.2.3 ref: v0.2.3
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: emqx-ubuntu20.04 name: emqx-ubuntu20.04
path: tf-emqx-performance-test/ path: tf-emqx-performance-test/
@ -257,13 +257,13 @@ jobs:
working-directory: ./tf-emqx-performance-test working-directory: ./tf-emqx-performance-test
run: | run: |
terraform destroy -auto-approve terraform destroy -auto-approve
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: success() if: success()
with: with:
name: metrics name: metrics
path: | path: |
"./tf-emqx-performance-test/*.tar.gz" "./tf-emqx-performance-test/*.tar.gz"
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: failure() if: failure()
with: with:
name: terraform name: terraform
@ -294,7 +294,7 @@ jobs:
repository: emqx/tf-emqx-performance-test repository: emqx/tf-emqx-performance-test
path: tf-emqx-performance-test path: tf-emqx-performance-test
ref: v0.2.3 ref: v0.2.3
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: emqx-ubuntu20.04 name: emqx-ubuntu20.04
path: tf-emqx-performance-test/ path: tf-emqx-performance-test/
@ -330,13 +330,13 @@ jobs:
working-directory: ./tf-emqx-performance-test working-directory: ./tf-emqx-performance-test
run: | run: |
terraform destroy -auto-approve terraform destroy -auto-approve
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: success() if: success()
with: with:
name: metrics name: metrics
path: | path: |
"./tf-emqx-performance-test/*.tar.gz" "./tf-emqx-performance-test/*.tar.gz"
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: failure() if: failure()
with: with:
name: terraform name: terraform

View File

@ -25,7 +25,7 @@ jobs:
- emqx - emqx
- emqx-enterprise - emqx-enterprise
steps: steps:
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: ${{ matrix.profile }} name: ${{ matrix.profile }}
- name: extract artifact - name: extract artifact
@ -40,7 +40,7 @@ jobs:
if: failure() if: failure()
run: | run: |
cat _build/${{ matrix.profile }}/rel/emqx/log/erlang.log.* cat _build/${{ matrix.profile }}/rel/emqx/log/erlang.log.*
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: failure() if: failure()
with: with:
name: conftest-logs-${{ matrix.profile }} name: conftest-logs-${{ matrix.profile }}

View File

@ -35,7 +35,7 @@ jobs:
source env.sh source env.sh
PKG_VSN=$(docker run --rm -v $(pwd):$(pwd) -w $(pwd) -u $(id -u) "$EMQX_BUILDER" ./pkg-vsn.sh "$EMQX_NAME") PKG_VSN=$(docker run --rm -v $(pwd):$(pwd) -w $(pwd) -u $(id -u) "$EMQX_BUILDER" ./pkg-vsn.sh "$EMQX_NAME")
echo "PKG_VSN=$PKG_VSN" >> "$GITHUB_ENV" echo "PKG_VSN=$PKG_VSN" >> "$GITHUB_ENV"
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: ${{ env.EMQX_NAME }}-docker name: ${{ env.EMQX_NAME }}-docker
path: /tmp path: /tmp
@ -69,6 +69,7 @@ jobs:
shell: bash shell: bash
env: env:
EMQX_NAME: ${{ matrix.profile }} EMQX_NAME: ${{ matrix.profile }}
_EMQX_TEST_DB_BACKEND: ${{ matrix.cluster_db_backend }}
strategy: strategy:
fail-fast: false fail-fast: false
@ -77,20 +78,18 @@ jobs:
- emqx - emqx
- emqx-enterprise - emqx-enterprise
- emqx-elixir - emqx-elixir
cluster_db_backend:
- mnesia
- rlog
steps: steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Set up environment - name: Set up environment
id: env id: env
run: | run: |
source env.sh source env.sh
if [ "$EMQX_NAME" = "emqx-enterprise" ]; then
_EMQX_TEST_DB_BACKEND='rlog'
else
_EMQX_TEST_DB_BACKEND='mnesia'
fi
PKG_VSN=$(docker run --rm -v $(pwd):$(pwd) -w $(pwd) -u $(id -u) "$EMQX_BUILDER" ./pkg-vsn.sh "$EMQX_NAME") PKG_VSN=$(docker run --rm -v $(pwd):$(pwd) -w $(pwd) -u $(id -u) "$EMQX_BUILDER" ./pkg-vsn.sh "$EMQX_NAME")
echo "PKG_VSN=$PKG_VSN" >> "$GITHUB_ENV" echo "PKG_VSN=$PKG_VSN" >> "$GITHUB_ENV"
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: ${{ env.EMQX_NAME }}-docker name: ${{ env.EMQX_NAME }}-docker
path: /tmp path: /tmp

View File

@ -95,7 +95,7 @@ jobs:
echo "Suites: $SUITES" echo "Suites: $SUITES"
./rebar3 as standalone_test ct --name 'test@127.0.0.1' -v --readable=true --suite="$SUITES" ./rebar3 as standalone_test ct --name 'test@127.0.0.1' -v --readable=true --suite="$SUITES"
fi fi
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: failure() if: failure()
with: with:
name: logs-emqx-app-tests-${{ matrix.type }} name: logs-emqx-app-tests-${{ matrix.type }}

View File

@ -44,7 +44,7 @@ jobs:
source env.sh source env.sh
PKG_VSN=$(docker run --rm -v $(pwd):$(pwd) -w $(pwd) -u $(id -u) "$EMQX_BUILDER" ./pkg-vsn.sh "$EMQX_NAME") PKG_VSN=$(docker run --rm -v $(pwd):$(pwd) -w $(pwd) -u $(id -u) "$EMQX_BUILDER" ./pkg-vsn.sh "$EMQX_NAME")
echo "EMQX_TAG=$PKG_VSN" >> "$GITHUB_ENV" echo "EMQX_TAG=$PKG_VSN" >> "$GITHUB_ENV"
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: "${{ env.EMQX_NAME }}-docker" name: "${{ env.EMQX_NAME }}-docker"
path: /tmp path: /tmp

View File

@ -31,7 +31,7 @@ jobs:
else else
wget --no-verbose --no-check-certificate -O /tmp/apache-jmeter.tgz $ARCHIVE_URL wget --no-verbose --no-check-certificate -O /tmp/apache-jmeter.tgz $ARCHIVE_URL
fi fi
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: apache-jmeter.tgz name: apache-jmeter.tgz
path: /tmp/apache-jmeter.tgz path: /tmp/apache-jmeter.tgz
@ -58,7 +58,7 @@ jobs:
source env.sh source env.sh
PKG_VSN=$(docker run --rm -v $(pwd):$(pwd) -w $(pwd) -u $(id -u) "$EMQX_BUILDER" ./pkg-vsn.sh emqx) PKG_VSN=$(docker run --rm -v $(pwd):$(pwd) -w $(pwd) -u $(id -u) "$EMQX_BUILDER" ./pkg-vsn.sh emqx)
echo "PKG_VSN=$PKG_VSN" >> "$GITHUB_ENV" echo "PKG_VSN=$PKG_VSN" >> "$GITHUB_ENV"
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: emqx-docker name: emqx-docker
path: /tmp path: /tmp
@ -95,7 +95,7 @@ jobs:
echo "check logs failed" echo "check logs failed"
exit 1 exit 1
fi fi
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: always() if: always()
with: with:
name: jmeter_logs-advanced_feat-${{ matrix.scripts_type }} name: jmeter_logs-advanced_feat-${{ matrix.scripts_type }}
@ -127,7 +127,7 @@ jobs:
source env.sh source env.sh
PKG_VSN=$(docker run --rm -v $(pwd):$(pwd) -w $(pwd) -u $(id -u) "$EMQX_BUILDER" ./pkg-vsn.sh emqx) PKG_VSN=$(docker run --rm -v $(pwd):$(pwd) -w $(pwd) -u $(id -u) "$EMQX_BUILDER" ./pkg-vsn.sh emqx)
echo "PKG_VSN=$PKG_VSN" >> "$GITHUB_ENV" echo "PKG_VSN=$PKG_VSN" >> "$GITHUB_ENV"
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: emqx-docker name: emqx-docker
path: /tmp path: /tmp
@ -175,7 +175,7 @@ jobs:
if: failure() if: failure()
run: | run: |
docker compose -f .ci/docker-compose-file/docker-compose-emqx-cluster.yaml logs --no-color > ./jmeter_logs/emqx.log docker compose -f .ci/docker-compose-file/docker-compose-emqx-cluster.yaml logs --no-color > ./jmeter_logs/emqx.log
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: always() if: always()
with: with:
name: jmeter_logs-pgsql_authn_authz-${{ matrix.scripts_type }}_${{ matrix.pgsql_tag }} name: jmeter_logs-pgsql_authn_authz-${{ matrix.scripts_type }}_${{ matrix.pgsql_tag }}
@ -204,7 +204,7 @@ jobs:
source env.sh source env.sh
PKG_VSN=$(docker run --rm -v $(pwd):$(pwd) -w $(pwd) -u $(id -u) "$EMQX_BUILDER" ./pkg-vsn.sh emqx) PKG_VSN=$(docker run --rm -v $(pwd):$(pwd) -w $(pwd) -u $(id -u) "$EMQX_BUILDER" ./pkg-vsn.sh emqx)
echo "PKG_VSN=$PKG_VSN" >> "$GITHUB_ENV" echo "PKG_VSN=$PKG_VSN" >> "$GITHUB_ENV"
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: emqx-docker name: emqx-docker
path: /tmp path: /tmp
@ -248,7 +248,7 @@ jobs:
echo "check logs failed" echo "check logs failed"
exit 1 exit 1
fi fi
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: always() if: always()
with: with:
name: jmeter_logs-mysql_authn_authz-${{ matrix.scripts_type }}_${{ matrix.mysql_tag }} name: jmeter_logs-mysql_authn_authz-${{ matrix.scripts_type }}_${{ matrix.mysql_tag }}
@ -273,7 +273,7 @@ jobs:
source env.sh source env.sh
PKG_VSN=$(docker run --rm -v $(pwd):$(pwd) -w $(pwd) -u $(id -u) "$EMQX_BUILDER" ./pkg-vsn.sh emqx) PKG_VSN=$(docker run --rm -v $(pwd):$(pwd) -w $(pwd) -u $(id -u) "$EMQX_BUILDER" ./pkg-vsn.sh emqx)
echo "PKG_VSN=$PKG_VSN" >> "$GITHUB_ENV" echo "PKG_VSN=$PKG_VSN" >> "$GITHUB_ENV"
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: emqx-docker name: emqx-docker
path: /tmp path: /tmp
@ -313,7 +313,7 @@ jobs:
echo "check logs failed" echo "check logs failed"
exit 1 exit 1
fi fi
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: always() if: always()
with: with:
name: jmeter_logs-JWT_authn-${{ matrix.scripts_type }} name: jmeter_logs-JWT_authn-${{ matrix.scripts_type }}
@ -339,7 +339,7 @@ jobs:
source env.sh source env.sh
PKG_VSN=$(docker run --rm -v $(pwd):$(pwd) -w $(pwd) -u $(id -u) "$EMQX_BUILDER" ./pkg-vsn.sh emqx) PKG_VSN=$(docker run --rm -v $(pwd):$(pwd) -w $(pwd) -u $(id -u) "$EMQX_BUILDER" ./pkg-vsn.sh emqx)
echo "PKG_VSN=$PKG_VSN" >> "$GITHUB_ENV" echo "PKG_VSN=$PKG_VSN" >> "$GITHUB_ENV"
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: emqx-docker name: emqx-docker
path: /tmp path: /tmp
@ -370,7 +370,7 @@ jobs:
echo "check logs failed" echo "check logs failed"
exit 1 exit 1
fi fi
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: always() if: always()
with: with:
name: jmeter_logs-built_in_database_authn_authz-${{ matrix.scripts_type }} name: jmeter_logs-built_in_database_authn_authz-${{ matrix.scripts_type }}

View File

@ -25,7 +25,7 @@ jobs:
run: run:
shell: bash shell: bash
steps: steps:
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: emqx-enterprise name: emqx-enterprise
- name: extract artifact - name: extract artifact
@ -45,7 +45,7 @@ jobs:
run: | run: |
export PROFILE='emqx-enterprise' export PROFILE='emqx-enterprise'
make emqx-enterprise-tgz make emqx-enterprise-tgz
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
name: Upload built emqx and test scenario name: Upload built emqx and test scenario
with: with:
name: relup_tests_emqx_built name: relup_tests_emqx_built
@ -72,7 +72,7 @@ jobs:
run: run:
shell: bash shell: bash
steps: steps:
- uses: erlef/setup-beam@b9c58b0450cd832ccdb3c17cc156a47065d2114f # v1.18.1 - uses: erlef/setup-beam@a6e26b22319003294c58386b6f25edbc7336819a # v1.18.0
with: with:
otp-version: 26.2.5 otp-version: 26.2.5
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
@ -88,7 +88,7 @@ jobs:
./configure ./configure
make make
echo "$(pwd)/bin" >> $GITHUB_PATH echo "$(pwd)/bin" >> $GITHUB_PATH
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
name: Download built emqx and test scenario name: Download built emqx and test scenario
with: with:
name: relup_tests_emqx_built name: relup_tests_emqx_built
@ -111,7 +111,7 @@ jobs:
docker logs node2.emqx.io | tee lux_logs/emqx2.log docker logs node2.emqx.io | tee lux_logs/emqx2.log
exit 1 exit 1
fi fi
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
name: Save debug data name: Save debug data
if: failure() if: failure()
with: with:

View File

@ -46,7 +46,7 @@ jobs:
contents: read contents: read
steps: steps:
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: ${{ matrix.profile }} name: ${{ matrix.profile }}
@ -90,7 +90,7 @@ jobs:
contents: read contents: read
steps: steps:
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: ${{ matrix.profile }} name: ${{ matrix.profile }}
- name: extract artifact - name: extract artifact
@ -133,7 +133,7 @@ jobs:
if: failure() if: failure()
run: tar -czf logs.tar.gz _build/test/logs run: tar -czf logs.tar.gz _build/test/logs
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: failure() if: failure()
with: with:
name: logs-${{ matrix.profile }}-${{ matrix.prefix }}-sg${{ matrix.suitegroup }} name: logs-${{ matrix.profile }}-${{ matrix.prefix }}-sg${{ matrix.suitegroup }}
@ -164,7 +164,7 @@ jobs:
CT_COVER_EXPORT_PREFIX: ${{ matrix.profile }}-sg${{ matrix.suitegroup }} CT_COVER_EXPORT_PREFIX: ${{ matrix.profile }}-sg${{ matrix.suitegroup }}
steps: steps:
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: ${{ matrix.profile }} name: ${{ matrix.profile }}
- name: extract artifact - name: extract artifact
@ -193,7 +193,7 @@ jobs:
if: failure() if: failure()
run: tar -czf logs.tar.gz _build/test/logs run: tar -czf logs.tar.gz _build/test/logs
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
if: failure() if: failure()
with: with:
name: logs-${{ matrix.profile }}-${{ matrix.prefix }}-sg${{ matrix.suitegroup }} name: logs-${{ matrix.profile }}-${{ matrix.prefix }}-sg${{ matrix.suitegroup }}

View File

@ -30,7 +30,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: "Run analysis" - name: "Run analysis"
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
with: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif
@ -40,7 +40,7 @@ jobs:
publish_results: true publish_results: true
- name: "Upload artifact" - name: "Upload artifact"
uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5 uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif

View File

@ -19,7 +19,7 @@ jobs:
- emqx-enterprise - emqx-enterprise
runs-on: ${{ endsWith(github.repository, '/emqx') && 'ubuntu-22.04' || fromJSON('["self-hosted","ephemeral","linux","x64"]') }} runs-on: ${{ endsWith(github.repository, '/emqx') && 'ubuntu-22.04' || fromJSON('["self-hosted","ephemeral","linux","x64"]') }}
steps: steps:
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
pattern: "${{ matrix.profile }}-schema-dump-*-x64" pattern: "${{ matrix.profile }}-schema-dump-*-x64"
merge-multiple: true merge-multiple: true

View File

@ -30,7 +30,7 @@ jobs:
include: ${{ fromJson(inputs.ct-matrix) }} include: ${{ fromJson(inputs.ct-matrix) }}
container: "${{ inputs.builder }}" container: "${{ inputs.builder }}"
steps: steps:
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with: with:
name: ${{ matrix.profile }} name: ${{ matrix.profile }}
- name: extract artifact - name: extract artifact

View File

@ -34,7 +34,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
with: with:
fetch-depth: 0 fetch-depth: 0

View File

@ -10,8 +10,8 @@ include env.sh
# Dashboard version # Dashboard version
# from https://github.com/emqx/emqx-dashboard5 # from https://github.com/emqx/emqx-dashboard5
export EMQX_DASHBOARD_VERSION ?= v1.10.0-beta.1 export EMQX_DASHBOARD_VERSION ?= v1.9.1
export EMQX_EE_DASHBOARD_VERSION ?= e1.8.0-beta.1 export EMQX_EE_DASHBOARD_VERSION ?= e1.7.1
export EMQX_RELUP ?= true export EMQX_RELUP ?= true
export EMQX_REL_FORM ?= tgz export EMQX_REL_FORM ?= tgz

View File

@ -65,20 +65,9 @@
%% Route %% Route
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-record(share_dest, {
session_id :: emqx_session:session_id(),
group :: emqx_types:group()
}).
-record(route, { -record(route, {
topic :: binary(), topic :: binary(),
dest :: dest :: node() | {binary(), node()} | emqx_session:session_id() | emqx_external_broker:dest()
node()
| {binary(), node()}
| emqx_session:session_id()
%% One session can also have multiple subscriptions to the same topic through different groups
| #share_dest{}
| emqx_external_broker:dest()
}). }).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -683,7 +683,6 @@ end).
-define(FRAME_PARSE_ERROR, frame_parse_error). -define(FRAME_PARSE_ERROR, frame_parse_error).
-define(FRAME_SERIALIZE_ERROR, frame_serialize_error). -define(FRAME_SERIALIZE_ERROR, frame_serialize_error).
-define(THROW_FRAME_ERROR(Reason), erlang:throw({?FRAME_PARSE_ERROR, Reason})). -define(THROW_FRAME_ERROR(Reason), erlang:throw({?FRAME_PARSE_ERROR, Reason})).
-define(THROW_SERIALIZE_ERROR(Reason), erlang:throw({?FRAME_SERIALIZE_ERROR, Reason})). -define(THROW_SERIALIZE_ERROR(Reason), erlang:throw({?FRAME_SERIALIZE_ERROR, Reason})).

View File

@ -32,7 +32,7 @@
%% `apps/emqx/src/bpapi/README.md' %% `apps/emqx/src/bpapi/README.md'
%% Opensource edition %% Opensource edition
-define(EMQX_RELEASE_CE, "5.8.0-alpha.1"). -define(EMQX_RELEASE_CE, "5.7.1").
%% Enterprise edition %% Enterprise edition
-define(EMQX_RELEASE_EE, "5.8.0-alpha.1"). -define(EMQX_RELEASE_EE, "5.7.1").

View File

@ -41,20 +41,16 @@
). ).
%% NOTE: do not forget to use atom for msg and add every used msg to %% NOTE: do not forget to use atom for msg and add every used msg to
%% the default value of `log.throttling.msgs` list. %% the default value of `log.thorttling.msgs` list.
-define(SLOG_THROTTLE(Level, Data), -define(SLOG_THROTTLE(Level, Data),
?SLOG_THROTTLE(Level, Data, #{}) ?SLOG_THROTTLE(Level, Data, #{})
). ).
-define(SLOG_THROTTLE(Level, Data, Meta), -define(SLOG_THROTTLE(Level, Data, Meta),
?SLOG_THROTTLE(Level, undefined, Data, Meta)
).
-define(SLOG_THROTTLE(Level, UniqueKey, Data, Meta),
case logger:allow(Level, ?MODULE) of case logger:allow(Level, ?MODULE) of
true -> true ->
(fun(#{msg := __Msg} = __Data) -> (fun(#{msg := __Msg} = __Data) ->
case emqx_log_throttler:allow(__Msg, UniqueKey) of case emqx_log_throttler:allow(__Msg) of
true -> true ->
logger:log(Level, __Data, Meta); logger:log(Level, __Data, Meta);
false -> false ->
@ -91,7 +87,7 @@
?_DO_TRACE(Tag, Msg, Meta), ?_DO_TRACE(Tag, Msg, Meta),
?SLOG( ?SLOG(
Level, Level,
(Meta)#{msg => Msg, tag => Tag}, (emqx_trace_formatter:format_meta_map(Meta))#{msg => Msg, tag => Tag},
#{is_trace => false} #{is_trace => false}
) )
end). end).

View File

@ -10,7 +10,6 @@
{emqx_bridge,5}. {emqx_bridge,5}.
{emqx_bridge,6}. {emqx_bridge,6}.
{emqx_broker,1}. {emqx_broker,1}.
{emqx_cluster_link,1}.
{emqx_cm,1}. {emqx_cm,1}.
{emqx_cm,2}. {emqx_cm,2}.
{emqx_cm,3}. {emqx_cm,3}.
@ -27,7 +26,6 @@
{emqx_ds,2}. {emqx_ds,2}.
{emqx_ds,3}. {emqx_ds,3}.
{emqx_ds,4}. {emqx_ds,4}.
{emqx_ds_shared_sub,1}.
{emqx_eviction_agent,1}. {emqx_eviction_agent,1}.
{emqx_eviction_agent,2}. {emqx_eviction_agent,2}.
{emqx_eviction_agent,3}. {emqx_eviction_agent,3}.

View File

@ -28,14 +28,15 @@
{lc, {git, "https://github.com/emqx/lc.git", {tag, "0.3.2"}}}, {lc, {git, "https://github.com/emqx/lc.git", {tag, "0.3.2"}}},
{gproc, {git, "https://github.com/emqx/gproc", {tag, "0.9.0.1"}}}, {gproc, {git, "https://github.com/emqx/gproc", {tag, "0.9.0.1"}}},
{cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.9.2"}}}, {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.9.2"}}},
{esockd, {git, "https://github.com/emqx/esockd", {tag, "5.12.0"}}}, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.11.3"}}},
{ekka, {git, "https://github.com/emqx/ekka", {tag, "0.19.5"}}}, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.19.5"}}},
{gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "3.3.1"}}}, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "3.3.1"}}},
{hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.43.2"}}}, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.43.1"}}},
{emqx_http_lib, {git, "https://github.com/emqx/emqx_http_lib.git", {tag, "0.5.3"}}}, {emqx_http_lib, {git, "https://github.com/emqx/emqx_http_lib.git", {tag, "0.5.3"}}},
{pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}}, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}},
{recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}}, {recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}},
{snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "1.0.10"}}} {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "1.0.10"}}},
{ra, "2.7.3"}
]}. ]}.
{plugins, [{rebar3_proper, "0.12.1"}, rebar3_path_deps]}. {plugins, [{rebar3_proper, "0.12.1"}, rebar3_path_deps]}.
@ -46,7 +47,7 @@
{meck, "0.9.2"}, {meck, "0.9.2"},
{proper, "1.4.0"}, {proper, "1.4.0"},
{bbmustache, "1.10.0"}, {bbmustache, "1.10.0"},
{emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.10.0"}}} {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.12.0"}}}
]}, ]},
{extra_src_dirs, [ {extra_src_dirs, [
{"test", [recursive]}, {"test", [recursive]},
@ -58,7 +59,7 @@
{meck, "0.9.2"}, {meck, "0.9.2"},
{proper, "1.4.0"}, {proper, "1.4.0"},
{bbmustache, "1.10.0"}, {bbmustache, "1.10.0"},
{emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.9.7"}}} {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.12.0"}}}
]}, ]},
{extra_src_dirs, [{"test", [recursive]}]} {extra_src_dirs, [{"test", [recursive]}]}
]} ]}

View File

@ -2,7 +2,7 @@
{application, emqx, [ {application, emqx, [
{id, "emqx"}, {id, "emqx"},
{description, "EMQX Core"}, {description, "EMQX Core"},
{vsn, "5.3.4"}, {vsn, "5.3.3"},
{modules, []}, {modules, []},
{registered, []}, {registered, []},
{applications, [ {applications, [

View File

@ -146,9 +146,7 @@
-type replies() :: emqx_types:packet() | reply() | [reply()]. -type replies() :: emqx_types:packet() | reply() | [reply()].
-define(IS_MQTT_V5, #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}). -define(IS_MQTT_V5, #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}).
-define(IS_CONNECTED_OR_REAUTHENTICATING(ConnState),
((ConnState == connected) orelse (ConnState == reauthenticating))
).
-define(IS_COMMON_SESSION_TIMER(N), -define(IS_COMMON_SESSION_TIMER(N),
((N == retry_delivery) orelse (N == expire_awaiting_rel)) ((N == retry_delivery) orelse (N == expire_awaiting_rel))
). ).
@ -339,7 +337,7 @@ take_conn_info_fields(Fields, ClientInfo, ConnInfo) ->
| {shutdown, Reason :: term(), channel()} | {shutdown, Reason :: term(), channel()}
| {shutdown, Reason :: term(), replies(), channel()}. | {shutdown, Reason :: term(), replies(), channel()}.
handle_in(?CONNECT_PACKET(), Channel = #channel{conn_state = ConnState}) when handle_in(?CONNECT_PACKET(), Channel = #channel{conn_state = ConnState}) when
?IS_CONNECTED_OR_REAUTHENTICATING(ConnState) ConnState =:= connected orelse ConnState =:= reauthenticating
-> ->
handle_out(disconnect, ?RC_PROTOCOL_ERROR, Channel); handle_out(disconnect, ?RC_PROTOCOL_ERROR, Channel);
handle_in(?CONNECT_PACKET(), Channel = #channel{conn_state = connecting}) -> handle_in(?CONNECT_PACKET(), Channel = #channel{conn_state = connecting}) ->
@ -396,6 +394,8 @@ handle_in(
case ConnState of case ConnState of
connecting -> connecting ->
process_connect(NProperties, NChannel); process_connect(NProperties, NChannel);
reauthenticating ->
process_connect(NProperties, NChannel);
_ -> _ ->
handle_out( handle_out(
auth, auth,
@ -569,8 +569,29 @@ handle_in(
process_disconnect(ReasonCode, Properties, NChannel); process_disconnect(ReasonCode, Properties, NChannel);
handle_in(?AUTH_PACKET(), Channel) -> handle_in(?AUTH_PACKET(), Channel) ->
handle_out(disconnect, ?RC_IMPLEMENTATION_SPECIFIC_ERROR, Channel); handle_out(disconnect, ?RC_IMPLEMENTATION_SPECIFIC_ERROR, Channel);
handle_in({frame_error, Reason}, Channel) -> handle_in({frame_error, Reason}, Channel = #channel{conn_state = idle}) ->
handle_frame_error(Reason, Channel); shutdown(shutdown_count(frame_error, Reason), Channel);
handle_in(
{frame_error, #{cause := frame_too_large} = R}, Channel = #channel{conn_state = connecting}
) ->
shutdown(
shutdown_count(frame_error, R), ?CONNACK_PACKET(?RC_PACKET_TOO_LARGE), Channel
);
handle_in({frame_error, Reason}, Channel = #channel{conn_state = connecting}) ->
shutdown(shutdown_count(frame_error, Reason), ?CONNACK_PACKET(?RC_MALFORMED_PACKET), Channel);
handle_in(
{frame_error, #{cause := frame_too_large}}, Channel = #channel{conn_state = ConnState}
) when
ConnState =:= connected orelse ConnState =:= reauthenticating
->
handle_out(disconnect, {?RC_PACKET_TOO_LARGE, frame_too_large}, Channel);
handle_in({frame_error, Reason}, Channel = #channel{conn_state = ConnState}) when
ConnState =:= connected orelse ConnState =:= reauthenticating
->
handle_out(disconnect, {?RC_MALFORMED_PACKET, Reason}, Channel);
handle_in({frame_error, Reason}, Channel = #channel{conn_state = disconnected}) ->
?SLOG(error, #{msg => "malformed_mqtt_message", reason => Reason}),
{ok, Channel};
handle_in(Packet, Channel) -> handle_in(Packet, Channel) ->
?SLOG(error, #{msg => "disconnecting_due_to_unexpected_message", packet => Packet}), ?SLOG(error, #{msg => "disconnecting_due_to_unexpected_message", packet => Packet}),
handle_out(disconnect, ?RC_PROTOCOL_ERROR, Channel). handle_out(disconnect, ?RC_PROTOCOL_ERROR, Channel).
@ -1002,68 +1023,6 @@ not_nacked({deliver, _Topic, Msg}) ->
true true
end. end.
%%--------------------------------------------------------------------
%% Handle Frame Error
%%--------------------------------------------------------------------
handle_frame_error(
Reason = #{cause := frame_too_large},
Channel = #channel{conn_state = ConnState, conninfo = ConnInfo}
) when
?IS_CONNECTED_OR_REAUTHENTICATING(ConnState)
->
ShutdownCount = shutdown_count(frame_error, Reason),
case proto_ver(Reason, ConnInfo) of
?MQTT_PROTO_V5 ->
handle_out(disconnect, {?RC_PACKET_TOO_LARGE, frame_too_large}, Channel);
_ ->
shutdown(ShutdownCount, Channel)
end;
%% Only send CONNACK with reason code `frame_too_large` for MQTT-v5.0 when connecting,
%% otherwise DONOT send any CONNACK or DISCONNECT packet.
handle_frame_error(
Reason,
Channel = #channel{conn_state = ConnState, conninfo = ConnInfo}
) when
is_map(Reason) andalso
(ConnState == idle orelse ConnState == connecting)
->
ShutdownCount = shutdown_count(frame_error, Reason),
ProtoVer = proto_ver(Reason, ConnInfo),
NChannel = Channel#channel{conninfo = ConnInfo#{proto_ver => ProtoVer}},
case ProtoVer of
?MQTT_PROTO_V5 ->
shutdown(ShutdownCount, ?CONNACK_PACKET(?RC_PACKET_TOO_LARGE), NChannel);
_ ->
shutdown(ShutdownCount, NChannel)
end;
handle_frame_error(
Reason,
Channel = #channel{conn_state = connecting}
) ->
shutdown(
shutdown_count(frame_error, Reason),
?CONNACK_PACKET(?RC_MALFORMED_PACKET),
Channel
);
handle_frame_error(
Reason,
Channel = #channel{conn_state = ConnState}
) when
?IS_CONNECTED_OR_REAUTHENTICATING(ConnState)
->
handle_out(
disconnect,
{?RC_MALFORMED_PACKET, Reason},
Channel
);
handle_frame_error(
Reason,
Channel = #channel{conn_state = disconnected}
) ->
?SLOG(error, #{msg => "malformed_mqtt_message", reason => Reason}),
{ok, Channel}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Handle outgoing packet %% Handle outgoing packet
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -1332,7 +1291,7 @@ handle_info(
session = Session session = Session
} }
) when ) when
?IS_CONNECTED_OR_REAUTHENTICATING(ConnState) ConnState =:= connected orelse ConnState =:= reauthenticating
-> ->
{Intent, Session1} = session_disconnect(ClientInfo, ConnInfo, Session), {Intent, Session1} = session_disconnect(ClientInfo, ConnInfo, Session),
Channel1 = ensure_disconnected(Reason, maybe_publish_will_msg(sock_closed, Channel)), Channel1 = ensure_disconnected(Reason, maybe_publish_will_msg(sock_closed, Channel)),
@ -2679,7 +2638,8 @@ save_alias(outbound, AliasId, Topic, TopicAliases = #{outbound := Aliases}) ->
NAliases = maps:put(Topic, AliasId, Aliases), NAliases = maps:put(Topic, AliasId, Aliases),
TopicAliases#{outbound => NAliases}. TopicAliases#{outbound => NAliases}.
-compile({inline, [reply/2, shutdown/2, shutdown/3]}). -compile({inline, [reply/2, shutdown/2, shutdown/3, sp/1, flag/1]}).
reply(Reply, Channel) -> reply(Reply, Channel) ->
{reply, Reply, Channel}. {reply, Reply, Channel}.
@ -2715,13 +2675,13 @@ disconnect_and_shutdown(
?IS_MQTT_V5 = ?IS_MQTT_V5 =
#channel{conn_state = ConnState} #channel{conn_state = ConnState}
) when ) when
?IS_CONNECTED_OR_REAUTHENTICATING(ConnState) ConnState =:= connected orelse ConnState =:= reauthenticating
-> ->
NChannel = ensure_disconnected(Reason, Channel), NChannel = ensure_disconnected(Reason, Channel),
shutdown(Reason, Reply, ?DISCONNECT_PACKET(reason_code(Reason)), NChannel); shutdown(Reason, Reply, ?DISCONNECT_PACKET(reason_code(Reason)), NChannel);
%% mqtt v3/v4 connected sessions %% mqtt v3/v4 connected sessions
disconnect_and_shutdown(Reason, Reply, Channel = #channel{conn_state = ConnState}) when disconnect_and_shutdown(Reason, Reply, Channel = #channel{conn_state = ConnState}) when
?IS_CONNECTED_OR_REAUTHENTICATING(ConnState) ConnState =:= connected orelse ConnState =:= reauthenticating
-> ->
NChannel = ensure_disconnected(Reason, Channel), NChannel = ensure_disconnected(Reason, Channel),
shutdown(Reason, Reply, NChannel); shutdown(Reason, Reply, NChannel);
@ -2764,13 +2724,6 @@ is_durable_session(#channel{session = Session}) ->
false false
end. end.
proto_ver(#{proto_ver := ProtoVer}, _ConnInfo) ->
ProtoVer;
proto_ver(_Reason, #{proto_ver := ProtoVer}) ->
ProtoVer;
proto_ver(_, _) ->
?MQTT_PROTO_V4.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% For CT tests %% For CT tests
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -783,8 +783,7 @@ parse_incoming(Data, Packets, State = #state{parse_state = ParseState}) ->
input_bytes => Data, input_bytes => Data,
parsed_packets => Packets parsed_packets => Packets
}), }),
NState = enrich_state(Reason, State), {[{frame_error, Reason} | Packets], State};
{[{frame_error, Reason} | Packets], NState};
error:Reason:Stacktrace -> error:Reason:Stacktrace ->
?LOG(error, #{ ?LOG(error, #{
at_state => emqx_frame:describe_state(ParseState), at_state => emqx_frame:describe_state(ParseState),
@ -1228,12 +1227,6 @@ inc_counter(Key, Inc) ->
_ = emqx_pd:inc_counter(Key, Inc), _ = emqx_pd:inc_counter(Key, Inc),
ok. ok.
enrich_state(#{parse_state := NParseState}, State) ->
Serialize = emqx_frame:serialize_opts(NParseState),
State#state{parse_state = NParseState, serialize = Serialize};
enrich_state(_, State) ->
State.
set_tcp_keepalive({quic, _Listener}) -> set_tcp_keepalive({quic, _Listener}) ->
ok; ok;
set_tcp_keepalive({Type, Id}) -> set_tcp_keepalive({Type, Id}) ->

View File

@ -117,13 +117,6 @@ try_subscribe(ClientId, Topic) ->
write write
), ),
allow; allow;
[#exclusive_subscription{clientid = ClientId, topic = Topic}] ->
%% Fixed the issue-13476
%% In this feature, the user must manually call `unsubscribe` to release the lock,
%% but sometimes the node may go down for some reason,
%% then the client will reconnect to this node and resubscribe.
%% We need to allow resubscription, otherwise the lock will never be released.
allow;
[_] -> [_] ->
deny deny
end. end.

View File

@ -43,9 +43,7 @@
add_shared_route/2, add_shared_route/2,
delete_shared_route/2, delete_shared_route/2,
add_persistent_route/2, add_persistent_route/2,
delete_persistent_route/2, delete_persistent_route/2
add_persistent_shared_route/3,
delete_persistent_shared_route/3
]). ]).
-export_type([dest/0]). -export_type([dest/0]).
@ -131,12 +129,6 @@ add_persistent_route(Topic, ID) ->
delete_persistent_route(Topic, ID) -> delete_persistent_route(Topic, ID) ->
?safe_with_provider(?FUNCTION_NAME(Topic, ID), ok). ?safe_with_provider(?FUNCTION_NAME(Topic, ID), ok).
add_persistent_shared_route(Topic, Group, ID) ->
?safe_with_provider(?FUNCTION_NAME(Topic, Group, ID), ok).
delete_persistent_shared_route(Topic, Group, ID) ->
?safe_with_provider(?FUNCTION_NAME(Topic, Group, ID), ok).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -267,50 +267,28 @@ packet(Header, Variable) ->
packet(Header, Variable, Payload) -> packet(Header, Variable, Payload) ->
#mqtt_packet{header = Header, variable = Variable, payload = Payload}. #mqtt_packet{header = Header, variable = Variable, payload = Payload}.
parse_connect(FrameBin, Options = #{strict_mode := StrictMode}) -> parse_connect(FrameBin, StrictMode) ->
{ProtoName, Rest0} = parse_utf8_string_with_cause(FrameBin, StrictMode, invalid_proto_name), {ProtoName, Rest} = parse_utf8_string_with_cause(FrameBin, StrictMode, invalid_proto_name),
%% No need to parse and check proto_ver if proto_name is invalid, check it first case ProtoName of
%% And the matching check of `proto_name` and `proto_ver` fields will be done in `emqx_packet:check_proto_ver/2` <<"MQTT">> ->
_ = validate_proto_name(ProtoName), ok;
{IsBridge, ProtoVer, Rest2} = parse_connect_proto_ver(Rest0), <<"MQIsdp">> ->
NOptions = Options#{version => ProtoVer}, ok;
try _ ->
do_parse_connect(ProtoName, IsBridge, ProtoVer, Rest2, StrictMode) %% from spec: the server MAY send disconnect with reason code 0x84
catch %% we chose to close socket because the client is likely not talking MQTT anyway
throw:{?FRAME_PARSE_ERROR, ReasonM} when is_map(ReasonM) -> ?PARSE_ERR(#{
?PARSE_ERR( cause => invalid_proto_name,
ReasonM#{ expected => <<"'MQTT' or 'MQIsdp'">>,
proto_ver => ProtoVer, received => ProtoName
proto_name => ProtoName, })
parse_state => ?NONE(NOptions) end,
} parse_connect2(ProtoName, Rest, StrictMode).
);
throw:{?FRAME_PARSE_ERROR, Reason} ->
?PARSE_ERR(
#{
cause => Reason,
proto_ver => ProtoVer,
proto_name => ProtoName,
parse_state => ?NONE(NOptions)
}
)
end.
do_parse_connect( parse_connect2(
ProtoName, ProtoName,
IsBridge, <<BridgeTag:4, ProtoVer:4, UsernameFlagB:1, PasswordFlagB:1, WillRetainB:1, WillQoS:2,
ProtoVer, WillFlagB:1, CleanStart:1, Reserved:1, KeepAlive:16/big, Rest2/binary>>,
<<
UsernameFlagB:1,
PasswordFlagB:1,
WillRetainB:1,
WillQoS:2,
WillFlagB:1,
CleanStart:1,
Reserved:1,
KeepAlive:16/big,
Rest/binary
>>,
StrictMode StrictMode
) -> ) ->
_ = validate_connect_reserved(Reserved), _ = validate_connect_reserved(Reserved),
@ -325,14 +303,14 @@ do_parse_connect(
UsernameFlag = bool(UsernameFlagB), UsernameFlag = bool(UsernameFlagB),
PasswordFlag = bool(PasswordFlagB) PasswordFlag = bool(PasswordFlagB)
), ),
{Properties, Rest3} = parse_properties(Rest, ProtoVer, StrictMode), {Properties, Rest3} = parse_properties(Rest2, ProtoVer, StrictMode),
{ClientId, Rest4} = parse_utf8_string_with_cause(Rest3, StrictMode, invalid_clientid), {ClientId, Rest4} = parse_utf8_string_with_cause(Rest3, StrictMode, invalid_clientid),
ConnPacket = #mqtt_packet_connect{ ConnPacket = #mqtt_packet_connect{
proto_name = ProtoName, proto_name = ProtoName,
proto_ver = ProtoVer, proto_ver = ProtoVer,
%% For bridge mode, non-standard implementation %% For bridge mode, non-standard implementation
%% Invented by mosquitto, named 'try_private': https://mosquitto.org/man/mosquitto-conf-5.html %% Invented by mosquitto, named 'try_private': https://mosquitto.org/man/mosquitto-conf-5.html
is_bridge = IsBridge, is_bridge = (BridgeTag =:= 8),
clean_start = bool(CleanStart), clean_start = bool(CleanStart),
will_flag = WillFlag, will_flag = WillFlag,
will_qos = WillQoS, will_qos = WillQoS,
@ -365,16 +343,16 @@ do_parse_connect(
unexpected_trailing_bytes => size(Rest7) unexpected_trailing_bytes => size(Rest7)
}) })
end; end;
do_parse_connect(_ProtoName, _IsBridge, _ProtoVer, Bin, _StrictMode) -> parse_connect2(_ProtoName, Bin, _StrictMode) ->
%% sent less than 24 bytes %% sent less than 32 bytes
?PARSE_ERR(#{cause => malformed_connect, header_bytes => Bin}). ?PARSE_ERR(#{cause => malformed_connect, header_bytes => Bin}).
parse_packet( parse_packet(
#mqtt_packet_header{type = ?CONNECT}, #mqtt_packet_header{type = ?CONNECT},
FrameBin, FrameBin,
Options #{strict_mode := StrictMode}
) -> ) ->
parse_connect(FrameBin, Options); parse_connect(FrameBin, StrictMode);
parse_packet( parse_packet(
#mqtt_packet_header{type = ?CONNACK}, #mqtt_packet_header{type = ?CONNACK},
<<AckFlags:8, ReasonCode:8, Rest/binary>>, <<AckFlags:8, ReasonCode:8, Rest/binary>>,
@ -538,12 +516,6 @@ parse_packet_id(<<PacketId:16/big, Rest/binary>>) ->
parse_packet_id(_) -> parse_packet_id(_) ->
?PARSE_ERR(invalid_packet_id). ?PARSE_ERR(invalid_packet_id).
parse_connect_proto_ver(<<BridgeTag:4, ProtoVer:4, Rest/binary>>) ->
{_IsBridge = (BridgeTag =:= 8), ProtoVer, Rest};
parse_connect_proto_ver(Bin) ->
%% sent less than 1 bytes or empty
?PARSE_ERR(#{cause => malformed_connect, header_bytes => Bin}).
parse_properties(Bin, Ver, _StrictMode) when Ver =/= ?MQTT_PROTO_V5 -> parse_properties(Bin, Ver, _StrictMode) when Ver =/= ?MQTT_PROTO_V5 ->
{#{}, Bin}; {#{}, Bin};
%% TODO: version mess? %% TODO: version mess?
@ -767,8 +739,6 @@ serialize_fun(#{version := Ver, max_size := MaxSize, strict_mode := StrictMode})
initial_serialize_opts(Opts) -> initial_serialize_opts(Opts) ->
maps:merge(?DEFAULT_OPTIONS, Opts). maps:merge(?DEFAULT_OPTIONS, Opts).
serialize_opts(?NONE(Options)) ->
maps:merge(?DEFAULT_OPTIONS, Options);
serialize_opts(#mqtt_packet_connect{proto_ver = ProtoVer, properties = ConnProps}) -> serialize_opts(#mqtt_packet_connect{proto_ver = ProtoVer, properties = ConnProps}) ->
MaxSize = get_property('Maximum-Packet-Size', ConnProps, ?MAX_PACKET_SIZE), MaxSize = get_property('Maximum-Packet-Size', ConnProps, ?MAX_PACKET_SIZE),
#{version => ProtoVer, max_size => MaxSize, strict_mode => false}. #{version => ProtoVer, max_size => MaxSize, strict_mode => false}.
@ -1187,34 +1157,18 @@ validate_subqos([3 | _]) -> ?PARSE_ERR(bad_subqos);
validate_subqos([_ | T]) -> validate_subqos(T); validate_subqos([_ | T]) -> validate_subqos(T);
validate_subqos([]) -> ok. validate_subqos([]) -> ok.
%% from spec: the server MAY send disconnect with reason code 0x84
%% we chose to close socket because the client is likely not talking MQTT anyway
validate_proto_name(<<"MQTT">>) ->
ok;
validate_proto_name(<<"MQIsdp">>) ->
ok;
validate_proto_name(ProtoName) ->
?PARSE_ERR(#{
cause => invalid_proto_name,
expected => <<"'MQTT' or 'MQIsdp'">>,
received => ProtoName
}).
%% MQTT-v3.1.1-[MQTT-3.1.2-3], MQTT-v5.0-[MQTT-3.1.2-3] %% MQTT-v3.1.1-[MQTT-3.1.2-3], MQTT-v5.0-[MQTT-3.1.2-3]
-compile({inline, [validate_connect_reserved/1]}).
validate_connect_reserved(0) -> ok; validate_connect_reserved(0) -> ok;
validate_connect_reserved(1) -> ?PARSE_ERR(reserved_connect_flag). validate_connect_reserved(1) -> ?PARSE_ERR(reserved_connect_flag).
-compile({inline, [validate_connect_will/3]}).
%% MQTT-v3.1.1-[MQTT-3.1.2-13], MQTT-v5.0-[MQTT-3.1.2-11] %% MQTT-v3.1.1-[MQTT-3.1.2-13], MQTT-v5.0-[MQTT-3.1.2-11]
validate_connect_will(false, _, WillQoS) when WillQoS > 0 -> ?PARSE_ERR(invalid_will_qos); validate_connect_will(false, _, WillQos) when WillQos > 0 -> ?PARSE_ERR(invalid_will_qos);
%% MQTT-v3.1.1-[MQTT-3.1.2-14], MQTT-v5.0-[MQTT-3.1.2-12] %% MQTT-v3.1.1-[MQTT-3.1.2-14], MQTT-v5.0-[MQTT-3.1.2-12]
validate_connect_will(true, _, WillQoS) when WillQoS > 2 -> ?PARSE_ERR(invalid_will_qos); validate_connect_will(true, _, WillQoS) when WillQoS > 2 -> ?PARSE_ERR(invalid_will_qos);
%% MQTT-v3.1.1-[MQTT-3.1.2-15], MQTT-v5.0-[MQTT-3.1.2-13] %% MQTT-v3.1.1-[MQTT-3.1.2-15], MQTT-v5.0-[MQTT-3.1.2-13]
validate_connect_will(false, WillRetain, _) when WillRetain -> ?PARSE_ERR(invalid_will_retain); validate_connect_will(false, WillRetain, _) when WillRetain -> ?PARSE_ERR(invalid_will_retain);
validate_connect_will(_, _, _) -> ok. validate_connect_will(_, _, _) -> ok.
-compile({inline, [validate_connect_password_flag/4]}).
%% MQTT-v3.1 %% MQTT-v3.1
%% Username flag and password flag are not strongly related %% Username flag and password flag are not strongly related
%% https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#connect %% https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/mqtt-v3r1.html#connect
@ -1229,7 +1183,6 @@ validate_connect_password_flag(true, ?MQTT_PROTO_V5, _, _) ->
validate_connect_password_flag(_, _, _, _) -> validate_connect_password_flag(_, _, _, _) ->
ok. ok.
-compile({inline, [bool/1]}).
bool(0) -> false; bool(0) -> false;
bool(1) -> true. bool(1) -> true.

View File

@ -432,7 +432,7 @@ do_start_listener(Type, Name, Id, #{bind := ListenOn} = Opts) when ?ESOCKD_LISTE
esockd:open( esockd:open(
Id, Id,
ListenOn, ListenOn,
merge_default(esockd_opts(Id, Type, Name, Opts, _OldOpts = undefined)) merge_default(esockd_opts(Id, Type, Name, Opts))
); );
%% Start MQTT/WS listener %% Start MQTT/WS listener
do_start_listener(Type, Name, Id, Opts) when ?COWBOY_LISTENER(Type) -> do_start_listener(Type, Name, Id, Opts) when ?COWBOY_LISTENER(Type) ->
@ -476,7 +476,7 @@ do_update_listener(Type, Name, OldConf, NewConf = #{bind := ListenOn}) when
Id = listener_id(Type, Name), Id = listener_id(Type, Name),
case maps:get(bind, OldConf) of case maps:get(bind, OldConf) of
ListenOn -> ListenOn ->
esockd:set_options({Id, ListenOn}, esockd_opts(Id, Type, Name, NewConf, OldConf)); esockd:set_options({Id, ListenOn}, esockd_opts(Id, Type, Name, NewConf));
_Different -> _Different ->
%% TODO %% TODO
%% Again, we're not strictly required to drop live connections in this case. %% Again, we're not strictly required to drop live connections in this case.
@ -588,7 +588,7 @@ perform_listener_change(update, {{Type, Name, ConfOld}, {_, _, ConfNew}}) ->
perform_listener_change(stop, {Type, Name, Conf}) -> perform_listener_change(stop, {Type, Name, Conf}) ->
stop_listener(Type, Name, Conf). stop_listener(Type, Name, Conf).
esockd_opts(ListenerId, Type, Name, Opts0, OldOpts) -> esockd_opts(ListenerId, Type, Name, Opts0) ->
Opts1 = maps:with([acceptors, max_connections, proxy_protocol, proxy_protocol_timeout], Opts0), Opts1 = maps:with([acceptors, max_connections, proxy_protocol, proxy_protocol_timeout], Opts0),
Limiter = limiter(Opts0), Limiter = limiter(Opts0),
Opts2 = Opts2 =
@ -620,7 +620,7 @@ esockd_opts(ListenerId, Type, Name, Opts0, OldOpts) ->
tcp -> tcp ->
Opts3#{tcp_options => tcp_opts(Opts0)}; Opts3#{tcp_options => tcp_opts(Opts0)};
ssl -> ssl ->
OptsWithCRL = inject_crl_config(Opts0, OldOpts), OptsWithCRL = inject_crl_config(Opts0),
OptsWithSNI = inject_sni_fun(ListenerId, OptsWithCRL), OptsWithSNI = inject_sni_fun(ListenerId, OptsWithCRL),
OptsWithRootFun = inject_root_fun(OptsWithSNI), OptsWithRootFun = inject_root_fun(OptsWithSNI),
OptsWithVerifyFun = inject_verify_fun(OptsWithRootFun), OptsWithVerifyFun = inject_verify_fun(OptsWithRootFun),
@ -996,7 +996,7 @@ inject_sni_fun(_ListenerId, Conf) ->
Conf. Conf.
inject_crl_config( inject_crl_config(
Conf = #{ssl_options := #{enable_crl_check := true} = SSLOpts}, _OldOpts Conf = #{ssl_options := #{enable_crl_check := true} = SSLOpts}
) -> ) ->
HTTPTimeout = emqx_config:get([crl_cache, http_timeout], timer:seconds(15)), HTTPTimeout = emqx_config:get([crl_cache, http_timeout], timer:seconds(15)),
Conf#{ Conf#{
@ -1006,16 +1006,7 @@ inject_crl_config(
crl_cache => {emqx_ssl_crl_cache, {internal, [{http, HTTPTimeout}]}} crl_cache => {emqx_ssl_crl_cache, {internal, [{http, HTTPTimeout}]}}
} }
}; };
inject_crl_config(#{ssl_options := SSLOpts0} = Conf0, #{} = OldOpts) -> inject_crl_config(Conf) ->
%% Note: we must set crl options to `undefined' to unset them. Otherwise,
%% `esockd' will retain such options when `esockd:merge_opts/2' is called and the SSL
%% options were previously enabled.
WasEnabled = emqx_utils_maps:deep_get([ssl_options, enable_crl_check], OldOpts, false),
Undefine = fun(Acc, K) -> emqx_utils_maps:put_if(Acc, K, undefined, WasEnabled) end,
SSLOpts1 = Undefine(SSLOpts0, crl_check),
SSLOpts = Undefine(SSLOpts1, crl_cache),
Conf0#{ssl_options := SSLOpts};
inject_crl_config(Conf, undefined = _OldOpts) ->
Conf. Conf.
maybe_unregister_ocsp_stapling_refresh( maybe_unregister_ocsp_stapling_refresh(

View File

@ -25,7 +25,7 @@
-export([start_link/0]). -export([start_link/0]).
%% throttler API %% throttler API
-export([allow/2]). -export([allow/1]).
%% gen_server callbacks %% gen_server callbacks
-export([ -export([
@ -40,29 +40,23 @@
-define(SEQ_ID(Msg), {?MODULE, Msg}). -define(SEQ_ID(Msg), {?MODULE, Msg}).
-define(NEW_SEQ, atomics:new(1, [{signed, false}])). -define(NEW_SEQ, atomics:new(1, [{signed, false}])).
-define(GET_SEQ(Msg), persistent_term:get(?SEQ_ID(Msg), undefined)). -define(GET_SEQ(Msg), persistent_term:get(?SEQ_ID(Msg), undefined)).
-define(ERASE_SEQ(Msg), persistent_term:erase(?SEQ_ID(Msg))).
-define(RESET_SEQ(SeqRef), atomics:put(SeqRef, 1, 0)). -define(RESET_SEQ(SeqRef), atomics:put(SeqRef, 1, 0)).
-define(INC_SEQ(SeqRef), atomics:add(SeqRef, 1, 1)). -define(INC_SEQ(SeqRef), atomics:add(SeqRef, 1, 1)).
-define(GET_DROPPED(SeqRef), atomics:get(SeqRef, 1) - 1). -define(GET_DROPPED(SeqRef), atomics:get(SeqRef, 1) - 1).
-define(IS_ALLOWED(SeqRef), atomics:add_get(SeqRef, 1, 1) =:= 1). -define(IS_ALLOWED(SeqRef), atomics:add_get(SeqRef, 1, 1) =:= 1).
-define(NEW_THROTTLE(Msg, SeqRef), persistent_term:put(?SEQ_ID(Msg), SeqRef)).
-define(MSGS_LIST, emqx:get_config([log, throttling, msgs], [])). -define(MSGS_LIST, emqx:get_config([log, throttling, msgs], [])).
-define(TIME_WINDOW_MS, timer:seconds(emqx:get_config([log, throttling, time_window], 60))). -define(TIME_WINDOW_MS, timer:seconds(emqx:get_config([log, throttling, time_window], 60))).
%% @doc Check if a throttled log message is allowed to pass down to the logger this time. -spec allow(atom()) -> boolean().
%% The Msg has to be an atom, and the second argument `UniqueKey' should be `undefined' allow(Msg) when is_atom(Msg) ->
%% for predefined message IDs.
%% For relatively static resources created from configurations such as data integration
%% resource IDs `UniqueKey' should be of `binary()' type.
-spec allow(atom(), undefined | binary()) -> boolean().
allow(Msg, UniqueKey) when
is_atom(Msg) andalso (is_binary(UniqueKey) orelse UniqueKey =:= undefined)
->
case emqx_logger:get_primary_log_level() of case emqx_logger:get_primary_log_level() of
debug -> debug ->
true; true;
_ -> _ ->
do_allow(Msg, UniqueKey) do_allow(Msg)
end. end.
-spec start_link() -> startlink_ret(). -spec start_link() -> startlink_ret().
@ -74,8 +68,7 @@ start_link() ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([]) ->
process_flag(trap_exit, true), ok = lists:foreach(fun(Msg) -> ?NEW_THROTTLE(Msg, ?NEW_SEQ) end, ?MSGS_LIST),
ok = lists:foreach(fun new_throttler/1, ?MSGS_LIST),
CurrentPeriodMs = ?TIME_WINDOW_MS, CurrentPeriodMs = ?TIME_WINDOW_MS,
TimerRef = schedule_refresh(CurrentPeriodMs), TimerRef = schedule_refresh(CurrentPeriodMs),
{ok, #{timer_ref => TimerRef, current_period_ms => CurrentPeriodMs}}. {ok, #{timer_ref => TimerRef, current_period_ms => CurrentPeriodMs}}.
@ -93,22 +86,16 @@ handle_info(refresh, #{current_period_ms := PeriodMs} = State) ->
DroppedStats = lists:foldl( DroppedStats = lists:foldl(
fun(Msg, Acc) -> fun(Msg, Acc) ->
case ?GET_SEQ(Msg) of case ?GET_SEQ(Msg) of
%% Should not happen, unless the static ids list is updated at run-time.
undefined -> undefined ->
%% Should not happen, unless the static ids list is updated at run-time. ?NEW_THROTTLE(Msg, ?NEW_SEQ),
new_throttler(Msg),
?tp(log_throttler_new_msg, #{throttled_msg => Msg}), ?tp(log_throttler_new_msg, #{throttled_msg => Msg}),
Acc; Acc;
SeqMap when is_map(SeqMap) ->
maps:fold(
fun(Key, Ref, Acc0) ->
ID = iolist_to_binary([atom_to_binary(Msg), $:, Key]),
drop_stats(Ref, ID, Acc0)
end,
Acc,
SeqMap
);
SeqRef -> SeqRef ->
drop_stats(SeqRef, Msg, Acc) Dropped = ?GET_DROPPED(SeqRef),
ok = ?RESET_SEQ(SeqRef),
?tp(log_throttler_dropped, #{dropped_count => Dropped, throttled_msg => Msg}),
maybe_add_dropped(Msg, Dropped, Acc)
end end
end, end,
#{}, #{},
@ -125,16 +112,7 @@ handle_info(Info, State) ->
?SLOG(error, #{msg => "unxpected_info", info => Info}), ?SLOG(error, #{msg => "unxpected_info", info => Info}),
{noreply, State}. {noreply, State}.
drop_stats(SeqRef, Msg, Acc) ->
Dropped = ?GET_DROPPED(SeqRef),
ok = ?RESET_SEQ(SeqRef),
?tp(log_throttler_dropped, #{dropped_count => Dropped, throttled_msg => Msg}),
maybe_add_dropped(Msg, Dropped, Acc).
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
%% atomics do not have delete/remove/release/deallocate API
%% after the reference is garbage-collected the resource is released
lists:foreach(fun(Msg) -> ?ERASE_SEQ(Msg) end, ?MSGS_LIST),
ok. ok.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
@ -144,27 +122,17 @@ code_change(_OldVsn, State, _Extra) ->
%% internal functions %% internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
do_allow(Msg, UniqueKey) -> do_allow(Msg) ->
case persistent_term:get(?SEQ_ID(Msg), undefined) of case persistent_term:get(?SEQ_ID(Msg), undefined) of
undefined -> undefined ->
%% This is either a race condition (emqx_log_throttler is not started yet) %% This is either a race condition (emqx_log_throttler is not started yet)
%% or a developer mistake (msg used in ?SLOG_THROTTLE/2,3 macro is %% or a developer mistake (msg used in ?SLOG_THROTTLE/2,3 macro is
%% not added to the default value of `log.throttling.msgs`. %% not added to the default value of `log.throttling.msgs`.
?SLOG(debug, #{ ?SLOG(info, #{
msg => "log_throttle_disabled", msg => "missing_log_throttle_sequence",
throttled_msg => Msg throttled_msg => Msg
}), }),
true; true;
%% e.g: unrecoverable msg throttle according resource_id
SeqMap when is_map(SeqMap) ->
case maps:find(UniqueKey, SeqMap) of
{ok, SeqRef} ->
?IS_ALLOWED(SeqRef);
error ->
SeqRef = ?NEW_SEQ,
new_throttler(Msg, SeqMap#{UniqueKey => SeqRef}),
true
end;
SeqRef -> SeqRef ->
?IS_ALLOWED(SeqRef) ?IS_ALLOWED(SeqRef)
end. end.
@ -186,11 +154,3 @@ maybe_log_dropped(_DroppedStats, _PeriodMs) ->
schedule_refresh(PeriodMs) -> schedule_refresh(PeriodMs) ->
?tp(log_throttler_sched_refresh, #{new_period_ms => PeriodMs}), ?tp(log_throttler_sched_refresh, #{new_period_ms => PeriodMs}),
erlang:send_after(PeriodMs, ?MODULE, refresh). erlang:send_after(PeriodMs, ?MODULE, refresh).
new_throttler(unrecoverable_resource_error = Msg) ->
new_throttler(Msg, #{});
new_throttler(Msg) ->
new_throttler(Msg, ?NEW_SEQ).
new_throttler(Msg, AtomicOrEmptyMap) ->
persistent_term:put(?SEQ_ID(Msg), AtomicOrEmptyMap).

View File

@ -105,7 +105,7 @@ format(Msg, Meta, Config) ->
maybe_format_msg(undefined, _Meta, _Config) -> maybe_format_msg(undefined, _Meta, _Config) ->
#{}; #{};
maybe_format_msg({report, Report0} = Msg, #{report_cb := Cb} = Meta, Config) -> maybe_format_msg({report, Report0} = Msg, #{report_cb := Cb} = Meta, Config) ->
Report = emqx_logger_textfmt:try_encode_meta(Report0, Config), Report = emqx_logger_textfmt:try_encode_payload(Report0, Config),
case is_map(Report) andalso Cb =:= ?DEFAULT_FORMATTER of case is_map(Report) andalso Cb =:= ?DEFAULT_FORMATTER of
true -> true ->
%% reporting a map without a customised format function %% reporting a map without a customised format function

View File

@ -20,7 +20,7 @@
-export([format/2]). -export([format/2]).
-export([check_config/1]). -export([check_config/1]).
-export([try_format_unicode/1, try_encode_meta/2]). -export([try_format_unicode/1, try_encode_payload/2]).
%% Used in the other log formatters %% Used in the other log formatters
-export([evaluate_lazy_values_if_dbg_level/1, evaluate_lazy_values/1]). -export([evaluate_lazy_values_if_dbg_level/1, evaluate_lazy_values/1]).
@ -111,7 +111,7 @@ is_list_report_acceptable(_) ->
enrich_report(ReportRaw0, Meta, Config) -> enrich_report(ReportRaw0, Meta, Config) ->
%% clientid and peername always in emqx_conn's process metadata. %% clientid and peername always in emqx_conn's process metadata.
%% topic and username can be put in meta using ?SLOG/3, or put in msg's report by ?SLOG/2 %% topic and username can be put in meta using ?SLOG/3, or put in msg's report by ?SLOG/2
ReportRaw = try_encode_meta(ReportRaw0, Config), ReportRaw = try_encode_payload(ReportRaw0, Config),
Topic = Topic =
case maps:get(topic, Meta, undefined) of case maps:get(topic, Meta, undefined) of
undefined -> maps:get(topic, ReportRaw, undefined); undefined -> maps:get(topic, ReportRaw, undefined);
@ -180,22 +180,9 @@ enrich_topic({Fmt, Args}, #{topic := Topic}) when is_list(Fmt) ->
enrich_topic(Msg, _) -> enrich_topic(Msg, _) ->
Msg. Msg.
try_encode_meta(Report, Config) -> try_encode_payload(#{payload := Payload} = Report, #{payload_encode := Encode}) ->
lists:foldl(
fun(Meta, Acc) ->
try_encode_meta(Meta, Acc, Config)
end,
Report,
[payload, packet]
).
try_encode_meta(payload, #{payload := Payload} = Report, #{payload_encode := Encode}) ->
Report#{payload := encode_payload(Payload, Encode)}; Report#{payload := encode_payload(Payload, Encode)};
try_encode_meta(packet, #{packet := Packet} = Report, #{payload_encode := Encode}) when try_encode_payload(Report, _Config) ->
is_tuple(Packet)
->
Report#{packet := emqx_packet:format(Packet, Encode)};
try_encode_meta(_, Report, _Config) ->
Report. Report.
encode_payload(Payload, text) -> encode_payload(Payload, text) ->
@ -203,5 +190,4 @@ encode_payload(Payload, text) ->
encode_payload(_Payload, hidden) -> encode_payload(_Payload, hidden) ->
"******"; "******";
encode_payload(Payload, hex) -> encode_payload(Payload, hex) ->
Bin = emqx_utils_conv:bin(Payload), binary:encode_hex(Payload).
binary:encode_hex(Bin).

View File

@ -51,6 +51,7 @@
]). ]).
-export([ -export([
format/1,
format/2 format/2
]). ]).
@ -480,6 +481,10 @@ will_msg(#mqtt_packet_connect{
headers = #{username => Username, properties => Props} headers = #{username => Username, properties => Props}
}. }.
%% @doc Format packet
-spec format(emqx_types:packet()) -> iolist().
format(Packet) -> format(Packet, emqx_trace_handler:payload_encode()).
%% @doc Format packet %% @doc Format packet
-spec format(emqx_types:packet(), hex | text | hidden) -> iolist(). -spec format(emqx_types:packet(), hex | text | hidden) -> iolist().
format(#mqtt_packet{header = Header, variable = Variable, payload = Payload}, PayloadEncode) -> format(#mqtt_packet{header = Header, variable = Variable, payload = Payload}, PayloadEncode) ->

View File

@ -621,13 +621,9 @@ handle_timeout(ClientInfo, ?TIMER_RETRY_REPLAY, Session0) ->
Session = replay_streams(Session0, ClientInfo), Session = replay_streams(Session0, ClientInfo),
{ok, [], Session}; {ok, [], Session};
handle_timeout(ClientInfo, ?TIMER_GET_STREAMS, Session0 = #{s := S0, shared_sub_s := SharedSubS0}) -> handle_timeout(ClientInfo, ?TIMER_GET_STREAMS, Session0 = #{s := S0, shared_sub_s := SharedSubS0}) ->
%% `gc` and `renew_streams` methods may drop unsubscribed streams. S1 = emqx_persistent_session_ds_subs:gc(S0),
%% Shared subscription handler must have a chance to see unsubscribed streams S2 = emqx_persistent_session_ds_stream_scheduler:renew_streams(S1),
%% in the fully replayed state. {S, SharedSubS} = emqx_persistent_session_ds_shared_subs:renew_streams(S2, SharedSubS0),
{S1, SharedSubS1} = emqx_persistent_session_ds_shared_subs:pre_renew_streams(S0, SharedSubS0),
S2 = emqx_persistent_session_ds_subs:gc(S1),
S3 = emqx_persistent_session_ds_stream_scheduler:renew_streams(S2),
{S, SharedSubS} = emqx_persistent_session_ds_shared_subs:renew_streams(S3, SharedSubS1),
Interval = get_config(ClientInfo, [renew_streams_interval]), Interval = get_config(ClientInfo, [renew_streams_interval]),
Session = emqx_session:ensure_timer( Session = emqx_session:ensure_timer(
?TIMER_GET_STREAMS, ?TIMER_GET_STREAMS,
@ -761,7 +757,7 @@ skip_batch(StreamKey, SRS0, Session = #{s := S0}, ClientInfo, Reason) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec disconnect(session(), emqx_types:conninfo()) -> {shutdown, session()}. -spec disconnect(session(), emqx_types:conninfo()) -> {shutdown, session()}.
disconnect(Session = #{id := Id, s := S0, shared_sub_s := SharedSubS0}, ConnInfo) -> disconnect(Session = #{id := Id, s := S0}, ConnInfo) ->
S1 = maybe_set_offline_info(S0, Id), S1 = maybe_set_offline_info(S0, Id),
S2 = emqx_persistent_session_ds_state:set_last_alive_at(now_ms(), S1), S2 = emqx_persistent_session_ds_state:set_last_alive_at(now_ms(), S1),
S3 = S3 =
@ -771,9 +767,8 @@ disconnect(Session = #{id := Id, s := S0, shared_sub_s := SharedSubS0}, ConnInfo
_ -> _ ->
S2 S2
end, end,
{S4, SharedSubS} = emqx_persistent_session_ds_shared_subs:on_disconnect(S3, SharedSubS0), S = emqx_persistent_session_ds_state:commit(S3),
S = emqx_persistent_session_ds_state:commit(S4), {shutdown, Session#{s => S}}.
{shutdown, Session#{s => S, shared_sub_s => SharedSubS}}.
-spec terminate(Reason :: term(), session()) -> ok. -spec terminate(Reason :: term(), session()) -> ok.
terminate(_Reason, Session = #{id := Id, s := S}) -> terminate(_Reason, Session = #{id := Id, s := S}) ->
@ -821,12 +816,10 @@ list_client_subscriptions(ClientId) ->
{error, not_found} {error, not_found}
end. end.
-spec get_client_subscription(emqx_types:clientid(), topic_filter() | share_topic_filter()) -> -spec get_client_subscription(emqx_types:clientid(), emqx_types:topic()) ->
subscription() | undefined. subscription() | undefined.
get_client_subscription(ClientId, #share{} = ShareTopicFilter) -> get_client_subscription(ClientId, Topic) ->
emqx_persistent_session_ds_shared_subs:cold_get_subscription(ClientId, ShareTopicFilter); emqx_persistent_session_ds_subs:cold_get_subscription(ClientId, Topic).
get_client_subscription(ClientId, TopicFilter) ->
emqx_persistent_session_ds_subs:cold_get_subscription(ClientId, TopicFilter).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Session tables operations %% Session tables operations
@ -993,14 +986,14 @@ do_ensure_all_iterators_closed(_DSSessionID) ->
%% Normal replay: %% Normal replay:
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
fetch_new_messages(Session0 = #{s := S0, shared_sub_s := SharedSubS0}, ClientInfo) -> fetch_new_messages(Session0 = #{s := S0}, ClientInfo) ->
{S1, SharedSubS1} = emqx_persistent_session_ds_shared_subs:on_streams_replay(S0, SharedSubS0), LFS = maps:get(last_fetched_stream, Session0, beginning),
Session1 = Session0#{s => S1, shared_sub_s => SharedSubS1}, ItStream = emqx_persistent_session_ds_stream_scheduler:iter_next_streams(LFS, S0),
LFS = maps:get(last_fetched_stream, Session1, beginning),
ItStream = emqx_persistent_session_ds_stream_scheduler:iter_next_streams(LFS, S1),
BatchSize = get_config(ClientInfo, [batch_size]), BatchSize = get_config(ClientInfo, [batch_size]),
Session2 = fetch_new_messages(ItStream, BatchSize, Session1, ClientInfo), Session1 = fetch_new_messages(ItStream, BatchSize, Session0, ClientInfo),
Session2#{shared_sub_s => SharedSubS1}. #{s := S1, shared_sub_s := SharedSubS0} = Session1,
{S2, SharedSubS1} = emqx_persistent_session_ds_shared_subs:on_streams_replayed(S1, SharedSubS0),
Session1#{s => S2, shared_sub_s => SharedSubS1}.
fetch_new_messages(ItStream0, BatchSize, Session0, ClientInfo) -> fetch_new_messages(ItStream0, BatchSize, Session0, ClientInfo) ->
#{inflight := Inflight} = Session0, #{inflight := Inflight} = Session0,

View File

@ -17,7 +17,7 @@
-module(emqx_persistent_session_ds_router). -module(emqx_persistent_session_ds_router).
-include("emqx.hrl"). -include("emqx.hrl").
-include("emqx_ps_ds_int.hrl"). -include("emqx_persistent_session_ds/emqx_ps_ds_int.hrl").
-export([init_tables/0]). -export([init_tables/0]).
@ -47,7 +47,7 @@
-endif. -endif.
-type route() :: #ps_route{}. -type route() :: #ps_route{}.
-type dest() :: emqx_persistent_session_ds:id() | #share_dest{}. -type dest() :: emqx_persistent_session_ds:id().
-export_type([dest/0, route/0]). -export_type([dest/0, route/0]).
@ -161,7 +161,7 @@ topics() ->
print_routes(Topic) -> print_routes(Topic) ->
lists:foreach( lists:foreach(
fun(#ps_route{topic = To, dest = Dest}) -> fun(#ps_route{topic = To, dest = Dest}) ->
io:format("~ts -> ~tp~n", [To, Dest]) io:format("~ts -> ~ts~n", [To, Dest])
end, end,
match_routes(Topic) match_routes(Topic)
). ).
@ -247,8 +247,6 @@ mk_filtertab_fold_fun(FoldFun) ->
match_filters(Topic) -> match_filters(Topic) ->
emqx_topic_index:matches(Topic, ?PS_FILTERS_TAB, []). emqx_topic_index:matches(Topic, ?PS_FILTERS_TAB, []).
get_dest_session_id(#share_dest{session_id = DSSessionId}) ->
DSSessionId;
get_dest_session_id({_, DSSessionId}) -> get_dest_session_id({_, DSSessionId}) ->
DSSessionId; DSSessionId;
get_dest_session_id(DSSessionId) -> get_dest_session_id(DSSessionId) ->

View File

@ -2,37 +2,11 @@
%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved. %% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc This module
%% * handles creation and management of _shared_ subscriptions for the session;
%% * provides streams to the session;
%% * handles progress of stream replay.
%%
%% The logic is quite straightforward; most of the parts resemble the logic of the
%% `emqx_persistent_session_ds_subs` (subscribe/unsubscribe) and
%% `emqx_persistent_session_ds_scheduler` (providing new streams),
%% but some data is sent or received from the `emqx_persistent_session_ds_shared_subs_agent`
%% which communicates with remote shared subscription leaders.
%%
%% A tricky part is the concept of "scheduled actions". When we unsubscribe from a topic
%% we may have some streams that have unacked messages. So we do not have a reliable
%% progress for them. Sending the current progress to the leader and disconnecting
%% will lead to the duplication of messages. So after unsubscription, we need to wait
%% some time until all streams are acked, and only then we disconnect from the leader.
%%
%% For this purpose we have the `scheduled_actions` map in the state of the module.
%% We preserve there the streams that we need to wait for and collect their progress.
%% We also use `scheduled_actions` for resubscriptions. If a client quickly resubscribes
%% after unsubscription, we may still have the mentioned streams unacked. If we abandon
%% them, just connect to the leader, then it may lease us the same streams again, but with
%% the previous progress. So messages may duplicate.
-module(emqx_persistent_session_ds_shared_subs). -module(emqx_persistent_session_ds_shared_subs).
-include("emqx_mqtt.hrl"). -include("emqx_mqtt.hrl").
-include("emqx.hrl").
-include("logger.hrl"). -include("logger.hrl").
-include("session_internals.hrl"). -include("session_internals.hrl").
-include_lib("snabbkaffe/include/trace.hrl"). -include_lib("snabbkaffe/include/trace.hrl").
-export([ -export([
@ -41,56 +15,16 @@
on_subscribe/3, on_subscribe/3,
on_unsubscribe/4, on_unsubscribe/4,
on_disconnect/2,
on_streams_replay/2, on_streams_replayed/2,
on_info/3, on_info/3,
pre_renew_streams/2,
renew_streams/2, renew_streams/2,
to_map/2 to_map/2
]). ]).
%% Management API:
-export([
cold_get_subscription/2
]).
-export([
format_lease_events/1,
format_stream_progresses/1
]).
-define(schedule_subscribe, schedule_subscribe).
-define(schedule_unsubscribe, schedule_unsubscribe).
-type stream_key() :: {emqx_persistent_session_ds:id(), emqx_ds:stream()}.
-type scheduled_action_type() ::
{?schedule_subscribe, emqx_types:subopts()} | ?schedule_unsubscribe.
-type agent_stream_progress() :: #{
stream := emqx_ds:stream(),
progress := progress(),
use_finished := boolean()
}.
-type progress() ::
#{
iterator := emqx_ds:iterator()
}.
-type scheduled_action() :: #{
type := scheduled_action_type(),
stream_keys_to_wait := [stream_key()],
progresses := [agent_stream_progress()]
}.
-type t() :: #{ -type t() :: #{
agent := emqx_persistent_session_ds_shared_subs_agent:t(), agent := emqx_persistent_session_ds_shared_subs_agent:t()
scheduled_actions := #{
share_topic_filter() => scheduled_action()
}
}. }.
-type share_topic_filter() :: emqx_persistent_session_ds:share_topic_filter(). -type share_topic_filter() :: emqx_persistent_session_ds:share_topic_filter().
-type opts() :: #{ -type opts() :: #{
@ -100,90 +34,184 @@
-define(rank_x, rank_shared). -define(rank_x, rank_shared).
-define(rank_y, 0). -define(rank_y, 0).
-export_type([
progress/0,
agent_stream_progress/0
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%%--------------------------------------------------------------------
%% new
-spec new(opts()) -> t(). -spec new(opts()) -> t().
new(Opts) -> new(Opts) ->
#{ #{
agent => emqx_persistent_session_ds_shared_subs_agent:new( agent => emqx_persistent_session_ds_shared_subs_agent:new(
agent_opts(Opts) agent_opts(Opts)
), )
scheduled_actions => #{}
}. }.
%%--------------------------------------------------------------------
%% open
-spec open(emqx_persistent_session_ds_state:t(), opts()) -> -spec open(emqx_persistent_session_ds_state:t(), opts()) ->
{ok, emqx_persistent_session_ds_state:t(), t()}. {ok, emqx_persistent_session_ds_state:t(), t()}.
open(S0, Opts) -> open(S, Opts) ->
SharedSubscriptions = fold_shared_subs( SharedSubscriptions = fold_shared_subs(
fun(#share{} = ShareTopicFilter, Sub, Acc) -> fun(#share{} = TopicFilter, Sub, Acc) ->
[{ShareTopicFilter, to_agent_subscription(S0, Sub)} | Acc] [{TopicFilter, to_agent_subscription(S, Sub)} | Acc]
end, end,
[], [],
S0 S
), ),
Agent = emqx_persistent_session_ds_shared_subs_agent:open( Agent = emqx_persistent_session_ds_shared_subs_agent:open(
SharedSubscriptions, agent_opts(Opts) SharedSubscriptions, agent_opts(Opts)
), ),
SharedSubS = #{agent => Agent, scheduled_actions => #{}}, SharedSubS = #{agent => Agent},
S1 = revoke_all_streams(S0), {ok, S, SharedSubS}.
{ok, S1, SharedSubS}.
%%--------------------------------------------------------------------
%% on_subscribe
-spec on_subscribe( -spec on_subscribe(
share_topic_filter(), share_topic_filter(),
emqx_types:subopts(), emqx_types:subopts(),
emqx_persistent_session_ds:session() emqx_persistent_session_ds:session()
) -> {ok, emqx_persistent_session_ds_state:t(), t()} | {error, emqx_types:reason_code()}. ) -> {ok, emqx_persistent_session_ds_state:t(), t()} | {error, emqx_types:reason_code()}.
on_subscribe(#share{} = ShareTopicFilter, SubOpts, #{s := S} = Session) -> on_subscribe(TopicFilter, SubOpts, #{s := S} = Session) ->
Subscription = emqx_persistent_session_ds_state:get_subscription(ShareTopicFilter, S), Subscription = emqx_persistent_session_ds_state:get_subscription(TopicFilter, S),
on_subscribe(Subscription, ShareTopicFilter, SubOpts, Session). on_subscribe(Subscription, TopicFilter, SubOpts, Session).
-spec on_unsubscribe(
emqx_persistent_session_ds:id(),
emqx_persistent_session_ds:topic_filter(),
emqx_persistent_session_ds_state:t(),
t()
) ->
{ok, emqx_persistent_session_ds_state:t(), t(), emqx_persistent_session_ds:subscription()}
| {error, emqx_types:reason_code()}.
on_unsubscribe(SessionId, TopicFilter, S0, #{agent := Agent0} = SharedSubS0) ->
case lookup(TopicFilter, S0) of
undefined ->
{error, ?RC_NO_SUBSCRIPTION_EXISTED};
Subscription ->
?tp(persistent_session_ds_subscription_delete, #{
session_id => SessionId, topic_filter => TopicFilter
}),
Agent1 = emqx_persistent_session_ds_shared_subs_agent:on_unsubscribe(
Agent0, TopicFilter
),
SharedSubS = SharedSubS0#{agent => Agent1},
S = emqx_persistent_session_ds_state:del_subscription(TopicFilter, S0),
{ok, S, SharedSubS, Subscription}
end.
-spec renew_streams(emqx_persistent_session_ds_state:t(), t()) ->
{emqx_persistent_session_ds_state:t(), t()}.
renew_streams(S0, #{agent := Agent0} = SharedSubS0) ->
{StreamLeaseEvents, Agent1} = emqx_persistent_session_ds_shared_subs_agent:renew_streams(
Agent0
),
?tp(info, shared_subs_new_stream_lease_events, #{stream_lease_events => StreamLeaseEvents}),
S1 = lists:foldl(
fun
(#{type := lease} = Event, S) -> accept_stream(Event, S);
(#{type := revoke} = Event, S) -> revoke_stream(Event, S)
end,
S0,
StreamLeaseEvents
),
SharedSubS1 = SharedSubS0#{agent => Agent1},
{S1, SharedSubS1}.
-spec on_streams_replayed(
emqx_persistent_session_ds_state:t(),
t()
) -> {emqx_persistent_session_ds_state:t(), t()}.
on_streams_replayed(S, #{agent := Agent0} = SharedSubS0) ->
%% TODO
%% Is it sufficient for a report?
Progress = fold_shared_stream_states(
fun(TopicFilter, Stream, SRS, Acc) ->
#srs{it_begin = BeginIt} = SRS,
StreamProgress = #{
topic_filter => TopicFilter,
stream => Stream,
iterator => BeginIt
},
[StreamProgress | Acc]
end,
[],
S
),
Agent1 = emqx_persistent_session_ds_shared_subs_agent:on_stream_progress(
Agent0, Progress
),
SharedSubS1 = SharedSubS0#{agent => Agent1},
{S, SharedSubS1}.
-spec on_info(emqx_persistent_session_ds_state:t(), t(), term()) ->
{emqx_persistent_session_ds_state:t(), t()}.
on_info(S, #{agent := Agent0} = SharedSubS0, Info) ->
Agent1 = emqx_persistent_session_ds_shared_subs_agent:on_info(Agent0, Info),
SharedSubS1 = SharedSubS0#{agent => Agent1},
{S, SharedSubS1}.
-spec to_map(emqx_persistent_session_ds_state:t(), t()) -> map().
to_map(_S, _SharedSubS) ->
%% TODO
#{}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% on_subscribe internal functions %% Internal functions
%%--------------------------------------------------------------------
on_subscribe(undefined, ShareTopicFilter, SubOpts, #{props := Props, s := S} = Session) -> fold_shared_subs(Fun, Acc, S) ->
emqx_persistent_session_ds_state:fold_subscriptions(
fun
(#share{} = TopicFilter, Sub, Acc0) -> Fun(TopicFilter, Sub, Acc0);
(_, _Sub, Acc0) -> Acc0
end,
Acc,
S
).
fold_shared_stream_states(Fun, Acc, S) ->
%% TODO
%% Optimize or cache
TopicFilters = fold_shared_subs(
fun
(#share{} = TopicFilter, #{id := Id} = _Sub, Acc0) ->
Acc0#{Id => TopicFilter};
(_, _, Acc0) ->
Acc0
end,
#{},
S
),
emqx_persistent_session_ds_state:fold_streams(
fun({SubId, Stream}, SRS, Acc0) ->
case TopicFilters of
#{SubId := TopicFilter} ->
Fun(TopicFilter, Stream, SRS, Acc0);
_ ->
Acc0
end
end,
Acc,
S
).
on_subscribe(undefined, TopicFilter, SubOpts, #{props := Props, s := S} = Session) ->
#{max_subscriptions := MaxSubscriptions} = Props, #{max_subscriptions := MaxSubscriptions} = Props,
case emqx_persistent_session_ds_state:n_subscriptions(S) < MaxSubscriptions of case emqx_persistent_session_ds_state:n_subscriptions(S) < MaxSubscriptions of
true -> true ->
create_new_subscription(ShareTopicFilter, SubOpts, Session); create_new_subscription(TopicFilter, SubOpts, Session);
false -> false ->
{error, ?RC_QUOTA_EXCEEDED} {error, ?RC_QUOTA_EXCEEDED}
end; end;
on_subscribe(Subscription, ShareTopicFilter, SubOpts, Session) -> on_subscribe(Subscription, TopicFilter, SubOpts, Session) ->
update_subscription(Subscription, ShareTopicFilter, SubOpts, Session). update_subscription(Subscription, TopicFilter, SubOpts, Session).
-dialyzer({nowarn_function, create_new_subscription/3}). -dialyzer({nowarn_function, create_new_subscription/3}).
create_new_subscription(#share{topic = TopicFilter, group = Group} = ShareTopicFilter, SubOpts, #{ create_new_subscription(TopicFilter, SubOpts, #{
id := SessionId, id := SessionId, s := S0, shared_sub_s := #{agent := Agent0} = SharedSubS0, props := Props
s := S0,
shared_sub_s := #{agent := Agent} = SharedSubS0,
props := Props
}) -> }) ->
case case
emqx_persistent_session_ds_shared_subs_agent:can_subscribe( emqx_persistent_session_ds_shared_subs_agent:on_subscribe(
Agent, ShareTopicFilter, SubOpts Agent0, TopicFilter, SubOpts
) )
of of
ok -> {ok, Agent1} ->
ok = emqx_persistent_session_ds_router:do_add_route(TopicFilter, #share_dest{
session_id = SessionId, group = Group
}),
_ = emqx_external_broker:add_persistent_shared_route(TopicFilter, Group, SessionId),
#{upgrade_qos := UpgradeQoS} = Props, #{upgrade_qos := UpgradeQoS} = Props,
{SubId, S1} = emqx_persistent_session_ds_state:new_id(S0), {SubId, S1} = emqx_persistent_session_ds_state:new_id(S0),
{SStateId, S2} = emqx_persistent_session_ds_state:new_id(S1), {SStateId, S2} = emqx_persistent_session_ds_state:new_id(S1),
@ -199,20 +227,20 @@ create_new_subscription(#share{topic = TopicFilter, group = Group} = ShareTopicF
start_time => now_ms() start_time => now_ms()
}, },
S = emqx_persistent_session_ds_state:put_subscription( S = emqx_persistent_session_ds_state:put_subscription(
ShareTopicFilter, Subscription, S3 TopicFilter, Subscription, S3
), ),
SharedSubS = SharedSubS0#{agent => Agent1},
SharedSubS = schedule_subscribe(SharedSubS0, ShareTopicFilter, SubOpts), ?tp(persistent_session_ds_shared_subscription_added, #{
topic_filter => TopicFilter, session => SessionId
}),
{ok, S, SharedSubS}; {ok, S, SharedSubS};
{error, _} = Error -> {error, _} = Error ->
Error Error
end. end.
update_subscription( update_subscription(#{current_state := SStateId0, id := SubId} = Sub0, TopicFilter, SubOpts, #{
#{current_state := SStateId0, id := SubId} = Sub0, ShareTopicFilter, SubOpts, #{ s := S0, shared_sub_s := SharedSubS, props := Props
s := S0, shared_sub_s := SharedSubS, props := Props }) ->
}
) ->
#{upgrade_qos := UpgradeQoS} = Props, #{upgrade_qos := UpgradeQoS} = Props,
SState = #{parent_subscription => SubId, upgrade_qos => UpgradeQoS, subopts => SubOpts}, SState = #{parent_subscription => SubId, upgrade_qos => UpgradeQoS, subopts => SubOpts},
case emqx_persistent_session_ds_state:get_subscription_state(SStateId0, S0) of case emqx_persistent_session_ds_state:get_subscription_state(SStateId0, S0) of
@ -226,173 +254,36 @@ update_subscription(
SStateId, SState, S1 SStateId, SState, S1
), ),
Sub = Sub0#{current_state => SStateId}, Sub = Sub0#{current_state => SStateId},
S = emqx_persistent_session_ds_state:put_subscription(ShareTopicFilter, Sub, S2), S = emqx_persistent_session_ds_state:put_subscription(TopicFilter, Sub, S2),
{ok, S, SharedSubS} {ok, S, SharedSubS}
end. end.
-dialyzer({nowarn_function, schedule_subscribe/3}). lookup(TopicFilter, S) ->
schedule_subscribe( case emqx_persistent_session_ds_state:get_subscription(TopicFilter, S) of
#{agent := Agent0, scheduled_actions := ScheduledActions0} = SharedSubS0, Sub = #{current_state := SStateId} ->
ShareTopicFilter, case emqx_persistent_session_ds_state:get_subscription_state(SStateId, S) of
SubOpts #{subopts := SubOpts} ->
) -> Sub#{subopts => SubOpts};
case ScheduledActions0 of undefined ->
#{ShareTopicFilter := ScheduledAction} -> undefined
ScheduledActions1 = ScheduledActions0#{ end;
ShareTopicFilter => ScheduledAction#{type => {?schedule_subscribe, SubOpts}}
},
?tp(debug, shared_subs_schedule_subscribe_override, #{
share_topic_filter => ShareTopicFilter,
new_type => {?schedule_subscribe, SubOpts},
old_action => format_schedule_action(ScheduledAction)
}),
SharedSubS0#{scheduled_actions := ScheduledActions1};
_ ->
?tp(debug, shared_subs_schedule_subscribe_new, #{
share_topic_filter => ShareTopicFilter, subopts => SubOpts
}),
Agent1 = emqx_persistent_session_ds_shared_subs_agent:on_subscribe(
Agent0, ShareTopicFilter, SubOpts
),
SharedSubS0#{agent => Agent1}
end.
%%--------------------------------------------------------------------
%% on_unsubscribe
-spec on_unsubscribe(
emqx_persistent_session_ds:id(),
share_topic_filter(),
emqx_persistent_session_ds_state:t(),
t()
) ->
{ok, emqx_persistent_session_ds_state:t(), t(), emqx_persistent_session_ds:subscription()}
| {error, emqx_types:reason_code()}.
on_unsubscribe(
SessionId, #share{topic = TopicFilter, group = Group} = ShareTopicFilter, S0, SharedSubS0
) ->
case lookup(ShareTopicFilter, S0) of
undefined -> undefined ->
{error, ?RC_NO_SUBSCRIPTION_EXISTED}; undefined
#{id := SubId} = Subscription ->
?tp(persistent_session_ds_subscription_delete, #{
session_id => SessionId, share_topic_filter => ShareTopicFilter
}),
_ = emqx_external_broker:delete_persistent_shared_route(TopicFilter, Group, SessionId),
ok = emqx_persistent_session_ds_router:do_delete_route(TopicFilter, #share_dest{
session_id = SessionId, group = Group
}),
S = emqx_persistent_session_ds_state:del_subscription(ShareTopicFilter, S0),
SharedSubS = schedule_unsubscribe(S, SharedSubS0, SubId, ShareTopicFilter),
{ok, S, SharedSubS, Subscription}
end.
%%--------------------------------------------------------------------
%% on_unsubscribe internal functions
schedule_unsubscribe(
S, #{scheduled_actions := ScheduledActions0} = SharedSubS0, UnsubscridedSubId, ShareTopicFilter
) ->
case ScheduledActions0 of
#{ShareTopicFilter := ScheduledAction0} ->
ScheduledAction1 = ScheduledAction0#{type => ?schedule_unsubscribe},
ScheduledActions1 = ScheduledActions0#{
ShareTopicFilter => ScheduledAction1
},
?tp(debug, shared_subs_schedule_unsubscribe_override, #{
share_topic_filter => ShareTopicFilter,
new_type => ?schedule_unsubscribe,
old_action => format_schedule_action(ScheduledAction0)
}),
SharedSubS0#{scheduled_actions := ScheduledActions1};
_ ->
StreamKeys = stream_keys_by_sub_id(S, UnsubscridedSubId),
ScheduledActions1 = ScheduledActions0#{
ShareTopicFilter => #{
type => ?schedule_unsubscribe,
stream_keys_to_wait => StreamKeys,
progresses => []
}
},
?tp(debug, shared_subs_schedule_unsubscribe_new, #{
share_topic_filter => ShareTopicFilter,
stream_keys => format_stream_keys(StreamKeys)
}),
SharedSubS0#{scheduled_actions := ScheduledActions1}
end.
%%--------------------------------------------------------------------
%% pre_renew_streams
-spec pre_renew_streams(emqx_persistent_session_ds_state:t(), t()) ->
{emqx_persistent_session_ds_state:t(), t()}.
pre_renew_streams(S, SharedSubS) ->
on_streams_replay(S, SharedSubS).
%%--------------------------------------------------------------------
%% renew_streams
-spec renew_streams(emqx_persistent_session_ds_state:t(), t()) ->
{emqx_persistent_session_ds_state:t(), t()}.
renew_streams(S0, #{agent := Agent0, scheduled_actions := ScheduledActions} = SharedSubS0) ->
{StreamLeaseEvents, Agent1} = emqx_persistent_session_ds_shared_subs_agent:renew_streams(
Agent0
),
StreamLeaseEvents =/= [] andalso
?tp(debug, shared_subs_new_stream_lease_events, #{
stream_lease_events => format_lease_events(StreamLeaseEvents)
}),
S1 = lists:foldl(
fun
(#{type := lease} = Event, S) -> accept_stream(Event, S, ScheduledActions);
(#{type := revoke} = Event, S) -> revoke_stream(Event, S)
end,
S0,
StreamLeaseEvents
),
SharedSubS1 = SharedSubS0#{agent => Agent1},
{S1, SharedSubS1}.
%%--------------------------------------------------------------------
%% renew_streams internal functions
accept_stream(#{share_topic_filter := ShareTopicFilter} = Event, S, ScheduledActions) ->
%% If we have a pending action (subscribe or unsubscribe) for this topic filter,
%% we should not accept a stream and start replaying it. We won't use it anyway:
%% * if subscribe is pending, we will reset agent obtain a new lease
%% * if unsubscribe is pending, we will drop connection
case ScheduledActions of
#{ShareTopicFilter := _Action} ->
S;
_ ->
accept_stream(Event, S)
end. end.
accept_stream( accept_stream(
#{ #{topic_filter := TopicFilter, stream := Stream, iterator := Iterator}, S0
share_topic_filter := ShareTopicFilter,
stream := Stream,
progress := #{iterator := Iterator} = _Progress
} = _Event,
S0
) -> ) ->
case emqx_persistent_session_ds_state:get_subscription(ShareTopicFilter, S0) of case emqx_persistent_session_ds_state:get_subscription(TopicFilter, S0) of
undefined -> undefined ->
%% We unsubscribed %% This should not happen.
S0; %% Agent should have received unsubscribe callback
%% and should not have passed this stream as a new one
error(new_stream_without_sub);
#{id := SubId, current_state := SStateId} -> #{id := SubId, current_state := SStateId} ->
Key = {SubId, Stream}, Key = {SubId, Stream},
NeedCreateStream = case emqx_persistent_session_ds_state:get_stream(Key, S0) of
case emqx_persistent_session_ds_state:get_stream(Key, S0) of undefined ->
undefined ->
true;
#srs{unsubscribed = true} ->
true;
_SRS ->
false
end,
case NeedCreateStream of
true ->
NewSRS = NewSRS =
#srs{ #srs{
rank_x = ?rank_x, rank_x = ?rank_x,
@ -403,15 +294,15 @@ accept_stream(
}, },
S1 = emqx_persistent_session_ds_state:put_stream(Key, NewSRS, S0), S1 = emqx_persistent_session_ds_state:put_stream(Key, NewSRS, S0),
S1; S1;
false -> _SRS ->
S0 S0
end end
end. end.
revoke_stream( revoke_stream(
#{share_topic_filter := ShareTopicFilter, stream := Stream}, S0 #{topic_filter := TopicFilter, stream := Stream}, S0
) -> ) ->
case emqx_persistent_session_ds_state:get_subscription(ShareTopicFilter, S0) of case emqx_persistent_session_ds_state:get_subscription(TopicFilter, S0) of
undefined -> undefined ->
%% This should not happen. %% This should not happen.
%% Agent should have received unsubscribe callback %% Agent should have received unsubscribe callback
@ -429,363 +320,19 @@ revoke_stream(
end end
end. end.
%%-------------------------------------------------------------------- -spec to_agent_subscription(
%% on_streams_replay emqx_persistent_session_ds_state:t(), emqx_persistent_session_ds:subscription()
-spec on_streams_replay(
emqx_persistent_session_ds_state:t(),
t()
) -> {emqx_persistent_session_ds_state:t(), t()}.
on_streams_replay(S0, SharedSubS0) ->
{S1, #{agent := Agent0, scheduled_actions := ScheduledActions0} = SharedSubS1} =
renew_streams(S0, SharedSubS0),
Progresses = all_stream_progresses(S1, Agent0),
Agent1 = emqx_persistent_session_ds_shared_subs_agent:on_stream_progress(
Agent0, Progresses
),
{Agent2, ScheduledActions1} = run_scheduled_actions(S1, Agent1, ScheduledActions0),
SharedSubS2 = SharedSubS1#{
agent => Agent2,
scheduled_actions => ScheduledActions1
},
{S1, SharedSubS2}.
%%--------------------------------------------------------------------
%% on_streams_replay internal functions
all_stream_progresses(S, Agent) ->
all_stream_progresses(S, Agent, _NeedUnacked = false).
all_stream_progresses(S, _Agent, NeedUnacked) ->
CommQos1 = emqx_persistent_session_ds_state:get_seqno(?committed(?QOS_1), S),
CommQos2 = emqx_persistent_session_ds_state:get_seqno(?committed(?QOS_2), S),
fold_shared_stream_states(
fun(ShareTopicFilter, Stream, SRS, ProgressesAcc0) ->
case
is_stream_started(CommQos1, CommQos2, SRS) and
(NeedUnacked or is_stream_fully_acked(CommQos1, CommQos2, SRS))
of
true ->
StreamProgress = stream_progress(CommQos1, CommQos2, Stream, SRS),
maps:update_with(
ShareTopicFilter,
fun(Progresses) -> [StreamProgress | Progresses] end,
[StreamProgress],
ProgressesAcc0
);
false ->
ProgressesAcc0
end
end,
#{},
S
).
run_scheduled_actions(S, Agent, ScheduledActions) ->
maps:fold(
fun(ShareTopicFilter, Action0, {AgentAcc0, ScheduledActionsAcc}) ->
case run_scheduled_action(S, AgentAcc0, ShareTopicFilter, Action0) of
{ok, AgentAcc1} ->
{AgentAcc1, maps:remove(ShareTopicFilter, ScheduledActionsAcc)};
{continue, Action1} ->
{AgentAcc0, ScheduledActionsAcc#{ShareTopicFilter => Action1}}
end
end,
{Agent, ScheduledActions},
ScheduledActions
).
run_scheduled_action(
S,
Agent0,
ShareTopicFilter,
#{type := Type, stream_keys_to_wait := StreamKeysToWait0, progresses := Progresses0} = Action
) -> ) ->
StreamKeysToWait1 = filter_unfinished_streams(S, StreamKeysToWait0), emqx_persistent_session_ds_shared_subs_agent:subscription().
Progresses1 = stream_progresses(S, StreamKeysToWait0 -- StreamKeysToWait1) ++ Progresses0,
case StreamKeysToWait1 of
[] ->
?tp(debug, shared_subs_schedule_action_complete, #{
share_topic_filter => ShareTopicFilter,
progresses => format_stream_progresses(Progresses1),
type => Type
}),
%% Regular progress won't se unsubscribed streams, so we need to
%% send the progress explicitly.
Agent1 = emqx_persistent_session_ds_shared_subs_agent:on_stream_progress(
Agent0, #{ShareTopicFilter => Progresses1}
),
case Type of
{?schedule_subscribe, SubOpts} ->
{ok,
emqx_persistent_session_ds_shared_subs_agent:on_subscribe(
Agent1, ShareTopicFilter, SubOpts
)};
?schedule_unsubscribe ->
{ok,
emqx_persistent_session_ds_shared_subs_agent:on_unsubscribe(
Agent1, ShareTopicFilter, Progresses1
)}
end;
_ ->
Action1 = Action#{stream_keys_to_wait => StreamKeysToWait1, progresses => Progresses1},
?tp(debug, shared_subs_schedule_action_continue, #{
share_topic_filter => ShareTopicFilter,
new_action => format_schedule_action(Action1)
}),
{continue, Action1}
end.
filter_unfinished_streams(S, StreamKeysToWait) ->
CommQos1 = emqx_persistent_session_ds_state:get_seqno(?committed(?QOS_1), S),
CommQos2 = emqx_persistent_session_ds_state:get_seqno(?committed(?QOS_2), S),
lists:filter(
fun(Key) ->
case emqx_persistent_session_ds_state:get_stream(Key, S) of
undefined ->
%% This should not happen: we should see any stream
%% in completed state before deletion
true;
SRS ->
not is_stream_fully_acked(CommQos1, CommQos2, SRS)
end
end,
StreamKeysToWait
).
stream_progresses(S, StreamKeys) ->
CommQos1 = emqx_persistent_session_ds_state:get_seqno(?committed(?QOS_1), S),
CommQos2 = emqx_persistent_session_ds_state:get_seqno(?committed(?QOS_2), S),
lists:map(
fun({_SubId, Stream} = Key) ->
SRS = emqx_persistent_session_ds_state:get_stream(Key, S),
stream_progress(CommQos1, CommQos2, Stream, SRS)
end,
StreamKeys
).
%%--------------------------------------------------------------------
%% on_disconnect
on_disconnect(S0, #{agent := Agent0} = SharedSubS0) ->
S1 = revoke_all_streams(S0),
Progresses = all_stream_progresses(S1, Agent0, _NeedUnacked = true),
Agent1 = emqx_persistent_session_ds_shared_subs_agent:on_disconnect(Agent0, Progresses),
SharedSubS1 = SharedSubS0#{agent => Agent1, scheduled_actions => #{}},
{S1, SharedSubS1}.
%%--------------------------------------------------------------------
%% on_disconnect helpers
revoke_all_streams(S0) ->
fold_shared_stream_states(
fun(ShareTopicFilter, Stream, _SRS, S) ->
revoke_stream(#{share_topic_filter => ShareTopicFilter, stream => Stream}, S)
end,
S0,
S0
).
%%--------------------------------------------------------------------
%% on_info
-spec on_info(emqx_persistent_session_ds_state:t(), t(), term()) ->
{emqx_persistent_session_ds_state:t(), t()}.
on_info(S, #{agent := Agent0} = SharedSubS0, Info) ->
Agent1 = emqx_persistent_session_ds_shared_subs_agent:on_info(Agent0, Info),
SharedSubS1 = SharedSubS0#{agent => Agent1},
{S, SharedSubS1}.
%%--------------------------------------------------------------------
%% to_map
-spec to_map(emqx_persistent_session_ds_state:t(), t()) -> map().
to_map(S, _SharedSubS) ->
fold_shared_subs(
fun(ShareTopicFilter, _, Acc) -> Acc#{ShareTopicFilter => lookup(ShareTopicFilter, S)} end,
#{},
S
).
%%--------------------------------------------------------------------
%% cold_get_subscription
-spec cold_get_subscription(emqx_persistent_session_ds:id(), share_topic_filter()) ->
emqx_persistent_session_ds:subscription() | undefined.
cold_get_subscription(SessionId, ShareTopicFilter) ->
case emqx_persistent_session_ds_state:cold_get_subscription(SessionId, ShareTopicFilter) of
[Sub = #{current_state := SStateId}] ->
case
emqx_persistent_session_ds_state:cold_get_subscription_state(SessionId, SStateId)
of
[#{subopts := Subopts}] ->
Sub#{subopts => Subopts};
_ ->
undefined
end;
_ ->
undefined
end.
%%--------------------------------------------------------------------
%% Generic helpers
%%--------------------------------------------------------------------
lookup(ShareTopicFilter, S) ->
case emqx_persistent_session_ds_state:get_subscription(ShareTopicFilter, S) of
Sub = #{current_state := SStateId} ->
case emqx_persistent_session_ds_state:get_subscription_state(SStateId, S) of
#{subopts := SubOpts} ->
Sub#{subopts => SubOpts};
undefined ->
undefined
end;
undefined ->
undefined
end.
stream_keys_by_sub_id(S, MatchSubId) ->
emqx_persistent_session_ds_state:fold_streams(
fun({SubId, _Stream} = StreamKey, _SRS, StreamKeys) ->
case SubId of
MatchSubId ->
[StreamKey | StreamKeys];
_ ->
StreamKeys
end
end,
[],
S
).
stream_progress(
CommQos1,
CommQos2,
Stream,
#srs{
it_end = EndIt,
it_begin = BeginIt
} = SRS
) ->
Iterator =
case is_stream_fully_acked(CommQos1, CommQos2, SRS) of
true -> EndIt;
false -> BeginIt
end,
#{
stream => Stream,
progress => #{
iterator => Iterator
},
use_finished => is_use_finished(SRS)
}.
fold_shared_subs(Fun, Acc, S) ->
emqx_persistent_session_ds_state:fold_subscriptions(
fun
(#share{} = ShareTopicFilter, Sub, Acc0) -> Fun(ShareTopicFilter, Sub, Acc0);
(_, _Sub, Acc0) -> Acc0
end,
Acc,
S
).
fold_shared_stream_states(Fun, Acc, S) ->
%% TODO
%% Optimize or cache
ShareTopicFilters = fold_shared_subs(
fun
(#share{} = ShareTopicFilter, #{id := Id} = _Sub, Acc0) ->
Acc0#{Id => ShareTopicFilter};
(_, _, Acc0) ->
Acc0
end,
#{},
S
),
emqx_persistent_session_ds_state:fold_streams(
fun({SubId, Stream}, SRS, Acc0) ->
case ShareTopicFilters of
#{SubId := ShareTopicFilter} ->
Fun(ShareTopicFilter, Stream, SRS, Acc0);
_ ->
Acc0
end
end,
Acc,
S
).
to_agent_subscription(_S, Subscription) -> to_agent_subscription(_S, Subscription) ->
%% TODO
%% do we need anything from sub state?
maps:with([start_time], Subscription). maps:with([start_time], Subscription).
-spec agent_opts(opts()) -> emqx_persistent_session_ds_shared_subs_agent:opts().
agent_opts(#{session_id := SessionId}) -> agent_opts(#{session_id := SessionId}) ->
#{session_id => SessionId}. #{session_id => SessionId}.
-dialyzer({nowarn_function, now_ms/0}). -dialyzer({nowarn_function, now_ms/0}).
now_ms() -> now_ms() ->
erlang:system_time(millisecond). erlang:system_time(millisecond).
is_use_finished(#srs{unsubscribed = Unsubscribed}) ->
Unsubscribed.
is_stream_started(CommQos1, CommQos2, #srs{first_seqno_qos1 = Q1, last_seqno_qos1 = Q2}) ->
(CommQos1 >= Q1) or (CommQos2 >= Q2).
is_stream_fully_acked(_, _, #srs{
first_seqno_qos1 = Q1, last_seqno_qos1 = Q1, first_seqno_qos2 = Q2, last_seqno_qos2 = Q2
}) ->
%% Streams where the last chunk doesn't contain any QoS1 and 2
%% messages are considered fully acked:
true;
is_stream_fully_acked(Comm1, Comm2, #srs{last_seqno_qos1 = S1, last_seqno_qos2 = S2}) ->
(Comm1 >= S1) andalso (Comm2 >= S2).
%%--------------------------------------------------------------------
%% Formatters
%%--------------------------------------------------------------------
format_schedule_action(#{
type := Type, progresses := Progresses, stream_keys_to_wait := StreamKeysToWait
}) ->
#{
type => Type,
progresses => format_stream_progresses(Progresses),
stream_keys_to_wait => format_stream_keys(StreamKeysToWait)
}.
format_stream_progresses(Streams) ->
lists:map(
fun format_stream_progress/1,
Streams
).
format_stream_progress(#{stream := Stream, progress := Progress} = Value) ->
Value#{stream => format_opaque(Stream), progress => format_progress(Progress)}.
format_progress(#{iterator := Iterator} = Progress) ->
Progress#{iterator => format_opaque(Iterator)}.
format_stream_key(beginning) -> beginning;
format_stream_key({SubId, Stream}) -> {SubId, format_opaque(Stream)}.
format_stream_keys(StreamKeys) ->
lists:map(
fun format_stream_key/1,
StreamKeys
).
format_lease_events(Events) ->
lists:map(
fun format_lease_event/1,
Events
).
format_lease_event(#{stream := Stream, progress := Progress} = Event) ->
Event#{stream => format_opaque(Stream), progress => format_progress(Progress)};
format_lease_event(#{stream := Stream} = Event) ->
Event#{stream => format_opaque(Stream)}.
format_opaque(Opaque) ->
erlang:phash2(Opaque).

View File

@ -15,7 +15,7 @@
}. }.
-type t() :: term(). -type t() :: term().
-type share_topic_filter() :: emqx_persistent_session_ds:share_topic_filter(). -type topic_filter() :: emqx_persistent_session_ds:share_topic_filter().
-type opts() :: #{ -type opts() :: #{
session_id := session_id() session_id := session_id()
@ -28,44 +28,41 @@
-type stream_lease() :: #{ -type stream_lease() :: #{
type => lease, type => lease,
%% Used as "external" subscription_id %% Used as "external" subscription_id
share_topic_filter := share_topic_filter(), topic_filter := topic_filter(),
stream := emqx_ds:stream(), stream := emqx_ds:stream(),
iterator := emqx_ds:iterator() iterator := emqx_ds:iterator()
}. }.
-type stream_revoke() :: #{ -type stream_revoke() :: #{
type => revoke, type => revoke,
share_topic_filter := share_topic_filter(), topic_filter := topic_filter(),
stream := emqx_ds:stream() stream := emqx_ds:stream()
}. }.
-type stream_lease_event() :: stream_lease() | stream_revoke(). -type stream_lease_event() :: stream_lease() | stream_revoke().
-type stream_progress() :: #{ -type stream_progress() :: #{
share_topic_filter := share_topic_filter(), topic_filter := topic_filter(),
stream := emqx_ds:stream(), stream := emqx_ds:stream(),
iterator := emqx_ds:iterator(), iterator := emqx_ds:iterator()
use_finished := boolean()
}. }.
-export_type([ -export_type([
t/0, t/0,
subscription/0, subscription/0,
session_id/0, session_id/0,
stream_lease_event/0, stream_lease/0,
opts/0 opts/0
]). ]).
-export([ -export([
new/1, new/1,
open/2, open/2,
can_subscribe/3,
on_subscribe/3, on_subscribe/3,
on_unsubscribe/3, on_unsubscribe/2,
on_stream_progress/2, on_stream_progress/2,
on_info/2, on_info/2,
on_disconnect/2,
renew_streams/1 renew_streams/1
]). ]).
@ -80,13 +77,12 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-callback new(opts()) -> t(). -callback new(opts()) -> t().
-callback open([{share_topic_filter(), subscription()}], opts()) -> t(). -callback open([{topic_filter(), subscription()}], opts()) -> t().
-callback can_subscribe(t(), share_topic_filter(), emqx_types:subopts()) -> ok | {error, term()}. -callback on_subscribe(t(), topic_filter(), emqx_types:subopts()) ->
-callback on_subscribe(t(), share_topic_filter(), emqx_types:subopts()) -> t(). {ok, t()} | {error, term()}.
-callback on_unsubscribe(t(), share_topic_filter(), [stream_progress()]) -> t(). -callback on_unsubscribe(t(), topic_filter()) -> t().
-callback on_disconnect(t(), [stream_progress()]) -> t().
-callback renew_streams(t()) -> {[stream_lease_event()], t()}. -callback renew_streams(t()) -> {[stream_lease_event()], t()}.
-callback on_stream_progress(t(), #{share_topic_filter() => [stream_progress()]}) -> t(). -callback on_stream_progress(t(), [stream_progress()]) -> t().
-callback on_info(t(), term()) -> t(). -callback on_info(t(), term()) -> t().
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -97,31 +93,24 @@
new(Opts) -> new(Opts) ->
?shared_subs_agent:new(Opts). ?shared_subs_agent:new(Opts).
-spec open([{share_topic_filter(), subscription()}], opts()) -> t(). -spec open([{topic_filter(), subscription()}], opts()) -> t().
open(Topics, Opts) -> open(Topics, Opts) ->
?shared_subs_agent:open(Topics, Opts). ?shared_subs_agent:open(Topics, Opts).
-spec can_subscribe(t(), share_topic_filter(), emqx_types:subopts()) -> ok | {error, term()}. -spec on_subscribe(t(), topic_filter(), emqx_types:subopts()) ->
can_subscribe(Agent, ShareTopicFilter, SubOpts) -> {ok, t()} | {error, emqx_types:reason_code()}.
?shared_subs_agent:can_subscribe(Agent, ShareTopicFilter, SubOpts). on_subscribe(Agent, TopicFilter, SubOpts) ->
?shared_subs_agent:on_subscribe(Agent, TopicFilter, SubOpts).
-spec on_subscribe(t(), share_topic_filter(), emqx_types:subopts()) -> t(). -spec on_unsubscribe(t(), topic_filter()) -> t().
on_subscribe(Agent, ShareTopicFilter, SubOpts) -> on_unsubscribe(Agent, TopicFilter) ->
?shared_subs_agent:on_subscribe(Agent, ShareTopicFilter, SubOpts). ?shared_subs_agent:on_unsubscribe(Agent, TopicFilter).
-spec on_unsubscribe(t(), share_topic_filter(), [stream_progress()]) -> t().
on_unsubscribe(Agent, ShareTopicFilter, StreamProgresses) ->
?shared_subs_agent:on_unsubscribe(Agent, ShareTopicFilter, StreamProgresses).
-spec on_disconnect(t(), #{share_topic_filter() => [stream_progress()]}) -> t().
on_disconnect(Agent, StreamProgresses) ->
?shared_subs_agent:on_disconnect(Agent, StreamProgresses).
-spec renew_streams(t()) -> {[stream_lease_event()], t()}. -spec renew_streams(t()) -> {[stream_lease_event()], t()}.
renew_streams(Agent) -> renew_streams(Agent) ->
?shared_subs_agent:renew_streams(Agent). ?shared_subs_agent:renew_streams(Agent).
-spec on_stream_progress(t(), #{share_topic_filter() => [stream_progress()]}) -> t(). -spec on_stream_progress(t(), [stream_progress()]) -> t().
on_stream_progress(Agent, StreamProgress) -> on_stream_progress(Agent, StreamProgress) ->
?shared_subs_agent:on_stream_progress(Agent, StreamProgress). ?shared_subs_agent:on_stream_progress(Agent, StreamProgress).

View File

@ -9,13 +9,11 @@
-export([ -export([
new/1, new/1,
open/2, open/2,
can_subscribe/3,
on_subscribe/3, on_subscribe/3,
on_unsubscribe/3, on_unsubscribe/2,
on_stream_progress/2, on_stream_progress/2,
on_info/2, on_info/2,
on_disconnect/2,
renew_streams/1 renew_streams/1
]). ]).
@ -32,16 +30,10 @@ new(_Opts) ->
open(_Topics, _Opts) -> open(_Topics, _Opts) ->
undefined. undefined.
can_subscribe(_Agent, _TopicFilter, _SubOpts) -> on_subscribe(_Agent, _TopicFilter, _SubOpts) ->
{error, ?RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED}. {error, ?RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED}.
on_subscribe(Agent, _TopicFilter, _SubOpts) -> on_unsubscribe(Agent, _TopicFilter) ->
Agent.
on_unsubscribe(Agent, _TopicFilter, _Progresses) ->
Agent.
on_disconnect(Agent, _) ->
Agent. Agent.
renew_streams(Agent) -> renew_streams(Agent) ->

View File

@ -399,9 +399,7 @@ new_id(Rec) ->
get_subscription(TopicFilter, Rec) -> get_subscription(TopicFilter, Rec) ->
gen_get(?subscriptions, TopicFilter, Rec). gen_get(?subscriptions, TopicFilter, Rec).
-spec cold_get_subscription( -spec cold_get_subscription(emqx_persistent_session_ds:id(), emqx_types:topic()) ->
emqx_persistent_session_ds:id(), emqx_types:topic() | emqx_types:share()
) ->
[emqx_persistent_session_ds_subs:subscription()]. [emqx_persistent_session_ds_subs:subscription()].
cold_get_subscription(SessionId, Topic) -> cold_get_subscription(SessionId, Topic) ->
kv_pmap_read(?subscription_tab, SessionId, Topic). kv_pmap_read(?subscription_tab, SessionId, Topic).

View File

@ -21,7 +21,7 @@
-record(ps_route, { -record(ps_route, {
topic :: binary(), topic :: binary(),
dest :: emqx_persistent_session_ds_router:dest() | '_' dest :: emqx_persistent_session_ds:id() | '_'
}). }).
-record(ps_routeidx, { -record(ps_routeidx, {

View File

@ -21,7 +21,6 @@
%% Till full implementation we need to dispach to the null agent. %% Till full implementation we need to dispach to the null agent.
%% It will report "not implemented" error for attempts to use shared subscriptions. %% It will report "not implemented" error for attempts to use shared subscriptions.
-define(shared_subs_agent, emqx_persistent_session_ds_shared_subs_null_agent). -define(shared_subs_agent, emqx_persistent_session_ds_shared_subs_null_agent).
% -define(shared_subs_agent, emqx_ds_shared_sub_agent).
%% end of -ifdef(TEST). %% end of -ifdef(TEST).
-endif. -endif.

View File

@ -62,7 +62,7 @@
streams := [{pid(), quicer:stream_handle()}], streams := [{pid(), quicer:stream_handle()}],
%% New stream opts %% New stream opts
stream_opts := map(), stream_opts := map(),
%% If connection is resumed from session ticket %% If conneciton is resumed from session ticket
is_resumed => boolean(), is_resumed => boolean(),
%% mqtt message serializer config %% mqtt message serializer config
serialize => undefined, serialize => undefined,
@ -70,8 +70,8 @@
}. }.
-type cb_ret() :: quicer_lib:cb_ret(). -type cb_ret() :: quicer_lib:cb_ret().
%% @doc Data streams initializations are started in parallel with control streams, data streams are blocked %% @doc Data streams initializions are started in parallel with control streams, data streams are blocked
%% for the activation from control stream after it is accepted as a legit connection. %% for the activation from control stream after it is accepted as a legit conneciton.
%% For security, the initial number of allowed data streams from client should be limited by %% For security, the initial number of allowed data streams from client should be limited by
%% 'peer_bidi_stream_count` & 'peer_unidi_stream_count` %% 'peer_bidi_stream_count` & 'peer_unidi_stream_count`
-spec activate_data_streams(pid(), { -spec activate_data_streams(pid(), {
@ -80,7 +80,7 @@
activate_data_streams(ConnOwner, {PS, Serialize, Channel}) -> activate_data_streams(ConnOwner, {PS, Serialize, Channel}) ->
gen_server:call(ConnOwner, {activate_data_streams, {PS, Serialize, Channel}}, infinity). gen_server:call(ConnOwner, {activate_data_streams, {PS, Serialize, Channel}}, infinity).
%% @doc connection owner init callback %% @doc conneciton owner init callback
-spec init(map()) -> {ok, cb_state()}. -spec init(map()) -> {ok, cb_state()}.
init(#{stream_opts := SOpts} = S) when is_list(SOpts) -> init(#{stream_opts := SOpts} = S) when is_list(SOpts) ->
init(S#{stream_opts := maps:from_list(SOpts)}); init(S#{stream_opts := maps:from_list(SOpts)});

View File

@ -351,7 +351,6 @@ fields("authz_cache") ->
#{ #{
default => true, default => true,
required => true, required => true,
importance => ?IMPORTANCE_NO_DOC,
desc => ?DESC(fields_cache_enable) desc => ?DESC(fields_cache_enable)
} }
)}, )},
@ -388,7 +387,6 @@ fields("flapping_detect") ->
boolean(), boolean(),
#{ #{
default => false, default => false,
%% importance => ?IMPORTANCE_NO_DOC,
desc => ?DESC(flapping_detect_enable) desc => ?DESC(flapping_detect_enable)
} }
)}, )},
@ -425,7 +423,6 @@ fields("force_shutdown") ->
boolean(), boolean(),
#{ #{
default => true, default => true,
importance => ?IMPORTANCE_NO_DOC,
desc => ?DESC(force_shutdown_enable) desc => ?DESC(force_shutdown_enable)
} }
)}, )},
@ -455,7 +452,6 @@ fields("overload_protection") ->
boolean(), boolean(),
#{ #{
desc => ?DESC(overload_protection_enable), desc => ?DESC(overload_protection_enable),
%% importance => ?IMPORTANCE_NO_DOC,
default => false default => false
} }
)}, )},
@ -516,11 +512,7 @@ fields("force_gc") ->
{"enable", {"enable",
sc( sc(
boolean(), boolean(),
#{ #{default => true, desc => ?DESC(force_gc_enable)}
default => true,
importance => ?IMPORTANCE_NO_DOC,
desc => ?DESC(force_gc_enable)
}
)}, )},
{"count", {"count",
sc( sc(
@ -1673,7 +1665,6 @@ fields("durable_sessions") ->
sc( sc(
boolean(), #{ boolean(), #{
desc => ?DESC(durable_sessions_enable), desc => ?DESC(durable_sessions_enable),
%% importance => ?IMPORTANCE_NO_DOC,
default => false default => false
} }
)}, )},
@ -1897,7 +1888,6 @@ base_listener(Bind) ->
#{ #{
default => true, default => true,
aliases => [enabled], aliases => [enabled],
importance => ?IMPORTANCE_NO_DOC,
desc => ?DESC(fields_listener_enabled) desc => ?DESC(fields_listener_enabled)
} }
)}, )},
@ -2426,7 +2416,6 @@ client_ssl_opts_schema(Defaults) ->
boolean(), boolean(),
#{ #{
default => false, default => false,
%% importance => ?IMPORTANCE_NO_DOC,
desc => ?DESC(client_ssl_opts_schema_enable) desc => ?DESC(client_ssl_opts_schema_enable)
} }
)}, )},

View File

@ -589,14 +589,6 @@ ensure_valid_options(Options, Versions) ->
ensure_valid_options([], _, Acc) -> ensure_valid_options([], _, Acc) ->
lists:reverse(Acc); lists:reverse(Acc);
ensure_valid_options([{K, undefined} | T], Versions, Acc) when
K =:= crl_check;
K =:= crl_cache
->
%% Note: we must set crl options to `undefined' to unset them. Otherwise,
%% `esockd' will retain such options when `esockd:merge_opts/2' is called and the SSL
%% options were previously enabled.
ensure_valid_options(T, Versions, [{K, undefined} | Acc]);
ensure_valid_options([{_, undefined} | T], Versions, Acc) -> ensure_valid_options([{_, undefined} | T], Versions, Acc) ->
ensure_valid_options(T, Versions, Acc); ensure_valid_options(T, Versions, Acc);
ensure_valid_options([{_, ""} | T], Versions, Acc) -> ensure_valid_options([{_, ""} | T], Versions, Acc) ->

View File

@ -17,6 +17,7 @@
-include("emqx_mqtt.hrl"). -include("emqx_mqtt.hrl").
-export([format/2]). -export([format/2]).
-export([format_meta_map/1]).
%% logger_formatter:config/0 is not exported. %% logger_formatter:config/0 is not exported.
-type config() :: map(). -type config() :: map().
@ -42,6 +43,10 @@ format(
format(Event, Config) -> format(Event, Config) ->
emqx_logger_textfmt:format(Event, Config). emqx_logger_textfmt:format(Event, Config).
format_meta_map(Meta) ->
Encode = emqx_trace_handler:payload_encode(),
format_meta_map(Meta, Encode).
format_meta_map(Meta, Encode) -> format_meta_map(Meta, Encode) ->
format_meta_map(Meta, Encode, [ format_meta_map(Meta, Encode, [
{packet, fun format_packet/2}, {packet, fun format_packet/2},

View File

@ -436,7 +436,6 @@ websocket_handle({Frame, _}, State) ->
%% TODO: should not close the ws connection %% TODO: should not close the ws connection
?LOG(error, #{msg => "unexpected_frame", frame => Frame}), ?LOG(error, #{msg => "unexpected_frame", frame => Frame}),
shutdown(unexpected_ws_frame, State). shutdown(unexpected_ws_frame, State).
websocket_info({call, From, Req}, State) -> websocket_info({call, From, Req}, State) ->
handle_call(From, Req, State); handle_call(From, Req, State);
websocket_info({cast, rate_limit}, State) -> websocket_info({cast, rate_limit}, State) ->
@ -738,8 +737,7 @@ parse_incoming(Data, Packets, State = #state{parse_state = ParseState}) ->
input_bytes => Data input_bytes => Data
}), }),
FrameError = {frame_error, Reason}, FrameError = {frame_error, Reason},
NState = enrich_state(Reason, State), {[{incoming, FrameError} | Packets], State};
{[{incoming, FrameError} | Packets], NState};
error:Reason:Stacktrace -> error:Reason:Stacktrace ->
?LOG(error, #{ ?LOG(error, #{
at_state => emqx_frame:describe_state(ParseState), at_state => emqx_frame:describe_state(ParseState),
@ -832,7 +830,7 @@ serialize_and_inc_stats_fun(#state{serialize = Serialize}) ->
?LOG(warning, #{ ?LOG(warning, #{
msg => "packet_discarded", msg => "packet_discarded",
reason => "frame_too_large", reason => "frame_too_large",
packet => Packet packet => emqx_packet:format(Packet)
}), }),
ok = emqx_metrics:inc('delivery.dropped.too_large'), ok = emqx_metrics:inc('delivery.dropped.too_large'),
ok = emqx_metrics:inc('delivery.dropped'), ok = emqx_metrics:inc('delivery.dropped'),
@ -1071,13 +1069,6 @@ check_max_connection(Type, Listener) ->
{denny, Reason} {denny, Reason}
end end
end. end.
enrich_state(#{parse_state := NParseState}, State) ->
Serialize = emqx_frame:serialize_opts(NParseState),
State#state{parse_state = NParseState, serialize = Serialize};
enrich_state(_, State) ->
State.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% For CT tests %% For CT tests
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -414,32 +414,24 @@ t_handle_in_auth(_) ->
emqx_channel:handle_in(?AUTH_PACKET(), Channel). emqx_channel:handle_in(?AUTH_PACKET(), Channel).
t_handle_in_frame_error(_) -> t_handle_in_frame_error(_) ->
IdleChannelV5 = channel(#{conn_state => idle}), IdleChannel = channel(#{conn_state => idle}),
%% no CONNACK packet for v4 {shutdown, #{shutdown_count := frame_too_large, cause := frame_too_large}, _Chan} =
?assertMatch( emqx_channel:handle_in({frame_error, #{cause => frame_too_large}}, IdleChannel),
{shutdown, #{shutdown_count := frame_too_large, cause := frame_too_large}, _Chan},
emqx_channel:handle_in(
{frame_error, #{cause => frame_too_large}}, v4(IdleChannelV5)
)
),
ConnectingChan = channel(#{conn_state => connecting}), ConnectingChan = channel(#{conn_state => connecting}),
ConnackPacket = ?CONNACK_PACKET(?RC_PACKET_TOO_LARGE), ConnackPacket = ?CONNACK_PACKET(?RC_PACKET_TOO_LARGE),
?assertMatch( {shutdown,
{shutdown, #{
#{ shutdown_count := frame_too_large,
shutdown_count := frame_too_large, cause := frame_too_large,
cause := frame_too_large, limit := 100,
limit := 100, received := 101
received := 101 },
}, ConnackPacket,
ConnackPacket, _}, _} =
emqx_channel:handle_in( emqx_channel:handle_in(
{frame_error, #{cause => frame_too_large, received => 101, limit => 100}}, {frame_error, #{cause => frame_too_large, received => 101, limit => 100}},
ConnectingChan ConnectingChan
) ),
),
DisconnectPacket = ?DISCONNECT_PACKET(?RC_PACKET_TOO_LARGE), DisconnectPacket = ?DISCONNECT_PACKET(?RC_PACKET_TOO_LARGE),
ConnectedChan = channel(#{conn_state => connected}), ConnectedChan = channel(#{conn_state => connected}),
?assertMatch( ?assertMatch(

View File

@ -78,7 +78,6 @@
start_epmd/0, start_epmd/0,
start_peer/2, start_peer/2,
stop_peer/1, stop_peer/1,
ebin_path/0,
listener_port/2 listener_port/2
]). ]).

View File

@ -138,14 +138,13 @@ init_per_testcase(t_refresh_config = TestCase, Config) ->
]; ];
init_per_testcase(TestCase, Config) when init_per_testcase(TestCase, Config) when
TestCase =:= t_update_listener; TestCase =:= t_update_listener;
TestCase =:= t_update_listener_enable_disable;
TestCase =:= t_validations TestCase =:= t_validations
-> ->
ct:timetrap({seconds, 30}), ct:timetrap({seconds, 30}),
ok = snabbkaffe:start_trace(), ok = snabbkaffe:start_trace(),
%% when running emqx standalone tests, we can't use those %% when running emqx standalone tests, we can't use those
%% features. %% features.
case does_module_exist(emqx_mgmt) of case does_module_exist(emqx_management) of
true -> true ->
DataDir = ?config(data_dir, Config), DataDir = ?config(data_dir, Config),
CRLFile = filename:join([DataDir, "intermediate-revoked.crl.pem"]), CRLFile = filename:join([DataDir, "intermediate-revoked.crl.pem"]),
@ -166,7 +165,7 @@ init_per_testcase(TestCase, Config) when
{emqx_conf, #{config => #{listeners => #{ssl => #{default => ListenerConf}}}}}, {emqx_conf, #{config => #{listeners => #{ssl => #{default => ListenerConf}}}}},
emqx, emqx,
emqx_management, emqx_management,
emqx_mgmt_api_test_util:emqx_dashboard() {emqx_dashboard, "dashboard.listeners.http { enable = true, bind = 18083 }"}
], ],
#{work_dir => emqx_cth_suite:work_dir(TestCase, Config)} #{work_dir => emqx_cth_suite:work_dir(TestCase, Config)}
), ),
@ -207,7 +206,6 @@ read_crl(Filename) ->
end_per_testcase(TestCase, Config) when end_per_testcase(TestCase, Config) when
TestCase =:= t_update_listener; TestCase =:= t_update_listener;
TestCase =:= t_update_listener_enable_disable;
TestCase =:= t_validations TestCase =:= t_validations
-> ->
Skip = proplists:get_bool(skip_does_not_apply, Config), Skip = proplists:get_bool(skip_does_not_apply, Config),
@ -1059,104 +1057,3 @@ do_t_validations(_Config) ->
), ),
ok. ok.
%% Checks that if CRL is ever enabled and then disabled, clients can connect, even if they
%% would otherwise not have their corresponding CRLs cached and fail with `{bad_crls,
%% no_relevant_crls}`.
t_update_listener_enable_disable(Config) ->
case proplists:get_bool(skip_does_not_apply, Config) of
true ->
ct:pal("skipping as this test does not apply in this profile"),
ok;
false ->
do_t_update_listener_enable_disable(Config)
end.
do_t_update_listener_enable_disable(Config) ->
DataDir = ?config(data_dir, Config),
Keyfile = filename:join([DataDir, "server.key.pem"]),
Certfile = filename:join([DataDir, "server.cert.pem"]),
Cacertfile = filename:join([DataDir, "ca-chain.cert.pem"]),
ClientCert = filename:join(DataDir, "client.cert.pem"),
ClientKey = filename:join(DataDir, "client.key.pem"),
ListenerId = "ssl:default",
%% Enable CRL
{ok, {{_, 200, _}, _, ListenerData0}} = get_listener_via_api(ListenerId),
CRLConfig0 =
#{
<<"ssl_options">> =>
#{
<<"keyfile">> => Keyfile,
<<"certfile">> => Certfile,
<<"cacertfile">> => Cacertfile,
<<"enable_crl_check">> => true,
<<"fail_if_no_peer_cert">> => true
}
},
ListenerData1 = emqx_utils_maps:deep_merge(ListenerData0, CRLConfig0),
{ok, {_, _, ListenerData2}} = update_listener_via_api(ListenerId, ListenerData1),
?assertMatch(
#{
<<"ssl_options">> :=
#{
<<"enable_crl_check">> := true,
<<"verify">> := <<"verify_peer">>,
<<"fail_if_no_peer_cert">> := true
}
},
ListenerData2
),
%% Disable CRL
CRLConfig1 =
#{
<<"ssl_options">> =>
#{
<<"keyfile">> => Keyfile,
<<"certfile">> => Certfile,
<<"cacertfile">> => Cacertfile,
<<"enable_crl_check">> => false,
<<"fail_if_no_peer_cert">> => true
}
},
ListenerData3 = emqx_utils_maps:deep_merge(ListenerData2, CRLConfig1),
redbug:start(
[
"esockd_server:get_listener_prop -> return",
"esockd_server:set_listener_prop -> return",
"esockd:merge_opts -> return",
"esockd_listener_sup:set_options -> return",
"emqx_listeners:inject_crl_config -> return"
],
[{msgs, 100}]
),
{ok, {_, _, ListenerData4}} = update_listener_via_api(ListenerId, ListenerData3),
?assertMatch(
#{
<<"ssl_options">> :=
#{
<<"enable_crl_check">> := false,
<<"verify">> := <<"verify_peer">>,
<<"fail_if_no_peer_cert">> := true
}
},
ListenerData4
),
%% Now the client that would be blocked tries to connect and should now be allowed.
{ok, C} = emqtt:start_link([
{ssl, true},
{ssl_opts, [
{certfile, ClientCert},
{keyfile, ClientKey},
{verify, verify_none}
]},
{port, 8883}
]),
?assertMatch({ok, _}, emqtt:connect(C)),
emqtt:stop(C),
?assertNotReceive({http_get, _}),
ok.

View File

@ -79,8 +79,6 @@
%% "Unofficial" `emqx_config_handler' and `emqx_conf' APIs %% "Unofficial" `emqx_config_handler' and `emqx_conf' APIs
-export([schema_module/0, upgrade_raw_conf/1]). -export([schema_module/0, upgrade_raw_conf/1]).
-export([skip_if_oss/0]).
-export_type([appspec/0]). -export_type([appspec/0]).
-export_type([appspec_opts/0]). -export_type([appspec_opts/0]).
@ -391,8 +389,6 @@ default_appspec(emqx_schema_validation, _SuiteOpts) ->
#{schema_mod => emqx_schema_validation_schema, config => #{}}; #{schema_mod => emqx_schema_validation_schema, config => #{}};
default_appspec(emqx_message_transformation, _SuiteOpts) -> default_appspec(emqx_message_transformation, _SuiteOpts) ->
#{schema_mod => emqx_message_transformation_schema, config => #{}}; #{schema_mod => emqx_message_transformation_schema, config => #{}};
default_appspec(emqx_ds_shared_sub, _SuiteOpts) ->
#{schema_mod => emqx_ds_shared_sub_schema, config => #{}};
default_appspec(_, _) -> default_appspec(_, _) ->
#{}. #{}.
@ -523,14 +519,3 @@ upgrade_raw_conf(Conf) ->
ce -> ce ->
emqx_conf_schema:upgrade_raw_conf(Conf) emqx_conf_schema:upgrade_raw_conf(Conf)
end. end.
skip_if_oss() ->
try emqx_release:edition() of
ee ->
false;
_ ->
{skip, not_supported_in_oss}
catch
error:undef ->
{skip, standalone_not_supported}
end.

View File

@ -56,8 +56,6 @@ t_exclusive_sub(_) ->
{ok, _} = emqtt:connect(C1), {ok, _} = emqtt:connect(C1),
?CHECK_SUB(C1, 0), ?CHECK_SUB(C1, 0),
?CHECK_SUB(C1, 0),
{ok, C2} = emqtt:start_link([ {ok, C2} = emqtt:start_link([
{clientid, <<"client2">>}, {clientid, <<"client2">>},
{clean_start, false}, {clean_start, false},

View File

@ -63,7 +63,6 @@ groups() ->
t_parse_malformed_properties, t_parse_malformed_properties,
t_malformed_connect_header, t_malformed_connect_header,
t_malformed_connect_data, t_malformed_connect_data,
t_malformed_connect_data_proto_ver,
t_reserved_connect_flag, t_reserved_connect_flag,
t_invalid_clientid, t_invalid_clientid,
t_undefined_password, t_undefined_password,
@ -168,8 +167,6 @@ t_parse_malformed_utf8_string(_) ->
ParseState = emqx_frame:initial_parse_state(#{strict_mode => true}), ParseState = emqx_frame:initial_parse_state(#{strict_mode => true}),
?ASSERT_FRAME_THROW(utf8_string_invalid, emqx_frame:parse(MalformedPacket, ParseState)). ?ASSERT_FRAME_THROW(utf8_string_invalid, emqx_frame:parse(MalformedPacket, ParseState)).
%% TODO: parse v3 with 0 length clientid
t_serialize_parse_v3_connect(_) -> t_serialize_parse_v3_connect(_) ->
Bin = Bin =
<<16, 37, 0, 6, 77, 81, 73, 115, 100, 112, 3, 2, 0, 60, 0, 23, 109, 111, 115, 113, 112, 117, <<16, 37, 0, 6, 77, 81, 73, 115, 100, 112, 3, 2, 0, 60, 0, 23, 109, 111, 115, 113, 112, 117,
@ -327,7 +324,7 @@ t_serialize_parse_bridge_connect(_) ->
header = #mqtt_packet_header{type = ?CONNECT}, header = #mqtt_packet_header{type = ?CONNECT},
variable = #mqtt_packet_connect{ variable = #mqtt_packet_connect{
clientid = <<"C_00:0C:29:2B:77:52">>, clientid = <<"C_00:0C:29:2B:77:52">>,
proto_ver = ?MQTT_PROTO_V3, proto_ver = 16#03,
proto_name = <<"MQIsdp">>, proto_name = <<"MQIsdp">>,
is_bridge = true, is_bridge = true,
will_retain = true, will_retain = true,
@ -689,36 +686,15 @@ t_malformed_connect_header(_) ->
). ).
t_malformed_connect_data(_) -> t_malformed_connect_data(_) ->
ProtoNameWithLen = <<0, 6, "MQIsdp">>,
ConnectFlags = <<2#00000000>>,
ClientIdwithLen = <<0, 1, "a">>,
UnexpectedRestBin = <<0, 1, 2>>,
?ASSERT_FRAME_THROW( ?ASSERT_FRAME_THROW(
#{cause := malformed_connect, unexpected_trailing_bytes := 3}, #{cause := malformed_connect, unexpected_trailing_bytes := _},
emqx_frame:parse( emqx_frame:parse(<<16, 15, 0, 6, 77, 81, 73, 115, 100, 112, 3, 0, 0, 0, 0, 0, 0>>)
<<16, 18, ProtoNameWithLen/binary, ?MQTT_PROTO_V3, ConnectFlags/binary, 0, 0,
ClientIdwithLen/binary, UnexpectedRestBin/binary>>
)
).
t_malformed_connect_data_proto_ver(_) ->
Proto3NameWithLen = <<0, 6, "MQIsdp">>,
?ASSERT_FRAME_THROW(
#{cause := malformed_connect, header_bytes := <<>>},
emqx_frame:parse(<<16, 8, Proto3NameWithLen/binary>>)
),
ProtoNameWithLen = <<0, 4, "MQTT">>,
?ASSERT_FRAME_THROW(
#{cause := malformed_connect, header_bytes := <<>>},
emqx_frame:parse(<<16, 6, ProtoNameWithLen/binary>>)
). ).
t_reserved_connect_flag(_) -> t_reserved_connect_flag(_) ->
?assertException( ?assertException(
throw, throw,
{frame_parse_error, #{ {frame_parse_error, reserved_connect_flag},
cause := reserved_connect_flag, proto_ver := ?MQTT_PROTO_V3, proto_name := <<"MQIsdp">>
}},
emqx_frame:parse(<<16, 15, 0, 6, 77, 81, 73, 115, 100, 112, 3, 1, 0, 0, 1, 0, 0>>) emqx_frame:parse(<<16, 15, 0, 6, 77, 81, 73, 115, 100, 112, 3, 1, 0, 0, 1, 0, 0>>)
). ).
@ -750,7 +726,7 @@ t_undefined_password(_) ->
}, },
variable = #mqtt_packet_connect{ variable = #mqtt_packet_connect{
proto_name = <<"MQTT">>, proto_name = <<"MQTT">>,
proto_ver = ?MQTT_PROTO_V4, proto_ver = 4,
is_bridge = false, is_bridge = false,
clean_start = true, clean_start = true,
will_flag = false, will_flag = false,
@ -798,9 +774,7 @@ t_invalid_will_retain(_) ->
54, 75, 78, 112, 57, 0, 6, 68, 103, 55, 87, 87, 87>>, 54, 75, 78, 112, 57, 0, 6, 68, 103, 55, 87, 87, 87>>,
?assertException( ?assertException(
throw, throw,
{frame_parse_error, #{ {frame_parse_error, invalid_will_retain},
cause := invalid_will_retain, proto_ver := ?MQTT_PROTO_V5, proto_name := <<"MQTT">>
}},
emqx_frame:parse(ConnectBin) emqx_frame:parse(ConnectBin)
), ),
ok. ok.
@ -822,30 +796,22 @@ t_invalid_will_qos(_) ->
), ),
?assertException( ?assertException(
throw, throw,
{frame_parse_error, #{ {frame_parse_error, invalid_will_qos},
cause := invalid_will_qos, proto_ver := ?MQTT_PROTO_V5, proto_name := <<"MQTT">>
}},
emqx_frame:parse(ConnectBinFun(Will_F_WillQoS1)) emqx_frame:parse(ConnectBinFun(Will_F_WillQoS1))
), ),
?assertException( ?assertException(
throw, throw,
{frame_parse_error, #{ {frame_parse_error, invalid_will_qos},
cause := invalid_will_qos, proto_ver := ?MQTT_PROTO_V5, proto_name := <<"MQTT">>
}},
emqx_frame:parse(ConnectBinFun(Will_F_WillQoS2)) emqx_frame:parse(ConnectBinFun(Will_F_WillQoS2))
), ),
?assertException( ?assertException(
throw, throw,
{frame_parse_error, #{ {frame_parse_error, invalid_will_qos},
cause := invalid_will_qos, proto_ver := ?MQTT_PROTO_V5, proto_name := <<"MQTT">>
}},
emqx_frame:parse(ConnectBinFun(Will_F_WillQoS3)) emqx_frame:parse(ConnectBinFun(Will_F_WillQoS3))
), ),
?assertException( ?assertException(
throw, throw,
{frame_parse_error, #{ {frame_parse_error, invalid_will_qos},
cause := invalid_will_qos, proto_ver := ?MQTT_PROTO_V5, proto_name := <<"MQTT">>
}},
emqx_frame:parse(ConnectBinFun(Will_T_WillQoS3)) emqx_frame:parse(ConnectBinFun(Will_T_WillQoS3))
), ),
ok. ok.

View File

@ -26,7 +26,6 @@
%% Have to use real msgs, as the schema is guarded by enum. %% Have to use real msgs, as the schema is guarded by enum.
-define(THROTTLE_MSG, authorization_permission_denied). -define(THROTTLE_MSG, authorization_permission_denied).
-define(THROTTLE_MSG1, cannot_publish_to_topic_due_to_not_authorized). -define(THROTTLE_MSG1, cannot_publish_to_topic_due_to_not_authorized).
-define(THROTTLE_UNRECOVERABLE_MSG, unrecoverable_resource_error).
-define(TIME_WINDOW, <<"1s">>). -define(TIME_WINDOW, <<"1s">>).
all() -> emqx_common_test_helpers:all(?MODULE). all() -> emqx_common_test_helpers:all(?MODULE).
@ -60,11 +59,6 @@ end_per_suite(Config) ->
emqx_cth_suite:stop(?config(suite_apps, Config)), emqx_cth_suite:stop(?config(suite_apps, Config)),
emqx_config:delete_override_conf_files(). emqx_config:delete_override_conf_files().
init_per_testcase(t_throttle_recoverable_msg, Config) ->
ok = snabbkaffe:start_trace(),
[?THROTTLE_MSG] = Conf = emqx:get_config([log, throttling, msgs]),
{ok, _} = emqx_conf:update([log, throttling, msgs], [?THROTTLE_UNRECOVERABLE_MSG | Conf], #{}),
Config;
init_per_testcase(t_throttle_add_new_msg, Config) -> init_per_testcase(t_throttle_add_new_msg, Config) ->
ok = snabbkaffe:start_trace(), ok = snabbkaffe:start_trace(),
[?THROTTLE_MSG] = Conf = emqx:get_config([log, throttling, msgs]), [?THROTTLE_MSG] = Conf = emqx:get_config([log, throttling, msgs]),
@ -78,10 +72,6 @@ init_per_testcase(_TC, Config) ->
ok = snabbkaffe:start_trace(), ok = snabbkaffe:start_trace(),
Config. Config.
end_per_testcase(t_throttle_recoverable_msg, _Config) ->
ok = snabbkaffe:stop(),
{ok, _} = emqx_conf:update([log, throttling, msgs], [?THROTTLE_MSG], #{}),
ok;
end_per_testcase(t_throttle_add_new_msg, _Config) -> end_per_testcase(t_throttle_add_new_msg, _Config) ->
ok = snabbkaffe:stop(), ok = snabbkaffe:stop(),
{ok, _} = emqx_conf:update([log, throttling, msgs], [?THROTTLE_MSG], #{}), {ok, _} = emqx_conf:update([log, throttling, msgs], [?THROTTLE_MSG], #{}),
@ -111,8 +101,8 @@ t_throttle(_Config) ->
5000 5000
), ),
?assert(emqx_log_throttler:allow(?THROTTLE_MSG, undefined)), ?assert(emqx_log_throttler:allow(?THROTTLE_MSG)),
?assertNot(emqx_log_throttler:allow(?THROTTLE_MSG, undefined)), ?assertNot(emqx_log_throttler:allow(?THROTTLE_MSG)),
{ok, _} = ?block_until( {ok, _} = ?block_until(
#{ #{
?snk_kind := log_throttler_dropped, ?snk_kind := log_throttler_dropped,
@ -125,48 +115,14 @@ t_throttle(_Config) ->
[] []
). ).
t_throttle_recoverable_msg(_Config) ->
ResourceId = <<"resource_id">>,
ThrottledMsg = iolist_to_binary([atom_to_list(?THROTTLE_UNRECOVERABLE_MSG), ":", ResourceId]),
?check_trace(
begin
%% Warm-up and block to increase the probability that next events
%% will be in the same throttling time window.
{ok, _} = ?block_until(
#{?snk_kind := log_throttler_new_msg, throttled_msg := ?THROTTLE_UNRECOVERABLE_MSG},
5000
),
{_, {ok, _}} = ?wait_async_action(
events(?THROTTLE_UNRECOVERABLE_MSG, ResourceId),
#{
?snk_kind := log_throttler_dropped,
throttled_msg := ThrottledMsg
},
5000
),
?assert(emqx_log_throttler:allow(?THROTTLE_UNRECOVERABLE_MSG, ResourceId)),
?assertNot(emqx_log_throttler:allow(?THROTTLE_UNRECOVERABLE_MSG, ResourceId)),
{ok, _} = ?block_until(
#{
?snk_kind := log_throttler_dropped,
throttled_msg := ThrottledMsg,
dropped_count := 1
},
3000
)
end,
[]
).
t_throttle_add_new_msg(_Config) -> t_throttle_add_new_msg(_Config) ->
?check_trace( ?check_trace(
begin begin
{ok, _} = ?block_until( {ok, _} = ?block_until(
#{?snk_kind := log_throttler_new_msg, throttled_msg := ?THROTTLE_MSG1}, 5000 #{?snk_kind := log_throttler_new_msg, throttled_msg := ?THROTTLE_MSG1}, 5000
), ),
?assert(emqx_log_throttler:allow(?THROTTLE_MSG1, undefined)), ?assert(emqx_log_throttler:allow(?THROTTLE_MSG1)),
?assertNot(emqx_log_throttler:allow(?THROTTLE_MSG1, undefined)), ?assertNot(emqx_log_throttler:allow(?THROTTLE_MSG1)),
{ok, _} = ?block_until( {ok, _} = ?block_until(
#{ #{
?snk_kind := log_throttler_dropped, ?snk_kind := log_throttler_dropped,
@ -181,15 +137,10 @@ t_throttle_add_new_msg(_Config) ->
t_throttle_no_msg(_Config) -> t_throttle_no_msg(_Config) ->
%% Must simply pass with no crashes %% Must simply pass with no crashes
Pid = erlang:whereis(emqx_log_throttler), ?assert(emqx_log_throttler:allow(no_test_throttle_msg)),
?assert(emqx_log_throttler:allow(no_test_throttle_msg, undefined)), ?assert(emqx_log_throttler:allow(no_test_throttle_msg)),
?assert(emqx_log_throttler:allow(no_test_throttle_msg, undefined)), timer:sleep(10),
%% assert process is not restarted ?assert(erlang:is_process_alive(erlang:whereis(emqx_log_throttler))).
?assertEqual(Pid, erlang:whereis(emqx_log_throttler)),
%% make a gen_call to ensure the process is alive
%% note: this call result in an 'unexpected_call' error log.
?assertEqual(ignored, gen_server:call(Pid, probe)),
ok.
t_update_time_window(_Config) -> t_update_time_window(_Config) ->
?check_trace( ?check_trace(
@ -217,8 +168,8 @@ t_throttle_debug_primary_level(_Config) ->
#{?snk_kind := log_throttler_dropped, throttled_msg := ?THROTTLE_MSG}, #{?snk_kind := log_throttler_dropped, throttled_msg := ?THROTTLE_MSG},
5000 5000
), ),
?assert(emqx_log_throttler:allow(?THROTTLE_MSG, undefined)), ?assert(emqx_log_throttler:allow(?THROTTLE_MSG)),
?assertNot(emqx_log_throttler:allow(?THROTTLE_MSG, undefined)), ?assertNot(emqx_log_throttler:allow(?THROTTLE_MSG)),
{ok, _} = ?block_until( {ok, _} = ?block_until(
#{ #{
?snk_kind := log_throttler_dropped, ?snk_kind := log_throttler_dropped,
@ -236,13 +187,10 @@ t_throttle_debug_primary_level(_Config) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
events(Msg) -> events(Msg) ->
events(100, Msg, undefined). events(100, Msg).
events(Msg, Id) -> events(N, Msg) ->
events(100, Msg, Id). [emqx_log_throttler:allow(Msg) || _ <- lists:seq(1, N)].
events(N, Msg, Id) ->
[emqx_log_throttler:allow(Msg, Id) || _ <- lists:seq(1, N)].
module_exists(Mod) -> module_exists(Mod) ->
case erlang:module_loaded(Mod) of case erlang:module_loaded(Mod) of

View File

@ -377,60 +377,42 @@ t_will_msg(_) ->
t_format(_) -> t_format(_) ->
io:format("~ts", [ io:format("~ts", [
emqx_packet:format( emqx_packet:format(#mqtt_packet{
#mqtt_packet{ header = #mqtt_packet_header{type = ?CONNACK, retain = true, dup = 0},
header = #mqtt_packet_header{type = ?CONNACK, retain = true, dup = 0}, variable = undefined
variable = undefined })
}, ]),
text io:format("~ts", [
) emqx_packet:format(#mqtt_packet{
header = #mqtt_packet_header{type = ?CONNACK}, variable = 1, payload = <<"payload">>
})
]), ]),
io:format(
"~ts",
[
emqx_packet:format(
#mqtt_packet{
header = #mqtt_packet_header{type = ?CONNACK},
variable = 1,
payload = <<"payload">>
},
text
)
]
),
io:format("~ts", [ io:format("~ts", [
emqx_packet:format( emqx_packet:format(
?CONNECT_PACKET( ?CONNECT_PACKET(#mqtt_packet_connect{
#mqtt_packet_connect{ will_flag = true,
will_flag = true, will_retain = true,
will_retain = true, will_qos = ?QOS_2,
will_qos = ?QOS_2, will_topic = <<"topic">>,
will_topic = <<"topic">>, will_payload = <<"payload">>
will_payload = <<"payload">> })
}
),
text
) )
]), ]),
io:format("~ts", [ io:format("~ts", [
emqx_packet:format(?CONNECT_PACKET(#mqtt_packet_connect{password = password}), text) emqx_packet:format(?CONNECT_PACKET(#mqtt_packet_connect{password = password}))
]), ]),
io:format("~ts", [emqx_packet:format(?CONNACK_PACKET(?CONNACK_SERVER), text)]), io:format("~ts", [emqx_packet:format(?CONNACK_PACKET(?CONNACK_SERVER))]),
io:format("~ts", [emqx_packet:format(?PUBLISH_PACKET(?QOS_1, 1), text)]), io:format("~ts", [emqx_packet:format(?PUBLISH_PACKET(?QOS_1, 1))]),
io:format("~ts", [emqx_packet:format(?PUBLISH_PACKET(?QOS_2, <<"topic">>, 10, <<"payload">>))]),
io:format("~ts", [emqx_packet:format(?PUBACK_PACKET(?PUBACK, 98))]),
io:format("~ts", [emqx_packet:format(?PUBREL_PACKET(99))]),
io:format("~ts", [ io:format("~ts", [
emqx_packet:format(?PUBLISH_PACKET(?QOS_2, <<"topic">>, 10, <<"payload">>), text) emqx_packet:format(?SUBSCRIBE_PACKET(15, [{<<"topic">>, ?QOS_0}, {<<"topic1">>, ?QOS_1}]))
]), ]),
io:format("~ts", [emqx_packet:format(?PUBACK_PACKET(?PUBACK, 98), text)]), io:format("~ts", [emqx_packet:format(?SUBACK_PACKET(40, [?QOS_0, ?QOS_1]))]),
io:format("~ts", [emqx_packet:format(?PUBREL_PACKET(99), text)]), io:format("~ts", [emqx_packet:format(?UNSUBSCRIBE_PACKET(89, [<<"t">>, <<"t2">>]))]),
io:format("~ts", [ io:format("~ts", [emqx_packet:format(?UNSUBACK_PACKET(90))]),
emqx_packet:format( io:format("~ts", [emqx_packet:format(?DISCONNECT_PACKET(128))]).
?SUBSCRIBE_PACKET(15, [{<<"topic">>, ?QOS_0}, {<<"topic1">>, ?QOS_1}]), text
)
]),
io:format("~ts", [emqx_packet:format(?SUBACK_PACKET(40, [?QOS_0, ?QOS_1]), text)]),
io:format("~ts", [emqx_packet:format(?UNSUBSCRIBE_PACKET(89, [<<"t">>, <<"t2">>]), text)]),
io:format("~ts", [emqx_packet:format(?UNSUBACK_PACKET(90), text)]),
io:format("~ts", [emqx_packet:format(?DISCONNECT_PACKET(128), text)]).
t_parse_empty_publish(_) -> t_parse_empty_publish(_) ->
%% 52: 0011(type=PUBLISH) 0100 (QoS=2) %% 52: 0011(type=PUBLISH) 0100 (QoS=2)

View File

@ -573,7 +573,7 @@ app_specs(Opts) ->
cluster() -> cluster() ->
ExtraConf = "\n durable_storage.messages.n_sites = 2", ExtraConf = "\n durable_storage.messages.n_sites = 2",
Spec = #{apps => app_specs(#{extra_emqx_conf => ExtraConf})}, Spec = #{role => core, apps => app_specs(#{extra_emqx_conf => ExtraConf})},
[ [
{persistent_messages_SUITE1, Spec}, {persistent_messages_SUITE1, Spec},
{persistent_messages_SUITE2, Spec} {persistent_messages_SUITE2, Spec}

View File

@ -64,28 +64,18 @@ init_per_group(routing_schema_v2, Config) ->
init_per_group(batch_sync_on, Config) -> init_per_group(batch_sync_on, Config) ->
[{emqx_config, "broker.routing.batch_sync.enable_on = all"} | Config]; [{emqx_config, "broker.routing.batch_sync.enable_on = all"} | Config];
init_per_group(batch_sync_replicants, Config) -> init_per_group(batch_sync_replicants, Config) ->
case emqx_cth_suite:skip_if_oss() of [{emqx_config, "broker.routing.batch_sync.enable_on = replicant"} | Config];
false ->
[{emqx_config, "broker.routing.batch_sync.enable_on = replicant"} | Config];
True ->
True
end;
init_per_group(batch_sync_off, Config) -> init_per_group(batch_sync_off, Config) ->
[{emqx_config, "broker.routing.batch_sync.enable_on = none"} | Config]; [{emqx_config, "broker.routing.batch_sync.enable_on = none"} | Config];
init_per_group(cluster, Config) -> init_per_group(cluster, Config) ->
case emqx_cth_suite:skip_if_oss() of WorkDir = emqx_cth_suite:work_dir(Config),
false -> NodeSpecs = [
WorkDir = emqx_cth_suite:work_dir(Config), {emqx_routing_SUITE1, #{apps => [mk_emqx_appspec(1, Config)], role => core}},
NodeSpecs = [ {emqx_routing_SUITE2, #{apps => [mk_emqx_appspec(2, Config)], role => core}},
{emqx_routing_SUITE1, #{apps => [mk_emqx_appspec(1, Config)], role => core}}, {emqx_routing_SUITE3, #{apps => [mk_emqx_appspec(3, Config)], role => replicant}}
{emqx_routing_SUITE2, #{apps => [mk_emqx_appspec(2, Config)], role => core}}, ],
{emqx_routing_SUITE3, #{apps => [mk_emqx_appspec(3, Config)], role => replicant}} Nodes = emqx_cth_cluster:start(NodeSpecs, #{work_dir => WorkDir}),
], [{cluster, Nodes} | Config];
Nodes = emqx_cth_cluster:start(NodeSpecs, #{work_dir => WorkDir}),
[{cluster, Nodes} | Config];
True ->
True
end;
init_per_group(GroupName, Config) when init_per_group(GroupName, Config) when
GroupName =:= single_batch_on; GroupName =:= single_batch_on;
GroupName =:= single GroupName =:= single

View File

@ -1247,7 +1247,7 @@ recv_msgs(Count, Msgs) ->
start_peer(Name, Port) -> start_peer(Name, Port) ->
{ok, Node} = emqx_cth_peer:start_link( {ok, Node} = emqx_cth_peer:start_link(
Name, Name,
emqx_common_test_helpers:ebin_path() ebin_path()
), ),
pong = net_adm:ping(Node), pong = net_adm:ping(Node),
setup_node(Node, Port), setup_node(Node, Port),
@ -1261,6 +1261,9 @@ host() ->
[_, Host] = string:tokens(atom_to_list(node()), "@"), [_, Host] = string:tokens(atom_to_list(node()), "@"),
Host. Host.
ebin_path() ->
["-pa" | code:get_path()].
setup_node(Node, Port) -> setup_node(Node, Port) ->
EnvHandler = EnvHandler =
fun(_) -> fun(_) ->

View File

@ -28,7 +28,7 @@
-type authenticator_id() :: binary(). -type authenticator_id() :: binary().
-define(AUTHN_RESOURCE_GROUP, <<"authn">>). -define(AUTHN_RESOURCE_GROUP, <<"emqx_authn">>).
%% VAR_NS_CLIENT_ATTRS is added here because it can be initialized before authn. %% VAR_NS_CLIENT_ATTRS is added here because it can be initialized before authn.
%% NOTE: authn return may add more to (or even overwrite) client_attrs. %% NOTE: authn return may add more to (or even overwrite) client_attrs.

View File

@ -156,7 +156,7 @@
count => 1 count => 1
}). }).
-define(AUTHZ_RESOURCE_GROUP, <<"authz">>). -define(AUTHZ_RESOURCE_GROUP, <<"emqx_authz">>).
-define(AUTHZ_FEATURES, [rich_actions]). -define(AUTHZ_FEATURES, [rich_actions]).

View File

@ -1,7 +1,7 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
{application, emqx_auth, [ {application, emqx_auth, [
{description, "EMQX Authentication and authorization"}, {description, "EMQX Authentication and authorization"},
{vsn, "0.3.4"}, {vsn, "0.3.3"},
{modules, []}, {modules, []},
{registered, [emqx_auth_sup]}, {registered, [emqx_auth_sup]},
{applications, [ {applications, [

View File

@ -203,7 +203,6 @@ common_fields() ->
enable(type) -> boolean(); enable(type) -> boolean();
enable(default) -> true; enable(default) -> true;
enable(importance) -> ?IMPORTANCE_NO_DOC;
enable(desc) -> ?DESC(?FUNCTION_NAME); enable(desc) -> ?DESC(?FUNCTION_NAME);
enable(_) -> undefined. enable(_) -> undefined.

View File

@ -198,7 +198,7 @@ qos_from_opts(Opts) ->
) )
end end
catch catch
throw:{bad_qos, QoS} -> {bad_qos, QoS} ->
throw(#{ throw(#{
reason => invalid_authorization_qos, reason => invalid_authorization_qos,
qos => QoS qos => QoS

View File

@ -170,12 +170,7 @@ api_authz_refs() ->
authz_common_fields(Type) -> authz_common_fields(Type) ->
[ [
{type, ?HOCON(Type, #{required => true, desc => ?DESC(type)})}, {type, ?HOCON(Type, #{required => true, desc => ?DESC(type)})},
{enable, {enable, ?HOCON(boolean(), #{default => true, desc => ?DESC(enable)})}
?HOCON(boolean(), #{
default => true,
importance => ?IMPORTANCE_NO_DOC,
desc => ?DESC(enable)
})}
]. ].
source_types() -> source_types() ->

View File

@ -16,9 +16,6 @@
-module(emqx_authz_utils). -module(emqx_authz_utils).
-feature(maybe_expr, enable).
-include_lib("emqx/include/emqx_placeholder.hrl").
-include_lib("emqx_authz.hrl"). -include_lib("emqx_authz.hrl").
-include_lib("snabbkaffe/include/trace.hrl"). -include_lib("snabbkaffe/include/trace.hrl").
@ -31,7 +28,7 @@
remove_resource/1, remove_resource/1,
update_config/2, update_config/2,
vars_for_rule_query/2, vars_for_rule_query/2,
do_authorize/6 parse_rule_from_row/2
]). ]).
-export([ -export([
@ -136,18 +133,14 @@ content_type(Headers) when is_list(Headers) ->
-define(RAW_RULE_KEYS, [<<"permission">>, <<"action">>, <<"topic">>, <<"qos">>, <<"retain">>]). -define(RAW_RULE_KEYS, [<<"permission">>, <<"action">>, <<"topic">>, <<"qos">>, <<"retain">>]).
-spec parse_rule_from_row([binary()], [binary()] | map()) ->
{ok, emqx_authz_rule:rule()} | {error, term()}.
parse_rule_from_row(_ColumnNames, RuleMap = #{}) ->
case emqx_authz_rule_raw:parse_rule(RuleMap) of
{ok, {Permission, Action, Topics}} ->
{ok, emqx_authz_rule:compile({Permission, all, Action, Topics})};
{error, Reason} ->
{error, Reason}
end;
parse_rule_from_row(ColumnNames, Row) -> parse_rule_from_row(ColumnNames, Row) ->
RuleMap = maps:with(?RAW_RULE_KEYS, maps:from_list(lists:zip(ColumnNames, to_list(Row)))), RuleRaw = maps:with(?RAW_RULE_KEYS, maps:from_list(lists:zip(ColumnNames, to_list(Row)))),
parse_rule_from_row(ColumnNames, RuleMap). case emqx_authz_rule_raw:parse_rule(RuleRaw) of
{ok, {Permission, Action, Topics}} ->
emqx_authz_rule:compile({Permission, all, Action, Topics});
{error, Reason} ->
error(Reason)
end.
vars_for_rule_query(Client, ?authz_action(PubSub, Qos) = Action) -> vars_for_rule_query(Client, ?authz_action(PubSub, Qos) = Action) ->
Client#{ Client#{
@ -164,39 +157,3 @@ to_list(Tuple) when is_tuple(Tuple) ->
tuple_to_list(Tuple); tuple_to_list(Tuple);
to_list(List) when is_list(List) -> to_list(List) when is_list(List) ->
List. List.
do_authorize(Type, Client, Action, Topic, ColumnNames, Row) ->
try
maybe
{ok, Rule} ?= parse_rule_from_row(ColumnNames, Row),
{matched, Permission} ?= emqx_authz_rule:match(Client, Action, Topic, Rule),
{matched, Permission}
else
nomatch ->
nomatch;
{error, Reason0} ->
log_match_rule_error(Type, Row, Reason0),
nomatch
end
catch
throw:Reason1 ->
log_match_rule_error(Type, Row, Reason1),
nomatch
end.
log_match_rule_error(Type, Row, Reason0) ->
Msg0 = #{
msg => "match_rule_error",
rule => Row,
type => Type
},
Msg1 =
case is_map(Reason0) of
true -> maps:merge(Msg0, Reason0);
false -> Msg0#{reason => Reason0}
end,
?SLOG(
error,
Msg1,
#{tag => "AUTHZ"}
).

View File

@ -122,6 +122,14 @@ t_union_member_selector(_) ->
}, },
check(BadMechanism) check(BadMechanism)
), ),
BadCombination = Base#{<<"mechanism">> => <<"scram">>, <<"backend">> => <<"http">>},
?assertThrow(
#{
reason := "unknown_mechanism",
expected := "password_based"
},
check(BadCombination)
),
ok. ok.
t_http_auth_selector(_) -> t_http_auth_selector(_) ->

View File

@ -118,8 +118,8 @@ mk_cluster_spec(Opts) ->
Node1Apps = Apps ++ [{emqx_dashboard, "dashboard.listeners.http {enable=true,bind=18083}"}], Node1Apps = Apps ++ [{emqx_dashboard, "dashboard.listeners.http {enable=true,bind=18083}"}],
Node2Apps = Apps, Node2Apps = Apps,
[ [
{emqx_authz_api_cluster_SUITE1, Opts#{apps => Node1Apps}}, {emqx_authz_api_cluster_SUITE1, Opts#{role => core, apps => Node1Apps}},
{emqx_authz_api_cluster_SUITE2, Opts#{apps => Node2Apps}} {emqx_authz_api_cluster_SUITE2, Opts#{role => core, apps => Node2Apps}}
]. ].
request(Method, URL, Body, Config) -> request(Method, URL, Body, Config) ->

View File

@ -22,15 +22,8 @@
-define(AUTHN_MECHANISM, password_based). -define(AUTHN_MECHANISM, password_based).
-define(AUTHN_MECHANISM_BIN, <<"password_based">>). -define(AUTHN_MECHANISM_BIN, <<"password_based">>).
-define(AUTHN_MECHANISM_SCRAM, scram).
-define(AUTHN_MECHANISM_SCRAM_BIN, <<"scram">>).
-define(AUTHN_BACKEND, http). -define(AUTHN_BACKEND, http).
-define(AUTHN_BACKEND_BIN, <<"http">>). -define(AUTHN_BACKEND_BIN, <<"http">>).
-define(AUTHN_TYPE, {?AUTHN_MECHANISM, ?AUTHN_BACKEND}). -define(AUTHN_TYPE, {?AUTHN_MECHANISM, ?AUTHN_BACKEND}).
-define(AUTHN_TYPE_SCRAM, {?AUTHN_MECHANISM_SCRAM, ?AUTHN_BACKEND}).
-define(AUTHN_DATA_FIELDS, [is_superuser, client_attrs, expire_at, acl]).
-endif. -endif.

View File

@ -1,7 +1,7 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
{application, emqx_auth_http, [ {application, emqx_auth_http, [
{description, "EMQX External HTTP API Authentication and Authorization"}, {description, "EMQX External HTTP API Authentication and Authorization"},
{vsn, "0.3.1"}, {vsn, "0.3.0"},
{registered, []}, {registered, []},
{mod, {emqx_auth_http_app, []}}, {mod, {emqx_auth_http_app, []}},
{applications, [ {applications, [

View File

@ -25,12 +25,10 @@
start(_StartType, _StartArgs) -> start(_StartType, _StartArgs) ->
ok = emqx_authz:register_source(?AUTHZ_TYPE, emqx_authz_http), ok = emqx_authz:register_source(?AUTHZ_TYPE, emqx_authz_http),
ok = emqx_authn:register_provider(?AUTHN_TYPE, emqx_authn_http), ok = emqx_authn:register_provider(?AUTHN_TYPE, emqx_authn_http),
ok = emqx_authn:register_provider(?AUTHN_TYPE_SCRAM, emqx_authn_scram_restapi),
{ok, Sup} = emqx_auth_http_sup:start_link(), {ok, Sup} = emqx_auth_http_sup:start_link(),
{ok, Sup}. {ok, Sup}.
stop(_State) -> stop(_State) ->
ok = emqx_authn:deregister_provider(?AUTHN_TYPE), ok = emqx_authn:deregister_provider(?AUTHN_TYPE),
ok = emqx_authn:deregister_provider(?AUTHN_TYPE_SCRAM),
ok = emqx_authz:unregister_source(?AUTHZ_TYPE), ok = emqx_authz:unregister_source(?AUTHZ_TYPE),
ok. ok.

View File

@ -28,15 +28,6 @@
destroy/1 destroy/1
]). ]).
-export([
with_validated_config/2,
generate_request/2,
request_for_log/2,
response_for_log/1,
extract_auth_data/2,
safely_parse_body/2
]).
-define(DEFAULT_CONTENT_TYPE, <<"application/json">>). -define(DEFAULT_CONTENT_TYPE, <<"application/json">>).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -196,14 +187,34 @@ handle_response(Headers, Body) ->
case safely_parse_body(ContentType, Body) of case safely_parse_body(ContentType, Body) of
{ok, NBody} -> {ok, NBody} ->
body_to_auth_data(NBody); body_to_auth_data(NBody);
{error, _Reason} -> {error, Reason} ->
?TRACE_AUTHN_PROVIDER(
error,
"parse_http_response_failed",
#{content_type => ContentType, body => Body, reason => Reason}
),
ignore ignore
end. end.
body_to_auth_data(Body) -> body_to_auth_data(Body) ->
case maps:get(<<"result">>, Body, <<"ignore">>) of case maps:get(<<"result">>, Body, <<"ignore">>) of
<<"allow">> -> <<"allow">> ->
extract_auth_data(http, Body); IsSuperuser = emqx_authn_utils:is_superuser(Body),
Attrs = emqx_authn_utils:client_attrs(Body),
try
ExpireAt = expire_at(Body),
ACL = acl(ExpireAt, Body),
Result = merge_maps([ExpireAt, IsSuperuser, ACL, Attrs]),
{ok, Result}
catch
throw:{bad_acl_rule, Reason} ->
%% it's a invalid token, so ok to log
?TRACE_AUTHN_PROVIDER("bad_acl_rule", Reason#{http_body => Body}),
{error, bad_username_or_password};
throw:Reason ->
?TRACE_AUTHN_PROVIDER("bad_response_body", Reason#{http_body => Body}),
{error, bad_username_or_password}
end;
<<"deny">> -> <<"deny">> ->
{error, not_authorized}; {error, not_authorized};
<<"ignore">> -> <<"ignore">> ->
@ -212,24 +223,6 @@ body_to_auth_data(Body) ->
ignore ignore
end. end.
extract_auth_data(Source, Body) ->
IsSuperuser = emqx_authn_utils:is_superuser(Body),
Attrs = emqx_authn_utils:client_attrs(Body),
try
ExpireAt = expire_at(Body),
ACL = acl(ExpireAt, Source, Body),
Result = merge_maps([ExpireAt, IsSuperuser, ACL, Attrs]),
{ok, Result}
catch
throw:{bad_acl_rule, Reason} ->
%% it's a invalid token, so ok to log
?TRACE_AUTHN_PROVIDER("bad_acl_rule", Reason#{http_body => Body}),
{error, bad_username_or_password};
throw:Reason ->
?TRACE_AUTHN_PROVIDER("bad_response_body", Reason#{http_body => Body}),
{error, bad_username_or_password}
end.
merge_maps([]) -> #{}; merge_maps([]) -> #{};
merge_maps([Map | Maps]) -> maps:merge(Map, merge_maps(Maps)). merge_maps([Map | Maps]) -> maps:merge(Map, merge_maps(Maps)).
@ -268,43 +261,40 @@ expire_sec(#{<<"expire_at">> := _}) ->
expire_sec(_) -> expire_sec(_) ->
undefined. undefined.
acl(#{expire_at := ExpireTimeMs}, Source, #{<<"acl">> := Rules}) -> acl(#{expire_at := ExpireTimeMs}, #{<<"acl">> := Rules}) ->
#{ #{
acl => #{ acl => #{
source_for_logging => Source, source_for_logging => http,
rules => emqx_authz_rule_raw:parse_and_compile_rules(Rules), rules => emqx_authz_rule_raw:parse_and_compile_rules(Rules),
%% It's seconds level precision (like JWT) for authz %% It's seconds level precision (like JWT) for authz
%% see emqx_authz_client_info:check/1 %% see emqx_authz_client_info:check/1
expire => erlang:convert_time_unit(ExpireTimeMs, millisecond, second) expire => erlang:convert_time_unit(ExpireTimeMs, millisecond, second)
} }
}; };
acl(_NoExpire, Source, #{<<"acl">> := Rules}) -> acl(_NoExpire, #{<<"acl">> := Rules}) ->
#{ #{
acl => #{ acl => #{
source_for_logging => Source, source_for_logging => http,
rules => emqx_authz_rule_raw:parse_and_compile_rules(Rules) rules => emqx_authz_rule_raw:parse_and_compile_rules(Rules)
} }
}; };
acl(_, _, _) -> acl(_, _) ->
#{}. #{}.
safely_parse_body(ContentType, Body) -> safely_parse_body(ContentType, Body) ->
try try
parse_body(ContentType, Body) parse_body(ContentType, Body)
catch catch
_Class:Reason -> _Class:_Reason ->
?TRACE_AUTHN_PROVIDER(
error,
"parse_http_response_failed",
#{content_type => ContentType, body => Body, reason => Reason}
),
{error, invalid_body} {error, invalid_body}
end. end.
parse_body(<<"application/json", _/binary>>, Body) -> parse_body(<<"application/json", _/binary>>, Body) ->
{ok, emqx_utils_json:decode(Body, [return_maps])}; {ok, emqx_utils_json:decode(Body, [return_maps])};
parse_body(<<"application/x-www-form-urlencoded", _/binary>>, Body) -> parse_body(<<"application/x-www-form-urlencoded", _/binary>>, Body) ->
NBody = maps:from_list(cow_qs:parse_qs(Body)), Flags = [<<"result">>, <<"is_superuser">>],
RawMap = maps:from_list(cow_qs:parse_qs(Body)),
NBody = maps:with(Flags, RawMap),
{ok, NBody}; {ok, NBody};
parse_body(ContentType, _) -> parse_body(ContentType, _) ->
{error, {unsupported_content_type, ContentType}}. {error, {unsupported_content_type, ContentType}}.

View File

@ -27,8 +27,6 @@
namespace/0 namespace/0
]). ]).
-export([url/1, headers/1, headers_no_content_type/1, request_timeout/1]).
-include("emqx_auth_http.hrl"). -include("emqx_auth_http.hrl").
-include_lib("emqx_auth/include/emqx_authn.hrl"). -include_lib("emqx_auth/include/emqx_authn.hrl").
-include_lib("hocon/include/hoconsc.hrl"). -include_lib("hocon/include/hoconsc.hrl").
@ -63,6 +61,12 @@ select_union_member(
got => Else got => Else
}) })
end; end;
select_union_member(#{<<"backend">> := ?AUTHN_BACKEND_BIN}) ->
throw(#{
reason => "unknown_mechanism",
expected => "password_based",
got => undefined
});
select_union_member(_Value) -> select_union_member(_Value) ->
undefined. undefined.

View File

@ -1,161 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
%%--------------------------------------------------------------------
%% Note:
%% This is not an implementation of the RFC 7804:
%% Salted Challenge Response HTTP Authentication Mechanism.
%% This backend is an implementation of scram,
%% which uses an external web resource as a source of user information.
-module(emqx_authn_scram_restapi).
-feature(maybe_expr, enable).
-include("emqx_auth_http.hrl").
-include_lib("emqx/include/logger.hrl").
-include_lib("emqx_auth/include/emqx_authn.hrl").
-behaviour(emqx_authn_provider).
-export([
create/2,
update/2,
authenticate/2,
destroy/1
]).
-define(REQUIRED_USER_INFO_KEYS, [
<<"stored_key">>,
<<"server_key">>,
<<"salt">>
]).
%%------------------------------------------------------------------------------
%% APIs
%%------------------------------------------------------------------------------
create(_AuthenticatorID, Config) ->
create(Config).
create(Config0) ->
emqx_authn_http:with_validated_config(Config0, fun(Config, State) ->
ResourceId = emqx_authn_utils:make_resource_id(?MODULE),
% {Config, State} = parse_config(Config0),
{ok, _Data} = emqx_authn_utils:create_resource(
ResourceId,
emqx_bridge_http_connector,
Config
),
{ok, merge_scram_conf(Config, State#{resource_id => ResourceId})}
end).
update(Config0, #{resource_id := ResourceId} = _State) ->
emqx_authn_http:with_validated_config(Config0, fun(Config, NState) ->
% {Config, NState} = parse_config(Config0),
case emqx_authn_utils:update_resource(emqx_bridge_http_connector, Config, ResourceId) of
{error, Reason} ->
error({load_config_error, Reason});
{ok, _} ->
{ok, merge_scram_conf(Config, NState#{resource_id => ResourceId})}
end
end).
authenticate(
#{
auth_method := AuthMethod,
auth_data := AuthData,
auth_cache := AuthCache
} = Credential,
State
) ->
RetrieveFun = fun(Username) ->
retrieve(Username, Credential, State)
end,
OnErrFun = fun(Msg, Reason) ->
?TRACE_AUTHN_PROVIDER(Msg, #{
reason => Reason
})
end,
emqx_utils_scram:authenticate(
AuthMethod, AuthData, AuthCache, State, RetrieveFun, OnErrFun, ?AUTHN_DATA_FIELDS
);
authenticate(_Credential, _State) ->
ignore.
destroy(#{resource_id := ResourceId}) ->
_ = emqx_resource:remove_local(ResourceId),
ok.
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
retrieve(
Username,
Credential,
#{
resource_id := ResourceId,
method := Method,
request_timeout := RequestTimeout
} = State
) ->
Request = emqx_authn_http:generate_request(Credential#{username := Username}, State),
Response = emqx_resource:simple_sync_query(ResourceId, {Method, Request, RequestTimeout}),
?TRACE_AUTHN_PROVIDER("scram_restapi_response", #{
request => emqx_authn_http:request_for_log(Credential, State),
response => emqx_authn_http:response_for_log(Response),
resource => ResourceId
}),
case Response of
{ok, 200, Headers, Body} ->
handle_response(Headers, Body);
{ok, _StatusCode, _Headers} ->
{error, bad_response};
{ok, _StatusCode, _Headers, _Body} ->
{error, bad_response};
{error, _Reason} = Error ->
Error
end.
handle_response(Headers, Body) ->
ContentType = proplists:get_value(<<"content-type">>, Headers),
maybe
{ok, NBody} ?= emqx_authn_http:safely_parse_body(ContentType, Body),
{ok, UserInfo} ?= body_to_user_info(NBody),
{ok, AuthData} ?= emqx_authn_http:extract_auth_data(scram_restapi, NBody),
{ok, maps:merge(AuthData, UserInfo)}
end.
body_to_user_info(Body) ->
Required0 = maps:with(?REQUIRED_USER_INFO_KEYS, Body),
case maps:size(Required0) =:= erlang:length(?REQUIRED_USER_INFO_KEYS) of
true ->
case safely_convert_hex(Required0) of
{ok, Required} ->
{ok, emqx_utils_maps:safe_atom_key_map(Required)};
Error ->
?TRACE_AUTHN_PROVIDER("decode_keys_failed", #{http_body => Body}),
Error
end;
_ ->
?TRACE_AUTHN_PROVIDER("missing_requried_keys", #{http_body => Body}),
{error, bad_response}
end.
safely_convert_hex(Required) ->
try
{ok,
maps:map(
fun(_Key, Hex) ->
binary:decode_hex(Hex)
end,
Required
)}
catch
_Class:Reason ->
{error, Reason}
end.
merge_scram_conf(Conf, State) ->
maps:merge(maps:with([algorithm, iteration_count], Conf), State).

View File

@ -1,81 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
%%--------------------------------------------------------------------
-module(emqx_authn_scram_restapi_schema).
-behaviour(emqx_authn_schema).
-export([
fields/1,
validations/0,
desc/1,
refs/0,
select_union_member/1,
namespace/0
]).
-include("emqx_auth_http.hrl").
-include_lib("emqx_auth/include/emqx_authn.hrl").
-include_lib("hocon/include/hoconsc.hrl").
namespace() -> "authn".
refs() ->
[?R_REF(scram_restapi_get), ?R_REF(scram_restapi_post)].
select_union_member(
#{<<"mechanism">> := ?AUTHN_MECHANISM_SCRAM_BIN, <<"backend">> := ?AUTHN_BACKEND_BIN} = Value
) ->
case maps:get(<<"method">>, Value, undefined) of
<<"get">> ->
[?R_REF(scram_restapi_get)];
<<"post">> ->
[?R_REF(scram_restapi_post)];
Else ->
throw(#{
reason => "unknown_http_method",
expected => "get | post",
field_name => method,
got => Else
})
end;
select_union_member(_Value) ->
undefined.
fields(scram_restapi_get) ->
[
{method, #{type => get, required => true, desc => ?DESC(emqx_authn_http_schema, method)}},
{headers, fun emqx_authn_http_schema:headers_no_content_type/1}
] ++ common_fields();
fields(scram_restapi_post) ->
[
{method, #{type => post, required => true, desc => ?DESC(emqx_authn_http_schema, method)}},
{headers, fun emqx_authn_http_schema:headers/1}
] ++ common_fields().
desc(scram_restapi_get) ->
?DESC(emqx_authn_http_schema, get);
desc(scram_restapi_post) ->
?DESC(emqx_authn_http_schema, post);
desc(_) ->
undefined.
validations() ->
emqx_authn_http_schema:validations().
common_fields() ->
emqx_authn_schema:common_fields() ++
[
{mechanism, emqx_authn_schema:mechanism(?AUTHN_MECHANISM_SCRAM)},
{backend, emqx_authn_schema:backend(?AUTHN_BACKEND)},
{algorithm, fun emqx_authn_scram_mnesia_schema:algorithm/1},
{iteration_count, fun emqx_authn_scram_mnesia_schema:iteration_count/1},
{url, fun emqx_authn_http_schema:url/1},
{body,
hoconsc:mk(typerefl:alias("map", map([{fuzzy, term(), binary()}])), #{
required => false, desc => ?DESC(emqx_authn_http_schema, body)
})},
{request_timeout, fun emqx_authn_http_schema:request_timeout/1}
] ++
proplists:delete(pool_type, emqx_bridge_http_connector:fields(config)).

View File

@ -67,11 +67,7 @@ description() ->
create(Config) -> create(Config) ->
NConfig = parse_config(Config), NConfig = parse_config(Config),
ResourceId = emqx_authn_utils:make_resource_id(?MODULE), ResourceId = emqx_authn_utils:make_resource_id(?MODULE),
{ok, _Data} = emqx_authz_utils:create_resource( {ok, _Data} = emqx_authz_utils:create_resource(ResourceId, emqx_bridge_http_connector, NConfig),
ResourceId,
emqx_bridge_http_connector,
NConfig
),
NConfig#{annotations => #{id => ResourceId}}. NConfig#{annotations => #{id => ResourceId}}.
update(Config) -> update(Config) ->

View File

@ -1,509 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
%%--------------------------------------------------------------------
-module(emqx_authn_scram_restapi_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("emqx/include/emqx_mqtt.hrl").
-include_lib("emqx_auth/include/emqx_authn.hrl").
-define(PATH, [authentication]).
-define(HTTP_PORT, 34333).
-define(HTTP_PATH, "/user/[...]").
-define(ALGORITHM, sha512).
-define(ALGORITHM_STR, <<"sha512">>).
-define(ITERATION_COUNT, 4096).
-define(T_ACL_USERNAME, <<"username">>).
-define(T_ACL_PASSWORD, <<"password">>).
-include_lib("emqx/include/emqx_placeholder.hrl").
all() ->
case emqx_release:edition() of
ce ->
[];
_ ->
emqx_common_test_helpers:all(?MODULE)
end.
init_per_suite(Config) ->
Apps = emqx_cth_suite:start([cowboy, emqx, emqx_conf, emqx_auth, emqx_auth_http], #{
work_dir => ?config(priv_dir, Config)
}),
IdleTimeout = emqx_config:get([mqtt, idle_timeout]),
[{apps, Apps}, {idle_timeout, IdleTimeout} | Config].
end_per_suite(Config) ->
ok = emqx_config:put([mqtt, idle_timeout], ?config(idle_timeout, Config)),
emqx_authn_test_lib:delete_authenticators(
[authentication],
?GLOBAL
),
ok = emqx_cth_suite:stop(?config(apps, Config)),
ok.
init_per_testcase(_Case, Config) ->
{ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000),
emqx_authn_test_lib:delete_authenticators(
[authentication],
?GLOBAL
),
{ok, _} = emqx_authn_scram_restapi_test_server:start_link(?HTTP_PORT, ?HTTP_PATH),
Config.
end_per_testcase(_Case, _Config) ->
ok = emqx_authn_scram_restapi_test_server:stop().
%%------------------------------------------------------------------------------
%% Tests
%%------------------------------------------------------------------------------
t_create(_Config) ->
AuthConfig = raw_config(),
{ok, _} = emqx:update_config(
?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}
),
{ok, [#{provider := emqx_authn_scram_restapi}]} = emqx_authn_chains:list_authenticators(
?GLOBAL
).
t_create_invalid(_Config) ->
AuthConfig = raw_config(),
InvalidConfigs =
[
AuthConfig#{<<"headers">> => []},
AuthConfig#{<<"method">> => <<"delete">>},
AuthConfig#{<<"url">> => <<"localhost">>},
AuthConfig#{<<"url">> => <<"http://foo.com/xxx#fragment">>},
AuthConfig#{<<"url">> => <<"http://${foo}.com/xxx">>},
AuthConfig#{<<"url">> => <<"//foo.com/xxx">>},
AuthConfig#{<<"algorithm">> => <<"sha128">>}
],
lists:foreach(
fun(Config) ->
ct:pal("creating authenticator with invalid config: ~p", [Config]),
{error, _} =
try
emqx:update_config(
?PATH,
{create_authenticator, ?GLOBAL, Config}
)
catch
throw:Error ->
{error, Error}
end,
?assertEqual(
{error, {not_found, {chain, ?GLOBAL}}},
emqx_authn_chains:list_authenticators(?GLOBAL)
)
end,
InvalidConfigs
).
t_authenticate(_Config) ->
Username = <<"u">>,
Password = <<"p">>,
set_user_handler(Username, Password),
init_auth(),
ok = emqx_config:put([mqtt, idle_timeout], 500),
{ok, Pid} = create_connection(Username, Password),
emqx_authn_mqtt_test_client:stop(Pid).
t_authenticate_bad_props(_Config) ->
Username = <<"u">>,
Password = <<"p">>,
set_user_handler(Username, Password),
init_auth(),
{ok, Pid} = emqx_authn_mqtt_test_client:start_link("127.0.0.1", 1883),
ConnectPacket = ?CONNECT_PACKET(
#mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V5,
properties = #{
'Authentication-Method' => <<"SCRAM-SHA-512">>
}
}
),
ok = emqx_authn_mqtt_test_client:send(Pid, ConnectPacket),
?CONNACK_PACKET(?RC_NOT_AUTHORIZED) = receive_packet().
t_authenticate_bad_username(_Config) ->
Username = <<"u">>,
Password = <<"p">>,
set_user_handler(Username, Password),
init_auth(),
{ok, Pid} = emqx_authn_mqtt_test_client:start_link("127.0.0.1", 1883),
ClientFirstMessage = esasl_scram:client_first_message(<<"badusername">>),
ConnectPacket = ?CONNECT_PACKET(
#mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V5,
properties = #{
'Authentication-Method' => <<"SCRAM-SHA-512">>,
'Authentication-Data' => ClientFirstMessage
}
}
),
ok = emqx_authn_mqtt_test_client:send(Pid, ConnectPacket),
?CONNACK_PACKET(?RC_NOT_AUTHORIZED) = receive_packet().
t_authenticate_bad_password(_Config) ->
Username = <<"u">>,
Password = <<"p">>,
set_user_handler(Username, Password),
init_auth(),
{ok, Pid} = emqx_authn_mqtt_test_client:start_link("127.0.0.1", 1883),
ClientFirstMessage = esasl_scram:client_first_message(Username),
ConnectPacket = ?CONNECT_PACKET(
#mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V5,
properties = #{
'Authentication-Method' => <<"SCRAM-SHA-512">>,
'Authentication-Data' => ClientFirstMessage
}
}
),
ok = emqx_authn_mqtt_test_client:send(Pid, ConnectPacket),
?AUTH_PACKET(
?RC_CONTINUE_AUTHENTICATION,
#{'Authentication-Data' := ServerFirstMessage}
) = receive_packet(),
{continue, ClientFinalMessage, _ClientCache} =
esasl_scram:check_server_first_message(
ServerFirstMessage,
#{
client_first_message => ClientFirstMessage,
password => <<"badpassword">>,
algorithm => ?ALGORITHM
}
),
AuthContinuePacket = ?AUTH_PACKET(
?RC_CONTINUE_AUTHENTICATION,
#{
'Authentication-Method' => <<"SCRAM-SHA-512">>,
'Authentication-Data' => ClientFinalMessage
}
),
ok = emqx_authn_mqtt_test_client:send(Pid, AuthContinuePacket),
?CONNACK_PACKET(?RC_NOT_AUTHORIZED) = receive_packet().
t_destroy(_Config) ->
Username = <<"u">>,
Password = <<"p">>,
set_user_handler(Username, Password),
init_auth(),
ok = emqx_config:put([mqtt, idle_timeout], 500),
{ok, Pid} = emqx_authn_mqtt_test_client:start_link("127.0.0.1", 1883),
ConnectPacket = ?CONNECT_PACKET(
#mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V5,
properties = #{
'Authentication-Method' => <<"SCRAM-SHA-512">>
}
}
),
ok = emqx_authn_mqtt_test_client:send(Pid, ConnectPacket),
ok = ct:sleep(1000),
?CONNACK_PACKET(?RC_NOT_AUTHORIZED) = receive_packet(),
%% emqx_authn_mqtt_test_client:stop(Pid),
emqx_authn_test_lib:delete_authenticators(
[authentication],
?GLOBAL
),
{ok, Pid2} = emqx_authn_mqtt_test_client:start_link("127.0.0.1", 1883),
ok = emqx_authn_mqtt_test_client:send(Pid2, ConnectPacket),
ok = ct:sleep(1000),
?CONNACK_PACKET(
?RC_SUCCESS,
_,
_
) = receive_packet().
t_acl(_Config) ->
init_auth(),
ACL = emqx_authn_http_SUITE:acl_rules(),
set_user_handler(?T_ACL_USERNAME, ?T_ACL_PASSWORD, #{acl => ACL}),
{ok, Pid} = create_connection(?T_ACL_USERNAME, ?T_ACL_PASSWORD),
Cases = [
{allow, <<"http-authn-acl/#">>},
{deny, <<"http-authn-acl/1">>},
{deny, <<"t/#">>}
],
try
lists:foreach(
fun(Case) ->
test_acl(Case, Pid)
end,
Cases
)
after
ok = emqx_authn_mqtt_test_client:stop(Pid)
end.
t_auth_expire(_Config) ->
init_auth(),
ExpireSec = 3,
WaitTime = timer:seconds(ExpireSec + 1),
ACL = emqx_authn_http_SUITE:acl_rules(),
set_user_handler(?T_ACL_USERNAME, ?T_ACL_PASSWORD, #{
acl => ACL,
expire_at =>
erlang:system_time(second) + ExpireSec
}),
{ok, Pid} = create_connection(?T_ACL_USERNAME, ?T_ACL_PASSWORD),
timer:sleep(WaitTime),
?assertEqual(false, erlang:is_process_alive(Pid)).
t_is_superuser() ->
State = init_auth(),
ok = test_is_superuser(State, false),
ok = test_is_superuser(State, true),
ok = test_is_superuser(State, false).
test_is_superuser(State, ExpectedIsSuperuser) ->
Username = <<"u">>,
Password = <<"p">>,
set_user_handler(Username, Password, #{is_superuser => ExpectedIsSuperuser}),
ClientFirstMessage = esasl_scram:client_first_message(Username),
{continue, ServerFirstMessage, ServerCache} =
emqx_authn_scram_restapi:authenticate(
#{
auth_method => <<"SCRAM-SHA-512">>,
auth_data => ClientFirstMessage,
auth_cache => #{}
},
State
),
{continue, ClientFinalMessage, ClientCache} =
esasl_scram:check_server_first_message(
ServerFirstMessage,
#{
client_first_message => ClientFirstMessage,
password => Password,
algorithm => ?ALGORITHM
}
),
{ok, UserInfo1, ServerFinalMessage} =
emqx_authn_scram_restapi:authenticate(
#{
auth_method => <<"SCRAM-SHA-512">>,
auth_data => ClientFinalMessage,
auth_cache => ServerCache
},
State
),
ok = esasl_scram:check_server_final_message(
ServerFinalMessage, ClientCache#{algorithm => ?ALGORITHM}
),
?assertMatch(#{is_superuser := ExpectedIsSuperuser}, UserInfo1).
%%------------------------------------------------------------------------------
%% Helpers
%%------------------------------------------------------------------------------
raw_config() ->
#{
<<"mechanism">> => <<"scram">>,
<<"backend">> => <<"http">>,
<<"enable">> => <<"true">>,
<<"method">> => <<"get">>,
<<"url">> => <<"http://127.0.0.1:34333/user">>,
<<"body">> => #{<<"username">> => ?PH_USERNAME},
<<"headers">> => #{<<"X-Test-Header">> => <<"Test Value">>},
<<"algorithm">> => ?ALGORITHM_STR,
<<"iteration_count">> => ?ITERATION_COUNT
}.
set_user_handler(Username, Password) ->
set_user_handler(Username, Password, #{is_superuser => false}).
set_user_handler(Username, Password, Extra0) ->
%% HTTP Server
Handler = fun(Req0, State) ->
#{
username := Username
} = cowboy_req:match_qs([username], Req0),
UserInfo = make_user_info(Password, ?ALGORITHM, ?ITERATION_COUNT),
Extra = maps:merge(#{is_superuser => false}, Extra0),
Req = cowboy_req:reply(
200,
#{<<"content-type">> => <<"application/json">>},
emqx_utils_json:encode(maps:merge(Extra, UserInfo)),
Req0
),
{ok, Req, State}
end,
ok = emqx_authn_scram_restapi_test_server:set_handler(Handler).
init_auth() ->
init_auth(raw_config()).
init_auth(Config) ->
{ok, _} = emqx:update_config(
?PATH,
{create_authenticator, ?GLOBAL, Config}
),
{ok, [#{state := State}]} = emqx_authn_chains:list_authenticators(?GLOBAL),
State.
make_user_info(Password, Algorithm, IterationCount) ->
{StoredKey, ServerKey, Salt} = esasl_scram:generate_authentication_info(
Password,
#{
algorithm => Algorithm,
iteration_count => IterationCount
}
),
#{
stored_key => binary:encode_hex(StoredKey),
server_key => binary:encode_hex(ServerKey),
salt => binary:encode_hex(Salt)
}.
receive_packet() ->
receive
{packet, Packet} ->
ct:pal("Delivered packet: ~p", [Packet]),
Packet
after 1000 ->
ct:fail("Deliver timeout")
end.
create_connection(Username, Password) ->
{ok, Pid} = emqx_authn_mqtt_test_client:start_link("127.0.0.1", 1883),
ClientFirstMessage = esasl_scram:client_first_message(Username),
ConnectPacket = ?CONNECT_PACKET(
#mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V5,
properties = #{
'Authentication-Method' => <<"SCRAM-SHA-512">>,
'Authentication-Data' => ClientFirstMessage
}
}
),
ok = emqx_authn_mqtt_test_client:send(Pid, ConnectPacket),
%% Intentional sleep to trigger idle timeout for the connection not yet authenticated
ok = ct:sleep(1000),
?AUTH_PACKET(
?RC_CONTINUE_AUTHENTICATION,
#{'Authentication-Data' := ServerFirstMessage}
) = receive_packet(),
{continue, ClientFinalMessage, ClientCache} =
esasl_scram:check_server_first_message(
ServerFirstMessage,
#{
client_first_message => ClientFirstMessage,
password => Password,
algorithm => ?ALGORITHM
}
),
AuthContinuePacket = ?AUTH_PACKET(
?RC_CONTINUE_AUTHENTICATION,
#{
'Authentication-Method' => <<"SCRAM-SHA-512">>,
'Authentication-Data' => ClientFinalMessage
}
),
ok = emqx_authn_mqtt_test_client:send(Pid, AuthContinuePacket),
?CONNACK_PACKET(
?RC_SUCCESS,
_,
#{'Authentication-Data' := ServerFinalMessage}
) = receive_packet(),
ok = esasl_scram:check_server_final_message(
ServerFinalMessage, ClientCache#{algorithm => ?ALGORITHM}
),
{ok, Pid}.
test_acl({allow, Topic}, C) ->
?assertMatch(
[0],
send_subscribe(C, Topic)
);
test_acl({deny, Topic}, C) ->
?assertMatch(
[?RC_NOT_AUTHORIZED],
send_subscribe(C, Topic)
).
send_subscribe(Client, Topic) ->
TopicOpts = #{nl => 0, rap => 0, rh => 0, qos => 0},
Packet = ?SUBSCRIBE_PACKET(1, [{Topic, TopicOpts}]),
emqx_authn_mqtt_test_client:send(Client, Packet),
timer:sleep(200),
?SUBACK_PACKET(1, ReasonCode) = receive_packet(),
ReasonCode.

View File

@ -1,115 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
%%--------------------------------------------------------------------
-module(emqx_authn_scram_restapi_test_server).
-behaviour(supervisor).
-behaviour(cowboy_handler).
% cowboy_server callbacks
-export([init/2]).
% supervisor callbacks
-export([init/1]).
% API
-export([
start_link/2,
start_link/3,
stop/0,
set_handler/1
]).
%%------------------------------------------------------------------------------
%% API
%%------------------------------------------------------------------------------
start_link(Port, Path) ->
start_link(Port, Path, false).
start_link(Port, Path, SSLOpts) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, [Port, Path, SSLOpts]).
stop() ->
gen_server:stop(?MODULE).
set_handler(F) when is_function(F, 2) ->
true = ets:insert(?MODULE, {handler, F}),
ok.
%%------------------------------------------------------------------------------
%% supervisor API
%%------------------------------------------------------------------------------
init([Port, Path, SSLOpts]) ->
Dispatch = cowboy_router:compile(
[
{'_', [{Path, ?MODULE, []}]}
]
),
ProtoOpts = #{env => #{dispatch => Dispatch}},
Tab = ets:new(?MODULE, [set, named_table, public]),
ets:insert(Tab, {handler, fun default_handler/2}),
{Transport, TransOpts, CowboyModule} = transport_settings(Port, SSLOpts),
ChildSpec = ranch:child_spec(?MODULE, Transport, TransOpts, CowboyModule, ProtoOpts),
{ok, {#{}, [ChildSpec]}}.
%%------------------------------------------------------------------------------
%% cowboy_server API
%%------------------------------------------------------------------------------
init(Req, State) ->
[{handler, Handler}] = ets:lookup(?MODULE, handler),
Handler(Req, State).
%%------------------------------------------------------------------------------
%% Internal functions
%%------------------------------------------------------------------------------
transport_settings(Port, false) ->
TransOpts = #{
socket_opts => [{port, Port}],
connection_type => supervisor
},
{ranch_tcp, TransOpts, cowboy_clear};
transport_settings(Port, SSLOpts) ->
TransOpts = #{
socket_opts => [
{port, Port},
{next_protocols_advertised, [<<"h2">>, <<"http/1.1">>]},
{alpn_preferred_protocols, [<<"h2">>, <<"http/1.1">>]}
| SSLOpts
],
connection_type => supervisor
},
{ranch_ssl, TransOpts, cowboy_tls}.
default_handler(Req0, State) ->
Req = cowboy_req:reply(
400,
#{<<"content-type">> => <<"text/plain">>},
<<"">>,
Req0
),
{ok, Req, State}.
make_user_info(Password, Algorithm, IterationCount) ->
{StoredKey, ServerKey, Salt} = esasl_scram:generate_authentication_info(
Password,
#{
algorithm => Algorithm,
iteration_count => IterationCount
}
),
#{
stored_key => StoredKey,
server_key => ServerKey,
salt => Salt,
is_superuser => false
}.

View File

@ -1,7 +1,7 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
{application, emqx_auth_jwt, [ {application, emqx_auth_jwt, [
{description, "EMQX JWT Authentication and Authorization"}, {description, "EMQX JWT Authentication and Authorization"},
{vsn, "0.3.3"}, {vsn, "0.3.2"},
{registered, []}, {registered, []},
{mod, {emqx_auth_jwt_app, []}}, {mod, {emqx_auth_jwt_app, []}},
{applications, [ {applications, [

View File

@ -22,7 +22,6 @@
%% callbacks of behaviour emqx_resource %% callbacks of behaviour emqx_resource
-export([ -export([
resource_type/0,
callback_mode/0, callback_mode/0,
on_start/2, on_start/2,
on_stop/2, on_stop/2,
@ -33,8 +32,6 @@
-define(DEFAULT_POOL_SIZE, 8). -define(DEFAULT_POOL_SIZE, 8).
resource_type() -> jwks.
callback_mode() -> always_sync. callback_mode() -> always_sync.
on_start(InstId, Opts) -> on_start(InstId, Opts) ->

View File

@ -188,8 +188,7 @@ do_create(
ResourceId, ResourceId,
?AUTHN_RESOURCE_GROUP, ?AUTHN_RESOURCE_GROUP,
emqx_authn_jwks_connector, emqx_authn_jwks_connector,
connector_opts(Config), connector_opts(Config)
#{}
), ),
{ok, #{ {ok, #{
jwk_resource => ResourceId, jwk_resource => ResourceId,

View File

@ -0,0 +1,94 @@
Business Source License 1.1
Licensor: Hangzhou EMQ Technologies Co., Ltd.
Licensed Work: EMQX Enterprise Edition
The Licensed Work is (c) 2023
Hangzhou EMQ Technologies Co., Ltd.
Additional Use Grant: Students and educators are granted right to copy,
modify, and create derivative work for research
or education.
Change Date: 2028-01-26
Change License: Apache License, Version 2.0
For information about alternative licensing arrangements for the Software,
please contact Licensor: https://www.emqx.com/en/contact
Notice
The Business Source License (this document, or the “License”) is not an Open
Source license. However, the Licensed Work will eventually be made available
under an Open Source License, as stated in this License.
License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
“Business Source License” is a trademark of MariaDB Corporation Ab.
-----------------------------------------------------------------------------
Business Source License 1.1
Terms
The Licensor hereby grants you the right to copy, modify, create derivative
works, redistribute, and make non-production use of the Licensed Work. The
Licensor may make an Additional Use Grant, above, permitting limited
production use.
Effective on the Change Date, or the fourth anniversary of the first publicly
available distribution of a specific version of the Licensed Work under this
License, whichever comes first, the Licensor hereby grants you rights under
the terms of the Change License, and the rights granted in the paragraph
above terminate.
If your use of the Licensed Work does not comply with the requirements
currently in effect as described in this License, you must purchase a
commercial license from the Licensor, its affiliated entities, or authorized
resellers, or you must refrain from using the Licensed Work.
All copies of the original and modified Licensed Work, and derivative works
of the Licensed Work, are subject to this License. This License applies
separately for each version of the Licensed Work and the Change Date may vary
for each version of the Licensed Work released by Licensor.
You must conspicuously display this License on each original or modified copy
of the Licensed Work. If you receive the Licensed Work in original or
modified form from a third party, the terms and conditions set forth in this
License apply to your use of that work.
Any use of the Licensed Work in violation of this License will automatically
terminate your rights under this License for the current and all other
versions of the Licensed Work.
This License does not grant you any right in any trademark or logo of
Licensor or its affiliates (provided that you may use a trademark or logo of
Licensor as expressly required by this License).
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
TITLE.
MariaDB hereby grants you permission to use this Licenses text to license
your works, and to refer to it using the trademark “Business Source License”,
as long as you comply with the Covenants of Licensor below.
Covenants of Licensor
In consideration of the right to use this Licenses text and the “Business
Source License” name and trademark, Licensor covenants to MariaDB, and to all
other recipients of the licensed work to be provided by Licensor:
1. To specify as the Change License the GPL Version 2.0 or any later version,
or a license that is compatible with GPL Version 2.0 or a later version,
where “compatible” means that software provided under the Change License can
be included in a program with software provided under GPL Version 2.0 or a
later version. Licensor may specify additional Change Licenses without
limitation.
2. To either: (a) specify an additional grant of rights to use that does not
impose any additional restriction on the right granted in this License, as
the Additional Use Grant; or (b) insert the text “None”.
3. To specify a Change Date.
4. Not to modify this License in any other way.

View File

@ -0,0 +1 @@
kdc

View File

@ -0,0 +1,18 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
%%--------------------------------------------------------------------
-ifndef(EMQX_AUTH_KERBEROS_HRL).
-define(EMQX_AUTH_KERBEROS_HRL, true).
-define(AUTHN_MECHANISM_GSSAPI, gssapi).
-define(AUTHN_MECHANISM_GSSAPI_BIN, <<"gssapi">>).
-define(AUTHN_BACKEND, kerberos).
-define(AUTHN_BACKEND_BIN, <<"kerberos">>).
-define(AUTHN_TYPE_KERBEROS, {?AUTHN_MECHANISM_GSSAPI, ?AUTHN_BACKEND}).
-define(AUTHN_METHOD, <<"GSSAPI-KERBEROS">>).
-endif.

View File

@ -0,0 +1,7 @@
%% -*- mode: erlang -*-
{deps, [
{emqx, {path, "../emqx"}},
{emqx_utils, {path, "../emqx_utils"}},
{sasl_auth, {git, "https://github.com/kafka4beam/sasl_auth.git", {tag, "v2.1.1"}}}
]}.

View File

@ -1,15 +1,17 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
{application, emqx_connector_jwt, [ {application, emqx_auth_kerberos, [
{description, "EMQX JWT Connector Utility"}, {description, "EMQX Kerberos Authentication"},
{vsn, "0.1.0"}, {vsn, "0.1.0"},
{registered, []}, {registered, []},
{mod, {emqx_auth_kerberos_app, []}},
{applications, [ {applications, [
kernel, kernel,
stdlib stdlib,
emqx_auth,
sasl_auth
]}, ]},
{env, []}, {env, []},
{modules, []}, {modules, []},
{mod, {emqx_connector_jwt_app, []}},
{licenses, ["Apache 2.0"]}, {licenses, ["Apache 2.0"]},
{links, []} {links, []}

View File

@ -0,0 +1,20 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
%%--------------------------------------------------------------------
-module(emqx_auth_kerberos_app).
-include("emqx_auth_kerberos.hrl").
-behaviour(application).
-export([start/2, stop/1]).
start(_StartType, _StartArgs) ->
ok = emqx_authn:register_provider(?AUTHN_TYPE_KERBEROS, emqx_authn_kerberos),
{ok, Sup} = emqx_auth_kerberos_sup:start_link(),
{ok, Sup}.
stop(_State) ->
ok = emqx_authn:deregister_provider(?AUTHN_TYPE_KERBEROS),
ok.

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