Python Tacacs故障诊断记录

2023-12-13 05:19:42

背景:客户现场说我们的设备在3A鉴权时失败,没有认证成功
第一步,先看下我们log
在这里插入图片描述
没有明显的错误记录,貌似认证成功了但是确提示认证失败,有点迷
第二步,家里搭建和现场一致的环境,模拟登录发现是可以进行3A认证的,继续迷
第三步,对比差异,家里用的Cisco的3A进行验证的,客户使用的是一家国内品牌做的3A鉴权服务,那问题可能出现在这里
第四部,继续对比差异,将两者的鉴权结果进行对比
国内3A品牌
国内品牌
Cisco 3A
在这里插入图片描述
差异不大,就一个Flag差异,对方说该Flag没有含义,但是作为程序员的Tiger表示怀疑。。。。

看代码。
下面是我们的程序,看起来没啥问题直接调用的Python库进行验证,目前看来valid字段被置为False了

cli = TACACSClient( data_server.encode("ascii"),
					data_port,
					data_secret.encode("ascii"),
					timeout=10,
					family=socket.AF_INET)
authen = cli.authenticate(username.encode("ascii"), password.encode("ascii"))
if authen.valid:
    verified = True

我们的代码没啥问题,继续跟踪Python库的代码,查看authenticate是如何实现的

def authenticate(self, username, password, priv_lvl=TAC_PLUS_PRIV_LVL_MIN,
                     authen_type=TAC_PLUS_AUTHEN_TYPE_ASCII,
                     chap_ppp_id=None, chap_challenge=None,
                     rem_addr=TAC_PLUS_VIRTUAL_REM_ADDR, port=TAC_PLUS_VIRTUAL_PORT):
        """
        Authenticate to a TACACS+ server with a username and password.

        :param username:
        :param password:
        :param priv_lvl:
        :param authen_type:    TAC_PLUS_AUTHEN_TYPE_ASCII,
                               TAC_PLUS_AUTHEN_TYPE_PAP,
                               TAC_PLUS_AUTHEN_TYPE_CHAP
        :param chap_ppp_id:    PPP ID when authen_type == 'chap'
        :param chap_challenge: challenge value when authen_type == 'chap'
        :param rem_addr:       AAA request source, default to TAC_PLUS_VIRTUAL_REM_ADDR
        :param port:           AAA port, default to TAC_PLUS_VIRTUAL_PORT
        :return:               TACACSAuthenticationReply
        :raises:               socket.timeout, socket.error
        """
        start_data = six.b('')
        if authen_type in (TAC_PLUS_AUTHEN_TYPE_PAP,
                           TAC_PLUS_AUTHEN_TYPE_CHAP):
            self.version_min = TAC_PLUS_MINOR_VER_ONE

            if authen_type == TAC_PLUS_AUTHEN_TYPE_PAP:
                start_data = six.b(password)

            if authen_type == TAC_PLUS_AUTHEN_TYPE_CHAP:
                if not isinstance(chap_ppp_id, six.string_types):
                    raise ValueError('chap_ppp_id must be a string')
                if len(chap_ppp_id) != 1:
                    raise ValueError('chap_ppp_id must be a 1-byte string')
                if not isinstance(chap_challenge, six.string_types):
                    raise ValueError('chap_challenge must be a string')
                if len(chap_challenge) > 255:
                    raise ValueError('chap_challenge may not be more 255 bytes')
                start_data = (
                    six.b(chap_ppp_id) +
                    six.b(chap_challenge) +
                    md5(six.b(
                        chap_ppp_id + password + chap_challenge
                    )).digest()
                )
        with self.closing():
            packet = self.send(
                TACACSAuthenticationStart(username, authen_type, priv_lvl,
                                          start_data, rem_addr=rem_addr, port=port),
                TAC_PLUS_AUTHEN
            )
            reply = TACACSAuthenticationReply.unpacked(packet.body)
            logger.debug('\n'.join([
                reply.__class__.__name__,
                'recv header <%s>' % packet.header,
                'recv body <%s>' % reply
            ]))
            if authen_type == TAC_PLUS_AUTHEN_TYPE_ASCII and reply.getpass:
                packet = self.send(TACACSAuthenticationContinue(password),
                                   TAC_PLUS_AUTHEN,
                                   packet.seq_no + 1)
                reply = TACACSAuthenticationReply.unpacked(packet.body)
                logger.debug('\n'.join([
                    reply.__class__.__name__,
                    'recv header <%s>' % packet.header,
                    'recv body <%s>' % reply
                ]))
                if reply.flags == TAC_PLUS_CONTINUE_FLAG_ABORT:
                    reply.status = TAC_PLUS_AUTHEN_STATUS_FAIL

        return reply

解读代码,因为我们的代码是直接调用插件的,所以没有设置的值使用的是默认值
authen_type=TAC_PLUS_AUTHEN_TYPE_ASCII所以默认的授权类型是使用ASCII进行的,

 def authenticate(self, username, password, priv_lvl=TAC_PLUS_PRIV_LVL_MIN,
                     authen_type=TAC_PLUS_AUTHEN_TYPE_ASCII,
                     chap_ppp_id=None, chap_challenge=None,
                     rem_addr=TAC_PLUS_VIRTUAL_REM_ADDR, port=TAC_PLUS_VIRTUAL_PORT):

所以可以看的当我们使用ASCII进行验证的时候会有以下流程
Authentication Start:认证开始时,客户端发送START消息,START消息中包括认证类型,同时可能包括用户名和一些认证数据。
Authentication Reply:REPLY消息是TACACS+服务器向客户端发送的唯一一种Authentication 消息,用于向客户端反馈当前认证的状态。
Authentication Continue:客户端收到REPLY消息后,如果确认认证过程没有结束,使用CONTINUE消息应答。
user_msg字段用于回应REPLY消息中的server_msg字段,向服务器提供客户端或用户的一些信息
flags字段用于中断认证过程

这是标准的ASCII认证。START包可能包含用户名。如果用户不包含用户名,那么服务器必须通过一个CONTINUE包的TAC_PLUS_AUTHEN_STATUS_GETUSER从客户端获取它。如果用户没有提供用户名,那么服务器可以发送另一个TAC_PLUS_AUTHEN_STATUS_GETUSER请求,但是服务器必须限制允许的重试次数,建议限制为三次。当服务器获得用户名时,它将使用带有TAC_PLUS_AUTHEN_STATUS_GETPASS的CONTINUE获得密码。ASCII登录使用user_msg字段作为用户名和密码。START和CONTINUE包中的数据字段都不用于ASCII登录,任何内容都必须被忽略。该会话由一个单独的START,接着是零个或更多对REPLYs,然后CONTINUEs,最后是一个是表示PASS、FAIL或ERROR的回复。

所以建议国内的厂商还是要多查看下协议,被认为没有用的flag字段其实是有用的,在
Authentication Continue的过程中可以用来阻断认证的,那么我们看下代码里面是否有呢

if authen_type == TAC_PLUS_AUTHEN_TYPE_ASCII and reply.getpass:
                packet = self.send(TACACSAuthenticationContinue(password),
                                   TAC_PLUS_AUTHEN,
                                   packet.seq_no + 1)
                reply = TACACSAuthenticationReply.unpacked(packet.body)
                logger.debug('\n'.join([
                    reply.__class__.__name__,
                    'recv header <%s>' % packet.header,
                    'recv body <%s>' % reply
                ]))
                if reply.flags == TAC_PLUS_CONTINUE_FLAG_ABORT:
                    reply.status = TAC_PLUS_AUTHEN_STATUS_FAIL

可以看到当TACACSAuthenticationContinue过程中如果响应的flag被置上的话status的值是为fail的
那么回过头看下valid是如何取值的呢
可以看的它是做了一次运算的,结果可以知道valid返回的是False,所以鉴权失败

@property
    def valid(self):
        return self.status == TAC_PLUS_AUTHEN_STATUS_PASS

解决方案:
第一步让厂商吧该字段置为0;
第二步我们自己的代码应该根据灵活性,在做验证时可以灵活选择鉴权方式;

#authen_type TAC_PLUS_AUTHEN_TYPE_ASCII,TAC_PLUS_AUTHEN_TYPE_PAP, TAC_PLUS_AUTHEN_TYPE_CHAP
authen = cli.authenticate(username.encode("ascii"), password.encode("ascii"),authen_type=authen_type)
if authen.valid:
     verified = True

Ok,故障诊断完毕,支持结束。

文章来源:https://blog.csdn.net/qq_34399969/article/details/134876881
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。