From 083fd383ae4b3ed9593cfb6a52a877a8cc5d8350 Mon Sep 17 00:00:00 2001 From: fwang12 Date: Fri, 9 Dec 2022 20:58:43 +0800 Subject: [PATCH] [KYUUBI #3951] Support to audit the authentication http request ### _Why are the changes needed?_ Support to audit the http request. ``` 08:10:43.231 INFO AuthenticationAuditLogger: user=fwang12(auth:BASIC) ip=192.168.3.159 proxyIp=null method=GET uri=/api/v1/sessions/count protocol=HTTP/1.1 status=200 08:10:43.265 INFO AuthenticationAuditLogger: user=null(auth:BASIC) ip=192.168.3.159 proxyIp=null method=GET uri=/api/v1/sessions/count protocol=HTTP/1.1 status=403 08:10:43.273 INFO AuthenticationAuditLogger: user=null(auth:null) ip=192.168.3.159 proxyIp=null method=GET uri=/api/v1/sessions/count protocol=HTTP/1.1 status=401 08:10:43.320 INFO AuthenticationAuditLogger: user=client(auth:NEGOTIATE) ip=192.168.3.159 proxyIp=null method=GET uri=/api/v1/sessions/count protocol=HTTP/1.1 status=200 08:10:43.324 INFO AuthenticationAuditLogger: user=null(auth:NEGOTIATE) ip=192.168.3.159 proxyIp=null method=GET uri=/api/v1/sessions/count protocol=HTTP/1.1 status=403 08:10:43.331 INFO AuthenticationAuditLogger: user=null(auth:null) ip=192.168.3.159 proxyIp=null method=GET uri=/api/v1/sessions/count protocol=HTTP/1.1 status=401 08:10:47.940 INFO AuthenticationAuditLogger: user=client(auth:NEGOTIATE) ip=192.168.3.159 proxyIp=null method=POST uri=/api/v1/sessions protocol=HTTP/1.1 status=200 08:10:47.999 INFO AuthenticationAuditLogger: user=client(auth:NEGOTIATE) ip=192.168.3.159 proxyIp=null method=DELETE uri=/api/v1/sessions/86d3e4f5-2739-4759-9320-82a29914ab63 protocol=HTTP/1.1 status=200 ``` ### _How was this patch tested?_ - [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible - [x] Add screenshots for manual tests if appropriate image - [ ] [Run test](https://kyuubi.apache.org/docs/latest/develop_tools/testing.html#running-tests) locally before make a pull request Closes #3951 from turboFei/batch_log. Closes #3951 1f1c313d [fwang12] md 30b6e6d6 [fwang12] refactor log db2dff8a [fwang12] refactor c8e532f2 [fwang12] log format a8aa7825 [fwang12] update log4j2 xml 22905186 [fwang12] log4j2 629f93b9 [fwang12] add year db783eaa [fwang12] add log4j pattern 697f02fa [fwang12] save e9cd0bfc [fwang12] audit rest log Authored-by: fwang12 Signed-off-by: fwang12 --- conf/log4j2.xml.template | 15 +++++++ docs/deployment/settings.md | 15 +++++++ .../AuthenticationAuditLogger.scala | 42 +++++++++++++++++++ .../authentication/AuthenticationFilter.scala | 18 ++++++-- .../KyuubiHttpAuthenticationFactory.scala | 2 + .../src/test/resources/log4j2-test.xml | 9 ++++ 6 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/AuthenticationAuditLogger.scala diff --git a/conf/log4j2.xml.template b/conf/log4j2.xml.template index 6aedf7652ff..37fc8acf036 100644 --- a/conf/log4j2.xml.template +++ b/conf/log4j2.xml.template @@ -20,6 +20,10 @@ + + rest-audit.log + rest-audit-%d{yyyy-MM-dd}-%i.log + @@ -27,6 +31,14 @@ + + + + + + + @@ -43,5 +55,8 @@ + + + diff --git a/docs/deployment/settings.md b/docs/deployment/settings.md index 15b0be9c49a..b006330f977 100644 --- a/docs/deployment/settings.md +++ b/docs/deployment/settings.md @@ -607,6 +607,10 @@ Kyuubi uses [log4j](https://logging.apache.org/log4j/2.x/) for logging. You can + + rest-audit.log + rest-audit-%d{yyyy-MM-dd}-%i.log + @@ -614,6 +618,14 @@ Kyuubi uses [log4j](https://logging.apache.org/log4j/2.x/) for logging. You can + + + + + + + @@ -630,6 +642,9 @@ Kyuubi uses [log4j](https://logging.apache.org/log4j/2.x/) for logging. You can + + + ``` diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/AuthenticationAuditLogger.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/AuthenticationAuditLogger.scala new file mode 100644 index 00000000000..ac1ee2a63a6 --- /dev/null +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/AuthenticationAuditLogger.scala @@ -0,0 +1,42 @@ +/* + * 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.kyuubi.server.http.authentication + +import javax.servlet.http.{HttpServletRequest, HttpServletResponse} + +import org.apache.kyuubi.Logging +import org.apache.kyuubi.server.http.authentication.AuthenticationFilter.{HTTP_AUTH_TYPE, HTTP_CLIENT_IP_ADDRESS, HTTP_CLIENT_USER_NAME, HTTP_PROXY_HEADER_CLIENT_IP_ADDRESS} + +object AuthenticationAuditLogger extends Logging { + final private val AUDIT_BUFFER = new ThreadLocal[StringBuilder]() { + override protected def initialValue: StringBuilder = new StringBuilder() + } + + def audit(request: HttpServletRequest, response: HttpServletResponse): Unit = { + val sb = AUDIT_BUFFER.get() + sb.setLength(0) + sb.append(s"user=${HTTP_CLIENT_USER_NAME.get()}(auth:${HTTP_AUTH_TYPE.get()})").append("\t") + sb.append(s"ip=${HTTP_CLIENT_IP_ADDRESS.get()}").append("\t") + sb.append(s"proxyIp=${HTTP_PROXY_HEADER_CLIENT_IP_ADDRESS.get()}").append("\t") + sb.append(s"method=${request.getMethod}").append("\t") + sb.append(s"uri=${request.getRequestURI}").append("\t") + sb.append(s"protocol=${request.getProtocol}").append("\t") + sb.append(s"status=${response.getStatus}") + info(sb.toString()) + } +} diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/AuthenticationFilter.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/AuthenticationFilter.scala index ece0baf56aa..740937d8ec9 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/AuthenticationFilter.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/AuthenticationFilter.scala @@ -106,29 +106,34 @@ class AuthenticationFilter(conf: KyuubiConf) extends Filter with Logging { val authorization = httpRequest.getHeader(AUTHORIZATION_HEADER) val matchedHandler = getMatchedHandler(authorization).orNull + HTTP_CLIENT_IP_ADDRESS.set(httpRequest.getRemoteAddr) + HTTP_PROXY_HEADER_CLIENT_IP_ADDRESS.set( + httpRequest.getHeader(conf.get(FRONTEND_PROXY_HTTP_CLIENT_IP_HEADER))) if (matchedHandler == null) { debug(s"No auth scheme matched for url: ${httpRequest.getRequestURL}") httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED) + AuthenticationAuditLogger.audit(httpRequest, httpResponse) httpResponse.sendError( HttpServletResponse.SC_UNAUTHORIZED, s"No auth scheme matched for $authorization") } else { - HTTP_CLIENT_IP_ADDRESS.set(httpRequest.getRemoteAddr) - HTTP_PROXY_HEADER_CLIENT_IP_ADDRESS.set( - httpRequest.getHeader(conf.get(FRONTEND_PROXY_HTTP_CLIENT_IP_HEADER))) + HTTP_AUTH_TYPE.set(matchedHandler.authScheme.toString) try { val authUser = matchedHandler.authenticate(httpRequest, httpResponse) if (authUser != null) { HTTP_CLIENT_USER_NAME.set(authUser) doFilter(filterChain, httpRequest, httpResponse) } + AuthenticationAuditLogger.audit(httpRequest, httpResponse) } catch { case e: AuthenticationException => + httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN) + AuthenticationAuditLogger.audit(httpRequest, httpResponse) HTTP_CLIENT_USER_NAME.remove() HTTP_CLIENT_IP_ADDRESS.remove() HTTP_PROXY_HEADER_CLIENT_IP_ADDRESS.remove() - httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN) + HTTP_AUTH_TYPE.remove() httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage) } } @@ -171,10 +176,15 @@ object AuthenticationFilter { final val HTTP_CLIENT_USER_NAME = new ThreadLocal[String]() { override protected def initialValue: String = null } + final val HTTP_AUTH_TYPE = new ThreadLocal[String]() { + override protected def initialValue(): String = null + } def getUserIpAddress: String = HTTP_CLIENT_IP_ADDRESS.get def getUserProxyHeaderIpAddress: String = HTTP_PROXY_HEADER_CLIENT_IP_ADDRESS.get() def getUserName: String = HTTP_CLIENT_USER_NAME.get + + def getAuthType: String = HTTP_AUTH_TYPE.get() } diff --git a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/KyuubiHttpAuthenticationFactory.scala b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/KyuubiHttpAuthenticationFactory.scala index 4556f1c823d..ca95fda3d9a 100644 --- a/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/KyuubiHttpAuthenticationFactory.scala +++ b/kyuubi-server/src/main/scala/org/apache/kyuubi/server/http/authentication/KyuubiHttpAuthenticationFactory.scala @@ -79,6 +79,8 @@ class KyuubiHttpAuthenticationFactory(conf: KyuubiConf) { } AuthenticationFilter.HTTP_CLIENT_USER_NAME.remove() AuthenticationFilter.HTTP_CLIENT_IP_ADDRESS.remove() + AuthenticationFilter.HTTP_PROXY_HEADER_CLIENT_IP_ADDRESS.remove() + AuthenticationFilter.HTTP_AUTH_TYPE.remove() } } diff --git a/kyuubi-server/src/test/resources/log4j2-test.xml b/kyuubi-server/src/test/resources/log4j2-test.xml index bfc40dd6df4..623dd71fd14 100644 --- a/kyuubi-server/src/test/resources/log4j2-test.xml +++ b/kyuubi-server/src/test/resources/log4j2-test.xml @@ -19,6 +19,9 @@ + + target/rest-audit.log + @@ -33,11 +36,17 @@ + + + + + +