From 12fe6aa275d05a675eb45bb4d31f063027b12b85 Mon Sep 17 00:00:00 2001 From: Ewoud Kohl van Wijngaarden Date: Fri, 29 May 2020 18:10:41 +0200 Subject: [PATCH 1/9] Set codes on the exception classes --- katello_certs_tools/katello_ssl_tool.py | 53 ++++++------------------- katello_certs_tools/sslToolCli.py | 3 ++ katello_certs_tools/sslToolLib.py | 1 + 3 files changed, 16 insertions(+), 41 deletions(-) diff --git a/katello_certs_tools/katello_ssl_tool.py b/katello_certs_tools/katello_ssl_tool.py index 335a853..1ec8a44 100644 --- a/katello_certs_tools/katello_ssl_tool.py +++ b/katello_certs_tools/katello_ssl_tool.py @@ -60,30 +60,37 @@ class GenPrivateCaKeyException(KatelloSslToolException): """ private CA key generation error """ + code = 10 class GenPublicCaCertException(KatelloSslToolException): """ public CA cert generation error """ + code = 11 class GenServerKeyException(KatelloSslToolException): """ private server key generation error """ + code = 20 class GenServerCertReqException(KatelloSslToolException): """ server cert request generation error """ + code = 21 class GenServerCertException(KatelloSslToolException): """ server cert generation error """ + code = 22 class GenCaCertRpmException(KatelloSslToolException): """ CA public certificate RPM generation error """ + code = 12 class GenServerRpmException(KatelloSslToolException): """ server RPM generation error """ + code = 23 class FailedFileDependencyException(Exception): @@ -948,57 +955,21 @@ def main(): 100 general RHN SSL tool error """ - def writeError(e): - sys.stderr.write('\nERROR: %s\n' % e) - try: _main() ret = 0 - # CA key set errors - except GenPrivateCaKeyException as e: - writeError(e) - ret = 10 - except GenPublicCaCertException as e: - writeError(e) - ret = 11 - except GenCaCertRpmException as e: - writeError(e) - ret = 12 - # server key set errors - except GenServerKeyException as e: - writeError(e) - ret = 20 - except GenServerCertReqException as e: - writeError(e) - ret = 21 - except GenServerCertException as e: - writeError(e) - ret = 22 - except GenServerRpmException as e: - writeError(e) - ret = 23 - # other errors - except CertExpTooShortException as e: - writeError(e) - ret = 30 - except CertExpTooLongException as e: - writeError(e) - ret = 31 - except InvalidCountryCodeException as e: - writeError(e) - ret = 32 except FailedFileDependencyException as e: # already wrote a nice error message - msg = """\ -can't find a file that should have been created during an earlier step: + msg = """ +ERROR: can't find a file that should have been created during an earlier step: %s %s --help""" % (e, os.path.basename(sys.argv[0])) - writeError(msg) + sys.stderr.write(msg) ret = 33 except KatelloSslToolException as e: - writeError(e) - ret = 100 + sys.stderr.write('\nERROR: %s\n' % e) + ret = e.code except KeyboardInterrupt: sys.stderr.write("\nUser interrupted process.\n") ret = 0 diff --git a/katello_certs_tools/sslToolCli.py b/katello_certs_tools/sslToolCli.py index 7035eff..4dad400 100644 --- a/katello_certs_tools/sslToolCli.py +++ b/katello_certs_tools/sslToolCli.py @@ -353,14 +353,17 @@ def optionParse(): class CertExpTooShortException(KatelloSslToolException): "certificate expiration must be at least 1 day" + code = 30 class CertExpTooLongException(KatelloSslToolException): "cert expiration cannot be > 1 year before the 32-bit overflow (in days)" + code = 31 class InvalidCountryCodeException(KatelloSslToolException): "invalid country code. Probably != 2 characters in length." + code = 32 def processCommandline(): diff --git a/katello_certs_tools/sslToolLib.py b/katello_certs_tools/sslToolLib.py index d56a597..bebfeb8 100644 --- a/katello_certs_tools/sslToolLib.py +++ b/katello_certs_tools/sslToolLib.py @@ -28,6 +28,7 @@ class KatelloSslToolException(Exception): """ general exception class for the tool """ + code = 100 errnoGeneralError = 1 From d4d6ab7d11448577f6d714c8bcb7cc242ebb39d0 Mon Sep 17 00:00:00 2001 From: Ewoud Kohl van Wijngaarden Date: Fri, 29 May 2020 17:27:21 +0200 Subject: [PATCH 2/9] Remove genServerRpm_dependencies This can easily be checked in the function itself without duplicating all the variables. --- katello_certs_tools/katello_ssl_tool.py | 28 ++++--------------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/katello_certs_tools/katello_ssl_tool.py b/katello_certs_tools/katello_ssl_tool.py index 1ec8a44..79da4ed 100644 --- a/katello_certs_tools/katello_ssl_tool.py +++ b/katello_certs_tools/katello_ssl_tool.py @@ -720,41 +720,24 @@ def genCaRpm(d, verbosity=0): return '%s.noarch.rpm' % clientRpmName -def genServerRpm_dependencies(d): - """ generates server's SSL key set RPM - dependencies check """ - - serverKeyPairDir = os.path.join(d['--dir'], - d['--set-hostname']) - gendir(serverKeyPairDir) - - server_key_name = os.path.basename(d['--server-key']) - server_key = os.path.join(serverKeyPairDir, server_key_name) - - server_cert_name = os.path.basename(d['--server-cert']) - server_cert = os.path.join(serverKeyPairDir, server_cert_name) - - server_cert_req_name = os.path.basename(d['--server-cert-req']) - server_cert_req = os.path.join(serverKeyPairDir, server_cert_req_name) - - dependencyCheck(server_key) - dependencyCheck(server_cert) - dependencyCheck(server_cert_req) - - def genServerRpm(d, verbosity=0): """ generates server's SSL key set RPM """ serverKeyPairDir = os.path.join(d['--dir'], d['--set-hostname']) + gendir(serverKeyPairDir) server_key_name = os.path.basename(d['--server-key']) server_key = os.path.join(serverKeyPairDir, server_key_name) + dependencyCheck(server_key) server_cert_name = os.path.basename(d['--server-cert']) server_cert = os.path.join(serverKeyPairDir, server_cert_name) + dependencyCheck(server_cert) server_cert_req_name = os.path.basename(d['--server-cert-req']) server_cert_req = os.path.join(serverKeyPairDir, server_cert_req_name) + dependencyCheck(server_cert_req) server_rpm_name = os.path.basename(d['--server-rpm']) server_rpm = os.path.join(serverKeyPairDir, server_rpm_name) @@ -763,8 +746,6 @@ def genServerRpm(d, verbosity=0): postun_scriptlet = os.path.join(d['--dir'], 'postun.scriptlet') - genServerRpm_dependencies(d) - if verbosity >= 0: sys.stderr.write("\n...working...\n") @@ -920,7 +901,6 @@ def _main(): genServerCert_dependencies(getCAPassword(options, confirmYN=0), DEFS) genServerCert(getCAPassword(options, confirmYN=0), DEFS, options.verbose) elif getOption(options, 'rpm_only'): - genServerRpm_dependencies(DEFS) genServerRpm(DEFS, options.verbose) else: genServer_dependencies(getCAPassword(options, confirmYN=0), DEFS) From 3f81c51af09cd2da49702da57c7ccefa4d7181cf Mon Sep 17 00:00:00 2001 From: Ewoud Kohl van Wijngaarden Date: Fri, 29 May 2020 17:34:17 +0200 Subject: [PATCH 3/9] Remove redundant genServerCertReq_dependencies This was always called twice. It now does the checking as part of the function itself which reduces duplication. --- katello_certs_tools/katello_ssl_tool.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/katello_certs_tools/katello_ssl_tool.py b/katello_certs_tools/katello_ssl_tool.py index 79da4ed..7bb3231 100644 --- a/katello_certs_tools/katello_ssl_tool.py +++ b/katello_certs_tools/katello_ssl_tool.py @@ -380,7 +380,7 @@ def genServerKey(d, verbosity=0): os.chmod(server_key, 0o600) -def genServerCertReq_dependencies(d): +def genServerCertReq(d, verbosity=0): """ private server cert request generation """ serverKeyPairDir = os.path.join(d['--dir'], @@ -391,21 +391,11 @@ def genServerCertReq_dependencies(d): os.path.basename(d['--server-key'])) dependencyCheck(server_key) - -def genServerCertReq(d, verbosity=0): - """ private server cert request generation """ - - serverKeyPairDir = os.path.join(d['--dir'], - d['--set-hostname']) - server_key = os.path.join(serverKeyPairDir, - os.path.basename(d['--server-key'])) server_cert_req = os.path.join(serverKeyPairDir, os.path.basename(d['--server-cert-req'])) server_openssl_cnf = os.path.join(serverKeyPairDir, SERVER_OPENSSL_CNF_NAME) - genServerCertReq_dependencies(d) - # XXX: hmm.. should private_key, etc. be set for this before the write? # either that you pull the key/certs from the files all together? configFile = ConfigFile(server_openssl_cnf) @@ -895,7 +885,6 @@ def _main(): if getOption(options, 'key_only'): genServerKey(DEFS, options.verbose) elif getOption(options, 'cert_req_only'): - genServerCertReq_dependencies(DEFS) genServerCertReq(DEFS, options.verbose) elif getOption(options, 'cert_only'): genServerCert_dependencies(getCAPassword(options, confirmYN=0), DEFS) From 4440389e11bcff1e9d2a0a376472ea5e7d386a1c Mon Sep 17 00:00:00 2001 From: Ewoud Kohl van Wijngaarden Date: Fri, 29 May 2020 17:39:10 +0200 Subject: [PATCH 4/9] Factor out a password check --- katello_certs_tools/katello_ssl_tool.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/katello_certs_tools/katello_ssl_tool.py b/katello_certs_tools/katello_ssl_tool.py index 7bb3231..4077dc8 100644 --- a/katello_certs_tools/katello_ssl_tool.py +++ b/katello_certs_tools/katello_ssl_tool.py @@ -102,6 +102,12 @@ def dependencyCheck(filename): raise FailedFileDependencyException(filename) +def passwordCheck(password): + if password is None: + sys.stderr.write('ERROR: a CA password must be supplied.\n') + sys.exit(errnoGeneralError) + + def pathJoin(path, filename): filename = os.path.basename(filename) return os.path.join(path, filename) @@ -253,9 +259,7 @@ def genPublicCaCert_dependencies(password, d, forceYN=0): dependencyCheck(ca_key) - if password is None: - sys.stderr.write('ERROR: a CA password must be supplied.\n') - sys.exit(errnoGeneralError) + passwordCheck(password) def genPublicCaCert(password, d, verbosity=0, forceYN=0): @@ -452,9 +456,7 @@ def genServerCertReq(d, verbosity=0): def genServerCert_dependencies(password, d): """ server cert generation and signing dependency check """ - if password is None: - sys.stderr.write('ERROR: a CA password must be supplied.\n') - sys.exit(errnoGeneralError) + passwordCheck(password) serverKeyPairDir = os.path.join(d['--dir'], d['--set-hostname']) @@ -852,9 +854,7 @@ def genServer_dependencies(password, d): dependencyCheck(ca_key) dependencyCheck(ca_cert) - if password is None: - sys.stderr.write('ERROR: a CA password must be supplied.\n') - sys.exit(errnoGeneralError) + passwordCheck(password) def _main(): From 29dde82d75e747edc7519dfb24f30b011825644d Mon Sep 17 00:00:00 2001 From: Ewoud Kohl van Wijngaarden Date: Fri, 29 May 2020 17:41:37 +0200 Subject: [PATCH 5/9] Remove redundant genServerCert_dependencies function By moving this all into genServerCert, a lot of variable duplication is avoided. --- katello_certs_tools/katello_ssl_tool.py | 29 ++++++------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/katello_certs_tools/katello_ssl_tool.py b/katello_certs_tools/katello_ssl_tool.py index 4077dc8..84bee38 100644 --- a/katello_certs_tools/katello_ssl_tool.py +++ b/katello_certs_tools/katello_ssl_tool.py @@ -453,8 +453,9 @@ def genServerCertReq(d, verbosity=0): os.chmod(server_cert_req, 0o600) -def genServerCert_dependencies(password, d): - """ server cert generation and signing dependency check """ + +def genServerCert(password, d, verbosity=0): + """ server cert generation and signing """ passwordCheck(password) @@ -463,34 +464,19 @@ def genServerCert_dependencies(password, d): gendir(serverKeyPairDir) ca_key = os.path.join(d['--dir'], os.path.basename(d['--ca-key'])) - ca_cert = os.path.join(d['--dir'], os.path.basename(d['--ca-cert'])) - - server_cert_req = os.path.join(serverKeyPairDir, - os.path.basename(d['--server-cert-req'])) - ca_openssl_cnf = os.path.join(d['--dir'], CA_OPENSSL_CNF_NAME) - - dependencyCheck(ca_openssl_cnf) dependencyCheck(ca_key) - dependencyCheck(ca_cert) - dependencyCheck(server_cert_req) - -def genServerCert(password, d, verbosity=0): - """ server cert generation and signing """ - - serverKeyPairDir = os.path.join(d['--dir'], - d['--set-hostname']) - - genServerCert_dependencies(password, d) - - ca_key = os.path.join(d['--dir'], os.path.basename(d['--ca-key'])) ca_cert = os.path.join(d['--dir'], os.path.basename(d['--ca-cert'])) + dependencyCheck(ca_cert) server_cert_req = os.path.join(serverKeyPairDir, os.path.basename(d['--server-cert-req'])) + dependencyCheck(server_cert_req) + server_cert = os.path.join(serverKeyPairDir, os.path.basename(d['--server-cert'])) ca_openssl_cnf = os.path.join(d['--dir'], CA_OPENSSL_CNF_NAME) + dependencyCheck(ca_openssl_cnf) index_txt = os.path.join(d['--dir'], 'index.txt') serial = os.path.join(d['--dir'], 'serial') @@ -887,7 +873,6 @@ def _main(): elif getOption(options, 'cert_req_only'): genServerCertReq(DEFS, options.verbose) elif getOption(options, 'cert_only'): - genServerCert_dependencies(getCAPassword(options, confirmYN=0), DEFS) genServerCert(getCAPassword(options, confirmYN=0), DEFS, options.verbose) elif getOption(options, 'rpm_only'): genServerRpm(DEFS, options.verbose) From 9b6bc4939178888e30b6d183636c2088e1d40c92 Mon Sep 17 00:00:00 2001 From: Ewoud Kohl van Wijngaarden Date: Fri, 29 May 2020 17:45:52 +0200 Subject: [PATCH 6/9] Remove genCaRpm_dependencies function --- katello_certs_tools/katello_ssl_tool.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/katello_certs_tools/katello_ssl_tool.py b/katello_certs_tools/katello_ssl_tool.py index 84bee38..e74e68b 100644 --- a/katello_certs_tools/katello_ssl_tool.py +++ b/katello_certs_tools/katello_ssl_tool.py @@ -584,25 +584,18 @@ def _reenableRpmMacros(): os.rename(macTmp, mac) -def genCaRpm_dependencies(d): - """ generates ssl cert RPM. """ - - gendir(d['--dir']) - ca_cert_name = os.path.basename(d['--ca-cert']) - ca_cert = os.path.join(d['--dir'], ca_cert_name) - dependencyCheck(ca_cert) - - def genCaRpm(d, verbosity=0): """ generates ssl cert RPM. """ ca_cert_path = d['--ca-cert-dir'] ca_cert_name = os.path.basename(d['--ca-cert']) ca_cert = os.path.join(d['--dir'], ca_cert_name) + gendir(d['--dir']) + dependencyCheck(ca_cert) + ca_cert_rpm_name = os.path.basename(d['--ca-cert-rpm']) ca_cert_rpm = os.path.join(d['--dir'], ca_cert_rpm_name) - genCaRpm_dependencies(d) appendOtherCACerts(d, ca_cert) if verbosity >= 0: @@ -857,7 +850,6 @@ def _main(): genPublicCaCert(getCAPassword(options), DEFS, options.verbose, options.force) elif getOption(options, 'rpm_only'): - genCaRpm_dependencies(DEFS) genCaRpm(DEFS, options.verbose) else: genPrivateCaKey(getCAPassword(options), DEFS, From 216973753c4db32f3d06f04afed7c2c32a8a238a Mon Sep 17 00:00:00 2001 From: Ewoud Kohl van Wijngaarden Date: Fri, 29 May 2020 17:53:13 +0200 Subject: [PATCH 7/9] Remove genPublicCaCert_dependencies function --- katello_certs_tools/katello_ssl_tool.py | 27 +++++++++---------------- 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/katello_certs_tools/katello_ssl_tool.py b/katello_certs_tools/katello_ssl_tool.py index e74e68b..b7ebf06 100644 --- a/katello_certs_tools/katello_ssl_tool.py +++ b/katello_certs_tools/katello_ssl_tool.py @@ -242,12 +242,19 @@ def genPrivateCaKey(password, d, verbosity=0, forceYN=0): os.chmod(ca_key, 0o600) -def genPublicCaCert_dependencies(password, d, forceYN=0): +def genPublicCaCert(password, d, verbosity=0, forceYN=0): """ public CA certificate (client-side) generation """ + passwordCheck(password) + gendir(d['--dir']) + ca_key = os.path.join(d['--dir'], os.path.basename(d['--ca-key'])) - ca_cert = os.path.join(d['--dir'], os.path.basename(d['--ca-cert'])) + dependencyCheck(ca_key) + + ca_cert_name = os.path.basename(d['--ca-cert']) + ca_cert = os.path.join(d['--dir'], ca_cert_name) + ca_openssl_cnf = os.path.join(d['--dir'], CA_OPENSSL_CNF_NAME) if not forceYN and os.path.exists(ca_cert): sys.stderr.write("""\ @@ -257,21 +264,6 @@ def genPublicCaCert_dependencies(password, d, forceYN=0): """ % ca_cert) sys.exit(errnoGeneralError) - dependencyCheck(ca_key) - - passwordCheck(password) - - -def genPublicCaCert(password, d, verbosity=0, forceYN=0): - """ public CA certificate (client-side) generation """ - - ca_key = os.path.join(d['--dir'], os.path.basename(d['--ca-key'])) - ca_cert_name = os.path.basename(d['--ca-cert']) - ca_cert = os.path.join(d['--dir'], ca_cert_name) - ca_openssl_cnf = os.path.join(d['--dir'], CA_OPENSSL_CNF_NAME) - - genPublicCaCert_dependencies(password, d, forceYN) - configFile = ConfigFile(ca_openssl_cnf) if '--set-hostname' in d: del d['--set-hostname'] @@ -846,7 +838,6 @@ def _main(): genPrivateCaKey(getCAPassword(options), DEFS, options.verbose, options.force) elif getOption(options, 'cert_only'): - genPublicCaCert_dependencies(getCAPassword(options), DEFS, options.force) genPublicCaCert(getCAPassword(options), DEFS, options.verbose, options.force) elif getOption(options, 'rpm_only'): From a1b277fc89aa9e5e571a512fb5cc761d1a919ef4 Mon Sep 17 00:00:00 2001 From: Ewoud Kohl van Wijngaarden Date: Fri, 29 May 2020 18:00:25 +0200 Subject: [PATCH 8/9] Introduce genServerCert_dependencies again This method allows to fail fast --- katello_certs_tools/katello_ssl_tool.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/katello_certs_tools/katello_ssl_tool.py b/katello_certs_tools/katello_ssl_tool.py index b7ebf06..5a9aebc 100644 --- a/katello_certs_tools/katello_ssl_tool.py +++ b/katello_certs_tools/katello_ssl_tool.py @@ -449,17 +449,14 @@ def genServerCertReq(d, verbosity=0): def genServerCert(password, d, verbosity=0): """ server cert generation and signing """ - passwordCheck(password) + genServerCert_dependencies(password, d) serverKeyPairDir = os.path.join(d['--dir'], d['--set-hostname']) gendir(serverKeyPairDir) ca_key = os.path.join(d['--dir'], os.path.basename(d['--ca-key'])) - dependencyCheck(ca_key) - ca_cert = os.path.join(d['--dir'], os.path.basename(d['--ca-cert'])) - dependencyCheck(ca_cert) server_cert_req = os.path.join(serverKeyPairDir, os.path.basename(d['--server-cert-req'])) @@ -812,7 +809,7 @@ def genServerRpm(d, verbosity=0): return "%s.noarch.rpm" % serverRpmName -def genServer_dependencies(password, d): +def genServerCert_dependencies(password, d): """ deps for the general --gen-server command. I.e., generation of server.{key,csr,crt}. """ @@ -860,10 +857,11 @@ def _main(): elif getOption(options, 'rpm_only'): genServerRpm(DEFS, options.verbose) else: - genServer_dependencies(getCAPassword(options, confirmYN=0), DEFS) + ca_password = getCAPassword(options, confirmYN=0) + genServerCert_dependencies(ca_password, DEFS) genServerKey(DEFS, options.verbose) genServerCertReq(DEFS, options.verbose) - genServerCert(getCAPassword(options, confirmYN=0), DEFS, options.verbose) + genServerCert(ca_password, DEFS, options.verbose) if not getOption(options, 'no_rpm'): genServerRpm(DEFS, options.verbose) From 8439c4ba28e54a8141a88424d5f98ef6bf041922 Mon Sep 17 00:00:00 2001 From: Ewoud Kohl van Wijngaarden Date: Tue, 19 May 2020 14:05:31 +0200 Subject: [PATCH 9/9] [WIP] Remove rhn_popen --- katello_certs_tools/fileutils.py | 80 ------ katello_certs_tools/katello_ssl_tool.py | 331 +++++++++--------------- katello_certs_tools/sslToolLib.py | 101 +++++--- 3 files changed, 196 insertions(+), 316 deletions(-) diff --git a/katello_certs_tools/fileutils.py b/katello_certs_tools/fileutils.py index 5487257..c5b23e8 100644 --- a/katello_certs_tools/fileutils.py +++ b/katello_certs_tools/fileutils.py @@ -14,11 +14,8 @@ # import os -import select import shutil -import subprocess import sys -import tempfile def _file_contents_match(first, second): @@ -145,80 +142,3 @@ def rotateFile(filepath, depth=5, suffix='.', verbosity=0): # return the full filepath of the backed up file return pathNSuffix1 - - -def rhn_popen(cmd, progressCallback=None, bufferSize=16384, outputLog=None): - """ popen-like function, that accepts execvp-style arguments too (i.e. an - array of params, thus making shell escaping unnecessary) - - cmd can be either a string (like "ls -l /dev"), or an array of - arguments ["ls", "-l", "/dev"] - - Returns the command's error code, a stream with stdout's contents - and a stream with stderr's contents - - progressCallback --> progress bar twiddler - outputLog --> optional log file file object write method - """ - cmd_is_list = isinstance(cmd, list) or isinstance(cmd, tuple) - if cmd_is_list: - cmd = map(str, cmd) - c = subprocess.Popen(cmd, bufsize=0, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - close_fds=True, shell=(not cmd_is_list)) - - # We don't write to the child process - c.stdin.close() - - # Create two temporary streams to hold the info from stdout and stderr - child_out = tempfile.TemporaryFile(prefix='/tmp/my-popen-', mode='r+b') - child_err = tempfile.TemporaryFile(prefix='/tmp/my-popen-', mode='r+b') - - # Map the input file descriptor with the temporary (output) one - fd_mappings = [(c.stdout, child_out), (c.stderr, child_err)] - exitcode = None - count = 1 - - while 1: - # Is the child process done? - status = c.poll() - if status is not None: - if status >= 0: - # Save the exit code, we still have to read from the pipes - exitcode = status - else: - # Some signal sent to this process - if outputLog is not None: - outputLog("rhn_popen: Signal %s received\n" % (-status)) - exitcode = status - break - - fd_set = map(lambda x: x[0], fd_mappings) - readfds = select.select(fd_set, [], [])[0] - - for in_fd, out_fd in fd_mappings: - if in_fd in readfds: - # There was activity on this file descriptor - output = os.read(in_fd.fileno(), bufferSize) - if output: - # show progress - if progressCallback: - count = count + len(output) - progressCallback(count) - - if outputLog is not None: - outputLog(output) - - # write to the output buffer(s) - out_fd.write(output) - out_fd.flush() - - if exitcode is not None: - # Child process is done - break - - for f_in, f_out in fd_mappings: - f_in.close() - f_out.seek(0, 0) - - return exitcode, child_out, child_err diff --git a/katello_certs_tools/katello_ssl_tool.py b/katello_certs_tools/katello_ssl_tool.py index 5a9aebc..1930da7 100644 --- a/katello_certs_tools/katello_ssl_tool.py +++ b/katello_certs_tools/katello_ssl_tool.py @@ -38,6 +38,7 @@ import glob import os import sys +import subprocess # Package imports import rpm @@ -47,10 +48,9 @@ CertExpTooLongException, InvalidCountryCodeException from katello_certs_tools.sslToolLib import KatelloSslToolException, \ - gendir, chdir, TempDir, \ - errnoGeneralError + gendir, chdir, TemporaryDirectory, disabled_rpm_macros, errnoGeneralError -from katello_certs_tools.fileutils import rotateFile, rhn_popen, cleanupAbsPath +from katello_certs_tools.fileutils import rotateFile, cleanupAbsPath from katello_certs_tools.sslToolConfig import ConfigFile, figureSerial, getOption, \ DEFS, MD, CRYPTO, \ @@ -113,14 +113,15 @@ def pathJoin(path, filename): return os.path.join(path, filename) -_workDirObj = None +def _fill_password(command, password, placeholder='pass:%s'): + cmd = command.copy() + cmd[command.index(placeholder)] = placeholder % password + return cmd -def _getWorkDir(): - global _workDirObj - if not _workDirObj: - _workDirObj = TempDir() - return _workDirObj.getdir() +def _run_command(command): + with TemporaryDirectory('-katello-certs-tools') as workdir, chdir(workdir): + return subprocess.check_output(command) def get_max_rpm_version(package_name, glob_prefix=None): @@ -217,26 +218,14 @@ def genPrivateCaKey(password, d, verbosity=0, forceYN=0): except ValueError: pass - cwd = chdir(_getWorkDir()) try: - ret, out_stream, err_stream = rhn_popen(args % repr(password)) - finally: - chdir(cwd) - - out = out_stream.read().decode('utf-8') - out_stream.close() - err = err_stream.read().decode('utf-8') - err_stream.close() - - if ret: + out = _run_command(args % repr(password)) + except subprocess.CalledProcessError as exception: raise GenPrivateCaKeyException("Certificate Authority private SSL " "key generation failed:\n%s\n%s" - % (out, err)) - if verbosity > 2: - if out: - print("STDOUT:", out) - if err: - print("STDERR:", err) + % (exception.stdout, exception.stderr)) + if verbosity > 2 and out: + print("STDOUT:", out) # permissions: os.chmod(ca_key, 0o600) @@ -269,12 +258,19 @@ def genPublicCaCert(password, d, verbosity=0, forceYN=0): del d['--set-hostname'] configFile.save(d, caYN=1, verbosity=verbosity) - args = ("/usr/bin/openssl req -passin pass:%s -config %s " - "-new -x509 -days %s -%s -key %s -out %s" - % ('%s', repr(cleanupAbsPath(configFile.filename)), - repr(d['--cert-expiration']), - MD, repr(cleanupAbsPath(ca_key)), - repr(cleanupAbsPath(ca_cert)))) + command = [ + "/usr/bin/openssl", + "req", + "-passin", "pass:%s", + "-text", + "-config", cleanupAbsPath(configFile.filename), + "-new", + "-x509", + "-days", d['--cert-expiration'], + "-%s" % MD, + "-key", cleanupAbsPath(ca_key), + "-out", cleanupAbsPath(ca_cert), + ] if verbosity >= 0: print("\nGenerating public CA certificate: %s" % ca_cert) @@ -283,7 +279,7 @@ def genPublicCaCert(password, d, verbosity=0, forceYN=0): '--set-org-unit', '--set-common-name', '--set-email'): print(' %s%s = "%s"' % (k, ' '*(18-len(k)), d[k])) if verbosity > 1: - print("Commandline:", args % "PASSWORD") + print("Commandline: %r", _fill_password(command, 'PASSWORD')) try: rotated = rotateFile(filepath=ca_cert, verbosity=verbosity) @@ -293,26 +289,14 @@ def genPublicCaCert(password, d, verbosity=0, forceYN=0): except ValueError: pass - cwd = chdir(_getWorkDir()) try: - ret, out_stream, err_stream = rhn_popen(args % repr(password)) - finally: - chdir(cwd) - - out = out_stream.read().decode('utf-8') - out_stream.close() - err = err_stream.read().decode('utf-8') - err_stream.close() - - if ret: + out = _run_command(_fill_password(command, password)) + except subprocess.CalledProcessError as exception: raise GenPublicCaCertException("Certificate Authority public " "SSL certificate generation failed:\n%s\n" - "%s" % (out, err)) - if verbosity > 2: - if out: - print("STDOUT:", out) - if err: - print("STDERR:", err) + "%s" % (exception.stdout, exception.stderr)) + if verbosity > 2 and out: + print("STDOUT:", out) appendOtherCACerts(d, ca_cert) @@ -335,14 +319,19 @@ def genServerKey(d, verbosity=0): server_key = os.path.join(serverKeyPairDir, os.path.basename(d['--server-key'])) - args = ("/usr/bin/openssl genpkey -out %s -algorithm rsa -pkeyopt rsa_keygen_bits:4096" - % (repr(cleanupAbsPath(server_key)))) + command = [ + "/usr/bin/openssl", + "genpkey", + "-out", cleanupAbsPath(server_key), + "-algorithm", "rsa", + "-pkeyopt", "rsa_keygen_bits:4096", + ] # generate the server key if verbosity >= 0: print("\nGenerating the web server's SSL private key: %s" % server_key) if verbosity > 1: - print("Commandline:", args) + print("Commandline: %r" % command) try: rotated = rotateFile(filepath=server_key, verbosity=verbosity) @@ -352,25 +341,14 @@ def genServerKey(d, verbosity=0): except ValueError: pass - cwd = chdir(_getWorkDir()) try: - ret, out_stream, err_stream = rhn_popen(args) - finally: - chdir(cwd) - - out = out_stream.read().decode('utf-8') - out_stream.close() - err = err_stream.read().decode('utf-8') - err_stream.close() - - if ret: + out = _run_command(command) + except subprocess.CalledProcessError as exception: raise GenServerKeyException("web server's SSL key generation failed:\n%s\n%s" - % (out, err)) - if verbosity > 2: - if out: - print("STDOUT:", out) - if err: - print("STDERR:", err) + % (exception.stdout, exception.stderr)) + + if verbosity > 2 and out: + print("STDOUT:", out) # permissions: os.chmod(server_key, 0o600) @@ -420,26 +398,15 @@ def genServerCertReq(d, verbosity=0): except ValueError: pass - cwd = chdir(_getWorkDir()) try: - ret, out_stream, err_stream = rhn_popen(args) - finally: - chdir(cwd) - - out = out_stream.read().decode('utf-8') - out_stream.close() - err = err_stream.read().decode('utf-8') - err_stream.close() - - if ret: + out = _run_command(args) + except subprocess.CalledProcessError as exception: raise GenServerCertReqException( - "web server's SSL certificate request generation " - "failed:\n%s\n%s" % (out, err)) - if verbosity > 2: - if out: - print("STDOUT:", out) - if err: - print("STDERR:", err) + "web server's SSL certificate request generation " + "failed:\n%s\n%s" % (exception.stdout, exception.stderr)) + + if verbosity > 2 and out: + print("STDOUT:", out) # permissions: os.chmod(server_cert_req, 0o600) @@ -485,21 +452,26 @@ def genServerCert(password, d, verbosity=0): configFile = ConfigFile(ca_openssl_cnf) configFile.updateDir() - args = ("/usr/bin/openssl ca -extensions req_%s_x509_extensions -passin pass:%s -outdir ./ -config %s " - "-in %s -batch -cert %s -keyfile %s -startdate %s -days %s " - "-md %s -out %s" - % (purpose, - '%s', repr(cleanupAbsPath(ca_openssl_cnf)), - repr(cleanupAbsPath(server_cert_req)), - repr(cleanupAbsPath(ca_cert)), - repr(cleanupAbsPath(ca_key)), d['--startdate'], - repr(d['--cert-expiration']), MD, - repr(cleanupAbsPath(server_cert)))) + command = [ + "/usr/bin/openssl", "ca", "-batch", + "-extensions", "req_%s_x509_extensions" % purpose, + "-passin", "pass:%s", + "-outdir", "./", + "-config", cleanupAbsPath(ca_openssl_cnf), + "-in", cleanupAbsPath(server_cert_req), + "-cert", cleanupAbsPath(ca_cert), + "-keyfile", cleanupAbsPath(ca_key), + "-startdate", d['--startdate'], + "-days", d['--cert-expiration'], + "-md", MD, + "-out", cleanupAbsPath(server_cert), + ] + if verbosity >= 0: print("\nGenerating/signing web server's SSL certificate: %s" % d['--server-cert']) if verbosity > 1: - print("Commandline:", args % 'PASSWORD') + print("Commandline: %r", _fill_password(command, 'PASSWORD')) try: rotated = rotateFile(filepath=server_cert, verbosity=verbosity) if verbosity >= 0 and rotated: @@ -508,35 +480,23 @@ def genServerCert(password, d, verbosity=0): except ValueError: pass - cwd = chdir(_getWorkDir()) try: - ret, out_stream, err_stream = rhn_popen(args % repr(password)) - finally: - chdir(cwd) - - out = out_stream.read().decode('utf-8') - out_stream.close() - err = err_stream.read().decode('utf-8') - err_stream.close() - - if ret: + out = _run_command(_fill_password(command, password)) + except subprocess.CalledProcessError as exception: + err = exception.stderr # signature for a mistyped CA password if "unable to load CA private key" in err \ and "error:0906A065:PEM routines:PEM_do_header:bad decrypt:pem_lib.c" in err \ and "error:06065064:digital envelope routines:EVP_DecryptFinal:bad decrypt:evp_enc.c" in err: raise GenServerCertException( - "web server's SSL certificate generation/signing " - "failed:\nDid you mistype your CA password?") - else: - raise GenServerCertException( - "web server's SSL certificate generation/signing " - "failed:\n%s\n%s" % (out, err)) + "web server's SSL certificate generation/signing failed:\n" + "Did you mistype your CA password?") - if verbosity > 2: - if out: - print("STDOUT:", out) - if err: - print("STDERR:", err) + raise GenServerCertException("web server's SSL certificate generation/signing failed:\n" + "%s\n%s" % (exception.stdout, err)) + + if verbosity > 2 and out: + print("STDOUT:", out) # permissions: os.chmod(server_cert, 0o644) @@ -559,20 +519,6 @@ def genServerCert(password, d, verbosity=0): pass -def _disableRpmMacros(): - mac = cleanupAbsPath('~/.rpmmacros') - macTmp = cleanupAbsPath('~/RENAME_ME_BACK_PLEASE-lksjdflajsd.rpmmacros') - if os.path.exists(mac): - os.rename(mac, macTmp) - - -def _reenableRpmMacros(): - mac = cleanupAbsPath('~/.rpmmacros') - macTmp = cleanupAbsPath('~/RENAME_ME_BACK_PLEASE-lksjdflajsd.rpmmacros') - if os.path.exists(macTmp): - os.rename(macTmp, mac) - - def genCaRpm(d, verbosity=0): """ generates ssl cert RPM. """ @@ -609,24 +555,17 @@ def genCaRpm(d, verbosity=0): # build the CA certificate RPM args = [ 'katello-certs-gen-rpm', - "--name %s", - "--version %s", - "--release %s", - "--packager %s", - "--vendor %s", - "--group 'Applications/System'", - "--summary %s", - "--description %s", - os.path.join(ca_cert_path, "%s=%s") + '--name', ca_cert_rpm_name, + '--version', ver, + '--release', rel, + '--packager', d['--rpm-packager'], + '--vendor', d['--rpm-vendor'], + '--group', 'Applications/System', + '--summary', CA_CERT_RPM_SUMMARY, + '--description', CA_CERT_RPM_SUMMARY, + os.path.join(ca_cert_path, "%s=%s" % (ca_cert_name, cleanupAbsPath(ca_cert))), ] - args = " ".join(args) - - args = args % ((repr(ca_cert_rpm_name), ver, rel, repr(d['--rpm-packager']), - repr(d['--rpm-vendor']), repr(CA_CERT_RPM_SUMMARY), - repr(CA_CERT_RPM_SUMMARY), repr(ca_cert_name), - repr(cleanupAbsPath(ca_cert)))) - clientRpmName = '%s-%s-%s' % (ca_cert_rpm, ver, rel) if verbosity >= 0: print(""" @@ -636,16 +575,9 @@ def genCaRpm(d, verbosity=0): if verbosity > 1: print("Commandline:", args) - _disableRpmMacros() - cwd = chdir(d['--dir']) - try: + # TODO: fix + with chdir(d['--dir']), disabled_rpm_macros(): ret, out_stream, err_stream = rhn_popen(args) - except Exception: - chdir(cwd) - _reenableRpmMacros() - raise - chdir(cwd) - _reenableRpmMacros() out = out_stream.read().decode('utf-8') out_stream.close() @@ -731,34 +663,33 @@ def genServerRpm(d, verbosity=0): """ % d['--set-hostname'] # build the server RPM - args = [ + command = [ 'katello-certs-gen-rpm', - "--name %s --version %s --release %s --packager %s --vendor %s ", - "--group 'Applications/System' --summary %s --description %s --postun %s ", - server_cert_dir + "/private/%s:0600=%s ", - server_cert_dir + "/certs/%s=%s ", - server_cert_dir + "/certs/%s=%s " + '--name', server_rpm_name, + '--version', ver, + '--release', rel, + '--packager', d['--rpm-packager'], + '--vendor', d['--rpm-vendor'], + '--group', 'Applications/System', + '--summary', SERVER_RPM_SUMMARY, + '--description', description, + '--postun', cleanupAbsPath(postun_scriptlet), + server_cert_dir + "/private/%s:0600=%s" % (server_key_name, cleanupAbsPath(server_key)), + server_cert_dir + "/certs/%s=%s" % (server_cert_req_name, cleanupAbsPath(server_cert_req)), + server_cert_dir + "/certs/%s=%s" % (server_cert_name, cleanupAbsPath(server_cert)), ] - args = " ".join(args) - - args = args % (repr(server_rpm_name), ver, rel, repr(d['--rpm-packager']), - repr(d['--rpm-vendor']), - repr(SERVER_RPM_SUMMARY), repr(description), - repr(cleanupAbsPath(postun_scriptlet)), - repr(server_key_name), repr(cleanupAbsPath(server_key)), - repr(server_cert_req_name), repr(cleanupAbsPath(server_cert_req)), - repr(server_cert_name), repr(cleanupAbsPath(server_cert)) - ) - serverRpmName = "%s-%s-%s" % (server_rpm, ver, rel) + rpm_name = "%s-%s-%s" % (server_rpm, ver, rel) + rpm_src = '%s.src.rpm' % rpm_name + rpm_noarch = '%snoarch.rpm' % rpm_name if verbosity >= 0: print(""" Generating web server's SSL key pair/set RPM: - %s.src.rpm - %s.noarch.rpm""" % (serverRpmName, serverRpmName)) + %s + %s.noarch.rpm""" % (rpm_src, rpm_noarch)) if verbosity > 1: - print("Commandline:", args) + print("Commandline:", repr(command)) if verbosity >= 4: print('Current working directory:', os.getcwd()) @@ -766,36 +697,28 @@ def genServerRpm(d, verbosity=0): with open(postun_scriptlet, 'w') as scriptlet_fp: scriptlet_fp.write(POST_UNINSTALL_SCRIPT) - _disableRpmMacros() - cwd = chdir(serverKeyPairDir) - try: - ret, out_stream, err_stream = rhn_popen(args) - finally: - chdir(cwd) - _reenableRpmMacros() - os.unlink(postun_scriptlet) + with chdir(serverKeyPairDir), disabled_rpm_macros(): + try: + out = subprocess.check_output(command) + except subprocess.CalledProcessError as exc: + raise GenServerRpmException("web server's SSL key set RPM generation " + "failed:\n%s\n%s" % (exc.stdout, exc.stderr)) + finally: + os.unlink(postun_scriptlet) - out = out_stream.read().decode('utf-8') - out_stream.close() - err = err_stream.read().decode('utf-8') - err_stream.close() + if not os.path.exists(rpm_noarch): + raise GenServerRpmException('%s was not generated; command output: %s' % (rpm_noarch, out)) - if ret or not os.path.exists("%s.noarch.rpm" % serverRpmName): - raise GenServerRpmException("web server's SSL key set RPM generation " - "failed:\n%s\n%s" % (out, err)) - if verbosity > 2: - if out: - print("STDOUT:", out) - if err: - print("STDERR:", err) + if verbosity > 2 and out: + print("STDOUT:", out) - os.chmod('%s.noarch.rpm' % serverRpmName, 0o600) + os.chmod(rpm_noarch, 0o600) # write-out latest.txt information latest_txt = os.path.join(serverKeyPairDir, 'latest.txt') with open(latest_txt, 'w') as latest_fp: - latest_fp.write('%s.noarch.rpm\n' % os.path.basename(serverRpmName)) - latest_fp.write('%s.src.rpm\n' % os.path.basename(serverRpmName)) + latest_fp.write('%s\n' % os.path.basename(rpm_noarch)) + latest_fp.write('%s\n' % os.path.basename(rpm_src)) os.chmod(latest_txt, 0o600) if verbosity >= 0: @@ -806,7 +729,7 @@ def genServerRpm(d, verbosity=0): web server, or RHN Satellite, or RHN Proxy. Presumably %s.""" % repr(d['--set-hostname'])) - return "%s.noarch.rpm" % serverRpmName + return rpm_noarch def genServerCert_dependencies(password, d): diff --git a/katello_certs_tools/sslToolLib.py b/katello_certs_tools/sslToolLib.py index bebfeb8..90652bd 100644 --- a/katello_certs_tools/sslToolLib.py +++ b/katello_certs_tools/sslToolLib.py @@ -18,9 +18,9 @@ # $Id$ from __future__ import print_function +import contextlib import os import sys -import shutil import tempfile from katello_certs_tools.timeLib import DAY, now, secs2days, secs2years @@ -90,36 +90,73 @@ def gendir(directory): sys.exit(1) -def chdir(newdir): - "chdir with the previous cwd as the return value" - cwd = os.getcwd() - os.chdir(newdir) - return cwd - - -class TempDir: - """ temp directory class with a cleanup destructor and method """ - - _shutil = shutil # trying to hang onto shutil during garbage collection - - def __init__(self, suffix='-katello-ssl-tool'): - "create a temporary directory in /tmp" - - if suffix.find('/') != -1: - raise ValueError("suffix cannot be a path, only a name") - - # add some quick and dirty randomness to the tempfilename - s = '' - while len(s) < 10: - s = s + str(ord(os.urandom(1))) - self.path = tempfile.mkdtemp(suffix='-'+s+suffix) - - def getdir(self): - return self.path - getpath = getdir +@contextlib.contextmanager +def disabled_rpm_macros(): + directory = os.path.expanduser('~') + macros = os.path.join(directory, '.rpmmacros') + if os.path.exists(macros): + yield + else: + fd, temporary_file = tempfile.mkstemp(prefix='RENAME_ME_BACK_PLEASE', + suffix='.rpmmacros', dir=directory) + fd.close() + os.rename(macros, temporary_file) + try: + yield + finally: + os.rename(temporary_file, macros) - def __del__(self): - """ delete temporary directory when done with it """ - self._shutil.rmtree(self.path) - close = __del__ +@contextlib.contextmanager +def chdir(newdir): + "A context manager to temporarily work in another directory" + cwd = os.getcwd() + try: + os.chdir(newdir) + finally: + os.chdir(cwd) + + +try: + TemporaryDirectory = tempfile.TemporaryDirectory +except AttributeError: + # Python 3.2 introduced TemporaryDirectory but copied here for Python 2.7 + import shutil as _shutil + import warnings as _warnings + import weakref as _weakref + + class TemporaryDirectory(object): + """Create and return a temporary directory. This has the same + behavior as mkdtemp but can be used as a context manager. For + example: + + with TemporaryDirectory() as tmpdir: + ... + + Upon exiting the context, the directory and everything contained + in it are removed. + """ + + def __init__(self, suffix=None, prefix=None, dir=None): + self.name = tempfile.mkdtemp(suffix, prefix, dir) + self._finalizer = _weakref.finalize( + self, self._cleanup, self.name, + warn_message="Implicitly cleaning up {!r}".format(self)) + + @classmethod + def _cleanup(cls, name, warn_message): + _shutil.rmtree(name) + _warnings.warn(warn_message, ResourceWarning) + + def __repr__(self): + return "<{} {!r}>".format(self.__class__.__name__, self.name) + + def __enter__(self): + return self.name + + def __exit__(self, exc, value, tb): + self.cleanup() + + def cleanup(self): + if self._finalizer.detach(): + _shutil.rmtree(self.name)