diff --git a/scripts/configure_ssh.py b/scripts/configure_ssh.py index 7e310da..f70e761 100644 --- a/scripts/configure_ssh.py +++ b/scripts/configure_ssh.py @@ -16,42 +16,47 @@ import os import subprocess import uuid + def getVendordataFromMetadataAPI(): - response = requests.get( - 'http://169.254.169.254/openstack/latest/vendor_data2.json', - ) - assert response.status_code == 200 - return json.loads(response.content) + response = requests.get( + 'http://169.254.169.254/openstack/latest/vendor_data2.json', + ) + assert response.status_code == 200 + return json.loads(response.content) + def getInstanceAndProjectIdFromMetadataAPI(): - response = requests.get( - 'http://169.254.169.254/openstack/latest/meta_data.json', - ) - assert response.status_code == 200 - metadata = json.loads(response.content) - assert 'uuid' in metadata - assert 'project_id' in metadata - return metadata['uuid'], metadata['project_id'] + response = requests.get( + 'http://169.254.169.254/openstack/latest/meta_data.json', + ) + assert response.status_code == 200 + metadata = json.loads(response.content) + assert 'uuid' in metadata + assert 'project_id' in metadata + return metadata['uuid'], metadata['project_id'] + def getVendordataFromConfigDrive(): - path = '/mnt/config/openstack/latest/vendor_data2.json' - with open(path, 'r') as f: - json_string = f.read() - return json.loads(json_string) + path = '/mnt/config/openstack/latest/vendor_data2.json' + with open(path, 'r') as f: + json_string = f.read() + return json.loads(json_string) + def getInstanceAndProjectIdFromConfigDrive(): - path = '/mnt/config/openstack/latest/meta_data.json' - with open(path, 'r') as f: - json_string = f.read() - metadata = json.loads(json_string) - assert 'uuid' in metadata - assert 'project_id' in metadata - return str(uuid.UUID(metadata['uuid'], version=4)), str(uuid.UUID(metadata['project_id'], version=4)) + path = '/mnt/config/openstack/latest/meta_data.json' + with open(path, 'r') as f: + json_string = f.read() + metadata = json.loads(json_string) + assert 'uuid' in metadata + assert 'project_id' in metadata + return str(uuid.UUID(metadata['uuid'], version=4)), str(uuid.UUID(metadata['project_id'], version=4)) + vendordata = getVendordataFromConfigDrive() -#vendordata = getVendordataFromMetadataAPI() +# vendordata = getVendordataFromMetadataAPI() instance_id, project_id = getInstanceAndProjectIdFromConfigDrive() -#instance_id, project_id = getInstanceIdFromMetadataAPI() +# instance_id, project_id = getInstanceIdFromMetadataAPI() assert 'tatu' in vendordata tatu = vendordata['tatu'] @@ -61,25 +66,25 @@ assert 'principals' in tatu principals = tatu['principals'].split(',') with open('/etc/ssh/ssh_host_rsa_key.pub', 'r') as f: - host_key_pub = f.read() + host_key_pub = f.read() server = 'http://172.24.4.1:18321' hostcert_request = { - 'token_id': tatu['token'], - 'host_id': instance_id, - 'key.pub': host_key_pub + 'token_id': tatu['token'], + 'host_id': instance_id, + 'key.pub': host_key_pub } response = requests.post( - # Hard-coded SSHaaS API address will only work for devstack and requires - # routing and SNAT or DNAT. - # This eventually needs to be either: - # 1) 169.254.169.254 if there's a SSHaaS-proxy; OR - # 2) the real address of the API, possibly supplied in the vendordata and - # still requiring routing and SNAT or DNAT. - server + '/hostcerts', - data=json.dumps(hostcert_request) + # Hard-coded SSHaaS API address will only work for devstack and requires + # routing and SNAT or DNAT. + # This eventually needs to be either: + # 1) 169.254.169.254 if there's a SSHaaS-proxy; OR + # 2) the real address of the API, possibly supplied in the vendordata and + # still requiring routing and SNAT or DNAT. + server + '/hostcerts', + data=json.dumps(hostcert_request) ) assert response.status_code == 201 assert 'location' in response.headers @@ -98,19 +103,19 @@ assert 'key-cert.pub' in hostcert # Write the host's certificate with open('/etc/ssh/ssh_host_rsa_key-cert.pub', 'w') as f: - f.write(hostcert['key-cert.pub']) + f.write(hostcert['key-cert.pub']) # Write the authorized principals file os.mkdir('/etc/ssh/auth_principals') with open('/etc/ssh/auth_principals/ubuntu', 'w') as f: - for p in principals: - f.write(p + os.linesep) + for p in principals: + f.write(p + os.linesep) # Write the User CA public key file with open('/etc/ssh/ca_user.pub', 'w') as f: - f.write(tatu['auth_pub_key_user']) + f.write(tatu['auth_pub_key_user']) subprocess.check_output("sed -i -e '$aTrustedUserCAKeys /etc/ssh/ca_user.pub' /etc/ssh/sshd_config") subprocess.check_output("sed -i -e '$aAuthorizedPrincipalsFile /etc/ssh/auth_principals/%u' /etc/ssh/sshd_config") subprocess.check_output("sed -i -e '$aHostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub' /etc/ssh/sshd_config") -subprocess.check_output("systemctl restart ssh") \ No newline at end of file +subprocess.check_output("systemctl restart ssh") diff --git a/tatu/api/app.py b/tatu/api/app.py index c107a29..1181272 100644 --- a/tatu/api/app.py +++ b/tatu/api/app.py @@ -11,9 +11,10 @@ # under the License. import falcon -import models import os.path from oslo_config import cfg + +import models from tatu.castellano import validate_config as validate_castellan_config from tatu.db.persistence import SQLAlchemySessionManager diff --git a/tatu/api/models.py b/tatu/api/models.py index 2fc8254..978a02f 100644 --- a/tatu/api/models.py +++ b/tatu/api/models.py @@ -14,9 +14,10 @@ import falcon import json import logging import uuid -from tatu.db import models as db from Crypto.PublicKey import RSA +from tatu.db import models as db + def validate_uuid(map, key): try: @@ -29,7 +30,8 @@ def validate_uuid(map, key): 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'): for key in id_keys: if key in req.body: @@ -53,7 +55,10 @@ class Logger(object): self.logger = logging.getLogger(__name__) def process_resource(self, req, resp, resource, params): - self.logger.debug('Received request {0} {1} with headers {2}'.format(req.method, req.relative_uri, req.headers)) + self.logger.debug( + 'Received request {0} {1} with headers {2}'.format(req.method, + req.relative_uri, + req.headers)) def process_response(self, req, resp, resource, params): self.logger.debug( diff --git a/tatu/db/models.py b/tatu/db/models.py index 7ee8ef0..89be1f2 100644 --- a/tatu/db/models.py +++ b/tatu/db/models.py @@ -10,17 +10,18 @@ # License for the specific language governing permissions and limitations # under the License. -from datetime import datetime import falcon import os import sqlalchemy as sa -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.exc import IntegrityError import sshpubkeys -from tatu.castellano import get_secret, store_secret -from tatu.utils import generateCert, random_uuid import uuid from Crypto.PublicKey import RSA +from datetime import datetime +from sqlalchemy.exc import IntegrityError +from sqlalchemy.ext.declarative import declarative_base + +from tatu.castellano import get_secret, store_secret +from tatu.utils import generateCert, random_uuid Base = declarative_base() @@ -78,14 +79,17 @@ def createUserCert(session, user_id, auth_id, pub): # Retrieve the authority's private key and generate the certificate auth = getAuthority(session, auth_id) if auth is None: - raise falcon.HTTPNotFound(description='No Authority found with that ID') + raise falcon.HTTPNotFound( + description='No Authority found with that ID') fingerprint = sshpubkeys.SSHKey(pub).hash_md5() certRecord = session.query(UserCert).get([user_id, fingerprint]) if certRecord is not None: return certRecord - cert = generateCert(get_secret(auth.user_key), pub, principals='admin,root') + cert = generateCert(get_secret(auth.user_key), pub, + principals='admin,root') if cert is None: - raise falcon.HTTPInternalServerError("Failed to generate the certificate") + raise falcon.HTTPInternalServerError( + "Failed to generate the certificate") user = UserCert( user_id=user_id, fingerprint=fingerprint, @@ -114,7 +118,8 @@ def createToken(session, host_id, auth_id, hostname): # Validate the certificate authority auth = getAuthority(session, auth_id) if auth is None: - raise falcon.HTTPNotFound(description='No Authority found with that ID') + raise falcon.HTTPNotFound( + description='No Authority found with that ID') # Check whether a token was already created for this host_id try: token = session.query(Token).filter(Token.host_id == host_id).one() @@ -152,12 +157,14 @@ def createHostCert(session, token_id, host_id, pub): if token is None: 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') + raise falcon.HTTPConflict( + description='The token is not valid for this instance ID') fingerprint = sshpubkeys.SSHKey(pub).hash_md5() if token.used: if token.fingerprint_used != fingerprint: - raise falcon.HTTPConflict(description='The token was previously used with a different public key') + raise falcon.HTTPConflict( + description='The token was previously used with a different public key') # The token was already used for same host and pub key. Return record. host = session.query(HostCert).get([host_id, fingerprint]) if host is None: @@ -165,17 +172,21 @@ def createHostCert(session, token_id, host_id, pub): description='The token was used, but no corresponding Host record was found.') if host.token_id == token_id: return host - raise falcon.HTTPConflict(description='The presented token was previously used') + raise falcon.HTTPConflict( + description='The presented token was previously used') auth = getAuthority(session, token.auth_id) if auth is None: - raise falcon.HTTPNotFound(description='No Authority found with that ID') + raise falcon.HTTPNotFound( + description='No Authority found with that ID') certRecord = session.query(HostCert).get([host_id, fingerprint]) if certRecord is not None: raise falcon.HTTPConflict('This public key is already signed.') - cert = generateCert(get_secret(auth.host_key), pub, hostname=token.hostname) + cert = generateCert(get_secret(auth.host_key), pub, + hostname=token.hostname) if cert == '': - raise falcon.HTTPInternalServerError("Failed to generate the certificate") + raise falcon.HTTPInternalServerError( + "Failed to generate the certificate") host = HostCert(host_id=host_id, fingerprint=fingerprint, auth_id=token.auth_id, diff --git a/tatu/db/persistence.py b/tatu/db/persistence.py index b9be131..a7b330a 100644 --- a/tatu/db/persistence.py +++ b/tatu/db/persistence.py @@ -11,9 +11,9 @@ # under the License. import os - from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, scoped_session + from tatu.db.models import Base diff --git a/tatu/ftests/test_api.py b/tatu/ftests/test_api.py index fca6870..d5184bd 100644 --- a/tatu/ftests/test_api.py +++ b/tatu/ftests/test_api.py @@ -11,12 +11,13 @@ # under the License. import json -import requests import os -from tatu.utils import random_uuid -from Crypto.PublicKey import RSA +import requests import sshpubkeys import uuid +from Crypto.PublicKey import RSA + +from tatu.utils import random_uuid server = 'http://172.24.4.1:18322' @@ -68,7 +69,8 @@ def test_host_certificate_generation(): for j in range(3): response = requests.post( server + '/novavendordata', - data=json.dumps(vendordata_request(instance_id, project_id, hostname)) + data=json.dumps( + vendordata_request(instance_id, project_id, hostname)) ) assert response.status_code == 201 assert 'location' in response.headers diff --git a/tatu/notifications.py b/tatu/notifications.py index 2a757f9..6710aef 100644 --- a/tatu/notifications.py +++ b/tatu/notifications.py @@ -10,17 +10,18 @@ # License for the specific language governing permissions and limitations # under the License. +import oslo_messaging +import sys +import time +import uuid from oslo_config import cfg from oslo_log import log as logging -import oslo_messaging from oslo_serialization import jsonutils from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, scoped_session -import sys + from tatu.db.models import Base, createAuthority from tatu.db.persistence import get_url -import time -import uuid LOG = logging.getLogger(__name__) CONF = cfg.CONF @@ -52,7 +53,9 @@ class NotificationEndpoint(object): auth_id = str(uuid.UUID(proj_id, version=4)) createAuthority(se, auth_id) except Exception as e: - LOG.error("Failed to create Tatu CA for new project with ID {} due to exception {}".format(proj_id, e)) + LOG.error( + "Failed to create Tatu CA for new project with ID {} due to exception {}".format( + proj_id, e)) se.rollback() self.Session.remove() else: diff --git a/tatu/tests/test_app.py b/tatu/tests/test_app.py index 1763175..83e52e9 100644 --- a/tatu/tests/test_app.py +++ b/tatu/tests/test_app.py @@ -9,18 +9,19 @@ # 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 falcon -from falcon import testing +import json import pytest -import uuid -from tatu.api.app import create_app -from tatu.db.persistence import SQLAlchemySessionManager -from tatu.db.models import Authority -from tatu.utils import random_uuid -from Crypto.PublicKey import RSA import sshpubkeys import time +import uuid +from Crypto.PublicKey import RSA +from falcon import testing + +from tatu.api.app import create_app +from tatu.db.models import Authority +from tatu.db.persistence import SQLAlchemySessionManager +from tatu.utils import random_uuid @pytest.fixture @@ -161,7 +162,8 @@ def test_post_user(client): @pytest.mark.dependency(depends=['test_post_user']) def test_get_user(client): - response = client.simulate_get('/usercerts/' + user_id + '/' + user_fingerprint) + response = client.simulate_get( + '/usercerts/' + user_id + '/' + user_fingerprint) assert response.status == falcon.HTTP_OK body = json.loads(response.content) assert 'user_id' in body @@ -172,7 +174,8 @@ def test_get_user(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 @@ -384,7 +387,8 @@ def test_post_token_same_host_id(client): @pytest.mark.dependency(depends=['test_post_token_and_host']) def test_get_host(client): - response = client.simulate_get('/hostcerts/' + host_id + '/' + host_fingerprint) + response = client.simulate_get( + '/hostcerts/' + host_id + '/' + host_fingerprint) assert response.status == falcon.HTTP_OK body = json.loads(response.content) assert 'host_id' in body @@ -397,7 +401,8 @@ def test_get_host(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