get current state from local and remote

This commit is contained in:
Andreas Stuerz
2021-02-16 17:28:25 +01:00
parent 4da0f2c25f
commit bd5817d592
4 changed files with 258 additions and 7 deletions
@@ -145,10 +145,10 @@ class showSslCrtList(Cmd):
for line in res.split("\n"):
if line.startswith('# '):
list_id = line.split("# ")[1]
result[f"{list_id}"] = []
result["certs"] = []
if list_id and line.startswith('/'):
result[f"{list_id}"].append(line)
result["certs"].append(line)
if result:
return result
@@ -186,10 +186,9 @@ class showSslCert(Cmd):
if key == 'Filename':
cert_id = val
result[f"{cert_id}"] = {}
if cert_id:
result[f"{cert_id}"][key] = val
result[key] = val
if result:
return result
@@ -261,6 +260,7 @@ class showFBEnds(Cmd):
for e in lines:
me = re.match(cl, e)
if me:
print(e)
result.append(e.split(",")[0])
return result
@@ -51,7 +51,7 @@ def get_args():
)
parser.add_argument(
'--server-ids',
help='Attempt action on a list of server, specified as a comma seperated list e.g. back1/server1,back2/server3',
help='Attempt action on a list of server, specified as a comma separated list e.g. back1/server1,back2/server3',
default=None
)
parser.add_argument(
@@ -142,7 +142,6 @@ try:
if result:
print(f"{server_id}: {result.strip()}")
con.close()
else:
# single
con = HaPConn(SOCKET)
@@ -0,0 +1,252 @@
#!/usr/bin/env python3
# Sync ssl certificates from a yaml file into haproxy memory
import os
import sys
import argparse
import traceback
import yaml
import ssl
from io import StringIO
import base64
import OpenSSL
sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
from haproxy.conn import HaPConn
from haproxy import cmds
class Diff:
def __init__(self, local=None, remote=None):
if local is None:
local = []
if remote is None:
remote = []
self.local = local
self.remote = remote
self.state = str(self)
def show_state(self):
""" Shows current local and remote state """
print("## STATE ##")
print(str(self))
def show_diff(self):
""" Shows what will be synced to target """
print("## DIFF ##")
print("TODO: Show the diff")
def sync(self):
print("## SYNC ##")
print("TODO: Sync to target")
def __iter__(self):
return iter(self.local)
def __str__(self):
result = ""
for item in self:
result += f"{str(item)}\n"
return result
class SyncWithTarget:
""" Base class for sync objects to a target """
def __init__(self, socket='/var/run/haproxy.socket'):
self.socket = socket
def execute_remote_cmd(self, command_class, **command_args):
con = HaPConn(self.socket)
if con:
result = con.sendCmd(command_class(**command_args), objectify=True)
con.close()
return result
def get_remote_state(self, command_class, **command_args):
return self.execute_remote_cmd(command_class, **command_args)
class CertList(SyncWithTarget):
""" Represents a haproxy ssl-crt-list """
def __init__(self, path, certs=None):
super().__init__()
if certs is None:
certs = []
self.path = path
self.certs = certs
self.local = self.get_local_state()
self.remote = self.get_remote_state(cmds.showSslCrtList, crt_list=self.path)
def __iter__(self):
return iter(self.local)
def __str__(self):
result = f"CRT LIST: {self.path}\n"
result += f" LOCAL: {self.local}\n"
result += f" REMOTE: {self.remote}\n"
for cert in self.certs:
result += f"\n{str(cert)}\n"
return result
def get_local_state(self):
return [f"{repr(cert)}" for cert in self.certs]
def get_remote_state(self, command_class, **command_args):
crt_list_data = super().get_remote_state(command_class, **command_args)
return crt_list_data.get('certs', {})
class Cert(SyncWithTarget):
""" Represents a haproxy ssl-cert """
def __init__(self, path, pem):
super().__init__()
self.path = path
self.pem = pem
self.local = self.get_local_state()
self.remote = self.get_remote_state(cmds.showSslCert, certfile=self.path)
def __repr__(self):
return self.path
def __str__(self):
result = f" CERT: {self.path}"
result += f"\n LOCAL: {self.local}"
result += f"\n REMOTE: {self.remote}"
return result
def get_cert_data(self, dump=False, encoding='utf-8'):
result = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, self.pem)
if dump:
result = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_TEXT, result).decode(encoding)
return result
def glue(self, components):
return "".join("/{0:s}={1:s}".format(name.decode(), value.decode()) for name, value in components)
def get_local_state(self):
cert_obj = self.get_cert_data()
return {
"Serial": '%.2x' % cert_obj.get_serial_number(),
"Subject": self.glue(cert_obj.get_subject().get_components()),
"Issuer": self.glue(cert_obj.get_issuer().get_components())
}
def get_remote_state(self, command_class, **command_args):
cert_data = super().get_remote_state(command_class, **command_args)
if 'error' in cert_data:
return {}
return {
"Serial": cert_data['Serial'],
"Subject": cert_data['Subject'],
"Issuer": cert_data['Issuer']
}
def dict_from_yaml(path):
with open(path, 'r') as yaml_file:
data = yaml.load(yaml_file, Loader=yaml.SafeLoader)
return data
def skip_frontend(frontend_id, frontend):
filter_frontend_names = list(filter(None, args.frontends.split(",")))
filter_frontend_ids = list(filter(None, args.frontend_ids.split(",")))
skip_id = False
if filter_frontend_names and frontend['name'] not in filter_frontend_names:
skip_id = True
skip_name = False
if filter_frontend_ids and frontend_id not in filter_frontend_ids:
skip_name = True
return skip_id and skip_name
def get_cert_data(cert, dump=False, encoding='utf-8'):
if os.path.isfile(cert):
cert = open(cert).read()
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
if dump:
cert = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_TEXT, cert).decode(encoding)
return cert
def base64_decode(base64_str, encoding='utf-8'):
if base64_str:
base64_bytes = base64_str.encode(encoding)
message_bytes = base64.b64decode(base64_bytes)
message = message_bytes.decode(encoding)
return message
return ''
def get_args():
# noinspection PyTypeChecker
parser = argparse.ArgumentParser(
description="""
Sync ssl certificates into HAProxys memory with certificates read from a configfile. If no frontend filter is
given, all certificates will be synced.""",
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument(
'--config',
help='Path to the ssl certificate information configfile.',
default="/usr/local/etc/haproxy/sslCerts.yaml"
)
parser.add_argument(
'--frontends',
help='Attempt action on a list of frontend names, specified as a comma separated list.',
default=""
)
parser.add_argument(
'--frontend_ids',
help='Attempt action on a list of frontend ids, specified as a comma separated list.',
default=""
)
parser.add_argument(
'--output',
help='Specify output format.',
choices=['json', 'raw'],
default="raw"
)
parser.add_argument(
'--debug',
type=bool,
help='Show debug output.',
default=False
)
return parser.parse_args()
args = get_args()
config = dict_from_yaml(args.config)
""" Get ssl crt-list with certificates from configfile"""
crt_lists = []
for frontend_id, frontend in config['frontends'].items():
if skip_frontend(id, frontend_id):
continue
certs = []
for cert_id, cert_data in frontend['certs'].items():
crt = base64_decode(cert_data['crt'])
key = base64_decode(cert_data['key'])
ca = base64_decode(cert_data['ca'])
full_cert = crt + key + ca
certs.append(Cert(path=cert_data['path'], pem=full_cert))
crt_lists.append(CertList(path=frontend['crt_list_path'], certs=certs))
""" Sync ssl certs from configfile to HaProxy """
diff = Diff(local=crt_lists)
diff.show_state()
diff.show_diff()
diff.sync()
#print(crt_lists)
#print(diff)
#diff.sync()
@@ -47,7 +47,7 @@ frontends:
{% for frontend in enabled_frontends %}
"{{ frontend.id }}":
name: {{ frontend.name }}
crt_list_path: {{ cert_template % frontend.id }}
crt_list_path: {{ crt_list_template % frontend.id }}
certs:
{% for cert_refid in frontend.certs %}
{{ cert_refid }}: