Merge of previous project work into this project

This commit is contained in:
John Wood 2013-04-01 18:26:03 -05:00
parent 81d7598ca4
commit 8f783f473b
84 changed files with 1866 additions and 10258 deletions

3
.gitignore vendored
View File

@ -40,3 +40,6 @@ nosetests.xml
# Sqlite databases
*.sqlite3
*.db
# Misc. generated files
versiononly.txt

2
Authors Normal file
View File

@ -0,0 +1,2 @@
John Wood <john.wood@rackspace.com>

190
HACKING.rst Normal file
View File

@ -0,0 +1,190 @@
Barbican Style Commandments
=======================
TBD: Translate the content below to the Barbican project.
...Adding cosmetic change to trigger build process...
- Step 1: Read http://www.python.org/dev/peps/pep-0008/
- Step 2: Read http://www.python.org/dev/peps/pep-0008/ again
- Step 3: Read on
General
-------
- Put two newlines between top-level code (funcs, classes, etc)
- Put one newline between methods in classes and anywhere else
- Do not write "except:", use "except Exception:" at the very least
- Include your name with TODOs as in "#TODO(termie)"
- Do not name anything the same name as a built-in or reserved word
Imports
-------
- Do not make relative imports
- Order your imports by the full module path
- Organize your imports according to the following template
Example::
# vim: tabstop=4 shiftwidth=4 softtabstop=4
{{stdlib imports in human alphabetical order}}
\n
{{third-party lib imports in human alphabetical order}}
\n
{{glance imports in human alphabetical order}}
\n
\n
{{begin your code}}
Human Alphabetical Order Examples
---------------------------------
Example::
import httplib
import logging
import random
import StringIO
import time
import unittest
import eventlet
import webob.exc
import glance.api.middleware
from glance.api import images
from glance.auth import users
import glance.common
from glance.endpoint import cloud
from glance import test
Docstrings
----------
Docstrings are required for all functions and methods.
Docstrings should ONLY use triple-double-quotes (``"""``)
Single-line docstrings should NEVER have extraneous whitespace
between enclosing triple-double-quotes.
**INCORRECT** ::
""" There is some whitespace between the enclosing quotes :( """
**CORRECT** ::
"""There is no whitespace between the enclosing quotes :)"""
Docstrings that span more than one line should look like this:
Example::
"""
Start the docstring on the line following the opening triple-double-quote
If you are going to describe parameters and return values, use Sphinx, the
appropriate syntax is as follows.
:param foo: the foo parameter
:param bar: the bar parameter
:returns: return_type -- description of the return value
:returns: description of the return value
:raises: AttributeError, KeyError
"""
**DO NOT** leave an extra newline before the closing triple-double-quote.
Dictionaries/Lists
------------------
If a dictionary (dict) or list object is longer than 80 characters, its items
should be split with newlines. Embedded iterables should have their items
indented. Additionally, the last item in the dictionary should have a trailing
comma. This increases readability and simplifies future diffs.
Example::
my_dictionary = {
"image": {
"name": "Just a Snapshot",
"size": 2749573,
"properties": {
"user_id": 12,
"arch": "x86_64",
},
"things": [
"thing_one",
"thing_two",
],
"status": "ACTIVE",
},
}
Calling Methods
---------------
Calls to methods 80 characters or longer should format each argument with
newlines. This is not a requirement, but a guideline::
unnecessarily_long_function_name('string one',
'string two',
kwarg1=constants.ACTIVE,
kwarg2=['a', 'b', 'c'])
Rather than constructing parameters inline, it is better to break things up::
list_of_strings = [
'what_a_long_string',
'not as long',
]
dict_of_numbers = {
'one': 1,
'two': 2,
'twenty four': 24,
}
object_one.call_a_method('string three',
'string four',
kwarg1=list_of_strings,
kwarg2=dict_of_numbers)
Internationalization (i18n) Strings
-----------------------------------
In order to support multiple languages, we have a mechanism to support
automatic translations of exception and log strings.
Example::
msg = _("An error occurred")
raise HTTPBadRequest(explanation=msg)
If you have a variable to place within the string, first internationalize the
template string then do the replacement.
Example::
msg = _("Missing parameter: %s") % ("flavor",)
LOG.error(msg)
If you have multiple variables to place in the string, use keyword parameters.
This helps our translators reorder parameters when needed.
Example::
msg = _("The server with id %(s_id)s has no key %(m_key)s")
LOG.error(msg % {"s_id": "1234", "m_key": "imageId"})
Creating Unit Tests
-------------------
For every new feature, unit tests should be created that both test and
(implicitly) document the usage of said feature. If submitting a patch for a
bug that had no unit test, a new passing unit test should be added. If a
submitted bug fix does have a unit test, be sure to add a new one that fails
without the patch and passes with the patch.

View File

@ -1,4 +1,3 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
@ -187,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2013, Rackspace (http://www.rackspace.com)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -199,4 +198,4 @@
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.

11
MANIFEST.in Normal file
View File

@ -0,0 +1,11 @@
include ChangeLog
include README.rst
include MANIFEST.in pylintrc
include Authors
include HACKING.rst
include LICENSE
include setup.cfg
include babel.cfg tox.ini
include barbican/tests/api/resources_test.py
graft tools
graft etc

View File

@ -1,4 +1,41 @@
barbican
========
# Barbican
Barbican is a ReST API designed for the secure storage, provisioning and management of secrets. It is aimed at being useful for all environments, including large ephemeral Clouds. The project is supported by [Rackspace Hosting](http://www.rackspace.com).
Barbican is part of a set of applications that make up the CloudKeep ecosystem. The other systems are:
* [Postern](https://github.com/cloudkeep/postern) - Go based agent that provides access to secrets from the Barbican API.
* [Palisade](https://github.com/cloudkeep/palisade) - AngularJS based web ui for the Barbican API.
* [Keep](https://github.com/cloudkeep/keep) - A python-based command line client for the Barbican API.
Additional documentation can be found on the [Github Wiki](https://github.com/cloudkeep/barbican/wiki). For questions, comments or concerns, hop on the [mailing list](https://groups.google.com/forum/#!forum/cloudkeep) and let us know what you think.
## Getting Started
* Installation
* Contributing
* API Documentation
* Technology
* FAQ
## Why Should You Use Barbican?
The current state of key management is atrocious. While Windows does have some decent options through the use of the Data Protection API (DPAPI) and Active Directory, Linux lacks a cohesive story around how to manage keys for applicaiton use.
Barbican was designed to solve this problem. The system was motivated by internal Rackspace needs, requirements from [OpenStack](http://www.openstack.org/) and a realization that the current state of the art could use some help.
Barbican will handle many types of secrets, including:
* **Symmetric Keys** - Used to perform reversible encryption of data at rest, typically using the AES algorithm set. This type of key is required to enable features like [encrypted Swift containers and Cinder volumes](http://www.openstack.org/software/openstack-storage/), [encrypted Cloud Backups](http://www.rackspace.com/cloud/backup/), etc.
* **Asymmetric Keys** - Asymmetric key pairs (sometimes referred to as [public / private keys](http://en.wikipedia.org/wiki/Public-key_cryptography)) are used in many scenarios where communication between untrusted parties is desired. The most common case is with SSL/TLS certificates, but also is used in solutions like SSH keys, S/MIME (mail) encryption and digital signatures.
* **Raw Secrets** - Barbican stores secrets as a base64 encoded block of data (encrypted, naturally). Clients can use the API to store any secrets in any format they desire. The [Postern](https://github.com/cloudkeep/postern) agent is capable of presenting these secrets in various formats to ease integration.
For the symmetric and asymmetric key types, Barbican supports full lifecycle management including provisioning, expiration, reporting, etc. A plugin system allows for multiple certificate authority support (including public and private CAs).
## Design Goals
1. Provide a central secret-store capable of distributing secret / keying material to all types of deployments including ephemeral Cloud instances.
2. Support reasonable compliance regimes through reporting and auditability.
3. Application adoption costs should be minimal or non-existent.
4. Build a community and ecosystem by being open-source and extensible.
5. Improve security through sane defaults and centralized management of [policies for all secrets](https://github.com/cloudkeep/barbican/wiki/Policies).
6. Out of band communication mechanism to notify and protect sensitive assets.
A server providing secure, audited access to encryption materials.

48
README.rst Normal file
View File

@ -0,0 +1,48 @@
====
Barbican
====
TBD: Make this Barbican specific....
Glance is a project that defines services for discovering, registering,
retrieving and storing virtual machine images. The discovery and registration
responsibilities are handled by the `glance-registry` component while the
retrieval and storage responsiblities are handled by the `glance-api`
component.
Quick Start
-----------
If you'd like to run trunk, you can clone the git repo:
git clone git@github.com:openstack/glance.git
Install Glance by running::
python setup.py build
sudo python setup.py install
By default, `glance-registry` will use a SQLite database. If you'd like to use
MySQL, or make other adjustments, you can modify the glance.cnf file (see
documentation for more details).
Now that Glance is installed, you can start the service. The easiest way to
do that is by using the `glance-control` utility which runs both the
`glance-api` and `glance-registry` services::
glance-control all start
Once both services are running, you can now use the `glance` tool to
register new images in Glance.
glance add name="My Image" < /path/to/my/image
With an image registered, you can now configure your IAAS provider to use
Glance as its image service and begin spinning up instances from your
newly registered images.

1
babel.cfg Normal file
View File

@ -0,0 +1 @@
[python: **.py]

View File

@ -1,90 +0,0 @@
# -*- coding: utf-8 -*-
"""
Barbican
~~~~~~~~
A proof of concept implementation of a key management server for
use with the postern agent (https://github.com/cloudkeep/postern).
DO NOT USE THIS IN PRODUCTION. IT IS NOT SECURE IN ANY WAY.
YOU HAVE BEEN WARNED.
:copyright: (c) 2013 by Jarret Raim
:license: Apache 2.0, see LICENSE for details
"""
import os
from flask import Flask, render_template, redirect, flash, request
from flask.ext.admin import Admin
from flask.ext.admin.contrib.sqlamodel import ModelView
from flask.ext import login, wtf
from flask.ext.login import login_user
from barbican_api import api
from database import db_session, init_db
from models import User, Tenant, Key, Policy, Event
app = Flask(__name__)
app.secret_key = '79f9823f1f0---DEVELOPMENT---c46cebdd1c8f3d0742e02'
app.register_blueprint(api)
admin = Admin(app, name="Barbican Admin")
admin.add_view(ModelView(User, db_session))
admin.add_view(ModelView(Tenant, db_session))
admin.add_view(ModelView(Key, db_session))
admin.add_view(ModelView(Policy, db_session))
admin.add_view(ModelView(Event, db_session))
login_manager = login.LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
@app.route("/")
@login.login_required
def hello():
return render_template("index.html")
#
# Login forms
#
class LoginForm(wtf.Form):
login = wtf.TextField(validators=[wtf.required()])
password = wtf.PasswordField(validators=[wtf.required()])
def validate_login(self, field):
user = self.get_user()
if user is None or user.password != self.password.data:
raise wtf.ValidationError('Invalid username or credentials.')
def get_user(self):
return User.query.filter_by(name=self.login.data).first()
@app.route("/login", methods=["GET", "POST"])
def login():
form = LoginForm(request.form)
if form.validate_on_submit():
user = form.get_user()
login_user(user)
flash('Logged in successfully.')
return redirect('/admin/')
return render_template("login.html", form=form)
@login_manager.user_loader
def load_user(user_id):
return User.query.get(user_id)
@app.teardown_request
def shutdown_session(exception=None):
db_session.remove()
if __name__ == '__main__':
if not os.path.exists('/tmp/barbican.db'):
app.logger.info('No database detected at /tmp/barbican.db. Creating one and the admin user.')
init_db()
app.run(debug=True)

View File

@ -0,0 +1,18 @@
# Copyright 2010-2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Cloudkeep's Barbican module root
"""

56
barbican/api/__init__.py Normal file
View File

@ -0,0 +1,56 @@
# Copyright 2010-2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
API handler for Cloudkeep's Barbican
"""
import json
import falcon
class ApiResource(object):
"""
Base class for API resources
"""
pass
def abort(status=falcon.HTTP_500, message=None):
"""
Helper function for aborting an API request process. Useful for error
reporting and expcetion handling.
"""
raise falcon.HTTPError(status, message)
def load_body(req, required=[]):
"""
Helper function for loading an HTTP request body from JSON into a
Python dictionary
"""
try:
raw_json = req.stream.read()
except Exception:
abort(falcon.HTTP_500, 'Read Error')
try:
parsed_body = json.loads(raw_json, 'utf-8')
except ValueError as ve:
abort(falcon.HTTP_400, 'Malformed JSON')
return parsed_body

63
barbican/api/app.py Normal file
View File

@ -0,0 +1,63 @@
import falcon
from barbican.api.resources import *
from config import config
from sqlalchemy import create_engine, MetaData
from sqlalchemy.orm import scoped_session, sessionmaker
from barbican.model.tenant import Base
"""
Locally scoped db session
"""
_Session = scoped_session(sessionmaker())
_engine = None
def db_session():
return _Session
def _engine_from_config(configuration):
configuration = dict(configuration)
url = configuration.pop('url')
return create_engine(url, **configuration)
def init_tenant_model():
_engine = _engine_from_config(config['sqlalchemy'])
from barbican.model.tenant import Tenant, Secret
Base.metadata.create_all(_engine)
_Session.bind = _engine
# Initialize the data model
init_tenant_model()
# test the database out
#from barbican.model.tenant import Tenant, Secret
#jw_user = Tenant("jwoody")
#_Session.add(jw_user)
# select all and print out all the results sorted by id
#for instance in _Session.query(Tenant).order_by(Tenant.id):
# print instance.username
# Resources
versions = VersionResource()
tenants = TenantsResource(db_session())
tenant = TenantResource(db_session())
secrets = SecretsResource(db_session())
secret = SecretResource(db_session())
# Routing
application = api = falcon.API()
api.add_route('/', versions)
api.add_route('/v1', tenants)
api.add_route('/v1/{tenant_id}', tenant)
api.add_route('/v1/{tenant_id}/secrets', secrets)
api.add_route('/v1/{tenant_id}/secrets/{secret_id}', secret)

184
barbican/api/resources.py Normal file
View File

@ -0,0 +1,184 @@
import json
import falcon
import logging
from barbican.version import __version__
from barbican.api import ApiResource, load_body, abort
from barbican.model.util import find_tenant
from barbican.model.util import find_secret
from barbican.model.tenant import Tenant, Secret
# from barbican.model.secret import Secret
def _tenant_not_found():
abort(falcon.HTTP_404, 'Unable to locate tenant.')
def _tenant_already_exists():
abort(falcon.HTTP_400, 'Tenant already exists.')
def _secret_not_found():
abort(falcon.HTTP_400, 'Unable to locate secret profile.')
def format_tenant(tenant):
if not isinstance(tenant, dict):
tenant = tenant.__dict__
return {'id': tenant['id'],
'tenant_id': tenant['tenant_id']}
class VersionResource(ApiResource):
def on_get(self, req, resp):
resp.status = falcon.HTTP_200
resp.body = json.dumps({'v1': 'current',
'build': __version__})
class TenantsResource(ApiResource):
def __init__(self, db_session):
self.db = db_session
def on_post(self, req, resp):
body = load_body(req)
username = body['username']
logging.debug('Username is {0}'.format(username))
tenant = find_tenant(self.db, username=username)
if tenant:
abort(falcon.HTTP_400, 'Tenant with username {0} '
'already exists'.format(username))
new_tenant = Tenant(username)
self.db.add(new_tenant)
self.db.commit()
resp.status = falcon.HTTP_201
resp.set_header('Location', '/v1/{0}'.format(new_tenant.id))
class TenantResource(ApiResource):
def __init__(self, db_session):
self.db = db_session
def on_get(self, req, resp, tenant_id):
tenant = find_tenant(self.db, id=tenant_id,
when_not_found=_tenant_not_found)
resp.status = falcon.HTTP_200
resp.body = json.dumps(tenant.format())
def on_delete(self, req, resp, tenant_id):
tenant = find_tenant(self.db, id=tenant_id,
when_not_found=_tenant_not_found)
self.db.delete(tenant)
self.db.commit()
resp.status = falcon.HTTP_200
class SecretsResource(ApiResource):
def __init__(self, db_session):
self.db = db_session
def on_get(self, req, resp, tenant_id):
tenant = find_tenant(self.db, id=tenant_id,
when_not_found=_tenant_not_found)
resp.status = falcon.HTTP_200
#jsonify a list of formatted secrets
resp.body = json.dumps([s.format() for s in tenant.secrets])
def on_post(self, req, resp, tenant_id):
tenant = find_tenant(self.db, id=tenant_id,
when_not_found=_tenant_not_found)
body = load_body(req)
secret_name = body['name']
# Check if the tenant already has a secret with this name
for secret in tenant.secrets:
if secret.name == secret_name:
abort(falcon.HTTP_400, 'Secret with name {0} already exists.'
.format(secret.name, secret.id))
# Create the new secret
new_secret = Secret(tenant.id, secret_name)
tenant.secrets.append(new_secret)
self.db.add(new_secret)
self.db.commit()
resp.status = falcon.HTTP_201
resp.set_header('Location',
'/v1/{0}/secrets/{1}'
.format(tenant_id, new_secret.id))
class SecretResource(ApiResource):
def __init__(self, db_session):
self.db = db_session
def on_get(self, req, resp, tenant_id, secret_id):
#verify the tenant exists
tenant = find_tenant(self.db, tenant_id=tenant_id,
when_not_found=_tenant_not_found)
#verify the secret exists
secret = find_secret(self.db, id=secret_id,
when_not_found=_secret_not_found)
#verify the secret belongs to the tenant
if not secret in tenant.secrets:
_secret_not_found()
resp.status = falcon.HTTP_200
resp.body = json.dumps(secret.format())
def on_put(self, req, resp, tenant_id, secret_id):
#verify the tenant exists
tenant = find_tenant(self.db, tenant_id=tenant_id,
when_not_found=_tenant_not_found)
#verify the secret exists
secret = find_secret(self.db, id=secret_id,
when_not_found=_secret_not_found)
#verify the secret belongs to the tenant
if not secret in tenant.secrets:
_secret_not_found()
#load the message
body = load_body(req)
#if attributes are present in message, update the secret
if 'name' in body.keys():
secret.name = body['name']
self.db.commit()
resp.status = falcon.HTTP_200
def on_delete(self, req, resp, tenant_id, secret_id):
#verify the tenant exists
tenant = find_tenant(self.db, tenant_id=tenant_id,
when_not_found=_tenant_not_found)
#verify the secret exists
secret = find_secret(self.db, id=secret_id,
when_not_found=_secret_not_found)
#verify the secret belongs to the tenant
if not secret in tenant.secrets:
_secret_not_found()
self.db.delete(secret)
self.db.commit()
resp.status = falcon.HTTP_200

18
barbican/data/__init__.py Normal file
View File

@ -0,0 +1,18 @@
# Copyright 2010-2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Data operations for Cloudkeep's Barbican
"""

View File

@ -0,0 +1,18 @@
# Copyright 2010-2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Database-specific implementations
"""

View File

@ -0,0 +1,77 @@
#from oslo.config import cfg
#from barbican.config import get_config
# Handler configuration options
#datasource_group = cfg.OptGroup(name='datasource', title='Datasource Configuration Options')
#get_config().register_group(datasource_group)
#HANDLER_OPTIONS = [
# cfg.StrOpt('handler_name',
# default='memory',
# help="""Sets the name of the handler to load for
# datasource interactions. e.g. mongodb
# """
# ),
# cfg.BoolOpt('verbose',
# default=False,
# help="""Sets whether or not the datasource handlers
# should be verbose in their logging output.
# """
# )
#]
#get_config().register_opts(HANDLER_OPTIONS, group=datasource_group)
# Handler registration
#_DATASOURCE_HANDLERS = DatasourceHandlerManager()
STATUS_NEW = 'NEW'
STATUS_CONNECTED = 'CONNTECTED'
STATUS_CLOSED = 'CLOSED'
def datasource_handler(conf):
handler_def = _DATASOURCE_HANDLERS[conf.handler_name]
return handler_def(conf)
def register_handler(handler_name, handler_def):
_DATASOURCE_HANDLERS.register(handler_name, handler_def)
class DatasourceHandlerManager():
def __init__(self):
self.registered_handlers = dict()
def register(self, handler_name, handler_def):
self.registered_handlers[handler_name] = handler_def
def get(self, handler_name):
return self.registered_handlers[handler_name]
_DATASOURCE_HANDLERS = DatasourceHandlerManager()
class DatasourceHandler():
status = STATUS_NEW
def status(self):
return self.status
def connect(self):
raise NotImplementedError
def close(self):
raise NotImplementedError
def get(self, object_name, object_id):
raise NotImplementedError
def put(self, object_name, update_object):
raise NotImplementedError
def delete(self, object_name, object_id):
raise NotImplementedError

15
barbican/data/tenant.py Normal file
View File

@ -0,0 +1,15 @@
from barbican.data.adapters.handler import datasource_handler
handler = datasource_handler
def get_tenant(tenant_id):
pass
def save_tenant(tenant_object):
pass
def create_tenant(tenant_id):
pass
def delete_tenant(tenant_id):
pass

18
barbican/locale/keep.pot Normal file
View File

@ -0,0 +1,18 @@
# Translations template for barbican.
# Copyright (C) 2013 ORGANIZATION
# This file is distributed under the same license as
# the barbican project. FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: barbican 2013.1\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2013-03-20 17:19-0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.6\n"

View File

@ -0,0 +1,18 @@
# Copyright 2010-2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Model objects for Cloudkeep's Barbican
"""

35
barbican/model/base.py Normal file
View File

@ -0,0 +1,35 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2011 Justin Santa Barbara
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Base class for all model objects."""
from sqlalchemy import Column
from sqlalchemy import Integer, ForeignKey
from sqlalchemy.ext.declarative import declarative_base, declared_attr
class Persisted(object):
id = Column(Integer, primary_key=True)
@declared_attr
def __tablename__(self):
return self.__name__.lower()
Base = declarative_base(cls=Persisted)

43
barbican/model/secret.py Normal file
View File

@ -0,0 +1,43 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2011 Justin Santa Barbara
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Represents a secret to store in Cloudkeep's Barbican."""
#import barbican.model.Tenant
from base import Base
from sqlalchemy import Table, Column, String
from sqlalchemy import Integer, ForeignKey, Boolean
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.declarative import declarative_base, declared_attr
#
# class Secret(Base):
# """
# A secret is any information that needs to be stored and protected within
# Cloudkeep's Barbican.
# """
#
# secret_id = Column(String)
# name = Column(String)
# tenant_id = Column(Integer, ForeignKey('tenant.id'))
# tenant = relationship(Tenant, primaryjoin=tenant_id == Tenant.id)
#
# def __init__(self, secret_id):
# self.secret_id = secret_id

86
barbican/model/tenant.py Normal file
View File

@ -0,0 +1,86 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2011 Justin Santa Barbara
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Generic Node base class for all workers that run on hosts."""
import logging
from base import Base
from sqlalchemy import Table, Column, String
from sqlalchemy import Integer, ForeignKey, Boolean
from sqlalchemy.orm import relationship, relation, backref
from sqlalchemy.ext.declarative import declarative_base, declared_attr
class Tenant(Base):
"""
Tenants are users that wish to store secret information within Cloudkeep's Barbican.
"""
logging.debug('In Tenant table setup')
__tablename__ = "tenants"
id = Column(Integer, primary_key=True)
username = Column(String)
# secrets = relationship('Secret', backref='tenant', lazy='dynamic')
# secrets = relationship('Secret', secondary=_secrets)
# secrets = relationship("Secret",
# order_by="desc(Secret.name)",
# primaryjoin="Secret.tenant_id==Tenant.id")
def __init__(self, username):
self.username = username
def __init__(self, username, secrets=[]):
self.username = username
self.secrets = secrets
def format(self):
return {'id': self.id,
'username': self.username}
class Secret(Base):
"""
A secret is any information that needs to be stored and protected within Cloudkeep's Barbican.
"""
__tablename__ = "secrets"
id = Column(Integer, primary_key=True)
name = Column(String)
tenant_id = Column(Integer, ForeignKey('tenants.id'))
tenant = relationship("Tenant", backref=backref('secrets', order_by=id))
# tenant = relationship(Tenant, primaryjoin=tenant_id == Tenant.id)
# creates a bidirectional relationship
# from Secret to Tenant it's Many-to-One
# from Tenant to Secret it's One-to-Many
# tenant = relation(Tenant, backref=backref('secret', order_by=id))
def __init__(self, tenant_id, name):
self.tenant_id = tenant_id
self.name = name
def format(self):
return {'id': self.id,
'name': self.username,
'tenant_id': self.tenant_id}

33
barbican/model/util.py Normal file
View File

@ -0,0 +1,33 @@
from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
from barbican.model.tenant import Tenant, Secret
def _empty_condition():
pass
def find_tenant(db_session, id=None, username=None,
when_not_found=_empty_condition,
when_multiple_found=_empty_condition):
try:
if id:
return db_session.query(Tenant).filter_by(id=id).one()
elif username:
return db_session.query(Tenant).filter_by(username=username).one()
except NoResultFound:
when_not_found()
except MultipleResultsFound:
when_multiple_found()
return None
def find_secret(db_session, id, when_not_found=_empty_condition,
when_multiple_found=_empty_condition):
try:
return db_session.query(Secret).filter_by(id=id).one()
except NoResultFound:
when_not_found()
except MultipleResultsFound:
when_multiple_found()

View File

@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
"""
Sample Python file, here to test out code coverage in Jenkins.
~~~~~~~~~~~~
DO NOT USE THIS IN PRODUCTION. IT IS NOT SECURE IN ANY WAY.
YOU HAVE BEEN WARNED.
:copyright: (c) 2013 by Jarret Raim
:license: Apache 2.0, see LICENSE for details
"""
def a_sample_method_here():
foo = "bar"
i = 1
if ("bar" == foo):
print "saw bar"
i += 2
else:
print "not bar"
i += 4
print "total",i
a_sample_method_here() # Do something coverage can chew on.

View File

@ -0,0 +1 @@
__author__ = 'john.wood'

View File

@ -0,0 +1 @@
__author__ = 'john.wood'

View File

@ -0,0 +1,67 @@
from datetime import datetime
from barbican.api.resources import *
from barbican.model.tenant import Tenant
from mock import MagicMock
import falcon
import unittest
def suite():
suite = unittest.TestSuite()
suite.addTest(WhenTestingVersionResource())
return suite
class WhenTestingVersionResource(unittest.TestCase):
def setUp(self):
self.req = MagicMock()
self.resp = MagicMock()
self.resource = VersionResource()
def test_should_return_200_on_get(self):
self.resource.on_get(self.req, self.resp)
self.assertEqual(falcon.HTTP_200, self.resp.status)
def test_should_return_version_json(self):
self.resource.on_get(self.req, self.resp)
parsed_body = json.loads(self.resp.body)
self.assertTrue('v1' in parsed_body)
self.assertEqual('current', parsed_body['v1'])
class WhenCreatingTenantsUsingTenantsResource(unittest.TestCase):
def setUp(self):
db_filter = MagicMock()
db_filter.one.return_value = Tenant('tenant_id')
db_query = MagicMock()
db_query.filter_by.return_value = db_filter
self.db_session = MagicMock()
self.db_session.query.return_value = db_query
self.stream = MagicMock()
self.stream.read.return_value = u'{ "username" : "1234" }'
self.req = MagicMock()
self.req.stream = self.stream
self.resp = MagicMock()
self.resource = TenantsResource(self.db_session)
def test_should_throw_exception_for_tenants_that_exist(self):
with self.assertRaises(falcon.HTTPError):
self.resource.on_post(self.req, self.resp)
self.db_session.query.assert_called_once_with(Tenant)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1 @@
__author__ = 'john.wood'

View File

@ -1,38 +0,0 @@
#import os
import unittest
#from oslo.config import cfg
#from meniscus.config import init_config, get_config
# Configuration test configuration options
#test_group = cfg.OptGroup(name='test', title='Configuration Test')
#CFG_TEST_OPTIONS = [
# cfg.BoolOpt('should_pass',
# default=False,
# help="""Example option to make sure configuration
# loading passes test.
# """
# )
#]
def suite():
suite = unittest.TestSuite()
suite.addTest(WhenConfiguring())
return suite
class WhenConfiguring(unittest.TestCase):
def test_loading(self):
self.assertTrue(True)
# init_config(['--config-file', '../etc/meniscus/meniscus.conf'])
# conf = get_config()
# conf.register_group(test_group)
# conf.register_opts(CFG_TEST_OPTIONS, group=test_group)
# self.assertTrue(conf.test.should_pass)

21
barbican/version.py Normal file
View File

@ -0,0 +1,21 @@
# Copyright 2010-2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Cloudkeep's Barbican version
"""
__version__ = '0.1.74dev'
__version_info__ = tuple(__version__.split('.'))

View File

@ -1,106 +0,0 @@
# -*- coding: utf-8 -*-
"""
Barbican API
~~~~~~~~~~~~
The API for Barbican.
DO NOT USE THIS IN PRODUCTION. IT IS NOT SECURE IN ANY WAY.
YOU HAVE BEEN WARNED.
:copyright: (c) 2013 by Jarret Raim
:license: Apache 2.0, see LICENSE for details
"""
import uuid
import datetime
from dateutil.parser import parse
from flask import Blueprint, request, jsonify, Response, json
from models import Event, Tenant, Key, Agent, Policy
from database import db_session
api = Blueprint('api', __name__, url_prefix="/api")
@api.route('/')
def root():
return jsonify(hello='World')
@api.route('/<int:tenant_id>/policies/', methods=['GET', 'POST'])
def policies(tenant_id):
if request.method == 'POST':
for policy in request.json['policies']:
keys = []
for k in policy['keys']:
key = Key(uuid=k['uuid'], filename=k['filename'], mime_type=k['mime_type'],
expiration=parse(k['expiration']), secret=k['secret'], owner=k['owner'],
group=k['group'], cacheable=k['cacheable'])
keys.append(key)
policy = Policy(uuid=policy['uuid'], name=policy['name'], tenant_id=tenant_id,
directory_name=policy['directory_name'],
max_key_accesses=policy['max_key_accesses'],
time_available_after_reboot=policy['time_available_after_reboot'])
policy.keys.extend(keys)
db_session.add(policy)
db_session.commit()
return Response(status=200)
else:
policy = Policy.query.filter_by(tenant_id=tenant_id).first()
if policy is None:
return Response('No policies defined for tenant', status=404)
return jsonify(policy.as_dict())
@api.route('/<int:tenant_id>/agents/', methods=['GET', 'POST'])
def agents(tenant_id):
if request.method == 'POST':
tenant = Tenant.query.get(tenant_id)
agent = Agent(tenant=tenant, uuid=request.json['uuid'])
db_session.add(agent)
db_session.commit()
return jsonify(agent.as_dict())
else:
agents = Agent.query.filter_by(tenant_id=tenant_id)
agents_dicts = map(Agent.as_dict, agents.all())
return Response(json.dumps(agents_dicts, cls=DateTimeJsonEncoder), mimetype='application/json')
@api.route('/<int:tenant_id>/logs/', methods=['GET', 'POST'])
def logs(tenant_id):
if request.method == 'POST':
agent_id = uuid.UUID(request.json['agent_id'])
received_on = parse(request.json['received_on'])
key_id = uuid.UUID(request.json['key_id'])
if request.json['severity'] in ['DEBUG', 'INFO', 'WARN', 'FATAL']:
severity = request.json['severity']
else:
severity = 'UNKNOWN'
# Load the key and tenant
tenant = Tenant.query.get(tenant_id)
key = Key.query.filter_by(uuid=str(key_id)).first()
ev = Event(tenant_id=tenant_id, agent_id=str(agent_id), received_on=received_on,
severity=severity, message=request.json['message'], tenant=tenant, key=key)
db_session.add(ev)
db_session.commit()
return Response(json.dumps(ev.as_dict(), cls=DateTimeJsonEncoder), mimetype='application/json')
else:
events = Event.query.filter_by(tenant_id=tenant_id).order_by(Event.received_on)
events_dicts = map(Event.as_dict, events.all())
return Response(json.dumps(events_dicts, cls=DateTimeJsonEncoder), mimetype='application/json')
class DateTimeJsonEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
return obj.isoformat()
else:
return super(DateTimeJsonEncoder, self).default(obj)

View File

@ -1,12 +0,0 @@
# -*- coding: utf-8 -*-
"""
Barbican Tests
~~~~~~~~~~~~~~
Tests the Barbican application.
:copyright: (c) 2013 by Jarret Raim
:license: Apache 2.0, see LICENSE for more details.
"""

View File

@ -1,34 +0,0 @@
# -*- coding: utf-8 -*-
"""
Barbican Models
~~~~~~~~~~~~~~~
The models for the Barbican application.
DO NOT USE THIS IN PRODUCTION. IT IS NOT SECURE IN ANY WAY.
YOU HAVE BEEN WARNED.
:copyright: (c) 2013 by Jarret Raim
:license: Apache 2.0, see LICENSE for details
"""
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('sqlite:////tmp/barbican.db', convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()
def init_db():
# import all modules here that might define models so that
# they will be registered properly on the metadata. Otherwise
# you will have to import them first before calling init_db()
import models
Base.metadata.create_all(bind=engine)
db_session.add(models.User('admin', 'admin@localhost', 'Passw0rd'))
db_session.add(models.Tenant(id=123))
db_session.commit()

1
debian/barbican-api.dirs vendored Normal file
View File

@ -0,0 +1 @@
/var/lib/barbican/temp

94
debian/barbican-api.init vendored Normal file
View File

@ -0,0 +1,94 @@
#!/bin/sh
### BEGIN INIT INFO
# Provides: barbican-api
# Required-Start: $network $local_fs $remote_fs $syslog
# Required-Stop: $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Barbican API server
# Description: Frontend Barbican API server
### END INIT INFO
# Author: John Wood <john.wood@rackspace.com>
# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Key Management Service API"
NAME=barbican-api
DAEMON=/usr/bin/barbican-api
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME