From 8a7bf0980b81f1b7ae95df7a6e939daaa9853f76 Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Wed, 15 Jan 2025 09:53:32 -0600 Subject: [PATCH] Update pki CLI to use ArgumentParser The pki CLI has been updated to parse the main arguments (which are also defined in MainCLI.java) using ArgumentParser, determine the subcommand, then pass the remaining arguments to the Python or Java module corresponding to the subcommand. It does not use subparsers like in pki-server since the pki CLI does not have the list of Java subcommands. The pki CLI have options that might conflict with other options in some subcommands. That will be addressed separately later. The help message for default message format in MainCLI.java has also been updated. --- base/common/python/pki/cli/__init__.py | 33 +- base/common/python/pki/cli/main.py | 370 +++++++++++------- base/common/python/pki/cli/pkcs12.py | 8 +- base/server/python/pki/server/cli/cert.py | 2 +- .../com/netscape/cmstools/cli/MainCLI.java | 2 +- 5 files changed, 236 insertions(+), 179 deletions(-) diff --git a/base/common/python/pki/cli/__init__.py b/base/common/python/pki/cli/__init__.py index bba418940ed..682a133feab 100644 --- a/base/common/python/pki/cli/__init__.py +++ b/base/common/python/pki/cli/__init__.py @@ -20,9 +20,7 @@ # import collections -import getopt import logging -import sys from six import itervalues logger = logging.getLogger(__name__) @@ -184,40 +182,11 @@ def parse_args(self, args): return (module, module_args) - def execute(self, argv, args=None): + def execute(self, argv, args=None): # pylint: disable=W0613 ''' :param argv: Argument values :param args: Parsed arguments ''' - try: - opts, args = getopt.getopt(argv, 'v', [ - 'verbose', 'help']) - - except getopt.GetoptError as e: - logger.error(e) - self.print_help() - sys.exit(1) - - if len(args) == 0: - self.print_help() - sys.exit() - - for o, _ in opts: - if o == '--debug': - logging.getLogger().setLevel(logging.DEBUG) - - elif o in ('-v', '--verbose'): - logging.getLogger().setLevel(logging.INFO) - - elif o == '--help': - self.print_help() - sys.exit() - - else: - logger.error('Unknown option: %s', o) - self.print_help() - sys.exit(1) - (module, module_argv) = self.parse_args(argv) module.execute(module_argv) diff --git a/base/common/python/pki/cli/main.py b/base/common/python/pki/cli/main.py index 5cd28a5eac9..8cfd07b38dd 100644 --- a/base/common/python/pki/cli/main.py +++ b/base/common/python/pki/cli/main.py @@ -18,6 +18,7 @@ # All rights reserved. # +import argparse import logging import os import shlex @@ -41,40 +42,149 @@ def __init__(self): super(PKICLI, self).__init__( 'pki', 'PKI command-line interface') - self.database = None + self.properties = {} + + self.nss_database = None + self.nss_password = None + self.nss_password_file = None + self.nss_password_conf = None + + self.token = None + self.nickname = None + self.username = None self.password = None self.password_file = None - self.password_conf = None - self.token = None + + self.url = None + self.protocol = None + self.hostname = None + self.port = None + self.subsystem = None + self.api = None + self.output = None + self.message_format = None + + self.reject_cert_status = False + self.ignore_cert_status = False self.ignore_banner = False self.add_module(pki.cli.password.PasswordCLI()) self.add_module(pki.cli.pkcs12.PKCS12CLI()) + def create_parser(self, subparsers=None): + + # create main parser + self.parser = argparse.ArgumentParser( + prog=self.name, + add_help=False) + + self.parser.add_argument( + '--client-type', + default='java') + self.parser.add_argument( + '-D', + action='append') + + self.parser.add_argument('-d') + self.parser.add_argument('-c') + self.parser.add_argument('-C') + self.parser.add_argument('-f') + self.parser.add_argument('--token') + self.parser.add_argument('-n') + self.parser.add_argument('-u') + self.parser.add_argument('-w') + self.parser.add_argument('-W') + + self.parser.add_argument('-U') + self.parser.add_argument('-P') + self.parser.add_argument('-h') + self.parser.add_argument('-p') + self.parser.add_argument('-t') + self.parser.add_argument('--api') + self.parser.add_argument('--output') + self.parser.add_argument('--message-format') + self.parser.add_argument('--reject-cert-status') + self.parser.add_argument('--ignore-cert-status') + self.parser.add_argument( + '--ignore-banner', + action='store_true') + + self.parser.add_argument( + '-v', + '--verbose', + action='store_true') + self.parser.add_argument( + '--debug', + action='store_true') + self.parser.add_argument( + '--help', + action='store_true') + self.parser.add_argument( + '--version', + action='store_true') + + self.parser.add_argument( + 'remainder', + nargs=argparse.REMAINDER) + + # create parsers in modules + super().create_parser() + def get_full_module_name(self, module_name): return module_name def print_help(self): print('Usage: pki [OPTIONS]') + print() - print(' --client-type PKI client type (default: java)') - print(' -d NSS database location ' + + print(' --client-type PKI client type (default: java)') + print(' -D = System propery') + + print(' -d NSS database location ' + '(default: ~/.dogtag/nssdb)') - print(' -c NSS database password ' + + print(' -c NSS database password ' + '(mutually exclusive to -C and -f options)') - print(' -C NSS database password file ' + + print(' -C NSS database password file ' + '(mutually exclusive to -c and -f options)') - print(' -f NSS database password configuration ' + + print(' -f NSS database password configuration ' + '(mutually exclusive to -c and -C options)') - print(' --token Security token name') + print(' --token Security token name') + print(' -n Nickname for client certificate authentication ' + + '(mutually exclusive to -u option)') + print(' -u Username for basic authentication ' + + '(mutually exclusive to -n option)') + print(' -w Password for basic authentication ' + + '(mutually exclusive to -W option)') + print(' -W Password file for basic authentication ' + + '(mutually exclusive to -w option)') + + print(' -U PKI server URL') + print(' -P Protocol (default: https)') + print(' -h Hostname') + print(' -p Port (default: 8443)') + print(' -t Subsystem type (deprecated)') + + print(' --api API version: v1, v2') + print(' --output Folder to store HTTP messages') + print(' --message-format Message format: json (default), xml') + + print(' --reject-cert-status Comma-separated list of rejected ' + + 'certificate validity statuses') + print(' --ignore-cert-status Comma-separated list of ignored ' + + 'certificate validity statuses') + print(' --ignore-banner Ignore banner') print() print(' -v, --verbose Run in verbose mode.') print(' --debug Show debug messages.') print(' --help Show help message.') + print(' --version Show version number.') print() super(PKICLI, self).print_help() + def print_version(self): + print('PKI Command-Line Interface %s' % pki.implementation_version()) + def set_nss_default_db_type(self): """Validate NSS_DEFAULT_DB_TYPE @@ -90,7 +200,7 @@ def set_nss_default_db_type(self): ) return dbtype - def execute_java(self, args, properties=None, stdout=sys.stdout): + def execute_java(self, args, stdout=sys.stdout): self.set_nss_default_db_type() @@ -110,36 +220,75 @@ def execute_java(self, args, properties=None, stdout=sys.stdout): java_fips_cmd ]) - if not properties: - properties = {} - - for name in properties: - option = '-D' + name + '=' + properties[name] + for name in self.properties: + option = '-D' + name + '=' + self.properties[name] cmd.append(option) logging_config = os.getenv('PKI_LOGGING_CONFIG') - if logging_config and 'java.util.logging.config.file' not in properties: + if logging_config and 'java.util.logging.config.file' not in self.properties: cmd.append('-Djava.util.logging.config.file=' + logging_config) cmd.append('com.netscape.cmstools.cli.MainCLI') # restore options for Java commands - if self.database: - cmd.extend(['-d', self.database]) + if self.nss_database: + cmd.extend(['-d', self.nss_database]) - if self.password: - cmd.extend(['-c', self.password]) + if self.nss_password is not None: + cmd.extend(['-c', self.nss_password]) - if self.password_file: - cmd.extend(['-C', self.password_file]) + if self.nss_password_file: + cmd.extend(['-C', self.nss_password_file]) - if self.password_conf: - cmd.extend(['-f', self.password_conf]) + if self.nss_password_conf: + cmd.extend(['-f', self.nss_password_conf]) if not pki.nssdb.internal_token(self.token): cmd.extend(['--token', self.token]) + if self.nickname: + cmd.extend(['-n', self.nickname]) + + if self.username: + cmd.extend(['-u', self.username]) + + if self.password is not None: + cmd.extend(['-w', self.password]) + + if self.password_file: + cmd.extend(['-W', self.password_file]) + + if self.url: + cmd.extend(['-U', self.url]) + + if self.protocol: + cmd.extend(['-P', self.protocol]) + + if self.hostname: + cmd.extend(['-h', self.hostname]) + + if self.port: + cmd.extend(['-p', self.port]) + + if self.subsystem: + cmd.extend(['-t', self.subsystem]) + + if self.api: + cmd.extend(['--api', self.api]) + + if self.output: + cmd.extend(['--output', self.output]) + + if self.message_format: + cmd.extend(['--message-format', self.message_format]) + + if self.reject_cert_status: + cmd.extend(['--reject-cert-status', self.reject_cert_status]) + + if self.ignore_cert_status: + cmd.extend(['--ignore-cert-status', self.ignore_cert_status]) + if self.ignore_banner: cmd.extend(['--ignore-banner']) @@ -162,128 +311,67 @@ def execute(self, argv, args=None): args = shlex.split(value) args.extend(argv) - client_type = 'java' - - properties = {} - pki_options = [] - command = None - cmd_args = [] - - # read pki options before the command - # remove options for Python module - - i = 0 - while i < len(args): - # if arg is a command, stop - if args[i][0] != '-': - command = args[i] - break - - # get properties - if args[i] == '-D': - try: - name, value = args[i + 1].split('=', 1) - properties[name] = value - except IndexError: - break - i = i + 2 - - # get database path - if args[i] == '-d': - try: - self.database = args[i + 1] - except IndexError: - break - pki_options.append(args[i]) - pki_options.append(args[i + 1]) - i = i + 2 - - # get database password - elif args[i] == '-c': - try: - self.password = args[i + 1] - except IndexError: - break - pki_options.append(args[i]) - pki_options.append(args[i + 1]) - i = i + 2 - - # get database password file path - elif args[i] == '-C': - try: - self.password_file = args[i + 1] - except IndexError: - break - pki_options.append(args[i]) - pki_options.append(args[i + 1]) - i = i + 2 - - # get database password config path - elif args[i] == '-f': - try: - self.password_conf = args[i + 1] - except IndexError: - break - pki_options.append(args[i]) - pki_options.append(args[i + 1]) - i = i + 2 - - # get token name - elif args[i] == '--token': - try: - self.token = args[i + 1] - except IndexError: - break - pki_options.append(args[i]) - pki_options.append(args[i + 1]) - i = i + 2 - - # check ignore banner option - elif args[i] == '--ignore-banner': - self.ignore_banner = True - pki_options.append(args[i]) - i = i + 1 - - # check verbose option - elif args[i] == '-v' or args[i] == '--verbose': - logging.getLogger().setLevel(logging.INFO) - pki_options.append(args[i]) - i = i + 1 - - # check debug option - elif args[i] == '--debug': - logging.getLogger().setLevel(logging.DEBUG) - pki_options.append(args[i]) - i = i + 1 - - # get client type - elif args[i] == '--client-type': - try: - client_type = args[i + 1] - except IndexError: - break - pki_options.append(args[i]) - pki_options.append(args[i + 1]) - i = i + 2 - - else: # otherwise, save the arg for the next module - cmd_args.append(args[i]) - i = i + 1 - - # save the rest of the args - while i < len(args): - cmd_args.append(args[i]) - i = i + 1 - - logger.debug('PKI options: %s', ' '.join(pki_options)) - logger.debug('PKI command: %s %s', command, ' '.join(cmd_args)) + args = self.parser.parse_args(args=argv) + + if args.help: + self.print_help() + return + + if args.version: + self.print_version() + return + + if args.debug: + logging.getLogger().setLevel(logging.DEBUG) + + elif args.verbose: + logging.getLogger().setLevel(logging.INFO) + + client_type = args.client_type + + if args.D: + for param in args.D: + name, value = param.split('=', 1) + self.properties[name] = value + + self.nss_database = args.d + self.nss_password = args.c + self.nss_password_file = args.C + self.nss_password_conf = args.f + + self.token = args.token + self.nickname = args.n + self.username = args.u + self.password = args.w + self.password_file = args.W + + self.url = args.U + self.protocol = args.P + self.hostname = args.h + self.port = args.p + self.subsystem = args.t + self.api = args.api + self.output = args.output + self.message_format = args.message_format + + self.reject_cert_status = args.reject_cert_status + self.ignore_cert_status = args.ignore_cert_status + self.ignore_banner = args.ignore_banner + + command = args.remainder[0] + logger.debug('Command: %s', command) if client_type == 'python' or command in PYTHON_COMMANDS: - (module, module_args) = self.parse_args(cmd_args) + module = self.find_module(command) + logger.debug('Module: %s', module.get_full_name()) + + module_args = args.remainder[1:] + logger.debug('Arguments: %s', ' '.join(module_args)) + module.execute(module_args) elif client_type == 'java': - self.execute_java(cmd_args, properties) + self.execute_java(args.remainder) else: raise Exception('Unsupported client type: ' + client_type) diff --git a/base/common/python/pki/cli/pkcs12.py b/base/common/python/pki/cli/pkcs12.py index 3b097bd4889..08a8c144844 100644 --- a/base/common/python/pki/cli/pkcs12.py +++ b/base/common/python/pki/cli/pkcs12.py @@ -230,11 +230,11 @@ def execute(self, argv, args=None): cert_file = os.path.join(tmpdir, 'ca-cert.pem') nssdb = pki.nssdb.NSSDatabase( - main_cli.database, + main_cli.nss_database, token=main_cli.token, - password=main_cli.password, - password_file=main_cli.password_file, - password_conf=main_cli.password_conf) + password=main_cli.nss_password, + password_file=main_cli.nss_password_file, + password_conf=main_cli.nss_password_conf) for cert_info in certs: diff --git a/base/server/python/pki/server/cli/cert.py b/base/server/python/pki/server/cli/cert.py index 6caef1bfb88..82bdf451757 100644 --- a/base/server/python/pki/server/cli/cert.py +++ b/base/server/python/pki/server/cli/cert.py @@ -1250,7 +1250,7 @@ def execute(self, argv, args=None): if args.cert: all_certs = False fix_certs.append(args.cert) - + if args.extra_cert: # TODO: add support for hex serial number try: diff --git a/base/tools/src/main/java/com/netscape/cmstools/cli/MainCLI.java b/base/tools/src/main/java/com/netscape/cmstools/cli/MainCLI.java index 0c4b7ee95fb..71a54c65aac 100644 --- a/base/tools/src/main/java/com/netscape/cmstools/cli/MainCLI.java +++ b/base/tools/src/main/java/com/netscape/cmstools/cli/MainCLI.java @@ -237,7 +237,7 @@ public void createOptions() throws UnknownHostException { option = new Option(null, "ignore-banner", false, "Ignore access banner"); options.addOption(option); - option = new Option(null, "message-format", true, "Message format: xml (default), json"); + option = new Option(null, "message-format", true, "Message format: json (default), xml"); option.setArgName("format"); options.addOption(option);