From 0b1c82f01613ada626cc9d3989903c61e11945a3 Mon Sep 17 00:00:00 2001
From: Pino de Candia <giuseppe.decandia@gmail.com>
Date: Fri, 8 Dec 2017 00:15:18 +0000
Subject: [PATCH] Debugged Castellan API with TatuKeyManager

---
 requirements.txt                   |   1 +
 tatu/api/app.py                    |  34 +++++----
 tatu/castellan/__init__.py         |   0
 tatu/castellan/config.py           |  52 --------------
 tatu/castellan/tatu_key_manager.py |  68 ------------------
 tatu/castellan/utils.py            |  52 --------------
 tatu/castellano.py                 | 109 +++++++++++++++++++++++++++++
 tatu/db/models.py                  |   2 +-
 tatu/tests/test_app.py             |  10 +--
 9 files changed, 134 insertions(+), 194 deletions(-)
 delete mode 100644 tatu/castellan/__init__.py
 delete mode 100644 tatu/castellan/config.py
 delete mode 100644 tatu/castellan/tatu_key_manager.py
 delete mode 100644 tatu/castellan/utils.py
 create mode 100644 tatu/castellano.py

diff --git a/requirements.txt b/requirements.txt
index 41bebec..bc03c0e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,6 +2,7 @@
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
 
+castellan>=0.16.0
 requests>=2.18.2 # Apache-2.0
 python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0
 keystoneauth1>=2.7.0 # Apache-2.0
diff --git a/tatu/api/app.py b/tatu/api/app.py
index 7c26ddd..7be99cc 100644
--- a/tatu/api/app.py
+++ b/tatu/api/app.py
@@ -1,22 +1,30 @@
 import falcon
 import models
+import os.path
+from oslo_config import cfg
+from tatu.castellano import validate_config as validate_castellan_config
 from tatu.db.persistence import SQLAlchemySessionManager
 
-def create_app(sa):
-  api = falcon.API(middleware=[models.Logger(), sa])
-  api.add_route('/authorities', models.Authorities())
-  api.add_route('/authorities/{auth_id}', models.Authority())
-  api.add_route('/usercerts', models.UserCerts())
-  api.add_route('/usercerts/{user_id}/{fingerprint}', models.UserCert())
-  api.add_route('/hostcerts', models.HostCerts())
-  api.add_route('/hostcerts/{host_id}/{fingerprint}', models.HostCert())
-  api.add_route('/hosttokens', models.Tokens())
-  api.add_route('/novavendordata', models.NovaVendorData())
-  return api
+validate_castellan_config()
+fname = 'tatu.conf'
+CONF = cfg.CONF
+if os.path.isfile(fname):
+    CONF(default_config_files=[fname])
 
+def create_app(sa):
+    api = falcon.API(middleware=[models.Logger(), sa])
+    api.add_route('/authorities', models.Authorities())
+    api.add_route('/authorities/{auth_id}', models.Authority())
+    api.add_route('/usercerts', models.UserCerts())
+    api.add_route('/usercerts/{user_id}/{fingerprint}', models.UserCert())
+    api.add_route('/hostcerts', models.HostCerts())
+    api.add_route('/hostcerts/{host_id}/{fingerprint}', models.HostCert())
+    api.add_route('/hosttokens', models.Tokens())
+    api.add_route('/novavendordata', models.NovaVendorData())
+    return api
 
 def get_app():
-  return create_app(SQLAlchemySessionManager())
+    return create_app(SQLAlchemySessionManager())
 
 def main(global_config, **settings):
-  return create_app(SQLAlchemySessionManager())
+    return create_app(SQLAlchemySessionManager())
diff --git a/tatu/castellan/__init__.py b/tatu/castellan/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tatu/castellan/config.py b/tatu/castellan/config.py
deleted file mode 100644
index 1576b57..0000000
--- a/tatu/castellan/config.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# 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.
-
-from tatu.castellan import options as castellan
-from oslo_config import cfg
-
-#from sahara.utils.openstack import base as utils
-
-
-opts = [
-    cfg.BoolOpt('use_barbican_key_manager', default=False,
-                help='Enable the usage of the OpenStack Key Management '
-                     'service provided by barbican.'),
-]
-
-castellan_opts = [
-    cfg.StrOpt('barbican_api_endpoint',
-               help='The endpoint to use for connecting to the barbican '
-                    'api controller. By default, castellan will use the '
-                    'URL from the service catalog.'),
-    cfg.StrOpt('barbican_api_version', default='v1',
-               help='Version of the barbican API, for example: "v1"'),
-]
-
-castellan_group = cfg.OptGroup(name='castellan',
-                               title='castellan key manager options')
-
-CONF = cfg.CONF
-CONF.register_group(castellan_group)
-CONF.register_opts(opts)
-CONF.register_opts(castellan_opts, group=castellan_group)
-
-
-def validate_config():
-    if CONF.use_barbican_key_manager:
-        # NOTE (elmiko) there is no need to set the api_class as castellan
-        # uses barbican by default.
-        #castellan.set_defaults(CONF, auth_endpoint=utils.retrieve_auth_url())
-        castellan.set_defaults(CONF)
-    else:
-        castellan.set_defaults(CONF, api_class='tatu.castellan.'
-                               'tatu_key_manager.TatuKeyManager')
\ No newline at end of file
diff --git a/tatu/castellan/tatu_key_manager.py b/tatu/castellan/tatu_key_manager.py
deleted file mode 100644
index 488c366..0000000
--- a/tatu/castellan/tatu_key_manager.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# 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.
-
-from castellan.common.objects import passphrase as key
-from castellan.key_manager import key_manager as km
-
-
-"""tatu.castellan.tatu_key_manager
-This module contains the KeyManager class that will be used by the
-castellan library, it is not meant for direct usage within tatu.
-"""
-
-
-class TatuKeyManager(km.KeyManager):
-    """Tatu specific key manager
-    This manager is a thin wrapper around the secret being stored. It is
-    intended for backward compatible use only. It will not store keys
-    or generate UUIDs but instead return the secret that is being stored.
-    This behavior allows Tatu to continue storing secrets in its database
-    while using the Castellan key manager abstraction.
-    """
-    def __init__(self, configuration=None):
-        pass
-
-    def create_key(self, context, algorithm=None, length=0,
-                   expiration=None, **kwargs):
-        """creates a key
-        algorithm, length, and expiration are unused by tatu keys.
-        """
-        return key.Passphrase(passphrase=kwargs.get('passphrase', ''))
-
-    def create_key_pair(self, *args, **kwargs):
-        pass
-
-    def store(self, context, key, expiration=None, **kwargs):
-        """store a key
-        in normal usage a store_key will return the UUID of the key as
-        dictated by the key manager. Tatu would then store this UUID in
-        its database to use for retrieval. As tatu is not actually using
-        a key manager in this context it will return the key's payload for
-        storage.
-        """
-        return key.get_encoded()
-
-    def get(self, context, key_id, **kwargs):
-        """get a key
-        since tatu is not actually storing key UUIDs the key_id to this
-        function should actually be the key payload. this function will
-        simply return a new TatuKey based on that value.
-        """
-        return key.Passphrase(passphrase=key_id)
-
-    def delete(self, context, key_id, **kwargs):
-        """delete a key
-        as there is no external key manager, this function will not
-        perform any external actions. therefore, it won't change anything.
-        """
-        pass
\ No newline at end of file
diff --git a/tatu/castellan/utils.py b/tatu/castellan/utils.py
deleted file mode 100644
index 9799ff8..0000000
--- a/tatu/castellan/utils.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# 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.
-
-from castellan.common.objects import passphrase
-from castellan import key_manager
-
-from oslo_context import context
-
-
-def delete_secret(id, ctx=None):
-  """delete a secret from the external key manager
-  :param id: The identifier of the secret to delete
-  :param ctx: The context, and associated authentication, to use with
-              this operation (defaults to the current context)
-  """
-  if ctx is None:
-    ctx = context.current()
-  key_manager.API().delete(ctx, id)
-
-
-def get_secret(id, ctx=None):
-    """get a secret associated with an id
-    :param id: The identifier of the secret to retrieve
-    :param ctx: The context, and associated authentication, to use with
-                this operation (defaults to the current context)
-    """
-    if ctx is None:
-        ctx = context.current()
-    key = key_manager.API().get(ctx, id)
-    return key.get_encoded()
-
-
-def store_secret(secret, ctx=None):
-    """store a secret and return its identifier
-    :param secret: The secret to store, this should be a string
-    :param ctx: The context, and associated authentication, to use with
-                this operation (defaults to the current context)
-    """
-    if ctx is None:
-        ctx = context.current()
-    key = passphrase.Passphrase(secret)
-    return key_manager.API().store(ctx, key)
\ No newline at end of file
diff --git a/tatu/castellano.py b/tatu/castellano.py
new file mode 100644
index 0000000..4e16c2b
--- /dev/null
+++ b/tatu/castellano.py
@@ -0,0 +1,109 @@
+from castellan.common.objects.passphrase import Passphrase
+from castellan.common.utils import credential_factory
+from castellan.key_manager import API
+from castellan.key_manager.key_manager import KeyManager
+from castellan.options import set_defaults as set_castellan_defaults
+from oslo_config import cfg
+
+opts = [
+    cfg.BoolOpt('use_barbican_key_manager', default=False,
+                help='Enable the usage of the OpenStack Key Management '
+                     'service provided by barbican.'),
+]
+
+CONF = cfg.CONF
+CONF.register_opts(opts, group='tatu')
+_context = None
+_api = None
+
+def validate_config():
+    if CONF.tatu.use_barbican_key_manager:
+        set_castellan_defaults(CONF)
+    else:
+        set_castellan_defaults(CONF,
+                               api_class='tatu.castellano.TatuKeyManager')
+
+def context():
+    global _context
+    if _context is None and CONF.tatu.use_barbican_key_manager:
+        _context = credential_factory(conf=CONF)
+    return _context
+
+def api():
+    global _api
+    if _api is None:
+        _api = API()
+    return _api 
+
+def delete_secret(id, ctx=None):
+    """delete a secret from the external key manager
+    :param id: The identifier of the secret to delete
+    :param ctx: The context, and associated authentication, to use with
+                this operation (defaults to the current context)
+    """
+    api().delete(ctx or context(), id)
+
+def get_secret(id, ctx=None):
+    """get a secret associated with an id
+    :param id: The identifier of the secret to retrieve
+    :param ctx: The context, and associated authentication, to use with
+                this operation (defaults to the current context)
+    """
+    key = api().get(ctx or context(), id)
+    return key.get_encoded()
+
+def store_secret(secret, ctx=None):
+    """store a secret and return its identifier
+    :param secret: The secret to store, this should be a string
+    :param ctx: The context, and associated authentication, to use with
+                this operation (defaults to the current context)
+    """
+    key = Passphrase(secret)
+    return api().store(ctx or context(), key)
+
+"""
+This module contains the KeyManager class that will be used by the
+castellan library, it is not meant for direct usage within tatu.
+"""
+class TatuKeyManager(KeyManager):
+    """Tatu specific key manager
+    This manager is a thin wrapper around the secret being stored. It is
+    intended for backward compatible use only. It will not store keys
+    or generate UUIDs but instead return the secret that is being stored.
+    This behavior allows Tatu to continue storing secrets in its database
+    while using the Castellan key manager abstraction.
+    """
+    def __init__(self, configuration=None):
+        pass
+
+    def create_key(self, context, algorithm=None, length=0,
+                   expiration=None, **kwargs):
+        pass
+
+    def create_key_pair(self, *args, **kwargs):
+        pass
+
+    def store(self, context, key, expiration=None, **kwargs):
+        """store a key
+        in normal usage a store_key will return the UUID of the key as
+        dictated by the key manager. Tatu would then store this UUID in
+        its database to use for retrieval. As tatu is not actually using
+        a key manager in this context it will return the key's payload for
+        storage.
+        """
+        return key.get_encoded()
+
+    def get(self, context, key_id, **kwargs):
+        """get a key
+        since tatu is not actually storing key UUIDs the key_id to this
+        function should actually be the key payload. this function will
+        simply return a new TatuKey based on that value.
+        """
+        return Passphrase(passphrase=key_id)
+
+    def delete(self, context, key_id, **kwargs):
+        """delete a key
+        as there is no external key manager, this function will not
+        perform any external actions. therefore, it won't change anything.
+        """
+        pass
diff --git a/tatu/db/models.py b/tatu/db/models.py
index b079412..16336fa 100644
--- a/tatu/db/models.py
+++ b/tatu/db/models.py
@@ -5,7 +5,7 @@ import sqlalchemy as sa
 from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy.exc import IntegrityError
 import sshpubkeys
-from tatu.castellan.utils import get_secret, store_secret
+from tatu.castellano import get_secret, store_secret
 from tatu.utils import generateCert,random_uuid
 import uuid
 from Crypto.PublicKey import RSA
diff --git a/tatu/tests/test_app.py b/tatu/tests/test_app.py
index 2c6160b..8a022e8 100644
--- a/tatu/tests/test_app.py
+++ b/tatu/tests/test_app.py
@@ -178,14 +178,8 @@ def test_post_user_unknown_auth(client):
   assert response.status == falcon.HTTP_NOT_FOUND
 
 @pytest.mark.dependency(depends=['test_post_user'])
-def test_post_same_user_same_key_fails(client):
-  # Show that using the same user ID and public key fails.
-  body = user_request()
-  response = client.simulate_post(
-    '/usercerts',
-    body=json.dumps(body)
-  )
-  assert response.status == falcon.HTTP_CONFLICT
+def test_post_same_user_and_key_returns_same_result(client):
+  test_post_user(client)
 
 def token_request(auth=auth_id, host=host_id):
   return {