暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

#gStore-weekly | gStore源码解析(四):安全机制之黑白名单配置解析

原创 图谱学苑 2022-06-23
457
上一章我们介绍了安全机制的用户权限管理,接下来将对安全机制中黑白名单配置的源码进行解析。

1.1 简介

在gstore安全机制中的黑白名单是指访问IP的黑名单库和白名单库;黑名单规则为:黑名单库中的IP将被禁止访问,白名单规则为:仅允许白名单库中的IP访问;原则上黑白名单的配置为二选一,若二者都开启则是白名单优先。

1.2 配置黑白名单

首先通过gstore根目录的init.conf文件进行配置:启动黑名单或者白名单模式;默认为黑名单模式,如下配置文件所示:
  1. [ghttp]

  2. ......

  3. # 白名单库文件路径,为空表示不启用

  4. ip_allow_path=""

  5. # 黑名单库文件路径,为空表示不启用

  6. ip_deny_path="ipDeny.config"

  7. ......

然后根据配置的模式,可以复制对应的黑白名单库示例文件进行配置,配置支持单个IP和某一范围的IP组,如下所示:
  1. #ipDeny.config.example

  2. #two kinds of ip format

  3. #1. a single ip address

  4. 1.2.2.1

  5. #2. a closed intervel represent by "-", to allow for a segment of ips or deny a segment of ips

  6. 1.1.1.1-1.1.1.2



  7. ###########################################################

  8. # Notice!!!! #

  9. # 1. use "#" to comment out a Whole line #

  10. # 2. ip address write in one line #

  11. # 3. do not include extra space or other char #

  12. # 4. keep it only numbers or with one '-' #

  13. # 5. to enable black list, use "--ipDeny=<filename>" #

  14. ###########################################################


1.3 黑白名单管理

在完成黑白名单的配置后,我们还可以通过接口进行动态的配置,ghttp网络服务提供了黑白名单的查询和更新接口。
当ghttp网络服务启动时,将会对黑白名单进行初始化,读取配置文件init.conf配置的黑白名单库路径,并读取解析黑白名单的库数据,部分关键代码如下:
  1. // 与黑白名单相关的全局参数

  2. // 是否启用黑名单

  3. int blackList = 0;

  4. // 是否启用白名单

  5. int whiteList = 0;

  6. // 白名单库路径

  7. string ipBlackFile = "ipDeny.config";

  8. // 黑名单库路径

  9. string ipWhiteFile = "ipAllow.config";

  10. // 白名单库

  11. IPWhiteList* ipWhiteList;

  12. // 黑名单库

  13. IPBlackList* ipBlackList;

ghttp服务器启动时将调用函数ghttp::initialize进行初始化:
  1. int initialize(int argc, char *argv[])

  2. {

  3. cout << "ghttp begin initialize..." << endl;

  4. cout << "init param..." << endl;

  5. Util util;

  6. // 读取init.conf文件中的配置参数

  7. Util::configure_new();

  8. ......

  9. else

  10. {

  11. ......

  12. ipWhiteFile = Util::getConfigureValue("ip_allow_path");

  13. ipBlackFile = Util::getConfigureValue("ip_deny_path");

  14. cout<<"ipWhiteFile:"<<ipWhiteFile<<endl;

  15. cout<<"ipBlackFile:"<<ipBlackFile<<endl;

  16. // 判断是否配置白名单库

  17. if (ipWhiteFile.empty()) {

  18. whiteList = 0;

  19. } else {

  20. whiteList = 1;

  21. }

  22. // 判断是否配置黑名单库

  23. if (ipBlackFile.empty()) {

  24. blackList = 0;

  25. } else {

  26. blackList = 1;

  27. }

  28. ......

  29. }

  30. // 判断黑白名单的启用规则,在二者同时启用的情况下,白名单优先

  31. if (whiteList) {

  32. cout << "IP white List enabled." << endl;

  33. ipWhiteList = new IPWhiteList();

  34. ipWhiteList->Load(ipWhiteFile);

  35. } else if (blackList) {

  36. cout << "IP black list enabled." << endl;

  37. ipBlackList = new IPBlackList();

  38. ipBlackList->Load(ipBlackFile);

  39. }

  40. }

在初始化白名单的库时将调用IPWhiteList::Init和IPWhiteList::Load函数进行解析(黑名单库也类似):
  1. // IPWhiteList中的参数ipList用于保存IP库

  2. std::set<std::string> ipList;


  3. // IPWhiteList构造函数

  4. IPWhiteList::IPWhiteList(){

  5. this->Init();

  6. }

  7. // 初始化参数ipList

  8. void IPWhiteList::Init(){

  9. ipList.erase(ipList.begin(), ipList.end());

  10. }

  11. // 通过文件加载IP库

  12. void IPWhiteList::Load(std::string file){

  13. this->Init();

  14. this->ReadIPFromFile(file);

  15. }

  16. // 解析IP库

  17. void IPWhiteList::ReadIPFromFile(std::string file){

  18. ifstream infile(file.c_str());

  19. string line;

  20. if (!infile.is_open())

  21. {

  22. cout << "open white list file failed." << endl;

  23. return;

  24. }

  25. while(getline(infile, line)) {

  26. if (line.length() >= 7) {

  27. int pos = line.find("#");

  28. if(pos != -1)

  29. continue;

  30. this->ipList.insert(line);

  31. }

  32. }

  33. infile.close();

  34. }

在完成初始化后,如果需要实时的更新黑白名单库数据,可以通过调用api接口来修改,将会调用ghttp::IPManager_thread_new(说明:黑白名单的启用规则不支持动态切换,只能通过init.conf进行修改,修改后需要重启ghttp服务才生效),代码如下:
  1. /**

  2. * @brief IP manager

  3. *

  4. * @param response

  5. * @param ips ip string

  6. * @param ip_type 1-black ip 2-white ip

  7. * @param type 1-query 2-save

  8. */

  9. void IPManager_thread_new(const shared_ptr<HttpServer::Response>& response, string ips, string ip_type, string type) {

  10. Document all;

  11. Document::AllocatorType& allocator = all.GetAllocator();

  12. all.SetObject();

  13. // 查询接口将返回当前生效的规则是白名单还是黑名单,以及生效规则对应的IP库列表

  14. if ( type == "1") { // query

  15. Document responseBody;

  16. Document listDoc;

  17. responseBody.SetObject();

  18. listDoc.SetArray();

  19. // 在初始化时通过是否配置白名单路径来设置改参数值

  20. if (whiteList) // white IP

  21. {

  22. cout << "IP white List enabled." << endl;

  23. responseBody.AddMember("ip_type", "2", allocator);

  24. for (std::set<std::string>::iterator it = ipWhiteList->ipList.begin(); it!=ipWhiteList->ipList.end();it++)

  25. {

  26. Value item(kStringType);

  27. item.SetString((*it).c_str(), allocator);

  28. listDoc.PushBack(item, allocator);

  29. }

  30. }

  31. // 在初始化时通过是否配置黑名单路径来设置改参数值

  32. // (若白名单已生效,则优先使用白名单,即便配置了黑名单库路径)

  33. else if (blackList) // black IP

  34. {

  35. cout << "IP black List enabled." << endl;

  36. responseBody.AddMember("ip_type", "1", allocator);

  37. for (std::set<std::string>::iterator it = ipBlackList->ipList.begin(); it!=ipBlackList->ipList.end();it++)

  38. {

  39. Value item(kStringType);

  40. item.SetString((*it).c_str(), allocator);

  41. listDoc.PushBack(item, allocator);

  42. }

  43. }

  44. ......

  45. responseBody.AddMember("ips", listDoc, allocator);

  46. ......

  47. }

  48. else if (type == "2") { // save

  49. ......

  50. vector<string> ipVector;

  51. Util::split(ips,",", ipVector);

  52. if (ip_type == "1") { // black IP

  53. // 更新黑名单库并重新加载

  54. if (blackList) {

  55. ipBlackList->UpdateIPToFile(ipBlackFile, ipVector, "update by wrokbanch");

  56. // realod ip list

  57. ipBlackList->Load(ipBlackFile);

  58. }

  59. ......

  60. }

  61. else if (ip_type == "2") { // white IP

  62. // 更新白名单库并重新加载

  63. if (whiteList) {

  64. ipWhiteList->UpdateIPToFile(ipWhiteFile, ipVector, "update by wrokbanch");

  65. // realod ip list

  66. ipWhiteList->Load(ipWhiteFile);

  67. }

  68. ......

  69. }

  70. ......

  71. }

  72. }

在更新黑白名单库时将会调用IPWhiteList::UpdateIPToFile和IPBlackList::UpdateIPToFile函数,将最新的配置数据更新到文件中,部分代码片段如下:
  1. /**

  2. * @brief

  3. *

  4. * @param file

  5. * @param ips

  6. * @param reason

  7. */

  8. void IPWhiteList::UpdateIPToFile(std::string file, vector<std::string>& ips, std::string reason)

  9. {

  10. ofstream outfile;

  11. outfile.open(file.c_str());

  12. if (!outfile)

  13. {

  14. cout << "open white list file failed." << endl;

  15. return;

  16. }

  17. // 记录每次变更的原因

  18. outfile << "#" << reason << "\n";

  19. for(vector<std::string>::iterator it = ips.begin(); it != ips.end(); it++)

  20. {

  21. outfile << (*it) << "\n";

  22. }

  23. outfile.close();

  24. }

1.4 黑白名单校验

黑白名单校验一般是在开启gstore网络服务(如ghttp服务)后,外部通过接口对数据库进行操作时,会对发起请求的客户端IP进行验证,是否满足黑白名单规则要求,部分代码片段如下:
  1. // 在请求处理的子线程中会对请求的接口进行IP验证

  2. void request_thread(const shared_ptr<HttpServer::Response>& response,

  3. const shared_ptr<HttpServer::Request>& request, string RequestType)

  4. {

  5. // 验证IP是否符合黑白名单的访问规则

  6. if (!ipCheck(request)) {

  7. string error = "IP Blocked!";

  8. sendResponseMsg(1101, error, response,remote_ip,"ipCheck");

  9. return;

  10. }

  11. ......

  12. }

在函数ghttp::ipCheck中,会判断当前黑白名单生效的规则,再根据生效规则进行校验,代码如下:
  1. bool ghttp::ipCheck(const shared_ptr<HttpServer::Request>& request){

  2. // 获取请求的IP地址

  3. string remote_ip;

  4. unordered_multimap<std::string, std::string, case_insensitive_hash, case_insensitive_equals> m=request->header;

  5. string map_key = "X-Real-IP";

  6. 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);

  7. if(lu.first != lu.second)

  8. remote_ip = lu.first->second;

  9. else

  10. remote_ip = request->remote_endpoint_address;

  11. // 执行白名单规则

  12. if(whiteList == 1) {

  13. return ipWhiteList->Check(remote_ip);

  14. }

  15. // 执行黑名单规则

  16. else if(blackList == 1){

  17. return ipBlackList->Check(remote_ip);

  18. }

  19. return true;

  20. }

  1. // 白名单规则匹配:命中规则返回true,否则返回false

  2. bool IPWhiteList::Check(std::string ip){

  3. if(this->ipList.empty())

  4. return false;

  5. else if(this->ipList.find(ip) != this->ipList.end())

  6. return true;

  7. for(std::set<string>::iterator it=this->ipList.begin(); it!=this->ipList.end(); ++it) {

  8. // case for xxx.xxx.xxx.xxx-xxx.xxx.xxx.xxx

  9. string test = *it;

  10. if(test.find("-") != -1){

  11. std::vector<std::string> fields;

  12. std::vector<std::string> start;

  13. std::vector<std::string> end;

  14. std::vector<std::string> test_ip;

  15. boost::split( fields, test, boost::is_any_of( "-" ) );

  16. boost::split( start, fields[0], boost::is_any_of( "\\." ) );

  17. boost::split( end, fields[1], boost::is_any_of( "\\." ) );

  18. boost::split( test_ip, ip, boost::is_any_of( "\\." ) );

  19. bool res = true;

  20. for(int i = 0; i < 4; i++){

  21. int s = std::stoi(start[i]);

  22. int e = std::stoi(end[i]);

  23. int candidate = std::stoi(test_ip[i]);


  24. if(!(s <= candidate && candidate <= e)){

  25. res = false;

  26. break;

  27. }

  28. }

  29. if(res)

  30. return true;

  31. }

  32. }

  33. return false;

  34. }

  1. // 黑名单规则匹配:命中规则返回false,否则返回true

  2. bool IPBlackList::Check(std::string ip){

  3. if(this->ipList.empty())

  4. return true;

  5. else if(this->ipList.find(ip) != this->ipList.end())

  6. return false;

  7. for(std::set<string>::iterator it=this->ipList.begin(); it!=this->ipList.end(); ++it){

  8. // case for xxx.xxx.xxx.xxx-xxx.xxx.xxx.xxx

  9. string test = *it;

  10. if(test.find("-") != -1){

  11. std::vector<std::string> fields;

  12. std::vector<std::string> start;

  13. std::vector<std::string> end;

  14. std::vector<std::string> test_ip;

  15. boost::split( fields, test, boost::is_any_of( "-" ) );

  16. boost::split( start, fields[0], boost::is_any_of( "\\." ) );

  17. boost::split( end, fields[1], boost::is_any_of( "\\." ) );

  18. boost::split( test_ip, ip, boost::is_any_of( "\\." ) );

  19. bool res = false;

  20. for(int i = 0; i < 4; i++){

  21. int s = std::stoi(start[i]);

  22. int e = std::stoi(end[i]);

  23. int candidate = std::stoi(test_ip[i]);

  24. if(!(s <= candidate && candidate <= e)){

  25. res = true;

  26. break;

  27. }

  28. }

  29. if(!res)

  30. return false;

  31. }

  32. }

  33. return true;

  34. }

1.6 小结

本章节介绍了安全机制的黑白名单模块,分析了如何配置黑白名单、如何管理黑白名单以及如何使用黑白名单规则来校验请求,建议在阅读的同时结合源码Main/ghttp.cpp、Util/IPWhiteList.cpp、Util/IPBlackList.cpp一起分析,会更容易理解。下一章将解析gstore安全机制中的日志追踪模块。


针对gStore有任何问题也可通过加运营同学微信,邀请加入gStore图谱社区咨询。
诚邀大家参加
·gStore-weekly技术文章征集活动·
  相关技术文章,包含但不限于以下内容:系统技术解析、案例分享、实践总结、开发心得、客户案例、使用技巧、学习笔记等。文章要求原创。
  入选周刊即送精美礼品~
最后修改时间:2022-06-23 11:50:50
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论