blockchain-access-control

git clone git://git.codymlewis.com/blockchain-access-control.git
Log | Files | Refs | README

commit 50f747b422f033985b2c5269262efd4430e52791
Author: Cody Lewis <cody@codymlewis.com>
Date:   Sat, 25 Apr 2020 19:41:02 +1000

Initial commit

Diffstat:
A.gitignore | 2++
ABlockchain.py | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AREADME.md | 9+++++++++
AServer.py | 20++++++++++++++++++++
Acreate_network.py | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anetwork_controller.py | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 303 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/* +Session.vim diff --git a/Blockchain.py b/Blockchain.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +''' +A simple blockchain implementation +Based on the original bitcoin and the example from +https://developer.ibm.com/technologies/blockchain/tutorials/develop-a-blockchain-application-from-scratch-in-python/ +''' + +from hashlib import sha256 +import json +import time + +class Block: + "A single block that holds some data as a transaction" + def __init__(self, index, transactions, timestamp, previous_hash): + ''' + Create a block + :param index: ID of the block + :param transactions: data stored in the block + :param timestamp: Time block was created + :param previous_hash: hash of the previous block + ''' + self.index = index + self.transactions = transactions + self.timestamp = timestamp + self.previous_hash = previous_hash + + def compute_hash(self): + ''' + Compute the hash of this block + ''' + b = json.dumps(self.__dict__, sort_keys=True) + return sha256(b.encode()).hexdigest() + + +class Blockchain: + "A chain of immutable blocks" + def __init__(self, difficulty=2): + ''' + Create the blockchain + :param difficulty: Difficulty in perform the PoW + ''' + b = Block(0, "", time.time(), "0") + b.hash = b.compute_hash() + self.chain = [b] + self.difficulty = difficulty + + def proof_of_work(self, block): + ''' + Prove that computational work was spent in creating the block + :param block: The block to prove + ''' + block.nonce = 0 + computed_hash = block.compute_hash() + while not computed_hash.startswith("0" * self.difficulty): + block.nonce += 1 + computed_hash = block.compute_hash() + return computed_hash + + def add_block(self, block, proof): + ''' + Add a block to the chain if its proof is valid + :param block: Block to add + :param proof: Proof of work in adding the block + ''' + previous_hash = self.last_block.hash + if previous_hash != block.previous_hash: + return False + if not self.is_valid_proof(block, proof): + return False + block.hash = proof + self.chain.append(block) + return True + + def is_valid_proof(self, block, proof): + ''' + Check whether a proof is valid + :param block: Block to check + :param proof: proof to check against the block + ''' + return (proof.startswith("0" * self.difficulty)) and \ + (proof == block.compute_hash()) + + def mine(self, transactions): + ''' + Perfom computational work to add data to the chain + ''' + b = Block( + self.last_block.index + 1, + transactions, + time.time(), + self.last_block.hash + ) + p = self.proof_of_work(b) + self.add_block(b, p) + self.unconfirmed_trans = [] + return True + + @property + def last_block(self): + ''' + Get the last block in the chain + ''' + return self.chain[-1] diff --git a/README.md b/README.md @@ -0,0 +1,9 @@ +# Blockchain Access Control + +A SDN that uses the block chain to store access control policies. + +## Running +``` +./network_controller.py +sudo ./create_network.py +``` diff --git a/Server.py b/Server.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +''' +A simple flask server that gives some random sensor like data +''' + +import random + +from flask import Flask + +APP = Flask(__name__) + +@APP.route("/data") +def data(): + return f"{random.randint(0, 20)}ms", 200 + +@APP.route("/add") +def add(): + return f"You may now access this data", 200 diff --git a/create_network.py b/create_network.py @@ -0,0 +1,56 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +''' +Create a mininet based network for the IoT +''' + +from mininet.topo import Topo +from mininet.net import Mininet +from mininet.node import OVSSwitch, RemoteController +from mininet.log import setLogLevel, info +from mininet.cli import CLI +from mininet.link import TCLink + +class IoTTopo(Topo): + "A star shaped network" + def build(self, **opts): + switch = self.addSwitch(f"s1") + for i in range(opts["n"]): + self.addLink( + self.addHost(f"h{i}"), + switch, + **{ + "bw": 0.1, + "delay": "5ms", + "loss": 0, + "max_queue_size": 1000, + "use_htb": True + } + ) + +def run_network(n=5): + ''' + Start up and run the network + :param n: Number of hosts + ''' + topo = IoTTopo(n=n) + net = Mininet( + topo=topo, + link=TCLink, + switch=OVSSwitch, + controller=RemoteController + ) + net.start() + info("*** Starting a server on all hosts") + for host in net.hosts: + host.cmd( + "export FLASK_APP=Server.py && flask run --host=0.0.0.0 --port=80 &" + ) + CLI(net) + net.stop() + + +if __name__ == '__main__': + setLogLevel('info') + run_network() diff --git a/network_controller.py b/network_controller.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +''' +Controller for the SDN +''' + +import sys + +import pox3.lib.packet as pac +from pox3.boot import boot +from pox3.core import core + +import pox3.openflow.libopenflow_01 as of + +from Blockchain import Blockchain + + +if __name__ != "__main__": + LOG = core.getLogger() + + +class Controller(object): + '''A controller that can detect attacks or generate data on flows''' + def __init__(self, connection): + self.connection = connection + connection.addListeners(self) + self.mac_to_port = {} + self.blockchain = Blockchain() + + def resend_packet(self, packet_in, out_port): + ''' + Pass the packet from this switch on to the next port + :param packet_in: Packet to pass + :param out_port: Port to pass to + ''' + msg = of.ofp_packet_out() + msg.data = packet_in + action = of.ofp_action_output(port=out_port) + msg.actions.append(action) + self.connection.send(msg) + + def flow_is_allowed(self, flow): + for b in self.blockchain.chain: + if b.transactions == flow: + return True + return False + + def act_like_switch(self, packet, packet_in): + ''' + Act like a switch by learning the mappings between the MACs and ports + :param packet The packet processed at this point + :param packet_in The packet to pass + ''' + pl = packet.payload + if len(packet_in.data) == packet_in.total_len: + self.mac_to_port[packet.src] = packet_in.in_port + if isinstance(pl, pac.ipv4): + if pl.protocol == pac.ipv4.TCP_PROTOCOL: + src = pl.srcip + dst = pl.dstip + flow = f"{src} -> {dst}" + tcp_pl = pl.next.payload.decode() + if tcp_pl.find("GET") == 0: + route = tcp_pl[4:tcp_pl.find("HTTP") - 1] + if "data" in route: + if not self.flow_is_allowed(flow): + LOG.debug("Flow %s is not allowed", flow) + return + elif "add" in route: + if self.blockchain.mine(flow): + LOG.debug("Added acceptable flow %s", flow) + else: + return + if self.mac_to_port.get(packet.dst): + self.resend_packet(packet_in, self.mac_to_port[packet.dst]) + else: + self.resend_packet(packet_in, of.OFPP_ALL) + + def _handle_PacketIn(self, event): + ''' + Handle a packet in + :param event Event that triggered this + ''' + packet = event.parsed + if not packet.parsed: + LOG.warning("Ignoring incomplete packet") + else: + packet_in = event.ofp + self.act_like_switch(packet, packet_in) + + +def launch(): + ''' + Launch this controller + ''' + def start_switch(event): + ''' + Start up the switch + :param event Event that triggered this + ''' + LOG.debug("Controlling %s with this", (event.connection,)) + Controller(event.connection,) + core.openflow.addListenerByName("ConnectionUp", start_switch) + + +if __name__ == '__main__': + boot( + (["log.level", "--DEBUG"] if "--debug" in sys.argv else []) + + ["network_controller"] + )