Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ISSUE #2986] Support for multiple ACL files in a fixed directory #3761

Merged
merged 13 commits into from Feb 19, 2022

Conversation

sunxi92
Copy link
Contributor

@sunxi92 sunxi92 commented Jan 14, 2022

Make sure set the target branch to develop

What is the purpose of the change

Support for multiple ACL files in a fixed directory
#2986

Brief changelog

XX

Verifying this change

XXXX

Follow this checklist to help us incorporate your contribution quickly and easily. Notice, it would be helpful if you could finish the following 5 checklist(the last one is not necessary)before request the community to review your PR.

  • Make sure there is a Github issue filed for the change (usually before you start working on it). Trivial changes like typos do not require a Github issue. Your pull request should address just this issue, without pulling in other changes - one PR resolves one issue.
  • Format the pull request title like [ISSUE #123] Fix UnknownException when host config not exist. Each commit in the pull request should have a meaningful subject line and body.
  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
  • Write necessary unit-test(over 80% coverage) to verify your logic correction, more mock a little better when cross module dependency exist. If the new feature or significant change is committed, please remember to add integration-test in test module.
  • Run mvn -B clean apache-rat:check findbugs:findbugs checkstyle:checkstyle to make sure basic checks pass. Run mvn clean install -DskipITs to make sure unit-test pass. Run mvn clean test-compile failsafe:integration-test to make sure integration-test pass.
  • If this contribution is large, please file an Apache Individual Contributor License Agreement.

@coveralls
Copy link

coveralls commented Jan 14, 2022

Coverage Status

Coverage increased (+0.5%) to 51.399% when pulling 4dae014 on sunxi92:acl_update into 6dff04f on apache:develop.

@codecov-commenter
Copy link

codecov-commenter commented Jan 14, 2022

Codecov Report

Merging #3761 (a5691ab) into develop (6dff04f) will increase coverage by 0.52%.
The diff coverage is 63.15%.

Impacted file tree graph

@@              Coverage Diff              @@
##             develop    #3761      +/-   ##
=============================================
+ Coverage      46.85%   47.37%   +0.52%     
- Complexity      4835     4930      +95     
=============================================
  Files            636      636              
  Lines          42251    42650     +399     
  Branches        5523     5593      +70     
=============================================
+ Hits           19795    20204     +409     
+ Misses         19973    19926      -47     
- Partials        2483     2520      +37     
Impacted Files Coverage Δ
...ocketmq/broker/processor/AdminBrokerProcessor.java 34.34% <0.00%> (-0.04%) ⬇️
...mq/common/protocol/body/ClusterAclVersionInfo.java 0.00% <0.00%> (ø)
...tocol/header/GetBrokerAclConfigResponseHeader.java 0.00% <0.00%> (ø)
...and/acl/ClusterAclConfigVersionListSubCommand.java 20.40% <0.00%> (-1.82%) ⬇️
...g/apache/rocketmq/client/impl/MQClientAPIImpl.java 13.93% <16.66%> (-0.05%) ⬇️
...pache/rocketmq/acl/plain/PlainAccessValidator.java 89.70% <50.00%> (-2.61%) ⬇️
...che/rocketmq/acl/plain/PlainPermissionManager.java 70.89% <69.86%> (-0.98%) ⬇️
...tmq/broker/longpolling/PullRequestHoldService.java 12.04% <0.00%> (-8.44%) ⬇️
...ocketmq/store/schedule/ScheduleMessageService.java 66.75% <0.00%> (-7.09%) ⬇️
...ketmq/common/protocol/body/ConsumerConnection.java 95.83% <0.00%> (-4.17%) ⬇️
... and 44 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6dff04f...a5691ab. Read the comment docs.

Copy link
Contributor

@caigy caigy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this PR, there is a very important assumption: The ACL directory MUST contain and ONLY contain ACL config files without subdirectories, but I couldn't find any strong statements and checking about it.
It requires more considerations on compatibility to the original one ACL config file and scalability of supporting multiple config files in different directories.
At present, there are some incompatible modifications:

  • Return type of AccessValidator.getAclConfigVersion() changed from String to Map<String, DataVersion>, users who have implemented their own implementation would meet NoSuchMethod error.
  • PlainPermissionManager.getAclConfigDataVersion() is deleted.
  • Some modification breaks protocol, such as changing the type of version in GetBrokerAclConfigResponseHeader, removing aclConfigDataVersion in ClusterAclVersionInfo.
  • Users using DEFAULT_PLAIN_ACL_FILE (/conf/plain_acl.yml) can't load ACL config file after this change, because it will be loaded from /conf/acl directory. If they change the acl directory to /conf/, not only acl config files, but also other config files like broker config will be loaded, and the loading process will encounter errors.

@@ -60,16 +63,18 @@
*
* @return
*/
String getAclConfigVersion();
Map<String, DataVersion> getAclConfigVersion();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes the interface incompatible to older versions, you'd better add a new method and deprecate the original ones.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines -47 to -52
private static final String DEFAULT_PLAIN_ACL_FILE = "/conf/plain_acl.yml";

private String fileHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY,
System.getenv(MixAll.ROCKETMQ_HOME_ENV));

private String fileName = System.getProperty("rocketmq.acl.plain.file", DEFAULT_PLAIN_ACL_FILE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users using DEFAULT_PLAIN_ACL_FILE can't load ACL config file after this change, it requires more compatible considerations.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1.
This pr need to supply a detailed design.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems users can't specify config file by property rocketmq.acl.plain.file?

Comment on lines 77 to 79
if (fileHome == null || fileHome.isEmpty()) {
throw new AclException(String.format("%s file is empty", fileHome));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it more appropriate to just returning than throwing exception?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 80 to 81
File aclDir = new File(defaultAclDir);
File[] aclFiles = aclDir.listFiles();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array of File objects is created here but only path string is used(org.apache.rocketmq.acl.common.AclUtils#getYamlDataObject uses file name to read this file again), it will use less resource if using java.nio.file.Files#list, or add overloaded method for org.apache.rocketmq.acl.common.AclUtils#getYamlDataObject accepting file object.

List<String> fileList = new ArrayList<>();
for (File aclFile : aclFiles) {
String aclFileAbsolutePath = aclFile.getAbsolutePath();
load(aclFileAbsolutePath);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we surround load by try-catch?

public class GetBrokerAclConfigResponseHeader implements CommandCustomHeader {

@CFNotNull
private String version;
private Map<String, DataVersion> version;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It changes the protocol, some users may already use it as String.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

public class ClusterAclVersionInfo extends RemotingSerializable {

private String brokerName;

private String brokerAddr;

private DataVersion aclConfigDataVersion;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a new field other than changing it for compatibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

} else {
for (int i = 0; i < aclFilesNum; i++) {
String fileName = aclFiles[i].getAbsolutePath();
String newHash = hash(fileName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calculating hash of a file consumes a lot of system resources, can file modified time and file size be used instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

for (int i = 0; i < aclFilesNum; i++) {
String fileName = aclFiles[i].getAbsolutePath();
String newHash = hash(fileName);
if (!newHash.equals(fileCurrentHash.get(i))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be fileCurrentHash.get(fileName)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

private int aclFilesNum;
private final Map<String, String> fileCurrentHash;
private final AclFileWatchService.Listener listener;
private static final int WATCH_INTERVAL = 500;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it too often?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@dongeforever
Copy link
Member

The multi ACL files are not compatible with the default ACL file naturally.
If it is necessary to support multi ACL files, a detailed design with compatibility is needed.

@dongeforever
Copy link
Member

IMO.
The default access validator should keep simple. If it has difficulty supporting the requirements, it is better to supply a new validator implemntation, instead of changing the default one.

…le and scalability of supporting multiple config files in different directories.
@caigy
Copy link
Contributor

caigy commented Jan 25, 2022

@sunxi92 Could you post your detailed design so that more people can join this pr? BTW, it should be well documented about how to use this new feature and how it effects the old implementation, not only in comments but also in user guide.

versionNum,
timeStampStr
);
for (Map.Entry<String, DataVersion> entry : aclDataVersion.entrySet()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would AccessValidator.getAllAclConfigVersion() return an empty set?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment on lines +66 to 67
@Deprecated
String getAclConfigVersion();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pls add more description about:

  • the reason to deprecate String getAclConfigVersion() and replace it with Map<String, DataVersion> getAllAclConfigVersion()
  • What is the object or scope that version describe? In previous implementation, version is used to mark the whole config. In this multi-filed feature, each config file has its own version. And if ACL config are stored in DB, each record may have its own version. We should define version precisely.
  • In Map<String, DataVersion> getAllAclConfigVersion(), what is the key of Map? Users may curious of it. If this is the full path of config files, it brings implementation to interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment on lines -47 to -52
private static final String DEFAULT_PLAIN_ACL_FILE = "/conf/plain_acl.yml";

private String fileHome = System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY,
System.getenv(MixAll.ROCKETMQ_HOME_ENV));

private String fileName = System.getProperty("rocketmq.acl.plain.file", DEFAULT_PLAIN_ACL_FILE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems users can't specify config file by property rocketmq.acl.plain.file?


private Map<String/** AccessKey **/, PlainAccessResource> plainAccessResourceMap = new HashMap<>();
private Map<String/** aclFileName **/, Map<String/** AccessKey **/, PlainAccessResource>> aclPlainAccessResourceMap = new HashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 'file full path' more precise than 'aclFileName'?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

File f = new File(fileName);
if (fileName.endsWith(".yml")) {
fileList.add(fileName);
} else if (!f.isFile()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it should be file.isDirectory()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment on lines 161 to 163
this.globalWhiteRemoteAddressStrategy.remove(remoteAddressStrategyList.get(i));
}
this.globalWhiteRemoteAddressStrategyMap.remove(aclFilePath);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing items in globalWhiteRemoteAddressStrategy may cause ACL check failures.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment on lines 133 to 137
if (dataVersionMap.size() == 1) {
for (Map.Entry<String, DataVersion> entry : dataVersionMap.entrySet()) {
dataVersion.assignNewOne(entry.getValue());
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would dataVersion be empty or updated if there are multiple ACL config files?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment on lines 111 to 120
if (aclPlainAccessResourceMap.size() != 0 && accessKeyTable.size() != 0) {
aclPlainAccessResourceMap.clear();
accessKeyTable.clear();
}
if (globalWhiteRemoteAddressStrategy.size() != 0) {
globalWhiteRemoteAddressStrategy.clear();
}
if (globalWhiteRemoteAddressStrategyMap.size() != 0) {
globalWhiteRemoteAddressStrategyMap.clear();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearing globalWhiteRemoteAddressStrategy or globalWhiteRemoteAddressStrategyMap may cause ACL check failures before load() finished.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment on lines +422 to +424
globalWhiteRemoteAddrList.clear();
if (globalWhiteAddrsList != null) {
globalWhiteRemoteAddrList.addAll(globalWhiteAddrsList);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Combination of clear() and addAll() is not atomic, which may cause ACL failure in this process.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Comment on lines 29 to 31
private String version;

private Map<String, DataVersion> versions;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

version and versions are too similar and confusing, maybe we should name them more clearly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.


public class PlainPermissionManager {

private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.COMMON_LOGGER_NAME);

private static final String DEFAULT_PLAIN_ACL_FILE = "/conf/plain_acl.yml";

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove old acl file directory incompatible with old version. Can old acl file and new acl dir coexist?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default acl file is added.

public List<String> getAllAclFiles(String path) {
List<String> allAclFileFullPath = new ArrayList<>();
File file = new File(path);
File[] files = file.listFiles();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, check file is directory will be better

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


public AclFileWatchService(String path, final AclFileWatchService.Listener listener) throws Exception {
this.aclPath = path;
this.defaultAclFile = path.substring(0, path.length() - 4) + System.getProperty("rocketmq.acl.plain.file", "conf/plain_acl.yml");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just get parent directory instead of string truncation, or make path as a List.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

2.Add the logic to check if path is a directory in the method of getAllAclFiles(String path)
…th) method in PlainPermissionManager.java and AclFileWatchService.java
…AclConfigVersion command

2.Improve the logic of updateAccessConfig method
@@ -387,6 +388,12 @@ public ClusterAclVersionInfo getBrokerClusterAclInfo(final String addr,
clusterAclVersionInfo.setBrokerName(responseHeader.getBrokerName());
clusterAclVersionInfo.setBrokerAddr(responseHeader.getBrokerAddr());
clusterAclVersionInfo.setAclConfigDataVersion(DataVersion.fromJson(responseHeader.getVersion(), DataVersion.class));
HashMap<String, Object> dataVersionMap = JSON.parseObject(responseHeader.getAllAclFileVersion(), HashMap.class);
Map<String, DataVersion> allAclConfigDataVersion = new HashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, specify the concrete type for HashMap for java 1.6.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@duhenglucky duhenglucky merged commit c1bb6aa into apache:develop Feb 19, 2022
devfat pushed a commit to devfat/rocketmq that referenced this pull request Mar 3, 2022
…ry (apache#3761)

* acl temp

* acl

* fix test case

* fix code style issues

* add considerations on compatibility to the original one ACL config file and scalability of supporting multiple config files in different directories.

* fix test case testWatch

* 1.fix some issues
2.add a detailed design document

* Add warn log when the accesskey is repeated in multiple ACL files.

* 1.Change the folder of acl configuration to conf/acl
2.Add the logic to check if path is a directory in the method of getAllAclFiles(String path)

* Add a parameter in AclFileWatchService constructor.

* Add logic to determine if path exists in the getAllAclFiles(String path) method in PlainPermissionManager.java and AclFileWatchService.java

* Fix the serialization problem of allAclFileVersion field in clusterAclConfigVersion command

* 1.Fix the serialization problem of allAclFileVersion field in clusterAclConfigVersion command
2.Improve the logic of updateAccessConfig method
GenerousMan pushed a commit to GenerousMan/rocketmq that referenced this pull request Aug 12, 2022
…ry (apache#3761)

* acl temp

* acl

* fix test case

* fix code style issues

* add considerations on compatibility to the original one ACL config file and scalability of supporting multiple config files in different directories.

* fix test case testWatch

* 1.fix some issues
2.add a detailed design document

* Add warn log when the accesskey is repeated in multiple ACL files.

* 1.Change the folder of acl configuration to conf/acl
2.Add the logic to check if path is a directory in the method of getAllAclFiles(String path)

* Add a parameter in AclFileWatchService constructor.

* Add logic to determine if path exists in the getAllAclFiles(String path) method in PlainPermissionManager.java and AclFileWatchService.java

* Fix the serialization problem of allAclFileVersion field in clusterAclConfigVersion command

* 1.Fix the serialization problem of allAclFileVersion field in clusterAclConfigVersion command
2.Improve the logic of updateAccessConfig method
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants