aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/src/main/scala/org/apache/spark/SecurityManager.scala124
-rw-r--r--core/src/main/scala/org/apache/spark/deploy/history/FsHistoryProvider.scala2
-rw-r--r--core/src/main/scala/org/apache/spark/scheduler/ApplicationEventListener.scala4
-rw-r--r--core/src/main/scala/org/apache/spark/security/GroupMappingServiceProvider.scala38
-rw-r--r--core/src/main/scala/org/apache/spark/security/ShellBasedGroupsMappingProvider.scala45
-rw-r--r--core/src/main/scala/org/apache/spark/util/Utils.scala19
-rw-r--r--core/src/test/scala/org/apache/spark/SecurityManagerSuite.scala198
-rw-r--r--docs/configuration.md55
-rw-r--r--docs/monitoring.md4
-rw-r--r--docs/security.md6
-rw-r--r--yarn/src/main/scala/org/apache/spark/deploy/yarn/YarnSparkHadoopUtil.scala8
11 files changed, 468 insertions, 35 deletions
diff --git a/core/src/main/scala/org/apache/spark/SecurityManager.scala b/core/src/main/scala/org/apache/spark/SecurityManager.scala
index e8f68224d5..f72c7ded5e 100644
--- a/core/src/main/scala/org/apache/spark/SecurityManager.scala
+++ b/core/src/main/scala/org/apache/spark/SecurityManager.scala
@@ -50,17 +50,19 @@ import org.apache.spark.util.Utils
* secure the UI if it has data that other users should not be allowed to see. The javax
* servlet filter specified by the user can authenticate the user and then once the user
* is logged in, Spark can compare that user versus the view acls to make sure they are
- * authorized to view the UI. The configs 'spark.acls.enable' and 'spark.ui.view.acls'
- * control the behavior of the acls. Note that the person who started the application
- * always has view access to the UI.
+ * authorized to view the UI. The configs 'spark.acls.enable', 'spark.ui.view.acls' and
+ * 'spark.ui.view.acls.groups' control the behavior of the acls. Note that the person who
+ * started the application always has view access to the UI.
*
- * Spark has a set of modify acls (`spark.modify.acls`) that controls which users have permission
- * to modify a single application. This would include things like killing the application. By
- * default the person who started the application has modify access. For modify access through
- * the UI, you must have a filter that does authentication in place for the modify acls to work
- * properly.
+ * Spark has a set of individual and group modify acls (`spark.modify.acls`) and
+ * (`spark.modify.acls.groups`) that controls which users and groups have permission to
+ * modify a single application. This would include things like killing the application.
+ * By default the person who started the application has modify access. For modify access
+ * through the UI, you must have a filter that does authentication in place for the modify
+ * acls to work properly.
*
- * Spark also has a set of admin acls (`spark.admin.acls`) which is a set of users/administrators
+ * Spark also has a set of individual and group admin acls (`spark.admin.acls`) and
+ * (`spark.admin.acls.groups`) which is a set of users/administrators and admin groups
* who always have permission to view or modify the Spark application.
*
* Starting from version 1.3, Spark has partial support for encrypted connections with SSL.
@@ -184,6 +186,9 @@ private[spark] class SecurityManager(sparkConf: SparkConf)
import SecurityManager._
+ // allow all users/groups to have view/modify permissions
+ private val WILDCARD_ACL = "*"
+
private val authOn = sparkConf.getBoolean(SecurityManager.SPARK_AUTH_CONF, false)
// keep spark.ui.acls.enable for backwards compatibility with 1.0
private var aclsOn =
@@ -193,12 +198,20 @@ private[spark] class SecurityManager(sparkConf: SparkConf)
private var adminAcls: Set[String] =
stringToSet(sparkConf.get("spark.admin.acls", ""))
+ // admin group acls should be set before view or modify group acls
+ private var adminAclsGroups : Set[String] =
+ stringToSet(sparkConf.get("spark.admin.acls.groups", ""))
+
private var viewAcls: Set[String] = _
+ private var viewAclsGroups: Set[String] = _
+
// list of users who have permission to modify the application. This should
// apply to both UI and CLI for things like killing the application.
private var modifyAcls: Set[String] = _
+ private var modifyAclsGroups: Set[String] = _
+
// always add the current user and SPARK_USER to the viewAcls
private val defaultAclUsers = Set[String](System.getProperty("user.name", ""),
Utils.getCurrentUserName())
@@ -206,11 +219,16 @@ private[spark] class SecurityManager(sparkConf: SparkConf)
setViewAcls(defaultAclUsers, sparkConf.get("spark.ui.view.acls", ""))
setModifyAcls(defaultAclUsers, sparkConf.get("spark.modify.acls", ""))
+ setViewAclsGroups(sparkConf.get("spark.ui.view.acls.groups", ""));
+ setModifyAclsGroups(sparkConf.get("spark.modify.acls.groups", ""));
+
private val secretKey = generateSecretKey()
logInfo("SecurityManager: authentication " + (if (authOn) "enabled" else "disabled") +
"; ui acls " + (if (aclsOn) "enabled" else "disabled") +
- "; users with view permissions: " + viewAcls.toString() +
- "; users with modify permissions: " + modifyAcls.toString())
+ "; users with view permissions: " + viewAcls.toString() +
+ "; groups with view permissions: " + viewAclsGroups.toString() +
+ "; users with modify permissions: " + modifyAcls.toString() +
+ "; groups with modify permissions: " + modifyAclsGroups.toString())
// Set our own authenticator to properly negotiate user/password for HTTP connections.
// This is needed by the HTTP client fetching from the HttpServer. Put here so its
@@ -303,16 +321,33 @@ private[spark] class SecurityManager(sparkConf: SparkConf)
}
/**
+ * Admin acls groups should be set before the view or modify acls groups. If you modify the admin
+ * acls groups you should also set the view and modify acls groups again to pick up the changes.
+ */
+ def setViewAclsGroups(allowedUserGroups: String) {
+ viewAclsGroups = (adminAclsGroups ++ stringToSet(allowedUserGroups));
+ logInfo("Changing view acls groups to: " + viewAclsGroups.mkString(","))
+ }
+
+ /**
* Checking the existence of "*" is necessary as YARN can't recognize the "*" in "defaultuser,*"
*/
def getViewAcls: String = {
- if (viewAcls.contains("*")) {
- "*"
+ if (viewAcls.contains(WILDCARD_ACL)) {
+ WILDCARD_ACL
} else {
viewAcls.mkString(",")
}
}
+ def getViewAclsGroups: String = {
+ if (viewAclsGroups.contains(WILDCARD_ACL)) {
+ WILDCARD_ACL
+ } else {
+ viewAclsGroups.mkString(",")
+ }
+ }
+
/**
* Admin acls should be set before the view or modify acls. If you modify the admin
* acls you should also set the view and modify acls again to pick up the changes.
@@ -323,16 +358,33 @@ private[spark] class SecurityManager(sparkConf: SparkConf)
}
/**
+ * Admin acls groups should be set before the view or modify acls groups. If you modify the admin
+ * acls groups you should also set the view and modify acls groups again to pick up the changes.
+ */
+ def setModifyAclsGroups(allowedUserGroups: String) {
+ modifyAclsGroups = (adminAclsGroups ++ stringToSet(allowedUserGroups));
+ logInfo("Changing modify acls groups to: " + modifyAclsGroups.mkString(","))
+ }
+
+ /**
* Checking the existence of "*" is necessary as YARN can't recognize the "*" in "defaultuser,*"
*/
def getModifyAcls: String = {
- if (modifyAcls.contains("*")) {
- "*"
+ if (modifyAcls.contains(WILDCARD_ACL)) {
+ WILDCARD_ACL
} else {
modifyAcls.mkString(",")
}
}
+ def getModifyAclsGroups: String = {
+ if (modifyAclsGroups.contains(WILDCARD_ACL)) {
+ WILDCARD_ACL
+ } else {
+ modifyAclsGroups.mkString(",")
+ }
+ }
+
/**
* Admin acls should be set before the view or modify acls. If you modify the admin
* acls you should also set the view and modify acls again to pick up the changes.
@@ -342,6 +394,15 @@ private[spark] class SecurityManager(sparkConf: SparkConf)
logInfo("Changing admin acls to: " + adminAcls.mkString(","))
}
+ /**
+ * Admin acls groups should be set before the view or modify acls groups. If you modify the admin
+ * acls groups you should also set the view and modify acls groups again to pick up the changes.
+ */
+ def setAdminAclsGroups(adminUserGroups: String) {
+ adminAclsGroups = stringToSet(adminUserGroups)
+ logInfo("Changing admin acls groups to: " + adminAclsGroups.mkString(","))
+ }
+
def setAcls(aclSetting: Boolean) {
aclsOn = aclSetting
logInfo("Changing acls enabled to: " + aclsOn)
@@ -398,36 +459,49 @@ private[spark] class SecurityManager(sparkConf: SparkConf)
def aclsEnabled(): Boolean = aclsOn
/**
- * Checks the given user against the view acl list to see if they have
+ * Checks the given user against the view acl and groups list to see if they have
* authorization to view the UI. If the UI acls are disabled
* via spark.acls.enable, all users have view access. If the user is null
- * it is assumed authentication is off and all users have access.
+ * it is assumed authentication is off and all users have access. Also if any one of the
+ * UI acls or groups specify the WILDCARD(*) then all users have view access.
*
* @param user to see if is authorized
* @return true is the user has permission, otherwise false
*/
def checkUIViewPermissions(user: String): Boolean = {
logDebug("user=" + user + " aclsEnabled=" + aclsEnabled() + " viewAcls=" +
- viewAcls.mkString(","))
- !aclsEnabled || user == null || viewAcls.contains(user) || viewAcls.contains("*")
+ viewAcls.mkString(",") + " viewAclsGroups=" + viewAclsGroups.mkString(","))
+ if (!aclsEnabled || user == null || viewAcls.contains(user) ||
+ viewAcls.contains(WILDCARD_ACL) || viewAclsGroups.contains(WILDCARD_ACL)) {
+ return true
+ }
+ val currentUserGroups = Utils.getCurrentUserGroups(sparkConf, user)
+ logDebug("userGroups=" + currentUserGroups.mkString(","))
+ viewAclsGroups.exists(currentUserGroups.contains(_))
}
/**
- * Checks the given user against the modify acl list to see if they have
- * authorization to modify the application. If the UI acls are disabled
+ * Checks the given user against the modify acl and groups list to see if they have
+ * authorization to modify the application. If the modify acls are disabled
* via spark.acls.enable, all users have modify access. If the user is null
- * it is assumed authentication isn't turned on and all users have access.
+ * it is assumed authentication isn't turned on and all users have access. Also if any one
+ * of the modify acls or groups specify the WILDCARD(*) then all users have modify access.
*
* @param user to see if is authorized
* @return true is the user has permission, otherwise false
*/
def checkModifyPermissions(user: String): Boolean = {
logDebug("user=" + user + " aclsEnabled=" + aclsEnabled() + " modifyAcls=" +
- modifyAcls.mkString(","))
- !aclsEnabled || user == null || modifyAcls.contains(user) || modifyAcls.contains("*")
+ modifyAcls.mkString(",") + " modifyAclsGroups=" + modifyAclsGroups.mkString(","))
+ if (!aclsEnabled || user == null || modifyAcls.contains(user) ||
+ modifyAcls.contains(WILDCARD_ACL) || modifyAclsGroups.contains(WILDCARD_ACL)) {
+ return true
+ }
+ val currentUserGroups = Utils.getCurrentUserGroups(sparkConf, user)
+ logDebug("userGroups=" + currentUserGroups)
+ modifyAclsGroups.exists(currentUserGroups.contains(_))
}
-
/**
* Check to see if authentication for the Spark communication protocols is enabled
* @return true if authentication is enabled, otherwise false
diff --git a/core/src/main/scala/org/apache/spark/deploy/history/FsHistoryProvider.scala b/core/src/main/scala/org/apache/spark/deploy/history/FsHistoryProvider.scala
index 07cbcec8e5..110d882f05 100644
--- a/core/src/main/scala/org/apache/spark/deploy/history/FsHistoryProvider.scala
+++ b/core/src/main/scala/org/apache/spark/deploy/history/FsHistoryProvider.scala
@@ -245,6 +245,8 @@ private[history] class FsHistoryProvider(conf: SparkConf, clock: Clock)
ui.getSecurityManager.setAdminAcls(appListener.adminAcls.getOrElse(""))
ui.getSecurityManager.setViewAcls(attempt.sparkUser,
appListener.viewAcls.getOrElse(""))
+ ui.getSecurityManager.setAdminAclsGroups(appListener.adminAclsGroups.getOrElse(""))
+ ui.getSecurityManager.setViewAclsGroups(appListener.viewAclsGroups.getOrElse(""))
LoadedAppUI(ui, updateProbe(appId, attemptId, attempt.fileSize))
}
}
diff --git a/core/src/main/scala/org/apache/spark/scheduler/ApplicationEventListener.scala b/core/src/main/scala/org/apache/spark/scheduler/ApplicationEventListener.scala
index 9f218c64ca..28c45d800e 100644
--- a/core/src/main/scala/org/apache/spark/scheduler/ApplicationEventListener.scala
+++ b/core/src/main/scala/org/apache/spark/scheduler/ApplicationEventListener.scala
@@ -32,6 +32,8 @@ private[spark] class ApplicationEventListener extends SparkListener {
var endTime: Option[Long] = None
var viewAcls: Option[String] = None
var adminAcls: Option[String] = None
+ var viewAclsGroups: Option[String] = None
+ var adminAclsGroups: Option[String] = None
override def onApplicationStart(applicationStart: SparkListenerApplicationStart) {
appName = Some(applicationStart.appName)
@@ -51,6 +53,8 @@ private[spark] class ApplicationEventListener extends SparkListener {
val allProperties = environmentDetails("Spark Properties").toMap
viewAcls = allProperties.get("spark.ui.view.acls")
adminAcls = allProperties.get("spark.admin.acls")
+ viewAclsGroups = allProperties.get("spark.ui.view.acls.groups")
+ adminAclsGroups = allProperties.get("spark.admin.acls.groups")
}
}
}
diff --git a/core/src/main/scala/org/apache/spark/security/GroupMappingServiceProvider.scala b/core/src/main/scala/org/apache/spark/security/GroupMappingServiceProvider.scala
new file mode 100644
index 0000000000..ea047a4f75
--- /dev/null
+++ b/core/src/main/scala/org/apache/spark/security/GroupMappingServiceProvider.scala
@@ -0,0 +1,38 @@
+/*
+ * 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.spark.security
+
+/**
+ * This Spark trait is used for mapping a given userName to a set of groups which it belongs to.
+ * This is useful for specifying a common group of admins/developers to provide them admin, modify
+ * and/or view access rights. Based on whether access control checks are enabled using
+ * spark.acls.enable, every time a user tries to access or modify the application, the
+ * SecurityManager gets the corresponding groups a user belongs to from the instance of the groups
+ * mapping provider specified by the entry spark.user.groups.mapping.
+ */
+
+trait GroupMappingServiceProvider {
+
+ /**
+ * Get the groups the user belongs to.
+ * @param userName User's Name
+ * @return set of groups that the user belongs to. Empty in case of an invalid user.
+ */
+ def getGroups(userName : String) : Set[String]
+
+}
diff --git a/core/src/main/scala/org/apache/spark/security/ShellBasedGroupsMappingProvider.scala b/core/src/main/scala/org/apache/spark/security/ShellBasedGroupsMappingProvider.scala
new file mode 100644
index 0000000000..f71dd08246
--- /dev/null
+++ b/core/src/main/scala/org/apache/spark/security/ShellBasedGroupsMappingProvider.scala
@@ -0,0 +1,45 @@
+/*
+ * 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.spark.security
+
+import org.apache.spark.internal.Logging
+import org.apache.spark.util.Utils
+
+/**
+ * This class is responsible for getting the groups for a particular user in Unix based
+ * environments. This implementation uses the Unix Shell based id command to fetch the user groups
+ * for the specified user. It does not cache the user groups as the invocations are expected
+ * to be infrequent.
+ */
+
+private[spark] class ShellBasedGroupsMappingProvider extends GroupMappingServiceProvider
+ with Logging {
+
+ override def getGroups(username: String): Set[String] = {
+ val userGroups = getUnixGroups(username)
+ logDebug("User: " + username + " Groups: " + userGroups.mkString(","))
+ userGroups
+ }
+
+ // shells out a "bash -c id -Gn username" to get user groups
+ private def getUnixGroups(username: String): Set[String] = {
+ val cmdSeq = Seq("bash", "-c", "id -Gn " + username)
+ // we need to get rid of the trailing "\n" from the result of command execution
+ Utils.executeAndGetOutput(cmdSeq).stripLineEnd.split(" ").toSet
+ }
+}
diff --git a/core/src/main/scala/org/apache/spark/util/Utils.scala b/core/src/main/scala/org/apache/spark/util/Utils.scala
index ea49991493..a8bb0002a7 100644
--- a/core/src/main/scala/org/apache/spark/util/Utils.scala
+++ b/core/src/main/scala/org/apache/spark/util/Utils.scala
@@ -2181,6 +2181,25 @@ private[spark] object Utils extends Logging {
.getOrElse(UserGroupInformation.getCurrentUser().getShortUserName())
}
+ val EMPTY_USER_GROUPS = Set[String]()
+
+ // Returns the groups to which the current user belongs.
+ def getCurrentUserGroups(sparkConf: SparkConf, username: String): Set[String] = {
+ val groupProviderClassName = sparkConf.get("spark.user.groups.mapping",
+ "org.apache.spark.security.ShellBasedGroupsMappingProvider")
+ if (groupProviderClassName != "") {
+ try {
+ val groupMappingServiceProvider = classForName(groupProviderClassName).newInstance.
+ asInstanceOf[org.apache.spark.security.GroupMappingServiceProvider]
+ val currentUserGroups = groupMappingServiceProvider.getGroups(username)
+ return currentUserGroups
+ } catch {
+ case e: Exception => logError(s"Error getting groups for user=$username", e)
+ }
+ }
+ EMPTY_USER_GROUPS
+ }
+
/**
* Split the comma delimited string of master URLs into a list.
* For instance, "spark://abc,def" becomes [spark://abc, spark://def].
diff --git a/core/src/test/scala/org/apache/spark/SecurityManagerSuite.scala b/core/src/test/scala/org/apache/spark/SecurityManagerSuite.scala
index 8bdb237c28..9801b2638c 100644
--- a/core/src/test/scala/org/apache/spark/SecurityManagerSuite.scala
+++ b/core/src/test/scala/org/apache/spark/SecurityManagerSuite.scala
@@ -19,8 +19,18 @@ package org.apache.spark
import java.io.File
+import org.apache.spark.security.GroupMappingServiceProvider
import org.apache.spark.util.{ResetSystemProperties, SparkConfWithEnv, Utils}
+class DummyGroupMappingServiceProvider extends GroupMappingServiceProvider {
+
+ val userGroups: Set[String] = Set[String]("group1", "group2", "group3")
+
+ override def getGroups(username: String): Set[String] = {
+ userGroups
+ }
+}
+
class SecurityManagerSuite extends SparkFunSuite with ResetSystemProperties {
test("set security with conf") {
@@ -37,6 +47,45 @@ class SecurityManagerSuite extends SparkFunSuite with ResetSystemProperties {
assert(securityManager.checkUIViewPermissions("user3") === false)
}
+ test("set security with conf for groups") {
+ val conf = new SparkConf
+ conf.set("spark.authenticate", "true")
+ conf.set("spark.authenticate.secret", "good")
+ conf.set("spark.ui.acls.enable", "true")
+ conf.set("spark.ui.view.acls.groups", "group1,group2")
+ // default ShellBasedGroupsMappingProvider is used to resolve user groups
+ val securityManager = new SecurityManager(conf);
+ // assuming executing user does not belong to group1,group2
+ assert(securityManager.checkUIViewPermissions("user1") === false)
+ assert(securityManager.checkUIViewPermissions("user2") === false)
+
+ val conf2 = new SparkConf
+ conf2.set("spark.authenticate", "true")
+ conf2.set("spark.authenticate.secret", "good")
+ conf2.set("spark.ui.acls.enable", "true")
+ conf2.set("spark.ui.view.acls.groups", "group1,group2")
+ // explicitly specify a custom GroupsMappingServiceProvider
+ conf2.set("spark.user.groups.mapping", "org.apache.spark.DummyGroupMappingServiceProvider")
+
+ val securityManager2 = new SecurityManager(conf2);
+ // group4,group5 do not match
+ assert(securityManager2.checkUIViewPermissions("user1") === true)
+ assert(securityManager2.checkUIViewPermissions("user2") === true)
+
+ val conf3 = new SparkConf
+ conf3.set("spark.authenticate", "true")
+ conf3.set("spark.authenticate.secret", "good")
+ conf3.set("spark.ui.acls.enable", "true")
+ conf3.set("spark.ui.view.acls.groups", "group4,group5")
+ // explicitly specify a bogus GroupsMappingServiceProvider
+ conf3.set("spark.user.groups.mapping", "BogusServiceProvider")
+
+ val securityManager3 = new SecurityManager(conf3);
+ // BogusServiceProvider cannot be loaded and an error is logged returning an empty group set
+ assert(securityManager3.checkUIViewPermissions("user1") === false)
+ assert(securityManager3.checkUIViewPermissions("user2") === false)
+ }
+
test("set security with api") {
val conf = new SparkConf
conf.set("spark.ui.view.acls", "user1,user2")
@@ -60,6 +109,40 @@ class SecurityManagerSuite extends SparkFunSuite with ResetSystemProperties {
assert(securityManager.checkUIViewPermissions(null) === true)
}
+ test("set security with api for groups") {
+ val conf = new SparkConf
+ conf.set("spark.user.groups.mapping", "org.apache.spark.DummyGroupMappingServiceProvider")
+
+ val securityManager = new SecurityManager(conf);
+ securityManager.setAcls(true)
+ securityManager.setViewAclsGroups("group1,group2")
+
+ // group1,group2 match
+ assert(securityManager.checkUIViewPermissions("user1") === true)
+ assert(securityManager.checkUIViewPermissions("user2") === true)
+
+ // change groups so they do not match
+ securityManager.setViewAclsGroups("group4,group5")
+ assert(securityManager.checkUIViewPermissions("user1") === false)
+ assert(securityManager.checkUIViewPermissions("user2") === false)
+
+ val conf2 = new SparkConf
+ conf.set("spark.user.groups.mapping", "BogusServiceProvider")
+
+ val securityManager2 = new SecurityManager(conf2)
+ securityManager2.setAcls(true)
+ securityManager2.setViewAclsGroups("group1,group2")
+
+ // group1,group2 do not match because of BogusServiceProvider
+ assert(securityManager.checkUIViewPermissions("user1") === false)
+ assert(securityManager.checkUIViewPermissions("user2") === false)
+
+ // setting viewAclsGroups to empty should still not match because of BogusServiceProvider
+ securityManager2.setViewAclsGroups("")
+ assert(securityManager.checkUIViewPermissions("user1") === false)
+ assert(securityManager.checkUIViewPermissions("user2") === false)
+ }
+
test("set security modify acls") {
val conf = new SparkConf
conf.set("spark.modify.acls", "user1,user2")
@@ -84,6 +167,29 @@ class SecurityManagerSuite extends SparkFunSuite with ResetSystemProperties {
assert(securityManager.checkModifyPermissions(null) === true)
}
+ test("set security modify acls for groups") {
+ val conf = new SparkConf
+ conf.set("spark.user.groups.mapping", "org.apache.spark.DummyGroupMappingServiceProvider")
+
+ val securityManager = new SecurityManager(conf);
+ securityManager.setAcls(true)
+ securityManager.setModifyAclsGroups("group1,group2")
+
+ // group1,group2 match
+ assert(securityManager.checkModifyPermissions("user1") === true)
+ assert(securityManager.checkModifyPermissions("user2") === true)
+
+ // change groups so they do not match
+ securityManager.setModifyAclsGroups("group4,group5")
+ assert(securityManager.checkModifyPermissions("user1") === false)
+ assert(securityManager.checkModifyPermissions("user2") === false)
+
+ // change so they match again
+ securityManager.setModifyAclsGroups("group2,group3")
+ assert(securityManager.checkModifyPermissions("user1") === true)
+ assert(securityManager.checkModifyPermissions("user2") === true)
+ }
+
test("set security admin acls") {
val conf = new SparkConf
conf.set("spark.admin.acls", "user1,user2")
@@ -122,7 +228,48 @@ class SecurityManagerSuite extends SparkFunSuite with ResetSystemProperties {
assert(securityManager.checkUIViewPermissions("user1") === false)
assert(securityManager.checkUIViewPermissions("user3") === false)
assert(securityManager.checkUIViewPermissions(null) === true)
+ }
+
+ test("set security admin acls for groups") {
+ val conf = new SparkConf
+ conf.set("spark.admin.acls.groups", "group1")
+ conf.set("spark.ui.view.acls.groups", "group2")
+ conf.set("spark.modify.acls.groups", "group3")
+ conf.set("spark.user.groups.mapping", "org.apache.spark.DummyGroupMappingServiceProvider")
+
+ val securityManager = new SecurityManager(conf);
+ securityManager.setAcls(true)
+ assert(securityManager.aclsEnabled() === true)
+
+ // group1,group2,group3 match
+ assert(securityManager.checkModifyPermissions("user1") === true)
+ assert(securityManager.checkUIViewPermissions("user1") === true)
+ // change admin groups so they do not match. view and modify groups are set to admin groups
+ securityManager.setAdminAclsGroups("group4,group5")
+ // invoke the set ui and modify to propagate the changes
+ securityManager.setViewAclsGroups("")
+ securityManager.setModifyAclsGroups("")
+
+ assert(securityManager.checkModifyPermissions("user1") === false)
+ assert(securityManager.checkUIViewPermissions("user1") === false)
+
+ // change modify groups so they match
+ securityManager.setModifyAclsGroups("group3")
+ assert(securityManager.checkModifyPermissions("user1") === true)
+ assert(securityManager.checkUIViewPermissions("user1") === false)
+
+ // change view groups so they match
+ securityManager.setViewAclsGroups("group2")
+ securityManager.setModifyAclsGroups("group4")
+ assert(securityManager.checkModifyPermissions("user1") === false)
+ assert(securityManager.checkUIViewPermissions("user1") === true)
+
+ // change modify and view groups so they do not match
+ securityManager.setViewAclsGroups("group7")
+ securityManager.setModifyAclsGroups("group8")
+ assert(securityManager.checkModifyPermissions("user1") === false)
+ assert(securityManager.checkUIViewPermissions("user1") === false)
}
test("set security with * in acls") {
@@ -166,6 +313,57 @@ class SecurityManagerSuite extends SparkFunSuite with ResetSystemProperties {
assert(securityManager.checkModifyPermissions("user8") === true)
}
+ test("set security with * in acls for groups") {
+ val conf = new SparkConf
+ conf.set("spark.ui.acls.enable", "true")
+ conf.set("spark.admin.acls.groups", "group4,group5")
+ conf.set("spark.ui.view.acls.groups", "*")
+ conf.set("spark.modify.acls.groups", "group6")
+
+ val securityManager = new SecurityManager(conf)
+ assert(securityManager.aclsEnabled() === true)
+
+ // check for viewAclsGroups with *
+ assert(securityManager.checkUIViewPermissions("user1") === true)
+ assert(securityManager.checkUIViewPermissions("user2") === true)
+ assert(securityManager.checkModifyPermissions("user1") === false)
+ assert(securityManager.checkModifyPermissions("user2") === false)
+
+ // check for modifyAcls with *
+ securityManager.setModifyAclsGroups("*")
+ securityManager.setViewAclsGroups("group6")
+ assert(securityManager.checkUIViewPermissions("user1") === false)
+ assert(securityManager.checkUIViewPermissions("user2") === false)
+ assert(securityManager.checkModifyPermissions("user1") === true)
+ assert(securityManager.checkModifyPermissions("user2") === true)
+
+ // check for adminAcls with *
+ securityManager.setAdminAclsGroups("group9,*")
+ securityManager.setModifyAclsGroups("group4,group5")
+ securityManager.setViewAclsGroups("group6,group7")
+ assert(securityManager.checkUIViewPermissions("user5") === true)
+ assert(securityManager.checkUIViewPermissions("user6") === true)
+ assert(securityManager.checkModifyPermissions("user7") === true)
+ assert(securityManager.checkModifyPermissions("user8") === true)
+ }
+
+ test("security for groups default behavior") {
+ // no groups or userToGroupsMapper provided
+ // this will default to the ShellBasedGroupsMappingProvider
+ val conf = new SparkConf
+
+ val securityManager = new SecurityManager(conf)
+ securityManager.setAcls(true)
+
+ assert(securityManager.checkUIViewPermissions("user1") === false)
+ assert(securityManager.checkModifyPermissions("user1") === false)
+
+ // set groups only
+ securityManager.setAdminAclsGroups("group1,group2")
+ assert(securityManager.checkUIViewPermissions("user1") === false)
+ assert(securityManager.checkModifyPermissions("user1") === false)
+ }
+
test("ssl on setup") {
val conf = SSLSampleConfigs.sparkSSLConfig()
val expectedAlgorithms = Set(
diff --git a/docs/configuration.md b/docs/configuration.md
index 6512e16faf..9191570d07 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -1231,7 +1231,7 @@ Apart from these, the following properties are also available, and may be useful
<td><code>spark.acls.enable</code></td>
<td>false</td>
<td>
- Whether Spark acls should are enabled. If enabled, this checks to see if the user has
+ Whether Spark acls should be enabled. If enabled, this checks to see if the user has
access permissions to view or modify the job. Note this requires the user to be known,
so if the user comes across as null no checks are done. Filters can be used with the UI
to authenticate and set the user.
@@ -1243,8 +1243,33 @@ Apart from these, the following properties are also available, and may be useful
<td>
Comma separated list of users/administrators that have view and modify access to all Spark jobs.
This can be used if you run on a shared cluster and have a set of administrators or devs who
- help debug when things work. Putting a "*" in the list means any user can have the privilege
- of admin.
+ help debug when things do not work. Putting a "*" in the list means any user can have the
+ privilege of admin.
+ </td>
+</tr>
+<tr>
+ <td><code>spark.admin.acls.groups</code></td>
+ <td>Empty</td>
+ <td>
+ Comma separated list of groups that have view and modify access to all Spark jobs.
+ This can be used if you have a set of administrators or developers who help maintain and debug
+ the underlying infrastructure. Putting a "*" in the list means any user in any group can have
+ the privilege of admin. The user groups are obtained from the instance of the groups mapping
+ provider specified by <code>spark.user.groups.mapping</code>. Check the entry
+ <code>spark.user.groups.mapping</code> for more details.
+ </td>
+</tr>
+<tr>
+ <td><code>spark.user.groups.mapping</code></td>
+ <td><code>org.apache.spark.security.ShellBasedGroupsMappingProvider</code></td>
+ <td>
+ The list of groups for a user are determined by a group mapping service defined by the trait
+ org.apache.spark.security.GroupMappingServiceProvider which can configured by this property.
+ A default unix shell based implementation is provided <code>org.apache.spark.security.ShellBasedGroupsMappingProvider</code>
+ which can be specified to resolve a list of groups for a user.
+ <em>Note:</em> This implementation supports only a Unix/Linux based environment. Windows environment is
+ currently <b>not</b> supported. However, a new platform/protocol can be supported by implementing
+ the trait <code>org.apache.spark.security.GroupMappingServiceProvider</code>.
</td>
</tr>
<tr>
@@ -1306,6 +1331,18 @@ Apart from these, the following properties are also available, and may be useful
</td>
</tr>
<tr>
+ <td><code>spark.modify.acls.groups</code></td>
+ <td>Empty</td>
+ <td>
+ Comma separated list of groups that have modify access to the Spark job. This can be used if you
+ have a set of administrators or developers from the same team to have access to control the job.
+ Putting a "*" in the list means any user in any group has the access to modify the Spark job.
+ The user groups are obtained from the instance of the groups mapping provider specified by
+ <code>spark.user.groups.mapping</code>. Check the entry <code>spark.user.groups.mapping</code>
+ for more details.
+ </td>
+</tr>
+<tr>
<td><code>spark.ui.filters</code></td>
<td>None</td>
<td>
@@ -1328,6 +1365,18 @@ Apart from these, the following properties are also available, and may be useful
have view access to this Spark job.
</td>
</tr>
+<tr>
+ <td><code>spark.ui.view.acls.groups</code></td>
+ <td>Empty</td>
+ <td>
+ Comma separated list of groups that have view access to the Spark web ui to view the Spark Job
+ details. This can be used if you have a set of administrators or developers or users who can
+ monitor the Spark job submitted. Putting a "*" in the list means any user in any group can view
+ the Spark job details on the Spark web ui. The user groups are obtained from the instance of the
+ groups mapping provider specified by <code>spark.user.groups.mapping</code>. Check the entry
+ <code>spark.user.groups.mapping</code> for more details.
+ </td>
+</tr>
</table>
#### Encryption
diff --git a/docs/monitoring.md b/docs/monitoring.md
index 88002ebdc3..697962ae3a 100644
--- a/docs/monitoring.md
+++ b/docs/monitoring.md
@@ -162,8 +162,8 @@ The history server can be configured as follows:
If enabled, access control checks are made regardless of what the individual application had
set for <code>spark.ui.acls.enable</code> when the application was run. The application owner
will always have authorization to view their own application and any users specified via
- <code>spark.ui.view.acls</code> when the application was run will also have authorization
- to view that application.
+ <code>spark.ui.view.acls</code> and groups specified via <code>spark.ui.view.acls.groups<code>
+ when the application was run will also have authorization to view that application.
If disabled, no access control checks are made.
</td>
</tr>
diff --git a/docs/security.md b/docs/security.md
index 32c33d2857..d2708a8070 100644
--- a/docs/security.md
+++ b/docs/security.md
@@ -16,10 +16,10 @@ and by using [https/SSL](http://en.wikipedia.org/wiki/HTTPS) via the `spark.ui.h
### Authentication
-A user may want to secure the UI if it has data that other users should not be allowed to see. The javax servlet filter specified by the user can authenticate the user and then once the user is logged in, Spark can compare that user versus the view ACLs to make sure they are authorized to view the UI. The configs `spark.acls.enable` and `spark.ui.view.acls` control the behavior of the ACLs. Note that the user who started the application always has view access to the UI. On YARN, the Spark UI uses the standard YARN web application proxy mechanism and will authenticate via any installed Hadoop filters.
+A user may want to secure the UI if it has data that other users should not be allowed to see. The javax servlet filter specified by the user can authenticate the user and then once the user is logged in, Spark can compare that user versus the view ACLs to make sure they are authorized to view the UI. The configs `spark.acls.enable`, `spark.ui.view.acls` and `spark.ui.view.acls.groups` control the behavior of the ACLs. Note that the user who started the application always has view access to the UI. On YARN, the Spark UI uses the standard YARN web application proxy mechanism and will authenticate via any installed Hadoop filters.
-Spark also supports modify ACLs to control who has access to modify a running Spark application. This includes things like killing the application or a task. This is controlled by the configs `spark.acls.enable` and `spark.modify.acls`. Note that if you are authenticating the web UI, in order to use the kill button on the web UI it might be necessary to add the users in the modify acls to the view acls also. On YARN, the modify acls are passed in and control who has modify access via YARN interfaces.
-Spark allows for a set of administrators to be specified in the acls who always have view and modify permissions to all the applications. is controlled by the config `spark.admin.acls`. This is useful on a shared cluster where you might have administrators or support staff who help users debug applications.
+Spark also supports modify ACLs to control who has access to modify a running Spark application. This includes things like killing the application or a task. This is controlled by the configs `spark.acls.enable`, `spark.modify.acls` and `spark.modify.acls.groups`. Note that if you are authenticating the web UI, in order to use the kill button on the web UI it might be necessary to add the users in the modify acls to the view acls also. On YARN, the modify acls are passed in and control who has modify access via YARN interfaces.
+Spark allows for a set of administrators to be specified in the acls who always have view and modify permissions to all the applications. is controlled by the configs `spark.admin.acls` and `spark.admin.acls.groups`. This is useful on a shared cluster where you might have administrators or support staff who help users debug applications.
## Event Logging
diff --git a/yarn/src/main/scala/org/apache/spark/deploy/yarn/YarnSparkHadoopUtil.scala b/yarn/src/main/scala/org/apache/spark/deploy/yarn/YarnSparkHadoopUtil.scala
index ee002f6223..44181610d7 100644
--- a/yarn/src/main/scala/org/apache/spark/deploy/yarn/YarnSparkHadoopUtil.scala
+++ b/yarn/src/main/scala/org/apache/spark/deploy/yarn/YarnSparkHadoopUtil.scala
@@ -464,11 +464,15 @@ object YarnSparkHadoopUtil {
}
}
+ // YARN/Hadoop acls are specified as user1,user2 group1,group2
+ // Users and groups are separated by a space and hence we need to pass the acls in same format
def getApplicationAclsForYarn(securityMgr: SecurityManager)
: Map[ApplicationAccessType, String] = {
Map[ApplicationAccessType, String] (
- ApplicationAccessType.VIEW_APP -> securityMgr.getViewAcls,
- ApplicationAccessType.MODIFY_APP -> securityMgr.getModifyAcls
+ ApplicationAccessType.VIEW_APP -> (securityMgr.getViewAcls + " " +
+ securityMgr.getViewAclsGroups),
+ ApplicationAccessType.MODIFY_APP -> (securityMgr.getModifyAcls + " " +
+ securityMgr.getModifyAclsGroups)
)
}