AWS Directory Service
管理指南 (版本 1.0)
AWS 文档中描述的 AWS 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 AWS 服务入门

AD Connector 先决条件

要使用 AD Connector 连接到您的现有目录,您需要:

VPC

对 VPC 进行如下设置:

  • 至少两个子网。每个子网必须位于不同的可用区。

  • 必须通过虚拟专用网络 (VPN) 连接或 AWS Direct Connect 将 VPC 连接到您的现有网络。

  • VPC 必须具有默认硬件租户。

有关更多信息,请参阅 Amazon VPC 用户指南 中的以下主题:

有关 AWS Direct Connect 的更多信息,请参阅 AWS Direct Connect 用户指南

现有网络

您将需要连接到具有 Active Directory 域的现有网络。此域的功能级别必须是 Windows Server 2003 或更高版本。AD Connector 还支持连接到 Amazon EC2 实例上托管的域。

注意

与 Amazon EC2 域加入功能结合使用时,AD Connector 不支持只读域控制器 (RODC)。

服务账户

您必须拥有现有目录中被委派了以下权限的服务账户的凭证:

  • 读取用户和组 – 必需

  • 将计算机加入到域中– 必需

  • 创建计算机对象 – 仅当使用无缝域加入和 Amazon WorkSpaces 时必需

有关更多信息,请参阅 向您的服务账户委派权限

用户权限

所有 Active Directory 用户必须有权读取自己的属性。具体而言,包括以下属性:

  • GivenName

  • SurName

  • Mail

  • SamAccountName

  • UserPrincipalName

  • UserAccountControl

  • MemberOf

默认情况下,Active Directory 用户确实有权读取这些属性。但是,管理员可以随时修改这些权限,因此,您可能希望在设置 AD Connector 之前,验证用户是否具有这些读取权限。

IP 地址

获取您现有目录域中两个 DNS 服务器或域控制器的 IP 地址。

AD Connector在连接到您的目录时将从这些服务器获取 _ldap._tcp.<DnsDomainName>_kerberos._tcp.<DnsDomainName> SRV 记录,因此这些服务器必须包含这些 SRV 记录。AD Connector 尝试查找将同时提供 LDAP 和 Kerberos 服务的公用域控制器,因此这些 SRV 记录必须至少包含一个公用域控制器。有关 SRV 记录的更多信息,请转到 Microsoft TechNet 上的 SRV Resource Records

子网的端口

为了使 AD Connector 将目录请求重定向到您的现有 Active Directory 域控制器,现有网络的防火墙必须对 Amazon VPC 中两个子网的 CIDR 开放以下端口。

  • TCP/UDP 53 - DNS

  • TCP/UDP 88 - Kerberos authentication

  • TCP/UDP 389 - LDAP

这些是 AD Connector 能够连接到目录所需的最少端口。根据您的特定配置,您可能需要打开其他端口。

注意

如果现有 Active Directory 域的 DNS 服务器或域控制器服务器在 VPC 内,与这些服务器相关的安全组必须向 VPC 中的两个子网的 CIDR 开放上述端口。

有关其他端口要求,请参见 Microsoft TechNet 上的 AD and AD DS Port Requirements

Kerberos 预身份验证

用户账户必须启用 Kerberos 预身份验证。有关如何启用此设置的详细说明,请参阅确保已启用 Kerberos 预身份验证。有关此设置的一般信息,请访问 Microsoft TechNet 上的预身份验证

加密类型

当对您的 Active Directory 域控制器进行身份验证时,AD Connector 支持以下加密类型:

  • AES-256-HMAC

  • AES-128-HMAC

  • RC4-HMAC

多重验证先决条件

为了使用您的 AD Connector 目录支持多重验证,您需要以下内容:

  • 现有网络中具有两个客户端终端节点的远程身份验证拨入用户服务 (RADIUS) 服务器。RADIUS 客户端终端节点具有以下要求:

    • 要创建终端节点,您需要 AWS Directory Service 服务器的 IP 地址。这些 IP 地址可以从目录详细信息的 Directory IP Address 字段中获取。

    • 两个 RADIUS 终端节点必须使用相同的共享密码。

  • 您的现有网络必须允许通过默认的 RADIUS 服务器端口 (1812) 从 AWS Directory Service 服务器传入入站流量。

  • 您的 RADIUS 服务器与您的现有目录的用户名必须相同。

有关将 AD Connector 与 MFA 搭配使用的更多信息,请参阅 为 AD Connector 启用多重验证

向您的服务账户委派权限

要连接到您的现有目录,您必须在现有目录中拥有被委派了某些权限的 AD Connector 服务账户的凭证。尽管 Domain Admins 组的成员有足够的权限连接到目录,但是作为最佳实践,您应使用仅具有连接到目录所需的最小权限的服务账户。以下过程演示如何创建名为 Connectors 的新组,委派将 AWS Directory Service 连接到该组所需的必要权限,然后将新服务账户添加到该组。

必须在已加入到目录且已安装 Active Directory User and Computers MMC 管理单元的计算机上执行此过程。您还必须以域管理员身份登录。

向您的服务账户委派权限

  1. 打开 Active Directory User and Computers 并在导航树中选择您的域根。

  2. 在左侧窗格的列表中,右键单击 Users,选择 New,然后选择 Group

  3. New Object - Group 对话框中,输入以下内容,然后单击 OK

    字段 值/选择
    Group name Connectors
    Group scope Global
    Group type 安全性
  4. Active Directory User and Computers 导航树中,选择您的域根。在菜单中,选择 Action,然后选择 Delegate Control

  5. Delegation of Control Wizard 页面上,单击 Next,然后单击 Add

  6. Select Users, Computers, or Groups 对话框中,输入 Connectors,然后单击 OK。如果找到多个对象,请选择上面创建的 Connectors 组。单击 Next (下一步)

  7. Tasks to Delegate 页面上,选择 Create a custom task to delegate,然后选择 Next

  8. 选择 Only the following objects in the folder,然后选择 Computer objectsUser objects

  9. 选择 Create selected objects in this folder,然后选择 Delete selected objects in this folder。然后选择 Next

    
                            对象类型
  10. 选择 Read,然后选择 Next

    注意

    如果您将使用无缝域加入或 Amazon WorkSpaces, ,则还必须启用 写入 权限,以便 AWS Managed Microsoft AD 可以创建计算机对象。

    
                            对象类型
  11. Completing the Delegation of Control Wizard 页面上验证信息,然后单击 Finish

  12. 使用强密码创建一个用户账户,并将该用户添加到 Connectors 组。此用户将被称为您的 AD Connector 服务账户,由于它现在是 Connectors 组的成员,因此具有足够的权限将 AWS Directory Service 连接到目录。

测试 AD Connector

为使 AD Connector 能够连接您的现有目录,您的现有网络的防火墙必须向 VPC 中的两个子网的 CIDR 开放特定的端口。要测试是否满足这些条件,请执行以下步骤:

测试 连接

  1. 在 VPC 中启动一个 Windows 实例并通过 RDP 连接它。实例必须是您现有域的成员。在该 VPC 实例上执行剩余步骤。

  2. 下载并解压缩 DirectoryServicePortTest 测试应用程序。其中包含源代码及 Visual Studio 项目文件,您可以根据需要修改该测试应用程序。

    注意

    Windows Server 2003 及更低版本的操作系统不支持此脚本。

  3. 在 Windows 命令提示符下,使用以下选项运行 DirectoryServicePortTest 测试应用程序:

    DirectoryServicePortTest.exe -d <domain_name> -ip <server_IP_address> -tcp "53,88,135,389,445,3268,5722,9389" -udp "53,88,123,138,389,445"
    <domain_name>

    完全限定域名。这可用于测试林和域功能级别。如果不指定域名,则不测试功能级别。

    <server_IP_address>

    现有域中域控制器的 IP 地址。将针对该 IP 地址来测试端口。如果不指定 IP 地址,则不测试端口。

    此测试应用程序确定是否打开了从 VPC 到域的必要端口,并验证最低的林和域功能级别。

    该输出值将类似于以下内容:

    Testing forest functional level. Forest Functional Level = Windows2008R2Forest : PASSED Testing domain functional level. Domain Functional Level = Windows2008R2Domain : PASSED Testing required TCP ports to <server_IP_address>: Checking TCP port 53: PASSED Checking TCP port 88: PASSED Checking TCP port 389: PASSED Testing required UDP ports to <server_IP_address>: Checking UDP port 53: PASSED Checking UDP port 88: PASSED Checking UDP port 389: PASSED

以下是 DirectoryServicePortTest 应用程序的源代码。

/* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. This file is licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ This file 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. */ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using System.DirectoryServices.ActiveDirectory; using System.Threading; using System.DirectoryServices.AccountManagement; using System.DirectoryServices; using System.Security.Authentication; using System.Security.AccessControl; using System.Security.Principal; namespace DirectoryServicePortTest { class Program { private static List<int> _tcpPorts; private static List<int> _udpPorts; private static string _domain = ""; private static IPAddress _ipAddr = null; static void Main(string[] args) { if (ParseArgs(args)) { try { if (_domain.Length > 0) { try { TestForestFunctionalLevel(); TestDomainFunctionalLevel(); } catch (ActiveDirectoryObjectNotFoundException) { Console.WriteLine("The domain {0} could not be found.\n", _domain); } } if (null != _ipAddr) { if (_tcpPorts.Count > 0) { TestTcpPorts(_tcpPorts); } if (_udpPorts.Count > 0) { TestUdpPorts(_udpPorts); } } } catch (AuthenticationException ex) { Console.WriteLine(ex.Message); } } else { PrintUsage(); } Console.Write("Press <enter> to continue."); Console.ReadLine(); } static void PrintUsage() { string currentApp = Path.GetFileName(System.Reflection.Assembly.GetExecutingAssembly().Location); Console.WriteLine("Usage: {0} \n-d <domain> \n-ip \"<server IP address>\" \n[-tcp \"<tcp_port1>,<tcp_port2>,etc\"] \n[-udp \"<udp_port1>,<udp_port2>,etc\"]", currentApp); } static bool ParseArgs(string[] args) { bool fReturn = false; string ipAddress = ""; try { _tcpPorts = new List<int>(); _udpPorts = new List<int>(); for (int i = 0; i < args.Length; i++) { string arg = args[i]; if ("-tcp" == arg | "/tcp" == arg) { i++; string portList = args[i]; _tcpPorts = ParsePortList(portList); } if ("-udp" == arg | "/udp" == arg) { i++; string portList = args[i]; _udpPorts = ParsePortList(portList); } if ("-d" == arg | "/d" == arg) { i++; _domain = args[i]; } if ("-ip" == arg | "/ip" == arg) { i++; ipAddress = args[i]; } } } catch (ArgumentOutOfRangeException) { return false; } if (_domain.Length > 0 || ipAddress.Length > 0) { fReturn = true; } if (ipAddress.Length > 0) { _ipAddr = IPAddress.Parse(ipAddress); } return fReturn; } static List<int> ParsePortList(string portList) { List<int> ports = new List<int>(); char[] separators = {',', ';', ':'}; string[] portStrings = portList.Split(separators); foreach (string portString in portStrings) { try { ports.Add(Convert.ToInt32(portString)); } catch (FormatException) { } } return ports; } static void TestForestFunctionalLevel() { Console.WriteLine("Testing forest functional level."); DirectoryContext dirContext = new DirectoryContext(DirectoryContextType.Forest, _domain, null, null); Forest forestContext = Forest.GetForest(dirContext); Console.Write("Forest Functional Level = {0} : ", forestContext.ForestMode); if (forestContext.ForestMode >= ForestMode.Windows2003Forest) { Console.WriteLine("PASSED"); } else { Console.WriteLine("FAILED"); } Console.WriteLine(); } static void TestDomainFunctionalLevel() { Console.WriteLine("Testing domain functional level."); DirectoryContext dirContext = new DirectoryContext(DirectoryContextType.Domain, _domain, null, null); Domain domainObject = Domain.GetDomain(dirContext); Console.Write("Domain Functional Level = {0} : ", domainObject.DomainMode); if (domainObject.DomainMode >= DomainMode.Windows2003Domain) { Console.WriteLine("PASSED"); } else { Console.WriteLine("FAILED"); } Console.WriteLine(); } static List<int> TestTcpPorts(List<int> portList) { Console.WriteLine("Testing TCP ports to {0}:", _ipAddr.ToString()); List<int> failedPorts = new List<int>(); foreach (int port in portList) { Console.Write("Checking TCP port {0}: ", port); TcpClient tcpClient = new TcpClient(); try { tcpClient.Connect(_ipAddr, port); tcpClient.Close(); Console.WriteLine("PASSED"); } catch (SocketException) { failedPorts.Add(port); Console.WriteLine("FAILED"); } } Console.WriteLine(); return failedPorts; } static List<int> TestUdpPorts(List<int> portList) { Console.WriteLine("Testing UDP ports to {0}:", _ipAddr.ToString()); List<int> failedPorts = new List<int>(); foreach (int port in portList) { Console.Write("Checking UDP port {0}: ", port); UdpClient udpClient = new UdpClient(); try { udpClient.Connect(_ipAddr, port); udpClient.Close(); Console.WriteLine("PASSED"); } catch (SocketException) { failedPorts.Add(port); Console.WriteLine("FAILED"); } } Console.WriteLine(); return failedPorts; } } }