使用 .NET 管理 Azure Data Lake Storage 中的 ACL

本文介绍如何使用 .NET 来获取、设置和更新目录和文件的访问控制列表。

ACL 继承已可用于在父目录下创建的新子项。 但是,还可以为父目录的现有子项以递归方式添加、更新和删除 ACL,而不必为每个子项单独进行这些更改。

包 (NuGet) | 示例 | API 参考 | 提供反馈

先决条件

  • Azure 订阅。 请参阅获取 Azure 试用版
  • 已启用分层命名空间 (HNS) 的 Azure 存储帐户。 按这些说明创建一个。
  • Azure CLI 版本 2.6.0 或更高版本。
  • 以下安全权限之一:
    • 一个预配的 Microsoft Entra ID 安全主体,它已分配有存储 Blob 数据所有者角色,并且范围限定为目标容器、存储帐户、父资源组或订阅。
    • 计划将 ACL 设置应用到的目标容器或目录的拥有用户。 为了以递归方式设置 ACL,这包括目标容器或目录中的所有子项。
    • 存储帐户密钥。

设置项目

本部分介绍如何设置项目以使用 Azure Storage Data Lake 客户端库。

安装包

从项目目录中,使用 dotnet add package 命令安装 Azure Storage Data Lake 和 Azure 标识客户端库的包。 与 Azure 服务的无密码连接需要 Azure.Identity 包。

dotnet add package Azure.Storage.Files.DataLake
dotnet add package Azure.Identity

添加 using 指令

将这些 using 指令添加到代码文件的顶部:

using Azure;
using Azure.Core;
using Azure.Storage;
using Azure.Storage.Files.DataLake;
using Azure.Storage.Files.DataLake.Models;
using System.Collections.Generic;
using System.Threading.Tasks;

连接到帐户

若要运行本文中的代码示例,需创建一个表示存储帐户的 DataLakeServiceClient 实例。 你可以使用 Microsoft Entra ID 凭据或帐户密钥为客户端对象授权。

可以使用适用于 .NET 的 Azure 标识客户端库,通过 Microsoft Entra ID 对应用程序进行身份验证。

注意

如果使用 Microsoft Entra ID 来授予访问权限,请确保已为安全主体分配了存储 Blob 数据所有者角色。 若要详细了解如何应用 ACL 权限以及更改这些权限的影响,请参阅 Azure Data Lake Storage 中的访问控制模型

首先,向安全主体分配以下 Azure 基于角色的访问控制 (Azure RBAC) 角色之一:

角色 ACL 设置功能
存储 Blob 数据所有者 帐户中的所有目录和文件。
存储 Blob 数据参与者 仅限安全主体拥有的目录和文件。

接下来,创建一个 DataLakeServiceClient 实例并传入 DefaultAzureCredential 类的新实例。

public static DataLakeServiceClient GetDataLakeServiceClient(string accountName)
{
    string dfsUri = $"https://{accountName}.dfs.core.chinacloudapi.cn";

    DataLakeServiceClient dataLakeServiceClient = new DataLakeServiceClient(
        new Uri(dfsUri),
        new DefaultAzureCredential());

    return dataLakeServiceClient;
}

要详细了解如何使用 DefaultAzureCredential 授权访问数据,请参阅 如何使用 Azure 服务对 .NET 应用程序进行身份验证

设置 ACL

设置 ACL 时,你将替换整个 ACL,包括其所有条目。 如果要更改安全主体的权限级别,或将新的安全主体添加到 ACL 而不影响其他现有项,则应改为更新 ACL。 若要更新 ACL 而不是替换它,请参阅本文的更新 ACL 部分。

如果选择设置 ACL,则必须为责任用户添加一个条目,为责任组添加一个条目,为所有其他用户添加一个条目。 若要详细了解责任用户、责任组和所有其他用户,请参阅用户和标识

本节介绍如何完成下列操作:

  • 设置目录的 ACL
  • 设置文件的 ACL
  • 以递归方式设置 ACL

设置目录的 ACL

可以通过调用 DataLakeDirectoryClient.GetAccessControlAsync 方法获取目录的访问控制列表 (ACL),并通过调用 DataLakeDirectoryClient.SetAccessControlList 方法来设置 ACL。

此示例获取并设置名为 my-directory 的目录的 ACL。 字符串 user::rwx,group::r-x,other::rw- 为拥有用户提供读取、写入和执行权限,为拥有组授予读取和执行权限,并为所有其他用户提供读取和写入权限。

public async Task ManageDirectoryACLs(DataLakeFileSystemClient fileSystemClient)
{
    DataLakeDirectoryClient directoryClient =
      fileSystemClient.GetDirectoryClient("");

    PathAccessControl directoryAccessControl =
        await directoryClient.GetAccessControlAsync();

    foreach (var item in directoryAccessControl.AccessControlList)
    {
        Console.WriteLine(item.ToString());
    }

    IList<PathAccessControlItem> accessControlList
        = PathAccessControlExtensions.ParseAccessControlList
        ("user::rwx,group::r-x,other::rw-");

    directoryClient.SetAccessControlList(accessControlList);

}

还可以获取和设置容器根目录的 ACL。 若要获取根目录,请将空字符串 ("") 传递到 DataLakeFileSystemClient.GetDirectoryClient 方法。

设置文件的 ACL

可以通过调用 DataLakeFileClient.GetAccessControlAsync 方法获取文件的访问控制列表 (ACL),并通过调用 DataLakeFileClient.SetAccessControlList 方法来设置 ACL。

此示例获取并设置名为 my-file.txt 的文件的 ACL。 字符串 user::rwx,group::r-x,other::rw- 为拥有用户提供读取、写入和执行权限,为拥有组授予读取和执行权限,并为所有其他用户提供读取和写入权限。

public async Task ManageFileACLs(DataLakeFileSystemClient fileSystemClient)
{
    DataLakeDirectoryClient directoryClient =
        fileSystemClient.GetDirectoryClient("my-directory");

    DataLakeFileClient fileClient =
        directoryClient.GetFileClient("hello.txt");

    PathAccessControl FileAccessControl =
        await fileClient.GetAccessControlAsync();

    foreach (var item in FileAccessControl.AccessControlList)
    {
        Console.WriteLine(item.ToString());
    }

    IList<PathAccessControlItem> accessControlList
        = PathAccessControlExtensions.ParseAccessControlList
        ("user::rwx,group::r-x,other::rw-");

    fileClient.SetAccessControlList(accessControlList);
}

以递归方式设置 ACL

通过调用 DataLakeDirectoryClient.SetAccessControlRecursiveAsync 方法以递归方式设置 ACL。 向此方法传递 PathAccessControlItem列表。 每个 PathAccessControlItem 定义一个 ACL 条目。

如果要设置默认 ACL 条目,请将 PathAccessControlItemPathAccessControlItem.DefaultScope 属性设置为 true

此示例设置名为 my-parent-directory 的目录的 ACL。 此方法接受一个名为 isDefaultScope 的布尔参数,该参数指定是否设置默认 ACL。 该参数用在 PathAccessControlItem 的构造函数中。 ACL 的条目为所有者用户提供读取、写入和执行权限,仅为负责人组授予读取和执行权限,不为所有其他用户提供任何访问权限。 此示例中的最后一个 ACL 条目向对象 ID 为 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 的特定用户提供读取和执行权限。

public async Task SetACLRecursively(DataLakeServiceClient serviceClient, bool isDefaultScope)
{
    DataLakeDirectoryClient directoryClient =
        serviceClient.GetFileSystemClient("my-container").
            GetDirectoryClient("my-parent-directory");

    List<PathAccessControlItem> accessControlList =
        new List<PathAccessControlItem>()
    {
new PathAccessControlItem(AccessControlType.User,
    RolePermissions.Read |
    RolePermissions.Write |
    RolePermissions.Execute, isDefaultScope),

new PathAccessControlItem(AccessControlType.Group,
    RolePermissions.Read |
    RolePermissions.Execute, isDefaultScope),

new PathAccessControlItem(AccessControlType.Other,
    RolePermissions.None, isDefaultScope),

new PathAccessControlItem(AccessControlType.User,
    RolePermissions.Read |
    RolePermissions.Execute, isDefaultScope,
    entityId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"),
    };

    await directoryClient.SetAccessControlRecursiveAsync
        (accessControlList, null);
}

更新 ACL

更新 ACL 时,你将修改 ACL 而非替换 ACL。 例如,你可以将一个新的安全主体添加到 ACL,而不影响 ACL 中列出的其他安全主体。 若要替换 ACL 而不是更新它,请参阅本文的 设置 ACL 部分。

本节介绍如何完成下列操作:

  • 更新 ACL
  • 以递归方式更新 ACL

更新 ACL

首先,通过调用 DataLakeDirectoryClient.GetAccessControlAsync 方法获取目录的 ACL。 将 ACL 条目列表复制到 PathAccessControl 对象的新列表。 然后找到要更新的条目并在列表中替换它。 通过调用 DataLakeDirectoryClient.SetAccessControlList 方法设置 ACL。

此示例通过替换所有其他用户的 ACL 条目来更新容器的根 ACL。

public async Task UpdateDirectoryACLs(DataLakeFileSystemClient fileSystemClient)
{
    DataLakeDirectoryClient directoryClient =
      fileSystemClient.GetDirectoryClient("");

    PathAccessControl directoryAccessControl =
        await directoryClient.GetAccessControlAsync();

    List<PathAccessControlItem> accessControlListUpdate 
        = (List<PathAccessControlItem>)directoryAccessControl.AccessControlList;

    int index = -1;

    foreach (var item in accessControlListUpdate)
    {
        if (item.AccessControlType == AccessControlType.Other)
        {
            index = accessControlListUpdate.IndexOf(item);
            break;
        }
    }

    if (index > -1)
    {
        accessControlListUpdate[index] = new PathAccessControlItem(AccessControlType.Other,
        RolePermissions.Read |
        RolePermissions.Execute);

        directoryClient.SetAccessControlList(accessControlListUpdate);
    }

   }

以递归方式更新 ACL

若要以递归方式更新 ACL,请创建包含要更新的 ACL 条目的一个新 ACL 对象,然后在“更新 ACL”操作中使用该对象。 不要获取现有 ACL,只需要提供要更新的 ACL 条目。

通过调用 DataLakeDirectoryClient.UpdateAccessControlRecursiveAsync 方法以递归方式更新 ACL。 向此方法传递 PathAccessControlItem列表。 每个 PathAccessControlItem 定义一个 ACL 条目。

如果要更新默认 ACL 条目,请将 PathAccessControlItemPathAccessControlItem.DefaultScope 属性设置为 true

此示例以写入权限更新某个 ACL 条目。 此方法接受一个名为 isDefaultScope 的布尔参数,该参数指定是否更新默认 ACL。 该参数用在 PathAccessControlItem 的构造函数中。

public async Task UpdateACLsRecursively(DataLakeServiceClient serviceClient, bool isDefaultScope)
{
    DataLakeDirectoryClient directoryClient =
        serviceClient.GetFileSystemClient("my-container").
        GetDirectoryClient("my-parent-directory");

    List<PathAccessControlItem> accessControlListUpdate =
        new List<PathAccessControlItem>()
    {
new PathAccessControlItem(AccessControlType.User,
    RolePermissions.Read |
    RolePermissions.Write |
    RolePermissions.Execute, isDefaultScope,
    entityId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"),
    };

    await directoryClient.UpdateAccessControlRecursiveAsync
        (accessControlListUpdate, null);

}

删除 ACL 条目

可以删除一个或多个 ACL 条目。 本节介绍如何完成下列操作:

  • 删除 ACL 条目
  • 以递归方式删除 ACL 条目

删除 ACL 条目

首先,通过调用 DataLakeDirectoryClient.GetAccessControlAsync 方法获取目录的 ACL。 将 ACL 条目列表复制到 PathAccessControl 对象的新列表。 然后找到要删除的条目并调用集合的 Remove 方法。 通过调用 DataLakeDirectoryClient.SetAccessControlList 方法设置已更新的 ACL。

此示例通过替换所有其他用户的 ACL 条目来更新容器的根 ACL。

public async Task RemoveDirectoryACLEntry
    (DataLakeFileSystemClient fileSystemClient)
{
    DataLakeDirectoryClient directoryClient =
      fileSystemClient.GetDirectoryClient("");

    PathAccessControl directoryAccessControl =
        await directoryClient.GetAccessControlAsync();

    List<PathAccessControlItem> accessControlListUpdate
        = (List<PathAccessControlItem>)directoryAccessControl.AccessControlList;

    PathAccessControlItem entryToRemove = null;

    foreach (var item in accessControlListUpdate)
    {
        if (item.EntityId == "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
        {
            entryToRemove = item;
            break;
        }
    }

    if (entryToRemove != null)
    {
        accessControlListUpdate.Remove(entryToRemove);
        directoryClient.SetAccessControlList(accessControlListUpdate);
    }

}

以递归方式删除 ACL 条目

若要以递归方式删除 ACL 条目,请为要删除的 ACL 条目创建一个新的 ACL 对象,然后在“删除 ACL”操作中使用该对象。 不要获取现有 ACL,只需要提供要删除的 ACL 条目。

通过调用 DataLakeDirectoryClient.RemoveAccessControlRecursiveAsync 方法删除 ACL 条目。 向此方法传递 PathAccessControlItem列表。 每个 PathAccessControlItem 定义一个 ACL 条目。

如果要删除默认 ACL 条目,请将 PathAccessControlItemPathAccessControlItem.DefaultScope 属性设置为 true

此示例从名为 my-parent-directory 的目录的 ACL 中删除 ACL 条目。 此方法接受一个名为 isDefaultScope 的布尔参数,该参数指定是否删除默认 ACL 中的条目。 该参数用在 PathAccessControlItem 的构造函数中。

public async Task RemoveACLsRecursively(DataLakeServiceClient serviceClient, bool isDefaultScope)
{
    DataLakeDirectoryClient directoryClient =
        serviceClient.GetFileSystemClient("my-container").
            GetDirectoryClient("my-parent-directory");

    List<RemovePathAccessControlItem> accessControlListForRemoval =
        new List<RemovePathAccessControlItem>()
        {
    new RemovePathAccessControlItem(AccessControlType.User, isDefaultScope,
    entityId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"),
        };

    await directoryClient.RemoveAccessControlRecursiveAsync
        (accessControlListForRemoval, null);

}

从故障中恢复

以递归方式修改 ACL 时,可能会遇到运行时或权限错误。 对于运行时错误,请从头开始重启此过程。 如果安全主体没有足够的权限修改要修改的目录层次结构中的目录或文件的 ACL,则会出现权限错误。 请解决权限问题,然后选择通过使用继续标记从故障点继续执行此过程,或者从头重启此过程。 如果希望从头开始重启,则无需使用继续标记。 你可以重新应用 ACL 条目,而不会产生任何负面影响。

此示例在失败时返回一个继续标记。 应用程序可以在错误得到解决后再次调用此示例方法,并传入继续标记。 如果是第一次调用此示例方法,则应用程序可以为继续标记参数传入 null 值。

public async Task<string> ResumeAsync(DataLakeServiceClient serviceClient,
    DataLakeDirectoryClient directoryClient,
    List<PathAccessControlItem> accessControlList,
    string continuationToken)
{
    try
    {
        var accessControlChangeResult =
            await directoryClient.SetAccessControlRecursiveAsync(
                accessControlList, continuationToken: continuationToken, null);

        if (accessControlChangeResult.Value.Counters.FailedChangesCount > 0)
        {
            continuationToken =
                accessControlChangeResult.Value.ContinuationToken;
        }

        return continuationToken;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
        return continuationToken;
    }

}

如果你希望过程继续完成而不被权限错误中断,则可以指定它。

若要确保过程无中断地完成,请传入 AccessControlChangedOptions 对象,并将该对象的 ContinueOnFailure 属性设置为 true

此示例以递归方式设置 ACL 条目。 如果此代码遇到权限错误,它会记录该故障并继续执行。 此示例将故障数输出到控制台。

public async Task ContinueOnFailureAsync(DataLakeServiceClient serviceClient,
    DataLakeDirectoryClient directoryClient,
    List<PathAccessControlItem> accessControlList)
{
    var accessControlChangeResult =
        await directoryClient.SetAccessControlRecursiveAsync(
            accessControlList, null, new AccessControlChangeOptions()
            { ContinueOnFailure = true });

    var counters = accessControlChangeResult.Value.Counters;

    Console.WriteLine("Number of directories changed: " +
        counters.ChangedDirectoriesCount.ToString());

    Console.WriteLine("Number of files changed: " +
        counters.ChangedFilesCount.ToString());

    Console.WriteLine("Number of failures: " +
        counters.FailedChangesCount.ToString());
}

最佳实践

本部分提供了有关以递归方式设置 ACL 的一些最佳做法指南。

处理运行时错误

发生运行时错误可能有许多原因(例如:中断或客户端连接问题)。 如果遇到运行时错误,请重启递归 ACL 过程。 可以将 ACL 重新应用于项,而不会造成负面影响。

处理权限错误 (403)

如果在运行递归 ACL 过程时遇到访问控制异常,则表明 AD 安全主体可能没有足够的权限将 ACL 应用于目录层次结构中的一个或多个子项。 发生权限错误时,此过程会停止,系统会提供一个继续标记。 请修复权限问题,然后使用继续标记来处理剩余的数据集。 已成功处理的目录和文件不需要再次处理。 你还可以选择重启递归 ACL 过程。 可以将 ACL 重新应用于项,而不会造成负面影响。

凭据

建议你预配一个在目标存储帐户或容器范围中分配有存储 Blob 数据所有者角色的 Microsoft Entra 安全主体。

性能

为了减少延迟,建议你在与存储帐户位于同一区域中的 Azure 虚拟机 (VM) 中运行递归 ACL 过程。

ACL 限制

可应用于目录或文件的 ACL 的最大数目为 32 个访问 ACL 和 32 个默认 ACL。 有关详细信息,请参阅 Azure Data Lake Storage Gen2 中的访问控制

另请参阅