Skip to content

Commit

Permalink
Move Custodia secrets handler to scripts
Browse files Browse the repository at this point in the history
Implement the import and export handlers for Custodia keys as external
scripts. It's a prerequisite to drop DAC override permission and proper
SELinux rules for ipa-custodia.

Except for DMLDAP,  handlers no longer run as root but as handler
specific users with reduced privileges. The Dogtag-related handlers run
as pkiuser, which also help with HSM support.

The export and import handles are designed to be executed by sudo, too.
In the future, ipa-custodia could be executed as an unprivileged process
that runs the minimal helper scripts with higher privileges.

Fixes: https://pagure.io/freeipa/issue/6888
Signed-off-by: Christian Heimes <[email protected]>
Reviewed-By: Alexander Bokovoy <[email protected]>
  • Loading branch information
tiran committed Apr 26, 2019
1 parent d2c5ce1 commit beffa7b
Show file tree
Hide file tree
Showing 18 changed files with 642 additions and 185 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ daemons/dnssec/ipa-dnskeysync-replica
daemons/dnssec/ipa-ods-exporter
install/certmonger/dogtag-ipa-ca-renew-agent-submit
install/certmonger/ipa-server-guard
install/custodia/ipa-custodia-dmldap
install/custodia/ipa-custodia-pki-tomcat
install/custodia/ipa-custodia-pki-tomcat-wrapped
install/custodia/ipa-custodia-ra-agent
install/oddjob/com.redhat.idm.trust-fetch-domains
install/oddjob/etc/oddjobd.conf.d/ipa-server.conf
install/oddjob/etc/oddjobd.conf.d/oddjobd-ipa-trust.conf
Expand Down
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ AC_CONFIG_FILES([
init/Makefile
install/Makefile
install/certmonger/Makefile
install/custodia/Makefile
install/html/Makefile
install/migration/Makefile
install/share/Makefile
Expand Down
4 changes: 4 additions & 0 deletions freeipa.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,10 @@ fi
%{_sbindir}/ipa-crlgen-manage
%{_libexecdir}/certmonger/dogtag-ipa-ca-renew-agent-submit
%{_libexecdir}/certmonger/ipa-server-guard
%{_libexecdir}/ipa/custodia/ipa-custodia-dmldap
%{_libexecdir}/ipa/custodia/ipa-custodia-pki-tomcat
%{_libexecdir}/ipa/custodia/ipa-custodia-pki-tomcat-wrapped
%{_libexecdir}/ipa/custodia/ipa-custodia-ra-agent
%dir %{_libexecdir}/ipa
%{_libexecdir}/ipa/ipa-custodia
%{_libexecdir}/ipa/ipa-custodia-check
Expand Down
1 change: 1 addition & 0 deletions install/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ NULL =

SUBDIRS = \
certmonger \
custodia \
html \
migration \
share \
Expand Down
22 changes: 22 additions & 0 deletions install/custodia/Makefile.am
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
NULL =

appdir = $(libexecdir)/ipa/custodia/
nodist_app_SCRIPTS = \
ipa-custodia-dmldap \
ipa-custodia-pki-tomcat \
ipa-custodia-pki-tomcat-wrapped \
ipa-custodia-ra-agent \
$(NULL)

dist_noinst_DATA = \
ipa-custodia-dmldap.in \
ipa-custodia-pki-tomcat.in \
ipa-custodia-pki-tomcat-wrapped.in \
ipa-custodia-ra-agent.in \
$(NULL)

PYTHON_SHEBANG = $(nodist_app_SCRIPTS)

CLEANFILES = $(PYTHON_SHEBANG)

include $(top_srcdir)/Makefile.pythonscripts.am
8 changes: 8 additions & 0 deletions install/custodia/ipa-custodia-dmldap.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@PYTHONSHEBANG@
#
# Copyright (C) 2019 IPA Project Contributors, see COPYING for license
#

from ipaserver.secrets.handlers.dmldap import main

main()
8 changes: 8 additions & 0 deletions install/custodia/ipa-custodia-pki-tomcat-wrapped.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@PYTHONSHEBANG@
#
# Copyright (C) 2019 IPA Project Contributors, see COPYING for license
#

from ipaserver.secrets.handlers.nsswrappedcert import main, pki_tomcat_parser

main(pki_tomcat_parser())
8 changes: 8 additions & 0 deletions install/custodia/ipa-custodia-pki-tomcat.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@PYTHONSHEBANG@
#
# Copyright (C) 2019 IPA Project Contributors, see COPYING for license
#

from ipaserver.secrets.handlers.nsscert import main, pki_tomcat_parser

main(pki_tomcat_parser())
8 changes: 8 additions & 0 deletions install/custodia/ipa-custodia-ra-agent.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@PYTHONSHEBANG@
#
# Copyright (C) 2019 IPA Project Contributors, see COPYING for license
#

from ipaserver.secrets.handlers.pemfile import main, ra_agent_parser

main(ra_agent_parser())
1 change: 1 addition & 0 deletions ipaplatform/base/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ class BasePathNamespace:
IPA_CUSTODIA_KEYS = '/etc/ipa/custodia/server.keys'
IPA_CUSTODIA_SOCKET = '/run/httpd/ipa-custodia.sock'
IPA_CUSTODIA_AUDIT_LOG = '/var/log/ipa-custodia.audit.log'
IPA_CUSTODIA_HANDLER = "/usr/libexec/ipa/custodia"
IPA_GETKEYTAB = '/usr/sbin/ipa-getkeytab'
EXTERNAL_SCHEMA_DIR = '/usr/share/ipa/schema.d'
GSSPROXY_CONF = '/etc/gssproxy/10-ipa.conf'
Expand Down
2 changes: 2 additions & 0 deletions ipaserver/secrets/handlers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""Export / import handlers
"""
75 changes: 75 additions & 0 deletions ipaserver/secrets/handlers/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#
# Copyright (C) 2019 IPA Project Contributors, see COPYING for license
#
"""Common helpers for handlers
"""
import argparse
import base64
import json
import shutil
import tempfile


def default_json(obj):
"""JSON encoder default handler
"""
if isinstance(obj, (bytes, bytearray)):
return base64.b64encode(obj).decode('ascii')
raise TypeError(
"Object of type {} is not JSON serializable".format(type(obj))
)


def json_dump(data, exportfile):
"""Dump JSON to file
"""
json.dump(
data,
exportfile,
default=default_json,
separators=(',', ':'),
sort_keys=True
)


def mkparser(supports_import=True, **kwargs):
"""Create default parser for handler with export / import args
All commands support export to file or stdout. Most commands can also
import from a file or stdin. Export and import are mutually exclusive
options.
"""
parser = argparse.ArgumentParser(**kwargs)

group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
'--export',
help='JSON export file ("-" for stdout)',
dest='exportfile',
type=argparse.FileType('w')
)
if supports_import:
group.add_argument(
'--import',
help='JSON import file ("-" for stdin)',
dest='importfile',
type=argparse.FileType('r')
)

return parser


def main(parser, export_func, import_func=None, **kwargs):
"""Common main function for handlers
"""
args = parser.parse_args()
if args.exportfile is not None:
func = export_func
else:
func = import_func

tmpdir = tempfile.mkdtemp()
try:
func(args, tmpdir, **kwargs)
finally:
shutil.rmtree(tmpdir)
63 changes: 63 additions & 0 deletions ipaserver/secrets/handlers/dmldap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#
# Copyright (C) 2019 IPA Project Contributors, see COPYING for license
#
"""Export / import Directory Manager password hash
"""
import json
import os

from ipalib import api
from ipalib import errors
from ipapython.dn import DN
from ipapython.ipaldap import LDAPClient
from ipaserver.install.installutils import realm_to_ldapi_uri
from . import common

CN_CONFIG = DN(('cn', 'config'))
ROOTPW = 'nsslapd-rootpw'


def export_key(args, tmpdir, conn):
entry = conn.get_entry(CN_CONFIG, [ROOTPW])
data = {
'dmhash': entry.single_value[ROOTPW],
}
common.json_dump(data, args.exportfile)


def import_key(args, tmpdir, conn):
data = json.load(args.importfile)
dmhash = data['dmhash'].encode('ascii')
entry = conn.get_entry(CN_CONFIG, [ROOTPW])
entry.single_value[ROOTPW] = dmhash
try:
conn.update_entry(entry)
except errors.EmptyModlist:
pass


def main():
parser = common.mkparser(
description='ipa-custodia LDAP DM hash handler'
)

if os.getegid() != 0:
parser.error("Must be run as root user.\n")

# create LDAP connection using LDAPI and EXTERNAL bind as root
if not api.isdone('bootstrap'):
api.bootstrap()
realm = api.env.realm
ldap_uri = realm_to_ldapi_uri(realm)
conn = LDAPClient(ldap_uri=ldap_uri, no_schema=True)
try:
conn.external_bind()
except Exception as e:
parser.error("Failed to connect to {}: {}\n".format(ldap_uri, e))

with conn:
common.main(parser, export_key, import_key, conn=conn)


if __name__ == '__main__':
main()
122 changes: 122 additions & 0 deletions ipaserver/secrets/handlers/nsscert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#
# Copyright (C) 2019 IPA Project Contributors, see COPYING for license
#
"""Export / import cert and key from NSS DB as PKCS#12 data
"""
import base64
import json
import os

from ipaplatform.paths import paths
from ipapython import ipautil
from ipapython.certdb import NSSDatabase
from . import common


def export_key(args, tmpdir):
"""Export key and certificate from the NSS DB to a PKCS#12 file.
The PKCS#12 file is encrypted with a password.
"""
pk12file = os.path.join(tmpdir, 'export.p12')

password = ipautil.ipa_generate_password()
pk12pk12pwfile = os.path.join(tmpdir, 'passwd')
with open(pk12pk12pwfile, 'w') as f:
f.write(password)

nssdb = NSSDatabase(args.nssdb_path)
nssdb.run_pk12util([
"-o", pk12file,
"-n", args.nickname,
"-k", args.nssdb_pwdfile,
"-w", pk12pk12pwfile,
])

with open(pk12file, 'rb') as f:
p12data = f.read()

data = {
'export password': password,
'pkcs12 data': p12data,
}
common.json_dump(data, args.exportfile)


def import_key(args, tmpdir):
"""Import key and certificate from a PKCS#12 file to a NSS DB.
"""
data = json.load(args.importfile)
password = data['export password']
p12data = base64.b64decode(data['pkcs12 data'])

pk12pwfile = os.path.join(tmpdir, 'passwd')
with open(pk12pwfile, 'w') as f:
f.write(password)

pk12file = os.path.join(tmpdir, 'import.p12')
with open(pk12file, 'wb') as f:
f.write(p12data)

nssdb = NSSDatabase(args.nssdb_path)
nssdb.run_pk12util([
"-i", pk12file,
"-n", args.nickname,
"-k", args.nssdb_pwdfile,
"-w", pk12pwfile,
])


def default_parser():
"""Generic interface
"""
parser = common.mkparser(
description='ipa-custodia NSS cert handler'
)
parser.add_argument(
'--nssdb',
dest='nssdb_path',
help='path to NSS DB',
required=True
)
parser.add_argument(
'--pwdfile',
dest='nssdb_pwdfile',
help='path to password file for NSS DB',
required=True
)
parser.add_argument(
'--nickname',
help='nick name of certificate',
required=True
)
return parser


def pki_tomcat_parser():
"""Hard-code Dogtag's NSSDB and its password file
"""
parser = common.mkparser(
description='ipa-custodia pki-tomcat NSS cert handler'
)
parser.add_argument(
'--nickname',
help='nick name of certificate',
required=True
)
parser.set_defaults(
nssdb_path=paths.PKI_TOMCAT_ALIAS_DIR,
nssdb_pwdfile=paths.PKI_TOMCAT_ALIAS_PWDFILE_TXT,
)
return parser


def main(parser=None):
if parser is None:
parser = default_parser()

common.main(parser, export_key, import_key)


if __name__ == '__main__':
main()
Loading

0 comments on commit beffa7b

Please sign in to comment.