upload source code
Some checks are pending
Build and Run Tests by Bazel / bazel-compile (ubuntu-latest) (push) Waiting to run
CodeQL Analysis / CodeQL-Build (push) Waiting to run
Coverage / calculate-coverage (push) Waiting to run
Run Integration Tests / maven-compile (ubuntu-latest, JDK-8) (push) Waiting to run
Build and Run Tests by Maven / maven-compile (macos-latest, JDK-8) (push) Waiting to run
Build and Run Tests by Maven / maven-compile (ubuntu-latest, JDK-8) (push) Waiting to run
Build and Run Tests by Maven / maven-compile (windows-latest, JDK-8) (push) Waiting to run
Misspell Check / misspell-check (push) Waiting to run
PUSH-CI / Build dist tar (push) Waiting to run
PUSH-CI / Docker images (ubuntu, 8) (push) Blocked by required conditions
PUSH-CI / List version (push) Blocked by required conditions
PUSH-CI / Deploy RocketMQ For E2E (push) Blocked by required conditions
PUSH-CI / Deploy RocketMQ For Benchmarking (push) Blocked by required conditions
PUSH-CI / Test E2E grpc java (push) Blocked by required conditions
PUSH-CI / Test E2E golang (push) Blocked by required conditions
PUSH-CI / Test E2E remoting java (push) Blocked by required conditions
PUSH-CI / Performance benchmark test (push) Blocked by required conditions
PUSH-CI / Clean E2E (push) Blocked by required conditions
PUSH-CI / Clean Benchmarking (push) Blocked by required conditions

This commit is contained in:
amos
2025-05-21 14:41:59 +08:00
commit c0d23dbbe1
2556 changed files with 368989 additions and 0 deletions

53
.asf.yaml Normal file
View File

@@ -0,0 +1,53 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
github:
description: "Apache RocketMQ is a cloud native messaging and streaming platform, making it simple to build event-driven applications."
homepage: https://rocketmq.apache.org/
labels:
- messaging
- streaming
- eventing
- cloud-native
- rocketmq
- java
- hacktoberfest
enabled_merge_buttons:
# Enable squash button
squash: true
# Disable merge button
merge: false
# Disable rebase button
rebase: false
protected_branches:
master: {}
develop:
required_pull_request_reviews:
dismiss_stale_reviews: true
require_code_owner_reviews: false
required_approving_review_count: 1
required_status_checks:
contexts:
- misspell-check
- check-license
- maven-compile (ubuntu-latest, JDK-8)
- maven-compile (windows-latest, JDK-8)
- maven-compile (macos-latest, JDK-8)
notifications:
commits: commits@rocketmq.apache.org
issues: commits@rocketmq.apache.org
pullrequests: commits@rocketmq.apache.org
jobs: commits@rocketmq.apache.org
discussions: dev@rocketmq.apache.org

69
.bazelrc Normal file
View File

@@ -0,0 +1,69 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
startup --host_jvm_args=-Xmx2g
run --color=yes
build --color=yes
build --enable_platform_specific_config
test --action_env=TEST_TMPDIR=/tmp
test --experimental_strict_java_deps=warn
test --experimental_ui_max_stdouterr_bytes=10485760
build --experimental_strict_java_deps=warn
test --test_output=errors
# This .bazelrc file contains all of the flags required for the provided
# toolchain with Remote Build Execution.
# Note your WORKSPACE must contain an rbe_autoconfig target with
# name="rbe_default" to use these flags as-is.
# Depending on how many machines are in the remote execution instance, setting
# this higher can make builds faster by allowing more jobs to run in parallel.
# Setting it too high can result in jobs that timeout, however, while waiting
# for a remote machine to execute them.
build:remote --jobs=150
build:remote --remote_executor=grpcs://remote.buildbuddy.io
build:remote --host_platform=@buildbuddy_toolchain//:platform
build:remote --platforms=@buildbuddy_toolchain//:platform
build:remote --extra_execution_platforms=@buildbuddy_toolchain//:platform
build:remote --crosstool_top=@buildbuddy_toolchain//:toolchain
build:remote --extra_toolchains=@buildbuddy_toolchain//:cc_toolchain
build:remote --javabase=@buildbuddy_toolchain//:javabase_jdk8
build:remote --host_javabase=@buildbuddy_toolchain//:javabase_jdk8
build:remote --java_toolchain=@buildbuddy_toolchain//:toolchain_jdk8
build:remote --host_java_toolchain=@buildbuddy_toolchain//:toolchain_jdk8
build:remote --define=EXECUTOR=remote
# Enable remote execution so actions are performed on the remote systems.
build:remote --remote_executor=grpcs://remote.buildbuddy.io
# Enforce stricter environment rules, which eliminates some non-hermetic
# behavior and therefore improves both the remote cache hit rate and the
# correctness and repeatability of the build.
build:remote --incompatible_strict_action_env=true
# Set a higher timeout value, just in case.
build:remote --remote_timeout=3600
# Use a pre-configured account, such that we may have pull-request replacing pull-request-target
build:remote --remote_header=x-buildbuddy-api-key=FD819nUEY7WjvqmoufsU
test:remote --remote_header=x-buildbuddy-api-key=FD819nUEY7WjvqmoufsU

1
.bazelversion Normal file
View File

@@ -0,0 +1 @@
5.2.0

112
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,112 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
name: Bug Report
title: "[Bug] Bug title "
description: Create a report to help us identify any unintended flaws, errors, or faults.
body:
- type: checkboxes
attributes:
label: Before Creating the Bug Report
options:
- label: >
I found a bug, not just asking a question, which should be created in [GitHub Discussions](https://github.com/apache/rocketmq/discussions).
required: true
- label: >
I have searched the [GitHub Issues](https://github.com/apache/rocketmq/issues) and [GitHub Discussions](https://github.com/apache/rocketmq/discussions) of this repository and believe that this is not a duplicate.
required: true
- label: >
I have confirmed that this bug belongs to the current repository, not other repositories of RocketMQ.
required: true
- type: textarea
attributes:
label: Runtime platform environment
description: Describe the runtime platform environment.
placeholder: >
OS: (e.g., "Ubuntu 20.04")
OS: (e.g., "Windows Server 2019")
validations:
required: true
- type: textarea
attributes:
label: RocketMQ version
description: Describe the RocketMQ version.
placeholder: >
branch: (e.g develop|4.9.x)
version: (e.g. 5.1.0|4.9.5)
Git commit id: (e.g. c88b5cfa72e204962929eea105687647146112c6)
validations:
required: true
- type: textarea
attributes:
label: JDK Version
description: Run or Compiler version.
placeholder: >
Compiler: (e.g., "Oracle JDK 11.0.17")
OS: (e.g., "Ubuntu 20.04")
Runtime (if different from JDK above): (e.g., "Oracle JRE 8u251")
OS (if different from OS compiled on): (e.g., "Windows Server 2019")
validations:
required: false
- type: textarea
attributes:
label: Describe the Bug
description: Describe what happened.
placeholder: >
A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
attributes:
label: Steps to Reproduce
description: Describe the steps to reproduce the bug here.
placeholder: >
If possible, provide a recipe for reproducing the error.
validations:
required: true
- type: textarea
attributes:
label: What Did You Expect to See?
description: You expect to see result.
placeholder: >
A clear and concise description of what you expected to see.
validations:
required: true
- type: textarea
attributes:
label: What Did You See Instead?
description: You instead to see result.
placeholder: >
A clear and concise description of what you saw instead.
validations:
required: true
- type: textarea
attributes:
label: Additional Context
description: Additional context.
placeholder: >
Add any other context about the problem here.
validations:
required: false

23
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
blank_issues_enabled: false
contact_links:
- name: Ask Question
url: https://github.com/apache/rocketmq/discussions
about: Please go to GitHub Disccusions to ask questions

55
.github/ISSUE_TEMPLATE/doc.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
name: Documentation Related
title: "[Doc] Documentation Related "
description: I find some issues related to the documentation.
labels: [ "module/doc" ]
body:
- type: checkboxes
attributes:
label: Search before creation
description: >
Please make sure to search in the [issues](https://github.com/apache/rocketmq/issues)
first to see whether the same issue was reported already.
options:
- label: >
I had searched in the [issues](https://github.com/apache/rocketmq/issues) and found
no similar issues.
required: true
- type: textarea
attributes:
label: Documentation Related
description: Describe the suggestion about document.
placeholder: >
e.g There is a typo
validations:
required: true
- type: checkboxes
attributes:
label: Are you willing to submit PR?
description: >
This is absolutely not required, but we are happy to guide you in the contribution process
especially if you already have a good understanding of how to implement the fix.
options:
- label: Yes I am willing to submit a PR!
- type: markdown
attributes:
value: "Thanks for completing our form!"

View File

@@ -0,0 +1,75 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
name: Enhancement Request
title: "[Enhancement] Enhancement title"
description: Suggest an enhancement for this project
labels: [ "type/enhancement" ]
body:
- type: checkboxes
attributes:
label: Before Creating the Enhancement Request
description: >
Most of issues should be classified as bug or feature request. An issue should be considered as an enhancement when it proposes improvements to
existing functionality or user experience, without necessarily introducing new features or fixing existing bugs.
options:
- label: >
I have confirmed that this should be classified as an enhancement rather than a bug/feature.
required: true
- type: textarea
attributes:
label: Summary
placeholder: >
A clear and concise description of the enhancement you would like to see in the project.
validations:
required: true
- type: textarea
attributes:
label: Motivation
placeholder: >
Explain why you believe this enhancement is necessary, and how it benefits the project and community.
Include any specific use cases that you have in mind.
validations:
required: true
- type: textarea
attributes:
label: Describe the Solution You'd Like
placeholder: >
Describe the enhancement you propose, detailing the change and implementation steps involved.
If you have multiple solutions, please list them separately.
validations:
required: true
- type: textarea
attributes:
label: Describe Alternatives You've Considered
placeholder: >
List any alternative enhancements or implementations you have considered, and explain why they may not be as effective or appropriate.
validations:
required: true
- type: textarea
attributes:
label: Additional Context
placeholder: >
Add any relevant context, screenshots, prototypes, or other supplementary information to help illustrate the enhancement.
validations:
required: false

View File

@@ -0,0 +1,58 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
name: Feature Request
title: "[Feature] New feature title"
description: Suggest an idea for this project.
labels: [ "type/new feature" ]
body:
- type: textarea
attributes:
label: Is Your Feature Request Related to a Problem?
description: Please Describe It.
placeholder: >
A clear and concise description of what the problem is.
validations:
required: true
- type: textarea
attributes:
label: Describe the Solution You'd Like
description: Describe how you solved it.
placeholder: >
A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Describe Alternatives You've Considered
description: Describe your solution
placeholder: >
A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true
- type: textarea
attributes:
label: Additional Context
description: Additional context.
placeholder: >
Add any other context about the problem here.
validations:
required: false

15
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,15 @@
<!-- Please make sure the target branch is right. In most case, the target branch should be `develop`. -->
### Which Issue(s) This PR Fixes
<!-- Please ensure that the related issue has already been created, and [link this pull request to that issue using keywords](<https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword>) to ensure automatic closure. -->
Fixes #issue_id
### Brief Description
<!-- Write a brief description for your pull request to help the maintainer understand the reasons behind your changes. -->
### How Did You Test This Change?
<!-- In order to ensure the code quality of Apache RocketMQ, we expect every pull request to have undergone thorough testing. -->

38
.github/asf-deploy-settings.xml vendored Normal file
View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You under the Apache License, Version 2.0
~ (the "License"); you may not use this file except in compliance with
~ the License. You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>apache.snapshots.https</id>
<username>${env.NEXUS_DEPLOY_USERNAME}</username>
<password>${env.NEXUS_DEPLOY_PASSWORD}</password>
<configuration>
<snapshotRepository>
<maxUniqueSnapshots>60</maxUniqueSnapshots>
</snapshotRepository>
</configuration>
</server>
</servers>
</settings>

23
.github/workflows/bazel.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Build and Run Tests by Bazel
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches:
- master
- develop
- bazel
jobs:
build:
name: "bazel-compile (${{ matrix.os }})"
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v2
- name: Build
run: bazel build --config=remote //...
- name: Run Tests
run: bazel test --config=remote //...

32
.github/workflows/codeql_analysis.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: CodeQL Analysis
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches:
- master
jobs:
CodeQL-Build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: java
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

25
.github/workflows/coverage.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Coverage
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches: [master, develop]
jobs:
calculate-coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Set up JDK 8
uses: actions/setup-java@v4
with:
java-version: "8"
distribution: "corretto"
cache: "maven"
- name: Generate coverage report
run: mvn -B test -T 2C --file pom.xml
- name: Upload to Codecov
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: true
verbose: true
token: cf0cba0a-22f8-4580-89ab-4f1dec3bda6f

45
.github/workflows/integration-test.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Run Integration Tests
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches: [master, develop]
jobs:
it-test:
name: "maven-compile (${{ matrix.os }}, JDK-${{ matrix.jdk }})"
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
jdk: [8]
steps:
- name: Cache Maven Repos
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Checkout
uses: actions/checkout@v2
- name: Set up JDK ${{ matrix.jdk }}
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.jdk }}
distribution: "corretto"
cache: "maven"
- name: Run integration tests with Maven
run: mvn clean verify -Pit-test -Pskip-unit-tests
- name: Publish Test Report
uses: mikepenz/action-junit-report@v3
if: always()
with:
report_paths: 'test/target/failsafe-reports/TEST-*.xml'
annotate_only: true
include_passed: true
detailed_summary: true

34
.github/workflows/license-checker.yaml vendored Normal file
View File

@@ -0,0 +1,34 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: License checker
on:
pull_request:
branches:
- develop
- master
jobs:
check-license:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Check License Header
uses: apache/skywalking-eyes@v0.2.0
with:
log: info
config: .licenserc.yaml

46
.github/workflows/maven.yaml vendored Normal file
View File

@@ -0,0 +1,46 @@
name: Build and Run Tests by Maven
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches: [master, develop, bazel]
jobs:
java_build:
name: "maven-compile (${{ matrix.os }}, JDK-${{ matrix.jdk }})"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# see https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
os: [ubuntu-latest, windows-latest, macos-latest]
jdk: [8]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up JDK ${{ matrix.jdk }}
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.jdk }}
# See https://github.com/actions/setup-java?tab=readme-ov-file#supported-distributions
# AdoptOpenJDK got moved to Eclipse Temurin and won't be updated anymore.
distribution: "corretto"
cache: "maven"
- name: Build with Maven
run: mvn -B package --file pom.xml
- name: Upload Auth JVM crash logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: jvm-crash-logs
path: /Users/runner/work/rocketmq/rocketmq/auth/hs_err_pid*.log
retention-days: 1
- name: Upload broker JVM crash logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: jvm-crash-logs
path: /Users/runner/work/rocketmq/rocketmq/broker/hs_err_pid*.log
retention-days: 1

17
.github/workflows/misspell_check.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Misspell Check
on:
pull_request:
types: [opened, reopened, synchronize]
push:
branches: [master, develop]
jobs:
misspell-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install misspell
run: |
curl -L -o ./install-misspell.sh https://git.io/misspell
sh ./install-misspell.sh
- name: Run misspell
run: find . -type f -print0 | xargs -0 bin/misspell -error -i transfered,derivate

36
.github/workflows/pr-ci.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: PR-CI
on:
pull_request:
types: [opened, reopened, synchronize]
jobs:
dist-tar:
name: Build distribution tar
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- uses: actions/checkout@v3
with:
submodules: true
- uses: actions/setup-java@v3
with:
distribution: "temurin"
java-version: "8"
cache: "maven"
- name: Build distribution tar
run: |
mvn -Prelease-all -DskipTests -Dspotbugs.skip=true clean install -U
- uses: actions/upload-artifact@v4
name: Upload distribution tar
with:
name: rocketmq
path: distribution/target/rocketmq*/rocketmq*
- name: Save PR number
run: |
mkdir -p ./pr
echo ${{ github.event.number }} > ./pr/NR
- uses: actions/upload-artifact@v4
with:
name: pr
path: pr/

263
.github/workflows/pr-e2e-test.yml vendored Normal file
View File

@@ -0,0 +1,263 @@
name: E2E test for pull request
# read-write repo token
# access to secrets
on:
workflow_run:
workflows: ["PR-CI"]
types:
- completed
env:
DOCKER_REPO: apache/rocketmq-ci
jobs:
docker:
if: >
github.repository == 'apache/rocketmq' &&
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
base-image: ["ubuntu"]
java-version: ["8"]
steps:
- name: 'Download artifact'
uses: actions/github-script@v6
with:
script: |
let artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{github.event.workflow_run.id }},
});
let matchArtifactRmq = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "rocketmq"
})[0];
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifactRmq.id,
archive_format: 'zip',
});
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/rocketmq.zip', Buffer.from(download.data));
- run: |
unzip rocketmq.zip
mkdir rocketmq
cp -r rocketmq-* rocketmq/
ls
- uses: actions/checkout@v3
with:
repository: apache/rocketmq-docker.git
ref: master
path: rocketmq-docker
- name: docker-login
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and save docker images
id: build-images
run: |
cd rocketmq-docker/image-build-ci
version=${{ github.event.pull_request.number || github.ref_name }}-$(uuidgen)
mkdir versionlist
touch versionlist/"${version}-`echo ${{ matrix.base-image }} | sed -e "s/:/-/g"`"
sh ./build-image-local.sh ${version} ${{ matrix.base-image }} ${{ matrix.java-version }} ${DOCKER_REPO}
- uses: actions/upload-artifact@v4
name: Upload distribution tar
with:
name: versionlist
path: rocketmq-docker/image-build-ci/versionlist/*
list-version:
if: >
github.repository == 'apache/rocketmq' &&
always()
name: List version
needs: [docker]
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
version-json: ${{ steps.show_versions.outputs.version-json }}
steps:
- uses: actions/download-artifact@v4
name: Download versionlist
with:
name: versionlist
path: versionlist
- name: Show versions
id: show_versions
run: |
a=(`ls versionlist`)
printf '%s\n' "${a[@]}" | jq -R . | jq -s .
echo version-json=`printf '%s\n' "${a[@]}" | jq -R . | jq -s .` >> $GITHUB_OUTPUT
deploy:
if: ${{ success() }}
name: Deploy RocketMQ
needs: [list-version,docker]
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
steps:
- uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
name: Deploy rocketmq
with:
action: "deploy"
ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
test-version: "${{ matrix.version }}"
chart-git: "https://ghproxy.com/https://github.com/apache/rocketmq-docker.git"
chart-branch: "master"
chart-path: "./rocketmq-k8s-helm"
job-id: ${{ strategy.job-index }}
helm-values: |
nameserver:
image:
repository: ${{env.DOCKER_REPO}}
tag: ${{ matrix.version }}
broker:
image:
repository: ${{env.DOCKER_REPO}}
tag: ${{ matrix.version }}
proxy:
image:
repository: ${{env.DOCKER_REPO}}
tag: ${{ matrix.version }}
test-e2e-grpc-java:
if: ${{ success() }}
name: Test E2E grpc java
needs: [list-version, deploy]
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
steps:
- uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
name: e2e test
with:
action: "test"
ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
test-version: "${{ matrix.version }}"
test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e"
test-code-branch: "master"
test-code-path: java/e2e
test-cmd: "mvn -B test"
job-id: 0
- name: Publish Test Report
uses: mikepenz/action-junit-report@v3
if: always() # always run even if the previous step fails
with:
report_paths: '**/test_report/TEST-*.xml'
annotate_only: true
include_passed: true
detailed_summary: true
- uses: actions/upload-artifact@v4
if: always()
name: Upload test log
with:
name: test-e2e-grpc-java-log.txt
path: testlog.txt
test-e2e-golang:
if: ${{ success() }}
name: Test E2E golang
needs: [list-version, deploy]
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
steps:
- uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
name: e2e test
with:
action: "test"
ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
test-version: "${{ matrix.version }}"
test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e"
test-code-branch: "master"
test-code-path: golang
test-cmd: |
cd ../common && mvn -Prelease -DskipTests clean package -U
cd ../rocketmq-admintools && source bin/env.sh
LATEST_GO_VERSION=$(curl -s https://go.dev/VERSION?m=text | awk 'NR==1')
wget "https://go.dev/dl/${LATEST_GO_VERSION}.linux-amd64.tar.gz" && \
rm -rf /usr/local/go && tar -C /usr/local -xzf ${LATEST_GO_VERSION}.linux-amd64.tar.gz
cd ../golang && go get -u github.com/apache/rocketmq-clients/golang && gotestsum --junitfile ./target/surefire-reports/TEST-report.xml ./mqgotest/... -timeout 2m -v
job-id: 0
- name: Publish Test Report
uses: mikepenz/action-junit-report@v3
if: always() # always run even if the previous step fails
with:
report_paths: '**/test_report/TEST-*.xml'
annotate_only: true
include_passed: true
detailed_summary: true
- uses: actions/upload-artifact@v4
if: always()
name: Upload test log
with:
name: test-e2e-golang-log.txt
path: testlog.txt
test-e2e-remoting-java:
if: ${{ success() }}
name: Test E2E remoting java
needs: [ list-version, deploy ]
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
steps:
- uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
name: e2e test
with:
action: "test"
ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
test-version: "${{ matrix.version }}"
test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e"
test-code-branch: "master"
test-code-path: java/e2e-v4
test-cmd: "mvn -B test"
job-id: 0
- name: Publish Test Report
uses: mikepenz/action-junit-report@v3
if: always() # always run even if the previous step fails
with:
report_paths: '**/test_report/TEST-*.xml'
annotate_only: true
include_passed: true
detailed_summary: true
- uses: actions/upload-artifact@v4
if: always()
name: Upload test log
with:
name: test-e2e-remoting-java-log.txt
path: testlog.txt
clean:
if: always()
name: Clean
needs: [list-version, test-e2e-grpc-java, test-e2e-golang, test-e2e-remoting-java]
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
steps:
- uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
name: clean
with:
action: "clean"
ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
test-version: "${{ matrix.version }}"
job-id: ${{ strategy.job-index }}

348
.github/workflows/push-ci.yml vendored Normal file
View File

@@ -0,0 +1,348 @@
name: PUSH-CI
on:
push:
branches: [master, develop]
#schedule:
# - cron: "0 18 * * *" # TimeZone: UTC 0
concurrency:
group: rocketmq-${{ github.ref }}
env:
MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120
DOCKER_REPO: apache/rocketmq-ci
jobs:
dist-tar:
if: github.repository == 'apache/rocketmq'
name: Build dist tar
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
with:
submodules: true
- uses: actions/setup-java@v3
with:
distribution: "temurin"
java-version: "8"
cache: "maven"
- name: Build distribution tar
run: |
mvn -Prelease-all -DskipTests -Dspotbugs.skip=true clean install -U
- uses: actions/upload-artifact@v4
name: Upload distribution tar
with:
name: rocketmq
path: distribution/target/rocketmq*/rocketmq*
docker:
if: ${{ success() }}
name: Docker images
needs: [dist-tar]
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
base-image: ["ubuntu"]
java-version: ["8"]
steps:
- uses: actions/checkout@v3
with:
repository: apache/rocketmq-docker.git
ref: master
path: rocketmq-docker
- uses: actions/download-artifact@v4
name: Download distribution tar
with:
name: rocketmq
path: rocketmq
- name: docker-login
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and save docker images
id: build-images
run: |
cd rocketmq-docker/image-build-ci
version=${{ github.event.pull_request.number || github.ref_name }}-$(uuidgen)
mkdir versionlist
touch versionlist/"${version}-`echo ${{ matrix.base-image }} | sed -e "s/:/-/g"`"
sh ./build-image-local.sh ${version} ${{ matrix.base-image }} ${{ matrix.java-version }} ${DOCKER_REPO}
- uses: actions/upload-artifact@v4
name: Upload distribution tar
with:
name: versionlist
path: rocketmq-docker/image-build-ci/versionlist/*
list-version:
if: >
github.repository == 'apache/rocketmq' &&
always()
name: List version
needs: [docker]
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
version-json: ${{ steps.show_versions.outputs.version-json }}
steps:
- uses: actions/download-artifact@v4
name: Download versionlist
with:
name: versionlist
path: versionlist
- name: Show versions
id: show_versions
run: |
a=(`ls versionlist`)
printf '%s\n' "${a[@]}" | jq -R . | jq -s .
echo version-json=`printf '%s\n' "${a[@]}" | jq -R . | jq -s .` >> $GITHUB_OUTPUT
deploy-e2e:
if: ${{ success() }}
name: Deploy RocketMQ For E2E
needs: [list-version,docker]
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
steps:
- uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
name: Deploy rocketmq
with:
action: "deploy"
ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
test-version: "${{ matrix.version }}"
chart-git: "https://ghproxy.com/https://github.com/apache/rocketmq-docker.git"
chart-branch: "master"
chart-path: "./rocketmq-k8s-helm"
job-id: ${{ strategy.job-index }}
helm-values: |
nameserver:
image:
repository: ${{env.DOCKER_REPO}}
tag: ${{ matrix.version }}
broker:
image:
repository: ${{env.DOCKER_REPO}}
tag: ${{ matrix.version }}
proxy:
image:
repository: ${{env.DOCKER_REPO}}
tag: ${{ matrix.version }}
deploy-benchmark:
if: ${{ success() }}
name: Deploy RocketMQ For Benchmarking
needs: [list-version,docker]
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
steps:
- uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
name: Deploy rocketmq
with:
action: "deploy"
ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
test-version: "${{ matrix.version }}"
chart-git: "https://ghproxy.com/https://github.com/apache/rocketmq-docker.git"
chart-branch: "master"
chart-path: "./rocketmq-k8s-helm"
job-id: "001-${{ strategy.job-index }}"
helm-values: |
nameserver:
image:
repository: ${{env.DOCKER_REPO}}
tag: ${{ matrix.version }}
broker:
image:
repository: ${{env.DOCKER_REPO}}
tag: ${{ matrix.version }}
proxy:
image:
repository: ${{env.DOCKER_REPO}}
tag: ${{ matrix.version }}
test-e2e-grpc-java:
if: ${{ success() }}
name: Test E2E grpc java
needs: [list-version, deploy-e2e]
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
steps:
- uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
name: e2e test
with:
action: "test"
ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
test-version: "${{ matrix.version }}"
test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e"
test-code-branch: "master"
test-code-path: java/e2e
test-cmd: "mvn -B test"
job-id: 0
- name: Publish Test Report
uses: mikepenz/action-junit-report@v3
if: always() # always run even if the previous step fails
with:
report_paths: '**/test_report/TEST-*.xml'
annotate_only: true
include_passed: true
detailed_summary: true
- uses: actions/upload-artifact@v4
if: always()
name: Upload test log
with:
name: test-e2e-grpc-java-log.txt
path: testlog.txt
test-e2e-golang:
if: ${{ success() }}
name: Test E2E golang
needs: [list-version, deploy-e2e]
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
steps:
- uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
name: e2e test
with:
action: "test"
ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
test-version: "${{ matrix.version }}"
test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e"
test-code-branch: "master"
test-code-path: golang
test-cmd: |
cd ../common && mvn -Prelease -DskipTests clean package -U
cd ../rocketmq-admintools && source bin/env.sh
LATEST_GO_VERSION=$(curl -s https://go.dev/VERSION?m=text | awk 'NR==1')
wget "https://go.dev/dl/${LATEST_GO_VERSION}.linux-amd64.tar.gz" && \
rm -rf /usr/local/go && tar -C /usr/local -xzf ${LATEST_GO_VERSION}.linux-amd64.tar.gz
cd ../golang && go get -u github.com/apache/rocketmq-clients/golang && gotestsum --junitfile ./target/surefire-reports/TEST-report.xml ./mqgotest/... -timeout 2m -v
job-id: 0
- name: Publish Test Report
uses: mikepenz/action-junit-report@v3
if: always() # always run even if the previous step fails
with:
report_paths: '**/test_report/TEST-*.xml'
annotate_only: true
include_passed: true
detailed_summary: true
- uses: actions/upload-artifact@v4
if: always()
name: Upload test log
with:
name: test-e2e-golang-log.txt
path: testlog.txt
test-e2e-remoting-java:
if: ${{ success() }}
name: Test E2E remoting java
needs: [ list-version, deploy-e2e ]
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
steps:
- uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
name: e2e test
with:
action: "test"
ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
test-version: "${{ matrix.version }}"
test-code-git: "https://ghproxy.com/https://github.com/apache/rocketmq-e2e"
test-code-branch: "master"
test-code-path: java/e2e-v4
test-cmd: "mvn -B test"
job-id: 0
- name: Publish Test Report
uses: mikepenz/action-junit-report@v3
if: always() # always run even if the previous step fails
with:
report_paths: '**/test_report/TEST-*.xml'
annotate_only: true
include_passed: true
detailed_summary: true
- uses: actions/upload-artifact@v4
if: always()
name: Upload test log
with:
name: test-e2e-remoting-java-log.txt
path: testlog.txt
benchmark-test:
if: ${{ success() }}
runs-on: ubuntu-latest
name: Performance benchmark test
needs: [ list-version, deploy-benchmark ]
timeout-minutes: 60
steps:
- uses: apache/rocketmq-test-tool/benchmark-runner@ce372e5f3906ca1891e4918b05be14608eae608e
name: Performance benchmark
with:
action: "performance-benchmark"
ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
job-id: "001-${{ strategy.job-index }}"
# The time to run the test, 15 minutes
test-time: "900"
# Some thresholds set in advance
min-send-tps-threshold: "12000"
max-rt-ms-threshold: "500"
avg-rt-ms-threshold: "10"
max-2c-rt-ms-threshold: "150"
avg-2c-rt-ms-threshold: "10"
- name: Upload test report
if: always()
uses: actions/upload-artifact@v4
with:
name: benchmark-report
path: benchmark/
clean-e2e:
if: always()
name: Clean E2E
needs: [ list-version, test-e2e-grpc-java, test-e2e-golang, test-e2e-remoting-java ]
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
steps:
- uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
name: clean
with:
action: "clean"
ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
test-version: "${{ matrix.version }}"
job-id: ${{ strategy.job-index }}
clean-benchmark:
if: always()
name: Clean Benchmarking
needs: [ list-version, benchmark-test ]
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
steps:
- uses: apache/rocketmq-test-tool@7d84d276ad7755b1dc5cf9657a7a9bff6ae6d288
name: clean
with:
action: "clean"
ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
test-version: "${{ matrix.version }}"
job-id: "001-${{ strategy.job-index }}"

22
.github/workflows/rerun-workflow.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Rerun workflow
on:
workflow_run:
workflows: ["Build and Run Tests by Maven" , "Build and Run Tests by Bazel"]
types:
- completed
permissions:
actions: write
jobs:
rerun:
if: github.event.workflow_run.conclusion == 'failure' && fromJSON(github.event.workflow_run.run_attempt) < 3
runs-on: ubuntu-latest
steps:
- name: rerun ${{ github.event.workflow_run.id }}
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh run watch ${{ github.event.workflow_run.id }} > /dev/null 2>&1
gh run rerun ${{ github.event.workflow_run.id }} --failed

View File

@@ -0,0 +1,261 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Snapshot Daily Release Automation
on:
schedule: # schedule the job to run at 12 a.m. daily
- cron: "0 0 * * *"
workflow_dispatch:
inputs:
branch:
description: 'The branch to trigger the workflow, The default branch is "develop" when both branch and commit_id are empty'
required: false
commit_id:
description: 'The commit id to trigger the workflow. Do not set branch and commit_id together'
required: false
rocketmq_version:
description: 'Name of the SNAPSHOT version to be generated. The default version is "$VERSION-stable-SNAPSHOT"'
required: false
env:
MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120
DOCKER_REPO: apache/rocketmq-ci
jobs:
dist-tar:
if: github.repository == 'apache/rocketmq'
name: Build dist tar
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout develop
if: github.event.inputs.branch == '' && github.event.inputs.commit_id == ''
uses: actions/checkout@v3
with:
ref: develop
- name: Checkout specific commit
if: github.event.inputs.branch == '' && github.event.inputs.commit_id != ''
uses: actions/checkout@v3
with:
ref: ${{ github.event.inputs.commit_id }}
- name: Checkout specific branch
if: github.event.inputs.branch != '' && github.event.inputs.commit_id == ''
uses: actions/checkout@v3
with:
ref: ${{ github.event.inputs.branch }}
- uses: actions/setup-java@v4
with:
distribution: "corretto"
java-version: "8"
cache: "maven"
- name: Build distribution tar
env:
MAVEN_SETTINGS: ${{ github.workspace }}/.github/asf-deploy-settings.xml
run: |
mvn -Prelease-all -DskipTests -Dspotbugs.skip=true clean install -U
- uses: actions/upload-artifact@v4
name: Upload distribution tar
with:
name: rocketmq
path: distribution/target/rocketmq*/rocketmq*
docker-build:
if: ${{ success() }}
name: Docker images
needs: [ dist-tar ]
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
base-image: [ "ubuntu" ]
java-version: [ "8" ]
steps:
- uses: actions/checkout@v3
with:
repository: apache/rocketmq-docker.git
ref: master
path: rocketmq-docker
- uses: actions/download-artifact@v4
name: Download distribution tar
with:
name: rocketmq
path: rocketmq
- name: docker-login
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKERHUB_USER }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and save docker images
id: build-images
run: |
cd rocketmq-docker/image-build-ci
version=${{ github.event.pull_request.number || github.ref_name }}-$(uuidgen)
mkdir versionlist
touch versionlist/"${version}-`echo ${{ matrix.base-image }} | sed -e "s/:/-/g"`"
sh ./build-image-local.sh ${version} ${{ matrix.base-image }} ${{ matrix.java-version }} ${DOCKER_REPO}
- uses: actions/upload-artifact@v4
name: Upload distribution tar
with:
name: versionlist
path: rocketmq-docker/image-build-ci/versionlist/*
list-version:
if: >
github.repository == 'apache/rocketmq' &&
always()
name: List version
needs: [ docker-build ]
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
version-json: ${{ steps.show_versions.outputs.version-json }}
steps:
- uses: actions/download-artifact@v4
name: Download versionlist
with:
name: versionlist
path: versionlist
- name: Show versions
id: show_versions
run: |
a=(`ls versionlist`)
printf '%s\n' "${a[@]}" | jq -R . | jq -s .
echo version-json=`printf '%s\n' "${a[@]}" | jq -R . | jq -s .` >> $GITHUB_OUTPUT
deploy-rocketmq:
if: ${{ success() }}
name: Deploy RocketMQ
needs: [ list-version,docker-build ]
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
steps:
- uses: apache/rocketmq-test-tool@1a646589accad17070423eabf0f54925e52b0666
name: Deploy rocketmq
with:
action: "deploy"
ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
test-version: "${{ matrix.version }}"
chart-git: "https://github.com/apache/rocketmq-docker.git"
chart-branch: "master"
chart-path: "./rocketmq-k8s-helm"
job-id: ${{ strategy.job-index }}
helm-values: |
nameserver:
image:
repository: ${{env.DOCKER_REPO}}
tag: ${{ matrix.version }}
broker:
image:
repository: ${{env.DOCKER_REPO}}
tag: ${{ matrix.version }}
proxy:
image:
repository: ${{env.DOCKER_REPO}}
tag: ${{ matrix.version }}
java-grpc-e2e-test:
if: ${{ success() }}
name: E2E Test
needs: [ list-version, deploy-rocketmq ]
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
steps:
- uses: apache/rocketmq-test-tool@1a646589accad17070423eabf0f54925e52b0666
name: e2e test
with:
action: "test"
ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
test-version: "${{ matrix.version }}"
test-code-git: "https://github.com/apache/rocketmq-e2e.git"
test-code-branch: "master"
test-code-path: java/e2e
test-cmd: "mvn -B test"
job-id: ${{ strategy.job-index }}
- name: Publish Test Report
uses: mikepenz/action-junit-report@v3
if: always() # always run even if the previous step fails
with:
report_paths: '**/test_report/TEST-*.xml'
annotate_only: true
include_passed: true
detailed_summary: true
- uses: actions/upload-artifact@v4
if: always()
name: Upload test log
with:
name: testlog.txt
path: testlog.txt
clean:
if: always()
name: Clean
needs: [ list-version, java-grpc-e2e-test ]
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
version: ${{ fromJSON(needs.list-version.outputs.version-json) }}
steps:
- uses: apache/rocketmq-test-tool@1a646589accad17070423eabf0f54925e52b0666
name: clean
with:
action: "clean"
ask-config: "${{ secrets.ASK_CONFIG_VIRGINA }}"
test-version: "${{ matrix.version }}"
job-id: ${{ strategy.job-index }}
snapshot:
runs-on: ubuntu-latest
needs: [ java-grpc-e2e-test ]
env:
NEXUS_DEPLOY_USERNAME: ${{ secrets.NEXUS_USER }}
NEXUS_DEPLOY_PASSWORD: ${{ secrets.NEXUS_PW }}
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: develop
persist-credentials: false
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: 8
distribution: "corretto"
cache: "maven"
- name: Update default pom version
if: github.event.inputs.rocketmq_version == ''
run: |
VERSION=$(mvn -q -Dexec.executable='echo' -Dexec.args='${project.version}' --non-recursive exec:exec)
VERSION=$(echo $VERSION | awk -F '-SNAPSHOT' '{print $1}')
VERSION=$VERSION-stable-SNAPSHOT
mvn versions:set -DnewVersion=$VERSION
- name: Update User-defined pom version
if: github.event.inputs.rocketmq_version != ''
run: |
mvn versions:set -DnewVersion=${{ github.event.inputs.rocketmq_version }}
- name: Deploy to ASF Snapshots Repository
timeout-minutes: 40
run: mvn clean deploy -DskipTests=true --settings .github/asf-deploy-settings.xml

32
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Close Stale Issues/PRs
permissions:
issues: write
pull-requests: write
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v5
with:
operations-per-run: 128
days-before-issue-stale: 365
days-before-issue-close: 3
exempt-issue-labels: "no stale"
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 365 days with no activity. It will be closed in 3 days if no further activity occurs."
close-issue-message: "This issue was closed because it has been inactive for 3 days since being marked as stale."
remove-issue-stale-when-updated: true
days-before-pr-stale: 365
days-before-pr-close: 3
exempt-pr-labels: "no stale"
stale-pr-label: "stale"
stale-pr-message: "This PR is stale because it has been open for 365 days with no activity. It will be closed in 3 days if no further activity occurs. If you wish not to mark it as stale, please leave a comment in this PR."
close-pr-message: "This PR was closed because it has been inactive for 3 days since being marked as stale."
remove-pr-stale-when-updated: true

21
.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
*dependency-reduced-pom.xml
.classpath
.project
.settings/
target/
devenv
*.log.*
*.iml
.idea/
*.versionsBackup
!NOTICE-BIN
!LICENSE-BIN
.DS_Store
localbin
nohup.out
bazel-out
bazel-bin
bazel-rocketmq
bazel-testlogs
.vscode
MODULE.bazel.lock

50
.licenserc.yaml Normal file
View File

@@ -0,0 +1,50 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
header:
license:
spdx-id: Apache-2.0
copyright-owner: Apache Software Foundation
paths-ignore:
- '.gitignore'
- '.travis.yml'
- 'CONTRIBUTING.md'
- 'LICENSE'
- 'NOTICE'
- '**/*.md'
- 'BUILDING'
- '.github/**'
- '*/src/test/resources/certs/*'
- 'src/test/**/*.log'
- '*/src/test/resources/META-INF/service/*'
- '*/src/main/resources/META-INF/service/*'
- '*/src/test/resources/rmq-proxy-home/conf/rmq-proxy.json'
- '*/src/test/resources/mockito-extensions/*'
- '**/target/**'
- '**/*.iml'
- 'docs/**'
- 'localbin/**'
- 'distribution/LICENSE-BIN'
- 'distribution/NOTICE-BIN'
- 'distribution/conf/rmq-proxy.json'
- '.bazelversion'
- 'common/src/main/resources/META-INF/services/org.apache.rocketmq.logging.ch.qos.logback.classic.spi.Configurator'
comment: on-failure

49
BUILD.bazel Normal file
View File

@@ -0,0 +1,49 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
load("@bazel_toolchains//rules/exec_properties:exec_properties.bzl", "create_rbe_exec_properties_dict")
platform(
name = "custom_platform",
# Inherit from the platform target generated by 'rbe_configs_gen' assuming the generated configs
# were imported as a Bazel external repository named 'rbe_default'. If you extracted the
# generated configs elsewhere in your source repository, replace the following with the label
# to the 'platform' target in the generated configs.
parents = ["@rbe_default//config:platform"],
# Example custom execution property instructing RBE to use e2-standard-2 GCE VMs.
exec_properties = create_rbe_exec_properties_dict(
container_image = "ubuntu:latest",
),
)
java_library(
name = "test_deps",
visibility = ["//visibility:public"],
exports = [
"@maven//:junit_junit",
"@maven//:org_assertj_assertj_core",
"@maven//:org_hamcrest_hamcrest_library",
"@maven//:org_mockito_mockito_core",
"@maven//:org_powermock_powermock_module_junit4",
"@maven//:org_powermock_powermock_api_mockito2",
"@maven//:org_hamcrest_hamcrest_core",
"@maven//:ch_qos_logback_logback_classic",
"@maven//:org_awaitility_awaitility",
"@maven//:org_openjdk_jmh_jmh_core",
"@maven//:org_openjdk_jmh_jmh_generator_annprocess",
"@maven//:org_mockito_mockito_junit_jupiter",
],
)

37
BUILDING Normal file
View File

@@ -0,0 +1,37 @@
Build Instructions for Apache RocketMQ
====================================================
(1) Prerequisites
JDK 1.7+ is required in order to compile and run RocketMQ.
RocketMQ utilizes Maven as a distribution management and packaging tool. Version 3.0.3 or later is required.
Maven installation and configuration instructions can be found here:
http://maven.apache.org/run-maven/index.html
(2) Run test cases
Execute the following command in order to compile and run test cases of each components:
$ mvn test
(3) Import projects to Eclipse IDE
First, generate eclipse project files:
$ mvn -U eclipse:eclipse
Then, import to eclipse by specifying the root directory of the project via:
[File] > [Import] > [Existing Projects into Workspace].
(4) Build distribution packages
Execute the following command in order to build the tar.gz packages and install JAR into local repository:
$ mvn -Prelease-all -DskipTests clean install -U

48
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,48 @@
## How To Contribute
We are always very happy to have contributions, whether for trivial cleanups or big new features.
We want to have high quality, well documented codes for each programming language, as well as the surrounding [ecosystem](https://github.com/apache/rocketmq-externals) of integration tools that people use with RocketMQ.
Nor is code the only way to contribute to the project. We strongly value documentation, integration with other project, and gladly accept improvements for these aspects.
Recommend reading:
* [Contributors Tech Guide](http://www.apache.org/dev/contributors)
* [Get involved!](http://www.apache.org/foundation/getinvolved.html)
## Contributing code
To submit a change for inclusion, please do the following:
#### If the change is non-trivial please include some unit tests that cover the new functionality.
#### If you are introducing a completely new feature or API it is a good idea to start a [RIP](https://github.com/apache/rocketmq/wiki/RocketMQ-Improvement-Proposal) and get consensus on the basic design first.
#### It is our job to follow up on patches in a timely fashion. Nag us if we aren't doing our job (sometimes we drop things).
### Squash commits
If your have a pull request on GitHub, and updated more than once, it's better to squash all commits.
1. Identify how many commits you made since you began: ``git log``.
2. Squash these commits by N: ``git rebase -i HEAD~N`` .
3. Leave "pick" tag in the first line.
4. Change all other commits from "pick" to "fixup".
5. Then do "force push" to overwrite remote history: ``git push -u origin ROCKETMQ-9999 --force``
6. All your changes are now in a single commit, that would be much better for review.
More details of squash can be found at [stackoverflow](https://stackoverflow.com/questions/5189560/squash-my-last-x-commits-together-using-git).
## Becoming a Committer
We are always interested in adding new contributors. What we look for are series of contributions, good taste and ongoing interest in the project. If you are interested in becoming a committer, please let one of the existing committers know and they can help you walk through the process.
Nowadays,we have several important contribution points:
#### Wiki & JavaDoc
#### RocketMQ SDK(C++\.Net\Php\Python\Go\Node.js)
#### RocketMQ Connectors
##### Prerequisite
If you want to contribute the above listing points, you must abide our some prerequisites:
###### Readability - API must have Javadoc, some very important methods also must have javadoc
###### Testability - 80% above unit test coverage about main process
###### Maintainability - Comply with our [checkstyle spec](style/rmq_checkstyle.xml), and at least 3 month update frequency
###### Deployability - We encourage you to deploy into [maven repository](http://search.maven.org/)

201
LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

22
MODULE.bazel Normal file
View File

@@ -0,0 +1,22 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
###############################################################################
# Bazel now uses Bzlmod by default to manage external dependencies.
# Please consider migrating your external dependencies from WORKSPACE to MODULE.bazel.
#
# For more details, please check https://github.com/bazelbuild/bazel/issues/18958
###############################################################################

5
NOTICE Normal file
View File

@@ -0,0 +1,5 @@
Apache RocketMQ
Copyright 2016-2025 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).

250
README.md Normal file
View File

@@ -0,0 +1,250 @@
## Apache RocketMQ
[![Build Status][maven-build-image]][maven-build-url]
[![CodeCov][codecov-image]][codecov-url]
[![Maven Central][maven-central-image]][maven-central-url]
[![Release][release-image]][release-url]
[![License][license-image]][license-url]
[![Average Time to Resolve An Issue][percentage-of-issues-still-open-image]][percentage-of-issues-still-open-url]
[![Percentage of Issues Still Open][average-time-to-resolve-an-issue-image]][average-time-to-resolve-an-issue-url]
[![Twitter Follow][twitter-follow-image]][twitter-follow-url]
**[Apache RocketMQ](https://rocketmq.apache.org) is a distributed messaging and streaming platform with low latency, high performance and reliability, trillion-level capacity and flexible scalability.**
It offers a variety of features:
* Messaging patterns including publish/subscribe, request/reply and streaming
* Financial grade transactional message
* Built-in fault tolerance and high availability configuration options base on [DLedger Controller](docs/en/controller/quick_start.md)
* Built-in message tracing capability, also support opentracing
* Versatile big-data and streaming ecosystem integration
* Message retroactivity by time or offset
* Reliable FIFO and strict ordered messaging in the same queue
* Efficient pull and push consumption model
* Million-level message accumulation capacity in a single queue
* Multiple messaging protocols like gRPC, MQTT, JMS and OpenMessaging
* Flexible distributed scale-out deployment architecture
* Lightning-fast batch message exchange system
* Various message filter mechanics such as SQL and Tag
* Docker images for isolated testing and cloud isolated clusters
* Feature-rich administrative dashboard for configuration, metrics and monitoring
* Authentication and authorization
* Free open source connectors, for both sources and sinks
* Lightweight real-time computing
----------
## Quick Start
This paragraph guides you through steps of installing RocketMQ in different ways.
For local development and testing, only one instance will be created for each component.
### Run RocketMQ locally
RocketMQ runs on all major operating systems and requires only a Java JDK version 8 or higher to be installed.
To check, run `java -version`:
```shell
$ java -version
java version "1.8.0_121"
```
For Windows users, click [here](https://dist.apache.org/repos/dist/release/rocketmq/5.3.3/rocketmq-all-5.3.3-bin-release.zip) to download the 5.3.3 RocketMQ binary release,
unpack it to your local disk, such as `D:\rocketmq`.
For macOS and Linux users, execute following commands:
```shell
# Download release from the Apache mirror
$ wget https://dist.apache.org/repos/dist/release/rocketmq/5.3.3/rocketmq-all-5.3.3-bin-release.zip
# Unpack the release
$ unzip rocketmq-all-5.3.3-bin-release.zip
```
Prepare a terminal and change to the extracted `bin` directory:
```shell
$ cd rocketmq-all-5.3.3-bin-release/bin
```
**1) Start NameServer**
NameServer will be listening at `0.0.0.0:9876`, make sure that the port is not used by others on the local machine, and then do as follows.
For macOS and Linux users:
```shell
### start Name Server
$ nohup sh mqnamesrv &
### check whether Name Server is successfully started
$ tail -f ~/logs/rocketmqlogs/namesrv.log
The Name Server boot success...
```
For Windows users, you need set environment variables first:
- From the desktop, right click the Computer icon.
- Choose Properties from the context menu.
- Click the Advanced system settings link.
- Click Environment Variables.
- Add Environment `ROCKETMQ_HOME="D:\rocketmq"`.
Then change directory to rocketmq, type and run:
```shell
$ mqnamesrv.cmd
The Name Server boot success...
```
**2) Start Broker**
For macOS and Linux users:
```shell
### start Broker
$ nohup sh bin/mqbroker -n localhost:9876 &
### check whether Broker is successfully started, eg: Broker's IP is 192.168.1.2, Broker's name is broker-a
$ tail -f ~/logs/rocketmqlogs/broker.log
The broker[broker-a, 192.169.1.2:10911] boot success...
```
For Windows users:
```shell
$ mqbroker.cmd -n localhost:9876
The broker[broker-a, 192.169.1.2:10911] boot success...
```
### Run RocketMQ in Docker
You can run RocketMQ on your own machine within Docker containers,
`host` network will be used to expose listening port in the container.
**1) Start NameServer**
```shell
$ docker run -it --net=host apache/rocketmq ./mqnamesrv
```
**2) Start Broker**
```shell
$ docker run -it --net=host --mount type=bind,source=/tmp/store,target=/home/rocketmq/store apache/rocketmq ./mqbroker -n localhost:9876
```
### Run RocketMQ in Kubernetes
You can also run a RocketMQ cluster within a Kubernetes cluster using [RocketMQ Operator](https://github.com/apache/rocketmq-operator).
Before your operations, make sure that `kubectl` and related kubeconfig file installed on your machine.
**1) Install CRDs**
```shell
### install CRDs
$ git clone https://github.com/apache/rocketmq-operator
$ cd rocketmq-operator && make deploy
### check whether CRDs is successfully installed
$ kubectl get crd | grep rocketmq.apache.org
brokers.rocketmq.apache.org 2022-05-12T09:23:18Z
consoles.rocketmq.apache.org 2022-05-12T09:23:19Z
nameservices.rocketmq.apache.org 2022-05-12T09:23:18Z
topictransfers.rocketmq.apache.org 2022-05-12T09:23:19Z
### check whether operator is running
$ kubectl get pods | grep rocketmq-operator
rocketmq-operator-6f65c77c49-8hwmj 1/1 Running 0 93s
```
**2) Create Cluster Instance**
```shell
### create RocketMQ cluster resource
$ cd example && kubectl create -f rocketmq_v1alpha1_rocketmq_cluster.yaml
### check whether cluster resources is running
$ kubectl get sts
NAME READY AGE
broker-0-master 1/1 107m
broker-0-replica-1 1/1 107m
name-service 1/1 107m
```
---
## Apache RocketMQ Community
* [RocketMQ Streams](https://github.com/apache/rocketmq-streams): A lightweight stream computing engine based on Apache RocketMQ.
* [RocketMQ Flink](https://github.com/apache/rocketmq-flink): The Apache RocketMQ connector of Apache Flink that supports source and sink connector in data stream and Table.
* [RocketMQ APIs](https://github.com/apache/rocketmq-apis): RocketMQ protobuf protocol.
* [RocketMQ Clients](https://github.com/apache/rocketmq-clients): gRPC/protobuf-based RocketMQ clients.
* RocketMQ Remoting-based Clients
- [RocketMQ Client CPP](https://github.com/apache/rocketmq-client-cpp)
- [RocketMQ Client Go](https://github.com/apache/rocketmq-client-go)
- [RocketMQ Client Python](https://github.com/apache/rocketmq-client-python)
- [RocketMQ Client Nodejs](https://github.com/apache/rocketmq-client-nodejs)
* [RocketMQ Spring](https://github.com/apache/rocketmq-spring): A project which helps developers quickly integrate Apache RocketMQ with Spring Boot.
* [RocketMQ Exporter](https://github.com/apache/rocketmq-exporter): An Apache RocketMQ exporter for Prometheus.
* [RocketMQ Operator](https://github.com/apache/rocketmq-operator): Providing a way to run an Apache RocketMQ cluster on Kubernetes.
* [RocketMQ Docker](https://github.com/apache/rocketmq-docker): The Git repo of the Docker Image for Apache RocketMQ.
* [RocketMQ Dashboard](https://github.com/apache/rocketmq-dashboard): Operation and maintenance console of Apache RocketMQ.
* [RocketMQ Connect](https://github.com/apache/rocketmq-connect): A tool for scalably and reliably streaming data between Apache RocketMQ and other systems.
* [RocketMQ MQTT](https://github.com/apache/rocketmq-mqtt): A new MQTT protocol architecture model, based on which Apache RocketMQ can better support messages from terminals such as IoT devices and Mobile APP.
* [RocketMQ EventBridge](https://github.com/apache/rocketmq-eventbridge): EventBridge make it easier to build a event-driven application.
* [RocketMQ Incubating Community Projects](https://github.com/apache/rocketmq-externals): Incubator community projects of Apache RocketMQ, including [logappender](https://github.com/apache/rocketmq-externals/tree/master/logappender), [rocketmq-ansible](https://github.com/apache/rocketmq-externals/tree/master/rocketmq-ansible), [rocketmq-beats-integration](https://github.com/apache/rocketmq-externals/tree/master/rocketmq-beats-integration), [rocketmq-cloudevents-binding](https://github.com/apache/rocketmq-externals/tree/master/rocketmq-cloudevents-binding), etc.
* [RocketMQ Site](https://github.com/apache/rocketmq-site): The repository for Apache RocketMQ website.
* [RocketMQ E2E](https://github.com/apache/rocketmq-e2e): A project for testing Apache RocketMQ, including end-to-end, performance, compatibility tests.
----------
## Learn it & Contact us
* Mailing Lists: <https://rocketmq.apache.org/about/contact/>
* Home: <https://rocketmq.apache.org>
* Docs: <https://rocketmq.apache.org/docs/quick-start/>
* Issues: <https://github.com/apache/rocketmq/issues>
* Rips: <https://github.com/apache/rocketmq/wiki/RocketMQ-Improvement-Proposal>
* Ask: <https://stackoverflow.com/questions/tagged/rocketmq>
* Slack: <https://rocketmq-invite-automation.herokuapp.com/>
----------
## Contributing
We always welcome new contributions, whether for trivial cleanups, [big new features](https://github.com/apache/rocketmq/wiki/RocketMQ-Improvement-Proposal) or other material rewards, more details see [here](http://rocketmq.apache.org/docs/how-to-contribute/).
----------
## License
[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) Copyright (C) Apache Software Foundation
----------
## Export Control Notice
This distribution includes cryptographic software. The country in which you currently reside may have
restrictions on the import, possession, use, and/or re-export to another country, of encryption software.
BEFORE using any encryption software, please check your country's laws, regulations and policies concerning
the import, possession, or use, and re-export of encryption software, to see if this is permitted. See
<http://www.wassenaar.org/> for more information.
The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this
software as Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software
using or performing cryptographic functions with asymmetric algorithms. The form and manner of this Apache
Software Foundation distribution makes it eligible for export under the License Exception ENC Technology
Software Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Section 740.13) for
both object code and source code.
The following provides more details on the included cryptographic software:
This software uses Apache Commons Crypto (https://commons.apache.org/proper/commons-crypto/) to
support authentication, and encryption and decryption of data sent across the network between
services.
[maven-build-image]: https://github.com/apache/rocketmq/actions/workflows/maven.yaml/badge.svg
[maven-build-url]: https://github.com/apache/rocketmq/actions/workflows/maven.yaml
[codecov-image]: https://codecov.io/gh/apache/rocketmq/branch/master/graph/badge.svg
[codecov-url]: https://codecov.io/gh/apache/rocketmq
[maven-central-image]: https://maven-badges.herokuapp.com/maven-central/org.apache.rocketmq/rocketmq-all/badge.svg
[maven-central-url]: http://search.maven.org/#search%7Cga%7C1%7Corg.apache.rocketmq
[release-image]: https://img.shields.io/badge/release-download-orange.svg
[release-url]: https://www.apache.org/licenses/LICENSE-2.0.html
[license-image]: https://img.shields.io/badge/license-Apache%202-4EB1BA.svg
[license-url]: https://www.apache.org/licenses/LICENSE-2.0.html
[average-time-to-resolve-an-issue-image]: http://isitmaintained.com/badge/resolution/apache/rocketmq.svg
[average-time-to-resolve-an-issue-url]: http://isitmaintained.com/project/apache/rocketmq
[percentage-of-issues-still-open-image]: http://isitmaintained.com/badge/open/apache/rocketmq.svg
[percentage-of-issues-still-open-url]: http://isitmaintained.com/project/apache/rocketmq
[twitter-follow-image]: https://img.shields.io/twitter/follow/ApacheRocketMQ?style=social
[twitter-follow-url]: https://twitter.com/intent/follow?screen_name=ApacheRocketMQ

144
WORKSPACE Normal file
View File

@@ -0,0 +1,144 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
RULES_JVM_EXTERNAL_TAG = "4.2"
RULES_JVM_EXTERNAL_SHA = "cd1a77b7b02e8e008439ca76fd34f5b07aecb8c752961f9640dea15e9e5ba1ca"
http_archive(
name = "rules_jvm_external",
sha256 = RULES_JVM_EXTERNAL_SHA,
strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
)
load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps")
rules_jvm_external_deps()
load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup")
rules_jvm_external_setup()
load("@rules_jvm_external//:defs.bzl", "maven_install")
maven_install(
artifacts = [
"junit:junit:4.13.2",
"com.alibaba:fastjson:1.2.76",
"com.alibaba.fastjson2:fastjson2:2.0.43",
"org.hamcrest:hamcrest-library:1.3",
"io.netty:netty-all:4.1.65.Final",
"org.assertj:assertj-core:3.22.0",
"org.mockito:mockito-core:3.10.0",
"org.powermock:powermock-module-junit4:2.0.9",
"org.powermock:powermock-api-mockito2:2.0.9",
"org.powermock:powermock-core:2.0.9",
"com.github.luben:zstd-jni:1.5.2-2",
"org.lz4:lz4-java:1.8.0",
"commons-validator:commons-validator:1.7",
"org.apache.commons:commons-lang3:3.12.0",
"org.hamcrest:hamcrest-core:1.3",
"io.openmessaging.storage:dledger:0.3.1",
"net.java.dev.jna:jna:4.2.2",
"ch.qos.logback:logback-classic:1.2.10",
"ch.qos.logback:logback-core:1.2.10",
"io.opentracing:opentracing-api:0.33.0",
"io.opentracing:opentracing-mock:0.33.0",
"commons-collections:commons-collections:3.2.2",
"org.awaitility:awaitility:4.1.0",
"commons-cli:commons-cli:1.5.0",
"com.google.guava:guava:31.0.1-jre",
"org.yaml:snakeyaml:1.30",
"commons-codec:commons-codec:1.13",
"commons-io:commons-io:2.7",
"com.google.truth:truth:0.30",
"org.bouncycastle:bcpkix-jdk15on:1.69",
"com.google.code.gson:gson:2.8.9",
"com.googlecode.concurrentlinkedhashmap:concurrentlinkedhashmap-lru:1.4.2",
"org.apache.rocketmq:rocketmq-proto:2.0.4",
"com.google.protobuf:protobuf-java:3.20.1",
"com.google.protobuf:protobuf-java-util:3.20.1",
"com.conversantmedia:disruptor:1.2.10",
"org.apache.tomcat:annotations-api:6.0.53",
"com.google.code.findbugs:jsr305:3.0.2",
"org.checkerframework:checker-qual:3.12.0",
"org.reflections:reflections:0.9.11",
"org.openjdk.jmh:jmh-core:1.19",
"org.openjdk.jmh:jmh-generator-annprocess:1.19",
"com.github.ben-manes.caffeine:caffeine:2.9.3",
"io.grpc:grpc-services:1.47.0",
"io.grpc:grpc-netty-shaded:1.47.0",
"io.grpc:grpc-context:1.47.0",
"io.grpc:grpc-stub:1.47.0",
"io.grpc:grpc-api:1.47.0",
"io.grpc:grpc-testing:1.47.0",
"org.springframework:spring-core:5.3.26",
"io.opentelemetry:opentelemetry-exporter-otlp:1.29.0",
"io.opentelemetry:opentelemetry-exporter-prometheus:1.29.0-alpha",
"io.opentelemetry:opentelemetry-exporter-logging:1.29.0",
"io.opentelemetry:opentelemetry-sdk:1.29.0",
"io.opentelemetry:opentelemetry-exporter-logging-otlp:1.29.0",
"com.squareup.okio:okio-jvm:3.0.0",
"io.opentelemetry:opentelemetry-api:1.29.0",
"io.opentelemetry:opentelemetry-sdk-metrics:1.29.0",
"io.opentelemetry:opentelemetry-sdk-common:1.29.0",
"io.github.aliyunmq:rocketmq-slf4j-api:1.0.0",
"io.github.aliyunmq:rocketmq-logback-classic:1.0.0",
"org.slf4j:jul-to-slf4j:2.0.6",
"org.jetbrains:annotations:23.1.0",
"io.github.aliyunmq:rocketmq-shaded-slf4j-api-bridge:1.0.0",
"software.amazon.awssdk:s3:2.20.29",
"com.fasterxml.jackson.core:jackson-databind:2.13.4.2",
"com.adobe.testing:s3mock-junit4:2.11.0",
"io.github.aliyunmq:rocketmq-grpc-netty-codec-haproxy:1.0.0",
"org.apache.rocketmq:rocketmq-rocksdb:1.0.2",
"com.alipay.sofa:jraft-core:1.3.14",
"com.alipay.sofa:hessian:3.3.6",
"io.netty:netty-tcnative-boringssl-static:2.0.48.Final",
"org.mockito:mockito-junit-jupiter:4.11.0",
"com.alibaba.fastjson2:fastjson2:2.0.43",
"org.junit.jupiter:junit-jupiter-api:5.9.1",
],
fetch_sources = True,
repositories = [
# Private repositories are supported through HTTP Basic auth
"https://repo1.maven.org/maven2",
],
)
http_archive(
name = "io_buildbuddy_buildbuddy_toolchain",
sha256 = "b12273608db627eb14051eb75f8a2134590172cd69392086d392e25f3954ea6e",
strip_prefix = "buildbuddy-toolchain-8d5d18373adfca9d8e33b4812915abc9b132f1ee",
urls = ["https://github.com/buildbuddy-io/buildbuddy-toolchain/archive/8d5d18373adfca9d8e33b4812915abc9b132f1ee.tar.gz"],
)
load("@io_buildbuddy_buildbuddy_toolchain//:deps.bzl", "buildbuddy_deps")
buildbuddy_deps()
load("@io_buildbuddy_buildbuddy_toolchain//:rules.bzl", "buildbuddy")
buildbuddy(name = "buildbuddy_toolchain")
http_archive(
name = "bazel_toolchains",
sha256 = "1adf5db506a7e3c465a26988514cfc3971af6d5b3c2218925cd6e71ee443fc3f",
strip_prefix = "bazel-toolchains-4.0.0",
urls = [
"https://github.com/bazelbuild/bazel-toolchains/releases/download/4.0.0/bazel-toolchains-4.0.0.tar.gz",
"https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/releases/download/4.0.0/bazel-toolchains-4.0.0.tar.gz",
],
)

77
auth/BUILD.bazel Normal file
View File

@@ -0,0 +1,77 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
load("//bazel:GenTestRules.bzl", "GenTestRules")
java_library(
name = "auth",
srcs = glob(["src/main/java/**/*.java"]),
visibility = ["//visibility:public"],
deps = [
"//common",
"//remoting",
"//srvutil",
"//client",
"@maven//:commons_codec_commons_codec",
"@maven//:org_apache_commons_commons_lang3",
"@maven//:commons_collections_commons_collections",
"@maven//:com_alibaba_fastjson2_fastjson2",
"@maven//:org_apache_rocketmq_rocketmq_proto",
"@maven//:org_slf4j_slf4j_api",
"@maven//:com_github_ben_manes_caffeine_caffeine",
"@maven//:io_grpc_grpc_api",
"@maven//:com_google_protobuf_protobuf_java",
"@maven//:com_google_protobuf_protobuf_java_util",
"@maven//:io_netty_netty_all",
"@maven//:com_google_guava_guava",
"@maven//:org_apache_rocketmq_rocketmq_rocksdb",
],
)
java_library(
name = "tests",
srcs = glob(["src/test/java/**/*.java"]),
resources = glob(["src/test/resources/**/*.yml"]),
visibility = ["//visibility:public"],
deps = [
":auth",
"//:test_deps",
"//common",
"//remoting",
"//client",
"@maven//:commons_codec_commons_codec",
"@maven//:org_apache_commons_commons_lang3",
"@maven//:commons_collections_commons_collections",
"@maven//:com_alibaba_fastjson2_fastjson2",
"@maven//:org_apache_rocketmq_rocketmq_proto",
"@maven//:org_slf4j_slf4j_api",
"@maven//:com_github_ben_manes_caffeine_caffeine",
"@maven//:io_grpc_grpc_api",
"@maven//:com_google_protobuf_protobuf_java",
"@maven//:com_google_protobuf_protobuf_java_util",
"@maven//:io_netty_netty_all",
"@maven//:com_google_guava_guava",
"@maven//:org_apache_rocketmq_rocketmq_rocksdb",
],
)
GenTestRules(
name = "GeneratedTestRules",
test_files = glob(["src/test/java/**/*Test.java"]),
deps = [
":tests",
],
)

78
auth/pom.xml Normal file
View File

@@ -0,0 +1,78 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
license agreements. See the NOTICE file distributed with this work for additional
information regarding copyright ownership. The ASF licenses this file to
You under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of
the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License. -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-all</artifactId>
<version>5.3.4-SNAPSHOT</version>
</parent>
<artifactId>rocketmq-auth</artifactId>
<name>rocketmq-auth ${project.version}</name>
<properties>
<project.root>${basedir}/..</project.root>
</properties>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>rocketmq-proto</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>rocketmq-client</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<exclusions>
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<forkCount>1</forkCount>
<reuseForks>false</reuseForks>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,43 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication;
import java.util.function.Supplier;
import org.apache.rocketmq.auth.authentication.context.AuthenticationContext;
import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
import org.apache.rocketmq.auth.authentication.strategy.AuthenticationStrategy;
import org.apache.rocketmq.auth.config.AuthConfig;
public class AuthenticationEvaluator {
private final AuthenticationStrategy authenticationStrategy;
public AuthenticationEvaluator(AuthConfig authConfig) {
this(authConfig, null);
}
public AuthenticationEvaluator(AuthConfig authConfig, Supplier<?> metadataService) {
this.authenticationStrategy = AuthenticationFactory.getStrategy(authConfig, metadataService);
}
public void evaluate(AuthenticationContext context) {
if (context == null) {
return;
}
this.authenticationStrategy.evaluate(context);
}
}

View File

@@ -0,0 +1,29 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.builder;
import com.google.protobuf.GeneratedMessageV3;
import io.grpc.Metadata;
import io.netty.channel.ChannelHandlerContext;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public interface AuthenticationContextBuilder<AuthenticationContext> {
AuthenticationContext build(Metadata metadata, GeneratedMessageV3 request);
AuthenticationContext build(ChannelHandlerContext context, RemotingCommand request);
}

View File

@@ -0,0 +1,131 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.builder;
import com.google.protobuf.GeneratedMessageV3;
import io.grpc.Metadata;
import io.netty.channel.ChannelHandlerContext;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.acl.common.AclUtils;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext;
import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
import org.apache.rocketmq.common.MQVersion;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.constant.CommonConstants;
import org.apache.rocketmq.common.constant.GrpcConstants;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class DefaultAuthenticationContextBuilder implements AuthenticationContextBuilder<DefaultAuthenticationContext> {
private static final String CREDENTIAL = "Credential";
private static final String SIGNATURE = "Signature";
@Override
public DefaultAuthenticationContext build(Metadata metadata, GeneratedMessageV3 request) {
try {
DefaultAuthenticationContext context = new DefaultAuthenticationContext();
context.setChannelId(metadata.get(GrpcConstants.CHANNEL_ID));
context.setRpcCode(request.getDescriptorForType().getFullName());
String authorization = metadata.get(GrpcConstants.AUTHORIZATION);
if (StringUtils.isEmpty(authorization)) {
return context;
}
String datetime = metadata.get(GrpcConstants.DATE_TIME);
if (StringUtils.isEmpty(datetime)) {
throw new AuthenticationException("datetime is null.");
}
String[] result = authorization.split(CommonConstants.SPACE, 2);
if (result.length != 2) {
throw new AuthenticationException("authentication header is incorrect.");
}
String[] keyValues = result[1].split(CommonConstants.COMMA);
for (String keyValue : keyValues) {
String[] kv = keyValue.trim().split(CommonConstants.EQUAL, 2);
int kvLength = kv.length;
if (kv.length != 2) {
throw new AuthenticationException("authentication keyValues length is incorrect, actual length={}.", kvLength);
}
String authItem = kv[0];
if (CREDENTIAL.equals(authItem)) {
String[] credential = kv[1].split(CommonConstants.SLASH);
int credentialActualLength = credential.length;
if (credentialActualLength == 0) {
throw new AuthenticationException("authentication credential length is incorrect, actual length={}.", credentialActualLength);
}
context.setUsername(credential[0]);
continue;
}
if (SIGNATURE.equals(authItem)) {
context.setSignature(this.hexToBase64(kv[1]));
}
}
context.setContent(datetime.getBytes(StandardCharsets.UTF_8));
return context;
} catch (AuthenticationException e) {
throw e;
} catch (Throwable e) {
throw new AuthenticationException("create authentication context error.", e);
}
}
@Override
public DefaultAuthenticationContext build(ChannelHandlerContext context, RemotingCommand request) {
HashMap<String, String> fields = request.getExtFields();
DefaultAuthenticationContext result = new DefaultAuthenticationContext();
result.setChannelId(context.channel().id().asLongText());
result.setRpcCode(String.valueOf(request.getCode()));
if (MapUtils.isEmpty(fields)) {
return result;
}
if (!fields.containsKey(SessionCredentials.ACCESS_KEY)) {
return result;
}
result.setUsername(fields.get(SessionCredentials.ACCESS_KEY));
result.setSignature(fields.get(SessionCredentials.SIGNATURE));
// Content
SortedMap<String, String> map = new TreeMap<>();
for (Map.Entry<String, String> entry : fields.entrySet()) {
if (request.getVersion() <= MQVersion.Version.V4_9_3.ordinal() &&
MixAll.UNIQUE_MSG_QUERY_FLAG.equals(entry.getKey())) {
continue;
}
if (!SessionCredentials.SIGNATURE.equals(entry.getKey())) {
map.put(entry.getKey(), entry.getValue());
}
}
result.setContent(AclUtils.combineRequestContent(request, map));
return result;
}
public String hexToBase64(String input) throws DecoderException {
byte[] bytes = Hex.decodeHex(input);
return Base64.encodeBase64String(bytes);
}
}

View File

@@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.chain;
import java.security.MessageDigest;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.acl.common.AclSigner;
import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext;
import org.apache.rocketmq.auth.authentication.enums.UserStatus;
import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
import org.apache.rocketmq.auth.authentication.model.User;
import org.apache.rocketmq.auth.authentication.provider.AuthenticationMetadataProvider;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.chain.Handler;
import org.apache.rocketmq.common.chain.HandlerChain;
public class DefaultAuthenticationHandler implements Handler<DefaultAuthenticationContext, CompletableFuture<Void>> {
private final AuthenticationMetadataProvider authenticationMetadataProvider;
public DefaultAuthenticationHandler(AuthConfig config, Supplier<?> metadataService) {
this.authenticationMetadataProvider = AuthenticationFactory.getMetadataProvider(config, metadataService);
}
@Override
public CompletableFuture<Void> handle(DefaultAuthenticationContext context,
HandlerChain<DefaultAuthenticationContext, CompletableFuture<Void>> chain) {
return getUser(context).thenAccept(user -> doAuthenticate(context, user));
}
protected CompletableFuture<User> getUser(DefaultAuthenticationContext context) {
if (this.authenticationMetadataProvider == null) {
throw new AuthenticationException("The authenticationMetadataProvider is not configured");
}
if (StringUtils.isEmpty(context.getUsername())) {
throw new AuthenticationException("username cannot be null.");
}
return this.authenticationMetadataProvider.getUser(context.getUsername());
}
protected void doAuthenticate(DefaultAuthenticationContext context, User user) {
if (user == null) {
throw new AuthenticationException("User:{} is not found.", context.getUsername());
}
if (user.getUserStatus() == UserStatus.DISABLE) {
throw new AuthenticationException("User:{} is disabled.", context.getUsername());
}
String signature = AclSigner.calSignature(context.getContent(), user.getPassword());
if (context.getSignature() == null
|| !MessageDigest.isEqual(signature.getBytes(AclSigner.DEFAULT_CHARSET), context.getSignature().getBytes(AclSigner.DEFAULT_CHARSET))) {
throw new AuthenticationException("check signature failed.");
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.context;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
public abstract class AuthenticationContext {
private String channelId;
private String rpcCode;
private Map<String, Object> extInfo;
public String getChannelId() {
return channelId;
}
public void setChannelId(String channelId) {
this.channelId = channelId;
}
public String getRpcCode() {
return rpcCode;
}
public void setRpcCode(String rpcCode) {
this.rpcCode = rpcCode;
}
@SuppressWarnings("unchecked")
public <T> T getExtInfo(String key) {
if (StringUtils.isBlank(key)) {
return null;
}
if (this.extInfo == null) {
return null;
}
Object value = this.extInfo.get(key);
if (value == null) {
return null;
}
return (T) value;
}
public void setExtInfo(String key, Object value) {
if (StringUtils.isBlank(key) || value == null) {
return;
}
if (this.extInfo == null) {
this.extInfo = new HashMap<>();
}
this.extInfo.put(key, value);
}
public boolean hasExtInfo(String key) {
Object value = getExtInfo(key);
return value != null;
}
public Map<String, Object> getExtInfo() {
return extInfo;
}
public void setExtInfo(Map<String, Object> extInfo) {
this.extInfo = extInfo;
}
}

View File

@@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.context;
public class DefaultAuthenticationContext extends AuthenticationContext {
private String username;
private byte[] content;
private String signature;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public byte[] getContent() {
return content;
}
public void setContent(byte[] content) {
this.content = content;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
}

View File

@@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.enums;
import com.alibaba.fastjson2.annotation.JSONField;
import org.apache.commons.lang3.StringUtils;
public enum SubjectType {
USER((byte) 1, "User");
@JSONField(value = true)
private final byte code;
private final String name;
SubjectType(byte code, String name) {
this.code = code;
this.name = name;
}
public static SubjectType getByName(String name) {
for (SubjectType subjectType : SubjectType.values()) {
if (StringUtils.equalsIgnoreCase(subjectType.getName(), name)) {
return subjectType;
}
}
return null;
}
public byte getCode() {
return code;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.enums;
import com.alibaba.fastjson2.annotation.JSONField;
import org.apache.commons.lang3.StringUtils;
public enum UserStatus {
ENABLE((byte) 1, "enable"),
DISABLE((byte) 2, "disable");
@JSONField(value = true)
private final byte code;
private final String name;
UserStatus(byte code, String name) {
this.code = code;
this.name = name;
}
public static UserStatus getByName(String name) {
for (UserStatus subjectType : UserStatus.values()) {
if (StringUtils.equalsIgnoreCase(subjectType.getName(), name)) {
return subjectType;
}
}
return null;
}
public byte getCode() {
return code;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.enums;
import com.alibaba.fastjson2.annotation.JSONField;
import org.apache.commons.lang3.StringUtils;
public enum UserType {
SUPER((byte) 1, "Super"),
NORMAL((byte) 2, "Normal");
@JSONField(value = true)
private final byte code;
private final String name;
UserType(byte code, String name) {
this.code = code;
this.name = name;
}
public static UserType getByName(String name) {
for (UserType subjectType : UserType.values()) {
if (StringUtils.equalsIgnoreCase(subjectType.getName(), name)) {
return subjectType;
}
}
return null;
}
public byte getCode() {
return code;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.exception;
import org.slf4j.helpers.MessageFormatter;
public class AuthenticationException extends RuntimeException {
public AuthenticationException(String message) {
super(message);
}
public AuthenticationException(String message, Throwable cause) {
super(message, cause);
}
public AuthenticationException(String messagePattern, Object... argArray) {
super(MessageFormatter.arrayFormat(messagePattern, argArray).getMessage());
}
}

View File

@@ -0,0 +1,153 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.factory;
import com.google.protobuf.GeneratedMessageV3;
import io.grpc.Metadata;
import io.netty.channel.ChannelHandlerContext;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.auth.authentication.AuthenticationEvaluator;
import org.apache.rocketmq.auth.authentication.context.AuthenticationContext;
import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager;
import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManagerImpl;
import org.apache.rocketmq.auth.authentication.provider.AuthenticationMetadataProvider;
import org.apache.rocketmq.auth.authentication.provider.AuthenticationProvider;
import org.apache.rocketmq.auth.authentication.provider.DefaultAuthenticationProvider;
import org.apache.rocketmq.auth.authentication.strategy.AuthenticationStrategy;
import org.apache.rocketmq.auth.authentication.strategy.StatelessAuthenticationStrategy;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class AuthenticationFactory {
private static final Map<String, Object> INSTANCE_MAP = new HashMap<>();
private static final String PROVIDER_PREFIX = "PROVIDER_";
private static final String METADATA_PROVIDER_PREFIX = "METADATA_PROVIDER_";
private static final String EVALUATOR_PREFIX = "EVALUATOR_";
@SuppressWarnings("unchecked")
public static AuthenticationProvider<AuthenticationContext> getProvider(AuthConfig config) {
if (config == null) {
return null;
}
return computeIfAbsent(PROVIDER_PREFIX + config.getConfigName(), key -> {
try {
Class<? extends AuthenticationProvider<? extends AuthenticationContext>> clazz =
DefaultAuthenticationProvider.class;
if (StringUtils.isNotBlank(config.getAuthenticationProvider())) {
clazz = (Class<? extends AuthenticationProvider<? extends AuthenticationContext>>) Class.forName(config.getAuthenticationProvider());
}
return (AuthenticationProvider<AuthenticationContext>) clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("Failed to load the authentication provider.", e);
}
});
}
public static AuthenticationMetadataProvider getMetadataProvider(AuthConfig config) {
return getMetadataProvider(config, null);
}
public static AuthenticationMetadataManager getMetadataManager(AuthConfig config) {
return new AuthenticationMetadataManagerImpl(config);
}
@SuppressWarnings("unchecked")
public static AuthenticationMetadataProvider getMetadataProvider(AuthConfig config, Supplier<?> metadataService) {
if (config == null) {
return null;
}
return computeIfAbsent(METADATA_PROVIDER_PREFIX + config.getConfigName(), key -> {
try {
if (StringUtils.isBlank(config.getAuthenticationMetadataProvider())) {
return null;
}
Class<? extends AuthenticationMetadataProvider> clazz = (Class<? extends AuthenticationMetadataProvider>)
Class.forName(config.getAuthenticationMetadataProvider());
AuthenticationMetadataProvider result = clazz.getDeclaredConstructor().newInstance();
result.initialize(config, metadataService);
return result;
} catch (Exception e) {
throw new RuntimeException("Failed to load the authentication metadata provider", e);
}
});
}
public static AuthenticationEvaluator getEvaluator(AuthConfig config) {
return computeIfAbsent(EVALUATOR_PREFIX + config.getConfigName(), key -> new AuthenticationEvaluator(config));
}
public static AuthenticationEvaluator getEvaluator(AuthConfig config, Supplier<?> metadataService) {
return computeIfAbsent(EVALUATOR_PREFIX + config.getConfigName(), key -> new AuthenticationEvaluator(config, metadataService));
}
@SuppressWarnings("unchecked")
public static AuthenticationStrategy getStrategy(AuthConfig config, Supplier<?> metadataService) {
try {
Class<? extends AuthenticationStrategy> clazz = StatelessAuthenticationStrategy.class;
if (StringUtils.isNotBlank(config.getAuthenticationStrategy())) {
clazz = (Class<? extends AuthenticationStrategy>) Class.forName(config.getAuthenticationStrategy());
}
return clazz.getDeclaredConstructor(AuthConfig.class, Supplier.class).newInstance(config, metadataService);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static AuthenticationContext newContext(AuthConfig config, Metadata metadata, GeneratedMessageV3 request) {
AuthenticationProvider<AuthenticationContext> authenticationProvider = getProvider(config);
if (authenticationProvider == null) {
return null;
}
return authenticationProvider.newContext(metadata, request);
}
public static AuthenticationContext newContext(AuthConfig config, ChannelHandlerContext context,
RemotingCommand command) {
AuthenticationProvider<AuthenticationContext> authenticationProvider = getProvider(config);
if (authenticationProvider == null) {
return null;
}
return authenticationProvider.newContext(context, command);
}
@SuppressWarnings("unchecked")
private static <V> V computeIfAbsent(String key, Function<String, ? extends V> function) {
Object result = null;
if (INSTANCE_MAP.containsKey(key)) {
result = INSTANCE_MAP.get(key);
}
if (result == null) {
synchronized (INSTANCE_MAP) {
if (INSTANCE_MAP.containsKey(key)) {
result = INSTANCE_MAP.get(key);
}
if (result == null) {
result = function.apply(key);
if (result != null) {
INSTANCE_MAP.put(key, result);
}
}
}
}
return result != null ? (V) result : null;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.manager;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.auth.authentication.model.User;
import org.apache.rocketmq.auth.config.AuthConfig;
public interface AuthenticationMetadataManager {
void shutdown();
void initUser(AuthConfig authConfig);
CompletableFuture<Void> createUser(User user);
CompletableFuture<Void> updateUser(User user);
CompletableFuture<Void> deleteUser(String username);
CompletableFuture<User> getUser(String username);
CompletableFuture<List<User>> listUser(String filter);
CompletableFuture<Boolean> isSuperUser(String username);
}

View File

@@ -0,0 +1,222 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.manager;
import com.alibaba.fastjson2.JSON;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.auth.authentication.enums.UserStatus;
import org.apache.rocketmq.auth.authentication.enums.UserType;
import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
import org.apache.rocketmq.auth.authentication.model.User;
import org.apache.rocketmq.auth.authentication.provider.AuthenticationMetadataProvider;
import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory;
import org.apache.rocketmq.auth.authorization.provider.AuthorizationMetadataProvider;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.utils.ExceptionUtils;
public class AuthenticationMetadataManagerImpl implements AuthenticationMetadataManager {
private final AuthenticationMetadataProvider authenticationMetadataProvider;
private final AuthorizationMetadataProvider authorizationMetadataProvider;
public AuthenticationMetadataManagerImpl(AuthConfig authConfig) {
this.authenticationMetadataProvider = AuthenticationFactory.getMetadataProvider(authConfig);
this.authorizationMetadataProvider = AuthorizationFactory.getMetadataProvider(authConfig);
this.initUser(authConfig);
}
@Override
public void shutdown() {
if (this.authenticationMetadataProvider != null) {
this.authenticationMetadataProvider.shutdown();
}
if (this.authorizationMetadataProvider != null) {
this.authorizationMetadataProvider.shutdown();
}
}
@Override
public void initUser(AuthConfig authConfig) {
if (authConfig == null) {
return;
}
if (StringUtils.isNotBlank(authConfig.getInitAuthenticationUser())) {
try {
User initUser = JSON.parseObject(authConfig.getInitAuthenticationUser(), User.class);
initUser.setUserType(UserType.SUPER);
this.getUser(initUser.getUsername()).thenCompose(user -> {
if (user != null) {
return CompletableFuture.completedFuture(null);
}
return this.createUser(initUser);
}).join();
} catch (Exception e) {
throw new AuthenticationException("Init authentication user error.", e);
}
}
if (StringUtils.isNotBlank(authConfig.getInnerClientAuthenticationCredentials())) {
try {
SessionCredentials credentials = JSON.parseObject(authConfig.getInnerClientAuthenticationCredentials(), SessionCredentials.class);
User innerUser = User.of(credentials.getAccessKey(), credentials.getSecretKey(), UserType.SUPER);
this.getUser(innerUser.getUsername()).thenCompose(user -> {
if (user != null) {
return CompletableFuture.completedFuture(null);
}
return this.createUser(innerUser);
}).join();
} catch (Exception e) {
throw new AuthenticationException("Init inner client authentication credentials error", e);
}
}
}
@Override
public CompletableFuture<Void> createUser(User user) {
CompletableFuture<Void> result = new CompletableFuture<>();
try {
this.validate(user, true);
if (user.getUserType() == null) {
user.setUserType(UserType.NORMAL);
}
if (user.getUserStatus() == null) {
user.setUserStatus(UserStatus.ENABLE);
}
result = this.getAuthenticationMetadataProvider().getUser(user.getUsername()).thenCompose(old -> {
if (old != null) {
throw new AuthenticationException("The user is existed");
}
return this.getAuthenticationMetadataProvider().createUser(user);
});
} catch (Exception e) {
this.handleException(e, result);
}
return result;
}
@Override
public CompletableFuture<Void> updateUser(User user) {
CompletableFuture<Void> result = new CompletableFuture<>();
try {
this.validate(user, false);
result = this.getAuthenticationMetadataProvider().getUser(user.getUsername()).thenCompose(old -> {
if (old == null) {
throw new AuthenticationException("The user is not exist");
}
if (StringUtils.isNotBlank(user.getPassword())) {
old.setPassword(user.getPassword());
}
if (user.getUserType() != null) {
old.setUserType(user.getUserType());
}
if (user.getUserStatus() != null) {
old.setUserStatus(user.getUserStatus());
}
return this.getAuthenticationMetadataProvider().updateUser(old);
});
} catch (Exception e) {
this.handleException(e, result);
}
return result;
}
@Override
public CompletableFuture<Void> deleteUser(String username) {
CompletableFuture<Void> result = new CompletableFuture<>();
try {
if (StringUtils.isBlank(username)) {
throw new AuthenticationException("username can not be blank");
}
CompletableFuture<Void> deleteUser = this.getAuthenticationMetadataProvider().deleteUser(username);
CompletableFuture<Void> deleteAcl = this.getAuthorizationMetadataProvider().deleteAcl(User.of(username));
return CompletableFuture.allOf(deleteUser, deleteAcl);
} catch (Exception e) {
this.handleException(e, result);
}
return result;
}
@Override
public CompletableFuture<User> getUser(String username) {
CompletableFuture<User> result = new CompletableFuture<>();
try {
if (StringUtils.isBlank(username)) {
throw new AuthenticationException("username can not be blank");
}
result = this.getAuthenticationMetadataProvider().getUser(username);
} catch (Exception e) {
this.handleException(e, result);
}
return result;
}
@Override
public CompletableFuture<List<User>> listUser(String filter) {
CompletableFuture<List<User>> result = new CompletableFuture<>();
try {
result = this.getAuthenticationMetadataProvider().listUser(filter);
} catch (Exception e) {
this.handleException(e, result);
}
return result;
}
@Override
public CompletableFuture<Boolean> isSuperUser(String username) {
return this.getUser(username).thenApply(user -> {
if (user == null) {
throw new AuthenticationException("User:{} is not found", username);
}
return user.getUserType() == UserType.SUPER;
});
}
private void validate(User user, boolean isCreate) {
if (user == null) {
throw new AuthenticationException("user can not be null");
}
if (StringUtils.isBlank(user.getUsername())) {
throw new AuthenticationException("username can not be blank");
}
if (isCreate && StringUtils.isBlank(user.getPassword())) {
throw new AuthenticationException("password can not be blank");
}
}
private void handleException(Exception e, CompletableFuture<?> result) {
Throwable throwable = ExceptionUtils.getRealException(e);
result.completeExceptionally(throwable);
}
private AuthenticationMetadataProvider getAuthenticationMetadataProvider() {
if (authenticationMetadataProvider == null) {
throw new IllegalStateException("The authenticationMetadataProvider is not configured");
}
return authenticationMetadataProvider;
}
private AuthorizationMetadataProvider getAuthorizationMetadataProvider() {
if (authorizationMetadataProvider == null) {
throw new IllegalStateException("The authorizationMetadataProvider is not configured");
}
return authorizationMetadataProvider;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.model;
import com.alibaba.fastjson2.annotation.JSONField;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.auth.authentication.enums.SubjectType;
import org.apache.rocketmq.common.constant.CommonConstants;
public interface Subject {
@JSONField(serialize = false)
String getSubjectKey();
SubjectType getSubjectType();
default boolean isSubject(SubjectType subjectType) {
return subjectType == this.getSubjectType();
}
@SuppressWarnings("unchecked")
static <T extends Subject> T of(String subjectKey) {
String type = StringUtils.substringBefore(subjectKey, CommonConstants.COLON);
SubjectType subjectType = SubjectType.getByName(type);
if (subjectType == null) {
return null;
}
if (subjectType == SubjectType.USER) {
return (T) User.of(StringUtils.substringAfter(subjectKey, CommonConstants.COLON));
}
return null;
}
}

View File

@@ -0,0 +1,96 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.model;
import org.apache.rocketmq.auth.authentication.enums.SubjectType;
import org.apache.rocketmq.auth.authentication.enums.UserStatus;
import org.apache.rocketmq.auth.authentication.enums.UserType;
import org.apache.rocketmq.common.constant.CommonConstants;
public class User implements Subject {
private String username;
private String password;
private UserType userType;
private UserStatus userStatus;
public static User of(String username) {
User user = new User();
user.setUsername(username);
return user;
}
public static User of(String username, String password) {
User user = new User();
user.setUsername(username);
user.setPassword(password);
return user;
}
public static User of(String username, String password, UserType userType) {
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setUserType(userType);
return user;
}
@Override
public String getSubjectKey() {
return this.getSubjectType().getName() + CommonConstants.COLON + this.username;
}
@Override
public SubjectType getSubjectType() {
return SubjectType.USER;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public UserType getUserType() {
return userType;
}
public void setUserType(UserType userType) {
this.userType = userType;
}
public UserStatus getUserStatus() {
return userStatus;
}
public void setUserStatus(UserStatus userStatus) {
this.userStatus = userStatus;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.provider;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import org.apache.rocketmq.auth.authentication.model.User;
import org.apache.rocketmq.auth.config.AuthConfig;
public interface AuthenticationMetadataProvider {
void initialize(AuthConfig authConfig, Supplier<?> metadataService);
void shutdown();
CompletableFuture<Void> createUser(User user);
CompletableFuture<Void> deleteUser(String username);
CompletableFuture<Void> updateUser(User user);
CompletableFuture<User> getUser(String username);
CompletableFuture<List<User>> listUser(String filter);
}

View File

@@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.provider;
import com.google.protobuf.GeneratedMessageV3;
import io.grpc.Metadata;
import io.netty.channel.ChannelHandlerContext;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public interface AuthenticationProvider<AuthenticationContext> {
void initialize(AuthConfig config, Supplier<?> metadataService);
CompletableFuture<Void> authenticate(AuthenticationContext context);
AuthenticationContext newContext(Metadata metadata, GeneratedMessageV3 request);
AuthenticationContext newContext(ChannelHandlerContext context, RemotingCommand command);
}

View File

@@ -0,0 +1,81 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.provider;
import com.google.protobuf.GeneratedMessageV3;
import io.grpc.Metadata;
import io.netty.channel.ChannelHandlerContext;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.auth.authentication.builder.AuthenticationContextBuilder;
import org.apache.rocketmq.auth.authentication.builder.DefaultAuthenticationContextBuilder;
import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext;
import org.apache.rocketmq.auth.authentication.chain.DefaultAuthenticationHandler;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.chain.HandlerChain;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DefaultAuthenticationProvider implements AuthenticationProvider<DefaultAuthenticationContext> {
protected final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_AUTH_AUDIT_LOGGER_NAME);
protected AuthConfig authConfig;
protected Supplier<?> metadataService;
protected AuthenticationContextBuilder<DefaultAuthenticationContext> authenticationContextBuilder;
@Override
public void initialize(AuthConfig config, Supplier<?> metadataService) {
this.authConfig = config;
this.metadataService = metadataService;
this.authenticationContextBuilder = new DefaultAuthenticationContextBuilder();
}
@Override
public CompletableFuture<Void> authenticate(DefaultAuthenticationContext context) {
return this.newHandlerChain().handle(context)
.whenComplete((nil, ex) -> doAuditLog(context, ex));
}
@Override
public DefaultAuthenticationContext newContext(Metadata metadata, GeneratedMessageV3 request) {
return this.authenticationContextBuilder.build(metadata, request);
}
@Override
public DefaultAuthenticationContext newContext(ChannelHandlerContext context, RemotingCommand command) {
return this.authenticationContextBuilder.build(context, command);
}
protected HandlerChain<DefaultAuthenticationContext, CompletableFuture<Void>> newHandlerChain() {
return HandlerChain.<DefaultAuthenticationContext, CompletableFuture<Void>>create()
.addNext(new DefaultAuthenticationHandler(this.authConfig, metadataService));
}
protected void doAuditLog(DefaultAuthenticationContext context, Throwable ex) {
if (StringUtils.isBlank(context.getUsername())) {
return;
}
if (ex != null) {
log.info("[AUTHENTICATION] User:{} is authenticated failed with Signature = {}.", context.getUsername(), context.getSignature());
} else {
log.debug("[AUTHENTICATION] User:{} is authenticated success with Signature = {}.", context.getUsername(), context.getSignature());
}
}
}

View File

@@ -0,0 +1,167 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.provider;
import com.alibaba.fastjson2.JSON;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
import org.apache.rocketmq.auth.authentication.model.User;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.config.ConfigRocksDBStorage;
import org.apache.rocketmq.common.thread.ThreadPoolMonitor;
import org.rocksdb.RocksIterator;
public class LocalAuthenticationMetadataProvider implements AuthenticationMetadataProvider {
private ConfigRocksDBStorage storage;
private LoadingCache<String, User> userCache;
@Override
public void initialize(AuthConfig authConfig, Supplier<?> metadataService) {
this.storage = new ConfigRocksDBStorage(authConfig.getAuthConfigPath() + File.separator + "users");
if (!this.storage.start()) {
throw new RuntimeException("Failed to load rocksdb for auth_user, please check whether it is occupied");
}
ThreadPoolExecutor cacheRefreshExecutor = ThreadPoolMonitor.createAndMonitor(
1,
1,
1000 * 60,
TimeUnit.MILLISECONDS,
"UserCacheRefresh",
100000
);
this.userCache = Caffeine.newBuilder()
.maximumSize(authConfig.getUserCacheMaxNum())
.expireAfterAccess(authConfig.getUserCacheExpiredSecond(), TimeUnit.SECONDS)
.refreshAfterWrite(authConfig.getUserCacheRefreshSecond(), TimeUnit.SECONDS)
.executor(cacheRefreshExecutor)
.build(new UserCacheLoader(this.storage));
}
@Override
public CompletableFuture<Void> createUser(User user) {
try {
byte[] keyBytes = user.getUsername().getBytes(StandardCharsets.UTF_8);
byte[] valueBytes = JSON.toJSONBytes(user);
this.storage.put(keyBytes, keyBytes.length, valueBytes);
this.storage.flushWAL();
this.userCache.invalidate(user.getUsername());
} catch (Exception e) {
throw new AuthenticationException("create user to RocksDB failed", e);
}
return CompletableFuture.completedFuture(null);
}
@Override
public CompletableFuture<Void> deleteUser(String username) {
try {
this.storage.delete(username.getBytes(StandardCharsets.UTF_8));
this.storage.flushWAL();
this.userCache.invalidate(username);
} catch (Exception e) {
throw new AuthenticationException("delete user from RocksDB failed", e);
}
return CompletableFuture.completedFuture(null);
}
@Override
public CompletableFuture<Void> updateUser(User user) {
try {
byte[] keyBytes = user.getUsername().getBytes(StandardCharsets.UTF_8);
byte[] valueBytes = JSON.toJSONBytes(user);
this.storage.put(keyBytes, keyBytes.length, valueBytes);
this.storage.flushWAL();
this.userCache.invalidate(user.getUsername());
} catch (Exception e) {
throw new AuthenticationException("update user to RocksDB failed", e);
}
return CompletableFuture.completedFuture(null);
}
@Override
public CompletableFuture<User> getUser(String username) {
User user = this.userCache.get(username);
if (user == UserCacheLoader.EMPTY_USER) {
return CompletableFuture.completedFuture(null);
}
return CompletableFuture.completedFuture(user);
}
@Override
public CompletableFuture<List<User>> listUser(String filter) {
List<User> result = new ArrayList<>();
try (RocksIterator iterator = this.storage.iterator()) {
iterator.seekToFirst();
while (iterator.isValid()) {
String username = new String(iterator.key(), StandardCharsets.UTF_8);
if (StringUtils.isNotBlank(filter) && !username.contains(filter)) {
iterator.next();
continue;
}
User user = JSON.parseObject(new String(iterator.value(), StandardCharsets.UTF_8), User.class);
result.add(user);
iterator.next();
}
}
return CompletableFuture.completedFuture(result);
}
@Override
public void shutdown() {
if (this.storage != null) {
this.storage.shutdown();
}
}
private static class UserCacheLoader implements CacheLoader<String, User> {
private final ConfigRocksDBStorage storage;
public static final User EMPTY_USER = new User();
public UserCacheLoader(ConfigRocksDBStorage storage) {
this.storage = storage;
}
@Override
public User load(String username) {
try {
byte[] keyBytes = username.getBytes(StandardCharsets.UTF_8);
byte[] valueBytes = storage.get(keyBytes);
if (ArrayUtils.isEmpty(valueBytes)) {
return EMPTY_USER;
}
return JSON.parseObject(new String(valueBytes, StandardCharsets.UTF_8), User.class);
} catch (Exception e) {
throw new AuthenticationException("Get user from RocksDB failed.", e);
}
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.strategy;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.auth.authentication.context.AuthenticationContext;
import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
import org.apache.rocketmq.auth.authentication.provider.AuthenticationProvider;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.utils.ExceptionUtils;
public abstract class AbstractAuthenticationStrategy implements AuthenticationStrategy {
protected final AuthConfig authConfig;
protected final List<String> authenticationWhitelist = new ArrayList<>();
protected final AuthenticationProvider<AuthenticationContext> authenticationProvider;
public AbstractAuthenticationStrategy(AuthConfig authConfig, Supplier<?> metadataService) {
this.authConfig = authConfig;
this.authenticationProvider = AuthenticationFactory.getProvider(authConfig);
if (this.authenticationProvider != null) {
this.authenticationProvider.initialize(authConfig, metadataService);
}
if (StringUtils.isNotBlank(authConfig.getAuthenticationWhitelist())) {
String[] whitelist = StringUtils.split(authConfig.getAuthenticationWhitelist(), ",");
for (String rpcCode : whitelist) {
this.authenticationWhitelist.add(StringUtils.trim(rpcCode));
}
}
}
protected void doEvaluate(AuthenticationContext context) {
if (context == null) {
return;
}
if (!authConfig.isAuthenticationEnabled()) {
return;
}
if (this.authenticationProvider == null) {
return;
}
if (this.authenticationWhitelist.contains(context.getRpcCode())) {
return;
}
try {
this.authenticationProvider.authenticate(context).join();
} catch (AuthenticationException ex) {
throw ex;
} catch (Throwable ex) {
Throwable exception = ExceptionUtils.getRealException(ex);
if (exception instanceof AuthenticationException) {
throw (AuthenticationException) exception;
}
throw new AuthenticationException("Authentication failed. Please verify the credentials and try again.", exception);
}
}
}

View File

@@ -0,0 +1,24 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.strategy;
import org.apache.rocketmq.auth.authentication.context.AuthenticationContext;
public interface AuthenticationStrategy {
void evaluate(AuthenticationContext context);
}

View File

@@ -0,0 +1,72 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.strategy;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.auth.authentication.context.AuthenticationContext;
import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext;
import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.Pair;
import org.apache.rocketmq.common.constant.CommonConstants;
public class StatefulAuthenticationStrategy extends AbstractAuthenticationStrategy {
protected Cache<String, Pair<Boolean, AuthenticationException>> authCache;
public StatefulAuthenticationStrategy(AuthConfig authConfig, Supplier<?> metadataService) {
super(authConfig, metadataService);
this.authCache = Caffeine.newBuilder()
.expireAfterWrite(authConfig.getStatefulAuthenticationCacheExpiredSecond(), TimeUnit.SECONDS)
.maximumSize(authConfig.getStatefulAuthenticationCacheMaxNum())
.build();
}
@Override
public void evaluate(AuthenticationContext context) {
if (StringUtils.isBlank(context.getChannelId())) {
this.doEvaluate(context);
return;
}
Pair<Boolean, AuthenticationException> result = this.authCache.get(buildKey(context), key -> {
try {
this.doEvaluate(context);
return Pair.of(true, null);
} catch (AuthenticationException ex) {
return Pair.of(false, ex);
}
});
if (result != null && result.getObject1() == Boolean.FALSE) {
throw result.getObject2();
}
}
private String buildKey(AuthenticationContext context) {
if (context instanceof DefaultAuthenticationContext) {
DefaultAuthenticationContext ctx = (DefaultAuthenticationContext) context;
if (StringUtils.isBlank(ctx.getUsername())) {
return ctx.getChannelId();
}
return ctx.getChannelId() + CommonConstants.POUND + ctx.getUsername();
}
throw new AuthenticationException("The request of {} is not support.", context.getClass().getSimpleName());
}
}

View File

@@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.strategy;
import java.util.function.Supplier;
import org.apache.rocketmq.auth.authentication.context.AuthenticationContext;
import org.apache.rocketmq.auth.config.AuthConfig;
public class StatelessAuthenticationStrategy extends AbstractAuthenticationStrategy {
public StatelessAuthenticationStrategy(AuthConfig authConfig, Supplier<?> metadataService) {
super(authConfig, metadataService);
}
@Override
public void evaluate(AuthenticationContext context) {
super.doEvaluate(context);
}
}

View File

@@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization;
import java.util.List;
import java.util.function.Supplier;
import org.apache.commons.collections.CollectionUtils;
import org.apache.rocketmq.auth.authorization.context.AuthorizationContext;
import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory;
import org.apache.rocketmq.auth.authorization.strategy.AuthorizationStrategy;
import org.apache.rocketmq.auth.config.AuthConfig;
public class AuthorizationEvaluator {
private final AuthorizationStrategy authorizationStrategy;
public AuthorizationEvaluator(AuthConfig authConfig) {
this(authConfig, null);
}
public AuthorizationEvaluator(AuthConfig authConfig, Supplier<?> metadataService) {
this.authorizationStrategy = AuthorizationFactory.getStrategy(authConfig, metadataService);
}
public void evaluate(List<AuthorizationContext> contexts) {
if (CollectionUtils.isEmpty(contexts)) {
return;
}
contexts.forEach(this.authorizationStrategy::evaluate);
}
}

View File

@@ -0,0 +1,31 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.builder;
import com.google.protobuf.GeneratedMessageV3;
import io.grpc.Metadata;
import io.netty.channel.ChannelHandlerContext;
import java.util.List;
import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public interface AuthorizationContextBuilder {
List<DefaultAuthorizationContext> build(Metadata metadata, GeneratedMessageV3 message);
List<DefaultAuthorizationContext> build(ChannelHandlerContext context, RemotingCommand command);
}

View File

@@ -0,0 +1,504 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.builder;
import apache.rocketmq.v2.AckMessageRequest;
import apache.rocketmq.v2.ChangeInvisibleDurationRequest;
import apache.rocketmq.v2.ClientType;
import apache.rocketmq.v2.EndTransactionRequest;
import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest;
import apache.rocketmq.v2.HeartbeatRequest;
import apache.rocketmq.v2.NotifyClientTerminationRequest;
import apache.rocketmq.v2.QueryAssignmentRequest;
import apache.rocketmq.v2.QueryRouteRequest;
import apache.rocketmq.v2.RecallMessageRequest;
import apache.rocketmq.v2.ReceiveMessageRequest;
import apache.rocketmq.v2.SendMessageRequest;
import apache.rocketmq.v2.Subscription;
import apache.rocketmq.v2.SubscriptionEntry;
import apache.rocketmq.v2.TelemetryCommand;
import com.google.protobuf.GeneratedMessageV3;
import io.grpc.Metadata;
import io.netty.channel.ChannelHandlerContext;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.acl.common.AclException;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.auth.authentication.model.Subject;
import org.apache.rocketmq.auth.authentication.model.User;
import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext;
import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
import org.apache.rocketmq.auth.authorization.model.Resource;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.action.Action;
import org.apache.rocketmq.common.action.RocketMQAction;
import org.apache.rocketmq.common.constant.CommonConstants;
import org.apache.rocketmq.common.constant.GrpcConstants;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.common.resource.ResourcePattern;
import org.apache.rocketmq.common.resource.ResourceType;
import org.apache.rocketmq.common.resource.RocketMQResource;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.protocol.NamespaceUtil;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.apache.rocketmq.remoting.protocol.RequestCode;
import org.apache.rocketmq.remoting.protocol.RequestHeaderRegistry;
import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody;
import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody;
import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.UnregisterClientRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader;
import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData;
import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData;
import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData;
public class DefaultAuthorizationContextBuilder implements AuthorizationContextBuilder {
private static final String TOPIC = "topic";
private static final String GROUP = "group";
private static final String A = "a";
private static final String B = "b";
private static final String CONSUMER_GROUP = "consumerGroup";
private final AuthConfig authConfig;
private final RequestHeaderRegistry requestHeaderRegistry;
public DefaultAuthorizationContextBuilder(AuthConfig authConfig) {
this.authConfig = authConfig;
this.requestHeaderRegistry = RequestHeaderRegistry.getInstance();
}
@Override
public List<DefaultAuthorizationContext> build(Metadata metadata, GeneratedMessageV3 message) {
List<DefaultAuthorizationContext> result = null;
if (message instanceof SendMessageRequest) {
SendMessageRequest request = (SendMessageRequest) message;
if (request.getMessagesCount() <= 0) {
throw new AuthorizationException("message is null.");
}
result = newPubContext(metadata, request.getMessages(0).getTopic());
}
if (message instanceof RecallMessageRequest) {
RecallMessageRequest request = (RecallMessageRequest) message;
result = newPubContext(metadata, request.getTopic());
}
if (message instanceof EndTransactionRequest) {
EndTransactionRequest request = (EndTransactionRequest) message;
result = newPubContext(metadata, request.getTopic());
}
if (message instanceof HeartbeatRequest) {
HeartbeatRequest request = (HeartbeatRequest) message;
if (!isConsumerClientType(request.getClientType())) {
return null;
}
result = newGroupSubContexts(metadata, request.getGroup());
}
if (message instanceof ReceiveMessageRequest) {
ReceiveMessageRequest request = (ReceiveMessageRequest) message;
if (!request.hasMessageQueue()) {
throw new AuthorizationException("messageQueue is null.");
}
result = newSubContexts(metadata, request.getGroup(), request.getMessageQueue().getTopic());
}
if (message instanceof AckMessageRequest) {
AckMessageRequest request = (AckMessageRequest) message;
result = newSubContexts(metadata, request.getGroup(), request.getTopic());
}
if (message instanceof ForwardMessageToDeadLetterQueueRequest) {
ForwardMessageToDeadLetterQueueRequest request = (ForwardMessageToDeadLetterQueueRequest) message;
result = newSubContexts(metadata, request.getGroup(), request.getTopic());
}
if (message instanceof NotifyClientTerminationRequest) {
NotifyClientTerminationRequest request = (NotifyClientTerminationRequest) message;
if (StringUtils.isNotBlank(request.getGroup().getName())) {
result = newGroupSubContexts(metadata, request.getGroup());
}
}
if (message instanceof ChangeInvisibleDurationRequest) {
ChangeInvisibleDurationRequest request = (ChangeInvisibleDurationRequest) message;
result = newGroupSubContexts(metadata, request.getGroup());
}
if (message instanceof QueryRouteRequest) {
QueryRouteRequest request = (QueryRouteRequest) message;
result = newContext(metadata, request);
}
if (message instanceof QueryAssignmentRequest) {
QueryAssignmentRequest request = (QueryAssignmentRequest) message;
result = newSubContexts(metadata, request.getGroup(), request.getTopic());
}
if (message instanceof TelemetryCommand) {
TelemetryCommand request = (TelemetryCommand) message;
result = newContext(metadata, request);
}
if (CollectionUtils.isNotEmpty(result)) {
result.forEach(context -> {
context.setChannelId(metadata.get(GrpcConstants.CHANNEL_ID));
context.setRpcCode(message.getDescriptorForType().getFullName());
});
}
return result;
}
@Override
public List<DefaultAuthorizationContext> build(ChannelHandlerContext context, RemotingCommand command) {
List<DefaultAuthorizationContext> result = new ArrayList<>();
try {
HashMap<String, String> fields = command.getExtFields();
if (MapUtils.isEmpty(fields)) {
return result;
}
Subject subject = null;
if (fields.containsKey(SessionCredentials.ACCESS_KEY)) {
subject = User.of(fields.get(SessionCredentials.ACCESS_KEY));
}
String remoteAddr = RemotingHelper.parseChannelRemoteAddr(context.channel());
String sourceIp = StringUtils.substringBeforeLast(remoteAddr, CommonConstants.COLON);
Resource topic;
Resource group;
switch (command.getCode()) {
case RequestCode.GET_ROUTEINFO_BY_TOPIC:
if (NamespaceUtil.isRetryTopic(fields.get(TOPIC))) {
group = Resource.ofGroup(fields.get(TOPIC));
result.add(DefaultAuthorizationContext.of(subject, group, Arrays.asList(Action.SUB, Action.GET), sourceIp));
} else {
topic = Resource.ofTopic(fields.get(TOPIC));
result.add(DefaultAuthorizationContext.of(subject, topic, Arrays.asList(Action.PUB, Action.SUB, Action.GET), sourceIp));
}
break;
case RequestCode.SEND_MESSAGE:
if (NamespaceUtil.isRetryTopic(fields.get(TOPIC))) {
if (StringUtils.isNotBlank(fields.get(GROUP))) {
group = Resource.ofGroup(fields.get(GROUP));
} else {
group = Resource.ofGroup(fields.get(TOPIC));
}
result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp));
} else {
topic = Resource.ofTopic(fields.get(TOPIC));
result.add(DefaultAuthorizationContext.of(subject, topic, Action.PUB, sourceIp));
}
break;
case RequestCode.SEND_MESSAGE_V2:
case RequestCode.SEND_BATCH_MESSAGE:
if (NamespaceUtil.isRetryTopic(fields.get(B))) {
if (StringUtils.isNotBlank(fields.get(A))) {
group = Resource.ofGroup(fields.get(A));
} else {
group = Resource.ofGroup(fields.get(B));
}
result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp));
} else {
topic = Resource.ofTopic(fields.get(B));
result.add(DefaultAuthorizationContext.of(subject, topic, Action.PUB, sourceIp));
}
break;
case RequestCode.RECALL_MESSAGE:
topic = Resource.ofTopic(fields.get(TOPIC));
result.add(DefaultAuthorizationContext.of(subject, topic, Action.PUB, sourceIp));
break;
case RequestCode.END_TRANSACTION:
if (StringUtils.isNotBlank(fields.get(TOPIC))) {
topic = Resource.ofTopic(fields.get(TOPIC));
result.add(DefaultAuthorizationContext.of(subject, topic, Action.PUB, sourceIp));
}
break;
case RequestCode.CONSUMER_SEND_MSG_BACK:
group = Resource.ofGroup(fields.get(GROUP));
result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp));
break;
case RequestCode.PULL_MESSAGE:
if (!NamespaceUtil.isRetryTopic(fields.get(TOPIC))) {
topic = Resource.ofTopic(fields.get(TOPIC));
result.add(DefaultAuthorizationContext.of(subject, topic, Action.SUB, sourceIp));
}
group = Resource.ofGroup(fields.get(CONSUMER_GROUP));
result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp));
break;
case RequestCode.QUERY_MESSAGE:
topic = Resource.ofTopic(fields.get(TOPIC));
result.add(DefaultAuthorizationContext.of(subject, topic, Arrays.asList(Action.SUB, Action.GET), sourceIp));
break;
case RequestCode.HEART_BEAT:
HeartbeatData heartbeatData = HeartbeatData.decode(command.getBody(), HeartbeatData.class);
for (ConsumerData data : heartbeatData.getConsumerDataSet()) {
group = Resource.ofGroup(data.getGroupName());
result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp));
for (SubscriptionData subscriptionData : data.getSubscriptionDataSet()) {
if (NamespaceUtil.isRetryTopic(subscriptionData.getTopic())) {
continue;
}
topic = Resource.ofTopic(subscriptionData.getTopic());
result.add(DefaultAuthorizationContext.of(subject, topic, Action.SUB, sourceIp));
}
}
break;
case RequestCode.UNREGISTER_CLIENT:
final UnregisterClientRequestHeader unregisterClientRequestHeader =
command.decodeCommandCustomHeader(UnregisterClientRequestHeader.class);
if (StringUtils.isNotBlank(unregisterClientRequestHeader.getConsumerGroup())) {
group = Resource.ofGroup(unregisterClientRequestHeader.getConsumerGroup());
result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp));
}
break;
case RequestCode.GET_CONSUMER_LIST_BY_GROUP:
final GetConsumerListByGroupRequestHeader getConsumerListByGroupRequestHeader =
command.decodeCommandCustomHeader(GetConsumerListByGroupRequestHeader.class);
group = Resource.ofGroup(getConsumerListByGroupRequestHeader.getConsumerGroup());
result.add(DefaultAuthorizationContext.of(subject, group, Arrays.asList(Action.SUB, Action.GET), sourceIp));
break;
case RequestCode.QUERY_CONSUMER_OFFSET:
final QueryConsumerOffsetRequestHeader queryConsumerOffsetRequestHeader =
command.decodeCommandCustomHeader(QueryConsumerOffsetRequestHeader.class);
if (!NamespaceUtil.isRetryTopic(queryConsumerOffsetRequestHeader.getTopic())) {
topic = Resource.ofTopic(queryConsumerOffsetRequestHeader.getTopic());
result.add(DefaultAuthorizationContext.of(subject, topic, Arrays.asList(Action.SUB, Action.GET), sourceIp));
}
group = Resource.ofGroup(queryConsumerOffsetRequestHeader.getConsumerGroup());
result.add(DefaultAuthorizationContext.of(subject, group, Arrays.asList(Action.SUB, Action.GET), sourceIp));
break;
case RequestCode.UPDATE_CONSUMER_OFFSET:
final UpdateConsumerOffsetRequestHeader updateConsumerOffsetRequestHeader =
command.decodeCommandCustomHeader(UpdateConsumerOffsetRequestHeader.class);
if (!NamespaceUtil.isRetryTopic(updateConsumerOffsetRequestHeader.getTopic())) {
topic = Resource.ofTopic(updateConsumerOffsetRequestHeader.getTopic());
result.add(DefaultAuthorizationContext.of(subject, topic, Arrays.asList(Action.SUB, Action.UPDATE), sourceIp));
}
group = Resource.ofGroup(updateConsumerOffsetRequestHeader.getConsumerGroup());
result.add(DefaultAuthorizationContext.of(subject, group, Arrays.asList(Action.SUB, Action.UPDATE), sourceIp));
break;
case RequestCode.LOCK_BATCH_MQ:
LockBatchRequestBody lockBatchRequestBody = LockBatchRequestBody.decode(command.getBody(), LockBatchRequestBody.class);
group = Resource.ofGroup(lockBatchRequestBody.getConsumerGroup());
result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp));
if (CollectionUtils.isNotEmpty(lockBatchRequestBody.getMqSet())) {
for (MessageQueue messageQueue : lockBatchRequestBody.getMqSet()) {
if (NamespaceUtil.isRetryTopic(messageQueue.getTopic())) {
continue;
}
topic = Resource.ofTopic(messageQueue.getTopic());
result.add(DefaultAuthorizationContext.of(subject, topic, Action.SUB, sourceIp));
}
}
break;
case RequestCode.UNLOCK_BATCH_MQ:
UnlockBatchRequestBody unlockBatchRequestBody = LockBatchRequestBody.decode(command.getBody(), UnlockBatchRequestBody.class);
group = Resource.ofGroup(unlockBatchRequestBody.getConsumerGroup());
result.add(DefaultAuthorizationContext.of(subject, group, Action.SUB, sourceIp));
if (CollectionUtils.isNotEmpty(unlockBatchRequestBody.getMqSet())) {
for (MessageQueue messageQueue : unlockBatchRequestBody.getMqSet()) {
if (NamespaceUtil.isRetryTopic(messageQueue.getTopic())) {
continue;
}
topic = Resource.ofTopic(messageQueue.getTopic());
result.add(DefaultAuthorizationContext.of(subject, topic, Action.SUB, sourceIp));
}
}
break;
default:
result = buildContextByAnnotation(subject, command, sourceIp);
break;
}
if (CollectionUtils.isNotEmpty(result)) {
result.forEach(r -> {
r.setChannelId(context.channel().id().asLongText());
r.setRpcCode(String.valueOf(command.getCode()));
});
}
} catch (AuthorizationException ex) {
throw ex;
} catch (Throwable t) {
throw new AuthorizationException("parse authorization context error.", t);
}
return result;
}
private List<DefaultAuthorizationContext> buildContextByAnnotation(Subject subject, RemotingCommand request,
String sourceIp) throws Exception {
List<DefaultAuthorizationContext> result = new ArrayList<>();
Class<? extends CommandCustomHeader> clazz = this.requestHeaderRegistry.getRequestHeader(request.getCode());
if (clazz == null) {
return result;
}
CommandCustomHeader header = request.decodeCommandCustomHeader(clazz);
RocketMQAction rocketMQAction = clazz.getAnnotation(RocketMQAction.class);
ResourceType resourceType = rocketMQAction.resource();
Action[] actions = rocketMQAction.action();
Resource resource = null;
if (resourceType == ResourceType.CLUSTER) {
resource = Resource.ofCluster(authConfig.getClusterName());
}
Field[] fields = clazz.getDeclaredFields();
if (ArrayUtils.isNotEmpty(fields)) {
for (Field field : fields) {
RocketMQResource rocketMQResource = field.getAnnotation(RocketMQResource.class);
if (rocketMQResource == null) {
continue;
}
field.setAccessible(true);
try {
resourceType = rocketMQResource.value();
String splitter = rocketMQResource.splitter();
Object value = field.get(header);
if (value == null) {
continue;
}
String[] resourceValues;
if (StringUtils.isNotBlank(splitter)) {
resourceValues = StringUtils.split(value.toString(), splitter);
} else {
resourceValues = new String[] {value.toString()};
}
for (String resourceValue : resourceValues) {
if (resourceType == ResourceType.TOPIC && NamespaceUtil.isRetryTopic(resourceValue)) {
resource = Resource.ofGroup(resourceValue);
result.add(DefaultAuthorizationContext.of(subject, resource, Arrays.asList(actions), sourceIp));
} else {
resource = Resource.of(resourceType, resourceValue, ResourcePattern.LITERAL);
result.add(DefaultAuthorizationContext.of(subject, resource, Arrays.asList(actions), sourceIp));
}
}
} finally {
field.setAccessible(false);
}
}
}
if (CollectionUtils.isEmpty(result) && resource != null) {
result.add(DefaultAuthorizationContext.of(subject, resource, Arrays.asList(actions), sourceIp));
}
return result;
}
private List<DefaultAuthorizationContext> newContext(Metadata metadata, QueryRouteRequest request) {
apache.rocketmq.v2.Resource topic = request.getTopic();
if (StringUtils.isBlank(topic.getName())) {
throw new AuthorizationException("topic is null.");
}
Subject subject = null;
if (metadata.containsKey(GrpcConstants.AUTHORIZATION_AK)) {
subject = User.of(metadata.get(GrpcConstants.AUTHORIZATION_AK));
}
Resource resource = Resource.ofTopic(topic.getName());
String sourceIp = StringUtils.substringBeforeLast(metadata.get(GrpcConstants.REMOTE_ADDRESS), CommonConstants.COLON);
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, Arrays.asList(Action.PUB, Action.SUB), sourceIp);
return Collections.singletonList(context);
}
private static List<DefaultAuthorizationContext> newContext(Metadata metadata, TelemetryCommand request) {
if (request.getCommandCase() != TelemetryCommand.CommandCase.SETTINGS) {
return null;
}
if (!request.getSettings().hasPublishing() && !request.getSettings().hasSubscription()) {
throw new AclException("settings command doesn't have publishing or subscription.");
}
List<DefaultAuthorizationContext> result = new ArrayList<>();
if (request.getSettings().hasPublishing()) {
List<apache.rocketmq.v2.Resource> topicList = request.getSettings().getPublishing().getTopicsList();
for (apache.rocketmq.v2.Resource topic : topicList) {
result.addAll(newPubContext(metadata, topic));
}
}
if (request.getSettings().hasSubscription()) {
Subscription subscription = request.getSettings().getSubscription();
result.addAll(newSubContexts(metadata, ResourceType.GROUP, subscription.getGroup()));
for (SubscriptionEntry entry : subscription.getSubscriptionsList()) {
result.addAll(newSubContexts(metadata, ResourceType.TOPIC, entry.getTopic()));
}
}
return result;
}
private boolean isConsumerClientType(ClientType clientType) {
return Arrays.asList(ClientType.PUSH_CONSUMER, ClientType.SIMPLE_CONSUMER, ClientType.PULL_CONSUMER)
.contains(clientType);
}
private static List<DefaultAuthorizationContext> newPubContext(Metadata metadata, apache.rocketmq.v2.Resource topic) {
if (topic == null || StringUtils.isBlank(topic.getName())) {
throw new AuthorizationException("topic is null.");
}
Subject subject = null;
if (metadata.containsKey(GrpcConstants.AUTHORIZATION_AK)) {
subject = User.of(metadata.get(GrpcConstants.AUTHORIZATION_AK));
}
Resource resource = Resource.ofTopic(topic.getName());
String sourceIp = StringUtils.substringBeforeLast(metadata.get(GrpcConstants.REMOTE_ADDRESS), CommonConstants.COLON);
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, Action.PUB, sourceIp);
return Collections.singletonList(context);
}
private List<DefaultAuthorizationContext> newSubContexts(Metadata metadata, apache.rocketmq.v2.Resource group,
apache.rocketmq.v2.Resource topic) {
List<DefaultAuthorizationContext> result = new ArrayList<>();
result.addAll(newGroupSubContexts(metadata, group));
result.addAll(newTopicSubContexts(metadata, topic));
return result;
}
private static List<DefaultAuthorizationContext> newTopicSubContexts(Metadata metadata,
apache.rocketmq.v2.Resource resource) {
return newSubContexts(metadata, ResourceType.TOPIC, resource);
}
private static List<DefaultAuthorizationContext> newGroupSubContexts(Metadata metadata,
apache.rocketmq.v2.Resource resource) {
return newSubContexts(metadata, ResourceType.GROUP, resource);
}
private static List<DefaultAuthorizationContext> newSubContexts(Metadata metadata, ResourceType resourceType,
apache.rocketmq.v2.Resource resource) {
if (resourceType == ResourceType.GROUP) {
if (resource == null || StringUtils.isBlank(resource.getName())) {
throw new AuthorizationException("group is null.");
}
return newSubContexts(metadata, Resource.ofGroup(resource.getName()));
}
if (resourceType == ResourceType.TOPIC) {
if (resource == null || StringUtils.isBlank(resource.getName())) {
throw new AuthorizationException("topic is null.");
}
return newSubContexts(metadata, Resource.ofTopic(resource.getName()));
}
throw new AuthorizationException("unknown resource type.");
}
private static List<DefaultAuthorizationContext> newSubContexts(Metadata metadata, Resource resource) {
List<DefaultAuthorizationContext> result = new ArrayList<>();
Subject subject = null;
if (metadata.containsKey(GrpcConstants.AUTHORIZATION_AK)) {
subject = User.of(metadata.get(GrpcConstants.AUTHORIZATION_AK));
}
String sourceIp = StringUtils.substringBeforeLast(metadata.get(GrpcConstants.REMOTE_ADDRESS), CommonConstants.COLON);
result.add(DefaultAuthorizationContext.of(subject, resource, Action.SUB, sourceIp));
return result;
}
}

View File

@@ -0,0 +1,167 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.chain;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.collections.CollectionUtils;
import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext;
import org.apache.rocketmq.auth.authorization.enums.Decision;
import org.apache.rocketmq.auth.authorization.enums.PolicyType;
import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory;
import org.apache.rocketmq.auth.authorization.model.Acl;
import org.apache.rocketmq.auth.authorization.model.Environment;
import org.apache.rocketmq.auth.authorization.model.Policy;
import org.apache.rocketmq.auth.authorization.model.PolicyEntry;
import org.apache.rocketmq.auth.authorization.model.Resource;
import org.apache.rocketmq.auth.authorization.provider.AuthorizationMetadataProvider;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.chain.Handler;
import org.apache.rocketmq.common.chain.HandlerChain;
import org.apache.rocketmq.common.resource.ResourcePattern;
import org.apache.rocketmq.common.resource.ResourceType;
public class AclAuthorizationHandler implements Handler<DefaultAuthorizationContext, CompletableFuture<Void>> {
private final AuthorizationMetadataProvider authorizationMetadataProvider;
public AclAuthorizationHandler(AuthConfig config) {
this.authorizationMetadataProvider = AuthorizationFactory.getMetadataProvider(config);
}
public AclAuthorizationHandler(AuthConfig config, Supplier<?> metadataService) {
this.authorizationMetadataProvider = AuthorizationFactory.getMetadataProvider(config, metadataService);
}
@Override
public CompletableFuture<Void> handle(DefaultAuthorizationContext context,
HandlerChain<DefaultAuthorizationContext, CompletableFuture<Void>> chain) {
if (this.authorizationMetadataProvider == null) {
throw new AuthorizationException("The authorizationMetadataProvider is not configured");
}
return this.authorizationMetadataProvider.getAcl(context.getSubject()).thenAccept(acl -> {
if (acl == null) {
throwException(context, "no matched policies.");
}
// 1. get the defined acl entries which match the request.
PolicyEntry matchedEntry = matchPolicyEntries(context, acl);
// 2. if no matched acl entries, return deny
if (matchedEntry == null) {
throwException(context, "no matched policies.");
}
// 3. judge is the entries has denied decision.
if (matchedEntry.getDecision() == Decision.DENY) {
throwException(context, "the decision is deny.");
}
});
}
private PolicyEntry matchPolicyEntries(DefaultAuthorizationContext context, Acl acl) {
List<PolicyEntry> policyEntries = new ArrayList<>();
Policy policy = acl.getPolicy(PolicyType.CUSTOM);
if (policy != null) {
List<PolicyEntry> entries = matchPolicyEntries(context, policy.getEntries());
if (CollectionUtils.isNotEmpty(entries)) {
policyEntries.addAll(entries);
}
}
if (CollectionUtils.isEmpty(policyEntries)) {
policy = acl.getPolicy(PolicyType.DEFAULT);
if (policy != null) {
List<PolicyEntry> entries = matchPolicyEntries(context, policy.getEntries());
if (CollectionUtils.isNotEmpty(entries)) {
policyEntries.addAll(entries);
}
}
}
if (CollectionUtils.isEmpty(policyEntries)) {
return null;
}
policyEntries.sort(this::comparePolicyEntries);
return policyEntries.get(0);
}
private List<PolicyEntry> matchPolicyEntries(DefaultAuthorizationContext context, List<PolicyEntry> entries) {
if (CollectionUtils.isEmpty(entries)) {
return null;
}
return entries.stream()
.filter(entry -> entry.isMatchResource(context.getResource()))
.filter(entry -> entry.isMatchAction(context.getActions()))
.filter(entry -> entry.isMatchEnvironment(Environment.of(context.getSourceIp())))
.collect(Collectors.toList());
}
private int comparePolicyEntries(PolicyEntry o1, PolicyEntry o2) {
int compare = 0;
Resource r1 = o1.getResource();
Resource r2 = o2.getResource();
if (r1.getResourceType() != r2.getResourceType()) {
if (r1.getResourceType() == ResourceType.ANY) {
compare = 1;
}
if (r2.getResourceType() == ResourceType.ANY) {
compare = -1;
}
} else if (r1.getResourcePattern() == r2.getResourcePattern()) {
if (r1.getResourcePattern() == ResourcePattern.PREFIXED) {
String n1 = r1.getResourceName();
String n2 = r2.getResourceName();
compare = Integer.compare(n1.length(), n2.length());
}
} else {
if (r1.getResourcePattern() == ResourcePattern.LITERAL) {
compare = 1;
}
if (r1.getResourcePattern() == ResourcePattern.LITERAL) {
compare = -1;
}
if (r1.getResourcePattern() == ResourcePattern.PREFIXED) {
compare = 1;
}
if (r1.getResourcePattern() == ResourcePattern.PREFIXED) {
compare = -1;
}
}
if (compare != 0) {
return compare;
}
// the decision deny has higher priority
Decision d1 = o1.getDecision();
Decision d2 = o2.getDecision();
return d1 == Decision.DENY ? 1 : d2 == Decision.DENY ? -1 : 0;
}
private static void throwException(DefaultAuthorizationContext context, String detail) {
throw new AuthorizationException("{} has no permission to access {} from {}, " + detail,
context.getSubject().getSubjectKey(), context.getResource().getResourceKey(), context.getSourceIp());
}
}

View File

@@ -0,0 +1,71 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.chain;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import org.apache.rocketmq.auth.authentication.enums.SubjectType;
import org.apache.rocketmq.auth.authentication.enums.UserStatus;
import org.apache.rocketmq.auth.authentication.enums.UserType;
import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
import org.apache.rocketmq.auth.authentication.model.Subject;
import org.apache.rocketmq.auth.authentication.model.User;
import org.apache.rocketmq.auth.authentication.provider.AuthenticationMetadataProvider;
import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext;
import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.chain.Handler;
import org.apache.rocketmq.common.chain.HandlerChain;
public class UserAuthorizationHandler implements Handler<DefaultAuthorizationContext, CompletableFuture<Void>> {
private final AuthenticationMetadataProvider authenticationMetadataProvider;
public UserAuthorizationHandler(AuthConfig config, Supplier<?> metadataService) {
this.authenticationMetadataProvider = AuthenticationFactory.getMetadataProvider(config, metadataService);
}
@Override
public CompletableFuture<Void> handle(DefaultAuthorizationContext context, HandlerChain<DefaultAuthorizationContext, CompletableFuture<Void>> chain) {
if (!context.getSubject().isSubject(SubjectType.USER)) {
return chain.handle(context);
}
return this.getUser(context.getSubject()).thenCompose(user -> {
if (user.getUserType() == UserType.SUPER) {
return CompletableFuture.completedFuture(null);
}
return chain.handle(context);
});
}
private CompletableFuture<User> getUser(Subject subject) {
if (this.authenticationMetadataProvider == null) {
throw new AuthorizationException("The authenticationMetadataProvider is not configured");
}
User user = (User) subject;
return authenticationMetadataProvider.getUser(user.getUsername()).thenApply(result -> {
if (result == null) {
throw new AuthorizationException("User:{} not found.", user.getUsername());
}
if (user.getUserStatus() == UserStatus.DISABLE) {
throw new AuthenticationException("User:{} is disabled.", user.getUsername());
}
return result;
});
}
}

View File

@@ -0,0 +1,84 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.context;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
public abstract class AuthorizationContext {
private String channelId;
private String rpcCode;
private Map<String, Object> extInfo;
@SuppressWarnings("unchecked")
public <T> T getExtInfo(String key) {
if (StringUtils.isBlank(key)) {
return null;
}
if (this.extInfo == null) {
return null;
}
Object value = this.extInfo.get(key);
if (value == null) {
return null;
}
return (T) value;
}
public void setExtInfo(String key, Object value) {
if (StringUtils.isBlank(key) || value == null) {
return;
}
if (this.extInfo == null) {
this.extInfo = new HashMap<>();
}
this.extInfo.put(key, value);
}
public boolean hasExtInfo(String key) {
Object value = getExtInfo(key);
return value != null;
}
public String getChannelId() {
return channelId;
}
public void setChannelId(String channelId) {
this.channelId = channelId;
}
public String getRpcCode() {
return rpcCode;
}
public void setRpcCode(String rpcCode) {
this.rpcCode = rpcCode;
}
public Map<String, Object> getExtInfo() {
return extInfo;
}
public void setExtInfo(Map<String, Object> extInfo) {
this.extInfo = extInfo;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.context;
import java.util.Collections;
import java.util.List;
import org.apache.rocketmq.auth.authentication.model.Subject;
import org.apache.rocketmq.auth.authorization.model.Resource;
import org.apache.rocketmq.common.action.Action;
public class DefaultAuthorizationContext extends AuthorizationContext {
private Subject subject;
private Resource resource;
private List<Action> actions;
private String sourceIp;
public static DefaultAuthorizationContext of(Subject subject, Resource resource, Action action, String sourceIp) {
DefaultAuthorizationContext context = new DefaultAuthorizationContext();
context.setSubject(subject);
context.setResource(resource);
context.setActions(Collections.singletonList(action));
context.setSourceIp(sourceIp);
return context;
}
public static DefaultAuthorizationContext of(Subject subject, Resource resource, List<Action> actions, String sourceIp) {
DefaultAuthorizationContext context = new DefaultAuthorizationContext();
context.setSubject(subject);
context.setResource(resource);
context.setActions(actions);
context.setSourceIp(sourceIp);
return context;
}
public String getSubjectKey() {
return this.subject != null ? this.subject.getSubjectKey() : null;
}
public String getResourceKey() {
return this.resource != null ? this.resource.getResourceKey() : null;
}
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
public Resource getResource() {
return resource;
}
public void setResource(Resource resource) {
this.resource = resource;
}
public List<Action> getActions() {
return actions;
}
public void setActions(List<Action> actions) {
this.actions = actions;
}
public String getSourceIp() {
return sourceIp;
}
public void setSourceIp(String sourceIp) {
this.sourceIp = sourceIp;
}
}

View File

@@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.enums;
import com.alibaba.fastjson2.annotation.JSONField;
import org.apache.commons.lang3.StringUtils;
public enum Decision {
ALLOW((byte) 1, "Allow"),
DENY((byte) 2, "Deny");
@JSONField(value = true)
private final byte code;
private final String name;
Decision(byte code, String name) {
this.code = code;
this.name = name;
}
public static Decision getByName(String name) {
for (Decision decision : Decision.values()) {
if (StringUtils.equalsIgnoreCase(decision.getName(), name)) {
return decision;
}
}
return null;
}
public byte getCode() {
return code;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.enums;
import com.alibaba.fastjson2.annotation.JSONField;
import org.apache.commons.lang3.StringUtils;
public enum PolicyType {
CUSTOM((byte) 1, "Custom"),
DEFAULT((byte) 2, "Default");
@JSONField(value = true)
private final byte code;
private final String name;
PolicyType(byte code, String name) {
this.code = code;
this.name = name;
}
public static PolicyType getByName(String name) {
for (PolicyType policyType : PolicyType.values()) {
if (StringUtils.equalsIgnoreCase(policyType.getName(), name)) {
return policyType;
}
}
return null;
}
public byte getCode() {
return code;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.exception;
import org.slf4j.helpers.MessageFormatter;
public class AuthorizationException extends RuntimeException {
public AuthorizationException(String message) {
super(message);
}
public AuthorizationException(String message, Throwable cause) {
super(message, cause);
}
public AuthorizationException(String messagePattern, Object... argArray) {
super(MessageFormatter.arrayFormat(messagePattern, argArray).getMessage());
}
}

View File

@@ -0,0 +1,156 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.factory;
import com.google.protobuf.GeneratedMessageV3;
import io.grpc.Metadata;
import io.netty.channel.ChannelHandlerContext;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.auth.authorization.AuthorizationEvaluator;
import org.apache.rocketmq.auth.authorization.context.AuthorizationContext;
import org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManager;
import org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManagerImpl;
import org.apache.rocketmq.auth.authorization.provider.AuthorizationMetadataProvider;
import org.apache.rocketmq.auth.authorization.provider.AuthorizationProvider;
import org.apache.rocketmq.auth.authorization.provider.DefaultAuthorizationProvider;
import org.apache.rocketmq.auth.authorization.strategy.AuthorizationStrategy;
import org.apache.rocketmq.auth.authorization.strategy.StatelessAuthorizationStrategy;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class AuthorizationFactory {
private static final Map<String, Object> INSTANCE_MAP = new HashMap<>();
private static final String PROVIDER_PREFIX = "PROVIDER_";
private static final String METADATA_PROVIDER_PREFIX = "METADATA_PROVIDER_";
private static final String EVALUATOR_PREFIX = "EVALUATOR_";
@SuppressWarnings("unchecked")
public static AuthorizationProvider<AuthorizationContext> getProvider(AuthConfig config) {
if (config == null) {
return null;
}
return computeIfAbsent(PROVIDER_PREFIX + config.getConfigName(), key -> {
try {
Class<? extends AuthorizationProvider<? extends AuthorizationContext>> clazz =
DefaultAuthorizationProvider.class;
if (StringUtils.isNotBlank(config.getAuthorizationProvider())) {
clazz = (Class<? extends AuthorizationProvider<? extends AuthorizationContext>>) Class.forName(config.getAuthorizationProvider());
}
return (AuthorizationProvider<AuthorizationContext>) clazz
.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("Failed to load the authorization provider.", e);
}
});
}
public static AuthorizationMetadataProvider getMetadataProvider(AuthConfig config) {
return getMetadataProvider(config, null);
}
public static AuthorizationMetadataManager getMetadataManager(AuthConfig config) {
return new AuthorizationMetadataManagerImpl(config);
}
@SuppressWarnings("unchecked")
public static AuthorizationMetadataProvider getMetadataProvider(AuthConfig config, Supplier<?> metadataService) {
if (config == null) {
return null;
}
return computeIfAbsent(METADATA_PROVIDER_PREFIX + config.getConfigName(), key -> {
try {
if (StringUtils.isBlank(config.getAuthorizationMetadataProvider())) {
return null;
}
Class<? extends AuthorizationMetadataProvider> clazz = (Class<? extends AuthorizationMetadataProvider>)
Class.forName(config.getAuthorizationMetadataProvider());
AuthorizationMetadataProvider result = clazz.getDeclaredConstructor().newInstance();
result.initialize(config, metadataService);
return result;
} catch (Exception e) {
throw new RuntimeException("Failed to load the authorization metadata provider.", e);
}
});
}
public static AuthorizationEvaluator getEvaluator(AuthConfig config) {
return computeIfAbsent(EVALUATOR_PREFIX + config.getConfigName(), key -> new AuthorizationEvaluator(config));
}
public static AuthorizationEvaluator getEvaluator(AuthConfig config, Supplier<?> metadataService) {
return computeIfAbsent(EVALUATOR_PREFIX + config.getConfigName(), key -> new AuthorizationEvaluator(config, metadataService));
}
@SuppressWarnings("unchecked")
public static AuthorizationStrategy getStrategy(AuthConfig config, Supplier<?> metadataService) {
try {
Class<? extends AuthorizationStrategy> clazz = StatelessAuthorizationStrategy.class;
if (StringUtils.isNotBlank(config.getAuthorizationStrategy())) {
clazz = (Class<? extends AuthorizationStrategy>) Class.forName(config.getAuthorizationStrategy());
}
return clazz.getDeclaredConstructor(AuthConfig.class, Supplier.class).newInstance(config, metadataService);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static List<AuthorizationContext> newContexts(AuthConfig config, Metadata metadata,
GeneratedMessageV3 message) {
AuthorizationProvider<AuthorizationContext> authorizationProvider = getProvider(config);
if (authorizationProvider == null) {
return null;
}
return authorizationProvider.newContexts(metadata, message);
}
public static List<AuthorizationContext> newContexts(AuthConfig config, ChannelHandlerContext context,
RemotingCommand command) {
AuthorizationProvider<AuthorizationContext> authorizationProvider = getProvider(config);
if (authorizationProvider == null) {
return null;
}
return authorizationProvider.newContexts(context, command);
}
@SuppressWarnings("unchecked")
private static <V> V computeIfAbsent(String key, Function<String, ? extends V> function) {
Object result = null;
if (INSTANCE_MAP.containsKey(key)) {
result = INSTANCE_MAP.get(key);
}
if (result == null) {
synchronized (INSTANCE_MAP) {
if (INSTANCE_MAP.containsKey(key)) {
result = INSTANCE_MAP.get(key);
}
if (result == null) {
result = function.apply(key);
if (result != null) {
INSTANCE_MAP.put(key, result);
}
}
}
}
return result != null ? (V) result : null;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.manager;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.auth.authentication.model.Subject;
import org.apache.rocketmq.auth.authorization.enums.PolicyType;
import org.apache.rocketmq.auth.authorization.model.Acl;
import org.apache.rocketmq.auth.authorization.model.Resource;
public interface AuthorizationMetadataManager {
void shutdown();
CompletableFuture<Void> createAcl(Acl acl);
CompletableFuture<Void> updateAcl(Acl acl);
CompletableFuture<Void> deleteAcl(Subject subject);
CompletableFuture<Void> deleteAcl(Subject subject, PolicyType policyType, Resource resource);
CompletableFuture<Acl> getAcl(Subject subject);
CompletableFuture<List<Acl>> listAcl(String subjectFilter, String resourceFilter);
}

View File

@@ -0,0 +1,284 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.manager;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.auth.authentication.enums.SubjectType;
import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
import org.apache.rocketmq.auth.authentication.model.Subject;
import org.apache.rocketmq.auth.authentication.model.User;
import org.apache.rocketmq.auth.authentication.provider.AuthenticationMetadataProvider;
import org.apache.rocketmq.auth.authorization.enums.PolicyType;
import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory;
import org.apache.rocketmq.auth.authorization.model.Acl;
import org.apache.rocketmq.auth.authorization.model.Environment;
import org.apache.rocketmq.auth.authorization.model.Policy;
import org.apache.rocketmq.auth.authorization.model.PolicyEntry;
import org.apache.rocketmq.auth.authorization.model.Resource;
import org.apache.rocketmq.auth.authorization.provider.AuthorizationMetadataProvider;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.action.Action;
import org.apache.rocketmq.common.utils.ExceptionUtils;
import org.apache.rocketmq.common.utils.IPAddressUtils;
public class AuthorizationMetadataManagerImpl implements AuthorizationMetadataManager {
private final AuthorizationMetadataProvider authorizationMetadataProvider;
private final AuthenticationMetadataProvider authenticationMetadataProvider;
public AuthorizationMetadataManagerImpl(AuthConfig authConfig) {
this.authorizationMetadataProvider = AuthorizationFactory.getMetadataProvider(authConfig);
this.authenticationMetadataProvider = AuthenticationFactory.getMetadataProvider(authConfig);
}
@Override
public void shutdown() {
if (this.authenticationMetadataProvider != null) {
this.authenticationMetadataProvider.shutdown();
}
if (this.authorizationMetadataProvider != null) {
this.authorizationMetadataProvider.shutdown();
}
}
@Override
public CompletableFuture<Void> createAcl(Acl acl) {
try {
validate(acl);
initAcl(acl);
CompletableFuture<? extends Subject> subjectFuture;
if (acl.getSubject().isSubject(SubjectType.USER)) {
User user = (User) acl.getSubject();
subjectFuture = this.getAuthenticationMetadataProvider().getUser(user.getUsername());
} else {
subjectFuture = CompletableFuture.completedFuture(acl.getSubject());
}
return subjectFuture.thenCompose(subject -> {
if (subject == null) {
throw new AuthorizationException("The subject of {} is not exist.", acl.getSubject().getSubjectKey());
}
return this.getAuthorizationMetadataProvider().getAcl(acl.getSubject());
}).thenCompose(oldAcl -> {
if (oldAcl == null) {
return this.getAuthorizationMetadataProvider().createAcl(acl);
}
oldAcl.updatePolicy(acl.getPolicies());
return this.getAuthorizationMetadataProvider().updateAcl(oldAcl);
});
} catch (Exception e) {
return this.handleException(e);
}
}
@Override
public CompletableFuture<Void> updateAcl(Acl acl) {
try {
validate(acl);
initAcl(acl);
CompletableFuture<? extends Subject> subjectFuture;
if (acl.getSubject().isSubject(SubjectType.USER)) {
User user = (User) acl.getSubject();
subjectFuture = this.getAuthenticationMetadataProvider().getUser(user.getUsername());
} else {
subjectFuture = CompletableFuture.completedFuture(acl.getSubject());
}
return subjectFuture.thenCompose(subject -> {
if (subject == null) {
throw new AuthorizationException("The subject of {} is not exist.", acl.getSubject().getSubjectKey());
}
return this.getAuthorizationMetadataProvider().getAcl(acl.getSubject());
}).thenCompose(oldAcl -> {
if (oldAcl == null) {
return this.getAuthorizationMetadataProvider().createAcl(acl);
}
oldAcl.updatePolicy(acl.getPolicies());
return this.getAuthorizationMetadataProvider().updateAcl(oldAcl);
});
} catch (Exception e) {
return this.handleException(e);
}
}
@Override
public CompletableFuture<Void> deleteAcl(Subject subject) {
return this.deleteAcl(subject, null, null);
}
@Override
public CompletableFuture<Void> deleteAcl(Subject subject, PolicyType policyType, Resource resource) {
try {
if (subject == null) {
throw new AuthorizationException("The subject is null.");
}
if (policyType == null) {
policyType = PolicyType.CUSTOM;
}
CompletableFuture<? extends Subject> subjectFuture;
if (subject.isSubject(SubjectType.USER)) {
User user = (User) subject;
subjectFuture = this.getAuthenticationMetadataProvider().getUser(user.getUsername());
} else {
subjectFuture = CompletableFuture.completedFuture(subject);
}
CompletableFuture<Acl> aclFuture = this.getAuthorizationMetadataProvider().getAcl(subject);
PolicyType finalPolicyType = policyType;
return subjectFuture.thenCombine(aclFuture, (sub, oldAcl) -> {
if (sub == null) {
throw new AuthorizationException("The subject is not exist.");
}
if (oldAcl == null) {
throw new AuthorizationException("The acl is not exist.");
}
return oldAcl;
}).thenCompose(oldAcl -> {
if (resource != null) {
oldAcl.deletePolicy(finalPolicyType, resource);
}
if (resource == null || CollectionUtils.isEmpty(oldAcl.getPolicies())) {
return this.getAuthorizationMetadataProvider().deleteAcl(subject);
}
return this.getAuthorizationMetadataProvider().updateAcl(oldAcl);
});
} catch (Exception e) {
return this.handleException(e);
}
}
@Override
public CompletableFuture<Acl> getAcl(Subject subject) {
CompletableFuture<? extends Subject> subjectFuture;
if (subject.isSubject(SubjectType.USER)) {
User user = (User) subject;
subjectFuture = this.getAuthenticationMetadataProvider().getUser(user.getUsername());
} else {
subjectFuture = CompletableFuture.completedFuture(subject);
}
return subjectFuture.thenCompose(sub -> {
if (sub == null) {
throw new AuthorizationException("The subject is not exist.");
}
return this.getAuthorizationMetadataProvider().getAcl(subject);
});
}
@Override
public CompletableFuture<List<Acl>> listAcl(String subjectFilter, String resourceFilter) {
return this.getAuthorizationMetadataProvider().listAcl(subjectFilter, resourceFilter);
}
private static void initAcl(Acl acl) {
acl.getPolicies().forEach(policy -> {
if (policy.getPolicyType() == null) {
policy.setPolicyType(PolicyType.CUSTOM);
}
});
}
private void validate(Acl acl) {
Subject subject = acl.getSubject();
if (subject.getSubjectType() == null) {
throw new AuthorizationException("The subject type is null.");
}
List<Policy> policies = acl.getPolicies();
if (CollectionUtils.isEmpty(policies)) {
throw new AuthorizationException("The policies is empty.");
}
for (Policy policy : policies) {
this.validate(policy);
}
}
private void validate(Policy policy) {
List<PolicyEntry> policyEntries = policy.getEntries();
if (CollectionUtils.isEmpty(policyEntries)) {
throw new AuthorizationException("The policy entries is empty.");
}
for (PolicyEntry policyEntry : policyEntries) {
this.validate(policyEntry);
}
}
private void validate(PolicyEntry entry) {
Resource resource = entry.getResource();
if (resource == null) {
throw new AuthorizationException("The resource is null.");
}
if (resource.getResourceType() == null) {
throw new AuthorizationException("The resource type is null.");
}
if (resource.getResourcePattern() == null) {
throw new AuthorizationException("The resource pattern is null.");
}
if (CollectionUtils.isEmpty(entry.getActions())) {
throw new AuthorizationException("The actions is empty.");
}
if (entry.getActions().contains(Action.ANY)) {
throw new AuthorizationException("The actions can not be Any.");
}
Environment environment = entry.getEnvironment();
if (environment != null && CollectionUtils.isNotEmpty(environment.getSourceIps())) {
for (String sourceIp : environment.getSourceIps()) {
if (StringUtils.isBlank(sourceIp)) {
throw new AuthorizationException("The source ip is empty.");
}
if (!IPAddressUtils.isValidIPOrCidr(sourceIp)) {
throw new AuthorizationException("The source ip is invalid.");
}
}
}
if (entry.getDecision() == null) {
throw new AuthorizationException("The decision is null or illegal.");
}
}
private <T> CompletableFuture<T> handleException(Exception e) {
CompletableFuture<T> result = new CompletableFuture<>();
Throwable throwable = ExceptionUtils.getRealException(e);
result.completeExceptionally(throwable);
return result;
}
private AuthenticationMetadataProvider getAuthenticationMetadataProvider() {
if (authorizationMetadataProvider == null) {
throw new IllegalStateException("The authenticationMetadataProvider is not configured.");
}
return authenticationMetadataProvider;
}
private AuthorizationMetadataProvider getAuthorizationMetadataProvider() {
if (authenticationMetadataProvider == null) {
throw new IllegalStateException("The authenticationMetadataProvider is not configured.");
}
return authorizationMetadataProvider;
}
}

View File

@@ -0,0 +1,110 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.model;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.apache.rocketmq.auth.authentication.model.Subject;
import org.apache.rocketmq.auth.authorization.enums.Decision;
import org.apache.rocketmq.auth.authorization.enums.PolicyType;
import org.apache.rocketmq.common.action.Action;
public class Acl {
private Subject subject;
private List<Policy> policies;
public static Acl of(Subject subject, Policy policy) {
return of(subject, Lists.newArrayList(policy));
}
public static Acl of(Subject subject, List<Policy> policies) {
Acl acl = new Acl();
acl.setSubject(subject);
acl.setPolicies(policies);
return acl;
}
public static Acl of(Subject subject, List<Resource> resources, List<Action> actions, Environment environment,
Decision decision) {
Acl acl = new Acl();
acl.setSubject(subject);
Policy policy = Policy.of(resources, actions, environment, decision);
acl.setPolicies(Lists.newArrayList(policy));
return acl;
}
public void updatePolicy(Policy policy) {
this.updatePolicy(Lists.newArrayList(policy));
}
public void updatePolicy(List<Policy> policies) {
if (this.policies == null) {
this.policies = new ArrayList<>();
}
policies.forEach(newPolicy -> {
Policy oldPolicy = this.getPolicy(newPolicy.getPolicyType());
if (oldPolicy == null) {
this.policies.add(newPolicy);
} else {
oldPolicy.updateEntry(newPolicy.getEntries());
}
});
}
public void deletePolicy(PolicyType policyType, Resource resource) {
Policy policy = getPolicy(policyType);
if (policy == null) {
return;
}
policy.deleteEntry(resource);
if (CollectionUtils.isEmpty(policy.getEntries())) {
this.policies.remove(policy);
}
}
public Policy getPolicy(PolicyType policyType) {
if (CollectionUtils.isEmpty(this.policies)) {
return null;
}
for (Policy policy : this.policies) {
if (policy.getPolicyType() == policyType) {
return policy;
}
}
return null;
}
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
public List<Policy> getPolicies() {
return policies;
}
public void setPolicies(List<Policy> policies) {
this.policies = policies;
}
}

View File

@@ -0,0 +1,68 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.model;
import java.util.Collections;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.utils.IPAddressUtils;
public class Environment {
private List<String> sourceIps;
public static Environment of(String sourceIp) {
if (StringUtils.isEmpty(sourceIp)) {
return null;
}
return of(Collections.singletonList(sourceIp));
}
public static Environment of(List<String> sourceIps) {
if (CollectionUtils.isEmpty(sourceIps)) {
return null;
}
Environment environment = new Environment();
environment.setSourceIps(sourceIps);
return environment;
}
public boolean isMatch(Environment environment) {
if (CollectionUtils.isEmpty(this.sourceIps)) {
return true;
}
if (CollectionUtils.isEmpty(environment.getSourceIps())) {
return false;
}
String targetIp = environment.getSourceIps().get(0);
for (String sourceIp : this.sourceIps) {
if (IPAddressUtils.isIPInRange(targetIp, sourceIp)) {
return true;
}
}
return false;
}
public List<String> getSourceIps() {
return sourceIps;
}
public void setSourceIps(List<String> sourceIps) {
this.sourceIps = sourceIps;
}
}

View File

@@ -0,0 +1,106 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.collections.CollectionUtils;
import org.apache.rocketmq.common.action.Action;
import org.apache.rocketmq.auth.authorization.enums.Decision;
import org.apache.rocketmq.auth.authorization.enums.PolicyType;
public class Policy {
private PolicyType policyType;
private List<PolicyEntry> entries;
public static Policy of(List<Resource> resources, List<Action> actions, Environment environment,
Decision decision) {
return of(PolicyType.CUSTOM, resources, actions, environment, decision);
}
public static Policy of(PolicyType policyType, List<Resource> resources, List<Action> actions,
Environment environment,
Decision decision) {
Policy policy = new Policy();
policy.setPolicyType(policyType);
List<PolicyEntry> entries = resources.stream()
.map(resource -> PolicyEntry.of(resource, actions, environment, decision))
.collect(Collectors.toList());
policy.setEntries(entries);
return policy;
}
public static Policy of(PolicyType type, List<PolicyEntry> entries) {
Policy policy = new Policy();
policy.setPolicyType(type);
policy.setEntries(entries);
return policy;
}
public void updateEntry(List<PolicyEntry> newEntries) {
if (this.entries == null) {
this.entries = new ArrayList<>();
}
newEntries.forEach(newEntry -> {
PolicyEntry entry = getEntry(newEntry.getResource());
if (entry == null) {
this.entries.add(newEntry);
} else {
entry.updateEntry(newEntry.getActions(), newEntry.getEnvironment(), newEntry.getDecision());
}
});
}
public void deleteEntry(Resource resources) {
PolicyEntry entry = getEntry(resources);
if (entry != null) {
this.entries.remove(entry);
}
}
private PolicyEntry getEntry(Resource resource) {
if (CollectionUtils.isEmpty(this.entries)) {
return null;
}
for (PolicyEntry entry : this.entries) {
if (Objects.equals(entry.getResource(), resource)) {
return entry;
}
}
return null;
}
public PolicyType getPolicyType() {
return policyType;
}
public void setPolicyType(PolicyType policyType) {
this.policyType = policyType;
}
public List<PolicyEntry> getEntries() {
return entries;
}
public void setEntries(List<PolicyEntry> entries) {
this.entries = entries;
}
}

View File

@@ -0,0 +1,120 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.model;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.collections.CollectionUtils;
import org.apache.rocketmq.common.action.Action;
import org.apache.rocketmq.auth.authorization.enums.Decision;
public class PolicyEntry {
private Resource resource;
private List<Action> actions;
private Environment environment;
private Decision decision;
public static PolicyEntry of(Resource resource, List<Action> actions, Environment environment, Decision decision) {
PolicyEntry policyEntry = new PolicyEntry();
policyEntry.setResource(resource);
policyEntry.setActions(actions);
policyEntry.setEnvironment(environment);
policyEntry.setDecision(decision);
return policyEntry;
}
public void updateEntry(List<Action> actions, Environment environment,
Decision decision) {
this.setActions(actions);
this.setEnvironment(environment);
this.setDecision(decision);
}
public boolean isMatchResource(Resource resource) {
return this.resource.isMatch(resource);
}
public boolean isMatchAction(List<Action> actions) {
if (CollectionUtils.isEmpty(this.actions)) {
return false;
}
if (actions.contains(Action.ANY)) {
return true;
}
return actions.stream()
.anyMatch(action -> this.actions.contains(action)
|| this.actions.contains(Action.ALL));
}
public boolean isMatchEnvironment(Environment environment) {
if (this.environment == null) {
return true;
}
return this.environment.isMatch(environment);
}
public String toResourceStr() {
if (resource == null) {
return null;
}
return resource.getResourceKey();
}
public List<String> toActionsStr() {
if (CollectionUtils.isEmpty(actions)) {
return null;
}
return actions.stream().map(Action::getName)
.collect(Collectors.toList());
}
public Resource getResource() {
return resource;
}
public void setResource(Resource resource) {
this.resource = resource;
}
public List<Action> getActions() {
return actions;
}
public void setActions(List<Action> actions) {
this.actions = actions;
}
public Environment getEnvironment() {
return environment;
}
public void setEnvironment(Environment environment) {
this.environment = environment;
}
public Decision getDecision() {
return decision;
}
public void setDecision(Decision decision) {
this.decision = decision;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.model;
import org.apache.rocketmq.auth.authentication.model.Subject;
import org.apache.rocketmq.common.action.Action;
public class RequestContext {
private Subject subject;
private Resource resource;
private Action action;
private String sourceIp;
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
public Resource getResource() {
return resource;
}
public void setResource(Resource resource) {
this.resource = resource;
}
public Action getAction() {
return action;
}
public void setAction(Action action) {
this.action = action;
}
public String getSourceIp() {
return sourceIp;
}
public void setSourceIp(String sourceIp) {
this.sourceIp = sourceIp;
}
}

View File

@@ -0,0 +1,168 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.model;
import com.alibaba.fastjson2.annotation.JSONField;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.resource.ResourceType;
import org.apache.rocketmq.common.resource.ResourcePattern;
import org.apache.rocketmq.common.constant.CommonConstants;
import org.apache.rocketmq.remoting.protocol.NamespaceUtil;
public class Resource {
private ResourceType resourceType;
private String resourceName;
private ResourcePattern resourcePattern;
public static Resource ofCluster(String clusterName) {
return of(ResourceType.CLUSTER, clusterName, ResourcePattern.LITERAL);
}
public static Resource ofTopic(String topicName) {
return of(ResourceType.TOPIC, topicName, ResourcePattern.LITERAL);
}
public static Resource ofGroup(String groupName) {
if (NamespaceUtil.isRetryTopic(groupName)) {
groupName = NamespaceUtil.withOutRetryAndDLQ(groupName);
}
return of(ResourceType.GROUP, groupName, ResourcePattern.LITERAL);
}
public static Resource of(ResourceType resourceType, String resourceName, ResourcePattern resourcePattern) {
Resource resource = new Resource();
resource.resourceType = resourceType;
resource.resourceName = resourceName;
resource.resourcePattern = resourcePattern;
return resource;
}
public static List<Resource> of(List<String> resourceKeys) {
if (CollectionUtils.isEmpty(resourceKeys)) {
return null;
}
return resourceKeys.stream().map(Resource::of).collect(Collectors.toList());
}
public static Resource of(String resourceKey) {
if (StringUtils.isBlank(resourceKey)) {
return null;
}
if (StringUtils.equals(resourceKey, CommonConstants.ASTERISK)) {
return of(ResourceType.ANY, null, ResourcePattern.ANY);
}
String type = StringUtils.substringBefore(resourceKey, CommonConstants.COLON);
ResourceType resourceType = ResourceType.getByName(type);
if (resourceType == null) {
return null;
}
String resourceName = StringUtils.substringAfter(resourceKey, CommonConstants.COLON);
ResourcePattern resourcePattern = ResourcePattern.LITERAL;
if (StringUtils.equals(resourceName, CommonConstants.ASTERISK)) {
resourceName = null;
resourcePattern = ResourcePattern.ANY;
} else if (StringUtils.endsWith(resourceName, CommonConstants.ASTERISK)) {
resourceName = StringUtils.substringBefore(resourceName, CommonConstants.ASTERISK);
resourcePattern = ResourcePattern.PREFIXED;
}
return of(resourceType, resourceName, resourcePattern);
}
@JSONField(serialize = false)
public String getResourceKey() {
if (resourceType == ResourceType.ANY) {
return CommonConstants.ASTERISK;
}
switch (resourcePattern) {
case ANY:
return resourceType.getName() + CommonConstants.COLON + CommonConstants.ASTERISK;
case LITERAL:
return resourceType.getName() + CommonConstants.COLON + resourceName;
case PREFIXED:
return resourceType.getName() + CommonConstants.COLON + resourceName + CommonConstants.ASTERISK;
default:
return null;
}
}
public boolean isMatch(Resource resource) {
if (this.resourceType == ResourceType.ANY) {
return true;
}
if (this.resourceType != resource.resourceType) {
return false;
}
switch (resourcePattern) {
case ANY:
return true;
case LITERAL:
return StringUtils.equals(resource.resourceName, this.resourceName);
case PREFIXED:
return StringUtils.startsWith(resource.resourceName, this.resourceName);
default:
return false;
}
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Resource resource = (Resource) o;
return resourceType == resource.resourceType
&& Objects.equals(resourceName, resource.resourceName)
&& resourcePattern == resource.resourcePattern;
}
@Override
public int hashCode() {
return Objects.hash(resourceType, resourceName, resourcePattern);
}
public ResourceType getResourceType() {
return resourceType;
}
public void setResourceType(ResourceType resourceType) {
this.resourceType = resourceType;
}
public String getResourceName() {
return resourceName;
}
public void setResourceName(String resourceName) {
this.resourceName = resourceName;
}
public ResourcePattern getResourcePattern() {
return resourcePattern;
}
public void setResourcePattern(ResourcePattern resourcePattern) {
this.resourcePattern = resourcePattern;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.provider;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import org.apache.rocketmq.auth.authentication.model.Subject;
import org.apache.rocketmq.auth.authorization.model.Acl;
import org.apache.rocketmq.auth.config.AuthConfig;
public interface AuthorizationMetadataProvider {
void initialize(AuthConfig authConfig, Supplier<?> metadataService);
void shutdown();
CompletableFuture<Void> createAcl(Acl acl);
CompletableFuture<Void> deleteAcl(Subject subject);
CompletableFuture<Void> updateAcl(Acl acl);
CompletableFuture<Acl> getAcl(Subject subject);
CompletableFuture<List<Acl>> listAcl(String subjectFilter, String resourceFilter);
}

View File

@@ -0,0 +1,39 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.provider;
import com.google.protobuf.GeneratedMessageV3;
import io.grpc.Metadata;
import io.netty.channel.ChannelHandlerContext;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public interface AuthorizationProvider<AuthorizationContext> {
void initialize(AuthConfig config);
void initialize(AuthConfig config, Supplier<?> metadataService);
CompletableFuture<Void> authorize(AuthorizationContext context);
List<AuthorizationContext> newContexts(Metadata metadata, GeneratedMessageV3 message);
List<AuthorizationContext> newContexts(ChannelHandlerContext context, RemotingCommand command);
}

View File

@@ -0,0 +1,102 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.provider;
import com.google.protobuf.GeneratedMessageV3;
import io.grpc.Metadata;
import io.netty.channel.ChannelHandlerContext;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.rocketmq.auth.authorization.builder.AuthorizationContextBuilder;
import org.apache.rocketmq.auth.authorization.builder.DefaultAuthorizationContextBuilder;
import org.apache.rocketmq.auth.authorization.chain.AclAuthorizationHandler;
import org.apache.rocketmq.auth.authorization.chain.UserAuthorizationHandler;
import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext;
import org.apache.rocketmq.auth.authorization.enums.Decision;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.action.Action;
import org.apache.rocketmq.common.chain.HandlerChain;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DefaultAuthorizationProvider implements AuthorizationProvider<DefaultAuthorizationContext> {
protected final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_AUTH_AUDIT_LOGGER_NAME);
protected AuthConfig authConfig;
protected Supplier<?> metadataService;
protected AuthorizationContextBuilder authorizationContextBuilder;
@Override
public void initialize(AuthConfig config) {
this.initialize(config, null);
}
@Override
public void initialize(AuthConfig config, Supplier<?> metadataService) {
this.authConfig = config;
this.metadataService = metadataService;
this.authorizationContextBuilder = new DefaultAuthorizationContextBuilder(config);
}
@Override
public CompletableFuture<Void> authorize(DefaultAuthorizationContext context) {
return this.newHandlerChain().handle(context)
.whenComplete((nil, ex) -> doAuditLog(context, ex));
}
@Override
public List<DefaultAuthorizationContext> newContexts(Metadata metadata, GeneratedMessageV3 message) {
return this.authorizationContextBuilder.build(metadata, message);
}
@Override
public List<DefaultAuthorizationContext> newContexts(ChannelHandlerContext context, RemotingCommand command) {
return this.authorizationContextBuilder.build(context, command);
}
protected HandlerChain<DefaultAuthorizationContext, CompletableFuture<Void>> newHandlerChain() {
return HandlerChain.<DefaultAuthorizationContext, CompletableFuture<Void>>create()
.addNext(new UserAuthorizationHandler(authConfig, metadataService))
.addNext(new AclAuthorizationHandler(authConfig, metadataService));
}
protected void doAuditLog(DefaultAuthorizationContext context, Throwable ex) {
if (context.getSubject() == null) {
return;
}
Decision decision = Decision.ALLOW;
if (ex != null) {
decision = Decision.DENY;
}
String subject = context.getSubject().getSubjectKey();
String actions = context.getActions().stream().map(Action::getName)
.collect(Collectors.joining(","));
String sourceIp = context.getSourceIp();
String resource = context.getResource().getResourceKey();
String request = context.getRpcCode();
String format = "[AUTHORIZATION] Subject = {} is {} Action = {} from sourceIp = {} on resource = {} for request = {}.";
if (decision == Decision.ALLOW) {
log.debug(format, subject, decision.getName(), actions, sourceIp, resource, request);
} else {
log.info(format, subject, decision.getName(), actions, sourceIp, resource, request);
}
}
}

View File

@@ -0,0 +1,199 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.provider;
import com.alibaba.fastjson2.JSON;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.auth.authentication.model.Subject;
import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
import org.apache.rocketmq.auth.authorization.model.Acl;
import org.apache.rocketmq.auth.authorization.model.Policy;
import org.apache.rocketmq.auth.authorization.model.PolicyEntry;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.config.ConfigRocksDBStorage;
import org.apache.rocketmq.common.thread.ThreadPoolMonitor;
import org.rocksdb.RocksIterator;
public class LocalAuthorizationMetadataProvider implements AuthorizationMetadataProvider {
private ConfigRocksDBStorage storage;
private LoadingCache<String, Acl> aclCache;
@Override
public void initialize(AuthConfig authConfig, Supplier<?> metadataService) {
this.storage = new ConfigRocksDBStorage(authConfig.getAuthConfigPath() + File.separator + "acls");
if (!this.storage.start()) {
throw new RuntimeException("Failed to load rocksdb for auth_acl, please check whether it is occupied.");
}
ThreadPoolExecutor cacheRefreshExecutor = ThreadPoolMonitor.createAndMonitor(
1,
1,
1000 * 60,
TimeUnit.MILLISECONDS,
"AclCacheRefresh",
100000
);
this.aclCache = Caffeine.newBuilder()
.maximumSize(authConfig.getAclCacheMaxNum())
.expireAfterAccess(authConfig.getAclCacheExpiredSecond(), TimeUnit.SECONDS)
.refreshAfterWrite(authConfig.getAclCacheRefreshSecond(), TimeUnit.SECONDS)
.executor(cacheRefreshExecutor)
.build(new AclCacheLoader(this.storage));
}
@Override
public CompletableFuture<Void> createAcl(Acl acl) {
try {
Subject subject = acl.getSubject();
byte[] keyBytes = subject.getSubjectKey().getBytes(StandardCharsets.UTF_8);
byte[] valueBytes = JSON.toJSONBytes(acl);
this.storage.put(keyBytes, keyBytes.length, valueBytes);
this.storage.flushWAL();
this.aclCache.invalidate(subject.getSubjectKey());
} catch (Exception e) {
throw new AuthorizationException("create Acl to RocksDB failed.", e);
}
return CompletableFuture.completedFuture(null);
}
@Override
public CompletableFuture<Void> deleteAcl(Subject subject) {
try {
byte[] keyBytes = subject.getSubjectKey().getBytes(StandardCharsets.UTF_8);
this.storage.delete(keyBytes);
this.storage.flushWAL();
this.aclCache.invalidate(subject.getSubjectKey());
} catch (Exception e) {
throw new AuthorizationException("delete Acl from RocksDB failed.", e);
}
return CompletableFuture.completedFuture(null);
}
@Override
public CompletableFuture<Void> updateAcl(Acl acl) {
try {
Subject subject = acl.getSubject();
byte[] keyBytes = subject.getSubjectKey().getBytes(StandardCharsets.UTF_8);
byte[] valueBytes = JSON.toJSONBytes(acl);
this.storage.put(keyBytes, keyBytes.length, valueBytes);
this.storage.flushWAL();
this.aclCache.invalidate(subject.getSubjectKey());
} catch (Exception e) {
throw new AuthorizationException("update Acl to RocksDB failed.", e);
}
return CompletableFuture.completedFuture(null);
}
@Override
public CompletableFuture<Acl> getAcl(Subject subject) {
Acl acl = aclCache.get(subject.getSubjectKey());
if (acl == AclCacheLoader.EMPTY_ACL) {
return CompletableFuture.completedFuture(null);
}
return CompletableFuture.completedFuture(acl);
}
@Override
public CompletableFuture<List<Acl>> listAcl(String subjectFilter, String resourceFilter) {
List<Acl> result = new ArrayList<>();
try (RocksIterator iterator = this.storage.iterator()) {
iterator.seekToFirst();
while (iterator.isValid()) {
String subjectKey = new String(iterator.key(), StandardCharsets.UTF_8);
if (StringUtils.isNotBlank(subjectFilter) && !subjectKey.contains(subjectFilter)) {
iterator.next();
continue;
}
Subject subject = Subject.of(subjectKey);
Acl acl = JSON.parseObject(new String(iterator.value(), StandardCharsets.UTF_8), Acl.class);
List<Policy> policies = acl.getPolicies();
if (!CollectionUtils.isNotEmpty(policies)) {
iterator.next();
continue;
}
Iterator<Policy> policyIterator = policies.iterator();
while (policyIterator.hasNext()) {
Policy policy = policyIterator.next();
List<PolicyEntry> entries = policy.getEntries();
if (CollectionUtils.isEmpty(entries)) {
continue;
}
if (StringUtils.isNotBlank(resourceFilter) && !subjectKey.contains(resourceFilter)) {
entries.removeIf(entry -> !entry.toResourceStr().contains(resourceFilter));
}
if (CollectionUtils.isEmpty(entries)) {
policyIterator.remove();
}
}
if (CollectionUtils.isNotEmpty(policies)) {
result.add(Acl.of(subject, policies));
}
iterator.next();
}
}
return CompletableFuture.completedFuture(result);
}
@Override
public void shutdown() {
if (this.storage != null) {
this.storage.shutdown();
}
}
private static class AclCacheLoader implements CacheLoader<String, Acl> {
private final ConfigRocksDBStorage storage;
public static final Acl EMPTY_ACL = new Acl();
public AclCacheLoader(ConfigRocksDBStorage storage) {
this.storage = storage;
}
@Override
public Acl load(String subjectKey) {
try {
byte[] keyBytes = subjectKey.getBytes(StandardCharsets.UTF_8);
Subject subject = Subject.of(subjectKey);
byte[] valueBytes = this.storage.get(keyBytes);
if (ArrayUtils.isEmpty(valueBytes)) {
return EMPTY_ACL;
}
Acl acl = JSON.parseObject(valueBytes, Acl.class);
return Acl.of(subject, acl.getPolicies());
} catch (Exception e) {
throw new AuthorizationException("get Acl from RocksDB failed.", e);
}
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.strategy;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.auth.authorization.context.AuthorizationContext;
import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory;
import org.apache.rocketmq.auth.authorization.provider.AuthorizationProvider;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.utils.ExceptionUtils;
public abstract class AbstractAuthorizationStrategy implements AuthorizationStrategy {
protected final AuthConfig authConfig;
protected final List<String> authorizationWhitelist = new ArrayList<>();
protected final AuthorizationProvider<AuthorizationContext> authorizationProvider;
public AbstractAuthorizationStrategy(AuthConfig authConfig, Supplier<?> metadataService) {
this.authConfig = authConfig;
this.authorizationProvider = AuthorizationFactory.getProvider(authConfig);
if (this.authorizationProvider != null) {
this.authorizationProvider.initialize(authConfig, metadataService);
}
if (StringUtils.isNotBlank(authConfig.getAuthorizationWhitelist())) {
String[] whitelist = StringUtils.split(authConfig.getAuthorizationWhitelist(), ",");
for (String rpcCode : whitelist) {
this.authorizationWhitelist.add(StringUtils.trim(rpcCode));
}
}
}
public void doEvaluate(AuthorizationContext context) {
if (context == null) {
return;
}
if (!this.authConfig.isAuthorizationEnabled()) {
return;
}
if (this.authorizationProvider == null) {
return;
}
if (this.authorizationWhitelist.contains(context.getRpcCode())) {
return;
}
try {
this.authorizationProvider.authorize(context).join();
} catch (AuthorizationException ex) {
throw ex;
} catch (Throwable ex) {
Throwable exception = ExceptionUtils.getRealException(ex);
if (exception instanceof AuthorizationException) {
throw (AuthorizationException) exception;
}
throw new AuthorizationException("Authorization failed. Please verify your access rights and try again.", exception);
}
}
}

View File

@@ -0,0 +1,24 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.strategy;
import org.apache.rocketmq.auth.authorization.context.AuthorizationContext;
public interface AuthorizationStrategy {
void evaluate(AuthorizationContext context);
}

View File

@@ -0,0 +1,73 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.strategy;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.auth.authorization.context.AuthorizationContext;
import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext;
import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.Pair;
import org.apache.rocketmq.common.constant.CommonConstants;
public class StatefulAuthorizationStrategy extends AbstractAuthorizationStrategy {
protected Cache<String, Pair<Boolean, AuthorizationException>> authCache;
public StatefulAuthorizationStrategy(AuthConfig authConfig, Supplier<?> metadataService) {
super(authConfig, metadataService);
this.authCache = Caffeine.newBuilder()
.expireAfterWrite(authConfig.getStatefulAuthorizationCacheExpiredSecond(), TimeUnit.SECONDS)
.maximumSize(authConfig.getStatefulAuthorizationCacheMaxNum())
.build();
}
@Override
public void evaluate(AuthorizationContext context) {
if (StringUtils.isBlank(context.getChannelId())) {
this.doEvaluate(context);
return;
}
Pair<Boolean, AuthorizationException> result = this.authCache.get(buildKey(context), key -> {
try {
this.doEvaluate(context);
return Pair.of(true, null);
} catch (AuthorizationException ex) {
return Pair.of(false, ex);
}
});
if (result != null && result.getObject1() == Boolean.FALSE) {
throw result.getObject2();
}
}
private String buildKey(AuthorizationContext context) {
if (context instanceof DefaultAuthorizationContext) {
DefaultAuthorizationContext ctx = (DefaultAuthorizationContext) context;
return ctx.getChannelId()
+ (ctx.getSubject() != null ? CommonConstants.POUND + ctx.getSubjectKey() : "")
+ CommonConstants.POUND + ctx.getResourceKey()
+ CommonConstants.POUND + StringUtils.join(ctx.getActions(), CommonConstants.COMMA)
+ CommonConstants.POUND + ctx.getSourceIp();
}
throw new AuthorizationException("The request of {} is not support.", context.getClass().getSimpleName());
}
}

View File

@@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.strategy;
import java.util.function.Supplier;
import org.apache.rocketmq.auth.authorization.context.AuthorizationContext;
import org.apache.rocketmq.auth.config.AuthConfig;
public class StatelessAuthorizationStrategy extends AbstractAuthorizationStrategy {
public StatelessAuthorizationStrategy(AuthConfig authConfig, Supplier<?> metadataService) {
super(authConfig, metadataService);
}
@Override
public void evaluate(AuthorizationContext context) {
super.doEvaluate(context);
}
}

View File

@@ -0,0 +1,289 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.config;
public class AuthConfig implements Cloneable {
private String configName;
private String clusterName;
private String authConfigPath;
private boolean authenticationEnabled = false;
private String authenticationProvider;
private String authenticationMetadataProvider;
private String authenticationStrategy;
private String authenticationWhitelist;
private String initAuthenticationUser;
private String innerClientAuthenticationCredentials;
private boolean authorizationEnabled = false;
private String authorizationProvider;
private String authorizationMetadataProvider;
private String authorizationStrategy;
private String authorizationWhitelist;
private boolean migrateAuthFromV1Enabled = false;
private int userCacheMaxNum = 1000;
private int userCacheExpiredSecond = 600;
private int userCacheRefreshSecond = 60;
private int aclCacheMaxNum = 1000;
private int aclCacheExpiredSecond = 600;
private int aclCacheRefreshSecond = 60;
private int statefulAuthenticationCacheMaxNum = 10000;
private int statefulAuthenticationCacheExpiredSecond = 60;
private int statefulAuthorizationCacheMaxNum = 10000;
private int statefulAuthorizationCacheExpiredSecond = 60;
@Override
public AuthConfig clone() {
try {
return (AuthConfig) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
public String getConfigName() {
return configName;
}
public void setConfigName(String configName) {
this.configName = configName;
}
public String getClusterName() {
return clusterName;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
public String getAuthConfigPath() {
return authConfigPath;
}
public void setAuthConfigPath(String authConfigPath) {
this.authConfigPath = authConfigPath;
}
public boolean isAuthenticationEnabled() {
return authenticationEnabled;
}
public void setAuthenticationEnabled(boolean authenticationEnabled) {
this.authenticationEnabled = authenticationEnabled;
}
public String getAuthenticationProvider() {
return authenticationProvider;
}
public void setAuthenticationProvider(String authenticationProvider) {
this.authenticationProvider = authenticationProvider;
}
public String getAuthenticationMetadataProvider() {
return authenticationMetadataProvider;
}
public void setAuthenticationMetadataProvider(String authenticationMetadataProvider) {
this.authenticationMetadataProvider = authenticationMetadataProvider;
}
public String getAuthenticationStrategy() {
return authenticationStrategy;
}
public void setAuthenticationStrategy(String authenticationStrategy) {
this.authenticationStrategy = authenticationStrategy;
}
public String getAuthenticationWhitelist() {
return authenticationWhitelist;
}
public void setAuthenticationWhitelist(String authenticationWhitelist) {
this.authenticationWhitelist = authenticationWhitelist;
}
public String getInitAuthenticationUser() {
return initAuthenticationUser;
}
public void setInitAuthenticationUser(String initAuthenticationUser) {
this.initAuthenticationUser = initAuthenticationUser;
}
public String getInnerClientAuthenticationCredentials() {
return innerClientAuthenticationCredentials;
}
public void setInnerClientAuthenticationCredentials(String innerClientAuthenticationCredentials) {
this.innerClientAuthenticationCredentials = innerClientAuthenticationCredentials;
}
public boolean isAuthorizationEnabled() {
return authorizationEnabled;
}
public void setAuthorizationEnabled(boolean authorizationEnabled) {
this.authorizationEnabled = authorizationEnabled;
}
public String getAuthorizationProvider() {
return authorizationProvider;
}
public void setAuthorizationProvider(String authorizationProvider) {
this.authorizationProvider = authorizationProvider;
}
public String getAuthorizationMetadataProvider() {
return authorizationMetadataProvider;
}
public void setAuthorizationMetadataProvider(String authorizationMetadataProvider) {
this.authorizationMetadataProvider = authorizationMetadataProvider;
}
public String getAuthorizationStrategy() {
return authorizationStrategy;
}
public void setAuthorizationStrategy(String authorizationStrategy) {
this.authorizationStrategy = authorizationStrategy;
}
public String getAuthorizationWhitelist() {
return authorizationWhitelist;
}
public void setAuthorizationWhitelist(String authorizationWhitelist) {
this.authorizationWhitelist = authorizationWhitelist;
}
public boolean isMigrateAuthFromV1Enabled() {
return migrateAuthFromV1Enabled;
}
public void setMigrateAuthFromV1Enabled(boolean migrateAuthFromV1Enabled) {
this.migrateAuthFromV1Enabled = migrateAuthFromV1Enabled;
}
public int getUserCacheMaxNum() {
return userCacheMaxNum;
}
public void setUserCacheMaxNum(int userCacheMaxNum) {
this.userCacheMaxNum = userCacheMaxNum;
}
public int getUserCacheExpiredSecond() {
return userCacheExpiredSecond;
}
public void setUserCacheExpiredSecond(int userCacheExpiredSecond) {
this.userCacheExpiredSecond = userCacheExpiredSecond;
}
public int getUserCacheRefreshSecond() {
return userCacheRefreshSecond;
}
public void setUserCacheRefreshSecond(int userCacheRefreshSecond) {
this.userCacheRefreshSecond = userCacheRefreshSecond;
}
public int getAclCacheMaxNum() {
return aclCacheMaxNum;
}
public void setAclCacheMaxNum(int aclCacheMaxNum) {
this.aclCacheMaxNum = aclCacheMaxNum;
}
public int getAclCacheExpiredSecond() {
return aclCacheExpiredSecond;
}
public void setAclCacheExpiredSecond(int aclCacheExpiredSecond) {
this.aclCacheExpiredSecond = aclCacheExpiredSecond;
}
public int getAclCacheRefreshSecond() {
return aclCacheRefreshSecond;
}
public void setAclCacheRefreshSecond(int aclCacheRefreshSecond) {
this.aclCacheRefreshSecond = aclCacheRefreshSecond;
}
public int getStatefulAuthenticationCacheMaxNum() {
return statefulAuthenticationCacheMaxNum;
}
public void setStatefulAuthenticationCacheMaxNum(int statefulAuthenticationCacheMaxNum) {
this.statefulAuthenticationCacheMaxNum = statefulAuthenticationCacheMaxNum;
}
public int getStatefulAuthenticationCacheExpiredSecond() {
return statefulAuthenticationCacheExpiredSecond;
}
public void setStatefulAuthenticationCacheExpiredSecond(int statefulAuthenticationCacheExpiredSecond) {
this.statefulAuthenticationCacheExpiredSecond = statefulAuthenticationCacheExpiredSecond;
}
public int getStatefulAuthorizationCacheMaxNum() {
return statefulAuthorizationCacheMaxNum;
}
public void setStatefulAuthorizationCacheMaxNum(int statefulAuthorizationCacheMaxNum) {
this.statefulAuthorizationCacheMaxNum = statefulAuthorizationCacheMaxNum;
}
public int getStatefulAuthorizationCacheExpiredSecond() {
return statefulAuthorizationCacheExpiredSecond;
}
public void setStatefulAuthorizationCacheExpiredSecond(int statefulAuthorizationCacheExpiredSecond) {
this.statefulAuthorizationCacheExpiredSecond = statefulAuthorizationCacheExpiredSecond;
}
}

View File

@@ -0,0 +1,230 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.migration;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.acl.common.AclConstants;
import org.apache.rocketmq.auth.authentication.enums.UserType;
import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager;
import org.apache.rocketmq.auth.authentication.model.Subject;
import org.apache.rocketmq.auth.authentication.model.User;
import org.apache.rocketmq.auth.authorization.enums.Decision;
import org.apache.rocketmq.auth.authorization.enums.PolicyType;
import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory;
import org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManager;
import org.apache.rocketmq.auth.authorization.model.Acl;
import org.apache.rocketmq.auth.authorization.model.Policy;
import org.apache.rocketmq.auth.authorization.model.PolicyEntry;
import org.apache.rocketmq.auth.authorization.model.Resource;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.auth.migration.v1.PlainPermissionManager;
import org.apache.rocketmq.auth.migration.v1.AclConfig;
import org.apache.rocketmq.auth.migration.v1.PlainAccessConfig;
import org.apache.rocketmq.common.action.Action;
import org.apache.rocketmq.common.constant.CommonConstants;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.resource.ResourcePattern;
import org.apache.rocketmq.common.resource.ResourceType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
public class AuthMigrator {
protected static final Logger LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME);
private final AuthConfig authConfig;
private final PlainPermissionManager plainPermissionManager;
private final AuthenticationMetadataManager authenticationMetadataManager;
private final AuthorizationMetadataManager authorizationMetadataManager;
public AuthMigrator(AuthConfig authConfig) {
this.authConfig = authConfig;
this.plainPermissionManager = new PlainPermissionManager();
this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(authConfig);
this.authorizationMetadataManager = AuthorizationFactory.getMetadataManager(authConfig);
}
public void migrate() {
if (!authConfig.isMigrateAuthFromV1Enabled()) {
return;
}
AclConfig aclConfig = this.plainPermissionManager.getAllAclConfig();
List<PlainAccessConfig> accessConfigs = aclConfig.getPlainAccessConfigs();
if (CollectionUtils.isEmpty(accessConfigs)) {
return;
}
for (PlainAccessConfig accessConfig : accessConfigs) {
doMigrate(accessConfig);
}
}
private void doMigrate(PlainAccessConfig accessConfig) {
this.isUserExisted(accessConfig.getAccessKey()).thenCompose(existed -> {
if (existed) {
return CompletableFuture.completedFuture(null);
}
return createUserAndAcl(accessConfig);
}).exceptionally(ex -> {
LOG.error("[ACL MIGRATE] An error occurred while migrating ACL configurations for AccessKey:{}.", accessConfig.getAccessKey(), ex);
return null;
}).join();
}
private CompletableFuture<Void> createUserAndAcl(PlainAccessConfig accessConfig) {
return createUser(accessConfig).thenCompose(nil -> createAcl(accessConfig));
}
private CompletableFuture<Void> createUser(PlainAccessConfig accessConfig) {
User user = new User();
user.setUsername(accessConfig.getAccessKey());
user.setPassword(accessConfig.getSecretKey());
if (accessConfig.isAdmin()) {
user.setUserType(UserType.SUPER);
} else {
user.setUserType(UserType.NORMAL);
}
return this.authenticationMetadataManager.createUser(user);
}
private CompletableFuture<Void> createAcl(PlainAccessConfig config) {
Subject subject = User.of(config.getAccessKey());
List<Policy> policies = new ArrayList<>();
Policy customPolicy = null;
if (CollectionUtils.isNotEmpty(config.getTopicPerms())) {
for (String topicPerm : config.getTopicPerms()) {
String[] temp = StringUtils.split(topicPerm, CommonConstants.EQUAL);
if (temp.length != 2) {
continue;
}
String topicName = StringUtils.trim(temp[0]);
String perm = StringUtils.trim(temp[1]);
Resource resource = Resource.ofTopic(topicName);
List<Action> actions = parseActions(perm);
Decision decision = parseDecision(perm);
PolicyEntry policyEntry = PolicyEntry.of(resource, actions, null, decision);
if (customPolicy == null) {
customPolicy = Policy.of(PolicyType.CUSTOM, new ArrayList<>());
}
customPolicy.getEntries().add(policyEntry);
}
}
if (CollectionUtils.isNotEmpty(config.getGroupPerms())) {
for (String groupPerm : config.getGroupPerms()) {
String[] temp = StringUtils.split(groupPerm, CommonConstants.EQUAL);
if (temp.length != 2) {
continue;
}
String groupName = StringUtils.trim(temp[0]);
String perm = StringUtils.trim(temp[1]);
Resource resource = Resource.ofGroup(groupName);
List<Action> actions = parseActions(perm);
Decision decision = parseDecision(perm);
PolicyEntry policyEntry = PolicyEntry.of(resource, actions, null, decision);
if (customPolicy == null) {
customPolicy = Policy.of(PolicyType.CUSTOM, new ArrayList<>());
}
customPolicy.getEntries().add(policyEntry);
}
}
if (customPolicy != null) {
policies.add(customPolicy);
}
Policy defaultPolicy = null;
if (StringUtils.isNotBlank(config.getDefaultTopicPerm())) {
String topicPerm = StringUtils.trim(config.getDefaultTopicPerm());
Resource resource = Resource.of(ResourceType.TOPIC, null, ResourcePattern.ANY);
List<Action> actions = parseActions(topicPerm);
Decision decision = parseDecision(topicPerm);
PolicyEntry policyEntry = PolicyEntry.of(resource, actions, null, decision);
defaultPolicy = Policy.of(PolicyType.DEFAULT, new ArrayList<>());
defaultPolicy.getEntries().add(policyEntry);
}
if (StringUtils.isNotBlank(config.getDefaultGroupPerm())) {
String groupPerm = StringUtils.trim(config.getDefaultGroupPerm());
Resource resource = Resource.of(ResourceType.GROUP, null, ResourcePattern.ANY);
List<Action> actions = parseActions(groupPerm);
Decision decision = parseDecision(groupPerm);
PolicyEntry policyEntry = PolicyEntry.of(resource, actions, null, decision);
if (defaultPolicy == null) {
defaultPolicy = Policy.of(PolicyType.DEFAULT, new ArrayList<>());
}
defaultPolicy.getEntries().add(policyEntry);
}
if (defaultPolicy != null) {
policies.add(defaultPolicy);
}
if (CollectionUtils.isEmpty(policies)) {
return CompletableFuture.completedFuture(null);
}
Acl acl = Acl.of(subject, policies);
return this.authorizationMetadataManager.createAcl(acl);
}
private Decision parseDecision(String str) {
if (StringUtils.isBlank(str)) {
return Decision.DENY;
}
return StringUtils.equals(str, AclConstants.DENY) ? Decision.DENY : Decision.ALLOW;
}
private List<Action> parseActions(String str) {
List<Action> result = new ArrayList<>();
if (StringUtils.isBlank(str)) {
result.add(Action.ALL);
}
switch (StringUtils.trim(str)) {
case AclConstants.PUB:
result.add(Action.PUB);
break;
case AclConstants.SUB:
result.add(Action.SUB);
break;
case AclConstants.PUB_SUB:
case AclConstants.SUB_PUB:
result.add(Action.PUB);
result.add(Action.SUB);
break;
case AclConstants.DENY:
result.add(Action.ALL);
break;
default:
result.add(Action.ALL);
break;
}
return result;
}
private CompletableFuture<Boolean> isUserExisted(String username) {
return this.authenticationMetadataManager.getUser(username).thenApply(Objects::nonNull);
}
}

View File

@@ -0,0 +1,21 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.migration.v1;
public interface AccessResource {
}

View File

@@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.migration.v1;
import java.util.List;
public class AclConfig {
private List<String> globalWhiteAddrs;
private List<PlainAccessConfig> plainAccessConfigs;
public List<String> getGlobalWhiteAddrs() {
return globalWhiteAddrs;
}
public void setGlobalWhiteAddrs(List<String> globalWhiteAddrs) {
this.globalWhiteAddrs = globalWhiteAddrs;
}
public List<PlainAccessConfig> getPlainAccessConfigs() {
return plainAccessConfigs;
}
public void setPlainAccessConfigs(List<PlainAccessConfig> plainAccessConfigs) {
this.plainAccessConfigs = plainAccessConfigs;
}
@Override
public String toString() {
return "AclConfig{" +
"globalWhiteAddrs=" + globalWhiteAddrs +
", plainAccessConfigs=" + plainAccessConfigs +
'}';
}
}

View File

@@ -0,0 +1,131 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.migration.v1;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
public class PlainAccessConfig implements Serializable {
private static final long serialVersionUID = -4517357000307227637L;
private String accessKey;
private String secretKey;
private String whiteRemoteAddress;
private boolean admin;
private String defaultTopicPerm;
private String defaultGroupPerm;
private List<String> topicPerms;
private List<String> groupPerms;
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getWhiteRemoteAddress() {
return whiteRemoteAddress;
}
public void setWhiteRemoteAddress(String whiteRemoteAddress) {
this.whiteRemoteAddress = whiteRemoteAddress;
}
public boolean isAdmin() {
return admin;
}
public void setAdmin(boolean admin) {
this.admin = admin;
}
public String getDefaultTopicPerm() {
return defaultTopicPerm;
}
public void setDefaultTopicPerm(String defaultTopicPerm) {
this.defaultTopicPerm = defaultTopicPerm;
}
public String getDefaultGroupPerm() {
return defaultGroupPerm;
}
public void setDefaultGroupPerm(String defaultGroupPerm) {
this.defaultGroupPerm = defaultGroupPerm;
}
public List<String> getTopicPerms() {
return topicPerms;
}
public void setTopicPerms(List<String> topicPerms) {
this.topicPerms = topicPerms;
}
public List<String> getGroupPerms() {
return groupPerms;
}
public void setGroupPerms(List<String> groupPerms) {
this.groupPerms = groupPerms;
}
@Override
public String toString() {
return "PlainAccessConfig{" +
"accessKey='" + accessKey + '\'' +
", whiteRemoteAddress='" + whiteRemoteAddress + '\'' +
", admin=" + admin +
", defaultTopicPerm='" + defaultTopicPerm + '\'' +
", defaultGroupPerm='" + defaultGroupPerm + '\'' +
", topicPerms=" + topicPerms +
", groupPerms=" + groupPerms +
'}';
}
@Override public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
PlainAccessConfig config = (PlainAccessConfig) o;
return admin == config.admin && Objects.equals(accessKey, config.accessKey) && Objects.equals(secretKey, config.secretKey) && Objects.equals(whiteRemoteAddress, config.whiteRemoteAddress) && Objects.equals(defaultTopicPerm, config.defaultTopicPerm) && Objects.equals(defaultGroupPerm, config.defaultGroupPerm) && Objects.equals(topicPerms, config.topicPerms) && Objects.equals(groupPerms, config.groupPerms);
}
@Override public int hashCode() {
return Objects.hash(accessKey, secretKey, whiteRemoteAddress, admin, defaultTopicPerm, defaultGroupPerm, topicPerms, groupPerms);
}
}

View File

@@ -0,0 +1,102 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.migration.v1;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class PlainAccessData implements Serializable {
private static final long serialVersionUID = -7971775135605117152L;
private List<String> globalWhiteRemoteAddresses = new ArrayList<>();
private List<PlainAccessConfig> accounts = new ArrayList<>();
private List<DataVersion> dataVersion = new ArrayList<>();
public List<String> getGlobalWhiteRemoteAddresses() {
return globalWhiteRemoteAddresses;
}
public void setGlobalWhiteRemoteAddresses(List<String> globalWhiteRemoteAddresses) {
this.globalWhiteRemoteAddresses = globalWhiteRemoteAddresses;
}
public List<PlainAccessConfig> getAccounts() {
return accounts;
}
public void setAccounts(List<PlainAccessConfig> accounts) {
this.accounts = accounts;
}
public List<DataVersion> getDataVersion() {
return dataVersion;
}
public void setDataVersion(List<DataVersion> dataVersion) {
this.dataVersion = dataVersion;
}
public static class DataVersion implements Serializable {
private static final long serialVersionUID = 6437361970079056954L;
private long timestamp;
private long counter;
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public long getCounter() {
return counter;
}
public void setCounter(long counter) {
this.counter = counter;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DataVersion that = (DataVersion) o;
return timestamp == that.timestamp && counter == that.counter;
}
@Override
public int hashCode() {
return Objects.hash(timestamp, counter);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PlainAccessData that = (PlainAccessData) o;
return Objects.equals(globalWhiteRemoteAddresses, that.globalWhiteRemoteAddresses) && Objects.equals(accounts, that.accounts) && Objects.equals(dataVersion, that.dataVersion);
}
@Override
public int hashCode() {
return Objects.hash(globalWhiteRemoteAddresses, accounts, dataVersion);
}
}

View File

@@ -0,0 +1,177 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.migration.v1;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.rocketmq.common.KeyBuilder;
import org.apache.rocketmq.common.MixAll;
import java.util.HashMap;
import java.util.Map;
public class PlainAccessResource implements AccessResource {
// Identify the user
private String accessKey;
private String secretKey;
private String whiteRemoteAddress;
private boolean admin;
private byte defaultTopicPerm = 1;
private byte defaultGroupPerm = 1;
private Map<String, Byte> resourcePermMap;
private int requestCode;
// The content to calculate the content
private byte[] content;
private String signature;
private String secretToken;
private String recognition;
public PlainAccessResource() {
}
public static String getGroupFromRetryTopic(String retryTopic) {
if (retryTopic == null) {
return null;
}
return KeyBuilder.parseGroup(retryTopic);
}
public static String getRetryTopic(String group) {
if (group == null) {
return null;
}
return MixAll.getRetryTopic(group);
}
public void addResourceAndPerm(String resource, byte perm) {
if (resource == null) {
return;
}
if (resourcePermMap == null) {
resourcePermMap = new HashMap<>();
}
resourcePermMap.put(resource, perm);
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getWhiteRemoteAddress() {
return whiteRemoteAddress;
}
public void setWhiteRemoteAddress(String whiteRemoteAddress) {
this.whiteRemoteAddress = whiteRemoteAddress;
}
public boolean isAdmin() {
return admin;
}
public void setAdmin(boolean admin) {
this.admin = admin;
}
public byte getDefaultTopicPerm() {
return defaultTopicPerm;
}
public void setDefaultTopicPerm(byte defaultTopicPerm) {
this.defaultTopicPerm = defaultTopicPerm;
}
public byte getDefaultGroupPerm() {
return defaultGroupPerm;
}
public void setDefaultGroupPerm(byte defaultGroupPerm) {
this.defaultGroupPerm = defaultGroupPerm;
}
public Map<String, Byte> getResourcePermMap() {
return resourcePermMap;
}
public String getRecognition() {
return recognition;
}
public void setRecognition(String recognition) {
this.recognition = recognition;
}
public int getRequestCode() {
return requestCode;
}
public void setRequestCode(int requestCode) {
this.requestCode = requestCode;
}
public String getSecretToken() {
return secretToken;
}
public void setSecretToken(String secretToken) {
this.secretToken = secretToken;
}
public String getSignature() {
return signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
public byte[] getContent() {
return content;
}
public void setContent(byte[] content) {
this.content = content;
}
}

View File

@@ -0,0 +1,148 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.migration.v1;
import org.apache.rocketmq.acl.common.AclException;
import org.apache.rocketmq.acl.common.AclUtils;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.constant.LoggerName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class PlainPermissionManager {
private static final Logger log = LoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME);
private String fileHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY,
System.getenv(MixAll.ROCKETMQ_HOME_ENV));
private String defaultAclDir;
private String defaultAclFile;
private List<String> fileList = new ArrayList<>();
public PlainPermissionManager() {
this.defaultAclDir = MixAll.dealFilePath(fileHome + File.separator + "conf" + File.separator + "acl");
this.defaultAclFile = MixAll.dealFilePath(fileHome + File.separator + System.getProperty("rocketmq.acl.plain.file", "conf" + File.separator + "plain_acl.yml"));
load();
}
public List<String> getAllAclFiles(String path) {
if (!new File(path).exists()) {
log.info("The default acl dir {} is not exist", path);
return new ArrayList<>();
}
List<String> allAclFileFullPath = new ArrayList<>();
File file = new File(path);
File[] files = file.listFiles();
for (int i = 0; files != null && i < files.length; i++) {
String fileName = files[i].getAbsolutePath();
File f = new File(fileName);
if (fileName.equals(fileHome + MixAll.ACL_CONF_TOOLS_FILE)) {
continue;
} else if (fileName.endsWith(".yml") || fileName.endsWith(".yaml")) {
allAclFileFullPath.add(fileName);
} else if (f.isDirectory()) {
allAclFileFullPath.addAll(getAllAclFiles(fileName));
}
}
return allAclFileFullPath;
}
public void load() {
if (fileHome == null || fileHome.isEmpty()) {
return;
}
assureAclConfigFilesExist();
fileList = getAllAclFiles(defaultAclDir);
if (new File(defaultAclFile).exists() && !fileList.contains(defaultAclFile)) {
fileList.add(defaultAclFile);
}
}
/**
* Currently GlobalWhiteAddress is defined in {@link #defaultAclFile}, so make sure it exists.
*/
private void assureAclConfigFilesExist() {
final Path defaultAclFilePath = Paths.get(this.defaultAclFile);
if (!Files.exists(defaultAclFilePath)) {
try {
Files.createFile(defaultAclFilePath);
} catch (FileAlreadyExistsException e) {
// Maybe created by other threads
} catch (IOException e) {
log.error("Error in creating " + this.defaultAclFile, e);
throw new AclException(e.getMessage());
}
}
}
public AclConfig getAllAclConfig() {
AclConfig aclConfig = new AclConfig();
List<PlainAccessConfig> configs = new ArrayList<>();
List<String> whiteAddrs = new ArrayList<>();
Set<String> accessKeySets = new HashSet<>();
for (String path : fileList) {
PlainAccessData plainAclConfData = AclUtils.getYamlDataObject(path, PlainAccessData.class);
if (plainAclConfData == null) {
continue;
}
List<String> globalWhiteAddrs = plainAclConfData.getGlobalWhiteRemoteAddresses();
if (globalWhiteAddrs != null && !globalWhiteAddrs.isEmpty()) {
whiteAddrs.addAll(globalWhiteAddrs);
}
List<PlainAccessConfig> plainAccessConfigs = plainAclConfData.getAccounts();
if (plainAccessConfigs != null && !plainAccessConfigs.isEmpty()) {
for (PlainAccessConfig accessConfig : plainAccessConfigs) {
if (!accessKeySets.contains(accessConfig.getAccessKey())) {
accessKeySets.add(accessConfig.getAccessKey());
PlainAccessConfig plainAccessConfig = new PlainAccessConfig();
plainAccessConfig.setGroupPerms(accessConfig.getGroupPerms());
plainAccessConfig.setDefaultTopicPerm(accessConfig.getDefaultTopicPerm());
plainAccessConfig.setDefaultGroupPerm(accessConfig.getDefaultGroupPerm());
plainAccessConfig.setAccessKey(accessConfig.getAccessKey());
plainAccessConfig.setSecretKey(accessConfig.getSecretKey());
plainAccessConfig.setAdmin(accessConfig.isAdmin());
plainAccessConfig.setTopicPerms(accessConfig.getTopicPerms());
plainAccessConfig.setWhiteRemoteAddress(accessConfig.getWhiteRemoteAddress());
configs.add(plainAccessConfig);
}
}
}
}
aclConfig.setPlainAccessConfigs(configs);
aclConfig.setGlobalWhiteAddrs(whiteAddrs);
return aclConfig;
}
}

View File

@@ -0,0 +1,148 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext;
import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager;
import org.apache.rocketmq.auth.authentication.model.User;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.auth.helper.AuthTestHelper;
import org.apache.rocketmq.common.MixAll;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class AuthenticationEvaluatorTest {
private AuthConfig authConfig;
private AuthenticationEvaluator evaluator;
private AuthenticationMetadataManager authenticationMetadataManager;
@Before
public void setUp() throws Exception {
if (MixAll.isMac()) {
return;
}
this.authConfig = AuthTestHelper.createDefaultConfig();
this.evaluator = new AuthenticationEvaluator(authConfig);
this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(authConfig);
this.clearAllUsers();
}
@After
public void tearDown() throws Exception {
if (MixAll.isMac()) {
return;
}
this.clearAllUsers();
this.authenticationMetadataManager.shutdown();
}
@Test
public void evaluate1() {
if (MixAll.isMac()) {
return;
}
User user = User.of("test", "test");
this.authenticationMetadataManager.createUser(user);
DefaultAuthenticationContext context = new DefaultAuthenticationContext();
context.setRpcCode("11");
context.setUsername("test");
context.setContent("test".getBytes(StandardCharsets.UTF_8));
context.setSignature("DJRRXBXlCVuKh6ULoN87847QX+Y=");
this.evaluator.evaluate(context);
}
@Test
public void evaluate2() {
if (MixAll.isMac()) {
return;
}
DefaultAuthenticationContext context = new DefaultAuthenticationContext();
context.setRpcCode("11");
context.setUsername("test");
context.setContent("test".getBytes(StandardCharsets.UTF_8));
context.setSignature("DJRRXBXlCVuKh6ULoN87847QX+Y=");
Assert.assertThrows(AuthenticationException.class, () -> this.evaluator.evaluate(context));
}
@Test
public void evaluate3() {
if (MixAll.isMac()) {
return;
}
User user = User.of("test", "test");
this.authenticationMetadataManager.createUser(user);
DefaultAuthenticationContext context = new DefaultAuthenticationContext();
context.setRpcCode("11");
context.setUsername("test");
context.setContent("test".getBytes(StandardCharsets.UTF_8));
context.setSignature("test");
Assert.assertThrows(AuthenticationException.class, () -> this.evaluator.evaluate(context));
}
@Test
public void evaluate4() {
if (MixAll.isMac()) {
return;
}
this.authConfig.setAuthenticationWhitelist("11");
this.evaluator = new AuthenticationEvaluator(authConfig);
DefaultAuthenticationContext context = new DefaultAuthenticationContext();
context.setRpcCode("11");
context.setUsername("test");
context.setContent("test".getBytes(StandardCharsets.UTF_8));
context.setSignature("test");
this.evaluator.evaluate(context);
}
@Test
public void evaluate5() {
if (MixAll.isMac()) {
return;
}
this.authConfig.setAuthenticationEnabled(false);
this.evaluator = new AuthenticationEvaluator(authConfig);
DefaultAuthenticationContext context = new DefaultAuthenticationContext();
context.setRpcCode("11");
context.setUsername("test");
context.setContent("test".getBytes(StandardCharsets.UTF_8));
context.setSignature("test");
this.evaluator.evaluate(context);
}
private void clearAllUsers() {
if (MixAll.isMac()) {
return;
}
List<User> users = this.authenticationMetadataManager.listUser(null).join();
if (CollectionUtils.isEmpty(users)) {
return;
}
users.forEach(user -> this.authenticationMetadataManager.deleteUser(user.getUsername()).join());
}
}

View File

@@ -0,0 +1,117 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.builder;
import apache.rocketmq.v2.Message;
import apache.rocketmq.v2.Resource;
import apache.rocketmq.v2.SendMessageRequest;
import com.google.protobuf.ByteString;
import io.grpc.Metadata;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import java.nio.charset.StandardCharsets;
import org.apache.rocketmq.auth.authentication.context.DefaultAuthenticationContext;
import org.apache.rocketmq.common.constant.GrpcConstants;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.apache.rocketmq.remoting.protocol.RequestCode;
import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.Silent.class)
public class DefaultAuthenticationContextBuilderTest {
private DefaultAuthenticationContextBuilder builder;
@Mock
private ChannelHandlerContext channelHandlerContext;
@Mock
private Channel channel;
@Before
public void setUp() throws Exception {
builder = new DefaultAuthenticationContextBuilder();
}
@Test
public void build1() {
Resource topic = Resource.newBuilder().setName("topic-test").build();
{
SendMessageRequest request = SendMessageRequest.newBuilder()
.addMessages(Message.newBuilder().setTopic(topic)
.setBody(ByteString.copyFromUtf8("message-body"))
.build())
.build();
Metadata metadata = new Metadata();
metadata.put(GrpcConstants.AUTHORIZATION, "MQv2-HMAC-SHA1 Credential=abc, SignedHeaders=x-mq-date-time, Signature=D18A9CBCDDBA9041D6693268FEF15A989E64430B");
metadata.put(GrpcConstants.DATE_TIME, "20231227T194619Z");
DefaultAuthenticationContext context = builder.build(metadata, request);
Assert.assertNotNull(context);
Assert.assertEquals("abc", context.getUsername());
Assert.assertEquals("0YqcvN26kEHWaTJo/vFamJ5kQws=", context.getSignature());
Assert.assertEquals("20231227T194619Z", new String(context.getContent(), StandardCharsets.UTF_8));
}
}
@Test
public void build2() {
when(channel.id()).thenReturn(mockChannelId("channel-id"));
when(channelHandlerContext.channel()).thenReturn(channel);
SendMessageRequestHeader requestHeader = new SendMessageRequestHeader();
requestHeader.setTopic("topic-test");
requestHeader.setQueueId(0);
requestHeader.setBornTimestamp(117036786441330L);
requestHeader.setBname("brokerName-1");
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, requestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "abc");
request.addExtField("Signature", "ZG26exJ5u9q1fwZlO4DCmz2Rs88=");
request.makeCustomHeaderToNet();
DefaultAuthenticationContext context = builder.build(channelHandlerContext, request);
Assert.assertNotNull(context);
Assert.assertEquals("abc", context.getUsername());
Assert.assertEquals("ZG26exJ5u9q1fwZlO4DCmz2Rs88=", context.getSignature());
Assert.assertEquals("abcbrokerName-11170367864413300topic-test", new String(context.getContent(), StandardCharsets.UTF_8));
}
private ChannelId mockChannelId(String channelId) {
return new ChannelId() {
@Override
public String asShortText() {
return channelId;
}
@Override
public String asLongText() {
return channelId;
}
@Override
public int compareTo(ChannelId o) {
return 0;
}
};
}
}

View File

@@ -0,0 +1,186 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authentication.manager;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.apache.rocketmq.auth.authentication.enums.UserType;
import org.apache.rocketmq.auth.authentication.exception.AuthenticationException;
import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
import org.apache.rocketmq.auth.authentication.model.User;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.auth.helper.AuthTestHelper;
import org.apache.rocketmq.common.MixAll;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class AuthenticationMetadataManagerTest {
private AuthConfig authConfig;
private AuthenticationMetadataManager authenticationMetadataManager;
@Before
public void setUp() throws Exception {
if (MixAll.isMac()) {
return;
}
this.authConfig = AuthTestHelper.createDefaultConfig();
this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(this.authConfig);
this.clearAllUsers();
}
@After
public void tearDown() throws Exception {
if (MixAll.isMac()) {
return;
}
this.clearAllUsers();
this.authenticationMetadataManager.shutdown();
}
@Test
public void createUser() {
if (MixAll.isMac()) {
return;
}
User user = User.of("test", "test");
this.authenticationMetadataManager.createUser(user).join();
user = this.authenticationMetadataManager.getUser("test").join();
Assert.assertNotNull(user);
Assert.assertEquals(user.getUsername(), "test");
Assert.assertEquals(user.getPassword(), "test");
Assert.assertEquals(user.getUserType(), UserType.NORMAL);
user = User.of("super", "super", UserType.SUPER);
this.authenticationMetadataManager.createUser(user).join();
user = this.authenticationMetadataManager.getUser("super").join();
Assert.assertNotNull(user);
Assert.assertEquals(user.getUsername(), "super");
Assert.assertEquals(user.getPassword(), "super");
Assert.assertEquals(user.getUserType(), UserType.SUPER);
Assert.assertThrows(AuthenticationException.class, () -> {
try {
User user2 = User.of("test", "test");
this.authenticationMetadataManager.createUser(user2).join();
} catch (Exception e) {
AuthTestHelper.handleException(e);
}
});
}
@Test
public void updateUser() {
if (MixAll.isMac()) {
return;
}
User user = User.of("test", "test");
this.authenticationMetadataManager.createUser(user).join();
user = this.authenticationMetadataManager.getUser("test").join();
Assert.assertNotNull(user);
Assert.assertEquals(user.getUsername(), "test");
Assert.assertEquals(user.getPassword(), "test");
Assert.assertEquals(user.getUserType(), UserType.NORMAL);
user.setPassword("123");
this.authenticationMetadataManager.updateUser(user).join();
user = this.authenticationMetadataManager.getUser("test").join();
Assert.assertNotNull(user);
Assert.assertEquals(user.getUsername(), "test");
Assert.assertEquals(user.getPassword(), "123");
Assert.assertEquals(user.getUserType(), UserType.NORMAL);
user.setUserType(UserType.SUPER);
this.authenticationMetadataManager.updateUser(user).join();
user = this.authenticationMetadataManager.getUser("test").join();
Assert.assertNotNull(user);
Assert.assertEquals(user.getUsername(), "test");
Assert.assertEquals(user.getPassword(), "123");
Assert.assertEquals(user.getUserType(), UserType.SUPER);
Assert.assertThrows(AuthenticationException.class, () -> {
try {
User user2 = User.of("no_user", "no_user");
this.authenticationMetadataManager.updateUser(user2).join();
} catch (Exception e) {
AuthTestHelper.handleException(e);
}
});
}
@Test
public void deleteUser() {
if (MixAll.isMac()) {
return;
}
User user = User.of("test", "test");
this.authenticationMetadataManager.createUser(user).join();
user = this.authenticationMetadataManager.getUser("test").join();
Assert.assertNotNull(user);
this.authenticationMetadataManager.deleteUser("test").join();
user = this.authenticationMetadataManager.getUser("test").join();
Assert.assertNull(user);
this.authenticationMetadataManager.deleteUser("no_user").join();
}
@Test
public void getUser() {
if (MixAll.isMac()) {
return;
}
User user = User.of("test", "test");
this.authenticationMetadataManager.createUser(user).join();
user = this.authenticationMetadataManager.getUser("test").join();
Assert.assertNotNull(user);
Assert.assertEquals(user.getUsername(), "test");
Assert.assertEquals(user.getPassword(), "test");
Assert.assertEquals(user.getUserType(), UserType.NORMAL);
user = this.authenticationMetadataManager.getUser("no_user").join();
Assert.assertNull(user);
}
@Test
public void listUser() {
if (MixAll.isMac()) {
return;
}
List<User> users = this.authenticationMetadataManager.listUser(null).join();
Assert.assertTrue(CollectionUtils.isEmpty(users));
User user = User.of("test-1", "test-1");
this.authenticationMetadataManager.createUser(user).join();
users = this.authenticationMetadataManager.listUser(null).join();
Assert.assertEquals(users.size(), 1);
user = User.of("test-2", "test-2");
this.authenticationMetadataManager.createUser(user).join();
users = this.authenticationMetadataManager.listUser("test").join();
Assert.assertEquals(users.size(), 2);
}
private void clearAllUsers() {
List<User> users = this.authenticationMetadataManager.listUser(null).join();
if (CollectionUtils.isEmpty(users)) {
return;
}
users.forEach(user -> this.authenticationMetadataManager.deleteUser(user.getUsername()).join());
}
}

View File

@@ -0,0 +1,363 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager;
import org.apache.rocketmq.auth.authentication.model.Subject;
import org.apache.rocketmq.auth.authentication.model.User;
import org.apache.rocketmq.auth.authorization.context.AuthorizationContext;
import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext;
import org.apache.rocketmq.auth.authorization.enums.Decision;
import org.apache.rocketmq.auth.authorization.enums.PolicyType;
import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory;
import org.apache.rocketmq.auth.authorization.manager.AuthorizationMetadataManager;
import org.apache.rocketmq.auth.authorization.model.Acl;
import org.apache.rocketmq.auth.authorization.model.Resource;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.auth.helper.AuthTestHelper;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.action.Action;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class AuthorizationEvaluatorTest {
private AuthConfig authConfig;
private AuthorizationEvaluator evaluator;
private AuthenticationMetadataManager authenticationMetadataManager;
private AuthorizationMetadataManager authorizationMetadataManager;
@Before
public void setUp() throws Exception {
if (MixAll.isMac()) {
return;
}
this.authConfig = AuthTestHelper.createDefaultConfig();
this.evaluator = new AuthorizationEvaluator(authConfig);
this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(authConfig);
this.authorizationMetadataManager = AuthorizationFactory.getMetadataManager(authConfig);
this.clearAllAcls();
this.clearAllUsers();
}
@After
public void tearDown() throws Exception {
if (MixAll.isMac()) {
return;
}
this.clearAllAcls();
this.clearAllUsers();
this.authenticationMetadataManager.shutdown();
}
@Test
public void evaluate1() {
if (MixAll.isMac()) {
return;
}
User user = User.of("test", "test");
this.authenticationMetadataManager.createUser(user).join();
Acl acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub", "192.168.0.0/24", Decision.ALLOW);
this.authorizationMetadataManager.createAcl(acl).join();
Subject subject = Subject.of("User:test");
Resource resource = Resource.ofTopic("test");
Action action = Action.PUB;
String sourceIp = "192.168.0.1";
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context.setRpcCode("10");
this.evaluator.evaluate(Collections.singletonList(context));
// acl sourceIp is null
acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub", null, Decision.ALLOW);
this.authorizationMetadataManager.updateAcl(acl).join();
subject = Subject.of("User:test");
resource = Resource.ofTopic("test");
action = Action.PUB;
sourceIp = "192.168.0.1";
context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context.setRpcCode("10");
this.evaluator.evaluate(Collections.singletonList(context));
}
@Test
public void evaluate2() {
if (MixAll.isMac()) {
return;
}
User user = User.of("test", "test");
this.authenticationMetadataManager.createUser(user).join();
Acl acl = AuthTestHelper.buildAcl("User:test", "Topic:test*,Group:test*", "Sub", "192.168.0.0/24", Decision.ALLOW);
this.authorizationMetadataManager.createAcl(acl).join();
List<AuthorizationContext> contexts = new ArrayList<>();
Subject subject = Subject.of("User:test");
Resource resource = Resource.ofTopic("test");
Action action = Action.SUB;
String sourceIp = "192.168.0.1";
DefaultAuthorizationContext context1 = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context1.setRpcCode("11");
contexts.add(context1);
subject = Subject.of("User:test");
resource = Resource.ofGroup("test");
action = Action.SUB;
sourceIp = "192.168.0.1";
DefaultAuthorizationContext context2 = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context2.setRpcCode("11");
contexts.add(context2);
this.evaluator.evaluate(contexts);
}
@Test
public void evaluate4() {
if (MixAll.isMac()) {
return;
}
User user = User.of("test", "test");
this.authenticationMetadataManager.createUser(user).join();
Acl acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub", "192.168.0.0/24", Decision.ALLOW);
this.authorizationMetadataManager.createAcl(acl).join();
// user not exist
Assert.assertThrows(AuthorizationException.class, () -> {
Subject subject = Subject.of("User:abc");
Resource resource = Resource.ofTopic("test");
Action action = Action.PUB;
String sourceIp = "192.168.0.1";
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context.setRpcCode("10");
this.evaluator.evaluate(Collections.singletonList(context));
});
// resource not match
Assert.assertThrows(AuthorizationException.class, () -> {
Subject subject = Subject.of("User:test");
Resource resource = Resource.ofTopic("abc");
Action action = Action.PUB;
String sourceIp = "192.168.0.1";
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context.setRpcCode("10");
this.evaluator.evaluate(Collections.singletonList(context));
});
// action not match
Assert.assertThrows(AuthorizationException.class, () -> {
Subject subject = Subject.of("User:test");
Resource resource = Resource.ofTopic("test");
Action action = Action.SUB;
String sourceIp = "192.168.0.1";
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context.setRpcCode("10");
this.evaluator.evaluate(Collections.singletonList(context));
});
// sourceIp not match
Assert.assertThrows(AuthorizationException.class, () -> {
Subject subject = Subject.of("User:test");
Resource resource = Resource.ofTopic("test");
Action action = Action.PUB;
String sourceIp = "10.10.0.1";
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context.setRpcCode("10");
this.evaluator.evaluate(Collections.singletonList(context));
});
// decision is deny
acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub", "192.168.0.0/24", Decision.DENY);
this.authorizationMetadataManager.updateAcl(acl).join();
Assert.assertThrows(AuthorizationException.class, () -> {
Subject subject = Subject.of("User:test");
Resource resource = Resource.ofTopic("test");
Action action = Action.PUB;
String sourceIp = "192.168.0.1";
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context.setRpcCode("10");
this.evaluator.evaluate(Collections.singletonList(context));
});
}
@Test
public void evaluate5() {
if (MixAll.isMac()) {
return;
}
User user = User.of("test", "test");
this.authenticationMetadataManager.createUser(user).join();
Acl acl = AuthTestHelper.buildAcl("User:test", "*", "Pub,Sub", "192.168.0.0/24", Decision.ALLOW);
this.authorizationMetadataManager.createAcl(acl).join();
acl = AuthTestHelper.buildAcl("User:test", "Topic:*", "Pub,Sub", "192.168.0.0/24", Decision.DENY);
this.authorizationMetadataManager.updateAcl(acl).join();
acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub,Sub", "192.168.0.0/24", Decision.ALLOW);
this.authorizationMetadataManager.updateAcl(acl).join();
acl = AuthTestHelper.buildAcl("User:test", "Topic:test-1", "Pub,Sub", "192.168.0.0/24", Decision.DENY);
this.authorizationMetadataManager.updateAcl(acl).join();
Assert.assertThrows(AuthorizationException.class, () -> {
Subject subject = Subject.of("User:test");
Resource resource = Resource.ofTopic("test-1");
Action action = Action.PUB;
String sourceIp = "192.168.0.1";
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context.setRpcCode("10");
this.evaluator.evaluate(Collections.singletonList(context));
});
{
Subject subject = Subject.of("User:test");
Resource resource = Resource.ofTopic("test-2");
Action action = Action.PUB;
String sourceIp = "192.168.0.1";
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context.setRpcCode("10");
this.evaluator.evaluate(Collections.singletonList(context));
}
Assert.assertThrows(AuthorizationException.class, () -> {
Subject subject = Subject.of("User:test");
Resource resource = Resource.ofTopic("abc");
Action action = Action.PUB;
String sourceIp = "192.168.0.1";
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context.setRpcCode("10");
this.evaluator.evaluate(Collections.singletonList(context));
});
{
Subject subject = Subject.of("User:test");
Resource resource = Resource.ofGroup("test-2");
Action action = Action.SUB;
String sourceIp = "192.168.0.1";
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context.setRpcCode("10");
this.evaluator.evaluate(Collections.singletonList(context));
}
}
@Test
public void evaluate6() {
if (MixAll.isMac()) {
return;
}
this.authConfig.setAuthorizationWhitelist("10");
this.evaluator = new AuthorizationEvaluator(this.authConfig);
Subject subject = Subject.of("User:test");
Resource resource = Resource.ofTopic("test");
Action action = Action.PUB;
String sourceIp = "192.168.0.1";
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context.setRpcCode("10");
this.evaluator.evaluate(Collections.singletonList(context));
}
@Test
public void evaluate7() {
if (MixAll.isMac()) {
return;
}
this.authConfig.setAuthorizationEnabled(false);
this.evaluator = new AuthorizationEvaluator(this.authConfig);
Subject subject = Subject.of("User:test");
Resource resource = Resource.ofTopic("test");
Action action = Action.PUB;
String sourceIp = "192.168.0.1";
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context.setRpcCode("10");
this.evaluator.evaluate(Collections.singletonList(context));
}
@Test
public void evaluate8() {
if (MixAll.isMac()) {
return;
}
User user = User.of("test", "test");
this.authenticationMetadataManager.createUser(user).join();
Acl acl = AuthTestHelper.buildAcl("User:test", "Topic:test*", "Pub", "192.168.0.0/24", Decision.DENY);
this.authorizationMetadataManager.createAcl(acl).join();
Assert.assertThrows(AuthorizationException.class, () -> {
Subject subject = Subject.of("User:test");
Resource resource = Resource.ofTopic("test");
Action action = Action.PUB;
String sourceIp = "192.168.0.1";
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context.setRpcCode("10");
this.evaluator.evaluate(Collections.singletonList(context));
});
Assert.assertThrows(AuthorizationException.class, () -> {
Subject subject = Subject.of("User:test");
Resource resource = Resource.ofTopic("abc");
Action action = Action.PUB;
String sourceIp = "192.168.0.1";
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context.setRpcCode("10");
this.evaluator.evaluate(Collections.singletonList(context));
});
acl = AuthTestHelper.buildAcl("User:test", PolicyType.DEFAULT, "Topic:*", "Pub", null, Decision.ALLOW);
this.authorizationMetadataManager.updateAcl(acl).join();
{
Subject subject = Subject.of("User:test");
Resource resource = Resource.ofTopic("abc");
Action action = Action.PUB;
String sourceIp = "192.168.0.1";
DefaultAuthorizationContext context = DefaultAuthorizationContext.of(subject, resource, action, sourceIp);
context.setRpcCode("10");
this.evaluator.evaluate(Collections.singletonList(context));
}
}
private void clearAllUsers() {
List<User> users = this.authenticationMetadataManager.listUser(null).join();
if (CollectionUtils.isEmpty(users)) {
return;
}
users.forEach(user -> this.authenticationMetadataManager.deleteUser(user.getUsername()).join());
}
private void clearAllAcls() {
List<Acl> acls = this.authorizationMetadataManager.listAcl(null, null).join();
if (CollectionUtils.isEmpty(acls)) {
return;
}
acls.forEach(acl -> this.authorizationMetadataManager.deleteAcl(acl.getSubject(), null, null).join());
}
}

View File

@@ -0,0 +1,580 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.builder;
import apache.rocketmq.v2.AckMessageRequest;
import apache.rocketmq.v2.ChangeInvisibleDurationRequest;
import apache.rocketmq.v2.ClientType;
import apache.rocketmq.v2.EndTransactionRequest;
import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest;
import apache.rocketmq.v2.HeartbeatRequest;
import apache.rocketmq.v2.Message;
import apache.rocketmq.v2.MessageQueue;
import apache.rocketmq.v2.NotifyClientTerminationRequest;
import apache.rocketmq.v2.Publishing;
import apache.rocketmq.v2.QueryAssignmentRequest;
import apache.rocketmq.v2.QueryRouteRequest;
import apache.rocketmq.v2.RecallMessageRequest;
import apache.rocketmq.v2.ReceiveMessageRequest;
import apache.rocketmq.v2.Resource;
import apache.rocketmq.v2.SendMessageRequest;
import apache.rocketmq.v2.Settings;
import apache.rocketmq.v2.Subscription;
import apache.rocketmq.v2.SubscriptionEntry;
import apache.rocketmq.v2.TelemetryCommand;
import com.alibaba.fastjson2.JSON;
import com.google.common.collect.Sets;
import com.google.protobuf.GeneratedMessageV3;
import io.grpc.Metadata;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import java.util.Arrays;
import java.util.List;
import org.apache.rocketmq.auth.authorization.context.DefaultAuthorizationContext;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.common.TopicFilterType;
import org.apache.rocketmq.common.action.Action;
import org.apache.rocketmq.common.constant.GrpcConstants;
import org.apache.rocketmq.common.resource.ResourceType;
import org.apache.rocketmq.remoting.netty.AttributeKeys;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.apache.rocketmq.remoting.protocol.RequestCode;
import org.apache.rocketmq.remoting.protocol.RequestHeaderRegistry;
import org.apache.rocketmq.remoting.protocol.header.ConsumerSendMsgBackRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.CreateTopicRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.CreateUserRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.EndTransactionRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.GetConsumerListByGroupRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.HeartbeatRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.QueryConsumerOffsetRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.QueryMessageRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.RecallMessageRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeaderV2;
import org.apache.rocketmq.remoting.protocol.header.UnregisterClientRequestHeader;
import org.apache.rocketmq.remoting.protocol.header.UpdateConsumerOffsetRequestHeader;
import org.apache.rocketmq.remoting.protocol.heartbeat.ConsumerData;
import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData;
import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.Silent.class)
public class DefaultAuthorizationContextBuilderTest {
private AuthorizationContextBuilder builder;
@Mock
private ChannelHandlerContext channelHandlerContext;
@Mock
private Channel channel;
@Before
public void setUp() throws Exception {
AuthConfig authConfig = new AuthConfig();
authConfig.setClusterName("DefaultCluster");
builder = new DefaultAuthorizationContextBuilder(authConfig);
RequestHeaderRegistry.getInstance().initialize();
}
@Test
public void buildGrpc() {
Metadata metadata = new Metadata();
metadata.put(GrpcConstants.AUTHORIZATION_AK, "rocketmq");
metadata.put(GrpcConstants.REMOTE_ADDRESS, "192.168.0.1");
metadata.put(GrpcConstants.CHANNEL_ID, "channel-id");
GeneratedMessageV3 request = SendMessageRequest.newBuilder()
.addMessages(Message.newBuilder()
.setTopic(Resource.newBuilder().setName("topic").build())
.build())
.build();
List<DefaultAuthorizationContext> result = builder.build(metadata, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Topic:topic");
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB)));
Assert.assertEquals(result.get(0).getSourceIp(), "192.168.0.1");
Assert.assertEquals(result.get(0).getChannelId(), "channel-id");
Assert.assertEquals(result.get(0).getRpcCode(), SendMessageRequest.getDescriptor().getFullName());
request = RecallMessageRequest.newBuilder()
.setTopic(Resource.newBuilder().setName("topic").build())
.setRecallHandle("handle")
.build();
result = builder.build(metadata, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Topic:topic");
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB)));
Assert.assertEquals(result.get(0).getSourceIp(), "192.168.0.1");
Assert.assertEquals(result.get(0).getChannelId(), "channel-id");
Assert.assertEquals(result.get(0).getRpcCode(), RecallMessageRequest.getDescriptor().getFullName());
request = EndTransactionRequest.newBuilder()
.setTopic(Resource.newBuilder().setName("topic").build())
.build();
result = builder.build(metadata, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Topic:topic");
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB)));
request = HeartbeatRequest.newBuilder()
.setClientType(ClientType.PUSH_CONSUMER)
.setGroup(Resource.newBuilder().setName("group").build())
.build();
result = builder.build(metadata, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Group:group");
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB)));
request = ReceiveMessageRequest.newBuilder()
.setMessageQueue(MessageQueue.newBuilder()
.setTopic(Resource.newBuilder().setName("topic").build())
.build())
.setGroup(Resource.newBuilder().setName("group").build())
.build();
result = builder.build(metadata, request);
Assert.assertEquals(2, result.size());
Assert.assertEquals(getContext(result, ResourceType.GROUP).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(getContext(result, ResourceType.GROUP).getResource().getResourceKey(), "Group:group");
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB)));
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic");
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB)));
request = AckMessageRequest.newBuilder()
.setTopic(Resource.newBuilder().setName("topic").build())
.setGroup(Resource.newBuilder().setName("group").build())
.build();
result = builder.build(metadata, request);
Assert.assertEquals(2, result.size());
Assert.assertEquals(getContext(result, ResourceType.GROUP).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(getContext(result, ResourceType.GROUP).getResource().getResourceKey(), "Group:group");
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB)));
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic");
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB)));
request = ForwardMessageToDeadLetterQueueRequest.newBuilder()
.setTopic(Resource.newBuilder().setName("topic").build())
.setGroup(Resource.newBuilder().setName("group").build())
.build();
result = builder.build(metadata, request);
Assert.assertEquals(2, result.size());
Assert.assertEquals(getContext(result, ResourceType.GROUP).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(getContext(result, ResourceType.GROUP).getResource().getResourceKey(), "Group:group");
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB)));
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic");
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB)));
request = NotifyClientTerminationRequest.newBuilder()
.setGroup(Resource.newBuilder().setName("group").build())
.build();
result = builder.build(metadata, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Group:group");
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB)));
request = ChangeInvisibleDurationRequest.newBuilder()
.setGroup(Resource.newBuilder().setName("group").build())
.build();
result = builder.build(metadata, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Group:group");
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB)));
request = QueryRouteRequest.newBuilder()
.setTopic(Resource.newBuilder().setName("topic").build())
.build();
result = builder.build(metadata, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals(result.get(0).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(result.get(0).getResource().getResourceKey(), "Topic:topic");
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB, Action.SUB)));
request = QueryAssignmentRequest.newBuilder()
.setTopic(Resource.newBuilder().setName("topic").build())
.setGroup(Resource.newBuilder().setName("group").build())
.build();
result = builder.build(metadata, request);
Assert.assertEquals(2, result.size());
Assert.assertEquals(getContext(result, ResourceType.GROUP).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(getContext(result, ResourceType.GROUP).getResource().getResourceKey(), "Group:group");
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB)));
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic");
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB)));
request = TelemetryCommand.newBuilder()
.setSettings(Settings.newBuilder()
.setPublishing(Publishing.newBuilder()
.addTopics(Resource.newBuilder().setName("topic").build())
.build())
.build())
.build();
result = builder.build(metadata, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic");
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.PUB)));
request = TelemetryCommand.newBuilder()
.setSettings(Settings.newBuilder()
.setSubscription(Subscription.newBuilder()
.setGroup(Resource.newBuilder().setName("group").build())
.addSubscriptions(SubscriptionEntry.newBuilder()
.setTopic(Resource.newBuilder().setName("topic").build())
.build())
.build())
.build())
.build();
result = builder.build(metadata, request);
Assert.assertEquals(2, result.size());
Assert.assertEquals(getContext(result, ResourceType.GROUP).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(getContext(result, ResourceType.GROUP).getResource().getResourceKey(), "Group:group");
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB)));
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey(), "User:rocketmq");
Assert.assertEquals(getContext(result, ResourceType.TOPIC).getResource().getResourceKey(), "Topic:topic");
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB)));
}
@Test
public void buildRemoting() {
when(channel.id()).thenReturn(mockChannelId("channel-id"));
when(channel.hasAttr(eq(AttributeKeys.PROXY_PROTOCOL_ADDR))).thenReturn(true);
when(channel.attr(eq(AttributeKeys.PROXY_PROTOCOL_ADDR))).thenReturn(mockAttribute("192.168.0.1"));
when(channel.hasAttr(eq(AttributeKeys.PROXY_PROTOCOL_PORT))).thenReturn(true);
when(channel.attr(eq(AttributeKeys.PROXY_PROTOCOL_PORT))).thenReturn(mockAttribute("1234"));
when(channelHandlerContext.channel()).thenReturn(channel);
SendMessageRequestHeader sendMessageRequestHeader = new SendMessageRequestHeader();
sendMessageRequestHeader.setTopic("topic");
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, sendMessageRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
List<DefaultAuthorizationContext> result = builder.build(channelHandlerContext, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey());
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB)));
Assert.assertEquals("192.168.0.1", result.get(0).getSourceIp());
Assert.assertEquals("channel-id", result.get(0).getChannelId());
Assert.assertEquals(RequestCode.SEND_MESSAGE + "", result.get(0).getRpcCode());
sendMessageRequestHeader = new SendMessageRequestHeader();
sendMessageRequestHeader.setTopic("%RETRY%group");
request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, sendMessageRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
result = builder.build(channelHandlerContext, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
Assert.assertEquals("Group:group", result.get(0).getResource().getResourceKey());
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB)));
SendMessageRequestHeaderV2 sendMessageRequestHeaderV2 = new SendMessageRequestHeaderV2();
sendMessageRequestHeaderV2.setTopic("topic");
request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, sendMessageRequestHeaderV2);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
result = builder.build(channelHandlerContext, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey());
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB)));
sendMessageRequestHeaderV2 = new SendMessageRequestHeaderV2();
sendMessageRequestHeaderV2.setTopic("%RETRY%group");
request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, sendMessageRequestHeaderV2);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
result = builder.build(channelHandlerContext, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
Assert.assertEquals("Group:group", result.get(0).getResource().getResourceKey());
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB)));
RecallMessageRequestHeader recallMessageRequestHeader = new RecallMessageRequestHeader();
recallMessageRequestHeader.setTopic("topic");
recallMessageRequestHeader.setRecallHandle("handle");
request = RemotingCommand.createRequestCommand(RequestCode.RECALL_MESSAGE, recallMessageRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
result = builder.build(channelHandlerContext, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey());
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB)));
Assert.assertEquals("192.168.0.1", result.get(0).getSourceIp());
Assert.assertEquals("channel-id", result.get(0).getChannelId());
Assert.assertEquals(RequestCode.RECALL_MESSAGE + "", result.get(0).getRpcCode());
EndTransactionRequestHeader endTransactionRequestHeader = new EndTransactionRequestHeader();
endTransactionRequestHeader.setTopic("topic");
request = RemotingCommand.createRequestCommand(RequestCode.END_TRANSACTION, endTransactionRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
result = builder.build(channelHandlerContext, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey());
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.PUB)));
endTransactionRequestHeader = new EndTransactionRequestHeader();
request = RemotingCommand.createRequestCommand(RequestCode.END_TRANSACTION, endTransactionRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
result = builder.build(channelHandlerContext, request);
Assert.assertEquals(0, result.size());
ConsumerSendMsgBackRequestHeader consumerSendMsgBackRequestHeader = new ConsumerSendMsgBackRequestHeader();
consumerSendMsgBackRequestHeader.setGroup("group");
request = RemotingCommand.createRequestCommand(RequestCode.CONSUMER_SEND_MSG_BACK, consumerSendMsgBackRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
result = builder.build(channelHandlerContext, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
Assert.assertEquals("Group:group", result.get(0).getResource().getResourceKey());
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB)));
PullMessageRequestHeader pullMessageRequestHeader = new PullMessageRequestHeader();
pullMessageRequestHeader.setTopic("topic");
pullMessageRequestHeader.setConsumerGroup("group");
request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, pullMessageRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
result = builder.build(channelHandlerContext, request);
Assert.assertEquals(2, result.size());
Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.GROUP).getSubject().getSubjectKey());
Assert.assertEquals("Group:group", getContext(result, ResourceType.GROUP).getResource().getResourceKey());
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB)));
Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey());
Assert.assertEquals("Topic:topic", getContext(result, ResourceType.TOPIC).getResource().getResourceKey());
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB)));
QueryMessageRequestHeader queryMessageRequestHeader = new QueryMessageRequestHeader();
queryMessageRequestHeader.setTopic("topic");
request = RemotingCommand.createRequestCommand(RequestCode.QUERY_MESSAGE, queryMessageRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
result = builder.build(channelHandlerContext, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey());
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB, Action.GET)));
HeartbeatRequestHeader heartbeatRequestHeader = new HeartbeatRequestHeader();
request = RemotingCommand.createRequestCommand(RequestCode.HEART_BEAT, heartbeatRequestHeader);
HeartbeatData heartbeatData = new HeartbeatData();
ConsumerData consumerData = new ConsumerData();
consumerData.setGroupName("group");
SubscriptionData subscriptionData = new SubscriptionData();
subscriptionData.setTopic("topic");
consumerData.setSubscriptionDataSet(Sets.newHashSet(subscriptionData));
heartbeatData.setConsumerDataSet(Sets.newHashSet(consumerData));
request.setBody(JSON.toJSONBytes(heartbeatData));
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
result = builder.build(channelHandlerContext, request);
Assert.assertEquals(2, result.size());
Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.GROUP).getSubject().getSubjectKey());
Assert.assertEquals("Group:group", getContext(result, ResourceType.GROUP).getResource().getResourceKey());
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB)));
Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey());
Assert.assertEquals("Topic:topic", getContext(result, ResourceType.TOPIC).getResource().getResourceKey());
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB)));
UnregisterClientRequestHeader unregisterClientRequestHeader = new UnregisterClientRequestHeader();
unregisterClientRequestHeader.setConsumerGroup("group");
request = RemotingCommand.createRequestCommand(RequestCode.UNREGISTER_CLIENT, unregisterClientRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
result = builder.build(channelHandlerContext, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
Assert.assertEquals("Group:group", result.get(0).getResource().getResourceKey());
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB)));
GetConsumerListByGroupRequestHeader getConsumerListByGroupRequestHeader = new GetConsumerListByGroupRequestHeader();
getConsumerListByGroupRequestHeader.setConsumerGroup("group");
request = RemotingCommand.createRequestCommand(RequestCode.GET_CONSUMER_LIST_BY_GROUP, getConsumerListByGroupRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
result = builder.build(channelHandlerContext, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
Assert.assertEquals("Group:group", result.get(0).getResource().getResourceKey());
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.SUB, Action.GET)));
QueryConsumerOffsetRequestHeader queryConsumerOffsetRequestHeader = new QueryConsumerOffsetRequestHeader();
queryConsumerOffsetRequestHeader.setTopic("topic");
queryConsumerOffsetRequestHeader.setConsumerGroup("group");
request = RemotingCommand.createRequestCommand(RequestCode.QUERY_CONSUMER_OFFSET, queryConsumerOffsetRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
result = builder.build(channelHandlerContext, request);
Assert.assertEquals(2, result.size());
Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.GROUP).getSubject().getSubjectKey());
Assert.assertEquals("Group:group", getContext(result, ResourceType.GROUP).getResource().getResourceKey());
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB)));
Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey());
Assert.assertEquals("Topic:topic", getContext(result, ResourceType.TOPIC).getResource().getResourceKey());
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB)));
UpdateConsumerOffsetRequestHeader updateConsumerOffsetRequestHeader = new UpdateConsumerOffsetRequestHeader();
updateConsumerOffsetRequestHeader.setTopic("topic");
updateConsumerOffsetRequestHeader.setConsumerGroup("group");
request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_CONSUMER_OFFSET, updateConsumerOffsetRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
result = builder.build(channelHandlerContext, request);
Assert.assertEquals(2, result.size());
Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.GROUP).getSubject().getSubjectKey());
Assert.assertEquals("Group:group", getContext(result, ResourceType.GROUP).getResource().getResourceKey());
Assert.assertTrue(getContext(result, ResourceType.GROUP).getActions().containsAll(Arrays.asList(Action.SUB, Action.UPDATE)));
Assert.assertEquals("User:rocketmq", getContext(result, ResourceType.TOPIC).getSubject().getSubjectKey());
Assert.assertEquals("Topic:topic", getContext(result, ResourceType.TOPIC).getResource().getResourceKey());
Assert.assertTrue(getContext(result, ResourceType.TOPIC).getActions().containsAll(Arrays.asList(Action.SUB, Action.UPDATE)));
CreateTopicRequestHeader createTopicRequestHeader = new CreateTopicRequestHeader();
createTopicRequestHeader.setTopic("topic");
createTopicRequestHeader.setTopicFilterType(TopicFilterType.SINGLE_TAG.name());
request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_TOPIC, createTopicRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
result = builder.build(channelHandlerContext, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
Assert.assertEquals("Topic:topic", result.get(0).getResource().getResourceKey());
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.CREATE)));
CreateUserRequestHeader createUserRequestHeader = new CreateUserRequestHeader();
createUserRequestHeader.setUsername("abc");
request = RemotingCommand.createRequestCommand(RequestCode.AUTH_CREATE_USER, createUserRequestHeader);
request.setVersion(441);
request.addExtField("AccessKey", "rocketmq");
request.makeCustomHeaderToNet();
result = builder.build(channelHandlerContext, request);
Assert.assertEquals(1, result.size());
Assert.assertEquals("User:rocketmq", result.get(0).getSubject().getSubjectKey());
Assert.assertEquals("Cluster:DefaultCluster", result.get(0).getResource().getResourceKey());
Assert.assertTrue(result.get(0).getActions().containsAll(Arrays.asList(Action.UPDATE)));
}
private DefaultAuthorizationContext getContext(List<DefaultAuthorizationContext> contexts,
ResourceType resourceType) {
return contexts.stream().filter(context -> context.getResource().getResourceType() == resourceType)
.findFirst().orElse(null);
}
private ChannelId mockChannelId(String channelId) {
return new ChannelId() {
@Override
public String asShortText() {
return channelId;
}
@Override
public String asLongText() {
return channelId;
}
@Override
public int compareTo(ChannelId o) {
return 0;
}
};
}
private Attribute<String> mockAttribute(String value) {
return new Attribute<String>() {
@Override
public AttributeKey<String> key() {
return null;
}
@Override
public String get() {
return value;
}
@Override
public void set(String value) {
}
@Override
public String getAndSet(String value) {
return null;
}
@Override
public String setIfAbsent(String value) {
return null;
}
@Override
public String getAndRemove() {
return null;
}
@Override
public boolean compareAndSet(String oldValue, String newValue) {
return false;
}
@Override
public void remove() {
}
};
}
}

View File

@@ -0,0 +1,262 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.manager;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory;
import org.apache.rocketmq.auth.authentication.manager.AuthenticationMetadataManager;
import org.apache.rocketmq.auth.authentication.model.Subject;
import org.apache.rocketmq.auth.authentication.model.User;
import org.apache.rocketmq.auth.authorization.enums.Decision;
import org.apache.rocketmq.auth.authorization.enums.PolicyType;
import org.apache.rocketmq.auth.authorization.exception.AuthorizationException;
import org.apache.rocketmq.auth.authorization.factory.AuthorizationFactory;
import org.apache.rocketmq.auth.authorization.model.Acl;
import org.apache.rocketmq.auth.authorization.model.Policy;
import org.apache.rocketmq.auth.authorization.model.Resource;
import org.apache.rocketmq.auth.config.AuthConfig;
import org.apache.rocketmq.auth.helper.AuthTestHelper;
import org.apache.rocketmq.common.MixAll;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class AuthorizationMetadataManagerTest {
private AuthConfig authConfig;
private AuthenticationMetadataManager authenticationMetadataManager;
private AuthorizationMetadataManager authorizationMetadataManager;
@Before
public void setUp() throws Exception {
if (MixAll.isMac()) {
return;
}
this.authConfig = AuthTestHelper.createDefaultConfig();
this.authenticationMetadataManager = AuthenticationFactory.getMetadataManager(this.authConfig);
this.authorizationMetadataManager = AuthorizationFactory.getMetadataManager(this.authConfig);
this.clearAllAcls();
this.clearAllUsers();
}
@After
public void tearDown() throws Exception {
if (MixAll.isMac()) {
return;
}
this.clearAllAcls();
this.clearAllUsers();
this.authenticationMetadataManager.shutdown();
this.authorizationMetadataManager.shutdown();
}
@Test
public void createAcl() {
if (MixAll.isMac()) {
return;
}
User user = User.of("test", "test");
this.authenticationMetadataManager.createUser(user).join();
Acl acl1 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test", "PUB,SUB",
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
this.authorizationMetadataManager.createAcl(acl1).join();
Acl acl2 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join();
Assert.assertTrue(AuthTestHelper.isEquals(acl1, acl2));
user = User.of("abc", "abc");
this.authenticationMetadataManager.createUser(user).join();
acl1 = AuthTestHelper.buildAcl("User:abc", PolicyType.DEFAULT, "Topic:*,Group:*", "PUB,SUB",
null, Decision.DENY);
this.authorizationMetadataManager.createAcl(acl1).join();
acl2 = this.authorizationMetadataManager.getAcl(Subject.of("User:abc")).join();
Assert.assertTrue(AuthTestHelper.isEquals(acl1, acl2));
Acl acl3 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test", "PUB,SUB",
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
this.authorizationMetadataManager.createAcl(acl3).join();
Acl acl4 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join();
Assert.assertTrue(AuthTestHelper.isEquals(acl3, acl4));
Assert.assertThrows(AuthorizationException.class, () -> {
try {
Acl acl5 = AuthTestHelper.buildAcl("User:ddd", "Topic:test,Group:test", "PUB,SUB",
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
this.authorizationMetadataManager.createAcl(acl5).join();
} catch (Exception e) {
AuthTestHelper.handleException(e);
}
});
}
@Test
public void updateAcl() {
if (MixAll.isMac()) {
return;
}
User user = User.of("test", "test");
this.authenticationMetadataManager.createUser(user).join();
Acl acl1 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test", "PUB,SUB",
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
this.authorizationMetadataManager.createAcl(acl1).join();
Acl acl2 = AuthTestHelper.buildAcl("User:test", "Topic:abc,Group:abc", "PUB,SUB",
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
this.authorizationMetadataManager.updateAcl(acl2).join();
Acl acl3 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test,Topic:abc,Group:abc", "PUB,SUB",
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
Acl acl4 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join();
Assert.assertTrue(AuthTestHelper.isEquals(acl3, acl4));
Policy policy = AuthTestHelper.buildPolicy("Topic:test,Group:test", "PUB,SUB,Create", "192.168.0.0/24", Decision.DENY);
acl4.updatePolicy(policy);
this.authorizationMetadataManager.updateAcl(acl4);
Acl acl5 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join();
Assert.assertTrue(AuthTestHelper.isEquals(acl4, acl5));
User user2 = User.of("abc", "abc");
this.authenticationMetadataManager.createUser(user2).join();
Acl acl6 = AuthTestHelper.buildAcl("User:abc", "Topic:test,Group:test", "PUB,SUB",
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
this.authorizationMetadataManager.updateAcl(acl6).join();
Acl acl7 = this.authorizationMetadataManager.getAcl(Subject.of("User:abc")).join();
Assert.assertTrue(AuthTestHelper.isEquals(acl6, acl7));
}
@Test
public void deleteAcl() {
if (MixAll.isMac()) {
return;
}
User user = User.of("test", "test");
this.authenticationMetadataManager.createUser(user).join();
Acl acl1 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test", "PUB,SUB",
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
this.authorizationMetadataManager.createAcl(acl1).join();
this.authorizationMetadataManager.deleteAcl(Subject.of("User:test"), PolicyType.CUSTOM, Resource.ofTopic("abc")).join();
Acl acl2 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join();
Assert.assertTrue(AuthTestHelper.isEquals(acl1, acl2));
this.authorizationMetadataManager.deleteAcl(Subject.of("User:test"), PolicyType.CUSTOM, Resource.ofTopic("test")).join();
Acl acl3 = AuthTestHelper.buildAcl("User:test", "Group:test", "PUB,SUB",
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
Acl acl4 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join();
Assert.assertTrue(AuthTestHelper.isEquals(acl3, acl4));
this.authorizationMetadataManager.deleteAcl(Subject.of("User:test"));
Acl acl5 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join();
Assert.assertNull(acl5);
Assert.assertThrows(AuthorizationException.class, () -> {
try {
this.authorizationMetadataManager.deleteAcl(Subject.of("User:abc")).join();
} catch (Exception e) {
AuthTestHelper.handleException(e);
}
});
}
@Test
public void getAcl() {
if (MixAll.isMac()) {
return;
}
User user = User.of("test", "test");
this.authenticationMetadataManager.createUser(user).join();
Acl acl1 = AuthTestHelper.buildAcl("User:test", "Topic:test,Group:test", "PUB,SUB",
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
this.authorizationMetadataManager.createAcl(acl1).join();
Acl acl2 = this.authorizationMetadataManager.getAcl(Subject.of("User:test")).join();
Assert.assertTrue(AuthTestHelper.isEquals(acl1, acl2));
Assert.assertThrows(AuthorizationException.class, () -> {
try {
this.authorizationMetadataManager.getAcl(Subject.of("User:abc")).join();
} catch (Exception e) {
AuthTestHelper.handleException(e);
}
});
}
@Test
public void listAcl() {
if (MixAll.isMac()) {
return;
}
User user1 = User.of("test-1", "test-1");
this.authenticationMetadataManager.createUser(user1).join();
User user2 = User.of("test-2", "test-2");
this.authenticationMetadataManager.createUser(user2).join();
Acl acl1 = AuthTestHelper.buildAcl("User:test-1", "Topic:test-1,Group:test-1", "PUB,SUB",
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
this.authorizationMetadataManager.createAcl(acl1).join();
Acl acl2 = AuthTestHelper.buildAcl("User:test-2", "Topic:test-2,Group:test-2", "PUB,SUB",
"192.168.0.0/24,10.10.0.0/24", Decision.ALLOW);
this.authorizationMetadataManager.createAcl(acl2).join();
List<Acl> acls1 = this.authorizationMetadataManager.listAcl(null, null).join();
Assert.assertEquals(acls1.size(), 2);
List<Acl> acls2 = this.authorizationMetadataManager.listAcl("User:test-1", null).join();
Assert.assertEquals(acls2.size(), 1);
List<Acl> acls3 = this.authorizationMetadataManager.listAcl("test", null).join();
Assert.assertEquals(acls3.size(), 2);
List<Acl> acls4 = this.authorizationMetadataManager.listAcl(null, "Topic:test-1").join();
Assert.assertEquals(acls4.size(), 1);
Assert.assertEquals(acls4.get(0).getPolicy(PolicyType.CUSTOM).getEntries().size(), 1);
List<Acl> acls5 = this.authorizationMetadataManager.listAcl(null, "test-1").join();
Assert.assertEquals(acls5.size(), 1);
Assert.assertEquals(acls4.get(0).getPolicy(PolicyType.CUSTOM).getEntries().size(), 1);
List<Acl> acls6 = this.authorizationMetadataManager.listAcl("User:abc", null).join();
Assert.assertTrue(CollectionUtils.isEmpty(acls6));
List<Acl> acls7 = this.authorizationMetadataManager.listAcl(null, "Topic:abc").join();
Assert.assertTrue(CollectionUtils.isEmpty(acls7));
}
private void clearAllUsers() {
List<User> users = this.authenticationMetadataManager.listUser(null).join();
if (CollectionUtils.isEmpty(users)) {
return;
}
users.forEach(user -> this.authenticationMetadataManager.deleteUser(user.getUsername()).join());
}
private void clearAllAcls() {
List<Acl> acls = this.authorizationMetadataManager.listAcl(null, null).join();
if (CollectionUtils.isEmpty(acls)) {
return;
}
acls.forEach(acl -> this.authorizationMetadataManager.deleteAcl(acl.getSubject(), null, null).join());
}
}

View File

@@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.auth.authorization.model;
import org.apache.rocketmq.common.resource.ResourcePattern;
import org.apache.rocketmq.common.resource.ResourceType;
import org.junit.Assert;
import org.junit.Test;
public class ResourceTest {
@Test
public void parseResource() {
Resource resource = Resource.of("*");
Assert.assertEquals(resource.getResourceType(), ResourceType.ANY);
Assert.assertNull(resource.getResourceName());
Assert.assertEquals(resource.getResourcePattern(), ResourcePattern.ANY);
resource = Resource.of("Topic:*");
Assert.assertEquals(resource.getResourceType(), ResourceType.TOPIC);
Assert.assertNull(resource.getResourceName());
Assert.assertEquals(resource.getResourcePattern(), ResourcePattern.ANY);
resource = Resource.of("Topic:test-*");
Assert.assertEquals(resource.getResourceType(), ResourceType.TOPIC);
Assert.assertEquals(resource.getResourceName(), "test-");
Assert.assertEquals(resource.getResourcePattern(), ResourcePattern.PREFIXED);
resource = Resource.of("Topic:test-1");
Assert.assertEquals(resource.getResourceType(), ResourceType.TOPIC);
Assert.assertEquals(resource.getResourceName(), "test-1");
Assert.assertEquals(resource.getResourcePattern(), ResourcePattern.LITERAL);
}
@Test
public void isMatch() {
}
}

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