4 space indentation

This commit is contained in:
Pino de Candia 2017-12-08 15:04:44 -06:00
parent 95c0f1011c
commit 7812e1e8b6
16 changed files with 941 additions and 676 deletions

View File

@ -1,4 +1,15 @@
#!/usr/bin/env python #!/usr/bin/env python
# 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 json import json
import requests import requests

View File

@ -1,4 +1,16 @@
#!/usr/bin/env python #!/usr/bin/env python
# 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 sys import sys
import json import json
import yaml import yaml

View File

@ -1,3 +1,15 @@
# 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 json import json
import requests import requests
import os import os

View File

@ -1,4 +1,16 @@
#!/usr/bin/env python #!/usr/bin/env python
# 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 argparse import argparse
import json import json
import requests import requests

View File

@ -1,4 +1,16 @@
#!/usr/bin/env python #!/usr/bin/env python
# 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 argparse import argparse
import json import json
import os import os

View File

@ -1,4 +1,16 @@
#!/usr/bin/env python #!/usr/bin/env python
# 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 sys import sys
import json import json
import yaml import yaml

View File

@ -1,3 +1,15 @@
# 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 setuptools import setuptools
# In python < 2.7.4, a lazy loading of package `pbr` will break # In python < 2.7.4, a lazy loading of package `pbr` will break

View File

@ -1,3 +1,15 @@
# 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 falcon import falcon
import models import models
import os.path import os.path
@ -11,6 +23,7 @@ CONF = cfg.CONF
if os.path.isfile(fname): if os.path.isfile(fname):
CONF(default_config_files=[fname]) CONF(default_config_files=[fname])
def create_app(sa): def create_app(sa):
api = falcon.API(middleware=[models.Logger(), sa]) api = falcon.API(middleware=[models.Logger(), sa])
api.add_route('/authorities', models.Authorities()) api.add_route('/authorities', models.Authorities())
@ -23,8 +36,10 @@ def create_app(sa):
api.add_route('/novavendordata', models.NovaVendorData()) api.add_route('/novavendordata', models.NovaVendorData())
return api return api
def get_app(): def get_app():
return create_app(SQLAlchemySessionManager()) return create_app(SQLAlchemySessionManager())
def main(global_config, **settings): def main(global_config, **settings):
return create_app(SQLAlchemySessionManager()) return create_app(SQLAlchemySessionManager())

View File

@ -1,3 +1,15 @@
# 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 falcon import falcon
import json import json
import logging import logging
@ -5,6 +17,7 @@ import uuid
from tatu.db import models as db from tatu.db import models as db
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
def validate_uuid(map, key): def validate_uuid(map, key):
try: try:
# Verify it's a valid UUID, then convert to canonical string representation # Verify it's a valid UUID, then convert to canonical string representation
@ -14,6 +27,7 @@ def validate_uuid(map, key):
msg = '{} is not a valid UUID'.format(map[key]) msg = '{} is not a valid UUID'.format(map[key])
raise falcon.HTTPBadRequest('Bad request', msg) raise falcon.HTTPBadRequest('Bad request', msg)
def validate_uuids(req, params): def validate_uuids(req, params):
id_keys = ['token_id', 'auth_id', 'host_id', 'user_id', 'project-id', 'instance-id'] id_keys = ['token_id', 'auth_id', 'host_id', 'user_id', 'project-id', 'instance-id']
if req.method in ('POST', 'PUT'): if req.method in ('POST', 'PUT'):
@ -24,6 +38,7 @@ def validate_uuids(req, params):
if key in params: if key in params:
validate_uuid(params, key) validate_uuid(params, key)
def validate(req, resp, resource, params): def validate(req, resp, resource, params):
if req.content_length: if req.content_length:
# Store the body since we cannot read the stream again later # Store the body since we cannot read the stream again later
@ -32,6 +47,7 @@ def validate(req, resp, resource, params):
raise falcon.HTTPBadRequest('The POST/PUT request is missing a body.') raise falcon.HTTPBadRequest('The POST/PUT request is missing a body.')
validate_uuids(req, params) validate_uuids(req, params)
class Logger(object): class Logger(object):
def __init__(self): def __init__(self):
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
@ -47,8 +63,8 @@ class Logger(object):
req.body if hasattr(req, 'body') else 'None', req.body if hasattr(req, 'body') else 'None',
resp.status, resp.location, resp.body)) resp.status, resp.location, resp.body))
class Authorities(object):
class Authorities(object):
@falcon.before(validate) @falcon.before(validate)
def on_post(self, req, resp): def on_post(self, req, resp):
try: try:
@ -61,8 +77,8 @@ class Authorities(object):
resp.status = falcon.HTTP_201 resp.status = falcon.HTTP_201
resp.location = '/authorities/' + req.body['auth_id'] resp.location = '/authorities/' + req.body['auth_id']
class Authority(object):
class Authority(object):
@falcon.before(validate) @falcon.before(validate)
def on_get(self, req, resp, auth_id): def on_get(self, req, resp, auth_id):
auth = db.getAuthority(self.session, auth_id) auth = db.getAuthority(self.session, auth_id)
@ -81,8 +97,8 @@ class Authority(object):
resp.body = json.dumps(body) resp.body = json.dumps(body)
resp.status = falcon.HTTP_OK resp.status = falcon.HTTP_OK
class UserCerts(object):
class UserCerts(object):
@falcon.before(validate) @falcon.before(validate)
def on_post(self, req, resp): def on_post(self, req, resp):
# TODO: validation # TODO: validation
@ -98,8 +114,8 @@ class UserCerts(object):
resp.status = falcon.HTTP_201 resp.status = falcon.HTTP_201
resp.location = '/usercerts/' + user.user_id + '/' + user.fingerprint resp.location = '/usercerts/' + user.user_id + '/' + user.fingerprint
class UserCert(object):
class UserCert(object):
@falcon.before(validate) @falcon.before(validate)
def on_get(self, req, resp, user_id, fingerprint): def on_get(self, req, resp, user_id, fingerprint):
user = db.getUserCert(self.session, user_id, fingerprint) user = db.getUserCert(self.session, user_id, fingerprint)
@ -115,6 +131,7 @@ class UserCert(object):
resp.body = json.dumps(body) resp.body = json.dumps(body)
resp.status = falcon.HTTP_OK resp.status = falcon.HTTP_OK
def hostToJson(host): def hostToJson(host):
return json.dumps({ return json.dumps({
'host_id': host.host_id, 'host_id': host.host_id,
@ -123,8 +140,8 @@ def hostToJson(host):
'key-cert.pub': host.cert, 'key-cert.pub': host.cert,
}) })
class HostCerts(object):
class HostCerts(object):
@falcon.before(validate) @falcon.before(validate)
def on_post(self, req, resp): def on_post(self, req, resp):
# Note that we could have found the host_id using the token_id. # Note that we could have found the host_id using the token_id.
@ -142,8 +159,8 @@ class HostCerts(object):
resp.status = falcon.HTTP_201 resp.status = falcon.HTTP_201
resp.location = '/hostcerts/' + host.host_id + '/' + host.fingerprint resp.location = '/hostcerts/' + host.host_id + '/' + host.fingerprint
class HostCert(object):
class HostCert(object):
@falcon.before(validate) @falcon.before(validate)
def on_get(self, req, resp, host_id, fingerprint): def on_get(self, req, resp, host_id, fingerprint):
host = db.getHostCert(self.session, host_id, fingerprint) host = db.getHostCert(self.session, host_id, fingerprint)
@ -153,8 +170,8 @@ class HostCert(object):
resp.body = hostToJson(host) resp.body = hostToJson(host)
resp.status = falcon.HTTP_OK resp.status = falcon.HTTP_OK
class Tokens(object):
class Tokens(object):
@falcon.before(validate) @falcon.before(validate)
def on_post(self, req, resp): def on_post(self, req, resp):
try: try:
@ -169,8 +186,8 @@ class Tokens(object):
resp.status = falcon.HTTP_201 resp.status = falcon.HTTP_201
resp.location = '/hosttokens/' + token.token_id resp.location = '/hosttokens/' + token.token_id
class NovaVendorData(object):
class NovaVendorData(object):
@falcon.before(validate) @falcon.before(validate)
def on_post(self, req, resp): def on_post(self, req, resp):
# An example of the data nova sends to vendordata services: # An example of the data nova sends to vendordata services:

View File

@ -1,3 +1,15 @@
# 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.passphrase import Passphrase from castellan.common.objects.passphrase import Passphrase
from castellan.common.utils import credential_factory from castellan.common.utils import credential_factory
from castellan.key_manager import API from castellan.key_manager import API
@ -16,6 +28,7 @@ CONF.register_opts(opts, group='tatu')
_context = None _context = None
_api = None _api = None
def validate_config(): def validate_config():
if CONF.tatu.use_barbican_key_manager: if CONF.tatu.use_barbican_key_manager:
set_castellan_defaults(CONF) set_castellan_defaults(CONF)
@ -23,18 +36,21 @@ def validate_config():
set_castellan_defaults(CONF, set_castellan_defaults(CONF,
api_class='tatu.castellano.TatuKeyManager') api_class='tatu.castellano.TatuKeyManager')
def context(): def context():
global _context global _context
if _context is None and CONF.tatu.use_barbican_key_manager: if _context is None and CONF.tatu.use_barbican_key_manager:
_context = credential_factory(conf=CONF) _context = credential_factory(conf=CONF)
return _context return _context
def api(): def api():
global _api global _api
if _api is None: if _api is None:
_api = API() _api = API()
return _api return _api
def delete_secret(id, ctx=None): def delete_secret(id, ctx=None):
"""delete a secret from the external key manager """delete a secret from the external key manager
:param id: The identifier of the secret to delete :param id: The identifier of the secret to delete
@ -43,6 +59,7 @@ def delete_secret(id, ctx=None):
""" """
api().delete(ctx or context(), id) api().delete(ctx or context(), id)
def get_secret(id, ctx=None): def get_secret(id, ctx=None):
"""get a secret associated with an id """get a secret associated with an id
:param id: The identifier of the secret to retrieve :param id: The identifier of the secret to retrieve
@ -52,6 +69,7 @@ def get_secret(id, ctx=None):
key = api().get(ctx or context(), id) key = api().get(ctx or context(), id)
return key.get_encoded() return key.get_encoded()
def store_secret(secret, ctx=None): def store_secret(secret, ctx=None):
"""store a secret and return its identifier """store a secret and return its identifier
:param secret: The secret to store, this should be a string :param secret: The secret to store, this should be a string
@ -61,10 +79,13 @@ def store_secret(secret, ctx=None):
key = Passphrase(secret) key = Passphrase(secret)
return api().store(ctx or context(), key) return api().store(ctx or context(), key)
""" """
This module contains the KeyManager class that will be used by the This module contains the KeyManager class that will be used by the
castellan library, it is not meant for direct usage within tatu. castellan library, it is not meant for direct usage within tatu.
""" """
class TatuKeyManager(KeyManager): class TatuKeyManager(KeyManager):
"""Tatu specific key manager """Tatu specific key manager
This manager is a thin wrapper around the secret being stored. It is This manager is a thin wrapper around the secret being stored. It is
@ -73,6 +94,7 @@ class TatuKeyManager(KeyManager):
This behavior allows Tatu to continue storing secrets in its database This behavior allows Tatu to continue storing secrets in its database
while using the Castellan key manager abstraction. while using the Castellan key manager abstraction.
""" """
def __init__(self, configuration=None): def __init__(self, configuration=None):
pass pass

View File

@ -1,3 +1,15 @@
# 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 datetime import datetime from datetime import datetime
import falcon import falcon
import os import os
@ -12,6 +24,7 @@ from Crypto.PublicKey import RSA
Base = declarative_base() Base = declarative_base()
class Authority(Base): class Authority(Base):
__tablename__ = 'authorities' __tablename__ = 'authorities'
@ -19,15 +32,19 @@ class Authority(Base):
user_key = sa.Column(sa.Text) user_key = sa.Column(sa.Text)
host_key = sa.Column(sa.Text) host_key = sa.Column(sa.Text)
def getAuthority(session, auth_id): def getAuthority(session, auth_id):
return session.query(Authority).get(auth_id) return session.query(Authority).get(auth_id)
def getAuthUserKey(auth): def getAuthUserKey(auth):
return get_secret(auth.user_key) return get_secret(auth.user_key)
def getAuthHostKey(auth): def getAuthHostKey(auth):
return get_secret(auth.host_key) return get_secret(auth.host_key)
def createAuthority(session, auth_id): def createAuthority(session, auth_id):
user_key = RSA.generate(2048).exportKey('PEM') user_key = RSA.generate(2048).exportKey('PEM')
user_secret_id = store_secret(user_key) user_secret_id = store_secret(user_key)
@ -43,6 +60,7 @@ def createAuthority(session, auth_id):
raise falcon.HTTPConflict("This certificate authority already exists.") raise falcon.HTTPConflict("This certificate authority already exists.")
return auth return auth
class UserCert(Base): class UserCert(Base):
__tablename__ = 'user_certs' __tablename__ = 'user_certs'
@ -51,9 +69,11 @@ class UserCert(Base):
auth_id = sa.Column(sa.String(36), sa.ForeignKey('authorities.auth_id')) auth_id = sa.Column(sa.String(36), sa.ForeignKey('authorities.auth_id'))
cert = sa.Column(sa.Text) cert = sa.Column(sa.Text)
def getUserCert(session, user_id, fingerprint): def getUserCert(session, user_id, fingerprint):
return session.query(UserCert).get([user_id, fingerprint]) return session.query(UserCert).get([user_id, fingerprint])
def createUserCert(session, user_id, auth_id, pub): def createUserCert(session, user_id, auth_id, pub):
# Retrieve the authority's private key and generate the certificate # Retrieve the authority's private key and generate the certificate
auth = getAuthority(session, auth_id) auth = getAuthority(session, auth_id)
@ -76,6 +96,7 @@ def createUserCert(session, user_id, auth_id, pub):
session.commit() session.commit()
return user return user
class Token(Base): class Token(Base):
__tablename__ = 'tokens' __tablename__ = 'tokens'
@ -88,6 +109,7 @@ class Token(Base):
date_used = sa.Column(sa.DateTime, default=datetime.min) date_used = sa.Column(sa.DateTime, default=datetime.min)
fingerprint_used = sa.Column(sa.String(36)) fingerprint_used = sa.Column(sa.String(36))
def createToken(session, host_id, auth_id, hostname): def createToken(session, host_id, auth_id, hostname):
# Validate the certificate authority # Validate the certificate authority
auth = getAuthority(session, auth_id) auth = getAuthority(session, auth_id)
@ -108,6 +130,7 @@ def createToken(session, host_id, auth_id, hostname):
session.commit() session.commit()
return token return token
class HostCert(Base): class HostCert(Base):
__tablename__ = 'host_certs' __tablename__ = 'host_certs'
@ -119,9 +142,11 @@ class HostCert(Base):
cert = sa.Column(sa.Text) cert = sa.Column(sa.Text)
hostname = sa.Column(sa.String(36)) hostname = sa.Column(sa.String(36))
def getHostCert(session, host_id, fingerprint): def getHostCert(session, host_id, fingerprint):
return session.query(HostCert).get([host_id, fingerprint]) return session.query(HostCert).get([host_id, fingerprint])
def createHostCert(session, token_id, host_id, pub): def createHostCert(session, token_id, host_id, pub):
token = session.query(Token).get(token_id) token = session.query(Token).get(token_id)
if token is None: if token is None:

View File

@ -1,3 +1,15 @@
# 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 os import os
from sqlalchemy import create_engine from sqlalchemy import create_engine
@ -9,6 +21,7 @@ def get_url():
return os.getenv("DATABASE_URL", "sqlite:///development.db") return os.getenv("DATABASE_URL", "sqlite:///development.db")
# return os.getenv("DATABASE_URL", "sqlite:///:memory:") # return os.getenv("DATABASE_URL", "sqlite:///:memory:")
class SQLAlchemySessionManager: class SQLAlchemySessionManager:
""" """
Create a scoped session for every request and close it when the request Create a scoped session for every request and close it when the request

View File

@ -1,3 +1,15 @@
# 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 json import json
import requests import requests
import os import os
@ -8,6 +20,7 @@ import uuid
server = 'http://172.24.4.1:18322' server = 'http://172.24.4.1:18322'
def vendordata_request(instance_id, project_id, hostname): def vendordata_request(instance_id, project_id, hostname):
return { return {
'instance-id': instance_id, 'instance-id': instance_id,
@ -15,6 +28,7 @@ def vendordata_request(instance_id, project_id, hostname):
'hostname': hostname 'hostname': hostname
} }
def host_request(token, host, pub_key): def host_request(token, host, pub_key):
return { return {
'token_id': token, 'token_id': token,
@ -22,6 +36,7 @@ def host_request(token, host, pub_key):
'key.pub': pub_key 'key.pub': pub_key
} }
def test_host_certificate_generation(): def test_host_certificate_generation():
project_id = random_uuid() project_id = random_uuid()
response = requests.post( response = requests.post(

View File

@ -1,3 +1,15 @@
# 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 oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
import oslo_messaging import oslo_messaging
@ -14,8 +26,8 @@ LOG = logging.getLogger(__name__)
CONF = cfg.CONF CONF = cfg.CONF
DOMAIN = 'tatu' DOMAIN = 'tatu'
class NotificationEndpoint(object):
class NotificationEndpoint(object):
filter_rule = oslo_messaging.NotificationFilter( filter_rule = oslo_messaging.NotificationFilter(
publisher_id='^identity.*', publisher_id='^identity.*',
event_type='^identity.project.created') event_type='^identity.project.created')
@ -46,6 +58,7 @@ class NotificationEndpoint(object):
else: else:
LOG.error("Status update or unknown") LOG.error("Status update or unknown")
def main(): def main():
logging.register_options(CONF) logging.register_options(CONF)
extra_log_level_defaults = ['tatu=DEBUG', '__main__=DEBUG'] extra_log_level_defaults = ['tatu=DEBUG', '__main__=DEBUG']
@ -74,5 +87,6 @@ def main():
server.stop() server.stop()
server.wait() server.wait()
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) sys.exit(main())

View File

@ -1,4 +1,14 @@
# coding=utf-8 # 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 json import json
import falcon import falcon
from falcon import testing from falcon import testing
@ -12,15 +22,18 @@ from Crypto.PublicKey import RSA
import sshpubkeys import sshpubkeys
import time import time
@pytest.fixture @pytest.fixture
def db(): def db():
return SQLAlchemySessionManager() return SQLAlchemySessionManager()
@pytest.fixture @pytest.fixture
def client(db): def client(db):
api = create_app(db) api = create_app(db)
return testing.TestClient(api) return testing.TestClient(api)
token_id = '' token_id = ''
host_id = random_uuid() host_id = random_uuid()
@ -36,6 +49,7 @@ user_fingerprint = sshpubkeys.SSHKey(user_pub_key).hash_md5()
auth_id = random_uuid() auth_id = random_uuid()
auth_user_pub_key = None auth_user_pub_key = None
@pytest.mark.dependency() @pytest.mark.dependency()
def test_post_authority(client, auth_id=auth_id): def test_post_authority(client, auth_id=auth_id):
body = { body = {
@ -48,6 +62,7 @@ def test_post_authority(client, auth_id=auth_id):
assert response.status == falcon.HTTP_CREATED assert response.status == falcon.HTTP_CREATED
assert response.headers['location'] == '/authorities/' + auth_id assert response.headers['location'] == '/authorities/' + auth_id
@pytest.mark.dependency(depends=['test_post_authority']) @pytest.mark.dependency(depends=['test_post_authority'])
def test_post_authority_duplicate(client): def test_post_authority_duplicate(client):
body = { body = {
@ -59,12 +74,14 @@ def test_post_authority_duplicate(client):
) )
assert response.status == falcon.HTTP_CONFLICT assert response.status == falcon.HTTP_CONFLICT
def test_post_no_body(client): def test_post_no_body(client):
for path in ['/authorities', '/usercerts', '/hosttokens', for path in ['/authorities', '/usercerts', '/hosttokens',
'/hostcerts', '/novavendordata']: '/hostcerts', '/novavendordata']:
response = client.simulate_post(path) response = client.simulate_post(path)
assert response.status == falcon.HTTP_BAD_REQUEST assert response.status == falcon.HTTP_BAD_REQUEST
def test_post_empty_body(client): def test_post_empty_body(client):
bodystr = json.dumps({}) bodystr = json.dumps({})
for path in ['/authorities', '/usercerts', '/hosttokens', for path in ['/authorities', '/usercerts', '/hosttokens',
@ -72,6 +89,7 @@ def test_post_empty_body(client):
response = client.simulate_post(path, body=bodystr) response = client.simulate_post(path, body=bodystr)
assert response.status == falcon.HTTP_BAD_REQUEST assert response.status == falcon.HTTP_BAD_REQUEST
def test_post_authority_bad_uuid(client): def test_post_authority_bad_uuid(client):
body = { body = {
'auth_id': 'foobar', 'auth_id': 'foobar',
@ -82,6 +100,7 @@ def test_post_authority_bad_uuid(client):
) )
assert response.status == falcon.HTTP_BAD_REQUEST assert response.status == falcon.HTTP_BAD_REQUEST
@pytest.mark.dependency(depends=['test_post_authority']) @pytest.mark.dependency(depends=['test_post_authority'])
def test_get_authority(client): def test_get_authority(client):
response = client.simulate_get('/authorities/' + auth_id) response = client.simulate_get('/authorities/' + auth_id)
@ -95,14 +114,17 @@ def test_get_authority(client):
assert 'user_key' not in body assert 'user_key' not in body
assert 'host_key' not in body assert 'host_key' not in body
def test_get_authority_doesnt_exist(client): def test_get_authority_doesnt_exist(client):
response = client.simulate_get('/authorities/' + random_uuid()) response = client.simulate_get('/authorities/' + random_uuid())
assert response.status == falcon.HTTP_NOT_FOUND assert response.status == falcon.HTTP_NOT_FOUND
def test_get_authority_with_bad_uuid(client): def test_get_authority_with_bad_uuid(client):
response = client.simulate_get('/authorities/foobar') response = client.simulate_get('/authorities/foobar')
assert response.status == falcon.HTTP_BAD_REQUEST assert response.status == falcon.HTTP_BAD_REQUEST
def user_request(auth=auth_id, user_id=user_id, pub_key=user_pub_key): def user_request(auth=auth_id, user_id=user_id, pub_key=user_pub_key):
return { return {
'user_id': user_id, 'user_id': user_id,
@ -110,6 +132,7 @@ def user_request(auth=auth_id, user_id=user_id, pub_key=user_pub_key):
'key.pub': pub_key 'key.pub': pub_key
} }
def test_post_user_bad_uuid(client): def test_post_user_bad_uuid(client):
for key in ['user_id', 'auth_id']: for key in ['user_id', 'auth_id']:
body = user_request() body = user_request()
@ -120,6 +143,7 @@ def test_post_user_bad_uuid(client):
) )
assert response.status == falcon.HTTP_BAD_REQUEST assert response.status == falcon.HTTP_BAD_REQUEST
@pytest.mark.dependency(depends=['test_post_authority']) @pytest.mark.dependency(depends=['test_post_authority'])
def test_post_user(client): def test_post_user(client):
body = user_request() body = user_request()
@ -134,6 +158,7 @@ def test_post_user(client):
assert location[2] == body['user_id'] assert location[2] == body['user_id']
assert location[3] == sshpubkeys.SSHKey(body['key.pub']).hash_md5() assert location[3] == sshpubkeys.SSHKey(body['key.pub']).hash_md5()
@pytest.mark.dependency(depends=['test_post_user']) @pytest.mark.dependency(depends=['test_post_user'])
def test_get_user(client): def test_get_user(client):
response = client.simulate_get('/usercerts/' + user_id + '/' + user_fingerprint) response = client.simulate_get('/usercerts/' + user_id + '/' + user_fingerprint)
@ -145,14 +170,17 @@ def test_get_user(client):
assert 'key-cert.pub' in body assert 'key-cert.pub' in body
assert body['auth_id'] == auth_id assert body['auth_id'] == auth_id
def test_get_user_doesnt_exist(client): def test_get_user_doesnt_exist(client):
response = client.simulate_get('/usercerts/' + random_uuid() + '/' + user_fingerprint) response = client.simulate_get('/usercerts/' + random_uuid() + '/' + user_fingerprint)
assert response.status == falcon.HTTP_NOT_FOUND assert response.status == falcon.HTTP_NOT_FOUND
def test_get_user_with_bad_uuid(client): def test_get_user_with_bad_uuid(client):
response = client.simulate_get('/usercerts/foobar/' + user_fingerprint) response = client.simulate_get('/usercerts/foobar/' + user_fingerprint)
assert response.status == falcon.HTTP_BAD_REQUEST assert response.status == falcon.HTTP_BAD_REQUEST
@pytest.mark.dependency(depends=['test_post_user']) @pytest.mark.dependency(depends=['test_post_user'])
def test_post_second_cert_same_user(client): def test_post_second_cert_same_user(client):
key = RSA.generate(2048) key = RSA.generate(2048)
@ -169,6 +197,7 @@ def test_post_second_cert_same_user(client):
assert location[2] == user_id assert location[2] == user_id
assert location[3] == sshpubkeys.SSHKey(pub_key).hash_md5() assert location[3] == sshpubkeys.SSHKey(pub_key).hash_md5()
def test_post_user_unknown_auth(client): def test_post_user_unknown_auth(client):
body = user_request(auth=random_uuid()) body = user_request(auth=random_uuid())
response = client.simulate_post( response = client.simulate_post(
@ -177,10 +206,12 @@ def test_post_user_unknown_auth(client):
) )
assert response.status == falcon.HTTP_NOT_FOUND assert response.status == falcon.HTTP_NOT_FOUND
@pytest.mark.dependency(depends=['test_post_user']) @pytest.mark.dependency(depends=['test_post_user'])
def test_post_same_user_and_key_returns_same_result(client): def test_post_same_user_and_key_returns_same_result(client):
test_post_user(client) test_post_user(client)
def token_request(auth=auth_id, host=host_id): def token_request(auth=auth_id, host=host_id):
return { return {
'host_id': host, 'host_id': host,
@ -188,6 +219,7 @@ def token_request(auth=auth_id, host=host_id):
'hostname': 'testname.local' 'hostname': 'testname.local'
} }
def host_request(token, host=host_id, pub_key=host_pub_key): def host_request(token, host=host_id, pub_key=host_pub_key):
return { return {
'token_id': token, 'token_id': token,
@ -195,6 +227,7 @@ def host_request(token, host=host_id, pub_key=host_pub_key):
'key.pub': pub_key 'key.pub': pub_key
} }
def vendordata_request(auth, host): def vendordata_request(auth, host):
return { return {
'instance-id': host, 'instance-id': host,
@ -202,6 +235,7 @@ def vendordata_request(auth, host):
'hostname': 'mytest.testing' 'hostname': 'mytest.testing'
} }
def test_post_vendordata_bad_uuid(client): def test_post_vendordata_bad_uuid(client):
for key in ['instance-id', 'project-id']: for key in ['instance-id', 'project-id']:
body = vendordata_request(auth_id, host_id) body = vendordata_request(auth_id, host_id)
@ -212,6 +246,7 @@ def test_post_vendordata_bad_uuid(client):
) )
assert response.status == falcon.HTTP_BAD_REQUEST assert response.status == falcon.HTTP_BAD_REQUEST
@pytest.mark.dependency(depends=['test_post_authority']) @pytest.mark.dependency(depends=['test_post_authority'])
def test_post_novavendordata(client): def test_post_novavendordata(client):
req = vendordata_request(auth_id, random_uuid()) req = vendordata_request(auth_id, random_uuid())
@ -231,6 +266,7 @@ def test_post_novavendordata(client):
assert 'principals' in vendordata assert 'principals' in vendordata
assert vendordata['principals'] == 'admin' assert vendordata['principals'] == 'admin'
def test_post_token_bad_uuid(client): def test_post_token_bad_uuid(client):
for key in ['auth_id', 'host_id']: for key in ['auth_id', 'host_id']:
body = token_request() body = token_request()
@ -241,6 +277,7 @@ def test_post_token_bad_uuid(client):
) )
assert response.status == falcon.HTTP_BAD_REQUEST assert response.status == falcon.HTTP_BAD_REQUEST
def test_post_host_bad_uuid(client): def test_post_host_bad_uuid(client):
for key in ['token_id', 'host_id']: for key in ['token_id', 'host_id']:
body = host_request(random_uuid()) body = host_request(random_uuid())
@ -251,6 +288,7 @@ def test_post_host_bad_uuid(client):
) )
assert response.status == falcon.HTTP_BAD_REQUEST assert response.status == falcon.HTTP_BAD_REQUEST
@pytest.mark.dependency(depends=['test_post_authority']) @pytest.mark.dependency(depends=['test_post_authority'])
def test_post_token_and_host(client): def test_post_token_and_host(client):
token = token_request() token = token_request()
@ -327,6 +365,7 @@ def test_stress_post_token_and_host(client):
assert location[3] == fingerprint assert location[3] == fingerprint
assert time.time() - start < 5 assert time.time() - start < 5
@pytest.mark.dependency(depends=['test_post_token_and_host']) @pytest.mark.dependency(depends=['test_post_token_and_host'])
def test_post_token_same_host_id(client): def test_post_token_same_host_id(client):
# Posting with the same host ID should return the same token # Posting with the same host ID should return the same token
@ -342,6 +381,7 @@ def test_post_token_same_host_id(client):
# The token id should be the same as that from the previous test. # The token id should be the same as that from the previous test.
assert token_id == location_path[-1] assert token_id == location_path[-1]
@pytest.mark.dependency(depends=['test_post_token_and_host']) @pytest.mark.dependency(depends=['test_post_token_and_host'])
def test_get_host(client): def test_get_host(client):
response = client.simulate_get('/hostcerts/' + host_id + '/' + host_fingerprint) response = client.simulate_get('/hostcerts/' + host_id + '/' + host_fingerprint)
@ -355,14 +395,17 @@ def test_get_host(client):
assert body['fingerprint'] == host_fingerprint assert body['fingerprint'] == host_fingerprint
assert body['auth_id'] == auth_id assert body['auth_id'] == auth_id
def test_get_host_doesnt_exist(client): def test_get_host_doesnt_exist(client):
response = client.simulate_get('/hostcerts/' + random_uuid() + '/' + host_fingerprint) response = client.simulate_get('/hostcerts/' + random_uuid() + '/' + host_fingerprint)
assert response.status == falcon.HTTP_NOT_FOUND assert response.status == falcon.HTTP_NOT_FOUND
def test_get_host_with_bad_uuid(client): def test_get_host_with_bad_uuid(client):
response = client.simulate_get('/hostcerts/foobar/' + host_fingerprint) response = client.simulate_get('/hostcerts/foobar/' + host_fingerprint)
assert response.status == falcon.HTTP_BAD_REQUEST assert response.status == falcon.HTTP_BAD_REQUEST
def test_post_token_unknown_auth(client): def test_post_token_unknown_auth(client):
token = token_request(auth=random_uuid()) token = token_request(auth=random_uuid())
response = client.simulate_post( response = client.simulate_post(
@ -371,6 +414,7 @@ def test_post_token_unknown_auth(client):
) )
assert response.status == falcon.HTTP_NOT_FOUND assert response.status == falcon.HTTP_NOT_FOUND
@pytest.mark.dependency(depends=['test_post_authority']) @pytest.mark.dependency(depends=['test_post_authority'])
def test_post_host_with_bogus_token(client): def test_post_host_with_bogus_token(client):
host = host_request(random_uuid(), random_uuid()) host = host_request(random_uuid(), random_uuid())
@ -380,6 +424,7 @@ def test_post_host_with_bogus_token(client):
) )
assert response.status == falcon.HTTP_NOT_FOUND assert response.status == falcon.HTTP_NOT_FOUND
@pytest.mark.dependency(depends=['test_post_token_and_host']) @pytest.mark.dependency(depends=['test_post_token_and_host'])
def test_post_host_with_wrong_host_id(client): def test_post_host_with_wrong_host_id(client):
# Get a new token for the same host_id as the base test. # Get a new token for the same host_id as the base test.
@ -403,6 +448,7 @@ def test_post_host_with_wrong_host_id(client):
) )
assert response.status == falcon.HTTP_CONFLICT assert response.status == falcon.HTTP_CONFLICT
@pytest.mark.dependency(depends=['test_post_token_and_host']) @pytest.mark.dependency(depends=['test_post_token_and_host'])
def test_post_host_different_public_key_fails(client): def test_post_host_different_public_key_fails(client):
# Use the same token compared to the test this depends on. # Use the same token compared to the test this depends on.
@ -425,6 +471,7 @@ def test_post_host_different_public_key_fails(client):
) )
assert response.status == falcon.HTTP_CONFLICT assert response.status == falcon.HTTP_CONFLICT
@pytest.mark.dependency(depends=['test_post_token_and_host']) @pytest.mark.dependency(depends=['test_post_token_and_host'])
def test_post_host_with_used_token(client): def test_post_host_with_used_token(client):
# Re-use the token from the test this depends on. # Re-use the token from the test this depends on.

View File

@ -1,10 +1,24 @@
# 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 os import os
import subprocess import subprocess
import uuid import uuid
def random_uuid(): def random_uuid():
return str(uuid.uuid4()) return str(uuid.uuid4())
def generateCert(auth_key, entity_key, hostname=None, principals='root'): def generateCert(auth_key, entity_key, hostname=None, principals='root'):
# Temporarily write the authority private key and entity public key to files # Temporarily write the authority private key and entity public key to files
prefix = uuid.uuid4().hex prefix = uuid.uuid4().hex