mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-01-26 16:16:31 -06:00
EPN: Allow authentication by SMTP client's certificate
SMTP server may ask or require client's certificate for verification. To support this the underlying Python's functionality is used [0]. Added 3 new options(corresponds to `load_cert_chain`): - smtp_client_cert - the path to a single file in PEM format containing the certificate. - smtp_client_key - the path to a file containing the private key in. - smtp_client_key_pass - the password for decrypting the private key. [0]: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_cert_chain Fixes: https://pagure.io/freeipa/issue/8580 Signed-off-by: Stanislav Levin <slev@altlinux.org> Reviewed-By: Rob Crittenden <rcritten@redhat.com> Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
This commit is contained in:
parent
32aa1540f0
commit
17f430efc4
@ -60,6 +60,15 @@ Specifies the id of the user to authenticate with the SMTP server. Default None.
|
||||
.B smtp_password <password>
|
||||
Specifies the password for the authorized user. Default None.
|
||||
.TP
|
||||
.B smtp_client_cert <certificate>
|
||||
Specifies the path to a single file in PEM format containing the certificate. Default None.
|
||||
.TP
|
||||
.B smtp_client_key <private key>
|
||||
Specifies the path to a file containing the private key in. Otherwise the private key will be taken from certfile as well. Default None.
|
||||
.TP
|
||||
.B smtp_client_key_pass <private key password>
|
||||
Specifies the password for decrypting the private key. Default None.
|
||||
.TP
|
||||
.B smtp_timeout <seconds>
|
||||
Specifies the number of seconds to wait for SMTP to respond. Default 60.
|
||||
.TP
|
||||
|
@ -23,6 +23,23 @@ smtp_port = 25
|
||||
# Default None (empty value).
|
||||
# smtp_password =
|
||||
|
||||
# Specifies the path to a single file in PEM format containing the certificate.
|
||||
# https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_cert_chain
|
||||
# Default None (empty value).
|
||||
# smtp_client_cert =
|
||||
|
||||
# Specifies the path to a file containing the private key in. Otherwise the
|
||||
# private key will be taken from certfile as well.
|
||||
# https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_cert_chain
|
||||
# Default None (empty value).
|
||||
# smtp_client_key =
|
||||
|
||||
# Specifies the password for decrypting the private key. It will be ignored if
|
||||
# the private key is not encrypted and no password is needed.
|
||||
# https://docs.python.org/3/library/ssl.html#ssl.SSLContext.load_cert_chain
|
||||
# Default None (empty value).
|
||||
# smtp_client_key_pass =
|
||||
|
||||
# Specifies the number of seconds to wait for SMTP to respond.
|
||||
smtp_timeout = 60
|
||||
|
||||
|
@ -56,6 +56,9 @@ EPN_CONFIG = {
|
||||
"smtp_port": 25,
|
||||
"smtp_user": None,
|
||||
"smtp_password": None,
|
||||
"smtp_client_cert": None,
|
||||
"smtp_client_key": None,
|
||||
"smtp_client_key_pass": None,
|
||||
"smtp_timeout": 60,
|
||||
"smtp_security": "none",
|
||||
"smtp_admin": "root@localhost",
|
||||
@ -470,6 +473,12 @@ class EPN(admintool.AdminTool):
|
||||
"""
|
||||
if api.env.smtp_security.lower() in ("starttls", "ssl"):
|
||||
self._ssl_context = ssl.create_default_context()
|
||||
if api.env.smtp_client_cert:
|
||||
self._ssl_context.load_cert_chain(
|
||||
certfile=api.env.smtp_client_cert,
|
||||
keyfile=api.env.smtp_client_key,
|
||||
password=str(api.env.smtp_client_key_pass),
|
||||
)
|
||||
|
||||
def _fetch_data_from_ldap(self, date_range):
|
||||
"""Run a LDAP query to fetch a list of user entries whose passwords
|
||||
|
@ -44,6 +44,13 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
EPN_PKG = ["*ipa-client-epn"]
|
||||
|
||||
SMTP_CLIENT_CERT = os.path.join(paths.OPENSSL_CERTS_DIR, "smtp_client.pem")
|
||||
SMTP_CLIENT_KEY = os.path.join(paths.OPENSSL_PRIVATE_DIR, "smtp_client.key")
|
||||
SMTP_CLIENT_KEY_PASS = "Secret123"
|
||||
|
||||
SMTPD_KEY = os.path.join(paths.OPENSSL_PRIVATE_DIR, "postfix.key")
|
||||
SMTPD_CERT = os.path.join(paths.OPENSSL_CERTS_DIR, "postfix.pem")
|
||||
|
||||
DEFAULT_EPN_CONF = textwrap.dedent(
|
||||
"""\
|
||||
[global]
|
||||
@ -72,6 +79,13 @@ SSL_EPN_CONF = USER_EPN_CONF + textwrap.dedent(
|
||||
"""
|
||||
)
|
||||
|
||||
CLIENT_CERT_EPN_CONF = textwrap.dedent(
|
||||
"""\
|
||||
smtp_client_cert={client_cert}
|
||||
smtp_client_key={client_key}
|
||||
smtp_client_key_pass={client_key_pass}
|
||||
"""
|
||||
)
|
||||
|
||||
def datetime_to_generalized_time(dt):
|
||||
"""Convert datetime to LDAP_GENERALIZED_TIME_FORMAT
|
||||
@ -146,17 +160,10 @@ def configure_starttls(host):
|
||||
Depends on configure_postfix() being executed first.
|
||||
"""
|
||||
|
||||
host.run_command(
|
||||
["rm", "-f", os.path.join(paths.OPENSSL_PRIVATE_DIR, "postfix.key")]
|
||||
)
|
||||
host.run_command(
|
||||
["rm", "-f", os.path.join(paths.OPENSSL_CERTS_DIR, "postfix.pem")]
|
||||
)
|
||||
host.run_command(["rm", "-f", SMTPD_KEY, SMTPD_CERT])
|
||||
host.run_command(["ipa-getcert", "request",
|
||||
"-f",
|
||||
os.path.join(paths.OPENSSL_CERTS_DIR, "postfix.pem"),
|
||||
"-k",
|
||||
os.path.join(paths.OPENSSL_PRIVATE_DIR, "postfix.key"),
|
||||
"-f", SMTPD_CERT,
|
||||
"-k", SMTPD_KEY,
|
||||
"-K", "smtp/%s" % host.hostname,
|
||||
"-D", host.hostname,
|
||||
"-O", "postfix",
|
||||
@ -167,18 +174,8 @@ def configure_starttls(host):
|
||||
])
|
||||
postconf(host, 'smtpd_tls_loglevel = 1')
|
||||
postconf(host, 'smtpd_tls_auth_only = yes')
|
||||
postconf(
|
||||
host,
|
||||
"smtpd_tls_key_file = {}".format(
|
||||
os.path.join(paths.OPENSSL_PRIVATE_DIR, "postfix.key")
|
||||
)
|
||||
)
|
||||
postconf(
|
||||
host,
|
||||
"smtpd_tls_cert_file = {}".format(
|
||||
os.path.join(paths.OPENSSL_CERTS_DIR, "postfix.pem")
|
||||
)
|
||||
)
|
||||
postconf(host, "smtpd_tls_key_file = {}".format(SMTPD_KEY))
|
||||
postconf(host, "smtpd_tls_cert_file = {}".format(SMTPD_CERT))
|
||||
postconf(host, 'smtpd_tls_received_header = yes')
|
||||
postconf(host, 'smtpd_tls_session_cache_timeout = 3600s')
|
||||
# announce STARTTLS support to remote SMTP clients, not require
|
||||
@ -187,6 +184,33 @@ def configure_starttls(host):
|
||||
host.run_command(["systemctl", "restart", "postfix"])
|
||||
|
||||
|
||||
def configure_ssl_client_cert(host):
|
||||
"""Obtain a TLS cert for the SMTP client and configure postfix for client
|
||||
certificate verification.
|
||||
|
||||
Depends on configure_starttls().
|
||||
"""
|
||||
host.run_command(["rm", "-f", SMTP_CLIENT_KEY, SMTP_CLIENT_CERT])
|
||||
|
||||
host.run_command(["ipa-getcert", "request",
|
||||
"-f", SMTP_CLIENT_CERT,
|
||||
"-k", SMTP_CLIENT_KEY,
|
||||
"-K", "smtp_client/%s" % host.hostname,
|
||||
"-D", host.hostname,
|
||||
"-P", "Secret123",
|
||||
"-w",
|
||||
])
|
||||
|
||||
# mandatory TLS encryption
|
||||
postconf(host, "smtpd_tls_security_level = encrypt")
|
||||
# require a trusted remote SMTP client certificate
|
||||
postconf(host, "smtpd_tls_req_ccert = yes")
|
||||
# CA certificates of root CAs trusted to sign remote SMTP client cert
|
||||
postconf(host, f"smtpd_tls_CAfile = {paths.IPA_CA_CRT}")
|
||||
|
||||
host.run_command(["systemctl", "restart", "postfix"])
|
||||
|
||||
|
||||
def configure_ssl(host):
|
||||
"""Enable the ssl listener on port 465.
|
||||
"""
|
||||
@ -303,26 +327,18 @@ class TestEPN(IntegrationTest):
|
||||
tasks.uninstall_packages(cls.clients[0], EPN_PKG)
|
||||
tasks.uninstall_packages(cls.clients[0], ["postfix"])
|
||||
cls.master.run_command(r'rm -f /etc/postfix/smtp.keytab')
|
||||
cls.master.run_command(
|
||||
[
|
||||
"getcert",
|
||||
"stop-tracking",
|
||||
"-f",
|
||||
os.path.join(paths.OPENSSL_CERTS_DIR, "postfix.pem"),
|
||||
]
|
||||
)
|
||||
|
||||
for cert in [SMTPD_CERT, SMTP_CLIENT_CERT]:
|
||||
cls.master.run_command(["getcert", "stop-tracking", "-f", cert])
|
||||
|
||||
cls.master.run_command(
|
||||
[
|
||||
"rm",
|
||||
"-f",
|
||||
os.path.join(paths.OPENSSL_PRIVATE_DIR, "postfix.key"),
|
||||
]
|
||||
)
|
||||
cls.master.run_command(
|
||||
[
|
||||
"rm",
|
||||
"-f",
|
||||
os.path.join(paths.OPENSSL_CERTS_DIR, "postfix.pem"),
|
||||
SMTPD_CERT,
|
||||
SMTPD_KEY,
|
||||
SMTP_CLIENT_CERT,
|
||||
SMTP_CLIENT_KEY,
|
||||
]
|
||||
)
|
||||
|
||||
@ -342,7 +358,7 @@ class TestEPN(IntegrationTest):
|
||||
assert epn_conf in cmd1.stdout_text
|
||||
assert epn_template in cmd1.stdout_text
|
||||
cmd2 = self.master.run_command(["sha256sum", epn_conf])
|
||||
ck = "192481b52fb591112afd7b55b12a44c6618fdbc7e05a3b1866fd67ec579c51df"
|
||||
ck = "9977d846539d4945900bd04bae25bf746ac75fb561d3769014002db04e1790b8"
|
||||
assert cmd2.stdout_text.find(ck) == 0
|
||||
|
||||
def test_EPN_connection_refused(self):
|
||||
@ -433,8 +449,10 @@ class TestEPN(IntegrationTest):
|
||||
@pytest.fixture
|
||||
def cleanupmail(self):
|
||||
"""Cleanup any existing mail that has been sent."""
|
||||
cmd = ["rm", "-f"]
|
||||
for i in range(30):
|
||||
self.master.run_command(["rm", "-f", "/var/mail/user%d" % i])
|
||||
cmd.append("/var/mail/user%d" % i)
|
||||
self.master.run_command(cmd)
|
||||
|
||||
def test_EPN_smoketest_2(self, cleanupusers):
|
||||
"""Add a user without password.
|
||||
@ -718,6 +736,49 @@ class TestEPN(IntegrationTest):
|
||||
validate_mail(self.master, i,
|
||||
"Hi test user,\nYour login entry user%d is going" % i)
|
||||
|
||||
def test_EPN_ssl_client_cert(self, cleanupmail):
|
||||
"""Configure with ssl + client certificate and test delivery
|
||||
"""
|
||||
epn_conf = (SSL_EPN_CONF + CLIENT_CERT_EPN_CONF).format(
|
||||
server=self.master.hostname,
|
||||
user=self.master.config.admin_name,
|
||||
password=self.master.config.admin_password,
|
||||
client_cert=SMTP_CLIENT_CERT,
|
||||
client_key=SMTP_CLIENT_KEY,
|
||||
client_key_pass=SMTP_CLIENT_KEY_PASS,
|
||||
)
|
||||
self.master.put_file_contents('/etc/ipa/epn.conf', epn_conf)
|
||||
configure_ssl_client_cert(self.master)
|
||||
|
||||
tasks.ipa_epn(self.master)
|
||||
for i in self.notify_ttls:
|
||||
validate_mail(
|
||||
self.master,
|
||||
i,
|
||||
"Hi test user,\nYour login entry user%d is going" % i
|
||||
)
|
||||
|
||||
def test_EPN_starttls_client_cert(self, cleanupmail):
|
||||
"""Configure with starttls + client certificate and test delivery
|
||||
"""
|
||||
epn_conf = (STARTTLS_EPN_CONF + CLIENT_CERT_EPN_CONF).format(
|
||||
server=self.master.hostname,
|
||||
user=self.master.config.admin_name,
|
||||
password=self.master.config.admin_password,
|
||||
client_cert=SMTP_CLIENT_CERT,
|
||||
client_key=SMTP_CLIENT_KEY,
|
||||
client_key_pass=SMTP_CLIENT_KEY_PASS,
|
||||
)
|
||||
self.master.put_file_contents('/etc/ipa/epn.conf', epn_conf)
|
||||
|
||||
tasks.ipa_epn(self.master)
|
||||
for i in self.notify_ttls:
|
||||
validate_mail(
|
||||
self.master,
|
||||
i,
|
||||
"Hi test user,\nYour login entry user%d is going" % i
|
||||
)
|
||||
|
||||
def test_EPN_delay_config(self, cleanupmail):
|
||||
"""Test the smtp_delay configuration option
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user