Implementation of access control module based on SDN

Posted by reyes99 on Mon, 25 Oct 2021 03:38:44 +0200

1, Background

1. Access control

Access control technology refers to preventing unauthorized access to any resources, so that the computer system can be used within the legal scope. It refers to a technology that restricts users' access to certain information items or the use of certain control functions by user identity and some item definition group to which they belong. Access control is usually used by system administrators to control users' access to servers, directories, files and other network resources.

2. Firewall

Firewall is the implementation of the most common access control technology. It limits the access of the access subject to the object by setting a series of access filtering rules. The most common firewalls are packet filtering firewall and state detection firewall.

(1) Packet filtering firewall

Packet filtering refers to checking each packet at the network layer and forwarding or discarding packets according to the configured security policy. The basic principle of packet filtering firewall is to filter packets by configuring access control list (ACL). It is mainly based on the source / destination IP address, source / destination port number, IP identification and message transmission direction in the packet.

(2) State detection firewall

State detection is an extension of packet filtering technology. Packet filtering based on connection status not only regards each packet as an independent unit, but also considers the historical relevance of the packets before and after. We know that the establishment of all data streams based on reliable connection (i.e. data streams based on TCP protocol) needs to go through three processes of "client synchronization request", "server response" and "client re response" (i.e. the "three handshakes" process), which shows that each data packet does not exist independently, but has a close state relationship. Based on this state relation, a state detection technology is developed.

(3) Proxy firewall

Agent service acts on the application layer of the network. Its essence is to take over the business directly between the internal network and the external network users by the agent. The proxy checks the request from the user. After the user passes the security policy check, the firewall will establish a connection with the real server on behalf of the external user, forward the external user request, and return the response returned by the real server to the external user.

3. Design background of this module

This module is an access control module based on SDN, which mainly realizes the function of packet filtering firewall. However, due to the packet filtering firewall, its static ACL rules are difficult to meet the dynamic security requirements. For example, in some networks, the permissions change dynamically. If static firewall rules are used, the dynamic change of rules is more troublesome, Therefore, with the flexibility of SDN, the access control is more flexible and adaptive.

Two. Module design and framework

1. Design Overview

According to the background, we know that access control is to control the terminal's access to external resources such as servers and files. That is, the link connection that controls the external access of the terminal device controls the access request. In the design of access control module based on SDN network architecture, the design quantifies access control into four steps: information acquisition, identification and matching, permission acquisition and flow table distribution.

(1) Information acquisition: as shown in the system model diagram, the actual access control is to realize the external access of the host by controlling OVS. SDN is used in this module design to realize the control of OVS flow table entries. That is, add an SDN switch between the switch and the gateway route to intercept the information, and then upload the IP address and other information to the controller (Ryu). The controller interacts with the system program through the north interface (API), and the system program obtains the host information from the controller.

(2) Identification: Based on the information obtained in the previous step, identification is mainly used to record and query according to the source ip, destination ip, destination port and other information to see whether there is access permission.

(3) Obtaining permission: after matching the system program with the database, you can find out whether the device has permission to access the resources it wants to access. The permissions include yes, no and exception (return exception information). (because the system focuses on controlling the acquisition of resources by the host, usually the acquisition of resources mainly uses tcp(http) protocol connection, so the system grabs TCP protocol packets)

(4) Distribution of flow table: after obtaining the permission, the system program is required to control the ovs. If there is permission, the flow table item is distributed to the SDN switch to allow the request data of the device to be forwarded; If you do not have permission, send the flow table entry to the SDN switch and do not allow (Drop) the request data forwarding of the device; If the permission is abnormal, send the flow table entry to the SDN switch, and there is no processing.

2. System framework

3, Experimental topo construction

1. topo construction

The experimental topo of this system is simulated based on mininet. topo is as follows.

  Two autonomous regions as1 and as2

as1: as1-c1 is the control host, as1-c2 and as1-c3 are the experimental host, and as1-ctrl 1 is the controller

as2: as2-c1 control host, as2-c2 and as2-c3 as experimental host, as2-ctrl 2 as controller

test-s1 and test-s2 are the resource servers of the experiment

  2. Controller configuration

The ryu controller used in this system has ready-made rest_api and switch cases. When setting topo, in order to more reasonably reflect the real network environment, each autonomous domain has a separate controller. In order to isolate things in each autonomous domain as much as possible, the controller is run in docker in this design. For the image and operation of docker used in this design, refer to the previous article of the author: Docker command, deployment of docker based SDN experimental environment (1)_ Beifeng CSDN blog.

First, create container 1 as the running environment of the controller of autonomous domain 1, in which ryu has been installed, and the ip address is 172.17.0.2:

docker run -it --name as1-ryu0 -v /home/ymumu/ryu/ryu/app:/home/ryu/ryu/app ymumu/ryu:0.1 /bin/bash

  In the container, run the ofctl of ryu_ Rest.py and simple_switch_13.py

  Then, create container 2 as the controller running environment of autonomous domain 2, and the ip address is 172.17.0.3:

docker run -it --name as2-ryu0 -v /home/ymumu/ryu/ryu/app:/home/ryu/ryu/app ymumu/ryu:0.1 /bin/bash

  In the container, run the ofctl of ryu_ Rest.py and simple_switch_13.py

  3. Router ip and routing table configuration

In order to make the whole network topo realize normal network interworking, the router and routing table must be configured.

Assign ip to router r8:

test-s1 add routing table:

test-s2 add routing table:

as1-c1 add routing information:

as1-c2 add routing information:

as1-c3 add routing information:

as2-c1 add routing information:

as2-c2 add routing information:

as2-c3 add routing information:

  4, Implementation of access control technology

1. C/S connection information acquisition

Connection information acquisition is the first step of access control. Only through the interception and analysis of connection information can we know the source ip, destination ip, destination port and other information of this connection, and then obtain the permission information through database query. The following is the method for the system to obtain connection information.

In the simulated topo, I use tcpdump to capture packets and upload them to the application layer end of the system (in practical application, this is not the way to capture packets, continuous attention and later update). Packet capture data is to monitor the connection between the host and the server, that is, for example, in this model system, as1-c2 accesses test-s1 to obtain its resources, and requires http protocol to obtain the connection. Tcpdump is used to capture the data packet and send it to the application layer end of the system. The application layer end extracts the source ip, destination ip and destination port information.

The code for capturing information is as follows:

"""
utilize python Real time acquisition tcpdump And send it to the server:
In the network structure, it is mainly monitoring as1-ovs1-eth2 The network data information of the network card, that is, monitoring as1-c2 and as1-c3 Access information for.
By capturing packets, you can know the access from the server to the client, and through the processing of this system, you can realize access authentication.
"""

import subprocess
import socket
import time


class GrabBag:
    def __init__(self, ipaddr, port):
        self.ipaddr = ipaddr
        self.port = port

    def py_tcpdump_as1(self):
        c1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        c1.connect((self.ipaddr, self.port))
        # For the tcp(http) connection where the destination host is 10.0.3.1 or 10.0.3.2 (two servers), as1-ovs1-eth2 is the necessary network card from the client to the server
        proc = subprocess.Popen('tcpdump dst host 10.0.3.1 or 10.0.3.2 -l -i as1-ovs1-eth2 and tcp -n',
                                stdout=subprocess.PIPE, shell=True)

        while True:
            line = proc.stdout.readline().decode(encoding='utf-8')
            line = line.strip()
            if not line:
                print('tcpdump finished...')
                break
            print(line)
            c1.send(line.encode())
            time.sleep(0.3)

The communication between the packet capturing program and the application layer end of the system adopts tcp socket. The packet capturing program is equivalent to tcp client, the receiving program at the application layer end is equivalent to server, and the server adopts multi process socket to receive and process the packet capturing information of different autonomous domains at the same time. The server code is as follows:

import socket
import re
from multiprocessing import Process
from app.dataBase_candle import FindAuth
from app.flowTable_operate import PostOperation

"""
tcpdump Packet capturing server:
It is mainly used to receive the connection information of the terminal accessing the resource server for packet capture monitoring, and send the caught packets here for processing,
Get source ip,objective ip,The destination port and other information, and then go to the database to find the connection permission. If the permission is available, issue the corresponding through flow table entry

The server of the system adopts multi process implementation socket The multi connection of the client can process and receive the information sent by multiple packet capturing programs.
"""


class Server1:
    def __init__(self, host, port, lst_num):
        self.host = host
        self.port = port
        self.lst_num = lst_num

    # data processing
    def recv_message(self, conn, addr):
        data = conn.recv(1024)
        # Analyze the received packet capture information to obtain the source ip, destination ip, destination port and other information
        result = re.findall(".*IP (.*): Flags.*", data.decode('utf-8'))
        list_res = result[0].split('> ')
        src_ip = '.'.join(list_res[0].split('.')[:4])
        dst_ip, dst_port = '.'.join(list_res[1].split('.')[0:4]), list_res[1].split('.')[4:]
        # print(src_ip, dst_ip, dst_port[0])

        # Get permission
        find_auth = FindAuth()
        auth_res = find_auth.get_data(src_ip=src_ip, dst_ip=dst_ip, dst_port=dst_port[0])
        as_res = find_auth.get_as(src_ip=src_ip)
        print("from {0}:".format(addr), data.decode('utf-8'))
        print('jurisdiction:', auth_res)

        # Select and distribute flow table entries to different controllers according to the autonomous domain
        if as_res == 'as1':
            postTable = PostOperation('172.17.0.2', '8080')
            postTable.post_add_flow(src_ip=src_ip, dst_ip=dst_ip, dst_port=dst_port[0], auth=auth_res)
        elif as_res == 'as2':
            postTable = PostOperation('172.17.0.3', '8080')
            postTable.post_add_flow(src_ip=src_ip, dst_ip=dst_ip, dst_port=dst_port[0], auth=auth_res)

    # The data reception of the server uses multiple processes when calling
    def server_link(self, conn, addr):
        conn.send("Welcome connect!".encode())

        while True:
            try:
                self.recv_message(conn, addr)
            except Exception:
                break

        conn.close()

    # Server startup program
    def server_start(self):
        # IPv4
        s_pro = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # The operating system will release the port of the server immediately after the server socket is closed or the server process is terminated
        s_pro.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        s_pro.bind((self.host, self.port))
        s_pro.listen(self.lst_num)
        print('Waiting link...')
        while True:
            conn, addr = s_pro.accept()
            print("Success connect from ", addr)
            # Start multi process to realize multi connection
            p = Process(target=self.server_link, args=(conn, addr))
            p.start()

Note: in fact, a simpler way is not to capture packets. Only when the permission in the database record changes, the system will actively distribute the flow table rules. However, the reason is not that when the permission changes, the flow table items are distributed by the application layer, but after the packet capture is analyzed and compared with the database, because there may be hosts not in the new database record in the network, At this time, you need to capture packets, find and report to the system.

2. Database design

(1) MySQL table design

The database designs two tables: one is the permission record table and the other is the host domain attribution table.

Permission record table: this table records the permissions of each connection. A unique record can be determined through source ip, destination ip and destination port to determine whether this connection has permission to access resources, as shown in the following figure.

Host domain attribution table: this table is used to record the domain corresponding to each host ip, that is, the corresponding controller. When issuing the flow table item, select the controller according to the domain, as shown in the following figure.

  MySQL statements are as follows.

CREATE DATABASE auth_data_db;
USE auth_data_db;

-- Create a permission record table to record the permissions of access connections. An access connection is controlled by the source ip,objective ip,Destination port determination
CREATE TABLE tb_as(
	rno INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
	src_ip VARCHAR(20) NOT NULL,
	dst_ip VARCHAR(20) NOT NULL,
	dst_port INT NOT NULL,
	acc_auth VARCHAR(10) NOT NULL
);

-- drop table tb_as;
-- delete from tb_as;

ALTER TABLE tb_as AUTO_INCREMENT=1001;

-- Insert permission data
INSERT INTO tb_as(src_ip, dst_ip, dst_port, acc_auth) VALUES
	('10.0.1.2', '10.0.3.1', 8001, 'yes'),
	('10.0.1.2', '10.0.3.1', 8002, 'yes'),
	('10.0.1.2', '10.0.3.1', 8003, 'no'),
	('10.0.1.3', '10.0.3.1', 8001, 'unknown'),
	('10.0.1.3', '10.0.3.1', 8002, 'no'),
	('10.0.1.3', '10.0.3.1', 8003, 'yes'),
	('10.0.1.2', '10.0.3.2', 8001, 'no'),
	('10.0.1.2', '10.0.3.2', 8002, 'no'),
	('10.0.1.2', '10.0.3.2', 8003, 'unknown'),
	('10.0.1.3', '10.0.3.2', 8001, 'yes'),
	('10.0.1.3', '10.0.3.2', 8002, 'no'),
	('10.0.1.3', '10.0.3.2', 8003, 'yes'),
	('10.0.2.2', '10.0.3.1', 8001, 'no'),
	('10.0.2.2', '10.0.3.1', 8002, 'yes'),
	('10.0.2.2', '10.0.3.1', 8003, 'yes'),
	('10.0.2.3', '10.0.3.1', 8001, 'no'),
	('10.0.2.3', '10.0.3.1', 8002, 'no'),
	('10.0.2.3', '10.0.3.1', 8003, 'yes'),
	('10.0.2.2', '10.0.3.2', 8001, 'unknown'),
	('10.0.2.2', '10.0.3.2', 8002, 'no'),
	('10.0.2.2', '10.0.3.2', 8003, 'no'),
	('10.0.2.3', '10.0.3.2', 8001, 'yes'),
	('10.0.2.3', '10.0.3.2', 8002, 'yes'),
	('10.0.2.3', '10.0.3.2', 8003, 'no');


select acc_auth from tb_as where src_ip='10.0.1.2' and dst_ip='10.0.3.1' and dst_port='8001';


-- Create the domain table, that is, the record host ip Address and its autonomous domain
Create table tb_as_ascription(
	ip varchar(20) PRIMARY KEY NOT NULL,
	asc_as varchar(10) not null
);

insert into tb_as_ascription values
	('10.0.1.1', 'as1'),
	('10.0.1.2', 'as1'),
	('10.0.1.3', 'as1'),
	('10.0.2.1', 'as2'),
	('10.0.2.2', 'as2'),
	('10.0.2.3', 'as2');

SELECT asc_as FROM tb_as_ascription WHERE ip='10.0.2.1';


select asc_as from tb_as_ascription WHERE ip is (select src_ip)


-- alter table tb_as AUTO_INCREMENT = 1001;
-- INSERT INTO tb_as(src_ip, dst_ip, dst_port, acc_auth) VALUES
-- 	('10.0.5.2', '10.0.5.1', 8001, 'yes');

-- select count(rno) from tb_as where src_ip='10.0.1.2' and dst_ip='10.0.3.1' and dst_port=8001;

update tb_as SET acc_auth='yes' WHERE src_ip='10.0.1.2' and dst_ip='10.0.3.1' and dst_port=8001;

  (2) . redis table design

  In the query process, because the query speed of MySQL is relatively slow, in order not to affect the connection speed, the system uses MySQL as the persistent storage database and redis as the cache database to speed up the query speed. In this system, the redis table is designed to use string data, so that each record is stored in the form of key value pairs. The source ip, destination ip and destination port of each connection are used as the key of the string table, and the permission information is used as the value of the string table, that is, a record:

set '10.0.1.2:10.0.3.1:8001' 'yes'

At the same time, in order to prevent too much redis cache data, clear the infrequent cache data and set the expiration time:

expire'10.0.1.2:10.0.3.1:8001' 300

That is, the expiration time is 5 minutes.

(3) Database record update code

During the operation of the system, the system administrator may change the connection permissions at any time, that is, update the database records. Because the system queries the redis cache first and then the MySQL database records, it is necessary to update the redis records first and then the MySQL records. The database update program code is as follows. For details on the data consistency between redis and MYSQL, see: Implementation of MySQL and redis data synchronization based on python (redis as cache)_ Beifeng CSDN blog

"""
Database update code:
This section is mainly used to update permission database records.
When updating the database, consider redis and MySQL Data consistency problem. Therefore, when updating data, query first redis Is there such a record in the? If so,
Delete first redis Record and insert data into redis,Then insert the data MySQL After successful insertion, update the data to redis. 
if redis If there is no record in, it will be updated directly MySQL Records in.
"""

    """
    The operation of updating data in order to avoid updating MySQL After, redis The query of this period of neutral time has not been updated, so update it first redis,
    Re update MySQL,then MySQL After the successful submission, the redis Update again
    """

    def post_data(self):
        # insert data
        print('Enter update data:')
        src_ip = input('src_ip:')
        dst_ip = input('dst_ip:')
        dst_port = input('dst_port:')
        auth = input('auth:')
        # key of redis string type table
        key = src_ip + ':' + dst_ip + ':' + dst_port

        # First query whether there is data in the redis database. If there is data, update redis and then mysql. If there is no data, update it in MySQL. Submit it successfully and update redis again
        result = self.r0.get(key)
        # If there is data in reids, you need to update the data, that is, clear it first and then write it; After writing redis, write the data to MySQL
        if result:
            # Clear data
            self.r0.delete(key)
            self.r0.set(key, auth)
            self.r0.expire(key, 600)  # Set expiration time

            # First query whether there are records in the MySQL table. If there are no records, insert the records; If there are records, modify them
            count = 0
            with self.conn.cursor() as find_cursor:
                try:
                    count = find_cursor.execute(
                        'select count(rno) from tb_as where src_ip=%s and dst_ip=%s and dst_port=%s',
                        (src_ip, dst_ip, int(dst_port),)
                    )
                except MySQLError as error:
                    print(error)

            with self.conn.cursor() as cursor:
                try:
                    if count == 0:
                        # Insert SQL statement, and result is the returned result
                        res_info = cursor.execute(
                            'insert into tb_student values (%s, %s, %s, %s)',
                            (src_ip, dst_ip, int(dst_port), auth,)
                        )
                    else:
                        # Update permission data
                        res_info = cursor.execute(
                            'UPDATE tb_as SET acc_auth=%s WHERE src_ip=%s AND dst_ip=%s AND dst_port=%s',
                            (auth, src_ip, dst_ip, int(dst_port),)
                        )
                    # After successful insertion, you need to submit to synchronize in the database
                    if isinstance(res_info, int):
                        print('Data update succeeded')
                        self.conn.commit()
                        # Update redis again
                        self.r0.set(key, auth)
                        self.r0.expire(key, 600)  # Set expiration time
                except MySQLError as error:
                    # If MySQL fails to submit, clear the redis data
                    self.r0.delete(key)
                    print(error)
                    self.conn.rollback()
                finally:
                    # After the operation is completed, the connection needs to be closed
                    self.conn.close()
        else:
            # First query whether there are records in the MySQL table. If there are no records, insert the records; If there are records, modify them
            count = 0
            with self.conn.cursor() as find_cursor:
                try:
                    count = find_cursor.execute(
                        'select count(rno) from tb_as where src_ip=%s and dst_ip=%s and dst_port=%s',
                        (src_ip, dst_ip, int(dst_port),)
                    )
                except MySQLError as error:
                    print(error)

            with self.conn.cursor() as cursor:
                try:
                    if count == 0:
                        # Insert SQL statement, and result is the returned result
                        res_info = cursor.execute(
                            'insert into tb_student values (%s, %s, %s, %s)',
                            (src_ip, dst_ip, int(dst_port), auth,)
                        )
                    else:
                        # Update permission data
                        res_info = cursor.execute(
                            'UPDATE tb_as SET acc_auth=%s WHERE src_ip=%s AND dst_ip=%s AND dst_port=%s',
                            (auth, src_ip, dst_ip, int(dst_port),)
                        )
                    # After successful insertion, you need to submit to synchronize in the database
                    if isinstance(res_info, int):
                        print('Data update succeeded')
                        self.conn.commit()
                except MySQLError as error:
                    print(error)
                    self.conn.rollback()
                finally:
                    # After the operation is completed, the connection needs to be closed
                    self.conn.close()

(4) . access code

For permission acquisition, the database uses MySQL+redis: redis as the cache to speed up the query. Therefore, when querying permission data, first query whether there are corresponding data records in the redis database. If so, return the query data. If not, enter MySQL for data query, return the results, and update the records to the redis cache at the same time. The main codes are as follows:

# Query connection permissions
    def get_data(self, src_ip, dst_ip, dst_port):
        src_ip, dst_ip, dst_port = str(src_ip), str(dst_ip), str(dst_port)
        # redis string table key
        find_info = src_ip + ':' + dst_ip + ':' + dst_port
        # print(find_info)

        # First query whether there is data in redis database. If there is data, return the output. If there is no data, query in MySQL, and then update the results to redis
        result = self.r0.get(find_info)
        # If the result is not empty, that is, there is query information in redis and the information is output directly. Otherwise, it does not exist in redis and MySQL needs to be queried
        if result:
            """
            Every time redis When updating or writing data in, you need to set the expiration time for 10 minutes, and then reset the expiration time for 10 minutes every time you query,
            If this data is not queried for 10 minutes, it will be cleared. Setting the expiration time in this way mainly prevents redis There are too many cached data. Clear the infrequently used cached data"""
            self.r0.expire(find_info, 600)
            # print(result)
            # Returns the permission result of the query
            return result
        else:
            with self.conn.cursor() as cursor:
                try:
                    # Execute MySQL query operation
                    cursor.execute('SELECT acc_auth FROM tb_as WHERE '
                                   'src_ip=%s AND dst_ip=%s AND dst_port=%s', (src_ip, dst_ip, dst_port))
                    result_sql = cursor.fetchall()
                    # print(result_sql)
                    if result_sql:
                        # Update query results into redis database
                        auth_res = result_sql[0][0]
                        # print(auth_res)
                        self.r0.set(find_info, auth_res)
                        self.r0.expire(find_info, 600)  # Set expiration time
                        # Returns the permission result of the query
                        return auth_res
                    else:
                        return 'NULL'
                except Exception as error:
                    print(error)

3. Implementation of flow table distribution

When the system is running, first issue the initialization flow table item, that is, the drop flow table item, to the switch. For initialization, first obtain the records stored in the MySQL database and extract all the records from the source ip, destination ip and destination port, that is, the client is prohibited from acquiring the server resources at the beginning. The data is distributed as follows:

The above command is issued from ryu north interface to the switch, which is equivalent to the following command:

ovs-ofctl add-flow as1-ovs1 priority=100,tcp,tcp_dst=8001/0xfff0,nw_src=10.0.1.2,nw_dst=10.0.3.1,actions=drop

Then, when the terminal accesses the server, the information intercepted by the packet capturing program is transmitted to the system. The system determines whether the access is authorized by querying the database. If so, the system issues the flow table entry to the switch. The initialized drop flow table entry will not be deleted here. The system sets a higher priority, Within the permission time, the new permission flow table entry will take precedence over the drop flow table entry. However, when the permission flow table entry expires, it will be deleted automatically, and the terminal access resource server must be authorized again. The data distributed are as follows:

If the permission query is "no", the corresponding flow table entry needs to be deleted. If yes, it will be deleted, and if no, it will be skipped. That is, during the last access, the permission is "yes", the time interval between the second access is no more than 5 minutes, and the distributed flow table item has not expired, but the permission of the database within this time period has been changed to "no", so the flow table item needs to be updated again, that is, the flow table item needs to be deleted. The deleted data are as follows:

  5, Conclusion

The above introduction is the implementation idea of access control module based on SDN. See the following for all codes and network topo Construction:

Yang mumu/AccessControl_Model

In the later stage, the access control version will be continuously updated and iterated, including capturing application layer packets and automatically detecting domain ownership. Please pay continuous attention. If you have any questions, please leave a message.

Topics: Python SDN acl