常用的Linux可插拔认证模块(PAM)应用举例(二)

本文紧接《常用的Linux可插拔认证模块(PAM)应用举例(一)》,继续介绍其他常用的Linux可插拔认证模块(PAM)。

\

pam_tally.so模块

\

pam_tally.so模块也是在系统中经常使用的一个pam模块。其主要作用是监控用户的不成功登录尝试的次数,在达到模块限制的次数时会锁定用户一段时间以防止一些黑客软件的暴力破解。

\

pam_tally.so模块的使用方法和刚才一样,也是需要通过修改/etc/pam.d/system-auth来实现相关功能。

\

例如要实现这样的功能:用户登录时候可以允许三次输入错误密码尝试,如果三次密码输入都错误会锁定用户并且将其登录行为记录到/var/log/faillog文件(或者可以自定义日志文件)中。

\

具体的修改方法是将下面的这两行分别加入auth和account部分:

\
\auth        required        /lib/security/pam_tally.so onerr=fail no_magic_root\account     required        /lib/security/pam_tally.so deny=3 no_magic_root reset\
\

另外在顺序方面最好能够将刚才的配置项放到sufficient之前,修改之后的全文如下:

\
\[root@localhost log]# cat /etc/pam.d/system-auth \#%PAM-1.0\# This file is auto-generated.\# User changes will be destroyed the next time authconfig is run.\auth        required      /lib/security/$ISA/pam_env.so\auth        required      /lib/security/pam_tally.so onerr=fail no_magic_root\auth        sufficient    /lib/security/$ISA/pam_unix.so likeauth nullok\auth        required      /lib/security/$ISA/pam_deny.so\account     required      /lib/security/$ISA/pam_unix.so\account     required      /lib/security/pam_tally.so deny=3 no_magic_root reset\account     sufficient    /lib/security/$ISA/pam_succeed_if.so uid \u0026lt; 100 quiet\account     required      /lib/security/$ISA/pam_permit.so\password    requisite     /lib/security/$ISA/pam_cracklib.so retry=3\password    sufficient    /lib/security/$ISA/pam_unix.so nullok use_authtok md5 shadow\password    required      /lib/security/$ISA/pam_deny.so\session     required      /lib/security/$ISA/pam_limits.so\session     required      /lib/security/$ISA/pam_unix.so\
\

并且手工建立/var/log/faillog文件用以记录多次尝试登录的用户信息。

\

在测试的时候可以发现,如果用户使用错误的密码连续登录三次,该账号将被锁定lock_time所定义的时间。如果用户使用正确的密码登录,再退出使用错误的密码连续登录三次,之后返回再次使用正确的密码登录也会出现authentication fail的错误提示,同时登录的控制台被记录到了/var/log/faillog文件中。

\
\[root@localhost log]# strings faillog \tty2\tty2\\
\

在刚才的例子中deny表示三次错误输入密码则认证失败,reset表示一旦成功输入密码则允许。

\

pam_winbind.so模块:

\

pam_winbind.so模块一般用于混和集中式认证环境的用户认证中。所谓混合集中式认证一般指的是当在企业环境中拥有大量不同类型的操作系统,例如不同数量的Windows,Linux,Unix服务器,此时通过一些配置方案,可以使用户能够通过一个账户口令登录和访问所有类型的操作系统。典型的实现方式是使用Windows Server自带的活动目录来存放和管理用户,而将所有的其它服务器和操作系统都加入到Windows的认证域中,这样这些操作系统上的登录用户不光可以支持本地的用户登录,还可以通过LDAP查找和支持目录服务中的用户登录。

\

而Winbind就是应用于这种环境的典型方案之一。Winbind实际上是Samba软件包的一款组件,在RHEL系统下被包含在了samba-common包中。Winbind在Linux上实现了对微软产品通讯的RPC调用、可插拔验证模块和名字服务切换,通过Samba接口与Windows域控制器获得联系,可以使Windows域用户能在Linux主机上以Linux用户身份方式进行操作。通过设定Linux服务器的name service switch配置可以让系统通过Winbind程序来解析用户信息。最终实现的效果就是可以使用Windows的活动目录来集中存放用户,这些用户只要通过活动目录认证就可以同时在Windows服务器和Linux服务器上登录以及使用相关的系统资源。

\

为了说明winbind组件和winbind.so模块的用法,下面我们将介绍一个结合Windows Server 2008和RHEL 5实现的集中式认证环境的配置案例。Windows Server 2003通过自带的活动目录(Active Directory)充当目录服务器,而RHEL 5会利用其自带的Samba软件包提供的winbind组件从活动目录中获取用户和认证信息进行认证。

\

下面我们将开始正式进入实际操作部分。按照以往的惯例,在动手之前有必要介绍一下拓扑结构、目标和大致的操作步骤。

\

例如:我将构建一个叫做jerrywjl.com的域作为一个公司基本的管理单位,并且在该域中安装了W2K8E的服务器将升级为域控制器和KDC服务器,由于微软活动目录需要依靠DNS命名空间定位资源,所以升级域控制器的操作需要事先有DNS服务器的支持。考虑到升级过程中活动目录会动态更新DNS服务器的各种资源记录,为了简化配置的难度,我将域控制器和DNS服务器放置在同一台主机上。即需要在该服务器上事先建立DNS服务。

\

所有其它本网络内的服务器都安装RHEL5U2系统,并通过配置加入到这个域网络环境中。由于硬件数量有限,所以服务器server.jerrywjl.com同时充当文件服务器、邮件服务器以及FTP服务器。这个需求可以通过在DNS服务器上指派多个别名来实现。所有的Linux服务器加入到Windows域内意味着对这些服务的访问要通过活动目录中的Kerberos认证,只有通过认证的域内用户才可以访问活动目录之内上述资源。

\

而其中比较特殊的是squid.jerrywjl.com是代理服务器,该服务器有两个接口,是内部网络用户访问外部的唯一合法出口。为了提高安全性,要求所有用户在使用该代理服务器之前先通过基本认证核实域内用户身份,通过认证的用户才能使用代理服务器访问外部网络。

\

现在正式开始。

\

第一步操作:构建和配置Windows Server 2008 Enterprise Edition服务器,并将该服务器升级为域控制器。

\

W2K8E的安装和以前版本有很大差异,首先在硬件方面,要求针对32bit版本至少要有512M内存和16G以上硬盘空间。在安装系统完成之后首次登录会以administrator身份进行,但必须要修改administrator用户的密码并满足包含字母、数字、特殊符号和最小七位的复杂度需求。由于这一部分操作比较简单,所以我就不花时间做过多介绍。在系统安装完成之后需要进行基本的网络配置。选择“start”并点击“network”之后选择“network and sharing center”然后是“manage network connections”开启“local area connections”最后选择“properties”,将IP地址修改为192.168.10.100,DNS地址指向本机,同时修改主机名为w2k8e,采用的域名后缀为jerrywjl.com。方法是选择“start”并右键点击“computer”并选择“properity”,在“system”属性中选择“advanced system setting”,之后点击“computer name”标签并选择“change”输入新的主机名并选择“more”来更改dns suffix。这一步操作在完成之后需要重启系统以生效。

\

在系统重启之后,点击“start”选择“cmd”并输入“dcpromo”,该命令即将把本服务器升级为域控制器。点击“next”之后点击“create a new domain in a new forest”,表示该域控制器将是整个域林的第一台域控制器。然后输入要添加的域名jerrywjl.com并选择下一步,在域的兼容模式中使用windows server 2008。进入下一步是DNS服务器的配置,众所周知升级活动目录必须要在网络中有DNS的支持,和W2K3E不同的是W2K8E将DNS的配置过程集成到了域控制器的升级过程中,所以只需要声明DNS服务器的地址,域控制器在升级完成之后也自动完成了DNS服务器的配置。确定之后将会弹出一个要求建立DNS委派的对话框,可以一路确定继续进行下去。

\

下面的接口就要求指定域控制器数据库、日志和系统文件的位置,这里都保持默认即可。在下面的步骤中将要指定一个还原模式的密码,也就是说如果该服务器从域控制器降级为普通服务器,需要输入该密码之后方可进行。到此为止,所有的信息都已经收集完成,下一步会给出一个信息汇总,之后点击“Next”将开始升级域控制器。在升级完成之后必须要按照要求重启系统以使所有配置生效。重启完成之后,系统的架构将发生重大改变,该服务器已经由一台普通的工作组中的计算机提升为域控制器。原来的管理员administrator也由普通的工作组内管理员提升为集域、企业、架构管理员权限于一身的administrator。在重启完成之后,有几个重要的标志用以确认该服务器的角色提升成功。如查看计算机名称的时候将发现该计算机已经加入域内,并且在“administrative tools”中看到增加了一些新的工具,如“Active Directory Users and Computers”和“Active Directory Site and Services”等。到此为止,在W2K8E上的域控制器升级的工作则顺利完成。

\

在配置其它的Red Hat服务器之前,由于W2K8E也是DNS服务器,所以需要在该服务器上增加RHEL所有服务器的A(主机)记录和CNAME(别名)记录,这样所有的域内资源都可以域名的方式去访问。具体的方法是在W2K8E上点击“start”并点击“run”在出现的命令行对话框中输入“dnsmgmt.msc”以开启DNS管理配置工具。然后在jerrywjl.com下中的空白区域右键点击并选择“New Host(A or AAAA)”然后输入相应主机名和IP地址,分别是server.jerrywjl.com和squid.jerrywjl.com,再为server.jerrywjl.com建立别名ftp,mail。完成之后可以在命令行中执行nslookup进行解析测试,如果能够正向解析到所有记录则表明DNS和各种资源记录都没有问题。另外,如果需要的话可以在DNS管理配置工具中建立反向区域和添加相应的PTR指针与CNAME记录。

\

到此为止在W2K8E上的配置就暂时告一段落。下面开始配置第一台RHEL5U2服务器。 首先指定基本信息,包括指定本机的IP地址和DNS服务器地址,同时比较重要的是保证RHEL和W2K8E的系统时间吻合,因为在Kerberos的工作流程中需要依靠时间戳来作为认证流程中的一个重要依据。所以我制定一个简单的任务计划使RHEL作为时间客户端并每隔十分钟到服务器同步并校正自己的时钟。

\

下面将要修改/etc/krb5.conf文件。如果Linux系统要配置通过Kerberos认证,/etc/krb5.conf文件十分重要。该文件包含了所有的Kerberos的基本配置信息。其中所谓的realm也称为Kerberos的认证领域,在大多数情况下Kerberos认证领域和DNS解析到的域名空间一致,只不过在/etc/krb5.conf中Kerberos认证领域需要大写,所以该档修改之后的结果如下:

\
\[logging]\ default = FILE:/var/log/krb5libs.log\ kdc = FILE:/var/log/krb5kdc.log\ admin_server = FILE:/var/log/kadmind.log\\[libdefaults]\ default_realm = JERRYWJL.COM\ dns_lookup_realm = false\ dns_lookup_kdc = false\ ticket_lifetime = 24h\ forwardable = yes\\[realms]\ JERRYWJL.COM = {\  kdc = w2k8e.jerrywjl.com:88\  admin_server = w2k8e.jerrywjl.com:749\  default_domain = jerrywjl.com\ }\\[domain_realm]\ .jerrywjl.com = JERRYWJL.COM\ jerrywjl.com = JERRYWJL.COM\\[appdefaults]\ pam = {\   debug = false\   ticket_lifetime = 36000\   renew_lifetime = 36000\   forwardable = true\   krb4_convert = false\ }\
\

然后进入/etc/samba目录下,备份保存原有配置文件smb.conf,然后修改该配置文件并加入新的内容。其中security = ads指定该服务器的验证源为Windows 2003以上版本的域控制器,password server用于指定该域控制器的地址以及相应的Kerberos realm。

\
\security = ads\password server = 192.168.10.100\realm = JERRYWJL.COM\
\

接着是指定使用Winbind服务所需要的信息。

\

在下面的Winbind配置信息中的“idmap uid”和“idmap gid”指定了一个用户ID范围。该ID范围实际上是Winbind从Windows的活动目录中获取用户信息之后该用户和相应组在Linux系统的id值映像范围,“winbind enum users”和“winbind enum groups”表示是否通过winbind列举活动目录中的用户和组。而“winbind separator”则用于指定映像的用户或者是服务器名称和Kerberos realm名称之间所使用的连接符号,默认是\\,如果改成+则表示通过Winbind获得的用户名称格式是“DOMAIN + user”以及“DOMAIN + group”而“template homedir”,“template shell”表示通过winbind获得的域用户在Linux系统中登录所使用的主目录位置以及登录shell,“winbind use default domain”表示用户名称将以何种方式被转换或映射。所以我对Winbind部分的配置如下:

\
\idmap uid = 100000-200000\idmap gid = 100000-300000\winbind enum groups = yes\winbind enum users = yes\winbind separator = +\template homedir = /home/%D/%U\template shell = /bin/bash\winbind use default domain = no\
\

对该档保存退出。然后还要修改一个关键的配置文件/etc/nsswitch.conf,将原有的:

\
\passwd:      files \shadow:      files\group:       files \
\

修改为:

\
\passwd:      files winbind\shadow:      files\group:       files winbind\
\

表示该服务器在验证用户登录的时候,如果用户不在本地用户数据库中则会自动通过winbind来搜索windows活动目录内的用户数据库。/etc/nsswitch.conf是NSS即Name Service Switch的配置文件。

\

保存之后可以重新启动samba和winbind服务,并使这两个服务在系统启动的时候自动启动。然后以Windows管理员administrator的身份执行kinit命令以从W2K8E上获得Kerberos的TGT票据。方法是执行命令:

\
\# kinit administrator@JERRYWJL.COM         
\

该命令执行成功没有任何提示,但即说明了已经正常获得了TGT票据。这里比较常见的错误是会出现诸如“Clock skew too great while getting initial credentials”这样的信息,最有可能的原因是当前系统和KDC服务器的时间差过大而导致获取TGT票据失败,因此需要检查一下两台主机的时钟。

\

如果上述命令执行无误,可以执行命令net ads join将RHEL加入到Windows域内。

\
\# net ads join –U administrator              
\

加入成功之后要确保在当前系统上执行wbinfo –t无错误信息提示:

\
\# wbinfo –t                                  
\

命令wbinfo的功能是从winbind daemon中查询所获得的信息。而-t选项用于验证当前的Linux服务器加入到Windows域内之后是否在域内建立了可信任的服务器账号。如果有报错信息的话,可能需要重启一下winbind服务。

\

在正常完成上述操作之后,就可以通过wbinfo和getent命令获得从Windows活动目录上获得的用户与组的信息。所有获得的域内用户信息都是以Kerberos realm name + username/groupname的格式出现。这就意味这以后在使用这些Windows帐户登录系统或者服务的时候也要使用该格式。而且在W2K8E的组件“active directory users and computers”的computers中可以看到刚加入域的该服务器的信息。

\

最后为当前服务器增加Winbind作为的获得域内资源和进行认证的方式之一。具体方法是在终端执行“setup”并选择“Authentication Configuration”并在“User info”和“Authentication”中都选择使用Winbind,确认所有的信息之后进入下一步并点击“OK”即可,在完成之后winbind服务将会被自动重启。这样操作最终的结果是Windows中的管理员administrator可以JERRYWJL+administrator这个登录名在RHEL上登录成功。

\

pam_mkhomedir.so模块

\

pam_mkhomedir.so模块的主要作用是帮助用户在登录系统的时候自动创建家目录,并且随之生成该用户的profile文件。所以该模块在实现一些混合集中式认证的场景中会非常有用。因为当这些来自目录服务的用户登录成功之后,一般不会像本地用户登录那样可以自动创建自己的家目录。

\

所以来自目录服务的用户登录之后所做的一切操作和配置都无法永久保存,除非管理员手动地给这些登录用户创建家目录。不过目录服务中的用户的数量一般都非常大,手工操作的工作量和准确率可想而知。所以在这种情况下就需要使用pam_mkhomedir.so模块,当用户登录系统认证成功的时候,该模块会自动为用户创建家目录以及生成相关的profile文件。

\

我们可以结合上面的RHEL + Windows Server实现混和集中式认证的案例来继续说明pam_mkhomedir.so模块的用法。在上例中,当windows系统上的用户administrator成功地在RHEL上登录之后,唯一的问题是没有自己的主目录。这个时候终于轮到pam_mkhomedir.so模块出场了!

\

在这个方案中我们只需要简单修改PAM和系统用户登录的相关配置文件/etc/pam.d/system-auth并增加一行session的控制信息如下:

\
\session     required     pam_mkhomedir.so skel=/etc/skel umask=0022 silent\
\

表示用户登录之后会利用pam_mkhomedir.so这个pam模块自动建立自己的主目录,并会从/etc/skel目录下拷贝用户原始配置文件到所建立的目录中,当然所有的操作都会在后台执行。这个更改是实时生效的,也就是说更改完成之后无需重启任何服务,只要使用域内用户重新登录Linux系统即可发现与这个用户同名的主目录就能够自动建立在/home/JERRYWJL目录下。这样通过W2K8E活动目录认证用户访问RHEL资源的基本架构就已经搭建完成。

\

另外进行这个实验的用户应该可以发现,在当执行setup并选择使用winbind结合活动目录进行认证的时候,当完成这一步骤时系统会自动修改/etc/pam.d/system-auth文件,并在其中添加一个新的配置项——即使用pam_winbind.so进行用户验证用户,此模块专用于结合活动目录进行用户认证,并将获取的信息转换成Linux系统所能兼容的用户信息格式,所以整个的配置文件将变成:

\
\#%PAM-1.0\# This file is auto-generated.\# User changes will be destroyed the next time authconfig is run.\auth        required      /lib/security/$ISA/pam_env.so\auth        sufficient    /lib/security/$ISA/pam_unix.so likeauth nullok\auth        sufficient    /lib/security/$ISA/pam_winbind.so use_first_pass\auth        required      /lib/security/$ISA/pam_deny.so\\account     required      /lib/security/$ISA/pam_unix.so broken_shadow\account     [default=bad success=ok user_unknown=ignore] /lib/security/$ISA/pam_winbind.so\account     required      /lib/security/$ISA/pam_permit.so\\password    required      /lib/security/$ISA/pam_cracklib.so retry=3 type=\password    sufficient    /lib/security/$ISA/pam_unix.so nullok use_authtok md5 shadow\password    sufficient    /lib/security/$ISA/pam_winbind.so use_authtok\password    required      /lib/security/$ISA/pam_deny.so\\session     required      /lib/security/$ISA/pam_limits.so\session     required      /lib/security/$ISA/pam_unix.so\session     required     pam_mkhomedir.so skel=/etc/skel umask=0022 silent\
\

此时当用administrator登录Linux系统的时候会发现其主目录会自动建立。

\

pam_ldap.so模块

\

其实结合RHEL + Windows Server进行集中式认证的实现方法不仅仅只有winbind一种手段。
\使用Winbind的方法由于涉及到AD,Kerberos,Samba,Winbind这几方面内容,因而在有些用户看来配置过程似乎有些复杂。所以在今天的内容中,我们将完全放弃使用Samba和Winbind而将RHEL系统配置为一个具有更出色兼容性的LDAP客户端来实现类似的功能。

\

这种方案的原理实际上包括下面几个内容:

\

首先我们将Windows AD作为网络中的IDM系统,所有的用户信息,包括用户名,UID,口令,登录shell、有效期等等按理说都会存储在AD当中。

\

然后Linux系统会通过自身的应用程序编程接口和LDAP查询工具集来和AD服务器通讯并访问AD中的这些用户信息。

\

这种访问方式实际上应用到了Linux系统中的一个PAM模块——pam_ldap。pam_ldap这个模块类似于常见的pam_unix.so。一般来说如果Linux本地用户登录系统时会使用的pam_unix.so,该模块可以将用户登录时输入的用户名和密码与系统本地用户数据库/etc/passwd和/etc/shadow中所记录的信息进行核对,如果验证通过则允许登录;而pam_ldap模块的基本功能则是会以AD上某个固定用户的身份和口令连接到Windows DC上查询AD数据库(之所以要以一个固定用户身份执行这个操作时因为默认情况下,Windows AD是不允许以匿名方式查询和访问的,所以这个固定用户在AD上不但要确实存在,而且要具备足够的权限),另外pam_ldap.so在查询到AD中用户信息的同时也能对AD中加密的用户口令进行正确转换以实现身份认证,认证成功之后才允许用户登录或进行后续访问。

\

除了pam_ldap.so模块之外,Linux系统还提供了一个Open LDAP的工具集,其中最关键的工具和命令是ldapsearch,管理员通过手动执行该命令能够从AD上获得目录内的信息。这对于在配置的过程中进行调试和排错将相当有用。

\

当然在这个认证过程中为了保证密码之类的敏感信息在网络中传递的安全性,Linux客户端要和基于Windows操作系统的Kerberos KDC进行安全通讯,这样则必须要启用另外一个PAM模块——pam_krb5.so,它能够使Linux客户端以正确的实例从Kerberos KDC上获取TGT(票据授权票据),并通过一个完整的Kerberos认证过程来实现用户信息的安全传递。因为在默认情况下Windows的域控制器和Kerberos的KDC是集成于一体的。

\

另外,当使用pam_ldap查询到用户信息时,还有一个非常关键的问题需要解决,这就是Windows AD中存储的用户信息和Linux Open LDAP所存储的用户信息格式和名称是不统一的。如果大家曾经看过我以前介绍Open LDAP的文章则很容易建立这样的概念:所有的LDAP服务器实际上基本上都在使用LDAP(轻量级目录访问协议),这个协议规定了LDAP中的所有信息都必须有一个命名方式,而命名方式归根结底是由架构档,即schema所决定。由于历史方面的问题,我们实在无法保证不同的目录服务产品都是用统一的架构档,这就意味着很有可能在Windows AD的目录服务器上添加的用户的一些属性虽然能够被Linux系统的LDAP查询工具和应用程序编程接口获得,但无法通过NSS名字转换服务转换成用户登录或者其它服务所需要的属性。

\

举一个简单的例子:假如我们在Linux系统中添加一个本地用户或者在Linux的Open LDAP中添加一个用户,这个用户如果要能够登录到Linux系统则必须包括用户名、用户ID(UID)、用户家目录、默认登录Shell以及用户口令等基本信息。这样当用户登录的时候,Linux系统的名称服务(NSS)会将用户转换成UID来验证用户身份,验证成功方可登录。但是在Windows系统中,显然我们不能指望所添加的用户具有和Linux系统上一样的用户属性和信息,无法获得这些信息将意味着无法登录Linux系统。

\

所以这个时候我们不但要使用pam_ldap,还要使用一个叫做nss_ldap的应用程序扩展库来实现将Windows AD用户的架构所定义的一些属性和Linux Open LDAP架构所定义的,用户登录必须的属性进行一一映像。(实际上pam_ldap和nss_ldap分别是RHEL系统中一个叫做nss_ldap的安装包提供的pam模块扩展和应用链接库扩展,所以在RHEL系统中必须确保已经安装了nss_ldap包。)而映射的方法也很简单,因为nss_ldap提供了一个配置文件/etc/ldap.conf,该档可以根据需求更改。我们只需要按照其规定的格式指定两种目录产品的用户属性对应关系即可。

\

那么这种对应包括:

\

将一些Windows AD的schema所定义的用户属性名称映像为Linux Open LDAP schema所拥有的用户属性。例如在Linux的Open LDAP中UID对应Windows AD中的sAMAccountName,当然类似这样的映射还有很多;
\这也是比较大的问题:Linux Open LDAP的架构中规定的一些用户属性实际上在Windows AD上是根本不存在的,针对这个问题相信诸位已经想到了方法,就是通过一些特殊的方式来扩展Windows AD上的用户属性。

\

所以在Windows系统上实际上已经提供了一个这样的工具,叫做SFU(Service For Unix)。该工具在W2K3E的R2版本上已经被加入到标准安装光盘中。而在W2K3E的非R2版本上也可以通过手动安装来实现其功能。这是一个在Windows系统上专门对Unix系统访问提供支持的组件。通过SFU可以将某些Windows操作系统集成到现有的UNIX环境中。它提供的组件可以简化跨UNIX和Windows平台的网络管理和用户管理。

\

SFU为Interix提供了一个完整的高性能的UNIX环境,这个环境包含诸如csh或ksh的UNIX shell、数百款工具和实用程序,以及一整套完整的开发工具和库(通过它们可以在Interix子系统中连接并使用基于 UNIX 的应用程序)。而SFU具体功能包括:

\

与Unix相结合,可将Unix的执行和客户机环境应用到Microsoft Windows主机上,反之亦然;
\可用于访问网络文件系统(NFS)的服务和命令。这样UNIX和Windows环境之间就可以共享档;
\可访问UNIX和Windows系统上的帐户和密码服务(PCNFS、NIS以及LDAP)的用户映像服务;
\用于Windows和UNIX环境的“一次性注册”功能,UNIX和Windows操作系统之间可以对密码进行同步并映像认证证书;
\在功能齐全的UNIX环境中的Windows计算机上执行UNIX shell脚本和应用程序的功能;

\

目前SFU比较成熟的版本是3.5。在W2K3E的AD安装完成之后通过“添加/删除程序”可以方便地安装。所以有了SFU,我们可以扩展AD目录信息的schema,以方便和Linux系统的集成。

\

下面我们将要开始配置和实现该环境。按照我们的惯例,在开工之前需要完整说明一下当前的实验拓扑结构和操作目标:

\

我将在一台W2K3E R2的操作系统上部署AD的域控制器,并且安装和部署SFU作为IDM服务器。该服务器的IP地址是192.168.10.10,全称域名(FQDN)为:winsrv.example.com。由于域控制器需要DNS服务支持,所以在该服务器升级为域控制器之前必须先安装和配置Windows的DNS管理器。另外有一台RHEL 5.2的操作系统,IP地址是192.168.10.20,该服务器将DNS指向Windows服务器,其FQDN为:linsrv.example.com,这样该服务器将获得DNS支持。

\

要实现的目标是管理员在域控制器上添加用户或组信息能够在RHEL系统上查询到。而且该用户能够在RHEL系统上登录。

\

首先是在Windows服务器上构建DNS服务器与域控制器。注意:我这里使用的是W2K3E的R2版本,该版本有两张安装光盘。在Windows系统上部署DNS和域控制器的方法这里不再赘述。

\

在Windows的AD部署完成之后需要安装SFU,在W2K3E的R2版本上,SFU是系统自带的组件。可以通过在Windows系统中点击“Start”-\u0026gt;“Run”并运行“appwiz.cpl”来开启“Add/Remove Rrogram”,接着选取“Add/Remove Windows Components”,双击选取其中的“Active Directory Services”以及其子项“Identity Management for Unix”和该项中的所有子项,其中包括“Administration Components”,“Password Synchronization”和“Service for Unix”并最终选择“OK”和“Next”以执行安装,同时在安装的过程中按照提示插入第二张光盘即可,由于要安装的软件比较大,所以安装时间将比较长,在安装完成之后按要求重启域控制器。

\

在域控制器重启之后,需要验证SFU是否成功安装。点击“Start”-\u0026gt;“Administrative Tools”-\u0026gt;“Microsoft Identity Management for Unix”,此时应该能够成功开启IDM的接口以及看到SFU,所以SFU实际上是Windows实现跨系统平台的IDM的组件之一。接着,需要开启“Active Directory Users and Computers”并选取其中任何一个OU中的用户或者组并右键点击“properties”,这时可以发现在它的属性中多出一个叫做“UNIX Attributes”的用户属性,并包含了“NIS Domain”、“UID”等只有Linux系统上才有的一些用户属性。这样即证明SFU已经安装成功。

\

实际上在W2K3E R2的版本上安装SFU最大的好处就是基本上不需要用户进行任何手动的配置,所有SFU配置采用默认即可。但是其实会有很多用户还在使用W2K3E的早期版本,在这个版本上SFU并没有作为一个默认的系统组件被加入,那么则需要从微软的官方网站去下载SFU的安装包执行手动安装和配置,这是一个200多MB的自解压安装程序,文件名是SFU35SEL_EN.exe。双击运行之后会默认解压到系统的c:\\documents and settings\\administrator\\local settings\\temp目录中,进入该目录执行setup.exe文件将看到SFU的手动安装接口,点击“Next”之后在“Username”和“Organization”处输入执行安装的用户和组织,这里我用“administrator”和“example.com”,接受协议并点击“Next”,在新打开的接口上选择“Standard Installation”并进入下一步。在出现的对话框中选择“Enable suid behavior for Interix programs”和“Changing Default Behavior to Case Sensitivity”这两个选项。

\

其中“Enable suid behavior for Interix programs”表示根据POSIX标准,在系统执行某文件时,该档有权限包含用于设置UID(setuid)和GID(setgid)的位。如果某档中设置了任一个位或同时设置了这两个位,则执行该档时的进程可获得它的UID或GID。如果使用得当,此机制允许非特权用户执行那些只允许具有更高权限的档管理器拥有者或组运行的程序。但如果使用不当,由于此行为允许非特权用户执行仅应由管理员来执行的操作,则因此可能带来安全隐患。所以默认情况下 Windows Services for UNIX 安装程序不启用对此机制的支持。但为了后续获得更好的兼容性,目前我们应该启用对setuid行为的支持。因此勾选该选项。

\

另外一个选项“Changing Default Behavior to Case Sensitivity”表示系统要求用户选择是否将对象名称(比如文件名)的默认行为更改为区分大小写。这个选择将影响到系统的安全性以及Windows Services for UNIX的作用原理。因为对于Windows系统大多数对象(比如文件和目录)的名称都是保留大小写、但不区分大小写的。所以在同一目录中不能同时存在名称为例如sample.txt和Sample.txt的两个档,原因是 Windows在识别档时会将它们看作是同一档。但是UNIX操作系统是完全区分大小写的。所以对象名称之间只要存在对象名称字符大小写的差异时UNIX系统就可区分它们,这样像sample.txt和 Sample.txt就可以出现在同一目录中并且UNIX系统对这些文件执行操作时可以区分它们。例如执行命令rm S*.txt可以删除Sample.txt,但不会删除sample.txt。为实现典型的UNIX行为,Server for NFS和Interix子系统在处理名称时一般都是区分大小写的。

\

此行为可能会带来安全问题,特别是对于已习惯Windows中不区分大小写的用户。例如黑客可能将木马版本的edit.exe以EDIT.EXE的名称保存在与原始档所处的同一目录中。如果用户在 Windows命令提示符下键入edit,则可能不执行标准版本、而是执行木马版本的(EDIT.EXE)。在启用区分大小写功能之前,Windows用户应意识到这类问题。

\

对于Windows XP(专业版)和Windows Server 2003系列操作系统,除了Win32子系统以外,其它子系统的默认行为均保留大小写但不区分大小写。在先前版本的Windows中,这类子系统默认情况下是完全区分大小写的。为支持标准的UNIX行为,在安装(用于安装Interix 子系统的)基本实用程序或Server for NFS时,Windows Services for UNIX安装程序允许您更改Windows XP和Windows Server 2003系列操作系统的非Win32子系统的默认行为。如果您启用了区分大小写的功能,但随后又卸除了Server for NFS和基本实用程序,则Windows Services for UNIX安装程序将恢复非Win32子系统的默认行为,即不区分大小写。这里仍然是选择该选项。 之后的另一个接口是配置用户名称映像:

\

用户名称映像起单一clearinghouse的作用,它提供了集中的用户映像服务。用户名称映像允许在Windows 与UNIX的用户和组帐户之间创建映射。

\

用户名称映像允许为整个企业维护单一的映像数据库。此特性大大方便了为多个运行Windows Services for UNIX的计算机配置验证的过程。除Windows与UNIX用户和组帐户之间一对一的映像之外,用户名称映像还允许创建一对多的映射,即可以将多个Windows账户与一个UNIX账户关联起来。此特性非常有用,举例来讲,它让管理员可以不必为个别用户维护多个分立的UNIX账户,而只使用少数几个账户提供不同类的访问权限即可。管理员可以使用简单映射,通过完全相同的名称将Windows与UNIX账户映射起来。当然管理员也可以创建高级映射,通过不同的名称将Windows与UNIX账户关联起来。可以将之与简单映射配合使用。

\

但在我们的实验中实际上和“Network Information Serivce(NIS)”没有任何关系。所以点击下一步之后不需要输入任何“NIS Domain”和“NIS Server Name”再次点击下一步之后完成SFU的安装。在SFU安装完成之后,同样可以像在R2系统那样通过“Administration Components”看到SFU的接口以及用户属性中的“UNIX Attributes”。然后重启系统以完成整个安装。最后在Windows的cmd接口中执行services.msc开启服务管理器,并确保其中的Server for Nis这个服务的“Start Type”为“Automatic”并且“Status”为“Started”。

\

下面开始进行RHEL5系统上的配置。

\

首先完成RHEL5基本环境的设置,包括配置IP地址,关闭和禁用防火墙以及SELinux,在/etc/resolv.conf中将DNS服务器指向Windows服务器,并且调整系统时间尽量和Windows服务器一致。这里值得一提的是,因为后续要使用到Kerberos认证,所以时间精确度的要求实际上将非常高。

\

由于我在内网中没有一个标准的事件源,所以此采用一种简单的方法,即将Windows服务器作为NTP服务器,并且在RHEL系统上制定一个任务计划,使用ntpdate每隔一分钟和Windows系统对时一次即可。另外要确保在RHEL系统中安装以下软件包,尤其是openldap-servers,nss-ldap这两个包和一些Kerberos的相关包。所需要的包全部在安装光盘中提供,openldap-servers、openldap、nss_ldap、mozldap、php-ldap、openldap-clients、python-ldap、openldap-devel以及krb5-devel、krb5-libs、pam_krb5、krb5-auth-dialog、krb5-workstation,挂载光盘切换到Server目录下执行rpm -ihv进行安装即可。 完成上述的几个步骤,RHEL5操作系统的基本环境配置即告一段落。

\

现在开始配置RHEL5作为Windows的AD和Kerberos KDC的客户端以及成员服务器,在终端执行命令“setup”并选择其中的“Authentication Configuration”并运行,然后选择“Use LDAP”,“Use Shadow Passwords”以及“Use Kerberos”并继续,在出现的“LDAP Setting”中填入LDAP方面的设置信息并执行下一步进入“Kerberos Setting”,分别如图设置Kerberos的“Realm”,“KDC”和“Admin Serer”。完成之后就可以退出该接口了。所有的设定实际上会写入到配置文件/etc/krb5.conf档中,该档内容如下:

\
\[logging]\ default = FILE:/var/log/krb5libs.log\ kdc = FILE:/var/log/krb5kdc.log\ admin_server = FILE:/var/log/kadmind.log\ \[libdefaults]\ default_realm = EXAMPLE.COM\ dns_lookup_realm = false\ dns_lookup_kdc = false\ ticket_lifetime = 24h\ forwardable = yes\ \[realms]\ EXAMPLE.COM = {\  kdc = 192.168.10.10:88\  admin_server = 192.168.10.10:749\  default_domain = example.com\ }\ \[domain_realm]\ .example.com = EXAMPLE.COM\ example.com = EXAMPLE.COM\ \[appdefaults]\ pam = {\   debug = false\   ticket_lifetime = 36000\   renew_lifetime = 36000\   forwardable = true\   krb4_convert = false\ }\
\

在完成了Kerberos客户端配置之后,执行kinit命令来从Windows的KDC上初始化TGT(票据授权票据)。Kerberos是一个非常复杂的安全协议,关于Kerberos的原理和工作流程,请参照上例,这里就不再赘述。命令为:

\
\# kinit administrator@EXAMPLE.COM      \
\

如果能够初始化TGT成功则不会有任何提示,当然前提是两台系统的时间必须要保证准确。同时要修改/etc/pam.d/system-auth,配置RHEL5系统使用pam_ldap接口从Windows的域控制器上查询用户信息,下面是我修改之后的信息:

\
\#%PAM-1.0\# This file is auto-generated.\# User changes will be destroyed the next time authconfig is run.\auth        required      pam_env.so\auth        sufficient    pam_unix.so nullok try_first_pass\auth        requisite     pam_succeed_if.so uid \u0026gt;= 500 quiet\auth        sufficient    pam_ldap.so use_first_pass\auth        sufficient    pam_krb5.so use_first_pass\auth        required      pam_deny.so\ \account     required      pam_unix.so broken_shadow\account     sufficient    pam_ldap.so\account     sufficient    pam_succeed_if.so uid \u0026lt; 500 quiet\account     [default=bad success=ok user_unknown=ignore] pam_krb5.so\account     required      pam_permit.so\ \password    requisite     pam_cracklib.so try_first_pass retry=3\password    sufficient    pam_ldap.so use_authok\password    sufficient    pam_unix.so md5 shadow nullok try_first_pass use_authtok\password    sufficient    pam_krb5.so use_authtok\password    required      pam_deny.so\ \session     optional      pam_keyinit.so revoke\session     required      pam_limits.so\session     sufficient    pam_mkhomedir.so skell=/etc/skell\session     [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid\session     required      pam_unix.so\session     optional      pam_krb5.so\
\

在这个档中实际上增加了下面的四行,这里所增加的内容表示只要在LDAP服务器中确定该用户合法身份以及验证密码无误之后则允许登录系统,并且允许登录时自动创建该用户的主目录。不难看出,实际上这步操作是在为用Windows AD用户直接登录RHEL系统做准备。

\
\auth        sufficient    pam_ldap.so use_first_pass\account     sufficient    pam_ldap.so\password    sufficient    pam_ldap.so use_authok\session     sufficient    pam_mkhomedir.so skell=/etc/skell\
\

而且除了pam_ldap.so模块之外,我们还将发现通过Kerberos进行认证的部分实际上调用了另外一个pam模块pam_krb5.so。Kerberos进行票据验证的细节将主要通过该模块来进行。所以这个模块的被自动添加到system-auth文件中,从很大程度上屏蔽了认证实现的复杂度。

\

下面就到了最重要的一个步骤:如果要将RHEL作为Windows域控制器的成员服务器或者客户端并且要确保从Windows AD上获得的用户信息可以在Linux系统上登录,就必须要修改/etc/ldap.conf,该档是由nss_ldap包所提供。该文件中首先会指定LDAP服务器的ip地址、主机名等基本信息,然后会指定将以LDAP Server中的哪个用户的身份和口令绑定到LDAP服务器上执行搜索和查询(Windows域控制器默认情况下不允许执行匿名搜索),另外也指定了该用户在LDAP服务器上能够搜索和查询的范围,最后也是最关键的,就是在该档中将设置从LDAP服务器上获取的用户属性和Linux系统上用户属性的映像关系和映像图。

\

前面曾经讲到,由于两种目录服务的命名架构不同,Windows AD上可登录的用户信息属性与类型和Linux上用于登录的用户信息属性与类型存在很大的不同,而且关键是Linux系统上用于登录的用户信息属性与类型在Windows AD上绝大部分是默认不存在的。那么之所以安装SFU是基于这需求扩展了Windows AD上的架构,实际上则增加了用户信息属性与类型,但是扩展之后属性与类型的名称和Linux系统上的仍然有所不同,这个时候就需要在该档中按照格式进行手工的映像了。

\

修改/etc/ldap.conf檔,像刚才解释的那样,首先确定使用Windows AD上的哪一个用户和密码登录Windows域进行搜索,所以在该文件中增加下面的信息:

\
\base dc=example,dc=com\binddn cn=administrator,cn=Users,dc=example,dc=com\bindpw redhat\
\

显而易见,这里我使用的是Windows AD上的系统管理员administrator和对应的密码redhat,该用户毋庸置疑在Windows系统以及域环境中已经具备了足够的权限。而搜索的范围自然也是整个的example.com这个域。

\

之后定义该用户所在LDAP服务器,在这里当然是Windows的域控制器,添加:

\
\uri ldap://192.168.10.10/\
\

最后也是最关键的配置部分是手工定义我刚才提到的这些映射关系,添加下面的这些配置信息:

\
\\host winsrv.example.com\nss_base_passwd cn=Users,dc=example,dc=com?one\nss_base_shadow cn=Users,dc=example,dc=com?one\nss_base_group cn=Group,dc=example,dc=com?one\nss_map_objectclass posixAccount User\nss_map_objectclass shadowAccount User\nss_map_objectclass posixGroup Group\nss_map_attribute uid sAMAccountName\nss_map_attribute cn sAMAccountName\nss_map_attribute homeDirectory unixHomeDirectory\nss_map_attribute shadowLastChange pwdLastSet\nss_map_attribute uniqueMember member\nss_map_attribute gecos cn\\pam_login_attribute sAMAccountName\pam_filter objectclass=User\nss_base_passwd cn=Users,dc=winsrv,dc=example,dc=com\pam_password ad\
\

上面的信息实际上就是一组标准的NSS信息的映像关系。其中:

\
\host winsrv.example.com用于指定LDAP服务器的主机名或者FQDN;\nss_base_passwd cn=Users,dc=example,dc=com?one\nss_base_shadow cn=Users,dc=example,dc=com?one\nss_base_group cn=Group,dc=example,dc=com?one\
\

表示客户端会根据/etc/nsswitch.conf文件中所定义的搜索顺序,到cn=Users,dc=example,dc=com这个范围中搜索相应的用户、组以及密码信息。这里的dc=com?one我认为应该是一个满足格式兼容所需要做的一个转换而已。而剩下的这一堆结构如“nss_map_objectclass A B”和“nss_map_attribute A B”这一类的信息相信大家已经能够猜到。实际上是Windows AD和Linux LDAP中的用户属性对应关系,其中A表示的是Linux系统中的用户属性名称,而B表示在Windows中的用户属性名称。例如在Linux的LDAP服务器上,每添加一个用户,它都需要有一个叫做“homeDirectory”的属性并且建立相应的主目录以满足登录需求,而如果在Windows的AD上,则可能通过安装SFU扩展出一个叫做unixHomeDirectory的属性,其作用和Linux LDAP的homeDirectory属性是大致一样的,只不过叫法不同而已。但名称不同则必须要在/etc/ldap.conf中明确指定,这样Linux系统上的名称服务才能够正确翻译来自Windows AD的用户属性。而只有正确翻译了这些相关的用户属性,Windows AD上的用户才可以在RHEL上登录。剩下的如pam_filter objectclass=Use,表示PAM模块只会对user这个对象类型执行相关操作,而pam_password ad表示如果pam_ldap.so模块对用户密码进行转换和识别,将使用AD的相关协议来进行。

\

为了更好地理解这个难点,我们完全可以使用另外一种方法来尝试直接从Windows的域控制器上获得AD中的用户信息,看看Windows AD域的用户都有哪些自己认为标准的属性和类型。这里将以windows上的administrator用户身份来直接搜索Windows AD目录,使用的就是上面提到的ldapsearch命令,这是Open LDAP工具集中的命令之一。

\

执行下面的命令:

\
\# ldapsearch -x -b \"dc=example,dc=com\" -D \"cn=administrator,cn=users,dc=example,dc=com\" -w \"redhat\" -h 192.168.10.10 | grep Administrator\\
\

从输出的Windows用户属性和类型信息中,大家应该能够理解这种对应关系的作用以及我们刚才的做法了。

\
\# Administrator, Users, example.com\dn: CN=Administrator,CN=Users,DC=example,DC=com\cn: Administrator\distinguishedName: CN=Administrator,CN=Users,DC=example,DC=com\memberOf: CN=Administrators,CN=Builtin,DC=example,DC=com\name: Administrator\sAMAccountName: Administrator\uid: Administrator\msSFU30Name: Administrator\unixHomeDirectory: /home/Administrator\
\

最后我们来看一下,如何在Windows的AD上添加用户并使该用户可以在Linux系统中看到和直接登录。

\

在执行这个操作前,还是首先使用administrator用户进行一个测试,即只为Windows中的administrator用户添加Unix属性。在Windows域控制器上选择“Start”-\u0026gt;“Administrative Tools”-\u0026gt;“Active Directory Users and Computers”,然后右键选择用户“Administrator”点击“Properties”并切换到“UNIX Attribute”,在该标签中定义“NIS Domain”,“UID”(可以自行指定),“Login Shell”,“Home Directory”,最后一定要指定一个“Primary Group Name”(往往和UID一致),并选择“Apply”。这样即定义了该Windows用户的Unix属性。

\

而此时在RHEL系统上,可以立刻执行命令getent passwd命令,如果一切顺利,如图所示我们将看到Windows系统上的用户Administrator用户,由于用户的信息只要满足登录需求就能被getent所显示出来,所以如果直接用该用户登录RHEL系统也能够成功!

\

既然这样,如果在Windows的“Active Directory Users and Computers”中选择“example.com”的“users”容器,并添加两个用户tuser1和tuser2,并像刚才那样定义好“UNIX Attribute”之后,在RHEL上一样可以看到该用户信息并且甚至可以直接登录系统成功。

\

尽管实验到此算是实现了我们需要的结果,不过不知道各位读者是否看出一些问题?对了!相信大家已经发现,在/etc/ldap.conf所定义的所有映像关系中似乎并没有见到什么通过安装SFU所扩展出来的特殊属性。这个问题实际上是因为在W2K3E的R2版本中已经整合了SFU,所以SFU的架构或者扩展属性已经加入到Windows AD的默认LDAP架构和属性中,故并不需要在/etc/ldap.conf中再进行额外的指定。但目前仍然有很多用户在使用W2K3E的非R2版本,并且要像我上面所提到的那样下载和完全手动安装SFU,这样通过SFU获得的扩展架构和属性就不是Windows AD的默认架构和属性,于是这样就需要在/etc/ldap.conf中去手动定义这些属性和Linux LDAP的属性相对应。因此,如果各位在使用W2K3E的非R2版本,那么在/etc/ldap.conf文件的目录属性映像应该更改为下面的样子:

\
\nss_base_passwd cn=Users,dc=example,dc=com?one\nss_base_shadow cn=Users,dc=example,dc=com?one\nss_base_group cn=Group,dc=example,dc=com?one\nss_map_objectclass posixAccount User\nss_map_objectclass shadowAccount User\nss_map_objectclass posixGroup Group\nss_map_attribute uid sAMAccountName\nss_map_attribute uidNumber msSFU30uidNumber\nss_map_attribute gidNumber msSFU30gidNumber\nss_map_attribute cn sAMAccountName\nss_map_attribute uniqueMember member\nss_map_attribute homeDirectory msSFU30homeDirectory\nss_map_attribute loginShell msSFU30LoginShell\nss_map_attribute gecos cn\\pam_login_attribute sAMAccountName\pam_filter objectclass=User\nss_base_passwd cn=Users,dc=winsrv,dc=example,dc=com\pam_password ad\\
\

这样在W2K3E的非R2版本中通过上述操作也一样可以在Linux中看到所有的Windows目录中的用户信息和组信息。

\

总结

\

通过以上几篇文章对PAM的基本概念,工作原理和十几个常用模块的在实际应用环境当中的举例讲解,相信大家对可插拔认证模块已经有了一定的了解和认识。 由于时间和篇幅的关系,本人不可能将所有模块的用法都一一举例,所以希望对此有兴趣的朋友通过充分的实验来充分掌握其原理与细节,并通过活学活用PAM来实现系统安全的有效增强。

\

作者简介

\

王基立,现工作于红帽软件(北京)有限公司,具备多年的售前解决方案规划与售后技术支持经验,熟悉红帽所有平台类产品和解决方案。现常驻深圳任红帽软件华南区解决方案架构师一职,主要负责红帽解决方案在华为、中兴等大型电信企业用户环境中的设计、规划、应用以及相关售前工作。同时也为包括各高级分销商以及金融、政府、教育等各方面在内的管道和区域用户提供相关解决方案、技术咨询、技术培训、现场实施、技术支持等服务。

\

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ)或者腾讯微博(@InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值