Add ticket handling to KDS
Adds creation of tickets to transmit communication keys between peers. SecurityImpact Change-Id: I4dbd23adb0bdd9011eb9a0b45e30dd862d390473
This commit is contained in:
parent
33c5b9f2e3
commit
6b54c09bad
|
@ -13,6 +13,7 @@
|
|||
import pecan
|
||||
|
||||
from kite.api.v1.controllers import key as key_controller
|
||||
from kite.api.v1.controllers import ticket as ticket_controller
|
||||
|
||||
|
||||
class Controller(object):
|
||||
|
@ -27,6 +28,7 @@ class Controller(object):
|
|||
'rel': 'self'}]}
|
||||
|
||||
keys = key_controller.KeyController()
|
||||
tickets = ticket_controller.TicketController()
|
||||
|
||||
@pecan.expose('json')
|
||||
def index(self):
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
# 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.
|
||||
|
||||
import base64
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import wsme
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from kite.api.v1 import models
|
||||
from kite.openstack.common import jsonutils
|
||||
|
||||
|
||||
class TicketController(rest.RestController):
|
||||
|
||||
@wsme.validate(models.Ticket)
|
||||
@wsme_pecan.wsexpose(models.Ticket, body=models.TicketRequest)
|
||||
def post(self, ticket_request):
|
||||
# verify all required fields present and the signature is correct
|
||||
ticket_request.verify()
|
||||
|
||||
# create a new random base key. With the combination of this base key
|
||||
# and the information available in the metadata a client will be able
|
||||
# to re-generate the keys required for this session.
|
||||
rndkey = pecan.request.crypto.extract(ticket_request.source.key,
|
||||
pecan.request.crypto.new_key())
|
||||
|
||||
# generate the keys to communicate between these two endpoints.
|
||||
s_key, e_key = pecan.request.crypto.generate_keys(rndkey,
|
||||
ticket_request.info)
|
||||
|
||||
# encrypt the base key for the target, this can be used to generate
|
||||
# the sek on the target
|
||||
esek_data = {'key': base64.b64encode(rndkey),
|
||||
'timestamp': ticket_request.time_str,
|
||||
'ttl': ticket_request.ttl.seconds}
|
||||
|
||||
# encrypt returns a base64 encrypted string
|
||||
esek = pecan.request.crypto.encrypt(ticket_request.destination.key,
|
||||
jsonutils.dumps(esek_data))
|
||||
|
||||
return ticket_request.new_response(e_key, s_key, esek)
|
|
@ -11,8 +11,11 @@
|
|||
# under the License.
|
||||
|
||||
from kite.api.v1.models import key
|
||||
from kite.api.v1.models import ticket
|
||||
|
||||
KeyInput = key.KeyInput
|
||||
KeyData = key.KeyData
|
||||
Ticket = ticket.Ticket
|
||||
TicketRequest = ticket.TicketRequest
|
||||
|
||||
__all__ = [KeyInput, KeyData]
|
||||
__all__ = [KeyInput, KeyData, Ticket, TicketRequest]
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
# 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.
|
||||
|
||||
import base64
|
||||
import functools
|
||||
|
||||
import pecan
|
||||
import wsme
|
||||
|
||||
from kite.common import exception
|
||||
from kite.common import utils
|
||||
from kite.openstack.common import jsonutils
|
||||
from kite.openstack.common import timeutils
|
||||
|
||||
|
||||
def memoize(f):
|
||||
"""Create a property and cache the return value for future."""
|
||||
@property
|
||||
@functools.wraps(f)
|
||||
def wrapper(self):
|
||||
try:
|
||||
val = self._cache[f.func_name]
|
||||
except KeyError:
|
||||
val = f(self)
|
||||
self._cache[f.func_name] = val
|
||||
|
||||
return val
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def malformed(msg):
|
||||
"""Raise a malformed message exception if something goes wrong."""
|
||||
def wrap(f):
|
||||
@functools.wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return f(*args, **kwargs)
|
||||
except Exception:
|
||||
pecan.abort(400, 'Invalid %s' % msg)
|
||||
|
||||
return wrapper
|
||||
return wrap
|
||||
|
||||
|
||||
class Endpoint(object):
|
||||
"""A source or destination for a ticket."""
|
||||
|
||||
def __init__(self, endpoint_str):
|
||||
self._cache = dict()
|
||||
self._set_endpoint(endpoint_str)
|
||||
|
||||
@malformed('endpoint')
|
||||
def _set_endpoint(self, endpoint_str):
|
||||
self.host, self.generation = utils.split_host(endpoint_str)
|
||||
|
||||
@memoize
|
||||
def key_data(self):
|
||||
try:
|
||||
return pecan.request.storage.get_key(self.host, self.generation)
|
||||
except exception.CryptoError:
|
||||
pecan.abort(500, "Failed to decrypt key for '%s:%s'. " %
|
||||
(self.host, self.generation))
|
||||
except exception.KeyNotFound:
|
||||
pecan.abort(404, "Could not find key")
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
return self.key_data['key']
|
||||
|
||||
@property
|
||||
def key_generation(self):
|
||||
return self.key_data['generation']
|
||||
|
||||
@property
|
||||
def key_str(self):
|
||||
return utils.join_host(self.host, self.key_generation)
|
||||
|
||||
|
||||
class BaseRequest(wsme.types.Base):
|
||||
|
||||
metadata = wsme.wsattr(wsme.types.text, mandatory=True)
|
||||
signature = wsme.wsattr(wsme.types.text, mandatory=True)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(BaseRequest, self).__init__(**kwargs)
|
||||
self._cache = dict()
|
||||
self.now = timeutils.utcnow()
|
||||
|
||||
@memoize
|
||||
@malformed("metadata")
|
||||
def meta(self):
|
||||
return jsonutils.loads(base64.decodestring(self.metadata))
|
||||
|
||||
@memoize
|
||||
@malformed("source")
|
||||
def source(self):
|
||||
return Endpoint(self.meta['source'])
|
||||
|
||||
@memoize
|
||||
@malformed("destination")
|
||||
def destination(self):
|
||||
return Endpoint(self.meta['destination'])
|
||||
|
||||
@memoize
|
||||
@malformed("timestamp")
|
||||
def timestamp(self):
|
||||
return timeutils.parse_strtime(self.meta['timestamp'])
|
||||
|
||||
@property
|
||||
@malformed("nonce")
|
||||
def nonce(self):
|
||||
return self.meta['nonce']
|
||||
|
||||
@property
|
||||
def time_str(self):
|
||||
return timeutils.strtime(self.now)
|
||||
|
||||
def verify(self):
|
||||
"""Ensure that the ticket request is recent enough to be valid and
|
||||
the signature is correct for the requestor.
|
||||
"""
|
||||
if (self.now - self.timestamp) > self.ttl:
|
||||
pecan.abort(401, 'Ticket validity expired')
|
||||
|
||||
if not self.nonce:
|
||||
# just check this until we actually use it
|
||||
pecan.abort(400, 'Invalid nonce')
|
||||
|
||||
try:
|
||||
sigc = pecan.request.crypto.sign(self.source.key, self.metadata)
|
||||
except exception.CryptoError:
|
||||
pecan.abort(400, "Unexpected error: Couldn't reproduce signature")
|
||||
|
||||
if sigc != self.signature:
|
||||
pecan.abort(401, 'Invalid Signature')
|
||||
|
||||
|
||||
class BaseResponse(wsme.types.Base):
|
||||
|
||||
metadata = wsme.wsattr(wsme.types.text, mandatory=True)
|
||||
signature = wsme.wsattr(wsme.types.text, mandatory=True)
|
||||
|
||||
def set_metadata(self, source, destination, expiration):
|
||||
"""Attach the generation metadata to the ticket.
|
||||
|
||||
This informs the client and server of expiration and the expect sending
|
||||
and receiving host and will be validated by both client and server.
|
||||
"""
|
||||
metadata = jsonutils.dumps({'source': source,
|
||||
'destination': destination,
|
||||
'expiration': expiration,
|
||||
'encryption': True})
|
||||
self.metadata = base64.b64encode(metadata)
|
||||
|
||||
def sign(self, key, data):
|
||||
"""Sign the response.
|
||||
|
||||
This will be signed with the requestor's key so that it knows that the
|
||||
issuing server has a correct copy of the key.
|
||||
"""
|
||||
self.signature = pecan.request.crypto.sign(key, self.metadata + data)
|
|
@ -0,0 +1,81 @@
|
|||
# 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.
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
|
||||
import pecan
|
||||
import wsme
|
||||
|
||||
from kite.api.v1.models import base
|
||||
from kite.openstack.common import jsonutils
|
||||
|
||||
|
||||
class Ticket(base.BaseResponse):
|
||||
|
||||
ticket = wsme.wsattr(wsme.types.text, mandatory=True)
|
||||
|
||||
def set_ticket(self, rkey, enc_key, signature, esek):
|
||||
"""Create and encrypt a ticket to the requestor.
|
||||
|
||||
The requestor will be able to decrypt the ticket with their key and the
|
||||
information in the metadata to get the new point-to-point key.
|
||||
"""
|
||||
ticket = jsonutils.dumps({'skey': base64.b64encode(signature),
|
||||
'ekey': base64.b64encode(enc_key),
|
||||
'esek': esek})
|
||||
|
||||
self.ticket = pecan.request.crypto.encrypt(rkey, ticket)
|
||||
|
||||
def sign(self, key):
|
||||
"""Sign the ticket response.
|
||||
|
||||
This will be signed with the requestor's key so that it knows that the
|
||||
issuing server has a correct copy of the key.
|
||||
"""
|
||||
super(Ticket, self).sign(key, self.ticket)
|
||||
|
||||
|
||||
class TicketRequest(base.BaseRequest):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(TicketRequest, self).__init__(**kwargs)
|
||||
|
||||
seconds = int(pecan.request.conf.ticket_lifetime)
|
||||
self.ttl = datetime.timedelta(seconds=seconds)
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
"""A predictable text string that can be used as the base for
|
||||
generating keys.
|
||||
"""
|
||||
return "%s,%s,%s" % (self.source.key_str,
|
||||
self.destination.key_str,
|
||||
self.time_str)
|
||||
|
||||
def new_response(self, enc_key, signature, esek):
|
||||
response = Ticket()
|
||||
|
||||
response.set_metadata(source=self.source.key_str,
|
||||
destination=self.destination.key_str,
|
||||
expiration=self.now + self.ttl)
|
||||
|
||||
# encrypt the sig and key back to the requester as well as the esek
|
||||
# to forward with messages.
|
||||
response.set_ticket(self.source.key, enc_key, signature, esek)
|
||||
|
||||
# finish building response and sign it, we sign it with the requester's
|
||||
# key at the end because the ticket doesn't have to be encrypted and we
|
||||
# still have to provide integrity of the ticket.
|
||||
response.sign(self.source.key)
|
||||
|
||||
return response
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
import base64
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
|
||||
from oslo.config import cfg
|
||||
|
@ -41,6 +42,8 @@ CONF.register_group(cfg.OptGroup(name='crypto',
|
|||
title='Cryptographic Options'))
|
||||
CONF.register_opts(CRYPTO_OPTS, group='crypto')
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CryptoManager(utils.SingletonManager):
|
||||
|
||||
|
@ -73,6 +76,10 @@ class CryptoManager(utils.SingletonManager):
|
|||
try:
|
||||
f = os.open(CONF.crypto.master_key_file, flags, 0o600)
|
||||
os.write(f, base64.b64encode(mkey))
|
||||
except Exception as x:
|
||||
_logger.warn('Failed to read master key initially: %s', e)
|
||||
_logger.warn('Failed to create new master key: %s', x)
|
||||
raise x
|
||||
finally:
|
||||
if f:
|
||||
os.close(f)
|
||||
|
@ -83,7 +90,10 @@ class CryptoManager(utils.SingletonManager):
|
|||
|
||||
return mkey
|
||||
|
||||
def generate_keys(self, prk, info, key_size):
|
||||
def new_key(self, key_size=KEY_SIZE):
|
||||
return self.crypto.new_key(key_size)
|
||||
|
||||
def generate_keys(self, prk, info, key_size=KEY_SIZE):
|
||||
"""Generate a new key from an existing key and information.
|
||||
|
||||
:param string prk: Existing pseudo-random key
|
||||
|
@ -94,6 +104,15 @@ class CryptoManager(utils.SingletonManager):
|
|||
key = self.hkdf.expand(prk, info, 2 * key_size)
|
||||
return key[:key_size], key[key_size:]
|
||||
|
||||
def extract(self, key, rnd_data):
|
||||
return self.hkdf.extract(key, rnd_data)
|
||||
|
||||
def encrypt(self, key, data):
|
||||
return self.crypto.encrypt(key, data)
|
||||
|
||||
def sign(self, key, data):
|
||||
return self.crypto.sign(key, data)
|
||||
|
||||
def get_storage_keys(self, name):
|
||||
"""Get a set of keys that will be used to encrypt the data for this
|
||||
identity in the database.
|
||||
|
|
|
@ -73,3 +73,21 @@ class SingletonManager(object):
|
|||
@classmethod
|
||||
def reset(cls):
|
||||
cls._instance = None
|
||||
|
||||
|
||||
def split_host(string):
|
||||
if not string:
|
||||
return (None, None)
|
||||
|
||||
try:
|
||||
host, generation = string.rsplit(':', 1)
|
||||
generation = int(generation)
|
||||
except ValueError:
|
||||
host = string
|
||||
generation = None
|
||||
|
||||
return (host, generation)
|
||||
|
||||
|
||||
def join_host(host, generation):
|
||||
return "%s:%d" % (host, generation)
|
||||
|
|
|
@ -14,6 +14,7 @@ import webtest
|
|||
|
||||
import pecan.testing
|
||||
|
||||
from kite.common import crypto
|
||||
from kite.common import storage
|
||||
from kite.db import api as db_api
|
||||
from kite.openstack.common import jsonutils
|
||||
|
@ -56,13 +57,14 @@ class BaseTestCase(base.BaseTestCase):
|
|||
},
|
||||
}
|
||||
|
||||
# self.useFixture(fixture.SqliteDb())
|
||||
self.CRYPTO = crypto.CryptoManager.get_instance()
|
||||
self.DB = db_api.get_instance()
|
||||
self.STORAGE = storage.StorageManager.get_instance()
|
||||
|
||||
self.app = pecan.testing.load_test_app(self.app_config)
|
||||
self.addCleanup(pecan.set_config, {}, overwrite=True)
|
||||
|
||||
def request(self, url, method, **kwargs):
|
||||
def request(self, url, method, expected_status=None, **kwargs):
|
||||
try:
|
||||
json = kwargs.pop('json')
|
||||
except KeyError:
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
# 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.
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
|
||||
import six
|
||||
|
||||
from kite.openstack.common.crypto import utils as cryptoutils
|
||||
from kite.openstack.common import jsonutils
|
||||
from kite.openstack.common import timeutils
|
||||
from kite.tests.api.v1 import base
|
||||
|
||||
SOURCE_KEY = base64.b64decode('LDIVKc+m4uFdrzMoxIhQOQ==')
|
||||
DEST_KEY = base64.b64decode('EEGfTxGFcZiT7oPO+brs+A==')
|
||||
|
||||
TEST_KEY = base64.b64decode('Jx5CVBcxuA86050355mTrg==')
|
||||
|
||||
DEFAULT_SOURCE = 'home.local'
|
||||
DEFAULT_DEST = 'tests.openstack.remote'
|
||||
DEFAULT_GROUP = 'home'
|
||||
DEFAULT_NONCE = '42'
|
||||
|
||||
|
||||
class TicketTest(base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TicketTest, self).setUp()
|
||||
|
||||
self.crypto = cryptoutils.SymmetricCrypto(
|
||||
enctype=self.CONF.crypto.enctype,
|
||||
hashtype=self.CONF.crypto.hashtype)
|
||||
|
||||
def _ticket_metadata(self, source=DEFAULT_SOURCE,
|
||||
destination=DEFAULT_DEST, nonce=DEFAULT_NONCE,
|
||||
timestamp=None, b64encode=True):
|
||||
if not timestamp:
|
||||
timestamp = timeutils.utcnow()
|
||||
|
||||
return {'source': source, 'destination': destination,
|
||||
'nonce': nonce, 'timestamp': timestamp}
|
||||
|
||||
def _add_key(self, name, key=None, b64encode=True):
|
||||
if not key:
|
||||
if name == DEFAULT_SOURCE:
|
||||
key = SOURCE_KEY
|
||||
elif name == DEFAULT_DEST:
|
||||
key = DEST_KEY
|
||||
else:
|
||||
raise ValueError("No default key available")
|
||||
|
||||
if b64encode:
|
||||
key = base64.b64encode(key)
|
||||
|
||||
resp = self.put('keys/%s' % name,
|
||||
status=200,
|
||||
json={'key': key}).json
|
||||
|
||||
return "%s:%s" % (resp['name'], resp['generation'])
|
||||
|
||||
def _request_ticket(self, metadata=None, signature=None,
|
||||
source=DEFAULT_SOURCE, destination=DEFAULT_DEST,
|
||||
nonce=DEFAULT_NONCE, timestamp=None,
|
||||
source_key=None, status=200):
|
||||
if not metadata:
|
||||
metadata = self._ticket_metadata(source=source,
|
||||
nonce=nonce,
|
||||
destination=destination,
|
||||
timestamp=timestamp)
|
||||
|
||||
if not isinstance(metadata, six.text_type):
|
||||
metadata = base64.b64encode(jsonutils.dumps(metadata))
|
||||
|
||||
if not signature:
|
||||
if not source_key and source == DEFAULT_SOURCE:
|
||||
source_key = SOURCE_KEY
|
||||
|
||||
signature = self.crypto.sign(source_key, metadata)
|
||||
|
||||
return self.post('tickets',
|
||||
json={'metadata': metadata, 'signature': signature},
|
||||
status=status)
|
||||
|
||||
def test_valid_ticket(self):
|
||||
self._add_key(DEFAULT_SOURCE)
|
||||
self._add_key(DEFAULT_DEST)
|
||||
|
||||
response = self._request_ticket().json
|
||||
|
||||
b64m = response['metadata']
|
||||
metadata = jsonutils.loads(base64.b64decode(b64m))
|
||||
signature = response['signature']
|
||||
b64t = response['ticket']
|
||||
|
||||
# check signature was signed to source
|
||||
csig = self.crypto.sign(SOURCE_KEY, b64m + b64t)
|
||||
self.assertEqual(signature, csig)
|
||||
|
||||
# decrypt the ticket base if required, done by source
|
||||
if metadata['encryption']:
|
||||
ticket = self.crypto.decrypt(SOURCE_KEY, b64t)
|
||||
|
||||
ticket = jsonutils.loads(ticket)
|
||||
|
||||
skey = base64.b64decode(ticket['skey'])
|
||||
ekey = base64.b64decode(ticket['ekey'])
|
||||
b64esek = ticket['esek']
|
||||
|
||||
# the esek part is sent to the destination, so destination should be
|
||||
# able to decrypt it from here.
|
||||
esek = self.crypto.decrypt(DEST_KEY, b64esek)
|
||||
esek = jsonutils.loads(esek)
|
||||
|
||||
self.assertEqual(int(self.CONF.ticket_lifetime), esek['ttl'])
|
||||
|
||||
# now should be able to reconstruct skey, ekey from esek data
|
||||
info = '%s,%s,%s' % (metadata['source'], metadata['destination'],
|
||||
esek['timestamp'])
|
||||
|
||||
key = base64.b64decode(esek['key'])
|
||||
new_sig, new_key = self.CRYPTO.generate_keys(key, info)
|
||||
|
||||
self.assertEqual(new_key, ekey)
|
||||
self.assertEqual(new_sig, skey)
|
||||
|
||||
def test_missing_source_key(self):
|
||||
self._add_key(DEFAULT_DEST)
|
||||
self._request_ticket(status=404)
|
||||
|
||||
def test_missing_dest_key(self):
|
||||
self._add_key(DEFAULT_SOURCE)
|
||||
self._request_ticket(status=404)
|
||||
|
||||
def test_wrong_source_key(self):
|
||||
# install TEST_KEY but sign with SOURCE_KEY
|
||||
self._add_key(DEFAULT_SOURCE, TEST_KEY)
|
||||
self._add_key(DEFAULT_DEST)
|
||||
|
||||
self._request_ticket(status=401)
|
||||
|
||||
def test_invalid_signature(self):
|
||||
self._add_key(DEFAULT_SOURCE)
|
||||
self._add_key(DEFAULT_DEST)
|
||||
|
||||
self._request_ticket(status=401, signature='bad-signature')
|
||||
|
||||
def test_invalid_expired_request(self):
|
||||
self._add_key(DEFAULT_SOURCE)
|
||||
self._add_key(DEFAULT_DEST)
|
||||
|
||||
timestamp = timeutils.utcnow() - datetime.timedelta(hours=5)
|
||||
|
||||
self._request_ticket(status=401, timestamp=timestamp)
|
||||
|
||||
def test_fails_on_garbage_metadata(self):
|
||||
self._request_ticket(metadata='garbage',
|
||||
signature='signature',
|
||||
status=400)
|
||||
|
||||
self._request_ticket(metadata='{"json": "string"}',
|
||||
signature='signature',
|
||||
status=400)
|
||||
|
||||
def test_missing_attributes_in_metadata(self):
|
||||
self._add_key(DEFAULT_SOURCE)
|
||||
self._add_key(DEFAULT_DEST)
|
||||
|
||||
for attr in ['source', 'timestamp', 'destination', 'nonce']:
|
||||
metadata = self._ticket_metadata(b64encode=False)
|
||||
del metadata[attr]
|
||||
|
||||
self._request_ticket(metadata=metadata, status=400)
|
|
@ -10,6 +10,8 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslotest import base
|
||||
|
||||
|
@ -17,7 +19,7 @@ from kite.common import crypto
|
|||
from kite.common import service
|
||||
from kite.common import storage
|
||||
from kite.openstack.common.fixture import config
|
||||
from kite.tests import paths
|
||||
from kite.openstack.common.fixture import mockpatch
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('master_key_file', 'kite.common.crypto', group='crypto')
|
||||
|
@ -33,12 +35,13 @@ class BaseTestCase(base.BaseTestCase):
|
|||
storage.StorageManager.reset()
|
||||
crypto.CryptoManager.reset()
|
||||
|
||||
service.parse_args(args=[])
|
||||
self.mkey = os.urandom(crypto.CryptoManager.KEY_SIZE)
|
||||
patch = mockpatch.Patch(
|
||||
'kite.common.crypto.CryptoManager._load_master_key',
|
||||
new=lambda x: self.mkey)
|
||||
self.useFixture(patch)
|
||||
|
||||
self.master_key_file = paths.tmp_path('mkey.key')
|
||||
self.config(group='crypto',
|
||||
master_key_file=self.master_key_file,
|
||||
)
|
||||
service.parse_args(args=[])
|
||||
|
||||
def config(self, *args, **kwargs):
|
||||
self.config_fixture.config(*args, **kwargs)
|
||||
|
|
|
@ -12,9 +12,13 @@
|
|||
|
||||
import base64
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from oslotest import base as oslo_base
|
||||
|
||||
from kite.common import crypto
|
||||
from kite.common import exception
|
||||
from kite.openstack.common.fixture import config
|
||||
from kite.tests import paths
|
||||
from kite.tests.unit import base
|
||||
|
||||
|
@ -74,7 +78,13 @@ class CryptoTests(base.BaseTestCase):
|
|||
anot_name, enc_key, sig)
|
||||
|
||||
|
||||
class CryptoMasterKeyTests(base.BaseTestCase):
|
||||
class CryptoMasterKeyTests(oslo_base.BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(CryptoMasterKeyTests, self).setUp()
|
||||
|
||||
self.config_fixture = self.useFixture(config.Config())
|
||||
self.CONF = self.config_fixture.conf
|
||||
|
||||
def _remove_file(self, f):
|
||||
try:
|
||||
|
@ -83,11 +93,11 @@ class CryptoMasterKeyTests(base.BaseTestCase):
|
|||
pass
|
||||
|
||||
def test_key_creation(self):
|
||||
keyfile = paths.test_path('test-kds.mkey')
|
||||
keyfile = paths.test_path('%s.mkey' % uuid.uuid4().hex)
|
||||
self._remove_file(keyfile)
|
||||
self.addCleanup(self._remove_file, keyfile)
|
||||
|
||||
self.config(group='crypto', master_key_file=keyfile)
|
||||
self.config_fixture.config(group='crypto', master_key_file=keyfile)
|
||||
|
||||
CRYPTO = crypto.CryptoManager()
|
||||
self.assertTrue(os.path.exists(keyfile))
|
||||
|
|
Loading…
Reference in New Issue