Pushing existing code

This commit is contained in:
Dom 2023-07-01 23:57:08 +01:00
parent 29a131b755
commit 439cf7e389
31 changed files with 1354 additions and 1 deletions

View File

@ -1,2 +1,32 @@
# Midas
# midas
A topology aware zero touch provisioning (ZTP) tool for network devices.
## Supported operating systems:
- Juniper Junos
- Cisco IOS
## Modules
### midasd
The socket DHCP portion of midas.
### midast
The networkx topology portion of midas.
### midasp
The YAML/Jinja provisioning portion of midas.
## Installation / usage
```
$ cd midas
$ chmod +x midas
$ sudo ./midas
```
## Tips
- When building your topology.yaml the visualisation builds top to bottom, arrange your nodes and edges in such a way that they correspond to the correct side of the graph, for neatness.
## Dependencies
- networkx
- pygraphviz
- yaml
- jinja2

119
client Executable file
View File

@ -0,0 +1,119 @@
#!/usr/bin/env python3
import socket, sys
MAX_BYTES = 1024
ServerPort = 67
ClientPort = 68
class DHCPClient():
def client(self):
print("DHCP client is starting...\n")
dest = ('<broadcast>', ServerPort)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
s.bind(('0.0.0.0', ServerPort))
print("Sending Juniper DHCP discovery.")
packet = DHCPClient.mock_juniper_packet()
s.sendto(packet, dest)
def mock_juniper_packet():
return b'\x01\x01\x06\x01O\x00\xe0\xa1\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x94\xbf\x94\xb3n\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x013\x04\x00\x01Q\x80\x0c\nLAB-SRX300\xff\x00'
def craft_discover_packet():
OP = bytes([0x01])
HTYPE = bytes([0x01])
HLEN = bytes([0x06])
HOPS = bytes([0x00])
XID = bytes([0x39, 0x03, 0xF3, 0x26])
SECS = bytes([0x00, 0x00])
FLAGS = bytes([0x00, 0x00])
CIADDR = bytes([0x00, 0x00, 0x00, 0x00])
YIADDR = bytes([0x00, 0x00, 0x00, 0x00])
SIADDR = bytes([0x00, 0x00, 0x00, 0x00])
GIADDR = bytes([0x00, 0x00, 0x00, 0x00])
CHADDR1 = bytes([0x00, 0x05, 0x3C, 0x04])
CHADDR2 = bytes([0x8D, 0x59, 0x00, 0x00])
CHADDR3 = bytes([0x00, 0x00, 0x00, 0x00])
CHADDR4 = bytes([0x00, 0x00, 0x00, 0x00])
CHADDR5 = bytes(192)
Magiccookie = bytes([0x63, 0x82, 0x53, 0x63])
DHCPOptions1 = bytes([53, 1, 1]) #DHCP Discover (value = 1)
DHCPOptions2 = bytes([50, 4, 0xC0, 0xA8, 0x01, 0x64])
packet = (
OP
+ HTYPE
+ HLEN
+ HOPS
+ XID
+ SECS
+ FLAGS
+ CIADDR
+ YIADDR
+ SIADDR
+ GIADDR
+ CHADDR1
+ CHADDR2
+ CHADDR3
+ CHADDR4
+ CHADDR5
+ Magiccookie
+ DHCPOptions1
+ DHCPOptions2
)
return packet
def craft_request_packet():
OP = bytes([0x01])
HTYPE = bytes([0x01])
HLEN = bytes([0x06])
HOPS = bytes([0x00])
XID = bytes([0x39, 0x03, 0xF3, 0x26])
SECS = bytes([0x00, 0x00])
FLAGS = bytes([0x00, 0x00])
CIADDR = bytes([0x00, 0x00, 0x00, 0x00])
YIADDR = bytes([0x00, 0x00, 0x00, 0x00])
SIADDR = bytes([0x00, 0x00, 0x00, 0x00])
GIADDR = bytes([0x00, 0x00, 0x00, 0x00])
CHADDR1 = bytes([0x00, 0x0C, 0x29, 0xDD])
CHADDR2 = bytes([0x5C, 0xA7, 0x00, 0x00])
CHADDR3 = bytes([0x00, 0x00, 0x00, 0x00])
CHADDR4 = bytes([0x00, 0x00, 0x00, 0x00])
CHADDR5 = bytes(192)
Magiccookie = bytes([0x63, 0x82, 0x53, 0x63])
DHCPOptions1 = bytes([53, 1, 3]) #DHCP Discover (value = 3)
DHCPOptions2 = bytes([50, 4, 0xC0, 0xA8, 0x01, 0x64])
DHCPOptions3 = bytes([54, 4, 0xC0, 0xA8, 0x01, 0x01])
packet = (
OP
+ HTYPE
+ HLEN
+ HOPS
+ XID
+ SECS
+ FLAGS
+ CIADDR
+ YIADDR
+ SIADDR
+ GIADDR
+ CHADDR1
+ CHADDR2
+ CHADDR3
+ CHADDR4
+ CHADDR5
+ Magiccookie
+ DHCPOptions1
+ DHCPOptions2
+ DHCPOptions3
)
return packet
if __name__ == '__main__':
dhcp_client = DHCPClient()
dhcp_client.client()

BIN
dhcp/.DS_Store vendored Normal file

Binary file not shown.

375
dhcp/midasd.py Normal file
View File

@ -0,0 +1,375 @@
import socket
import struct
from dataclasses import dataclass
from datetime import datetime
from utils.log import log
# Reference docs
# https://docs.microsoft.com/en-us/windows-server/troubleshoot/dynamic-host-configuration-protocol-basics
FORMAT_STRING = (
"!" # Specifies network byte order
"s" # OP: 1 byte
"s" # HTYPE: 1 byte
"s" # HLEN: 1 byte
"s" # HOPS: 1 byte
"4s" # XID: 4 bytes
"2s" # SECS: 2 bytes
"2s" # FLAGS: 2 bytes
"4s" # CIADDR: 4 bytes
"4s" # YIADDR: 4 bytes
"4s" # SIADDR: 4 bytes
"4s" # GIADDR: 4 bytes
"6s" # CHADDR: 6 bytes
"10s" # CHADDR: 10 bytes
"192s" # OVERFLOW: 192 bytes
"4s" # MAGICCOOKIE: 4 bytes
) # 240 bytes total
OPTION_DESCRIPTIONS = {
1: 'Subnet Mask',
3: 'Default Gateway',
12: 'Hostname',
43: 'Vendor Specific Information',
50: 'Requested IP Address',
51: 'IP Address Lease Time',
53: 'DHCP Message Type',
54: 'DHCP Server IP Address',
55: 'Parameter Request List',
57: 'DHCP Maximum Message Size',
60: 'Class Identifier',
61: 'Client Identifier',
67: 'Boot File Name',
150: 'TFTP Server IP Address',
}
@dataclass()
class DHCPPacket:
# Based on DHCP Discovery packet header from: https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol
# Packet header field byte sizing: https://support.huawei.com/enterprise/en/doc/EDOC1100058931/25cd2dfc/dhcp-messages
# DHCP packet types: https://www.omnisecu.com/tcpip/dhcp-dynamic-host-configuration-protocol-message-options.php
OP: bytes
HTYPE: bytes
HLEN: bytes
HOPS: bytes
XID: bytes
SECS: bytes
FLAGS: bytes
CIADDR: bytes # Client IP address
YIADDR: bytes # DHCP Server IP address
SIADDR: bytes # Server IP address
GIADDR: bytes # Gateway IP address
CHADDR: bytes # Client hardware address
CHADDR_PADDING: bytes
OVERFLOW: bytes # Overflow space for BOOTP legacy
MCOOKIE: bytes
OPTIONS: bytes
END: bytes = bytes([0xFF])
class Packet():
@staticmethod
def construct_tlv(_type, value):
'''
Summary:
Constructs a DHCP TLV - Type | Length | Value.
Takes:
_type: typically the DHCP option number
value: value of the TLV, can be text or integer
Returns:
A bytes object TLV
'''
if isinstance(value, str):
value = str.encode(value)
return bytes([_type, len(value)]) + value
@staticmethod
def construct_junos_suboptions(config_filename):
'''
Summary:
Constructs Junos specific suboptions required for Zero Touch Provisioning to work.
0: firmware file
1: configuration file
3: transfer mode
Additional options are available, see:
https://www.juniper.net/documentation/us/en/software/junos/junos-install-upgrade/topics/topic-map/zero-touch-provision.html#id-zero-touch-provisioning-using-dhcp-options
Takes:
config_filename: filename and path of the configuration file to be retrieved and applied to the client
Returns:
A bytes object consisting of multiple TLVs
'''
return Packet.construct_tlv(
_type=43,
value=b''.join(
[
#Packet.construct_tlv(0, '/path/to/junosimage.tgz'), # Image filename and path, for upgrading firmware
Packet.construct_tlv(1, config_filename), # Configuration filename and path
Packet.construct_tlv(3, 'tftp'), # Transfer mode
]
)
)
@staticmethod
def construct_reply_packet(reference_packet, _type, source_ip, topology):
'''
Summary:
Constructs a DHCP packet of type Offer or Ack, modelled from a received packet with some options changed/added.
Ack packets are the exact same as Offer packets, just with the packet type TLV adjusted.
Different options are changed/added based on client operating system extracted from the networkx Graph object.
Takes:
reference_packet: received DHCP packet to model the reply packet from
_type: type of packet to construct, Offer or Ack
source_ip: IP address that the DHCP packet was received from
topology: networkx Graph object detailing the network topology
Returns:
DHCP packet of type Offer or Ack
'''
packet_data = struct.unpack(FORMAT_STRING, reference_packet[:240])
reply_packet_object = DHCPPacket(*packet_data, OPTIONS=reference_packet[240:len(reference_packet)])
giaddr, yiaddr = Packet.process_giaddr(source_ip)
client_device_name, client_device_os = topology.get_client_calling_for_ip(source_ip, _type)
reply_packet_object.OP = bytes([0x02])
reply_packet_object.YIADDR = bytes([yiaddr[0], yiaddr[1], yiaddr[2], yiaddr[3]]) # Client IP
reply_packet_object.SIADDR = bytes([172, 16, 0, 200]) # Server IP: 172.16.0.200
if _type == 'offer':
packet_type = bytes([53, 1, 2]) # DHCP offer packet
elif _type == 'ack':
packet_type = bytes([53, 1, 5]) # DHCP ack packet
if client_device_os == 'junos':
reply_packet_object.OPTIONS = b"".join(
[
packet_type,
bytes([54, 4, 172, 16, 0, 200]), # Server identifier: 172.16.0.200
bytes([51, 4, 0x00, 0x01, 0x51, 0x80]), # Lease time: 86400
bytes([1, 4, 255, 255, 255, 254]), # Subnet mask: 255.255.255.254
bytes([3, 4, giaddr[0], giaddr[1], giaddr[2], giaddr[3]]), # Default gateway
bytes([150, 4, 172, 16, 0, 200]), # TFTP server: 172.16.0.200
Packet.construct_junos_suboptions(f'/configs/{client_device_name}.conf'), # Juniper specific suboptions
]
)
elif client_device_os == 'cisco_ios':
reply_packet_object.OPTIONS = b"".join(
[
packet_type,
bytes([54, 4, 172, 16, 0, 200]), # Server identifier: 172.16.0.200
bytes([51, 4, 0x00, 0x01, 0x51, 0x80]), # Lease time: 86400
bytes([1, 4, 255, 255, 255, 254]), # Subnet mask: 255.255.255.254
bytes([3, 4, giaddr[0], giaddr[1], giaddr[2], giaddr[3]]), # Default gateway
bytes([150, 4, 172, 16, 0, 200]), # TFTP server: 172.16.0.200
Packet.construct_tlv(67, f'/configs/{client_device_name}.conf'), # Cisco specific bootfile
]
)
reply_packet_list = []
for field, byte_value in reply_packet_object.__dict__.items():
reply_packet_list.append(byte_value)
reply_packet = b''.join(value for value in reply_packet_list)
return reply_packet
@staticmethod
def examine_packet(raw_packet):
'''
Summary:
Examines a DHCP packet and splits it into packet data and DHCP options.
It's expected that DHCP packet length after 240 will be DHCP options.
Takes:
raw_packet: raw DHCP packet to examine
Returns:
packet: DHCP packet data minus options
options: DHCP packet options
options_descriptions: DHCP packet options descriptions
'''
packet_data = struct.unpack(FORMAT_STRING, raw_packet[:240])
packet = DHCPPacket(*packet_data, OPTIONS=raw_packet[240:len(raw_packet)])
options, options_descriptions = Packet.extract_options(packet.OPTIONS)
return packet, options, options_descriptions
@staticmethod
def extract_options(packet_data):
'''
Summary:
Extracts DHCP options from a given raw DHCP packet.
Takes:
packet_data: DHCP packet as raw bytes
Returns:
option_dict: dictionairy of DHCP options extracted from a packet
option_description_dict: dictionairy of DHCP option descriptions that were found in a packet
'''
option_dict = {}
option_description_dict = {}
index = 0
while index < len(packet_data):
option_number = packet_data[index]
if option_number == 255:
break
option_length = packet_data[index + 1]
option_value, option_description = Packet.make_human_readable(option_number, packet_data[index + 2 : index + 2 + option_length])
option_dict[option_number] = option_value
option_description_dict[option_number] = option_description
index += 2 + option_length
return option_dict, option_description_dict
@staticmethod
def make_human_readable(option_number, raw_value):
'''
Summary:
Takes a raw DHCP option and makes it human readable.
DHCP options:
https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml
Takes:
option_number: DHCP option number
raw_value: raw bytes value received with the option number
Returns:
option_value: human readable value of the option number
option_description: description of the option
'''
if option_number in [1, 3, 50, 54, 150]:
octets = list(raw_value)
option_value = f'{octets[0]}.{octets[1]}.{octets[2]}.{octets[3]}'
elif option_number in [50, 55]:
option_value = list(raw_value)
elif option_number == 53:
option_value = list(raw_value)[0]
elif option_number in [60, 12, 61, 67, 43]:
option_value = raw_value.decode()
elif option_number in [51, 57]:
option_value = int.from_bytes(raw_value, byteorder='big')
else:
option_value = raw_value
if option_number in OPTION_DESCRIPTIONS:
option_description = OPTION_DESCRIPTIONS[option_number]
else:
option_description = 'Unknown'
return option_value, option_description
@staticmethod
def process_giaddr(giaddr):
'''
Summary:
Calculates IP address to offer the client and turns string giaddr into a list of 4 integers.
Takes:
giaddr: gateway or relay IP that the DHCP packet was received from (string)
Returns:
giaddr: gateway or relay IP that the DHCP packet was received from (list of 4 integers)
yiaddr: IP to offer the client (list of 4 integers)
'''
octets = giaddr.split('.')
last_octet = int(octets[3])
giaddr = [int(octets[0]), int(octets[1]), int(octets[2]), int(octets[3])]
if last_octet % 2 == 0:
yiaddr = [int(octets[0]), int(octets[1]), int(octets[2]), int(last_octet + 1)]
else:
yiaddr = [int(octets[0]), int(octets[1]), int(octets[2]), int(last_octet - 1)]
return giaddr, yiaddr
class DHCPServer():
MAX_BYTES = 1024
SERVER_IP = '172.16.0.200'
PORT = 67
def create_socket(self):
'''
Summary:
Creates a socket to allow the DHCP server to send and receive data to/from.
Socket characteristics:
AF_INET: type of socket, address format (host, port)
SOCK_DGRAM: socket protocol, UDP
SOL_SOCKET (socket options):
SO_REUSEADDR: socket address and port can be reused
SO_BROADCAST: datagrams can be broadcast from this socket
Socket bound to:
SERVER_IP
PORT
'''
_socket=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1)
_socket.bind((self.SERVER_IP, self.PORT))
return _socket
def run(self, topology):
'''
Summary:
Runs the DHCP server allowing packets to be sent and received through the created socket.
Takes:
topology: networkx Graph object detailing the network topology
'''
log('DHCP server is starting...', 'info')
socket = self.create_socket()
while True:
try:
log('Waiting for DHCP packets...', 'info')
received_packet, (source_address, source_port) = socket.recvfrom(self.MAX_BYTES)
log(f'DHCP packet received from IP {source_address} on port {source_port}', 'info')
packet_data, packet_options, option_descriptions = Packet.examine_packet(received_packet)
log(f'Received packet options:', 'info')
for option_number, option_value in packet_options.items():
log(f'{option_number} [{option_descriptions[option_number]}]: {option_value}', 'info')
if packet_options[53] == 1:
log('Received DHCP discover packet', 'info')
offer_packet = Packet.construct_reply_packet(received_packet, 'offer', source_address, topology)
log('Constructing DHCP offer packet...', 'info')
packet_data, packet_options, option_descriptions = Packet.examine_packet(offer_packet)
log(f'Offer packet options:', 'info')
for option_number, option_value in packet_options.items():
log(f'{option_number} [{option_descriptions[option_number]}]: {option_value}', 'info')
socket.sendto(offer_packet, (source_address, self.PORT))
elif packet_options[53] == 3:
log('Received DHCP request packet', 'info')
ack_packet = Packet.construct_reply_packet(offer_packet, 'ack', source_address, topology)
log('Constructing DHCP ack packet...', 'info')
packet_data, packet_options, option_descriptions = Packet.examine_packet(ack_packet)
log(f'Ack packet options:', 'info')
for option_number, option_value in packet_options.items():
log(f'{option_number} [{option_descriptions[option_number]}]: {option_value}', 'info')
socket.sendto(ack_packet, (source_address, self.PORT))
elif packet_options[53] == 7:
log('Received DHCP release packet', 'info')
log('See you around, partner\U0001F920', 'info')
except KeyboardInterrupt:
log('Exiting...', 'info')
break

BIN
dhcp/packet_examples/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
b'\x02\x01\x06\x01\x00\x00\x02+\x00\x04\x80\x00\x00\x00\x00\x00\n\x00\x00\x03\xac\x10\x00\xc8\n\x00\x00\x02\xcc\x16~/!Z\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x056\x04\xac\x10\x00\xc83\x04\x00\x01Q\x80\x01\x04\xff\xff\xff\xfe\x03\x04\n\x00\x00\x02\x96\x04\xac\x10\x00\xc8C\x17/configs/LAB-897VA.conf\xff'

View File

@ -0,0 +1 @@
b'\x01\x01\x06\x01\x00\x00\x02+\x00\x04\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x02\xcc\x16~/!Z\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x019\x02\x04\xb0=\x19\x00cisco-cc16.7e2f.215a-Gi8\x0c\x06Router7\x0b\x01B\x06\x0f,\x03C\x0c!\x96+<\x08ciscopnp\xff'

View File

@ -0,0 +1 @@
b'\x02\x01\x06\x01\x00\x00\x02+\x00\x04\x80\x00\x00\x00\x00\x00\n\x00\x00\x03\xac\x10\x00\xc8\n\x00\x00\x02\xcc\x16~/!Z\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x026\x04\xac\x10\x00\xc83\x04\x00\x01Q\x80\x01\x04\xff\xff\xff\xfe\x03\x04\n\x00\x00\x02\x96\x04\xac\x10\x00\xc8C\x17/configs/LAB-897VA.conf\xff'

View File

@ -0,0 +1 @@
b'\x01\x01\x06\x00\x00\x00\x02+\x00\x00\x00\x00\n\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xcc\x16~/!Z\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x076\x04\xac\x10\x00\xc8=\x19\x00cisco-cc16.7e2f.215a-Gi8<\x08ciscopnp\xff'

View File

@ -0,0 +1 @@
b'\x01\x01\x06\x01\x00\x00\x02+\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x02\xcc\x16~/!Z\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x039\x02\x04\xb0=\x19\x00cisco-cc16.7e2f.215a-Gi86\x04\xac\x10\x00\xc82\x04\n\x00\x00\x03\x0c\x06Router7\x0b\x01B\x06\x0f,\x03C\x0c!\x96+<\x08ciscopnp\xff'

View File

@ -0,0 +1 @@
b'\x02\x01\x06\x01O\x00\xe0\xa1\x00\x00\x80\x00\x00\x00\x00\x00\n\x00\x00\x01\xac\x10\x00\xc8\n\x00\x00\x00\x94\xbf\x94\xb3n\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x056\x04\xac\x10\x00\xc83\x04\x00\x01Q\x80\x01\x04\xff\xff\xff\xfe\x03\x04\n\x00\x00\x00\x96\x04\xac\x10\x00\xc8+ \x01\x18/configs/LAB-SRX300.conf\x03\x04tftp\xff'

View File

@ -0,0 +1 @@
b'\x01\x01\x06\x01O\x00\xe0\xa1\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x94\xbf\x94\xb3n\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x013\x04\x00\x01Q\x80\x0c\nLAB-SRX300\xff\x00'

View File

@ -0,0 +1 @@
b'\x02\x01\x06\x01O\x00\xe0\xa1\x00\x00\x80\x00\x00\x00\x00\x00\n\x00\x00\x01\xac\x10\x00\xc8\n\x00\x00\x00\x94\xbf\x94\xb3n\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x026\x04\xac\x10\x00\xc83\x04\x00\x01Q\x80\x01\x04\xff\xff\xff\xfe\x03\x04\n\x00\x00\x00\x96\x04\xac\x10\x00\xc8+ \x01\x18/configs/LAB-SRX300.conf\x03\x04tftp\xff'

View File

@ -0,0 +1 @@
b'\x01\x01\x06\x00;\x1b\x1f\xf6\x00\x00\x00\x00\n\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x94\xbf\x94\xb3n\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc6\x04\xac\x10\x00\xc85\x01\x07\x0c\nLAB-SRX300\xff\x00'

View File

@ -0,0 +1 @@
b'\x01\x01\x06\x01O\x00\xe0\xa1\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x94\xbf\x94\xb3n\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc6\x04\xac\x10\x00\xc87\x0e\x033\x01\x0f\x06BCx,+\x96\x0c\x07*2\x04\n\x00\x00\x015\x01\x033\x04\x00\x01Q\x80\x0c\nLAB-SRX300\xff\x00'

2
dhcp/settings.yaml Normal file
View File

@ -0,0 +1,2 @@
DHCP_SERVER_IP: 172.16.0.200
TFTP_SERVER_IP: 172.16.0.200

BIN
logs/Cisco and Juniper.log Normal file

Binary file not shown.

BIN
logs/Cisco.log Normal file

Binary file not shown.

57
logs/Juniper.log Normal file
View File

@ -0,0 +1,57 @@
2023-04-07 20:17:16,743 [INFO] 👾 | Building topology...
2023-04-07 20:17:16,793 [INFO] 👾 | Topology name: Lab
2023-04-07 20:17:16,796 [INFO] 👾 | Drawing topology...
2023-04-07 20:17:17,538 [INFO] 👾 | Topology drawn, saved as: topology/topology.png
2023-04-07 20:17:17,540 [INFO] 👾 | Topology build complete
2023-04-07 20:17:17,543 [INFO] 👽 | Rendering configs...
2023-04-07 20:17:17,575 [INFO] 👽 | Rendered /srv/tftp/configs/LAB-897VA.conf
2023-04-07 20:17:17,597 [INFO] 👽 | Rendered /srv/tftp/configs/LAB-SRX300.conf
2023-04-07 20:17:17,599 [INFO] 👽 | Config rendering complete
2023-04-07 20:17:17,602 [INFO] 🤖 | DHCP server is starting...
2023-04-07 20:17:17,604 [INFO] 🤖 | Waiting for DHCP packets...
2023-04-07 20:19:23,166 [INFO] 🤖 | DHCP packet received from IP 10.0.0.0 on port 67
2023-04-07 20:19:23,169 [INFO] 🤖 | Received packet options:
2023-04-07 20:19:23,172 [INFO] 🤖 | 53 [DHCP Message Type]: 1
2023-04-07 20:19:23,175 [INFO] 🤖 | 51 [IP Address Lease Time]: 86400
2023-04-07 20:19:23,178 [INFO] 🤖 | 12 [Hostname]: LAB-SRX300
2023-04-07 20:19:23,180 [INFO] 🤖 | Received DHCP discover packet
2023-04-07 20:19:23,183 [INFO] 👾 | Saw client: LAB-SRX300
2023-04-07 20:19:23,186 [INFO] 👾 | OS: junos
2023-04-07 20:19:23,188 [INFO] 🤖 | Constructing DHCP offer packet...
2023-04-07 20:19:23,191 [INFO] 🤖 | Offer packet options:
2023-04-07 20:19:23,193 [INFO] 🤖 | 53 [DHCP Message Type]: 2
2023-04-07 20:19:23,196 [INFO] 🤖 | 54 [DHCP Server IP Address]: 172.16.0.200
2023-04-07 20:19:23,198 [INFO] 🤖 | 51 [IP Address Lease Time]: 86400
2023-04-07 20:19:23,200 [INFO] 🤖 | 1 [Subnet Mask]: 255.255.255.254
2023-04-07 20:19:23,202 [INFO] 🤖 | 3 [Default Gateway]: 10.0.0.0
2023-04-07 20:19:23,205 [INFO] 🤖 | 150 [TFTP Server IP Address]: 172.16.0.200
2023-04-07 20:19:23,207 [INFO] 🤖 | 43 [Vendor Specific Information]: /configs/LAB-SRX300.conftftp
2023-04-07 20:19:23,209 [INFO] 🤖 | Waiting for DHCP packets...
2023-04-07 20:19:23,212 [INFO] 🤖 | DHCP packet received from IP 10.0.0.0 on port 67
2023-04-07 20:19:23,215 [INFO] 🤖 | Received packet options:
2023-04-07 20:19:23,217 [INFO] 🤖 | 54 [DHCP Server IP Address]: 172.16.0.200
2023-04-07 20:19:23,219 [INFO] 🤖 | 55 [Parameter Request List]: [3, 51, 1, 15, 6, 66, 67, 120, 44, 43, 150, 12, 7, 42]
2023-04-07 20:19:23,221 [INFO] 🤖 | 50 [Requested IP Address]: 10.0.0.1
2023-04-07 20:19:23,223 [INFO] 🤖 | 53 [DHCP Message Type]: 3
2023-04-07 20:19:23,225 [INFO] 🤖 | 51 [IP Address Lease Time]: 86400
2023-04-07 20:19:23,227 [INFO] 🤖 | 12 [Hostname]: LAB-SRX300
2023-04-07 20:19:23,229 [INFO] 🤖 | Received DHCP request packet
2023-04-07 20:19:23,231 [INFO] 🤖 | Constructing DHCP ack packet...
2023-04-07 20:19:23,233 [INFO] 🤖 | Ack packet options:
2023-04-07 20:19:23,235 [INFO] 🤖 | 53 [DHCP Message Type]: 5
2023-04-07 20:19:23,237 [INFO] 🤖 | 54 [DHCP Server IP Address]: 172.16.0.200
2023-04-07 20:19:23,239 [INFO] 🤖 | 51 [IP Address Lease Time]: 86400
2023-04-07 20:19:23,241 [INFO] 🤖 | 1 [Subnet Mask]: 255.255.255.254
2023-04-07 20:19:23,243 [INFO] 🤖 | 3 [Default Gateway]: 10.0.0.0
2023-04-07 20:19:23,245 [INFO] 🤖 | 150 [TFTP Server IP Address]: 172.16.0.200
2023-04-07 20:19:23,247 [INFO] 🤖 | 43 [Vendor Specific Information]: /configs/LAB-SRX300.conftftp
2023-04-07 20:19:23,249 [INFO] 🤖 | Waiting for DHCP packets...
2023-04-07 20:22:32,406 [INFO] 🤖 | DHCP packet received from IP 10.0.0.1 on port 68
2023-04-07 20:22:32,410 [INFO] 🤖 | Received packet options:
2023-04-07 20:22:32,413 [INFO] 🤖 | 54 [DHCP Server IP Address]: 172.16.0.200
2023-04-07 20:22:32,417 [INFO] 🤖 | 53 [DHCP Message Type]: 7
2023-04-07 20:22:32,419 [INFO] 🤖 | 12 [Hostname]: LAB-SRX300
2023-04-07 20:22:32,421 [INFO] 🤖 | Received DHCP release packet
2023-04-07 20:22:32,422 [INFO] 🤖 | See you around, partner🤠
2023-04-07 20:22:32,424 [INFO] 🤖 | Waiting for DHCP packets...
2023-04-07 20:23:00,802 [INFO] 🤖 | Exiting...

View File

@ -0,0 +1,11 @@
2023-04-08 14:16:10,531 [INFO] 👾 | Building topology...
2023-04-08 14:16:10,581 [INFO] 👾 | Topology name: Lab
2023-04-08 14:16:10,584 [INFO] 👾 | Drawing topology...
2023-04-08 14:16:11,510 [INFO] 👾 | Topology drawn, saved as: topology/topology.png
2023-04-08 14:16:11,512 [INFO] 👾 | Topology build complete
2023-04-08 14:16:11,515 [INFO] 👽 | Rendering configs...
2023-04-08 14:16:11,549 [INFO] 👽 | Rendered /srv/tftp/configs/LAB-897VA.conf
2023-04-08 14:16:11,573 [INFO] 👽 | Rendered /srv/tftp/configs/LAB-SRX300.conf
2023-04-08 14:16:11,575 [INFO] 👽 | Config rendering complete
2023-04-08 14:16:11,580 [INFO] 🤖 | DHCP server is starting...
2023-04-08 14:16:11,582 [INFO] 🤖 | Waiting for DHCP packets...

25
midas Executable file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env python3
from utils.log import get_loggers
from topology.midast import Topology
from provisioning.midasp import Provisioning
from dhcp.midasd import DHCPServer
if __name__ == '__main__':
get_loggers()
topology = Topology()
topology.build()
provisioning = Provisioning()
provisioning.render(topology)
dhcp_server = DHCPServer()
dhcp_server.run(topology)
# Todo
# 1. Implement giaddr inspection as backup to packet source address, current using socket source IP instead
# Not needed? Adds further complexity and lines of code, socket already reports source IP address
# 2. Add fingerprints / persistent lease storage system (ensure that duplicate packets are only printed once, i.e. Cisco sends multiple release packets)
# 3. Work out what variables can be made more dynamic, i.e. reply packet options - build from a YAML file?
# 4. Make IP allocation & config var (YAML) generation automatic
# 5. Implement socket.inet_aton() function
# 6. PCAP from PC to capture DHCP option values that are not yet printed in a human readable format
# 7. Mount lab Pi as NFS share on valykrie Pi to create automatic backups with duplicati

BIN
provisioning/.DS_Store vendored Normal file

Binary file not shown.

35
provisioning/midasp.py Normal file
View File

@ -0,0 +1,35 @@
import yaml
from jinja2 import Environment, FileSystemLoader
from utils.log import log
class Provisioning():
def render(self, topology):
'''
Summary:
Builds a configuration file for all network devices in the topology.
Takes:
topology: networkx Graph object detailing the network topology
'''
log('Rendering configs...', 'info')
for node in topology.G.nodes:
if topology.G.nodes[node].get('no_render') == True:
continue
node_os = topology.G.nodes[node]['os']
template_path = Environment(loader=FileSystemLoader('provisioning/templates/'), trim_blocks=True, lstrip_blocks=True)
device_vars = yaml.load(open('provisioning/vars/' + node + '.yaml'), Loader=yaml.SafeLoader)
template = template_path.get_template(node_os + '.j2')
rendered_config = template.render(device_vars)
config_path = '/srv/tftp/configs/' + node + '.conf'
with open(config_path, 'w') as configuration:
configuration.write(rendered_config)
log(f'Rendered {config_path}', 'info')
log('Config rendering complete', 'info')

View File

@ -0,0 +1,199 @@
service timestamps debug datetime msec
service timestamps log datetime msec
no service password-encryption
no service password-recovery
!
hostname {{ hostname }}
!
boot-start-marker
boot-end-marker
!
!
!
no aaa new-model
!
!
!
!
!
!
!
!
!
!
!
!
!
!
no ip domain lookup
ip domain name yourdomain.com
ip cef
no ipv6 cef
!
!
!
!
!
multilink bundle-name authenticated
!
!
!
!
!
!
!
!
cts logging verbose
license udi pid C897VA-K9 sn FCZ202990B6
!
!
username neteng privilege 15 secret Juniper1
!
!
!
!
!
controller VDSL 0
shutdown
lldp run
!
ip ssh version 2
!
!
!
!
!
!
!
!
!
!
!
interface Loopback0
ip address {{ loopback_ip }} 255.255.255.255
!
interface ATM0
no ip address
shutdown
no atm ilmi-keepalive
!
interface BRI0
no ip address
encapsulation hdlc
shutdown
isdn termination multidrop
!
interface Ethernet0
no ip address
shutdown
!
interface GigabitEthernet0
description "Management Network"
switchport access vlan 10
no ip address
!
interface GigabitEthernet1
description "{{ hostname }} gigabitEthernet1 <--> ge-0/0/1 LAB-SRX300"
switchport access vlan 20
no ip address
!
interface GigabitEthernet2
description "{{ hostname }} gigabitEthernet2 <--> ge-0/0/2 LAB-SRX300"
switchport access vlan 30
no ip address
!
interface GigabitEthernet3
no ip address
!
interface GigabitEthernet4
no ip address
!
interface GigabitEthernet5
no ip address
!
interface GigabitEthernet6
no ip address
!
interface GigabitEthernet7
no ip address
!
interface GigabitEthernet8
description "{{ hostname }} gigabitEthernet2 <--> gigabitEthernet1 LAB-RELAY"
ip address 10.0.0.3 255.255.255.254
duplex auto
speed auto
no shut
!
interface Vlan1
no ip address
!
interface Vlan10
ip address {{ management_ip }} 255.255.255.0
!
interface Vlan20
ip address 10.0.0.5 255.255.255.254
ip ospf authentication message-digest
ip ospf message-digest-key 1 md5 Juniper1
ip ospf network point-to-point
ip ospf 1 area 0
!
interface Vlan30
ip address 10.0.0.7 255.255.255.254
ip ospf authentication message-digest
ip ospf message-digest-key 1 md5 Juniper1
ip ospf network point-to-point
ip ospf 1 area 0
!
router ospf 1
router-id {{ loopback_ip }}
passive-interface Loopback0
!
ip forward-protocol nd
no ip http server
no ip http secure-server
!
!
!
!
!
control-plane
!
!
mgcp behavior rsip-range tgcp-only
mgcp behavior comedia-role none
mgcp behavior comedia-check-media-src disable
mgcp behavior comedia-sdp-force disable
!
mgcp profile default
!
!
!
!
!
!
!
line con 0
login local
no modem enable
line aux 0
line vty 0 4
login local
transport input ssh telnet
line vty 5 15
login local
transport input ssh telnet
!
scheduler allocate 20000 1000
!
!
!
event manager applet crypto-key
event timer cron cron-entry "@reboot"
action 1.0 cli command "enable"
action 1.1 cli command "config t"
action 1.2 cli command "file prompt quiet"
action 1.3 cli command "crypto key generate rsa modulus 2048"
action 1.4 cli command "no event manager applet crypto-key"
action 1.5 cli command "do wr mem"
!
end

View File

@ -0,0 +1,280 @@
system {
host-name {{ hostname }};
root-authentication {
plain-text-password-value "Juniper1";
}
login {
user datatech {
uid 2001;
class read-only;
authentication {
plain-text-password-value "Juniper1";
}
}
user neteng {
uid 2000;
class super-user;
authentication {
plain-text-password-value "Juniper1";
}
}
}
services {
ssh {
root-login allow;
}
netconf {
ssh;
}
}
tacplus-server {
10.10.10.10 secret "Juniper1"; ## SECRET-DATA
11.11.11.11 secret "Juniper1"; ## SECRET-DATA
}
syslog {
archive size 100k files 3;
user * {
any emergency;
}
file messages {
any notice;
authorization info;
}
file interactive-commands {
interactive-commands any;
}
}
}
security {
authentication-key-chains {
key-chain BGP-KC-LHR14-R101-NCL62-R2 {
key 1 {
secret "$9$Vyws4JZjq.57-YoZU.m"; ## SECRET-DATA
start-time "2022-7-1.00:00:00 +0000";
}
key 2 {
secret "$9$0Yfd1clVbs2oJdV69pOSybs2aUj5TF9AuiHP5FnpuNdVbs24aZ"; ## SECRET-DATA
start-time "2023-4-3.15:13:45 +0000";
}
}
key-chain BGP-KC-NCL62-R2-SC-FW2 {
key 1 {
secret "$9$8x0Xxdws4ZGiKM7Vs2GU"; ## SECRET-DATA
start-time "2022-7-1.00:00:00 +0000";
}
key 2 {
secret "$9$B74EeW24JikmlKDH.m3n1RhyvWxNV24JikSreKLX7-VYGjzF6"; ## SECRET-DATA
start-time "2023-4-3.15:42:04 +0000";
}
}
key-chain BRMA-KC-LHR30-R101-NCL60-R1 {
key 1 {
apply-flags omit;
secret "$9$1HCREyeK87NbuOhrKMN-"; ## SECRET-DATA
key-name 4953bd1120ffcc31e1d044870c52d67b215c04f0c2ba1fccc970fa16d18a6b6f;
start-time "2023-3-31.14:22:24 +0000";
}
}
}
forwarding-options {
family {
mpls {
mode packet-based;
}
}
}
macsec {
connectivity-association BRMA-WAN-LHR30-R101-NCL60-R1 {
security-mode static-cak;
mka {
transmit-interval 6000;
sak-rekey-interval 60;
}
pre-shared-key-chain BRMA-KC-LHR30-R101-NCL60-R1;
}
interfaces {
ge-0/0/7 {
connectivity-association BRMA-WAN-LHR30-R101-NCL60-R1;
}
}
}
}
interfaces {
ge-0/0/0 {
description "{{ hostname }} ge-0/0/0 <--> gigabitEthernet0 LAB-RELAY";
unit 0 {
family inet {
address 10.0.0.1/31;
}
}
}
ge-0/0/1 {
description "{{ hostname }} ge-0/0/1 <--> gigabitEthernet1 LAB-897VA";
unit 0 {
family inet {
address 10.0.0.4/31;
}
}
}
ge-0/0/2 {
description "{{ hostname }} ge-0/0/2 <--> gigabitEthernet2 LAB-897VA";
unit 0 {
family inet {
address 10.0.0.6/31;
}
}
}
ge-0/0/5 {
description "Management Network";
unit 0 {
family inet {
address {{ management_ip }}/24;
}
}
}
lo0 {
unit 0 {
family inet {
address {{ loopback_ip }}/32;
}
}
}
}
snmp {
v3 {
usm {
local-engine {
user snmp-user {
authentication-sha {
authentication-password "Juniper1"; ## SECRET-DATA
}
privacy-aes128 {
privacy-password "Juniper1"; ## SECRET-DATA
}
}
}
}
}
}
policy-options {
policy-statement BN-10-RMA-EXPORT {
term BGP {
from protocol bgp;
then {
community add BN-10-RMA-TARGET;
accept;
}
}
term OSPF {
from protocol ospf;
then {
community add BN-10-RMA-TARGET;
accept;
}
}
term AGGREGATE {
from protocol aggregate;
then {
community add BN-10-RMA-TARGET;
accept;
}
}
term REJECT-ALL {
then reject;
}
}
policy-statement BN-10-RMA-IMPORT {
term BGP {
from {
protocol bgp;
community BN-10-RMA-TARGET;
}
then accept;
}
term REJECT-ALL {
then reject;
}
}
community BN-10-RMA-TARGET members target:65100:1000;
}
access {
radius-server {
10.10.10.10 secret "Juniper1"; ## SECRET-DATA
11.11.11.11 secret "Juniper1"; ## SECRET-DATA
}
}
routing-instances {
BN-10-RMA {
protocols {
bgp {
group SEC-NET-FW {
type external;
description "eBGP to Security Network Firewall";
local-address 192.168.32.78;
hold-time 30;
peer-as 64900;
neighbor 192.168.32.79 {
description ncl62-sc-fw2;
authentication-key-chain BGP-KC-NCL62-R2-SC-FW2;
}
}
traceoptions {
file bgp.log;
flag state;
}
log-updown;
}
}
instance-type vrf;
route-distinguisher 65100:1000;
vrf-import BN-10-RMA-IMPORT;
vrf-export BN-10-RMA-EXPORT;
vrf-table-label;
}
}
protocols {
ospf {
area 0.0.0.0 {
interface ge-0/0/1.0 {
interface-type p2p;
authentication {
md5 1 key "Juniper1"; ## SECRET-DATA
}
}
interface ge-0/0/2.0 {
interface-type p2p;
authentication {
md5 1 key "Juniper1"; ## SECRET-DATA
}
}
interface lo0.0 {
passive;
}
}
}
bgp {
group IBGP-FULL-MESH {
type internal;
description "IBGP Full Mesh";
hold-time 30;
multipath {
multiple-as;
}
neighbor 172.17.0.0 {
description lhr14-bn-com-agg-r101;
authentication-key-chain BGP-KC-LHR14-R101-NCL62-R2;
}
}
traceoptions {
file bgp.log;
flag state;
}
log-updown;
}
lldp {
interface all;
}
}
routing-options {
autonomous-system 65100;
}

View File

@ -0,0 +1,3 @@
hostname: LAB-897VA
management_ip: 172.16.0.3
loopback_ip: 2.2.2.2

View File

@ -0,0 +1,3 @@
hostname: LAB-SRX300
management_ip: 172.16.0.1
loopback_ip: 1.1.1.1

87
topology/midast.py Normal file
View File

@ -0,0 +1,87 @@
import networkx as nx
import yaml
from networkx.drawing.nx_agraph import to_agraph
from utils.log import log
class Topology():
def __init__(self):
self.G = nx.MultiGraph()
def build(self):
'''
Summary:
Builds a network topology from the topology.yaml file into memory, stored in a networkx Graph object.
Once the networkx Graph object has been built, it's drawn and saved as topology.png.
Nodes: network devices
Node attributes: dict of attributes assigned to a specific node
Edges: links between network devices
Edge attributes: dict of attributes assigned to a specific edge
'''
log('Building topology...', 'info')
topology_data = yaml.load(open('topology/topology.yaml'), Loader=yaml.SafeLoader)
for topology_name, topology_vars in topology_data.items():
log(f'Topology name: {topology_name}', 'info')
for node, node_attributes in topology_vars['nodes'].items():
self.G.add_node(node)
self.G.nodes[node]['shape'] = 'box'
for attribute_name, attribute_value in node_attributes.items():
self.G.nodes[node][attribute_name] = attribute_value
for edge_number, edge_attributes in topology_vars['edges'].items():
self.G.add_edge(edge_attributes['a_end'], edge_attributes['b_end'])
nx.set_edge_attributes(
self.G,
{
(edge_attributes['a_end'],
edge_attributes['b_end'],
edge_attributes['edge_index']): edge_attributes
}
)
for node1, node2, edge_attributes in self.G.edges(data=True):
edge_attributes['label'] = f"""
{edge_attributes['a_end']} ({self.G.nodes[edge_attributes['a_end']]['os']})
{edge_attributes['a_end_ip']}, {edge_attributes['a_end_interface']}\n
{edge_attributes['b_end']} ({self.G.nodes[edge_attributes['b_end']]['os']})
{edge_attributes['b_end_ip']}, {edge_attributes['b_end_interface']}
"""
log('Drawing topology...', 'info')
vis = to_agraph(self.G)
vis.layout('dot')
vis_path = 'topology/topology.png'
vis.draw(vis_path)
log(f'Topology drawn, saved as: {vis_path}', 'info')
log('Topology build complete', 'info')
def get_client_calling_for_ip(self, giaddr, _type):
'''
Summary:
Gets the hostname and operating system of the client calling for an IP from the networkx Graph object.
Takes:
giaddr: Gateway or relay IP that the DHCP packet was received from
_type: Type of DHCP packet, if type is offer log the client name and OS
Returns:
client_device_name: hostname of the client device
client_device_os: operating system of the client device
'''
for node1, node2, edge_attributes in self.G.edges(data=True):
if giaddr in edge_attributes.values():
if giaddr == edge_attributes['a_end_ip']:
client_device_name, client_device_os = edge_attributes['b_end'], self.G.nodes[edge_attributes['b_end']]['os']
elif giaddr == edge_attributes['b_end_ip']:
client_device_name, client_device_os = edge_attributes['a_end'], self.G.nodes[edge_attributes['a_end']]['os']
if _type == 'offer':
log(f'Saw client: {client_device_name}', 'info')
log(f'OS: {client_device_os}', 'info')
return client_device_name, client_device_os

BIN
topology/topology.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

54
topology/topology.yaml Normal file
View File

@ -0,0 +1,54 @@
Lab:
nodes:
PI-DHCP:
os: ubuntu
no_render: True
LAB-RELAY:
os: cisco_ios
no_render: True
LAB-897VA:
os: cisco_ios
LAB-SRX300:
os: junos
edges:
1:
a_end: LAB-RELAY
b_end: LAB-SRX300
a_end_ip: 10.0.0.0
b_end_ip: 10.0.0.1
a_end_interface: GigabitEthernet0
b_end_interface: ge-0/0/0
edge_index: 0
2:
a_end: LAB-RELAY
b_end: LAB-897VA
a_end_ip: 10.0.0.2
b_end_ip: 10.0.0.3
a_end_interface: GigabitEthernet1
b_end_interface: GigabitEthernet8
edge_index: 0
3:
a_end: PI-DHCP
b_end: LAB-RELAY
a_end_ip: 172.16.0.200
b_end_ip: 172.16.0.2
a_end_interface: eth0
b_end_interface: GigabitEthernet8
edge_index: 0
4:
a_end: LAB-897VA
b_end: LAB-SRX300
a_end_ip: 10.0.0.5
b_end_ip: 10.0.0.4
a_end_interface: GigabitEthernet1
b_end_interface: ge-0/0/1
edge_index: 0
5:
a_end: LAB-897VA
b_end: LAB-SRX300
a_end_ip: 10.0.0.7
b_end_ip: 10.0.0.6
a_end_interface: GigabitEthernet2
b_end_interface: ge-0/0/2
edge_index: 1

63
utils/log.py Normal file
View File

@ -0,0 +1,63 @@
import logging
import sys
import os
import inspect
from datetime import datetime
def get_loggers():
'''
Summary:
Creates 3 loggers with the same datetime; midasd-logger (DHCP), midast-logger (Topology), midasp-logger (Provisioning).
Each logger has a different emoji to make log sources easily identifiable.
'''
now = datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
for logger_name in ['midasd-logger', 'midast-logger', 'midasp-logger']:
logger = logging.getLogger(logger_name)
logger.setLevel(logging.DEBUG)
if logger_name == 'midasd-logger':
formatter = logging.Formatter('%(asctime)s [%(levelname)s] \U0001F916 | %(message)s')
elif logger_name == 'midast-logger':
formatter = logging.Formatter('%(asctime)s [%(levelname)s] \U0001F47E | %(message)s')
elif logger_name == 'midasp-logger':
formatter = logging.Formatter('%(asctime)s [%(levelname)s] \U0001F47D | %(message)s')
file_handler = logging.FileHandler(os.getcwd() + '/logs/midas_' + now + '.log')
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.addHandler(stream_handler)
def log(log_message, level):
'''
Summary:
Logs information to logfile at the specified level.
Takes:
log_message: Information to log
level: Level of which to log the information at
'''
caller_filename = inspect.stack()[1].filename
if 'midasd' in caller_filename:
logger = logging.getLogger('midasd-logger')
elif 'midast' in caller_filename:
logger = logging.getLogger('midast-logger')
elif 'midasp' in caller_filename:
logger = logging.getLogger('midasp-logger')
log_message_types = {
'debug': logger.debug,
'info': logger.info,
'warning': logger.warning,
'error': logger.error,
'critical': logger.critical
}
log_message_types[level](log_message)