Provide ssh setup script via static vendor data.

This commit is contained in:
Pino de Candia 2017-11-27 21:50:50 +00:00
parent 6235f5e2b0
commit 857d4dd028
7 changed files with 33 additions and 17 deletions

View File

@ -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"}

View File

@ -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

View File

@ -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})

View File

@ -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']

View File

@ -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):

View File

@ -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

View File

@ -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()