Spark SQL 权限控制

背景

Spark如今已经是我们最常用的计算和查询引擎之一,但是很遗憾的是社区版的Spark本身没有任何权限控制手段(据说Spark的Thrift server服务支持create/drop权限控制,但我并未试过,而且只有这种层次的控制并不够),我们当然可以在应用层通过业务功能来从平台上控制用户的权限,但随着业务进化,从中间件层次对Spark做出和hive一样的权限控制也十分重要。由于目前还没有很成熟方案,下面是我的一些探索,给有同样需求的同学一些思路。

1. 通过控制hive权限来控制Spark权限

我们平时使用spark大多数是通过hive元数据来获取数仓中的库表信息,那么我们是否可以通过控制hive的权限来间接控制spark呢?

先说结论,不可行。下边是尝试过程。

首先我们通过root用户,在数仓的db_test1数据库下创建了一张test1表,并插入了简单的数据。结构很简单:
在这里插入图片描述
下面我们使用root用户通过hive cli查询:
在这里插入图片描述
可以正常查出数据,没有问题,接下来我们切换非同组的另一不相关的admin用户,同样通过hive cli来查询这张表:
在这里插入图片描述
提示我们没有查询权限(hive权限控制启用方法此处不再赘述),可见hive权限控制是成功的,下面我们仍然使用admin用户,进入spark-sql cli查询:
在这里插入图片描述
Spark仍然成功查到了数据,可见hive的权限控制是不会影响Spark查询的,猜测hive应该是在执行SQL的时候,通过HiveParser解析SQL时去进行权限控制的,而Spark只是使用了hive的元数据信息,所以不会受到权限影响。

2.改造Spark源码控制SQL权限

在hive权限不会影响spark权限的情况下,如果我们想要和hive共享同一套权限怎么办呢?我们可以尝试去改造Spark源码并重新编译,思路是通过Aspect拦截Spark的方法,实现权限控制。
下载Spark源码后,我们在sql module下新增一个aspect package,新建了一个名为SparkSqlAspect的Scala类
在这里插入图片描述
下面是我添加的代码:

@Aspect
class SparkSqlAspect {
  val execution = "execution(public org.apache.spark.sql.Dataset<org.apache.spark.sql.Row> " +
    "org.apache.spark.sql.SparkSession.sql(java.lang.String)) && args(sqlRaw)"
  @Around(execution)
  def around(pjp: ProceedingJoinPoint, sqlRaw: String): Dataset[Row] = {
    val sql = sqlRaw.trim
    val spark = pjp
      .getThis
      .asInstanceOf[SparkSession]
    val userName = spark
      .sparkContext
      .sparkUser
    if (accessControl(sql, userName, spark)) {
      pjp
        .proceed(pjp.getArgs)
        .asInstanceOf[Dataset[Row]]
    } else {
      throw new IllegalAccessException("Permission denied")
    }
  }

  /**
   * 权限控制
   * @param sql sql内容
   * @param userName 用户名
   * @param spark SparkSession
   * @return Boolean
   */
  def accessControl(sql: String, userName: String, spark: SparkSession): Boolean = {
    val logicalPlan = spark.sessionState.sqlParser.parsePlan(sql)
    import org.apache.spark.sql.catalyst.analysis.UnresolvedRelation
    val tableSeq = logicalPlan
      .collect { case r: UnresolvedRelation => r.tableName }
//  结合sql,tableSeq,userName进行权限控制
    
    true
  }

}

通过拦截spark.sql()等方法,来进行权限控制,比如此处可以链接到hive元数据结合校验。改造完成后,需要重新编译源码。这种方法是可行的,但是不是很推荐,因为对日后升级版本很不利,而且重新编译源码费时费力。

3.通过控制Hadoop的HDFS文件权限来控制Spark权限

HDFS文件系统本身是有rwx这种权限控制的,我们可以通过控制HDFS文件的权限来影响Spark权限,我们仍然使用1中的db_test1库测试,分别使用root和admin用户在这个库下建立两张表test1和test2。
在这里插入图片描述
可以看到默认的表权限为755,理论上我们通过Spark是可以任意查询数据的,下面我们使用root用户进入spark-sql cli,查询test2这张表:
在这里插入图片描述
成功查询出了数据,接下来我们修改test2的权限为700:
在这里插入图片描述
再次使用root用户在spark-sql cli下查询:
在这里插入图片描述
提示没有权限,可见权限控制成功了。
那么这个权限是否也会影响hive呢?答案是肯定的,我们使用root用户在hive cli下尝试:
在这里插入图片描述
同样的效果。主流的Sentry等框架,也是基于这个思路来进行权限控制的。
控制HDFS权限是一种更为底层的控制方式,假设root用户在HDFS上拥有了test2库的读取权限,但在hive上未赋权,那在hive中仍然是无法查询的:
在这里插入图片描述
但在Spark上是可以查询的,原因前边已经论述过了。

到此为止,看起来我们已经可以控制SparkSQL的权限了,但这种方式真的足够安全吗?

需要注意的是,Hadoop开发之初,是假定Hadoop工作在安全的集群环境下的,所以只有单纯的文件权限这种控制。实际这是比较薄弱的,并不是一种非常安全的控制方式,因为Hadoop在登陆时,获取用户名的方式是这样的:

在使用了kerberos的情况下,从javax.security.auth.kerberos.KerberosPrincipal的实例获取username。
在未使用kerberos的情况下,优先读取HADOOP_USER_NAME这个系统环境变量
如果没有读取到,则读取HADOOP_USER_NAME这个java环境变量。
如果没有读取到,则从com.sun.security.auth.NTUserPrincipal或com.sun.security.auth.UnixPrincipal的实例获取username。
如果以上尝试都失败,那么抛出异常LoginException(“Can’t find user name”)。
最终拿username构造org.apache.hadoop.security.User的实例添加到Subject中。

可见我们完全可以模拟一个用户来骗过服务器,我们写一个简单的Demo:

object StartTest {
  val logger: Logger = LoggerFactory.getLogger(StartTest.getClass)

  def main(args: Array[String]): Unit = {
    System.setProperty("HADOOP_USER_NAME", "root")

    SparkSession
      .builder()
      .appName("test")
      .enableHiveSupport()
      .master("local[*]")
      .getOrCreate()
      .sql("select * from db_test1.test1")
      .rdd
      .collect()
  }

}

还是刚才的权限环境,我们通过 System.setProperty(“HADOOP_USER_NAME”, “root”),把用户名写进JAVA环境变量中,将自己的身份伪造成root用户,这样,无论使用哪个用户执行这段程序,都可以读取到db_test1.test1的内容。

可见如果想做到更强力的权限控制,还是应该考虑结合kerberos做身份鉴别,由kerberos保证登录到集群的用户就是他所声称的用户,由HDFS权限来决定用户的权限。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值