Skip to content

Commit

Permalink
Allow for alternative storage services via the entry point `credsmash…
Browse files Browse the repository at this point in the history
….storage_service`.
  • Loading branch information
nathan-muir committed Oct 3, 2016
1 parent cfc7a2f commit 081ae66
Show file tree
Hide file tree
Showing 14 changed files with 266 additions and 210 deletions.
16 changes: 14 additions & 2 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,24 @@
- You can define alternative key-services, by using the `credsmash.key_service` [entry point](http://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points).

eg, to load a key service called `custom_ks`
eg, to load a key service called `custom_ks`
```cfg
[credsmash]
key_service = custom_ks
[credsmash:key_service:custom_ks]
option_1 = a
option_2 = b
```

- You can define alternative storage-services, by using the `credsmash.storage_service` [entry point](http://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points).

eg, to load a storage service called `custom_s3_backend`

```cfg
[credsmash]
storage_service = custom_s3_backend
[credsmash:storage_service:custom_s3_backend]
option_1 = a
option_2 = b
```
1 change: 0 additions & 1 deletion credsmash/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@
from .list import list_secrets
from .prune import prune_secret
from .put import put_secret
from .setup import create_secrets_table
17 changes: 7 additions & 10 deletions credsmash/api/delete.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
from __future__ import absolute_import, division, print_function, unicode_literals

import logging
from boto3.dynamodb.conditions import Attr

logger = logging.getLogger(__name__)


def delete_secret(secrets_table, secret_name):
response = secrets_table.scan(
FilterExpression=Attr("name").eq(secret_name),
ProjectionExpression="#N, version",
ExpressionAttributeNames={"#N": "name"}
)
if response['Count'] == 0:
def delete_secret(storage_service, secret_name):
secrets = storage_service.list_one(secret_name)
if not secrets:
logger.info('Not found: %s', secret_name)
return

for secret in response["Items"]:
for secret in secrets:
logger.info("Deleting %s -- version %s",
secret["name"], secret["version"])
secrets_table.delete_item(Key=secret)
storage_service.delete_one(
secret["name"], secret["version"]
)
logger.info('Deleted %s', secret_name)
43 changes: 5 additions & 38 deletions credsmash/api/get.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,11 @@
from __future__ import absolute_import, division, print_function, unicode_literals

from boto3.dynamodb.types import Binary
from boto3.dynamodb.conditions import Key as ConditionKey
from credsmash.util import ItemNotFound, padded_int
from credsmash.crypto import open_secret
from credsmash.util import ItemNotFound


def get_secret(secrets_table, key_service, secret_name, version=None):
if version is None:
ciphertext = get_latest_secret(secrets_table, secret_name)
else:
ciphertext = get_versioned_secret(secrets_table, secret_name, version)

def get_secret(storage_service, key_service, secret_name, version=None):
ciphertext = storage_service.get_one(secret_name, version=version)
if not ciphertext:
raise ItemNotFound("Item {'name': '%s', 'version': %s} couldn't be found." % (secret_name, version))
return open_secret(key_service, ciphertext)


def _unwrap_dynamodb_types(obj):
return {
k: (v.value if isinstance(v, Binary) else v)
for k, v in obj.items()
}


def get_latest_secret(secrets_table, secret_name):
# do a consistent fetch of the credential with the highest version
response = secrets_table.query(
Limit=1,
ScanIndexForward=False,
ConsistentRead=True,
KeyConditionExpression=ConditionKey("name").eq(secret_name)
)
if response["Count"] == 0:
raise ItemNotFound("Item {'name': '%s'} couldn't be found." % secret_name)
return _unwrap_dynamodb_types(response["Items"][0])


def get_versioned_secret(secrets_table, secret_name, version):
version = padded_int(version)
response = secrets_table.get_item(Key={"name": secret_name, "version": version})
if "Item" not in response:
raise ItemNotFound(
"Item {'name': '%s', 'version': '%s'} couldn't be found." % (secret_name, version))
return _unwrap_dynamodb_types(response["Item"])
8 changes: 2 additions & 6 deletions credsmash/api/list.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
from __future__ import absolute_import, division, print_function, unicode_literals


def list_secrets(secrets_table):
response = secrets_table.scan(
ProjectionExpression="#N, version",
ExpressionAttributeNames={"#N": "name"}
)
return response["Items"]
def list_secrets(storage_service):
return storage_service.list_all()
23 changes: 10 additions & 13 deletions credsmash/api/prune.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
from __future__ import absolute_import, division, print_function, unicode_literals

import logging
from boto3.dynamodb.conditions import Attr

logger = logging.getLogger(__name__)


def prune_secret(secrets_table, secret_name):
response = secrets_table.scan(
FilterExpression=Attr("name").eq(secret_name),
ProjectionExpression="#N, version",
ExpressionAttributeNames={"#N": "name"}
)
if response['Count'] == 0:
def prune_secret(storage_service, secret_name):
secrets = storage_service.list_one(secret_name)
if not secrets:
logger.info('Not found: %s', secret_name)
return

max_version = max(
int(secret['version'])
for secret in response['Items']
secret['version']
for secret in secrets
)

for secret in response["Items"]:
if int(secret['version']) == max_version:
for secret in secrets:
if secret['version'] == max_version:
continue
logger.info("Deleting %s -- version %s",
secret["name"], secret["version"])
secrets_table.delete_item(Key=secret)
storage_service.delete_one(
secret['name'], secret['version']
)
logger.info('Pruned %s (current version=%d)', secret_name, max_version)
39 changes: 8 additions & 31 deletions credsmash/api/put.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,26 @@
from __future__ import absolute_import, division, print_function, unicode_literals

from boto3.dynamodb.conditions import Attr, Key as ConditionKey
from boto3.dynamodb.types import Binary

from credsmash.crypto import seal_secret, ALGO_AES_CTR
from credsmash.util import padded_int


def put_secret(
secrets_table, key_service, secret_name,
storage_service, key_service, secret_name,
plaintext, version=None, algorithm=ALGO_AES_CTR, **seal_kwargs
):
sealed = seal_secret(
key_service,
plaintext,
algorithm=algorithm,
binary_type=Binary,
binary_type=storage_service.binary_type,
**seal_kwargs
)

if version is None:
version = 1 + get_highest_version(
secrets_table, secret_name
)
latest_secret = storage_service.get_one(secret_name)
version = 1
if latest_secret:
version += latest_secret['version']

data = {
'name': secret_name,
'version': padded_int(version),
}
data.update(sealed)
secrets_table.put_item(Item=data, ConditionExpression=Attr('name').not_exists())
storage_service.put_one(secret_name, version, sealed)
return version


def get_highest_version(secrets_table, secret_name):
response = secrets_table.query(
Limit=1,
ScanIndexForward=False,
ConsistentRead=True,
KeyConditionExpression=ConditionKey("name").eq(secret_name),
ProjectionExpression="version"
)

if response["Count"] == 0:
return 0
try:
return int(response["Items"][0]["version"], 10)
except ValueError:
raise RuntimeError('Could not parse current version: %s' % response["Items"][0]["version"])
46 changes: 0 additions & 46 deletions credsmash/api/setup.py

This file was deleted.

Loading

0 comments on commit 081ae66

Please sign in to comment.