Add ability to deliver cleartext when keys can't be loaded #135

Merged
pfm merged 3 commits from fix/keys-not-loaded into main 2023-12-10 21:41:49 +01:00
4 changed files with 18 additions and 9 deletions

View File

@ -68,6 +68,11 @@ max_data_bytes = 33554432
# This should never be PII, but information like encoding, content types, etc. # This should never be PII, but information like encoding, content types, etc.
log_headers = no log_headers = no
# Sometimes we might fail to load keys and need to choose between delivering
# in cleartext or not delivering. The default is to deliver cleartext, but
# administrators can make this decision on their own.
bounce_on_keys_missing = no
[relay] [relay]
# the relay settings to use for Postfix # the relay settings to use for Postfix
# gpg-mailgate will submit email to this relay after it is done processing # gpg-mailgate will submit email to this relay after it is done processing

View File

@ -37,9 +37,7 @@ class MailEncryptionProxy:
with time_logger('Message delivery', LOG): with time_logger('Message delivery', LOG):
try: try:
keys = self._keyring.freeze_identities() keys = self._keyring.freeze_identities()
LOG.debug('Parsing message: %s', self._beginning(envelope))
message = email.message_from_bytes(envelope.original_content, policy=SMTPUTF8) message = email.message_from_bytes(envelope.original_content, policy=SMTPUTF8)
LOG.debug('Parsed into %s: %s', type(message), repr(message))
if message.defects: if message.defects:
LOG.warning("Issues found: %d; %s", len(message.defects), repr(message.defects)) LOG.warning("Issues found: %d; %s", len(message.defects), repr(message.defects))

View File

@ -4,6 +4,7 @@ from sqlalchemy import create_engine, select, delete, and_, func
from sqlalchemy.exc import OperationalError from sqlalchemy.exc import OperationalError
import logging import logging
from lacre.config import flag_enabled
from lacre._keyringcommon import KeyRing, KeyCache from lacre._keyringcommon import KeyRing, KeyCache
import lacre.dbschema as db import lacre.dbschema as db
@ -13,6 +14,7 @@ LOG = logging.getLogger(__name__)
# Internal state # Internal state
_engine = None _engine = None
def connect(url): def connect(url):
global _engine global _engine
@ -78,13 +80,21 @@ class IdentityRepository(KeyRing):
self._conn.execute(delq) self._conn.execute(delq)
def freeze_identities(self) -> KeyCache: def freeze_identities(self) -> KeyCache:
"""Return a static, async-safe copy of the identity map.""" """Return a static, async-safe copy of the identity map.
Depending on the value of [daemon]bounce_on_keys_missing value,
if we get a database exception, this method will either return
empty collection or let the exception be propagated.
"""
self._ensure_connected() self._ensure_connected()
try: try:
return self._load_identities() return self._load_identities()
except OperationalError: except OperationalError:
LOG.exception('Cannot retrieve identities') if flag_enabled('daemon', 'bounce_on_keys_missing'):
return None raise
else:
LOG.exception('Failed to load keys, returning empty collection')
return KeyCache({})
def _load_identities(self) -> KeyCache: def _load_identities(self) -> KeyCache:
all_identities = select(self._identities.c.fingerprint, self._identities.c.email) all_identities = select(self._identities.c.fingerprint, self._identities.c.email)

View File

@ -65,10 +65,6 @@ class GnuPGUtilitiesTest(unittest.TestCase):
uid = GnuPG._parse_uid_line(sample_in) uid = GnuPG._parse_uid_line(sample_in)
self.assertEqual(uid, 'alice@disposlab') self.assertEqual(uid, 'alice@disposlab')
def test_exception_formatting(self):
e = GnuPG.EncryptionException('alice@disposlab', 'key expired', None, b'DEADBEEF')
self.assertEqual(str(e), 'To: alice@disposlab; Issue: key expired; Key: DEADBEEF')
def test_parse_statusfd_key_expired(self): def test_parse_statusfd_key_expired(self):
key_expired = b""" key_expired = b"""
[GNUPG:] KEYEXPIRED 1668272263 [GNUPG:] KEYEXPIRED 1668272263