为Visual SVN Server添加认证用户IP功能
背景
内网环境下,某位大佬觉得Visual SVN Server的安全性不够,要求用户只能在指定的一个或几个IP上登录???
方案
内网环境下,Visual SVN Server使用https进行通信,有两种可以实现IP与用户绑定:
- 做一个网关,用户访问SVN时,都通过网关进行访问,然后就可以在网关上进行IP与用户名校验了。由于使用的是https通信,网关配置https并转发到SVN Server比较麻烦,网关方式不进行实现。
- 写一个apache module,在处理请求之前进行拦截校验
从SVN请求中提取用户名
SVN的每次请求都会带有Authorization
Header参数,其内容就是使用Base64编码的用户名和密码,其格式为:“Basic Base64(用户名:密码)”。因此,只需要解析Authorization
字段就可以获取到用户名。无论是使用添加网关还是添加apache module,都比较容易取得Authorization
参数。
主要实现
apache module实际上是一个动态库,apache httpd在启动的时候会动态加载该动态库,加载配置参数后正式进行工作,以下是主要模块的主要代码:
#include <stdio.h>
#include <http_protocol.h>
#include <httpd.h>
#include <http_config.h>
#include <apr_want.h>
#include <ap_config.h>
#include <apr_base64.h>
#include "host_bind_filter/host_bind_filter.h"
module AP_MODULE_DECLARE_DATA auth_host_bind_module;
static int auth_host_bind_handler(request_rec *r) {
int len = 0;
char* user = NULL;
const char* authorization = apr_table_get(r->headers_in, "Authorization");
// Basic Base64(用户名:密码)
if(authorization != NULL && (authorization = strstr(authorization, "Basic ")) != NULL) {
authorization += 6;// 跳过 "Basic ",然后进行base64解密
len = apr_base64_decode_len(authorization);
user = (char*)malloc(len);
apr_base64_decode(user, authorization);
// 通过user_host_filter函数进行用户名IP校验,其实现完整Demo.
int res = user_host_filter(user, r->useragent_ip);
free(user);
show_request("host=%s, ip=%s", r->useragent_host, r->useragent_ip);
if (res != 0) { // 如果校验不通过时,直接截断HTTP调用链
ap_rprintf(r, "user cannot log in on the host[%s]", r->useragent_ip); // 写入返回信息
return OK; // 返回OK,请求就截断了
}
}
return DECLINED; // 没有authorization参数或校验通过时,返回DECLINED,请求会继续进行,让Visual SVN Server处理。
}
static void register_hooks(apr_pool_t *p) {
ap_hook_handler(auth_host_bind_handler, NULL, NULL, APR_HOOK_REALLY_FIRST); // APR_HOOK_REALLY_FIRST 表示在最前面进行IP认证处理。
}
typedef struct context_host_filename_config {
char filename[1024];
} context_host_filename_config;
/* 读取设置字符串方法 */
static const char *set_context_host_filename(cmd_parms *cmd,
void *mconfig,
const char *name)
{
context_host_filename_config *conf = (context_host_filename_config *) mconfig;
strcpy_s(conf->filename, sizeof(conf->filename), name);
load_context_host(name); // 加载用户IP绑定表,更好的方式是通过读取数据库来进行校验,这里使用cvs文件来绑定
return NULL;
}
static const command_rec auth_context_host_filename_cmd[] =
{
AP_INIT_TAKE1("HostBindFileName", set_context_host_filename, NULL, OR_FILEINFO,
"set HostBindFileName."),
{ NULL }
};
/* init per dir */
static void *create_context_host_filename_config(apr_pool_t *p, char *d)
{
context_host_filename_config *conf = (context_host_filename_config *)apr_pcalloc(p, sizeof(*conf));
if(conf == NULL) return NULL;
memset(conf->filename, 0, sizeof(conf->filename));
return conf;
}
/* module structure */
module AP_MODULE_DECLARE_DATA
auth_host_bind_module = {
STANDARD20_MODULE_STUFF,
create_context_host_filename_config, /* dir config creater */
NULL, /* dir merger — default is to override */
NULL, /* server config */
NULL, /* merge server configs */
auth_context_host_filename_cmd, /* command apr_table_t */
register_hooks /* register hooks */
};