Add encrypt subcommand
zuul-client can be used to encrypt a secret that can be then used in a project's jobs. Also improve the documentation section about using a configuration file. Change-Id: I10e56883f0b24ac429051af36f9bf58c7594c0ed
This commit is contained in:
parent
ff9b60a809
commit
5766ed1785
@ -67,6 +67,15 @@ Examples::
|
|||||||
zuul-client dequeue --tenant openstack --pipeline check --project example_project --change 5,1
|
zuul-client dequeue --tenant openstack --pipeline check --project example_project --change 5,1
|
||||||
zuul-client dequeue --tenant openstack --pipeline periodic --project example_project --ref refs/heads/master
|
zuul-client dequeue --tenant openstack --pipeline periodic --project example_project --ref refs/heads/master
|
||||||
|
|
||||||
|
Encrypt
|
||||||
|
^^^^^^^
|
||||||
|
.. program-output:: zuul-client encrypt --help
|
||||||
|
|
||||||
|
Examples::
|
||||||
|
|
||||||
|
zuul-client encrypt --tenant openstack --project config --infile .pypirc --outfile encrypted.yaml --secret-name pypi_creds --field-name pypirc
|
||||||
|
cat .pypirc | zuul-client encrypt --tenant openstack --project config
|
||||||
|
|
||||||
Enqueue
|
Enqueue
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
||||||
|
@ -6,7 +6,15 @@ Configuration
|
|||||||
The web client will look by default for a ``.zuul.conf`` file for its
|
The web client will look by default for a ``.zuul.conf`` file for its
|
||||||
configuration. The file should consist of a ``[webclient]`` section with at least
|
configuration. The file should consist of a ``[webclient]`` section with at least
|
||||||
the ``url`` attribute set. The optional ``verify_ssl`` can be set to False to
|
the ``url`` attribute set. The optional ``verify_ssl`` can be set to False to
|
||||||
disable SSL verifications when connecting to Zuul (defaults to True).
|
disable SSL verifications when connecting to Zuul (defaults to True). An
|
||||||
|
authentication token can also be stored in the configuration file under the attribute
|
||||||
|
``auth_token`` to avoid passing the token in the clear on the command line.
|
||||||
|
|
||||||
|
Here is an example of a ``.zuul.conf`` file that can be used with zuul-client:
|
||||||
|
|
||||||
|
.. literalinclude:: /examples/.zuul.conf
|
||||||
|
:language: ini
|
||||||
|
|
||||||
|
|
||||||
It is also possible to run the web client without a configuration file, by using the
|
It is also possible to run the web client without a configuration file, by using the
|
||||||
``--zuul-url`` option to specify the base URL of the Zuul web server.
|
``--zuul-url`` option to specify the base URL of the Zuul web server.
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
[opendev]
|
# Example of a zuul-client configuration file.
|
||||||
url=https://zuul.opendev.org
|
# Several sections can be created for different Zuul instances or settings.
|
||||||
verify_ssl=True
|
# The "example" section below can be used when calling zuul-client like so:
|
||||||
|
#
|
||||||
|
# zuul-client --use-conf example ...
|
||||||
|
|
||||||
[softwarefactory]
|
[example]
|
||||||
url=https://softwarefactory-project.io/zuul/
|
url=https://example.com/zuul/
|
||||||
verify_ssl=True
|
# verify_ssl=False
|
||||||
|
auth_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Add the **encrypt** subcommand to zuul-client, allowing users to encrypt files
|
||||||
|
or text to be used with a job for a specific project.
|
@ -25,8 +25,10 @@ class BaseTestCase(testtools.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class FakeRequestResponse(object):
|
class FakeRequestResponse(object):
|
||||||
def __init__(self, status_code=None, json=None, exception_msg=None):
|
def __init__(self, status_code=None, json=None, text=None,
|
||||||
|
exception_msg=None):
|
||||||
self._json = json
|
self._json = json
|
||||||
|
self.text = text
|
||||||
self.status_code = status_code
|
self.status_code = status_code
|
||||||
self.exception_msg = exception_msg or 'Error'
|
self.exception_msg = exception_msg or 'Error'
|
||||||
|
|
||||||
|
@ -282,3 +282,26 @@ class TestApi(BaseTestCase):
|
|||||||
'pipeline': 'check'}
|
'pipeline': 'check'}
|
||||||
)
|
)
|
||||||
self.assertEqual(True, prom)
|
self.assertEqual(True, prom)
|
||||||
|
|
||||||
|
def test_get_key(self):
|
||||||
|
"""Test getting a project's public key"""
|
||||||
|
pubkey = """
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqiwMzHBCMu8Nsz6LH5Rr
|
||||||
|
E0hUJvuHhEfGF2S+1Y7ux7MtrE7zFsKK3JYZLbJuuQ62w5UsDtjRCQ8A4RhDVItZ
|
||||||
|
lPzEIvrz3SVnOX61cAkc3FOZq3GG+vXZHzbyZUgQV6eh7cvvxKACaI10WLNTKvD2
|
||||||
|
0Hb8comVtrFFG333x+9MxGQIKhoaBFGDcBnTsWlSVFxyWFxkvmlmFfglR2IV7c5O
|
||||||
|
YAKWItpRYDCfZMvptwsDm8fRnafW7ADvMsFhKgSkQX0YnXBwVDIjywYMiaz9zzo6
|
||||||
|
zOfxhwe8fGWxUtaQObpnJ7uAiXrFBEefXdTR+5Zh5j0mR1MB0W0VupK7ezVOQ6LW
|
||||||
|
JNKtggslhDR/iPUbRaMMILWUJtLAin4I6ZOP05wNrau0zoYp5iW3hY4AV4+i+oYL
|
||||||
|
Rcl2SNzPYnZXMTvfsZV1f4J6fu1vLivRS6ynYWjYZWucK0C2NpD0NTMfP5jcUU3K
|
||||||
|
uM10zi/xzsPZ42xkVQFv0OfznwJVBDVMovQFOCBVKFP52wT44mmcMcTZQjyMBJLR
|
||||||
|
psLogzoSlPF9MfewbYwStYcA1HroexMPifQ7unvdzdb0S9y/RiN2WJgt8meXGrWU
|
||||||
|
JHyRBXb/ZW7Hy5CEMEkPY8+DcwvyNfN6cdTni8htcDZA/N1hzhaslKoUYcdCS8dH
|
||||||
|
GuS6/ewjS+arA1Iyeg/IxmECAwEAAQ==
|
||||||
|
-----END PUBLIC KEY-----"""
|
||||||
|
req = FakeRequestResponse(200, text=pubkey)
|
||||||
|
client = ZuulRESTClient(url='https://fake.zuul/')
|
||||||
|
client.session.get = MagicMock(return_value=req)
|
||||||
|
key = client.get_key('tenant1', 'project1')
|
||||||
|
self.assertEqual(pubkey, key)
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
from tests.unit import BaseTestCase
|
from tests.unit import BaseTestCase
|
||||||
from tests.unit import FakeRequestResponse
|
from tests.unit import FakeRequestResponse
|
||||||
|
|
||||||
@ -21,6 +23,19 @@ from unittest.mock import MagicMock, patch
|
|||||||
from zuulclient.cmd import ZuulClient
|
from zuulclient.cmd import ZuulClient
|
||||||
|
|
||||||
|
|
||||||
|
chunks = [
|
||||||
|
'V+Q+8Gq7u7YFq6mbmM+vM/4Z7xCx+qy3YHilYYSN6apJeqSjU2xyJVuNYI680kwBEFFXt'
|
||||||
|
'QmEqDlVIOG3yYTHgGbDq9gemMj2lMTzoTyftaE8yfK2uGZqWGwplh8PcGR67IhdH2UdDh'
|
||||||
|
'8xD5ehKwX9j/ZBoSJ0LQCy4KBvpB6sccc8wywGvNaJZxte8StLHgBxUFFxmO96deNkhUS'
|
||||||
|
'7xcpT+aU86uXYspJXmVrGOpy1/5QahIdi171rReRUToTO850M7JYuqcNrDm5rNiCdtwwT'
|
||||||
|
'BnEJbdXa6ZMvyD9UB4roXi8VIWp3laueh8qoE2INiZtxjOrVIJkhm2HASqZ13ROyycv1z'
|
||||||
|
'96Cr7UxH+LjrCm/yNfRMJpk00LZMwUOGUCueqH244e96UX5j6t+S/atkO+wVpG+9KDLhA'
|
||||||
|
'BQ7pyiW/CDqK9Z1ZpQPlnFM5PX4Mu7LemYXjFH+7eSxp+N/T5V0MrVt41MPv0h6al9vAM'
|
||||||
|
'sVJIQYeBNagYpjFSkEkMsJMXNAINJbfoT6vD4AS7pnCqiTaMgDC/6RQPwP9fklF+dJWq/'
|
||||||
|
'Au3QSQb7YIrjKiz2A75xQLxWoz9T+Lz4qZkF00zh5nMPUrzJQRPaBwxH5I0wZG0bYi9AJ'
|
||||||
|
'1tlAuq+vIhlY3iYlzVtPTiIOtF/6V+qPHnq1k6Tiv8YzJms1WyOuw106Bzl9XM=']
|
||||||
|
|
||||||
|
|
||||||
class TestCmd(BaseTestCase):
|
class TestCmd(BaseTestCase):
|
||||||
|
|
||||||
def test_client_args_errors(self):
|
def test_client_args_errors(self):
|
||||||
@ -303,3 +318,45 @@ class TestCmd(BaseTestCase):
|
|||||||
'pipeline': 'gate'}
|
'pipeline': 'gate'}
|
||||||
)
|
)
|
||||||
self.assertEqual(0, exit_code)
|
self.assertEqual(0, exit_code)
|
||||||
|
|
||||||
|
def test_encrypt(self):
|
||||||
|
"""Test encrypting a secret via CLI"""
|
||||||
|
infile = tempfile.NamedTemporaryFile(delete=False)
|
||||||
|
infile.write(b'my little secret')
|
||||||
|
infile.close()
|
||||||
|
outfile = tempfile.NamedTemporaryFile(delete=False)
|
||||||
|
outfile.close()
|
||||||
|
ZC = ZuulClient()
|
||||||
|
with patch('requests.Session') as mock_sesh:
|
||||||
|
session = mock_sesh.return_value
|
||||||
|
session.get = MagicMock(
|
||||||
|
return_value=FakeRequestResponse(200, text='aaa'))
|
||||||
|
with patch('zuulclient.cmd.encrypt_with_openssl') as m_encrypt:
|
||||||
|
m_encrypt.return_value = chunks
|
||||||
|
exit_code = ZC._main(
|
||||||
|
['--zuul-url', 'https://fake.zuul', '-v',
|
||||||
|
'encrypt', '--tenant', 'tenant1', '--project', 'project1',
|
||||||
|
'--infile', infile.name, '--outfile', outfile.name])
|
||||||
|
self.assertEqual(0, exit_code)
|
||||||
|
session.get.assert_called()
|
||||||
|
m_encrypt.assert_called()
|
||||||
|
secret = '''
|
||||||
|
- secret:
|
||||||
|
name: <name>
|
||||||
|
data:
|
||||||
|
<fieldname>: !encrypted/pkcs1-oaep
|
||||||
|
- V+Q+8Gq7u7YFq6mbmM+vM/4Z7xCx+qy3YHilYYSN6apJeqSjU2xyJVuNYI680kwBEFFXt
|
||||||
|
QmEqDlVIOG3yYTHgGbDq9gemMj2lMTzoTyftaE8yfK2uGZqWGwplh8PcGR67IhdH2UdDh
|
||||||
|
8xD5ehKwX9j/ZBoSJ0LQCy4KBvpB6sccc8wywGvNaJZxte8StLHgBxUFFxmO96deNkhUS
|
||||||
|
7xcpT+aU86uXYspJXmVrGOpy1/5QahIdi171rReRUToTO850M7JYuqcNrDm5rNiCdtwwT
|
||||||
|
BnEJbdXa6ZMvyD9UB4roXi8VIWp3laueh8qoE2INiZtxjOrVIJkhm2HASqZ13ROyycv1z
|
||||||
|
96Cr7UxH+LjrCm/yNfRMJpk00LZMwUOGUCueqH244e96UX5j6t+S/atkO+wVpG+9KDLhA
|
||||||
|
BQ7pyiW/CDqK9Z1ZpQPlnFM5PX4Mu7LemYXjFH+7eSxp+N/T5V0MrVt41MPv0h6al9vAM
|
||||||
|
sVJIQYeBNagYpjFSkEkMsJMXNAINJbfoT6vD4AS7pnCqiTaMgDC/6RQPwP9fklF+dJWq/
|
||||||
|
Au3QSQb7YIrjKiz2A75xQLxWoz9T+Lz4qZkF00zh5nMPUrzJQRPaBwxH5I0wZG0bYi9AJ
|
||||||
|
1tlAuq+vIhlY3iYlzVtPTiIOtF/6V+qPHnq1k6Tiv8YzJms1WyOuw106Bzl9XM=
|
||||||
|
'''
|
||||||
|
with open(outfile.name) as f:
|
||||||
|
self.assertEqual(secret, f.read())
|
||||||
|
os.unlink(infile.name)
|
||||||
|
os.unlink(outfile.name)
|
||||||
|
@ -152,3 +152,11 @@ class ZuulRESTClient(object):
|
|||||||
req = self.session.post(url, json=args)
|
req = self.session.post(url, json=args)
|
||||||
self._check_request_status(req)
|
self._check_request_status(req)
|
||||||
return req.json()
|
return req.json()
|
||||||
|
|
||||||
|
def get_key(self, tenant, project):
|
||||||
|
url = urllib.parse.urljoin(
|
||||||
|
self.base_url,
|
||||||
|
'tenant/%s/key/%s.pub' % (tenant, project))
|
||||||
|
req = self.session.get(url)
|
||||||
|
self._check_request_status(req)
|
||||||
|
return req.text
|
||||||
|
@ -18,11 +18,13 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import prettytable
|
import prettytable
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
import textwrap
|
import textwrap
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from zuulclient.api import ZuulRESTClient
|
from zuulclient.api import ZuulRESTClient
|
||||||
from zuulclient.utils import get_default
|
from zuulclient.utils import get_default
|
||||||
|
from zuulclient.utils import encrypt_with_openssl
|
||||||
|
|
||||||
|
|
||||||
class ZuulClient():
|
class ZuulClient():
|
||||||
@ -84,7 +86,7 @@ class ZuulClient():
|
|||||||
self.add_enqueue_ref_subparser(subparsers)
|
self.add_enqueue_ref_subparser(subparsers)
|
||||||
self.add_dequeue_subparser(subparsers)
|
self.add_dequeue_subparser(subparsers)
|
||||||
self.add_promote_subparser(subparsers)
|
self.add_promote_subparser(subparsers)
|
||||||
|
self.add_encrypt_subparser(subparsers)
|
||||||
return subparsers
|
return subparsers
|
||||||
|
|
||||||
def parseArguments(self, args=None):
|
def parseArguments(self, args=None):
|
||||||
@ -414,6 +416,98 @@ class ZuulClient():
|
|||||||
client = ZuulRESTClient(server, verify, auth_token)
|
client = ZuulRESTClient(server, verify, auth_token)
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
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('--tenant', help='tenant name',
|
||||||
|
required=True)
|
||||||
|
cmd_encrypt.add_argument('--project', help='project name',
|
||||||
|
required=True)
|
||||||
|
cmd_encrypt.add_argument('--no-strip', action='store_true',
|
||||||
|
help='Do not strip whitespace from beginning '
|
||||||
|
'or end of input.',
|
||||||
|
default=False)
|
||||||
|
cmd_encrypt.add_argument('--secret-name',
|
||||||
|
default=None,
|
||||||
|
help='How the secret should be named. If not '
|
||||||
|
'supplied, a placeholder will be used.')
|
||||||
|
cmd_encrypt.add_argument('--field-name',
|
||||||
|
default=None,
|
||||||
|
help='How the name of the secret variable. '
|
||||||
|
'If not supplied, a placeholder will be '
|
||||||
|
'used.')
|
||||||
|
cmd_encrypt.add_argument('--infile',
|
||||||
|
default=None,
|
||||||
|
help='A filename whose contents will be '
|
||||||
|
'encrypted. If not supplied, the value '
|
||||||
|
'will be read from standard input.\n'
|
||||||
|
'If entering the secret manually, press '
|
||||||
|
'Ctrl+d when finished to process the '
|
||||||
|
'secret.')
|
||||||
|
cmd_encrypt.add_argument('--outfile',
|
||||||
|
default=None,
|
||||||
|
help='A filename to which the encrypted '
|
||||||
|
'value will be written. If not '
|
||||||
|
'supplied, the value will be written '
|
||||||
|
'to standard output.')
|
||||||
|
cmd_encrypt.set_defaults(func=self.encrypt)
|
||||||
|
|
||||||
|
def encrypt(self):
|
||||||
|
if self.args.infile:
|
||||||
|
try:
|
||||||
|
with open(self.args.infile) as f:
|
||||||
|
plaintext = f.read()
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise Exception('File "%s" not found' % self.args.infile)
|
||||||
|
except PermissionError:
|
||||||
|
raise Exception(
|
||||||
|
'Insufficient rights to open %s' % self.args.infile)
|
||||||
|
else:
|
||||||
|
plaintext = sys.stdin.read()
|
||||||
|
if not self.args.no_strip:
|
||||||
|
plaintext = plaintext.strip()
|
||||||
|
pubkey_file = tempfile.NamedTemporaryFile(delete=False)
|
||||||
|
self.log.debug('Creating temporary key file %s' % pubkey_file.name)
|
||||||
|
client = self.get_client()
|
||||||
|
try:
|
||||||
|
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,
|
||||||
|
self.log)
|
||||||
|
output = textwrap.dedent(
|
||||||
|
'''
|
||||||
|
- secret:
|
||||||
|
name: {}
|
||||||
|
data:
|
||||||
|
{}: !encrypted/pkcs1-oaep
|
||||||
|
'''.format(self.args.secret_name or '<name>',
|
||||||
|
self.args.field_name or '<fieldname>'))
|
||||||
|
|
||||||
|
twrap = textwrap.TextWrapper(width=79,
|
||||||
|
initial_indent=' ' * 8,
|
||||||
|
subsequent_indent=' ' * 10)
|
||||||
|
for chunk in ciphertext_chunks:
|
||||||
|
chunk = twrap.fill('- ' + chunk)
|
||||||
|
output += chunk + '\n'
|
||||||
|
|
||||||
|
if self.args.outfile:
|
||||||
|
with open(self.args.outfile, "w") as f:
|
||||||
|
f.write(output)
|
||||||
|
else:
|
||||||
|
print(output)
|
||||||
|
return_code = True
|
||||||
|
except Exception as e:
|
||||||
|
self.log.exception(e)
|
||||||
|
return_code = False
|
||||||
|
finally:
|
||||||
|
self.log.debug('Deleting temporary key file %s' % pubkey_file.name)
|
||||||
|
os.unlink(pubkey_file.name)
|
||||||
|
return return_code
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
ZuulClient().main()
|
ZuulClient().main()
|
||||||
|
@ -12,7 +12,11 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
def get_default(config, section, option, default=None, expand_user=False):
|
def get_default(config, section, option, default=None, expand_user=False):
|
||||||
@ -30,3 +34,64 @@ def get_default(config, section, option, default=None, expand_user=False):
|
|||||||
if expand_user and value:
|
if expand_user and value:
|
||||||
return os.path.expanduser(value)
|
return os.path.expanduser(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt_with_openssl(pubkey_path, plaintext, logger=None):
|
||||||
|
cmd = ['openssl', 'version']
|
||||||
|
if logger:
|
||||||
|
logger.debug('calling "%s"' % ' '.join(cmd))
|
||||||
|
try:
|
||||||
|
openssl_version = subprocess.check_output(
|
||||||
|
cmd).split()[1]
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise Exception('"openssl" is not installed on the system')
|
||||||
|
|
||||||
|
cmd = ['openssl', 'rsa', '-text', '-pubin', '-in', pubkey_path]
|
||||||
|
if logger:
|
||||||
|
logger.debug('calling "%s"' % ' '.join(cmd))
|
||||||
|
p = subprocess.Popen(cmd,
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
(stdout, stderr) = p.communicate()
|
||||||
|
if p.returncode != 0:
|
||||||
|
raise Exception('openssl failure (Return code %s)' % p.returncode)
|
||||||
|
|
||||||
|
output = stdout.decode('utf-8')
|
||||||
|
if openssl_version.startswith(b'0.'):
|
||||||
|
key_length_re = r'^Modulus \((?P<key_length>\d+) bit\):$'
|
||||||
|
else:
|
||||||
|
key_length_re = r'^(|RSA )Public-Key: \((?P<key_length>\d+) bit\)$'
|
||||||
|
m = re.match(key_length_re, output, re.MULTILINE)
|
||||||
|
nbits = int(m.group('key_length'))
|
||||||
|
nbytes = int(nbits / 8)
|
||||||
|
max_bytes = nbytes - 42 # PKCS1-OAEP overhead
|
||||||
|
chunks = int(math.ceil(float(len(plaintext)) / max_bytes))
|
||||||
|
|
||||||
|
ciphertext_chunks = []
|
||||||
|
|
||||||
|
if logger:
|
||||||
|
logger.info(
|
||||||
|
'Public key length: {} bits ({} bytes)'.format(nbits, nbytes))
|
||||||
|
logger.info(
|
||||||
|
'Max plaintext length per chunk: {} bytes'.format(max_bytes))
|
||||||
|
logger.info(
|
||||||
|
'Input plaintext length: {} bytes'.format(len(plaintext)))
|
||||||
|
logger.info('Number of chunks: {}'.format(chunks))
|
||||||
|
|
||||||
|
cmd = ['openssl', 'rsautl', '-encrypt',
|
||||||
|
'-oaep', '-pubin', '-inkey',
|
||||||
|
pubkey_path]
|
||||||
|
if logger:
|
||||||
|
logger.debug('calling "%s" with each data chunk:' % ' '.join(cmd))
|
||||||
|
for count in range(chunks):
|
||||||
|
chunk = plaintext[int(count * max_bytes):
|
||||||
|
int((count + 1) * max_bytes)]
|
||||||
|
p = subprocess.Popen(cmd,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
if logger:
|
||||||
|
logger.debug('\tchunk %s' % (count + 1))
|
||||||
|
(stdout, stderr) = p.communicate(str.encode(chunk))
|
||||||
|
if p.returncode != 0:
|
||||||
|
raise Exception('openssl failure (Return code %s)' % p.returncode)
|
||||||
|
ciphertext_chunks.append(base64.b64encode(stdout).decode('utf-8'))
|
||||||
|
return ciphertext_chunks
|
||||||
|
Loading…
Reference in New Issue
Block a user