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


    ​ 上一章我们介绍了安全机制的用户权限管理,接下来将对安全机制中黑白名单配置的源码进行解析。

    1.1 简介

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

    1.2 配置黑白名单

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

    [ghttp]
    ......
    # 白名单库文件路径,为空表示不启用
    ip_allow_path=""
    # 黑名单库文件路径,为空表示不启用
    ip_deny_path="ipDeny.config"
    ......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    ​ 然后根据配置的模式,可以复制对应的黑白名单库示例文件进行配置,配置支持单个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>"   #
    ###########################################################
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    1.3 黑白名单管理

    ​ 在完成黑白名单的配置后,我们还可以通过接口进行动态的配置,ghttp网络服务提供了黑白名单的查询和更新接口。

    ​ 当ghttp网络服务启动时,将会对黑白名单进行初始化,读取配置文件init.conf配置的黑白名单库路径,并读取解析黑白名单的库数据,部分关键代码如下:

    // 与黑白名单相关的全局参数
    // 是否启用黑名单
    int blackList = 0;
    // 是否启用白名单
    int whiteList = 0;
    // 白名单库路径
    string ipBlackFile = "ipDeny.config";
    // 黑名单库路径
    string ipWhiteFile = "ipAllow.config";
    // 白名单库
    IPWhiteList* ipWhiteList;
    // 黑名单库
    IPBlackList* ipBlackList;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ​ 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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    ​ 在初始化白名单的库时将调用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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    ​ 在完成初始化后,如果需要实时的更新黑白名单库数据,可以通过调用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);
    			}
    			......
    		}
    		......
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    ​ 在更新黑白名单库时将会调用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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    1.4 黑白名单校验

    ​ 黑白名单校验一般是在开启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;
    	}
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    ​ 在函数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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    // 白名单规则匹配:命中规则返回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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    // 黑名单规则匹配:命中规则返回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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    1.6 小结

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

  • 相关阅读:
    C语言-入门-static(十五)
    《Vue入门到精通系列之四》--- vue cli详解
    【Jetpack Compose】BOM是什么?
    【Python脚本进阶】1.2、python脚本基础知识(中)
    Flink Log4j 2.x使用Filter过滤日志类型
    (实战)[自动驾驶赛车-中国联赛]-合集
    【每日一题】移除链表元素(C语言)
    【校招VIP】专业课考点之网络存储
    操作教程|如何将DataEase开源工具嵌入第三方系统?
    Multilinear Relationship Networks:多任务学习框架
  • 原文地址:https://blog.csdn.net/weixin_48167662/article/details/125479954