You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2022/03/16 13:26:54 UTC
[knox] branch master updated: KNOX-2714 - Added doAs support to KnoxToken service (#545)
This is an automated email from the ASF dual-hosted git repository.
smolnar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new 69a92c2 KNOX-2714 - Added doAs support to KnoxToken service (#545)
69a92c2 is described below
commit 69a92c2d33d467b855a397444a103e82f7e566de
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Wed Mar 16 14:26:46 2022 +0100
KNOX-2714 - Added doAs support to KnoxToken service (#545)
---
.../resources/applications/tokengen/app/index.html | 3 +
.../applications/tokengen/app/js/tokengen.js | 20 +++++-
.../hadoopauth/filter/HadoopAuthFilter.java | 70 ++++--------------
.../jwt/filter/JWTAccessTokenAssertionFilter.java | 11 +--
.../jwt/filter/JWTAuthCodeAssertionFilter.java | 2 +-
...okenIDAsHTTPBasicCredsFederationFilterTest.java | 18 ++++-
gateway-release/home/conf/topologies/homepage.xml | 12 ++++
.../token/impl/DefaultTokenAuthorityService.java | 2 +-
.../token/impl/DefaultTokenStateService.java | 18 ++++-
.../services/token/impl/JDBCTokenStateService.java | 10 +++
.../services/token/impl/TokenStateDatabase.java | 15 +++-
.../token/impl/TokenStateServiceMessages.java | 3 +
.../impl/DefaultTokenAuthorityServiceTest.java | 42 +++++------
.../gateway/service/knoxsso/WebSSOResource.java | 2 +-
.../service/knoxsso/WebSSOResourceTest.java | 2 +-
gateway-service-knoxtoken/pom.xml | 16 +++++
.../gateway/service/knoxtoken/TokenResource.java | 48 ++++++++++---
.../knoxtoken/TokenServiceResourceTest.java | 58 +++++++++++++--
gateway-spi/pom.xml | 15 ++++
.../services/security/token/JWTokenAttributes.java | 12 ++--
.../security/token/JWTokenAttributesBuilder.java | 15 ++--
.../services/security/token/TokenMetadata.java | 19 +++--
.../services/security/token/TokenStateService.java | 9 +++
.../apache/knox/gateway/util/AuthFilterUtils.java | 82 ++++++++++++++++++++++
.../app/token.management.component.html | 51 ++++++++++++--
.../app/token.management.component.ts | 19 +++--
.../app/token.management.service.ts | 6 +-
27 files changed, 434 insertions(+), 146 deletions(-)
diff --git a/gateway-applications/src/main/resources/applications/tokengen/app/index.html b/gateway-applications/src/main/resources/applications/tokengen/app/index.html
index dbde759..3810a8d 100644
--- a/gateway-applications/src/main/resources/applications/tokengen/app/index.html
+++ b/gateway-applications/src/main/resources/applications/tokengen/app/index.html
@@ -79,6 +79,9 @@
</table>
<label style="display: none; color: red;" id="invalidLifetimeText"><i class="icon-warning"></i>Invalid lifetime!</label>
</div>
+ <label><i class="icon-user"></i> Generating token for (impersonation):</label>
+ <input type="text" name="doas" id="doas" size="50" maxlength="255">
+ <label style="display: none; color: red;" id="invalidDoasText"><i class="icon-warning"></i>Invalid doAs!</label>
</div>
<span id="errorBox" class="help-inline" style="color:red;display:none;"><span class="errorMsg"></span>
<i class="icon-warning-sign" style="color:#ae2817;"></i>
diff --git a/gateway-applications/src/main/resources/applications/tokengen/app/js/tokengen.js b/gateway-applications/src/main/resources/applications/tokengen/app/js/tokengen.js
index 09b2521..6aed159 100644
--- a/gateway-applications/src/main/resources/applications/tokengen/app/js/tokengen.js
+++ b/gateway-applications/src/main/resources/applications/tokengen/app/js/tokengen.js
@@ -158,6 +158,19 @@ function validateComment(comment) {
return valid;
}
+function validateDoAs(doAs) {
+ var valid = true;
+ if (doAs.value != '') {
+ doAs.reportValidity();
+ valid = doAs.checkValidity();
+ if (!valid) {
+ $('#invalidDoasText').show();
+ }
+ }
+
+ return valid;
+}
+
function maximumLifetimeExceeded(maximumLifetime, days, hours, mins) {
if (maximumLifetime == -1) {
return false;
@@ -196,6 +209,11 @@ var gen = function() {
if (form.comment.value != '') {
params = params + (lifespanInputEnabled === "true" ? "&" : "?") + 'comment=' + encodeURIComponent(form.comment.value);
}
+
+ if (form.doas.value != '') {
+ params = params + (lifespanInputEnabled === "true" || form.comment.value != '' ? "&" : "?") + 'doAs=' + encodeURIComponent(form.doas.value);
+ }
+
var request = ((window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"));
request.open("GET", apiUrl + params, true);
request.send(null);
@@ -235,7 +253,7 @@ var gen = function() {
}
}
- if (validateLifespan(lifespanInputEnabled, form.lt_days, form.lt_hours, form.lt_mins) && validateComment(form.comment)) {
+ if (validateLifespan(lifespanInputEnabled, form.lt_days, form.lt_hours, form.lt_mins) && validateComment(form.comment) && validateDoAs(form.doas)) {
if (maximumLifetimeExceeded(form.maximumLifetimeSeconds.textContent, lt_days, lt_hours, lt_mins)) {
swal({
title: "Warning",
diff --git a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilter.java b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilter.java
index 1e23167..a12a21b 100755
--- a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilter.java
+++ b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilter.java
@@ -18,7 +18,6 @@
package org.apache.knox.gateway.hadoopauth.filter;
import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authorize.AuthorizationException;
import org.apache.hadoop.security.authorize.ProxyUsers;
import org.apache.hadoop.util.HttpExceptionUtils;
@@ -115,7 +114,10 @@ public class HadoopAuthFilter extends
@Override
public void init(FilterConfig filterConfig) throws ServletException {
- Configuration conf = getProxyuserConfiguration(filterConfig);
+ // Return a {@link Configuration} instance with the proxy user
+ // (<code>hadoop.proxyuser.*</code>) properties set using parameter information
+ // from the filterConfig.
+ final Configuration conf = AuthFilterUtils.getProxyUserConfiguration(filterConfig, PROXYUSER_PREFIX);
ProxyUsers.refreshSuperUserGroupsConfiguration(conf, PROXYUSER_PREFIX);
Collection<String> ignoredServices = null;
@@ -198,38 +200,18 @@ public class HadoopAuthFilter extends
* (proxy user) is allowed to set specified proxied user. It is expected that the relevant
* topology file has the required hadoop.proxyuser configurations set.
*/
- if (!ignoreDoAs(request.getRemoteUser())) {
- String doAsUser = request.getParameter(QUERY_PARAMETER_DOAS);
- if (doAsUser != null && !doAsUser.equals(request.getRemoteUser())) {
- LOG.hadoopAuthDoAsUser(doAsUser, request.getRemoteUser(), request.getRemoteAddr());
-
- UserGroupInformation requestUgi = (request.getUserPrincipal() != null)
- ? UserGroupInformation.createRemoteUser(request.getRemoteUser())
- : null;
-
- if (requestUgi != null) {
- requestUgi = UserGroupInformation.createProxyUser(doAsUser, requestUgi);
-
+ HttpServletRequest proxyRequest = null;
+ final String remoteUser = request.getRemoteUser();
+ if (!ignoreDoAs(remoteUser)) {
+ final String doAsUser = request.getParameter(QUERY_PARAMETER_DOAS);
+ if (doAsUser != null && !doAsUser.equals(remoteUser)) {
+ LOG.hadoopAuthDoAsUser(doAsUser, remoteUser, request.getRemoteAddr());
+ if (request.getUserPrincipal() != null) {
try {
- ProxyUsers.authorize(requestUgi, request.getRemoteAddr());
-
- final UserGroupInformation ugiF = requestUgi;
- request = new HttpServletRequestWrapper(request) {
- @Override
- public String getRemoteUser() {
- return ugiF.getShortUserName();
- }
-
- @Override
- public Principal getUserPrincipal() {
- return ugiF::getUserName;
- }
- };
-
+ proxyRequest = AuthFilterUtils.getProxyRequest(request, doAsUser);
LOG.hadoopAuthProxyUserSuccess();
} catch (AuthorizationException ex) {
- HttpExceptionUtils.createServletExceptionResponse(response,
- HttpServletResponse.SC_FORBIDDEN, ex);
+ HttpExceptionUtils.createServletExceptionResponse(response, HttpServletResponse.SC_FORBIDDEN, ex);
LOG.hadoopAuthProxyUserFailed(ex);
return;
}
@@ -237,7 +219,7 @@ public class HadoopAuthFilter extends
}
}
- super.doFilter(filterChain, request, response);
+ super.doFilter(filterChain, proxyRequest == null ? request : proxyRequest, response);
}
/**
@@ -327,30 +309,6 @@ public class HadoopAuthFilter extends
return (userName == null) || userName.isEmpty() || ignoreDoAs.contains(userName.toLowerCase(Locale.ROOT));
}
- /**
- * Return a {@link Configuration} instance with the proxy user (<code>hadoop.proxyuser.*</code>)
- * properties set using parameter information from the filterConfig.
- *
- * @param filterConfig the {@link FilterConfig} to query
- * @return a {@link Configuration}
- */
- private Configuration getProxyuserConfiguration(FilterConfig filterConfig) {
- Configuration conf = new Configuration(false);
-
- // Iterate through the init parameters of the filter configuration to add Hadoop proxyuser
- // parameters to the configuration instance
- Enumeration<?> names = filterConfig.getInitParameterNames();
- while (names.hasMoreElements()) {
- String name = (String) names.nextElement();
- if (name.startsWith(PROXYUSER_PREFIX + ".")) {
- String value = filterConfig.getInitParameter(name);
- conf.set(name, value);
- }
- }
-
- return conf;
- }
-
// Visible for testing
Properties getConfiguration(AliasService aliasService, String configPrefix, FilterConfig filterConfig) throws ServletException {
final Properties props = new Properties();
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTAccessTokenAssertionFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTAccessTokenAssertionFilter.java
index 37644d0..2bfcd9d 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTAccessTokenAssertionFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTAccessTokenAssertionFilter.java
@@ -19,7 +19,6 @@ package org.apache.knox.gateway.provider.federation.jwt.filter;
import java.io.IOException;
import java.security.AccessController;
-import java.security.Principal;
import java.text.ParseException;
import java.util.HashMap;
@@ -35,8 +34,8 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.knox.gateway.filter.security.AbstractIdentityAssertionFilter;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.provider.federation.jwt.JWTMessages;
-import org.apache.knox.gateway.services.ServiceType;
import org.apache.knox.gateway.services.GatewayServices;
+import org.apache.knox.gateway.services.ServiceType;
import org.apache.knox.gateway.services.registry.ServiceRegistry;
import org.apache.knox.gateway.services.security.token.JWTokenAttributes;
import org.apache.knox.gateway.services.security.token.JWTokenAttributesBuilder;
@@ -140,15 +139,9 @@ public class JWTAccessTokenAssertionFilter extends AbstractIdentityAssertionFilt
private String getAccessToken(final String principalName, String serviceName, long expires) {
String accessToken = null;
- Principal p = new Principal() {
- @Override
- public String getName() {
- return principalName;
- }
- };
JWT token;
try {
- final JWTokenAttributes jwtAttributes = new JWTokenAttributesBuilder().setPrincipal(p).setAudiences(serviceName).setAlgorithm(signatureAlgorithm).setExpires(expires).build();
+ final JWTokenAttributes jwtAttributes = new JWTokenAttributesBuilder().setUserName(principalName).setAudiences(serviceName).setAlgorithm(signatureAlgorithm).setExpires(expires).build();
token = authority.issueToken(jwtAttributes);
// Coverity CID 1327961
if( token != null ) {
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTAuthCodeAssertionFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTAuthCodeAssertionFilter.java
index ab03f42..7bd3b55 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTAuthCodeAssertionFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTAuthCodeAssertionFilter.java
@@ -65,7 +65,7 @@ public class JWTAuthCodeAssertionFilter extends AbstractIdentityAssertionFilter
principalName = mapper.mapUserPrincipal(principalName);
JWT authCode;
try {
- authCode = authority.issueToken(new JWTokenAttributesBuilder().setPrincipal(subject).setAlgorithm(signatureAlgorithm).build());
+ authCode = authority.issueToken(new JWTokenAttributesBuilder().setUserName(principalName).setAlgorithm(signatureAlgorithm).build());
// get the url for the token service
String url = null;
if (sr != null) {
diff --git a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/TokenIDAsHTTPBasicCredsFederationFilterTest.java b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/TokenIDAsHTTPBasicCredsFederationFilterTest.java
index 322debd..7a15255 100644
--- a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/TokenIDAsHTTPBasicCredsFederationFilterTest.java
+++ b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/TokenIDAsHTTPBasicCredsFederationFilterTest.java
@@ -31,6 +31,7 @@ import java.util.Properties;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -487,8 +488,23 @@ public class TokenIDAsHTTPBasicCredsFederationFilterTest extends JWTAsHTTPBasicC
@Override
public Collection<KnoxToken> getTokens(String userName) {
+ return fetchTokens(userName, false);
+ }
+
+ @Override
+ public Collection<KnoxToken> getDoAsTokens(String createdBy) {
+ return fetchTokens(createdBy, true);
+ }
+
+ private Collection<KnoxToken> fetchTokens(String userName, boolean createdBy) {
final Collection<KnoxToken> tokens = new TreeSet<>();
- tokenMetadata.entrySet().stream().filter(entry -> entry.getValue().getUserName().equals(userName)).forEach(metadata -> {
+ final Predicate<Map.Entry<String, TokenMetadata>> filterPredicate;
+ if (createdBy) {
+ filterPredicate = entry -> userName.equals(entry.getValue().getCreatedBy());
+ } else {
+ filterPredicate = entry -> userName.equals(entry.getValue().getUserName());
+ }
+ tokenMetadata.entrySet().stream().filter(filterPredicate).forEach(metadata -> {
String tokenId = metadata.getKey();
try {
tokens.add(new KnoxToken(tokenId, getTokenIssueTime(tokenId), getTokenExpiration(tokenId), 0L, metadata.getValue()));
diff --git a/gateway-release/home/conf/topologies/homepage.xml b/gateway-release/home/conf/topologies/homepage.xml
index b70d34e..a3fce65 100644
--- a/gateway-release/home/conf/topologies/homepage.xml
+++ b/gateway-release/home/conf/topologies/homepage.xml
@@ -101,6 +101,18 @@
<name>knox.token.type</name>
<value>JWT</value>
</param>
+ <param>
+ <name>knox.token.proxyuser.admin.users</name>
+ <value>*</value>
+ </param>
+ <param>
+ <name>knox.token.proxyuser.admin.groups</name>
+ <value>*</value>
+ </param>
+ <param>
+ <name>knox.token.proxyuser.admin.hosts</name>
+ <value>*</value>
+ </param>
</service>
<application>
<name>tokengen</name>
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityService.java
index 7c5c295..6ab7342 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityService.java
@@ -106,7 +106,7 @@ public class DefaultTokenAuthorityService implements JWTokenAuthority, Service {
public JWT issueToken(JWTokenAttributes jwtAttributes) throws TokenServiceException {
String[] claimArray = new String[6];
claimArray[0] = "KNOXSSO";
- claimArray[1] = jwtAttributes.getPrincipal().getName();
+ claimArray[1] = jwtAttributes.getUserName();
claimArray[2] = null;
if (jwtAttributes.getExpires() == -1) {
claimArray[3] = null;
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java
index 3096910..cfe9a4a 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenStateService.java
@@ -29,6 +29,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.management.InstanceAlreadyExistsException;
@@ -420,8 +421,23 @@ public class DefaultTokenStateService implements TokenStateService {
@Override
public Collection<KnoxToken> getTokens(String userName) {
+ return fetchTokens(userName, false);
+ }
+
+ @Override
+ public Collection<KnoxToken> getDoAsTokens(String createdBy) {
+ return fetchTokens(createdBy, true);
+ }
+
+ private Collection<KnoxToken> fetchTokens(String userName, boolean createdBy) {
final Collection<KnoxToken> tokens = new TreeSet<>();
- metadataMap.entrySet().stream().filter(entry -> entry.getValue().getUserName().equals(userName)).forEach(metadata -> {
+ final Predicate<Map.Entry<String, TokenMetadata>> filterPredicate;
+ if (createdBy) {
+ filterPredicate = entry -> userName.equals(entry.getValue().getCreatedBy());
+ } else {
+ filterPredicate = entry -> userName.equals(entry.getValue().getUserName());
+ }
+ metadataMap.entrySet().stream().filter(filterPredicate).forEach(metadata -> {
String tokenId = metadata.getKey();
try {
tokens.add(new KnoxToken(tokenId, getTokenIssueTime(tokenId), getTokenExpiration(tokenId), getMaxLifetime(tokenId), metadata.getValue()));
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/JDBCTokenStateService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/JDBCTokenStateService.java
index 13f9a1d..6b27ec5 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/JDBCTokenStateService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/JDBCTokenStateService.java
@@ -303,4 +303,14 @@ public class JDBCTokenStateService extends DefaultTokenStateService {
return Collections.emptyList();
}
}
+
+ @Override
+ public Collection<KnoxToken> getDoAsTokens(String createdBy) {
+ try {
+ return tokenDatabase.getDoAsTokens(createdBy);
+ } catch (SQLException e) {
+ log.errorFetchingDoAsTokensForUserFromDatabase(createdBy, e.getMessage(), e);
+ return Collections.emptyList();
+ }
+ }
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateDatabase.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateDatabase.java
index 73b3982..0b70514 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateDatabase.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateDatabase.java
@@ -59,6 +59,9 @@ public class TokenStateDatabase {
private static final String GET_TOKENS_BY_USER_NAME_SQL = "SELECT kt.token_id, kt.issue_time, kt.expiration, kt.max_lifetime, ktm.md_name, ktm.md_value FROM " + TOKENS_TABLE_NAME
+ " kt, " + TOKEN_METADATA_TABLE_NAME + " ktm WHERE kt.token_id = ktm.token_id AND kt.token_id IN (SELECT token_id FROM " + TOKEN_METADATA_TABLE_NAME + " WHERE md_name = '" + TokenMetadata.USER_NAME + "' AND md_value = ? )"
+ " ORDER BY kt.issue_time";
+ private static final String GET_TOKENS_CREATED_BY_USER_NAME_SQL = "SELECT kt.token_id, kt.issue_time, kt.expiration, kt.max_lifetime, ktm.md_name, ktm.md_value FROM " + TOKENS_TABLE_NAME
+ + " kt, " + TOKEN_METADATA_TABLE_NAME + " ktm WHERE kt.token_id = ktm.token_id AND kt.token_id IN (SELECT token_id FROM " + TOKEN_METADATA_TABLE_NAME + " WHERE md_name = '" + TokenMetadata.CREATED_BY + "' AND md_value = ? )"
+ + " ORDER BY kt.issue_time";
private final DataSource dataSource;
@@ -203,11 +206,19 @@ public class TokenStateDatabase {
}
Collection<KnoxToken> getTokens(String userName) throws SQLException {
+ return fetchTokens(userName, GET_TOKENS_BY_USER_NAME_SQL);
+ }
+
+ Collection<KnoxToken> getDoAsTokens(String userName) throws SQLException {
+ return fetchTokens(userName, GET_TOKENS_CREATED_BY_USER_NAME_SQL);
+ }
+
+ private Collection<KnoxToken> fetchTokens(String userName, String sql) throws SQLException {
Map<String, KnoxToken> tokenMap = new LinkedHashMap<>();
- try (Connection connection = dataSource.getConnection(); PreparedStatement getTokenIdsStatement = connection.prepareStatement(GET_TOKENS_BY_USER_NAME_SQL)) {
+ try (Connection connection = dataSource.getConnection(); PreparedStatement getTokenIdsStatement = connection.prepareStatement(sql)) {
getTokenIdsStatement.setString(1, userName);
try (ResultSet rs = getTokenIdsStatement.executeQuery()) {
- while(rs.next()) {
+ while (rs.next()) {
String tokenId = rs.getString(1);
long issueTime = rs.getLong(2);
long expiration = rs.getLong(3);
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateServiceMessages.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateServiceMessages.java
index 956ff9a..f24c188 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateServiceMessages.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/TokenStateServiceMessages.java
@@ -246,4 +246,7 @@ public interface TokenStateServiceMessages {
@Message(level = MessageLevel.ERROR, text = "An error occurred while fetching tokens for user {0} from the database : {1}")
void errorFetchingTokensForUserFromDatabase(String userName, String errorMessage, @StackTrace(level = MessageLevel.DEBUG) Exception e);
+
+ @Message(level = MessageLevel.ERROR, text = "An error occurred while fetching impersonation tokens for user {0} from the database : {1}")
+ void errorFetchingDoAsTokensForUserFromDatabase(String userName, String errorMessage, @StackTrace(level = MessageLevel.DEBUG) Exception e);
}
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java
index 3845a07..2efe159 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java
@@ -48,8 +48,7 @@ import static org.junit.Assert.assertTrue;
public class DefaultTokenAuthorityServiceTest {
@Test
public void testTokenCreation() throws Exception {
- Principal principal = EasyMock.createNiceMock(Principal.class);
- EasyMock.expect(principal.getName()).andReturn("john.doe@example.com");
+ final String userName = "john.doe@example.com";
GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
String basedir = System.getProperty("basedir");
@@ -74,7 +73,7 @@ public class DefaultTokenAuthorityServiceTest {
AliasService as = EasyMock.createNiceMock(AliasService.class);
EasyMock.expect(as.getSigningKeyPassphrase()).andReturn("horton".toCharArray()).anyTimes();
- EasyMock.replay(principal, config, ms, as);
+ EasyMock.replay(config, ms, as);
DefaultKeystoreService ks = new DefaultKeystoreService();
ks.setMasterService(ms);
@@ -88,7 +87,7 @@ public class DefaultTokenAuthorityServiceTest {
ta.init(config, new HashMap<>());
ta.start();
- JWT token = ta.issueToken(new JWTokenAttributesBuilder().setPrincipal(principal).setAlgorithm("RS256").setManaged(true).build());
+ JWT token = ta.issueToken(new JWTokenAttributesBuilder().setUserName(userName).setAlgorithm("RS256").setManaged(true).build());
assertEquals("KNOXSSO", token.getIssuer());
assertEquals("john.doe@example.com", token.getSubject());
assertTrue(Boolean.parseBoolean(token.getClaim(JWTToken.MANAGED_TOKEN_CLAIM)));
@@ -98,8 +97,7 @@ public class DefaultTokenAuthorityServiceTest {
@Test
public void testTokenCreationAudience() throws Exception {
- Principal principal = EasyMock.createNiceMock(Principal.class);
- EasyMock.expect(principal.getName()).andReturn("john.doe@example.com");
+ final String userName = "john.doe@example.com";
GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
String basedir = System.getProperty("basedir");
@@ -124,7 +122,7 @@ public class DefaultTokenAuthorityServiceTest {
AliasService as = EasyMock.createNiceMock(AliasService.class);
EasyMock.expect(as.getSigningKeyPassphrase()).andReturn("horton".toCharArray()).anyTimes();
- EasyMock.replay(principal, config, ms, as);
+ EasyMock.replay(config, ms, as);
DefaultKeystoreService ks = new DefaultKeystoreService();
ks.setMasterService(ms);
@@ -139,7 +137,7 @@ public class DefaultTokenAuthorityServiceTest {
ta.start();
JWT token = ta
- .issueToken(new JWTokenAttributesBuilder().setPrincipal(principal).setAudiences("https://login.example.com").setAlgorithm("RS256").build());
+ .issueToken(new JWTokenAttributesBuilder().setUserName(userName).setAudiences("https://login.example.com").setAlgorithm("RS256").build());
assertEquals("KNOXSSO", token.getIssuer());
assertEquals("john.doe@example.com", token.getSubject());
assertEquals("https://login.example.com", token.getAudience());
@@ -149,8 +147,7 @@ public class DefaultTokenAuthorityServiceTest {
@Test
public void testTokenCreationNullAudience() throws Exception {
- Principal principal = EasyMock.createNiceMock(Principal.class);
- EasyMock.expect(principal.getName()).andReturn("john.doe@example.com");
+ final String userName = "john.doe@example.com";
GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
String basedir = System.getProperty("basedir");
@@ -175,7 +172,7 @@ public class DefaultTokenAuthorityServiceTest {
AliasService as = EasyMock.createNiceMock(AliasService.class);
EasyMock.expect(as.getSigningKeyPassphrase()).andReturn("horton".toCharArray()).anyTimes();
- EasyMock.replay(principal, config, ms, as);
+ EasyMock.replay(config, ms, as);
DefaultKeystoreService ks = new DefaultKeystoreService();
ks.setMasterService(ms);
@@ -189,7 +186,7 @@ public class DefaultTokenAuthorityServiceTest {
ta.init(config, new HashMap<>());
ta.start();
- JWT token = ta.issueToken(new JWTokenAttributesBuilder().setPrincipal(principal).setAlgorithm("RS256").build());
+ JWT token = ta.issueToken(new JWTokenAttributesBuilder().setUserName(userName).setAlgorithm("RS256").build());
assertEquals("KNOXSSO", token.getIssuer());
assertEquals("john.doe@example.com", token.getSubject());
@@ -198,8 +195,7 @@ public class DefaultTokenAuthorityServiceTest {
@Test
public void testTokenCreationSignatureAlgorithm() throws Exception {
- Principal principal = EasyMock.createNiceMock(Principal.class);
- EasyMock.expect(principal.getName()).andReturn("john.doe@example.com");
+ final String userName = "john.doe@example.com";
GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
String basedir = System.getProperty("basedir");
@@ -224,7 +220,7 @@ public class DefaultTokenAuthorityServiceTest {
AliasService as = EasyMock.createNiceMock(AliasService.class);
EasyMock.expect(as.getSigningKeyPassphrase()).andReturn("horton".toCharArray()).anyTimes();
- EasyMock.replay(principal, config, ms, as);
+ EasyMock.replay(config, ms, as);
DefaultKeystoreService ks = new DefaultKeystoreService();
ks.setMasterService(ms);
@@ -238,7 +234,7 @@ public class DefaultTokenAuthorityServiceTest {
ta.init(config, new HashMap<>());
ta.start();
- JWT token = ta.issueToken(new JWTokenAttributesBuilder().setPrincipal(principal).setAlgorithm("RS512").build());
+ JWT token = ta.issueToken(new JWTokenAttributesBuilder().setUserName(userName).setAlgorithm("RS512").build());
assertEquals("KNOXSSO", token.getIssuer());
assertEquals("john.doe@example.com", token.getSubject());
assertTrue(token.getHeader().contains("RS512"));
@@ -248,8 +244,7 @@ public class DefaultTokenAuthorityServiceTest {
@Test (expected = TokenServiceException.class)
public void testTokenCreationBadSignatureAlgorithm() throws Exception {
- Principal principal = EasyMock.createNiceMock(Principal.class);
- EasyMock.expect(principal.getName()).andReturn("john.doe@example.com");
+ final String userName = "john.doe@example.com";
GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
String basedir = System.getProperty("basedir");
@@ -274,7 +269,7 @@ public class DefaultTokenAuthorityServiceTest {
AliasService as = EasyMock.createNiceMock(AliasService.class);
EasyMock.expect(as.getSigningKeyPassphrase()).andReturn("horton".toCharArray()).anyTimes();
- EasyMock.replay(principal, config, ms, as);
+ EasyMock.replay(config, ms, as);
DefaultKeystoreService ks = new DefaultKeystoreService();
ks.setMasterService(ms);
@@ -286,7 +281,7 @@ public class DefaultTokenAuthorityServiceTest {
ta.setKeystoreService(ks);
ta.init(config, new HashMap<>());
- ta.issueToken(new JWTokenAttributesBuilder().setPrincipal(principal).setAlgorithm("none").build());
+ ta.issueToken(new JWTokenAttributesBuilder().setUserName(userName).setAlgorithm("none").build());
}
@Test
@@ -303,8 +298,7 @@ public class DefaultTokenAuthorityServiceTest {
String customSigningKeyAlias = "testSigningKeyAlias";
String customSigningKeyPassphrase = "testSigningKeyPassphrase";
- Principal principal = EasyMock.createNiceMock(Principal.class);
- EasyMock.expect(principal.getName()).andReturn("john.doe@example.com");
+ final String userName = "john.doe@example.com";
GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
String basedir = System.getProperty("basedir");
@@ -328,7 +322,7 @@ public class DefaultTokenAuthorityServiceTest {
AliasService as = EasyMock.createNiceMock(AliasService.class);
EasyMock.expect(as.getSigningKeyPassphrase()).andReturn("horton".toCharArray()).anyTimes();
- EasyMock.replay(principal, config, ms, as);
+ EasyMock.replay(config, ms, as);
DefaultKeystoreService ks = new DefaultKeystoreService();
ks.setMasterService(ms);
@@ -340,7 +334,7 @@ public class DefaultTokenAuthorityServiceTest {
ta.init(config, new HashMap<>());
ta.start();
- final JWTokenAttributes jwtAttributes = new JWTokenAttributesBuilder().setPrincipal(principal).setAudiences(Collections.emptyList()).setAlgorithm("RS256").setExpires(-1)
+ final JWTokenAttributes jwtAttributes = new JWTokenAttributesBuilder().setUserName(userName).setAudiences(Collections.emptyList()).setAlgorithm("RS256").setExpires(-1)
.setSigningKeystoreName(customSigningKeyName).setSigningKeystoreAlias(customSigningKeyAlias).setSigningKeystorePassphrase(customSigningKeyPassphrase.toCharArray()).build();
JWT token = ta.issueToken(jwtAttributes);
assertEquals("KNOXSSO", token.getIssuer());
diff --git a/gateway-service-knoxsso/src/main/java/org/apache/knox/gateway/service/knoxsso/WebSSOResource.java b/gateway-service-knoxsso/src/main/java/org/apache/knox/gateway/service/knoxsso/WebSSOResource.java
index a902199..522e3b9 100644
--- a/gateway-service-knoxsso/src/main/java/org/apache/knox/gateway/service/knoxsso/WebSSOResource.java
+++ b/gateway-service-knoxsso/src/main/java/org/apache/knox/gateway/service/knoxsso/WebSSOResource.java
@@ -262,7 +262,7 @@ public class WebSSOResource {
signingKeystorePassphrase = as.getPasswordFromAliasForCluster(clusterName, signingKeystorePassphraseAlias);
}
- final JWTokenAttributes jwtAttributes = new JWTokenAttributesBuilder().setPrincipal(p).setAudiences(targetAudiences).setAlgorithm(signatureAlgorithm).setExpires(getExpiry())
+ final JWTokenAttributes jwtAttributes = new JWTokenAttributesBuilder().setUserName(p.getName()).setAudiences(targetAudiences).setAlgorithm(signatureAlgorithm).setExpires(getExpiry())
.setSigningKeystoreName(signingKeystoreName).setSigningKeystoreAlias(signingKeystoreAlias).setSigningKeystorePassphrase(signingKeystorePassphrase).build();
JWT token = tokenAuthority.issueToken(jwtAttributes);
diff --git a/gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java b/gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java
index a28a589..be85e76 100644
--- a/gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java
+++ b/gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java
@@ -772,7 +772,7 @@ public class WebSSOResourceTest {
throws TokenServiceException {
String[] claimArray = new String[6];
claimArray[0] = "KNOXSSO";
- claimArray[1] = jwtAttributes.getPrincipal().getName();
+ claimArray[1] = jwtAttributes.getUserName();
claimArray[2] = null;
if (jwtAttributes.getExpires() == -1) {
claimArray[3] = null;
diff --git a/gateway-service-knoxtoken/pom.xml b/gateway-service-knoxtoken/pom.xml
index cd0a884..30a762d 100644
--- a/gateway-service-knoxtoken/pom.xml
+++ b/gateway-service-knoxtoken/pom.xml
@@ -85,6 +85,22 @@
<artifactId>json-smart</artifactId>
<scope>compile</scope>
</dependency>
+
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-common</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>javax.activation</groupId>
+ <artifactId>javax.activation-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>dnsjava</groupId>
+ <artifactId>dnsjava</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
<dependency>
<groupId>org.apache.knox</groupId>
<artifactId>gateway-test-utils</artifactId>
diff --git a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
index ea03439..2d227cd 100644
--- a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
+++ b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java
@@ -19,7 +19,6 @@ package org.apache.knox.gateway.service.knoxtoken;
import java.nio.charset.StandardCharsets;
import java.security.KeyStoreException;
-import java.security.Principal;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
@@ -58,6 +57,9 @@ import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.util.ByteUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.authorize.AuthorizationException;
+import org.apache.hadoop.security.authorize.ProxyUsers;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.security.SubjectUtils;
@@ -80,6 +82,7 @@ import org.apache.knox.gateway.services.security.token.UnknownTokenException;
import org.apache.knox.gateway.services.security.token.impl.JWT;
import org.apache.knox.gateway.services.security.token.impl.JWTToken;
import org.apache.knox.gateway.services.security.token.impl.TokenMAC;
+import org.apache.knox.gateway.util.AuthFilterUtils;
import org.apache.knox.gateway.util.JsonUtils;
import org.apache.knox.gateway.util.Tokens;
@@ -132,6 +135,9 @@ public class TokenResource {
static final String ENABLE_PATH = "/enable";
static final String DISABLE_PATH = "/disable";
private static final String TARGET_ENDPOINT_PULIC_CERT_PEM = "knox.token.target.endpoint.cert.pem";
+ static final String QUERY_PARAMETER_DOAS = "doAs";
+ static final String PROXYUSER_PREFIX = "knox.token.proxyuser";
+
private static TokenServiceMessages log = MessagesFactory.get(TokenServiceMessages.class);
private long tokenTTL = TOKEN_TTL_DEFAULT;
private String tokenType;
@@ -289,6 +295,9 @@ public class TokenResource {
}
}
setTokenStateServiceStatusMap();
+
+ final Configuration conf = AuthFilterUtils.getProxyUserConfiguration(context, PROXYUSER_PREFIX);
+ ProxyUsers.refreshSuperUserGroupsConfiguration(conf, PROXYUSER_PREFIX);
}
private String getTokenTTLAsText() {
@@ -424,7 +433,8 @@ public class TokenResource {
});
final String userName = uriInfo.getQueryParameters().getFirst("userName");
- final Collection<KnoxToken> userTokens = tokenStateService.getTokens(userName);
+ final String createdBy = uriInfo.getQueryParameters().getFirst("createdBy");
+ final Collection<KnoxToken> userTokens = createdBy == null ? tokenStateService.getTokens(userName) : tokenStateService.getDoAsTokens(createdBy);
final Collection<KnoxToken> tokens = new TreeSet<>();
if (metadataMap.isEmpty()) {
tokens.addAll(userTokens);
@@ -672,7 +682,24 @@ public class TokenResource {
.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
JWTokenAuthority ts = services.getService(ServiceType.TOKEN_SERVICE);
- Principal p = request.getUserPrincipal();
+
+ String userName = request.getUserPrincipal().getName();
+ String createdBy = null;
+ // checking the doAs user only makes sense if tokens are managed (this is where we store the userName information)
+ if (tokenStateService != null) {
+ final String doAsUser = request.getParameter(QUERY_PARAMETER_DOAS);
+ if (doAsUser != null && !doAsUser.equals(userName)) {
+ try {
+ //this call will authorize the doAs request
+ AuthFilterUtils.authorizeImpersonationRequest(request, doAsUser);
+ createdBy = userName;
+ userName = doAsUser;
+ } catch (AuthorizationException e) {
+ return Response.status(Response.Status.FORBIDDEN).entity("{ \"" + e.getMessage() + "\" }").build();
+ }
+ }
+ }
+
long expires = getExpiry();
if (endpointPublicCert == null) {
@@ -699,15 +726,15 @@ public class TokenResource {
if (tokenStateService != null) {
if (tokenLimitPerUser != -1) { // if -1 => unlimited tokens for all users
- final Collection<KnoxToken> userTokens = tokenStateService.getTokens(p.getName());
+ final Collection<KnoxToken> userTokens = tokenStateService.getTokens(userName);
if (userTokens.size() >= tokenLimitPerUser) {
- log.tokenLimitExceeded(p.getName());
+ log.tokenLimitExceeded(userName);
if (UserLimitExceededAction.RETURN_ERROR == userLimitExceededAction) {
return Response.status(Response.Status.FORBIDDEN).entity("{ \"Unable to get token - token limit exceeded.\" }").build();
} else {
// userTokens is an ordered collection (by issue time) -> the first element is the oldest one
final String oldestTokenId = userTokens.iterator().next().getTokenId();
- log.generalInfoMessage(String.format(Locale.getDefault(), "Revoking %s's oldest token %s ...", p.getName(), Tokens.getTokenIDDisplayText(oldestTokenId)));
+ log.generalInfoMessage(String.format(Locale.getDefault(), "Revoking %s's oldest token %s ...", userName, Tokens.getTokenIDDisplayText(oldestTokenId)));
revoke(oldestTokenId);
}
}
@@ -720,7 +747,7 @@ public class TokenResource {
JWTokenAttributes jwtAttributes;
final JWTokenAttributesBuilder jwtAttributesBuilder = new JWTokenAttributesBuilder();
jwtAttributesBuilder
- .setPrincipal(p)
+ .setUserName(userName)
.setAlgorithm(signatureAlgorithm)
.setExpires(expires)
.setManaged(managedToken)
@@ -766,9 +793,12 @@ public class TokenResource {
expires,
maxTokenLifetime.orElse(tokenStateService.getDefaultMaxLifetimeDuration()));
final String comment = request.getParameter(COMMENT);
- final TokenMetadata tokenMetadata = new TokenMetadata(p.getName(), StringUtils.isBlank(comment) ? null : comment);
- tokenMetadata.setPasscode(tokenMAC.hash(tokenId, issueTime, p.getName(), passcode));
+ final TokenMetadata tokenMetadata = new TokenMetadata(userName, StringUtils.isBlank(comment) ? null : comment);
+ tokenMetadata.setPasscode(tokenMAC.hash(tokenId, issueTime, userName, passcode));
addArbitraryTokenMetadata(tokenMetadata);
+ if (createdBy != null) {
+ tokenMetadata.setCreatedBy(createdBy);
+ }
tokenStateService.addMetadata(tokenId, tokenMetadata);
log.storedToken(getTopologyName(), Tokens.getTokenDisplayText(accessToken), Tokens.getTokenIDDisplayText(tokenId));
}
diff --git a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
index eaf514f..a443106 100644
--- a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
+++ b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
@@ -86,6 +86,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -97,6 +98,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import java.util.function.Predicate;
/**
* Some tests for the token service
@@ -147,6 +149,7 @@ public class TokenServiceResourceTest {
private void configureCommonExpectations(Map<String, String> contextExpectations, String expectedSubjectDN, Boolean serverManagedTssEnabled) throws Exception {
context = EasyMock.createNiceMock(ServletContext.class);
contextExpectations.forEach((key, value) -> EasyMock.expect(context.getInitParameter(key)).andReturn(value).anyTimes());
+ EasyMock.expect(context.getInitParameterNames()).andReturn(Collections.enumeration(contextExpectations.keySet())).anyTimes();
request = EasyMock.createNiceMock(HttpServletRequest.class);
EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes();
Principal principal = EasyMock.createNiceMock(Principal.class);
@@ -156,6 +159,9 @@ public class TokenServiceResourceTest {
if (contextExpectations.containsKey(TokenResource.LIFESPAN)) {
EasyMock.expect(request.getParameter(TokenResource.LIFESPAN)).andReturn(contextExpectations.get(TokenResource.LIFESPAN)).anyTimes();
}
+ if (contextExpectations.containsKey(TokenResource.QUERY_PARAMETER_DOAS)) {
+ EasyMock.expect(request.getParameter(TokenResource.QUERY_PARAMETER_DOAS)).andReturn(contextExpectations.get(TokenResource.QUERY_PARAMETER_DOAS)).anyTimes();
+ }
EasyMock.expect(request.getParameterNames()).andReturn(Collections.emptyEnumeration()).anyTimes();
GatewayServices services = EasyMock.createNiceMock(GatewayServices.class);
@@ -1026,8 +1032,12 @@ public class TokenServiceResourceTest {
}
private Response getUserTokensResponse(TokenResource tokenResource) {
+ return getUserTokensResponse(tokenResource, false);
+ }
+
+ private Response getUserTokensResponse(TokenResource tokenResource, boolean createdBy) {
final MultivaluedMap<String, String> queryParameters = new MultivaluedHashMap<>();
- queryParameters.put("userName", Arrays.asList(USER_NAME));
+ queryParameters.put(createdBy ? "createdBy" : "userName", Arrays.asList(USER_NAME));
final UriInfo uriInfo = EasyMock.createNiceMock(UriInfo.class);
EasyMock.expect(uriInfo.getQueryParameters()).andReturn(queryParameters).anyTimes();
EasyMock.replay(uriInfo);
@@ -1082,6 +1092,31 @@ public class TokenServiceResourceTest {
assertEquals(tokens.size(), revokeOldestToken ? configuredLimit : numberOfTokens);
}
+ @Test
+ public void testCreateImpersonatedToken() throws Exception {
+ final String impersonatedUser = "testUser";
+ final Map<String, String> contextExpectations = new HashMap<>();
+ contextExpectations.put(TokenResource.QUERY_PARAMETER_DOAS, impersonatedUser);
+ contextExpectations.put(TokenResource.PROXYUSER_PREFIX + "." + USER_NAME + ".users", impersonatedUser);
+ contextExpectations.put(TokenResource.PROXYUSER_PREFIX + "." + USER_NAME + ".hosts", "*");
+ configureCommonExpectations(contextExpectations, Boolean.TRUE);
+
+ final TokenResource tr = new TokenResource();
+ tr.request = request;
+ tr.context = context;
+ tr.init();
+
+ tr.doGet();
+
+ final Response getKnoxTokensResponse = getUserTokensResponse(tr, true);
+ final Collection<LinkedHashMap<String, Object>> tokens = ((Map<String, Collection<LinkedHashMap<String, Object>>>) JsonUtils
+ .getObjectFromJsonString(getKnoxTokensResponse.getEntity().toString())).get("tokens");
+ final LinkedHashMap<String, Object> knoxToken = tokens.iterator().next();
+ final Map<String, String> metadata = (Map<String, String>) knoxToken.get("metadata");
+ assertEquals(metadata.get("createdBy"), USER_NAME);
+ assertEquals(metadata.get("userName"), impersonatedUser);
+ }
+
/**
*
* @param isTokenStateServerManaged true, if server-side token state management should be enabled; Otherwise, false or null.
@@ -1478,11 +1513,26 @@ public class TokenServiceResourceTest {
@Override
public Collection<KnoxToken> getTokens(String userName) {
+ return fetchTokens(userName, false);
+ }
+
+ @Override
+ public Collection<KnoxToken> getDoAsTokens(String createdBy) {
+ return fetchTokens(createdBy, true);
+ }
+
+ private Collection<KnoxToken> fetchTokens(String userName, boolean createdBy) {
final Collection<KnoxToken> tokens = new TreeSet<>();
- tokenMetadata.entrySet().stream().filter(entry -> entry.getValue().getUserName().equals(userName)).forEach(metadata -> {
+ final Predicate<Map.Entry<String, TokenMetadata>> filterPredicate;
+ if (createdBy) {
+ filterPredicate = entry -> userName.equals(entry.getValue().getCreatedBy());
+ } else {
+ filterPredicate = entry -> userName.equals(entry.getValue().getUserName());
+ }
+ tokenMetadata.entrySet().stream().filter(filterPredicate).forEach(metadata -> {
String tokenId = metadata.getKey();
try {
- tokens.add(new KnoxToken(tokenId, getTokenIssueTime(tokenId), getTokenExpiration(tokenId), 0L, metadata.getValue()));
+ tokens.add(new KnoxToken(tokenId, getTokenIssueTime(tokenId), getTokenExpiration(tokenId), getMaxLifetime(tokenId), metadata.getValue()));
} catch (UnknownTokenException e) {
// NOP
}
@@ -1523,7 +1573,7 @@ public class TokenServiceResourceTest {
public JWT issueToken(JWTokenAttributes jwtAttributes) {
String[] claimArray = new String[6];
claimArray[0] = "KNOXSSO";
- claimArray[1] = jwtAttributes.getPrincipal().getName();
+ claimArray[1] = jwtAttributes.getUserName();
claimArray[2] = null;
if (jwtAttributes.getExpires() == -1) {
claimArray[3] = null;
diff --git a/gateway-spi/pom.xml b/gateway-spi/pom.xml
index 63129b1..95b07c1 100644
--- a/gateway-spi/pom.xml
+++ b/gateway-spi/pom.xml
@@ -169,6 +169,21 @@
</dependency>
<dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-common</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>javax.activation</groupId>
+ <artifactId>javax.activation-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>dnsjava</groupId>
+ <artifactId>dnsjava</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
<groupId>org.apache.knox</groupId>
<artifactId>gateway-test-utils</artifactId>
<scope>test</scope>
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/JWTokenAttributes.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/JWTokenAttributes.java
index 6f3a9fa..b7e7a82 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/JWTokenAttributes.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/JWTokenAttributes.java
@@ -17,12 +17,11 @@
*/
package org.apache.knox.gateway.services.security.token;
-import java.security.Principal;
import java.util.List;
public class JWTokenAttributes {
- private final Principal principal;
+ private final String userName;
private final List<String> audiences;
private final String algorithm;
private final long expires;
@@ -33,10 +32,9 @@ public class JWTokenAttributes {
private final String jku;
private final String type;
- JWTokenAttributes(Principal principal, List<String> audiences, String algorithm, long expires, String signingKeystoreName, String signingKeystoreAlias,
+ JWTokenAttributes(String userName, List<String> audiences, String algorithm, long expires, String signingKeystoreName, String signingKeystoreAlias,
char[] signingKeystorePassphrase, boolean managed, String jku, String type) {
- super();
- this.principal = principal;
+ this.userName = userName;
this.audiences = audiences;
this.algorithm = algorithm;
this.expires = expires;
@@ -48,8 +46,8 @@ public class JWTokenAttributes {
this.type = type;
}
- public Principal getPrincipal() {
- return principal;
+ public String getUserName() {
+ return userName;
}
public List<String> getAudiences() {
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/JWTokenAttributesBuilder.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/JWTokenAttributesBuilder.java
index feb0758..faf4bf3 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/JWTokenAttributesBuilder.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/JWTokenAttributesBuilder.java
@@ -17,15 +17,12 @@
*/
package org.apache.knox.gateway.services.security.token;
-import java.security.Principal;
import java.util.Collections;
import java.util.List;
-import javax.security.auth.Subject;
-
public class JWTokenAttributesBuilder {
- private Principal principal;
+ private String userName;
private List<String> audiences;
private String algorithm;
private long expires;
@@ -36,12 +33,8 @@ public class JWTokenAttributesBuilder {
private String jku;
private String type;
- public JWTokenAttributesBuilder setPrincipal(Subject subject) {
- return setPrincipal((Principal) subject.getPrincipals().toArray()[0]);
- }
-
- public JWTokenAttributesBuilder setPrincipal(Principal principal) {
- this.principal = principal;
+ public JWTokenAttributesBuilder setUserName(String userName) {
+ this.userName = userName;
return this;
}
@@ -95,7 +88,7 @@ public class JWTokenAttributesBuilder {
}
public JWTokenAttributes build() {
- return new JWTokenAttributes(principal, (audiences == null ? Collections.emptyList() : audiences), algorithm, expires, signingKeystoreName, signingKeystoreAlias,
+ return new JWTokenAttributes(userName, (audiences == null ? Collections.emptyList() : audiences), algorithm, expires, signingKeystoreName, signingKeystoreAlias,
signingKeystorePassphrase, managed, jku, type);
}
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenMetadata.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenMetadata.java
index 8fa49cd..b90dfb4 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenMetadata.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenMetadata.java
@@ -35,7 +35,8 @@ public class TokenMetadata {
public static final String COMMENT = "comment";
public static final String ENABLED = "enabled";
public static final String PASSCODE = "passcode";
- private static final List<String> KNOWN_MD_NAMES = Arrays.asList(USER_NAME, COMMENT, ENABLED, PASSCODE);
+ public static final String CREATED_BY = "createdBy";
+ private static final List<String> KNOWN_MD_NAMES = Arrays.asList(USER_NAME, COMMENT, ENABLED, PASSCODE, CREATED_BY);
private final Map<String, String> metadataMap = new HashMap<>();
@@ -85,11 +86,11 @@ public class TokenMetadata {
}
public String getUserName() {
- return metadataMap.get(USER_NAME);
+ return getMetadata(USER_NAME);
}
public String getComment() {
- return metadataMap.get(COMMENT);
+ return getMetadata(COMMENT);
}
public void setEnabled(boolean enabled) {
@@ -97,7 +98,7 @@ public class TokenMetadata {
}
public boolean isEnabled() {
- return Boolean.parseBoolean(metadataMap.get(ENABLED));
+ return Boolean.parseBoolean(getMetadata(ENABLED));
}
public void setPasscode(String passcode) {
@@ -106,7 +107,15 @@ public class TokenMetadata {
@JsonIgnore
public String getPasscode() {
- return metadataMap.get(PASSCODE);
+ return getMetadata(PASSCODE);
+ }
+
+ public void setCreatedBy(String createdBy) {
+ saveMetadata(CREATED_BY, createdBy);
+ }
+
+ public String getCreatedBy() {
+ return getMetadata(CREATED_BY);
}
public String toJSON() {
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenStateService.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenStateService.java
index 72ad874..56df224 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenStateService.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/token/TokenStateService.java
@@ -203,4 +203,13 @@ public interface TokenStateService extends Service {
*/
Collection<KnoxToken> getTokens(String userName);
+ /**
+ * @param createdBy the user name that identified the CREATED_BY metadata (the
+ * person who created the token for the token's user as 'doAs'
+ * @return a collection of tokens associated to the given 'created by' user;
+ * it's an empty collection if there is no associated token found in the
+ * underlying token management backend
+ */
+ Collection<KnoxToken> getDoAsTokens(String createdBy);
+
}
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/util/AuthFilterUtils.java b/gateway-spi/src/main/java/org/apache/knox/gateway/util/AuthFilterUtils.java
index e58c8f7..aeea228 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/util/AuthFilterUtils.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/util/AuthFilterUtils.java
@@ -18,9 +18,19 @@
package org.apache.knox.gateway.util;
import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.authorize.AuthorizationException;
+import org.apache.hadoop.security.authorize.ProxyUsers;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+import java.security.Principal;
+import java.util.Enumeration;
import java.util.Set;
import java.util.StringTokenizer;
@@ -67,4 +77,76 @@ public class AuthFilterUtils {
AuthFilterUtils.parseStringThenAdd(unAuthenticatedPaths, list);
}
}
+
+ public static Configuration getProxyUserConfiguration(ServletContext context, String prefix) {
+ if (context == null) {
+ throw new IllegalArgumentException("Cannot get proxyuser configuration from NULL context");
+ }
+ return getProxyUserConfiguration(context, null, prefix);
+ }
+
+ public static Configuration getProxyUserConfiguration(FilterConfig filterConfig, String prefix) {
+ if (filterConfig == null) {
+ throw new IllegalArgumentException("Cannot get proxyuser configuration from NULL filter config");
+ }
+ return getProxyUserConfiguration(null, filterConfig, prefix);
+ }
+
+ private static Configuration getProxyUserConfiguration(ServletContext context, FilterConfig filterConfig, String prefix) {
+ final Configuration conf = new Configuration(false);
+ final Enumeration<?> names = context == null ? filterConfig.getInitParameterNames() : context.getInitParameterNames();
+ if (names != null) {
+ while (names.hasMoreElements()) {
+ String name = (String) names.nextElement();
+ if (name.startsWith(prefix + ".")) {
+ String value = context == null ? filterConfig.getInitParameter(name) : context.getInitParameter(name);
+ conf.set(name, value);
+ }
+ }
+ }
+
+ return conf;
+ }
+
+ public static HttpServletRequest getProxyRequest(HttpServletRequest request, String doAsUser) throws AuthorizationException {
+ final UserGroupInformation remoteRequestUgi = getRemoteRequestUgi(request, doAsUser);
+ if (remoteRequestUgi != null) {
+ authorizeImpersonationRequest(request, remoteRequestUgi);
+
+ return new HttpServletRequestWrapper(request) {
+ @Override
+ public String getRemoteUser() {
+ return remoteRequestUgi.getShortUserName();
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return remoteRequestUgi::getUserName;
+ }
+ };
+
+ }
+ return null;
+ }
+
+ public static void authorizeImpersonationRequest(HttpServletRequest request, String doAsUser) throws AuthorizationException {
+ final UserGroupInformation remoteRequestUgi = getRemoteRequestUgi(request, doAsUser);
+ if (remoteRequestUgi != null) {
+ authorizeImpersonationRequest(request, remoteRequestUgi);
+ }
+ }
+
+ private static void authorizeImpersonationRequest(HttpServletRequest request, UserGroupInformation remoteRequestUgi) throws AuthorizationException {
+ ProxyUsers.authorize(remoteRequestUgi, request.getRemoteAddr());
+ }
+
+ private static UserGroupInformation getRemoteRequestUgi(HttpServletRequest request, String doAsUser) {
+ if (request.getUserPrincipal() != null) {
+ final String remoteUser = request.getUserPrincipal().getName();
+ final UserGroupInformation remoteUserUgi = UserGroupInformation.createRemoteUser(remoteUser);
+ return UserGroupInformation.createProxyUser(doAsUser, remoteUserUgi);
+ }
+ return null;
+ }
+
}
diff --git a/knox-token-management-ui/token-management/app/token.management.component.html b/knox-token-management-ui/token-management/app/token.management.component.html
index 4e5c6fd..7aebe25 100644
--- a/knox-token-management-ui/token-management/app/token.management.component.html
+++ b/knox-token-management-ui/token-management/app/token.management.component.html
@@ -16,12 +16,13 @@
<div>
<button (click)="gotoTokenGenerationPage();">Generate New Token</button>
- <button type="button" title="Refresh Knox Tokens" (click)="fetchKnoxTokens();">
+ <button type="button" title="Refresh Knox Tokens" (click)="fetchAllKnoxTokens();">
<span class="glyphicon glyphicon-refresh"></span>
</button>
</div>
- <div class="table-responsive" style="width:100%; overflow: auto; overflow-y: scroll">
- <table class="table table-hover" [mfData]="knoxTokens" #mf="mfDataTable" [mfRowsOnPage]="10">
+ <div class="table-responsive" style="width:100%; overflow: auto; overflow-y: scroll; padding: 10px 0px 0px 0px;">
+ <label>My Knox Tokens</label>
+ <table class="table table-hover" [mfData]="knoxTokens" #tokens="mfDataTable" [mfRowsOnPage]="10">
<thead>
<tr>
<th>Token ID</th>
@@ -33,7 +34,7 @@
</tr>
</thead>
<tbody>
- <tr *ngFor="let knoxToken of mf.data">
+ <tr *ngFor="let knoxToken of tokens.data">
<td>{{knoxToken.tokenId}}</td>
<td>{{formatDateTime(knoxToken.issueTimeLong)}}</td>
<td *ngIf="!isTokenExpired(knoxToken.expirationLong)" style="color: green">{{formatDateTime(knoxToken.expirationLong)}}</td>
@@ -62,5 +63,47 @@
</tfoot>
</table>
</div>
+
+ <!-- 'doAs' Knox Tokens (tokens created by the current user on behalf on another user -->
+
+ <div class="table-responsive" style="width:100%; overflow: auto; overflow-y: scroll; padding: 10px 0px 0px 0px;">
+ <label>Impersonation Knox Tokens</label>
+ <table class="table table-hover" [mfData]="doAsKnoxTokens" #doAsTokens="mfDataTable" [mfRowsOnPage]="10">
+ <thead>
+ <tr>
+ <th>Token ID</th>
+ <th>Issued</th>
+ <th>Expires</th>
+ <th>Comment</th>
+ <th>Additional Metadata</th>
+ <th>Impersonated User<th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let doAsKnoxtoken of doAsTokens.data">
+ <td>{{doAsKnoxtoken.tokenId}}</td>
+ <td>{{formatDateTime(doAsKnoxtoken.issueTimeLong)}}</td>
+ <td *ngIf="!isTokenExpired(doAsKnoxtoken.expirationLong)" style="color: green">{{formatDateTime(doAsKnoxtoken.expirationLong)}}</td>
+ <td *ngIf="isTokenExpired(doAsKnoxtoken.expirationLong)" style="color: red">{{formatDateTime(doAsKnoxtoken.expirationLong)}}</td>
+ <td>{{doAsKnoxtoken.metadata.comment}}</td>
+ <td>
+ <ul>
+ <li *ngFor="let metadata of getCustomMetadataArray(doAsKnoxtoken)">
+ {{metadata[0]}} = {{metadata[1]}}
+ </li>
+ </ul>
+ </td>
+ <td>{{doAsKnoxtoken.metadata.userName}}</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td colspan="6">
+ <mfBootstrapPaginator [rowsOnPageSet]="[5,10,15]"></mfBootstrapPaginator>
+ </td>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
</div>
diff --git a/knox-token-management-ui/token-management/app/token.management.component.ts b/knox-token-management-ui/token-management/app/token.management.component.ts
index 15a0e8c..e4ebc0d 100644
--- a/knox-token-management-ui/token-management/app/token.management.component.ts
+++ b/knox-token-management-ui/token-management/app/token.management.component.ts
@@ -30,6 +30,7 @@ export class TokenManagementComponent implements OnInit {
userName: string;
knoxTokens: KnoxToken[];
+ doAsKnoxTokens: KnoxToken[];
toggleBoolean(propertyName: string) {
this[propertyName] = !this[propertyName];
@@ -49,23 +50,29 @@ export class TokenManagementComponent implements OnInit {
setUserName(userName: string) {
this.userName = userName;
- this.fetchKnoxTokens();
+ this.fetchAllKnoxTokens();
}
- fetchKnoxTokens(): void {
- this.tokenManagementService.getKnoxTokens(this.userName).then(tokens => this.knoxTokens = tokens);
+ fetchAllKnoxTokens(): void {
+ this.fetchKnoxTokens(true);
+ this.fetchKnoxTokens(false);
+ }
+
+ fetchKnoxTokens(impersonated: boolean): void {
+ this.tokenManagementService.getKnoxTokens(this.userName, impersonated)
+ .then(tokens => impersonated ? this.doAsKnoxTokens = tokens : this.knoxTokens = tokens);
}
disableToken(tokenId: string) {
- this.tokenManagementService.setEnabledDisabledFlag(false, tokenId).then((response: string) => this.fetchKnoxTokens());
+ this.tokenManagementService.setEnabledDisabledFlag(false, tokenId).then((response: string) => this.fetchAllKnoxTokens());
}
enableToken(tokenId: string) {
- this.tokenManagementService.setEnabledDisabledFlag(true, tokenId).then((response: string) => this.fetchKnoxTokens());
+ this.tokenManagementService.setEnabledDisabledFlag(true, tokenId).then((response: string) => this.fetchAllKnoxTokens());
}
revokeToken(tokenId: string) {
- this.tokenManagementService.revokeToken(tokenId).then((response: string) => this.fetchKnoxTokens());
+ this.tokenManagementService.revokeToken(tokenId).then((response: string) => this.fetchAllKnoxTokens());
}
gotoTokenGenerationPage() {
diff --git a/knox-token-management-ui/token-management/app/token.management.service.ts b/knox-token-management-ui/token-management/app/token.management.service.ts
index 7fa1ab6..4da100d 100644
--- a/knox-token-management-ui/token-management/app/token.management.service.ts
+++ b/knox-token-management-ui/token-management/app/token.management.service.ts
@@ -27,16 +27,18 @@ export class TokenManagementService {
sessionUrl = window.location.pathname.replace(new RegExp('token-management/.*'), 'session/api/v1/sessioninfo');
apiUrl = window.location.pathname.replace(new RegExp('token-management/.*'), 'knoxtoken/api/v1/token/');
getKnoxTokensUrl = this.apiUrl + 'getUserTokens?userName=';
+ getDoAsKnoxTokensUrl = this.apiUrl + 'getUserTokens?createdBy=';
enableKnoxTokenUrl = this.apiUrl + 'enable';
disableKnoxTokenUrl = this.apiUrl + 'disable';
revokeKnoxTokenUrl = this.apiUrl + 'revoke';
constructor(private http: HttpClient) {}
- getKnoxTokens(userName: string): Promise<KnoxToken[]> {
+ getKnoxTokens(userName: string, impersonated: boolean): Promise<KnoxToken[]> {
let headers = new HttpHeaders();
headers = this.addJsonHeaders(headers);
- return this.http.get(this.getKnoxTokensUrl + userName, { headers: headers})
+ let urlToUse = impersonated ? this.getDoAsKnoxTokensUrl : this.getKnoxTokensUrl;
+ return this.http.get(urlToUse + userName, { headers: headers})
.toPromise()
.then(response => response['tokens'] as KnoxToken[])
.catch((err: HttpErrorResponse) => {