Skip to content

Commit 068500a

Browse files
jerryshaoMarcelo Vanzin
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 <sshao@hortonworks.com> Closes #17755 from jerryshao/SPARK-20239-2.1-backport. (cherry picked from commit 359382c) Signed-off-by: Marcelo Vanzin <vanzin@cloudera.com>
1 parent ddf6dd8 commit 068500a

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
@@ -84,7 +84,7 @@ private[history] abstract class ApplicationHistoryProvider {
8484
* @return Count of application event logs that are currently under process
8585
*/
8686
def getEventLogsUnderProcess(): Int = {
87-
return 0;
87+
0
8888
}
8989

9090
/**
@@ -93,7 +93,7 @@ private[history] abstract class ApplicationHistoryProvider {
9393
* @return 0 if this is undefined or unsupported, otherwise the last updated time in millis
9494
*/
9595
def getLastUpdatedTime(): Long = {
96-
return 0;
96+
0
9797
}
9898

9999
/**

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
@@ -261,7 +261,7 @@ object HistoryServer extends Logging {
261261
Utils.initDaemon(log)
262262
new HistoryServerArguments(conf, argStrings)
263263
initSecurity()
264-
val securityManager = new SecurityManager(conf)
264+
val securityManager = createSecurityManager(conf)
265265

266266
val providerName = conf.getOption("spark.history.provider")
267267
.getOrElse(classOf[FsHistoryProvider].getName())
@@ -281,6 +281,24 @@ object HistoryServer extends Logging {
281281
while(true) { Thread.sleep(Int.MaxValue) }
282282
}
283283

284+
/**
285+
* Create a security manager.
286+
* This turns off security in the SecurityManager, so that the History Server can start
287+
* in a Spark cluster where security is enabled.
288+
* @param config configuration for the SecurityManager constructor
289+
* @return the security manager for use in constructing the History Server.
290+
*/
291+
private[history] def createSecurityManager(config: SparkConf): SecurityManager = {
292+
if (config.getBoolean("spark.acls.enable", config.getBoolean("spark.ui.acls.enable", false))) {
293+
logInfo("Either spark.acls.enable or spark.ui.acls.enable is configured, clearing it and " +
294+
"only using spark.history.ui.acl.enable")
295+
config.set("spark.acls.enable", "false")
296+
config.set("spark.ui.acls.enable", "false")
297+
}
298+
299+
new SecurityManager(config)
300+
}
301+
284302
def initSecurity() {
285303
// If we are accessing HDFS and it has security enabled (Kerberos), we have to login
286304
// 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
@@ -168,14 +168,27 @@ private[v1] class ApiRootResource extends ApiRequestContext {
168168
@Path("applications/{appId}/logs")
169169
def getEventLogs(
170170
@PathParam("appId") appId: String): EventLogDownloadResource = {
171-
new EventLogDownloadResource(uiRoot, appId, None)
171+
try {
172+
// withSparkUI will throw NotFoundException if attemptId exists for this application.
173+
// So we need to try again with attempt id "1".
174+
withSparkUI(appId, None) { _ =>
175+
new EventLogDownloadResource(uiRoot, appId, None)
176+
}
177+
} catch {
178+
case _: NotFoundException =>
179+
withSparkUI(appId, Some("1")) { _ =>
180+
new EventLogDownloadResource(uiRoot, appId, None)
181+
}
182+
}
172183
}
173184

174185
@Path("applications/{appId}/{attemptId}/logs")
175186
def getEventLogs(
176187
@PathParam("appId") appId: String,
177188
@PathParam("attemptId") attemptId: String): EventLogDownloadResource = {
178-
new EventLogDownloadResource(uiRoot, appId, Some(attemptId))
189+
withSparkUI(appId, Some(attemptId)) { _ =>
190+
new EventLogDownloadResource(uiRoot, appId, Some(attemptId))
191+
}
179192
}
180193

181194
@Path("version")
@@ -260,7 +273,6 @@ private[v1] trait ApiRequestContext {
260273
case None => throw new NotFoundException("no such app: " + appId)
261274
}
262275
}
263-
264276
}
265277

266278
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
@@ -545,12 +545,11 @@ class HistoryServerSuite extends SparkFunSuite with BeforeAndAfter with Matchers
545545
assert(jobcount === getNumJobs("/jobs"))
546546

547547
// no need to retain the test dir now the tests complete
548-
logDir.deleteOnExit();
549-
548+
logDir.deleteOnExit()
550549
}
551550

552551
test("ui and api authorization checks") {
553-
val appId = "local-1422981759269"
552+
val appId = "local-1430917381535"
554553
val owner = "irashid"
555554
val other = "alice"
556555

@@ -567,8 +566,11 @@ class HistoryServerSuite extends SparkFunSuite with BeforeAndAfter with Matchers
567566

568567
val port = server.boundPort
569568
val testUrls = Seq(
570-
s"http://localhost:$port/api/v1/applications/$appId/jobs",
571-
s"http://localhost:$port/history/$appId/jobs/")
569+
s"http://localhost:$port/api/v1/applications/$appId/1/jobs",
570+
s"http://localhost:$port/history/$appId/1/jobs/",
571+
s"http://localhost:$port/api/v1/applications/$appId/logs",
572+
s"http://localhost:$port/api/v1/applications/$appId/1/logs",
573+
s"http://localhost:$port/api/v1/applications/$appId/2/logs")
572574

573575
tests.foreach { case (user, expectedCode) =>
574576
testUrls.foreach { url =>

0 commit comments

Comments
 (0)