mirror of
https://github.com/opnsense/plugins.git
synced 2026-05-28 04:34:15 -04:00
Merge pull request #2244 from andeman/haproxy_live_cert_update
net/haproxy: support runtime certificate updates
This commit is contained in:
commit
d7e16d5512
9 changed files with 1117 additions and 105 deletions
|
|
@ -2,7 +2,7 @@ PLUGIN_NAME= haproxy
|
|||
PLUGIN_VERSION= 2.26
|
||||
PLUGIN_REVISION= 1
|
||||
PLUGIN_COMMENT= Reliable, high performance TCP/HTTP load balancer
|
||||
PLUGIN_DEPENDS= haproxy20
|
||||
PLUGIN_DEPENDS= haproxy
|
||||
PLUGIN_MAINTAINER= opnsense@moov.de
|
||||
|
||||
.include "../../Mk/plugins.mk"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
# pylint: disable=locally-disabled, too-few-public-methods, no-self-use, invalid-name
|
||||
"""cmds.py - Implementations of the different HAProxy commands"""
|
||||
|
||||
import re
|
||||
import csv
|
||||
import json
|
||||
from io import StringIO
|
||||
|
||||
|
||||
class Cmd():
|
||||
"""Cmd - Command base class"""
|
||||
req_args = []
|
||||
|
|
@ -42,44 +39,200 @@ class Cmd():
|
|||
"""
|
||||
return self.cmdTxt % self.args
|
||||
|
||||
def getBootstrapOutput(self, resObj):
|
||||
""" Returns results gathered from HAProxy as jquery bootstrap output """
|
||||
args = {
|
||||
"rows": resObj,
|
||||
"page": int(self.args['page']) if self.args['page'] != None else 1,
|
||||
"page_rows": int(self.args['page_rows']) if self.args['page_rows'] != None else len(rows),
|
||||
"search": self.args['search'],
|
||||
"sort_col": self.args['sort_col'] if self.args['sort_col'] else 'id',
|
||||
"sort_dir": self.args['sort_dir'],
|
||||
}
|
||||
rows = args['rows']
|
||||
# search
|
||||
if args['search']:
|
||||
filtered_rows = []
|
||||
for row in rows:
|
||||
def inner(row):
|
||||
for k, v in row.items():
|
||||
if args['search'] in v:
|
||||
return row
|
||||
return None
|
||||
|
||||
match = inner(row)
|
||||
if match:
|
||||
filtered_rows.append(match)
|
||||
rows = filtered_rows
|
||||
|
||||
# sort
|
||||
rows.sort(key=lambda k: k[args['sort_col']], reverse=True if args['sort_dir'] == 'desc' else False)
|
||||
|
||||
# pager
|
||||
total = len(rows)
|
||||
pages = [rows[i:i + args['page_rows']] for i in range(0, total, args['page_rows'])]
|
||||
if pages and (args['page'] > len(pages) or args['page'] < 1):
|
||||
raise KeyError(f"Current page {args['page']} does not exist. Available pages: {len(pages)}")
|
||||
page = pages[args['page'] - 1] if pages else []
|
||||
|
||||
return json.dumps({
|
||||
"rows": page,
|
||||
"total": total,
|
||||
"rowCount": args['page_rows'],
|
||||
"current": args['page']
|
||||
})
|
||||
|
||||
def getJsonOutput(self, resObj):
|
||||
"""Returns results gathered from HAProxy as json"""
|
||||
return json.dumps(resObj)
|
||||
|
||||
def getResult(self, res):
|
||||
"""Returns raw results gathered from HAProxy"""
|
||||
if res == '\n':
|
||||
res = None
|
||||
|
||||
if self.args['output'] == 'json':
|
||||
return self.getJsonOutput(self.getResultObj(res))
|
||||
|
||||
if self.args['output'] == 'bootstrap':
|
||||
return self.getBootstrapOutput(self.getResultObj(res))
|
||||
|
||||
return res
|
||||
|
||||
def getResultObj(self, res):
|
||||
"""Returns refined output from HAProxy, packed inside a Python obj i.e. a dict()"""
|
||||
return res
|
||||
|
||||
|
||||
class setServerAgent(Cmd):
|
||||
"""Set server agent command."""
|
||||
cmdTxt = "set server %(backend)s/%(server)s agent %(value)s\r\n"
|
||||
req_args = ['backend', 'server', 'value']
|
||||
helpTxt = "Force a server's agent to a new state."
|
||||
|
||||
|
||||
class setServerHealth(Cmd):
|
||||
"""Set server health command."""
|
||||
cmdTxt = "set server %(backend)s/%(server)s health %(value)s\r\n"
|
||||
req_args = ['backend', 'server', 'value']
|
||||
helpTxt = "Force a server's health to a new state."
|
||||
|
||||
|
||||
class setServerState(Cmd):
|
||||
"""Set server state command."""
|
||||
cmdTxt = "set server %(backend)s/%(server)s state %(value)s\r\n"
|
||||
req_args = ['backend', 'server', 'value']
|
||||
helpTxt = "Force a server's administrative state to a new state."
|
||||
|
||||
|
||||
class setServerWeight(Cmd):
|
||||
"""Set server weight command."""
|
||||
cmdTxt = "set server %(backend)s/%(server)s weight %(value)s\r\n"
|
||||
req_args = ['backend', 'server', 'value']
|
||||
helpTxt = "Force a server's weight to a new state."
|
||||
|
||||
class showSslCrtLists(Cmd):
|
||||
cmdTxt = "show ssl crt-list\r\n"
|
||||
helpTxt = "Show the list of crt-lists."
|
||||
|
||||
def getResultObj(self, res):
|
||||
result = { "crt_lists": []}
|
||||
for line in res.split("\n"):
|
||||
if line.startswith('/'):
|
||||
result["crt_lists"].append(line)
|
||||
return result
|
||||
|
||||
class showSslCrtList(Cmd):
|
||||
cmdTxt = "show ssl crt-list -n %(crt_list)s\r\n"
|
||||
req_args = ['crt_list']
|
||||
helpTxt = "Show the the content of a crt-list."
|
||||
|
||||
def getResultObj(self, res):
|
||||
result = {}
|
||||
list_id = None
|
||||
for line in res.split("\n"):
|
||||
if line.startswith('# '):
|
||||
list_id = line.split("# ")[1]
|
||||
result["certs"] = []
|
||||
|
||||
if list_id and line.startswith('/'):
|
||||
result["certs"].append(line)
|
||||
|
||||
if result:
|
||||
return result
|
||||
|
||||
return {"error": res.strip()}
|
||||
|
||||
class showSslCerts(Cmd):
|
||||
cmdTxt = "show ssl cert\r\n"
|
||||
helpTxt = "Display the SSL certificates used in memory."
|
||||
|
||||
def getResultObj(self, res):
|
||||
result = {
|
||||
"transaction": [],
|
||||
"filename": []
|
||||
}
|
||||
for line in res.split("\n"):
|
||||
if line.startswith('*'):
|
||||
result['transaction'].append(line)
|
||||
elif line.startswith('/'):
|
||||
result['filename'].append(line)
|
||||
return result
|
||||
|
||||
class showSslCert(Cmd):
|
||||
cmdTxt = "show ssl cert %(certfile)s\r\n"
|
||||
req_args = ['certfile']
|
||||
helpTxt = "Display the details of a SSL certificate used in memory."
|
||||
|
||||
def getResultObj(self, res):
|
||||
result = {}
|
||||
cert_id = None
|
||||
for line in res.split("\n"):
|
||||
if line:
|
||||
key = line.split(":")[0]
|
||||
val = line.split(":")[1].strip()
|
||||
|
||||
if key == 'Filename':
|
||||
cert_id = val
|
||||
|
||||
if cert_id:
|
||||
result[key] = val
|
||||
|
||||
if result:
|
||||
return result
|
||||
|
||||
return {"error": res.strip()}
|
||||
|
||||
class addToSslCrtList(Cmd):
|
||||
cmdTxt = "add ssl crt-list %(crt_list)s %(certfile)s\r\n"
|
||||
req_args = ['crt_list', 'certfile']
|
||||
helpTxt = "Add a ssl cert to a crt-list."
|
||||
|
||||
class delFromSslCrtList(Cmd):
|
||||
cmdTxt = "del ssl crt-list %(crt_list)s %(certfile)s\r\n"
|
||||
req_args = ['crt_list', 'certfile']
|
||||
helpTxt = "Delete a ssl cert from a crt-list."
|
||||
|
||||
class newSslCrt(Cmd):
|
||||
"""" Create an empty slot for the certificate in HAProxy’s memory """
|
||||
cmdTxt = "new ssl cert %(certfile)s\r\n"
|
||||
req_args = ['certfile']
|
||||
helpTxt = "Create a new certificate file to be used in a crt-list or a directory."
|
||||
|
||||
class updateSslCrt(Cmd):
|
||||
"""" Begin a transaction to upload the certificate into a slot in HAProxy’s memory """
|
||||
cmdTxt = "set ssl cert %(certfile)s <<\n%(payload)s\r\n"
|
||||
req_args = ['certfile', 'payload']
|
||||
helpTxt = "Replace a certificate file."
|
||||
|
||||
class delSslCrt(Cmd):
|
||||
"""" Begin a transaction to remove the certificate from a slot in HAProxy’s memory """
|
||||
cmdTxt = "del ssl cert %(certfile)s\r\n"
|
||||
req_args = ['certfile']
|
||||
helpTxt = "Delete delete an unused certificate file."
|
||||
|
||||
class commitSslCrt(Cmd):
|
||||
""" Commit the transaction so HAProxy detects the change. """
|
||||
cmdTxt = "commit ssl cert %(certfile)s\r\n"
|
||||
req_args = ['certfile']
|
||||
helpTxt = "Commit a certificate file."
|
||||
|
||||
class abortSslCrt(Cmd):
|
||||
cmdTxt = "abort ssl cert %(certfile)s\r\n"
|
||||
req_args = ['certfile']
|
||||
helpTxt = "Abort a transaction for a certificate file."
|
||||
|
||||
class showFBEnds(Cmd):
|
||||
"""Base class for getting a listing Frontends and Backends"""
|
||||
|
|
@ -107,22 +260,20 @@ class showFBEnds(Cmd):
|
|||
for e in lines:
|
||||
me = re.match(cl, e)
|
||||
if me:
|
||||
print(e)
|
||||
result.append(e.split(",")[0])
|
||||
return result
|
||||
|
||||
|
||||
class showFrontends(showFBEnds):
|
||||
"""Show frontends command."""
|
||||
switch = "frontend"
|
||||
helpTxt = "List all Frontends."
|
||||
|
||||
|
||||
class showBackends(showFBEnds):
|
||||
"""Show backends command."""
|
||||
switch = "backend"
|
||||
helpTxt = "List all Backends."
|
||||
|
||||
|
||||
class showInfo(Cmd):
|
||||
"""Show info HAProxy command"""
|
||||
cmdTxt = "show info\r\n"
|
||||
|
|
@ -136,7 +287,6 @@ class showInfo(Cmd):
|
|||
|
||||
return resDict
|
||||
|
||||
|
||||
class showSessions(Cmd):
|
||||
"""Show sess HAProxy command"""
|
||||
cmdTxt = "show sess\r\n"
|
||||
|
|
@ -145,7 +295,6 @@ class showSessions(Cmd):
|
|||
def getResultObj(self, res):
|
||||
return res.split('\n')
|
||||
|
||||
|
||||
class baseStat(Cmd):
|
||||
"""Base class for stats commands."""
|
||||
|
||||
|
|
@ -158,64 +307,11 @@ class baseStat(Cmd):
|
|||
csv_string = StringIO(res)
|
||||
return csv.DictReader(csv_string, delimiter=',')
|
||||
|
||||
def getBootstrapOutput(self, **kwargs):
|
||||
rows = kwargs['rows']
|
||||
# search
|
||||
if kwargs['search']:
|
||||
filtered_rows = []
|
||||
for row in rows:
|
||||
def inner(row):
|
||||
for k, v in row.items():
|
||||
if kwargs['search'] in v:
|
||||
return row
|
||||
return None
|
||||
|
||||
match = inner(row)
|
||||
if match:
|
||||
filtered_rows.append(match)
|
||||
rows = filtered_rows
|
||||
|
||||
# sort
|
||||
rows.sort(key=lambda k: k[kwargs['sort_col']], reverse=True if kwargs['sort_dir'] == 'desc' else False)
|
||||
|
||||
# pager
|
||||
total = len(rows)
|
||||
pages = [rows[i:i + kwargs['page_rows']] for i in range(0, total, kwargs['page_rows'])]
|
||||
if pages and (kwargs['page'] > len(pages) or kwargs['page'] < 1):
|
||||
raise KeyError(f"Current page {kwargs['page']} does not exist. Available pages: {len(pages)}")
|
||||
page = pages[kwargs['page'] - 1] if pages else []
|
||||
|
||||
return json.dumps({
|
||||
"rows": page,
|
||||
"total": total,
|
||||
"rowCount": kwargs['page_rows'],
|
||||
"current": kwargs['page']
|
||||
})
|
||||
|
||||
|
||||
class showServers(baseStat):
|
||||
"""Show all servers. If backend is given, show only servers for this backend. """
|
||||
cmdTxt = "show stat\r\n"
|
||||
helpTxt = "Lists all servers. Filter for servers in backend, if set."
|
||||
|
||||
def getResult(self, res):
|
||||
if self.args['output'] == 'json':
|
||||
return json.dumps(self.getResultObj(res))
|
||||
|
||||
if self.args['output'] == 'bootstrap':
|
||||
rows = self.getResultObj(res)
|
||||
args = {
|
||||
"rows": rows,
|
||||
"page": int(self.args['page']) if self.args['page'] != None else 1,
|
||||
"page_rows": int(self.args['page_rows']) if self.args['page_rows'] != None else len(rows),
|
||||
"search": self.args['search'],
|
||||
"sort_col": self.args['sort_col'] if self.args['sort_col'] else 'id',
|
||||
"sort_dir": self.args['sort_dir'],
|
||||
}
|
||||
return self.getBootstrapOutput(**args)
|
||||
|
||||
return self.getResultObj(res)
|
||||
|
||||
def getResultObj(self, res):
|
||||
servers = []
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class HaPConn(object):
|
|||
output = self.sock.recv(const.HAP_BUFSIZE)
|
||||
|
||||
while output:
|
||||
res += output.decode('ASCII')
|
||||
res += output.decode('UTF-8')
|
||||
output = self.sock.recv(const.HAP_BUFSIZE)
|
||||
|
||||
if objectify:
|
||||
|
|
|
|||
|
|
@ -5,69 +5,288 @@ import sys, os, unittest
|
|||
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
from haproxy import cmds
|
||||
|
||||
|
||||
class TestCommands(unittest.TestCase):
|
||||
"""Tests all of the commands."""
|
||||
|
||||
def setUp(self):
|
||||
self.maxDiff = None
|
||||
self.pem_cert_content = """
|
||||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
+VCAIWMkC36Dvg==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw
|
||||
GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2
|
||||
MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0
|
||||
8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym
|
||||
oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0
|
||||
ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN
|
||||
xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56
|
||||
dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9
|
||||
AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw
|
||||
HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0
|
||||
BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu
|
||||
b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu
|
||||
Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF
|
||||
UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9
|
||||
AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp
|
||||
DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7
|
||||
IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf
|
||||
zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI
|
||||
PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w
|
||||
SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em
|
||||
2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0
|
||||
WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt
|
||||
n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU=
|
||||
-----END CERTIFICATE-----
|
||||
"""
|
||||
|
||||
self.Resp = {"disable" : "disable server redis-ro/redis-ro0",
|
||||
"set-server-agent" : "set server redis-ro/redis-ro0 agent up",
|
||||
"set-server-health" : "set server redis-ro/redis-ro0 health stopping",
|
||||
"set-server-state" : "set server redis-ro/redis-ro0 state drain",
|
||||
"set-server-weight" : "set server redis-ro/redis-ro0 weight 10",
|
||||
"frontends" : "show stat",
|
||||
"info" : "show info",
|
||||
"sessions" : "show sess",
|
||||
"servers" : "show stat",
|
||||
|
||||
self.Resp = {
|
||||
"disable": "disable server redis-ro/redis-ro0",
|
||||
"set-server-agent": "set server redis-ro/redis-ro0 agent up",
|
||||
"set-server-health": "set server redis-ro/redis-ro0 health stopping",
|
||||
"set-server-state": "set server redis-ro/redis-ro0 state drain",
|
||||
"set-server-weight": "set server redis-ro/redis-ro0 weight 10",
|
||||
"frontends": "show stat",
|
||||
"info": "show info",
|
||||
"sessions": "show sess",
|
||||
"servers": "show stat",
|
||||
"show-ssl-crt-lists": "show ssl crt-list",
|
||||
"show-ssl-crt-list": "show ssl crt-list -n /tmp/haproxy/ssl/601a7392cc9984.99301413.certlist",
|
||||
"show-ssl-certs": "show ssl cert",
|
||||
"show-ssl-cert": "show ssl cert /tmp/haproxy/ssl/601a70e4844b0.pem",
|
||||
"add-to-crt-list": "add ssl crt-list /tmp/haproxy/ssl/601a7392cc9984.99301413.certlist /tmp/haproxy/ssl/601a70e4844b0.pem",
|
||||
"del-from-crt-list": "del ssl crt-list /tmp/haproxy/ssl/601a7392cc9984.99301413.certlist /tmp/haproxy/ssl/601a70e4844b0.pem",
|
||||
"new-ssl-cert": "new ssl cert /tmp/haproxy/ssl/601a70e4844b0.pem",
|
||||
"update-ssl-cert": "set ssl cert /tmp/haproxy/ssl/601a70e4844b0.pem <<\n%s" % self.pem_cert_content,
|
||||
"del-ssl-cert": "del ssl cert /tmp/haproxy/ssl/601a70e4844b0.pem",
|
||||
"commit-ssl-cert": "commit ssl cert /tmp/haproxy/ssl/601a70e4844b0.pem",
|
||||
"abort-ssl-cert": "abort ssl cert /tmp/haproxy/ssl/601a70e4844b0.pem",
|
||||
}
|
||||
|
||||
self.Resp = dict([(k, v + "\r\n") for k, v in self.Resp.items()])
|
||||
|
||||
def test_setServerAgent(self):
|
||||
"""Test 'set server agent' command"""
|
||||
args = {"backend": "redis-ro", "server" : "redis-ro0", "value": "up"}
|
||||
cmdSetServerAgent = cmds.setServerAgent(**args).getCmd()
|
||||
self.assertEqual(cmdSetServerAgent, self.Resp["set-server-agent"])
|
||||
args = {"backend": "redis-ro", "server": "redis-ro0", "value": "up"}
|
||||
cmdOutput = cmds.setServerAgent(**args).getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["set-server-agent"])
|
||||
|
||||
def test_setServerHealth(self):
|
||||
"""Test 'set server health' command"""
|
||||
args = {"backend": "redis-ro", "server" : "redis-ro0", "value": "stopping"}
|
||||
cmdSetServerHealth = cmds.setServerHealth(**args).getCmd()
|
||||
self.assertEqual(cmdSetServerHealth, self.Resp["set-server-health"])
|
||||
args = {"backend": "redis-ro", "server": "redis-ro0", "value": "stopping"}
|
||||
cmdOutput = cmds.setServerHealth(**args).getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["set-server-health"])
|
||||
|
||||
def test_setServerState(self):
|
||||
"""Test 'set server state' command"""
|
||||
args = {"backend": "redis-ro", "server" : "redis-ro0", "value": "drain"}
|
||||
cmdSetServerState = cmds.setServerState(**args).getCmd()
|
||||
self.assertEqual(cmdSetServerState, self.Resp["set-server-state"])
|
||||
args = {"backend": "redis-ro", "server": "redis-ro0", "value": "drain"}
|
||||
cmdOutput = cmds.setServerState(**args).getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["set-server-state"])
|
||||
|
||||
def test_setServerWeight(self):
|
||||
"""Test 'set server weight' command"""
|
||||
args = {"backend": "redis-ro", "server" : "redis-ro0", "value": "10"}
|
||||
cmdSetServerState = cmds.setServerWeight(**args).getCmd()
|
||||
self.assertEqual(cmdSetServerState, self.Resp["set-server-weight"])
|
||||
args = {"backend": "redis-ro", "server": "redis-ro0", "value": "10"}
|
||||
cmdOutput = cmds.setServerWeight(**args).getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["set-server-weight"])
|
||||
|
||||
def test_showFrontends(self):
|
||||
"""Test 'frontends/backends' commands"""
|
||||
args = {}
|
||||
cmdFrontends = cmds.showFrontends(**args).getCmd()
|
||||
self.assertEqual(cmdFrontends, self.Resp["frontends"])
|
||||
cmdOutput = cmds.showFrontends(**args).getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["frontends"])
|
||||
|
||||
def test_showInfo(self):
|
||||
"""Test 'show info' command"""
|
||||
cmdShowInfo = cmds.showInfo().getCmd()
|
||||
self.assertEqual(cmdShowInfo, self.Resp["info"])
|
||||
cmdOutput = cmds.showInfo().getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["info"])
|
||||
|
||||
def test_showSessions(self):
|
||||
"""Test 'show info' command"""
|
||||
cmdShowInfo = cmds.showSessions().getCmd()
|
||||
self.assertEqual(cmdShowInfo, self.Resp["sessions"])
|
||||
"""Test 'show sess' command"""
|
||||
cmdOutput = cmds.showSessions().getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["sessions"])
|
||||
|
||||
def test_showServers(self):
|
||||
"""Test 'show info' command"""
|
||||
"""Test 'show stat' command"""
|
||||
args = {"backend": "redis-ro"}
|
||||
cmdShowInfo = cmds.showServers(**args).getCmd()
|
||||
self.assertEqual(cmdShowInfo, self.Resp["servers"])
|
||||
cmdOutput = cmds.showServers(**args).getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["servers"])
|
||||
|
||||
def test_showSslCrtLists(self):
|
||||
"""Test 'show ssl crt-list' command"""
|
||||
cmdOutput = cmds.showSslCrtLists().getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["show-ssl-crt-lists"])
|
||||
|
||||
def test_showSslCrtList(self):
|
||||
"""Test 'show ssl crt-list <crt-list>' command"""
|
||||
args = {
|
||||
"crt_list": "/tmp/haproxy/ssl/601a7392cc9984.99301413.certlist",
|
||||
}
|
||||
cmdOutput = cmds.showSslCrtList(**args).getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["show-ssl-crt-list"])
|
||||
|
||||
def test_showSslCerts(self):
|
||||
"""Test 'show ssl cert' command"""
|
||||
cmdOutput = cmds.showSslCerts().getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["show-ssl-certs"])
|
||||
|
||||
def test_showSslCert(self):
|
||||
"""Test 'show ssl cert <certfile>' command"""
|
||||
args = {
|
||||
"certfile": "/tmp/haproxy/ssl/601a70e4844b0.pem"
|
||||
}
|
||||
cmdOutput = cmds.showSslCert(**args).getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["show-ssl-cert"])
|
||||
|
||||
def test_addToSslCrtList(self):
|
||||
"""Test 'add ssl crt-list <crt-list> <certfile>' command"""
|
||||
args = {
|
||||
"crt_list": "/tmp/haproxy/ssl/601a7392cc9984.99301413.certlist",
|
||||
"certfile": "/tmp/haproxy/ssl/601a70e4844b0.pem"
|
||||
}
|
||||
cmdOutput = cmds.addToSslCrtList(**args).getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["add-to-crt-list"])
|
||||
|
||||
def test_delFromSslCrtList(self):
|
||||
"""Test 'del ssl crt-list <crt-list> <certfile>' command"""
|
||||
args = {
|
||||
"crt_list": "/tmp/haproxy/ssl/601a7392cc9984.99301413.certlist",
|
||||
"certfile": "/tmp/haproxy/ssl/601a70e4844b0.pem"
|
||||
}
|
||||
cmdOutput = cmds.delFromSslCrtList(**args).getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["del-from-crt-list"])
|
||||
|
||||
def test_newSslCrt(self):
|
||||
"""Test 'new ssl cert <certfile>' command"""
|
||||
args = {
|
||||
"certfile": "/tmp/haproxy/ssl/601a70e4844b0.pem",
|
||||
}
|
||||
cmdOutput = cmds.newSslCrt(**args).getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["new-ssl-cert"])
|
||||
|
||||
def test_updateSslCrt(self):
|
||||
"""Test 'set ssl cert <certfile> <payload>' command"""
|
||||
args = {
|
||||
"certfile": "/tmp/haproxy/ssl/601a70e4844b0.pem",
|
||||
"payload": "%s" % self.pem_cert_content
|
||||
}
|
||||
cmdOutput = cmds.updateSslCrt(**args).getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["update-ssl-cert"])
|
||||
|
||||
def test_delSslCrt(self):
|
||||
"""Test 'del ssl cert <certfile>' command"""
|
||||
args = {
|
||||
"certfile": "/tmp/haproxy/ssl/601a70e4844b0.pem",
|
||||
}
|
||||
cmdOutput = cmds.delSslCrt(**args).getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["del-ssl-cert"])
|
||||
|
||||
def test_commitSslCrt(self):
|
||||
"""Test 'commit ssl cert <certfile>' command"""
|
||||
args = {
|
||||
"certfile": "/tmp/haproxy/ssl/601a70e4844b0.pem",
|
||||
}
|
||||
cmdOutput = cmds.commitSslCrt(**args).getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["commit-ssl-cert"])
|
||||
|
||||
def test_abortSslCrt(self):
|
||||
"""Test 'abort ssl cert <certfile>' command"""
|
||||
args = {
|
||||
"certfile": "/tmp/haproxy/ssl/601a70e4844b0.pem",
|
||||
}
|
||||
cmdOutput = cmds.abortSslCrt(**args).getCmd()
|
||||
self.assertEqual(cmdOutput, self.Resp["abort-ssl-cert"])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -19,6 +19,17 @@ VALID_COMMANDS = {
|
|||
"show-info": cmds.showInfo,
|
||||
"show-sessions": cmds.showSessions,
|
||||
"show-servers": cmds.showServers,
|
||||
"show-ssl-crt-lists": cmds.showSslCrtLists,
|
||||
"show-ssl-crt-list": cmds.showSslCrtList,
|
||||
"show-ssl-certs": cmds.showSslCerts,
|
||||
"show-ssl-cert": cmds.showSslCert,
|
||||
"add-to-crt-list": cmds.addToSslCrtList,
|
||||
"del-from-crt-list": cmds.delFromSslCrtList,
|
||||
"new-ssl-cert": cmds.newSslCrt,
|
||||
"update-ssl-cert": cmds.updateSslCrt,
|
||||
"del-ssl-cert": cmds.delSslCrt,
|
||||
"commit-ssl-cert": cmds.commitSslCrt,
|
||||
"abort-ssl-cert": cmds.abortSslCrt,
|
||||
}
|
||||
|
||||
def get_args():
|
||||
|
|
@ -40,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(
|
||||
|
|
@ -48,6 +59,21 @@ def get_args():
|
|||
help='Specify value for a set command.',
|
||||
default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
'--payload',
|
||||
help='Specify payload for a update command. either string or filepath',
|
||||
default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
'--crt-list',
|
||||
help='Set a filepath for a crt-list.',
|
||||
default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
'--certfile',
|
||||
help='Set a filepath for a certificate.',
|
||||
default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
'--output',
|
||||
help='Specify output format.',
|
||||
|
|
@ -89,6 +115,14 @@ def get_args():
|
|||
return parser.parse_args()
|
||||
|
||||
args = get_args()
|
||||
if args.payload and os.path.isfile(args.payload):
|
||||
with open(args.payload) as payload_file:
|
||||
payload_content = ""
|
||||
for line in payload_file:
|
||||
if line.rstrip():
|
||||
payload_content += line
|
||||
args.payload = payload_content
|
||||
|
||||
command_class = VALID_COMMANDS.get(args.command, None)
|
||||
command_args = {key: val for key, val in vars(args).items() if key != "command"}
|
||||
|
||||
|
|
@ -108,7 +142,6 @@ try:
|
|||
if result:
|
||||
print(f"{server_id}: {result.strip()}")
|
||||
con.close()
|
||||
|
||||
else:
|
||||
# single
|
||||
con = HaPConn(SOCKET)
|
||||
|
|
|
|||
577
net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/syncCerts.py
Executable file
577
net/haproxy/src/opnsense/scripts/OPNsense/HAProxy/syncCerts.py
Executable file
|
|
@ -0,0 +1,577 @@
|
|||
#!/usr/bin/env python3
|
||||
# Sync ssl certificates from a yaml file into haproxy memory
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import yaml
|
||||
import base64
|
||||
import OpenSSL
|
||||
import json
|
||||
from typing import List
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
|
||||
from haproxy.conn import HaPConn
|
||||
from haproxy import cmds
|
||||
|
||||
|
||||
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:
|
||||
command_obj = command_class(**command_args)
|
||||
result = con.sendCmd(command_obj, objectify=True)
|
||||
con.close()
|
||||
return result
|
||||
|
||||
def _calc_diff(self):
|
||||
""" return needed operations to get remote object in sync """
|
||||
raise Exception("need to be implemented!")
|
||||
|
||||
def diff_list(self, first: List, second: List):
|
||||
second = set(second)
|
||||
return [item for item in first if item not in second]
|
||||
|
||||
|
||||
class Diff(SyncWithTarget):
|
||||
""" Represents a full diff to sync with remote """
|
||||
|
||||
def __init__(self, crt_lists=None):
|
||||
super().__init__()
|
||||
if crt_lists is None:
|
||||
crt_lists = []
|
||||
self._crt_lists = crt_lists
|
||||
self._diff = self._calc_diff()
|
||||
self._status = self._get_status()
|
||||
self._transactions = self._get_transactions()
|
||||
|
||||
@property
|
||||
def diff(self):
|
||||
return self._diff
|
||||
|
||||
@property
|
||||
def crt_lists(self):
|
||||
return self._crt_lists
|
||||
|
||||
@property
|
||||
def transactions(self):
|
||||
return self._transactions
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self._status
|
||||
|
||||
def _calc_diff(self):
|
||||
result = {}
|
||||
for crt_list in self:
|
||||
result[crt_list.frontend_id] = crt_list.diff
|
||||
return result
|
||||
|
||||
def abort(self, output_format):
|
||||
""" Abort transactions"""
|
||||
aborted = []
|
||||
for certfile in self.transactions:
|
||||
certfile = certfile.replace('*/', "/")
|
||||
|
||||
output = self._execute_remote_cmd(cmds.abortSslCrt, certfile=certfile)
|
||||
aborted.append({
|
||||
"cert": certfile,
|
||||
"output": output,
|
||||
})
|
||||
|
||||
if output_format == 'json':
|
||||
print(json.dumps({'abort': aborted}))
|
||||
|
||||
if output_format == 'raw':
|
||||
for item in aborted:
|
||||
print(f"ABORT transaction: {item['cert']}")
|
||||
print(f" {repr(item['output'])}")
|
||||
|
||||
def _get_transactions(self):
|
||||
""" get open transactions"""
|
||||
return self._execute_remote_cmd(cmds.showSslCerts)['transaction']
|
||||
|
||||
def _get_status(self):
|
||||
status = {}
|
||||
crt_list: CertList
|
||||
for crt_list in self.crt_lists:
|
||||
status[crt_list.frontend_id] = {
|
||||
"frontend_name": crt_list.frontend_name,
|
||||
"path": crt_list.path,
|
||||
"local_certs": crt_list.local,
|
||||
"local_default": crt_list.local_default,
|
||||
"remote_certs": crt_list.remote,
|
||||
"remote_default": crt_list.remote_default,
|
||||
}
|
||||
cert: Cert
|
||||
status[crt_list.frontend_id]['certs'] = {}
|
||||
for cert in crt_list.certs:
|
||||
status[crt_list.frontend_id]['certs'][cert.cert_id] = {
|
||||
'path': cert.path,
|
||||
'local': cert.local,
|
||||
'remote': cert.remote,
|
||||
}
|
||||
return status
|
||||
|
||||
def show_status(self, output_format):
|
||||
""" Shows current local and remote state """
|
||||
if output_format == 'json':
|
||||
print(json.dumps(self.status))
|
||||
|
||||
if output_format == 'raw':
|
||||
print("## STATUS ##")
|
||||
for frontend_id, crt_list in self.status.items():
|
||||
print(f"CRT_LIST: {crt_list['path']}")
|
||||
print(f" FRONTEND NAME: {crt_list['frontend_name']}")
|
||||
print(f" FRONTEND ID: {frontend_id}")
|
||||
print(f" LOCAL CERTS: {crt_list['local_certs']}")
|
||||
print(f" REMOTE CERTS: {crt_list['remote_certs']}")
|
||||
print(f" LOCAL DEFAULT: {crt_list['local_default']}")
|
||||
print(f" REMOTE DEFAULT: {crt_list['remote_default']}")
|
||||
|
||||
for cert_id, cert in crt_list['certs'].items():
|
||||
print()
|
||||
print(f" CERT: {cert['path']}")
|
||||
print(f" LOCAL: {cert['local']}")
|
||||
print(f" REMOTE: {cert['remote']}")
|
||||
print()
|
||||
|
||||
def show_diff(self, output_format):
|
||||
""" Shows what will be synced to target """
|
||||
if output_format == 'json':
|
||||
print(json.dumps(self.diff))
|
||||
|
||||
if output_format == 'raw':
|
||||
print("## DIFF ##")
|
||||
for frontend_id, diff in self.diff.items():
|
||||
print(f"CRT LIST: {diff['path']}")
|
||||
print(f" FRONTEND NAME: {diff['frontend_name']}")
|
||||
print(f" FRONTEND ID: {diff['frontend_id']}")
|
||||
for update in diff['update']:
|
||||
print(f" CERT UPDATE:")
|
||||
print(f" Cert: {update['certfile']}")
|
||||
print(f" Serial: {update['meta']['Serial']}")
|
||||
print(f" Issuer: {update['meta']['Issuer']}")
|
||||
print(f" Subject: {update['meta']['Subject']}")
|
||||
else:
|
||||
if not diff['update']:
|
||||
print(f" CERT UPDATE: []")
|
||||
print(f" CERT ADD : {diff['add']}")
|
||||
print(f" CERT DEL : {diff['del']}")
|
||||
|
||||
def show_transactions(self, output_format):
|
||||
if output_format == 'json':
|
||||
print(json.dumps({'transactions': self.transactions}))
|
||||
|
||||
if output_format == 'raw':
|
||||
print("## OPEN TRANSACTIONS ##")
|
||||
for cert in self.transactions:
|
||||
print(cert)
|
||||
|
||||
def sync(self, output_format):
|
||||
""" Sync to target """
|
||||
sync = {}
|
||||
certs_to_delete = []
|
||||
for frontend_id, diff in self.diff.items():
|
||||
sync[frontend_id] = {
|
||||
'frontend_name': diff['frontend_name'],
|
||||
'frontend_id': diff['frontend_id'],
|
||||
'path': diff['path'],
|
||||
'add': [],
|
||||
'remove': [],
|
||||
'update': [],
|
||||
'del': []
|
||||
}
|
||||
|
||||
# update cert content
|
||||
for cert in diff['update']:
|
||||
messages = []
|
||||
if cert['certfile'] in diff['add']:
|
||||
output = self._execute_remote_cmd(cmds.newSslCrt, certfile=cert['certfile'])
|
||||
messages.append(output)
|
||||
|
||||
output = self._execute_remote_cmd(cmds.updateSslCrt, certfile=cert['certfile'], payload=cert['pem'])
|
||||
messages.append(output)
|
||||
|
||||
output = self._execute_remote_cmd(cmds.commitSslCrt, certfile=cert['certfile'])
|
||||
messages.append(output)
|
||||
|
||||
sync[frontend_id]['update'].append({
|
||||
'cert': cert['certfile'],
|
||||
'messages': messages
|
||||
})
|
||||
|
||||
# add to crt-list
|
||||
for cert in diff['add']:
|
||||
messages = []
|
||||
output = self._execute_remote_cmd(cmds.addToSslCrtList, crt_list=diff['path'], certfile=cert)
|
||||
messages.append(output)
|
||||
sync[frontend_id]['add'].append({
|
||||
'cert': cert,
|
||||
'messages': messages
|
||||
})
|
||||
|
||||
# remove from crt-list
|
||||
for cert in diff['del']:
|
||||
messages = []
|
||||
output = self._execute_remote_cmd(cmds.delFromSslCrtList, crt_list=diff['path'], certfile=cert)
|
||||
messages.append(output)
|
||||
certs_to_delete.append(cert.split(":")[0])
|
||||
sync[frontend_id]['remove'].append({
|
||||
'cert': cert,
|
||||
'messages': messages
|
||||
})
|
||||
|
||||
# delete unused certs operation - haproxy does not allow to delete certs in use
|
||||
for cert in certs_to_delete:
|
||||
messages = []
|
||||
output = self._execute_remote_cmd(cmds.delSslCrt, certfile=cert)
|
||||
messages.append(output)
|
||||
sync[frontend_id]['del'].append({
|
||||
'cert': cert,
|
||||
'messages': messages
|
||||
})
|
||||
|
||||
if output_format == 'json':
|
||||
print(json.dumps(self.diff))
|
||||
|
||||
if output_format == 'raw':
|
||||
print("## SYNC ##")
|
||||
for frontend_id, crt_list in sync.items():
|
||||
print(f"CRT-LIST: {crt_list['path']}")
|
||||
print(f" FRONTEND NAME: {crt_list['frontend_name']}")
|
||||
print(f" FRONTEND ID: {crt_list['frontend_id']}")
|
||||
for cert in crt_list['update']:
|
||||
print(f" UPDATE: {cert['cert']}")
|
||||
for message in cert['messages']:
|
||||
print(" " + repr(message))
|
||||
for cert in crt_list['add']:
|
||||
print(f" ADD: {cert['cert']}")
|
||||
for message in cert['messages']:
|
||||
print(" " + repr(message))
|
||||
|
||||
for cert in crt_list['remove']:
|
||||
print(f" REMOVE: {cert['cert']}")
|
||||
for message in cert['messages']:
|
||||
print(" " + repr(message))
|
||||
|
||||
for cert in crt_list['del']:
|
||||
print(f"\n DEL: {cert['cert']}")
|
||||
for message in cert['messages']:
|
||||
print(" " + repr(message))
|
||||
print()
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._crt_lists)
|
||||
|
||||
def __str__(self):
|
||||
return self.status
|
||||
|
||||
|
||||
class CertList(SyncWithTarget):
|
||||
""" Represents a haproxy ssl-crt-list """
|
||||
|
||||
def __init__(self, path, frontend_id=None, frontend_name=None, certs=None, default_cert=None):
|
||||
super().__init__()
|
||||
if certs is None:
|
||||
certs = []
|
||||
self._path = path
|
||||
self._certs = certs
|
||||
self._frontend_name = frontend_name
|
||||
self._frontend_id = frontend_id
|
||||
self._local_default = default_cert
|
||||
self._local = self._get_local_state()
|
||||
self._remote_ln = self._get_remote_state(cmds.showSslCrtList, crt_list=self._path)
|
||||
self._remote = [cert_ln.split(":")[0] for cert_ln in self._remote_ln]
|
||||
self._diff = self._calc_diff()
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return self._path
|
||||
|
||||
@property
|
||||
def frontend_name(self):
|
||||
return self._frontend_name
|
||||
|
||||
@property
|
||||
def frontend_id(self):
|
||||
return self._frontend_id
|
||||
|
||||
@property
|
||||
def certs(self):
|
||||
return self._certs
|
||||
|
||||
@property
|
||||
def local_default(self):
|
||||
return self._local_default
|
||||
|
||||
@property
|
||||
def remote_default(self):
|
||||
return next(iter(self._remote), None)
|
||||
|
||||
@property
|
||||
def local(self):
|
||||
return self._local
|
||||
|
||||
@property
|
||||
def remote_ln(self):
|
||||
""" Certs with line number"""
|
||||
return self._remote_ln
|
||||
|
||||
@property
|
||||
def remote(self):
|
||||
"""
|
||||
if default certs are different return remote certs with line numbers, so they are deleted in the crt list.
|
||||
This ensures that the default cert is always on top.
|
||||
"""
|
||||
if self._local_default is not None and self.local_default != self.remote_default:
|
||||
return self._remote_ln
|
||||
return self._remote
|
||||
|
||||
@property
|
||||
def diff(self):
|
||||
return self._diff
|
||||
|
||||
def _calc_diff(self):
|
||||
""" return needed operations to get remote object in sync """
|
||||
diff = {
|
||||
'frontend_name': self.frontend_name,
|
||||
'frontend_id': self.frontend_id,
|
||||
'path': self.path,
|
||||
'add': [],
|
||||
'del': [],
|
||||
'update': []
|
||||
}
|
||||
# skip when there is no remote crt list
|
||||
if self.remote is None:
|
||||
return diff
|
||||
|
||||
# certs to add, delete and update on the remote target
|
||||
diff['add'] = self.diff_list(self.local, self.remote)
|
||||
diff['del'] = self.diff_list(self.remote, self.local)
|
||||
diff['update'] = [cert.diff for cert in self.certs if cert.diff]
|
||||
|
||||
return diff
|
||||
|
||||
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 = self._execute_remote_cmd(command_class, **command_args)
|
||||
return crt_list_data.get('certs', None)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._local)
|
||||
|
||||
|
||||
class Cert(SyncWithTarget):
|
||||
""" Represents a haproxy ssl-cert """
|
||||
|
||||
def __init__(self, path, pem, cert_id=None):
|
||||
super().__init__()
|
||||
self._path = path
|
||||
self._pem = pem
|
||||
self._cert_id = cert_id
|
||||
self._local = self._get_local_state()
|
||||
self._remote = self._get_remote_state(cmds.showSslCert, certfile=self._path)
|
||||
self._diff = self._calc_diff()
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return self._path
|
||||
|
||||
@property
|
||||
def cert_id(self):
|
||||
return self._cert_id
|
||||
|
||||
@property
|
||||
def pem(self):
|
||||
return self._pem.replace("\n\n", "\n")
|
||||
|
||||
@property
|
||||
def local(self):
|
||||
return self._local
|
||||
|
||||
@property
|
||||
def remote(self):
|
||||
return self._remote
|
||||
|
||||
@property
|
||||
def diff(self):
|
||||
return self._diff
|
||||
|
||||
def __repr__(self):
|
||||
return self._path
|
||||
|
||||
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'.upper() % 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 = self._execute_remote_cmd(command_class, **command_args)
|
||||
|
||||
if 'error' in cert_data:
|
||||
return cert_data
|
||||
|
||||
if cert_data['Status'] == 'Empty':
|
||||
return {'Status': cert_data['Status']}
|
||||
|
||||
return {
|
||||
"Serial": cert_data.get('Serial', None),
|
||||
"Subject": cert_data.get('Subject', None),
|
||||
"Issuer": cert_data.get('Issuer', None),
|
||||
}
|
||||
|
||||
def _calc_diff(self):
|
||||
result = {}
|
||||
if self._remote != self._local:
|
||||
result['certfile'] = self.path
|
||||
result['pem'] = self.pem
|
||||
result['meta'] = self.local
|
||||
return result
|
||||
|
||||
|
||||
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(",")))
|
||||
|
||||
if not filter_frontend_ids and not filter_frontend_names:
|
||||
return False
|
||||
|
||||
if filter_frontend_ids and frontend_id in filter_frontend_ids:
|
||||
return False
|
||||
if filter_frontend_names and frontend['name'] in filter_frontend_names:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
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 HAProxy’s 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(
|
||||
'command',
|
||||
choices=['status', 'diff', 'sync', 'transactions', 'abort'],
|
||||
nargs='+',
|
||||
help="Execute one or more operations."
|
||||
)
|
||||
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"
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def get_crt_lists_from_config(configfile):
|
||||
""" Get ssl crt-list with certificates from configfile"""
|
||||
config = dict_from_yaml(configfile)
|
||||
crt_lists = []
|
||||
for frontend_id, frontend in config['frontends'].items():
|
||||
if skip_frontend(frontend_id, frontend):
|
||||
continue
|
||||
|
||||
certs = []
|
||||
default_cert = None
|
||||
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
|
||||
|
||||
if cert_data['default']:
|
||||
default_cert = cert_data['path']
|
||||
|
||||
certs.append(Cert(path=cert_data['path'], pem=full_cert, cert_id=cert_id))
|
||||
|
||||
params = {
|
||||
'path': frontend['crt_list_path'],
|
||||
'frontend_id': frontend_id,
|
||||
'frontend_name': frontend['name'],
|
||||
'certs': certs,
|
||||
'default_cert': default_cert
|
||||
}
|
||||
crt_lists.append(CertList(**params))
|
||||
|
||||
return crt_lists
|
||||
|
||||
|
||||
args = get_args()
|
||||
crt_lists = get_crt_lists_from_config(args.config)
|
||||
diff = Diff(crt_lists=crt_lists)
|
||||
|
||||
""" Sync ssl certs from configfile to HaProxy """
|
||||
if "status" in args.command:
|
||||
diff.show_status(args.output)
|
||||
if "diff" in args.command:
|
||||
diff.show_diff(args.output)
|
||||
if "abort" in args.command:
|
||||
diff.abort(args.output)
|
||||
if "transactions" in args.command:
|
||||
diff.show_transactions(args.output)
|
||||
if "sync" in args.command:
|
||||
diff.sync(args.output)
|
||||
|
|
@ -75,3 +75,28 @@ command:/usr/local/opnsense/scripts/OPNsense/HAProxy/socketCommand.py
|
|||
parameters: set-server-weight --server-ids %s --value %s
|
||||
type:script_output
|
||||
message:change haproxy weight for multiple server
|
||||
|
||||
[cert_diff]
|
||||
command:/usr/local/opnsense/scripts/OPNsense/HAProxy/syncCerts.py
|
||||
parameters: diff --output json --frontends %s
|
||||
type:script_output
|
||||
message:Show diff between configured ssl certificates and certs from HAProxy memory for multiple frontends
|
||||
|
||||
[cert_sync]
|
||||
command:/usr/local/opnsense/scripts/OPNsense/HAProxy/syncCerts.py
|
||||
parameters: sync --frontends %s --output json
|
||||
type:script_output
|
||||
message:Sync ssl certificates into HAProxy memory for multiple frontends
|
||||
|
||||
[cert_diff_bulk]
|
||||
command:/usr/local/opnsense/scripts/OPNsense/HAProxy/syncCerts.py diff --output json
|
||||
parameters:
|
||||
type:script_output
|
||||
message:Show diff between configured ssl certificates and certs from HAProxy memory for all frontends
|
||||
|
||||
[cert_sync_bulk]
|
||||
command:/usr/local/opnsense/scripts/OPNsense/HAProxy/syncCerts.py sync --output json
|
||||
parameters:
|
||||
type:script_output
|
||||
message:Sync ssl certificates into HAProxy memory for all frontends
|
||||
description:Sync ssl certificates changes into HAProxy memory
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
haproxy.conf:/usr/local/etc/haproxy.conf
|
||||
rc.conf.d:/etc/rc.conf.d/haproxy
|
||||
sslCerts.yaml:/usr/local/etc/haproxy/sslCerts.yaml
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
#
|
||||
# Automatically generated configuration.
|
||||
# Do not edit this file manually.
|
||||
#
|
||||
# List all frontends with configured ssl certificates in config.xml
|
||||
{# ################## #}
|
||||
{# ##### Macros ##### #}
|
||||
{# ################## #}
|
||||
{% macro getCA(refId) -%}
|
||||
{% set result = '{}' %}
|
||||
{% for data in helpers.getNodeByTag('ca') if data.refid == refId %}
|
||||
{{ data.crt -}}
|
||||
{% else %}
|
||||
{{ "{}" }}
|
||||
{% endfor %}
|
||||
{%- endmacro %}
|
||||
{% macro getCert(refId, indent=4) -%}
|
||||
{% for data in helpers.getNodeByTag('cert') if data.refid == refId %}
|
||||
{% if data.caref %}
|
||||
{% do data.update({'ca': getCA(data.caref)}) %}
|
||||
{% else %}
|
||||
{% do data.update({'ca': {} }) %}
|
||||
{% endif %}
|
||||
crt: {{ data.crt }}
|
||||
key: {{ data.prv }}
|
||||
ca: {{ data.ca }}
|
||||
{% endfor %}
|
||||
{%- endmacro %}
|
||||
{# ################## #}
|
||||
{# ##### Main ##### #}
|
||||
{# ################## #}
|
||||
{% set enabled_frontends = [] %}
|
||||
{% set crt_list_template = "/tmp/haproxy/ssl/%s.certlist" %}
|
||||
{% set cert_template = "/tmp/haproxy/ssl/%s.pem" %}
|
||||
{% for frontend in helpers.toList('OPNsense.HAProxy.frontends.frontend') %}
|
||||
{% set certs = [] %}
|
||||
{% for cert in frontend.get('ssl_default_certificate', '').split(',') + frontend.get('ssl_certificates', '').split(',') if cert %}
|
||||
{% do certs.append(cert) %}
|
||||
{% endfor %}
|
||||
{% do frontend.update({'certs': certs}) %}
|
||||
{% if frontend.enabled == '1' and frontend.ssl_enabled == '1' and frontend.certs|length > 0 %}
|
||||
{% do enabled_frontends.append(frontend) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if helpers.exists('OPNsense.HAProxy.frontends') and enabled_frontends|length > 0 %}
|
||||
frontends:
|
||||
{% for frontend in enabled_frontends %}
|
||||
"{{ frontend.id }}":
|
||||
name: {{ frontend.name }}
|
||||
crt_list_path: {{ crt_list_template % frontend.id }}
|
||||
certs:
|
||||
{% for cert_refid in frontend.certs %}
|
||||
{{ cert_refid }}:
|
||||
path: {{ cert_template % cert_refid }}
|
||||
default: {{ "True" if frontend.ssl_default_certificate == cert_refid else "False" }}
|
||||
{{ getCert(cert_refid) | indent( width=8, indentfirst=True) -}}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
frontends: {}
|
||||
{% endif %}
|
||||
Loading…
Reference in a new issue