From 3fbf6c36477bb001fbd2d1ec08de137d7d1fb02c Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Fri, 6 Apr 2018 13:06:48 -0700 Subject: [PATCH] Consolidating shared code from modules/nacl.py & runners/nacl.py to utils/nacl.py. Adding some new tests to test sealedbox & secretbox encryption & decryption. --- salt/modules/nacl.py | 267 ++-------------- salt/runners/nacl.py | 271 ++--------------- salt/utils/nacl.py | 404 +++++++++++++++++++++++++ tests/integration/modules/test_nacl.py | 62 ++++ tests/integration/runners/test_nacl.py | 62 ++++ 5 files changed, 568 insertions(+), 498 deletions(-) create mode 100644 salt/utils/nacl.py diff --git a/salt/modules/nacl.py b/salt/modules/nacl.py index 4cf2b33b54..bb16deb647 100644 --- a/salt/modules/nacl.py +++ b/salt/modules/nacl.py @@ -152,85 +152,15 @@ Optional small program to encrypt data without needing salt modules. # Import Python libs from __future__ import absolute_import, print_function, unicode_literals -import base64 -import os # Import Salt libs -from salt.ext import six -import salt.syspaths -import salt.utils.files -import salt.utils.platform -import salt.utils.stringutils -import salt.utils.versions -import salt.utils.win_functions -import salt.utils.win_dacl - -REQ_ERROR = None -try: - import libnacl.secret - import libnacl.sealed -except (ImportError, OSError) as e: - REQ_ERROR = 'libnacl import error, perhaps missing python libnacl package or should update.' +import salt.utils.nacl __virtualname__ = 'nacl' def __virtual__(): - return (REQ_ERROR is None, REQ_ERROR) - - -def _get_config(**kwargs): - ''' - Return configuration - ''' - config = { - 'box_type': 'sealedbox', - 'sk': None, - 'sk_file': os.path.join(__opts__['pki_dir'], 'master', 'nacl'), - 'pk': None, - 'pk_file': os.path.join(__opts__['pki_dir'], 'master', 'nacl.pub'), - } - config_key = '{0}.config'.format(__virtualname__) - try: - config.update(__salt__['config.get'](config_key, {})) - except (NameError, KeyError) as e: - # likly using salt-run so fallback to __opts__ - config.update(__opts__.get(config_key, {})) - # pylint: disable=C0201 - for k in set(config.keys()) & set(kwargs.keys()): - config[k] = kwargs[k] - return config - - -def _get_sk(**kwargs): - ''' - Return sk - ''' - config = _get_config(**kwargs) - key = config['sk'] - sk_file = config['sk_file'] - if not key and sk_file: - with salt.utils.files.fopen(sk_file, 'rb') as keyf: - key = salt.utils.stringutils.to_unicode(keyf.read()).rstrip('\n') - if key is None: - raise Exception('no key or sk_file found') - return base64.b64decode(key) - - -def _get_pk(**kwargs): - ''' - Return pk - ''' - config = _get_config(**kwargs) - pubkey = config['pk'] - pk_file = config['pk_file'] - if not pubkey and pk_file: - with salt.utils.files.fopen(pk_file, 'rb') as keyf: - pubkey = salt.utils.stringutils.to_unicode(keyf.read()).rstrip('\n') - if pubkey is None: - raise Exception('no pubkey or pk_file found') - pubkey = six.text_type(pubkey) - return base64.b64decode(pubkey) + return salt.utils.nacl.check_requirements() def keygen(sk_file=None, pk_file=None, **kwargs): @@ -253,66 +183,8 @@ def keygen(sk_file=None, pk_file=None, **kwargs): salt-call nacl.keygen sk_file=/etc/salt/pki/master/nacl pk_file=/etc/salt/pki/master/nacl.pub salt-call --local nacl.keygen ''' - if 'keyfile' in kwargs: - salt.utils.versions.warn_until( - 'Neon', - 'The \'keyfile\' argument has been deprecated and will be removed in Salt ' - '{version}. Please use \'sk_file\' argument instead.' - ) - sk_file = kwargs['keyfile'] - - if sk_file is None: - kp = libnacl.public.SecretKey() - return {'sk': base64.b64encode(kp.sk), 'pk': base64.b64encode(kp.pk)} - - if pk_file is None: - pk_file = '{0}.pub'.format(sk_file) - - if sk_file and pk_file is None: - if not os.path.isfile(sk_file): - kp = libnacl.public.SecretKey() - with salt.utils.files.fopen(sk_file, 'wb') as keyf: - keyf.write(base64.b64encode(kp.sk)) - if salt.utils.platform.is_windows(): - cur_user = salt.utils.win_functions.get_current_user() - salt.utils.win_dacl.set_owner(sk_file, cur_user) - salt.utils.win_dacl.set_permissions(sk_file, cur_user, 'full_control', 'grant', reset_perms=True, protected=True) - else: - # chmod 0600 file - os.chmod(sk_file, 1536) - return 'saved sk_file: {0}'.format(sk_file) - else: - raise Exception('sk_file:{0} already exist.'.format(sk_file)) - - if sk_file is None and pk_file: - raise Exception('sk_file: Must be set inorder to generate a public key.') - - if os.path.isfile(sk_file) and os.path.isfile(pk_file): - raise Exception('sk_file:{0} and pk_file:{1} already exist.'.format(sk_file, pk_file)) - - if os.path.isfile(sk_file) and not os.path.isfile(pk_file): - # generate pk using the sk - with salt.utils.files.fopen(sk_file, 'rb') as keyf: - sk = salt.utils.stringutils.to_unicode(keyf.read()).rstrip('\n') - sk = base64.b64decode(sk) - kp = libnacl.public.SecretKey(sk) - with salt.utils.files.fopen(pk_file, 'wb') as keyf: - keyf.write(base64.b64encode(kp.pk)) - return 'saved pk_file: {0}'.format(pk_file) - - kp = libnacl.public.SecretKey() - with salt.utils.files.fopen(sk_file, 'wb') as keyf: - keyf.write(base64.b64encode(kp.sk)) - if salt.utils.platform.is_windows(): - cur_user = salt.utils.win_functions.get_current_user() - salt.utils.win_dacl.set_owner(sk_file, cur_user) - salt.utils.win_dacl.set_permissions(sk_file, cur_user, 'full_control', 'grant', reset_perms=True, protected=True) - else: - # chmod 0600 file - os.chmod(sk_file, 1536) - with salt.utils.files.fopen(pk_file, 'wb') as keyf: - keyf.write(base64.b64encode(kp.pk)) - return 'saved sk_file:{0} pk_file: {1}'.format(sk_file, pk_file) + kwargs['opts'] = __opts__ + return salt.utils.nacl.keygen(sk_file, pk_file, **kwargs) def enc(data, **kwargs): @@ -321,31 +193,8 @@ def enc(data, **kwargs): box_type: secretbox, sealedbox(default) ''' - if 'keyfile' in kwargs: - salt.utils.versions.warn_until( - 'Neon', - 'The \'keyfile\' argument has been deprecated and will be removed in Salt ' - '{version}. Please use \'sk_file\' argument instead.' - ) - kwargs['sk_file'] = kwargs['keyfile'] - - if 'key' in kwargs: - salt.utils.versions.warn_until( - 'Neon', - 'The \'key\' argument has been deprecated and will be removed in Salt ' - '{version}. Please use \'sk\' argument instead.' - ) - kwargs['sk'] = kwargs['key'] - - # ensure data is in bytes - data = salt.utils.stringutils.to_bytes(data) - - box_type = _get_config(**kwargs)['box_type'] - if box_type == 'sealedbox': - return sealedbox_encrypt(data, **kwargs) - if box_type == 'secretbox': - return secretbox_encrypt(data, **kwargs) - return sealedbox_encrypt(data, **kwargs) + kwargs['opts'] = __opts__ + return salt.utils.nacl.enc(data, **kwargs) def enc_file(name, out=None, **kwargs): @@ -365,20 +214,8 @@ def enc_file(name, out=None, **kwargs): salt-run nacl.enc_file name=/tmp/id_rsa box_type=secretbox \ sk_file=/etc/salt/pki/master/nacl.pub ''' - try: - data = __salt__['cp.get_file_str'](name) - except Exception as e: - # likly using salt-run so fallback to local filesystem - with salt.utils.files.fopen(name, 'rb') as f: - data = salt.utils.stringutils.to_unicode(f.read()) - d = enc(data, **kwargs) - if out: - if os.path.isfile(out): - raise Exception('file:{0} already exist.'.format(out)) - with salt.utils.files.fopen(out, 'wb') as f: - f.write(salt.utils.stringutils.to_bytes(d)) - return 'Wrote: {0}'.format(out) - return d + kwargs['opts'] = __opts__ + return salt.utils.nacl.enc_file(name, out, **kwargs) def dec(data, **kwargs): @@ -387,37 +224,8 @@ def dec(data, **kwargs): box_type: secretbox, sealedbox(default) ''' - if 'keyfile' in kwargs: - salt.utils.versions.warn_until( - 'Neon', - 'The \'keyfile\' argument has been deprecated and will be removed in Salt ' - '{version}. Please use \'sk_file\' argument instead.' - ) - kwargs['sk_file'] = kwargs['keyfile'] - - # set boxtype to `secretbox` to maintain backward compatibility - kwargs['box_type'] = 'secretbox' - - if 'key' in kwargs: - salt.utils.versions.warn_until( - 'Neon', - 'The \'key\' argument has been deprecated and will be removed in Salt ' - '{version}. Please use \'sk\' argument instead.' - ) - kwargs['sk'] = kwargs['key'] - - # set boxtype to `secretbox` to maintain backward compatibility - kwargs['box_type'] = 'secretbox' - - # ensure data is in bytes - data = salt.utils.stringutils.to_bytes(data) - - box_type = _get_config(**kwargs)['box_type'] - if box_type == 'sealedbox': - return sealedbox_decrypt(data, **kwargs) - if box_type == 'secretbox': - return secretbox_decrypt(data, **kwargs) - return sealedbox_decrypt(data, **kwargs) + kwargs['opts'] = __opts__ + return salt.utils.nacl.dec(data, **kwargs) def dec_file(name, out=None, **kwargs): @@ -437,20 +245,8 @@ def dec_file(name, out=None, **kwargs): salt-run nacl.dec_file name=/tmp/id_rsa.nacl box_type=secretbox \ sk_file=/etc/salt/pki/master/nacl.pub ''' - try: - data = __salt__['cp.get_file_str'](name) - except Exception as e: - # likly using salt-run so fallback to local filesystem - with salt.utils.files.fopen(name, 'rb') as f: - data = salt.utils.stringutils.to_unicode(f.read()) - d = dec(data, **kwargs) - if out: - if os.path.isfile(out): - raise Exception('file:{0} already exist.'.format(out)) - with salt.utils.files.fopen(out, 'wb') as f: - f.write(salt.utils.stringutils.to_bytes(d)) - return 'Wrote: {0}'.format(out) - return d + kwargs['opts'] = __opts__ + return salt.utils.nacl.dec_file(name, out, **kwargs) def sealedbox_encrypt(data, **kwargs): @@ -466,12 +262,8 @@ def sealedbox_encrypt(data, **kwargs): salt-call --local nacl.sealedbox_encrypt datatoenc pk_file=/etc/salt/pki/master/nacl.pub salt-call --local nacl.sealedbox_encrypt datatoenc pk='vrwQF7cNiNAVQVAiS3bvcbJUnF0cN6fU9YTZD9mBfzQ=' ''' - # ensure data is in bytes - data = salt.utils.stringutils.to_bytes(data) - - pk = _get_pk(**kwargs) - b = libnacl.sealed.SealedBox(pk) - return base64.b64encode(b.encrypt(data)) + kwargs['opts'] = __opts__ + return salt.utils.nacl.sealedbox_encrypt(data, **kwargs) def sealedbox_decrypt(data, **kwargs): @@ -486,16 +278,8 @@ def sealedbox_decrypt(data, **kwargs): salt-call --local nacl.sealedbox_decrypt data='pEXHQM6cuaF7A=' sk_file=/etc/salt/pki/master/nacl salt-call --local nacl.sealedbox_decrypt data='pEXHQM6cuaF7A=' sk='YmFkcGFzcwo=' ''' - if data is None: - return None - - # ensure data is in bytes - data = salt.utils.stringutils.to_bytes(data) - - sk = _get_sk(**kwargs) - keypair = libnacl.public.SecretKey(sk) - b = libnacl.sealed.SealedBox(keypair) - return b.decrypt(base64.b64decode(data)) + kwargs['opts'] = __opts__ + return salt.utils.nacl.sealedbox_decrypt(data, **kwargs) def secretbox_encrypt(data, **kwargs): @@ -511,12 +295,8 @@ def secretbox_encrypt(data, **kwargs): salt-call --local nacl.secretbox_encrypt datatoenc sk_file=/etc/salt/pki/master/nacl salt-call --local nacl.secretbox_encrypt datatoenc sk='YmFkcGFzcwo=' ''' - # ensure data is in bytes - data = salt.utils.stringutils.to_bytes(data) - - sk = _get_sk(**kwargs) - b = libnacl.secret.SecretBox(sk) - return base64.b64encode(b.encrypt(data)) + kwargs['opts'] = __opts__ + return salt.utils.nacl.secretbox_encrypt(data, **kwargs) def secretbox_decrypt(data, **kwargs): @@ -532,12 +312,5 @@ def secretbox_decrypt(data, **kwargs): salt-call --local nacl.secretbox_decrypt data='pEXHQM6cuaF7A=' sk_file=/etc/salt/pki/master/nacl salt-call --local nacl.secretbox_decrypt data='pEXHQM6cuaF7A=' sk='YmFkcGFzcwo=' ''' - if data is None: - return None - - # ensure data is in bytes - data = salt.utils.stringutils.to_bytes(data) - - key = _get_sk(**kwargs) - b = libnacl.secret.SecretBox(key=key) - return b.decrypt(base64.b64decode(data)) + kwargs['opts'] = __opts__ + return salt.utils.nacl.secretbox_decrypt(data, **kwargs) diff --git a/salt/runners/nacl.py b/salt/runners/nacl.py index 2fae5915ab..5af875c995 100644 --- a/salt/runners/nacl.py +++ b/salt/runners/nacl.py @@ -114,87 +114,15 @@ Larger files like certificates can be encrypted with: # Import Python libs from __future__ import absolute_import, print_function, unicode_literals -import base64 -import os # Import Salt libs -import salt.utils.files -import salt.utils.platform -import salt.utils.stringutils -import salt.utils.versions -import salt.utils.win_functions -import salt.utils.win_dacl -import salt.syspaths - -# Import 3rd-party libs -from salt.ext import six - -REQ_ERROR = None -try: - import libnacl.secret - import libnacl.sealed -except (ImportError, OSError) as e: - REQ_ERROR = 'libnacl import error, perhaps missing python libnacl package or should update.' +import salt.utils.nacl __virtualname__ = 'nacl' def __virtual__(): - return (REQ_ERROR is None, REQ_ERROR) - - -def _get_config(**kwargs): - ''' - Return configuration - ''' - config = { - 'box_type': 'sealedbox', - 'sk': None, - 'sk_file': os.path.join(__opts__['pki_dir'], 'nacl'), - 'pk': None, - 'pk_file': os.path.join(__opts__['pki_dir'], 'nacl.pub'), - } - config_key = '{0}.config'.format(__virtualname__) - try: - config.update(__salt__['config.get'](config_key, {})) - except (NameError, KeyError) as e: - # likly using salt-run so fallback to __opts__ - config.update(__opts__.get(config_key, {})) - # pylint: disable=C0201 - for k in set(config.keys()) & set(kwargs.keys()): - config[k] = kwargs[k] - return config - - -def _get_sk(**kwargs): - ''' - Return sk - ''' - config = _get_config(**kwargs) - key = config['sk'] - sk_file = config['sk_file'] - if not key and sk_file: - with salt.utils.files.fopen(sk_file, 'rb') as keyf: - key = six.text_type(keyf.read()).rstrip('\n') - if key is None: - raise Exception('no key or sk_file found') - return base64.b64decode(key) - - -def _get_pk(**kwargs): - ''' - Return pk - ''' - config = _get_config(**kwargs) - pubkey = config['pk'] - pk_file = config['pk_file'] - if not pubkey and pk_file: - with salt.utils.files.fopen(pk_file, 'rb') as keyf: - pubkey = six.text_type(keyf.read()).rstrip('\n') - if pubkey is None: - raise Exception('no pubkey or pk_file found') - pubkey = six.text_type(pubkey) - return base64.b64decode(pubkey) + return salt.utils.nacl.check_requirements() def keygen(sk_file=None, pk_file=None, **kwargs): @@ -217,67 +145,8 @@ def keygen(sk_file=None, pk_file=None, **kwargs): salt-run nacl.keygen sk_file=/etc/salt/pki/master/nacl pk_file=/etc/salt/pki/master/nacl.pub salt-run nacl.keygen ''' - - if 'keyfile' in kwargs: - salt.utils.versions.warn_until( - 'Neon', - 'The \'keyfile\' argument has been deprecated and will be removed in Salt ' - '{version}. Please use \'sk_file\' argument instead.' - ) - sk_file = kwargs['keyfile'] - - if sk_file is None: - kp = libnacl.public.SecretKey() - return {'sk': base64.b64encode(kp.sk), 'pk': base64.b64encode(kp.pk)} - - if pk_file is None: - pk_file = '{0}.pub'.format(sk_file) - - if sk_file and pk_file is None: - if not os.path.isfile(sk_file): - kp = libnacl.public.SecretKey() - with salt.utils.files.fopen(sk_file, 'w') as keyf: - keyf.write(base64.b64encode(kp.sk)) - if salt.utils.platform.is_windows(): - cur_user = salt.utils.win_functions.get_current_user() - salt.utils.win_dacl.set_owner(sk_file, cur_user) - salt.utils.win_dacl.set_permissions(sk_file, cur_user, 'full_control', 'grant', reset_perms=True, protected=True) - else: - # chmod 0600 file - os.chmod(sk_file, 1536) - return 'saved sk_file: {0}'.format(sk_file) - else: - raise Exception('sk_file:{0} already exist.'.format(sk_file)) - - if sk_file is None and pk_file: - raise Exception('sk_file: Must be set inorder to generate a public key.') - - if os.path.isfile(sk_file) and os.path.isfile(pk_file): - raise Exception('sk_file:{0} and pk_file:{1} already exist.'.format(sk_file, pk_file)) - - if os.path.isfile(sk_file) and not os.path.isfile(pk_file): - # generate pk using the sk - with salt.utils.files.fopen(sk_file, 'rb') as keyf: - sk = six.text_type(keyf.read()).rstrip('\n') - sk = base64.b64decode(sk) - kp = libnacl.public.SecretKey(sk) - with salt.utils.files.fopen(pk_file, 'w') as keyf: - keyf.write(base64.b64encode(kp.pk)) - return 'saved pk_file: {0}'.format(pk_file) - - kp = libnacl.public.SecretKey() - with salt.utils.files.fopen(sk_file, 'w') as keyf: - keyf.write(base64.b64encode(kp.sk)) - if salt.utils.platform.is_windows(): - cur_user = salt.utils.win_functions.get_current_user() - salt.utils.win_dacl.set_owner(sk_file, cur_user) - salt.utils.win_dacl.set_permissions(sk_file, cur_user, 'full_control', 'grant', reset_perms=True, protected=True) - else: - # chmod 0600 file - os.chmod(sk_file, 1536) - with salt.utils.files.fopen(pk_file, 'w') as keyf: - keyf.write(base64.b64encode(kp.pk)) - return 'saved sk_file:{0} pk_file: {1}'.format(sk_file, pk_file) + kwargs['opts'] = __opts__ + return salt.utils.nacl.keygen(sk_file, pk_file, **kwargs) def enc(data, **kwargs): @@ -286,32 +155,8 @@ def enc(data, **kwargs): box_type: secretbox, sealedbox(default) ''' - - if 'keyfile' in kwargs: - salt.utils.versions.warn_until( - 'Neon', - 'The \'keyfile\' argument has been deprecated and will be removed in Salt ' - '{version}. Please use \'sk_file\' argument instead.' - ) - kwargs['sk_file'] = kwargs['keyfile'] - - if 'key' in kwargs: - salt.utils.versions.warn_until( - 'Neon', - 'The \'key\' argument has been deprecated and will be removed in Salt ' - '{version}. Please use \'sk\' argument instead.' - ) - kwargs['sk'] = kwargs['key'] - - # ensure data is bytes - data = salt.utils.stringutils.to_bytes(data) - - box_type = _get_config(**kwargs)['box_type'] - if box_type == 'sealedbox': - return sealedbox_encrypt(data, **kwargs) - if box_type == 'secretbox': - return secretbox_encrypt(data, **kwargs) - return sealedbox_encrypt(data, **kwargs) + kwargs['opts'] = __opts__ + return salt.utils.nacl.enc(data, **kwargs) def enc_file(name, out=None, **kwargs): @@ -330,20 +175,8 @@ def enc_file(name, out=None, **kwargs): salt-run nacl.enc_file name=/tmp/id_rsa box_type=secretbox \ sk_file=/etc/salt/pki/master/nacl.pub ''' - try: - data = __salt__['cp.get_file_str'](name) - except Exception as e: - # likly using salt-run so fallback to local filesystem - with salt.utils.files.fopen(name, 'rb') as f: - data = f.read() - d = enc(data, **kwargs) - if out: - if os.path.isfile(out): - raise Exception('file:{0} already exist.'.format(out)) - with salt.utils.files.fopen(out, 'wb') as f: - f.write(d) - return 'Wrote: {0}'.format(out) - return d + kwargs['opts'] = __opts__ + return salt.utils.nacl.enc_file(name, out, **kwargs) def dec(data, **kwargs): @@ -352,37 +185,8 @@ def dec(data, **kwargs): box_type: secretbox, sealedbox(default) ''' - if 'keyfile' in kwargs: - salt.utils.versions.warn_until( - 'Neon', - 'The \'keyfile\' argument has been deprecated and will be removed in Salt ' - '{version}. Please use \'sk_file\' argument instead.' - ) - kwargs['sk_file'] = kwargs['keyfile'] - - # set boxtype to `secretbox` to maintain backward compatibility - kwargs['box_type'] = 'secretbox' - - if 'key' in kwargs: - salt.utils.versions.warn_until( - 'Neon', - 'The \'key\' argument has been deprecated and will be removed in Salt ' - '{version}. Please use \'sk\' argument instead.' - ) - kwargs['sk'] = kwargs['key'] - - # set boxtype to `secretbox` to maintain backward compatibility - kwargs['box_type'] = 'secretbox' - - # ensure data is bytes - data = salt.utils.stringutils.to_bytes(data) - - box_type = _get_config(**kwargs)['box_type'] - if box_type == 'sealedbox': - return sealedbox_decrypt(data, **kwargs) - if box_type == 'secretbox': - return secretbox_decrypt(data, **kwargs) - return sealedbox_decrypt(data, **kwargs) + kwargs['opts'] = __opts__ + return salt.utils.nacl.dec(data, **kwargs) def dec_file(name, out=None, **kwargs): @@ -401,20 +205,8 @@ def dec_file(name, out=None, **kwargs): salt-run nacl.dec_file name=/tmp/id_rsa.nacl box_type=secretbox \ sk_file=/etc/salt/pki/master/nacl.pub ''' - try: - data = __salt__['cp.get_file_str'](name) - except Exception as e: - # likly using salt-run so fallback to local filesystem - with salt.utils.files.fopen(name, 'rb') as f: - data = f.read() - d = dec(data, **kwargs) - if out: - if os.path.isfile(out): - raise Exception('file:{0} already exist.'.format(out)) - with salt.utils.files.fopen(out, 'wb') as f: - f.write(d) - return 'Wrote: {0}'.format(out) - return d + kwargs['opts'] = __opts__ + return salt.utils.nacl.dec_file(name, out, **kwargs) def sealedbox_encrypt(data, **kwargs): @@ -428,12 +220,8 @@ def sealedbox_encrypt(data, **kwargs): salt-run nacl.sealedbox_encrypt datatoenc ''' - # ensure data is bytes - data = salt.utils.stringutils.to_bytes(data) - - pk = _get_pk(**kwargs) - b = libnacl.sealed.SealedBox(pk) - return base64.b64encode(b.encrypt(data)) + kwargs['opts'] = __opts__ + return salt.utils.nacl.sealedbox_encrypt(data, **kwargs) def sealedbox_decrypt(data, **kwargs): @@ -448,16 +236,8 @@ def sealedbox_decrypt(data, **kwargs): salt-run nacl.sealedbox_decrypt data='pEXHQM6cuaF7A=' sk_file=/etc/salt/pki/master/nacl salt-run nacl.sealedbox_decrypt data='pEXHQM6cuaF7A=' sk='YmFkcGFzcwo=' ''' - if data is None: - return None - - # ensure data is bytes - data = salt.utils.stringutils.to_bytes(data) - - sk = _get_sk(**kwargs) - keypair = libnacl.public.SecretKey(sk) - b = libnacl.sealed.SealedBox(keypair) - return b.decrypt(base64.b64decode(data)) + kwargs['opts'] = __opts__ + return salt.utils.nacl.sealedbox_decrypt(data, **kwargs) def secretbox_encrypt(data, **kwargs): @@ -473,12 +253,8 @@ def secretbox_encrypt(data, **kwargs): salt-run nacl.secretbox_encrypt datatoenc sk_file=/etc/salt/pki/master/nacl salt-run nacl.secretbox_encrypt datatoenc sk='YmFkcGFzcwo=' ''' - # ensure data is bytes - data = salt.utils.stringutils.to_bytes(data) - - sk = _get_sk(**kwargs) - b = libnacl.secret.SecretBox(sk) - return base64.b64encode(b.encrypt(data)) + kwargs['opts'] = __opts__ + return salt.utils.nacl.secretbox_encrypt(data, **kwargs) def secretbox_decrypt(data, **kwargs): @@ -494,12 +270,5 @@ def secretbox_decrypt(data, **kwargs): salt-run nacl.secretbox_decrypt data='pEXHQM6cuaF7A=' sk_file=/etc/salt/pki/master/nacl salt-run nacl.secretbox_decrypt data='pEXHQM6cuaF7A=' sk='YmFkcGFzcwo=' ''' - if data is None: - return None - - # ensure data is bytes - data = salt.utils.stringutils.to_bytes(data) - - key = _get_sk(**kwargs) - b = libnacl.secret.SecretBox(key=key) - return b.decrypt(base64.b64decode(data)) + kwargs['opts'] = __opts__ + return salt.utils.nacl.secretbox_decrypt(data, **kwargs) diff --git a/salt/utils/nacl.py b/salt/utils/nacl.py new file mode 100644 index 0000000000..1ff4cbf84d --- /dev/null +++ b/salt/utils/nacl.py @@ -0,0 +1,404 @@ +# -*- coding: utf-8 -*- +''' +Common code shared between the nacl module and runner. +''' + +# Import Python libs +from __future__ import absolute_import, print_function, unicode_literals +import base64 +import os + +# Import Salt libs +from salt.ext import six +import salt.syspaths +import salt.utils.files +import salt.utils.platform +import salt.utils.stringutils +import salt.utils.versions +import salt.utils.win_functions +import salt.utils.win_dacl + + +REQ_ERROR = None +try: + import libnacl.secret + import libnacl.sealed +except (ImportError, OSError) as e: + REQ_ERROR = 'libnacl import error, perhaps missing python libnacl package or should update.' + +__virtualname__ = 'nacl' + + +def __virtual__(): + return check_requirements() + + +def check_requirements(): + ''' + Check required libraries are available + ''' + return (REQ_ERROR is None, REQ_ERROR) + + +def _get_config(**kwargs): + ''' + Return configuration + ''' + config = { + 'box_type': 'sealedbox', + 'sk': None, + 'sk_file': os.path.join(kwargs['opts'].get('pki_dir'), 'master/nacl'), + 'pk': None, + 'pk_file': os.path.join(kwargs['opts'].get('pki_dir'), 'master/nacl.pub'), + } + + config_key = '{0}.config'.format(__virtualname__) + try: + config.update(__salt__['config.get'](config_key, {})) + except (NameError, KeyError) as e: + # likly using salt-run so fallback to __opts__ + config.update(kwargs['opts'].get(config_key, {})) + # pylint: disable=C0201 + for k in set(config.keys()) & set(kwargs.keys()): + config[k] = kwargs[k] + return config + + +def _get_sk(**kwargs): + ''' + Return sk + ''' + config = _get_config(**kwargs) + key = config['sk'] + sk_file = config['sk_file'] + if not key and sk_file: + with salt.utils.files.fopen(sk_file, 'rb') as keyf: + key = salt.utils.stringutils.to_unicode(keyf.read()).rstrip('\n') + if key is None: + raise Exception('no key or sk_file found') + return base64.b64decode(key) + + +def _get_pk(**kwargs): + ''' + Return pk + ''' + config = _get_config(**kwargs) + pubkey = config['pk'] + pk_file = config['pk_file'] + if not pubkey and pk_file: + with salt.utils.files.fopen(pk_file, 'rb') as keyf: + pubkey = salt.utils.stringutils.to_unicode(keyf.read()).rstrip('\n') + if pubkey is None: + raise Exception('no pubkey or pk_file found') + pubkey = six.text_type(pubkey) + return base64.b64decode(pubkey) + + +def keygen(sk_file=None, pk_file=None, **kwargs): + ''' + Use libnacl to generate a keypair. + + If no `sk_file` is defined return a keypair. + + If only the `sk_file` is defined `pk_file` will use the same name with a postfix `.pub`. + + When the `sk_file` is already existing, but `pk_file` is not. The `pk_file` will be generated + using the `sk_file`. + + CLI Examples: + + .. code-block:: bash + + salt-call nacl.keygen + salt-call nacl.keygen sk_file=/etc/salt/pki/master/nacl + salt-call nacl.keygen sk_file=/etc/salt/pki/master/nacl pk_file=/etc/salt/pki/master/nacl.pub + salt-call --local nacl.keygen + ''' + if 'keyfile' in kwargs: + salt.utils.versions.warn_until( + 'Neon', + 'The \'keyfile\' argument has been deprecated and will be removed in Salt ' + '{version}. Please use \'sk_file\' argument instead.' + ) + sk_file = kwargs['keyfile'] + + if sk_file is None: + kp = libnacl.public.SecretKey() + return {'sk': base64.b64encode(kp.sk), 'pk': base64.b64encode(kp.pk)} + + if pk_file is None: + pk_file = '{0}.pub'.format(sk_file) + + if sk_file and pk_file is None: + if not os.path.isfile(sk_file): + kp = libnacl.public.SecretKey() + with salt.utils.files.fopen(sk_file, 'wb') as keyf: + keyf.write(base64.b64encode(kp.sk)) + if salt.utils.platform.is_windows(): + cur_user = salt.utils.win_functions.get_current_user() + salt.utils.win_dacl.set_owner(sk_file, cur_user) + salt.utils.win_dacl.set_permissions(sk_file, cur_user, 'full_control', 'grant', reset_perms=True, protected=True) + else: + # chmod 0600 file + os.chmod(sk_file, 1536) + return 'saved sk_file: {0}'.format(sk_file) + else: + raise Exception('sk_file:{0} already exist.'.format(sk_file)) + + if sk_file is None and pk_file: + raise Exception('sk_file: Must be set inorder to generate a public key.') + + if os.path.isfile(sk_file) and os.path.isfile(pk_file): + raise Exception('sk_file:{0} and pk_file:{1} already exist.'.format(sk_file, pk_file)) + + if os.path.isfile(sk_file) and not os.path.isfile(pk_file): + # generate pk using the sk + with salt.utils.files.fopen(sk_file, 'rb') as keyf: + sk = salt.utils.stringutils.to_unicode(keyf.read()).rstrip('\n') + sk = base64.b64decode(sk) + kp = libnacl.public.SecretKey(sk) + with salt.utils.files.fopen(pk_file, 'wb') as keyf: + keyf.write(base64.b64encode(kp.pk)) + return 'saved pk_file: {0}'.format(pk_file) + + kp = libnacl.public.SecretKey() + with salt.utils.files.fopen(sk_file, 'wb') as keyf: + keyf.write(base64.b64encode(kp.sk)) + if salt.utils.platform.is_windows(): + cur_user = salt.utils.win_functions.get_current_user() + salt.utils.win_dacl.set_owner(sk_file, cur_user) + salt.utils.win_dacl.set_permissions(sk_file, cur_user, 'full_control', 'grant', reset_perms=True, protected=True) + else: + # chmod 0600 file + os.chmod(sk_file, 1536) + with salt.utils.files.fopen(pk_file, 'wb') as keyf: + keyf.write(base64.b64encode(kp.pk)) + return 'saved sk_file:{0} pk_file: {1}'.format(sk_file, pk_file) + + +def enc(data, **kwargs): + ''' + Alias to `{box_type}_encrypt` + + box_type: secretbox, sealedbox(default) + ''' + if 'keyfile' in kwargs: + salt.utils.versions.warn_until( + 'Neon', + 'The \'keyfile\' argument has been deprecated and will be removed in Salt ' + '{version}. Please use \'sk_file\' argument instead.' + ) + kwargs['sk_file'] = kwargs['keyfile'] + + if 'key' in kwargs: + salt.utils.versions.warn_until( + 'Neon', + 'The \'key\' argument has been deprecated and will be removed in Salt ' + '{version}. Please use \'sk\' argument instead.' + ) + kwargs['sk'] = kwargs['key'] + + # ensure data is in bytes + data = salt.utils.stringutils.to_bytes(data) + + box_type = _get_config(**kwargs)['box_type'] + if box_type == 'sealedbox': + return sealedbox_encrypt(data, **kwargs) + if box_type == 'secretbox': + return secretbox_encrypt(data, **kwargs) + return sealedbox_encrypt(data, **kwargs) + + +def enc_file(name, out=None, **kwargs): + ''' + This is a helper function to encrypt a file and return its contents. + + You can provide an optional output file using `out` + + `name` can be a local file or when not using `salt-run` can be a url like `salt://`, `https://` etc. + + CLI Examples: + + .. code-block:: bash + + salt-run nacl.enc_file name=/tmp/id_rsa + salt-call nacl.enc_file name=salt://crt/mycert out=/tmp/cert + salt-run nacl.enc_file name=/tmp/id_rsa box_type=secretbox \ + sk_file=/etc/salt/pki/master/nacl.pub + ''' + try: + data = __salt__['cp.get_file_str'](name) + except Exception as e: + # likly using salt-run so fallback to local filesystem + with salt.utils.files.fopen(name, 'rb') as f: + data = salt.utils.stringutils.to_unicode(f.read()) + d = enc(data, **kwargs) + if out: + if os.path.isfile(out): + raise Exception('file:{0} already exist.'.format(out)) + with salt.utils.files.fopen(out, 'wb') as f: + f.write(salt.utils.stringutils.to_bytes(d)) + return 'Wrote: {0}'.format(out) + return d + + +def dec(data, **kwargs): + ''' + Alias to `{box_type}_decrypt` + + box_type: secretbox, sealedbox(default) + ''' + if 'keyfile' in kwargs: + salt.utils.versions.warn_until( + 'Neon', + 'The \'keyfile\' argument has been deprecated and will be removed in Salt ' + '{version}. Please use \'sk_file\' argument instead.' + ) + kwargs['sk_file'] = kwargs['keyfile'] + + # set boxtype to `secretbox` to maintain backward compatibility + kwargs['box_type'] = 'secretbox' + + if 'key' in kwargs: + salt.utils.versions.warn_until( + 'Neon', + 'The \'key\' argument has been deprecated and will be removed in Salt ' + '{version}. Please use \'sk\' argument instead.' + ) + kwargs['sk'] = kwargs['key'] + + # set boxtype to `secretbox` to maintain backward compatibility + kwargs['box_type'] = 'secretbox' + + # ensure data is in bytes + data = salt.utils.stringutils.to_bytes(data) + + box_type = _get_config(**kwargs)['box_type'] + if box_type == 'sealedbox': + return sealedbox_decrypt(data, **kwargs) + if box_type == 'secretbox': + return secretbox_decrypt(data, **kwargs) + return sealedbox_decrypt(data, **kwargs) + + +def dec_file(name, out=None, **kwargs): + ''' + This is a helper function to decrypt a file and return its contents. + + You can provide an optional output file using `out` + + `name` can be a local file or when not using `salt-run` can be a url like `salt://`, `https://` etc. + + CLI Examples: + + .. code-block:: bash + + salt-run nacl.dec_file name=/tmp/id_rsa.nacl + salt-call nacl.dec_file name=salt://crt/mycert.nacl out=/tmp/id_rsa + salt-run nacl.dec_file name=/tmp/id_rsa.nacl box_type=secretbox \ + sk_file=/etc/salt/pki/master/nacl.pub + ''' + try: + data = __salt__['cp.get_file_str'](name) + except Exception as e: + # likly using salt-run so fallback to local filesystem + with salt.utils.files.fopen(name, 'rb') as f: + data = salt.utils.stringutils.to_unicode(f.read()) + d = dec(data, **kwargs) + if out: + if os.path.isfile(out): + raise Exception('file:{0} already exist.'.format(out)) + with salt.utils.files.fopen(out, 'wb') as f: + f.write(salt.utils.stringutils.to_bytes(d)) + return 'Wrote: {0}'.format(out) + return d + + +def sealedbox_encrypt(data, **kwargs): + ''' + Encrypt data using a public key generated from `nacl.keygen`. + The encryptd data can be decrypted using `nacl.sealedbox_decrypt` only with the secret key. + + CLI Examples: + + .. code-block:: bash + + salt-run nacl.sealedbox_encrypt datatoenc + salt-call --local nacl.sealedbox_encrypt datatoenc pk_file=/etc/salt/pki/master/nacl.pub + salt-call --local nacl.sealedbox_encrypt datatoenc pk='vrwQF7cNiNAVQVAiS3bvcbJUnF0cN6fU9YTZD9mBfzQ=' + ''' + # ensure data is in bytes + data = salt.utils.stringutils.to_bytes(data) + + pk = _get_pk(**kwargs) + b = libnacl.sealed.SealedBox(pk) + return base64.b64encode(b.encrypt(data)) + + +def sealedbox_decrypt(data, **kwargs): + ''' + Decrypt data using a secret key that was encrypted using a public key with `nacl.sealedbox_encrypt`. + + CLI Examples: + + .. code-block:: bash + + salt-call nacl.sealedbox_decrypt pEXHQM6cuaF7A= + salt-call --local nacl.sealedbox_decrypt data='pEXHQM6cuaF7A=' sk_file=/etc/salt/pki/master/nacl + salt-call --local nacl.sealedbox_decrypt data='pEXHQM6cuaF7A=' sk='YmFkcGFzcwo=' + ''' + if data is None: + return None + + # ensure data is in bytes + data = salt.utils.stringutils.to_bytes(data) + + sk = _get_sk(**kwargs) + keypair = libnacl.public.SecretKey(sk) + b = libnacl.sealed.SealedBox(keypair) + return b.decrypt(base64.b64decode(data)) + + +def secretbox_encrypt(data, **kwargs): + ''' + Encrypt data using a secret key generated from `nacl.keygen`. + The same secret key can be used to decrypt the data using `nacl.secretbox_decrypt`. + + CLI Examples: + + .. code-block:: bash + + salt-run nacl.secretbox_encrypt datatoenc + salt-call --local nacl.secretbox_encrypt datatoenc sk_file=/etc/salt/pki/master/nacl + salt-call --local nacl.secretbox_encrypt datatoenc sk='YmFkcGFzcwo=' + ''' + # ensure data is in bytes + data = salt.utils.stringutils.to_bytes(data) + + sk = _get_sk(**kwargs) + b = libnacl.secret.SecretBox(sk) + return base64.b64encode(b.encrypt(data)) + + +def secretbox_decrypt(data, **kwargs): + ''' + Decrypt data that was encrypted using `nacl.secretbox_encrypt` using the secret key + that was generated from `nacl.keygen`. + + CLI Examples: + + .. code-block:: bash + + salt-call nacl.secretbox_decrypt pEXHQM6cuaF7A= + salt-call --local nacl.secretbox_decrypt data='pEXHQM6cuaF7A=' sk_file=/etc/salt/pki/master/nacl + salt-call --local nacl.secretbox_decrypt data='pEXHQM6cuaF7A=' sk='YmFkcGFzcwo=' + ''' + if data is None: + return None + + # ensure data is in bytes + data = salt.utils.stringutils.to_bytes(data) + + key = _get_sk(**kwargs) + b = libnacl.secret.SecretBox(key=key) diff --git a/tests/integration/modules/test_nacl.py b/tests/integration/modules/test_nacl.py index dab624ee9a..ed3b297e11 100644 --- a/tests/integration/modules/test_nacl.py +++ b/tests/integration/modules/test_nacl.py @@ -65,3 +65,65 @@ class NaclTest(ModuleCase): sk=sk, ) self.assertEqual(unencrypted_data, ret) + + def test_sealedbox_enc_dec(self): + ''' + Generate keys, encrypt, then decrypt. + ''' + # Store the data + ret = self.run_function( + 'nacl.keygen', + ) + self.assertIn('pk', ret) + self.assertIn('sk', ret) + pk = ret['pk'] + sk = ret['sk'] + + unencrypted_data = salt.utils.stringutils.to_bytes('hello') + + # Encrypt with pk + ret = self.run_function( + 'nacl.sealedbox_encrypt', + data=unencrypted_data, + pk=pk, + ) + encrypted_data = ret + + # Decrypt with sk + ret = self.run_function( + 'nacl.sealedbox_decrypt', + data=encrypted_data, + sk=sk, + ) + self.assertEqual(unencrypted_data, ret) + + def test_secretbox_enc_dec(self): + ''' + Generate keys, encrypt, then decrypt. + ''' + # Store the data + ret = self.run_function( + 'nacl.keygen', + ) + self.assertIn('pk', ret) + self.assertIn('sk', ret) + pk = ret['pk'] + sk = ret['sk'] + + unencrypted_data = salt.utils.stringutils.to_bytes('hello') + + # Encrypt with pk + ret = self.run_function( + 'nacl.secretbox_encrypt', + data=unencrypted_data, + sk=sk, + ) + encrypted_data = ret + + # Decrypt with sk + ret = self.run_function( + 'nacl.secretbox_decrypt', + data=encrypted_data, + sk=sk, + ) + self.assertEqual(unencrypted_data, ret) diff --git a/tests/integration/runners/test_nacl.py b/tests/integration/runners/test_nacl.py index a564ed03b2..b74f707eb3 100644 --- a/tests/integration/runners/test_nacl.py +++ b/tests/integration/runners/test_nacl.py @@ -88,3 +88,65 @@ class NaclTest(ShellCase): ) self.assertIn('return', ret) self.assertEqual(unencrypted_data, ret['return']) + + def test_sealedbox_enc_dec(self): + ''' + Generate keys, encrypt, then decrypt. + ''' + # Store the data + ret = self.run_run_plus( + 'nacl.keygen', + ) + self.assertIn('pk', ret['return']) + self.assertIn('sk', ret['return']) + pk = ret['return']['pk'] + sk = ret['return']['sk'] + + unencrypted_data = 'hello' + + # Encrypt with pk + ret = self.run_run_plus( + 'nacl.sealedbox_encrypt', + data=unencrypted_data, + pk=pk, + ) + encrypted_data = ret['return'] + + # Decrypt with sk + ret = self.run_run_plus( + 'nacl.sealedbox_decrypt', + data=encrypted_data, + sk=sk, + ) + self.assertEqual(unencrypted_data, ret['return']) + + def test_secretbox_enc_dec(self): + ''' + Generate keys, encrypt, then decrypt. + ''' + # Store the data + ret = self.run_run_plus( + 'nacl.keygen', + ) + self.assertIn('pk', ret['return']) + self.assertIn('sk', ret['return']) + pk = ret['return']['pk'] + sk = ret['return']['sk'] + + unencrypted_data = 'hello' + + # Encrypt with pk + ret = self.run_run_plus( + 'nacl.secretbox_encrypt', + data=unencrypted_data, + sk=sk, + ) + encrypted_data = ret['return'] + + # Decrypt with sk + ret = self.run_run_plus( + 'nacl.secretbox_decrypt', + data=encrypted_data, + sk=sk, + ) + self.assertEqual(unencrypted_data, ret['return'])