Files
@ dcac57e9457b
Branch filter:
Location: gimmecert/gimmecert/cli.py
dcac57e9457b
6.2 KiB
text/x-python
GC-18: Error-out in case renew is called on uninitialised hierarchy:
- Added functional test for scenario.
- Implemented dummy renew command that for now just verifies the
hierarchy has been initialised.
- Implemented unit tests.
- Added functional test for scenario.
- Implemented dummy renew command that for now just verifies the
hierarchy has been initialised.
- Implemented unit tests.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | # -*- coding: utf-8 -*-
#
# Copyright (C) 2018 Branko Majic
#
# This file is part of Gimmecert.
#
# Gimmecert 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.
#
# Gimmecert 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
# Gimmecert. If not, see <http://www.gnu.org/licenses/>.
#
import argparse
import os
import sys
from .decorators import subcommand_parser, get_subcommand_parser_setup_functions
from .commands import client, help_, init, renew, server, usage, ExitCode
ERROR_GENERIC = 10
DESCRIPTION = """\
Issues server and client X.509 certificates using a local CA hierarchy.
Examples:
# Set-up and switch to directory - you can switch to existing directory too.
mkdir myproject/
cd myproject/
# Initialise the local CA hierarchy and all the necessary directories.
gimmecert init
# Issue a TLS server certificate with only the server name in DNS subject alternative name.
gimmecert server myserver
# Issue a TLS server certificate with additional DNS subject alternative names.
gimmecert server myserver extradns1.local extradns2.example.com
# Issue a TLS client certificate.
gimmecert client myclient
# Renew a TLS server certificate with updated DNS subject alternative names. Keeps the private key if any.
gimmecert server myserver wrongdns.local
gimmecert server --update-dns-names myserver correctdns1.local correctdns2.local
# Renew a TLS server certificate removing extra DNS subject alternative names. Keeps the private key if any.
gimmecert server myserver dontneedthisname.local
gimmecert server myserver --update-dns-names
"""
@subcommand_parser
def setup_init_subcommand_parser(parser, subparsers):
subparser = subparsers.add_parser('init', description='Initialise CA hierarchy.')
subparser.add_argument('--ca-base-name', '-b', help="Base name to use for CA naming. Default is to use the working directory base name.")
subparser.add_argument('--ca-hierarchy-depth', '-d', type=int, help="Depth of CA hierarchy to generate. Default is 1", default=1)
def init_wrapper(args):
project_directory = os.getcwd()
if args.ca_base_name is None:
args.ca_base_name = os.path.basename(project_directory)
return init(sys.stdout, sys.stderr, project_directory, args.ca_base_name, args.ca_hierarchy_depth)
subparser.set_defaults(func=init_wrapper)
return subparser
@subcommand_parser
def setup_help_subcommand_parser(parser, subparsers):
subparser = subparsers.add_parser('help', description='shows help')
def help_wrapper(args):
return help_(sys.stdout, sys.stderr, parser)
subparser.set_defaults(func=help_wrapper)
return subparser
@subcommand_parser
def setup_server_subcommand_parser(parser, subparsers):
subparser = subparsers.add_parser('server', description='Issues server certificate.')
subparser.add_argument('entity_name', help='Name of the server entity.')
subparser.add_argument('dns_name', nargs='*', help='Additional DNS names to include in subject alternative name.')
subparser.add_argument('--update-dns-names', '-u', action='store_true', help='''Renew certificate for an existing server entity by reusing \
the private key, but replacing the DNS subject alternative names with listed values (if any). \
If entity does not exist, this option has no effect, and a new private key/certificate will be generated as usual.''')
def server_wrapper(args):
project_directory = os.getcwd()
return server(sys.stdout, sys.stderr, project_directory, args.entity_name, args.dns_name, args.update_dns_names)
subparser.set_defaults(func=server_wrapper)
return subparser
@subcommand_parser
def setup_client_subcommand_parser(parser, subparsers):
subparser = subparsers.add_parser('client', description='Issue client certificate.')
subparser.add_argument('entity_name', help='Name of the client entity.')
def client_wrapper(args):
project_directory = os.getcwd()
return client(sys.stdout, sys.stderr, project_directory, args.entity_name)
subparser.set_defaults(func=client_wrapper)
return subparser
@subcommand_parser
def setup_renew_subcommand_parser(parser, subparsers):
subparser = subparsers.add_parser('renew', description='Renews existing certificates.')
subparser.add_argument('entity_type', help='Type of entity to renew.', choices=['server', 'client'])
subparser.add_argument('entity_name', help='Name of the entity')
def renew_wrapper(args):
project_directory = os.getcwd()
return renew(sys.stdout, sys.stderr, project_directory, args.entity_type, args.entity_name)
subparser.set_defaults(func=renew_wrapper)
return subparser
def get_parser():
"""
Sets-up and returns a CLI argument parser.
:returns: argparse.ArgumentParser -- argument parser for CLI.
"""
parser = argparse.ArgumentParser(description=DESCRIPTION, formatter_class=argparse.RawDescriptionHelpFormatter)
def usage_wrapper(args):
return usage(sys.stdout, sys.stderr, parser)
parser.set_defaults(func=usage_wrapper)
subparsers = parser.add_subparsers()
for setup_subcommad_parser in get_subcommand_parser_setup_functions():
setup_subcommad_parser(parser, subparsers)
return parser
def main():
"""
This function is a CLI entry point for the tool. It is a thin
wrapper around the argument parser, and underlying command
implementation.
In order for this to work, the parser needs to register the
callback function as a default parameter for attribute
'func'. This attribute is then invoked by the main function,
passing-in all the parsed arguments while at it.
"""
parser = get_parser()
args = parser.parse_args()
status_code = args.func(args)
if status_code != ExitCode.SUCCESS:
exit(status_code)
|