diff --git a/docs/en/deployment/hadoop_java_sdk.md b/docs/en/deployment/hadoop_java_sdk.md
index 539babd234ae..8742cde907e7 100644
--- a/docs/en/deployment/hadoop_java_sdk.md
+++ b/docs/en/deployment/hadoop_java_sdk.md
@@ -751,7 +751,6 @@ JuiceFS currently supports path permission control by integrating with Apache Ra
|-----------------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `juicefs.ranger-rest-url` | | `ranger`'s HTTP link url. Not configured means not using this feature. |
| `juicefs.ranger-service-name` | | `ranger`'s `service name` in `HDFS` module, required |
-| `juicefs.ranger-cache-dir` | | `ranger`'s policies cache path. By default, a `UUID` path hierarchy is added under the environment variable `java.io.tmpdir` to prevent multitasking from interfering with each other. After configuring a fixed directory, multiple tasks will share the cache, and only one JuiceFS is responsible for cache refreshing, to reduce the pressure on connecting to `Ranger Admin`. |
| `juicefs.ranger-poll-interval-ms` | `30000` | `ranger`'s interval to refresh cache, default is 30s |
### 2. Dependencies
diff --git a/docs/zh_cn/deployment/hadoop_java_sdk.md b/docs/zh_cn/deployment/hadoop_java_sdk.md
index f3c76b121b4e..9400ba952e87 100644
--- a/docs/zh_cn/deployment/hadoop_java_sdk.md
+++ b/docs/zh_cn/deployment/hadoop_java_sdk.md
@@ -876,7 +876,6 @@ JuiceFS 当前支持对接 Apache Ranger 的 `HDFS` 模块进行路径的权限
|-----------------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------|
| `juicefs.ranger-rest-url` | | `ranger`连接地址。不配置该参数即不使用该功能。 |
| `juicefs.ranger-service-name` | | `ranger`中配置的`service name`,必填 |
-| `juicefs.ranger-cache-dir` | | `ranger`策略的缓存路径。默认在环境变量`java.io.tmpdir`下,添加`UUID`路径层级防止多任务相互影响。当配置固定目录后,多个任务会共享缓存,有且仅有一个JuiceFS对象负责缓存刷新,减少对连接`Ranger Admin`压力。 |
| `juicefs.ranger-poll-interval-ms` | `30000` | `ranger`缓存刷新周期,默认30s |
### 2. 环境及依赖
diff --git a/sdk/java/libjfs/main.go b/sdk/java/libjfs/main.go
index c1dad91ae24a..cb1854b778db 100644
--- a/sdk/java/libjfs/main.go
+++ b/sdk/java/libjfs/main.go
@@ -695,6 +695,20 @@ func jfs_update_uid_grouping(cname, uidstr *C.char, grouping *C.char) {
}
}
+//export jfs_getGroups
+func jfs_getGroups(name, user string) string {
+ fslock.Lock()
+ defer fslock.Unlock()
+ userGroups := userGroupCache[name]
+ if userGroups != nil {
+ gs := userGroups[user]
+ if gs != nil {
+ return strings.Join(gs, ",")
+ }
+ }
+ return ""
+}
+
//export jfs_term
func jfs_term(pid int, h int64) int {
w := F(h)
diff --git a/sdk/java/pom.xml b/sdk/java/pom.xml
index dd251c07ad90..3f9b4c715274 100644
--- a/sdk/java/pom.xml
+++ b/sdk/java/pom.xml
@@ -82,6 +82,14 @@
com.google.common
io.juicefs.shaded.com.google.common
+
+ org.apache.commons.lang
+ io.juicefs.shaded.org.apache.commons.lang
+
+
+ com.kstruct.gethostname4j
+ io.juicefs.shaded.com.kstruct.gethostname4j
+
@@ -350,6 +358,11 @@
+
+ commons-lang
+ commons-lang
+ 2.6
+
org.apache.ranger
ranger-plugins-common
@@ -361,6 +374,21 @@
+
+ com.kstruct
+ gethostname4j
+ 0.0.2
+
+
+ com.sun.jersey
+ jersey-bundle
+ 1.19.3
+
+
+ org.codehaus.jackson
+ jackson-jaxrs
+ 1.9.13
+
org.apache.ranger
ranger-plugins-audit
@@ -372,11 +400,6 @@
-
- org.apache.httpcomponents
- httpclient
- 4.5.13
-
diff --git a/sdk/java/src/main/java/io/juicefs/JuiceFileSystemImpl.java b/sdk/java/src/main/java/io/juicefs/JuiceFileSystemImpl.java
index fc590febf602..9f501677a3ba 100644
--- a/sdk/java/src/main/java/io/juicefs/JuiceFileSystemImpl.java
+++ b/sdk/java/src/main/java/io/juicefs/JuiceFileSystemImpl.java
@@ -93,8 +93,6 @@ static String loadVersion() {
private Path workingDir;
private String name;
private String user;
- private String group;
- private Set groups;
private String superuser;
private String supergroup;
private URI uri;
@@ -102,8 +100,9 @@ static String loadVersion() {
private int minBufferSize;
private int cacheReplica;
private boolean fileChecksumEnabled;
- private static boolean permissionCheckEnabled = false;
private final boolean isSuperGroupFileSystem;
+ private boolean isBackGroundTask = false;
+
private JuiceFileSystemImpl superGroupFileSystem;
private RangerPermissionChecker rangerPermissionChecker;
private static Libjfs lib = loadLibrary();
@@ -207,6 +206,8 @@ public static interface Libjfs {
int jfs_setfacl(long pid, long h, String path, int acltype, Pointer b, int len);
+ String jfs_getGroups(String volName, String user);
+
void jfs_set_callback(LogCallBack callBack);
interface LogCallBack {
@@ -369,22 +370,18 @@ public void initialize(URI uri, Configuration conf) throws IOException {
minBufferSize = conf.getInt("juicefs.min-buffer-size", 128 << 10);
cacheReplica = Integer.parseInt(getConf(conf, "cache-replica", "1"));
fileChecksumEnabled = Boolean.parseBoolean(getConf(conf, "file.checksum", "false"));
- permissionCheckEnabled = getConf(conf, "ranger-rest-url", null) != null;
this.ugi = UserGroupInformation.getCurrentUser();
user = ugi.getShortUserName();
- group = "nogroup";
- String groupingFile = getConf(conf, "groups", null);
- if (isEmpty(groupingFile) && ugi.getGroupNames().length > 0) {
- group = String.join(",", ugi.getGroupNames());
+ String groupStr = "nogroup";
+ if (ugi.getGroupNames().length > 0) {
+ groupStr = String.join(",", ugi.getGroupNames());
}
- groups = Arrays.stream(group.split(",")).collect(Collectors.toSet());
superuser = getConf(conf, "superuser", "hdfs");
supergroup = getConf(conf, "supergroup", conf.get("dfs.permissions.superusergroup", "supergroup"));
- if (permissionCheckEnabled && isSuperGroupFileSystem) {
- group = supergroup;
- groups.clear();
- groups.add(supergroup);
+ isBackGroundTask = conf.getBoolean("juicefs.internal-bg-task", false);
+ if (isSuperGroupFileSystem || isBackGroundTask) {
+ groupStr = supergroup;
}
synchronized (JuiceFileSystemImpl.class) {
@@ -445,12 +442,11 @@ public void initialize(URI uri, Configuration conf) throws IOException {
obj.put("freeSpace", getConf(conf, "free-space", "0.1"));
obj.put("accessLog", getConf(conf, "access-log", ""));
String jsonConf = obj.toString(2);
- handle = lib.jfs_init(name, jsonConf, user, group, superuser, supergroup);
+ handle = lib.jfs_init(name, jsonConf, user, groupStr, superuser, supergroup);
if (handle <= 0) {
throw new IOException("JuiceFS initialized failed for jfs://" + name);
}
- boolean asBgTask = conf.getBoolean("juicefs.internal-bg-task", false);
- if (asBgTask) {
+ if (isBackGroundTask) {
LOG.debug("background fs {}|({})", name, handle);
} else {
BgTaskUtil.register(name, handle);
@@ -500,36 +496,30 @@ public void initialize(URI uri, Configuration conf) throws IOException {
JuiceFSInstrumentation.init(this, statistics);
}
- if (permissionCheckEnabled) {
- try {
- if (!isSuperGroupFileSystem) {
- RangerConfig rangerConfig = checkAndGetRangerParams(conf);
- Configuration superConf = new Configuration(conf);
- superGroupFileSystem = new JuiceFileSystemImpl(true);
- superGroupFileSystem.initialize(uri, superConf);
- rangerPermissionChecker = new RangerPermissionChecker(superGroupFileSystem, rangerConfig, user, group);
- }
- } catch (Exception e) {
- if (rangerPermissionChecker != null) {
- rangerPermissionChecker.cleanUp();
- }
- throw new RuntimeException("The initialization of the Permission Checker has failed. ", e);
- }
+
+ String rangerRestUrl = getConf(conf, "ranger-rest-url", null);
+ if (!isEmpty(rangerRestUrl) && !isSuperGroupFileSystem && !isBackGroundTask) {
+ RangerConfig rangerConfig = checkAndGetRangerParams(rangerRestUrl, conf);
+ Configuration superConf = new Configuration(conf);
+ superConf.set("juicefs.internal-bg-task", "true");
+ superGroupFileSystem = new JuiceFileSystemImpl(true);
+ superGroupFileSystem.initialize(uri, superConf);
+ rangerPermissionChecker = RangerPermissionChecker.acquire(name, handle, superGroupFileSystem, rangerConfig);
}
- if (!asBgTask && !isSuperGroupFileSystem) {
+ if (!isBackGroundTask && !isSuperGroupFileSystem) {
// use juicefs.users and juicefs.groups for global mapping
String uidFile = getConf(conf, "users", null);
- if (!isEmpty(uidFile) || !isEmpty(groupingFile)) {
+ String groupFile = getConf(conf, "groups", null);
+ if (!isEmpty(uidFile) || !isEmpty(groupFile)) {
BgTaskUtil.putTask(name, "Refresh guid", () -> {
- updateUidAndGrouping(uidFile, groupingFile);
+ updateUidAndGrouping(uidFile, groupFile);
}, 1, 1, TimeUnit.MINUTES);
}
}
}
- private RangerConfig checkAndGetRangerParams(Configuration conf) throws RuntimeException, IOException {
- String rangerRestUrl = getConf(conf, "ranger-rest-url", "");
+ private RangerConfig checkAndGetRangerParams(String rangerRestUrl, Configuration conf) throws IOException {
if (!rangerRestUrl.startsWith("http")) {
throw new IOException("illegal value for parameter 'juicefs.ranger-rest-url': " + rangerRestUrl);
}
@@ -539,38 +529,52 @@ private RangerConfig checkAndGetRangerParams(Configuration conf) throws RuntimeE
throw new IOException("illegal value for parameter 'juicefs.ranger-service-name': " + serviceName);
}
- String cacheDir = getConf(conf, "ranger-cache-dir", System.getProperty("java.io.tmpdir") + "/" + UUID.randomUUID());
String pollIntervalMs = getConf(conf, "ranger-poll-interval-ms", "30000");
- return new RangerConfig(rangerRestUrl, serviceName, cacheDir, pollIntervalMs);
+ return new RangerConfig(rangerRestUrl, serviceName, Long.parseLong(pollIntervalMs));
}
private JuiceFileSystemImpl(boolean isSuperGroupFileSystem) {
this.isSuperGroupFileSystem = isSuperGroupFileSystem;
}
+ private Set getGroups() {
+ String groupsFile = getConf(getConf(), "groups", null);
+ if (isEmpty(groupsFile)) {
+ return new HashSet<>(ugi.getGroups());
+ }
+ String gStr = lib.jfs_getGroups(name, user);
+ Set res;
+ if (!isEmpty(gStr)) {
+ res = new HashSet<>(Arrays.asList(gStr.split(","))) ;
+ } else {
+ res = new HashSet<>(ugi.getGroups());
+ }
+ return res;
+ }
+
private boolean hasSuperPermission() {
- return user.equals(superuser) || groups.contains(supergroup);
+ return user.equals(superuser) || getGroups().contains(supergroup);
}
private boolean needCheckPermission() {
- return permissionCheckEnabled && !hasSuperPermission();
+ return rangerPermissionChecker != null && !isSuperGroupFileSystem && !isBackGroundTask && !hasSuperPermission() ;
}
private boolean checkPathAccess(Path path, FsAction action, String operation) throws IOException {
- return rangerPermissionChecker.checkPermission(path, false, null, null, action, operation);
+ return rangerPermissionChecker.checkPermission(path, false, null, null, action, operation, user, getGroups());
}
private boolean checkParentPathAccess(Path path, FsAction action, String operation) throws IOException {
- return rangerPermissionChecker.checkPermission(path, false, null, action, null, operation);
+ return rangerPermissionChecker.checkPermission(path, false, null, action, null, operation, user, getGroups());
}
private boolean checkAncestorAccess(Path path, FsAction action, String operation) throws IOException {
- return rangerPermissionChecker.checkPermission(path, false, action, null, null, operation);
+ return rangerPermissionChecker.checkPermission(path, false, action, null, null, operation, user, getGroups());
}
private boolean checkOwner(Path path, String operation) throws IOException {
- return rangerPermissionChecker.checkPermission(path, true, null, null, null, operation);
+ return rangerPermissionChecker.checkPermission(path, true, null, null, null, operation, user, getGroups());
}
private boolean isEmpty(String str) {
@@ -623,7 +627,6 @@ private void updateUidAndGrouping(String uidFile, String groupFile) throws IOExc
}
lib.jfs_update_uid_grouping(name, uidstr, grouping);
- groups = Arrays.stream(group.split(",")).collect(Collectors.toSet());
}
private void initializeStorageIds(Configuration conf) throws IOException {
@@ -1858,6 +1861,7 @@ public void setTimes(Path p, long mtime, long atime) throws IOException {
@Override
public void close() throws IOException {
super.close();
+ RangerPermissionChecker.release(name, handle);
BgTaskUtil.unregister(name, handle, () -> {
cachedHostsForName.clear();
hashForName.clear();
@@ -1868,9 +1872,6 @@ public void close() throws IOException {
if (metricsEnable) {
JuiceFSInstrumentation.close();
}
- if (rangerPermissionChecker != null) {
- rangerPermissionChecker.cleanUp();
- }
}
@Override
diff --git a/sdk/java/src/main/java/io/juicefs/permission/LockFileChecker.java b/sdk/java/src/main/java/io/juicefs/permission/LockFileChecker.java
deleted file mode 100644
index 055eb73751b7..000000000000
--- a/sdk/java/src/main/java/io/juicefs/permission/LockFileChecker.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * JuiceFS, Copyright 2024 Juicedata, Inc.
- *
- * 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.
- */
-
-package io.juicefs.permission;
-
-import java.io.File;
-import java.io.IOException;
-
-public class LockFileChecker {
-
- public static boolean checkAndCreateLockFile(String directoryPath) {
- File directory = new File(directoryPath);
-
- if (!directory.exists()) {
- directory.mkdirs();
- }
-
- File lockFile = new File(directory, ".lock");
-
- if (lockFile.exists()) {
- return false;
- } else {
- try {
- lockFile.createNewFile();
- return true;
- } catch (IOException e) {
- throw new RuntimeException("ranger policies cache dir cannot created. ", e);
- }
- }
- }
-
- public static void cleanUp(String directoryPath) {
- File directory = new File(directoryPath + ".lock");
- directory.deleteOnExit();
- }
-
-}
diff --git a/sdk/java/src/main/java/io/juicefs/permission/RangerAdminRefresher.java b/sdk/java/src/main/java/io/juicefs/permission/RangerAdminRefresher.java
new file mode 100644
index 000000000000..0595b06eb573
--- /dev/null
+++ b/sdk/java/src/main/java/io/juicefs/permission/RangerAdminRefresher.java
@@ -0,0 +1,254 @@
+/*
+ * JuiceFS, Copyright 2024 Juicedata, Inc.
+ *
+ * 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.
+ */
+package io.juicefs.permission;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import org.apache.hadoop.fs.*;
+import org.apache.ranger.admin.client.RangerAdminClient;
+import org.apache.ranger.plugin.contextenricher.RangerTagEnricher;
+import org.apache.ranger.plugin.service.RangerBasePlugin;
+import org.apache.ranger.plugin.util.RangerRoles;
+import org.apache.ranger.plugin.util.RangerServiceNotFoundException;
+import org.apache.ranger.plugin.util.ServicePolicies;
+import org.apache.ranger.plugin.util.ServiceTags;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+
+public class RangerAdminRefresher {
+ private static final Logger LOG = LoggerFactory.getLogger(RangerAdminRefresher.class);
+
+ private static final String JFS_RANGER_DIR = "/.sys/ranger";
+
+ private RangerBasePlugin plugIn;
+ private Path rangerDir;
+ private Path rangerRulePath;
+ private long lastMtime;
+ private final long pollingIntervalMs;
+
+ private final RangerAdminClient rangerAdmin;
+ private final Gson gson = new GsonBuilder().setDateFormat("yyyyMMdd-HH:mm:ss.SSS-Z").create();
+ private long lastKnownPolicyVersion = -1L;
+ private long lastPolicyActivationTimeInMillis;
+ private long lastKnownRoleVersion = -1L;
+ private long lastRoleActivationTimeInMillis;
+ private long lastKnownTagVersion = -1L;
+ private long lastTagActivationTimeInMillis;
+
+ private final FileSystem fs;
+ private final ScheduledExecutorService refreshThread;
+
+ public RangerAdminRefresher(RangerBasePlugin plugIn, RangerAdminClient rangerAdmin, FileSystem fs, String rangerUrl, long pollingIntervalMs) {
+
+ this.plugIn = plugIn;
+ this.rangerAdmin = rangerAdmin;
+ this.fs = fs;
+ String serviceName = plugIn.getServiceName();
+ URI uri = URI.create(rangerUrl);
+ String rangerDirName = uri.getHost().replace(".", "_") + "_" + uri.getPort() + "_" + serviceName;
+ this.rangerDir = new Path(JFS_RANGER_DIR, rangerDirName);
+ this.rangerRulePath = new Path(rangerDir, "rules");
+ this.refreshThread = Executors.newScheduledThreadPool(1, r -> {
+ Thread t = new Thread(r, "JuiceFS Ranger Refresher");
+ t.setDaemon(true);
+ return t;
+ });
+ this.pollingIntervalMs = pollingIntervalMs;
+ }
+
+ public void start() {
+ loadRangerItem();
+ refreshThread.scheduleAtFixedRate(this::loadRangerItem, pollingIntervalMs, pollingIntervalMs, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * 1. read rules from jfs
+ * 2. choose one client to check ranger admin, if updated, download and save rules to jfs
+ */
+ public void loadRangerItem() {
+ RangerRules rangerRules = null;
+ // try to load rules from jfs
+ try {
+ rangerRules = loadRangerRules();
+ } catch (IOException e) {
+ LOG.debug("Load ranger rules failed", e);
+ }
+
+ if (rangerRules != null) {
+ if (updateRules(rangerRules.getPolicies(), rangerRules.getTags(), rangerRules.getRoles())) {
+ LOG.info("Ranger rules has been updated, use new rules from juicefs");
+ }
+ }
+
+ boolean checkUpdate = checkUpdate(pollingIntervalMs);
+ // load rules from ranger admin
+ if (rangerRules == null || checkUpdate) {
+ ServicePolicies policiesFromRanger = null;
+ ServiceTags tagsFromRanger = null;
+ RangerRoles rolesFromRanger = null;
+ try {
+ policiesFromRanger = rangerAdmin.getServicePoliciesIfUpdated(lastKnownPolicyVersion, lastPolicyActivationTimeInMillis);
+ tagsFromRanger = rangerAdmin.getServiceTagsIfUpdated(lastKnownTagVersion, lastTagActivationTimeInMillis);
+ rolesFromRanger = rangerAdmin.getRolesIfUpdated(lastKnownRoleVersion, lastRoleActivationTimeInMillis);
+ } catch (RangerServiceNotFoundException e) {
+ LOG.warn("Ranger service not found", e);
+ } catch (Exception e) {
+ LOG.warn("Load policies from ranger failed", e);
+ }
+ if (updateRules(policiesFromRanger, tagsFromRanger, rolesFromRanger)) {
+ if (checkUpdate) {
+ try {
+ ServicePolicies p = rangerRules != null ? rangerRules.getPolicies() : null;
+ ServiceTags t = rangerRules != null ? rangerRules.getTags() : null;
+ RangerRoles r = rangerRules != null ? rangerRules.getRoles() : null;
+ if (policiesFromRanger != null) {
+ LOG.info("ServicePolicies updated from Ranger Admin");
+ p = policiesFromRanger;
+ }
+ if (tagsFromRanger != null) {
+ LOG.info("ServiceTags updated from Ranger Admin");
+ t = tagsFromRanger;
+ }
+ if (rolesFromRanger != null) {
+ LOG.info("RangerRoles updated from Ranger Admin");
+ r = rolesFromRanger;
+ }
+ saveRangerRules(new RangerRules(p, t, r));
+ } catch (IOException e) {
+ LOG.warn("Save rules to juicefs failed", e);
+ }
+ }
+ }
+ }
+ }
+
+ private boolean checkUpdate(long pollingIntervalMs) {
+ try {
+ boolean exists = fs.exists(rangerDir);
+ if (!exists) {
+ fs.mkdirs(rangerDir);
+ }
+ FileStatus[] lockFiles = fs.listStatus(rangerDir, path -> {
+ String name = path.getName();
+ return name.endsWith(".lock");
+ });
+ String prefix = String.valueOf((System.currentTimeMillis() / pollingIntervalMs) * pollingIntervalMs);
+ Path lockPath = new Path(rangerDir, prefix + ".lock");
+ if (lockFiles == null || lockFiles.length == 0) {
+ try (FSDataOutputStream ignore = fs.create(lockPath, false)) {
+ return true;
+ }
+ } else {
+ if (lockFiles.length > 1) {
+ Arrays.sort(lockFiles, Comparator.comparing(o -> o.getPath().getName()));
+ }
+ if (lockFiles[lockFiles.length - 1].getPath().getName().compareTo(lockPath.getName()) >= 0) {
+ return false;
+ }
+ try (FSDataOutputStream ignore = fs.create(lockPath, false)) {
+ for (FileStatus lockFile : lockFiles) {
+ fs.delete(lockFile.getPath(), false);
+ }
+ return true;
+ }
+ }
+ } catch (FileAlreadyExistsException ignored) {
+ return false;
+ }
+ catch (IOException e) {
+ LOG.warn("Check update failed", e);
+ return false;
+ }
+ }
+
+ private void saveRangerRules(RangerRules rules) throws IOException {
+ String rulesJson = gson.toJson(rules, RangerRules.class);
+ byte[] bytes = rulesJson.getBytes();
+ try (FSDataOutputStream out = fs.create(rangerRulePath)) {
+ out.write(bytes);
+ } catch (FileNotFoundException e) {
+ fs.mkdirs(rangerRulePath.getParent());
+ try (FSDataOutputStream out = fs.create(rangerRulePath)) {
+ out.write(bytes);
+ }
+ }
+ }
+
+ private RangerRules loadRangerRules() throws IOException {
+ FileStatus fileStatus = fs.getFileStatus(rangerRulePath);
+ long mtime = fileStatus.getModificationTime();
+ if (lastMtime == mtime) {
+ return null;
+ }
+ try (FSDataInputStream in = fs.open(rangerRulePath)) {
+ byte[] bytes = new byte[(int) fileStatus.getLen()];
+ in.readFully(bytes);
+ String rulesJson = new String(bytes);
+ RangerRules rangerRules = gson.fromJson(rulesJson, RangerRules.class);
+ lastMtime = mtime;
+ return rangerRules;
+ }
+ }
+
+ private boolean updateRules(ServicePolicies newSvcPolicies, ServiceTags newTags, RangerRoles newRangerRoles) {
+ boolean updated = false;
+ if (newSvcPolicies != null) {
+ long policyVersion = newSvcPolicies.getPolicyVersion() == null ? -1 : newSvcPolicies.getPolicyVersion();
+ if (lastKnownPolicyVersion != policyVersion) {
+ plugIn.setPolicies(newSvcPolicies);
+ lastKnownPolicyVersion = policyVersion;
+ lastPolicyActivationTimeInMillis = System.currentTimeMillis();
+ updated = true;
+ }
+ }
+ if (newTags != null) {
+ long tagVersion = newTags.getTagVersion() == null ? -1 : newTags.getTagVersion();
+ if (lastKnownTagVersion != tagVersion) {
+ RangerTagEnricher tagEnricher = plugIn.getTagEnricher();
+ if (tagEnricher != null) {
+ tagEnricher.setServiceTags(newTags);
+ }
+ lastKnownTagVersion = tagVersion;
+ lastTagActivationTimeInMillis = System.currentTimeMillis();
+ updated = true;
+ }
+ }
+ if (newRangerRoles != null) {
+ long roleVersion = newRangerRoles.getRoleVersion() == null ? -1 : newRangerRoles.getRoleVersion();
+ if (lastKnownRoleVersion != roleVersion) {
+ plugIn.setRoles(newRangerRoles);
+ lastKnownRoleVersion = roleVersion;
+ lastRoleActivationTimeInMillis = System.currentTimeMillis();
+ updated = true;
+ }
+ }
+ return updated;
+ }
+
+ public void stop() {
+ refreshThread.shutdownNow();
+ }
+}
diff --git a/sdk/java/src/main/java/io/juicefs/permission/RangerConfig.java b/sdk/java/src/main/java/io/juicefs/permission/RangerConfig.java
index 4259047953e9..f13573a5812d 100644
--- a/sdk/java/src/main/java/io/juicefs/permission/RangerConfig.java
+++ b/sdk/java/src/main/java/io/juicefs/permission/RangerConfig.java
@@ -18,21 +18,17 @@
public class RangerConfig {
- public RangerConfig(String rangerRestUrl, String serviceName, String cacheDir, String pollIntervalMs) {
+ public RangerConfig(String rangerRestUrl, String serviceName, long pollIntervalMs) {
this.rangerRestUrl = rangerRestUrl;
this.serviceName = serviceName;
this.pollIntervalMs = pollIntervalMs;
- this.cacheDir = cacheDir;
}
private String rangerRestUrl;
private String serviceName;
- private String pollIntervalMs = "30000";
-
- private String cacheDir;
-
+ private long pollIntervalMs;
public String getRangerRestUrl() {
return rangerRestUrl;
@@ -50,20 +46,11 @@ public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
-
- public String getCacheDir() {
- return cacheDir;
- }
-
- public void setCacheDir(String cacheDir) {
- this.cacheDir = cacheDir;
- }
-
- public String getPollIntervalMs() {
+ public long getPollIntervalMs() {
return pollIntervalMs;
}
- public void setPollIntervalMs(String pollIntervalMs) {
+ public void setPollIntervalMs(long pollIntervalMs) {
this.pollIntervalMs = pollIntervalMs;
}
diff --git a/sdk/java/src/main/java/io/juicefs/permission/RangerJfsPlugin.java b/sdk/java/src/main/java/io/juicefs/permission/RangerJfsPlugin.java
new file mode 100644
index 000000000000..d23c7b7ac4b2
--- /dev/null
+++ b/sdk/java/src/main/java/io/juicefs/permission/RangerJfsPlugin.java
@@ -0,0 +1,72 @@
+/*
+ * JuiceFS, Copyright 2024 Juicedata, Inc.
+ *
+ * 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.
+ */
+package io.juicefs.permission;
+
+import io.juicefs.utils.ReflectionUtil;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.ranger.admin.client.RangerAdminClient;
+import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
+import org.apache.ranger.plugin.service.RangerBasePlugin;
+import org.apache.ranger.plugin.service.RangerChainedPlugin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+public class RangerJfsPlugin extends RangerBasePlugin {
+ private static final Logger LOG = LoggerFactory.getLogger(RangerJfsPlugin.class);
+
+ private FileSystem fs;
+ private String rangerUrl;
+ private RangerAdminRefresher refresher;
+ private long pollingIntervalMs;
+
+ public RangerJfsPlugin(FileSystem fs, String serviceName, String rangerUrl, long pollingIntervalMs) {
+ super(new RangerPluginCfg("hdfs", serviceName, "jfs", null, null, null));
+ this.fs = fs;
+ this.rangerUrl = rangerUrl;
+ RangerPluginConfig config = getConfig();
+ config.addResource(fs.getConf());
+ this.pollingIntervalMs = pollingIntervalMs;
+ }
+
+ @Override
+ public void init() {
+ cleanup();
+ RangerAdminClient admin = createAdminClient(getConfig());
+ refresher = new RangerAdminRefresher(this, admin, fs, rangerUrl, pollingIntervalMs);
+ refresher.start();
+ List chainedPlugins = null;
+ try {
+ chainedPlugins = (List) ReflectionUtil.getField(RangerBasePlugin.class.getName(), "chainedPlugins", this);
+ } catch (Exception e) {
+ LOG.warn("Get field \"chainedPlugins\" failed", e);
+ }
+ if (chainedPlugins != null) {
+ for (RangerChainedPlugin plugin : chainedPlugins) {
+ plugin.init();
+ }
+ }
+ }
+
+ @Override
+ public void cleanup() {
+ super.cleanup();
+ if (refresher != null) {
+ refresher.stop();
+ }
+ }
+}
diff --git a/sdk/java/src/main/java/io/juicefs/permission/RangerPermissionChecker.java b/sdk/java/src/main/java/io/juicefs/permission/RangerPermissionChecker.java
index b205d7d3f5f2..d437aee74bc7 100644
--- a/sdk/java/src/main/java/io/juicefs/permission/RangerPermissionChecker.java
+++ b/sdk/java/src/main/java/io/juicefs/permission/RangerPermissionChecker.java
@@ -17,23 +17,23 @@
package io.juicefs.permission;
import com.google.common.collect.Sets;
-import io.juicefs.JuiceFileSystemImpl;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsAction;
import org.apache.hadoop.security.AccessControlException;
-import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
import org.apache.ranger.plugin.policyengine.RangerAccessResult;
-import org.apache.ranger.plugin.policyengine.RangerPolicyEngineOptions;
-import org.apache.ranger.plugin.service.RangerBasePlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.util.*;
-import java.util.stream.Collectors;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* for auth checker
@@ -44,6 +44,9 @@ public class RangerPermissionChecker {
private static final Logger LOG = LoggerFactory.getLogger(RangerPermissionChecker.class);
+ private static final Map pcs = new ConcurrentHashMap<>();
+ private static final Map> runningInstance = new HashMap<>();
+
private final HashMap> fsAction2ActionMapper = new HashMap>() {
{
put(FsAction.NONE, new HashSet<>());
@@ -57,51 +60,64 @@ public class RangerPermissionChecker {
}
};
- private final JuiceFileSystemImpl superGroupFileSystem;
-
- private final String user;
-
- private final Set groups;
-
- private final String rangerCacheDir;
-
- private final RangerBasePlugin rangerPlugin;
-
- private static final String RANGER_SERVICE_TYPE = "hdfs";
+ private final FileSystem superGroupFileSystem;
+ private final RangerJfsPlugin rangerPlugin;
- public RangerPermissionChecker(JuiceFileSystemImpl superGroupFileSystem, RangerConfig config, String user, String group) {
+ private RangerPermissionChecker(FileSystem superGroupFileSystem, RangerConfig config) {
this.superGroupFileSystem = superGroupFileSystem;
- this.user = user;
- this.groups = Arrays.stream(group.split(",")).collect(Collectors.toSet());
-
- this.rangerCacheDir = config.getCacheDir();
- boolean startRangerRefresher = LockFileChecker.checkAndCreateLockFile(rangerCacheDir);
-
- RangerPluginConfig rangerPluginContext = buildRangerPluginContext(RANGER_SERVICE_TYPE, config.getServiceName(), startRangerRefresher);
- rangerPlugin = new RangerBasePlugin(rangerPluginContext);
- rangerPlugin.getConfig().set("ranger.plugin.hdfs.policy.cache.dir", this.rangerCacheDir);
+ rangerPlugin = new RangerJfsPlugin(superGroupFileSystem, config.getServiceName(), config.getRangerRestUrl(), config.getPollIntervalMs());
rangerPlugin.getConfig().set("ranger.plugin.hdfs.service.name", config.getServiceName());
rangerPlugin.getConfig().set("ranger.plugin.hdfs.policy.rest.url", config.getRangerRestUrl());
+ rangerPlugin.getConfig().setIsFallbackSupported(true);
rangerPlugin.init();
}
- protected RangerPolicyEngineOptions buildRangerPolicyEngineOptions(boolean startRangerRefresher) {
- if (startRangerRefresher) {
- return null;
+ public static RangerPermissionChecker acquire(String volName, long handle, FileSystem superGroupFileSystem, RangerConfig config) throws IOException {
+ synchronized (runningInstance) {
+ if (!runningInstance.containsKey(volName)) {
+ if (pcs.containsKey(volName)) {
+ throw new IOException("RangerPermissionChecker for volume: " + volName + " is already created, but no running instance found.");
+ }
+ RangerPermissionChecker pc = new RangerPermissionChecker(superGroupFileSystem, config);
+ pcs.put(volName, pc);
+ Set handles = new HashSet<>();
+ handles.add(handle);
+ runningInstance.put(volName, handles);
+ return pc;
+ } else {
+ RangerPermissionChecker pc = pcs.get(volName);
+ if (pc == null) {
+ throw new IOException("RangerPermissionChecker for volume: " + volName + " is already created, but no instance found.");
+ }
+ runningInstance.get(volName).add(handle);
+ return pc;
+ }
}
- LOG.info("Other JuiceFS Client is refreshing ranger policy, will close the refresher here.");
- RangerPolicyEngineOptions options = new RangerPolicyEngineOptions();
- options.disablePolicyRefresher = true;
- return options;
}
- protected RangerPluginConfig buildRangerPluginContext(String serviceType, String serviceName, boolean startRangerRefresher) {
- return new RangerPluginConfig(serviceType, serviceName, serviceName,
- null, null, buildRangerPolicyEngineOptions(startRangerRefresher));
+ public static void release(String volName, long handle) {
+ if (handle <= 0) {
+ return;
+ }
+ synchronized (runningInstance) {
+ if (!runningInstance.containsKey(volName)) {
+ return;
+ }
+ Set handles = runningInstance.get(volName);
+ boolean removed = handles.remove(handle);
+ if (!removed) {
+ return;
+ }
+ if (handles.size() == 0) {
+ RangerPermissionChecker pc = pcs.remove(volName);
+ pc.cleanUp();
+ runningInstance.remove(volName);
+ }
+ }
}
public boolean checkPermission(Path path, boolean checkOwner, FsAction ancestorAccess, FsAction parentAccess,
- FsAction access, String operationName) throws IOException {
+ FsAction access, String operationName, String user, Set groups) throws IOException {
RangerPermissionContext context = new RangerPermissionContext(user, groups, operationName);
PathObj obj = path2Obj(path);
@@ -157,7 +173,11 @@ public void cleanUp() {
} catch (Exception e) {
LOG.warn("Error when clean up ranger plugin threads.", e);
}
- LockFileChecker.cleanUp(rangerCacheDir);
+ try {
+ superGroupFileSystem.close();
+ } catch (Exception e) {
+ LOG.warn("Error when close super group file system.", e);
+ }
}
private static boolean checkResult(AuthzStatus authzStatus, String user, String action, String path) throws AccessControlException {
@@ -191,19 +211,12 @@ private AuthzStatus isAccessAllowed(FileStatus file, FsAction access, RangerPerm
String pathOwner = file.getOwner();
AuthzStatus authzStatus = null;
for (String accessType : accessTypes) {
- RangerJfsAccessRequest request = new RangerJfsAccessRequest(path, pathOwner, accessType, context.operationName, user, context.userGroups);
+ RangerJfsAccessRequest request = new RangerJfsAccessRequest(path, pathOwner, accessType, context.operationName, context.user, context.userGroups);
LOG.debug(request.toString());
-
- RangerAccessResult result = null;
- try {
- result = rangerPlugin.isAccessAllowed(request);
- if (result != null) {
- LOG.debug(result.toString());
- }
- } catch (Throwable e) {
- throw new RuntimeException("Check Permission Error. ", e);
+ RangerAccessResult result = rangerPlugin.isAccessAllowed(request);
+ if (result != null) {
+ LOG.debug(result.toString());
}
-
if (result == null || !result.getIsAccessDetermined()) {
authzStatus = AuthzStatus.NOT_DETERMINED;
} else if (!result.getIsAllowed()) {
diff --git a/sdk/java/src/main/java/io/juicefs/permission/RangerPluginCfg.java b/sdk/java/src/main/java/io/juicefs/permission/RangerPluginCfg.java
new file mode 100644
index 000000000000..c169edf7b7c1
--- /dev/null
+++ b/sdk/java/src/main/java/io/juicefs/permission/RangerPluginCfg.java
@@ -0,0 +1,78 @@
+/*
+ * JuiceFS, Copyright 2024 Juicedata, Inc.
+ *
+ * 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.
+ */
+package io.juicefs.permission;
+
+import org.apache.ranger.authorization.hadoop.config.RangerConfiguration;
+import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
+import org.apache.ranger.plugin.policyengine.RangerPolicyEngineOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+public class RangerPluginCfg extends RangerPluginConfig {
+ private static final Logger LOG = LoggerFactory.getLogger(RangerPluginCfg.class);
+
+ @Override
+ public boolean addResourceIfReadable(String aResourceName) {
+ URL fUrl = this.getFileLocation(aResourceName);
+ if (fUrl != null) {
+ try {
+ this.addResource(fUrl);
+ } catch (Exception e) {
+ LOG.error("Unable to load the resource name [" + aResourceName + "]. Ignoring the resource:" + fUrl);
+ }
+ }
+ return true;
+ }
+
+ public static boolean isEmpty(String str) {
+ return str == null || str.length() == 0;
+ }
+
+ private URL getFileLocation(String fileName) {
+ URL lurl = null;
+ if (!isEmpty(fileName)) {
+ lurl = RangerConfiguration.class.getClassLoader().getResource(fileName);
+
+ if (lurl == null ) {
+ lurl = RangerConfiguration.class.getClassLoader().getResource("/" + fileName);
+ }
+
+ if (lurl == null ) {
+ File f = new File(fileName);
+ if (f.exists()) {
+ try {
+ lurl=f.toURI().toURL();
+ } catch (MalformedURLException e) {
+ LOG.error("Unable to load the resource name [" + fileName + "]. Ignoring the resource:" + f.getPath());
+ }
+ } else {
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Conf file path " + fileName + " does not exists");
+ }
+ }
+ }
+ }
+ return lurl;
+ }
+
+ public RangerPluginCfg(String serviceType, String serviceName, String appId, String clusterName, String clusterType, RangerPolicyEngineOptions policyEngineOptions) {
+ super(serviceType, serviceName, appId, clusterName, clusterType, policyEngineOptions);
+ }
+}
\ No newline at end of file
diff --git a/sdk/java/src/main/java/io/juicefs/permission/RangerRules.java b/sdk/java/src/main/java/io/juicefs/permission/RangerRules.java
new file mode 100644
index 000000000000..ac7faa375e6b
--- /dev/null
+++ b/sdk/java/src/main/java/io/juicefs/permission/RangerRules.java
@@ -0,0 +1,61 @@
+/*
+ * JuiceFS, Copyright 2024 Juicedata, Inc.
+ *
+ * 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.
+ */
+package io.juicefs.permission;
+
+import org.apache.ranger.plugin.util.RangerRoles;
+import org.apache.ranger.plugin.util.ServicePolicies;
+import org.apache.ranger.plugin.util.ServiceTags;
+
+import java.io.Serializable;
+
+public class RangerRules implements Serializable {
+ private ServicePolicies policies;
+ private ServiceTags tags;
+ private RangerRoles roles;
+
+ public RangerRules() {
+ }
+
+ public RangerRules(ServicePolicies policies, ServiceTags tags, RangerRoles roles) {
+ this.policies = policies;
+ this.tags = tags;
+ this.roles = roles;
+ }
+
+ public ServicePolicies getPolicies() {
+ return policies;
+ }
+
+ public void setPolicies(ServicePolicies policies) {
+ this.policies = policies;
+ }
+
+ public ServiceTags getTags() {
+ return tags;
+ }
+
+ public void setTags(ServiceTags tags) {
+ this.tags = tags;
+ }
+
+ public RangerRoles getRoles() {
+ return roles;
+ }
+
+ public void setRoles(RangerRoles roles) {
+ this.roles = roles;
+ }
+}
diff --git a/sdk/java/src/main/java/io/juicefs/utils/ReflectionUtil.java b/sdk/java/src/main/java/io/juicefs/utils/ReflectionUtil.java
index 418be1736022..9bedcc24a278 100644
--- a/sdk/java/src/main/java/io/juicefs/utils/ReflectionUtil.java
+++ b/sdk/java/src/main/java/io/juicefs/utils/ReflectionUtil.java
@@ -16,6 +16,7 @@
package io.juicefs.utils;
import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
public class ReflectionUtil {
public static boolean hasMethod(String className, String method, String[] params) {
@@ -50,4 +51,11 @@ public static Constructor getConstructor(Class clazz, Class>... para
return null;
}
}
+
+ public static Object getField(String className, String field, Object obj) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
+ Class> clazz = Class.forName(className);
+ Field f = clazz.getDeclaredField(field);
+ f.setAccessible(true);
+ return f.get(obj);
+ }
}
diff --git a/sdk/java/src/main/resources/ranger-hdfs-audit.xml b/sdk/java/src/main/resources/ranger-hdfs-audit.xml
deleted file mode 100644
index 24856dcb7f76..000000000000
--- a/sdk/java/src/main/resources/ranger-hdfs-audit.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
- xasecure.audit.is.enabled
- false
-
-
\ No newline at end of file
diff --git a/sdk/java/src/main/resources/ranger-hdfs-security.xml b/sdk/java/src/main/resources/ranger-hdfs-security.xml
deleted file mode 100644
index 2ef9a06309d9..000000000000
--- a/sdk/java/src/main/resources/ranger-hdfs-security.xml
+++ /dev/null
@@ -1,83 +0,0 @@
-
-
-
-
-
- ranger.plugin.hdfs.service.name
- xxx
-
- Name of the Ranger service containing policies for this YARN instance
-
-
-
-
- ranger.plugin.hdfs.policy.source.impl
- org.apache.ranger.admin.client.RangerAdminRESTClient
-
- Class to retrieve policies from the source
-
-
-
-
- ranger.plugin.hdfs.policy.rest.url
- xxx
-
- URL to Ranger Admin
-
-
-
-
- ranger.plugin.hdfs.policy.pollIntervalMs
- 30000
-
- How often to poll for changes in policies?
-
-
-
-
- ranger.plugin.hdfs.policy.cache.dir
- xxx
-
- Directory where Ranger policies are cached after successful retrieval from the source
-
-
-
-
- ranger.plugin.hdfs.policy.rest.client.connection.timeoutMs
- 120000
-
- Hdfs Plugin RangerRestClient Connection Timeout in Milli Seconds
-
-
-
-
- ranger.plugin.hdfs.policy.rest.client.read.timeoutMs
- 30000
-
- Hdfs Plugin RangerRestClient read Timeout in Milli Seconds
-
-
-
-
- xasecure.add-hadoop-authorization
- true
-
- Enable/Disable the default hadoop authorization (based on
- rwxrwxrwx permission on the resource) if Ranger Authorization fails.
-
-
-
\ No newline at end of file