From 857d4dd028990e8a4589c45cd4b69027f3ac2f45 Mon Sep 17 00:00:00 2001 From: Pino de Candia Date: Mon, 27 Nov 2017 21:50:50 +0000 Subject: [PATCH] Provide ssh setup script via static vendor data. --- files/static-vendor-data.json | 1 + files/user-cloud-config | 9 +++++++-- scripts/cloud-config-to-vendor-data | 4 +--- scripts/vendor-data-to-cloud-config | 3 --- tatu/api/models.py | 17 ++++++++++------- tatu/ftests/test_api.py | 4 ++-- tatu/tests/test_app.py | 12 ++++++++++++ 7 files changed, 33 insertions(+), 17 deletions(-) create mode 100644 files/static-vendor-data.json diff --git a/files/static-vendor-data.json b/files/static-vendor-data.json new file mode 100644 index 0000000..3e2879f --- /dev/null +++ b/files/static-vendor-data.json @@ -0,0 +1 @@ +{"cloud-init": "#cloud-config\nmounts:\n - [ /dev/disk/by-label/config-2, /mnt/config ]\npackages:\n - python\n - python-requests\nwrite_files:\n - path: /root/setup-ssh.py\n permissions: '0700'\n owner: root:root\n content: |\n print 'Importing packages'\n import json\n import requests\n import os\n import subprocess\n import uuid\n def getVendordataFromConfigDrive():\n path = '/mnt/config/openstack/latest/vendor_data2.json'\n with open(path, 'r') as f:\n json_string = f.read()\n return json.loads(json_string)\n def getInstanceAndProjectIdFromConfigDrive():\n path = '/mnt/config/openstack/latest/meta_data.json'\n with open(path, 'r') as f:\n json_string = f.read()\n metadata = json.loads(json_string)\n assert 'uuid' in metadata\n assert 'project_id' in metadata\n return str(uuid.UUID(metadata['uuid'], version=4)), str(uuid.UUID(metadata['project_id'], version=4))\n print 'Getting vendordata from ConfigDrive'\n vendordata = getVendordataFromConfigDrive()\n print 'Getting instance and project IDs'\n instance_id, project_id = getInstanceAndProjectIdFromConfigDrive()\n assert 'tatu' in vendordata\n tatu = vendordata['tatu']\n assert 'token' in tatu\n assert 'auth_pub_key_user' in tatu\n assert 'principals' in tatu\n principals = tatu['principals'].split(',')\n with open('/etc/ssh/ssh_host_rsa_key.pub', 'r') as f:\n host_key_pub = f.read()\n server = 'http://172.24.4.1:18322'\n hostcert_request = {\n 'token_id': tatu['token'],\n 'host_id': instance_id,\n 'key.pub': host_key_pub\n }\n print 'Request the host certificate.'\n response = requests.post(\n # Hard-coded SSHaaS API address will only work for devstack and requires\n # routing and SNAT or DNAT.\n # This eventually needs to be either:\n # 1) 169.254.169.254 if there's a SSHaaS-proxy; OR\n # 2) the real address of the API, possibly supplied in the vendordata and\n # still requiring routing and SNAT or DNAT.\n server + '/hostcerts',\n data=json.dumps(hostcert_request)\n )\n print 'Got the host certificate: {}'.format(response.content)\n assert response.status_code == 201\n assert 'location' in response.headers\n location = response.headers['location']\n # No need to GET the host cert - it's returned in the POST\n #response = requests.get(server + location)\n hostcert = json.loads(response.content)\n assert 'host_id' in hostcert\n assert hostcert['host_id'] == instance_id\n assert 'fingerprint' in hostcert\n assert 'auth_id' in hostcert\n auth_id = str(uuid.UUID(hostcert['auth_id'], version=4))\n assert auth_id == project_id\n assert 'key-cert.pub' in hostcert\n print 'Begin writing files.'\n # Write the host's certificate\n with open('/etc/ssh/ssh_host_rsa_key-cert.pub', 'w') as f:\n f.write(hostcert['key-cert.pub'])\n # Write the authorized principals file\n os.mkdir('/etc/ssh/auth_principals')\n with open('/etc/ssh/auth_principals/ubuntu', 'w') as f:\n for p in principals:\n f.write(p + os.linesep)\n # Write the User CA public key file\n with open('/etc/ssh/ca_user.pub', 'w') as f:\n f.write(tatu['auth_pub_key_user'])\n print 'All tasks completed.'\nruncmd:\n - python /root/setup-ssh.py > /var/log/setup-ssh.log 2>&1\n - sed -i -e '$aTrustedUserCAKeys /etc/ssh/ca_user.pub' /etc/ssh/sshd_config\n - sed -i -e '$aAuthorizedPrincipalsFile /etc/ssh/auth_principals/%u' /etc/ssh/sshd_config\n - sed -i -e '$aHostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub' /etc/ssh/sshd_config\n - systemctl restart ssh\n"} diff --git a/files/user-cloud-config b/files/user-cloud-config index 7f7c5d4..a0414c5 100644 --- a/files/user-cloud-config +++ b/files/user-cloud-config @@ -9,6 +9,7 @@ write_files: permissions: '0700' owner: root:root content: | + print 'Importing packages' import json import requests import os @@ -27,7 +28,9 @@ write_files: 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)) + print 'Getting vendordata from ConfigDrive' vendordata = getVendordataFromConfigDrive() + print 'Getting instance and project IDs' instance_id, project_id = getInstanceAndProjectIdFromConfigDrive() assert 'tatu' in vendordata tatu = vendordata['tatu'] @@ -37,7 +40,7 @@ write_files: 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' + server = 'http://172.24.4.1:18322' hostcert_request = { 'token_id': tatu['token'], 'host_id': instance_id, @@ -54,10 +57,12 @@ write_files: server + '/hostcerts', data=json.dumps(hostcert_request) ) + print 'Got the host certificate: {}'.format(response.content) assert response.status_code == 201 assert 'location' in response.headers location = response.headers['location'] - response = requests.get(server + location) + # No need to GET the host cert - it's returned in the POST + #response = requests.get(server + location) hostcert = json.loads(response.content) assert 'host_id' in hostcert assert hostcert['host_id'] == instance_id diff --git a/scripts/cloud-config-to-vendor-data b/scripts/cloud-config-to-vendor-data index 13c7e85..a693a3d 100755 --- a/scripts/cloud-config-to-vendor-data +++ b/scripts/cloud-config-to-vendor-data @@ -7,6 +7,4 @@ import yaml with open(sys.argv[1], 'r') as f: yaml_string = f.read() -# save to file: -with open(sys.argv[2], 'w') as f: - f.write(json.dumps({"cloud-init":yaml_string})) +print json.dumps({"cloud-init":yaml_string}) diff --git a/scripts/vendor-data-to-cloud-config b/scripts/vendor-data-to-cloud-config index 80e4081..e96152c 100755 --- a/scripts/vendor-data-to-cloud-config +++ b/scripts/vendor-data-to-cloud-config @@ -7,7 +7,4 @@ import yaml with open(sys.argv[1], 'r') as f: js = json.loads(f.read()) -# save to file: -#with open(sys.argv[2], 'w') as f: -# f.write(js['cloud-init']) print js['cloud-init'] diff --git a/tatu/api/models.py b/tatu/api/models.py index a781893..89f21b9 100644 --- a/tatu/api/models.py +++ b/tatu/api/models.py @@ -115,6 +115,14 @@ class UserCert(object): resp.body = json.dumps(body) resp.status = falcon.HTTP_OK +def hostToJson(host): + return json.dumps({ + 'host_id': host.host_id, + 'fingerprint': host.fingerprint, + 'auth_id': host.auth_id, + 'key-cert.pub': host.cert, + }) + class HostCerts(object): @falcon.before(validate) @@ -130,6 +138,7 @@ class HostCerts(object): ) except KeyError as e: raise falcon.HTTPBadRequest(str(e)) + resp.body = hostToJson(host) resp.status = falcon.HTTP_201 resp.location = '/hostcerts/' + host.host_id + '/' + host.fingerprint @@ -141,13 +150,7 @@ class HostCert(object): if host is None: resp.status = falcon.HTTP_NOT_FOUND return - body = { - 'host_id': host.host_id, - 'fingerprint': host.fingerprint, - 'auth_id': host.auth_id, - 'key-cert.pub': host.cert, - } - resp.body = json.dumps(body) + resp.body = hostToJson(host) resp.status = falcon.HTTP_OK class Tokens(object): diff --git a/tatu/ftests/test_api.py b/tatu/ftests/test_api.py index 6859833..497ceeb 100644 --- a/tatu/ftests/test_api.py +++ b/tatu/ftests/test_api.py @@ -6,7 +6,7 @@ from Crypto.PublicKey import RSA import sshpubkeys import uuid -server = 'http://127.0.0.1:18321' +server = 'http://172.24.4.1:18322' def vendordata_request(instance_id, project_id, hostname): return { @@ -44,7 +44,7 @@ def test_host_certificate_generation(): key = RSA.generate(2048) pub_key = key.publickey().exportKey('OpenSSH') fingerprint = sshpubkeys.SSHKey(pub_key).hash_md5() - for i in range(100): + for i in range(10): instance_id = random_uuid() hostname = 'host{}'.format(i) # Simulate Nova's separate requests for each version of metadata API diff --git a/tatu/tests/test_app.py b/tatu/tests/test_app.py index 1fdca5b..2c6160b 100644 --- a/tatu/tests/test_app.py +++ b/tatu/tests/test_app.py @@ -284,6 +284,18 @@ def test_post_token_and_host(client): assert location[1] == 'hostcerts' assert location[2] == host_id assert location[3] == host_fingerprint + # Re-trying the same exact calls returns identical results + response = client.simulate_post( + '/hostcerts', + body=json.dumps(host) + ) + assert response.status == falcon.HTTP_CREATED + assert 'location' in response.headers + location = response.headers['location'].split('/') + assert location[1] == 'hostcerts' + assert location[2] == host_id + assert location[3] == host_fingerprint + def test_stress_post_token_and_host(client): my_auth_id = random_uuid()