Changeset - 6179b9f6ceb6
[Not reviewed]
0 3 1
Branko Majic (branko) - 6 years ago 2018-04-09 22:51:02
branko@majic.rs
GC-22: Added options to parser for accepting CSR for issuing/renewing certificates:

- Implemented functional test for verifying option availability.
- Added CSR option to server, client, and renew commands.
- Make the new CSR option and --new-private-key option exclusive with
each other.
4 files changed with 110 insertions and 2 deletions:
0 comments (0 inline, 0 general)
functional_tests/test_csr.py
Show inline comments
 
new file 100644
 
# -*- 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/>.
 
#
 

	
 

	
 
from .base import run_command
 

	
 

	
 
def test_commands_report_csr_option_as_available():
 
    # John is in the process of testing a new project deployment. As
 
    # part of the process, he generates private keys on the servers,
 
    # and needs to issue corresponding certificates.
 

	
 
    # John knows that he can generate both private key and certificate
 
    # using Gimmecert, but in this particular case he would like to
 
    # keep his private keys on the server side intact. John goes ahead
 
    # and checks if the issuance commands support passing-in a CSR
 
    # instead of using locally generated private key.
 

	
 
    # He checks help for the server command.
 
    stdout, stderr, exit_code = run_command("gimmecert", "server", "-h")
 

	
 
    # John notcies the option for passing-in a CSR.
 
    assert " --csr " in stdout
 
    assert " -c " in stdout
 

	
 
    # He checks help for the client command.
 
    stdout, stderr, exit_code = run_command("gimmecert", "client", "-h")
 

	
 
    # John notcies the option for passing-in a CSR.
 
    assert " --csr " in stdout
 
    assert " -c " in stdout
 

	
 
    # He checks help for the renew command.
 
    stdout, stderr, exit_code = run_command("gimmecert", "renew", "-h")
 

	
 
    # John notcies the option for passing-in a CSR.
 
    assert " --csr " in stdout
 
    assert " -c " in stdout
functional_tests/test_renew.py
Show inline comments
 
@@ -46,7 +46,7 @@ def test_renew_command_available_with_help():
 
    assert exit_code == 0
 
    assert stderr == ""
 
    assert stdout.startswith("usage: gimmecert renew")
 
    assert stdout.split('\n')[0].endswith("{server,client} entity_name")  # First line of help
 
    assert stdout.split('\n')[1].endswith("{server,client} entity_name")  # Second line of help (first is options)
 

	
 

	
 
def test_renew_command_requires_initialised_hierarchy(tmpdir):
gimmecert/cli.py
Show inline comments
 
@@ -108,6 +108,8 @@ def setup_server_subcommand_parser(parser, subparsers):
 
    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.''')
 
    subparser.add_argument('--csr', '-c', type=str, default=None, help='''Do not generate server private key locally, and use the passed-in \
 
    certificate signing request (CSR) instead.''')
 

	
 
    def server_wrapper(args):
 
        project_directory = os.getcwd()
 
@@ -123,6 +125,8 @@ def setup_server_subcommand_parser(parser, subparsers):
 
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.')
 
    subparser.add_argument('--csr', '-c', type=str, help='''Do not generate client private key locally, and use the passed-in \
 
    certificate signing request (CSR) instead.''')
 

	
 
    def client_wrapper(args):
 
        project_directory = os.getcwd()
 
@@ -139,7 +143,14 @@ 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')
 
    subparser.add_argument('--new-private-key', '-p', action='store_true', help="Generate new private key for renewal. Default is to keep the existing key.")
 

	
 
    new_private_key_or_csr_group = subparser.add_mutually_exclusive_group()
 

	
 
    new_private_key_or_csr_group.add_argument('--new-private-key', '-p', action='store_true', help='''Generate new private key for renewal. \
 
    Default is to keep the existing key. Mutually exclusive with the --csr option.''')
 
    new_private_key_or_csr_group.add_argument('--csr', '-c', type=str, help='''Do not use local private key and public key information from \
 
    existing certificate, and use the passed-in certificate signing request (CSR) instead. If private key exists, it will be removed. \
 
    Mutually exclusive with the --new-private-key option.''')
 

	
 
    def renew_wrapper(args):
 
        project_directory = os.getcwd()
tests/test_cli.py
Show inline comments
 
@@ -238,9 +238,17 @@ VALID_CLI_INVOCATIONS = [
 
    ("gimmecert.cli.server", ["gimmecert", "server", "--update-dns-names", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "-u", "myserver"]),
 

	
 
    # server, CSR long and short option
 
    ("gimmecert.cli.server", ["gimmecert", "server", "--csr", "myserver.csr.pem", "myserver"]),
 
    ("gimmecert.cli.server", ["gimmecert", "server", "-c", "myserver.csr.pem", "myserver"]),
 

	
 
    # client, no options
 
    ("gimmecert.cli.client", ["gimmecert", "client", "myclient"]),
 

	
 
    # client, CSR long and short option
 
    ("gimmecert.cli.client", ["gimmecert", "client", "--csr", "myclient.csr.pem", "myclient"]),
 
    ("gimmecert.cli.client", ["gimmecert", "client", "-c", "myclient.csr.pem", "myclient"]),
 

	
 
    # renew, no options
 
    ("gimmecert.cli.renew", ["gimmecert", "renew", "server", "myserver"]),
 
    ("gimmecert.cli.renew", ["gimmecert", "renew", "client", "myclient"]),
 
@@ -251,6 +259,12 @@ VALID_CLI_INVOCATIONS = [
 
    ("gimmecert.cli.renew", ["gimmecert", "renew", "-p", "server", "myserver"]),
 
    ("gimmecert.cli.renew", ["gimmecert", "renew", "-p", "client", "myclient"]),
 

	
 
    # renew, CSR long and short option
 
    ("gimmecert.cli.renew", ["gimmecert", "renew", "--csr", "myserver.csr.pem", "server", "myserver"]),
 
    ("gimmecert.cli.renew", ["gimmecert", "renew", "--csr", "myclient.csr.pem", "client", "myclient"]),
 
    ("gimmecert.cli.renew", ["gimmecert", "renew", "-c", "myserver.csr.pem", "server", "myserver"]),
 
    ("gimmecert.cli.renew", ["gimmecert", "renew", "-c", "myclient.csr.pem", "client", "myclient"]),
 

	
 
    # status, no options
 
    ("gimmecert.cli.status", ["gimmecert", "status"]),
 
]
 
@@ -578,3 +592,31 @@ def test_status_command_invoked_with_correct_parameters(mock_status, tmpdir):
 
    gimmecert.cli.main()
 

	
 
    mock_status.assert_called_once_with(sys.stdout, sys.stderr, tmpdir.strpath)
 

	
 

	
 
@mock.patch('sys.argv', ['gimmecert', 'renew', 'server', '--new-private-key', '--csr', 'myserver.csr.pem', 'myserver'])
 
@mock.patch('gimmecert.cli.renew')
 
def test_renew_command_fails_if_both_new_private_key_and_csr_options_are_specified_for_server(mock_renew, tmpdir):
 
    # This should ensure we don't accidentally create artifacts
 
    # outside of test directory.
 
    tmpdir.chdir()
 

	
 
    with pytest.raises(SystemExit) as e_info:
 
        gimmecert.cli.main()
 

	
 
    assert mock_renew.called is False
 
    assert e_info.value.code != 0
 

	
 

	
 
@mock.patch('sys.argv', ['gimmecert', 'renew', 'client', '--new-private-key', '--csr', 'myclient.csr.pem', 'myclient'])
 
@mock.patch('gimmecert.cli.renew')
 
def test_renew_command_fails_if_both_new_private_key_and_csr_options_are_specified_for_client(mock_renew, tmpdir):
 
    # This should ensure we don't accidentally create artifacts
 
    # outside of test directory.
 
    tmpdir.chdir()
 

	
 
    with pytest.raises(SystemExit) as e_info:
 
        gimmecert.cli.main()
 

	
 
    assert mock_renew.called is False
 
    assert e_info.value.code != 0
0 comments (0 inline, 0 general)