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 <jcholast@redhat.com>
This commit is contained in:
Ben Lipton 2016-08-22 10:46:02 -04:00 committed by Jan Cholasta
parent 16dac0252e
commit 39a5d9c5aa
2 changed files with 79 additions and 2 deletions

View File

@ -19,6 +19,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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):

View File

@ -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: