This is an automated email from the ASF dual-hosted git repository.
oleewere pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/trunk by this push:
new 9d307c8 AMBARI-24644. Log Search: support trusted knox proxy. (#2327)
9d307c8 is described below
commit 9d307c8758088cde7e7fb9a960525c9e6bc7f799
Author: Olivér Szabó <[email protected]>
AuthorDate: Mon Sep 17 14:51:40 2018 +0200
AMBARI-24644. Log Search: support trusted knox proxy. (#2327)
* AMBARI-24644. Log Search: support trusted knox proxy.
* AMBARI-24644. Add javadoc for trusted proxy filter.
* AMBARI-24644. Stop if non null header found.
---
.../ambari/logsearch/conf/AuthPropsConfig.java | 90 +++++++++-
.../ambari/logsearch/conf/SecurityConfig.java | 11 +-
.../web/filters/LogsearchTrustedProxyFilter.java | 191 +++++++++++++++++++++
ambari-logsearch/docker/Dockerfile | 2 +-
.../test-config/logsearch/logsearch-sso.properties | 3 +
5 files changed, 294 insertions(+), 3 deletions(-)
diff --git
a/ambari-logsearch/ambari-logsearch-server/src/main/java/org/apache/ambari/logsearch/conf/AuthPropsConfig.java
b/ambari-logsearch/ambari-logsearch-server/src/main/java/org/apache/ambari/logsearch/conf/AuthPropsConfig.java
index 2facf86..300a57f 100644
---
a/ambari-logsearch/ambari-logsearch-server/src/main/java/org/apache/ambari/logsearch/conf/AuthPropsConfig.java
+++
b/ambari-logsearch/ambari-logsearch-server/src/main/java/org/apache/ambari/logsearch/conf/AuthPropsConfig.java
@@ -21,7 +21,6 @@ package org.apache.ambari.logsearch.conf;
import org.apache.ambari.logsearch.config.api.LogSearchPropertyDescription;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
-
import java.util.List;
import static
org.apache.ambari.logsearch.common.LogSearchConstants.LOGSEARCH_PROPERTIES_FILE;
@@ -187,6 +186,55 @@ public class AuthPropsConfig {
)
private boolean redirectForward;
+ @Value("${logsearch.auth.trusted.proxy:false}")
+ @LogSearchPropertyDescription(
+ name = "logsearch.auth.trusted.proxy",
+ description = "A boolean property to enable/disable trusted-proxy 'knox'
authentication",
+ examples = {"true"},
+ defaultValue = "false",
+ sources = {LOGSEARCH_PROPERTIES_FILE}
+ )
+ private boolean trustedProxy;
+
+
@Value("#{propertiesSplitter.parseList('${logsearch.auth.proxyuser.users:knox}')}")
+ @LogSearchPropertyDescription(
+ name = "logsearch.auth.proxyuser.users",
+ description = "List of users which the trusted-proxy user ‘knox’ can proxy
for",
+ examples = {"knox,hdfs"},
+ defaultValue = "knox",
+ sources = {LOGSEARCH_PROPERTIES_FILE}
+ )
+ private List<String> proxyUsers;
+
+
@Value("#{propertiesSplitter.parseList('${logsearch.auth.proxyuser.groups:*}')}")
+ @LogSearchPropertyDescription(
+ name = "logsearch.auth.proxyuser.groups",
+ description = "List of user-groups which trusted-proxy user ‘knox’ can
proxy for",
+ examples = {"admin,user"},
+ defaultValue = "*",
+ sources = {LOGSEARCH_PROPERTIES_FILE}
+ )
+ private List<String> proxyUserGroups;
+
+
@Value("#{propertiesSplitter.parseList('${logsearch.auth.proxyuser.hosts:*}')}")
+ @LogSearchPropertyDescription(
+ name = "logsearch.auth.proxyuser.hosts",
+ description = "List of hosts from which trusted-proxy user ‘knox’ can
connect from",
+ examples = {"host1,host2"},
+ defaultValue = "*",
+ sources = {LOGSEARCH_PROPERTIES_FILE}
+ )
+ private List<String> proxyUserHosts;
+
+
@Value("#{propertiesSplitter.parseList('${logsearch.auth.proxyserver.ip:}')}")
+ @LogSearchPropertyDescription(
+ name = "logsearch.auth.proxyserver.ip",
+ description = "IP of trusted Knox Proxy server(s) that Log Search will
trust on",
+ examples = {"192.168.0.1,192.168.0.2"},
+ sources = {LOGSEARCH_PROPERTIES_FILE}
+ )
+ private List<String> proxyIp;
+
public boolean isAuthFileEnabled() {
return authFileEnabled;
}
@@ -314,4 +362,44 @@ public class AuthPropsConfig {
public void setUserAgentList(List<String> userAgentList) {
this.userAgentList = userAgentList;
}
+
+ public boolean isTrustedProxy() {
+ return trustedProxy;
+ }
+
+ public void setTrustedProxy(boolean trustedProxy) {
+ this.trustedProxy = trustedProxy;
+ }
+
+ public List<String> getProxyUsers() {
+ return proxyUsers;
+ }
+
+ public void setProxyUsers(List<String> proxyUsers) {
+ this.proxyUsers = proxyUsers;
+ }
+
+ public List<String> getProxyUserGroups() {
+ return proxyUserGroups;
+ }
+
+ public void setProxyUserGroups(List<String> proxyUserGroups) {
+ this.proxyUserGroups = proxyUserGroups;
+ }
+
+ public List<String> getProxyUserHosts() {
+ return proxyUserHosts;
+ }
+
+ public void setProxyUserHosts(List<String> proxyUserHosts) {
+ this.proxyUserHosts = proxyUserHosts;
+ }
+
+ public List<String> getProxyIp() {
+ return proxyIp;
+ }
+
+ public void setProxyIP(List<String> proxyIp) {
+ this.proxyIp = proxyIp;
+ }
}
diff --git
a/ambari-logsearch/ambari-logsearch-server/src/main/java/org/apache/ambari/logsearch/conf/SecurityConfig.java
b/ambari-logsearch/ambari-logsearch-server/src/main/java/org/apache/ambari/logsearch/conf/SecurityConfig.java
index e66842f..058cef5 100644
---
a/ambari-logsearch/ambari-logsearch-server/src/main/java/org/apache/ambari/logsearch/conf/SecurityConfig.java
+++
b/ambari-logsearch/ambari-logsearch-server/src/main/java/org/apache/ambari/logsearch/conf/SecurityConfig.java
@@ -42,6 +42,7 @@ import
org.apache.ambari.logsearch.web.filters.LogsearchFilter;
import org.apache.ambari.logsearch.web.filters.LogsearchJWTFilter;
import
org.apache.ambari.logsearch.web.filters.LogsearchKRBAuthenticationFilter;
import
org.apache.ambari.logsearch.web.filters.LogsearchSecurityContextFormationFilter;
+import org.apache.ambari.logsearch.web.filters.LogsearchTrustedProxyFilter;
import
org.apache.ambari.logsearch.web.filters.LogsearchUsernamePasswordAuthenticationFilter;
import
org.apache.ambari.logsearch.web.security.LogsearchAuthenticationProvider;
import org.springframework.context.annotation.Bean;
@@ -111,7 +112,8 @@ public class SecurityConfig extends
WebSecurityConfigurerAdapter {
.httpBasic()
.authenticationEntryPoint(logsearchAuthenticationEntryPoint())
.and()
- .addFilterBefore(logsearchKRBAuthenticationFilter(),
BasicAuthenticationFilter.class)
+ .addFilterBefore(logsearchTrustedProxyFilter(),
BasicAuthenticationFilter.class)
+ .addFilterAfter(logsearchKRBAuthenticationFilter(),
LogsearchTrustedProxyFilter.class)
.addFilterBefore(logsearchUsernamePasswordAuthenticationFilter(),
LogsearchKRBAuthenticationFilter.class)
.addFilterAfter(securityContextFormationFilter(),
FilterSecurityInterceptor.class)
.addFilterAfter(logsearchEventHistoryFilter(),
LogsearchSecurityContextFormationFilter.class)
@@ -151,6 +153,13 @@ public class SecurityConfig extends
WebSecurityConfigurerAdapter {
}
@Bean
+ public LogsearchTrustedProxyFilter logsearchTrustedProxyFilter() throws
Exception {
+ LogsearchTrustedProxyFilter filter = new
LogsearchTrustedProxyFilter(requestMatcher(), authPropsConfig);
+ filter.setAuthenticationManager(authenticationManagerBean());
+ return filter;
+ }
+
+ @Bean
public LogsearchJWTFilter logsearchJwtFilter() throws Exception {
LogsearchJWTFilter filter = new LogsearchJWTFilter(requestMatcher(),
authPropsConfig);
filter.setAuthenticationManager(authenticationManagerBean());
diff --git
a/ambari-logsearch/ambari-logsearch-server/src/main/java/org/apache/ambari/logsearch/web/filters/LogsearchTrustedProxyFilter.java
b/ambari-logsearch/ambari-logsearch-server/src/main/java/org/apache/ambari/logsearch/web/filters/LogsearchTrustedProxyFilter.java
new file mode 100644
index 0000000..8d152bf
--- /dev/null
+++
b/ambari-logsearch/ambari-logsearch-server/src/main/java/org/apache/ambari/logsearch/web/filters/LogsearchTrustedProxyFilter.java
@@ -0,0 +1,191 @@
+/*
+ * 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.logsearch.web.filters;
+
+import org.apache.ambari.logsearch.conf.AuthPropsConfig;
+import org.apache.ambari.logsearch.web.model.Privilege;
+import org.apache.ambari.logsearch.web.model.Role;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import
org.springframework.security.authentication.AnonymousAuthenticationToken;
+import
org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
+import
org.springframework.security.web.authentication.WebAuthenticationDetails;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * Filter servlet to handle trusted proxy authentication.
+ * It is disabled by default (see: {@link AuthPropsConfig#isTrustedProxy()})
<br/>
+ * There are 4 main configuration properties of this filter (allow
authentication only if these are matches with the request details): <br/>
+ * - {@link AuthPropsConfig#getProxyUsers()} - Proxy users <br/>
+ * - {@link AuthPropsConfig#getProxyUserGroups()} - Proxy groups <br/>
+ * - {@link AuthPropsConfig#getProxyUserHosts()} - Proxy hosts <br/>
+ * - {@link AuthPropsConfig#getProxyIp()} - Proxy server IPs<br/>
+ */
+public class LogsearchTrustedProxyFilter extends
AbstractAuthenticationProcessingFilter {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(LogsearchTrustedProxyFilter.class);
+
+ private static final String TRUSTED_PROXY_KNOX_HEADER = "X-Forwarded-For";
+
+ private AuthPropsConfig authPropsConfig;
+
+ public LogsearchTrustedProxyFilter(RequestMatcher requestMatcher,
AuthPropsConfig authPropsConfig) {
+ super(requestMatcher);
+ this.authPropsConfig = authPropsConfig;
+ }
+
+ @Override
+ public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException, IOException,
ServletException {
+ String doAsUserName = request.getParameter("doAs");
+ final List<GrantedAuthority> authorities = getDefaultGrantedAuthorities();
+ final UserDetails principal = new User(doAsUserName, "", authorities);
+ final Authentication finalAuthentication = new
UsernamePasswordAuthenticationToken(principal, "", authorities);
+ WebAuthenticationDetails webDetails = new
WebAuthenticationDetails(request);
+ ((AbstractAuthenticationToken) finalAuthentication).setDetails(webDetails);
+ SecurityContextHolder.getContext().setAuthentication(finalAuthentication);
+ LOG.info("Logged into Log Search User as doAsUser = {}", doAsUserName);
+ return finalAuthentication;
+ }
+
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain
chain) throws IOException, ServletException {
+ Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
+ boolean skip = true;
+ if (authPropsConfig.isTrustedProxy() && !isAuthenticated(authentication) )
{
+ String doAsUserName = req.getParameter("doAs");
+ String remoteAddr = req.getRemoteAddr();
+ if (StringUtils.isNotEmpty(doAsUserName) &&
isTrustedProxySever(remoteAddr)
+ && isTrustedHost(getXForwardHeader((HttpServletRequest) req))) {
+ List<GrantedAuthority> grantedAuths = getDefaultGrantedAuthorities();
+ if (!(isTrustedProxyUser(doAsUserName) ||
isTrustedProxyUserGroup(grantedAuths))) {
+ skip = false;
+ }
+ }
+ }
+ if (skip) {
+ chain.doFilter(req, res);
+ return;
+ }
+ super.doFilter(req, res, chain);
+ }
+
+ private boolean isTrustedProxySever(String requestHosts) {
+ if (authPropsConfig.getProxyIp() == null || requestHosts == null) {
+ return false;
+ }
+ final List<String> proxyServers = authPropsConfig.getProxyIp();
+ return (proxyServers.size() == 1 && proxyServers.contains("*")) ||
authPropsConfig.getProxyIp().contains(requestHosts);
+ }
+
+ private boolean isTrustedHost(String requestHosts) {
+ if (requestHosts == null) {
+ return false;
+ }
+ List<String> trustedProxyHosts = authPropsConfig.getProxyUserHosts();
+ return (trustedProxyHosts.size() == 1 && trustedProxyHosts.contains("*"))
|| trustedProxyHosts.contains(requestHosts);
+ }
+
+ private boolean isTrustedProxyUser(String doAsUser) {
+ if (doAsUser == null) {
+ return false;
+ }
+ List<String> trustedProxyUsers = authPropsConfig.getProxyUsers();
+ return (trustedProxyUsers.size() == 1 && trustedProxyUsers.contains("*"))
|| trustedProxyUsers.contains(doAsUser);
+
+ }
+
+ private boolean isTrustedProxyUserGroup(List<GrantedAuthority>
proxyUserGroup) {
+ if (proxyUserGroup == null) {
+ return false;
+ }
+ List<String> trustedProxyGroups = authPropsConfig.getProxyUserGroups();
+ if (trustedProxyGroups.size() == 1 && trustedProxyGroups.contains("*")) {
+ return true;
+ } else {
+ for (GrantedAuthority group : proxyUserGroup) {
+ if (trustedProxyGroups.contains(group.getAuthority())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isAuthenticated(Authentication authentication) {
+ return authentication != null && !(authentication instanceof
AnonymousAuthenticationToken) && authentication.isAuthenticated();
+ }
+
+ private String getXForwardHeader(HttpServletRequest httpRequest) {
+ Enumeration<String> names = httpRequest.getHeaderNames();
+ while (names.hasMoreElements()) {
+ String name = names.nextElement();
+ Enumeration<String> values = httpRequest.getHeaders(name);
+ String value = "";
+ if (values != null) {
+ while (values.hasMoreElements()) {
+ value = values.nextElement();
+ if (StringUtils.isNotBlank(value)) {
+ break;
+ }
+ }
+ }
+ if (StringUtils.trimToNull(name) != null
+ && StringUtils.trimToNull(value) != null) {
+ if (name.equalsIgnoreCase(TRUSTED_PROXY_KNOX_HEADER)) {
+ return value;
+ }
+ }
+ }
+ return "";
+ }
+
+ private List<GrantedAuthority> getDefaultGrantedAuthorities() {
+ // TODO: add proper roles if ACLs should be handled in the right way
(cluster based roles)
+ List<GrantedAuthority> authorities = new ArrayList<>();
+ Role role = new Role();
+ role.setName("admin");
+ Privilege priv = new Privilege();
+ priv.setName("READ_PRIVILEGE");
+ List<Privilege> privileges = new ArrayList<>();
+ privileges.add(priv);
+ role.setPrivileges(privileges);
+ authorities.add(role);
+ return authorities;
+ }
+}
diff --git a/ambari-logsearch/docker/Dockerfile
b/ambari-logsearch/docker/Dockerfile
index d076565..c1101cb 100644
--- a/ambari-logsearch/docker/Dockerfile
+++ b/ambari-logsearch/docker/Dockerfile
@@ -61,7 +61,7 @@ RUN cd /root && tar -zxvf /root/solr-$SOLR_VERSION.tgz
# Install Knox
WORKDIR /
RUN adduser knox
-ENV KNOX_VERSION 1.0.0
+ENV KNOX_VERSION 1.1.0
RUN wget -q -O /knox-${KNOX_VERSION}.zip
http://download.nextag.com/apache/knox/${KNOX_VERSION}/knox-${KNOX_VERSION}.zip
&& unzip /knox-${KNOX_VERSION}.zip && rm knox-${KNOX_VERSION}.zip && ln -nsf
knox-${KNOX_VERSION} knox && chmod +x /knox/bin/*.sh && chown -R knox /knox/
ADD knox/keystores /knox-secrets
diff --git
a/ambari-logsearch/docker/test-config/logsearch/logsearch-sso.properties
b/ambari-logsearch/docker/test-config/logsearch/logsearch-sso.properties
index d34860a..ad1946e 100644
--- a/ambari-logsearch/docker/test-config/logsearch/logsearch-sso.properties
+++ b/ambari-logsearch/docker/test-config/logsearch/logsearch-sso.properties
@@ -61,3 +61,6 @@
logsearch.auth.jwt.public_key=MIICOjCCAaOgAwIBAgIJAMY1lA6gY1V/MA0GCSqGSIb3DQEBBQ
logsearch.auth.jwt.provider_url=https://localhost:8443/gateway/knoxsso/api/v1/websso
logsearch.auth.jwt.cookie.name=hadoop-jwt
logsearch.auth.jwt.query.param.original_url=originalUrl
+
+logsearch.auth.trusted.proxy=true
+logsearch.auth.proxyuser.users=*