Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LIVY-785] Enable adding security related HTTP headers to responses #396

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions conf/livy.conf.template
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@
# http-header "X-Requested-By" in request if the http method is POST/DELETE/PUT/PATCH.
# livy.server.csrf-protection.enabled =

# Whether to add security related HTTP headers to responses, by default false. If enabled,
# Livy server adds HTTP headers to responses based on below configuration parameters starting with
# `Livy.server.http.header.`
# livy.server.security-headers.enabled =

# Security headers added to responses by default when
# configuration `livy.server.security-headers.enabled` is set to true.
# livy.server.http.header.X-XSS-Protection = 1; mode=block
# livy.server.http.header.X-Frame-Options = SAMEORIGIN
# livy.server.http.header.X-Content-Type-Options = nosniff

# Whether to enable HiveContext in livy interpreter, if it is true hive-site.xml will be detected
# on user request and then livy server classpath automatically.
# livy.repl.enable-hive-context =
Expand Down
8 changes: 8 additions & 0 deletions server/src/main/scala/org/apache/livy/LivyConf.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ object LivyConf {

val CSRF_PROTECTION = Entry("livy.server.csrf-protection.enabled", false)

val SECURITY_HEADERS_ENABLED = Entry("livy.server.security-headers.enabled", false)
val SECURITY_HEADERS_XSS_PROTECTION =
Entry("livy.server.http.header.X-XSS-Protection", "1; mode=block")
val SECURITY_HEADERS_FRAME_OPTIONS =
Entry("livy.server.http.header.X-Frame-Options", "SAMEORIGIN")
val SECURITY_HEADERS_CONTENT_TYPE_OPTIONS =
Entry("livy.server.http.header.X-Content-Type-Options", "nosniff")

val IMPERSONATION_ENABLED = Entry("livy.impersonation.enabled", false)
val SUPERUSERS = Entry("livy.superusers", null)

Expand Down
24 changes: 21 additions & 3 deletions server/src/main/scala/org/apache/livy/server/LivyServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import scala.concurrent.Future
import org.apache.hadoop.security.{SecurityUtil, UserGroupInformation}
import org.apache.hadoop.security.authentication.server._
import org.eclipse.jetty.servlet.FilterHolder
import org.scalatra.{NotFound, ScalatraServlet}
import org.scalatra.{ApiFormats, NotFound, ScalatraServlet}
import org.scalatra.metrics.MetricsBootstrap
import org.scalatra.metrics.MetricsSupportExtensions._
import org.scalatra.servlet.{MultipartConfig, ServletApiImplicits}
Expand Down Expand Up @@ -177,14 +177,26 @@ class LivyServer extends Logging {
// Servlet for hosting static files such as html, css, and js
// Necessary since Jetty cannot set it's resource base inside a jar
// Returns 404 if the file does not exist
val staticResourceServlet = new ScalatraServlet {
val staticResourceServlet = new ScalatraServlet with ApiFormats {

addMimeMapping("image/png", "png")
addMimeMapping("application/vnd.ms-fontobject", "eot")
addMimeMapping("image/svg+xml", "svg")
addMimeMapping("font/ttf", "ttf")
addMimeMapping("font/woff", "woff")
addMimeMapping("font/woff2", "woff2")

get("/*") {
val fileName = params("splat")
val notFoundMsg = "File not found"

if (!fileName.isEmpty) {
getClass.getResourceAsStream(s"ui/static/$fileName") match {
case is: InputStream => new BufferedInputStream(is)
case is: InputStream => {
val extension = fileName.split("\\.").last
contentType = formats(extension)
new BufferedInputStream(is)
}
case null => NotFound(notFoundMsg)
}
} else {
Expand Down Expand Up @@ -315,6 +327,12 @@ class LivyServer extends Logging {
server.context.addFilter(csrfHolder, "/*", EnumSet.allOf(classOf[DispatcherType]))
}

if(livyConf.getBoolean(SECURITY_HEADERS_ENABLED)) {
info("Adding security headers is enabled.")
val securityHeadersHolder = new FilterHolder(new SecurityHeadersFilter(livyConf))
server.context.addFilter(securityHeadersHolder, "/*", EnumSet.allOf(classOf[DispatcherType]))
}

if (accessManager.isAccessControlOn) {
info("Access control is enabled")
val accessHolder = new FilterHolder(new AccessFilter(accessManager))
Expand Down