diff --git a/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier b/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier
new file mode 100644
index 0000000000..81b257380d
--- /dev/null
+++ b/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier
@@ -0,0 +1,16 @@
+# 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.
+
+org.apache.ranger.plugin.util.RangerDelegationTokenIdentifier
diff --git a/META-INF/services/org.apache.hadoop.security.token.TokenRenewer b/META-INF/services/org.apache.hadoop.security.token.TokenRenewer
new file mode 100644
index 0000000000..e976a9e7c5
--- /dev/null
+++ b/META-INF/services/org.apache.hadoop.security.token.TokenRenewer
@@ -0,0 +1,16 @@
+# 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.
+
+org.apache.ranger.plugin.util.RangerTokenRenewer
diff --git a/META-INF/services/org.apache.spark.security.HadoopDelegationTokenProvider b/META-INF/services/org.apache.spark.security.HadoopDelegationTokenProvider
new file mode 100644
index 0000000000..6734fc14fe
--- /dev/null
+++ b/META-INF/services/org.apache.spark.security.HadoopDelegationTokenProvider
@@ -0,0 +1,15 @@
+#
+# 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.
+
+org.apache.kyuubi.plugin.spark.authz.ranger.RangerDelegationTokenProvider
diff --git a/agents-audit/pom.xml b/agents-audit/pom.xml
index 046abf3f51..ba6d8b2581 100644
--- a/agents-audit/pom.xml
+++ b/agents-audit/pom.xml
@@ -274,6 +274,17 @@
ranger-plugins-cred
${project.version}
+
+ org.apache.solr
+ solr-hadoop-auth-framework
+ ${solr.version}
+
+
+ org.apache.hadoop
+ *
+
+
+
org.apache.solr
solr-solrj
diff --git a/agents-audit/src/main/java/org/apache/ranger/audit/destination/SolrAuditDestination.java b/agents-audit/src/main/java/org/apache/ranger/audit/destination/SolrAuditDestination.java
index 4e7cbe8e3f..1225c94f59 100644
--- a/agents-audit/src/main/java/org/apache/ranger/audit/destination/SolrAuditDestination.java
+++ b/agents-audit/src/main/java/org/apache/ranger/audit/destination/SolrAuditDestination.java
@@ -27,15 +27,19 @@
import org.apache.ranger.audit.utils.KerberosAction;
import org.apache.ranger.audit.utils.KerberosUser;
import org.apache.ranger.audit.utils.KerberosJAASConfigUser;
+import org.apache.hadoop.security.Credentials;
+import org.apache.hadoop.security.UserGroupInformation;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.HttpClientUtil;
+import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.impl.Krb5HttpClientBuilder;
import org.apache.solr.client.solrj.impl.SolrHttpClientBuilder;
import org.apache.solr.client.solrj.impl.LBHttpSolrClient;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.hadoop.SolrDelegationTokenUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -160,26 +164,57 @@ synchronized void connect() {
LOG.info("Solr zkHosts=" + zkHosts + ", solrURLs=" + urls
+ ", collectionName=" + collectionName);
+ // Check for a Solr delegation token in UGI credentials
+ String delegationToken = null;
+ try {
+ Credentials creds = UserGroupInformation.getLoginUser().getCredentials();
+ delegationToken = SolrDelegationTokenUtil.findTokenInCredentials(creds);
+ if (delegationToken != null) {
+ LOG.info("Found Solr delegation token in UGI credentials, will use it for authentication");
+ }
+ } catch (Exception e) {
+ LOG.debug("Failed to look up Solr delegation token, falling back to Kerberos", e);
+ }
+
if (zkHosts != null && !zkHosts.isEmpty()) {
LOG.info("Connecting to solr cloud using zkHosts="
+ zkHosts);
try {
- // Instantiate
- Krb5HttpClientBuilder krbBuild = new Krb5HttpClientBuilder();
- SolrHttpClientBuilder kb = krbBuild.getBuilder();
- HttpClientUtil.setHttpClientBuilder(kb);
-
final List zkhosts = new ArrayList(Arrays.asList(zkHosts.split(",")));
- final CloudSolrClient solrCloudClient = MiscUtil.executePrivilegedAction(new PrivilegedExceptionAction() {
- @Override
- public CloudSolrClient run() throws Exception {
- CloudSolrClient solrCloudClient = new CloudSolrClient.Builder(zkhosts, Optional.empty()).build();
- return solrCloudClient;
- };
- });
-
- solrCloudClient.setDefaultCollection(collectionName);
- me = solrClient = solrCloudClient;
+
+ if (delegationToken != null) {
+ final String dt = delegationToken;
+ final CloudSolrClient solrCloudClient = MiscUtil.executePrivilegedAction(new PrivilegedExceptionAction() {
+ @Override
+ public CloudSolrClient run() throws Exception {
+ return new CloudSolrClient.Builder(zkhosts, Optional.empty())
+ .withLBHttpSolrClientBuilder(new LBHttpSolrClient.Builder()
+ .withConnectionTimeout(1000)
+ .withHttpSolrClientBuilder(
+ new HttpSolrClient.Builder()
+ .withKerberosDelegationToken(dt)))
+ .build();
+ }
+ });
+ solrCloudClient.setDefaultCollection(collectionName);
+ me = solrClient = solrCloudClient;
+ } else {
+ // Instantiate
+ Krb5HttpClientBuilder krbBuild = new Krb5HttpClientBuilder();
+ SolrHttpClientBuilder kb = krbBuild.getBuilder();
+ HttpClientUtil.setHttpClientBuilder(kb);
+
+ final CloudSolrClient solrCloudClient = MiscUtil.executePrivilegedAction(new PrivilegedExceptionAction() {
+ @Override
+ public CloudSolrClient run() throws Exception {
+ CloudSolrClient solrCloudClient = new CloudSolrClient.Builder(zkhosts, Optional.empty()).build();
+ return solrCloudClient;
+ };
+ });
+
+ solrCloudClient.setDefaultCollection(collectionName);
+ me = solrClient = solrCloudClient;
+ }
} catch (Throwable t) {
LOG.error("Can't connect to Solr server. ZooKeepers="
+ zkHosts, t);
@@ -190,25 +225,46 @@ public CloudSolrClient run() throws Exception {
} else if (solrURLs != null && !solrURLs.isEmpty()) {
try {
LOG.info("Connecting to Solr using URLs=" + solrURLs);
- Krb5HttpClientBuilder krbBuild = new Krb5HttpClientBuilder();
- SolrHttpClientBuilder kb = krbBuild.getBuilder();
- HttpClientUtil.setHttpClientBuilder(kb);
final List solrUrls = solrURLs;
- final LBHttpSolrClient lbSolrClient = MiscUtil.executePrivilegedAction(new PrivilegedExceptionAction() {
- @Override
- public LBHttpSolrClient run() throws Exception {
- LBHttpSolrClient.Builder builder = new LBHttpSolrClient.Builder();
- builder.withBaseSolrUrl(solrUrls.get(0));
- builder.withConnectionTimeout(1000);
- LBHttpSolrClient lbSolrClient = builder.build();
- return lbSolrClient;
- };
- });
-
- for (int i = 1; i < solrURLs.size(); i++) {
- lbSolrClient.addSolrServer(solrURLs.get(i));
+
+ if (delegationToken != null) {
+ final String dt = delegationToken;
+ final LBHttpSolrClient lbSolrClient = MiscUtil.executePrivilegedAction(new PrivilegedExceptionAction() {
+ @Override
+ public LBHttpSolrClient run() throws Exception {
+ return new LBHttpSolrClient.Builder()
+ .withBaseSolrUrl(solrUrls.get(0))
+ .withConnectionTimeout(1000)
+ .withHttpSolrClientBuilder(
+ new HttpSolrClient.Builder()
+ .withKerberosDelegationToken(dt))
+ .build();
+ }
+ });
+ for (int i = 1; i < solrURLs.size(); i++) {
+ lbSolrClient.addSolrServer(solrURLs.get(i));
+ }
+ me = solrClient = lbSolrClient;
+ } else {
+ Krb5HttpClientBuilder krbBuild = new Krb5HttpClientBuilder();
+ SolrHttpClientBuilder kb = krbBuild.getBuilder();
+ HttpClientUtil.setHttpClientBuilder(kb);
+ final LBHttpSolrClient lbSolrClient = MiscUtil.executePrivilegedAction(new PrivilegedExceptionAction() {
+ @Override
+ public LBHttpSolrClient run() throws Exception {
+ LBHttpSolrClient.Builder builder = new LBHttpSolrClient.Builder();
+ builder.withBaseSolrUrl(solrUrls.get(0));
+ builder.withConnectionTimeout(1000);
+ LBHttpSolrClient lbSolrClient = builder.build();
+ return lbSolrClient;
+ };
+ });
+
+ for (int i = 1; i < solrURLs.size(); i++) {
+ lbSolrClient.addSolrServer(solrURLs.get(i));
+ }
+ me = solrClient = lbSolrClient;
}
- me = solrClient = lbSolrClient;
} catch (Throwable t) {
LOG.error("Can't connect to Solr server. URL="
+ solrURLs, t);
diff --git a/agents-common/src/main/java/org/apache/ranger/admin/client/AbstractRangerAdminClient.java b/agents-common/src/main/java/org/apache/ranger/admin/client/AbstractRangerAdminClient.java
index fbac4ea9f3..e440e675c0 100644
--- a/agents-common/src/main/java/org/apache/ranger/admin/client/AbstractRangerAdminClient.java
+++ b/agents-common/src/main/java/org/apache/ranger/admin/client/AbstractRangerAdminClient.java
@@ -23,12 +23,14 @@
import com.google.gson.GsonBuilder;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.token.Token;
import org.apache.ranger.plugin.model.RangerRole;
import org.apache.ranger.plugin.model.ResourceMappingDiffs;
import org.apache.ranger.plugin.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.Collection;
import java.util.List;
public abstract class AbstractRangerAdminClient implements RangerAdminClient {
@@ -127,6 +129,20 @@ public ResourceMappingDiffs getResourceMappingDiffs(String sourceService, String
return null;
}
+ @Override
+ public Token getDelegationToken(String renewer) throws Exception {
+ return null;
+ }
+
+ @Override
+ public long renewDelegationToken(Token token) throws Exception {
+ return 0;
+ }
+
+ @Override
+ public void cancelDelegationToken(Token token) throws Exception {
+ }
+
public boolean isKerberosEnabled(UserGroupInformation user) {
final boolean ret;
@@ -138,4 +154,22 @@ public boolean isKerberosEnabled(UserGroupInformation user) {
return ret;
}
+
+ @SuppressWarnings("unchecked")
+ public static Token getDelegationTokenFromUGI() {
+ try {
+ UserGroupInformation ugi = UserGroupInformation.getLoginUser();
+ if (ugi != null) {
+ Collection> tokens = ugi.getCredentials().getAllTokens();
+ for (Token> token : tokens) {
+ if (RangerDelegationTokenIdentifier.RANGER_DELEGATION_KIND.equals(token.getKind())) {
+ return (Token) token;
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOG.debug("Failed to get delegation token from UGI", e);
+ }
+ return null;
+ }
}
diff --git a/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminClient.java b/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminClient.java
index d16608460f..0dc5b7bbf7 100644
--- a/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminClient.java
+++ b/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminClient.java
@@ -21,10 +21,12 @@
import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.token.Token;
import org.apache.ranger.plugin.model.RangerRole;
import org.apache.ranger.plugin.model.ResourceMappingDiffs;
import org.apache.ranger.plugin.util.GrantRevokeRequest;
import org.apache.ranger.plugin.util.GrantRevokeRoleRequest;
+import org.apache.ranger.plugin.util.RangerDelegationTokenIdentifier;
import org.apache.ranger.plugin.util.RangerRoles;
import org.apache.ranger.plugin.util.ServicePolicies;
import org.apache.ranger.plugin.util.ServiceTags;
@@ -66,4 +68,10 @@ public interface RangerAdminClient {
RangerUserStore getUserStoreIfUpdated(long lastKnownUserStoreVersion, long lastActivationTimeInMillis) throws Exception;
ResourceMappingDiffs getResourceMappingDiffs(String sourceService, String targetService, Long diffId) throws Exception;
+
+ Token getDelegationToken(String renewer) throws Exception;
+
+ long renewDelegationToken(Token token) throws Exception;
+
+ void cancelDelegationToken(Token token) throws Exception;
}
diff --git a/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java b/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java
index f2e86aa406..bae3d295a9 100644
--- a/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java
+++ b/agents-common/src/main/java/org/apache/ranger/admin/client/RangerAdminRESTClient.java
@@ -32,10 +32,13 @@
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.NewCookie;
import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.io.Text;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.token.Token;
import org.apache.http.HttpStatus;
import org.apache.ranger.admin.client.datatype.RESTResponse;
+import org.apache.ranger.plugin.util.RangerDelegationTokenIdentifier;
import org.apache.ranger.audit.provider.MiscUtil;
import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
import org.apache.ranger.authorization.utils.StringUtil;
@@ -141,29 +144,9 @@ public ServicePolicies getServicePoliciesIfUpdated(final long lastKnownVersion,
queryParams.put(RangerRESTUtils.REST_PARAM_SUPPORTS_POLICY_DELTAS, Boolean.toString(supportsPolicyDeltas));
queryParams.put(RangerRESTUtils.REST_PARAM_CAPABILITIES, pluginCapabilities);
- if (isSecureMode) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Checking Service policy if updated as user : " + user);
- }
-
- response = MiscUtil.executePrivilegedAction((PrivilegedExceptionAction) () -> {
- try {
- String relativeURL = RangerRESTUtils.REST_URL_POLICY_GET_FOR_SECURE_SERVICE_IF_UPDATED + serviceNameUrlParam;
-
- return restClient.get(relativeURL, queryParams, sessionId);
- } catch (Exception e) {
- LOG.error("Failed to get response, Error is : "+e.getMessage());
- }
-
- return null;
- });
- } else {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Checking Service policy if updated with old api call");
- }
- String relativeURL = RangerRESTUtils.REST_URL_POLICY_GET_FOR_SERVICE_IF_UPDATED + serviceNameUrlParam;
- response = restClient.get(relativeURL, queryParams, sessionId);
- }
+ String secureURL = RangerRESTUtils.REST_URL_POLICY_GET_FOR_SECURE_SERVICE_IF_UPDATED + serviceNameUrlParam;
+ String nonSecureURL = RangerRESTUtils.REST_URL_POLICY_GET_FOR_SERVICE_IF_UPDATED + serviceNameUrlParam;
+ response = getWithAuth(secureURL, nonSecureURL, queryParams, user, isSecureMode, sessionId);
checkAndResetSessionCookie(response);
@@ -226,28 +209,9 @@ public RangerRoles getRolesIfUpdated(final long lastKnownRoleVersion, final long
queryParams.put(RangerRESTUtils.REST_PARAM_CLUSTER_NAME, clusterName);
queryParams.put(RangerRESTUtils.REST_PARAM_CAPABILITIES, pluginCapabilities);
- if (isSecureMode) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Checking Roles updated as user : " + user);
- }
- response = MiscUtil.executePrivilegedAction((PrivilegedExceptionAction) () -> {
- try {
- String relativeURL = RangerRESTUtils.REST_URL_SERVICE_SERCURE_GET_USER_GROUP_ROLES + serviceNameUrlParam;
-
- return restClient.get(relativeURL, queryParams, sessionId);
- } catch (Exception e) {
- LOG.error("Failed to get response, Error is : "+e.getMessage());
- }
-
- return null;
- });
- } else {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Checking Roles updated as user : " + user);
- }
- String relativeURL = RangerRESTUtils.REST_URL_SERVICE_GET_USER_GROUP_ROLES + serviceNameUrlParam;
- response = restClient.get(relativeURL, queryParams, sessionId);
- }
+ String secureURL = RangerRESTUtils.REST_URL_SERVICE_SERCURE_GET_USER_GROUP_ROLES + serviceNameUrlParam;
+ String nonSecureURL = RangerRESTUtils.REST_URL_SERVICE_GET_USER_GROUP_ROLES + serviceNameUrlParam;
+ response = getWithAuth(secureURL, nonSecureURL, queryParams, user, isSecureMode, sessionId);
checkAndResetSessionCookie(response);
@@ -820,25 +784,9 @@ public ServiceTags getServiceTagsIfUpdated(final long lastKnownVersion, final lo
queryParams.put(RangerRESTUtils.REST_PARAM_SUPPORTS_TAG_DELTAS, Boolean.toString(supportsTagDeltas));
queryParams.put(RangerRESTUtils.REST_PARAM_CAPABILITIES, pluginCapabilities);
- if (isSecureMode) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("getServiceTagsIfUpdated as user " + user);
- }
- response = MiscUtil.executePrivilegedAction((PrivilegedExceptionAction) () -> {
- try {
- String relativeURL = RangerRESTUtils.REST_URL_GET_SECURE_SERVICE_TAGS_IF_UPDATED + serviceNameUrlParam;
-
- return restClient.get(relativeURL, queryParams, sessionId);
- } catch (Exception e) {
- LOG.error("Failed to get response, Error is : "+e.getMessage());
- }
-
- return null;
- });
- } else {
- String relativeURL = RangerRESTUtils.REST_URL_GET_SERVICE_TAGS_IF_UPDATED + serviceNameUrlParam;
- response = restClient.get(relativeURL, queryParams, sessionId);
- }
+ String secureURL = RangerRESTUtils.REST_URL_GET_SECURE_SERVICE_TAGS_IF_UPDATED + serviceNameUrlParam;
+ String nonSecureURL = RangerRESTUtils.REST_URL_GET_SERVICE_TAGS_IF_UPDATED + serviceNameUrlParam;
+ response = getWithAuth(secureURL, nonSecureURL, queryParams, user, isSecureMode, sessionId);
checkAndResetSessionCookie(response);
@@ -951,28 +899,9 @@ public RangerUserStore getUserStoreIfUpdated(long lastKnownUserStoreVersion, lon
queryParams.put(RangerRESTUtils.REST_PARAM_CLUSTER_NAME, clusterName);
queryParams.put(RangerRESTUtils.REST_PARAM_CAPABILITIES, pluginCapabilities);
- if (isSecureMode) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Checking UserStore updated as user : " + user);
- }
- response = MiscUtil.executePrivilegedAction((PrivilegedExceptionAction) () -> {
- try {
- String relativeURL = RangerRESTUtils.REST_URL_SERVICE_SERCURE_GET_USERSTORE + serviceNameUrlParam;
-
- return restClient.get(relativeURL, queryParams, sessionId);
- } catch (Exception e) {
- LOG.error("Failed to get response, Error is : "+e.getMessage());
- }
-
- return null;
- });
- } else {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Checking UserStore updated as user : " + user);
- }
- String relativeURL = RangerRESTUtils.REST_URL_SERVICE_GET_USERSTORE + serviceNameUrlParam;
- response = restClient.get(relativeURL, queryParams, sessionId);
- }
+ String secureURL = RangerRESTUtils.REST_URL_SERVICE_SERCURE_GET_USERSTORE + serviceNameUrlParam;
+ String nonSecureURL = RangerRESTUtils.REST_URL_SERVICE_GET_USERSTORE + serviceNameUrlParam;
+ response = getWithAuth(secureURL, nonSecureURL, queryParams, user, isSecureMode, sessionId);
checkAndResetSessionCookie(response);
@@ -1021,8 +950,9 @@ public ResourceMappingDiffs getResourceMappingDiffs(String sourceService, String
LOG.debug("==> RangerAdminRESTClient.getResourceMappingDiffs({}, {}, {})", sourceService, targetService, diffId);
}
- UserGroupInformation user = MiscUtil.getUGILoginUser();
- Cookie sessionId = this.sessionId;
+ final UserGroupInformation user = MiscUtil.getUGILoginUser();
+ final boolean isSecureMode = isKerberosEnabled(user);
+ final Cookie sessionId = this.sessionId;
Map queryParams = new HashMap<>();
if (diffId != null) {
@@ -1030,40 +960,199 @@ public ResourceMappingDiffs getResourceMappingDiffs(String sourceService, String
}
String relativeURL = String.format("/service/resource-mappings/%s/%s/diffs/new", sourceService, targetService);
- final ClientResponse response;
- if (isKerberosEnabled(user)) {
+ final ClientResponse response = getWithAuth(relativeURL, relativeURL, queryParams, user, isSecureMode, sessionId);
+
+ checkAndResetSessionCookie(response);
+
+ ResourceMappingDiffs diffs;
+ if (response != null && response.getStatus() == HttpServletResponse.SC_OK) {
+ diffs = JsonUtilsV2.readResponse(response, ResourceMappingDiffs.class);
+ } else {
+ RESTResponse resp = RESTResponse.fromClientResponse(response);
+ LOG.error("Error getting resource mappings. Response={}", resp);
+ throw new Exception(resp.getMessage());
+ }
+
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("<== RangerAdminRESTClient.getResourceMappingDiffs({}, {}, {})", sourceService, targetService, diffId);
+ }
+
+ return diffs;
+ }
+
+ @Override
+ public Token getDelegationToken(final String renewer) throws Exception {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("==> RangerAdminRESTClient.getDelegationToken(renewer=" + renewer + ")");
+ }
+
+ final UserGroupInformation user = MiscUtil.getUGILoginUser();
+ final boolean isSecureMode = isKerberosEnabled(user);
+
+ if (!isSecureMode) {
+ throw new UnsupportedOperationException("Delegation tokens require Kerberos authentication");
+ }
+
+ final Cookie sessionId = this.sessionId;
+
+ Map queryParams = new HashMap();
+ if (renewer != null) {
+ queryParams.put(RangerRESTUtils.REST_PARAM_RENEWER, renewer);
+ }
+
+ final ClientResponse response = MiscUtil.executePrivilegedAction((PrivilegedExceptionAction) () -> {
+ try {
+ return restClient.get(RangerRESTUtils.REST_URL_DELEGATION_TOKEN, queryParams, sessionId);
+ } catch (Exception e) {
+ LOG.error("Failed to get delegation token", e);
+ }
+ return null;
+ });
+
+ checkAndResetSessionCookie(response);
+
+ if (response != null && response.getStatus() == HttpServletResponse.SC_OK) {
+ @SuppressWarnings("unchecked")
+ Map result = JsonUtilsV2.readResponse(response, Map.class);
+ String tokenEncoded = result.get("urlString");
+ Token token = new Token<>();
+ token.decodeFromUrlString(tokenEncoded);
+ token.setService(new Text(restClient.getUrl()));
+
if (LOG.isDebugEnabled()) {
- LOG.debug("getResourceMappingDiffs as user {}", user);
+ LOG.debug("<== RangerAdminRESTClient.getDelegationToken(): token obtained, service={}", restClient.getUrl());
}
+ return token;
+ } else {
+ RESTResponse resp = RESTResponse.fromClientResponse(response);
+ throw new Exception("Failed to get delegation token: " + (resp != null ? resp.getMessage() : "null response"));
+ }
+ }
+
+ @Override
+ public long renewDelegationToken(final Token token) throws Exception {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("==> RangerAdminRESTClient.renewDelegationToken()");
+ }
+
+ final UserGroupInformation user = MiscUtil.getUGILoginUser();
+ final boolean isSecureMode = isKerberosEnabled(user);
+ final Cookie sessionId = this.sessionId;
+
+ final Map requestBody = new HashMap<>();
+ requestBody.put("token", token.encodeToUrlString());
+
+ final ClientResponse response;
+ if (isSecureMode) {
response = MiscUtil.executePrivilegedAction((PrivilegedExceptionAction) () -> {
try {
- return restClient.get(relativeURL, queryParams, sessionId);
+ return restClient.put(RangerRESTUtils.REST_URL_DELEGATION_TOKEN_RENEW, (Object) requestBody, sessionId);
} catch (Exception e) {
- LOG.error("Failed to get response", e);
+ LOG.error("Failed to renew delegation token", e);
}
-
return null;
});
} else {
- response = restClient.get(relativeURL, queryParams, sessionId);
+ response = restClient.put(RangerRESTUtils.REST_URL_DELEGATION_TOKEN_RENEW, (Object) requestBody, sessionId);
}
checkAndResetSessionCookie(response);
- ResourceMappingDiffs diffs;
if (response != null && response.getStatus() == HttpServletResponse.SC_OK) {
- diffs = JsonUtilsV2.readResponse(response, ResourceMappingDiffs.class);
+ @SuppressWarnings("unchecked")
+ Map result = JsonUtilsV2.readResponse(response, Map.class);
+ Number expiryTime = (Number) result.get("expirationTime");
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("<== RangerAdminRESTClient.renewDelegationToken(): newExpiry={}", expiryTime);
+ }
+ return expiryTime.longValue();
} else {
RESTResponse resp = RESTResponse.fromClientResponse(response);
- LOG.error("Error getting resource mappings. Response={}", resp);
- throw new Exception(resp.getMessage());
+ throw new Exception("Failed to renew delegation token: " + (resp != null ? resp.getMessage() : "null response"));
}
+ }
- if(LOG.isDebugEnabled()) {
- LOG.debug("<== RangerAdminRESTClient.getResourceMappingDiffs({}, {}, {})", sourceService, targetService, diffId);
+ @Override
+ public void cancelDelegationToken(final Token token) throws Exception {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("==> RangerAdminRESTClient.cancelDelegationToken()");
}
- return diffs;
+ final UserGroupInformation user = MiscUtil.getUGILoginUser();
+ final boolean isSecureMode = isKerberosEnabled(user);
+ final Cookie sessionId = this.sessionId;
+
+ final Map requestBody = new HashMap<>();
+ requestBody.put("token", token.encodeToUrlString());
+
+ final ClientResponse response;
+ if (isSecureMode) {
+ response = MiscUtil.executePrivilegedAction((PrivilegedExceptionAction) () -> {
+ try {
+ return restClient.put(RangerRESTUtils.REST_URL_DELEGATION_TOKEN_CANCEL, (Object) requestBody, sessionId);
+ } catch (Exception e) {
+ LOG.error("Failed to cancel delegation token", e);
+ }
+ return null;
+ });
+ } else {
+ response = restClient.put(RangerRESTUtils.REST_URL_DELEGATION_TOKEN_CANCEL, (Object) requestBody, sessionId);
+ }
+
+ checkAndResetSessionCookie(response);
+
+ if (response == null || (response.getStatus() != HttpServletResponse.SC_OK && response.getStatus() != HttpServletResponse.SC_NO_CONTENT)) {
+ RESTResponse resp = RESTResponse.fromClientResponse(response);
+ throw new Exception("Failed to cancel delegation token: " + (resp != null ? resp.getMessage() : "null response"));
+ }
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("<== RangerAdminRESTClient.cancelDelegationToken()");
+ }
+ }
+
+ private ClientResponse getWithAuth(String secureRelativeURL, String nonSecureRelativeURL,
+ Map queryParams,
+ UserGroupInformation user, boolean isSecureMode,
+ Cookie sessionId) throws Exception {
+ Token delegationToken = getDelegationTokenFromUGI();
+
+ if (delegationToken != null) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Using delegation token for Ranger auth");
+ }
+ Map headers = new HashMap<>();
+ headers.put(RangerRESTUtils.HEADER_DELEGATION_TOKEN, delegationToken.encodeToUrlString());
+ ClientResponse response = restClient.get(secureRelativeURL, queryParams, sessionId, headers);
+
+ if (response != null && response.getStatus() == HttpServletResponse.SC_UNAUTHORIZED && isSecureMode) {
+ LOG.warn("Delegation token auth failed (HTTP 401). Falling back to Kerberos/SPNEGO");
+
+ response = getWithKerberos(secureRelativeURL, queryParams, sessionId);
+ }
+
+ return response;
+ } else if (isSecureMode) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Using Kerberos auth as user: " + user);
+ }
+ return getWithKerberos(secureRelativeURL, queryParams, sessionId);
+ } else {
+ return restClient.get(nonSecureRelativeURL, queryParams, sessionId);
+ }
+ }
+
+ private ClientResponse getWithKerberos(String relativeURL, Map queryParams,
+ Cookie sessionId) throws Exception {
+ return MiscUtil.executePrivilegedAction((PrivilegedExceptionAction) () -> {
+ try {
+ return restClient.get(relativeURL, queryParams, sessionId);
+ } catch (Exception e) {
+ LOG.error("Kerberos/SPNEGO auth failed: " + e.getMessage());
+ }
+ return null;
+ });
}
private void checkAndResetSessionCookie(ClientResponse response) {
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerDelegationTokenIdentifier.java b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerDelegationTokenIdentifier.java
new file mode 100644
index 0000000000..bcd23b5806
--- /dev/null
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerDelegationTokenIdentifier.java
@@ -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.ranger.plugin.util;
+
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenIdentifier;
+
+public class RangerDelegationTokenIdentifier extends AbstractDelegationTokenIdentifier {
+ public static final Text RANGER_DELEGATION_KIND = new Text("RANGER_DELEGATION_TOKEN");
+
+ public RangerDelegationTokenIdentifier() {
+ }
+
+ public RangerDelegationTokenIdentifier(Text owner, Text renewer, Text realUser) {
+ super(owner, renewer, realUser);
+ }
+
+ @Override
+ public Text getKind() {
+ return RANGER_DELEGATION_KIND;
+ }
+}
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTClient.java b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTClient.java
index e5461c2e68..4df25a8122 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTClient.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTClient.java
@@ -531,6 +531,10 @@ public ClientResponse get(String relativeUrl, Map params) throws
}
public ClientResponse get(String relativeUrl, Map params, Cookie sessionId) throws Exception{
+ return get(relativeUrl, params, sessionId, null);
+ }
+
+ public ClientResponse get(String relativeUrl, Map params, Cookie sessionId, Map headers) throws Exception{
ClientResponse finalResponse = null;
int startIndex = this.lastKnownActiveUrlIndex;
int retryAttempt = 0;
@@ -541,6 +545,12 @@ public ClientResponse get(String relativeUrl, Map params, Cookie
try {
WebResource.Builder br = createWebResource(currentIndex, relativeUrl, params, sessionId);
+ if (headers != null) {
+ for (Map.Entry entry : headers.entrySet()) {
+ br = br.header(entry.getKey(), entry.getValue());
+ }
+ }
+
finalResponse = br.accept(RangerRESTUtils.REST_EXPECTED_MIME_TYPE).type(RangerRESTUtils.REST_MIME_TYPE_JSON).get(ClientResponse.class);
if (finalResponse != null) {
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTUtils.java b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTUtils.java
index 4018193b2d..6c00555b9a 100644
--- a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTUtils.java
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTUtils.java
@@ -89,6 +89,12 @@ public class RangerRESTUtils {
public static final String REST_PARAM_DIFF_ID = "diffId";
+ public static final String REST_URL_DELEGATION_TOKEN = "/service/delegation-token";
+ public static final String REST_URL_DELEGATION_TOKEN_RENEW = "/service/delegation-token/renew";
+ public static final String REST_URL_DELEGATION_TOKEN_CANCEL = "/service/delegation-token/cancel";
+ public static final String REST_PARAM_RENEWER = "renewer";
+ public static final String HEADER_DELEGATION_TOKEN = "X-Delegation-Token-Encoded";
+
public static String hostname;
static {
diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerTokenRenewer.java b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerTokenRenewer.java
new file mode 100644
index 0000000000..60003420d8
--- /dev/null
+++ b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerTokenRenewer.java
@@ -0,0 +1,108 @@
+/*
+ * 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.ranger.plugin.util;
+
+import java.io.IOException;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.security.token.TokenRenewer;
+import org.apache.ranger.admin.client.RangerAdminRESTClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RangerTokenRenewer extends TokenRenewer {
+ private static final Logger LOG = LoggerFactory.getLogger(RangerTokenRenewer.class);
+
+ private static final String CONFIG_PREFIX = "ranger.plugin.delegation-token";
+
+ private volatile RangerAdminRESTClient client;
+
+ @Override
+ public boolean handleKind(Text kind) {
+ return RangerDelegationTokenIdentifier.RANGER_DELEGATION_KIND.equals(kind);
+ }
+
+ @Override
+ public boolean isManaged(Token> token) throws IOException {
+ return handleKind(token.getKind());
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public long renew(Token> token, Configuration conf) throws IOException, InterruptedException {
+ LOG.info("Renewing Ranger delegation token");
+ Token rangerToken = (Token) token;
+ try {
+ RangerAdminRESTClient adminClient = getOrCreateClient(conf, token);
+ long newExpiry = adminClient.renewDelegationToken(rangerToken);
+ LOG.info("Ranger delegation token renewed, new expiry={}", newExpiry);
+ return newExpiry;
+ } catch (InterruptedException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IOException("Failed to renew Ranger delegation token", e);
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void cancel(Token> token, Configuration conf) throws IOException, InterruptedException {
+ LOG.info("Cancelling Ranger delegation token");
+ Token rangerToken = (Token) token;
+ try {
+ RangerAdminRESTClient adminClient = getOrCreateClient(conf, token);
+ adminClient.cancelDelegationToken(rangerToken);
+ LOG.info("Ranger delegation token cancelled");
+ } catch (InterruptedException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IOException("Failed to cancel Ranger delegation token", e);
+ }
+ }
+
+ private RangerAdminRESTClient getOrCreateClient(Configuration conf, Token> token) throws IOException {
+ RangerAdminRESTClient ret = client;
+ if (ret != null) {
+ return ret;
+ }
+
+ synchronized (this) {
+ if (client == null) {
+ String rangerAdminUrl = token.getService().toString();
+ if (rangerAdminUrl == null || rangerAdminUrl.isEmpty()) {
+ throw new IOException("Token does not have Ranger admin URL in service field");
+ }
+
+ conf.set(CONFIG_PREFIX + ".policy.rest.url", rangerAdminUrl);
+
+ try {
+ RangerAdminRESTClient newClient = new RangerAdminRESTClient();
+ newClient.init("ranger", "tokenRenewer", CONFIG_PREFIX, conf);
+ client = newClient;
+ } catch (Exception e) {
+ throw new IOException("Failed to create RangerAdminRESTClient for " + rangerAdminUrl, e);
+ }
+ }
+ return client;
+ }
+ }
+}
diff --git a/agents-common/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier b/agents-common/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier
new file mode 100644
index 0000000000..2b699b087a
--- /dev/null
+++ b/agents-common/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenIdentifier
@@ -0,0 +1,17 @@
+# 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.
+
+org.apache.ranger.plugin.util.RangerDelegationTokenIdentifier
diff --git a/agents-common/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenRenewer b/agents-common/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenRenewer
new file mode 100644
index 0000000000..deb853d8c6
--- /dev/null
+++ b/agents-common/src/main/resources/META-INF/services/org.apache.hadoop.security.token.TokenRenewer
@@ -0,0 +1,17 @@
+# 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.
+
+org.apache.ranger.plugin.util.RangerTokenRenewer
diff --git a/distro/src/main/assembly/admin-web.xml b/distro/src/main/assembly/admin-web.xml
index 944624e427..8f8fef34e1 100644
--- a/distro/src/main/assembly/admin-web.xml
+++ b/distro/src/main/assembly/admin-web.xml
@@ -269,6 +269,7 @@
commons-lang:commons-lang
commons-io:commons-io
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
org.apache.httpcomponents:httpclient:jar:${httpcomponents.httpclient.version}
org.apache.httpcomponents:httpcore:jar:${httpcomponents.httpcore.version}
org.noggit:noggit
diff --git a/distro/src/main/assembly/hbase-agent.xml b/distro/src/main/assembly/hbase-agent.xml
index 02e35e3477..af8a542203 100644
--- a/distro/src/main/assembly/hbase-agent.xml
+++ b/distro/src/main/assembly/hbase-agent.xml
@@ -63,6 +63,7 @@
org.apache.httpcomponents:httpmime:jar:${httpcomponents.httpmime.version}
org.noggit:noggit:jar:${noggit.version}
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
com.kstruct:gethostname4j:jar:${kstruct.gethostname4j.version}
net.java.dev.jna:jna:jar:${jna.version}
net.java.dev.jna:jna-platform:jar:${jna-platform.version}
diff --git a/distro/src/main/assembly/hdfs-agent.xml b/distro/src/main/assembly/hdfs-agent.xml
index 2a3425a5b5..8d66e71e53 100644
--- a/distro/src/main/assembly/hdfs-agent.xml
+++ b/distro/src/main/assembly/hdfs-agent.xml
@@ -89,6 +89,7 @@
org.apache.httpcomponents:httpcore:jar:${httpcomponents.httpcore.version}
org.noggit:noggit:jar:${noggit.version}
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
com.kstruct:gethostname4j:jar:${kstruct.gethostname4j.version}
net.java.dev.jna:jna:jar:${jna.version}
net.java.dev.jna:jna-platform:jar:${jna-platform.version}
diff --git a/distro/src/main/assembly/hive-agent.xml b/distro/src/main/assembly/hive-agent.xml
index c26754bbbe..c6decc3db0 100644
--- a/distro/src/main/assembly/hive-agent.xml
+++ b/distro/src/main/assembly/hive-agent.xml
@@ -59,6 +59,7 @@
org.apache.httpcomponents:httpcore:jar:${httpcomponents.httpcore.version}
org.noggit:noggit:jar:${noggit.version}
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
com.kstruct:gethostname4j:jar:${kstruct.gethostname4j.version}
net.java.dev.jna:jna:jar:${jna.version}
net.java.dev.jna:jna-platform:jar:${jna-platform.version}
diff --git a/distro/src/main/assembly/kms.xml b/distro/src/main/assembly/kms.xml
index e2063a457c..c1f7689b20 100755
--- a/distro/src/main/assembly/kms.xml
+++ b/distro/src/main/assembly/kms.xml
@@ -214,6 +214,7 @@
org.apache.hadoop:hadoop-common:jar:${hadoop.version}
org.apache.hadoop:hadoop-auth:jar:${hadoop.version}
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
org.apache.ranger:ranger-plugins-common
com.kstruct:gethostname4j:jar:${kstruct.gethostname4j.version}
net.java.dev.jna:jna:jar:${jna.version}
@@ -308,6 +309,7 @@
org.noggit:noggit:jar:${noggit.version}
org.apache.zookeeper:zookeeper:jar:${zookeeper.version}
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
com.kstruct:gethostname4j:jar:${kstruct.gethostname4j.version}
net.java.dev.jna:jna:jar:${jna.version}
net.java.dev.jna:jna-platform:jar:${jna-platform.version}
diff --git a/distro/src/main/assembly/knox-agent.xml b/distro/src/main/assembly/knox-agent.xml
index ab21063de8..ef5b788e7f 100644
--- a/distro/src/main/assembly/knox-agent.xml
+++ b/distro/src/main/assembly/knox-agent.xml
@@ -68,6 +68,7 @@
com.fasterxml.jackson.core:jackson-core:jar:${fasterxml.jackson.version}
com.fasterxml.jackson.core:jackson-databind:jar:${fasterxml.jackson.version}
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
com.kstruct:gethostname4j:jar:${kstruct.gethostname4j.version}
net.java.dev.jna:jna:jar:${jna.version}
net.java.dev.jna:jna-platform:jar:${jna-platform.version}
diff --git a/distro/src/main/assembly/plugin-atlas.xml b/distro/src/main/assembly/plugin-atlas.xml
index 9e45d6ba28..5fbd866fd5 100644
--- a/distro/src/main/assembly/plugin-atlas.xml
+++ b/distro/src/main/assembly/plugin-atlas.xml
@@ -64,6 +64,7 @@
org.apache.httpcomponents:httpclient:jar:${httpcomponents.httpclient.version}
org.apache.httpcomponents:httpcore:jar:${httpcomponents.httpcore.version}
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
com.kstruct:gethostname4j:jar:${kstruct.gethostname4j.version}
net.java.dev.jna:jna:jar:${jna.version}
net.java.dev.jna:jna-platform:jar:${jna-platform.version}
diff --git a/distro/src/main/assembly/plugin-elasticsearch.xml b/distro/src/main/assembly/plugin-elasticsearch.xml
index aae97eed3c..b8a6763f5c 100644
--- a/distro/src/main/assembly/plugin-elasticsearch.xml
+++ b/distro/src/main/assembly/plugin-elasticsearch.xml
@@ -79,6 +79,7 @@
com.fasterxml.jackson.jaxrs:jackson-jaxrs-base:jar:${fasterxml.jackson.version}
com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:jar:${fasterxml.jackson.version}
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
commons-codec:commons-codec
com.kstruct:gethostname4j:jar:${kstruct.gethostname4j.version}
net.java.dev.jna:jna:jar:${jna.version}
diff --git a/distro/src/main/assembly/plugin-kafka.xml b/distro/src/main/assembly/plugin-kafka.xml
index ab509aee01..a6075f128a 100644
--- a/distro/src/main/assembly/plugin-kafka.xml
+++ b/distro/src/main/assembly/plugin-kafka.xml
@@ -73,6 +73,7 @@
com.fasterxml.jackson.jaxrs:jackson-jaxrs-base:jar:${fasterxml.jackson.version}
com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:jar:${fasterxml.jackson.version}
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
commons-codec:commons-codec
org.codehaus.woodstox:stax2-api
com.fasterxml.woodstox:woodstox-core
diff --git a/distro/src/main/assembly/plugin-kms.xml b/distro/src/main/assembly/plugin-kms.xml
index 1529b20ad2..9f0dee2feb 100755
--- a/distro/src/main/assembly/plugin-kms.xml
+++ b/distro/src/main/assembly/plugin-kms.xml
@@ -65,6 +65,7 @@
org.noggit:noggit:jar:${noggit.version}
org.apache.zookeeper:zookeeper:jar:${zookeeper.version}
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
com.kstruct:gethostname4j:jar:${kstruct.gethostname4j.version}
net.java.dev.jna:jna:jar:${jna.version}
net.java.dev.jna:jna-platform:jar:${jna-platform.version}
diff --git a/distro/src/main/assembly/plugin-kylin.xml b/distro/src/main/assembly/plugin-kylin.xml
index 6d7b29c8be..da87929930 100644
--- a/distro/src/main/assembly/plugin-kylin.xml
+++ b/distro/src/main/assembly/plugin-kylin.xml
@@ -63,6 +63,7 @@
org.apache.httpcomponents:httpcore:jar:${httpcomponents.httpcore.version}
org.noggit:noggit:jar:${noggit.version}
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
com.kstruct:gethostname4j:jar:${kstruct.gethostname4j.version}
net.java.dev.jna:jna:jar:${jna.version}
net.java.dev.jna:jna-platform:jar:${jna-platform.version}
diff --git a/distro/src/main/assembly/plugin-ozone.xml b/distro/src/main/assembly/plugin-ozone.xml
index e6db381d8b..23dd5ec9c9 100644
--- a/distro/src/main/assembly/plugin-ozone.xml
+++ b/distro/src/main/assembly/plugin-ozone.xml
@@ -99,6 +99,7 @@
org.apache.zookeeper:zookeeper-jute:jar:${zookeeper.version}
org.noggit:noggit:jar:${noggit.version}
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
com.fasterxml.woodstox:woodstox-core:jar:${fasterxml.woodstox.version}
org.codehaus.woodstox:stax2-api:jar:${codehaus.woodstox.stax2api.version}
com.sun.jersey:jersey-core
diff --git a/distro/src/main/assembly/plugin-presto.xml b/distro/src/main/assembly/plugin-presto.xml
index 3818f45565..9cdfc9fb19 100644
--- a/distro/src/main/assembly/plugin-presto.xml
+++ b/distro/src/main/assembly/plugin-presto.xml
@@ -72,6 +72,7 @@
org.apache.httpcomponents:httpcore:jar:${httpcomponents.httpcore.version}
org.noggit:noggit:jar:${noggit.version}
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
com.sun.jersey:jersey-core
com.sun.jersey:jersey-server
commons-cli:commons-cli
diff --git a/distro/src/main/assembly/plugin-solr.xml b/distro/src/main/assembly/plugin-solr.xml
index 72fd83f55f..7cc3933a00 100644
--- a/distro/src/main/assembly/plugin-solr.xml
+++ b/distro/src/main/assembly/plugin-solr.xml
@@ -75,6 +75,7 @@
org.apache.orc:orc-shims:jar:${orc.version}
io.airlift:aircompressor:jar:${aircompressor.version}
org.apache.hadoop.thirdparty:hadoop-shaded-guava:jar:${hadoop-shaded-guava.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
diff --git a/distro/src/main/assembly/plugin-sqoop.xml b/distro/src/main/assembly/plugin-sqoop.xml
index 071b873e16..dd66c095fa 100644
--- a/distro/src/main/assembly/plugin-sqoop.xml
+++ b/distro/src/main/assembly/plugin-sqoop.xml
@@ -59,6 +59,7 @@
org.apache.httpcomponents:httpcore:jar:${httpcomponents.httpcore.version}
org.noggit:noggit:jar:${noggit.version}
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
com.kstruct:gethostname4j:jar:${kstruct.gethostname4j.version}
net.java.dev.jna:jna:jar:${jna.version}
net.java.dev.jna:jna-platform:jar:${jna-platform.version}
diff --git a/distro/src/main/assembly/plugin-trino.xml b/distro/src/main/assembly/plugin-trino.xml
index 5e629276af..28f1d2c990 100644
--- a/distro/src/main/assembly/plugin-trino.xml
+++ b/distro/src/main/assembly/plugin-trino.xml
@@ -60,6 +60,7 @@
org.apache.httpcomponents:httpcore:jar:${httpcomponents.httpcore.version}
org.noggit:noggit:jar:${noggit.version}
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
com.sun.jersey:jersey-core
com.sun.jersey:jersey-server
commons-cli:commons-cli
diff --git a/distro/src/main/assembly/plugin-yarn.xml b/distro/src/main/assembly/plugin-yarn.xml
index 6d469933f8..8dd15597bf 100644
--- a/distro/src/main/assembly/plugin-yarn.xml
+++ b/distro/src/main/assembly/plugin-yarn.xml
@@ -60,6 +60,7 @@
org.apache.httpcomponents:httpcore:jar:${httpcomponents.httpcore.version}
org.noggit:noggit:jar:${noggit.version}
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
com.kstruct:gethostname4j:jar:${kstruct.gethostname4j.version}
net.java.dev.jna:jna:jar:${jna.version}
net.java.dev.jna:jna-platform:jar:${jna-platform.version}
diff --git a/distro/src/main/assembly/storm-agent.xml b/distro/src/main/assembly/storm-agent.xml
index 6c8cbd6a8a..66dc4d3e3c 100644
--- a/distro/src/main/assembly/storm-agent.xml
+++ b/distro/src/main/assembly/storm-agent.xml
@@ -82,6 +82,7 @@
com.fasterxml.jackson.core:jackson-databind
com.fasterxml.jackson.core:jackson-core
org.apache.solr:solr-solrj:jar:${solr.version}
+ org.apache.solr:solr-hadoop-auth-framework:jar:${solr.version}
commons-codec:commons-codec
com.kstruct:gethostname4j:jar:${kstruct.gethostname4j.version}
net.java.dev.jna:jna:jar:${jna.version}
diff --git a/pom.xml b/pom.xml
index fa530c3fe6..8cc3a9fcc7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -199,7 +199,7 @@
2.5
2.0.13
2.0.13
- 8.11.3
+ 8.11.4
reuseReports
jacoco
java
diff --git a/security-admin/db/mysql/patches/076-add-delegation-token-tables.sql b/security-admin/db/mysql/patches/076-add-delegation-token-tables.sql
new file mode 100644
index 0000000000..75b6d081a7
--- /dev/null
+++ b/security-admin/db/mysql/patches/076-add-delegation-token-tables.sql
@@ -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.
+
+DROP PROCEDURE IF EXISTS add_delegation_token_tables;
+
+DELIMITER ;;
+CREATE PROCEDURE add_delegation_token_tables() BEGIN
+
+ IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'x_ranger_dt_master_key') THEN
+ CREATE TABLE x_ranger_dt_master_key (
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ key_id INT NOT NULL,
+ expiry_date BIGINT NOT NULL,
+ key_bytes BLOB NOT NULL,
+ create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE KEY uk_key_id (key_id)
+ ) ;
+ END IF;
+
+ IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'x_ranger_delegation_token') THEN
+ CREATE TABLE x_ranger_delegation_token (
+ id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ sequence_number INT NOT NULL,
+ owner VARCHAR(255) NOT NULL,
+ renewer VARCHAR(255),
+ real_user VARCHAR(255),
+ issue_date BIGINT NOT NULL,
+ max_date BIGINT NOT NULL,
+ renew_date BIGINT NOT NULL,
+ master_key_id INT NOT NULL,
+ token_password BLOB NOT NULL,
+ create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE KEY uk_sequence_number (sequence_number)
+ ) ;
+ END IF;
+
+END;;
+
+DELIMITER ;
+
+CALL add_delegation_token_tables();
+
+DROP PROCEDURE IF EXISTS add_delegation_token_tables;
diff --git a/security-admin/db/oracle/patches/076-add-delegation-token-tables.sql b/security-admin/db/oracle/patches/076-add-delegation-token-tables.sql
new file mode 100644
index 0000000000..bc333cbcfb
--- /dev/null
+++ b/security-admin/db/oracle/patches/076-add-delegation-token-tables.sql
@@ -0,0 +1,48 @@
+-- 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.
+
+call spdropsequence('X_RANGER_DT_MASTER_KEY_SEQ');
+CREATE SEQUENCE X_RANGER_DT_MASTER_KEY_SEQ START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE;
+
+call spdropsequence('X_RANGER_DELEGATION_TOKEN_SEQ');
+CREATE SEQUENCE X_RANGER_DELEGATION_TOKEN_SEQ START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE;
+
+CREATE TABLE x_ranger_dt_master_key (
+ id NUMBER(20) NOT NULL,
+ key_id NUMBER(10) NOT NULL,
+ expiry_date NUMBER(20) NOT NULL,
+ key_bytes BLOB NOT NULL,
+ create_time DATE DEFAULT SYSDATE,
+ PRIMARY KEY (id),
+ CONSTRAINT uk_dt_mk_key_id UNIQUE (key_id)
+);
+
+CREATE TABLE x_ranger_delegation_token (
+ id NUMBER(20) NOT NULL,
+ sequence_number NUMBER(10) NOT NULL,
+ owner VARCHAR2(255) NOT NULL,
+ renewer VARCHAR2(255),
+ real_user VARCHAR2(255),
+ issue_date NUMBER(20) NOT NULL,
+ max_date NUMBER(20) NOT NULL,
+ renew_date NUMBER(20) NOT NULL,
+ master_key_id NUMBER(10) NOT NULL,
+ token_password BLOB NOT NULL,
+ create_time DATE DEFAULT SYSDATE,
+ PRIMARY KEY (id),
+ CONSTRAINT uk_dt_seq_number UNIQUE (sequence_number)
+);
+
+commit;
diff --git a/security-admin/db/postgres/patches/076-add-delegation-token-tables.sql b/security-admin/db/postgres/patches/076-add-delegation-token-tables.sql
new file mode 100644
index 0000000000..258c4d156a
--- /dev/null
+++ b/security-admin/db/postgres/patches/076-add-delegation-token-tables.sql
@@ -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.
+
+CREATE SEQUENCE IF NOT EXISTS x_ranger_dt_master_key_seq;
+
+CREATE TABLE IF NOT EXISTS x_ranger_dt_master_key (
+ id BIGINT DEFAULT nextval('x_ranger_dt_master_key_seq'::regclass) PRIMARY KEY,
+ key_id INT NOT NULL UNIQUE,
+ expiry_date BIGINT NOT NULL,
+ key_bytes BYTEA NOT NULL,
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE SEQUENCE IF NOT EXISTS x_ranger_delegation_token_seq;
+
+CREATE TABLE IF NOT EXISTS x_ranger_delegation_token (
+ id BIGINT DEFAULT nextval('x_ranger_delegation_token_seq'::regclass) PRIMARY KEY,
+ sequence_number INT NOT NULL UNIQUE,
+ owner VARCHAR(255) NOT NULL,
+ renewer VARCHAR(255),
+ real_user VARCHAR(255),
+ issue_date BIGINT NOT NULL,
+ max_date BIGINT NOT NULL,
+ renew_date BIGINT NOT NULL,
+ master_key_id INT NOT NULL,
+ token_password BYTEA NOT NULL,
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
diff --git a/security-admin/db/sqlanywhere/patches/076-add-delegation-token-tables.sql b/security-admin/db/sqlanywhere/patches/076-add-delegation-token-tables.sql
new file mode 100644
index 0000000000..4d92fb056b
--- /dev/null
+++ b/security-admin/db/sqlanywhere/patches/076-add-delegation-token-tables.sql
@@ -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.
+
+call dbo.removeForeignKeysAndTable('x_ranger_dt_master_key')
+GO
+
+CREATE TABLE dbo.x_ranger_dt_master_key (
+ id bigint IDENTITY NOT NULL,
+ key_id int NOT NULL,
+ expiry_date bigint NOT NULL,
+ key_bytes varbinary(max) NOT NULL,
+ create_time datetime DEFAULT CURRENT_TIMESTAMP,
+ CONSTRAINT x_ranger_dt_master_key_PK_id PRIMARY KEY CLUSTERED(id),
+ CONSTRAINT x_ranger_dt_master_key_UK_key_id UNIQUE(key_id)
+) GO
+
+call dbo.removeForeignKeysAndTable('x_ranger_delegation_token')
+GO
+
+CREATE TABLE dbo.x_ranger_delegation_token (
+ id bigint IDENTITY NOT NULL,
+ sequence_number int NOT NULL,
+ owner varchar(255) NOT NULL,
+ renewer varchar(255),
+ real_user varchar(255),
+ issue_date bigint NOT NULL,
+ max_date bigint NOT NULL,
+ renew_date bigint NOT NULL,
+ master_key_id int NOT NULL,
+ token_password varbinary(max) NOT NULL,
+ create_time datetime DEFAULT CURRENT_TIMESTAMP,
+ CONSTRAINT x_ranger_delegation_token_PK_id PRIMARY KEY CLUSTERED(id),
+ CONSTRAINT x_ranger_delegation_token_UK_seq UNIQUE(sequence_number)
+) GO
+EXIT
diff --git a/security-admin/db/sqlserver/patches/076-add-delegation-token-tables.sql b/security-admin/db/sqlserver/patches/076-add-delegation-token-tables.sql
new file mode 100644
index 0000000000..fc2fd10675
--- /dev/null
+++ b/security-admin/db/sqlserver/patches/076-add-delegation-token-tables.sql
@@ -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.
+
+SET ANSI_NULLS ON
+SET QUOTED_IDENTIFIER ON
+SET ANSI_PADDING ON
+GO
+
+IF (OBJECT_ID('x_ranger_dt_master_key') IS NULL)
+BEGIN
+ CREATE TABLE [dbo].[x_ranger_dt_master_key] (
+ [id] [bigint] IDENTITY(1,1) NOT NULL,
+ [key_id] [int] NOT NULL,
+ [expiry_date] [bigint] NOT NULL,
+ [key_bytes] [varbinary](max) NOT NULL,
+ [create_time] [datetime] DEFAULT GETDATE(),
+ PRIMARY KEY CLUSTERED ([id] ASC),
+ CONSTRAINT [x_ranger_dt_mk_UK_key_id] UNIQUE NONCLUSTERED ([key_id] ASC)
+ )
+END
+GO
+
+IF (OBJECT_ID('x_ranger_delegation_token') IS NULL)
+BEGIN
+ CREATE TABLE [dbo].[x_ranger_delegation_token] (
+ [id] [bigint] IDENTITY(1,1) NOT NULL,
+ [sequence_number] [int] NOT NULL,
+ [owner] [varchar](255) NOT NULL,
+ [renewer] [varchar](255) NULL,
+ [real_user] [varchar](255) NULL,
+ [issue_date] [bigint] NOT NULL,
+ [max_date] [bigint] NOT NULL,
+ [renew_date] [bigint] NOT NULL,
+ [master_key_id] [int] NOT NULL,
+ [token_password] [varbinary](max) NOT NULL,
+ [create_time] [datetime] DEFAULT GETDATE(),
+ PRIMARY KEY CLUSTERED ([id] ASC),
+ CONSTRAINT [x_ranger_dt_UK_seq_number] UNIQUE NONCLUSTERED ([sequence_number] ASC)
+ )
+END
+GO
+
+exit
diff --git a/security-admin/src/main/java/org/apache/ranger/biz/RangerDelegationTokenSecretManager.java b/security-admin/src/main/java/org/apache/ranger/biz/RangerDelegationTokenSecretManager.java
new file mode 100644
index 0000000000..08789c1123
--- /dev/null
+++ b/security-admin/src/main/java/org/apache/ranger/biz/RangerDelegationTokenSecretManager.java
@@ -0,0 +1,323 @@
+/*
+ * 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.ranger.biz;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager;
+import org.apache.hadoop.security.token.delegation.DelegationKey;
+import org.apache.ranger.common.PropertiesUtil;
+import org.apache.ranger.db.RangerDaoManager;
+import org.apache.ranger.entity.XXRangerDTMasterKey;
+import org.apache.ranger.entity.XXRangerDelegationToken;
+import org.apache.ranger.plugin.util.RangerDelegationTokenIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
+
+@Component
+public class RangerDelegationTokenSecretManager
+ extends AbstractDelegationTokenSecretManager {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RangerDelegationTokenSecretManager.class);
+
+ private static final long DEFAULT_KEY_UPDATE_INTERVAL_SEC = 86400;
+ private static final long DEFAULT_TOKEN_MAX_LIFETIME_SEC = 604800;
+ private static final long DEFAULT_TOKEN_RENEW_INTERVAL_SEC = 86400;
+ private static final long DEFAULT_TOKEN_REMOVER_SCAN_INTERVAL_SEC = 3600;
+
+ @Autowired
+ private RangerDaoManager daoManager;
+
+ @Autowired
+ @Qualifier("transactionManager")
+ private PlatformTransactionManager txManager;
+
+ private final boolean enabled;
+ private volatile boolean started = false;
+
+ public RangerDelegationTokenSecretManager() {
+ super(
+ PropertiesUtil.getLongProperty("ranger.admin.delegation-token.key.update-interval.sec", DEFAULT_KEY_UPDATE_INTERVAL_SEC) * 1000,
+ PropertiesUtil.getLongProperty("ranger.admin.delegation-token.max-lifetime.sec", DEFAULT_TOKEN_MAX_LIFETIME_SEC) * 1000,
+ PropertiesUtil.getLongProperty("ranger.admin.delegation-token.renew-interval.sec", DEFAULT_TOKEN_RENEW_INTERVAL_SEC) * 1000,
+ PropertiesUtil.getLongProperty("ranger.admin.delegation-token.removal-scan-interval.sec", DEFAULT_TOKEN_REMOVER_SCAN_INTERVAL_SEC) * 1000
+ );
+ this.enabled = PropertiesUtil.getBooleanProperty("ranger.admin.delegation-token.enabled", false);
+ }
+
+ @PostConstruct
+ public void init() {
+ if (!enabled) {
+ LOG.info("Ranger Delegation Token support is disabled");
+ return;
+ }
+ LOG.info("Initializing RangerDelegationTokenSecretManager");
+ try {
+ loadFromDB();
+ startThreads();
+ started = true;
+ LOG.info("RangerDelegationTokenSecretManager started successfully");
+ } catch (IOException e) {
+ LOG.error("Failed to start RangerDelegationTokenSecretManager. " +
+ "Delegation token support will be unavailable until restart.", e);
+ }
+ }
+
+ @PreDestroy
+ public void shutdown() {
+ if (enabled) {
+ LOG.info("Shutting down RangerDelegationTokenSecretManager");
+ try {
+ stopThreads();
+ } catch (Exception e) {
+ LOG.warn("Error shutting down RangerDelegationTokenSecretManager", e);
+ }
+ }
+ }
+
+ public boolean isEnabled() {
+ return enabled && started;
+ }
+
+ @Override
+ public RangerDelegationTokenIdentifier createIdentifier() {
+ return new RangerDelegationTokenIdentifier();
+ }
+
+ public Token createDelegationToken(String owner, String renewer) throws IOException {
+ Text ownerText = new Text(owner);
+ Text renewerText = renewer != null ? new Text(renewer) : null;
+ RangerDelegationTokenIdentifier ident = new RangerDelegationTokenIdentifier(ownerText, renewerText, ownerText);
+
+ Token token = new Token<>(ident, this);
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Created delegation token for owner={}, renewer={}, sequenceNumber={}", owner, renewer, ident.getSequenceNumber());
+ }
+
+ return token;
+ }
+
+ public long renewDelegationToken(Token token, String callerUser) throws IOException {
+ return renewToken(token, callerUser);
+ }
+
+ public void cancelDelegationToken(Token token, String callerUser) throws IOException {
+ cancelToken(token, callerUser);
+ }
+
+ public RangerDelegationTokenIdentifier verifyToken(Token token) throws IOException {
+ ByteArrayInputStream buf = new ByteArrayInputStream(token.getIdentifier());
+ DataInputStream in = new DataInputStream(buf);
+
+ RangerDelegationTokenIdentifier ident = createIdentifier();
+ ident.readFields(in);
+
+ byte[] expectedPassword = retrievePassword(ident);
+
+ if (expectedPassword == null) {
+ throw new IOException("Token is invalid or expired");
+ }
+
+ if (!MessageDigest.isEqual(expectedPassword, token.getPassword())) {
+ throw new IOException("Token password does not match");
+ }
+
+ return ident;
+ }
+
+ @Override
+ protected void storeNewMasterKey(DelegationKey key) throws IOException {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("==> storeNewMasterKey(keyId={})", key.getKeyId());
+ }
+ try {
+ byte[] keyBytes = serializeDelegationKey(key);
+ executeInTransaction(status -> {
+ XXRangerDTMasterKey entity = new XXRangerDTMasterKey();
+ entity.setKeyId(key.getKeyId());
+ entity.setExpiryDate(key.getExpiryDate());
+ entity.setKeyBytes(keyBytes);
+ daoManager.getXXRangerDTMasterKey().create(entity);
+ return null;
+ });
+ } catch (Exception e) {
+ throw new IOException("Failed to store master key: keyId=" + key.getKeyId(), e);
+ }
+ }
+
+ @Override
+ protected void removeStoredMasterKey(DelegationKey key) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("==> removeStoredMasterKey(keyId={})", key.getKeyId());
+ }
+ try {
+ executeInTransaction(status -> {
+ daoManager.getXXRangerDTMasterKey().deleteByKeyId(key.getKeyId());
+ return null;
+ });
+ } catch (Exception e) {
+ LOG.warn("Failed to remove master key: keyId=" + key.getKeyId(), e);
+ }
+ }
+
+ @Override
+ protected void storeNewToken(RangerDelegationTokenIdentifier ident, long renewDate) throws IOException {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("==> storeNewToken(seqNum={}, owner={})", ident.getSequenceNumber(), ident.getOwner());
+ }
+ try {
+ byte[] tokenPassword = retrievePassword(ident);
+ executeInTransaction(status -> {
+ XXRangerDelegationToken entity = new XXRangerDelegationToken();
+ entity.setSequenceNumber(ident.getSequenceNumber());
+ entity.setOwner(ident.getOwner().toString());
+ entity.setRenewer(ident.getRenewer() != null ? ident.getRenewer().toString() : null);
+ entity.setRealUser(ident.getRealUser() != null ? ident.getRealUser().toString() : null);
+ entity.setIssueDate(ident.getIssueDate());
+ entity.setMaxDate(ident.getMaxDate());
+ entity.setRenewDate(renewDate);
+ entity.setMasterKeyId(ident.getMasterKeyId());
+ entity.setTokenPassword(tokenPassword);
+ daoManager.getXXRangerDelegationToken().create(entity);
+ return null;
+ });
+ } catch (Exception e) {
+ throw new IOException("Failed to store token: seqNum=" + ident.getSequenceNumber(), e);
+ }
+ }
+
+ @Override
+ protected void updateStoredToken(RangerDelegationTokenIdentifier ident, long renewDate) throws IOException {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("==> updateStoredToken(seqNum={}, renewDate={})", ident.getSequenceNumber(), renewDate);
+ }
+ try {
+ executeInTransaction(status -> {
+ daoManager.getXXRangerDelegationToken().updateRenewDate(ident.getSequenceNumber(), renewDate);
+ return null;
+ });
+ } catch (Exception e) {
+ throw new IOException("Failed to update token: seqNum=" + ident.getSequenceNumber(), e);
+ }
+ }
+
+ @Override
+ protected void removeStoredToken(RangerDelegationTokenIdentifier ident) throws IOException {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("==> removeStoredToken(seqNum={})", ident.getSequenceNumber());
+ }
+ try {
+ executeInTransaction(status -> {
+ daoManager.getXXRangerDelegationToken().deleteBySequenceNumber(ident.getSequenceNumber());
+ return null;
+ });
+ } catch (Exception e) {
+ LOG.warn("Failed to remove token: seqNum=" + ident.getSequenceNumber(), e);
+ }
+ }
+
+ protected T executeInTransaction(TransactionCallback action) {
+ TransactionTemplate txTemplate = new TransactionTemplate(txManager);
+ txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
+ return txTemplate.execute(action);
+ }
+
+ private void loadFromDB() {
+ LOG.info("Loading delegation token state from DB");
+
+ executeInTransaction(status -> {
+ List masterKeys = daoManager.getXXRangerDTMasterKey().findAll();
+ if (masterKeys != null) {
+ for (XXRangerDTMasterKey entity : masterKeys) {
+ try {
+ DelegationKey key = deserializeDelegationKey(entity.getKeyBytes());
+ addKey(key);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Loaded master key: keyId={}", key.getKeyId());
+ }
+ } catch (Exception e) {
+ LOG.error("Failed to load master key: keyId=" + entity.getKeyId(), e);
+ }
+ }
+ LOG.info("Loaded {} master keys from DB", masterKeys.size());
+ }
+
+ List tokens = daoManager.getXXRangerDelegationToken().findAll();
+ if (tokens != null) {
+ for (XXRangerDelegationToken entity : tokens) {
+ try {
+ RangerDelegationTokenIdentifier ident = new RangerDelegationTokenIdentifier(
+ new Text(entity.getOwner()),
+ entity.getRenewer() != null ? new Text(entity.getRenewer()) : null,
+ entity.getRealUser() != null ? new Text(entity.getRealUser()) : null
+ );
+ ident.setIssueDate(entity.getIssueDate());
+ ident.setMaxDate(entity.getMaxDate());
+ ident.setSequenceNumber(entity.getSequenceNumber());
+ ident.setMasterKeyId(entity.getMasterKeyId());
+ addPersistedDelegationToken(ident, entity.getRenewDate());
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Loaded delegation token: seqNum={}, owner={}", entity.getSequenceNumber(), entity.getOwner());
+ }
+ } catch (Exception e) {
+ LOG.error("Failed to load delegation token: seqNum=" + entity.getSequenceNumber(), e);
+ }
+ }
+ LOG.info("Loaded {} delegation tokens from DB", tokens.size());
+ }
+ return null;
+ });
+ }
+
+ private static byte[] serializeDelegationKey(DelegationKey key) throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(bos);
+ key.write(out);
+ out.flush();
+ return bos.toByteArray();
+ }
+
+ private static DelegationKey deserializeDelegationKey(byte[] data) throws IOException {
+ ByteArrayInputStream bis = new ByteArrayInputStream(data);
+ DataInputStream in = new DataInputStream(bis);
+ DelegationKey key = new DelegationKey();
+ key.readFields(in);
+ return key;
+ }
+}
diff --git a/security-admin/src/main/java/org/apache/ranger/db/RangerDaoManagerBase.java b/security-admin/src/main/java/org/apache/ranger/db/RangerDaoManagerBase.java
index a268cb9113..34a6eceee4 100644
--- a/security-admin/src/main/java/org/apache/ranger/db/RangerDaoManagerBase.java
+++ b/security-admin/src/main/java/org/apache/ranger/db/RangerDaoManagerBase.java
@@ -325,5 +325,8 @@ public XXPolicyRefAccessTypeDao getXXPolicyRefAccessType() {
public XXRMSResourceMappingDao getXXRMSResourceMapping() { return new XXRMSResourceMappingDao(this); }
public XXResourceMappingDiffDao getXXResourceMappingDiff() { return new XXResourceMappingDiffDao(this); }
+
+ public XXRangerDTMasterKeyDao getXXRangerDTMasterKey() { return new XXRangerDTMasterKeyDao(this); }
+ public XXRangerDelegationTokenDao getXXRangerDelegationToken() { return new XXRangerDelegationTokenDao(this); }
}
diff --git a/security-admin/src/main/java/org/apache/ranger/db/XXRangerDTMasterKeyDao.java b/security-admin/src/main/java/org/apache/ranger/db/XXRangerDTMasterKeyDao.java
new file mode 100644
index 0000000000..da1f6ed945
--- /dev/null
+++ b/security-admin/src/main/java/org/apache/ranger/db/XXRangerDTMasterKeyDao.java
@@ -0,0 +1,67 @@
+/*
+ * 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.ranger.db;
+
+import java.util.List;
+
+import javax.persistence.NoResultException;
+
+import org.apache.ranger.common.DateUtil;
+import org.apache.ranger.common.db.BaseDao;
+import org.apache.ranger.entity.XXRangerDTMasterKey;
+import org.springframework.stereotype.Service;
+
+@Service
+public class XXRangerDTMasterKeyDao extends BaseDao {
+
+ public XXRangerDTMasterKeyDao(RangerDaoManagerBase daoManager) {
+ super(daoManager);
+ }
+
+ @Override
+ public XXRangerDTMasterKey create(XXRangerDTMasterKey obj) {
+ obj.setCreateTime(DateUtil.getUTCDate());
+ return super.create(obj);
+ }
+
+ public XXRangerDTMasterKey findByKeyId(int keyId) {
+ try {
+ return getEntityManager()
+ .createQuery("SELECT obj FROM XXRangerDTMasterKey obj WHERE obj.keyId = :keyId", tClass)
+ .setParameter("keyId", keyId)
+ .getSingleResult();
+ } catch (NoResultException e) {
+ return null;
+ }
+ }
+
+ public List findAll() {
+ return getEntityManager()
+ .createQuery("SELECT obj FROM XXRangerDTMasterKey obj ORDER BY obj.keyId", tClass)
+ .getResultList();
+ }
+
+ public void deleteByKeyId(int keyId) {
+ getEntityManager()
+ .createQuery("DELETE FROM XXRangerDTMasterKey obj WHERE obj.keyId = :keyId")
+ .setParameter("keyId", keyId)
+ .executeUpdate();
+ }
+}
diff --git a/security-admin/src/main/java/org/apache/ranger/db/XXRangerDelegationTokenDao.java b/security-admin/src/main/java/org/apache/ranger/db/XXRangerDelegationTokenDao.java
new file mode 100644
index 0000000000..436a8fb81b
--- /dev/null
+++ b/security-admin/src/main/java/org/apache/ranger/db/XXRangerDelegationTokenDao.java
@@ -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.ranger.db;
+
+import java.util.List;
+
+import javax.persistence.NoResultException;
+
+import org.apache.ranger.common.DateUtil;
+import org.apache.ranger.common.db.BaseDao;
+import org.apache.ranger.entity.XXRangerDelegationToken;
+import org.springframework.stereotype.Service;
+
+@Service
+public class XXRangerDelegationTokenDao extends BaseDao {
+
+ public XXRangerDelegationTokenDao(RangerDaoManagerBase daoManager) {
+ super(daoManager);
+ }
+
+ @Override
+ public XXRangerDelegationToken create(XXRangerDelegationToken obj) {
+ obj.setCreateTime(DateUtil.getUTCDate());
+ return super.create(obj);
+ }
+
+ public XXRangerDelegationToken findBySequenceNumber(int sequenceNumber) {
+ try {
+ return getEntityManager()
+ .createQuery("SELECT obj FROM XXRangerDelegationToken obj WHERE obj.sequenceNumber = :seqNum", tClass)
+ .setParameter("seqNum", sequenceNumber)
+ .getSingleResult();
+ } catch (NoResultException e) {
+ return null;
+ }
+ }
+
+ public List findAll() {
+ return getEntityManager()
+ .createQuery("SELECT obj FROM XXRangerDelegationToken obj", tClass)
+ .getResultList();
+ }
+
+ public void deleteBySequenceNumber(int sequenceNumber) {
+ getEntityManager()
+ .createQuery("DELETE FROM XXRangerDelegationToken obj WHERE obj.sequenceNumber = :seqNum")
+ .setParameter("seqNum", sequenceNumber)
+ .executeUpdate();
+ }
+
+ public void updateRenewDate(int sequenceNumber, long renewDate) {
+ getEntityManager()
+ .createQuery("UPDATE XXRangerDelegationToken obj SET obj.renewDate = :renewDate WHERE obj.sequenceNumber = :seqNum")
+ .setParameter("renewDate", renewDate)
+ .setParameter("seqNum", sequenceNumber)
+ .executeUpdate();
+ }
+}
diff --git a/security-admin/src/main/java/org/apache/ranger/entity/XXAuthSession.java b/security-admin/src/main/java/org/apache/ranger/entity/XXAuthSession.java
index c03bb11296..bd838bea9c 100644
--- a/security-admin/src/main/java/org/apache/ranger/entity/XXAuthSession.java
+++ b/security-admin/src/main/java/org/apache/ranger/entity/XXAuthSession.java
@@ -123,11 +123,15 @@ public Long getId() {
* AUTH_TYPE_TRUSTED_PROXY is an element of enum AuthType. Its value is "AUTH_TYPE_TRUSTED_PROXY".
*/
public static final int AUTH_TYPE_TRUSTED_PROXY = 4;
+ /**
+ * AUTH_TYPE_DELEGATION_TOKEN is an element of enum AuthType. Its value is "AUTH_TYPE_DELEGATION_TOKEN".
+ */
+ public static final int AUTH_TYPE_DELEGATION_TOKEN = 5;
/**
* Max value for enum AuthType_MAX
*/
- public static final int AuthType_MAX = 4;
+ public static final int AuthType_MAX = 5;
diff --git a/security-admin/src/main/java/org/apache/ranger/entity/XXRangerDTMasterKey.java b/security-admin/src/main/java/org/apache/ranger/entity/XXRangerDTMasterKey.java
new file mode 100644
index 0000000000..2844da72d1
--- /dev/null
+++ b/security-admin/src/main/java/org/apache/ranger/entity/XXRangerDTMasterKey.java
@@ -0,0 +1,107 @@
+/*
+ * 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.ranger.entity;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Lob;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+@Entity
+@Table(name = "x_ranger_dt_master_key")
+public class XXRangerDTMasterKey implements java.io.Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @SequenceGenerator(name = "X_RANGER_DT_MASTER_KEY_SEQ", sequenceName = "X_RANGER_DT_MASTER_KEY_SEQ", allocationSize = 1)
+ @GeneratedValue(strategy = GenerationType.AUTO, generator = "X_RANGER_DT_MASTER_KEY_SEQ")
+ @Column(name = "id")
+ protected Long id;
+
+ @Column(name = "key_id", nullable = false, unique = true)
+ protected Integer keyId;
+
+ @Column(name = "expiry_date", nullable = false)
+ protected Long expiryDate;
+
+ @Lob
+ @Column(name = "key_bytes", nullable = false)
+ protected byte[] keyBytes;
+
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name = "create_time")
+ protected Date createTime;
+
+ public XXRangerDTMasterKey() {
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Integer getKeyId() {
+ return keyId;
+ }
+
+ public void setKeyId(Integer keyId) {
+ this.keyId = keyId;
+ }
+
+ public Long getExpiryDate() {
+ return expiryDate;
+ }
+
+ public void setExpiryDate(Long expiryDate) {
+ this.expiryDate = expiryDate;
+ }
+
+ public byte[] getKeyBytes() {
+ return keyBytes;
+ }
+
+ public void setKeyBytes(byte[] keyBytes) {
+ this.keyBytes = keyBytes;
+ }
+
+ public Date getCreateTime() {
+ return createTime;
+ }
+
+ public void setCreateTime(Date createTime) {
+ this.createTime = createTime;
+ }
+
+ @Override
+ public String toString() {
+ return "XXRangerDTMasterKey [id=" + id + ", keyId=" + keyId + ", expiryDate=" + expiryDate + "]";
+ }
+}
diff --git a/security-admin/src/main/java/org/apache/ranger/entity/XXRangerDelegationToken.java b/security-admin/src/main/java/org/apache/ranger/entity/XXRangerDelegationToken.java
new file mode 100644
index 0000000000..9406930277
--- /dev/null
+++ b/security-admin/src/main/java/org/apache/ranger/entity/XXRangerDelegationToken.java
@@ -0,0 +1,175 @@
+/*
+ * 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.ranger.entity;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Lob;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+@Entity
+@Table(name = "x_ranger_delegation_token")
+public class XXRangerDelegationToken implements java.io.Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @Id
+ @SequenceGenerator(name = "X_RANGER_DELEGATION_TOKEN_SEQ", sequenceName = "X_RANGER_DELEGATION_TOKEN_SEQ", allocationSize = 1)
+ @GeneratedValue(strategy = GenerationType.AUTO, generator = "X_RANGER_DELEGATION_TOKEN_SEQ")
+ @Column(name = "id")
+ protected Long id;
+
+ @Column(name = "sequence_number", nullable = false, unique = true)
+ protected Integer sequenceNumber;
+
+ @Column(name = "owner", nullable = false)
+ protected String owner;
+
+ @Column(name = "renewer")
+ protected String renewer;
+
+ @Column(name = "real_user")
+ protected String realUser;
+
+ @Column(name = "issue_date", nullable = false)
+ protected Long issueDate;
+
+ @Column(name = "max_date", nullable = false)
+ protected Long maxDate;
+
+ @Column(name = "renew_date", nullable = false)
+ protected Long renewDate;
+
+ @Column(name = "master_key_id", nullable = false)
+ protected Integer masterKeyId;
+
+ @Lob
+ @Column(name = "token_password", nullable = false)
+ protected byte[] tokenPassword;
+
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name = "create_time")
+ protected Date createTime;
+
+ public XXRangerDelegationToken() {
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Integer getSequenceNumber() {
+ return sequenceNumber;
+ }
+
+ public void setSequenceNumber(Integer sequenceNumber) {
+ this.sequenceNumber = sequenceNumber;
+ }
+
+ public String getOwner() {
+ return owner;
+ }
+
+ public void setOwner(String owner) {
+ this.owner = owner;
+ }
+
+ public String getRenewer() {
+ return renewer;
+ }
+
+ public void setRenewer(String renewer) {
+ this.renewer = renewer;
+ }
+
+ public String getRealUser() {
+ return realUser;
+ }
+
+ public void setRealUser(String realUser) {
+ this.realUser = realUser;
+ }
+
+ public Long getIssueDate() {
+ return issueDate;
+ }
+
+ public void setIssueDate(Long issueDate) {
+ this.issueDate = issueDate;
+ }
+
+ public Long getMaxDate() {
+ return maxDate;
+ }
+
+ public void setMaxDate(Long maxDate) {
+ this.maxDate = maxDate;
+ }
+
+ public Long getRenewDate() {
+ return renewDate;
+ }
+
+ public void setRenewDate(Long renewDate) {
+ this.renewDate = renewDate;
+ }
+
+ public Integer getMasterKeyId() {
+ return masterKeyId;
+ }
+
+ public void setMasterKeyId(Integer masterKeyId) {
+ this.masterKeyId = masterKeyId;
+ }
+
+ public byte[] getTokenPassword() {
+ return tokenPassword;
+ }
+
+ public void setTokenPassword(byte[] tokenPassword) {
+ this.tokenPassword = tokenPassword;
+ }
+
+ public Date getCreateTime() {
+ return createTime;
+ }
+
+ public void setCreateTime(Date createTime) {
+ this.createTime = createTime;
+ }
+
+ @Override
+ public String toString() {
+ return "XXRangerDelegationToken [id=" + id + ", sequenceNumber=" + sequenceNumber
+ + ", owner=" + owner + ", renewer=" + renewer + ", issueDate=" + issueDate
+ + ", maxDate=" + maxDate + ", renewDate=" + renewDate + ", masterKeyId=" + masterKeyId + "]";
+ }
+}
diff --git a/security-admin/src/main/java/org/apache/ranger/rest/DelegationTokenREST.java b/security-admin/src/main/java/org/apache/ranger/rest/DelegationTokenREST.java
new file mode 100644
index 0000000000..9ca77e9731
--- /dev/null
+++ b/security-admin/src/main/java/org/apache/ranger/rest/DelegationTokenREST.java
@@ -0,0 +1,205 @@
+/*
+ * 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.ranger.rest;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+
+import org.apache.hadoop.security.AccessControlException;
+import org.apache.hadoop.security.token.Token;
+import org.apache.ranger.biz.RangerBizUtil;
+import org.apache.ranger.biz.RangerDelegationTokenSecretManager;
+import org.apache.ranger.common.RESTErrorUtil;
+import org.apache.ranger.plugin.util.RangerDelegationTokenIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+@Path("delegation-token")
+@Component
+@Scope("request")
+@Transactional(propagation = Propagation.REQUIRES_NEW)
+public class DelegationTokenREST {
+ private static final Logger LOG = LoggerFactory.getLogger(DelegationTokenREST.class);
+
+ private static final int MAX_RENEWER_LENGTH = 255;
+
+ @Autowired
+ RangerDelegationTokenSecretManager secretManager;
+
+ @Autowired
+ RangerBizUtil bizUtil;
+
+ @Autowired
+ RESTErrorUtil restErrorUtil;
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public Map getDelegationToken(@QueryParam("renewer") String renewer,
+ @Context HttpServletRequest request) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("==> DelegationTokenREST.getDelegationToken(renewer={})", renewer);
+ }
+
+ if (!secretManager.isEnabled()) {
+ throw restErrorUtil.createRESTException(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
+ "Delegation token support is not enabled", true);
+ }
+
+ String authenticatedUser = bizUtil.getCurrentUserLoginId();
+ if (authenticatedUser == null) {
+ throw restErrorUtil.createRESTException(HttpServletResponse.SC_UNAUTHORIZED,
+ "Authentication required", true);
+ }
+
+ if (renewer != null && renewer.length() > MAX_RENEWER_LENGTH) {
+ throw restErrorUtil.createRESTException(HttpServletResponse.SC_BAD_REQUEST,
+ "Renewer name is too long", true);
+ }
+
+ try {
+ Token token = secretManager.createDelegationToken(authenticatedUser, renewer);
+
+ Map result = new HashMap<>();
+ result.put("urlString", token.encodeToUrlString());
+
+ LOG.info("Delegation token created for user={}, renewer={}", authenticatedUser, renewer);
+
+ return result;
+ } catch (Exception e) {
+ LOG.error("Failed to create delegation token for user=" + authenticatedUser, e);
+ throw restErrorUtil.createRESTException("Failed to create delegation token");
+ }
+ }
+
+ @PUT
+ @Path("/renew")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Map renewDelegationToken(Map requestBody,
+ @Context HttpServletRequest request) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("==> DelegationTokenREST.renewDelegationToken()");
+ }
+
+ if (!secretManager.isEnabled()) {
+ throw restErrorUtil.createRESTException(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
+ "Delegation token support is not enabled", true);
+ }
+
+ String tokenEncoded = requestBody != null ? requestBody.get("token") : null;
+ if (tokenEncoded == null || tokenEncoded.isEmpty()) {
+ throw restErrorUtil.createRESTException(HttpServletResponse.SC_BAD_REQUEST,
+ "Token parameter is required", true);
+ }
+
+ String authenticatedUser = bizUtil.getCurrentUserLoginId();
+ if (authenticatedUser == null) {
+ throw restErrorUtil.createRESTException(HttpServletResponse.SC_UNAUTHORIZED,
+ "Authentication required", true);
+ }
+
+ try {
+ Token token = new Token<>();
+ token.decodeFromUrlString(tokenEncoded);
+
+ long newExpiryTime = secretManager.renewDelegationToken(token, authenticatedUser);
+
+ Map result = new HashMap<>();
+ result.put("expirationTime", newExpiryTime);
+
+ LOG.info("Delegation token renewed by user={}, newExpiryTime={}", authenticatedUser, newExpiryTime);
+
+ return result;
+ } catch (AccessControlException e) {
+ LOG.warn("Delegation token renewal denied for user={}", authenticatedUser);
+ throw restErrorUtil.create403RESTException("Delegation token renewal denied");
+ } catch (IOException e) {
+ LOG.warn("Delegation token renewal failed: {}", e.getMessage());
+ throw restErrorUtil.createRESTException(HttpServletResponse.SC_UNAUTHORIZED,
+ "Delegation token is invalid or expired", true);
+ } catch (Exception e) {
+ LOG.error("Failed to renew delegation token", e);
+ throw restErrorUtil.createRESTException("Internal error during delegation token renewal");
+ }
+ }
+
+ @PUT
+ @Path("/cancel")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void cancelDelegationToken(Map requestBody,
+ @Context HttpServletRequest request) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("==> DelegationTokenREST.cancelDelegationToken()");
+ }
+
+ if (!secretManager.isEnabled()) {
+ throw restErrorUtil.createRESTException(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
+ "Delegation token support is not enabled", true);
+ }
+
+ String tokenEncoded = requestBody != null ? requestBody.get("token") : null;
+ if (tokenEncoded == null || tokenEncoded.isEmpty()) {
+ throw restErrorUtil.createRESTException(HttpServletResponse.SC_BAD_REQUEST,
+ "Token parameter is required", true);
+ }
+
+ String authenticatedUser = bizUtil.getCurrentUserLoginId();
+ if (authenticatedUser == null) {
+ throw restErrorUtil.createRESTException(HttpServletResponse.SC_UNAUTHORIZED,
+ "Authentication required", true);
+ }
+
+ try {
+ Token token = new Token<>();
+ token.decodeFromUrlString(tokenEncoded);
+
+ secretManager.cancelDelegationToken(token, authenticatedUser);
+
+ LOG.info("Delegation token cancelled by user={}", authenticatedUser);
+ } catch (AccessControlException e) {
+ LOG.warn("Delegation token cancellation denied for user={}", authenticatedUser);
+ throw restErrorUtil.create403RESTException("Delegation token cancellation denied");
+ } catch (IOException e) {
+ LOG.warn("Delegation token cancellation failed: {}", e.getMessage());
+ throw restErrorUtil.createRESTException(HttpServletResponse.SC_UNAUTHORIZED,
+ "Delegation token is invalid or expired", true);
+ } catch (Exception e) {
+ LOG.error("Failed to cancel delegation token", e);
+ throw restErrorUtil.createRESTException("Internal error during delegation token cancellation");
+ }
+ }
+}
diff --git a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerDelegationTokenAuthFilter.java b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerDelegationTokenAuthFilter.java
new file mode 100644
index 0000000000..51785023ed
--- /dev/null
+++ b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerDelegationTokenAuthFilter.java
@@ -0,0 +1,121 @@
+/*
+ * 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.ranger.security.web.filter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.hadoop.security.token.Token;
+import org.apache.ranger.biz.RangerDelegationTokenSecretManager;
+import org.apache.ranger.plugin.util.RangerDelegationTokenIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetails;
+import org.springframework.web.filter.GenericFilterBean;
+
+public class RangerDelegationTokenAuthFilter extends GenericFilterBean {
+ private static final Logger LOG = LoggerFactory.getLogger(RangerDelegationTokenAuthFilter.class);
+
+ public static final String HEADER_DELEGATION_TOKEN = "X-Delegation-Token-Encoded";
+ public static final String PARAM_DELEGATION_TOKEN = "delegationToken";
+
+ private static final String DEFAULT_ROLE = "ROLE_USER";
+
+ @Autowired
+ RangerDelegationTokenSecretManager secretManager;
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+
+ if (!secretManager.isEnabled()) {
+ chain.doFilter(request, response);
+ return;
+ }
+
+ String tokenEncoded = httpRequest.getHeader(HEADER_DELEGATION_TOKEN);
+ if (StringUtils.isEmpty(tokenEncoded)) {
+ tokenEncoded = httpRequest.getParameter(PARAM_DELEGATION_TOKEN);
+ }
+
+ if (StringUtils.isEmpty(tokenEncoded)) {
+ chain.doFilter(request, response);
+ return;
+ }
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("==> RangerDelegationTokenAuthFilter: found delegation token in request for URI={}", httpRequest.getRequestURI());
+ }
+
+ try {
+ Token token = new Token<>();
+ token.decodeFromUrlString(tokenEncoded);
+
+ RangerDelegationTokenIdentifier ident = secretManager.verifyToken(token);
+ String userName = ident.getUser().getShortUserName();
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Delegation token verified for user={}", userName);
+ }
+
+ final List grantedAuths = new ArrayList<>();
+ grantedAuths.add(new SimpleGrantedAuthority(DEFAULT_ROLE));
+
+ final UserDetails principal = new User(userName, "", grantedAuths);
+ final Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "", grantedAuths);
+ WebAuthenticationDetails webDetails = new WebAuthenticationDetails(httpRequest);
+ ((AbstractAuthenticationToken) authentication).setDetails(webDetails);
+
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ httpRequest.setAttribute("delegationTokenEnabled", true);
+ httpRequest.getSession(true);
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("<== RangerDelegationTokenAuthFilter: authenticated user={} via delegation token", userName);
+ }
+
+ chain.doFilter(request, response);
+ } catch (Exception e) {
+ LOG.warn("Delegation token authentication failed: {}", e.getMessage());
+ httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Delegation token authentication failed");
+ }
+ }
+}
diff --git a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerSecurityContextFormationFilter.java b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerSecurityContextFormationFilter.java
index 71d7af0d11..d16b4c287a 100644
--- a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerSecurityContextFormationFilter.java
+++ b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerSecurityContextFormationFilter.java
@@ -166,6 +166,8 @@ private int getAuthType(HttpServletRequest request) {
if (ssoEnabled) {
authType = XXAuthSession.AUTH_TYPE_SSO;
+ } else if (request.getAttribute("delegationTokenEnabled") != null && Boolean.valueOf(String.valueOf(request.getAttribute("delegationTokenEnabled")))) {
+ authType = XXAuthSession.AUTH_TYPE_DELEGATION_TOKEN;
} else if (request.getAttribute("spnegoEnabled") != null && Boolean.valueOf(String.valueOf(request.getAttribute("spnegoEnabled")))){
if (request.getAttribute("trustedProxyEnabled") != null && Boolean.valueOf(String.valueOf(request.getAttribute("trustedProxyEnabled")))) {
if (logger.isDebugEnabled()) {
diff --git a/security-admin/src/main/resources/META-INF/persistence.xml b/security-admin/src/main/resources/META-INF/persistence.xml
index 827a312fdf..21027da7f8 100644
--- a/security-admin/src/main/resources/META-INF/persistence.xml
+++ b/security-admin/src/main/resources/META-INF/persistence.xml
@@ -79,6 +79,8 @@
org.apache.ranger.entity.XXServiceVersionInfo
org.apache.ranger.entity.XXPluginInfo
org.apache.ranger.entity.XXUgsyncAuditInfo
+ org.apache.ranger.entity.XXRangerDTMasterKey
+ org.apache.ranger.entity.XXRangerDelegationToken
NONE
diff --git a/security-admin/src/main/resources/conf.dist/security-applicationContext.xml b/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
index d922d2721d..260cad52d2 100644
--- a/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
+++ b/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
@@ -65,6 +65,7 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd">
+
@@ -105,6 +106,9 @@ http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd">
+
+
+
diff --git a/security-admin/src/main/webapp/scripts/utils/XAEnums.js b/security-admin/src/main/webapp/scripts/utils/XAEnums.js
index f81fa16197..bcc4c0c59b 100644
--- a/security-admin/src/main/webapp/scripts/utils/XAEnums.js
+++ b/security-admin/src/main/webapp/scripts/utils/XAEnums.js
@@ -154,7 +154,8 @@ define(function(require) {
AUTH_TYPE_PASSWORD:{value:1, label:'Username/Password', rbkey:'xa.enum.AuthType.AUTH_TYPE_PASSWORD', tt: 'lbl.AuthType_AUTH_TYPE_PASSWORD'},
AUTH_TYPE_KERBEROS:{value:2, label:'Kerberos', rbkey:'xa.enum.AuthType.AUTH_TYPE_KERBEROS', tt: 'lbl.AuthType_AUTH_TYPE_KERBEROS'},
AUTH_TYPE_SSO:{value:3, label:'SingleSignOn', rbkey:'xa.enum.AuthType.AUTH_TYPE_SSO', tt: 'lbl.AuthType_AUTH_TYPE_SSO'},
- AUTH_TYPE_TRUSTED_PROXY:{value:4, label:'Trusted Proxy', rbkey:'xa.enum.AuthType.AUTH_TYPE_TRUSTED_PROXY', tt: 'lbl.AuthType_AUTH_TYPE_TRUSTED_PROXY'}
+ AUTH_TYPE_TRUSTED_PROXY:{value:4, label:'Trusted Proxy', rbkey:'xa.enum.AuthType.AUTH_TYPE_TRUSTED_PROXY', tt: 'lbl.AuthType_AUTH_TYPE_TRUSTED_PROXY'},
+ AUTH_TYPE_DELEGATION_TOKEN:{value:5, label:'Delegation Token', rbkey:'xa.enum.AuthType.AUTH_TYPE_DELEGATION_TOKEN', tt: 'lbl.AuthType_AUTH_TYPE_DELEGATION_TOKEN'}
});
XAEnums.BooleanValue = mergeParams(XAEnums.BooleanValue, {
diff --git a/security-admin/src/test/java/org/apache/ranger/biz/TestRangerDelegationTokenSecretManager.java b/security-admin/src/test/java/org/apache/ranger/biz/TestRangerDelegationTokenSecretManager.java
new file mode 100644
index 0000000000..ccffd2c1ce
--- /dev/null
+++ b/security-admin/src/test/java/org/apache/ranger/biz/TestRangerDelegationTokenSecretManager.java
@@ -0,0 +1,244 @@
+/*
+ * 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.ranger.biz;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import org.apache.hadoop.security.token.Token;
+import org.apache.ranger.db.RangerDaoManager;
+import org.apache.ranger.db.XXRangerDTMasterKeyDao;
+import org.apache.ranger.db.XXRangerDelegationTokenDao;
+import org.apache.ranger.entity.XXRangerDTMasterKey;
+import org.apache.ranger.entity.XXRangerDelegationToken;
+import org.apache.ranger.plugin.util.RangerDelegationTokenIdentifier;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+public class TestRangerDelegationTokenSecretManager {
+
+ @Mock
+ private RangerDaoManager daoManager;
+
+ @Mock
+ private XXRangerDTMasterKeyDao masterKeyDao;
+
+ @Mock
+ private XXRangerDelegationTokenDao tokenDao;
+
+ private RangerDelegationTokenSecretManager secretManager;
+
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ secretManager = new TestableSecretManager();
+
+ Mockito.when(daoManager.getXXRangerDTMasterKey()).thenReturn(masterKeyDao);
+ Mockito.when(daoManager.getXXRangerDelegationToken()).thenReturn(tokenDao);
+ Mockito.when(masterKeyDao.findAll()).thenReturn(Collections.emptyList());
+ Mockito.when(tokenDao.findAll()).thenReturn(Collections.emptyList());
+ Mockito.when(masterKeyDao.create(Mockito.any(XXRangerDTMasterKey.class)))
+ .thenAnswer(inv -> inv.getArgument(0));
+ Mockito.when(tokenDao.create(Mockito.any(XXRangerDelegationToken.class)))
+ .thenAnswer(inv -> inv.getArgument(0));
+
+ java.lang.reflect.Field daoField = RangerDelegationTokenSecretManager.class.getDeclaredField("daoManager");
+ daoField.setAccessible(true);
+ daoField.set(secretManager, daoManager);
+
+ secretManager.startThreads();
+ }
+
+ @Test
+ public void testCreateAndVerifyToken() throws Exception {
+ Token token = secretManager.createDelegationToken("testUser", "yarn");
+
+ assertNotNull(token);
+ assertNotNull(token.getIdentifier());
+ assertNotNull(token.getPassword());
+ assertTrue(token.getPassword().length > 0);
+
+ RangerDelegationTokenIdentifier ident = secretManager.verifyToken(token);
+ assertNotNull(ident);
+ assertEquals("testUser", ident.getOwner().toString());
+ assertEquals("yarn", ident.getRenewer().toString());
+ }
+
+ @Test
+ public void testVerifyTokenWithWrongPassword() throws Exception {
+ Token token = secretManager.createDelegationToken("testUser", "yarn");
+
+ Token tamperedToken = new Token<>(
+ token.getIdentifier(),
+ new byte[]{1, 2, 3, 4}, // wrong password
+ token.getKind(),
+ token.getService()
+ );
+
+ try {
+ secretManager.verifyToken(tamperedToken);
+ fail("Should have thrown IOException for wrong password");
+ } catch (IOException e) {
+ assertTrue(e.getMessage().contains("password does not match"));
+ }
+ }
+
+ @Test
+ public void testRenewTokenByDesignatedRenewer() throws Exception {
+ Token token = secretManager.createDelegationToken("owner", "yarn");
+
+ long newExpiry = secretManager.renewDelegationToken(token, "yarn");
+ assertTrue(newExpiry > System.currentTimeMillis());
+ }
+
+ @Test
+ public void testRenewTokenByNonRenewer() throws Exception {
+ Token token = secretManager.createDelegationToken("owner", "yarn");
+
+ try {
+ secretManager.renewDelegationToken(token, "attacker");
+ fail("Should have thrown IOException for non-renewer");
+ } catch (Exception e) {
+ // Expected: Hadoop throws AccessControlException wrapped in IOException
+ }
+ }
+
+ @Test
+ public void testCancelTokenByOwner() throws Exception {
+ Token token = secretManager.createDelegationToken("owner", "yarn");
+
+ secretManager.cancelDelegationToken(token, "owner");
+
+ try {
+ secretManager.verifyToken(token);
+ fail("Should have thrown IOException for cancelled token");
+ } catch (IOException e) {
+ // Expected
+ }
+ }
+
+ @Test
+ public void testCancelTokenByRenewer() throws Exception {
+ Token token = secretManager.createDelegationToken("owner", "yarn");
+
+ secretManager.cancelDelegationToken(token, "yarn");
+
+ try {
+ secretManager.verifyToken(token);
+ fail("Should have thrown IOException for cancelled token");
+ } catch (IOException e) {
+ // Expected
+ }
+ }
+
+ @Test
+ public void testCancelTokenByNonOwner() throws Exception {
+ Token token = secretManager.createDelegationToken("owner", "yarn");
+
+ try {
+ secretManager.cancelDelegationToken(token, "attacker");
+ fail("Should have thrown exception for non-owner/non-renewer cancel");
+ } catch (Exception e) {
+ // Expected: Hadoop throws AccessControlException
+ }
+ }
+
+ @Test
+ public void testCreateTokenWithNullRenewer() throws Exception {
+ Token token = secretManager.createDelegationToken("testUser", null);
+
+ assertNotNull(token);
+
+ RangerDelegationTokenIdentifier ident = secretManager.verifyToken(token);
+ assertEquals("testUser", ident.getOwner().toString());
+ }
+
+ @Test
+ public void testMultipleTokensIndependent() throws Exception {
+ Token token1 = secretManager.createDelegationToken("user1", "yarn");
+ Token token2 = secretManager.createDelegationToken("user2", "yarn");
+
+ RangerDelegationTokenIdentifier ident1 = secretManager.verifyToken(token1);
+ RangerDelegationTokenIdentifier ident2 = secretManager.verifyToken(token2);
+
+ assertEquals("user1", ident1.getOwner().toString());
+ assertEquals("user2", ident2.getOwner().toString());
+
+ secretManager.cancelDelegationToken(token1, "user1");
+
+ try {
+ secretManager.verifyToken(token1);
+ fail("Cancelled token should not verify");
+ } catch (IOException e) {
+ // Expected
+ }
+
+ // Token2 still valid
+ assertNotNull(secretManager.verifyToken(token2));
+ }
+
+ @Test
+ public void testTokenRoundTripEncoding() throws Exception {
+ Token token = secretManager.createDelegationToken("testUser", "yarn");
+
+ String encoded = token.encodeToUrlString();
+ Token decoded = new Token<>();
+ decoded.decodeFromUrlString(encoded);
+
+ RangerDelegationTokenIdentifier ident = secretManager.verifyToken(decoded);
+ assertEquals("testUser", ident.getOwner().toString());
+ }
+
+ @Test
+ public void testDbPersistenceOnCreate() throws Exception {
+ secretManager.createDelegationToken("testUser", "yarn");
+
+ Mockito.verify(masterKeyDao, Mockito.atLeastOnce()).create(Mockito.any(XXRangerDTMasterKey.class));
+
+ Mockito.verify(tokenDao).create(Mockito.any(XXRangerDelegationToken.class));
+ }
+
+ /**
+ * Testable subclass that bypasses PropertiesUtil config loading
+ * and executes DB callbacks directly without TransactionTemplate.
+ */
+ private static class TestableSecretManager extends RangerDelegationTokenSecretManager {
+ TestableSecretManager() {
+ super();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+ @Override
+ protected T executeInTransaction(org.springframework.transaction.support.TransactionCallback action) {
+ return action.doInTransaction(null);
+ }
+ }
+}
diff --git a/security-admin/src/test/java/org/apache/ranger/rest/TestDelegationTokenREST.java b/security-admin/src/test/java/org/apache/ranger/rest/TestDelegationTokenREST.java
new file mode 100644
index 0000000000..f3a3e0ce1c
--- /dev/null
+++ b/security-admin/src/test/java/org/apache/ranger/rest/TestDelegationTokenREST.java
@@ -0,0 +1,358 @@
+/*
+ * 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.ranger.rest;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.WebApplicationException;
+
+import org.apache.hadoop.security.AccessControlException;
+import org.apache.hadoop.security.token.Token;
+import org.apache.ranger.biz.RangerBizUtil;
+import org.apache.ranger.biz.RangerDelegationTokenSecretManager;
+import org.apache.ranger.common.RESTErrorUtil;
+import org.apache.ranger.plugin.util.RangerDelegationTokenIdentifier;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestDelegationTokenREST {
+
+ @Mock
+ private RangerDelegationTokenSecretManager secretManager;
+
+ @Mock
+ private RangerBizUtil bizUtil;
+
+ @Mock
+ private RESTErrorUtil restErrorUtil;
+
+ @Mock
+ private HttpServletRequest request;
+
+ @InjectMocks
+ private DelegationTokenREST delegationTokenREST = new DelegationTokenREST();
+
+ @Before
+ public void setup() {
+ Mockito.lenient().when(restErrorUtil.createRESTException(Mockito.anyInt(), Mockito.anyString(), Mockito.anyBoolean()))
+ .thenAnswer(invocation -> {
+ int status = invocation.getArgument(0);
+ return new WebApplicationException(javax.ws.rs.core.Response.status(status).build());
+ });
+ Mockito.lenient().when(restErrorUtil.createRESTException(Mockito.anyString()))
+ .thenAnswer(invocation -> new WebApplicationException(
+ javax.ws.rs.core.Response.status(HttpServletResponse.SC_BAD_REQUEST).build()));
+ Mockito.lenient().when(restErrorUtil.create403RESTException(Mockito.anyString()))
+ .thenAnswer(invocation -> new WebApplicationException(
+ javax.ws.rs.core.Response.status(HttpServletResponse.SC_FORBIDDEN).build()));
+ }
+
+ // --- Get tests ---
+
+ @Test
+ public void testGetDelegationToken_disabled() {
+ Mockito.when(secretManager.isEnabled()).thenReturn(false);
+
+ try {
+ delegationTokenREST.getDelegationToken("yarn", request);
+ fail("Expected WebApplicationException");
+ } catch (WebApplicationException e) {
+ assertEquals(HttpServletResponse.SC_SERVICE_UNAVAILABLE, e.getResponse().getStatus());
+ }
+ }
+
+ @Test
+ public void testGetDelegationToken_noAuth() {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+ Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn(null);
+
+ try {
+ delegationTokenREST.getDelegationToken("yarn", request);
+ fail("Expected WebApplicationException");
+ } catch (WebApplicationException e) {
+ assertEquals(HttpServletResponse.SC_UNAUTHORIZED, e.getResponse().getStatus());
+ }
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testGetDelegationToken_success() throws Exception {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+ Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn("testUser");
+
+ Token mockToken = Mockito.mock(Token.class);
+ Mockito.when(mockToken.encodeToUrlString()).thenReturn("encodedTokenString");
+ Mockito.when(secretManager.createDelegationToken("testUser", "yarn")).thenReturn(mockToken);
+
+ Map result = delegationTokenREST.getDelegationToken("yarn", request);
+
+ assertEquals("encodedTokenString", result.get("urlString"));
+ }
+
+ @Test
+ public void testGetDelegationToken_internalError() throws Exception {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+ Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn("testUser");
+ Mockito.when(secretManager.createDelegationToken(Mockito.anyString(), Mockito.anyString()))
+ .thenThrow(new IOException("test error"));
+
+ try {
+ delegationTokenREST.getDelegationToken("yarn", request);
+ fail("Expected WebApplicationException");
+ } catch (WebApplicationException e) {
+ // error thrown via restErrorUtil
+ }
+ }
+
+ @Test
+ public void testGetDelegationToken_renewerTooLong() {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+ Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn("testUser");
+
+ String longRenewer = new String(new char[300]).replace('\0', 'a');
+
+ try {
+ delegationTokenREST.getDelegationToken(longRenewer, request);
+ fail("Expected WebApplicationException");
+ } catch (WebApplicationException e) {
+ assertEquals(HttpServletResponse.SC_BAD_REQUEST, e.getResponse().getStatus());
+ }
+ }
+
+ // --- Renew tests ---
+
+ @Test
+ public void testRenewDelegationToken_disabled() {
+ Mockito.when(secretManager.isEnabled()).thenReturn(false);
+
+ Map body = new HashMap<>();
+ body.put("token", "someToken");
+
+ try {
+ delegationTokenREST.renewDelegationToken(body, request);
+ fail("Expected WebApplicationException");
+ } catch (WebApplicationException e) {
+ assertEquals(HttpServletResponse.SC_SERVICE_UNAVAILABLE, e.getResponse().getStatus());
+ }
+ }
+
+ @Test
+ public void testRenewDelegationToken_nullToken() {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+
+ try {
+ delegationTokenREST.renewDelegationToken(null, request);
+ fail("Expected WebApplicationException");
+ } catch (WebApplicationException e) {
+ assertEquals(HttpServletResponse.SC_BAD_REQUEST, e.getResponse().getStatus());
+ }
+ }
+
+ @Test
+ public void testRenewDelegationToken_noAuth() {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+ Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn(null);
+
+ Map body = new HashMap<>();
+ body.put("token", "someToken");
+
+ try {
+ delegationTokenREST.renewDelegationToken(body, request);
+ fail("Expected WebApplicationException");
+ } catch (WebApplicationException e) {
+ assertEquals(HttpServletResponse.SC_UNAUTHORIZED, e.getResponse().getStatus());
+ }
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRenewDelegationToken_success() throws Exception {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+ Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn("testUser");
+ Mockito.when(secretManager.renewDelegationToken(Mockito.any(Token.class), Mockito.eq("testUser")))
+ .thenReturn(9999999L);
+
+ Token fakeToken = new Token<>();
+ String encoded = fakeToken.encodeToUrlString();
+
+ Map body = new HashMap<>();
+ body.put("token", encoded);
+ Map result = delegationTokenREST.renewDelegationToken(body, request);
+
+ assertEquals(9999999L, result.get("expirationTime"));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRenewDelegationToken_passesCallerIdentity() throws Exception {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+ Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn("testUser");
+ Mockito.when(secretManager.renewDelegationToken(Mockito.any(Token.class), Mockito.eq("testUser")))
+ .thenReturn(1L);
+
+ Token fakeToken = new Token<>();
+ Map body = new HashMap<>();
+ body.put("token", fakeToken.encodeToUrlString());
+
+ delegationTokenREST.renewDelegationToken(body, request);
+
+ Mockito.verify(secretManager).renewDelegationToken(Mockito.any(Token.class), Mockito.eq("testUser"));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRenewDelegationToken_accessDenied() throws Exception {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+ Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn("testUser");
+ Mockito.when(secretManager.renewDelegationToken(Mockito.any(Token.class), Mockito.anyString()))
+ .thenThrow(new AccessControlException("not the designated renewer"));
+
+ Token fakeToken = new Token<>();
+ Map body = new HashMap<>();
+ body.put("token", fakeToken.encodeToUrlString());
+
+ try {
+ delegationTokenREST.renewDelegationToken(body, request);
+ fail("Expected WebApplicationException");
+ } catch (WebApplicationException e) {
+ assertEquals(HttpServletResponse.SC_FORBIDDEN, e.getResponse().getStatus());
+ }
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testRenewDelegationToken_invalidToken() throws Exception {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+ Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn("testUser");
+ Mockito.when(secretManager.renewDelegationToken(Mockito.any(Token.class), Mockito.anyString()))
+ .thenThrow(new IOException("token is expired"));
+
+ Token fakeToken = new Token<>();
+ Map body = new HashMap<>();
+ body.put("token", fakeToken.encodeToUrlString());
+
+ try {
+ delegationTokenREST.renewDelegationToken(body, request);
+ fail("Expected WebApplicationException");
+ } catch (WebApplicationException e) {
+ assertEquals(HttpServletResponse.SC_UNAUTHORIZED, e.getResponse().getStatus());
+ }
+ }
+
+ // --- Cancel tests ---
+
+ @Test
+ public void testCancelDelegationToken_disabled() {
+ Mockito.when(secretManager.isEnabled()).thenReturn(false);
+
+ Map body = new HashMap<>();
+ body.put("token", "someToken");
+
+ try {
+ delegationTokenREST.cancelDelegationToken(body, request);
+ fail("Expected WebApplicationException");
+ } catch (WebApplicationException e) {
+ assertEquals(HttpServletResponse.SC_SERVICE_UNAVAILABLE, e.getResponse().getStatus());
+ }
+ }
+
+ @Test
+ public void testCancelDelegationToken_noAuth() {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+ Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn(null);
+
+ Map body = new HashMap<>();
+ body.put("token", "someToken");
+
+ try {
+ delegationTokenREST.cancelDelegationToken(body, request);
+ fail("Expected WebApplicationException");
+ } catch (WebApplicationException e) {
+ assertEquals(HttpServletResponse.SC_UNAUTHORIZED, e.getResponse().getStatus());
+ }
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testCancelDelegationToken_passesCallerIdentity() throws Exception {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+ Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn("testUser");
+
+ Token fakeToken = new Token<>();
+ Map body = new HashMap<>();
+ body.put("token", fakeToken.encodeToUrlString());
+
+ delegationTokenREST.cancelDelegationToken(body, request);
+
+ Mockito.verify(secretManager).cancelDelegationToken(Mockito.any(Token.class), Mockito.eq("testUser"));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testCancelDelegationToken_accessDenied() throws Exception {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+ Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn("testUser");
+ Mockito.doThrow(new AccessControlException("not authorized"))
+ .when(secretManager).cancelDelegationToken(Mockito.any(Token.class), Mockito.anyString());
+
+ Token fakeToken = new Token<>();
+ Map body = new HashMap<>();
+ body.put("token", fakeToken.encodeToUrlString());
+
+ try {
+ delegationTokenREST.cancelDelegationToken(body, request);
+ fail("Expected WebApplicationException");
+ } catch (WebApplicationException e) {
+ assertEquals(HttpServletResponse.SC_FORBIDDEN, e.getResponse().getStatus());
+ }
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testCancelDelegationToken_invalidToken() throws Exception {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+ Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn("testUser");
+ Mockito.doThrow(new IOException("token does not exist"))
+ .when(secretManager).cancelDelegationToken(Mockito.any(Token.class), Mockito.anyString());
+
+ Token fakeToken = new Token<>();
+ Map body = new HashMap<>();
+ body.put("token", fakeToken.encodeToUrlString());
+
+ try {
+ delegationTokenREST.cancelDelegationToken(body, request);
+ fail("Expected WebApplicationException");
+ } catch (WebApplicationException e) {
+ assertEquals(HttpServletResponse.SC_UNAUTHORIZED, e.getResponse().getStatus());
+ }
+ }
+}
diff --git a/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerDelegationTokenAuthFilter.java b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerDelegationTokenAuthFilter.java
new file mode 100644
index 0000000000..3e16a94ad5
--- /dev/null
+++ b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerDelegationTokenAuthFilter.java
@@ -0,0 +1,202 @@
+/*
+ * 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.ranger.security.web.filter;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.security.token.Token;
+import org.apache.ranger.biz.RangerDelegationTokenSecretManager;
+import org.apache.ranger.plugin.util.RangerDelegationTokenIdentifier;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TestRangerDelegationTokenAuthFilter {
+
+ @Mock
+ private RangerDelegationTokenSecretManager secretManager;
+
+ @InjectMocks
+ private RangerDelegationTokenAuthFilter filter = new RangerDelegationTokenAuthFilter();
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+
+ @Mock
+ private FilterChain chain;
+
+ @Before
+ public void setup() {
+ SecurityContextHolder.clearContext();
+ }
+
+ @After
+ public void teardown() {
+ SecurityContextHolder.clearContext();
+ }
+
+ @Test
+ public void testPassThrough_whenDisabled() throws IOException, ServletException {
+ Mockito.when(secretManager.isEnabled()).thenReturn(false);
+
+ filter.doFilter(request, response, chain);
+
+ Mockito.verify(chain).doFilter(request, response);
+ assertNull(SecurityContextHolder.getContext().getAuthentication());
+ }
+
+ @Test
+ public void testPassThrough_whenAlreadyAuthenticated() throws IOException, ServletException {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+ SecurityContextHolder.getContext().setAuthentication(
+ new UsernamePasswordAuthenticationToken("existingUser", "pass"));
+
+ filter.doFilter(request, response, chain);
+
+ Mockito.verify(chain).doFilter(request, response);
+ assertEquals("existingUser", SecurityContextHolder.getContext().getAuthentication().getName());
+ }
+
+ @Test
+ public void testPassThrough_whenNoTokenPresent() throws IOException, ServletException {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+ Mockito.when(request.getHeader(RangerDelegationTokenAuthFilter.HEADER_DELEGATION_TOKEN)).thenReturn(null);
+ Mockito.when(request.getParameter(RangerDelegationTokenAuthFilter.PARAM_DELEGATION_TOKEN)).thenReturn(null);
+
+ filter.doFilter(request, response, chain);
+
+ Mockito.verify(chain).doFilter(request, response);
+ assertNull(SecurityContextHolder.getContext().getAuthentication());
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testAuthentication_validTokenInHeader() throws Exception {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+
+ Token fakeToken = new Token<>();
+ String encoded = fakeToken.encodeToUrlString();
+
+ Mockito.when(request.getHeader(RangerDelegationTokenAuthFilter.HEADER_DELEGATION_TOKEN))
+ .thenReturn(encoded);
+
+ RangerDelegationTokenIdentifier ident = new RangerDelegationTokenIdentifier(
+ new Text("tokenOwner"), new Text("yarn"), new Text("tokenOwner"));
+ Mockito.when(secretManager.verifyToken(Mockito.any(Token.class))).thenReturn(ident);
+
+ filter.doFilter(request, response, chain);
+
+ Mockito.verify(chain).doFilter(request, response);
+
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+ assertNotNull(auth);
+ assertTrue(auth.isAuthenticated());
+ assertEquals("tokenOwner", auth.getName());
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testAuthentication_validTokenInParam() throws Exception {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+
+ Token fakeToken = new Token<>();
+ String encoded = fakeToken.encodeToUrlString();
+
+ Mockito.when(request.getHeader(RangerDelegationTokenAuthFilter.HEADER_DELEGATION_TOKEN))
+ .thenReturn(null);
+ Mockito.when(request.getParameter(RangerDelegationTokenAuthFilter.PARAM_DELEGATION_TOKEN))
+ .thenReturn(encoded);
+
+ RangerDelegationTokenIdentifier ident = new RangerDelegationTokenIdentifier(
+ new Text("paramUser"), new Text("yarn"), new Text("paramUser"));
+ Mockito.when(secretManager.verifyToken(Mockito.any(Token.class))).thenReturn(ident);
+
+ filter.doFilter(request, response, chain);
+
+ Mockito.verify(chain).doFilter(request, response);
+
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+ assertNotNull(auth);
+ assertEquals("paramUser", auth.getName());
+ assertTrue(auth.getAuthorities().stream()
+ .anyMatch(a -> a.getAuthority().equals("ROLE_USER")));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testAuthentication_invalidToken_returns401() throws Exception {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+
+ Token fakeToken = new Token<>();
+ String encoded = fakeToken.encodeToUrlString();
+
+ Mockito.when(request.getHeader(RangerDelegationTokenAuthFilter.HEADER_DELEGATION_TOKEN))
+ .thenReturn(encoded);
+ Mockito.when(secretManager.verifyToken(Mockito.any(Token.class)))
+ .thenThrow(new IOException("Token password does not match"));
+
+ filter.doFilter(request, response, chain);
+
+ Mockito.verify(chain, Mockito.never()).doFilter(request, response);
+ Mockito.verify(response).sendError(
+ Mockito.eq(HttpServletResponse.SC_UNAUTHORIZED),
+ Mockito.anyString());
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testAuthentication_setsRequestAttribute() throws Exception {
+ Mockito.when(secretManager.isEnabled()).thenReturn(true);
+
+ Token fakeToken = new Token<>();
+ String encoded = fakeToken.encodeToUrlString();
+
+ Mockito.when(request.getHeader(RangerDelegationTokenAuthFilter.HEADER_DELEGATION_TOKEN))
+ .thenReturn(encoded);
+
+ RangerDelegationTokenIdentifier ident = new RangerDelegationTokenIdentifier(
+ new Text("testUser"), new Text("yarn"), new Text("testUser"));
+ Mockito.when(secretManager.verifyToken(Mockito.any(Token.class))).thenReturn(ident);
+
+ filter.doFilter(request, response, chain);
+
+ Mockito.verify(request).setAttribute("delegationTokenEnabled", true);
+ }
+}