79 character length

This commit is contained in:
Pino de Candia 2017-12-08 15:11:14 -06:00
parent 7812e1e8b6
commit 3cee92f37f
8 changed files with 116 additions and 84 deletions

View File

@ -16,42 +16,47 @@ import os
import subprocess import subprocess
import uuid import uuid
def getVendordataFromMetadataAPI(): def getVendordataFromMetadataAPI():
response = requests.get( response = requests.get(
'http://169.254.169.254/openstack/latest/vendor_data2.json', 'http://169.254.169.254/openstack/latest/vendor_data2.json',
) )
assert response.status_code == 200 assert response.status_code == 200
return json.loads(response.content) return json.loads(response.content)
def getInstanceAndProjectIdFromMetadataAPI(): def getInstanceAndProjectIdFromMetadataAPI():
response = requests.get( response = requests.get(
'http://169.254.169.254/openstack/latest/meta_data.json', 'http://169.254.169.254/openstack/latest/meta_data.json',
) )
assert response.status_code == 200 assert response.status_code == 200
metadata = json.loads(response.content) metadata = json.loads(response.content)
assert 'uuid' in metadata assert 'uuid' in metadata
assert 'project_id' in metadata assert 'project_id' in metadata
return metadata['uuid'], metadata['project_id'] return metadata['uuid'], metadata['project_id']
def getVendordataFromConfigDrive(): def getVendordataFromConfigDrive():
path = '/mnt/config/openstack/latest/vendor_data2.json' path = '/mnt/config/openstack/latest/vendor_data2.json'
with open(path, 'r') as f: with open(path, 'r') as f:
json_string = f.read() json_string = f.read()
return json.loads(json_string) return json.loads(json_string)
def getInstanceAndProjectIdFromConfigDrive(): def getInstanceAndProjectIdFromConfigDrive():
path = '/mnt/config/openstack/latest/meta_data.json' path = '/mnt/config/openstack/latest/meta_data.json'
with open(path, 'r') as f: with open(path, 'r') as f:
json_string = f.read() json_string = f.read()
metadata = json.loads(json_string) metadata = json.loads(json_string)
assert 'uuid' in metadata assert 'uuid' in metadata
assert 'project_id' in metadata assert 'project_id' in metadata
return str(uuid.UUID(metadata['uuid'], version=4)), str(uuid.UUID(metadata['project_id'], version=4)) return str(uuid.UUID(metadata['uuid'], version=4)), str(uuid.UUID(metadata['project_id'], version=4))
vendordata = getVendordataFromConfigDrive() vendordata = getVendordataFromConfigDrive()
#vendordata = getVendordataFromMetadataAPI() # vendordata = getVendordataFromMetadataAPI()
instance_id, project_id = getInstanceAndProjectIdFromConfigDrive() instance_id, project_id = getInstanceAndProjectIdFromConfigDrive()
#instance_id, project_id = getInstanceIdFromMetadataAPI() # instance_id, project_id = getInstanceIdFromMetadataAPI()
assert 'tatu' in vendordata assert 'tatu' in vendordata
tatu = vendordata['tatu'] tatu = vendordata['tatu']
@ -61,25 +66,25 @@ assert 'principals' in tatu
principals = tatu['principals'].split(',') principals = tatu['principals'].split(',')
with open('/etc/ssh/ssh_host_rsa_key.pub', 'r') as f: 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' server = 'http://172.24.4.1:18321'
hostcert_request = { hostcert_request = {
'token_id': tatu['token'], 'token_id': tatu['token'],
'host_id': instance_id, 'host_id': instance_id,
'key.pub': host_key_pub 'key.pub': host_key_pub
} }
response = requests.post( response = requests.post(
# Hard-coded SSHaaS API address will only work for devstack and requires # Hard-coded SSHaaS API address will only work for devstack and requires
# routing and SNAT or DNAT. # routing and SNAT or DNAT.
# This eventually needs to be either: # This eventually needs to be either:
# 1) 169.254.169.254 if there's a SSHaaS-proxy; OR # 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 # 2) the real address of the API, possibly supplied in the vendordata and
# still requiring routing and SNAT or DNAT. # still requiring routing and SNAT or DNAT.
server + '/hostcerts', server + '/hostcerts',
data=json.dumps(hostcert_request) data=json.dumps(hostcert_request)
) )
assert response.status_code == 201 assert response.status_code == 201
assert 'location' in response.headers assert 'location' in response.headers
@ -98,19 +103,19 @@ assert 'key-cert.pub' in hostcert
# Write the host's certificate # Write the host's certificate
with open('/etc/ssh/ssh_host_rsa_key-cert.pub', 'w') as f: 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 # Write the authorized principals file
os.mkdir('/etc/ssh/auth_principals') os.mkdir('/etc/ssh/auth_principals')
with open('/etc/ssh/auth_principals/ubuntu', 'w') as f: with open('/etc/ssh/auth_principals/ubuntu', 'w') as f:
for p in principals: for p in principals:
f.write(p + os.linesep) f.write(p + os.linesep)
# Write the User CA public key file # Write the User CA public key file
with open('/etc/ssh/ca_user.pub', 'w') as f: 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 '$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 '$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("sed -i -e '$aHostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub' /etc/ssh/sshd_config")
subprocess.check_output("systemctl restart ssh") subprocess.check_output("systemctl restart ssh")

View File

@ -11,9 +11,10 @@
# under the License. # under the License.
import falcon import falcon
import models
import os.path import os.path
from oslo_config import cfg from oslo_config import cfg
import models
from tatu.castellano import validate_config as validate_castellan_config from tatu.castellano import validate_config as validate_castellan_config
from tatu.db.persistence import SQLAlchemySessionManager from tatu.db.persistence import SQLAlchemySessionManager

View File

@ -14,9 +14,10 @@ import falcon
import json import json
import logging import logging
import uuid import uuid
from tatu.db import models as db
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from tatu.db import models as db
def validate_uuid(map, key): def validate_uuid(map, key):
try: try:
@ -29,7 +30,8 @@ def validate_uuid(map, key):
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'):
for key in id_keys: for key in id_keys:
if key in req.body: if key in req.body:
@ -53,7 +55,10 @@ class Logger(object):
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
def process_resource(self, req, resp, resource, params): 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): def process_response(self, req, resp, resource, params):
self.logger.debug( self.logger.debug(

View File

@ -10,17 +10,18 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from datetime import datetime
import falcon import falcon
import os import os
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.exc import IntegrityError
import sshpubkeys import sshpubkeys
from tatu.castellano import get_secret, store_secret
from tatu.utils import generateCert, random_uuid
import uuid import uuid
from Crypto.PublicKey import RSA 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() 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 # Retrieve the authority's private key and generate the certificate
auth = getAuthority(session, auth_id) auth = getAuthority(session, auth_id)
if auth is None: 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() fingerprint = sshpubkeys.SSHKey(pub).hash_md5()
certRecord = session.query(UserCert).get([user_id, fingerprint]) certRecord = session.query(UserCert).get([user_id, fingerprint])
if certRecord is not None: if certRecord is not None:
return certRecord 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: if cert is None:
raise falcon.HTTPInternalServerError("Failed to generate the certificate") raise falcon.HTTPInternalServerError(
"Failed to generate the certificate")
user = UserCert( user = UserCert(
user_id=user_id, user_id=user_id,
fingerprint=fingerprint, fingerprint=fingerprint,
@ -114,7 +118,8 @@ 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)
if auth is None: 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 # Check whether a token was already created for this host_id
try: try:
token = session.query(Token).filter(Token.host_id == host_id).one() 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: if token is None:
raise falcon.HTTPNotFound(description='No Token found with that ID') raise falcon.HTTPNotFound(description='No Token found with that ID')
if token.host_id != host_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() fingerprint = sshpubkeys.SSHKey(pub).hash_md5()
if token.used: if token.used:
if token.fingerprint_used != fingerprint: 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. # The token was already used for same host and pub key. Return record.
host = session.query(HostCert).get([host_id, fingerprint]) host = session.query(HostCert).get([host_id, fingerprint])
if host is None: 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.') description='The token was used, but no corresponding Host record was found.')
if host.token_id == token_id: if host.token_id == token_id:
return host 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) auth = getAuthority(session, token.auth_id)
if auth is None: 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]) certRecord = session.query(HostCert).get([host_id, fingerprint])
if certRecord is not None: if certRecord is not None:
raise falcon.HTTPConflict('This public key is already signed.') 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 == '': if cert == '':
raise falcon.HTTPInternalServerError("Failed to generate the certificate") raise falcon.HTTPInternalServerError(
"Failed to generate the certificate")
host = HostCert(host_id=host_id, host = HostCert(host_id=host_id,
fingerprint=fingerprint, fingerprint=fingerprint,
auth_id=token.auth_id, auth_id=token.auth_id,

View File

@ -11,9 +11,9 @@
# under the License. # under the License.
import os import os
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session from sqlalchemy.orm import sessionmaker, scoped_session
from tatu.db.models import Base from tatu.db.models import Base

View File

@ -11,12 +11,13 @@
# under the License. # under the License.
import json import json
import requests
import os import os
from tatu.utils import random_uuid import requests
from Crypto.PublicKey import RSA
import sshpubkeys import sshpubkeys
import uuid import uuid
from Crypto.PublicKey import RSA
from tatu.utils import random_uuid
server = 'http://172.24.4.1:18322' server = 'http://172.24.4.1:18322'
@ -68,7 +69,8 @@ def test_host_certificate_generation():
for j in range(3): for j in range(3):
response = requests.post( response = requests.post(
server + '/novavendordata', 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 response.status_code == 201
assert 'location' in response.headers assert 'location' in response.headers

View File

@ -10,17 +10,18 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import oslo_messaging
import sys
import time
import uuid
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
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session from sqlalchemy.orm import sessionmaker, scoped_session
import sys
from tatu.db.models import Base, createAuthority from tatu.db.models import Base, createAuthority
from tatu.db.persistence import get_url from tatu.db.persistence import get_url
import time
import uuid
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
CONF = cfg.CONF CONF = cfg.CONF
@ -52,7 +53,9 @@ class NotificationEndpoint(object):
auth_id = str(uuid.UUID(proj_id, version=4)) auth_id = str(uuid.UUID(proj_id, version=4))
createAuthority(se, auth_id) createAuthority(se, auth_id)
except Exception as e: 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() se.rollback()
self.Session.remove() self.Session.remove()
else: else:

View File

@ -9,18 +9,19 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json
import falcon import falcon
from falcon import testing import json
import pytest 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 sshpubkeys
import time 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 @pytest.fixture
@ -161,7 +162,8 @@ def test_post_user(client):
@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)
assert response.status == falcon.HTTP_OK assert response.status == falcon.HTTP_OK
body = json.loads(response.content) body = json.loads(response.content)
assert 'user_id' in body assert 'user_id' in body
@ -172,7 +174,8 @@ def test_get_user(client):
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
@ -384,7 +387,8 @@ def test_post_token_same_host_id(client):
@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)
assert response.status == falcon.HTTP_OK assert response.status == falcon.HTTP_OK
body = json.loads(response.content) body = json.loads(response.content)
assert 'host_id' in body assert 'host_id' in body
@ -397,7 +401,8 @@ def test_get_host(client):
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