Secure AJP connector between Dogtag and Apache proxy

AJP implementation in Tomcat is vulnerable to CVE-2020-1938 if used
without shared secret. Set up a shared secret between localhost
connector and Apache mod_proxy_ajp pass-through.

For existing secured AJP pass-through make sure the option used for
configuration on the tomcat side is up to date. Tomcat 9.0.31.0
deprecated 'requiredSecret' option name in favor of 'secret'. Details
can be found at https://tomcat.apache.org/migration-9.html#Upgrading_9.0.x

Fixes: https://pagure.io/freeipa/issue/8221

Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Christian Heimes <cheimes@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
This commit is contained in:
Alexander Bokovoy 2020-03-10 23:00:55 +02:00 committed by Florence Blanc-Renaud
parent 593fac1ca9
commit ec73de969f
7 changed files with 99 additions and 8 deletions

View File

@ -1,4 +1,4 @@
# VERSION 13 - DO NOT REMOVE THIS LINE
# VERSION 14 - DO NOT REMOVE THIS LINE
ProxyRequests Off
@ -6,7 +6,7 @@ ProxyRequests Off
<LocationMatch "^/ca/ee/ca/checkRequest|^/ca/ee/ca/getCertChain|^/ca/ee/ca/getTokenInfo|^/ca/ee/ca/tokenAuthenticate|^/ca/ocsp|^/ca/ee/ca/updateNumberRange|^/ca/ee/ca/getCRL|^/ca/ee/ca/profileSubmit">
SSLOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
SSLVerifyClient none
ProxyPassMatch ajp://localhost:$DOGTAG_PORT
ProxyPassMatch ajp://localhost:$DOGTAG_PORT $DOGTAG_AJP_SECRET
ProxyPassReverse ajp://localhost:$DOGTAG_PORT
</LocationMatch>
@ -14,7 +14,7 @@ ProxyRequests Off
<LocationMatch "^/ca/admin/ca/getCertChain|^/ca/admin/ca/getConfigEntries|^/ca/admin/ca/getCookie|^/ca/admin/ca/getStatus|^/ca/admin/ca/securityDomainLogin|^/ca/admin/ca/getDomainXML|^/ca/admin/ca/updateNumberRange|^/ca/admin/ca/tokenAuthenticate|^/ca/admin/ca/updateNumberRange|^/ca/admin/ca/updateDomainXML|^/ca/admin/ca/updateConnector|^/ca/admin/ca/getSubsystemCert|^/kra/admin/kra/updateNumberRange|^/kra/admin/kra/getConfigEntries">
SSLOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
SSLVerifyClient none
ProxyPassMatch ajp://localhost:$DOGTAG_PORT
ProxyPassMatch ajp://localhost:$DOGTAG_PORT $DOGTAG_AJP_SECRET
ProxyPassReverse ajp://localhost:$DOGTAG_PORT
</LocationMatch>
@ -22,7 +22,7 @@ ProxyRequests Off
<LocationMatch "^/ca/agent/ca/displayBySerial|^/ca/agent/ca/doRevoke|^/ca/agent/ca/doUnrevoke|^/ca/agent/ca/updateDomainXML|^/ca/eeca/ca/profileSubmitSSLClient|^/kra/agent/kra/connector">
SSLOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
SSLVerifyClient require
ProxyPassMatch ajp://localhost:$DOGTAG_PORT
ProxyPassMatch ajp://localhost:$DOGTAG_PORT $DOGTAG_AJP_SECRET
ProxyPassReverse ajp://localhost:$DOGTAG_PORT
</LocationMatch>
@ -30,7 +30,7 @@ ProxyRequests Off
<LocationMatch "^/ca/rest/account/login|^/ca/rest/account/logout|^/ca/rest/installer/installToken|^/ca/rest/securityDomain/domainInfo|^/ca/rest/securityDomain/installToken|^/ca/rest/profiles|^/ca/rest/authorities|^/ca/rest/certrequests|^/ca/rest/admin/kraconnector/remove|^/ca/rest/certs/search">
SSLOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
SSLVerifyClient optional
ProxyPassMatch ajp://localhost:$DOGTAG_PORT
ProxyPassMatch ajp://localhost:$DOGTAG_PORT $DOGTAG_AJP_SECRET
ProxyPassReverse ajp://localhost:$DOGTAG_PORT
</LocationMatch>
@ -38,7 +38,7 @@ ProxyRequests Off
<LocationMatch "^/kra/rest/config/cert/transport|^/kra/rest/account|^/kra/rest/agent/keyrequests|^/kra/rest/agent/keys">
SSLOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
SSLVerifyClient optional
ProxyPassMatch ajp://localhost:$DOGTAG_PORT
ProxyPassMatch ajp://localhost:$DOGTAG_PORT $DOGTAG_AJP_SECRET
ProxyPassReverse ajp://localhost:$DOGTAG_PORT
</LocationMatch>

View File

@ -108,6 +108,7 @@ class BasePathNamespace:
PKI_TOMCAT_ALIAS_DIR = "/etc/pki/pki-tomcat/alias"
PKI_TOMCAT_ALIAS_PWDFILE_TXT = "/etc/pki/pki-tomcat/alias/pwdfile.txt"
PKI_TOMCAT_PASSWORD_CONF = "/etc/pki/pki-tomcat/password.conf"
PKI_TOMCAT_SERVER_XML = "/etc/pki/pki-tomcat/server.xml"
ETC_REDHAT_RELEASE = "/etc/redhat-release"
RESOLV_CONF = "/etc/resolv.conf"
SAMBA_KEYTAB = "/etc/samba/samba.keytab"
@ -339,6 +340,7 @@ class BasePathNamespace:
KRB5KDC_LOG = "/var/log/krb5kdc.log"
MESSAGES = "/var/log/messages"
VAR_LOG_PKI_DIR = "/var/log/pki/"
BIN_TOMCAT = "/usr/sbin/tomcat"
TOMCAT_TOPLEVEL_DIR = "/var/log/pki/pki-tomcat"
TOMCAT_CA_DIR = "/var/log/pki/pki-tomcat/ca"
TOMCAT_CA_ARCHIVE_DIR = "/var/log/pki/pki-tomcat/ca/archive"

View File

@ -59,6 +59,7 @@ class DebianPathNamespace(BasePathNamespace):
SYSCONFIG_PKI = "/etc/dogtag/"
SYSCONFIG_PKI_TOMCAT = "/etc/default/pki-tomcat"
SYSCONFIG_PKI_TOMCAT_PKI_TOMCAT_DIR = "/etc/dogtag/tomcat/pki-tomcat"
BIN_TOMCAT = "/usr/share/tomcat9/bin/version.sh"
SYSTEMD_SYSTEM_HTTPD_D_DIR = "/etc/systemd/system/apache2.service.d/"
SYSTEMD_SYSTEM_HTTPD_IPA_CONF = "/etc/systemd/system/apache2.service.d/ipa.conf"
DNSSEC_TRUSTED_KEY = "/etc/bind/trusted-key.key"

View File

@ -401,6 +401,7 @@ class CAInstance(DogtagInstance):
self.step("configuring certificate server instance",
self.__spawn_instance)
self.step("Add ipa-pki-wait-running", self.add_ipa_wait)
self.step("secure AJP connector", self.secure_ajp_connector)
self.step("reindex attributes", self.reindex_task)
self.step("exporting Dogtag certificate store pin",
self.create_certstore_passwdfile)
@ -452,6 +453,7 @@ class CAInstance(DogtagInstance):
self.step("configure certificate renewals", self.configure_renewal)
self.step("Configure HTTP to proxy connections",
self.http_proxy)
# This restart is needed for ACL reload in CA, do not remove it
self.step("restarting certificate server", self.restart_instance)
self.step("updating IPA configuration", update_ipa_conf)
self.step("enabling CA instance", self.__enable_instance)

View File

@ -28,6 +28,9 @@ import os
import shutil
import traceback
import dbus
import re
import pwd
import lxml.etree
from configparser import DEFAULTSECT, ConfigParser, RawConfigParser
@ -42,6 +45,7 @@ from ipalib.constants import CA_DBUS_TIMEOUT, IPA_CA_RECORD, RENEWAL_CA_NAME
from ipaplatform import services
from ipaplatform.constants import constants
from ipaplatform.paths import paths
from ipaplatform.tasks import tasks
from ipapython import directivesetter
from ipapython import ipaldap
from ipapython import ipautil
@ -158,6 +162,7 @@ class DogtagInstance(service.Service):
self.pki_config_override = None
self.ca_subject = None
self.subject_base = None
self.ajp_secret = None
def is_installed(self):
"""
@ -275,6 +280,72 @@ class DogtagInstance(service.Service):
logger.critical("failed to uninstall %s instance %s",
self.subsystem, e)
def __is_newer_tomcat_version(self, default=None):
try:
result = ipautil.run([paths.BIN_TOMCAT, "version"],
capture_output=True)
sn = re.search(
r'Server number:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)',
result.output)
if sn is None:
logger.info("tomcat version cannot be parsed, "
"default to pre-%s", default)
return False
v = tasks.parse_ipa_version(sn.group(1))
if v >= tasks.parse_ipa_version(default):
return True
except ipautil.CalledProcessError as e:
logger.info(
"failed to discover tomcat version, "
"default to pre-%s, error: %s",
default, str(e))
return False
def secure_ajp_connector(self):
""" Update AJP connector to use a password protection """
server_xml = lxml.etree.parse(paths.PKI_TOMCAT_SERVER_XML)
doc = server_xml.getroot()
# no AJP connector means no need to update anything
connectors = doc.xpath('//Connector[@port="8009"]')
if len(connectors) == 0:
return
# AJP connector is set on port 8009. Use non-greedy search to find it
connector = connectors[0]
# Detect tomcat version and choose the right option name
# pre-9.0.31.0 uses 'requiredSecret'
# 9.0.31.0 or later uses 'secret'
secretattr = 'requiredSecret'
oldattr = 'requiredSecret'
if self.__is_newer_tomcat_version('9.0.31.0'):
secretattr = 'secret'
rewrite = True
if secretattr in connector.attrib:
# secret is already in place
# Perhaps, we need to synchronize it with Apache configuration
self.ajp_secret = connector.attrib[secretattr]
rewrite = False
else:
if oldattr in connector.attrib:
self.ajp_secret = connector.attrib[oldattr]
connector.attrib[secretattr] = self.ajp_secret
del connector.attrib[oldattr]
else:
# Generate password, don't use special chars to not break XML
self.ajp_secret = ipautil.ipa_generate_password(special=None)
connector.attrib[secretattr] = self.ajp_secret
if rewrite:
pent = pwd.getpwnam(constants.PKI_USER)
with open(paths.PKI_TOMCAT_SERVER_XML, "wb") as fd:
server_xml.write(fd, pretty_print=True, encoding="utf-8")
os.fchmod(fd.fileno(), 0o660)
os.fchown(fd.fileno(), pent.pw_uid, pent.pw_gid)
def http_proxy(self):
""" Update the http proxy file """
template_filename = (
@ -284,11 +355,20 @@ class DogtagInstance(service.Service):
DOGTAG_PORT=8009,
CLONE='' if self.clone else '#',
FQDN=self.fqdn,
DOGTAG_AJP_SECRET='',
)
if self.ajp_secret:
sub_dict['DOGTAG_AJP_SECRET'] = "secret={}".format(self.ajp_secret)
template = ipautil.template_file(template_filename, sub_dict)
with open(paths.HTTPD_IPA_PKI_PROXY_CONF, "w") as fd:
fd.write(template)
os.fchmod(fd.fileno(), 0o640)
# Restart httpd
http_service = services.knownservices.httpd
logger.debug("Restarting %s to apply AJP changes",
http_service.service_name)
http_service.restart()
logger.debug("%s successfully restarted", http_service.service_name)
def configure_certmonger_renewal_helpers(self):
"""

View File

@ -133,8 +133,6 @@ class KRAInstance(DogtagInstance):
self.step("configure certmonger for renewals",
self.configure_certmonger_renewal_helpers)
self.step("configure certificate renewals", self.configure_renewal)
self.step("configure HTTP to proxy connections",
self.http_proxy)
if not self.clone:
self.step("add vault container", self.__add_vault_container)
self.step("apply LDAP updates", self.__apply_updates)

View File

@ -1966,6 +1966,14 @@ def upgrade_configuration():
os.path.join(paths.USR_SHARE_IPA_DIR,
"ipa-kdc-proxy.conf.template"))
if ca.is_configured():
# Handle upgrade of AJP connector configuration
ca.secure_ajp_connector()
if ca.ajp_secret:
sub_dict['DOGTAG_AJP_SECRET'] = "secret={}".format(
ca.ajp_secret)
else:
sub_dict['DOGTAG_AJP_SECRET'] = ''
upgrade_file(
sub_dict,
paths.HTTPD_IPA_PKI_PROXY_CONF,