基于Django实现的Openvpn管理工具

该工具能管理基于用户名密码认证的Openvpn服务器,并能详细记录用户登录日志。除此之外,还能对不同的用户分配不同的路由表,可用来实现RBAC的功能,但更多的是让不同的用户能够访问不同的网络。

一:openvpn的auth-user-pass-verify、client-connect、client-disconnect参数

这三个参数的作用分别是指定用于验证用户名密码的脚本、客户端连接成功的脚本、客户端断开的脚本。openvpn通过接收退出代码来判断用户名密码是否正确,若为0则正确,其他则不正确

在明白这几个参数的作用之后,我们需要在服务端的配置文件中添加如下代码,并给予相应脚本的可执行权限

username-as-common-name
script-security 2
auth-user-pass-verify /etc/openvpn/auth.py via-file
client-connect /etc/openvpn/client-handler.py
client-disconnect /etc/openvpn/client-handler.py

上面的via-file代表通过文件传递用户名和密码,还有个选项为via-env代表通过环境变量传递用户名密码

二:编写Django服务端

此处省略掉若干Django的基础搭建必备命令和环境………

#model.py
#RoutingTable表,用来创建不同的路由表并推送给客户端
#VPNCredentials表,用来存储用户凭证
#Log表,存储认证日志
from django.db import models
from django.utils import timezone
from datetime import datetime


class RoutingTable(models.Model):
    name = models.CharField(max_length=30,unique=True)
    network = models.CharField(max_length=30)
    netmask = models.CharField(max_length=30)
    next_hop = models.CharField(max_length=30)

    def __str__(self):
        return self.name


class VPNCredentials(models.Model):
    username = models.CharField(max_length=30, unique=True)
    password = models.CharField(max_length=30)
    realm = models.CharField(max_length=10)
    enable = models.BooleanField(default=True)
    routing_table = models.ManyToManyField(RoutingTable,blank=True)
    push_default_route = models.BooleanField(default=False)
    create_time = models.DateTimeField(default=timezone.now, editable=False)
    last_login = models.DateTimeField(default=datetime(1970, 1, 1), editable=False)
    status_online = models.BooleanField(default=False, editable=False)


class Log(models.Model):
    log_type = models.CharField(max_length=10,null=True)
    time = models.DateTimeField(default=timezone.now)
    source_addr = models.CharField(max_length=30,null=True)
    username = models.CharField(max_length=30)
    password = models.CharField(max_length=30)
    result = models.BooleanField()
    realm = models.CharField(max_length=10)

#views.py

from .models import VPNCredentials, Log
from django.utils import timezone
from django.shortcuts import HttpResponse
from django.http import JsonResponse


def auth(request):
    realm = request.GET.get('realm', None)
    username = request.GET.get('username', None)
    password = request.GET.get('password', None)
    if realm and username and password:
        user = VPNCredentials.objects.filter(realm=realm, username=username, password=password)
        if user.exists():
            user = user[0]
            user.last_login = timezone.now()
            user.save()
            print('%s login' % username)

            return HttpResponse('OK', status=200)
        else:
            print('%s login failed' % username)
            Log.objects.create(realm=realm, username=username, password=password, result=False, time=timezone.now())
            return HttpResponse('NO', status=403)
    else:
        return HttpResponse('NO', status=403)


def get_config(request):
    realm = request.GET.get('realm', None)
    username = request.GET.get('username', None)
    action = request.GET.get('action', None)
    source_addr = request.GET.get('source_addr', None)
    user = VPNCredentials.objects.get(realm=realm, username=username)
    if action == 'login':
        user.status_online = True
        user.last_login = timezone.now()
        user.save()
        Log.objects.create(realm=realm, username=username, result=True, source_addr=source_addr, time=timezone.now())
    elif action == 'logoff':
        user.status_online = False
        user.save()
    routing_table = []
    print(user.routing_table.all())
    for route in user.routing_table.all():
        routing_table.append(
            {'id': route.id, 'network': route.network, 'netmask': route.netmask, 'next_hop': route.next_hop})
    route_config = {'user_id': user.id, 'default_route': user.push_default_route, 'routing_table': routing_table}
    return JsonResponse(route_config)
#admin.py
from django.contrib import admin
from .models import VPNCredentials, Log, RoutingTable


# Register your models here.
@admin.register(VPNCredentials)
class VPNCredentialsAdmin(admin.ModelAdmin):
    list_display = ('username', 'realm', 'enable', 'last_login','status_online')


@admin.register(Log)
class LogAdmin(admin.ModelAdmin):
    list_display = ('id', 'time', 'realm', 'username', 'source_addr', 'result')

    def has_change_permission(self, request, obj=None):
        return False


@admin.register(RoutingTable)
class RoutingTableAdmin(admin.ModelAdmin):
    list_display = ('name', 'network', 'netmask', 'next_hop')

此时,我们的服务端就基本写完了,我们在django的管理界面进行配置。首先添加路由表,这里的路由表的下一跳是针对VPN服务器设置的,并不是针对客户端。如果客户端需要访问10.0.0.0/8,那么他的数据首先是到VPN服务器,然后VPN服务器将数据送入下一跳172.16.1.1

下一步添加用户名和密码,REALM用来标识不同的用户区域,因为你可能会多台VPN服务器使用同一个认证服务器。

配置完成之后,使用URL来验证,返回200代表正确否则错误

http://127.0.0.1:8000/auth?realm=SVR_1&username=user1&password=123456

三:编写认证客户端

这一步编写最开始提到的那2个脚本

#auth.py
import requests
import sys

REALM = 'SVR_1'
AUTH_URL = 'http://172.16.1.1:8000/auth'


def auth(username, password):
    try:
        url = '%s?realm=%s&username=%s&password=%s' % (AUTH_URL, REALM, username, password)
        response = requests.get(url)
        if response.status_code == 200:
            # print('%s %s Login success' % (username, password))
            return True
        else:
            # print('%s %s Login failed' % (username, password))
            return False
    except:
        return False


if __name__ == '__main__':
    username = open(sys.argv[1], 'r').read().split('\n')[0]
    password = open(sys.argv[1], 'r').read().split('\n')[1]
    if auth(username, password):
        sys.exit(0)
    else:
        sys.exit(1)
#client-handler.py

import os
import sys
import json
import datetime
import requests
import json
import os
import subprocess

REALM = 'SVR_1'
AUTH_URL = 'http://172.16.1.1:8000/auth'

def make_openvpn_config(config):
    text = ''
    if config['default_route']:
        text += 'push "redirect-gateway def1"\n'
    if len(config['routing_table']) > 0:
        for route in config['routing_table']:
            text += 'push "route %s %s"\n' % (route['network'], route['netmask'])
    return text


def make_ip_routes(config, route_table_name):
    tables = ['ip route flush table %s' % route_table_name]
    for table in config['routing_table']:
        ip_route = 'ip r a %s/%s via %s table %s' % (
            table['network'], table['netmask'], table['next_hop'], route_table_name)
        tables.append(ip_route)
    return tables


def add_ip_routes(config, route_table_name):
    routes = make_ip_routes(config, route_table_name)
    for route in routes:
        os.system(route)


def make_ip_rule(remote_ip, route_table_name):
    return 'ip ru add from %s lookup %s pri %s' % (remote_ip, route_table_name, route_table_name)


def add_ip_rule(remote_ip, route_table_name):
    current_rules = subprocess.check_output(['/sbin/ip', 'ru']).decode()
    if current_rules.count(remote_ip) == 0:
        os.system(make_ip_rule(remote_ip, route_table_name))


if __name__ == '__main__':
    CLIENT_DATA = {}
    for key in os.environ:
        value = os.getenv(key)
        CLIENT_DATA[key] = value
        CLIENT_JSON = json.dumps(CLIENT_DATA, sort_keys=True)
    script_type = CLIENT_DATA['script_type']
    username = CLIENT_DATA['common_name']
    src_ip = CLIENT_DATA['trusted_ip']
    if script_type == 'client-connect':
        url = '%s?realm=%s&username=%s&source_addr=%s&action=login' % (CONFIG_URL, REALM, username,src_ip)
        resp = requests.get(url)
        if resp.status_code == 200:
            config = resp.json()
            route_table_name = config['user_id'] * 8
            remote_ip = CLIENT_DATA['ifconfig_pool_remote_ip']
            config_file = open(sys.argv[1], 'w')
            config_file.write(make_openvpn_config(config))
            config_file.close()
            add_ip_routes(config, route_table_name)
            add_ip_rule(remote_ip, route_table_name)
    elif script_type == 'client-disconnect':
        url = '%s?realm=%s&username=%s&action=logoff' % (CONFIG_URL, REALM, username)
        resp = requests.get(url)

所有的配置完成之后,就能在一台认证服务器上,管理多台VPN服务器啦!下图为日志示例

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注