Skip to content

Commit 92caa51

Browse files
jerryshaoThomas Graves
authored andcommitted
[SPARK-20239][CORE][2.1-BACKPORT] Improve HistoryServer's ACL mechanism
Current SHS (Spark History Server) has two different ACLs: * ACL of base URL, it is controlled by "spark.acls.enabled" or "spark.ui.acls.enabled", and with this enabled, only user configured with "spark.admin.acls" (or group) or "spark.ui.view.acls" (or group), or the user who started SHS could list all the applications, otherwise none of them can be listed. This will also affect REST APIs which listing the summary of all apps and one app. * Per application ACL. This is controlled by "spark.history.ui.acls.enabled". With this enabled only history admin user and user/group who ran this app can access the details of this app. With this two ACLs, we may encounter several unexpected behaviors: 1. if base URL's ACL (`spark.acls.enable`) is enabled but user A has no view permission. User "A" cannot see the app list but could still access details of it's own app. 2. if ACLs of base URL (`spark.acls.enable`) is disabled, then user "A" could download any application's event log, even it is not run by user "A". 3. The changes of Live UI's ACL will affect History UI's ACL which share the same conf file. The unexpected behaviors is mainly because we have two different ACLs, ideally we should have only one to manage all. So to improve SHS's ACL mechanism, here in this PR proposed to: 1. Disable "spark.acls.enable" and only use "spark.history.ui.acls.enable" for history server. 2. Check permission for event-log download REST API. With this PR: 1. Admin user could see/download the list of all applications, as well as application details. 2. Normal user could see the list of all applications, but can only download and check the details of applications accessible to him. New UTs are added, also verified in real cluster. CC tgravescs vanzin please help to review, this PR changes the semantics you did previously. Thanks a lot. Author: jerryshao <[email protected]> Closes apache#17755 from jerryshao/SPARK-20239-2.1-backport. (cherry picked from commit 359382c) Signed-off-by: Marcelo Vanzin <[email protected]>
1 parent e104221 commit 92caa51

File tree

4 files changed

+43
-11
lines changed

4 files changed

+43
-11
lines changed

core/src/main/scala/org/apache/spark/deploy/history/ApplicationHistoryProvider.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ private[history] abstract class ApplicationHistoryProvider {
8686
* @return Count of application event logs that are currently under process
8787
*/
8888
def getEventLogsUnderProcess(): Int = {
89-
return 0;
89+
0
9090
}
9191

9292
/**
@@ -95,7 +95,7 @@ private[history] abstract class ApplicationHistoryProvider {
9595
* @return 0 if this is undefined or unsupported, otherwise the last updated time in millis
9696
*/
9797
def getLastUpdatedTime(): Long = {
98-
return 0;
98+
0
9999
}
100100

101101
/**

core/src/main/scala/org/apache/spark/deploy/history/HistoryServer.scala

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ object HistoryServer extends Logging {
269269
Utils.initDaemon(log)
270270
new HistoryServerArguments(conf, argStrings)
271271
initSecurity()
272-
val securityManager = new SecurityManager(conf)
272+
val securityManager = createSecurityManager(conf)
273273

274274
val providerName = conf.getOption("spark.history.provider")
275275
.getOrElse(classOf[FsHistoryProvider].getName())
@@ -289,6 +289,24 @@ object HistoryServer extends Logging {
289289
while(true) { Thread.sleep(Int.MaxValue) }
290290
}
291291

292+
/**
293+
* Create a security manager.
294+
* This turns off security in the SecurityManager, so that the History Server can start
295+
* in a Spark cluster where security is enabled.
296+
* @param config configuration for the SecurityManager constructor
297+
* @return the security manager for use in constructing the History Server.
298+
*/
299+
private[history] def createSecurityManager(config: SparkConf): SecurityManager = {
300+
if (config.getBoolean("spark.acls.enable", config.getBoolean("spark.ui.acls.enable", false))) {
301+
logInfo("Either spark.acls.enable or spark.ui.acls.enable is configured, clearing it and " +
302+
"only using spark.history.ui.acl.enable")
303+
config.set("spark.acls.enable", "false")
304+
config.set("spark.ui.acls.enable", "false")
305+
}
306+
307+
new SecurityManager(config)
308+
}
309+
292310
def initSecurity() {
293311
// If we are accessing HDFS and it has security enabled (Kerberos), we have to login
294312
// from a keytab file so that we can access HDFS beyond the kerberos ticket expiration.

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,14 +184,27 @@ private[v1] class ApiRootResource extends ApiRequestContext {
184184
@Path("applications/{appId}/logs")
185185
def getEventLogs(
186186
@PathParam("appId") appId: String): EventLogDownloadResource = {
187-
new EventLogDownloadResource(uiRoot, appId, None)
187+
try {
188+
// withSparkUI will throw NotFoundException if attemptId exists for this application.
189+
// So we need to try again with attempt id "1".
190+
withSparkUI(appId, None) { _ =>
191+
new EventLogDownloadResource(uiRoot, appId, None)
192+
}
193+
} catch {
194+
case _: NotFoundException =>
195+
withSparkUI(appId, Some("1")) { _ =>
196+
new EventLogDownloadResource(uiRoot, appId, None)
197+
}
198+
}
188199
}
189200

190201
@Path("applications/{appId}/{attemptId}/logs")
191202
def getEventLogs(
192203
@PathParam("appId") appId: String,
193204
@PathParam("attemptId") attemptId: String): EventLogDownloadResource = {
194-
new EventLogDownloadResource(uiRoot, appId, Some(attemptId))
205+
withSparkUI(appId, Some(attemptId)) { _ =>
206+
new EventLogDownloadResource(uiRoot, appId, Some(attemptId))
207+
}
195208
}
196209

197210
@Path("version")
@@ -276,7 +289,6 @@ private[v1] trait ApiRequestContext {
276289
case None => throw new NotFoundException("no such app: " + appId)
277290
}
278291
}
279-
280292
}
281293

282294
private[v1] class ForbiddenException(msg: String) extends WebApplicationException(

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -547,12 +547,11 @@ class HistoryServerSuite extends SparkFunSuite with BeforeAndAfter with Matchers
547547
assert(jobcount === getNumJobs("/jobs"))
548548

549549
// no need to retain the test dir now the tests complete
550-
logDir.deleteOnExit();
551-
550+
logDir.deleteOnExit()
552551
}
553552

554553
test("ui and api authorization checks") {
555-
val appId = "local-1422981759269"
554+
val appId = "local-1430917381535"
556555
val owner = "irashid"
557556
val admin = "root"
558557
val other = "alice"
@@ -572,8 +571,11 @@ class HistoryServerSuite extends SparkFunSuite with BeforeAndAfter with Matchers
572571

573572
val port = server.boundPort
574573
val testUrls = Seq(
575-
s"http://localhost:$port/api/v1/applications/$appId/jobs",
576-
s"http://localhost:$port/history/$appId/jobs/")
574+
s"http://localhost:$port/api/v1/applications/$appId/1/jobs",
575+
s"http://localhost:$port/history/$appId/1/jobs/",
576+
s"http://localhost:$port/api/v1/applications/$appId/logs",
577+
s"http://localhost:$port/api/v1/applications/$appId/1/logs",
578+
s"http://localhost:$port/api/v1/applications/$appId/2/logs")
577579

578580
tests.foreach { case (user, expectedCode) =>
579581
testUrls.foreach { url =>

0 commit comments

Comments
 (0)