From 6235f5e2b04fcb21b82ad1aab7d85c9e0552809c Mon Sep 17 00:00:00 2001 From: Pino de Candia Date: Wed, 22 Nov 2017 21:09:49 +0000 Subject: [PATCH] Use hash_md5 instead of deprecated hash. Fixed fd leak. --- .gitignore | 3 ++ tatu/api/models.py | 7 ++- tatu/db/models.py | 4 +- tatu/db/persistence.py | 1 + tatu/ftests/__init__.py | 0 tatu/ftests/test_api.py | 97 +++++++++++++++++++++++++++++++++++++++++ tatu/tests/test_app.py | 33 +++++++------- tatu/utils.py | 5 ++- 8 files changed, 128 insertions(+), 22 deletions(-) create mode 100644 .gitignore create mode 100644 tatu/ftests/__init__.py create mode 100644 tatu/ftests/test_api.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1559f2e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +*.swp +*.swo diff --git a/tatu/api/models.py b/tatu/api/models.py index 046690c..a781893 100644 --- a/tatu/api/models.py +++ b/tatu/api/models.py @@ -36,10 +36,13 @@ class Logger(object): def __init__(self): self.logger = logging.getLogger('gunicorn.error') + def process_resource(self, req, resp, resource, params): + self.logger.debug('Received request {0} {1}'.format(req.method, req.relative_uri)) + def process_response(self, req, resp, resource, params): self.logger.debug( - 'Request {0} {1} with body {2} produced' - 'response with status {3} location {4} and body {5}'.format( + 'Request {0} {1} with body {2} produced response ' + 'with status {3} location {4} and body {5}'.format( req.method, req.relative_uri, req.body if hasattr(req, 'body') else 'None', resp.status, resp.location, resp.body)) diff --git a/tatu/db/models.py b/tatu/db/models.py index 560f50e..391c1fb 100644 --- a/tatu/db/models.py +++ b/tatu/db/models.py @@ -48,7 +48,7 @@ def createUserCert(session, user_id, auth_id, pub): auth = getAuthority(session, auth_id) if auth is None: raise falcon.HTTPNotFound(description='No Authority found with that ID') - fingerprint = sshpubkeys.SSHKey(pub).hash() + fingerprint = sshpubkeys.SSHKey(pub).hash_md5() certRecord = session.query(UserCert).get([user_id, fingerprint]) if certRecord is not None: raise falcon.HTTPConflict('This public key is already signed.') @@ -117,7 +117,7 @@ def createHostCert(session, token_id, host_id, pub): raise falcon.HTTPNotFound(description='No Token found with that ID') if token.host_id != host_id: raise falcon.HTTPConflict(description='The token is not valid for this instance ID') - fingerprint = sshpubkeys.SSHKey(pub).hash() + fingerprint = sshpubkeys.SSHKey(pub).hash_md5() if token.used: if token.fingerprint_used != fingerprint: diff --git a/tatu/db/persistence.py b/tatu/db/persistence.py index c0c8b07..0f449da 100644 --- a/tatu/db/persistence.py +++ b/tatu/db/persistence.py @@ -7,6 +7,7 @@ from tatu.db.models import Base def get_url(): return os.getenv("DATABASE_URL", "sqlite:///development.db") + #return os.getenv("DATABASE_URL", "sqlite:///:memory:") class SQLAlchemySessionManager: """ diff --git a/tatu/ftests/__init__.py b/tatu/ftests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tatu/ftests/test_api.py b/tatu/ftests/test_api.py new file mode 100644 index 0000000..6859833 --- /dev/null +++ b/tatu/ftests/test_api.py @@ -0,0 +1,97 @@ +import json +import requests +import os +from tatu.utils import random_uuid +from Crypto.PublicKey import RSA +import sshpubkeys +import uuid + +server = 'http://127.0.0.1:18321' + +def vendordata_request(instance_id, project_id, hostname): + return { + 'instance-id': instance_id, + 'project-id': project_id, + 'hostname': hostname + } + +def host_request(token, host, pub_key): + return { + 'token_id': token, + 'host_id': host, + 'key.pub': pub_key + } + +def test_host_certificate_generation(): + project_id = random_uuid() + response = requests.post( + server + '/authorities', + data=json.dumps({'auth_id': project_id}) + ) + assert response.status_code == 201 + assert 'location' in response.headers + assert response.headers['location'] == '/authorities/' + project_id + + response = requests.get(server + response.headers['location']) + assert response.status_code == 200 + auth = json.loads(response.content) + assert 'auth_id' in auth + assert auth['auth_id'] == project_id + assert 'user_key.pub' in auth + assert 'host_key.pub' in auth + ca_user = auth['user_key.pub'] + + key = RSA.generate(2048) + pub_key = key.publickey().exportKey('OpenSSH') + fingerprint = sshpubkeys.SSHKey(pub_key).hash_md5() + for i in range(100): + instance_id = random_uuid() + hostname = 'host{}'.format(i) + # Simulate Nova's separate requests for each version of metadata API + vendordata = None + token = None + for j in range(3): + response = requests.post( + server + '/novavendordata', + data=json.dumps(vendordata_request(instance_id, project_id, hostname)) + ) + assert response.status_code == 201 + assert 'location' in response.headers + location_path = response.headers['location'].split('/') + assert location_path[1] == 'hosttokens' + vendordata = json.loads(response.content) + assert 'token' in vendordata + tok = vendordata['token'] + if token is None: + token = tok + else: + assert token == tok + assert token == location_path[-1] + assert 'auth_pub_key_user' in vendordata + assert vendordata['auth_pub_key_user'] == ca_user + assert 'principals' in vendordata + assert vendordata['principals'] == 'admin' + + response = requests.post( + server + '/hostcerts', + data=json.dumps(host_request(token, instance_id, pub_key)) + ) + assert response.status_code == 201 + assert 'location' in response.headers + location = response.headers['location'] + location_path = location.split('/') + assert location_path[1] == 'hostcerts' + assert location_path[2] == instance_id + assert location_path[3] == fingerprint + + response = requests.get(server + location) + assert response.status_code == 200 + hostcert = json.loads(response.content) + assert 'host_id' in hostcert + assert hostcert['host_id'] == instance_id + assert 'fingerprint' in hostcert + assert hostcert['fingerprint'] + assert 'auth_id' in hostcert + auth_id = str(uuid.UUID(hostcert['auth_id'], version=4)) + assert auth_id == project_id + assert 'key-cert.pub' in hostcert diff --git a/tatu/tests/test_app.py b/tatu/tests/test_app.py index 1ed2692..1fdca5b 100644 --- a/tatu/tests/test_app.py +++ b/tatu/tests/test_app.py @@ -10,6 +10,7 @@ from tatu.db.models import Authority from tatu.utils import random_uuid from Crypto.PublicKey import RSA import sshpubkeys +import time @pytest.fixture def db(): @@ -25,12 +26,12 @@ token_id = '' host_id = random_uuid() host_key = RSA.generate(2048) host_pub_key = host_key.publickey().exportKey('OpenSSH') -host_fingerprint = sshpubkeys.SSHKey(host_pub_key).hash() +host_fingerprint = sshpubkeys.SSHKey(host_pub_key).hash_md5() user_id = random_uuid() user_key = RSA.generate(2048) user_pub_key = user_key.publickey().exportKey('OpenSSH') -user_fingerprint = sshpubkeys.SSHKey(user_pub_key).hash() +user_fingerprint = sshpubkeys.SSHKey(user_pub_key).hash_md5() auth_id = random_uuid() auth_user_pub_key = None @@ -47,10 +48,6 @@ def test_post_authority(client, auth_id=auth_id): assert response.status == falcon.HTTP_CREATED assert response.headers['location'] == '/authorities/' + auth_id -def test_stress_post_authority(client): - for i in range(10000): - test_post_authority(client, auth_id=random_uuid()) - @pytest.mark.dependency(depends=['test_post_authority']) def test_post_authority_duplicate(client): body = { @@ -135,7 +132,7 @@ def test_post_user(client): location = response.headers['location'].split('/') assert location[1] == 'usercerts' assert location[2] == body['user_id'] - assert location[3] == sshpubkeys.SSHKey(body['key.pub']).hash() + assert location[3] == sshpubkeys.SSHKey(body['key.pub']).hash_md5() @pytest.mark.dependency(depends=['test_post_user']) def test_get_user(client): @@ -170,7 +167,7 @@ def test_post_second_cert_same_user(client): location = response.headers['location'].split('/') assert location[1] == 'usercerts' assert location[2] == user_id - assert location[3] == sshpubkeys.SSHKey(pub_key).hash() + assert location[3] == sshpubkeys.SSHKey(pub_key).hash_md5() def test_post_user_unknown_auth(client): body = user_request(auth=random_uuid()) @@ -288,15 +285,18 @@ def test_post_token_and_host(client): assert location[2] == host_id assert location[3] == host_fingerprint -def test_stress_post_token_same_host_id(client): +def test_stress_post_token_and_host(client): my_auth_id = random_uuid() test_post_authority(client, my_auth_id) - for i in range(10000): + # Generate a single RSA key pair and reuse it - it takes a few seconds. + key = RSA.generate(2048) + pub_key = key.publickey().exportKey('OpenSSH') + fingerprint = sshpubkeys.SSHKey(pub_key).hash_md5() + # Should do about 15 iterations/second, so only do 4 seconds worth. + start = time.time() + for i in range(60): hid = random_uuid() - key = RSA.generate(2048) - pub_key = key.publickey().exportKey('OpenSSH') - fingerprint = sshpubkeys.SSHKey(key).hash() - token = token_request(auth=auth_id, host=hid) + token = token_request(auth=my_auth_id, host=hid) response = client.simulate_post( '/hosttokens', body=json.dumps(token) @@ -305,6 +305,7 @@ def test_stress_post_token_same_host_id(client): assert 'location' in response.headers location_path = response.headers['location'].split('/') assert location_path[1] == 'hosttokens' + token_id = location_path[-1] # Verify that it's a valid UUID uuid.UUID(token_id, version=4) host = host_request(token_id, host=hid, pub_key=pub_key) @@ -316,9 +317,9 @@ def test_stress_post_token_same_host_id(client): assert 'location' in response.headers location = response.headers['location'].split('/') assert location[1] == 'hostcerts' - assert location[2] == host_id + assert location[2] == hid assert location[3] == fingerprint - + assert time.time() - start < 5 @pytest.mark.dependency(depends=['test_post_token_and_host']) def test_post_token_same_host_id(client): diff --git a/tatu/utils.py b/tatu/utils.py index 6adc69b..2318633 100644 --- a/tatu/utils.py +++ b/tatu/utils.py @@ -15,7 +15,8 @@ def generateCert(auth_key, entity_key, hostname=None, principals='root'): cert_file = ''.join([dir, prefix, '-cert.pub']) cert = '' try: - os.open(ca_file, os.O_WRONLY | os.O_CREAT, 0o600) + fd = os.open(ca_file, os.O_WRONLY | os.O_CREAT, 0o600) + os.close(fd) with open(ca_file, "w") as text_file: text_file.write(auth_key) with open(pub_file, "w", 0o644) as text_file: @@ -26,7 +27,7 @@ def generateCert(auth_key, entity_key, hostname=None, principals='root'): args.extend(['-n', principals, pub_file]) else: args.extend(['-h', pub_file]) - print subprocess.check_output(args, stderr=subprocess.STDOUT) + subprocess.check_output(args, stderr=subprocess.STDOUT) # Read the contents of the certificate file cert = '' with open(cert_file, 'r') as text_file: