From c1c13b9ef64f6d1a43b6fc951f272e5cfca0d5e7 Mon Sep 17 00:00:00 2001 From: Matthieu Huin Date: Thu, 3 Dec 2020 11:36:05 +0100 Subject: [PATCH] encrypt: add --public-key argument the --public-key argument allows the user to provide a local copy of a project's public key instead of querying the REST API to fetch it. Change-Id: Id37f991f2a16916e1c4338fb336b9b21eb23b498 --- zuulclient/cmd/__init__.py | 48 +++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/zuulclient/cmd/__init__.py b/zuulclient/cmd/__init__.py index 6a689ff..65b1b05 100644 --- a/zuulclient/cmd/__init__.py +++ b/zuulclient/cmd/__init__.py @@ -17,6 +17,7 @@ import configparser import logging import os import prettytable +import shutil import sys import tempfile import textwrap @@ -27,6 +28,10 @@ from zuulclient.utils import get_default from zuulclient.utils import encrypt_with_openssl +class ArgumentException(Exception): + pass + + class ZuulClient(): app_name = 'zuul-client' app_description = 'Zuul User CLI' @@ -100,7 +105,7 @@ class ZuulClient(): if (self.args.oldrev is not None) or \ (self.args.newrev is not None): if self.args.oldrev == self.args.newrev: - raise Exception( + raise ArgumentException( "The old and new revisions must not be the same.") # if they're not set, we pad them out to zero if self.args.oldrev is None: @@ -109,9 +114,9 @@ class ZuulClient(): self.args.newrev = '0000000000000000000000000000000000000000' if self.args.func == self.dequeue: if self.args.change is None and self.args.ref is None: - raise Exception("Change or ref needed.") + raise ArgumentException("Change or ref needed.") if self.args.change is not None and self.args.ref is not None: - raise Exception( + raise ArgumentException( "The 'change' and 'ref' arguments are mutually exclusive.") def readConfig(self): @@ -128,7 +133,8 @@ class ZuulClient(): if os.path.exists(os.path.expanduser(fp)): self.config.read(os.path.expanduser(fp)) return - raise Exception("Unable to locate config file in %s" % locations) + raise ArgumentException( + "Unable to locate config file in %s" % locations) def setup_logging(self): """Client logging does not rely on conf file""" @@ -157,12 +163,12 @@ class ZuulClient(): tenant_scope = client.info.get('tenant', None) if self.args.tenant != '': if tenant_scope is not None and tenant_scope != self.args.tenant: - raise Exception( + raise ArgumentException( 'Error: Zuul API URL %s is ' 'scoped to tenant "%s"' % (client.base_url, tenant_scope)) else: if tenant_scope is None: - raise Exception( + raise ArgumentException( "Error: the --tenant argument is required" ) @@ -440,10 +446,15 @@ class ZuulClient(): def add_encrypt_subparser(self, subparsers): cmd_encrypt = subparsers.add_parser( 'encrypt', help='Encrypt a secret to be used in a project\'s jobs') + cmd_encrypt.add_argument('--public-key', + help='path to project public key ' + '(bypass API call)', + metavar='/path/to/pubkey', + required=False, default=None) cmd_encrypt.add_argument('--tenant', help='tenant name', required=False, default='') cmd_encrypt.add_argument('--project', help='project name', - required=True) + required=False, default=None) cmd_encrypt.add_argument('--no-strip', action='store_true', help='Do not strip whitespace from beginning ' 'or end of input.', @@ -474,6 +485,10 @@ class ZuulClient(): cmd_encrypt.set_defaults(func=self.encrypt) def encrypt(self): + if self.args.project is None and self.args.public_key is None: + raise ArgumentException( + 'Either provide a public key or a project to continue' + ) if self.args.infile: try: with open(self.args.infile) as f: @@ -489,13 +504,17 @@ class ZuulClient(): plaintext = plaintext.strip() pubkey_file = tempfile.NamedTemporaryFile(delete=False) self.log.debug('Creating temporary key file %s' % pubkey_file.name) - client = self.get_client() - self._check_tenant_scope(client) - try: - key = client.get_key(self.args.tenant, self.args.project) - pubkey_file.write(str.encode(key)) - pubkey_file.close() + try: + if self.args.public_key is not None: + self.log.debug('Using local public key') + shutil.copy(self.args.public_key, pubkey_file.name) + else: + client = self.get_client() + self._check_tenant_scope(client) + key = client.get_key(self.args.tenant, self.args.project) + pubkey_file.write(str.encode(key)) + pubkey_file.close() self.log.debug('Calling openssl') ciphertext_chunks = encrypt_with_openssl(pubkey_file.name, plaintext, @@ -522,6 +541,9 @@ class ZuulClient(): else: print(output) return_code = True + except ArgumentException as e: + # do not log and re-raise, caught later + raise e except Exception as e: self.log.exception(e) return_code = False