Merge of previous project work into this project
This commit is contained in:
parent
81d7598ca4
commit
8f783f473b
3
.gitignore
vendored
3
.gitignore
vendored
@ -40,3 +40,6 @@ nosetests.xml
|
||||
# Sqlite databases
|
||||
*.sqlite3
|
||||
*.db
|
||||
|
||||
# Misc. generated files
|
||||
versiononly.txt
|
||||
|
190
HACKING.rst
Normal file
190
HACKING.rst
Normal 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.
|
5
LICENSE
5
LICENSE
@ -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
11
MANIFEST.in
Normal 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
|
43
README.md
43
README.md
@ -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
48
README.rst
Normal 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.
|
90
barbican.py
90
barbican.py
@ -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)
|
@ -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
56
barbican/api/__init__.py
Normal 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
63
barbican/api/app.py
Normal 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
184
barbican/api/resources.py
Normal 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
18
barbican/data/__init__.py
Normal 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
|
||||
"""
|
18
barbican/data/adapters/__init__.py
Normal file
18
barbican/data/adapters/__init__.py
Normal 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
|
||||
"""
|
77
barbican/data/adapters/handler.py
Normal file
77
barbican/data/adapters/handler.py
Normal 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
15
barbican/data/tenant.py
Normal 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
18
barbican/locale/keep.pot
Normal 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"
|
18
barbican/model/__init__.py
Normal file
18
barbican/model/__init__.py
Normal 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
35
barbican/model/base.py
Normal 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
43
barbican/model/secret.py
Normal 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
86
barbican/model/tenant.py
Normal 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
33
barbican/model/util.py
Normal 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()
|
@ -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.
|
@ -0,0 +1 @@
|
||||
__author__ = 'john.wood'
|
1
barbican/tests/api/__init__.py
Normal file
1
barbican/tests/api/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
__author__ = 'john.wood'
|
67
barbican/tests/api/resources_test.py
Normal file
67
barbican/tests/api/resources_test.py
Normal 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()
|
1
barbican/tests/api/tenant/__init__.py
Normal file
1
barbican/tests/api/tenant/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
__author__ = 'john.wood'
|
@ -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
21
barbican/version.py
Normal 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('.'))
|
106
barbican_api.py
106
barbican_api.py
@ -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)
|
@ -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.
|
||||
"""
|
||||
|
||||
|
34
database.py
34
database.py
@ -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
1
debian/barbican-api.dirs
vendored
Normal file
@ -0,0 +1 @@
|
||||
/var/lib/barbican/temp
|
94
debian/barbican-api.init
vendored
Normal file
94
debian/barbican-api.init
vendored
Normal 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
|
||||
|
||||