secretconf module

secretconf - secret configurations easily

Source code
"""secretconf - secret configurations easily"""


__version__ = '0.1.0'
__author__ = 'Ahmed Youssef <xmonader@gmail.com>'

"""
secretconf file looks like this
```
[sillyapp2]
user = xmonader
__password = RSE6sNZDb04mnQhF6bPRWW3SVrCyy+u13hpBiiman4XmBcip9N8Ga3q9O2sZxZadLqCd

[sillyapp3]
user = xmon121
__password = rjR4gSRQCCfOu4q0g7GyUFXiTTocYyKMP+cWbuHL9QaPfh9a/pKxrZEfpiQbhQ==

[sillyapp1]
user = ash
__password = 8wNHS635V5Dxu/aeX1T4xt+OuH2KFzLU4TgOSU90VzMZh2nDY9ui0yhFX8yzyg==
```



hush --section sillyapp1 --fields 'user,__password'


"""

import os
import base64
import hashlib
from configparser import ConfigParser
import nacl.utils
from nacl.public import PrivateKey, Box
# from nacl.secret import SecretBox
import click
import npyscreen


def hash32(data):
    """
    Get sha256 of bytes

    @param data bytes: usually the private_key to be used later with encrypt, decrypt functions.

    returns sha256 digets of data bytes.
    """
    m = hashlib.sha256()
    m.update(data)
    return m.digest()


def encrypt(data, box):
    """
    Encrypt data using private_key

    @param data bytes : bytes to encrypt
    @param box: nacl box to encrypt data

    returns string of base64 encoded encrytped data using private_key
    """

    if isinstance(data, str):
        data = data.encode()

    return base64.b64encode(box.encrypt(data)).decode()


def decrypt(data, box):
    """
    Decrypt data using private_key

    @param data bytes : data to decrypt
    @param private_key: nacl box to decrypt data

    returns string of the original data 
    """
    _bytes = base64.b64decode(data)
    return box.decrypt(_bytes).decode()


def make_config(section=None, data=None, config_path='/tmp/secrets.conf', private_key=''):
    """
    stores credentials section or app with data in specific configuration path using a private key
    data keys prefixed with __ are considered private and will be encrypted.

    @param section str: application or section name.
        e.g: myapp, githubuser, gitlaborg
    @param data dict: dict of fields and their values [fields prefixed with __ are private]
        e.g: {'name': 'xmonader', '__password': 'notmypassword'}

    @param config_path str: secretconf path defaults to /tmp/secrets.conf
    @param private_key: key of 32 bytes (you should use sha256 or hash32 function on the bytes of your private key)
     """
    data = data or {}
    if not os.path.exists(config_path):
        os.mknod(config_path)
    conf = ConfigParser()
    conf.read_file(open(config_path))
    conf[section] = {}
    sk = PrivateKey(private_key)
    pk = sk.public_key
    box = Box(sk, pk)

    for k, v in data.items():
        if k.startswith("__"):
            v = encrypt(v, box)
        conf[section][k] = v

    with open(config_path, "w") as cf:
        conf.write(cf)


def read_config(section=None, config_path='/tmp/secrets.conf', private_key=''):
    """
    reads credentials section or app with data in specific configuration path using a private key
    data keys prefixed with __ are considered private and will be encrypted.

    @param section str: application or section name.
        e.g: myapp, githubuser, gitlaborg
    @param config_path str: secretconf path defaults to /tmp/secrets.conf
    @param private_key: key of 32 bytes (you should use sha256 or hash32 function on the bytes of your private key)
     """

    data = {}
    if not os.path.exists(config_path):
        os.mknod(config_path)

    conf = ConfigParser()
    conf.read_file(open(config_path))

    sk = PrivateKey(private_key)
    pk = sk.public_key
    box = Box(sk, pk)

    for s in conf.sections():
        secdict = {}
        for k, v in conf[s].items():
            if k.startswith("__"):
                v = decrypt(v, box)
            secdict[k] = v
        data[s] = secdict

    return data


# TODO: support passphrases and use ssh-key in agent.
@click.command()
@click.option('--section', default='', help='Section (Appname)')
@click.option('--privatekey', default='~/.ssh/id_rsa', help='Privatekey path')
@click.option('--configpath', default='/tmp/secrets.conf', help='Secret configuration path')
@click.option('--fields', default='', help='quoted comma separated fields; secret fields are prefixed with __')
def hush(section, privatekey, configpath, fields):
    privatekey = os.path.expanduser(privatekey)
    configpath = os.path.expanduser(configpath)
    assert os.path.exists(privatekey)

    privatekey = open(privatekey, 'rb').read()
    privatekey = hash32(privatekey)

    widgets = []

    data = read_config(section, configpath, privatekey)
    if section not in data:
        data[section] = {}
    fields = [f.strip() for f in fields.split(",")]

    def curses_app(*args):
        form = npyscreen.Form()
        for f in fields:
            w = form.add(npyscreen.TitleText, name=f,
                         value=data[section].get(f, ''))
            w._forfield = f
            widgets.append(w)

        form.edit()
        for w in widgets:
            data[section][w._forfield] = w.value

    npyscreen.wrapper_basic(curses_app)
    make_config(section=section, data=data[section],
                config_path=configpath, private_key=privatekey)


if __name__ == '__main__':
    hush()

Functions

def decrypt(data, box)

Decrypt data using private_key

@param data bytes : data to decrypt @param private_key: nacl box to decrypt data

returns string of the original data

Source code
def decrypt(data, box):
    """
    Decrypt data using private_key

    @param data bytes : data to decrypt
    @param private_key: nacl box to decrypt data

    returns string of the original data 
    """
    _bytes = base64.b64decode(data)
    return box.decrypt(_bytes).decode()
def encrypt(data, box)

Encrypt data using private_key

@param data bytes : bytes to encrypt @param box: nacl box to encrypt data

returns string of base64 encoded encrytped data using private_key

Source code
def encrypt(data, box):
    """
    Encrypt data using private_key

    @param data bytes : bytes to encrypt
    @param box: nacl box to encrypt data

    returns string of base64 encoded encrytped data using private_key
    """

    if isinstance(data, str):
        data = data.encode()

    return base64.b64encode(box.encrypt(data)).decode()
def hash32(data)

Get sha256 of bytes

@param data bytes: usually the private_key to be used later with encrypt, decrypt functions.

returns sha256 digets of data bytes.

Source code
def hash32(data):
    """
    Get sha256 of bytes

    @param data bytes: usually the private_key to be used later with encrypt, decrypt functions.

    returns sha256 digets of data bytes.
    """
    m = hashlib.sha256()
    m.update(data)
    return m.digest()
def make_config(section=None, data=None, config_path='/tmp/secrets.conf', private_key='')

stores credentials section or app with data in specific configuration path using a private key data keys prefixed with __ are considered private and will be encrypted.

@param section str: application or section name. e.g: myapp, githubuser, gitlaborg @param data dict: dict of fields and their values [fields prefixed with __ are private] e.g: {'name': 'xmonader', '__password': 'notmypassword'}

@param config_path str: secretconf path defaults to /tmp/secrets.conf @param private_key: key of 32 bytes (you should use sha256 or hash32 function on the bytes of your private key)

Source code
def make_config(section=None, data=None, config_path='/tmp/secrets.conf', private_key=''):
    """
    stores credentials section or app with data in specific configuration path using a private key
    data keys prefixed with __ are considered private and will be encrypted.

    @param section str: application or section name.
        e.g: myapp, githubuser, gitlaborg
    @param data dict: dict of fields and their values [fields prefixed with __ are private]
        e.g: {'name': 'xmonader', '__password': 'notmypassword'}

    @param config_path str: secretconf path defaults to /tmp/secrets.conf
    @param private_key: key of 32 bytes (you should use sha256 or hash32 function on the bytes of your private key)
     """
    data = data or {}
    if not os.path.exists(config_path):
        os.mknod(config_path)
    conf = ConfigParser()
    conf.read_file(open(config_path))
    conf[section] = {}
    sk = PrivateKey(private_key)
    pk = sk.public_key
    box = Box(sk, pk)

    for k, v in data.items():
        if k.startswith("__"):
            v = encrypt(v, box)
        conf[section][k] = v

    with open(config_path, "w") as cf:
        conf.write(cf)
def read_config(section=None, config_path='/tmp/secrets.conf', private_key='')

reads credentials section or app with data in specific configuration path using a private key data keys prefixed with __ are considered private and will be encrypted.

@param section str: application or section name. e.g: myapp, githubuser, gitlaborg @param config_path str: secretconf path defaults to /tmp/secrets.conf @param private_key: key of 32 bytes (you should use sha256 or hash32 function on the bytes of your private key)

Source code
def read_config(section=None, config_path='/tmp/secrets.conf', private_key=''):
    """
    reads credentials section or app with data in specific configuration path using a private key
    data keys prefixed with __ are considered private and will be encrypted.

    @param section str: application or section name.
        e.g: myapp, githubuser, gitlaborg
    @param config_path str: secretconf path defaults to /tmp/secrets.conf
    @param private_key: key of 32 bytes (you should use sha256 or hash32 function on the bytes of your private key)
     """

    data = {}
    if not os.path.exists(config_path):
        os.mknod(config_path)

    conf = ConfigParser()
    conf.read_file(open(config_path))

    sk = PrivateKey(private_key)
    pk = sk.public_key
    box = Box(sk, pk)

    for s in conf.sections():
        secdict = {}
        for k, v in conf[s].items():
            if k.startswith("__"):
                v = decrypt(v, box)
            secdict[k] = v
        data[s] = secdict

    return data