ambari-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From oleew...@apache.org
Subject [ambari] branch trunk updated: AMBARI-24644. Log Search: support trusted knox proxy. (#2327)
Date Mon, 17 Sep 2018 12:51:46 GMT
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ó <oleewere@gmail.com>
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=*


Mime
View raw message