该工具能管理基于用户名密码认证的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服务器啦!下图为日志示例