From d6f4f557ac39caa86d1599c871180fedf2e3115b Mon Sep 17 00:00:00 2001 From: Pino de Candia <32303022+pinodeca@users.noreply.github.com> Date: Tue, 21 Nov 2017 01:31:36 -0600 Subject: [PATCH] Debugged cloud-init script; added script to get user cert. --- files/user-cloud-config | 86 +++++++++++++++++++++++++++++++++++++--- scripts/configure_ssh.py | 80 +++++++++++++++++++------------------ scripts/get_user_cert.py | 59 +++++++++++++++++++++++++++ tatu/db/models.py | 11 ++--- tatu/tests/test_app.py | 4 +- tatu/utils.py | 7 +++- 6 files changed, 192 insertions(+), 55 deletions(-) create mode 100644 scripts/get_user_cert.py diff --git a/files/user-cloud-config b/files/user-cloud-config index d1ef28f..7f7c5d4 100644 --- a/files/user-cloud-config +++ b/files/user-cloud-config @@ -1,11 +1,87 @@ #cloud-config +mounts: + - [ /dev/disk/by-label/config-2, /mnt/config ] +packages: + - python + - python-requests write_files: - - path: /etc/ssh/auth_principals/ubuntu - content: webRoot - - path: /etc/ssh/ca_users.pub - content: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDM+YVCEZ4xCqBIGOQOEsGzBzOFS3JNDtPxLAviBMtS4zCwuGmOMvAvatKtPY5E9JMnkhI72faJnwYc4w/pnXf4Sh6AnLfwcOoQ6U16iucfY8tPOeFQhKJokSRdwnfm08QMOHN0xzCA/tL6HHZgPXGHUgTL18kkjv5Zk5Nv1H/ciuOSz24edo94Fu9eIQkK1pUhdejC6hDKdbki/c/3coZU4ZNDdtIpRlGnrUNTaAIq+E0TYEZkgClglTlBQOTvUoRkxEng/U23dfBCCz5DfewfA+6higUil5lIvidbaFjUiTMox38w9fM0wzUUs3o5pC9X/H3BE4mBrfpS9VmYHgll root@Bamboo + - path: /root/setup-ssh.py + permissions: '0700' + owner: root:root + content: | + import json + import requests + import os + import subprocess + import uuid + 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) + 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)) + vendordata = getVendordataFromConfigDrive() + instance_id, project_id = getInstanceAndProjectIdFromConfigDrive() + assert 'tatu' in vendordata + tatu = vendordata['tatu'] + assert 'token' in tatu + assert 'auth_pub_key_user' in tatu + 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() + server = 'http://172.24.4.1:18321' + hostcert_request = { + 'token_id': tatu['token'], + 'host_id': instance_id, + 'key.pub': host_key_pub + } + print 'Request the host certificate.' + 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) + ) + assert response.status_code == 201 + assert 'location' in response.headers + location = response.headers['location'] + response = requests.get(server + location) + hostcert = json.loads(response.content) + assert 'host_id' in hostcert + assert hostcert['host_id'] == instance_id + assert 'fingerprint' in hostcert + 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 + print 'Begin writing files.' + # Write the host's certificate + with open('/etc/ssh/ssh_host_rsa_key-cert.pub', 'w') as f: + 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) + # Write the User CA public key file + with open('/etc/ssh/ca_user.pub', 'w') as f: + f.write(tatu['auth_pub_key_user']) + print 'All tasks completed.' runcmd: + - python /root/setup-ssh.py > /var/log/setup-ssh.log 2>&1 - sed -i -e '$aTrustedUserCAKeys /etc/ssh/ca_user.pub' /etc/ssh/sshd_config - sed -i -e '$aAuthorizedPrincipalsFile /etc/ssh/auth_principals/%u' /etc/ssh/sshd_config - + - sed -i -e '$aHostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub' /etc/ssh/sshd_config - systemctl restart ssh diff --git a/scripts/configure_ssh.py b/scripts/configure_ssh.py index 8b3d082..53d8fdc 100644 --- a/scripts/configure_ssh.py +++ b/scripts/configure_ssh.py @@ -2,20 +2,15 @@ import json import requests import os import subprocess +import uuid def getVendordataFromMetadataAPI(): response = requests.get( - 'http://169.254.169.254/openstack/2016-10-06/vendor_data2.json', + 'http://169.254.169.254/openstack/latest/vendor_data2.json', ) assert response.status_code == 200 return json.loads(response.content) -def getVendordataFromConfigDrive(): - path = '/mnt/openstack/2016-10-06/vendor_data2.json' - with open(path, 'r') as f: - json_string = f.read() - return json.loads(json_string) - def getInstanceAndProjectIdFromMetadataAPI(): response = requests.get( 'http://169.254.169.254/openstack/latest/meta_data.json', @@ -26,35 +21,44 @@ def getInstanceAndProjectIdFromMetadataAPI(): 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) + def getInstanceAndProjectIdFromConfigDrive(): - path = '/mnt/openstack/latest/meta_data.json' + 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 metadata['uuid'], metadata['project_id'] + return str(uuid.UUID(metadata['uuid'], version=4)), str(uuid.UUID(metadata['project_id'], version=4)) -#vendordata = getVendordataFromConfigDrive() -vendordata = getVendordataFromMetadataAPI() -#instance_id = getInstanceIdFromConfigDrive() -instance_id, project_id = getInstanceIdFromMetadataAPI() +vendordata = getVendordataFromConfigDrive() +#vendordata = getVendordataFromMetadataAPI() +instance_id, project_id = getInstanceAndProjectIdFromConfigDrive() +#instance_id, project_id = getInstanceIdFromMetadataAPI() -assert 'sshaas' in vendordata -sshaas = vendordata['sshaas'] -assert 'token' in sshaas -assert 'auth_pub_key_user' in sshaas -assert 'principals' in sshaas -principals = sshaas['principals'].split(',') +assert 'tatu' in vendordata +tatu = vendordata['tatu'] +assert 'token' in tatu +assert 'auth_pub_key_user' in tatu +assert 'principals' in tatu +principals = tatu['principals'].split(',') -with open('~/.ssh/id_rsa.pub', 'r') as f: +with open('/etc/ssh/ssh_host_rsa_key.pub', 'r') as f: host_key_pub = f.read() +server = 'http://172.24.4.1:18321' + hostcert_request = { - 'token_id': sshaas['token'], + '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. @@ -62,27 +66,27 @@ response = requests.post( # 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. - 'http://localhost:8000/hostcerts', + server + '/hostcerts', data=json.dumps(hostcert_request) ) assert response.status_code == 201 assert 'location' in response.headers location = response.headers['location'] +print location -response = requests.get( - 'http://169.254.169.254' + location -) +response = requests.get(server + location) hostcert = json.loads(response.content) -assert 'host_id' in metadata -assert metadata['host_id'] == instance_id -assert 'fingerprint' in metadata -assert 'auth_id' in metadata -assert metadata['auth_id'] == project_id -assert 'key-cert.pub' in metadata +assert 'host_id' in hostcert +assert hostcert['host_id'] == instance_id +assert 'fingerprint' in hostcert +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 # Write the host's certificate with open('/etc/ssh/ssh_host_rsa_key-cert.pub', 'w') as f: - f.write(metadata['key-cert.pub']) + f.write(hostcert['key-cert.pub']) # Write the authorized principals file os.mkdir('/etc/ssh/auth_principals') @@ -90,11 +94,11 @@ with open('/etc/ssh/auth_principals/ubuntu', 'w') as f: for p in principals: f.write(p + os.linesep) -# Write the UserCA public key file -with open('/etc/ssh/user_ca.pub', 'w') as f: - f.write(sshaas['auth_pub_key_user']) +# Write the User CA public key file +with open('/etc/ssh/ca_user.pub', 'w') as f: + f.write(tatu['auth_pub_key_user']) -subprocess.check_output("sed -i -e '$aTrustedUserCAKeys /etc/ssh/user_ca.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("set -i -e '$aHostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub' /etc/ssh/sshd_config") -subprocess.check_output("systemctl restart ssh") +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 diff --git a/scripts/get_user_cert.py b/scripts/get_user_cert.py new file mode 100644 index 0000000..828a471 --- /dev/null +++ b/scripts/get_user_cert.py @@ -0,0 +1,59 @@ +import json +import requests +import os +import subprocess +import uuid +from Crypto.PublicKey import RSA + +keyfile = '/opt/stack/.ssh/mykey' +user_id = str(uuid.uuid4()) +auth_id = str(uuid.UUID('0852c6cd6209425c88de582acbcd1170', version=4)) +key = RSA.generate(2048) +keytxt = key.exportKey('PEM') +pubkeytxt = key.publickey().exportKey('OpenSSH') +server = 'http://127.0.0.1:18321' + +with open('/etc/ssh/ssh_host_rsa_key.pub', 'r') as f: + host_key_pub = f.read() + + +user = { + 'user_id': user_id, + 'auth_id': auth_id, + 'key.pub': pubkeytxt +} + +response = requests.post( + server + '/usercerts', + data=json.dumps(user) +) +assert response.status_code == 201 +assert 'location' in response.headers +location = response.headers['location'] +print location + +response = requests.get(server + location) +usercert = json.loads(response.content) +assert 'user_id' in usercert +assert usercert['user_id'] == user_id +assert 'fingerprint' in usercert +assert 'auth_id' in usercert +au = str(uuid.UUID(usercert['auth_id'], version=4)) +assert au == auth_id +assert 'key-cert.pub' in usercert + +# Write the user's ID +with open(keyfile + '_user_id', 'w') as f: + f.write(user_id) + +# Write the user private key +with open(keyfile, 'w') as f: + f.write(keytxt) + +# Write the user public key +with open(keyfile + '.pub', 'w') as f: + f.write(pubkeytxt) + +# Write the user certificate +with open(keyfile + '-cert.pub', 'w') as f: + f.write(usercert['key-cert.pub']) diff --git a/tatu/db/models.py b/tatu/db/models.py index 27f2053..560f50e 100644 --- a/tatu/db/models.py +++ b/tatu/db/models.py @@ -6,14 +6,11 @@ import falcon import sshpubkeys import uuid import os -from tatu.utils import generateCert +from tatu.utils import generateCert,random_uuid from Crypto.PublicKey import RSA Base = declarative_base() -def generate_uuid(): - return str(uuid.uuid4()) - class Authority(Base): __tablename__ = 'authorities' @@ -55,7 +52,7 @@ def createUserCert(session, user_id, auth_id, pub): certRecord = session.query(UserCert).get([user_id, fingerprint]) if certRecord is not None: raise falcon.HTTPConflict('This public key is already signed.') - cert = generateCert(auth.user_key, pub) + cert = generateCert(auth.user_key, pub, principals='admin,root') if cert is None: raise falcon.HTTPInternalServerError("Failed to generate the certificate") user = UserCert( @@ -72,7 +69,7 @@ class Token(Base): __tablename__ = 'tokens' token_id = sa.Column(sa.String(36), primary_key=True, - default=generate_uuid) + default=random_uuid) auth_id = sa.Column(sa.String(36), sa.ForeignKey('authorities.auth_id')) host_id = sa.Column(sa.String(36), index=True, unique=True) hostname = sa.Column(sa.String(36)) @@ -140,7 +137,7 @@ def createHostCert(session, token_id, host_id, pub): certRecord = session.query(HostCert).get([host_id, fingerprint]) if certRecord is not None: raise falcon.HTTPConflict('This public key is already signed.') - cert = generateCert(auth.host_key, pub, token.hostname) + cert = generateCert(auth.host_key, pub, hostname=token.hostname) if cert == '': raise falcon.HTTPInternalServerError("Failed to generate the certificate") host = HostCert(host_id=host_id, diff --git a/tatu/tests/test_app.py b/tatu/tests/test_app.py index 3127f36..58be9ae 100644 --- a/tatu/tests/test_app.py +++ b/tatu/tests/test_app.py @@ -7,6 +7,7 @@ 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 @@ -19,9 +20,6 @@ def client(db): api = create_app(db) return testing.TestClient(api) -def random_uuid(): - return str(uuid.uuid4()) - token_id = '' host_id = random_uuid() diff --git a/tatu/utils.py b/tatu/utils.py index 41a2e8e..45c4f51 100644 --- a/tatu/utils.py +++ b/tatu/utils.py @@ -2,7 +2,10 @@ import os import subprocess import uuid -def generateCert(auth_key, entity_key, hostname=None): +def random_uuid(): + return str(uuid.uuid4()) + +def generateCert(auth_key, entity_key, hostname=None, principals='root'): # Temporarily write the authority private key and entity public key to files prefix = uuid.uuid4().hex # Todo: make the temporary directory configurable or secure it. @@ -20,7 +23,7 @@ def generateCert(auth_key, entity_key, hostname=None): args = ['ssh-keygen', '-P "pinot"', '-s', ca_file, '-I testID', '-V', '-1d:+365d', '-n'] if hostname is None: - args.extend(['"myRoot,yourRoot"', pub_file]) + args.extend(['"' + principals + '"', pub_file]) else: args.extend([hostname, '-h', pub_file]) print subprocess.check_output(args, stderr=subprocess.STDOUT)