Skip to content

Commit

Permalink
Merge branch '51-new-release-0-5-1' into 'master'
Browse files Browse the repository at this point in the history
Resolve "New release 0.6"

Closes #51

See merge request open/stapled!36
  • Loading branch information
SnijderC committed Mar 30, 2018
2 parents 79c4efe + 9a2cd9c commit a745d34
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 63 deletions.
11 changes: 10 additions & 1 deletion debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
stapled (0.6) stretch; urgency=low

* Improvement: Remove some spaces from log lines.
* FIX: Broken exception handling in python 2 version.
* FIX: Parsing file extensions incorrectly from config file.
* FIX: Opening, re-opening and sending of commands to sockets broken.n

-- Chris Snijder <[email protected]> Thu, 29 Mar 2018 18:46:44 +0200

stapled (0.5) stretch; urgency=low

* Improved consistency in source code.
Expand All @@ -6,7 +15,7 @@ stapled (0.5) stretch; urgency=low
sockets and certificate directories.
* It is now possible to specify single certificate aside from entire
directories.
* DEPRECATED: --directories was superseded by --cert-paths.
* DEPRECATED: --directories was superseded by --cert-paths.

-- Chris Snijder <[email protected]> Wed, 7 Mar 2018 16:35:22 +0100

Expand Down
7 changes: 5 additions & 2 deletions stapled/core/certfinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import fnmatch
import os
import stapled
import errno
from stapled.core.excepthandler import stapled_except_handle
from stapled.core.taskcontext import StapleTaskContext
from stapled.core.certmodel import CertModel
Expand Down Expand Up @@ -165,13 +166,15 @@ def _find_new_certs(self, paths, cert_path=None):
dirs = []
try:
dirs = os.listdir(path)
except NotADirectoryError:
except (OSError, IOError) as exc:
# If a path is actually a file we can still use it..
if os.path.isfile(path):
if exc.errno == errno.ENOTDIR and os.path.isfile(path):
LOG.debug("%s may be a single file", path)
# This will allow us to use our usual iteration.
dirs = [os.path.basename(path)]
path = os.path.dirname(path)
else:
raise exc
for entry in dirs:
entry = os.path.join(path, entry)
if os.path.isdir(entry):
Expand Down
2 changes: 1 addition & 1 deletion stapled/core/daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def __init__(self, **kwargs):
'haproxy_socket_mapping', None
)
self.file_extensions = kwargs.pop('file_extensions')
self.file_extensions.replace(" ", "").split(",")
self.file_extensions = self.file_extensions.replace(" ", "").split(",")
self.renewal_threads = kwargs.pop('renewal_threads')
self.refresh_interval = kwargs.pop('refresh_interval')
self.minimum_validity = kwargs.pop('minimum_validity')
Expand Down
14 changes: 12 additions & 2 deletions stapled/core/excepthandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import datetime
import logging
import os
import errno
import traceback
import configargparse
from stapled.core.exceptions import OCSPBadResponse
Expand Down Expand Up @@ -150,8 +151,17 @@ def stapled_except_handle(ctx=None):
"entries",
err_count, len_ocsp_urls
)
except PermissionError as exc:
LOG.critical(exc)
except (IOError, OSError) as exc:
if exc.errno==errno.EPERM or exc.errno==errno.EACCES:
reason = "Permission error"
elif exc.errno==errno.ENOENT:
reason = "File not found error"
elif isinstance(exc, IOError):
reason = "I/O Error"
else:
reason = "OS Error"

LOG.critical("{}: {}".format(reason, str(exc)))

# the show must go on..
except Exception as exc: # pylint: disable=broad-except
Expand Down
154 changes: 98 additions & 56 deletions stapled/core/stapleadder.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ class StapleAdder(threading.Thread):
#: the base64 encoded OCSP staple
OCSP_ADD = 'set ssl ocsp-response {}'

#: Predefines commands to send to sockets just after opening them.
CONNECT_COMMANDS = [
"prompt",
"set timeout cli {}".format(SOCKET_TIMEOUT)
]

def __init__(self, *args, **kwargs):
"""
Initialise the thread and its parent :class:`threading.Thread`.
Expand Down Expand Up @@ -74,10 +80,33 @@ def __init__(self, *args, **kwargs):
for paths in self.haproxy_socket_mapping.values():
for path in paths:
with stapled_except_handle():
self.socks[path] = self._open_socket(path)
self._open_socket(path)

super(StapleAdder, self).__init__(*args, **kwargs)

def _re_open_socket(self, path):
"""
Re-open socket located at path, and return the socket.
Closes open sockets and wraps appropriate logging around the
``_open_socket`` method.
:param str path: A valid HAProxy socket path.
:return socket.socket: An open socket.
:raises :exc:stapled.core.exceptions.SocketError: when the socket can
not be opened.
"""
# Try to re-open the socket. If that doesn't work, that
# will raise a :exc:`~stapled.core.exceptions.SocketError`
LOG.info("Re-opening socket %s", path)
try:
sock = self.socks[path]
sock.close()
except (KeyError, UnboundLocalError):
# Socket not openend, no need to close anything.
pass
# Open socket again..
return self._open_socket(path)

def _open_socket(self, path):
"""
Open socket located at path, and return the socket.
Expand All @@ -87,27 +116,30 @@ def _open_socket(self, path):
socket.
:param str path: A valid HAProxy socket path.
:return socket.socket: An open socket.
:raises :exc:stapled.core.exceptions.SocketError: when the socket can
not be opened.
"""
sock = socket.socket(
socket.AF_UNIX,
socket.SOCK_STREAM
)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
sock.connect(path)
except (FileNotFoundError, PermissionError) as exc:
result = []
for command in self.CONNECT_COMMANDS:
result.extend(self._send(sock, command))
# Results (index 1) come per path (index 0), we need only results
result = [res[1] for res in result]
# Indented and on separate lines or None if an empty list
result = "\n\t{}".format("\n\t".join(result)) if result else "None"
LOG.debug("Opened prompt with result: %s", result)
self.socks[path] = sock
return sock
except (OSError, IOError) as exc:
raise stapled.core.exceptions.SocketError(
"Could not initialize StapleAdder with socket {}: {}".format(
path,
exc
)
)
result = self.send(path, "prompt")
LOG.debug("Opened prompt with result: '%s'", result)
result = self.send(path, "set timeout cli {}".format(SOCKET_TIMEOUT))
return sock

def __del__(self):
"""Close the sockets on exit."""
Expand Down Expand Up @@ -146,12 +178,12 @@ def add_staple(self, model):
"""
command = self.OCSP_ADD.format(model.ocsp_staple.base64)
LOG.debug("Setting OCSP staple with command '%s'", command)
path = self.haproxy_socket_mapping[model.cert_path]
if path is None:
paths = self.haproxy_socket_mapping[model.cert_path]
if not paths:
LOG.debug("No socket set for %s", model.filename)
return
responses = self.send(path, command)
for response in responses:
responses = self.send(paths, command)
for path, response in responses:
if response != 'OCSP Response updated!':
raise stapled.core.exceptions.StapleAdderBadResponse(
"Bad HAProxy response: '{}' from socket {}".format(
Expand All @@ -160,15 +192,54 @@ def add_staple(self, model):
)
)

def send(self, paths, command):
def _send(self, sock, command):
"""
Send the command through the socket at ``path``.
Send the command through the ``socket`` and handle response.
:param list paths: The path(s) to the socket(s) which should already
be open.
:param list sock: An already opened socket.
:param str command: String with the HAProxy command. For a list of
possible commands, see the `haproxy documentation`_
:return list: List of tuples containing path and response from HAProxy.
:raises IOError if an error occurs and it's not errno.EAGAIN or
errno.EINTR
.. _haproxy documentation:
http://haproxy.tech-notes.net/9-2-unix-socket-commands/
"""
sock.sendall((command + "\n").encode())
buff = StringIO()
# Get new response.
while True:
try:
chunk = sock.recv(SOCKET_BUFFER_SIZE)
if chunk:
decoded_chunk = chunk.decode('ascii')
buff.write(decoded_chunk)
# TODO: Find out what happens if several threads
# are talking to HAProxy on this socket
if '> ' in decoded_chunk:
break
else:
break
except IOError as err:
if err.errno not in (errno.EAGAIN, errno.EINTR):
raise

# Strip *all* \n, > and space characters from the end
response = buff.getvalue().strip('\n> ')
buff.close()
return response

def send(self, paths, command):
"""
Send the command through the sockets at ``paths``.
:param str|list paths: The path(s) to the socket(s) which should
already be open.
:param str command: String with the HAProxy command. For a list of
possible commands, see the `haproxy documentation`_
:return list: List of tuples containing path and response from HAProxy.
:raises IOError if an error occurs and it's not errno.EAGAIN or
errno.EINTR
Expand All @@ -194,47 +265,18 @@ def send(self, paths, command):
# raise
# else:
# break

# Send command
with stapled_except_handle():
responses = []
if not isinstance(paths, (list, tuple)):
paths = [paths]
for path in paths:
try:
sock = self.socks[path]
sock.sendall((command + "\n").encode())
response = self._send(sock, "{}\n".format(command))
except (BrokenPipeError, KeyError):
# Try to re-open the socket. If that doesn't work, that
# will raise a :exc:`~stapled.core.exceptions.SocketError`
LOG.info("Re-opening socket %s", path)
try:
sock.close()
except UnboundLocalError: # sock is not yet defined.
pass
sock = self.socks[path] = self._open_socket(path)
# Try again, if this results in a BrokenPipeError *again*,
# it will be caught by stapled_except_handle
sock.sendall((command + "\n").encode())
buff = StringIO()
# Get new response.
while True:
try:
chunk = sock.recv(SOCKET_BUFFER_SIZE)
if chunk:
d_chunk = chunk.decode('ascii')
buff.write(d_chunk)
# TODO: Find out what happens if several threads
# are talking to HAProxy on this socket
if '> ' in d_chunk:
break
else:
break
except IOError as err:
if err.errno not in (errno.EAGAIN, errno.EINTR):
raise

# Strip *all* \n, > and space characters from the end
response = buff.getvalue().strip('\n> ')
sock = self._re_open_socket(path)
response = self._send(sock, "{}\n".format(command))

LOG.debug("Received HAProxy response '%s'", response)
responses.append()
buff.close()
return responses
responses.append((path, response))
return responses
2 changes: 1 addition & 1 deletion stapled/version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__version__ = '0.5'
__version__ = '0.6'
__app_name__ = 'stapled'
__debian_version__ = 'stretch'

0 comments on commit a745d34

Please sign in to comment.