Presto产品化改造-基于knox实现统一登录

356 阅读4分钟
问题

社区提供了openlookeng对接Kerberos的指导说明:[openLooKeng AA安全配置指导(一)----对接Kerberos | openLooKeng](https://openlookeng.io/zh/information/blog/configguide-01/),主要分为两块:开启ssl通信和配置Kerberos。按照这篇配置指导可能会遇到以下问题:

  • 开启ssl通信后,建立http连接的时候可能会遇到验证主机名的问题,所以我们在创建jks的时候配置了所有域名和IP信息;

    -ext "SAN=IP:xxx,IP:xxx,DNS:localhost,DNS:xxx,DNS:" 
    
  • 开启Kerberos后,由于采用的是ticker的认证方式,在浏览器端输入用户名和密码的方式无法通过认证,需要使用火狐浏览器并下载KFW。

改造目的
  • 开启ssl通信后,通过jdbc连接的时候需要加上jks相关的信息,使用不便;
  • 开启Kerberos后需要使用火狐浏览器并需要安装KFW,使用不便。
改造的目标:
  • 目前只有火狐浏览器对ticker的认证方式支持较好,但是也需要安装KFW,因此使用非常不便。需要改造成UI请求不使用ticker认证方式,而是采用公司内部的knox统一访问入口;
  • 开启ssl通信后,jdbc访问的时候需要配置私钥公钥等,使用不变;需要改造成安全认证与ssl通信解除绑定,客户根据需要决定是否开启ssl通信;
代码分析

openlookeng认证相关功能还是挺多的,这里只介绍我们本次改造涉及的部分,主要是围绕io.prestosql.server.security.AuthenticationFilter进行改造的。在AuthenticationFilter中的doFilter方法中,主要有三个功能:

  • isInternalRequest

    如果是内部请求,则从request中解析出principal,然后进入到nestFilter中,如果解析的principal为null,则认证失败。

  • isWebUI

    • 如果是/ui/disabled.html.*、/ui/assets/.*、/ui/vendor/.*,则不需要认证,直接进入nestFilter

    • 否则使用uiAuthenticator从request中获取Presto-UI-Token并解析出authenticatedUser,解析之后,如果request的地址是/ui/login.html,则跳转到/ui/页面,否则进入nestFilter中;

    • 如果authenticatedUser为null,则继续往下走,首先根据request获取accessType;

      conditionaccessTypesecurity
      !pwdAuthentication && !kerberosAuthenticationDISABLEyes
      request path "/"REDIRECTyes
      kerberosAuthenticationDIRECTyes
      elseREDIRECTyes
      isAllowInsecureOverHttpREDIRECTno
      elseDISABLEno

      然后根据accessType进行处理:

      • 如果是DISSABLE,则跳转到对应的DISABLE页面
      • 如果是login/logout页面,则跳转到对应的登录登出页面
      • 如果是REDIRECT,则会跳转到根据request构造出的redirectUri页面
  • Authenticator

    如果既不是内部request,也不是UI请求,则接下来就会使用配置的authenticator进行认证,如果认证成功,则会在request中放入一个header:PRESTO_USER,值为认证的用户名。如果认证失败则会在响应中放入一个WWW-Authenticate的header。

改造
  • 解除ssl通信与安全认证机制的绑定

    • presto-cli/src/main/java/io/prestosql/cli/QueryRunner.java中可以看到,如果配置了Kerberos相关的配置,则request的协议必须是htts,所以需要删除这个条件:

      if (kerberosRemoteServiceName.isPresent()) {            checkArgument(session.getServer().getScheme().equalsIgnoreCase("https"),
                         "Authentication using Kerberos requires HTTPS to be enabled");
      
    • io.prestosql.server.security.AuthenticationFilter中getAccessType方法中,原来的逻辑是如果request是security类型,则会根据request进行判断,返回相应的accessType类型,这里增加一个knoxUtils.isSecurity的条件:

      if (request.isSecure() || knoxUtils.isSecurity())
      
  • 基于knox实现统一登录

    • 集成knox
    • UI页面发送的未认证的请求需要拦截

step 1: 首先是集成knox,我们定义一个KnoxUtil的工具类:

public class KnoxUtils
{
   
    private static HetuConfig hetuConfig;
    private static SecurityConfig securityConfig;
​
    private static final String cookieName = "hadoop-jwt";
    private static final String ORIGINAL_URL_QUERY_PARAM = "originalUrl=";
​
    @Inject
    public KnoxUtils(HetuConfig hetuConfig, SecurityConfig securityConfig)
    {
        this.hetuConfig = hetuConfig;
        this.securityConfig = securityConfig;
    }
    ...

​ KnoxUtils中有以下几个主要方法: ​

public RSAPublicKey getPublicKey()
    {
        ...
    }
public String getLoginUrl(HttpServletRequest req)
    {
        ...
    }
public String getJWTFromCookie(HttpServletRequest req)
    {
        ...
    }
private boolean validateToken(SignedJWT jwtToken, RSAPublicKey publicKey)
    {
        ...
    }
...

​ KnoxUtils定义好之后,还需要注入到Openlookneg对应的module中,不然我们没法使用,在presto-main/src/main/java/io/prestosql/server/security/ServerSecurityModule.java中完成注册: ​ binder.bind(KnoxUtils.class).in(Scopes.SINGLETON);

step 2 : 请求拦截

整体流程:前端发送登录请求,程序走到到isWebUI,如果authenticatedUser.isPresent,则进入nextfilter,否则进入getAccessType方法,在此方法中,我们拦截指定的url,返回REDIRECT;在处理REDIRECT时,我们将符合条件的request重定向到knox的登录页面。

第一点,我们需要在isWebUI方法中增加authenticatedUser的获取方式,除了原有的获取方式,我们新增一个从cookie中获取authenticatedUser的方法:

if (!authenticatedUser.isPresent() && knoxUtils.isSecurity()) {
    //如果是登出请求,则清除认证cookie并跳转到登录页面
    ...
    //从request中获取认证cookie并解析用户名
    ...
    if (StringUtils.isEmpty(username)) {
       authenticatedUser = Optional.empty();
     }
     else {
       authenticatedUser = Optional.of(username);
     }
  }

getAccessType方法中我们拦截指定的url,返回REDIRECT:

String browserHeader = request.getHeader("User-Agent");
            String referer = request.getHeader("referer");
            if (!StringUtils.isEmpty(browserHeader) && browserHeader.startsWith("Mozilla/")) {
                String pathInfo = request.getPathInfo();
                if (pathInfo.equalsIgnoreCase("/ui") ||
                        pathInfo.equalsIgnoreCase("/ui/") ||
                        referer != null && referer.matches(".*/ui/.*")) {
                    return AccessType.REDIRECT;
                }
            }

在处理REDIRECT时,我们将符合条件的request重定向到knox的登录页面:

if (accessType.equals(AccessType.REDIRECT)) {
                // redirect to login page
                URI redirectUri = UiAuthenticator.buildLoginFormURI(URI.create(request.getRequestURI()));
                if (request.getMethod().equalsIgnoreCase("get") && redirectUri.getPath().equalsIgnoreCase(UiAuthenticator.LOGIN_FORM) && knoxUtils.isSecurity()) {
                    //构造knox的redirectUri
                    ...
                }
                response.sendRedirect(redirectUri.toString());
                return;
            }

UI页面可以发送查询请求,查询请求不会进入到isWebUI流程,所以我们还需要修改Kerberos认证相关的代码,在1.5.0/presto-main/src/main/java/io/prestosql/server/security/KerberosAuthenticator.java中增加使用cookie认证的方式,如果原生Kerberos认证失败,则进行cookie认证:

if (principal == null) {
     //如果kerberos认证失败,则从request中获取认证cookie并解析,获取cookie的用户名并作为认证用户
     ...
  }
效果
  • 前端登录:

1.png

  • cli登录:

2.png

3.png