问题
社区提供了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;
condition accessType security !pwdAuthentication && !kerberosAuthentication DISABLE yes request path "/" REDIRECT yes kerberosAuthentication DIRECT yes else REDIRECT yes isAllowInsecureOverHttp REDIRECT no else DISABLE no 然后根据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的用户名并作为认证用户
...
}
效果
- 前端登录:
- cli登录: