Skip to content

Commit cba1ef6

Browse files
committed
add security (maybe?) for metrics json
1 parent f0264a7 commit cba1ef6

File tree

5 files changed

+74
-9
lines changed

5 files changed

+74
-9
lines changed

core/src/main/scala/org/apache/spark/status/api/v1/JsonRootResource.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,16 @@ import javax.servlet.ServletContext
2020
import javax.ws.rs._
2121
import javax.ws.rs.core.{Response, Context}
2222

23+
import com.sun.jersey.api.core.ResourceConfig
2324
import com.sun.jersey.spi.container.servlet.ServletContainer
24-
import org.apache.spark.ui.SparkUI
25+
2526
import org.eclipse.jetty.server.handler.ContextHandler
2627
import org.eclipse.jetty.servlet.{ServletHolder, ServletContextHandler}
2728
import org.glassfish.jersey.jackson._
2829

30+
import org.apache.spark.SecurityManager
31+
import org.apache.spark.ui.SparkUI
32+
2933

3034
@Path("/v1")
3135
class JsonRootResource extends UIRootFromServletContext {
@@ -81,6 +85,8 @@ object JsonRootResource {
8185
"com.sun.jersey.api.core.PackagesResourceConfig")
8286
holder.setInitParameter("com.sun.jersey.config.property.packages",
8387
"org.apache.spark.status.api.v1")
88+
holder.setInitParameter(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
89+
classOf[SecurityFilter].getCanonicalName)
8490
UIRootFromServletContext.setUiRoot(jerseyContext, uiRoot)
8591
jerseyContext.addServlet(holder, "/*")
8692
jerseyContext
@@ -97,6 +103,7 @@ private[spark] trait UIRoot {
97103
case None => throw new NotFoundException("no such app: " + appId)
98104
}
99105
}
106+
def securityManager: SecurityManager
100107
}
101108

102109
object UIRootFromServletContext {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.spark.status.api.v1
18+
19+
import javax.ws.rs.WebApplicationException
20+
import javax.ws.rs.core.Response
21+
22+
import com.sun.jersey.spi.container.{ContainerRequest,ContainerRequestFilter}
23+
24+
class SecurityFilter extends ContainerRequestFilter with UIRootFromServletContext {
25+
def filter(req: ContainerRequest): ContainerRequest = {
26+
val user = Option(req.getUserPrincipal).map{_.getName}.orNull
27+
if (uiRoot.securityManager.checkUIViewPermissions(user)) {
28+
req
29+
} else {
30+
throw new WebApplicationException(
31+
Response
32+
.status(Response.Status.UNAUTHORIZED)
33+
.entity("user \"" + user + "\"is not authorized")
34+
.build()
35+
)
36+
}
37+
}
38+
}

core/src/main/scala/org/apache/spark/ui/SparkUI.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import org.apache.spark.ui.storage.{StorageListener, StorageTab}
3434
private[spark] class SparkUI private (
3535
val sc: Option[SparkContext],
3636
val conf: SparkConf,
37-
val securityManager: SecurityManager,
37+
securityManager: SecurityManager,
3838
val environmentListener: EnvironmentListener,
3939
val storageStatusListener: StorageStatusListener,
4040
val executorsListener: ExecutorsListener,

core/src/main/scala/org/apache/spark/ui/WebUI.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import org.apache.spark.util.Utils
3636
* pages. The use of tabs is optional, however; a WebUI may choose to include pages directly.
3737
*/
3838
private[spark] abstract class WebUI(
39-
securityManager: SecurityManager,
39+
val securityManager: SecurityManager,
4040
port: Int,
4141
conf: SparkConf,
4242
basePath: String = "",

core/src/test/scala/org/apache/spark/deploy/history/HistoryServerSuite.scala

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,32 @@ class HistoryServerSuite extends FunSuite with BeforeAndAfter with Matchers {
9191
}
9292
}
9393

94-
test("fields w/ None are skipped (not written as null)") {
95-
pending
96-
}
97-
9894
test("security") {
99-
pending
95+
val conf = new SparkConf()
96+
.set("spark.history.fs.logDirectory", logDir.getAbsolutePath)
97+
.set("spark.history.fs.updateInterval", "0")
98+
.set("spark.acls.enable", "true")
99+
.set("spark.ui.view.acls", "user1")
100+
val securityManager = new SecurityManager(conf)
101+
102+
val securePort = port + 1
103+
val secureServer = new HistoryServer(conf, provider, securityManager, securePort)
104+
secureServer.initialize()
105+
secureServer.bind()
106+
107+
securityManager.checkUIViewPermissions("user1") should be (true)
108+
securityManager.checkUIViewPermissions("user2") should be (false)
109+
110+
try {
111+
112+
//TODO figure out a way to authenticate as the users in the requests
113+
// getContentAndCode("applications", securePort)._1 should be (200)
114+
pending
115+
116+
} finally {
117+
secureServer.stop()
118+
}
119+
100120
}
101121

102122
test("response codes on bad paths") {
@@ -117,7 +137,7 @@ class HistoryServerSuite extends FunSuite with BeforeAndAfter with Matchers {
117137

118138
}
119139

120-
def getContentAndCode(path: String): (Int, Option[String], Option[String]) = {
140+
def getContentAndCode(path: String, port: Int = port): (Int, Option[String], Option[String]) = {
121141
val url = new URL(s"http://localhost:$port/json/v1/$path")
122142
val connection = url.openConnection().asInstanceOf[HttpURLConnection]
123143
connection.setRequestMethod("GET")

0 commit comments

Comments
 (0)