Python Tacacs故障诊断记录
背景:客户现场说我们的设备在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,故障诊断完毕,支持结束。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!