EPN: Enable certificate validation and hostname checking

https://pagure.io/freeipa/issue/8579
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:
Stanislav Levin 2020-11-12 18:52:51 +03:00 committed by Alexander Bokovoy
parent 977063a56e
commit 32aa1540f0
2 changed files with 38 additions and 10 deletions

View File

@ -29,6 +29,7 @@ import os
import pwd import pwd
import logging import logging
import smtplib import smtplib
import ssl
import time import time
from collections import deque from collections import deque
@ -205,6 +206,7 @@ class EPN(admintool.AdminTool):
def __init__(self, options, args): def __init__(self, options, args):
super(EPN, self).__init__(options, args) super(EPN, self).__init__(options, args)
self._conn = None self._conn = None
self._ssl_context = None
self._expiring_password_user_list = EPNUserList() self._expiring_password_user_list = EPNUserList()
self._ldap_data = [] self._ldap_data = []
self._date_ranges = [] self._date_ranges = []
@ -291,12 +293,15 @@ class EPN(admintool.AdminTool):
logger.error("IPA client is not configured on this system.") logger.error("IPA client is not configured on this system.")
raise admintool.ScriptError() raise admintool.ScriptError()
# tasks required privileges
self._get_krb5_ticket() self._get_krb5_ticket()
self._read_configuration() self._read_configuration()
self._validate_configuration() self._validate_configuration()
self._parse_configuration() self._parse_configuration()
self._get_connection() self._get_connection()
self._read_ipa_configuration() self._read_ipa_configuration()
self._create_ssl_context()
drop_privileges() drop_privileges()
if self.options.mailtest: if self.options.mailtest:
self._gentestdata() self._gentestdata()
@ -316,6 +321,7 @@ class EPN(admintool.AdminTool):
smtp_timeout=api.env.smtp_timeout, smtp_timeout=api.env.smtp_timeout,
smtp_username=api.env.smtp_user, smtp_username=api.env.smtp_user,
smtp_password=api.env.smtp_password, smtp_password=api.env.smtp_password,
ssl_context=self._ssl_context,
x_mailer=self.command_name, x_mailer=self.command_name,
msg_subtype=api.env.msg_subtype, msg_subtype=api.env.msg_subtype,
msg_charset=api.env.msg_charset, msg_charset=api.env.msg_charset,
@ -457,6 +463,14 @@ class EPN(admintool.AdminTool):
return self._conn return self._conn
def _create_ssl_context(self):
"""Create SSL context.
This must be done before the dropping priviliges to allow
read in the smtp client's certificate and private key if specified.
"""
if api.env.smtp_security.lower() in ("starttls", "ssl"):
self._ssl_context = ssl.create_default_context()
def _fetch_data_from_ldap(self, date_range): def _fetch_data_from_ldap(self, date_range):
"""Run a LDAP query to fetch a list of user entries whose passwords """Run a LDAP query to fetch a list of user entries whose passwords
would expire in the near future. Store in self._ldap_data. would expire in the near future. Store in self._ldap_data.
@ -603,15 +617,15 @@ class MTAClient:
smtp_timeout=60, smtp_timeout=60,
smtp_username=None, smtp_username=None,
smtp_password=None, smtp_password=None,
ssl_context=None,
): ):
# We only support "none" (cleartext) for now.
# Future values: "ssl", "starttls"
self._security_protocol = security_protocol self._security_protocol = security_protocol
self._smtp_hostname = smtp_hostname self._smtp_hostname = smtp_hostname
self._smtp_port = smtp_port self._smtp_port = smtp_port
self._smtp_timeout = smtp_timeout self._smtp_timeout = smtp_timeout
self._username = smtp_username self._username = smtp_username
self._password = smtp_password self._password = smtp_password
self._ssl_context = ssl_context
# This should not be touched # This should not be touched
self._conn = None self._conn = None
@ -664,6 +678,7 @@ class MTAClient:
host=self._smtp_hostname, host=self._smtp_hostname,
port=self._smtp_port, port=self._smtp_port,
timeout=self._smtp_timeout, timeout=self._smtp_timeout,
context=self._ssl_context,
) )
except (socketerror, smtplib.SMTPException) as e: except (socketerror, smtplib.SMTPException) as e:
msg = \ msg = \
@ -687,7 +702,7 @@ class MTAClient:
if self._security_protocol.lower() == "starttls": if self._security_protocol.lower() == "starttls":
try: try:
self._conn.starttls() self._conn.starttls(context=self._ssl_context)
self._conn.ehlo() self._conn.ehlo()
except smtplib.SMTPException as e: except smtplib.SMTPException as e:
raise RuntimeError( raise RuntimeError(
@ -743,6 +758,7 @@ class MailUserAgent:
smtp_timeout=60, smtp_timeout=60,
smtp_username=None, smtp_username=None,
smtp_password=None, smtp_password=None,
ssl_context=None,
x_mailer=None, x_mailer=None,
msg_subtype="plain", msg_subtype="plain",
msg_charset="utf8", msg_charset="utf8",
@ -766,6 +782,7 @@ class MailUserAgent:
smtp_timeout=smtp_timeout, smtp_timeout=smtp_timeout,
smtp_username=smtp_username, smtp_username=smtp_username,
smtp_password=smtp_password, smtp_password=smtp_password,
ssl_context=ssl_context,
) )
def cleanup(self): def cleanup(self):

View File

@ -37,6 +37,7 @@ from subprocess import CalledProcessError
from ipaplatform.paths import paths from ipaplatform.paths import paths
from ipatests.test_integration.base import IntegrationTest from ipatests.test_integration.base import IntegrationTest
from ipatests.pytest_ipa.integration.firewall import Firewall
from ipatests.pytest_ipa.integration import tasks from ipatests.pytest_ipa.integration import tasks
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -58,12 +59,14 @@ USER_EPN_CONF = DEFAULT_EPN_CONF + textwrap.dedent(
STARTTLS_EPN_CONF = USER_EPN_CONF + textwrap.dedent( STARTTLS_EPN_CONF = USER_EPN_CONF + textwrap.dedent(
"""\ """\
smtp_server={server}
smtp_security=starttls smtp_security=starttls
""" """
) )
SSL_EPN_CONF = USER_EPN_CONF + textwrap.dedent( SSL_EPN_CONF = USER_EPN_CONF + textwrap.dedent(
"""\ """\
smtp_server={server}
smtp_port=465 smtp_port=465
smtp_security=ssl smtp_security=ssl
""" """
@ -125,6 +128,9 @@ def configure_postfix(host, realm):
# disable procmail if exists, make use of default local(8) delivery agent # disable procmail if exists, make use of default local(8) delivery agent
postconf(host, "mailbox_command=") postconf(host, "mailbox_command=")
# listen on all active interfaces
postconf(host, "inet_interfaces = all")
host.run_command(["systemctl", "restart", "saslauthd"]) host.run_command(["systemctl", "restart", "saslauthd"])
result = host.run_command(["postconf", "mydestination"]) result = host.run_command(["postconf", "mydestination"])
@ -264,6 +270,7 @@ class TestEPN(IntegrationTest):
# doesn't know about. # doesn't know about.
# - Adds a class variable, pkg, containing the package name of # - Adds a class variable, pkg, containing the package name of
# the downloaded *ipa-client-epn rpm. # the downloaded *ipa-client-epn rpm.
hosts = [cls.master, cls.clients[0]]
tasks.uninstall_packages(cls.clients[0],EPN_PKG) tasks.uninstall_packages(cls.clients[0],EPN_PKG)
pkgdir = tasks.download_packages(cls.clients[0], EPN_PKG) pkgdir = tasks.download_packages(cls.clients[0], EPN_PKG)
pkg = cls.clients[0].run_command(r'ls -1 {}'.format(pkgdir)) pkg = cls.clients[0].run_command(r'ls -1 {}'.format(pkgdir))
@ -273,20 +280,20 @@ class TestEPN(IntegrationTest):
'/tmp']) '/tmp'])
cls.clients[0].run_command(r'rm -rf {}'.format(pkgdir)) cls.clients[0].run_command(r'rm -rf {}'.format(pkgdir))
tasks.install_packages(cls.master, EPN_PKG) for host in hosts:
tasks.install_packages(cls.master, ["postfix"]) tasks.install_packages(host, EPN_PKG + ["postfix"])
tasks.install_packages(cls.clients[0], EPN_PKG)
tasks.install_packages(cls.clients[0], ["postfix"])
for host in (cls.master, cls.clients[0]):
try: try:
tasks.install_packages(host, ["cyrus-sasl"]) tasks.install_packages(host, ["cyrus-sasl"])
except Exception: except Exception:
# the package is likely already installed # the package is likely already installed
pass pass
tasks.install_master(cls.master, setup_dns=True) tasks.install_master(cls.master, setup_dns=True)
tasks.install_client(cls.master, cls.clients[0]) tasks.install_client(cls.master, cls.clients[0])
configure_postfix(cls.master, cls.master.domain.realm) for host in hosts:
configure_postfix(cls.clients[0], cls.master.domain.realm) configure_postfix(host, cls.master.domain.realm)
Firewall(host).enable_services(["smtp", "smtps"])
@classmethod @classmethod
def uninstall(cls, mh): def uninstall(cls, mh):
@ -356,6 +363,7 @@ class TestEPN(IntegrationTest):
"""Configure postfix without starttls and test no auth happens """Configure postfix without starttls and test no auth happens
""" """
epn_conf = STARTTLS_EPN_CONF.format( epn_conf = STARTTLS_EPN_CONF.format(
server=self.master.hostname,
user=self.master.config.admin_name, user=self.master.config.admin_name,
password=self.master.config.admin_password, password=self.master.config.admin_password,
) )
@ -373,6 +381,7 @@ class TestEPN(IntegrationTest):
"""Configure postfix without tls and test no auth happens """Configure postfix without tls and test no auth happens
""" """
epn_conf = SSL_EPN_CONF.format( epn_conf = SSL_EPN_CONF.format(
server=self.master.hostname,
user=self.master.config.admin_name, user=self.master.config.admin_name,
password=self.master.config.admin_password, password=self.master.config.admin_password,
) )
@ -681,6 +690,7 @@ class TestEPN(IntegrationTest):
"""Configure with starttls and test delivery """Configure with starttls and test delivery
""" """
epn_conf = STARTTLS_EPN_CONF.format( epn_conf = STARTTLS_EPN_CONF.format(
server=self.master.hostname,
user=self.master.config.admin_name, user=self.master.config.admin_name,
password=self.master.config.admin_password, password=self.master.config.admin_password,
) )
@ -696,6 +706,7 @@ class TestEPN(IntegrationTest):
"""Configure with ssl and test delivery """Configure with ssl and test delivery
""" """
epn_conf = SSL_EPN_CONF.format( epn_conf = SSL_EPN_CONF.format(
server=self.master.hostname,
user=self.master.config.admin_name, user=self.master.config.admin_name,
password=self.master.config.admin_password, password=self.master.config.admin_password,
) )