This is an automated email from the ASF dual-hosted git repository.
amagyar pushed a commit to branch branch-2.6
in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/branch-2.6 by this push:
new 37aa671 AMBARI-25155. Backport knox trusted proxy support for ambari
for 2.6 (amagyar) (#2821)
37aa671 is described below
commit 37aa6718779215ce345826de0ff62c6d153bbdd0
Author: Attila Magyar <[email protected]>
AuthorDate: Thu Feb 21 15:45:48 2019 +0100
AMBARI-25155. Backport knox trusted proxy support for ambari for 2.6
(amagyar) (#2821)
---
ambari-server/sbin/ambari-server | 6 +-
.../ambari/server/api/predicate/QueryLexer.java | 2 +
.../ambari/server/api/services/LogoutService.java | 3 +-
.../server/audit/event/AbstractUserAuditEvent.java | 23 ++
.../audit/event/OperationStatusAuditEvent.java | 19 +-
.../server/audit/event/TaskStatusAuditEvent.java | 22 +-
.../AmbariBasicAuthenticationFilter.java | 22 +-
.../AmbariProxiedUserDetailsImpl.java | 96 +++++++
.../authentication/AmbariProxyUserDetails.java | 32 +++
.../authentication/AmbariProxyUserDetailsImpl.java | 39 +++
.../security/authentication/AmbariUserDetails.java | 29 ++
.../authentication/AmbariUserDetailsImpl.java | 86 ++++++
.../AmbariAuthToLocalUserDetailsService.java | 53 ++--
.../AmbariKerberosAuthenticationFilter.java | 10 +
.../AmbariKerberosAuthenticationProvider.java | 123 +++++++++
.../kerberos/AmbariProxiedUserDetailsService.java | 304 +++++++++++++++++++++
.../AmbariProxyUserKerberosDetailsImpl.java | 39 +++
.../tproxy/AmbariTProxyConfiguration.java | 121 ++++++++
.../tproxy/TrustedProxyAuthenticationDetails.java | 102 +++++++
.../TrustedProxyAuthenticationDetailsSource.java | 36 +++
...stedProxyAuthenticationNotAllowedException.java | 32 +++
.../authorization/AmbariAuthorizationFilter.java | 3 +
.../authorization/AuthorizationHelper.java | 48 +++-
.../server/security/authorization/Users.java | 36 +++
.../apache/ambari/server/utils/RequestUtils.java | 121 +++++++-
ambari-server/src/main/python/ambari-server.py | 15 +-
.../src/main/python/ambari_server/serverUtils.py | 3 +
.../src/main/python/ambari_server/setupActions.py | 1 +
.../main/python/ambari_server/setupTrustedProxy.py | 124 +++++++++
.../resources/webapp/WEB-INF/spring-security.xml | 25 +-
.../audit/AccessUnauthorizedAuditEventTest.java | 19 ++
.../ambari/server/audit/LoginAuditEventTest.java | 41 +++
.../ambari/server/audit/LogoutAuditEventTest.java | 17 ++
.../audit/OperationStatusAuditEventTest.java | 4 +-
.../audit/StartOperationRequestAuditEventTest.java | 18 ++
.../server/audit/TaskStatusAuditEventTest.java | 4 +-
.../AmbariBasicAuthenticationFilterTest.java | 1 +
.../AmbariProxiedUserDetailsServiceTest.java | 204 ++++++++++++++
ambari-web/app/utils/http_client.js | 2 +-
39 files changed, 1801 insertions(+), 84 deletions(-)
diff --git a/ambari-server/sbin/ambari-server b/ambari-server/sbin/ambari-server
index d51cbfa..96352b1 100755
--- a/ambari-server/sbin/ambari-server
+++ b/ambari-server/sbin/ambari-server
@@ -182,6 +182,10 @@ case "${1:-}" in
echo -e "Purge database history..."
$PYTHON "$AMBARI_PYTHON_EXECUTABLE" $@
;;
+ setup-trusted-proxy)
+ echo -e "Setting up Trusted Proxy support..."
+ $PYTHON "$AMBARI_PYTHON_EXECUTABLE" "$@"
+ ;;
install-mpack)
echo -e "Installing management pack"
$PYTHON "$AMBARI_PYTHON_EXECUTABLE" $@
@@ -200,7 +204,7 @@ case "${1:-}" in
;;
*)
echo "Usage: $AMBARI_EXECUTABLE
-
{start|stop|reset|restart|upgrade|status|upgradestack|setup|setup-jce|setup-ldap|sync-ldap|set-current|setup-security|refresh-stack-hash|backup|restore|update-host-names|check-database|enable-stack|setup-sso|db-purge-history|install-mpack|uninstall-mpack|upgrade-mpack|setup-kerberos|setup-pam|migrate-ldap-pam}
[options]
+
{start|stop|reset|restart|upgrade|status|upgradestack|setup|setup-jce|setup-ldap|sync-ldap|set-current|setup-security|refresh-stack-hash|backup|restore|update-host-names|check-database|enable-stack|setup-sso|setup-trusted-proxy|db-purge-history|install-mpack|uninstall-mpack|upgrade-mpack|setup-kerberos|setup-pam|migrate-ldap-pam}
[options]
Use $AMBARI_PYTHON_EXECUTABLE <action> --help to get details on
options available.
Or, simply invoke ambari-server.py --help to print the options."
exit 1
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/api/predicate/QueryLexer.java
b/ambari-server/src/main/java/org/apache/ambari/server/api/predicate/QueryLexer.java
index afaae58..68e8102 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/api/predicate/QueryLexer.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/api/predicate/QueryLexer.java
@@ -44,6 +44,7 @@ public class QueryLexer {
public static final String QUERY_FROM = "from";
public static final String QUERY_MINIMAL = "minimal_response";
public static final String QUERY_SORT = "sortBy";
+ public static final String QUERY_DOAS = "doAs";
/**
* All valid deliminators.
@@ -209,6 +210,7 @@ public class QueryLexer {
SET_IGNORE.add(QUERY_FROM);
SET_IGNORE.add(QUERY_MINIMAL);
SET_IGNORE.add(QUERY_SORT);
+ SET_IGNORE.add(QUERY_DOAS);
SET_IGNORE.add("_");
}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/LogoutService.java
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/LogoutService.java
index 02403f9..88c096e 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/LogoutService.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/LogoutService.java
@@ -29,9 +29,9 @@ import org.apache.ambari.server.audit.AuditLogger;
import org.apache.ambari.server.audit.event.LogoutAuditEvent;
import org.apache.ambari.server.security.authorization.AuthorizationHelper;
import org.apache.ambari.server.utils.RequestUtils;
+import org.springframework.security.core.context.SecurityContextHolder;
import com.google.inject.Inject;
-import org.springframework.security.core.context.SecurityContextHolder;
/**
* Service performing logout of current user
@@ -64,6 +64,7 @@ public class LogoutService {
.withTimestamp(System.currentTimeMillis())
.withRemoteIp(RequestUtils.getRemoteAddress(servletRequest))
.withUserName(AuthorizationHelper.getAuthenticatedName())
+ .withProxyUserName(AuthorizationHelper.getProxyUserName())
.build();
auditLogger.log(logoutEvent);
}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/audit/event/AbstractUserAuditEvent.java
b/ambari-server/src/main/java/org/apache/ambari/server/audit/event/AbstractUserAuditEvent.java
index 6aab071..9acdfde 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/audit/event/AbstractUserAuditEvent.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/audit/event/AbstractUserAuditEvent.java
@@ -19,6 +19,7 @@
package org.apache.ambari.server.audit.event;
import org.apache.ambari.server.security.authorization.AuthorizationHelper;
+import org.apache.commons.lang.StringUtils;
/**
* Base class for audit events which are result of user actions. It appends
@@ -39,6 +40,10 @@ public abstract class AbstractUserAuditEvent extends
AbstractAuditEvent {
* Ip of the user who started the operation. Note: remote ip might not be
the original ip (proxies, routers can modify it)
*/
private String remoteIp;
+ /**
+ * Name of the proxy user if proxied
+ */
+ private String proxyUserName = AuthorizationHelper.getProxyUserName();
/**
* Appends to audit event details the user name and remote ip of the host
@@ -54,6 +59,12 @@ public abstract class AbstractUserAuditEvent extends
AbstractAuditEvent {
.append("), RemoteIp(")
.append(this.remoteIp)
.append(")");
+ if (StringUtils.isNotEmpty(this.proxyUserName)){
+ builder
+ .append(", ProxyUser(")
+ .append(this.proxyUserName)
+ .append(")");
+ }
}
/**
@@ -79,6 +90,18 @@ public abstract class AbstractUserAuditEvent extends
AbstractAuditEvent {
return (TBuilder) this;
}
+
+ /**
+ * Sets the proxy user name.
+ *
+ * @param proxyUserName
+ * @return the builder
+ */
+ public TBuilder withProxyUserName(String proxyUserName) {
+ this.proxyUserName = proxyUserName;
+
+ return (TBuilder) this;
+ }
}
protected AbstractUserAuditEvent() {
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/audit/event/OperationStatusAuditEvent.java
b/ambari-server/src/main/java/org/apache/ambari/server/audit/event/OperationStatusAuditEvent.java
index fd0068e..63c7efd9 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/audit/event/OperationStatusAuditEvent.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/audit/event/OperationStatusAuditEvent.java
@@ -25,9 +25,9 @@ import javax.annotation.concurrent.Immutable;
* Audit event for tracking operations
*/
@Immutable
-public class OperationStatusAuditEvent extends AbstractAuditEvent {
+public class OperationStatusAuditEvent extends AbstractUserAuditEvent {
- public static class OperationStatusAuditEventBuilder extends
AbstractAuditEventBuilder<OperationStatusAuditEvent,
OperationStatusAuditEventBuilder> {
+ public static class OperationStatusAuditEventBuilder extends
AbstractUserAuditEventBuilder<OperationStatusAuditEvent,
OperationStatusAuditEventBuilder> {
/**
* Request identifier
@@ -44,11 +44,6 @@ public class OperationStatusAuditEvent extends
AbstractAuditEvent {
*/
private String operation;
- /**
- * Name of the logged in user who sent the request
- */
- private String userName;
-
private OperationStatusAuditEventBuilder() {
}
@@ -64,10 +59,9 @@ public class OperationStatusAuditEvent extends
AbstractAuditEvent {
*/
@Override
protected void buildAuditMessage(StringBuilder builder) {
+ super.buildAuditMessage(builder);
builder
- .append("User(")
- .append(this.userName)
- .append("), Operation(")
+ .append(", Operation(")
.append(this.operation)
.append("), Status(")
.append(this.status)
@@ -91,11 +85,6 @@ public class OperationStatusAuditEvent extends
AbstractAuditEvent {
this.operation = operation;
return this;
}
-
- public OperationStatusAuditEventBuilder withUserName(String userName) {
- this.userName = userName;
- return this;
- }
}
private OperationStatusAuditEvent() {
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/audit/event/TaskStatusAuditEvent.java
b/ambari-server/src/main/java/org/apache/ambari/server/audit/event/TaskStatusAuditEvent.java
index 1682e74..13a14e6 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/audit/event/TaskStatusAuditEvent.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/audit/event/TaskStatusAuditEvent.java
@@ -25,9 +25,9 @@ import javax.annotation.concurrent.Immutable;
* Audit event for tracking task status
*/
@Immutable
-public class TaskStatusAuditEvent extends AbstractAuditEvent {
+public class TaskStatusAuditEvent extends AbstractUserAuditEvent {
- public static class TaskStatusAuditEventBuilder extends
AbstractAuditEventBuilder<TaskStatusAuditEvent, TaskStatusAuditEventBuilder> {
+ public static class TaskStatusAuditEventBuilder extends
AbstractUserAuditEventBuilder<TaskStatusAuditEvent,
TaskStatusAuditEventBuilder> {
/**
* Request identifier
@@ -59,11 +59,6 @@ public class TaskStatusAuditEvent extends AbstractAuditEvent
{
*/
private String details;
- /**
- * User name
- */
- private String userName;
-
private TaskStatusAuditEventBuilder() {
}
@@ -79,14 +74,9 @@ public class TaskStatusAuditEvent extends AbstractAuditEvent
{
*/
@Override
protected void buildAuditMessage(StringBuilder builder) {
- builder
- .append("User(")
- .append(this.userName)
- .append("), Operation(")
- .append(this.operation);
-
+ super.buildAuditMessage(builder);
if (details != null) {
- builder.append("), Details(")
+ builder.append(", Details(")
.append(this.details);
}
@@ -131,10 +121,6 @@ public class TaskStatusAuditEvent extends
AbstractAuditEvent {
this.details = details;
return this;
}
- public TaskStatusAuditEventBuilder withUserName(String userName) {
- this.userName = userName;
- return this;
- }
}
private TaskStatusAuditEvent() {
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariBasicAuthenticationFilter.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariBasicAuthenticationFilter.java
index 9e83f73..b6e08e8 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariBasicAuthenticationFilter.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariBasicAuthenticationFilter.java
@@ -32,6 +32,7 @@ import org.apache.ambari.server.security.AmbariEntryPoint;
import org.apache.ambari.server.security.authorization.AuthorizationHelper;
import org.apache.ambari.server.security.authorization.PermissionHelper;
import org.apache.ambari.server.utils.RequestUtils;
+import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
@@ -99,8 +100,27 @@ public class AmbariBasicAuthenticationFilter extends
BasicAuthenticationFilter i
*/
@Override
public boolean shouldApply(HttpServletRequest httpServletRequest) {
+ if (LOG.isDebugEnabled()) {
+ RequestUtils.logRequestHeadersAndQueryParams(httpServletRequest, LOG);
+ }
+
String header = httpServletRequest.getHeader("Authorization");
- return (header != null) && header.startsWith("Basic ");
+ if ((header != null) && header.startsWith("Basic ")) {
+ // If doAs is sent as a query parameter, ignore the basic auth header.
+ // This logic is here to help deal with a potential issue when Knox is
the trusted proxy and it
+ // forwards the original request's Authorization header (for Basic Auth)
when Kerberos authentication
+ // is required.
+ String doAsQueryParameterValue =
RequestUtils.getQueryStringParameterValue(httpServletRequest, "doAs");
+ if (StringUtils.isEmpty(doAsQueryParameterValue)) {
+ return true;
+ } else {
+ LOG.warn("The 'doAs' query parameter was provided; however, the
BasicAuth header is found. " +
+ "Ignoring the BasicAuth header hoping to negotiate Kerberos
authentication.");
+ return false;
+ }
+ } else {
+ return false;
+ }
}
/**
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxiedUserDetailsImpl.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxiedUserDetailsImpl.java
new file mode 100644
index 0000000..0ec0b5a
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxiedUserDetailsImpl.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.security.authentication;
+
+import java.util.Collection;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+/**
+ * AmbariProxiedUserDetailsImpl is an {@link AmbariUserDetails} implementation
that contains details
+ * about the proxied user as well as the proxy user.
+ * <p>
+ * This data can be helpful with logging information about the user performing
an action.
+ */
+public class AmbariProxiedUserDetailsImpl implements AmbariUserDetails {
+
+ /**
+ * Details about the acting user.
+ * <p>
+ * This user was specified as the doAs or proxied user during a trusted
proxy authentication attempt
+ */
+ private final UserDetails proxiedUserDetails;
+
+ /**
+ * Details about the proxy user that was authenticated.
+ * <p>
+ * This user that authenticated wth Ambari but specified a doAs or proxied
user to be used as the acting user for operations.
+ */
+ private final AmbariProxyUserDetails proxyUserDetails;
+
+ public AmbariProxiedUserDetailsImpl(UserDetails proxiedUserDetails,
AmbariProxyUserDetails proxyUserDetails) {
+ this.proxiedUserDetails = proxiedUserDetails;
+ this.proxyUserDetails = proxyUserDetails;
+ }
+
+ @Override
+ public Integer getUserId() {
+ return (proxiedUserDetails instanceof AmbariUserDetails) ?
((AmbariUserDetails) proxiedUserDetails).getUserId() : null;
+ }
+
+ @Override
+ public Collection<? extends GrantedAuthority> getAuthorities() {
+ return (proxiedUserDetails == null) ? null :
proxiedUserDetails.getAuthorities();
+ }
+
+ @Override
+ public String getPassword() {
+ return (proxiedUserDetails == null) ? null :
proxiedUserDetails.getPassword();
+ }
+
+ @Override
+ public String getUsername() {
+ return (proxiedUserDetails == null) ? null :
proxiedUserDetails.getUsername();
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return (proxiedUserDetails != null) &&
proxiedUserDetails.isAccountNonExpired();
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return (proxiedUserDetails != null) &&
proxiedUserDetails.isAccountNonLocked();
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return (proxiedUserDetails != null) &&
proxiedUserDetails.isCredentialsNonExpired();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return (proxiedUserDetails != null) && proxiedUserDetails.isEnabled();
+ }
+
+ public AmbariProxyUserDetails getProxyUserDetails() {
+ return proxyUserDetails;
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxyUserDetails.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxyUserDetails.java
new file mode 100644
index 0000000..35b16d7
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxyUserDetails.java
@@ -0,0 +1,32 @@
+/*
+ * 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.ambari.server.security.authentication;
+
+/**
+ * AmbariProxyUserDetails contains information about the proxy user
authenticated during a trusted
+ * proxy authentication attempt.
+ */
+public interface AmbariProxyUserDetails {
+ /**
+ * Returns the local username of the authenticated proxy user.
+ *
+ * @return the local username of the authenticated proxy user
+ */
+ String getUsername();
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxyUserDetailsImpl.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxyUserDetailsImpl.java
new file mode 100644
index 0000000..94bd997
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxyUserDetailsImpl.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.ambari.server.security.authentication;
+
+/**
+ * AmbariProxyUserDetailsImpl is a general implementation of a {@link
AmbariProxyUserDetails}.
+ */
+public class AmbariProxyUserDetailsImpl implements AmbariProxyUserDetails {
+ private final String username;
+
+ /**
+ * Constructor
+ * @param username the local username
+ */
+ public AmbariProxyUserDetailsImpl(String username) {
+ this.username = username;
+ }
+
+ @Override
+ public String getUsername() {
+ return username;
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariUserDetails.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariUserDetails.java
new file mode 100644
index 0000000..c0b00cd
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariUserDetails.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.security.authentication;
+
+import org.springframework.security.core.userdetails.UserDetails;
+
+/**
+ * AmbariUserDetails implementations are extensions of {@link UserDetails}
that contain information
+ * about the authenticated user that is needed specifically by Ambari. For
example, the user's
+ * <code>userId</code>.
+ */
+public interface AmbariUserDetails extends UserDetails {
+ Integer getUserId();
+}
\ No newline at end of file
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariUserDetailsImpl.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariUserDetailsImpl.java
new file mode 100644
index 0000000..235a545
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariUserDetailsImpl.java
@@ -0,0 +1,86 @@
+/*
+ * 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.ambari.server.security.authentication;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.ambari.server.security.authorization.User;
+import org.springframework.security.core.GrantedAuthority;
+
+/**
+ * AmbariUserDetails is an implementation of {@link AmbariUserDetails}
+ * <p>
+ * Ideally instances of this class are used as the value returned by {@link
org.springframework.security.core.Authentication#getPrincipal()}
+ */
+public class AmbariUserDetailsImpl implements AmbariUserDetails {
+
+ private final User user;
+ private final String password;
+ private final Collection<? extends GrantedAuthority> grantedAuthorities;
+
+ public AmbariUserDetailsImpl(User user, String password, Collection<?
extends GrantedAuthority> grantedAuthorities) {
+ this.user = user;
+ this.password = password;
+ this.grantedAuthorities = (grantedAuthorities == null)
+ ? Collections.<GrantedAuthority>emptyList()
+ : Collections.unmodifiableCollection(new
ArrayList<>(grantedAuthorities));
+ }
+
+ @Override
+ public Collection<? extends GrantedAuthority> getAuthorities() {
+ return grantedAuthorities;
+ }
+
+ @Override
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public String getUsername() {
+ return (user == null) ? null : user.getUserName();
+ }
+
+ @Override
+ public Integer getUserId() {
+ return (user == null) ? null : user.getUserId();
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return (user != null) && user.isActive();
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariAuthToLocalUserDetailsService.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariAuthToLocalUserDetailsService.java
index 6e84233..7d89e7e 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariAuthToLocalUserDetailsService.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariAuthToLocalUserDetailsService.java
@@ -61,7 +61,7 @@ public class AmbariAuthToLocalUserDetailsService implements
UserDetailsService {
* @param users the Ambari users access object
* @throws AmbariException if an error occurs parsing the user-provided
auth-to-local rules
*/
- public AmbariAuthToLocalUserDetailsService(Configuration configuration,
Users users) throws AmbariException {
+ public AmbariAuthToLocalUserDetailsService(Configuration configuration,
Users users) {
String authToLocalRules = null;
List<UserType> orderedUserTypes = null;
@@ -89,29 +89,40 @@ public class AmbariAuthToLocalUserDetailsService implements
UserDetailsService {
@Override
public UserDetails loadUserByUsername(String principal) throws
UsernameNotFoundException {
- try {
- String username;
-
- // Since KerberosName relies on a static variable to hold on to the
auth-to-local rules, attempt
- // to protect access to the rule set by blocking other threads from
chaning the rules out from
- // under us during this operation. Similar logic is used in
org.apache.ambari.server.view.ViewContextImpl.getUsername().
- synchronized (KerberosName.class) {
- KerberosName.setRules(authToLocalRules);
- username = new KerberosName(principal).getShortName();
- }
+ String username = translatePrincipalName(principal);
+ if (username == null) {
+ String message = String.format("Failed to translate %s to a local
username during Kerberos authentication.", principal);
+ LOG.warn(message);
+ throw new UsernameNotFoundException(message);
+ }
+ LOG.info("Translated {} to {} using auth-to-local rules during Kerberos
authentication.", principal, username);
+ return createUser(username);
+ }
- if (username == null) {
- String message = String.format("Failed to translate %s to a local
username during Kerberos authentication.", principal);
+ /**
+ * Using the auth-to-local rules stored in
<code>authentication.kerberos.auth_to_local.rules</code>
+ * in the <code>ambari.properties</code> file, translate the supplied
principal name to a local name.
+ *
+ * @param principalName the principal name to translate
+ * @return a local username
+ */
+ public String translatePrincipalName(String principalName) {
+ if (StringUtils.isNotEmpty(principalName) && principalName.contains("@")) {
+ try {
+ // Since KerberosName relies on a static variable to hold on to the
auth-to-local rules,
+ // attempt to protect access to the rule set by blocking other threads
from changing the
+ // rules out from under us during this operation.
+ synchronized (KerberosName.class) {
+ KerberosName.setRules(authToLocalRules);
+ return new KerberosName(principalName).getShortName();
+ }
+ } catch (IOException e) {
+ String message = String.format("Failed to translate %s to a local
username during Kerberos authentication: %s", principalName,
e.getLocalizedMessage());
LOG.warn(message);
- throw new UsernameNotFoundException(message);
+ throw new UsernameNotFoundException(message, e);
}
-
- LOG.info("Translated {} to {} using auth-to-local rules during Kerberos
authentication.", principal, username);
- return createUser(username);
- } catch (IOException e) {
- String message = String.format("Failed to translate %s to a local
username during Kerberos authentication: %s", principal,
e.getLocalizedMessage());
- LOG.warn(message);
- throw new UsernameNotFoundException(message, e);
+ } else {
+ return principalName;
}
}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationFilter.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationFilter.java
index a5a3922..2ae25e2 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationFilter.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationFilter.java
@@ -23,9 +23,12 @@ import org.apache.ambari.server.audit.event.AuditEvent;
import org.apache.ambari.server.audit.event.LoginAuditEvent;
import org.apache.ambari.server.configuration.Configuration;
import
org.apache.ambari.server.security.authentication.AmbariAuthenticationFilter;
+import
org.apache.ambari.server.security.authentication.tproxy.TrustedProxyAuthenticationDetailsSource;
import org.apache.ambari.server.security.authorization.AuthorizationHelper;
import org.apache.ambari.server.security.authorization.PermissionHelper;
import org.apache.ambari.server.utils.RequestUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@@ -50,6 +53,7 @@ import java.io.IOException;
*/
public class AmbariKerberosAuthenticationFilter extends
SpnegoAuthenticationProcessingFilter implements AmbariAuthenticationFilter {
+ private static final Logger LOG =
LoggerFactory.getLogger(AmbariKerberosAuthenticationFilter.class);
/**
* Audit logger
*/
@@ -83,6 +87,8 @@ public class AmbariKerberosAuthenticationFilter extends
SpnegoAuthenticationProc
setAuthenticationManager(authenticationManager);
+ setAuthenticationDetailsSource(new
TrustedProxyAuthenticationDetailsSource());
+
setFailureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest
httpServletRequest, HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
@@ -128,6 +134,10 @@ public class AmbariKerberosAuthenticationFilter extends
SpnegoAuthenticationProc
*/
@Override
public boolean shouldApply(HttpServletRequest httpServletRequest) {
+ if (LOG.isDebugEnabled()) {
+ RequestUtils.logRequestHeadersAndQueryParams(httpServletRequest, LOG);
+ }
+
if (kerberosAuthenticationEnabled) {
String header = httpServletRequest.getHeader("Authorization");
return (header != null) && (header.startsWith("Negotiate ") ||
header.startsWith("Kerberos "));
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationProvider.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationProvider.java
new file mode 100644
index 0000000..e0e9145
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationProvider.java
@@ -0,0 +1,123 @@
+/*
+ * 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.ambari.server.security.authentication.kerberos;
+
+import
org.apache.ambari.server.security.authentication.AmbariProxiedUserDetailsImpl;
+import
org.apache.ambari.server.security.authentication.tproxy.TrustedProxyAuthenticationDetails;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UserDetails;
+import
org.springframework.security.kerberos.authentication.KerberosServiceRequestToken;
+import
org.springframework.security.kerberos.authentication.KerberosTicketValidation;
+import
org.springframework.security.kerberos.authentication.KerberosTicketValidator;
+
+/**
+ * AmbariKerberosAuthenticationProvider is an {@link
org.springframework.security.authentication.AuthenticationProvider}
+ * implementation used to authenticate users using a Kerberos ticket.
+ * <p>
+ * In order for this provider to be usable, Kerberos authentication must be
enabled. This may be done
+ * using the <code>ambari-server setup-kerberos</code> CLI.
+ * <p>
+ * This implementation supports Trusted Proxy authentication.
+ */
+public class AmbariKerberosAuthenticationProvider implements
AuthenticationProvider, InitializingBean {
+ private static final Logger LOG =
LoggerFactory.getLogger(AmbariKerberosAuthenticationProvider.class);
+
+ private AmbariAuthToLocalUserDetailsService authToLocalUserDetailsService;
+ private AmbariProxiedUserDetailsService proxiedUserDetailsService;
+ private KerberosTicketValidator ticketValidator;
+
+ public
AmbariKerberosAuthenticationProvider(AmbariAuthToLocalUserDetailsService
authToLocalUserDetailsService,
+ AmbariProxiedUserDetailsService
proxiedUserDetailsService,
+ KerberosTicketValidator
ticketValidator) {
+ this.authToLocalUserDetailsService = authToLocalUserDetailsService;
+ this.proxiedUserDetailsService = proxiedUserDetailsService;
+ this.ticketValidator = ticketValidator;
+ }
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws
AuthenticationException {
+
+ if (authentication == null) {
+ throw new BadCredentialsException("Missing credentials");
+ } else if (authentication instanceof KerberosServiceRequestToken) {
+ KerberosServiceRequestToken auth = (KerberosServiceRequestToken)
authentication;
+ byte[] token = auth.getToken();
+
+ LOG.debug("Validating Kerberos token");
+ KerberosTicketValidation ticketValidation =
ticketValidator.validateTicket(token);
+ LOG.debug("Kerberos token validated: {}", ticketValidation.username());
+
+ Object requestDetails = authentication.getDetails();
+ UserDetails userDetails;
+
+ if (requestDetails instanceof TrustedProxyAuthenticationDetails) {
+ TrustedProxyAuthenticationDetails trustedProxyAuthenticationDetails =
(TrustedProxyAuthenticationDetails) requestDetails;
+ String proxiedUserName = trustedProxyAuthenticationDetails.getDoAs();
+
+ if (StringUtils.isNotEmpty(proxiedUserName)) {
+ String localProxyUserName =
authToLocalUserDetailsService.translatePrincipalName(ticketValidation.username());
+
+ userDetails = new AmbariProxiedUserDetailsImpl(
+ proxiedUserDetailsService.loadProxiedUser(proxiedUserName,
localProxyUserName, trustedProxyAuthenticationDetails),
+ new
AmbariProxyUserKerberosDetailsImpl(ticketValidation.username(),
localProxyUserName));
+ } else {
+ // This is not a trusted proxy request... handle normally
+ userDetails =
authToLocalUserDetailsService.loadUserByUsername(ticketValidation.username());
+ }
+ } else {
+ // This is not a trusted proxy request... handle normally
+ userDetails =
authToLocalUserDetailsService.loadUserByUsername(ticketValidation.username());
+ }
+
+ KerberosServiceRequestToken responseAuth = new
KerberosServiceRequestToken(userDetails, ticketValidation,
userDetails.getAuthorities(), token);
+ responseAuth.setDetails(requestDetails);
+ return responseAuth;
+ } else {
+ throw new BadCredentialsException(String.format("Unexpected
Authentication class: %s", authentication.getClass().getName()));
+ }
+ }
+
+ @Override
+ public boolean supports(Class<? extends Object> auth) {
+ return KerberosServiceRequestToken.class.isAssignableFrom(auth);
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ }
+
+ public void
setAuthToLocalUserDetailsService(AmbariAuthToLocalUserDetailsService
authToLocalUserDetailsService) {
+ this.authToLocalUserDetailsService = authToLocalUserDetailsService;
+ }
+
+ public void setProxiedUserDetailsService(AmbariProxiedUserDetailsService
proxiedUserDetailsService) {
+ this.proxiedUserDetailsService = proxiedUserDetailsService;
+ }
+
+ public void setTicketValidator(KerberosTicketValidator ticketValidator) {
+ this.ticketValidator = ticketValidator;
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxiedUserDetailsService.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxiedUserDetailsService.java
new file mode 100644
index 0000000..e558cac
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxiedUserDetailsService.java
@@ -0,0 +1,304 @@
+/*
+ * 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.ambari.server.security.authentication.kerberos;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.orm.entities.GroupEntity;
+import org.apache.ambari.server.orm.entities.MemberEntity;
+import org.apache.ambari.server.orm.entities.UserEntity;
+import org.apache.ambari.server.security.authentication.AmbariUserDetailsImpl;
+import
org.apache.ambari.server.security.authentication.tproxy.AmbariTProxyConfiguration;
+import
org.apache.ambari.server.security.authentication.tproxy.TrustedProxyAuthenticationDetails;
+import
org.apache.ambari.server.security.authentication.tproxy.TrustedProxyAuthenticationNotAllowedException;
+import org.apache.ambari.server.security.authorization.User;
+import org.apache.ambari.server.security.authorization.Users;
+import org.apache.commons.lang.StringUtils;
+import org.apache.hadoop.security.authentication.util.KerberosName;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+/**
+ * AmbariProxiedUserDetailsService is a {@link UserDetailsService} that
handles proxied users via the
+ * Trusted Proxy protocol.
+ * <p>
+ * If trusted proxy support is enabled and a proxied user is declared, the
proxy user and remote host is validated.
+ * If all criteria is met and a user account with the the proxied user's
username exist, that user
+ * account will be set as the authenticated user.
+ */
+public class AmbariProxiedUserDetailsService implements UserDetailsService {
+ private static final Logger LOG =
LoggerFactory.getLogger(AmbariProxiedUserDetailsService.class);
+
+ private static final Pattern IP_ADDRESS_PATTERN =
Pattern.compile("^(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$");
+ private static final Pattern IP_ADDRESS_RANGE_PATTERN =
Pattern.compile("^((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3})/(\\d{1,2})$");
+ private final Configuration configuration;
+ private final Users users;
+
+ /**
+ * Constructor.
+ * <p>
+ * Given the Ambari {@link Configuration}, initializes the {@link
KerberosName} class using
+ * the <code>auth-to-local</code> rules from {@link
AmbariKerberosAuthenticationProperties#getAuthToLocalRules()}.
+ *
+ * @param configuration the Ambari configuration data
+ * @param users the Ambari users access object
+ */
+ public AmbariProxiedUserDetailsService(Configuration configuration, Users
users) {
+ this.configuration = configuration;
+ this.users = users;
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(String proxiedUserName) throws
UsernameNotFoundException {
+ return loadProxiedUser(proxiedUserName, null, null);
+ }
+
+ public UserDetails loadProxiedUser(String proxiedUserName,
+ String proxyUserName,
+ TrustedProxyAuthenticationDetails
trustedProxyAuthenticationDetails)
+ throws AuthenticationException {
+
+ LOG.info("Proxy user {} specified {} as proxied user.", proxyUserName,
proxiedUserName);
+
+ if (StringUtils.isEmpty(proxiedUserName)) {
+ String message = "No proxied username was specified.";
+ LOG.warn(message);
+ throw new UsernameNotFoundException(message);
+ }
+
+ if (trustedProxyAuthenticationDetails == null) {
+ String message = "Trusted proxy details have not been provided.";
+ LOG.warn(message);
+ throw new TrustedProxyAuthenticationNotAllowedException(message);
+ }
+
+ AmbariTProxyConfiguration tProxyConfiguration =
AmbariTProxyConfiguration.fromConfig(configuration);
+
+ // Make sure the trusted proxy support is enabled
+ if (!tProxyConfiguration.isEnabled()) {
+ String message = "Trusted proxy support is not enabled.";
+ LOG.warn(message);
+ throw new TrustedProxyAuthenticationNotAllowedException(message);
+ }
+
+ // Validate the host...
+ if (!validateHost(tProxyConfiguration, proxyUserName,
trustedProxyAuthenticationDetails.getRemoteAddress())) {
+ String message = String.format("Trusted proxy is not allowed for %s ->
%s: host match not found.", proxyUserName, proxiedUserName);
+ LOG.warn(message);
+ throw new TrustedProxyAuthenticationNotAllowedException(message);
+ }
+
+ // Validate the user...
+ if (!validateUser(tProxyConfiguration, proxyUserName, proxiedUserName)) {
+ String message = String.format("Trusted proxy is not allowed for %s ->
%s: user match not found.", proxyUserName, proxiedUserName);
+ LOG.warn(message);
+ throw new TrustedProxyAuthenticationNotAllowedException(message);
+ }
+
+ // Retrieve the userEntity so we can validate the groups now....
+ // We also need this later if all validations succeed.
+ UserEntity userEntity = users.getUserEntity(proxiedUserName);
+ if (userEntity == null) {
+ String message = String.format("Failed to find an account for the
proxied user, %s.", proxiedUserName);
+ LOG.warn(message);
+ throw new UsernameNotFoundException(message);
+ }
+
+ // Validate the proxied user's groups
+ if (!validateGroup(tProxyConfiguration, proxyUserName, userEntity)) {
+ String message = String.format("Trusted proxy is not allowed for %s ->
%s: group match not found.", proxyUserName, proxiedUserName);
+ LOG.warn(message);
+ throw new TrustedProxyAuthenticationNotAllowedException(message);
+ }
+
+ return createUserDetails(userEntity);
+ }
+
+ boolean validateGroup(AmbariTProxyConfiguration tProxyConfiguration, String
proxyUserName, UserEntity userEntity) {
+ String allowedGroups = tProxyConfiguration.getAllowedGroups(proxyUserName);
+
+ if (StringUtils.isNotEmpty(allowedGroups)) {
+ Set<String> groupSpecs = explode(allowedGroups);
+
+ if (groupSpecs.contains("*")) {
+ return true;
+ } else {
+ Set<MemberEntity> memberEntities = userEntity.getMemberEntities();
+ if (memberEntities != null) {
+ for (MemberEntity memberEntity : memberEntities) {
+ GroupEntity group = memberEntity.getGroup();
+ if (group != null) {
+ String groupName = group.getGroupName();
+ if (StringUtils.isNotEmpty(groupName) &&
groupSpecs.contains(groupName.toLowerCase())) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static Set<String> explode(String commaSeparated) {
+ Set<String> result = new HashSet<>();
+ for (String each : commaSeparated.split("\\s*,\\s*")) {
+ result.add(each.trim().toLowerCase());
+ }
+ return result;
+ }
+
+ boolean validateUser(AmbariTProxyConfiguration tProxyConfiguration, String
proxyUserName, String proxiedUserName) {
+ String allowedUsers = tProxyConfiguration.getAllowedUsers(proxyUserName);
+
+ if (StringUtils.isNotEmpty(allowedUsers)) {
+ String[] userSpecs = allowedUsers.split("\\s*,\\s*");
+ for (String userSpec : userSpecs) {
+ if ("*".equals(userSpec) ||
(userSpec.equalsIgnoreCase(proxiedUserName))) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ boolean validateHost(AmbariTProxyConfiguration tProxyConfiguration, String
proxyUserName, String remoteAddress) {
+ String allowedHosts = tProxyConfiguration.getAllowedHosts(proxyUserName);
+
+ if (StringUtils.isNotEmpty(allowedHosts)) {
+ Set<String> hostSpecs = explode(allowedHosts);
+
+ if (hostSpecs.contains("*")) {
+ return true;
+ } else {
+ for (String hostSpec : hostSpecs) {
+ if (isIPAddress(hostSpec)) {
+ if (hostSpec.equals(remoteAddress)) {
+ return true;
+ }
+ } else if (isIPAddressRange(hostSpec)) {
+ if (isInIpAddressRange(hostSpec, remoteAddress)) {
+ return true;
+ }
+ } else if (matchesHostname(hostSpec, remoteAddress)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ boolean matchesHostname(String hostSpec, String remoteAddress) {
+ // We assume this is a hostname... convert it to an IP address
+ try {
+ String ipAddress = getIpAddress(hostSpec);
+ if (StringUtils.isNotEmpty(ipAddress) &&
ipAddress.equals(remoteAddress)) {
+ return true;
+ }
+ } catch (Throwable t) {
+ LOG.warn("Invalid hostname in host specification, skipping: " +
hostSpec, t);
+ }
+
+ return false;
+ }
+
+ String getIpAddress(String hostname) throws UnknownHostException {
+ InetAddress inetAddress = InetAddress.getByName(hostname);
+ return (inetAddress == null) ? null : inetAddress.getHostAddress();
+ }
+
+ /**
+ * Determines if an IP address is in the subnet specified by the CIDR value.
+ * <p>
+ * The logic for ths method comes from an example found at
+ *
https://stackoverflow.com/questions/4209760/validate-an-ip-address-with-mask.
+ * <p>
+ * A CIDR is an IP address with a mask, denoting a subnet. For example,
<code>192.168.1.0/24</code> contains all
+ * IP addresses in the range of <code>192.168.1.0</code> through
<code>192.168.1.255</code>. See https://www.ipaddressguide.com/cidr
+ * for an on-line calculator.
+ *
+ * @param cidr a CIDR
+ * @param ipAddress the IP address to test
+ * @return <code>true</code> if the IP Address is withing the range
specified by the CIDR; <code>false</code> otherwise.
+ */
+ boolean isInIpAddressRange(String cidr, String ipAddress) {
+ Matcher matcher = IP_ADDRESS_RANGE_PATTERN.matcher(cidr);
+
+ if (matcher.matches() && matcher.groupCount() == 2) {
+ try {
+ String hostSpecIPAddress = matcher.group(1);
+ String hostSpecBits = matcher.group(2);
+ int hostSpecIPAddressInt = ipAddressToInt(hostSpecIPAddress);
+ int remoteAddressInt = ipAddressToInt(ipAddress);
+
+ int mask = -1 << (32 - Integer.valueOf(hostSpecBits));
+ return ((hostSpecIPAddressInt & mask) == (remoteAddressInt & mask));
+ } catch (Throwable t) {
+ LOG.warn("Invalid CIDR in host specification, skipping: " + cidr, t);
+ }
+ }
+
+ return false;
+ }
+
+ private int ipAddressToInt(String s) throws UnknownHostException {
+ InetAddress inetAddress = InetAddress.getByName(s);
+ byte[] b = inetAddress.getAddress();
+ return ((b[0] & 0xFF) << 24) |
+ ((b[1] & 0xFF) << 16) |
+ ((b[2] & 0xFF) << 8) |
+ ((b[3] & 0xFF) << 0);
+ }
+
+ private boolean isIPAddressRange(String hostSpec) {
+ return IP_ADDRESS_RANGE_PATTERN.matcher(hostSpec).matches();
+ }
+
+ private boolean isIPAddress(String hostSpec) {
+ return IP_ADDRESS_PATTERN.matcher(hostSpec).matches();
+ }
+
+ private UserDetails createUserDetails(UserEntity userEntity) {
+ String username = userEntity.getUserName();
+
+ // Ensure the user account is allowed to log in
+ if (!userEntity.getActive()) {
+ LOG.info("User account is disabled: {}", username);
+ // Do not give away information about the existence or status of a user
+ throw new BadCredentialsException("Bad credentials");
+ }
+
+ return new AmbariUserDetailsImpl(new User(userEntity), null,
users.getUserAuthorities(userEntity));
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxyUserKerberosDetailsImpl.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxyUserKerberosDetailsImpl.java
new file mode 100644
index 0000000..79d74cb
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxyUserKerberosDetailsImpl.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.ambari.server.security.authentication.kerberos;
+
+import
org.apache.ambari.server.security.authentication.AmbariProxyUserDetailsImpl;
+
+/**
+ * AmbariProxyUserKerberosDetailsImpl is a {@link AmbariProxyUserDetailsImpl}
implementation that
+ * adds allows for the proxy user's principal name to be retrieved if the
proxy user authenticated
+ * using Kerberos.
+ */
+public class AmbariProxyUserKerberosDetailsImpl extends
AmbariProxyUserDetailsImpl {
+ private final String principalName;
+
+ public AmbariProxyUserKerberosDetailsImpl(String principalName, String
localUsername) {
+ super(localUsername);
+ this.principalName = principalName;
+ }
+
+ public String getPrincipalName() {
+ return principalName;
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/AmbariTProxyConfiguration.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/AmbariTProxyConfiguration.java
new file mode 100644
index 0000000..29278da
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/AmbariTProxyConfiguration.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.ambari.server.security.authentication.tproxy;
+
+import java.util.Map;
+
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableMap;
+
+public class AmbariTProxyConfiguration {
+ private static final Logger LOGGER =
LoggerFactory.getLogger(AmbariTProxyConfiguration.class);
+ private static final String TPROXY_AUTHENTICATION_ENABLED =
"ambari.tproxy.authentication.enabled";
+ private static final String TEMPLATE_PROXY_USER_ALLOWED_HOSTS =
"ambari.tproxy.proxyuser.%s.hosts";
+ private static final String TEMPLATE_PROXY_USER_ALLOWED_USERS =
"ambari.tproxy.proxyuser.%s.users";
+ private static final String TEMPLATE_PROXY_USER_ALLOWED_GROUPS =
"ambari.tproxy.proxyuser.%s.groups";
+ private final ImmutableMap<String, String> configurationMap;
+
+ public static AmbariTProxyConfiguration fromConfig(Configuration
configuration) {
+ return new AmbariTProxyConfiguration(configuration.getAmbariProperties());
+ }
+
+ /**
+ * Constructor
+ * <p>
+ * Copies the given configuration propery map into an {@link ImmutableMap}
and pulls out propery
+ * values upon request.
+ *
+ * @param configurationMap a map of property names to values
+ */
+ AmbariTProxyConfiguration(Map<String, String> configurationMap) {
+ ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
+
+ if (configurationMap != null) {
+ builder.putAll(configurationMap);
+ }
+
+ this.configurationMap = builder.build();
+ }
+
+ /**
+ * Returns an immutable map of the contained properties
+ *
+ * @return an immutable map property names to values
+ */
+ public Map<String, String> toMap() {
+ return configurationMap;
+ }
+
+ /**
+ * Determines of tristed proxy support is enabled based on the configuration
data.
+ *
+ * @return <code>true</code> if trusted proxy support is enabed;
<code>false</code> otherwise
+ */
+ public boolean isEnabled() {
+ return Boolean.valueOf(getValue(TPROXY_AUTHENTICATION_ENABLED,
configurationMap, "false"));
+ }
+
+ public String getAllowedHosts(String proxyUser) {
+ return getValue(String.format(TEMPLATE_PROXY_USER_ALLOWED_HOSTS,
proxyUser), configurationMap, "");
+ }
+
+ public String getAllowedUsers(String proxyUser) {
+ return getValue(String.format(TEMPLATE_PROXY_USER_ALLOWED_USERS,
proxyUser), configurationMap, "");
+ }
+
+ public String getAllowedGroups(String proxyUser) {
+ return getValue(String.format(TEMPLATE_PROXY_USER_ALLOWED_GROUPS,
proxyUser), configurationMap, "");
+ }
+
+ protected String getValue(String propertyName, Map<String, String>
configurationMap, String defaultValue) {
+ if ((configurationMap != null) &&
configurationMap.containsKey(propertyName)) {
+ return configurationMap.get(propertyName);
+ } else {
+ LOGGER.debug("Ambari server configuration property [{}] hasn't been set;
using default value", propertyName);
+ return defaultValue;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ return new EqualsBuilder()
+ .append(configurationMap, ((AmbariTProxyConfiguration)
o).configurationMap)
+ .isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(17, 37)
+ .append(configurationMap)
+ .toHashCode();
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationDetails.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationDetails.java
new file mode 100644
index 0000000..b254e667
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationDetails.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.security.authentication.tproxy;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.ambari.server.utils.RequestUtils;
+import
org.springframework.security.web.authentication.WebAuthenticationDetails;
+
+/**
+ * TrustedProxyAuthenticationDetails is a {@link WebAuthenticationDetails}
implementation that
+ * contains information from an {@link HttpServletRequest} that can be used
for handling trusted
+ * proxy authentication requests.
+ * <p>
+ * If the request is not from a trusted proxy, the internal valiues will be
null.
+ */
+public class TrustedProxyAuthenticationDetails extends
WebAuthenticationDetails {
+ private final String doAs;
+ private final String xForwardedContext;
+ private final String xForwardedProto;
+ private final String xForwardedHost;
+ private final String xForwardedFor;
+ private final String xForwardedPort;
+ private final String xForwardedServer;
+
+ /**
+ * Constructor.
+ * <p>
+ * Given an {@link HttpServletRequest}, attempts to retrieve the following
data:
+ * <ul>
+ * <li>The proxied user from the <code>doAs</code> query parameter</li>
+ * <li>
+ * The following <code>X-Forwarded-*</code> values from the request header
information
+ * <ul>
+ * <li>X-Forwarded-Context</li>
+ * <li>X-Forwarded-Proto</li>
+ * <li>X-Forwarded-Host</li>
+ * <li>X-Forwarded-For</li>
+ * <li>X-Forwarded-Port</li>
+ * <li>X-Forwarded-Server</li>
+ * </ul>
+ * </li>
+ * </ul>
+ *
+ * @param context the {@link HttpServletRequest} to query
+ */
+ TrustedProxyAuthenticationDetails(HttpServletRequest context) {
+ super(context);
+
+ this.doAs = RequestUtils.getQueryStringParameterValue(context, "doAs");
+ this.xForwardedContext = context.getHeader("X-Forwarded-Context");
+ this.xForwardedProto = context.getHeader("X-Forwarded-Proto");
+ this.xForwardedHost = context.getHeader("X-Forwarded-Host");
+ this.xForwardedFor = context.getHeader("X-Forwarded-For");
+ this.xForwardedPort = context.getHeader("X-Forwarded-Port");
+ this.xForwardedServer = context.getHeader("X-Forwarded-Server");
+ }
+
+ public String getDoAs() {
+ return doAs;
+ }
+
+ public String getXForwardedContext() {
+ return xForwardedContext;
+ }
+
+ public String getXForwardedProto() {
+ return xForwardedProto;
+ }
+
+ public String getXForwardedHost() {
+ return xForwardedHost;
+ }
+
+ public String getXForwardedFor() {
+ return xForwardedFor;
+ }
+
+ public String getXForwardedPort() {
+ return xForwardedPort;
+ }
+
+ public String getXForwardedServer() {
+ return xForwardedServer;
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationDetailsSource.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationDetailsSource.java
new file mode 100644
index 0000000..3f0af65
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationDetailsSource.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.security.authentication.tproxy;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.security.authentication.AuthenticationDetailsSource;
+
+/**
+ * TrustedProxyAuthenticationDetailsSource is an implementation of a {@link
AuthenticationDetailsSource}
+ * that produces {@link TrustedProxyAuthenticationDetails} instances.
+ *
+ * @see TrustedProxyAuthenticationDetails
+ */
+public class TrustedProxyAuthenticationDetailsSource implements
AuthenticationDetailsSource<HttpServletRequest,
TrustedProxyAuthenticationDetails> {
+ @Override
+ public TrustedProxyAuthenticationDetails buildDetails(HttpServletRequest
httpServletRequest) {
+ return new TrustedProxyAuthenticationDetails(httpServletRequest);
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationNotAllowedException.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationNotAllowedException.java
new file mode 100644
index 0000000..033c18e
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationNotAllowedException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.ambari.server.security.authentication.tproxy;
+
+import org.springframework.security.core.AuthenticationException;
+
+/**
+ * TrustedProxyAuthenticationNotAllowedException is an AuthenticationException
implementation to be
+ * thrown a Trusted proxy authentication attempt fails.
+ */
+public class TrustedProxyAuthenticationNotAllowedException extends
AuthenticationException {
+
+ public TrustedProxyAuthenticationNotAllowedException(String message) {
+ super(message);
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthorizationFilter.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthorizationFilter.java
index f0dea59..b9bf6ce 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthorizationFilter.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthorizationFilter.java
@@ -178,6 +178,7 @@ public class AmbariAuthorizationFilter implements Filter {
if(auditLogger.isEnabled()) {
LoginAuditEvent loginAuditEvent = LoginAuditEvent.builder()
.withUserName(internalAuthenticationToken.getName())
+
.withProxyUserName(AuthorizationHelper.getProxyUserName(internalAuthenticationToken))
.withRemoteIp(RequestUtils.getRemoteAddress(httpRequest))
.withRoles(permissionHelper.getPermissionLabels(authentication))
.withTimestamp(System.currentTimeMillis()).build();
@@ -260,6 +261,7 @@ public class AmbariAuthorizationFilter implements Filter {
.withRemoteIp(RequestUtils.getRemoteAddress(httpRequest))
.withResourcePath(httpRequest.getRequestURI())
.withUserName(AuthorizationHelper.getAuthenticatedName())
+ .withProxyUserName(AuthorizationHelper.getProxyUserName())
.withTimestamp(System.currentTimeMillis())
.build();
auditLogger.log(auditEvent);
@@ -279,6 +281,7 @@ public class AmbariAuthorizationFilter implements Filter {
.withRemoteIp(RequestUtils.getRemoteAddress(httpRequest))
.withResourcePath(httpRequest.getRequestURI())
.withUserName(AuthorizationHelper.getAuthenticatedName())
+ .withProxyUserName(AuthorizationHelper.getProxyUserName())
.withTimestamp(System.currentTimeMillis())
.build();
auditLogger.log(auditEvent);
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AuthorizationHelper.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AuthorizationHelper.java
index e875e8a..ea3446e 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AuthorizationHelper.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AuthorizationHelper.java
@@ -17,16 +17,19 @@
*/
package org.apache.ambari.server.security.authorization;
-import com.google.common.collect.Lists;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
import org.apache.ambari.server.orm.dao.PrivilegeDAO;
import org.apache.ambari.server.orm.dao.ViewInstanceDAO;
import org.apache.ambari.server.orm.entities.PermissionEntity;
import org.apache.ambari.server.orm.entities.PrivilegeEntity;
import org.apache.ambari.server.orm.entities.ResourceEntity;
import org.apache.ambari.server.orm.entities.RoleAuthorizationEntity;
+import
org.apache.ambari.server.security.authentication.AmbariProxiedUserDetailsImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
@@ -37,11 +40,10 @@ import
org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import com.google.common.collect.Lists;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
/**
* Provides utility methods for authentication functionality
@@ -341,4 +343,32 @@ public class AuthorizationHelper {
return authorizationNames;
}
+ /**
+ * Gets the name of the logged-in proxy user, if any.
+ *
+ * @param authentication
+ * @return the name of the logged-in proxy user
+ */
+ public static String getProxyUserName(Authentication authentication) {
+ if (authentication==null){
+ return null;
+ }
+ Object userDetails = authentication.getPrincipal();
+ if (userDetails instanceof AmbariProxiedUserDetailsImpl) {
+ AmbariProxiedUserDetailsImpl ambariProxiedUserDetails =
(AmbariProxiedUserDetailsImpl) userDetails;
+ return ambariProxiedUserDetails.getProxyUserDetails().getUsername();
+ }
+ return null;
+ }
+
+ /**
+ * Gets the name of the logged-in proxy user, if any.
+ *
+ * @return the name of the logged-in proxy user
+ */
+ public static String getProxyUserName() {
+ SecurityContext securityContext = SecurityContextHolder.getContext();
+ Authentication auth = securityContext.getAuthentication();
+ return getProxyUserName(auth);
+ }
}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Users.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Users.java
index 69c750a..0b869a5 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Users.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/Users.java
@@ -140,6 +140,10 @@ public class Users {
return (null == userEntity) ? null : new User(userEntity);
}
+ public UserEntity getUserEntity(String userName) {
+ return (userName == null) ? null : userDAO.findUserByName(userName);
+ }
+
/**
* Retrieves User then userName is unique in users DB. Will return null if
there no user with provided userName or
* there are some users with provided userName but with different types.
@@ -963,6 +967,38 @@ public class Users {
}
/**
+ * Gets the explicit and implicit authorities for the given user.
+ * <p>
+ * The explicit authorities are the authorities that have be explicitly set
by assigning roles to
+ * a user. For example the Cluster Operator role on a given cluster gives
that the ability to
+ * start and stop services in that cluster, among other privileges for that
particular cluster.
+ * <p>
+ * The implicit authorities are the authorities that have been given to the
roles themselves which
+ * in turn are granted to the users that have been assigned those roles. For
example if the
+ * Cluster User role for a given cluster has been given View User access on
a specified File View
+ * instance, then all users who have the Cluster User role for that cluster
will implicitly be
+ * granted View User access on that File View instance.
+ *
+ * @param userEntity the relevant user
+ * @return the users collection of implicit and explicit granted authorities
+ */
+ public Collection<AmbariGrantedAuthority> getUserAuthorities(UserEntity
userEntity) {
+ if (userEntity == null) {
+ return Collections.emptyList();
+ }
+
+ Collection<PrivilegeEntity> privilegeEntities =
getUserPrivileges(userEntity);
+
+ Set<AmbariGrantedAuthority> authorities = new
HashSet<>(privilegeEntities.size());
+
+ for (PrivilegeEntity privilegeEntity : privilegeEntities) {
+ authorities.add(new AmbariGrantedAuthority(privilegeEntity));
+ }
+
+ return authorities;
+ }
+
+ /**
* Gets the implicit privileges based on the set of roles found in a
collection of privileges.
* <p>
* The implicit privileges are the privileges that have been given to the
roles themselves which
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/utils/RequestUtils.java
b/ambari-server/src/main/java/org/apache/ambari/server/utils/RequestUtils.java
index dbb0f11..747a6c6 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/utils/RequestUtils.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/utils/RequestUtils.java
@@ -17,15 +17,26 @@
*/
package org.apache.ambari.server.utils;
-import com.google.common.collect.ImmutableSet;
+import java.nio.charset.Charset;
import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
+
import javax.servlet.http.HttpServletRequest;
-import org.apache.ambari.server.api.services.Request;
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.slf4j.Logger;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
+import com.google.common.collect.ImmutableSet;
+
/**
* The purpose of this helper is to get remote address from an HTTP request
*/
@@ -95,4 +106,110 @@ public class RequestUtils {
((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest() != null;
}
+ /**
+ * Posts an informational message the the supplied {@link Logger} showing
the request headers and
+ * query parameters
+ * <p>
+ * For example:
+ * <pre>
+ * ##### HEADERS ########
+ * X-Requested-By = ambari
+ * ...
+ * Accept-Encoding = gzip, deflate
+ * Accept-Language = en-US,en;q=0.9
+ * ######################
+ * ##### PARAMETERS #####
+ * _ = 1543700737939
+ * ######################
+ * </pre>
+ *
+ * @param request the {@link HttpServletRequest} to log
+ * @param logger the {@link Logger}
+ */
+ public static void logRequestHeadersAndQueryParams(HttpServletRequest
request, Logger logger) {
+ if (logger != null) {
+ StringBuilder builder;
+
+ builder = new StringBuilder();
+ builder.append("\n##### HEADERS #######");
+ Enumeration<String> headerNames = request.getHeaderNames();
+ while (headerNames.hasMoreElements()) {
+ String name = headerNames.nextElement();
+ Enumeration<String> values = request.getHeaders(name);
+ while (values.hasMoreElements()) {
+ String value = values.nextElement();
+ builder.append("\n\t");
+ builder.append(name);
+ builder.append(" = ");
+ builder.append(value);
+ }
+ }
+ builder.append("\n#####################");
+ builder.append("\n##### PARAMETERS ####");
+ MultiValueMap<String, String> queryParams =
getQueryStringParameters(request);
+ if (queryParams != null) {
+ for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
+ String name = entry.getKey();
+ List<String> values = entry.getValue();
+ for (String value : values) {
+ builder.append("\n\t");
+ builder.append(name);
+ builder.append(" = ");
+ builder.append(value);
+ }
+ }
+ }
+ builder.append("\n#####################");
+
+ logger.info(builder.toString());
+ }
+ }
+
+ /**
+ * Returns a {@link MultiValueMap} of the parameters parsed from the
request's query string. The
+ * returned map will not contain any parameters that may be in the body of
the request as form data.
+ * <p>
+ * This implementation manually parses the query string rather than use
{@link HttpServletRequest#getParameterValues(String)}
+ * so that the message body remains intact and available. Calling {@link
HttpServletRequest#getParameterValues(String)}
+ * could interfere with processing the body of this request later since the
body is parsed to find
+ * any form parameters.
+ *
+ * @param request the {@link HttpServletRequest}
+ * @return a Map of query parameters to values
+ */
+ public static MultiValueMap<String, String>
getQueryStringParameters(HttpServletRequest request) {
+ // Manually parse the query string rather than use
HttpServletRequest#getParameter so that
+ // the message body remains intact and available. Calling
HttpServletRequest#getParameter
+ // could interfere with processing the body of this request later since
the body needs to be
+ // parsed to find any form parameters.
+ String queryString = request.getQueryString();
+ return (StringUtils.isEmpty(queryString)) ? null :
parseQueryParameters(queryString);
+ }
+
+ private static MultiValueMap<String, String> parseQueryParameters(String
queryString) {
+ LinkedMultiValueMap result = new LinkedMultiValueMap();
+ List<NameValuePair> params = URLEncodedUtils.parse(queryString,
Charset.forName("UTF-8"));
+ for (NameValuePair each : params) {
+ result.add(each.getName(), each.getValue());
+ }
+ return result;
+ }
+
+ /**
+ * Returns the value parsed from the request's query string for the given
parameter name.
+ * <p>
+ * If more than one value for the given parameter name is found, the first
value will be retured.
+ *
+ * @param request the {@link HttpServletRequest}
+ * @return the value for the specified query parameter; or <code>null</code>
if the
+ * requested parameter is not present
+ * @see #getQueryStringParameters(HttpServletRequest)
+ */
+ public static String getQueryStringParameterValue(HttpServletRequest
request, String parameterName) {
+ MultiValueMap<String, String> valueMap = getQueryStringParameters(request);
+ return ((valueMap == null) || !valueMap.containsKey(parameterName))
+ ? null
+ : valueMap.getFirst(parameterName);
+ }
+
}
diff --git a/ambari-server/src/main/python/ambari-server.py
b/ambari-server/src/main/python/ambari-server.py
index 3a67532..9477d16 100755
--- a/ambari-server/src/main/python/ambari-server.py
+++ b/ambari-server/src/main/python/ambari-server.py
@@ -42,6 +42,7 @@ from ambari_server.setupHttps import setup_https,
setup_truststore
from ambari_server.setupMpacks import install_mpack, uninstall_mpack,
upgrade_mpack, STACK_DEFINITIONS_RESOURCE_NAME, \
SERVICE_DEFINITIONS_RESOURCE_NAME, MPACKS_RESOURCE_NAME
from ambari_server.setupSso import setup_sso
+from ambari_server.setupTrustedProxy import setup_trusted_proxy
from ambari_server.dbCleanup import db_purge
from ambari_server.hostUpdate import update_host_names
from ambari_server.checkDatabase import check_database
@@ -52,7 +53,7 @@ from ambari_server.setupActions import BACKUP_ACTION,
LDAP_SETUP_ACTION, LDAP_SY
SETUP_ACTION, SETUP_SECURITY_ACTION, START_ACTION, STATUS_ACTION,
STOP_ACTION, RESTART_ACTION, UPGRADE_ACTION, \
SETUP_JCE_ACTION, SET_CURRENT_ACTION, START_ACTION, STATUS_ACTION,
STOP_ACTION, UPGRADE_ACTION, \
SETUP_JCE_ACTION, SET_CURRENT_ACTION, ENABLE_STACK_ACTION, SETUP_SSO_ACTION,
\
- DB_PURGE_ACTION, INSTALL_MPACK_ACTION, UNINSTALL_MPACK_ACTION,
UPGRADE_MPACK_ACTION, PAM_SETUP_ACTION, MIGRATE_LDAP_PAM_ACTION,
KERBEROS_SETUP_ACTION
+ DB_PURGE_ACTION, INSTALL_MPACK_ACTION, UNINSTALL_MPACK_ACTION,
UPGRADE_MPACK_ACTION, PAM_SETUP_ACTION, MIGRATE_LDAP_PAM_ACTION,
KERBEROS_SETUP_ACTION, SETUP_TPROXY_ACTION
from ambari_server.setupSecurity import setup_ldap, sync_ldap,
setup_master_key, setup_ambari_krb5_jaas, setup_pam, migrate_ldap_pam
from ambari_server.userInput import get_validated_string_input
from ambari_server.kerberos_setup import setup_kerberos
@@ -608,6 +609,12 @@ def init_parser_options(parser):
parser.add_option('--kerberos-spnego-user-types', default="LDAP", help="User
type search order (comma-delimited)", dest="kerberos_user_types")
parser.add_option('--kerberos-auth-to-local-rules', default="DEFAULT",
help="Auth-to-local rules", dest="kerberos_auth_to_local_rules")
+ parser.add_option('--tproxy-enabled', default=None, help="Indicates whether
to enable/disable Trusted Proxy Support", dest="tproxy_enabled")
+ parser.add_option('--tproxy-configuration-file-path', default=None,
+ help="The path where the Trusted Proxy configuration is
located. The content is expected to be in JSON format." \
+ "Sample configuration:[{\"proxyuser\": \"knox\",
\"hosts\": \"host1\", \"users\": \"user1, user2\", \"groups\": \"group1\"}]",
+ dest="tproxy_configuration_file_path"
+ )
@OsFamilyFuncImpl(OSConst.WINSRV_FAMILY)
def are_cmd_line_db_args_blank(options):
@@ -740,7 +747,8 @@ def create_user_action_map(args, options):
SETUP_SSO_ACTION: UserActionRestart(setup_sso, options),
INSTALL_MPACK_ACTION: UserAction(install_mpack, options),
UNINSTALL_MPACK_ACTION: UserAction(uninstall_mpack, options),
- UPGRADE_MPACK_ACTION: UserAction(upgrade_mpack, options)
+ UPGRADE_MPACK_ACTION: UserAction(upgrade_mpack, options),
+ SETUP_TPROXY_ACTION: UserAction(setup_trusted_proxy, options)
}
return action_map
@@ -772,7 +780,8 @@ def create_user_action_map(args, options):
UPGRADE_MPACK_ACTION: UserAction(upgrade_mpack, options),
PAM_SETUP_ACTION: UserAction(setup_pam, options),
MIGRATE_LDAP_PAM_ACTION: UserAction(migrate_ldap_pam, options),
- KERBEROS_SETUP_ACTION: UserAction(setup_kerberos, options)
+ KERBEROS_SETUP_ACTION: UserAction(setup_kerberos, options),
+ SETUP_TPROXY_ACTION: UserAction(setup_trusted_proxy, options)
}
return action_map
diff --git a/ambari-server/src/main/python/ambari_server/serverUtils.py
b/ambari-server/src/main/python/ambari_server/serverUtils.py
index 4621646..04b6149 100644
--- a/ambari-server/src/main/python/ambari_server/serverUtils.py
+++ b/ambari-server/src/main/python/ambari_server/serverUtils.py
@@ -133,3 +133,6 @@ def get_ambari_server_api_base(properties):
if api_port_prop is not None:
api_port = api_port_prop
return '{0}://{1}:{2!s}/api/v1/'.format(api_protocol, SERVER_API_HOST,
api_port)
+
+def get_value_from_dictionary(properties, key, default_value=None):
+ return properties[key] if properties and key in properties else default_value
\ No newline at end of file
diff --git a/ambari-server/src/main/python/ambari_server/setupActions.py
b/ambari-server/src/main/python/ambari_server/setupActions.py
index 61d20af..b0aec93 100644
--- a/ambari-server/src/main/python/ambari_server/setupActions.py
+++ b/ambari-server/src/main/python/ambari_server/setupActions.py
@@ -49,3 +49,4 @@ UPGRADE_MPACK_ACTION = "upgrade-mpack"
PAM_SETUP_ACTION = "setup-pam"
MIGRATE_LDAP_PAM_ACTION = "migrate-ldap-pam"
KERBEROS_SETUP_ACTION = "setup-kerberos"
+SETUP_TPROXY_ACTION = "setup-trusted-proxy"
\ No newline at end of file
diff --git a/ambari-server/src/main/python/ambari_server/setupTrustedProxy.py
b/ambari-server/src/main/python/ambari_server/setupTrustedProxy.py
new file mode 100644
index 0000000..d93542e
--- /dev/null
+++ b/ambari-server/src/main/python/ambari_server/setupTrustedProxy.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python
+
+'''
+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.
+'''
+
+import ambari_simplejson as json
+import os
+import re
+
+from ambari_commons.exceptions import FatalException, NonFatalException
+from ambari_commons.logging_utils import get_silent, print_info_msg
+from ambari_server.serverConfiguration import get_ambari_properties,
find_properties_file
+from ambari_server.setupSecurity import REGEX_TRUE_FALSE
+from ambari_server.userInput import get_validated_string_input, get_YN_input
+from ambari_commons.logging_utils import print_error_msg
+from ambari_server.serverUtils import get_value_from_dictionary
+
+TPROXY_SUPPORT_ENABLED = 'ambari.tproxy.authentication.enabled'
+PROXYUSER_HOSTS = 'ambari.tproxy.proxyuser.{}.hosts'
+PROXYUSER_USERS = 'ambari.tproxy.proxyuser.{}.users'
+PROXYUSER_GROUPS = 'ambari.tproxy.proxyuser.{}.groups'
+
+REGEX_ANYTHING = '.*'
+WILDCARD_FOR_ALL = '*'
+
+def populate_tproxy_configuration_property(properties, tproxy_user_name,
property_name, question_text_qualifier):
+ resolved_property_name = property_name.format(tproxy_user_name)
+ resolved_property_value = get_value_from_dictionary(properties,
resolved_property_name, WILDCARD_FOR_ALL)
+ resolved_property_value = get_validated_string_input("Allowed {0} for {1}
({2})? ".format(question_text_qualifier, tproxy_user_name,
resolved_property_value), resolved_property_value, REGEX_ANYTHING, "Invalid
input", False)
+ properties[resolved_property_name] = resolved_property_value
+
+def add_new_trusted_proxy_config(properties):
+ tproxy_user_name = get_validated_string_input("The proxy user's (local)
username? ", None, REGEX_ANYTHING, "Invalid Trusted Proxy User Name", False,
allowEmpty=False)
+ populate_tproxy_configuration_property(properties, tproxy_user_name,
PROXYUSER_HOSTS, "hosts")
+ populate_tproxy_configuration_property(properties, tproxy_user_name,
PROXYUSER_USERS, "users")
+ populate_tproxy_configuration_property(properties, tproxy_user_name,
PROXYUSER_GROUPS, "groups")
+ return get_YN_input("Add another proxy user [y/n] (n)? ", False)
+
+def parse_trusted_configuration_file(tproxy_configuration_file_path, result):
+ with open(tproxy_configuration_file_path) as tproxy_configuration_file:
+ tproxy_configurations = json.loads(tproxy_configuration_file.read())
+ if tproxy_configurations:
+ for tproxy_configuration in tproxy_configurations:
+ tproxy_user_name = tproxy_configuration['proxyuser']
+ result[PROXYUSER_HOSTS.format(tproxy_user_name)] =
tproxy_configuration['hosts']
+ result[PROXYUSER_USERS.format(tproxy_user_name)] =
tproxy_configuration['users']
+ result[PROXYUSER_GROUPS.format(tproxy_user_name)] =
tproxy_configuration['groups']
+
+def validate_options(options):
+ errors = []
+ if options.tproxy_enabled and not re.match(REGEX_TRUE_FALSE,
options.tproxy_enabled):
+ errors.append("--tproxy-enabled should be to either 'true' or 'false'")
+ if options.tproxy_configuration_file_path and
options.tproxy_configuration_file_path is not None:
+ if not os.path.isfile(options.tproxy_configuration_file_path):
+ errors.append("--tproxy-configuration-file-path is set to a non-existing
file: {}".format(options.tproxy_configuration_file_path))
+ if len(errors) > 0:
+ error_msg = "The following errors occurred while processing your request:
{0}"
+ raise FatalException(1, error_msg.format(str(errors)))
+
+def remove_existing_tproxy_properties(ambari_properties):
+ for key in (each for each in ambari_properties.propertyNames() if
each.startswith('ambari.tproxy.proxyuser')):
+ ambari_properties.removeOldProp(key)
+
+def save_ambari_properties(properties):
+ conf_file = find_properties_file()
+ try:
+ with open(conf_file, 'w') as f:
+ properties.store_ordered(f)
+ except Exception, e:
+ print_error_msg('Could not write ambari config file "%s": %s' %
(conf_file, e))
+
+def update_ambari_properties(ambari_properties, a_dict):
+ for key, value in a_dict.items():
+ ambari_properties.process_pair(key, value)
+
+def input_tproxy_config(options):
+ properties = {}
+ if not options.tproxy_configuration_file_path:
+ interactive_tproxy_input(properties)
+ else:
+ parse_trusted_configuration_file(options.tproxy_configuration_file_path,
properties)
+ return properties
+
+def interactive_tproxy_input(properties):
+ add_new_trusted_proxy = add_new_trusted_proxy_config(properties)
+ while add_new_trusted_proxy:
+ add_new_trusted_proxy = add_new_trusted_proxy_config(properties)
+
+def setup_trusted_proxy(options):
+ print_info_msg("Setup Trusted Proxy")
+ if get_silent():
+ raise NonFatalException('setup-trusted-proxy is not enabled in silent
mode.')
+ validate_options(options)
+ ambari_properties = get_ambari_properties()
+
+ if ambari_properties.get_property(TPROXY_SUPPORT_ENABLED) == 'true':
+ print_info_msg('\nTrusted Proxy support is currently enabled.\n')
+ if not get_YN_input('Do you want to disable Trusted Proxy support [y/n]
(n)? ', False):
+ return
+ ambari_properties.process_pair(TPROXY_SUPPORT_ENABLED, 'false')
+ else:
+ print_info_msg('\nTrusted Proxy support is currently disabled.\n')
+ if not get_YN_input('Do you want to configure Trusted Proxy Support [y/n]
(y)? ', True):
+ return
+ remove_existing_tproxy_properties(ambari_properties)
+ ambari_properties.process_pair(TPROXY_SUPPORT_ENABLED, 'true')
+ update_ambari_properties(ambari_properties, input_tproxy_config(options))
+
+ save_ambari_properties(ambari_properties)
diff --git
a/ambari-server/src/main/resources/webapp/WEB-INF/spring-security.xml
b/ambari-server/src/main/resources/webapp/WEB-INF/spring-security.xml
index bdbf0de..e668a6e 100644
--- a/ambari-server/src/main/resources/webapp/WEB-INF/spring-security.xml
+++ b/ambari-server/src/main/resources/webapp/WEB-INF/spring-security.xml
@@ -35,7 +35,7 @@
<authentication-provider ref="ambariPamAuthenticationProvider"/>
<authentication-provider ref="ambariLdapAuthenticationProvider"/>
<authentication-provider ref="ambariInternalAuthenticationProvider"/>
- <authentication-provider ref="kerberosServiceAuthenticationProvider"/>
+ <authentication-provider ref="ambariKerberosAuthenticationProvider"/>
</authentication-manager>
<beans:bean id="ambariEntryPoint"
class="org.apache.ambari.server.security.AmbariEntryPoint">
@@ -83,15 +83,10 @@
<beans:constructor-arg ref="permissionHelper"/>
</beans:bean>
- <beans:bean id="kerberosServiceAuthenticationProvider"
class="org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider">
- <beans:property name="ticketValidator">
- <beans:bean
class="org.apache.ambari.server.security.authentication.kerberos.AmbariKerberosTicketValidator">
- <beans:constructor-arg ref="ambariConfiguration"/>
- <beans:property name="debug" value="false"/>
- </beans:bean>
- </beans:property>
-
- <beans:property name="userDetailsService"
ref="authToLocalUserDetailsService"/>
+ <beans:bean id="ambariKerberosAuthenticationProvider"
class="org.apache.ambari.server.security.authentication.kerberos.AmbariKerberosAuthenticationProvider">
+ <beans:constructor-arg ref="authToLocalUserDetailsService"/>
+ <beans:constructor-arg ref="proxiedUserDetailsService"/>
+ <beans:constructor-arg ref="ticketValidator"/>
</beans:bean>
<beans:bean id="authToLocalUserDetailsService"
class="org.apache.ambari.server.security.authentication.kerberos.AmbariAuthToLocalUserDetailsService">
@@ -99,4 +94,14 @@
<beans:constructor-arg ref="ambariUsers"/>
</beans:bean>
+ <beans:bean id="ticketValidator"
class="org.apache.ambari.server.security.authentication.kerberos.AmbariKerberosTicketValidator">
+ <beans:constructor-arg ref="ambariConfiguration"/>
+ <beans:property name="debug" value="false"/>
+ </beans:bean>
+
+ <beans:bean id="proxiedUserDetailsService"
class="org.apache.ambari.server.security.authentication.kerberos.AmbariProxiedUserDetailsService">
+ <beans:constructor-arg ref="ambariConfiguration"/>
+ <beans:constructor-arg ref="ambariUsers"/>
+ </beans:bean>
+
</beans:beans>
diff --git
a/ambari-server/src/test/java/org/apache/ambari/server/audit/AccessUnauthorizedAuditEventTest.java
b/ambari-server/src/test/java/org/apache/ambari/server/audit/AccessUnauthorizedAuditEventTest.java
index 70e4b64..8a3a6d4 100644
---
a/ambari-server/src/test/java/org/apache/ambari/server/audit/AccessUnauthorizedAuditEventTest.java
+++
b/ambari-server/src/test/java/org/apache/ambari/server/audit/AccessUnauthorizedAuditEventTest.java
@@ -31,6 +31,7 @@ public class AccessUnauthorizedAuditEventTest {
public void testAuditMessage() throws Exception {
// Given
String testUserName = "USER1";
+ String testProxyUserName = "PROXYUSER1";
String testRemoteIp = "127.0.0.1";
String testHttpMethod = "GET";
String testResourcePath = "/api/v1/hosts";
@@ -39,6 +40,7 @@ public class AccessUnauthorizedAuditEventTest {
.withTimestamp(System.currentTimeMillis())
.withRemoteIp(testRemoteIp)
.withUserName(testUserName)
+ .withProxyUserName(null)
.withHttpMethodName(testHttpMethod)
.withResourcePath(testResourcePath)
.build();
@@ -50,6 +52,23 @@ public class AccessUnauthorizedAuditEventTest {
String expectedAuditMessage = String.format("User(%s), RemoteIp(%s),
Operation(%s), ResourcePath(%s), Status(Failed), Reason(Access not
authorized)", testUserName, testRemoteIp, testHttpMethod, testResourcePath);
assertThat(actualAuditMessage, equalTo(expectedAuditMessage));
+
+ evnt = AccessUnauthorizedAuditEvent.builder()
+ .withTimestamp(System.currentTimeMillis())
+ .withRemoteIp(testRemoteIp)
+ .withUserName(testUserName)
+ .withProxyUserName(testProxyUserName)
+ .withHttpMethodName(testHttpMethod)
+ .withResourcePath(testResourcePath)
+ .build();
+
+ // When
+ actualAuditMessage = evnt.getAuditMessage();
+
+ // Then
+ expectedAuditMessage = String.format("User(%s), RemoteIp(%s),
ProxyUser(PROXYUSER1), Operation(%s), ResourcePath(%s), Status(Failed),
Reason(Access not authorized)", testUserName, testRemoteIp, testHttpMethod,
testResourcePath);
+
+ assertThat(actualAuditMessage, equalTo(expectedAuditMessage));
}
diff --git
a/ambari-server/src/test/java/org/apache/ambari/server/audit/LoginAuditEventTest.java
b/ambari-server/src/test/java/org/apache/ambari/server/audit/LoginAuditEventTest.java
index a146176..3d998cc 100644
---
a/ambari-server/src/test/java/org/apache/ambari/server/audit/LoginAuditEventTest.java
+++
b/ambari-server/src/test/java/org/apache/ambari/server/audit/LoginAuditEventTest.java
@@ -39,6 +39,7 @@ public class LoginAuditEventTest {
// Given
String testUserName = "USER1";
String testRemoteIp = "127.0.0.1";
+ String testProxyUserName = "PROXYUSER1";
Map<String, List<String>> roles = new HashMap<>();
roles.put("a", Arrays.asList("r1", "r2", "r3"));
@@ -47,6 +48,7 @@ public class LoginAuditEventTest {
.withTimestamp(System.currentTimeMillis())
.withRemoteIp(testRemoteIp)
.withUserName(testUserName)
+ .withProxyUserName(null)
.withRoles(roles)
.build();
@@ -61,6 +63,24 @@ public class LoginAuditEventTest {
assertThat(actualAuditMessage, equalTo(expectedAuditMessage));
+ evnt = LoginAuditEvent.builder()
+ .withTimestamp(System.currentTimeMillis())
+ .withRemoteIp(testRemoteIp)
+ .withUserName(testUserName)
+ .withProxyUserName(testProxyUserName)
+ .withRoles(roles)
+ .build();
+
+ // When
+ actualAuditMessage = evnt.getAuditMessage();
+
+ roleMessage = System.lineSeparator() + " a: r1, r2, r3" +
System.lineSeparator();
+
+ // Then
+ expectedAuditMessage = String.format("User(%s), RemoteIp(%s),
ProxyUser(%s), Operation(User login), Roles(%s), Status(Success)",
+ testUserName, testRemoteIp, testProxyUserName, roleMessage);
+
+ assertThat(actualAuditMessage, equalTo(expectedAuditMessage));
}
@Test
@@ -68,6 +88,7 @@ public class LoginAuditEventTest {
// Given
String testUserName = "USER1";
String testRemoteIp = "127.0.0.1";
+ String testProxyUserName = "PROXYUSER1";
String reason = "Bad credentials";
Map<String, List<String>> roles = new HashMap<>();
@@ -78,6 +99,7 @@ public class LoginAuditEventTest {
.withRemoteIp(testRemoteIp)
.withUserName(testUserName)
.withRoles(roles)
+ .withProxyUserName(null)
.withReasonOfFailure(reason)
.build();
@@ -92,6 +114,25 @@ public class LoginAuditEventTest {
assertThat(actualAuditMessage, equalTo(expectedAuditMessage));
+ evnt = LoginAuditEvent.builder()
+ .withTimestamp(System.currentTimeMillis())
+ .withRemoteIp(testRemoteIp)
+ .withUserName(testUserName)
+ .withProxyUserName(testProxyUserName)
+ .withRoles(roles)
+ .withReasonOfFailure(reason)
+ .build();
+
+ // When
+ actualAuditMessage = evnt.getAuditMessage();
+
+ roleMessage = System.lineSeparator() + " a: r1, r2, r3" +
System.lineSeparator();
+
+ // Then
+ expectedAuditMessage = String.format("User(%s), RemoteIp(%s),
ProxyUser(%s), Operation(User login), Roles(%s), Status(Failed), Reason(%s)",
+ testUserName, testRemoteIp, testProxyUserName, roleMessage, reason);
+
+ assertThat(actualAuditMessage, equalTo(expectedAuditMessage));
}
@Test
diff --git
a/ambari-server/src/test/java/org/apache/ambari/server/audit/LogoutAuditEventTest.java
b/ambari-server/src/test/java/org/apache/ambari/server/audit/LogoutAuditEventTest.java
index 7fb6fef..5248a94 100644
---
a/ambari-server/src/test/java/org/apache/ambari/server/audit/LogoutAuditEventTest.java
+++
b/ambari-server/src/test/java/org/apache/ambari/server/audit/LogoutAuditEventTest.java
@@ -32,11 +32,13 @@ public class LogoutAuditEventTest {
// Given
String testUserName = "USER1";
String testRemoteIp = "127.0.0.1";
+ String testProxyUserName = "PROXYUSER1";
LogoutAuditEvent evnt = LogoutAuditEvent.builder()
.withTimestamp(System.currentTimeMillis())
.withRemoteIp(testRemoteIp)
.withUserName(testUserName)
+ .withProxyUserName(null)
.build();
// When
@@ -48,6 +50,21 @@ public class LogoutAuditEventTest {
assertThat(actualAuditMessage, equalTo(expectedAuditMessage));
+ evnt = LogoutAuditEvent.builder()
+ .withTimestamp(System.currentTimeMillis())
+ .withRemoteIp(testRemoteIp)
+ .withUserName(testUserName)
+ .withProxyUserName(testProxyUserName)
+ .build();
+
+ // When
+ actualAuditMessage = evnt.getAuditMessage();
+
+ // Then
+ expectedAuditMessage = String.format("User(%s), RemoteIp(%s),
ProxyUser(%s), Operation(Logout), Status(Success)",
+ testUserName, testRemoteIp, testProxyUserName);
+
+ assertThat(actualAuditMessage, equalTo(expectedAuditMessage));
}
@Test
diff --git
a/ambari-server/src/test/java/org/apache/ambari/server/audit/OperationStatusAuditEventTest.java
b/ambari-server/src/test/java/org/apache/ambari/server/audit/OperationStatusAuditEventTest.java
index 038cebc..f88b7d7 100644
---
a/ambari-server/src/test/java/org/apache/ambari/server/audit/OperationStatusAuditEventTest.java
+++
b/ambari-server/src/test/java/org/apache/ambari/server/audit/OperationStatusAuditEventTest.java
@@ -33,11 +33,13 @@ public class OperationStatusAuditEventTest {
// Given
Long testRequestId = 100L;
String testStatus = "IN PROGRESS";
+ String testRemoteIp = "127.0.0.1";
OperationStatusAuditEvent evnt = OperationStatusAuditEvent.builder()
.withTimestamp(System.currentTimeMillis())
.withRequestId(testRequestId.toString())
.withStatus(testStatus)
+ .withRemoteIp(testRemoteIp)
.withUserName("testuser")
.withRequestContext("Start Service")
.build();
@@ -46,7 +48,7 @@ public class OperationStatusAuditEventTest {
String actualAuditMessage = evnt.getAuditMessage();
// Then
- String expectedAuditMessage = String.format("User(testuser),
Operation(Start Service), Status(%s), RequestId(%s)", testStatus,
testRequestId);
+ String expectedAuditMessage = String.format("User(testuser),
RemoteIp(127.0.0.1), Operation(Start Service), Status(%s), RequestId(%s)",
testStatus, testRequestId);
assertThat(actualAuditMessage, equalTo(expectedAuditMessage));
}
diff --git
a/ambari-server/src/test/java/org/apache/ambari/server/audit/StartOperationRequestAuditEventTest.java
b/ambari-server/src/test/java/org/apache/ambari/server/audit/StartOperationRequestAuditEventTest.java
index a2097d5..8b12d86 100644
---
a/ambari-server/src/test/java/org/apache/ambari/server/audit/StartOperationRequestAuditEventTest.java
+++
b/ambari-server/src/test/java/org/apache/ambari/server/audit/StartOperationRequestAuditEventTest.java
@@ -35,11 +35,13 @@ public class StartOperationRequestAuditEventTest {
String testRemoteIp = "127.0.0.1";
String testRequestDetails = "{ \"key\": \"value\"}";
Long testRequestId = 100L;
+ String testProxyUserName = "PROXYUSER1";
StartOperationRequestAuditEvent evnt =
StartOperationRequestAuditEvent.builder()
.withTimestamp(System.currentTimeMillis())
.withRemoteIp(testRemoteIp)
.withUserName(testUserName)
+ .withProxyUserName(null)
.withOperation(testRequestDetails)
.withRequestId(testRequestId.toString())
.build();
@@ -52,6 +54,22 @@ public class StartOperationRequestAuditEventTest {
assertThat(actualAuditMessage, equalTo(expectedAuditMessage));
+ evnt = StartOperationRequestAuditEvent.builder()
+ .withTimestamp(System.currentTimeMillis())
+ .withRemoteIp(testRemoteIp)
+ .withUserName(testUserName)
+ .withProxyUserName(testProxyUserName)
+ .withOperation(testRequestDetails)
+ .withRequestId(testRequestId.toString())
+ .build();
+
+ // When
+ actualAuditMessage = evnt.getAuditMessage();
+
+ // Then
+ expectedAuditMessage = String.format("User(%s), RemoteIp(%s),
ProxyUser(%s), Operation(%s), RequestId(%d), Status(Successfully queued)",
testUserName, testRemoteIp, testProxyUserName, testRequestDetails,
testRequestId);
+
+ assertThat(actualAuditMessage, equalTo(expectedAuditMessage));
}
@Test
diff --git
a/ambari-server/src/test/java/org/apache/ambari/server/audit/TaskStatusAuditEventTest.java
b/ambari-server/src/test/java/org/apache/ambari/server/audit/TaskStatusAuditEventTest.java
index 8f769c7..7755468 100644
---
a/ambari-server/src/test/java/org/apache/ambari/server/audit/TaskStatusAuditEventTest.java
+++
b/ambari-server/src/test/java/org/apache/ambari/server/audit/TaskStatusAuditEventTest.java
@@ -31,6 +31,7 @@ public class TaskStatusAuditEventTest {
public void testAuditMessage() throws Exception {
// Given
String testUserName = "USER1";
+ String testRemoteIp = "127.0.0.1";
String testOperation = "START MYCOMPONENT";
String testRequestDetails = "Start MyComponent";
String testHostName = "ambari.example.com";
@@ -41,6 +42,7 @@ public class TaskStatusAuditEventTest {
TaskStatusAuditEvent event = TaskStatusAuditEvent.builder()
.withTimestamp(System.currentTimeMillis())
.withUserName(testUserName)
+ .withRemoteIp(testRemoteIp)
.withOperation(testOperation)
.withRequestId(testRequestId.toString())
.withDetails(testRequestDetails)
@@ -53,7 +55,7 @@ public class TaskStatusAuditEventTest {
String actualAuditMessage = event.getAuditMessage();
// Then
- String expectedAuditMessage = String.format("User(%s), Operation(%s),
Details(%s), Status(%s), RequestId(%d), TaskId(%d), Hostname(%s)",
testUserName, testOperation, testRequestDetails, testStatus, testRequestId,
testTaskId, testHostName);
+ String expectedAuditMessage = String.format("User(%s), RemoteIp(%s),
Details(%s), Status(%s), RequestId(%d), TaskId(%d), Hostname(%s)",
testUserName, testRemoteIp, testRequestDetails, testStatus, testRequestId,
testTaskId, testHostName);
assertThat(actualAuditMessage, equalTo(expectedAuditMessage));
diff --git
a/ambari-server/src/test/java/org/apache/ambari/server/security/authentication/AmbariBasicAuthenticationFilterTest.java
b/ambari-server/src/test/java/org/apache/ambari/server/security/authentication/AmbariBasicAuthenticationFilterTest.java
index 3df81f9..bafd931 100644
---
a/ambari-server/src/test/java/org/apache/ambari/server/security/authentication/AmbariBasicAuthenticationFilterTest.java
+++
b/ambari-server/src/test/java/org/apache/ambari/server/security/authentication/AmbariBasicAuthenticationFilterTest.java
@@ -72,6 +72,7 @@ public class AmbariBasicAuthenticationFilterTest extends
EasyMockSupport {
FilterChain filterChain = createMock(FilterChain.class);
expect(request.getHeader("Authorization")).andReturn("Basic
").andReturn(null);
expect(request.getHeader("X-Forwarded-For")).andReturn("1.2.3.4").anyTimes();
+ expect(request.getQueryString()).andReturn(null).anyTimes();
expect(mockedAuditLogger.isEnabled()).andReturn(true).anyTimes();
mockedAuditLogger.log(anyObject(AuditEvent.class));
expectLastCall().times(1);
diff --git
a/ambari-server/src/test/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxiedUserDetailsServiceTest.java
b/ambari-server/src/test/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxiedUserDetailsServiceTest.java
new file mode 100644
index 0000000..ba310d4
--- /dev/null
+++
b/ambari-server/src/test/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxiedUserDetailsServiceTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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.ambari.server.security.authentication.kerberos;
+
+import static org.easymock.EasyMock.expect;
+
+import java.net.UnknownHostException;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.orm.entities.GroupEntity;
+import org.apache.ambari.server.orm.entities.MemberEntity;
+import org.apache.ambari.server.orm.entities.UserEntity;
+import
org.apache.ambari.server.security.authentication.tproxy.AmbariTProxyConfiguration;
+import org.apache.ambari.server.security.authorization.Users;
+import org.easymock.EasyMockSupport;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AmbariProxiedUserDetailsServiceTest extends EasyMockSupport {
+ @Test
+ public void testValidateHost() throws UnknownHostException {
+ AmbariProxiedUserDetailsService service =
createMockBuilder(AmbariProxiedUserDetailsService.class)
+ .withConstructor(createNiceMock(Configuration.class),
createNiceMock(Users.class))
+ .addMockedMethod("getIpAddress", String.class)
+ .createMock();
+
expect(service.getIpAddress("host1.example.com")).andReturn("192.168.74.101").anyTimes();
+
expect(service.getIpAddress("host2.example.com")).andReturn("192.168.74.102").anyTimes();
+
+ AmbariTProxyConfiguration tproxyConfigration =
createMock(AmbariTProxyConfiguration.class);
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn("*").once();
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn("192.168.74.101").once();
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn("host1.example.com").once();
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn("192.168.74.0/24").once();
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn(null).once();
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn("").once();
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn("192.168.74.102").once();
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn("host2.example.com").once();
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn("192.168.74.1/32").once();
+
+ replayAll();
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "*"
+ Assert.assertTrue(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "192.168.74.101"
+ Assert.assertTrue(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "host1.example.com"
+ Assert.assertTrue(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "192.168.74.0/24"
+ Assert.assertTrue(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = null
+ Assert.assertFalse(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = ""
+ Assert.assertFalse(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "192.168.74.102"
+ Assert.assertFalse(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "host1.example.com"
+ Assert.assertFalse(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "192.168.74.1/32"
+ Assert.assertFalse(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ verifyAll();
+ }
+
+ @Test
+ public void testValidateUser() {
+ AmbariProxiedUserDetailsService service = new
AmbariProxiedUserDetailsService(createNiceMock(Configuration.class),
createNiceMock(Users.class));
+
+ AmbariTProxyConfiguration tproxyConfigration =
createMock(AmbariTProxyConfiguration.class);
+
expect(tproxyConfigration.getAllowedUsers("proxyUser")).andReturn("*").once();
+
expect(tproxyConfigration.getAllowedUsers("proxyUser")).andReturn("validUser").once();
+
expect(tproxyConfigration.getAllowedUsers("proxyUser")).andReturn("validuser").once();
+
expect(tproxyConfigration.getAllowedUsers("proxyUser")).andReturn("validUser,
tom, *").once();
+
expect(tproxyConfigration.getAllowedUsers("proxyUser")).andReturn(null).once();
+
expect(tproxyConfigration.getAllowedUsers("proxyUser")).andReturn("").once();
+
expect(tproxyConfigration.getAllowedUsers("proxyUser")).andReturn("notValidUser").once();
+
+ replayAll();
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "*"
+ Assert.assertTrue(service.validateUser(tproxyConfigration, "proxyUser",
"validUser"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "validUser"
+ Assert.assertTrue(service.validateUser(tproxyConfigration, "proxyUser",
"validUser"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "validuser" (different case)
+ Assert.assertTrue(service.validateUser(tproxyConfigration, "proxyUser",
"validUser"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "validUser, tom, *"
+ Assert.assertTrue(service.validateUser(tproxyConfigration, "proxyUser",
"validUser"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = null
+ Assert.assertFalse(service.validateUser(tproxyConfigration, "proxyUser",
"validUser"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = ""
+ Assert.assertFalse(service.validateUser(tproxyConfigration, "proxyUser",
"validUser"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "notValidUser"
+ Assert.assertFalse(service.validateUser(tproxyConfigration, "proxyUser",
"validUser"));
+
+ verifyAll();
+ }
+
+ @Test
+ public void testValidateGroup() {
+ AmbariProxiedUserDetailsService service = new
AmbariProxiedUserDetailsService(createNiceMock(Configuration.class),
createNiceMock(Users.class));
+
+ AmbariTProxyConfiguration tproxyConfigration =
createMock(AmbariTProxyConfiguration.class);
+
expect(tproxyConfigration.getAllowedGroups("proxyUser")).andReturn("*").once();
+
expect(tproxyConfigration.getAllowedGroups("proxyUser")).andReturn("validGroup").once();
+
expect(tproxyConfigration.getAllowedGroups("proxyUser")).andReturn("validgroup").once();
+
expect(tproxyConfigration.getAllowedGroups("proxyUser")).andReturn("validGroup,
*").once();
+
expect(tproxyConfigration.getAllowedGroups("proxyUser")).andReturn("").once();
+
expect(tproxyConfigration.getAllowedGroups("proxyUser")).andReturn(null).once();
+
expect(tproxyConfigration.getAllowedGroups("proxyUser")).andReturn("notValidGroup").once();
+
+ Set<MemberEntity> memberEntities = new HashSet<>();
+ memberEntities.add(createMockMemberEntity("validGroup"));
+ memberEntities.add(createMockMemberEntity("users"));
+
+ // Null Group name - maybe this is not possible
+ memberEntities.add(createMockMemberEntity(null));
+
+ // Null Group - maybe this is not possible
+ MemberEntity memberEntity = createMock(MemberEntity.class);
+ expect(memberEntity.getGroup()).andReturn(null).anyTimes();
+ memberEntities.add(memberEntity);
+
+ UserEntity userEntity = createMock(UserEntity.class);
+
expect(userEntity.getMemberEntities()).andReturn(memberEntities).anyTimes();
+
+ replayAll();
+
+ // ambari.tproxy.proxyuser.proxyUser.groups = "*"
+ Assert.assertTrue(service.validateGroup(tproxyConfigration, "proxyUser",
userEntity));
+
+ // ambari.tproxy.proxyuser.proxyUser.groups = "validGroup"
+ Assert.assertTrue(service.validateGroup(tproxyConfigration, "proxyUser",
userEntity));
+
+ // ambari.tproxy.proxyuser.proxyUser.groups = "validgroup" (different
case)
+ Assert.assertTrue(service.validateGroup(tproxyConfigration, "proxyUser",
userEntity));
+
+ // ambari.tproxy.proxyuser.proxyUser.groups = "validGroup, *"
+ Assert.assertTrue(service.validateGroup(tproxyConfigration, "proxyUser",
userEntity));
+
+ // ambari.tproxy.proxyuser.proxyUser.groups = null
+ Assert.assertFalse(service.validateGroup(tproxyConfigration, "proxyUser",
userEntity));
+
+ // ambari.tproxy.proxyuser.proxyUser.groups = ""
+ Assert.assertFalse(service.validateGroup(tproxyConfigration, "proxyUser",
userEntity));
+
+ // ambari.tproxy.proxyuser.proxyUser.groups = "notValidGroup"
+ Assert.assertFalse(service.validateGroup(tproxyConfigration, "proxyUser",
userEntity));
+
+ verifyAll();
+ }
+
+ @Test
+ public void testIsInIpAddressRange() {
+ AmbariProxiedUserDetailsService service = new
AmbariProxiedUserDetailsService(createNiceMock(Configuration.class),
createNiceMock(Users.class));
+
+ Assert.assertTrue(service.isInIpAddressRange("192.168.74.10/32",
"192.168.74.10"));
+ Assert.assertFalse(service.isInIpAddressRange("192.168.74.10/32",
"192.168.74.11"));
+
+ for (int i = 0; i <= 255; i++) {
+ Assert.assertTrue(service.isInIpAddressRange("192.168.1.0/24",
String.format("192.168.1.%d", i)));
+ }
+ Assert.assertFalse(service.isInIpAddressRange("192.168.1.0/24",
"192.168.2.100"));
+ }
+
+ private MemberEntity createMockMemberEntity(String groupName) {
+ GroupEntity groupEntity = createMock(GroupEntity.class);
+ expect(groupEntity.getGroupName()).andReturn(groupName).anyTimes();
+
+ MemberEntity memberEntity = createMock(MemberEntity.class);
+ expect(memberEntity.getGroup()).andReturn(groupEntity).anyTimes();
+ return memberEntity;
+ }
+
+}
\ No newline at end of file
diff --git a/ambari-web/app/utils/http_client.js
b/ambari-web/app/utils/http_client.js
index 0113679..7b7f21d 100644
--- a/ambari-web/app/utils/http_client.js
+++ b/ambari-web/app/utils/http_client.js
@@ -68,7 +68,7 @@ App.HttpClient = Em.Object.create({
xhr.open(method, url + (url.indexOf('?') >= 0 ? '&_=' : '?_=') + curTime,
true);
if (isGetAsPost) {
xhr.setRequestHeader("X-Http-Method-Override", "GET");
- xhr.setRequestHeader("Content-type",
"application/x-www-form-urlencoded");
+ xhr.setRequestHeader("Content-type", "text/plain");
}
xhr.send(params);