statusd.py: add support for ssl

server now speaks tls. new functions strip_argument() and display_peercert()
This commit is contained in:
berhsi 2019-07-29 18:32:27 +02:00
parent 60fccc57d4
commit d3534afaa3

View file

@ -4,9 +4,11 @@
# date: 26.07.2019 # date: 26.07.2019
# email: berhsi@web.de # email: berhsi@web.de
# server, who listen for ipv4 connections at port 10001. # server, who listen for ipv4 connections at port 10001. now with ssl
# encrypted connection and client side authentication.
import socket import socket
import ssl
import os import os
import logging import logging
from time import time, ctime, sleep from time import time, ctime, sleep
@ -27,10 +29,11 @@ def read_config(CONFIGFILE, CONFIG):
logging.debug('Configfile successfull read') logging.debug('Configfile successfull read')
for line in config.readlines(): for line in config.readlines():
if not line[0] in ('#', ';', '\n', '\r'): if not line[0] in ('#', ';', '\n', '\r'):
logging.debug('Read entry')
key, value = (line.strip().split('=')) key, value = (line.strip().split('='))
CONFIG[key.upper().strip()] = value.strip() key = strip_argument(key).upper()
logging.debug('Set {} to {}'.format(key.upper().strip(), value.strip())) value = strip_argument(value)
CONFIG[key] = value
logging.debug('Set {} to {}'.format(key, value))
else: else:
logging.error('Failed to read {}'.format(CONFIGFILE)) logging.error('Failed to read {}'.format(CONFIGFILE))
logging.error('Using default values') logging.error('Using default values')
@ -38,6 +41,19 @@ def read_config(CONFIGFILE, CONFIG):
return True return True
def strip_argument(argument):
'''
Becomes a string and strips at first whitespaces, second apostrops and
returns the clear string.
param 1: string
return: string
'''
argument = argument.strip()
argument = argument.strip('"')
argument = argument.strip("'")
return argument
def print_config(CONFIG): def print_config(CONFIG):
''' '''
Prints the used configuration, if loglevel ist debug. Prints the used configuration, if loglevel ist debug.
@ -50,14 +66,21 @@ def print_config(CONFIG):
return True return True
def display_peercert(cert):
for i in cert.keys():
print(i)
for j in cert[i]:
print('\t{}'.format(j))
return
def receive_buffer_is_valid(raw_data): def receive_buffer_is_valid(raw_data):
''' '''
checks, if the received buffer from the connection is valid or not. checks, if the received buffer from the connection is valid or not.
param 1: byte param 1: byte
return: boolean return: boolean
''' '''
data = bytes2int(raw_data) if raw_data == b'\x00' or raw_data == b'\x01':
if data == 0 or data == 1:
logging.debug('Argument is valid: {}'.format(raw_data)) logging.debug('Argument is valid: {}'.format(raw_data))
return True return True
else: else:
@ -65,23 +88,6 @@ def receive_buffer_is_valid(raw_data):
return False return False
def bytes2int(raw_data):
'''
Convert a given byte value into a integer. If it is possible, it returns
the integer, otherwise false.
param 1: byte
return: integer or false
'''
bom = byteorder
try:
data = int.from_bytes(raw_data, bom)
except Exception as e:
logging.error('Unabele to convert raw data to int: {}'.format(raw_data))
return False
return data
def replace_entry(line, new): def replace_entry(line, new):
''' '''
The function becomes two strings and replaces the second part of the The function becomes two strings and replaces the second part of the
@ -101,7 +107,6 @@ def replace_entry(line, new):
return line return line
def change_status(raw_data, api): def change_status(raw_data, api):
''' '''
Becomes the received byte and the path to API file. Grabs the content of Becomes the received byte and the path to API file. Grabs the content of
@ -176,8 +181,7 @@ def set_values(raw_data):
return: tuple return: tuple
''' '''
timestamp = str(time()).split('.')[0] timestamp = str(time()).split('.')[0]
callback = bytes2int(raw_data) if raw_data == 'b\x01':
if callback == 1:
status = "true" status = "true"
else: else:
status = "false" status = "false"
@ -186,23 +190,38 @@ def set_values(raw_data):
def main(): def main():
''' '''
The main function - opens a socket und listen for connections. The main function - opens a socket, create a ssl context, load certs and
listen for connections.
''' '''
CONFIG = { CONFIG = {
'HOST': 'localhost', 'HOST': 'localhost',
'PORT': 10001, 'PORT': 10001,
'CERT': None, 'SERVER_CERT': './server.crt',
'KEY' : None, 'SERVER_KEY' : './server.key',
'CLIENT_CERT': './client.crt',
'TIMEOUT': 3.0, 'TIMEOUT': 3.0,
'API': './api' 'API': './api',
'API_TEMPLATE': './api_template',
'VERBOSITY': 'info'
} }
CONFIG_FILE = './statusd.conf' CONFIG_FILE = './statusd.conf'
FINGERPRINT = \
'35:8E:35:FA:58:0A:DD:2B:C8:6A:F9:EA:A3:7B:10:F5:62:89:AB:D0:AB:53:3E:B5:8B:AB:E1:23:CF:93:F5:F9'
loglevel = logging.DEBUG loglevel = logging.DEBUG
logging.basicConfig(format='%(levelname)s: %(asctime)s: %(message)s', level=loglevel) logging.basicConfig(format='%(levelname)s: %(message)s', level=loglevel)
read_config(CONFIG_FILE, CONFIG) read_config(CONFIG_FILE, CONFIG)
print_config(CONFIG) print_config(CONFIG)
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.options &= ~ssl.PROTOCOL_TLS
context.verify_mode = ssl.CERT_REQUIRED
context.load_cert_chain(certfile = CONFIG['SERVER_CERT'],
keyfile = CONFIG['SERVER_KEY'])
context.load_verify_locations(cafile = CONFIG['CLIENT_CERT'])
context.options &= ~ssl.OP_NO_SSLv3
logging.debug('SSL context created')
with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as mySocket: with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as mySocket:
logging.debug('Socket created') logging.debug('Socket created')
try: try:
@ -215,15 +234,21 @@ def main():
exit() exit()
while True: while True:
try: try:
conn, addr = mySocket.accept() fromSocket, fromAddr = mySocket.accept()
logging.info('Connection from {}:{}'.format(addr[0], addr[1])) logging.info('Client connected: {}:{}'.format(fromAddr[0], fromAddr[1]))
try: try:
conn.settimeout(float(CONFIG['TIMEOUT'])) fromSocket.settimeout(float(CONFIG['TIMEOUT']))
logging.debug('Connection timeout set to {}'.format(CONFIG['TIMEOUT'])) logging.debug('Connection timeout set to {}'.format(CONFIG['TIMEOUT']))
except Exception as e: except Exception as e:
logging.error('Canot set timeout to {}'.format(CONFIG['TIMEOUT'])) logging.error('Canot set timeout to {}'.format(CONFIG['TIMEOUT']))
logging.error('Use default value: 3.0') logging.error('Use default value: 3.0')
conn.settimeout(3.0) fromSocket.settimeout(3.0)
try:
conn = context.wrap_socket(fromSocket, server_side = True)
# display_peercert(conn.getpeercert())
logging.debug('SSL established. Peer: {}'.format(conn.getpeercert()))
except Exception as e:
logging.error('SSL handshake failed: {}'.format(e))
raw_data = conn.recv(1) raw_data = conn.recv(1)
if receive_buffer_is_valid(raw_data) == True: if receive_buffer_is_valid(raw_data) == True:
if change_status(raw_data, CONFIG['API']) == True: if change_status(raw_data, CONFIG['API']) == True:
@ -232,12 +257,14 @@ def main():
# change_status returns false: # change_status returns false:
else: else:
logging.info('Failed to change status') logging.info('Failed to change status')
conn.send(b'\x03') if conn:
conn.send(b'\x03')
# recive_handle returns false: # recive_handle returns false:
else: else:
logging.info('Inalid argument recived: {}'.format(raw_data)) logging.info('Inalid argument recived: {}'.format(raw_data))
logging.debug('Send {} back'.format(b'\x03')) logging.debug('Send {} back'.format(b'\x03'))
conn.send(b'\x03') if conn:
conn.send(b'\x03')
sleep(0.1) # protection against dos sleep(0.1) # protection against dos
except KeyboardInterrupt: except KeyboardInterrupt:
print('\rExit') print('\rExit')
@ -245,6 +272,7 @@ def main():
exit() exit()
except Exception as e: except Exception as e:
logging.error('{}'.format(e)) logging.error('{}'.format(e))
continue
if __name__ == '__main__': if __name__ == '__main__':