启用授权

Alluxio 提供了一个灵活的授权框架,以保护数据访问和管理操作。根据您的安全要求,您可以选择适当的集成:

  • 对于数据访问控制(S3 API 和 Hadoop FS):Apache Ranger 集成,以管理访问文件和目录的用户和应用程序的细粒度权限。

  • 对于管理 API 控制(网关):Open Policy Agent (OPA) 集成,以为通过 Alluxio 网关执行的管理操作强制执行复杂的、基于策略的授权。

本指南提供了配置这些集成的分步说明。

使用 Apache Ranger 进行数据访问授权

Apache Ranger 为数据访问操作提供集中式、细粒度的授权。通过将 Alluxio 与 Ranger 集成,您可以管理 S3 API 和 Hadoop 文件系统客户端的策略,确保用户和应用程序仅具有在 Alluxio 命名空间内读取、写入或管理数据所需的权限。这是控制对数据平面访问的推荐解决方案。

先决条件

在配置 Ranger 与 Alluxio 的集成之前,请确保满足以下要求:

  1. 插件 JAR 可用性:确保授权插件 jar(例如 lib/alluxio-authorization-hdfs-AI-3.7-13.0.0.jar)在所有 Alluxio 节点上都可用。

  2. Ranger HDFS 插件:验证您的 Ranger 配置中已启用 HDFS 插件。

  3. HDFS 服务配置:按照 Cloudera 的 HDFS 服务文档 中的说明为 Alluxio 配置一个新的 HDFS 服务。

配置

请按照以下步骤配置集成。

步骤 1:设置配置文件

将以下配置文件复制或创建到 Alluxio 节点上的目录中,例如 /opt/alluxio/conf/ranger/

  • ranger-hdfs-security.xml

  • ranger-hdfs-audit.xml

以下是这些文件的示例配置。 ranger-hdfs-security.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration xmlns:xi="http://www.w3.org/2001/XInclude">
	<property>
		<name>ranger.plugin.hdfs.service.name</name>
		<value>alluxio-test</value>
		<description>
			Name of the Ranger service containing policies for this YARN instance
		</description>
	</property>
    <property>
		<name>ranger.plugin.hdfs.policy.source.impl</name>
		<value>org.apache.ranger.admin.client.RangerAdminRESTClient</value>
		<description>
			Class to retrieve policies from the source
		</description>
	</property>
    <property>
		<name>ranger.plugin.hdfs.policy.rest.url</name>
		<value>http://your-ranger-ip:6080</value>
		<description>
			URL to Ranger Admin
		</description>
	</property>

	<property>
		<name>ranger.plugin.hdfs.policy.pollIntervalMs</name>
		<value>30000</value>
		<description>
			How often to poll for changes in policies?
		</description>
	</property>

	<property>
		<name>ranger.plugin.hdfs.policy.cache.dir</name>
		<value>/path/to/policycache</value>
		<description>
			Directory where Ranger policies are cached after successful retrieval from the source
		</description>
	</property>

	<property>
		<name>ranger.plugin.hdfs.policy.rest.client.connection.timeoutMs</name>
		<value>120000</value>
		<description>
			Hdfs Plugin RangerRestClient Connection Timeout in Milli Seconds
		</description>
	</property>

	<property>
		<name>ranger.plugin.hdfs.policy.rest.client.read.timeoutMs</name>
		<value>30000</value>
		<description>
			Hdfs Plugin RangerRestClient read Timeout in Milli Seconds
		</description>
	</property>

	<!--  The following fields are used to customize the audit logging feature -->

	<property>
		<name>xasecure.auditlog.xasecureAcl.name</name>
		<value>ranger-acl</value>
		<description>
			The module name listed in the auditlog when the permission check is done by RangerACL
		</description>
	</property>
	<property>
		<name>xasecure.add-hadoop-authorization</name>
		<value>false</value>
		<description>
			Enable/Disable the default hadoop authorization (based on
			rwxrwxrwx permission on the resource) if Ranger Authorization fails.
		</description>
	</property>
</configuration>

ranger-hdfs-audit.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration xmlns:xi="http://www.w3.org/2001/XInclude">
    <property>
        <name>xasecure.audit.is.enabled</name>
        <value>false</value>
    </property>
</configuration>

步骤 2:更新 Ranger 配置

更新 ranger-hdfs-security.xml 中的配置设置以使用新的 HDFS 服务:

  1. ranger.plugin.hdfs.policy.cache.dir 设置为 Alluxio 节点上您希望存储策略缓存的有效目录。

  2. ranger.plugin.hdfs.service.name 设置为新的 HDFS 服务名称,例如 alluxio-test

  3. 验证 ranger.plugin.hdfs.policy.rest.url 指向正确的 Ranger 服务 URL。

  4. xasecure.add-hadoop-authorization 设置为 false 以禁用回退到 Hadoop 授权。

步骤 3:配置 Alluxio 节点

要在 worker 上使用 Ranger 插件对 S3 API 进行授权,请将以下内容添加到 alluxio-cluster.yamlproperties 部分。

对于 S3 API:

properties:
  alluxio.worker.s3.authorization.enabled: "true"
  alluxio.worker.s3.authorizer.classname: "alluxio.s3.auth.ExtendableAuthorizer"
  alluxio.security.authorization.plugin.name: "ranger-privacera-4.7"
  alluxio.security.authorization.plugin.paths: "/opt/alluxio/conf/ranger/"
  # 或您复制 ranger 配置 xml 文件的任何位置

对于 Hadoop 文件系统: 在客户端,通过添加以下内容启用授权:

alluxio.security.authorization.plugin.name=ranger-privacera-4.7
alluxio.security.authorization.plugin.paths=/opt/alluxio/conf/ranger/
# 或您复制 ranger 配置 xml 文件的任何位置

步骤 4:应用配置

重新启动所有 Alluxio 节点以应用新配置,或在启动 Alluxio 集群之前进行配置。您现在可以在 Ranger 中向 Alluxio 服务(例如 alluxio-test)添加策略,并验证它们在 Alluxio 中生效。

重要说明

  1. 路径映射: Ranger 策略中的路径对应于 Alluxio 命名空间中的路径,而不是底层的 S3 对象。

  2. 权限层次结构:

    • 读取文件需要对文件本身的 READ 权限。

    • 写入或创建文件需要对父目录的 WRITE 权限。

    • 列出目录内容需要对该目录的 EXECUTE 权限。

  3. 策略优先级: 更具体的策略优先于一般策略。拒绝策略将覆盖同一资源的允许策略。

  4. 服务连接: 在 Ranger 中为 Alluxio 配置 HDFS 服务时,连接测试预计会失败,因为 Ranger 无法直接与 Alluxio 的文件系统方案通信。这是正常现象。您可以安全地忽略该错误,保存服务,然后手动创建策略。

  5. 故障排除: 如果授权意外失败,请检查:

    • Ranger 策略缓存刷新状态。

    • Alluxio 日志中的详细错误消息。

    • 用户身份验证是否正常工作。

使用 Open Policy Agent (OPA) 进行管理 API 授权

为了控制对管理和管理操作的访问,Alluxio 网关与 Open Policy Agent (OPA) 集成。OPA 使您能够为所有网关 API 端点定义和强制执行细粒度的、基于策略的访问控制。这是保护管理平面的推荐解决方案,允许您根据用户身份(角色、组)和正在请求的特定 API 操作创建复杂的规则。

核心概念

在配置之前,了解三个主要组件会很有帮助:

  1. OPA 策略(.rego 文件): 授权的“逻辑引擎”。此文件包含一组用 Rego 语言编写的规则,这些规则定义了允许或拒绝请求的条件。

  2. OPA 数据(.yaml 文件): 策略使用的“配置”。此文件提供特定数据——例如超级管理员列表、组定义和路径权限——Rego 规则会参考这些数据来做出决策。

  3. Kubernetes ConfigMap: 将策略和数据文件提供给 Alluxio 网关 Pod 的标准 Kubernetes 方法。

设置和配置

请按照以下步骤为网关启用和配置 OPA 授权。

步骤 1:准备 OPA 策略和数据文件

首先,在本地创建策略(opa_auth_policy.rego)和数据(opa_data.yaml)文件。

1. opa_auth_policy.rego(策略文件)

这是一个全面的参考策略,您可以将其用作起点或根据您的需求进行自定义。

    package opa_auth_policy

    import rego.v1

    # -----------------------------------------------------------------------------
    # --- 默认策略
    # -----------------------------------------------------------------------------

    # 默认情况下,拒绝所有请求。
    default allow := false

    # -----------------------------------------------------------------------------
    # --- 主要授权规则
    # --- 这些是确定最终“允许”决策的顶级规则。
    # -----------------------------------------------------------------------------

    # 规则 1:超级管理员可以执行任何操作,绕过所有其他检查。
    allow if {
        user_is_superadmin
    }

    # 规则 2:对于通过基本检查的用户,允许 GET 访问全局允许的 API。
    allow if {
        base_checks_pass
        method_is_get
        api_in_global_allow_list
    }

    # 规则 3:管理员可以列出资源(即,没有特定路径或 ID 的 GET 请求)。
    allow if {
        base_checks_pass
        user_is_admin
        method_is_get
        request_is_for_listing
    }

    # 规则 4:允许任何用户谁通过基本检查 GET 资源,如果所有请求的路径都是允许的。
    allow if {
        base_checks_pass
        method_is_get
        all_request_paths_are_allowed
    }

    # 规则 5:允许管理员执行更新操作,如果所有请求的路径都是允许的。
    allow if {
        base_checks_pass
        user_is_admin
        method_is_update
        all_request_paths_are_allowed
    }

    # 规则 6:允许提供 ID 但没有路径的请求。
    # 这处理一个特定的更新场景。
    allow if {
        base_checks_pass
        user_is_admin
        method_is_update
        request_has_id_but_no_path
    }

    # 规则 7:允许提供 ID 但没有路径的请求。
    # 这处理一个特定的 GET 场景。
    allow if {
        base_checks_pass
        method_is_get
        request_has_id_but_no_path
    }

    # -----------------------------------------------------------------------------
    # --- 核心逻辑帮助程序
    # -----------------------------------------------------------------------------

    # 组合非超级管理员用户的常见检查以提高性能。
    base_checks_pass if {
        not user_is_superadmin
        user_is_valid
        not api_in_global_deny_list
    }

    # 检查请求中的所有路径是否都在用户的允许列表和不在拒绝列表中。
    all_request_paths_are_allowed if {
        count(relevant_paths) > 0
        count({x | relevant_paths[x]; path_is_allowed(x)}) == count(relevant_paths)
        count({x | relevant_paths[x]; path_is_denied(x)}) == 0
    }

    # 确定请求是否为“列表”操作(没有 ID 和路径)。
    request_is_for_listing if {
        not request_has_id
        count(relevant_paths) == 0
    }

    # 确定请求是否包含 ID 但没有路径。
    request_has_id_but_no_path if {
        request_has_id
        count(relevant_paths) == 0
    }

    # -----------------------------------------------------------------------------
    # --- 权限详细信息帮助程序
    # -----------------------------------------------------------------------------

    # 检查路径是否在用户的允许前缀中。
    path_is_allowed(path) if {
        some i, j
        some group in input_groups
        group == data.groups[i].group
        clean_path := trim_suffix(path, "/")
        clean_prefix := trim_suffix(data.groups[i].allow.pathPrefixes[j].prefix, "/")
        strings.any_prefix_match(clean_path, clean_prefix)
        is_valid_prefix_match(clean_path, clean_prefix)
        api_is_valid_for_path_rule(data.groups[i].allow.pathPrefixes[j])
    }

    # 检查路径是否在用户的拒绝前缀中。
    path_is_denied(path) if {
        some i, j
        some group in input_groups
        group == data.groups[i].group
        clean_path := trim_suffix(path, "/")
        clean_prefix := trim_suffix(data.groups[i].deny.pathPrefixes[j].prefix, "/")
        strings.any_prefix_match(clean_path, clean_prefix)
        is_valid_prefix_match(clean_path, clean_prefix)
        api_is_valid_for_path_rule(data.groups[i].deny.pathPrefixes[j])
    }

    # 验证前缀匹配是否合法。
    # 如果前缀与路径完全匹配,则此规则为真。
    is_valid_prefix_match(path, prefix) if {
        strings.any_prefix_match(path, prefix)
        suffix := trim_prefix(path, prefix)
        suffix == ""
    }

    # 如果前缀匹配目录边界,则此规则为真。
    # 示例:前缀“/a/b”匹配路径“/a/b/c”。
    is_valid_prefix_match(path, prefix) if {
        strings.any_prefix_match(path, prefix)
        suffix := trim_prefix(path, prefix)
        startswith(suffix, "/")
    }

    # 检查当前 API 是否对给定的路径规则有效。
    # 规则 1:如果路径规则未指定“apis”列表,则它适用于所有 API。
    api_is_valid_for_path_rule(rule) if {
        not rule.apis
    }

    # 规则 2:如果路径规则指定了“apis”列表,则当前 API 必须在该列表中。
    api_is_valid_for_path_rule(rule) if {
        input_api in rule.apis
    }

    # -----------------------------------------------------------------------------
    # --- 请求解析帮助程序
    # -----------------------------------------------------------------------------

    # 从请求路径中提取 API 端点。
    input_api := split(input.path, "/v1")[1]

    # HTTP 方法检查。
    method_is_get if input.method == "GET"
    method_is_update if input.method != "GET"

    # 从请求中的多个可能位置提取路径。
    # 使用“contains”关键字逐步构建路径集。
    paths_from_request contains path if {
        path := input.query.path[0]
    }

    paths_from_request contains path if {
        path := input.parsed_body.path
    }

    paths_from_request contains path if {
        path := input.parsed_body.paths[_]
    }

    paths_from_request contains path if {
        path := input.parsed_body.index
    }

    # 从请求中收集所有非空路径以进行验证。
    relevant_paths := {p | some p in paths_from_request; p != ""}

    # 检查请求是否包含 ID。
    request_has_id if input.parsed_body.id != ""
    request_has_id if input.query.id != ""

    # 全局 API 列表检查。
    api_in_global_deny_list if input_api in data.denyApis
    api_in_global_allow_list if input_api in data.allowApis

    # -----------------------------------------------------------------------------
    # --- 用户和角色帮助程序
    # -----------------------------------------------------------------------------

    claims := payload if {
        token := input.header.Authorization
        count(token) != 0
        startswith(token[0], "Bearer ")
        bearer_token := substring(token[0], count("Bearer "), -1)
        [_, payload, _] := io.jwt.decode(bearer_token)
    }

    else := user_info if {
        token := input.header.Authorization
        count(token) != 0
        not startswith(token[0], "Bearer ")
        base64.is_valid(token[0])
        ui = base64.decode(token[0])
        json.is_valid(ui)
        user_info = json.unmarshal(ui)
    }

    default input_roles := []

    input_roles := claims.roleFieldName if {
        claims.roleFieldName != ""
        is_array(claims.roleFieldName)
    }

    else := [claims.roleFieldName] if {
        claims.roleFieldName != ""
        is_string(claims.roleFieldName)
    }

    else := claims.role if  {
        claims.role != ""
        is_array(claims.role)
    }

    else := [claims.role] if {
        claims.role != ""
        is_string(claims.role)
    }

    default input_groups := []

    input_groups := claims.groupFieldName if {
        claims.groupFieldName != ""
        is_array(claims.groupFieldName)
    }

    else := [claims.groupFieldName] if {
        claims.groupFieldName != ""
        is_string(claims.groupFieldName)
    }

    else := claims.group if {
        claims.group != ""
        is_array(claims.group)
    }

    else := [claims.group] if {
        claims.group != ""
        is_string(claims.group)
    }

    user_is_valid if {
        count(input_roles) > 0
        count(input_groups) > 0
    }

    user_is_superadmin if {
        count(input_roles) > 0
        some i
        some role in input_roles
        role == data.superAdmin[i]
    }

    user_is_admin if {
        some i
        some role in input_roles
        role == data.groupAdmin[i]
    }

2. opa_data.yaml(数据文件)

在此文件中定义您的角色、API 限制和基于组的路径权限。

# 超级管理员定义
superAdmin: ["SuperAdmin"]

# 管理员用户定义
groupAdmin: ["GroupAdmin"]

# 只能由 superAdmin 访问的 API
denyApis:
  - /file_index
  - /nodes
  - /rebalance
  - /cache
  - /mount

# 每个人都可以访问的 API
allowApis: 

# 针对团队和路径的权限定义
groups:
  - group: Search
    allow: 
      pathPrefixes:
        - prefix: s3://search-bucket/dir1/dir2
        # 如果未定义 apis,则默认允许所有 API
        - prefix: s3://search-bucket/dir1/dir3/
          apis:
            - /load
            
  - group: Recommend
    allow:  
      pathPrefixes:
        - prefix: s3://recommend-bucket/dir1/dir2
        - prefix: s3://recommend-bucket/dir1/dir3
          apis:
            - /load

步骤 2:创建 Kubernetes ConfigMap

使用 kubectl CLI 从您准备的两个文件创建 ConfigMap。

kubectl create configmap opa-gateway-configmap \
  --from-file=opa_auth_policy.rego \
  --from-file=opa_data.yaml

步骤 3:配置 Alluxio 网关

最后,修改您的 alluxio-cluster.yaml 以启用 OPA 并指示网关使用您创建的 ConfigMap。

spec:
  global:
    authentication:
      enabled: true
      type: oidc
      oidc:
        jwksUri: 
        nbfCheck: false
        roleFieldName: 
        userFieldName: 
        groupFieldName:
    authorization:
      enabled: true
      opa:
        components:
          gateway:
            configMapName: opa-gateway-configmap
            filenames:
              - opa_auth_policy.rego
              - opa_data.yaml
            query: data.opa_auth_policy.allow

应用此配置后,Alluxio 网关将使用您的 OPA 策略来授权传入的 API 请求。

工作原理

OPA 输入格式

对于它收到的每个 API 请求,网关都会构造一个 JSON 对象,作为 OPA 策略评估的 input。此对象包含有关请求的所有关键信息。

{
    "header": {
        "Accept": [
            "*/*"
        ],
        "Accept-Encoding": [
            "gzip, deflate, br"
        ],
        "Authorization": [
            "Bearer eyJuYW1lIjoxxxx="
        ],
        "Connection": [
            "keep-alive"
        ],
        "Postman-Token": [
            "b9844ab1-27b5-41a2-83a5-40f4d9fb74f4"
        ],
        "User-Agent": [
            "PostmanRuntime/7.42.0"
        ],
        "X-Request-Id": [
            "97be1ace-dc50-40a2-b3a1-e02061ca9504"
        ]
    },
    "method": "POST",
    "parsed_body": {
        "paths": [
            "s3://search-bucket/dir1/dir1",
			"",
			"s3://search-bucket/dir1/dir3"
        ],
        "options": {
            "batchSize": 0,
            "fileFilterRegx": "",
            "replicas": 0,
            "skipIfExists": false
        }
    },
    "path": "/api/v1/load"
}

注意: 使用 OIDC 身份验证时,客户端必须提供带有 Bearer 前缀的 Authorization 标头。

策略决策流程

参考 OPA 策略遵循清晰的决策流程:

  1. 默认拒绝:所有请求都将被拒绝,除非有规则明确允许。

  2. 超级管理员覆盖:如果用户具有 SuperAdmin 角色,则始终允许该请求。

  3. 全局规则:策略检查如果 API 在全局拒绝列表或允许列表中。

  4. 基于组和路径的权限:策略检查用户的组是否有权访问请求中指定的资源路径。

  5. 特定于操作的规则:最终决定基于操作类型(例如 GETPOST/PUT)和用户角色(例如 GroupAdmin 与标准用户)做出。

实施指南和测试

  • 安全默认值:该策略建立在“默认拒绝”原则之上,确保只允许明确允许的操作。

  • 角色权限SuperAdmin 具有不受限制的访问权限。GroupAdmin 具有在其授权路径内更新和列出资源的提升权限。

  • 测试:通过使用不同的用户令牌发送请求来测试您的策略,以确保它们的行为符合预期。

# 使用用户的 OIDC 令牌进行测试
curl --location 'http://gateway-host:port/api/v1/load' \
--header 'Authorization: Bearer <YOUR_OIDC_TOKEN>' \
--header 'Content-Type: application/json' \
--data '{"paths": ["s3://search-bucket/dir1/dir2/test"], "alias":"loads3a"}'

附录:权限参考

本附录详细介绍了 Alluxio 中各种 S3 API 和 Hadoop 文件系统操作所需的权限。

S3 API 授权

授权分为对象级和存储桶级任务,并为每个操作推荐了 ACL。

对象操作 ACL 矩阵

操作类型
HTTP 方法
条件
目标资源
所需权限

ListParts

GET

查询中的 uploadId

当前对象

READ

GetObjectTagging

GET

查询中的 tagging

当前对象

READ

GetObject

GET

(其他情况的 GET 方法)

当前对象

READ

PutObjectTagging

PUT

查询中的 tagging

当前对象

WRITE

UploadPartCopy

PUT

查询中的 uploadId 和 x-amz-copy-source

当前对象

READ

目标父目录

WRITE

UploadPart

PUT

查询中的 uploadId

当前对象

WRITE

CopyObject

PUT

x-amz-copy-source 标头

源对象

READ

目标父目录

WRITE

PutObject

PUT

(其他情况的 PUT 方法)

父目录

WRITE

CreateMultipartUpload

POST

查询中的 uploads

父目录

WRITE

CompleteMultipartUpload

POST

查询中的 uploadId

父目录

WRITE

HeadObject

HEAD

-

当前对象

READ

AbortMultipartUpload

DELETE

查询中的 uploadId

当前对象

WRITE

DeleteObjectTagging

DELETE

查询中的 tagging

当前对象

WRITE

DeleteObject

DELETE

(其他情况的 DELETE 方法)

当前对象

WRITE

存储桶操作 ACL 矩阵

操作类型
HTTP 方法
条件
目标资源
所需权限

ListBuckets

GET

存储桶为空

根路径

EXECUTE

GetBucketTagging

GET

查询中的 tagging

存储桶目录

READ

ListMultipartUploads

GET

查询中的 uploads

存储桶目录

EXECUTE

ListObjects

GET

其他情况的 GET 方法

请求的目录

EXECUTE

PutBucketTagging

PUT

查询中的 tagging

存储桶目录

WRITE

CreateBucket

PUT

(其他情况的 PUT 方法)

根路径

WRITE

DeleteObjects

POST

查询中的 delete

每个指定的对象

WRITE(每个对象)

HeadBucket

HEAD

-

存储桶目录

READ

DeleteBucketTagging

DELETE

查询中的 tagging

存储桶目录

WRITE

DeleteBucket

DELETE

(其他情况的 DELETE 方法)

存储桶目录

WRITE

Hadoop 文件系统授权

Alluxio 的 Hadoop 文件系统集成需要对文件和目录操作进行权限检查。

Hadoop 文件系统函数和权限

函数
检查权限
检查路径
权限
注释

append

当前路径

WRITE

将数据附加到现有文件。

create

父路径

WRITE

创建一个新文件或覆盖一个现有文件。

delete

父路径

WRITE

删除文件或目录。

getFileBlockLocations

当前路径

READ

返回文件的块位置。

getFileStatus

当前路径

READ

返回文件或目录的状态。

setOwner

当前路径

WRITE

设置文件或目录的所有者/组。

setPermission

当前路径

WRITE

设置文件或目录的权限。

listStatus

当前路径

EXECUTE

列出路径中的文件/目录。

mkdirs

父路径

WRITE

创建目录和必要的父目录。

open

当前路径

READ

打开文件进行读取。

rename

源父目录,目标父目录

WRITE, WRITE

重命名文件或目录。

Last updated