上一章我们介绍了安全机制的用户权限管理,接下来将对安全机制中黑白名单配置的源码进行解析。
在gstore安全机制中的黑白名单是指访问IP的黑名单库和白名单库;黑名单规则为:黑名单库中的IP将被禁止访问,白名单规则为:仅允许白名单库中的IP访问;原则上黑白名单的配置为二选一,若二者都开启则是白名单优先。
首先通过gstore根目录的init.conf文件进行配置:启动黑名单或者白名单模式;默认为黑名单模式,如下配置文件所示:
[ghttp]
......
# 白名单库文件路径,为空表示不启用
ip_allow_path=""
# 黑名单库文件路径,为空表示不启用
ip_deny_path="ipDeny.config"
......
然后根据配置的模式,可以复制对应的黑白名单库示例文件进行配置,配置支持单个IP和某一范围的IP组,如下所示:
#ipDeny.config.example
#two kinds of ip format
#1. a single ip address
1.2.2.1
#2. a closed intervel represent by "-", to allow for a segment of ips or deny a segment of ips
1.1.1.1-1.1.1.2
###########################################################
# Notice!!!! #
# 1. use "#" to comment out a Whole line #
# 2. ip address write in one line #
# 3. do not include extra space or other char #
# 4. keep it only numbers or with one '-' #
# 5. to enable black list, use "--ipDeny=<filename>" #
###########################################################
在完成黑白名单的配置后,我们还可以通过接口进行动态的配置,ghttp网络服务提供了黑白名单的查询和更新接口。
当ghttp网络服务启动时,将会对黑白名单进行初始化,读取配置文件init.conf配置的黑白名单库路径,并读取解析黑白名单的库数据,部分关键代码如下:
// 与黑白名单相关的全局参数
// 是否启用黑名单
int blackList = 0;
// 是否启用白名单
int whiteList = 0;
// 白名单库路径
string ipBlackFile = "ipDeny.config";
// 黑名单库路径
string ipWhiteFile = "ipAllow.config";
// 白名单库
IPWhiteList* ipWhiteList;
// 黑名单库
IPBlackList* ipBlackList;
ghttp服务器启动时将调用函数ghttp::initialize进行初始化:
int initialize(int argc, char *argv[])
{
cout << "ghttp begin initialize..." << endl;
cout << "init param..." << endl;
Util util;
// 读取init.conf文件中的配置参数
Util::configure_new();
......
else
{
......
ipWhiteFile = Util::getConfigureValue("ip_allow_path");
ipBlackFile = Util::getConfigureValue("ip_deny_path");
cout<<"ipWhiteFile:"<<ipWhiteFile<<endl;
cout<<"ipBlackFile:"<<ipBlackFile<<endl;
// 判断是否配置白名单库
if (ipWhiteFile.empty()) {
whiteList = 0;
} else {
whiteList = 1;
}
// 判断是否配置黑名单库
if (ipBlackFile.empty()) {
blackList = 0;
} else {
blackList = 1;
}
......
}
// 判断黑白名单的启用规则,在二者同时启用的情况下,白名单优先
if (whiteList) {
cout << "IP white List enabled." << endl;
ipWhiteList = new IPWhiteList();
ipWhiteList->Load(ipWhiteFile);
} else if (blackList) {
cout << "IP black list enabled." << endl;
ipBlackList = new IPBlackList();
ipBlackList->Load(ipBlackFile);
}
}
在初始化白名单的库时将调用IPWhiteList::Init和IPWhiteList::Load函数进行解析(黑名单库也类似):
// IPWhiteList中的参数ipList用于保存IP库
std::set<std::string> ipList;
// IPWhiteList构造函数
IPWhiteList::IPWhiteList(){
this->Init();
}
// 初始化参数ipList
void IPWhiteList::Init(){
ipList.erase(ipList.begin(), ipList.end());
}
// 通过文件记载IP库
void IPWhiteList::Load(std::string file){
this->Init();
this->ReadIPFromFile(file);
}
// 解析IP库
void IPWhiteList::ReadIPFromFile(std::string file){
ifstream infile(file.c_str());
string line;
if (!infile.is_open())
{
cout << "open white list file failed." << endl;
return;
}
while(getline(infile, line)) {
if (line.length() >= 7) {
int pos = line.find("#");
if(pos != -1)
continue;
this->ipList.insert(line);
}
}
infile.close();
}
在完成初始化后,如果需要实时的更新黑白名单库数据,可以通过调用api接口来修改,将会调用ghttp::IPManager_thread_new(说明:黑白名单的启用规则不支持动态切换,只能通过init.conf进行修改,修改后需要重启ghttp服务才生效),代码如下:
/**
* @brief IP manager
*
* @param response
* @param ips ip string
* @param ip_type 1-black ip 2-white ip
* @param type 1-query 2-save
*/
void IPManager_thread_new(const shared_ptr<HttpServer::Response>& response, string ips, string ip_type, string type) {
Document all;
Document::AllocatorType& allocator = all.GetAllocator();
all.SetObject();
// 查询接口将返回当前生效的规则是白名单还是黑名单,以及生效规则对应的IP库列表
if ( type == "1") { // query
Document responseBody;
Document listDoc;
responseBody.SetObject();
listDoc.SetArray();
// 在初始化时通过是否配置白名单路径来设置改参数值
if (whiteList) // white IP
{
cout << "IP white List enabled." << endl;
responseBody.AddMember("ip_type", "2", allocator);
for (std::set<std::string>::iterator it = ipWhiteList->ipList.begin(); it!=ipWhiteList->ipList.end();it++)
{
Value item(kStringType);
item.SetString((*it).c_str(), allocator);
listDoc.PushBack(item, allocator);
}
}
// 在初始化时通过是否配置黑名单路径来设置改参数值
// (若白名单已生效,则优先使用白名单,即便配置了黑名单库路径)
else if (blackList) // black IP
{
cout << "IP black List enabled." << endl;
responseBody.AddMember("ip_type", "1", allocator);
for (std::set<std::string>::iterator it = ipBlackList->ipList.begin(); it!=ipBlackList->ipList.end();it++)
{
Value item(kStringType);
item.SetString((*it).c_str(), allocator);
listDoc.PushBack(item, allocator);
}
}
......
responseBody.AddMember("ips", listDoc, allocator);
......
}
else if (type == "2") { // save
......
vector<string> ipVector;
Util::split(ips,",", ipVector);
if (ip_type == "1") { // black IP
// 更新黑名单库并重新加载
if (blackList) {
ipBlackList->UpdateIPToFile(ipBlackFile, ipVector, "update by wrokbanch");
// realod ip list
ipBlackList->Load(ipBlackFile);
}
......
}
else if (ip_type == "2") { // white IP
// 更新白名单库并重新加载
if (whiteList) {
ipWhiteList->UpdateIPToFile(ipWhiteFile, ipVector, "update by wrokbanch");
// realod ip list
ipWhiteList->Load(ipWhiteFile);
}
......
}
......
}
}
在更新黑白名单库时将会调用IPWhiteList::UpdateIPToFile和IPBlackList::UpdateIPToFile函数,将最新的配置数据更新到文件中,部分代码片段如下:
/**
* @brief
*
* @param file
* @param ips
* @param reason
*/
void IPWhiteList::UpdateIPToFile(std::string file, vector<std::string>& ips, std::string reason)
{
ofstream outfile;
outfile.open(file.c_str());
if (!outfile)
{
cout << "open white list file failed." << endl;
return;
}
// 记录每次变更的原因
outfile << "#" << reason << "\n";
for(vector<std::string>::iterator it = ips.begin(); it != ips.end(); it++)
{
outfile << (*it) << "\n";
}
outfile.close();
}
黑白名单校验一般是在开启gstore网络服务(如ghttp服务)后,外部通过接口对数据库进行操作时,会对发起请求的客户端IP进行验证,是否满足黑白名单规则要求,部分代码片段如下:
// 在请求处理的子线程中会对请求的接口进行IP验证
void request_thread(const shared_ptr<HttpServer::Response>& response,
const shared_ptr<HttpServer::Request>& request, string RequestType)
{
// 验证IP是否符合黑白名单的访问规则
if (!ipCheck(request)) {
string error = "IP Blocked!";
sendResponseMsg(1101, error, response,remote_ip,"ipCheck");
return;
}
......
}
在函数ghttp::ipCheck中,会判断当前黑白名单生效的规则,再根据生效规则进行校验,代码如下:
bool ghttp::ipCheck(const shared_ptr<HttpServer::Request>& request){
// 获取请求的IP地址
string remote_ip;
unordered_multimap<std::string, std::string, case_insensitive_hash, case_insensitive_equals> m=request->header;
string map_key = "X-Real-IP";
pair<std::unordered_multimap<std::string, std::string, case_insensitive_hash, case_insensitive_equals>::iterator,std::unordered_multimap<std::string, std::string, case_insensitive_hash, case_insensitive_equals>::iterator> lu = m.equal_range(map_key);
if(lu.first != lu.second)
remote_ip = lu.first->second;
else
remote_ip = request->remote_endpoint_address;
// 执行白名单规则
if(whiteList == 1) {
return ipWhiteList->Check(remote_ip);
}
// 执行黑名单规则
else if(blackList == 1){
return ipBlackList->Check(remote_ip);
}
return true;
}
// 白名单规则匹配:命中规则返回true,否则返回false
bool IPWhiteList::Check(std::string ip){
if(this->ipList.empty())
return false;
else if(this->ipList.find(ip) != this->ipList.end())
return true;
for(std::set<string>::iterator it=this->ipList.begin(); it!=this->ipList.end(); ++it) {
// case for xxx.xxx.xxx.xxx-xxx.xxx.xxx.xxx
string test = *it;
if(test.find("-") != -1){
std::vector<std::string> fields;
std::vector<std::string> start;
std::vector<std::string> end;
std::vector<std::string> test_ip;
boost::split( fields, test, boost::is_any_of( "-" ) );
boost::split( start, fields[0], boost::is_any_of( "\\." ) );
boost::split( end, fields[1], boost::is_any_of( "\\." ) );
boost::split( test_ip, ip, boost::is_any_of( "\\." ) );
bool res = true;
for(int i = 0; i < 4; i++){
int s = std::stoi(start[i]);
int e = std::stoi(end[i]);
int candidate = std::stoi(test_ip[i]);
if(!(s <= candidate && candidate <= e)){
res = false;
break;
}
}
if(res)
return true;
}
}
return false;
}
// 黑名单规则匹配:命中规则返回false,否则返回true
bool IPBlackList::Check(std::string ip){
if(this->ipList.empty())
return true;
else if(this->ipList.find(ip) != this->ipList.end())
return false;
for(std::set<string>::iterator it=this->ipList.begin(); it!=this->ipList.end(); ++it){
// case for xxx.xxx.xxx.xxx-xxx.xxx.xxx.xxx
string test = *it;
if(test.find("-") != -1){
std::vector<std::string> fields;
std::vector<std::string> start;
std::vector<std::string> end;
std::vector<std::string> test_ip;
boost::split( fields, test, boost::is_any_of( "-" ) );
boost::split( start, fields[0], boost::is_any_of( "\\." ) );
boost::split( end, fields[1], boost::is_any_of( "\\." ) );
boost::split( test_ip, ip, boost::is_any_of( "\\." ) );
bool res = false;
for(int i = 0; i < 4; i++){
int s = std::stoi(start[i]);
int e = std::stoi(end[i]);
int candidate = std::stoi(test_ip[i]);
if(!(s <= candidate && candidate <= e)){
res = true;
break;
}
}
if(!res)
return false;
}
}
return true;
}
本章节介绍了安全机制的黑白名单模块,分析了如何配置黑白名单、如何管理黑白名单以及如何使用黑白名单规则来校验请求,建议在阅读的同时结合源码Main/ghttp.cpp、Util/IPWhiteList.cpp、Util/IPBlackList.cpp一起分析,会更容易理解。下一章将解析gstore安全机制中的日志追踪模块。