mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-01-26 16:16:31 -06:00
Don't hard-code client's TLS versions and ciphers
Client connections no longer override TLS version range and ciphers by default. Instead clients use the default settings from the system's crypto policy. Minimum TLS version is now TLS 1.2. The default crypto policy on RHEL 8 sets TLS 1.2 as minimum version, while Fedora 31 sets TLS 1.0 as minimum version. The minimum version is configured with OpenSSL 1.1.1 APIs. Python 3.6 lacks the setters to override the system policy. The effective minimum version is always TLS 1.2, because FreeIPA reconfigures Apache HTTPd on Fedora. Fixes: https://pagure.io/freeipa/issue/8125 Signed-off-by: Christian Heimes <cheimes@redhat.com> Reviewed-By: Rob Crittenden <rcritten@redhat.com> Reviewed-By: Thomas Woerner <twoerner@redhat.com>
This commit is contained in:
parent
0198eca795
commit
639bb71940
@ -633,19 +633,29 @@ class Env:
|
||||
# set the best known TLS version if min/max versions are not set
|
||||
if 'tls_version_min' not in self:
|
||||
self.tls_version_min = TLS_VERSION_DEFAULT_MIN
|
||||
elif self.tls_version_min not in TLS_VERSIONS:
|
||||
if (
|
||||
self.tls_version_min is not None and
|
||||
self.tls_version_min not in TLS_VERSIONS
|
||||
):
|
||||
raise errors.EnvironmentError(
|
||||
"Unknown TLS version '{ver}' set in tls_version_min."
|
||||
.format(ver=self.tls_version_min))
|
||||
|
||||
if 'tls_version_max' not in self:
|
||||
self.tls_version_max = TLS_VERSION_DEFAULT_MAX
|
||||
elif self.tls_version_max not in TLS_VERSIONS:
|
||||
if (
|
||||
self.tls_version_max is not None and
|
||||
self.tls_version_max not in TLS_VERSIONS
|
||||
):
|
||||
raise errors.EnvironmentError(
|
||||
"Unknown TLS version '{ver}' set in tls_version_max."
|
||||
.format(ver=self.tls_version_max))
|
||||
|
||||
if self.tls_version_max < self.tls_version_min:
|
||||
if (
|
||||
self.tls_version_min is not None and
|
||||
self.tls_version_max is not None and
|
||||
self.tls_version_max < self.tls_version_min
|
||||
):
|
||||
raise errors.EnvironmentError(
|
||||
"tls_version_min is set to a higher TLS version than "
|
||||
"tls_version_max.")
|
||||
|
@ -38,8 +38,9 @@ except Exception:
|
||||
# TLS related constants
|
||||
# * SSL2 and SSL3 are broken.
|
||||
# * TLS1.0 and TLS1.1 are no longer state of the art.
|
||||
# * TLS1.3 support is not yet stable, e.g. issues with PHA.
|
||||
# Therefore only TLS 1.2 is enabled by default.
|
||||
# * TLS1.2 and 1.3 are secure and working properly
|
||||
# * Crypto policies restrict TLS range to 1.2 and 1.3. Python 3.6 cannot
|
||||
# override the crypto policy.
|
||||
|
||||
TLS_VERSIONS = [
|
||||
"ssl2",
|
||||
@ -49,9 +50,10 @@ TLS_VERSIONS = [
|
||||
"tls1.2",
|
||||
"tls1.3",
|
||||
]
|
||||
TLS_VERSION_MINIMAL = "tls1.0"
|
||||
TLS_VERSION_DEFAULT_MIN = "tls1.2"
|
||||
TLS_VERSION_DEFAULT_MAX = "tls1.3"
|
||||
TLS_VERSION_MINIMAL = "tls1.2"
|
||||
TLS_VERSION_MAXIMAL = "tls1.3"
|
||||
TLS_VERSION_DEFAULT_MIN = None
|
||||
TLS_VERSION_DEFAULT_MAX = None
|
||||
|
||||
# regular expression NameSpace member names must match:
|
||||
NAME_REGEX = r'^[a-z][_a-z0-9]*[a-z0-9]$|^[a-z]$'
|
||||
|
@ -57,8 +57,8 @@ except ImportError:
|
||||
from ipalib import errors, messages
|
||||
from ipalib.constants import (
|
||||
DOMAIN_LEVEL_0,
|
||||
TLS_VERSIONS, TLS_VERSION_MINIMAL, TLS_VERSION_DEFAULT_MIN,
|
||||
TLS_VERSION_DEFAULT_MAX,
|
||||
TLS_VERSIONS, TLS_VERSION_MINIMAL, TLS_VERSION_MAXIMAL,
|
||||
TLS_VERSION_DEFAULT_MIN, TLS_VERSION_DEFAULT_MAX,
|
||||
)
|
||||
from ipalib.text import _
|
||||
from ipaplatform.constants import constants
|
||||
@ -248,6 +248,13 @@ def get_proper_tls_version_span(tls_version_min, tls_version_max):
|
||||
if lower than TLS_VERSION_MINIMAL
|
||||
:raises: ValueError
|
||||
"""
|
||||
if tls_version_min is None and tls_version_max is None:
|
||||
# no defaults, use system's default TLS version range
|
||||
return None
|
||||
if tls_version_min is None:
|
||||
tls_version_min = TLS_VERSION_MINIMAL
|
||||
if tls_version_max is None:
|
||||
tls_version_max = TLS_VERSION_MAXIMAL
|
||||
min_allowed_idx = TLS_VERSIONS.index(TLS_VERSION_MINIMAL)
|
||||
|
||||
try:
|
||||
@ -326,9 +333,6 @@ def create_https_connection(
|
||||
raise RuntimeError("cafile \'{file}\' doesn't exist or is unreadable".
|
||||
format(file=cafile))
|
||||
|
||||
# remove the slice of negating protocol options according to options
|
||||
tls_span = get_proper_tls_version_span(tls_version_min, tls_version_max)
|
||||
|
||||
# official Python documentation states that the best option to get
|
||||
# TLSv1 and later is to setup SSLContext with PROTOCOL_SSLv23
|
||||
# and then negate the insecure SSLv2 and SSLv3
|
||||
@ -343,16 +347,20 @@ def create_https_connection(
|
||||
# configure ciphers, uses system crypto policies on RH platforms.
|
||||
ctx.set_ciphers(constants.TLS_HIGH_CIPHERS)
|
||||
|
||||
# remove the slice of negating protocol options according to options
|
||||
tls_span = get_proper_tls_version_span(tls_version_min, tls_version_max)
|
||||
|
||||
# pylint: enable=no-member
|
||||
# set up the correct TLS version flags for the SSL context
|
||||
for version in TLS_VERSIONS:
|
||||
if version in tls_span:
|
||||
# make sure the required TLS versions are available if Python
|
||||
# decides to modify the default TLS flags
|
||||
ctx.options &= ~tls_cutoff_map[version]
|
||||
else:
|
||||
# disable all TLS versions not in tls_span
|
||||
ctx.options |= tls_cutoff_map[version]
|
||||
if tls_span is not None:
|
||||
for version in TLS_VERSIONS:
|
||||
if version in tls_span:
|
||||
# make sure the required TLS versions are available if Python
|
||||
# decides to modify the default TLS flags
|
||||
ctx.options &= ~tls_cutoff_map[version]
|
||||
else:
|
||||
# disable all TLS versions not in tls_span
|
||||
ctx.options |= tls_cutoff_map[version]
|
||||
|
||||
# Enable TLS 1.3 post-handshake auth
|
||||
if getattr(ctx, "post_handshake_auth", None) is not None:
|
||||
|
@ -14,10 +14,9 @@ from ipaplatform.base.constants import BaseConstantsNamespace
|
||||
|
||||
|
||||
class RedHatConstantsNamespace(BaseConstantsNamespace):
|
||||
# System-wide crypto policy, but without TripleDES, pre-shared key,
|
||||
# secure remote password, and DSA cert authentication.
|
||||
# Use system-wide crypto policy
|
||||
# see https://fedoraproject.org/wiki/Changes/CryptoPolicy
|
||||
TLS_HIGH_CIPHERS = "PROFILE=SYSTEM:!3DES:!PSK:!SRP:!aDSS"
|
||||
TLS_HIGH_CIPHERS = None
|
||||
|
||||
|
||||
constants = RedHatConstantsNamespace()
|
||||
|
@ -683,7 +683,7 @@ class TestIPACommand(IntegrationTest):
|
||||
assert 'Last name: %s' % (modlast) in cmd.stdout_text
|
||||
|
||||
def test_enabled_tls_protocols(self):
|
||||
"""Check that only TLS 1.2 is enabled in Apache.
|
||||
"""Check Apache has same TLS versions enabled as crypto policy
|
||||
|
||||
This is the regression test for issue
|
||||
https://pagure.io/freeipa/issue/7995.
|
||||
@ -698,9 +698,24 @@ class TestIPACommand(IntegrationTest):
|
||||
)
|
||||
return res.returncode == 0
|
||||
|
||||
# get minimum version from current crypto-policy
|
||||
openssl_cnf = self.master.get_file_contents(
|
||||
"/etc/crypto-policies/back-ends/opensslcnf.config",
|
||||
encoding="utf-8"
|
||||
)
|
||||
mo = re.search(r"MinProtocol\s*=\s*(TLSv[0-9.]+)", openssl_cnf)
|
||||
assert mo
|
||||
min_tls = mo.group(1)
|
||||
# Fedora DEFAULT has TLS 1.0 enabled, NEXT has TLS 1.2
|
||||
# even FUTURE crypto policy has TLS 1.2 as minimum version
|
||||
assert min_tls in {"TLSv1", "TLSv1.2"}
|
||||
|
||||
# On Fedora FreeIPA still disables TLS 1.0 and 1.1 in ssl.conf.
|
||||
|
||||
assert not is_tls_version_enabled('tls1')
|
||||
assert not is_tls_version_enabled('tls1_1')
|
||||
assert is_tls_version_enabled('tls1_2')
|
||||
assert is_tls_version_enabled('tls1_3')
|
||||
|
||||
def test_samba_config_file(self):
|
||||
"""Check that ipa-adtrust-install generates sane smb.conf
|
||||
|
@ -5,11 +5,15 @@
|
||||
"""
|
||||
|
||||
import os
|
||||
import ssl
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from ipalib.util import get_pager
|
||||
from ipalib.util import (
|
||||
get_pager, create_https_connection, get_proper_tls_version_span
|
||||
)
|
||||
from ipaplatform.constants import constants
|
||||
|
||||
|
||||
@pytest.mark.parametrize('pager,expected_result', [
|
||||
@ -24,3 +28,47 @@ def test_get_pager(pager, expected_result):
|
||||
with mock.patch.dict(os.environ, {'PAGER': pager}):
|
||||
pager = get_pager()
|
||||
assert(pager == expected_result or pager.endswith(expected_result))
|
||||
|
||||
|
||||
BASE_CTX = ssl.SSLContext(ssl.PROTOCOL_TLS)
|
||||
if constants.TLS_HIGH_CIPHERS is not None:
|
||||
BASE_CTX.set_ciphers(constants.TLS_HIGH_CIPHERS)
|
||||
else:
|
||||
BASE_CTX.set_ciphers("PROFILE=SYSTEM")
|
||||
|
||||
# options: IPA still supports Python 3.6 without min/max version setters
|
||||
BASE_OPT = BASE_CTX.options
|
||||
BASE_OPT |= (
|
||||
ssl.OP_ALL | ssl.OP_NO_COMPRESSION | ssl.OP_SINGLE_DH_USE |
|
||||
ssl.OP_SINGLE_ECDH_USE
|
||||
)
|
||||
TLS_OPT = (
|
||||
ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 |
|
||||
ssl.OP_NO_TLSv1_1
|
||||
)
|
||||
OP_NO_TLSv1_3 = getattr(ssl, "OP_NO_TLSv1_3", 0) # make pylint happy
|
||||
|
||||
|
||||
@pytest.mark.parametrize('minver,maxver,opt,expected', [
|
||||
(None, None, BASE_OPT, None),
|
||||
(None, "tls1.3", BASE_OPT | TLS_OPT, ["tls1.2", "tls1.3"]),
|
||||
("tls1.2", "tls1.3", BASE_OPT | TLS_OPT, ["tls1.2", "tls1.3"]),
|
||||
("tls1.2", None, BASE_OPT | TLS_OPT, ["tls1.2", "tls1.3"]),
|
||||
("tls1.2", "tls1.2", BASE_OPT | TLS_OPT | OP_NO_TLSv1_3, ["tls1.2"]),
|
||||
(None, "tls1.2", BASE_OPT | TLS_OPT | OP_NO_TLSv1_3, ["tls1.2"]),
|
||||
("tls1.3", "tls1.3", BASE_OPT | TLS_OPT | ssl.OP_NO_TLSv1_2, ["tls1.3"]),
|
||||
("tls1.3", None, BASE_OPT | TLS_OPT | ssl.OP_NO_TLSv1_2, ["tls1.3"]),
|
||||
])
|
||||
def test_tls_version_span(minver, maxver, opt, expected):
|
||||
assert get_proper_tls_version_span(minver, maxver) == expected
|
||||
# file must exist and contain certs
|
||||
cafile = ssl.get_default_verify_paths().cafile
|
||||
conn = create_https_connection(
|
||||
"invalid.test",
|
||||
cafile=cafile,
|
||||
tls_version_min=minver,
|
||||
tls_version_max=maxver
|
||||
)
|
||||
ctx = getattr(conn, "_context")
|
||||
assert ctx.options == BASE_OPT | opt
|
||||
assert ctx.get_ciphers() == BASE_CTX.get_ciphers()
|
||||
|
Loading…
Reference in New Issue
Block a user