OpenVPN 对接 Radius 实现强制下线的坑

之前介绍了 OpenVPN 通过 txradius 的插件模块来对接 Radius, 有人反映无法实现强制下线功能。

因为这里面其实有个坑,导致这个功能有点鸡肋,一直没说,要实现 Radius 强制下线,必须实现 COA 的协议,这个我在 txradius 插件里其实已经实现了,可以在启动时监听 udp 3799 端口接收 Radius 的授权消息,而在 OpenVPN 这里是通过 管理服务来做用户断开的,因此需要把 OpenVPN 的管理服务启动,在内部回环地址监听即可,txradius 插件会在收到 Radius 强制下线消息后调用 OpenVPN 的 本地telnet接口服务来断开用户。

但是问题接着来了,命令发出去了,却仍然踢不掉用户,原因是 OpenVPN 客户端和服务端之间采用了 keepalive 机制,会不断重连,如果不用 keepalive 机制,连接又会不稳定。

如果你有什么好办法,请告诉我。

基于 Twisted 的 RouterOS API

Mikrotik 的 api 还真是写的专业,一开始看着真不习惯,和我过去用过的,设计的 api 差异太大,这样的设计有其原因,看起来是对本身 RouterOS 命令行模块的高度复用,如果对 RouterOS 的命令行工具以及脚本机制很熟的话,会很容易上手,不过恰巧一开始我都不熟悉,像懒人一样用winbox来管理。

后来涉及到远程定制一些管理功能的时候,也偷懒适用 paramiko 通过 ssh 来调用命令行,但是这带来了安全问题,以及性能问题,不得不重视了。

Mikrotik 提供了很详细的文档和范例, github 上也有一些 repo,但是考虑到使用到生产环境感觉不够,于是就捣鼓了这个基于 twisted 的 api。

使用 twisted(当然也可以使用gevent等)的好处是能实现IO的多路复用,实现无阻塞的调用,让程序发挥更高的效率。

RouterOS API 支持在同一个连接上同时处理多条命令,通过 tag 关键字来进行区分,这是一个非常不错的特性,通过 Twisted 的 deferrd 延时处理机制,能有效的实现程序的异步执行。

源码如下

#!/usr/bin/env python
# coding=utf-8

import sys, time, md5, binascii, socket,traceback,random
from twisted.internet.protocol import Protocol, ReconnectingClientFactory
from twisted.internet import reactor,defer
from twisted.python import log
from StringIO import StringIO

class RosClient(Protocol):

    def __init__(self, apiuser, apipwd):
        self.apiuser = apiuser
        self.apipwd = apipwd
        self.deferrds = {}   

    def get_len_code(self, l):
        ret = []
        if l < 0x80:
            ret.append(chr(l))
        elif l < 0x4000:
            l |= 0x8000
            ret.append(chr((l >> 8) & 0xFF))
            ret.append(chr(l & 0xFF))
        elif l < 0x200000:
            l |= 0xC00000
            ret.append(chr((l >> 16) & 0xFF))
            ret.append(chr((l >> 8) & 0xFF))
            ret.append(chr(l & 0xFF))
        elif l < 0x10000000:        
            l |= 0xE0000000         
            ret.append(chr((l >> 24) & 0xFF))
            ret.append(chr((l >> 16) & 0xFF))
            ret.append(chr((l >> 8) & 0xFF))
            ret.append(chr(l & 0xFF))
        else:                       
            ret.append(chr(0xF0))
            ret.append(chr((l >> 24) & 0xFF))
            ret.append(chr((l >> 16) & 0xFF))
            ret.append(chr((l >> 8) & 0xFF))
            ret.append(chr(l & 0xFF))
        return ''.join(ret)


    def talk(self, words):
        words = [w.encode('utf-8') for w in words]
        datas = []
        for w in words:
            datas.append(self.get_len_code(len(w)))
            datas.append(w)
        datas.append('\x00')
        datastr = ''.join(datas)
        log.msg('roscli-senddata::'+repr(datastr))
        self.transport.write(datastr) 

    def send_cmd(self,tag,words):
        def on_resp(r):
            return r
        assert type(tag) == int
        words.append(".tag="+str(tag))
        self.talk(words)
        self.deferrds[tag] = defer.Deferred() 
        self.deferrds[tag].addCallbacks(on_resp,log.err)
        return self.deferrds[tag]        


    def login(self,ret=None):
        def logresp(resp):
            if resp and resp[0][0] == '!done':
                log.msg('roscli-login success')

        if not ret: 
            self.talk(["/login"])
            self.deferrds[0] = defer.Deferred() 
            self.deferrds[0].addCallbacks(self.login,log.err)
            return self.deferrds[0]
        else:
            for repl, attrs in ret:
                chal = binascii.unhexlify(attrs['=ret'])         
            md = md5.new()
            md.update('\x00')
            md.update(self.apipwd)
            md.update(chal)
            self.talk(["/login",".tag=1", "=name=" + self.apiuser,
                       "=response=00" + binascii.hexlify(md.digest())])
            self.deferrds[1] = defer.Deferred() 
            self.deferrds[1].addCallbacks(logresp,log.err)
            return self.deferrds[1]


    def connectionMade(self):
        log.msg('roccli-connected:: %s' % self.transport.getPeer())
        d = self.login()

    def read_len(self,bfile):              
        c = ord(bfile.read(1))    
        if (c & 0x80) == 0x00:      
            pass                    
        elif (c & 0xC0) == 0x80:    
            c &= ~0xC0              
            c <<= 8                 
            c += ord(bfile.read(1))    
        elif (c & 0xE0) == 0xC0:    
            c &= ~0xE0              
            c <<= 8                 
            c += ord(bfile.read(1))    
            c <<= 8                 
            c += ord(bfile.read(1))    
        elif (c & 0xF0) == 0xE0:    
            c &= ~0xF0              
            c <<= 8                 
            c += ord(bfile.read(1))    
            c <<= 8                 
            c += ord(bfile.read(1))    
            c <<= 8                 
            c += ord(bfile.read(1))    
        elif (c & 0xF8) == 0xF0:    
            c = ord(bfile.read(1))     
            c <<= 8                 
            c += ord(bfile.read(1))    
            c <<= 8                 
            c += ord(bfile.read(1))    
            c <<= 8                 
            c += ord(bfile.read(1))    
        return c             

    def dataReceived(self, data):
        log.msg('roscli-recvdata::'+repr(data))
        tag = 0
        tag_resp = {}
        for dt in [ d+'\x00' for d in data.split('\x00') if d]:
            idata = StringIO()
            idata.write(dt)
            idata.seek(0)

            resp_array = []
            while 1:
                w = idata.read(self.read_len(idata))
                if w == '': break
                resp_array.append(w)

            reply = resp_array[0]
            attrs = {}
            for w in resp_array[1:]:
                j = w.find('=', 1)
                if (j == -1):
                    attrs[w] = ''
                else:
                    attrs[w[:j]] = w[j+1:]

            _tag = attrs.get('.tag',0)    
            tag_resp.setdefault(_tag, []).append((reply, attrs))

        for t,r in tag_resp.iteritems():
            if int(t) in self.deferrds:
                self.deferrds[int(t)].callback(r)


class RouterOSClient(ReconnectingClientFactory):

    def __init__(self,apiaddr, apiuser, apipwd,debug=False):
        self.apiaddr =apiaddr
        self.apiuser = apiuser
        self.apipwd = apipwd     
        self.debug = debug

    def startedConnecting(self, connector):
        log.msg('Started to connect RouterOS')

    def buildProtocol(self, addr):
        self.client = RosClient(self.apiuser,self.apipwd)
        return self.client

    def clientConnectionLost(self, connector, reason):
        log.msg('Lost connection.  Reason:', reason)
        ReconnectingClientFactory.clientConnectionLost(self, connector, reason)

    def clientConnectionFailed(self, connector, reason):
        log.msg('Connection failed. Reason:', reason)
        ReconnectingClientFactory.clientConnectionFailed(self, connector,reason)


    def _on_ros_callback(self,resp):
        """ api async method callback
        """
        if self.debug:
            log.msg(resp)
        return resp


    ##############################################################################
    ## addrpool methods
    ##############################################################################

    def add_pool(self,name,ranges,next_pool=None):
        """ add addr pool

        :param name: pool name
        :type name: str
        :param ranges: ip ranges
        :type ranges: str
        :param next_pool: next addr pool
        :type next_pool: str
        """
        params = [
            '/ip/pool/add',
            '=name='+name,
            '=ranges='+ranges,
        ]

        if next_pool:
            params.append('=next_pool='+next_pool)

        d = self.client.send_cmd(random.randint(9999,999999),params)
        d.addCallbacks(self._on_ros_callback,log.err)
        return d

    def set_pool(self,name,ranges=None,next_pool=None):
        """ edit addr pool

        :param name: pool name
        :type name: str
        :param ranges: ip ranges
        :type ranges: str
        :param next_pool: next addr pool
        :type next_pool: str
        """
        if not ranges and not next_pool:
            return False

        params = [
            '/ip/pool/set',
            '=numbers='+name,
        ]

        if ranges:
            params.append('=ranges='+ranges)

        if next_pool:
            params.append('=next_pool='+next_pool)

        d = self.client.send_cmd(random.randint(9999,999999),params)
        d.addCallbacks(self._on_ros_callback,log.err)
        return d

    def del_pool(self,name):
        """ delete addr pool

        :param name: pool name
        :type name: str
        """
        params = ['/ip/pool/remove', '=numbers='+name]

        d = self.client.send_cmd(random.randint(9999,999999),params)
        d.addCallbacks(self._on_ros_callback,log.err)
        return d

class RosPool():

    pools = {}

    @staticmethod
    def get_client(apiaddr,apiport,apiuser,apipwd,debug=False):
        if apiaddr in RosPool.pools:
            return RosPool.pools[apiaddr]

        rf = RouterOSClient(apiaddr, apiuser, apipwd,debug=debug)
        reactor.connectTCP(apiaddr, int(apiport), rf)
        RosPool.pools[apiaddr] = rf
        return rf

rospool = RosPool

if __name__ == '__main__':
    log.startLogging(sys.stdout)
    rosapi = rospool.get_client(sys.argv[1], 8728, sys.argv[2], sys.argv[3],debug=True)
    reactor.callLater(2,rosapi.add_pool,'testpool1','10.0.0.1-10.0.0.100')
    reactor.callLater(4,rosapi.del_pool,'testpool1')
    reactor.callLater(6,reactor.stop)

    reactor.run()

USB 是个很不安全的东西

过去,我们都只是用USB来传输数据,而且我们也知道一旦有病毒的U盘插到电脑上,就会很容易感染电脑。

后来,USB 成了充电的接口标准,很多缺乏经验的人只以为 USB 是用来充电的。

简单说吧,USB 有两条线路通道,一个是用来供电,给手机以及各种新的电子玩意儿供电,比如迷你电风扇,迷你加湿器等等,另一个是用来传输数据,所有具备数据传输能力的计算设备。一旦两个激活的计算设备通过USB联通,他们就开始传输数据,而危险就是从这里开始。

在计算机世界里,有着各种各样的数据传输协议,有些少数的专有协议在开始传输有效数据前,会要求安全验证,然而机会90%以上的都不会,典型的如超文本传输协议,也就是我们的浏览器用来打开网页的功能,当我们打开一些网站时,我们的浏览器在后台还会不停的和远端交换传输数据,这些数据就包含了很多我们的隐私,比如个人信息,社交信息,如果这些信息通过不安全的通道,那就非常危险了。

有位名叫卡马尔(Samy Kamkar)发明了一个廉价的攻击设备,名为“PoisonTap”,硬件使用了市场售价只有五美元的廉价迷你电脑树莓派,实施攻击的软件则为免费软件。如果把这个设备连接到电脑USB接口上,它就可以开始工作。在30秒钟之内,它可以绕过用户的屏幕锁屏,并且安装一个后门程序,在拔开设备后,后门程序依然可以运行。

然而这并不是要通过暴力破解你的密码,而是巧妙的实现了一种“中间人攻击”,这个设备通过USB模拟了一个互联网连接,我们可以理解为你的电脑通过一个新的网卡连接了互联网,它的优先级更高,这导致你的电脑中一些激活的应用发送的数据被这个攻击设备截取,后果可以想像下。这和在公共场所提供虚假的wifi连接是一个原理。

如何防范呢,我觉得很难,你可以习惯性的经常清除电脑的状态,关闭浏览器,关闭隐私应用,最好直接禁用USB。

适用于 mysql 的备份脚本

该脚本来自硬派计费系统的自动备份脚本,对于安全的生产环境,备份永不嫌少。

根据你的实际环境,修改参数,在linux终端,执行 crontab -e 加入一行: 01 1 * sh /opt/toughee/bin/mysql_backup

#!/bin/bash
# toughee database backup script
# crontab -e   01 1 * * *  sh /opt/toughee/bin/mysql_backup

# 设置mysql备份用户密码
USER="root"
PASSWORD=
# 设置备份目录,自动创建
OUTPUT="/backup"
test -d $OUTPUT || mkdir -p $OUTPUT

# 设置要备份的表
DATABASES=("mysql" "toughee")

# 删除两周以前的历史备份
find $OUTPUT -name "*.sql.xz" -type f -mtime +14 -exec rm -f {} \; > /dev/null 2>&1

# 执行备份,可使用 ignore-table 参数忽略不需要备份的表,比如价值不大体积大的日志表
for db in ${DATABASES[@]}; do
    echo "backup database: $db"
    mysqldump --force --opt --user=$USER \
        --password=$PASSWORD --databases $db \
        --ignore-table=toughee.tr_ticket > $OUTPUT/`date +%Y%m%d`.$db.sql
    # 使用xz格式压缩,更高的压缩比,节省磁盘空间
    xz -f $OUTPUT/`date +%Y%m%d`.$db.sql
done

OpenVPN 通过 txradius 的插件模块来对接 Radius

大部分用户在需要将 OpenVPN 与 radius 对接实现认证计费时都会采用 freeradius client插件,不过基本上都停留在非常简单的配置,对出现的问题都没有很好的解决方法,比如这个插件默认把IP地址当作mac地址发送给radius,而IP地址属性又没有发送。我记得这是个属性映射的问题,但是怎么配置确实没有找到相关的文档说明。

最后还是自己来实现一个radius客户端插件了,在 txradius 的基础上稍加定制,就实现了适用于 OpenVPN 的客户端插件。

OpenVPN 的安装过程在此忽略,主要谈谈 txradius 的安装和配置

通过 pip 工具安装 txradius

pip install txradius

或者

pip install  https://github.com/talkincode/txradius/archive/master.zip

加入 txradius 的 openvpn 插件配置

/etc/openvpn/txovpn.conf

[DEFAULT]
nas_id=toughac
nas_coa_port=3799
nas_addr=127.0.0.1
radius_addr=127.0.0.1
radius_auth_port=18121
radius_acct_port=18131
radius_secret=secret
acct_interval=240
session_timeout=864000
logfile=/var/log/txovpn.log
statusfile=/etc/openvpn/openvpn-status.log
statusdb=/etc/openvpn/txovpn.db
client_config_dir=/etc/openvpn/ccd
server_manage_addr=127.0.0.1:7505
debug=false

配置说明

  • nas_id 是vpn的radius接入标识属性,某些radius 服务器会用来做接入鉴权识别。
  • nas_coa_port 是强制下线接口,插件会在openvpn启动时监听UDP,接受Radius 的强制下线请求。
  • nas_addr 是当前openvpn服务器的ip地址
  • radius_addr 是radius服务器的IP地址
  • radius_auth_port 是radius服务器认证端口
  • radius_acct_port 是radius服务器记账端口
  • radius_secret 是radius共享密钥
  • acct_interval 记账间隔
  • session_timeout 最大超时时间
  • logfile txradius插件日志记录
  • statusfile openvpn状态文件,和openvpn服务配置一致,如果需要做流量记账,需要启动状态记录。
  • statusdb txradius的openvpn插件数据库,在openvpn启动时初始化,停止时清空,由up和down脚本控制。
  • client_config_dir 客户端配置目录
  • server_manage_addr openvpn服务管理telnet,需要在openvpn服务配置启用。
  • debug 为true时,不单独记录日志,日志会重定向到标准输出。

配置 openvpn 服务,加入 radius 配置

在openvpn的服务配置里加入以下内容

client-cert-not-required 
username-as-common-name
script-security 3 system
auth-user-pass-verify /etc/openvpn/txovpn_auth.sh via-env
client-connect /etc/openvpn/txovpn_connect.sh
client-disconnect /etc/openvpn/txovpn_disconnect.sh
up /etc/openvpn/txovpn_up.sh
down /etc/openvpn/txovpn_down.sh

注意,其中 脚本文件可以从 https://github.com/talkincode/txradius/tree/master/scripts 获取

附录:脚本内容

/etc/openvpn/txovpn_auth.sh 内容

#!/bin/sh
txovpn_auth -c /etc/openvpn/txovpn.conf

/etc/openvpn/txovpn_connect.sh 内容

#!/bin/sh
txovpn_connect -c /etc/openvpn/txovpn.conf

/etc/openvpn/txovpn_disconnect.sh 内容

#!/bin/sh
txovpn_disconnect -c /etc/openvpn/txovpn.conf

/etc/openvpn/txovpn_up.sh 内容

#!/bin/sh
echo 'openvpn up'
txovpn_initdb -c /etc/openvpn/txovpn.conf
nohup txovpn_daemon > /dev/null 2>&1 &

/etc/openvpn/txovpn_down.sh 内容

#!/bin/sh
if [ $( pgrep -f txovpn_daemon | wc -l ) -gt 0 ];then
    pgrep -f txovpn_daemon | xargs  kill
fi
txovpn_initdb -c /etc/openvpn/txovpn.conf

记得配置好你的radius系统,启动openvpn服务,接下来就可以通过radius进行认证了。

安装fabric模块遇到的问题

一直使用 fabric 来做linux的软件自动部署,今天安装时突然遇到一个问题。

-> # pip install fabric --no-cache
You are using pip version 7.1.0, however version 9.0.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
Collecting fabric
  Downloading http://mirrors.aliyun.com/pypi/packages/c0/36/0cae0e509053a0986873cf671f1b920c8892807cc3762ed7d3632d535f53/Fabric-1.13.1-py2-none-any.whl (92kB)
    100% |################################| 94kB 2.2MB/s 
Collecting paramiko<3.0,>=1.10 (from fabric)
  Downloading http://mirrors.aliyun.com/pypi/packages/2f/b1/05db0419e6e344a0ae3c6c5f8911c6b937c81281e8e2016bec0af0cff8b0/paramiko-2.1.1-py2.py3-none-any.whl (172kB)
    100% |################################| 176kB 42.2MB/s 
Requirement already satisfied (use --upgrade to upgrade): pyasn1>=0.1.7 in /usr/lib/python2.7/site-packages (from paramiko<3.0,>=1.10->fabric)
Collecting cryptography>=1.1 (from paramiko<3.0,>=1.10->fabric)
  Downloading http://mirrors.aliyun.com/pypi/packages/82/f7/d6dfd7595910a20a563a83a762bf79a253c4df71759c3b228accb3d7e5e4/cryptography-1.7.1.tar.gz (420kB)
    100% |################################| 421kB 42.4MB/s 
Requirement already satisfied (use --upgrade to upgrade): idna>=2.0 in /usr/lib/python2.7/site-packages (from cryptography>=1.1->paramiko<3.0,>=1.10->fabric)
Requirement already satisfied (use --upgrade to upgrade): six>=1.4.1 in /usr/lib/python2.7/site-packages (from cryptography>=1.1->paramiko<3.0,>=1.10->fabric)
Requirement already satisfied (use --upgrade to upgrade): setuptools>=11.3 in /usr/lib/python2.7/site-packages (from cryptography>=1.1->paramiko<3.0,>=1.10->fabric)
Requirement already satisfied (use --upgrade to upgrade): enum34 in /usr/lib/python2.7/site-packages (from cryptography>=1.1->paramiko<3.0,>=1.10->fabric)
Requirement already satisfied (use --upgrade to upgrade): ipaddress in /usr/lib/python2.7/site-packages (from cryptography>=1.1->paramiko<3.0,>=1.10->fabric)
Requirement already satisfied (use --upgrade to upgrade): cffi>=1.4.1 in /usr/lib64/python2.7/site-packages (from cryptography>=1.1->paramiko<3.0,>=1.10->fabric)
Requirement already satisfied (use --upgrade to upgrade): pycparser in /usr/lib/python2.7/site-packages (from cffi>=1.4.1->cryptography>=1.1->paramiko<3.0,>=1.10->fabric)
Installing collected packages: cryptography, paramiko, fabric
  Running setup.py install for cryptography
Connection to git.coding.net closed by remote host.
Connection to git.coding.net closed by remote host.
    Complete output from command /usr/bin/python -c "import setuptools, tokenize;__file__='/tmp/pip-build-MiWMSr/cryptography/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record /tmp/pip-2X46QF-record/install-record.txt --single-version-externally-managed --compile:
    running install
    running build
    running build_py
    creating build
    creating build/lib.linux-x86_64-2.7
    creating build/lib.linux-x86_64-2.7/cryptography
    copying src/cryptography/__init__.py -> build/lib.linux-x86_64-2.7/cryptography
    copying src/cryptography/exceptions.py -> build/lib.linux-x86_64-2.7/cryptography
    copying src/cryptography/fernet.py -> build/lib.linux-x86_64-2.7/cryptography
    copying src/cryptography/utils.py -> build/lib.linux-x86_64-2.7/cryptography
    copying src/cryptography/__about__.py -> build/lib.linux-x86_64-2.7/cryptography
    creating build/lib.linux-x86_64-2.7/cryptography/hazmat
    copying src/cryptography/hazmat/__init__.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat
    creating build/lib.linux-x86_64-2.7/cryptography/x509
    copying src/cryptography/x509/__init__.py -> build/lib.linux-x86_64-2.7/cryptography/x509
    copying src/cryptography/x509/extensions.py -> build/lib.linux-x86_64-2.7/cryptography/x509
    copying src/cryptography/x509/general_name.py -> build/lib.linux-x86_64-2.7/cryptography/x509
    copying src/cryptography/x509/oid.py -> build/lib.linux-x86_64-2.7/cryptography/x509
    copying src/cryptography/x509/name.py -> build/lib.linux-x86_64-2.7/cryptography/x509
    copying src/cryptography/x509/base.py -> build/lib.linux-x86_64-2.7/cryptography/x509
    creating build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives
    copying src/cryptography/hazmat/primitives/constant_time.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives
    copying src/cryptography/hazmat/primitives/hmac.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives
    copying src/cryptography/hazmat/primitives/__init__.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives
    copying src/cryptography/hazmat/primitives/keywrap.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives
    copying src/cryptography/hazmat/primitives/serialization.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives
    copying src/cryptography/hazmat/primitives/hashes.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives
    copying src/cryptography/hazmat/primitives/padding.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives
    copying src/cryptography/hazmat/primitives/cmac.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives
    creating build/lib.linux-x86_64-2.7/cryptography/hazmat/backends
    copying src/cryptography/hazmat/backends/__init__.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends
    copying src/cryptography/hazmat/backends/multibackend.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends
    copying src/cryptography/hazmat/backends/interfaces.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends
    creating build/lib.linux-x86_64-2.7/cryptography/hazmat/bindings
    copying src/cryptography/hazmat/bindings/__init__.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/bindings
    creating build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/asymmetric
    copying src/cryptography/hazmat/primitives/asymmetric/rsa.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/asymmetric
    copying src/cryptography/hazmat/primitives/asymmetric/__init__.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/asymmetric
    copying src/cryptography/hazmat/primitives/asymmetric/dsa.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/asymmetric
    copying src/cryptography/hazmat/primitives/asymmetric/dh.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/asymmetric
    copying src/cryptography/hazmat/primitives/asymmetric/utils.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/asymmetric
    copying src/cryptography/hazmat/primitives/asymmetric/padding.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/asymmetric
    copying src/cryptography/hazmat/primitives/asymmetric/ec.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/asymmetric
    creating build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/ciphers
    copying src/cryptography/hazmat/primitives/ciphers/__init__.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/ciphers
    copying src/cryptography/hazmat/primitives/ciphers/modes.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/ciphers
    copying src/cryptography/hazmat/primitives/ciphers/algorithms.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/ciphers
    copying src/cryptography/hazmat/primitives/ciphers/base.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/ciphers
    creating build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/kdf
    copying src/cryptography/hazmat/primitives/kdf/scrypt.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/kdf
    copying src/cryptography/hazmat/primitives/kdf/__init__.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/kdf
    copying src/cryptography/hazmat/primitives/kdf/pbkdf2.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/kdf
    copying src/cryptography/hazmat/primitives/kdf/concatkdf.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/kdf
    copying src/cryptography/hazmat/primitives/kdf/kbkdf.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/kdf
    copying src/cryptography/hazmat/primitives/kdf/x963kdf.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/kdf
    copying src/cryptography/hazmat/primitives/kdf/hkdf.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/kdf
    creating build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/interfaces
    copying src/cryptography/hazmat/primitives/interfaces/__init__.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/interfaces
    creating build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/twofactor
    copying src/cryptography/hazmat/primitives/twofactor/__init__.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/twofactor
    copying src/cryptography/hazmat/primitives/twofactor/hotp.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/twofactor
    copying src/cryptography/hazmat/primitives/twofactor/totp.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/twofactor
    copying src/cryptography/hazmat/primitives/twofactor/utils.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/primitives/twofactor
    creating build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/openssl
    copying src/cryptography/hazmat/backends/openssl/rsa.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/openssl
    copying src/cryptography/hazmat/backends/openssl/hmac.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/openssl
    copying src/cryptography/hazmat/backends/openssl/__init__.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/openssl
    copying src/cryptography/hazmat/backends/openssl/dsa.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/openssl
    copying src/cryptography/hazmat/backends/openssl/decode_asn1.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/openssl
    copying src/cryptography/hazmat/backends/openssl/encode_asn1.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/openssl
    copying src/cryptography/hazmat/backends/openssl/dh.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/openssl
    copying src/cryptography/hazmat/backends/openssl/backend.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/openssl
    copying src/cryptography/hazmat/backends/openssl/ciphers.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/openssl
    copying src/cryptography/hazmat/backends/openssl/x509.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/openssl
    copying src/cryptography/hazmat/backends/openssl/hashes.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/openssl
    copying src/cryptography/hazmat/backends/openssl/utils.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/openssl
    copying src/cryptography/hazmat/backends/openssl/ec.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/openssl
    copying src/cryptography/hazmat/backends/openssl/cmac.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/openssl
    creating build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/commoncrypto
    copying src/cryptography/hazmat/backends/commoncrypto/hmac.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/commoncrypto
    copying src/cryptography/hazmat/backends/commoncrypto/__init__.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/commoncrypto
    copying src/cryptography/hazmat/backends/commoncrypto/backend.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/commoncrypto
    copying src/cryptography/hazmat/backends/commoncrypto/ciphers.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/commoncrypto
    copying src/cryptography/hazmat/backends/commoncrypto/hashes.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/backends/commoncrypto
    creating build/lib.linux-x86_64-2.7/cryptography/hazmat/bindings/openssl
    copying src/cryptography/hazmat/bindings/openssl/__init__.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/bindings/openssl
    copying src/cryptography/hazmat/bindings/openssl/binding.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/bindings/openssl
    copying src/cryptography/hazmat/bindings/openssl/_conditional.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/bindings/openssl
    creating build/lib.linux-x86_64-2.7/cryptography/hazmat/bindings/commoncrypto
    copying src/cryptography/hazmat/bindings/commoncrypto/__init__.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/bindings/commoncrypto
    copying src/cryptography/hazmat/bindings/commoncrypto/binding.py -> build/lib.linux-x86_64-2.7/cryptography/hazmat/bindings/commoncrypto
    running egg_info
    writing requirements to src/cryptography.egg-info/requires.txt
    writing src/cryptography.egg-info/PKG-INFO
    writing top-level names to src/cryptography.egg-info/top_level.txt
    writing dependency_links to src/cryptography.egg-info/dependency_links.txt
    writing entry points to src/cryptography.egg-info/entry_points.txt
    reading manifest file 'src/cryptography.egg-info/SOURCES.txt'
    reading manifest template 'MANIFEST.in'
    no previously-included directories found matching 'docs/_build'
    warning: no previously-included files matching '*' found under directory 'vectors'
    writing manifest file 'src/cryptography.egg-info/SOURCES.txt'
    running build_ext
    generating cffi module 'build/temp.linux-x86_64-2.7/_padding.c'
    creating build/temp.linux-x86_64-2.7
    generating cffi module 'build/temp.linux-x86_64-2.7/_constant_time.c'
    generating cffi module 'build/temp.linux-x86_64-2.7/_openssl.c'
    building '_openssl' extension
    creating build/temp.linux-x86_64-2.7/build
    creating build/temp.linux-x86_64-2.7/build/temp.linux-x86_64-2.7
    gcc -pthread -fno-strict-aliasing -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -DNDEBUG -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -fPIC -I/usr/include/python2.7 -c build/temp.linux-x86_64-2.7/_openssl.c -o build/temp.linux-x86_64-2.7/build/temp.linux-x86_64-2.7/_openssl.o
    gcc: internal compiler error: Killed (program cc1)
    Please submit a full bug report,
    with preprocessed source if appropriate.
    See <http://bugzilla.redhat.com/bugzilla> for instructions.
    error: command 'gcc' failed with exit status 4

    ----------------------------------------
Command "/usr/bin/python -c "import setuptools, tokenize;__file__='/tmp/pip-build-MiWMSr/cryptography/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record /tmp/pip-2X46QF-record/install-record.txt --single-version-externally-managed --compile" failed with error code 1 in /tmp/pip-build-MiWMSr/cryptography

很干净的centos7 系统,这个操作也做过n次了。

gcc: internal compiler error: Killed (program cc1)

终于搞明白,原来这货编译过程很耗资源,这个系统只有1核1G,还没有交换分区,资源不足导致进程被kill了。

增加交换分区解决:

sudo dd if=/dev/zero of=/swapfile bs=1024 count=524288
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

再次安装成功

-> # pip install fabric                                    
You are using pip version 7.1.0, however version 9.0.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
Collecting fabric
 Downloading http://mirrors.aliyun.com/pypi/packages/c0/36/0cae0e509053a0986873cf671f1b920c8892807cc3762ed7d3632d535f53/Fabric-1.13.1-py2-none-any.whl (92kB)
   100% |################################| 94kB 4.2MB/s
Collecting paramiko<3.0,>=1.10 (from fabric)
 Downloading http://mirrors.aliyun.com/pypi/packages/2f/b1/05db0419e6e344a0ae3c6c5f8911c6b937c81281e8e2016bec0af0cff8b0/paramiko-2.1.1-py2.py3-none-any.whl (172kB)
   100% |################################| 176kB 39.8MB/s
Requirement already satisfied (use --upgrade to upgrade): pyasn1>=0.1.7 in /usr/lib/python2.7/site-packages (from paramiko<3.0,>=1.10->fabric)
Collecting cryptography>=1.1 (from paramiko<3.0,>=1.10->fabric)
 Downloading http://mirrors.aliyun.com/pypi/packages/82/f7/d6dfd7595910a20a563a83a762bf79a253c4df71759c3b228accb3d7e5e4/cryptography-1.7.1.tar.gz (420kB)
   100% |################################| 421kB 41.4MB/s
Requirement already satisfied (use --upgrade to upgrade): idna>=2.0 in /usr/lib/python2.7/site-packages (from cryptography>=1.1->paramiko<3.0,>=1.10->fabric)
Requirement already satisfied (use --upgrade to upgrade): six>=1.4.1 in /usr/lib/python2.7/site-packages (from cryptography>=1.1->paramiko<3.0,>=1.10->fabric)
Requirement already satisfied (use --upgrade to upgrade): setuptools>=11.3 in /usr/lib/python2.7/site-packages (from cryptography>=1.1->paramiko<3.0,>=1.10->fabric)
Requirement already satisfied (use --upgrade to upgrade): enum34 in /usr/lib/python2.7/site-packages (from cryptography>=1.1->paramiko<3.0,>=1.10->fabric)
Requirement already satisfied (use --upgrade to upgrade): ipaddress in /usr/lib/python2.7/site-packages (from cryptography>=1.1->paramiko<3.0,>=1.10->fabric)
Requirement already satisfied (use --upgrade to upgrade): cffi>=1.4.1 in /usr/lib64/python2.7/site-packages (from cryptography>=1.1->paramiko<3.0,>=1.10->fabric)
Requirement already satisfied (use --upgrade to upgrade): pycparser in /usr/lib/python2.7/site-packages (from cffi>=1.4.1->cryptography>=1.1->paramiko<3.0,>=1.10->fabric)
Installing collected packages: cryptography, paramiko, fabric
 Running setup.py install for cryptography
Successfully installed cryptography-1.7.1 fabric-1.13.1 paramiko-2.1.1

关于 libcrypto no version information available 的提示

在安装好toughradius的二进制社区版或者二进制企业版后,在执行一些跟openssl相关的指令时,会出现以下警告信息,通常这个信息并不影响系统使用,这是因为软件自带的openssl库和系统库版本不一致的问题。

/lib64/libcrypto.so.10: no version information available

在centos 中可以通过以下方式解决:

yum -y install libcrypto.so.10 --setopt=protected_multilib=false

基于 docker 的 pypy 基础环境构建

为了做一些项目的发布测试,做了这个小项目。docker-pypy

一开始还是碰到不少棘手的问题,在确定使用 ubuntu14 还试过centos6,7,最终还是选定 ubuntu14,

为避免网络下载异常,distribute 和 pip 预先提交到了 github。

Dockerfile 内容:

FROM ubuntu:14.04
MAINTAINER jamiesun <jamiesun.net@gmail.com>

RUN \
  sed -i 's/# \(.*multiverse$\)/\1/g' /etc/apt/sources.list && \
  apt-get update && \
  apt-get -y upgrade && \
  apt-get install -y build-essential && \
  apt-get install -y software-properties-common && \
  apt-get install -y byobu curl git htop man unzip vim wget && \
  rm -rf /var/lib/apt/lists/*


RUN mkdir /pysetup
ADD get-pip.py /pysetup/get-pip.py
ADD distribute-0.7.3.zip /pysetup/distribute-0.7.3.zip

RUN apt-get update -y && \
    apt-get install -y  wget zip libffi-dev openssl libssl-dev git gcc tcpdump && \
    apt-get clean all

RUN cd /opt && wget https://bitbucket.org/pypy/pypy/downloads/pypy-4.0.0-linux64.tar.bz2 && \
    tar -xf pypy-4.0.0-linux64.tar.bz2 && \
    ln -s /opt/pypy-4.0.0-linux64/bin/pypy /usr/local/bin && \
    pypy --version

RUN cd /pysetup && unzip distribute-0.7.3.zip && cd distribute-0.7.3 && pypy setup.py install

RUN pypy /pysetup/get-pip.py && ln -s /opt/pypy-4.0.0-linux64/bin/pip /usr/local/bin

RUN rm -rf /pysetup

RUN pypy -m pip install  --upgrade setuptools

RUN pypy -m pip install  supervisor

RUN ln -s /opt/pypy-4.0.0-linux64/bin/supervisord /usr/local/bin && \
    ln -s /opt/pypy-4.0.0-linux64/bin/supervisorctl /usr/local/bin

RUN echo "set nocompatible" >> /root/.vimrc && echo "set backspace=2" >> /root/.vimrc
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

基于这个镜像的再次定制:

FROM talkincode/pypy
MAINTAINER jamiesun <jamiesun.net@gmail.com>

RUN add-apt-repository -y ppa:nginx/stable && \
    apt-get update -y && \
    apt-get install -y  mysql-client libmysqlclient-dev beanstalkd memcached nginx htop openssh-server libzmq-dev && \
    rm -rf /var/lib/apt/lists/*

RUN mkdir /var/run/sshd && \
    echo "root:toughstruct" | chpasswd && \
    sed -i 's/PermitRootLogin without-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \
    sed -i 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' /etc/pam.d/sshd

RUN pypy -m  pip install bottle
RUN pypy -m  pip install Mako
RUN pypy -m  pip install Beaker
RUN pypy -m  pip install MarkupSafe
RUN pypy -m  pip install PyYAML
RUN pypy -m  pip install Twisted
RUN pypy -m  pip install treq
RUN pypy -m  pip install tablib
RUN pypy -m  pip install cyclone
RUN pypy -m  pip install six
RUN pypy -m  pip install autobahn
RUN pypy -m  pip install pycrypto
RUN pypy -m  pip install pyOpenSSL>=0.14
RUN pypy -m  pip install service_identity
RUN pypy -m  pip install MySQL-python
RUN pypy -m  pip install SQLAlchemy
RUN pypy -m  pip install pyzmq
RUN pypy -m  pip install txzmq
RUN pypy -m  pip install beanstalkc
RUN pypy -m  pip install pybeanstalk
RUN pypy -m  pip install python-memcached
RUN pypy -m  pip install txyam
RUN pypy -m  pip install psutil
RUN pypy -m  pip install IPy

EXPOSE 22

mschapv2在Radius中的认证实现

在Radius的认证请求AccessRequest包中如果包含 MS-CHAP2-Response 和 MS-CHAP-Challenge 属性则意味着需要实现ms-chap-v2认证。

客户端 MS-CHAP2-Response 和 MS-CHAP-Challenge 生成的规则

MS-CHAP-Challenge

MS-CHAP-Challenge (AuthChallenge) 是客户端生成的随机16字节。

MS-CHAP2-Response

随机生成16字节属性 PeerChallenge,连同AuthChallenge,UserName,Password作为输入参数,调用方法 GenerateNTResponse 得到 NtResponse.

GenerateNTResponse(AuthChallenge, PeerChallenge, UserName, Password) 

GenerateNTResponse 方法的实现参考 http://tools.ietf.org/html/rfc2759.html#section-8.1

封装50字节的 MS-CHAP2-Response 属性:

[0 : 2]           Flags  \x00\x00
[2 : 18]          PeerChallenge 
[18 : 26]         Reserved \x00\x00\x00\x00\x00\x00\x00\x00
[26 : 50]         NtResponse

服务端认证规则

校验 MS-CHAP2-Response 长度,长度不等于50应该丢弃,并发送拒绝认证。

从 MS-CHAP2-Response 提取 PeerChallenge,NtResponse

NtResponse = MS-CHAP2-Response[26 : 50]
PeerChallenge = MS-CHAP2-Response[2 : 18]

调用 GenerateNTResponse 方法得到 MyNtResponse

GenerateNTResponse(AuthChallenge, PeerChallenge, UserName, Password)  

比较 MyNtResponse 与 NtResponse,不相等则验证失败。

Radius 认证响应

调用 GenerateAuthenticatorResponse 方法得到 AuthenticatorResponse

GenerateAuthenticatorResponse(
    Password,
    NtResponse,
    PeerChallenge, 
    AuthChallenge
    UserName
) 

GenerateAuthenticatorResponse 方法的实现参考 http://tools.ietf.org/html/rfc2759.html#section-8.7

设置Radius响应消息属性 MS-CHAP2-Success = AuthenticatorResponse

用 python 实现 openvpn 用户名密码认证脚本

编写脚本

vi /etc/openvpn/checkpwd.py

#!/usr/bin/env python
#coding:utf-8

import os

def chkpwd(user,pwd):
   with open('/etc/openvpn/users','rb') as pf:
       for line in pf:
           if line and '{0}:{1}'.format(user,pwd) == line.strip():
               return True
       return False


if __name__ == '__main__':
   username = os.environ.get('username','').strip()
   password = os.environ.get('password','').strip()
   if chkpwd(username, password):
       os._exit(0)
   else:
       os._exit(1)

对 python 熟悉的话,还可以写的更花哨一点。

chmod +x /etc/openvpn/checkpwd.py 修改可执行权限

增加认证用户

修改 /etc/openvpn/users 文件,加入用户名密码,每行一个,中间无空格,如:

user1:pwd1

服务端配置

修改 openvpn的服务配置文件

vi /etc/openvpn/server.conf 加入以下内容

auth-user-pass-verify /etc/openvpn/checkpwd.py via-env
client-cert-not-required
username-as-common-name

如果加上client-cert-not-required 代表只使用用户名密码方式验证登录,如果不加,则代表需要证书和用户名密码双重验证登录!

客户端配置

修改客户端配置文件

注释掉

;cert yangliangwei.crt
;key  yangliangwei.key

增加询问用户名和密码配置

auth-user-pass