From 60e19b585cc12e5b4d51b2d18c504f253cc692ca Mon Sep 17 00:00:00 2001 From: Jan Cholasta Date: Fri, 27 Jun 2014 12:31:50 +0200 Subject: [PATCH] Add client certificate update tool ipa-certupdate. Part of https://fedorahosted.org/freeipa/ticket/3259 Part of https://fedorahosted.org/freeipa/ticket/3520 Reviewed-By: Rob Crittenden --- freeipa.spec.in | 2 + ipa-client/ipa-install/Makefile.am | 1 + ipa-client/ipa-install/ipa-certupdate | 23 ++++ ipa-client/ipaclient/Makefile.am | 1 + ipa-client/ipaclient/ipa_certupdate.py | 171 +++++++++++++++++++++++++ ipa-client/man/Makefile.am | 1 + ipa-client/man/ipa-certupdate.1 | 39 ++++++ 7 files changed, 238 insertions(+) create mode 100755 ipa-client/ipa-install/ipa-certupdate create mode 100644 ipa-client/ipaclient/ipa_certupdate.py create mode 100644 ipa-client/man/ipa-certupdate.1 diff --git a/freeipa.spec.in b/freeipa.spec.in index ff2a4aa5c..6f22bc92f 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -741,6 +741,7 @@ fi %doc COPYING README Contributors.txt %{_sbindir}/ipa-client-install %{_sbindir}/ipa-client-automount +%{_sbindir}/ipa-certupdate %{_sbindir}/ipa-getkeytab %{_sbindir}/ipa-rmkeytab %{_sbindir}/ipa-join @@ -753,6 +754,7 @@ fi %{_mandir}/man1/ipa-rmkeytab.1.gz %{_mandir}/man1/ipa-client-install.1.gz %{_mandir}/man1/ipa-client-automount.1.gz +%{_mandir}/man1/ipa-certupdate.1.gz %{_mandir}/man1/ipa-join.1.gz %{_mandir}/man5/default.conf.5.gz diff --git a/ipa-client/ipa-install/Makefile.am b/ipa-client/ipa-install/Makefile.am index 2e9a04d10..7abdbf12b 100644 --- a/ipa-client/ipa-install/Makefile.am +++ b/ipa-client/ipa-install/Makefile.am @@ -3,6 +3,7 @@ NULL = sbin_SCRIPTS = \ ipa-client-install \ ipa-client-automount \ + ipa-certupdate \ $(NULL) EXTRA_DIST = \ diff --git a/ipa-client/ipa-install/ipa-certupdate b/ipa-client/ipa-install/ipa-certupdate new file mode 100755 index 000000000..072c451bc --- /dev/null +++ b/ipa-client/ipa-install/ipa-certupdate @@ -0,0 +1,23 @@ +#! /usr/bin/python2 -E +# Authors: Jan Cholasta +# +# Copyright (C) 2014 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +from ipaclient.ipa_certupdate import CertUpdate + +CertUpdate.run_cli() diff --git a/ipa-client/ipaclient/Makefile.am b/ipa-client/ipaclient/Makefile.am index 1d7df5266..01824b865 100644 --- a/ipa-client/ipaclient/Makefile.am +++ b/ipa-client/ipaclient/Makefile.am @@ -6,6 +6,7 @@ app_PYTHON = \ ipachangeconf.py \ ipadiscovery.py \ ntpconf.py \ + ipa_certupdate.py \ $(NULL) EXTRA_DIST = \ diff --git a/ipa-client/ipaclient/ipa_certupdate.py b/ipa-client/ipaclient/ipa_certupdate.py new file mode 100644 index 000000000..4199a293b --- /dev/null +++ b/ipa-client/ipaclient/ipa_certupdate.py @@ -0,0 +1,171 @@ +# Authors: Jan Cholasta +# +# Copyright (C) 2014 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import os +import tempfile +import shutil + +from ipapython import (admintool, ipautil, ipaldap, sysrestore, dogtag, + certmonger) +from ipaplatform import services +from ipaplatform.paths import paths +from ipaplatform.tasks import tasks +from ipalib import api, x509, certstore + + +class CertUpdate(admintool.AdminTool): + command_name = 'ipa-certupdate' + + usage = "%prog [options]" + + description = ("Update local IPA certificate databases with certificates " + "from the server.") + + def validate_options(self): + super(CertUpdate, self).validate_options(needs_root=True) + + def run(self): + api.bootstrap(context='cli_installer') + api.finalize() + + try: + server = api.env.server + except AttributeError: + server = api.env.host + ldap = ipaldap.IPAdmin(server) + + tmpdir = tempfile.mkdtemp(prefix="tmp-") + try: + principal = str('host/%s@%s' % (api.env.host, api.env.realm)) + ipautil.kinit_hostprincipal(paths.KRB5_KEYTAB, tmpdir, principal) + + ldap.do_sasl_gssapi_bind() + + certs = certstore.get_ca_certs(ldap, api.env.basedn, + api.env.realm, api.env.enable_ra) + finally: + shutil.rmtree(tmpdir) + + server_fstore = sysrestore.FileStore(paths.SYSRESTORE) + if server_fstore.has_files(): + self.update_server(certs) + + self.update_client(certs) + + def update_client(self, certs): + self.update_file(paths.IPA_CA_CRT, certs) + + self.update_db(paths.NSS_DB_DIR, certs) + + new_nicknames = set(c[1] for c in certs) + old_nicknames = set() + if ipautil.file_exists(paths.NSSDB_IPA_TXT): + try: + list_file = open(paths.NSSDB_IPA_TXT, 'r') + except IOError, e: + self.log.error("failed to open %s: %s", paths.NSSDB_IPA_TXT, e) + else: + try: + lines = list_file.readlines() + except IOError, e: + self.log.error( + "failed to read %s: %s", paths.NSSDB_IPA_TXT, e) + else: + for line in lines: + nickname = line.strip() + if nickname: + old_nicknames.add(nickname) + list_file.close() + if new_nicknames != old_nicknames: + try: + list_file = open(paths.NSSDB_IPA_TXT, 'w') + except IOError, e: + self.log.error("failed to open %s: %s", paths.NSSDB_IPA_TXT, e) + else: + try: + for nickname in new_nicknames: + list_file.write(nickname + '\n') + except IOError, e: + self.log.error( + "failed to write %s: %s", paths.NSSDB_IPA_TXT, e) + list_file.close() + + tasks.remove_ca_certs_from_systemwide_ca_store() + tasks.insert_ca_certs_into_systemwide_ca_store(certs) + + def update_server(self, certs): + instance = '-'.join(api.env.realm.split('.')) + self.update_db( + paths.ETC_DIRSRV_SLAPD_INSTANCE_TEMPLATE % instance, certs) + if services.knownservices.dirsrv.is_running(): + services.knownservices.dirsrv.restart(instance) + + self.update_db(paths.HTTPD_ALIAS_DIR, certs) + if services.knownservices.httpd.is_running(): + services.knownservices.httpd.restart() + + dogtag_constants = dogtag.configured_constants() + nickname = 'caSigningCert cert-pki-ca' + criteria = ( + ('cert_storage_location', dogtag_constants.ALIAS_DIR, + certmonger.NPATH), + ('cert_nickname', nickname, None), + ) + request_id = certmonger.get_request_id(criteria) + if request_id is not None: + timeout = api.env.startup_timeout + 60 + + self.log.debug("resubmitting certmonger request '%s'", request_id) + certmonger.resubmit_request(request_id, profile='ipaRetrieval') + try: + state = certmonger.wait_for_request(request_id, timeout) + except RuntimeError: + raise admintool.ScriptError( + "Resubmitting certmonger request '%s' timed out, " + "please check the request manually" % request_id) + if state != 'MONITORING': + raise admintool.ScriptError( + "Error resubmitting certmonger request '%s', " + "please check the request manually" % request_id) + + self.log.debug("modifying certmonger request '%s'", request_id) + certmonger.modify(request_id, profile='ipaCACertRenewal') + + self.update_file(paths.CA_CRT, certs) + + def update_file(self, filename, certs, mode=0444): + certs = (c[0] for c in certs if c[2] is not False) + try: + x509.write_certificate_list(certs, filename) + except Exception, e: + self.log.error("failed to update %s: %s", filename, e) + + def update_db(self, path, certs): + for cert, nickname, trusted, eku in certs: + trust_flags = certstore.key_policy_to_trust_flags( + trusted, True, eku) + try: + ipautil.run([paths.CERTUTIL, '-A', + '-d', path, + '-n', nickname, + '-t', trust_flags], + stdin=cert) + except ipautil.CalledProcessError, e: + self.log.error( + "failed to update %s in %s: %s", nickname, path, e) diff --git a/ipa-client/man/Makefile.am b/ipa-client/man/Makefile.am index 42772e43c..9d8a9c03d 100644 --- a/ipa-client/man/Makefile.am +++ b/ipa-client/man/Makefile.am @@ -9,6 +9,7 @@ man1_MANS = \ ipa-rmkeytab.1 \ ipa-client-install.1 \ ipa-client-automount.1 \ + ipa-certupdate.1 \ ipa-join.1 man5_MANS = \ diff --git a/ipa-client/man/ipa-certupdate.1 b/ipa-client/man/ipa-certupdate.1 new file mode 100644 index 000000000..d95790a36 --- /dev/null +++ b/ipa-client/man/ipa-certupdate.1 @@ -0,0 +1,39 @@ +.\" A man page for ipa-certupdate +.\" Copyright (C) 2014 Red Hat, Inc. +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation, either version 3 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, but +.\" WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +.\" General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program. If not, see . +.\" +.\" Author: Jan Cholasta +.\" +.TH "ipa-certupdate" "1" "Jul 2 2014" "FreeIPA" "FreeIPA Manual Pages" +.SH "NAME" +ipa\-certupdate \- Update local IPA certificate databases with certificates from the server +.SH "SYNOPSIS" +\fBipa\-certupdate\fR [\fIOPTIONS\fR...] +.SH "DESCRIPTION" +\fBipa\-certupdate\fR can be used to update local IPA certificate databases with certificates from the server. +.SH "OPTIONS" +.TP +\fB\-v\fR, \fB\-\-verbose\fR +Print debugging information. +.TP +\fB\-q\fR, \fB\-\-quiet\fR +Output only errors. +.TP +\fB\-\-log\-file\fR=\fIFILE\fR +Log to the given file. +.SH "EXIT STATUS" +0 if the command was successful + +1 if an error occurred