Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,106 @@ Usage

* `Read The API Documentation <https://laslabs.github.io/python-red-october>`_

Connect to Red October
----------------------

Connect to a Red October instance ``https://172.17.0.2:8080`` as ``Admin`` with the password ``password``:

.. code-block:: python

from red_october import RedOctober
ro = RedOctober('172.17.0.2', 8080, 'Admin', 'password')

Turn off certificate verification:

.. code-block:: python

from red_october import RedOctober
ro = RedOctober('172.17.0.2', 8080, 'Admin', 'password', verify=False)

Create a Vault
--------------

This will create a vault using the currently logged in credentials as the first admin:

.. code-block:: python

ro.create_vault()

Create a User
-------------

If you would like to create a user for the currently logged in session:

.. code-block:: python

ro.create_user('rsa')

If you are currently playing the role of an administrator, you can also create a user for someone else:

.. code-block:: python

ro.create_user('ecc', name="Ashley", password="@shl3y!")

Delegate Decryption Rights
--------------------------

In order to decrypt a piece of data, the Vault will need to be delegated decryption rights
by document owners.

In order to delegate rights to a Vault, and subsequently grant access to your data:

.. code-block:: python

from datetime import timedelta
delegate_time = timedelta(days=1)
delegate_uses = 20
ro.delegate(delegate_time, delegate_uses)

The above delegation will expire in 1 day or 20 uses, whichever comes first.

Encrypt Some Data
-----------------

To encrypt some data so that only you can decrypt it:

.. code-block:: python

data = 'Super Secret Stuff!'
encrypted = ro.encrypt(data)

To encrypt some data with multiple owners, also setting a minimum amount of delegations
that are required for decryption:

.. code-block:: python

owners = ['Admin', 'Ashley', 'Bob', 'Jenna']
minimum_delegations = 2
data = 'Super Secret Stuff!'
encrypted = ro.encrypt(data, owners, minimum_delegations)

To encrypt data as another user:

.. code-block:: python

data = 'Super Secret Stuff!'
encrypted_string = ro.encrypt(data, name='Todd', password='Todd Pass')

Decrypt Some Data
-----------------

Decryption will use the current session credentials by default:

.. code-block:: python

decrypted_string = ro.decrypt(encrypted_string)

To decrypt as another user:

.. code-block:: python

decrypted_string = ro.decrypt(encrypted_string, name='Todd', password='Todd Pass')

Known Issues / Road Map
=======================

Expand Down
77 changes: 56 additions & 21 deletions red_october/red_october.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Copyright 2016 LasLabs Inc.
# License MIT (https://opensource.org/licenses/MIT).

import json
import requests

from datetime import timedelta
Expand All @@ -20,7 +21,7 @@ class RedOctober(object):
https://github.com/cloudflare/redoctober
"""

def __init__(self, host, port, name, password, ssl=True):
def __init__(self, host, port, name, password, ssl=True, verify=True):
""" It initializes the RedOctober API using the provided credentials.

Args:
Expand All @@ -29,11 +30,15 @@ def __init__(self, host, port, name, password, ssl=True):
name (str): Account name for use as login.
password (str): Password for account.
ssl (bool): Is server SSL encrypted?
verify (bool or str): File path of CA cert for verification,
`True` to use system certs, or `False` to disable certificate
verification.
"""
ssl = 'https' if ssl else 'http'
self.uri_base = '%s://%s:%d' % (ssl, host, port)
self.name = name
self.password = password
self.verify = verify

def create_vault(self):
""" It creates a new vault.
Expand Down Expand Up @@ -67,7 +72,7 @@ def delegate(self, time=None, uses=None):
})
return self.call('delegate', data=data)

def create_user(self, user_type='rsa'):
def create_user(self, user_type='rsa', name=None, password=None):
""" It creates a new user account.

Allows an optional ``UserType`` to be specified which controls how the
Expand All @@ -77,12 +82,22 @@ def create_user(self, user_type='rsa'):
Args:
user_type (str): Controls how the record is encrypted. This can have
a value of either ``ecc`` or ``rsa``.
name (str, optional): Account name for use as login. Omit to use
current session credentials.
password (str, optional): Password for account. Omit to use
current session credentials.
Returns:
bool: Status of user creation.
"""

data = self._clean_mapping({
'UserType': EnumUserType[user_type].name.upper(),
})
if name and password:
data.update({
'Name': name,
'Password': password,
})
return self.call('create-user', data=data)

def get_summary(self):
Expand Down Expand Up @@ -118,31 +133,46 @@ def get_summary(self):
"""
return self.call('summary')

def encrypt(self, minimum, owners, data):
def encrypt(self, data, owners=None, minimum=1, name=None, password=None):
""" It allows a user to encrypt a piece of data.

Args:
minimum (int): Minimum number of users from ``owners`` set that
must have delegated their keys to the server.
owners (iter): Iterator of strings indicating users that may
decrypt the document.
data (str): Data to encrypt.
owners (iter of str, optional): Representing the users that may
decrypt the document. None for self-owned.
minimum (int, optional): Minimum number of users from ``owners``
set that must have delegated their keys to the server.
name (str, optional): Account name for use as login. Omit to use
current session credentials.
password (str, optional): Password for account. Omit to use
current session credentials.
Returns:
str: Base64 encoded string representing the encrypted string.
"""
if owners is None:
owners = [self.name]
data = self._clean_mapping({
'Minimum': minimum,
'Owners': owners,
'Data': data.encode('base64'),
})
if name and password:
data.update({
'Name': name,
'Password': password,
})
return self.call('encrypt', data=data)

def decrypt(self, data):
def decrypt(self, data, name=None, password=None):
""" It allows a user to decrypt a piece of data.

Args:
data (str): Base64 encoded string representing the encrypted
string.
name (str, optional): Account name for use as login. Omit to use
current session credentials.
password (str, optional): Password for account. Omit to use
current session credentials.
Raises:
RedOctoberDecryptException: If not enough ``minimum`` users from
the set of ``owners`` have delegated their keys to the server,
Expand All @@ -153,10 +183,18 @@ def decrypt(self, data):
data = self._clean_mapping({
'Data': data,
})
if name and password:
data.update({
'Name': name,
'Password': password,
})
try:
return self.call('decrypt', data=data)
data = self.call('decrypt', data=data)
except RedOctoberRemoteException as e:
raise RedOctoberDecryptException(e.message)
data = json.loads(data.decode('base64'))
return data['Data']


def get_owners(self, data):
""" It provides the delegates required to decrypt a piece of data.
Expand Down Expand Up @@ -333,9 +371,9 @@ def call(self, endpoint, method='POST', params=None, data=None):
Args:
endpoint (str): RedOctober endpoint to call (e.g. ``newcert``).
method (str): HTTP method to utilize for the Request.
params: (dict|bytes) Data to be sent in the query string
params: (dict or bytes) Data to be sent in the query string
for the Request.
data: (dict|bytes|file) Data to send in the body of the
data: (dict or bytes or file) Data to send in the body of the
Request.
Raises:
RedOctoberRemoteException: In the event of a ``False`` in the
Expand All @@ -346,7 +384,9 @@ def call(self, endpoint, method='POST', params=None, data=None):
success.
"""
endpoint = '%s/%s' % (self.uri_base, endpoint)
if data:
if data is None:
data = {}
if 'Name' not in data:
data.update({
'Name': self.name,
'Password': self.password,
Expand All @@ -355,20 +395,15 @@ def call(self, endpoint, method='POST', params=None, data=None):
method=method,
url=endpoint,
params=params,
data=data,
json=data,
verify=self.verify,
)
response = response.json()
if response['Status'] != 'ok':
raise RedOctoberRemoteException(
'\n'.join([
'Response:',
'\n'.join(response.get('Response', [])),
])
response['Status'],
)
try:
return response['Response']
except KeyError:
return True
return response.get('Response', True)

def _clean_mapping(self, mapping):
""" It removes false entries from mapping.
Expand Down
3 changes: 2 additions & 1 deletion red_october/tests/test_red_october.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ def test_call_request(self, requests):
method='method',
url='https://test:1/endpoint',
params='params',
data=data,
json=data,
verify=True,
)

@mock.patch.object(requests, 'request')
Expand Down