From 39a5d9c5aae77687f67d9be02457733bdfb99ead Mon Sep 17 00:00:00 2001 From: Ben Lipton Date: Mon, 22 Aug 2016 10:46:02 -0400 Subject: csrgen: Automate full cert request flow Allows the `ipa cert-request` command to generate its own CSR. It no longer requires a CSR passed on the command line, instead it creates a config (bash script) with `cert-get-requestdata`, then runs it to build a CSR, and submits that CSR. Example usage (NSS database): $ ipa cert-request --principal host/test.example.com --profile-id caIPAserviceCert --database /tmp/certs Example usage (PEM private key file): $ ipa cert-request --principal host/test.example.com --profile-id caIPAserviceCert --private-key /tmp/key.pem https://fedorahosted.org/freeipa/ticket/4899 Reviewed-By: Jan Cholasta --- ipaclient/plugins/cert.py | 76 ++++++++++++++++++++++++++++++++++++++++++++- ipaclient/plugins/csrgen.py | 5 ++- 2 files changed, 79 insertions(+), 2 deletions(-) (limited to 'ipaclient/plugins') diff --git a/ipaclient/plugins/cert.py b/ipaclient/plugins/cert.py index 1075972c4..5d712b516 100644 --- a/ipaclient/plugins/cert.py +++ b/ipaclient/plugins/cert.py @@ -19,6 +19,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import subprocess +from tempfile import NamedTemporaryFile as NTF + +import six + from ipaclient.frontend import MethodOverride from ipalib import errors from ipalib import x509 @@ -27,17 +32,86 @@ from ipalib.parameters import File, Flag, Str from ipalib.plugable import Registry from ipalib.text import _ +if six.PY3: + unicode = str + register = Registry() @register(override=True, no_fail=True) class cert_request(MethodOverride): + takes_options = ( + Str( + 'database?', + label=_('Path to NSS database'), + doc=_('Path to NSS database to use for private key'), + ), + Str( + 'private_key?', + label=_('Path to private key file'), + doc=_('Path to PEM file containing a private key'), + ), + ) + def get_args(self): for arg in super(cert_request, self).get_args(): if arg.name == 'csr': - arg = arg.clone_retype(arg.name, File) + arg = arg.clone_retype(arg.name, File, required=False) yield arg + def forward(self, csr=None, **options): + database = options.pop('database', None) + private_key = options.pop('private_key', None) + + if csr is None: + if database: + helper = u'certutil' + helper_args = ['-d', database] + elif private_key: + helper = u'openssl' + helper_args = [private_key] + else: + raise errors.InvocationError( + message=u"One of 'database' or 'private_key' is required") + + with NTF() as scriptfile, NTF() as csrfile: + profile_id = options.get('profile_id') + + self.api.Command.cert_get_requestdata( + profile_id=profile_id, + principal=options.get('principal'), + out=unicode(scriptfile.name), + helper=helper) + + helper_cmd = [ + 'bash', '-e', scriptfile.name, csrfile.name] + helper_args + + try: + subprocess.check_output(helper_cmd) + except subprocess.CalledProcessError as e: + raise errors.CertificateOperationError( + error=( + _('Error running "%(cmd)s" to generate CSR:' + ' %(err)s') % + {'cmd': ' '.join(helper_cmd), 'err': e.output})) + + try: + csr = unicode(csrfile.read()) + except IOError as e: + raise errors.CertificateOperationError( + error=(_('Unable to read generated CSR file: %(err)s') + % {'err': e})) + if not csr: + raise errors.CertificateOperationError( + error=(_('Generated CSR was empty'))) + else: + if database is not None or private_key is not None: + raise errors.MutuallyExclusiveError(reason=_( + "Options 'database' and 'private_key' are not compatible" + " with 'csr'")) + + return super(cert_request, self).forward(csr, **options) + @register(override=True, no_fail=True) class cert_show(MethodOverride): diff --git a/ipaclient/plugins/csrgen.py b/ipaclient/plugins/csrgen.py index 0669a4775..0d6eca0cf 100644 --- a/ipaclient/plugins/csrgen.py +++ b/ipaclient/plugins/csrgen.py @@ -13,6 +13,7 @@ from ipalib.frontend import Local, Str from ipalib.parameters import Principal from ipalib.plugable import Registry from ipalib.text import _ +from ipapython import dogtag if six.PY3: unicode = str @@ -36,7 +37,7 @@ class cert_get_requestdata(Local): ' HTTP/test.example.com)'), ), Str( - 'profile_id', + 'profile_id?', label=_('Profile ID'), doc=_('CSR Generation Profile to use'), ), @@ -73,6 +74,8 @@ class cert_get_requestdata(Local): principal = options.get('principal') profile_id = options.get('profile_id') + if profile_id is None: + profile_id = dogtag.DEFAULT_PROFILE helper = options.get('helper') if self.api.env.in_server: -- cgit