4 space indentation
This commit is contained in:
parent
95c0f1011c
commit
7812e1e8b6
@ -1,4 +1,15 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# 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 json
|
||||||
import requests
|
import requests
|
||||||
|
@ -1,4 +1,16 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# 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 sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import yaml
|
import yaml
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# 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 json
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
|
@ -1,4 +1,16 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# 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 argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
|
@ -1,4 +1,16 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# 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 argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
@ -1,4 +1,16 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# 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 sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import yaml
|
import yaml
|
||||||
|
12
setup.py
12
setup.py
@ -1,3 +1,15 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# 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 setuptools
|
import setuptools
|
||||||
|
|
||||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# 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 falcon
|
import falcon
|
||||||
import models
|
import models
|
||||||
import os.path
|
import os.path
|
||||||
@ -11,6 +23,7 @@ CONF = cfg.CONF
|
|||||||
if os.path.isfile(fname):
|
if os.path.isfile(fname):
|
||||||
CONF(default_config_files=[fname])
|
CONF(default_config_files=[fname])
|
||||||
|
|
||||||
|
|
||||||
def create_app(sa):
|
def create_app(sa):
|
||||||
api = falcon.API(middleware=[models.Logger(), sa])
|
api = falcon.API(middleware=[models.Logger(), sa])
|
||||||
api.add_route('/authorities', models.Authorities())
|
api.add_route('/authorities', models.Authorities())
|
||||||
@ -23,8 +36,10 @@ def create_app(sa):
|
|||||||
api.add_route('/novavendordata', models.NovaVendorData())
|
api.add_route('/novavendordata', models.NovaVendorData())
|
||||||
return api
|
return api
|
||||||
|
|
||||||
|
|
||||||
def get_app():
|
def get_app():
|
||||||
return create_app(SQLAlchemySessionManager())
|
return create_app(SQLAlchemySessionManager())
|
||||||
|
|
||||||
|
|
||||||
def main(global_config, **settings):
|
def main(global_config, **settings):
|
||||||
return create_app(SQLAlchemySessionManager())
|
return create_app(SQLAlchemySessionManager())
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# 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 falcon
|
import falcon
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
@ -5,203 +17,208 @@ import uuid
|
|||||||
from tatu.db import models as db
|
from tatu.db import models as db
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
|
|
||||||
|
|
||||||
def validate_uuid(map, key):
|
def validate_uuid(map, key):
|
||||||
try:
|
try:
|
||||||
# Verify it's a valid UUID, then convert to canonical string representation
|
# Verify it's a valid UUID, then convert to canonical string representation
|
||||||
# to avoiid DB errors.
|
# to avoiid DB errors.
|
||||||
map[key] = str(uuid.UUID(map[key], version=4))
|
map[key] = str(uuid.UUID(map[key], version=4))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
msg = '{} is not a valid UUID'.format(map[key])
|
msg = '{} is not a valid UUID'.format(map[key])
|
||||||
raise falcon.HTTPBadRequest('Bad request', msg)
|
raise falcon.HTTPBadRequest('Bad request', msg)
|
||||||
|
|
||||||
|
|
||||||
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:
|
||||||
|
if key in req.body:
|
||||||
|
validate_uuid(req.body, key)
|
||||||
for key in id_keys:
|
for key in id_keys:
|
||||||
if key in req.body:
|
if key in params:
|
||||||
validate_uuid(req.body, key)
|
validate_uuid(params, key)
|
||||||
for key in id_keys:
|
|
||||||
if key in params:
|
|
||||||
validate_uuid(params, key)
|
|
||||||
|
|
||||||
def validate(req, resp, resource, params):
|
def validate(req, resp, resource, params):
|
||||||
if req.content_length:
|
if req.content_length:
|
||||||
# Store the body since we cannot read the stream again later
|
# Store the body since we cannot read the stream again later
|
||||||
req.body = json.load(req.stream)
|
req.body = json.load(req.stream)
|
||||||
elif req.method in ('POST', 'PUT'):
|
elif req.method in ('POST', 'PUT'):
|
||||||
raise falcon.HTTPBadRequest('The POST/PUT request is missing a body.')
|
raise falcon.HTTPBadRequest('The POST/PUT request is missing a body.')
|
||||||
validate_uuids(req, params)
|
validate_uuids(req, params)
|
||||||
|
|
||||||
|
|
||||||
class Logger(object):
|
class Logger(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
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):
|
||||||
|
self.logger.debug(
|
||||||
|
'Request {0} {1} with body {2} produced response '
|
||||||
|
'with status {3} location {4} and body {5}'.format(
|
||||||
|
req.method, req.relative_uri,
|
||||||
|
req.body if hasattr(req, 'body') else 'None',
|
||||||
|
resp.status, resp.location, resp.body))
|
||||||
|
|
||||||
def process_response(self, req, resp, resource, params):
|
|
||||||
self.logger.debug(
|
|
||||||
'Request {0} {1} with body {2} produced response '
|
|
||||||
'with status {3} location {4} and body {5}'.format(
|
|
||||||
req.method, req.relative_uri,
|
|
||||||
req.body if hasattr(req, 'body') else 'None',
|
|
||||||
resp.status, resp.location, resp.body))
|
|
||||||
|
|
||||||
class Authorities(object):
|
class Authorities(object):
|
||||||
|
@falcon.before(validate)
|
||||||
|
def on_post(self, req, resp):
|
||||||
|
try:
|
||||||
|
db.createAuthority(
|
||||||
|
self.session,
|
||||||
|
req.body['auth_id'],
|
||||||
|
)
|
||||||
|
except KeyError as e:
|
||||||
|
raise falcon.HTTPBadRequest(str(e))
|
||||||
|
resp.status = falcon.HTTP_201
|
||||||
|
resp.location = '/authorities/' + req.body['auth_id']
|
||||||
|
|
||||||
@falcon.before(validate)
|
|
||||||
def on_post(self, req, resp):
|
|
||||||
try:
|
|
||||||
db.createAuthority(
|
|
||||||
self.session,
|
|
||||||
req.body['auth_id'],
|
|
||||||
)
|
|
||||||
except KeyError as e:
|
|
||||||
raise falcon.HTTPBadRequest(str(e))
|
|
||||||
resp.status = falcon.HTTP_201
|
|
||||||
resp.location = '/authorities/' + req.body['auth_id']
|
|
||||||
|
|
||||||
class Authority(object):
|
class Authority(object):
|
||||||
|
@falcon.before(validate)
|
||||||
|
def on_get(self, req, resp, auth_id):
|
||||||
|
auth = db.getAuthority(self.session, auth_id)
|
||||||
|
if auth is None:
|
||||||
|
resp.status = falcon.HTTP_NOT_FOUND
|
||||||
|
return
|
||||||
|
user_key = RSA.importKey(db.getAuthUserKey(auth))
|
||||||
|
user_pub_key = user_key.publickey().exportKey('OpenSSH')
|
||||||
|
host_key = RSA.importKey(db.getAuthHostKey(auth))
|
||||||
|
host_pub_key = host_key.publickey().exportKey('OpenSSH')
|
||||||
|
body = {
|
||||||
|
'auth_id': auth_id,
|
||||||
|
'user_key.pub': user_pub_key,
|
||||||
|
'host_key.pub': host_pub_key
|
||||||
|
}
|
||||||
|
resp.body = json.dumps(body)
|
||||||
|
resp.status = falcon.HTTP_OK
|
||||||
|
|
||||||
@falcon.before(validate)
|
|
||||||
def on_get(self, req, resp, auth_id):
|
|
||||||
auth = db.getAuthority(self.session, auth_id)
|
|
||||||
if auth is None:
|
|
||||||
resp.status = falcon.HTTP_NOT_FOUND
|
|
||||||
return
|
|
||||||
user_key = RSA.importKey(db.getAuthUserKey(auth))
|
|
||||||
user_pub_key = user_key.publickey().exportKey('OpenSSH')
|
|
||||||
host_key = RSA.importKey(db.getAuthHostKey(auth))
|
|
||||||
host_pub_key = host_key.publickey().exportKey('OpenSSH')
|
|
||||||
body = {
|
|
||||||
'auth_id': auth_id,
|
|
||||||
'user_key.pub': user_pub_key,
|
|
||||||
'host_key.pub': host_pub_key
|
|
||||||
}
|
|
||||||
resp.body = json.dumps(body)
|
|
||||||
resp.status = falcon.HTTP_OK
|
|
||||||
|
|
||||||
class UserCerts(object):
|
class UserCerts(object):
|
||||||
|
@falcon.before(validate)
|
||||||
|
def on_post(self, req, resp):
|
||||||
|
# TODO: validation
|
||||||
|
try:
|
||||||
|
user = db.createUserCert(
|
||||||
|
self.session,
|
||||||
|
req.body['user_id'],
|
||||||
|
req.body['auth_id'],
|
||||||
|
req.body['key.pub']
|
||||||
|
)
|
||||||
|
except KeyError as e:
|
||||||
|
raise falcon.HTTPBadRequest(str(e))
|
||||||
|
resp.status = falcon.HTTP_201
|
||||||
|
resp.location = '/usercerts/' + user.user_id + '/' + user.fingerprint
|
||||||
|
|
||||||
@falcon.before(validate)
|
|
||||||
def on_post(self, req, resp):
|
|
||||||
# TODO: validation
|
|
||||||
try:
|
|
||||||
user = db.createUserCert(
|
|
||||||
self.session,
|
|
||||||
req.body['user_id'],
|
|
||||||
req.body['auth_id'],
|
|
||||||
req.body['key.pub']
|
|
||||||
)
|
|
||||||
except KeyError as e:
|
|
||||||
raise falcon.HTTPBadRequest(str(e))
|
|
||||||
resp.status = falcon.HTTP_201
|
|
||||||
resp.location = '/usercerts/' + user.user_id + '/' + user.fingerprint
|
|
||||||
|
|
||||||
class UserCert(object):
|
class UserCert(object):
|
||||||
|
@falcon.before(validate)
|
||||||
|
def on_get(self, req, resp, user_id, fingerprint):
|
||||||
|
user = db.getUserCert(self.session, user_id, fingerprint)
|
||||||
|
if user is None:
|
||||||
|
resp.status = falcon.HTTP_NOT_FOUND
|
||||||
|
return
|
||||||
|
body = {
|
||||||
|
'user_id': user.user_id,
|
||||||
|
'fingerprint': user.fingerprint,
|
||||||
|
'auth_id': user.auth_id,
|
||||||
|
'key-cert.pub': user.cert
|
||||||
|
}
|
||||||
|
resp.body = json.dumps(body)
|
||||||
|
resp.status = falcon.HTTP_OK
|
||||||
|
|
||||||
@falcon.before(validate)
|
|
||||||
def on_get(self, req, resp, user_id, fingerprint):
|
|
||||||
user = db.getUserCert(self.session, user_id, fingerprint)
|
|
||||||
if user is None:
|
|
||||||
resp.status = falcon.HTTP_NOT_FOUND
|
|
||||||
return
|
|
||||||
body = {
|
|
||||||
'user_id': user.user_id,
|
|
||||||
'fingerprint': user.fingerprint,
|
|
||||||
'auth_id': user.auth_id,
|
|
||||||
'key-cert.pub': user.cert
|
|
||||||
}
|
|
||||||
resp.body = json.dumps(body)
|
|
||||||
resp.status = falcon.HTTP_OK
|
|
||||||
|
|
||||||
def hostToJson(host):
|
def hostToJson(host):
|
||||||
return json.dumps({
|
return json.dumps({
|
||||||
'host_id': host.host_id,
|
'host_id': host.host_id,
|
||||||
'fingerprint': host.fingerprint,
|
'fingerprint': host.fingerprint,
|
||||||
'auth_id': host.auth_id,
|
'auth_id': host.auth_id,
|
||||||
'key-cert.pub': host.cert,
|
'key-cert.pub': host.cert,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
class HostCerts(object):
|
class HostCerts(object):
|
||||||
|
@falcon.before(validate)
|
||||||
|
def on_post(self, req, resp):
|
||||||
|
# Note that we could have found the host_id using the token_id.
|
||||||
|
# But requiring the host_id makes it a little harder to steal the token.
|
||||||
|
try:
|
||||||
|
host = db.createHostCert(
|
||||||
|
self.session,
|
||||||
|
req.body['token_id'],
|
||||||
|
req.body['host_id'],
|
||||||
|
req.body['key.pub']
|
||||||
|
)
|
||||||
|
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
|
||||||
|
|
||||||
@falcon.before(validate)
|
|
||||||
def on_post(self, req, resp):
|
|
||||||
# Note that we could have found the host_id using the token_id.
|
|
||||||
# But requiring the host_id makes it a little harder to steal the token.
|
|
||||||
try:
|
|
||||||
host = db.createHostCert(
|
|
||||||
self.session,
|
|
||||||
req.body['token_id'],
|
|
||||||
req.body['host_id'],
|
|
||||||
req.body['key.pub']
|
|
||||||
)
|
|
||||||
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
|
|
||||||
|
|
||||||
class HostCert(object):
|
class HostCert(object):
|
||||||
|
@falcon.before(validate)
|
||||||
|
def on_get(self, req, resp, host_id, fingerprint):
|
||||||
|
host = db.getHostCert(self.session, host_id, fingerprint)
|
||||||
|
if host is None:
|
||||||
|
resp.status = falcon.HTTP_NOT_FOUND
|
||||||
|
return
|
||||||
|
resp.body = hostToJson(host)
|
||||||
|
resp.status = falcon.HTTP_OK
|
||||||
|
|
||||||
@falcon.before(validate)
|
|
||||||
def on_get(self, req, resp, host_id, fingerprint):
|
|
||||||
host = db.getHostCert(self.session, host_id, fingerprint)
|
|
||||||
if host is None:
|
|
||||||
resp.status = falcon.HTTP_NOT_FOUND
|
|
||||||
return
|
|
||||||
resp.body = hostToJson(host)
|
|
||||||
resp.status = falcon.HTTP_OK
|
|
||||||
|
|
||||||
class Tokens(object):
|
class Tokens(object):
|
||||||
|
@falcon.before(validate)
|
||||||
|
def on_post(self, req, resp):
|
||||||
|
try:
|
||||||
|
token = db.createToken(
|
||||||
|
self.session,
|
||||||
|
req.body['host_id'],
|
||||||
|
req.body['auth_id'],
|
||||||
|
req.body['hostname']
|
||||||
|
)
|
||||||
|
except KeyError as e:
|
||||||
|
raise falcon.HTTPBadRequest(str(e))
|
||||||
|
resp.status = falcon.HTTP_201
|
||||||
|
resp.location = '/hosttokens/' + token.token_id
|
||||||
|
|
||||||
@falcon.before(validate)
|
|
||||||
def on_post(self, req, resp):
|
|
||||||
try:
|
|
||||||
token = db.createToken(
|
|
||||||
self.session,
|
|
||||||
req.body['host_id'],
|
|
||||||
req.body['auth_id'],
|
|
||||||
req.body['hostname']
|
|
||||||
)
|
|
||||||
except KeyError as e:
|
|
||||||
raise falcon.HTTPBadRequest(str(e))
|
|
||||||
resp.status = falcon.HTTP_201
|
|
||||||
resp.location = '/hosttokens/' + token.token_id
|
|
||||||
|
|
||||||
class NovaVendorData(object):
|
class NovaVendorData(object):
|
||||||
|
@falcon.before(validate)
|
||||||
@falcon.before(validate)
|
def on_post(self, req, resp):
|
||||||
def on_post(self, req, resp):
|
# An example of the data nova sends to vendordata services:
|
||||||
# An example of the data nova sends to vendordata services:
|
# {
|
||||||
# {
|
# "hostname": "foo",
|
||||||
# "hostname": "foo",
|
# "image-id": "75a74383-f276-4774-8074-8c4e3ff2ca64",
|
||||||
# "image-id": "75a74383-f276-4774-8074-8c4e3ff2ca64",
|
# "instance-id": "2ae914e9-f5ab-44ce-b2a2-dcf8373d899d",
|
||||||
# "instance-id": "2ae914e9-f5ab-44ce-b2a2-dcf8373d899d",
|
# "metadata": {},
|
||||||
# "metadata": {},
|
# "project-id": "039d104b7a5c4631b4ba6524d0b9e981",
|
||||||
# "project-id": "039d104b7a5c4631b4ba6524d0b9e981",
|
# "user-data": null
|
||||||
# "user-data": null
|
# }
|
||||||
# }
|
try:
|
||||||
try:
|
token = db.createToken(
|
||||||
token = db.createToken(
|
self.session,
|
||||||
self.session,
|
req.body['instance-id'],
|
||||||
req.body['instance-id'],
|
req.body['project-id'],
|
||||||
req.body['project-id'],
|
req.body['hostname']
|
||||||
req.body['hostname']
|
)
|
||||||
)
|
except KeyError as e:
|
||||||
except KeyError as e:
|
raise falcon.HTTPBadRequest(str(e))
|
||||||
raise falcon.HTTPBadRequest(str(e))
|
auth = db.getAuthority(self.session, req.body['project-id'])
|
||||||
auth = db.getAuthority(self.session, req.body['project-id'])
|
if auth is None:
|
||||||
if auth is None:
|
resp.status = falcon.HTTP_NOT_FOUND
|
||||||
resp.status = falcon.HTTP_NOT_FOUND
|
return
|
||||||
return
|
key = RSA.importKey(auth.user_key)
|
||||||
key = RSA.importKey(auth.user_key)
|
pub_key = key.publickey().exportKey('OpenSSH')
|
||||||
pub_key = key.publickey().exportKey('OpenSSH')
|
vendordata = {
|
||||||
vendordata = {
|
'token': token.token_id,
|
||||||
'token': token.token_id,
|
'auth_pub_key_user': pub_key,
|
||||||
'auth_pub_key_user': pub_key,
|
'principals': 'admin'
|
||||||
'principals': 'admin'
|
}
|
||||||
}
|
resp.body = json.dumps(vendordata)
|
||||||
resp.body = json.dumps(vendordata)
|
resp.location = '/hosttokens/' + token.token_id
|
||||||
resp.location = '/hosttokens/' + token.token_id
|
resp.status = falcon.HTTP_201
|
||||||
resp.status = falcon.HTTP_201
|
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
from castellan.common.objects.passphrase import Passphrase
|
from castellan.common.objects.passphrase import Passphrase
|
||||||
from castellan.common.utils import credential_factory
|
from castellan.common.utils import credential_factory
|
||||||
from castellan.key_manager import API
|
from castellan.key_manager import API
|
||||||
@ -16,6 +28,7 @@ CONF.register_opts(opts, group='tatu')
|
|||||||
_context = None
|
_context = None
|
||||||
_api = None
|
_api = None
|
||||||
|
|
||||||
|
|
||||||
def validate_config():
|
def validate_config():
|
||||||
if CONF.tatu.use_barbican_key_manager:
|
if CONF.tatu.use_barbican_key_manager:
|
||||||
set_castellan_defaults(CONF)
|
set_castellan_defaults(CONF)
|
||||||
@ -23,17 +36,20 @@ def validate_config():
|
|||||||
set_castellan_defaults(CONF,
|
set_castellan_defaults(CONF,
|
||||||
api_class='tatu.castellano.TatuKeyManager')
|
api_class='tatu.castellano.TatuKeyManager')
|
||||||
|
|
||||||
|
|
||||||
def context():
|
def context():
|
||||||
global _context
|
global _context
|
||||||
if _context is None and CONF.tatu.use_barbican_key_manager:
|
if _context is None and CONF.tatu.use_barbican_key_manager:
|
||||||
_context = credential_factory(conf=CONF)
|
_context = credential_factory(conf=CONF)
|
||||||
return _context
|
return _context
|
||||||
|
|
||||||
|
|
||||||
def api():
|
def api():
|
||||||
global _api
|
global _api
|
||||||
if _api is None:
|
if _api is None:
|
||||||
_api = API()
|
_api = API()
|
||||||
return _api
|
return _api
|
||||||
|
|
||||||
|
|
||||||
def delete_secret(id, ctx=None):
|
def delete_secret(id, ctx=None):
|
||||||
"""delete a secret from the external key manager
|
"""delete a secret from the external key manager
|
||||||
@ -43,6 +59,7 @@ def delete_secret(id, ctx=None):
|
|||||||
"""
|
"""
|
||||||
api().delete(ctx or context(), id)
|
api().delete(ctx or context(), id)
|
||||||
|
|
||||||
|
|
||||||
def get_secret(id, ctx=None):
|
def get_secret(id, ctx=None):
|
||||||
"""get a secret associated with an id
|
"""get a secret associated with an id
|
||||||
:param id: The identifier of the secret to retrieve
|
:param id: The identifier of the secret to retrieve
|
||||||
@ -52,6 +69,7 @@ def get_secret(id, ctx=None):
|
|||||||
key = api().get(ctx or context(), id)
|
key = api().get(ctx or context(), id)
|
||||||
return key.get_encoded()
|
return key.get_encoded()
|
||||||
|
|
||||||
|
|
||||||
def store_secret(secret, ctx=None):
|
def store_secret(secret, ctx=None):
|
||||||
"""store a secret and return its identifier
|
"""store a secret and return its identifier
|
||||||
:param secret: The secret to store, this should be a string
|
:param secret: The secret to store, this should be a string
|
||||||
@ -61,10 +79,13 @@ def store_secret(secret, ctx=None):
|
|||||||
key = Passphrase(secret)
|
key = Passphrase(secret)
|
||||||
return api().store(ctx or context(), key)
|
return api().store(ctx or context(), key)
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This module contains the KeyManager class that will be used by the
|
This module contains the KeyManager class that will be used by the
|
||||||
castellan library, it is not meant for direct usage within tatu.
|
castellan library, it is not meant for direct usage within tatu.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class TatuKeyManager(KeyManager):
|
class TatuKeyManager(KeyManager):
|
||||||
"""Tatu specific key manager
|
"""Tatu specific key manager
|
||||||
This manager is a thin wrapper around the secret being stored. It is
|
This manager is a thin wrapper around the secret being stored. It is
|
||||||
@ -73,6 +94,7 @@ class TatuKeyManager(KeyManager):
|
|||||||
This behavior allows Tatu to continue storing secrets in its database
|
This behavior allows Tatu to continue storing secrets in its database
|
||||||
while using the Castellan key manager abstraction.
|
while using the Castellan key manager abstraction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, configuration=None):
|
def __init__(self, configuration=None):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import falcon
|
import falcon
|
||||||
import os
|
import os
|
||||||
@ -6,100 +18,110 @@ from sqlalchemy.ext.declarative import declarative_base
|
|||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
import sshpubkeys
|
import sshpubkeys
|
||||||
from tatu.castellano import get_secret, store_secret
|
from tatu.castellano import get_secret, store_secret
|
||||||
from tatu.utils import generateCert,random_uuid
|
from tatu.utils import generateCert, random_uuid
|
||||||
import uuid
|
import uuid
|
||||||
from Crypto.PublicKey import RSA
|
from Crypto.PublicKey import RSA
|
||||||
|
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
class Authority(Base):
|
|
||||||
__tablename__ = 'authorities'
|
|
||||||
|
|
||||||
auth_id = sa.Column(sa.String(36), primary_key=True)
|
class Authority(Base):
|
||||||
user_key = sa.Column(sa.Text)
|
__tablename__ = 'authorities'
|
||||||
host_key = sa.Column(sa.Text)
|
|
||||||
|
auth_id = sa.Column(sa.String(36), primary_key=True)
|
||||||
|
user_key = sa.Column(sa.Text)
|
||||||
|
host_key = sa.Column(sa.Text)
|
||||||
|
|
||||||
|
|
||||||
def getAuthority(session, auth_id):
|
def getAuthority(session, auth_id):
|
||||||
return session.query(Authority).get(auth_id)
|
return session.query(Authority).get(auth_id)
|
||||||
|
|
||||||
|
|
||||||
def getAuthUserKey(auth):
|
def getAuthUserKey(auth):
|
||||||
return get_secret(auth.user_key)
|
return get_secret(auth.user_key)
|
||||||
|
|
||||||
|
|
||||||
def getAuthHostKey(auth):
|
def getAuthHostKey(auth):
|
||||||
return get_secret(auth.host_key)
|
return get_secret(auth.host_key)
|
||||||
|
|
||||||
|
|
||||||
def createAuthority(session, auth_id):
|
def createAuthority(session, auth_id):
|
||||||
user_key = RSA.generate(2048).exportKey('PEM')
|
user_key = RSA.generate(2048).exportKey('PEM')
|
||||||
user_secret_id = store_secret(user_key)
|
user_secret_id = store_secret(user_key)
|
||||||
host_key = RSA.generate(2048).exportKey('PEM')
|
host_key = RSA.generate(2048).exportKey('PEM')
|
||||||
host_secret_id = store_secret(host_key)
|
host_secret_id = store_secret(host_key)
|
||||||
auth = Authority(auth_id=auth_id,
|
auth = Authority(auth_id=auth_id,
|
||||||
user_key=user_secret_id,
|
user_key=user_secret_id,
|
||||||
host_key=host_secret_id)
|
host_key=host_secret_id)
|
||||||
session.add(auth)
|
session.add(auth)
|
||||||
try:
|
try:
|
||||||
session.commit()
|
session.commit()
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
raise falcon.HTTPConflict("This certificate authority already exists.")
|
raise falcon.HTTPConflict("This certificate authority already exists.")
|
||||||
return auth
|
return auth
|
||||||
|
|
||||||
|
|
||||||
class UserCert(Base):
|
class UserCert(Base):
|
||||||
__tablename__ = 'user_certs'
|
__tablename__ = 'user_certs'
|
||||||
|
|
||||||
|
user_id = sa.Column(sa.String(36), primary_key=True)
|
||||||
|
fingerprint = sa.Column(sa.String(36), primary_key=True)
|
||||||
|
auth_id = sa.Column(sa.String(36), sa.ForeignKey('authorities.auth_id'))
|
||||||
|
cert = sa.Column(sa.Text)
|
||||||
|
|
||||||
user_id = sa.Column(sa.String(36), primary_key=True)
|
|
||||||
fingerprint = sa.Column(sa.String(36), primary_key=True)
|
|
||||||
auth_id = sa.Column(sa.String(36), sa.ForeignKey('authorities.auth_id'))
|
|
||||||
cert = sa.Column(sa.Text)
|
|
||||||
|
|
||||||
def getUserCert(session, user_id, fingerprint):
|
def getUserCert(session, user_id, fingerprint):
|
||||||
return session.query(UserCert).get([user_id, fingerprint])
|
return session.query(UserCert).get([user_id, fingerprint])
|
||||||
|
|
||||||
|
|
||||||
def createUserCert(session, user_id, auth_id, pub):
|
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,
|
||||||
auth_id=auth_id,
|
auth_id=auth_id,
|
||||||
cert=cert
|
cert=cert
|
||||||
)
|
)
|
||||||
session.add(user)
|
session.add(user)
|
||||||
session.commit()
|
session.commit()
|
||||||
return user
|
return user
|
||||||
|
|
||||||
class Token(Base):
|
|
||||||
__tablename__ = 'tokens'
|
|
||||||
|
|
||||||
token_id = sa.Column(sa.String(36), primary_key=True,
|
class Token(Base):
|
||||||
default=random_uuid)
|
__tablename__ = 'tokens'
|
||||||
auth_id = sa.Column(sa.String(36), sa.ForeignKey('authorities.auth_id'))
|
|
||||||
host_id = sa.Column(sa.String(36), index=True, unique=True)
|
token_id = sa.Column(sa.String(36), primary_key=True,
|
||||||
hostname = sa.Column(sa.String(36))
|
default=random_uuid)
|
||||||
used = sa.Column(sa.Boolean, default=False)
|
auth_id = sa.Column(sa.String(36), sa.ForeignKey('authorities.auth_id'))
|
||||||
date_used = sa.Column(sa.DateTime, default=datetime.min)
|
host_id = sa.Column(sa.String(36), index=True, unique=True)
|
||||||
fingerprint_used = sa.Column(sa.String(36))
|
hostname = sa.Column(sa.String(36))
|
||||||
|
used = sa.Column(sa.Boolean, default=False)
|
||||||
|
date_used = sa.Column(sa.DateTime, default=datetime.min)
|
||||||
|
fingerprint_used = sa.Column(sa.String(36))
|
||||||
|
|
||||||
|
|
||||||
def createToken(session, host_id, auth_id, hostname):
|
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()
|
||||||
if token is not None:
|
if token is not None:
|
||||||
return token
|
return token
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
token = Token(host_id=host_id,
|
token = Token(host_id=host_id,
|
||||||
auth_id=auth_id,
|
auth_id=auth_id,
|
||||||
@ -108,54 +130,57 @@ def createToken(session, host_id, auth_id, hostname):
|
|||||||
session.commit()
|
session.commit()
|
||||||
return token
|
return token
|
||||||
|
|
||||||
class HostCert(Base):
|
|
||||||
__tablename__ = 'host_certs'
|
|
||||||
|
|
||||||
host_id = sa.Column(sa.String(36), primary_key=True)
|
class HostCert(Base):
|
||||||
fingerprint = sa.Column(sa.String(36), primary_key=True)
|
__tablename__ = 'host_certs'
|
||||||
auth_id = sa.Column(sa.String(36), sa.ForeignKey('authorities.auth_id'))
|
|
||||||
token_id = sa.Column(sa.String(36), sa.ForeignKey('tokens.token_id'))
|
host_id = sa.Column(sa.String(36), primary_key=True)
|
||||||
pubkey = sa.Column(sa.Text)
|
fingerprint = sa.Column(sa.String(36), primary_key=True)
|
||||||
cert = sa.Column(sa.Text)
|
auth_id = sa.Column(sa.String(36), sa.ForeignKey('authorities.auth_id'))
|
||||||
hostname = sa.Column(sa.String(36))
|
token_id = sa.Column(sa.String(36), sa.ForeignKey('tokens.token_id'))
|
||||||
|
pubkey = sa.Column(sa.Text)
|
||||||
|
cert = sa.Column(sa.Text)
|
||||||
|
hostname = sa.Column(sa.String(36))
|
||||||
|
|
||||||
|
|
||||||
def getHostCert(session, host_id, fingerprint):
|
def getHostCert(session, host_id, fingerprint):
|
||||||
return session.query(HostCert).get([host_id, fingerprint])
|
return session.query(HostCert).get([host_id, fingerprint])
|
||||||
|
|
||||||
|
|
||||||
def createHostCert(session, token_id, host_id, pub):
|
def createHostCert(session, token_id, host_id, pub):
|
||||||
token = session.query(Token).get(token_id)
|
token = session.query(Token).get(token_id)
|
||||||
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:
|
||||||
raise falcon.HTTPInternalServerError(
|
raise falcon.HTTPInternalServerError(
|
||||||
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,
|
||||||
token_id=token_id,
|
token_id=token_id,
|
||||||
pubkey = pub,
|
pubkey=pub,
|
||||||
cert=cert,
|
cert=cert,
|
||||||
hostname=token.hostname)
|
hostname=token.hostname)
|
||||||
session.add(host)
|
session.add(host)
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# 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 os
|
import os
|
||||||
|
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
@ -7,7 +19,8 @@ from tatu.db.models import Base
|
|||||||
|
|
||||||
def get_url():
|
def get_url():
|
||||||
return os.getenv("DATABASE_URL", "sqlite:///development.db")
|
return os.getenv("DATABASE_URL", "sqlite:///development.db")
|
||||||
#return os.getenv("DATABASE_URL", "sqlite:///:memory:")
|
# return os.getenv("DATABASE_URL", "sqlite:///:memory:")
|
||||||
|
|
||||||
|
|
||||||
class SQLAlchemySessionManager:
|
class SQLAlchemySessionManager:
|
||||||
"""
|
"""
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# 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 json
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
@ -8,90 +20,93 @@ import uuid
|
|||||||
|
|
||||||
server = 'http://172.24.4.1:18322'
|
server = 'http://172.24.4.1:18322'
|
||||||
|
|
||||||
|
|
||||||
def vendordata_request(instance_id, project_id, hostname):
|
def vendordata_request(instance_id, project_id, hostname):
|
||||||
return {
|
return {
|
||||||
'instance-id': instance_id,
|
'instance-id': instance_id,
|
||||||
'project-id': project_id,
|
'project-id': project_id,
|
||||||
'hostname': hostname
|
'hostname': hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def host_request(token, host, pub_key):
|
def host_request(token, host, pub_key):
|
||||||
return {
|
return {
|
||||||
'token_id': token,
|
'token_id': token,
|
||||||
'host_id': host,
|
'host_id': host,
|
||||||
'key.pub': pub_key
|
'key.pub': pub_key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_host_certificate_generation():
|
def test_host_certificate_generation():
|
||||||
project_id = random_uuid()
|
project_id = random_uuid()
|
||||||
response = requests.post(
|
|
||||||
server + '/authorities',
|
|
||||||
data=json.dumps({'auth_id': project_id})
|
|
||||||
)
|
|
||||||
assert response.status_code == 201
|
|
||||||
assert 'location' in response.headers
|
|
||||||
assert response.headers['location'] == '/authorities/' + project_id
|
|
||||||
|
|
||||||
response = requests.get(server + response.headers['location'])
|
|
||||||
assert response.status_code == 200
|
|
||||||
auth = json.loads(response.content)
|
|
||||||
assert 'auth_id' in auth
|
|
||||||
assert auth['auth_id'] == project_id
|
|
||||||
assert 'user_key.pub' in auth
|
|
||||||
assert 'host_key.pub' in auth
|
|
||||||
ca_user = auth['user_key.pub']
|
|
||||||
|
|
||||||
key = RSA.generate(2048)
|
|
||||||
pub_key = key.publickey().exportKey('OpenSSH')
|
|
||||||
fingerprint = sshpubkeys.SSHKey(pub_key).hash_md5()
|
|
||||||
for i in range(1):
|
|
||||||
instance_id = random_uuid()
|
|
||||||
hostname = 'host{}'.format(i)
|
|
||||||
# Simulate Nova's separate requests for each version of metadata API
|
|
||||||
vendordata = None
|
|
||||||
token = None
|
|
||||||
for j in range(3):
|
|
||||||
response = requests.post(
|
|
||||||
server + '/novavendordata',
|
|
||||||
data=json.dumps(vendordata_request(instance_id, project_id, hostname))
|
|
||||||
)
|
|
||||||
assert response.status_code == 201
|
|
||||||
assert 'location' in response.headers
|
|
||||||
location_path = response.headers['location'].split('/')
|
|
||||||
assert location_path[1] == 'hosttokens'
|
|
||||||
vendordata = json.loads(response.content)
|
|
||||||
assert 'token' in vendordata
|
|
||||||
tok = vendordata['token']
|
|
||||||
if token is None:
|
|
||||||
token = tok
|
|
||||||
else:
|
|
||||||
assert token == tok
|
|
||||||
assert token == location_path[-1]
|
|
||||||
assert 'auth_pub_key_user' in vendordata
|
|
||||||
assert vendordata['auth_pub_key_user'] == ca_user
|
|
||||||
assert 'principals' in vendordata
|
|
||||||
assert vendordata['principals'] == 'admin'
|
|
||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
server + '/noauth/hostcerts',
|
server + '/authorities',
|
||||||
data=json.dumps(host_request(token, instance_id, pub_key))
|
data=json.dumps({'auth_id': project_id})
|
||||||
)
|
)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
assert 'location' in response.headers
|
assert 'location' in response.headers
|
||||||
location = response.headers['location']
|
assert response.headers['location'] == '/authorities/' + project_id
|
||||||
location_path = location.split('/')
|
|
||||||
assert location_path[1] == 'hostcerts'
|
|
||||||
assert location_path[2] == instance_id
|
|
||||||
assert location_path[3] == fingerprint
|
|
||||||
|
|
||||||
response = requests.get(server + location)
|
response = requests.get(server + response.headers['location'])
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
hostcert = json.loads(response.content)
|
auth = json.loads(response.content)
|
||||||
assert 'host_id' in hostcert
|
assert 'auth_id' in auth
|
||||||
assert hostcert['host_id'] == instance_id
|
assert auth['auth_id'] == project_id
|
||||||
assert 'fingerprint' in hostcert
|
assert 'user_key.pub' in auth
|
||||||
assert hostcert['fingerprint']
|
assert 'host_key.pub' in auth
|
||||||
assert 'auth_id' in hostcert
|
ca_user = auth['user_key.pub']
|
||||||
auth_id = str(uuid.UUID(hostcert['auth_id'], version=4))
|
|
||||||
assert auth_id == project_id
|
key = RSA.generate(2048)
|
||||||
assert 'key-cert.pub' in hostcert
|
pub_key = key.publickey().exportKey('OpenSSH')
|
||||||
|
fingerprint = sshpubkeys.SSHKey(pub_key).hash_md5()
|
||||||
|
for i in range(1):
|
||||||
|
instance_id = random_uuid()
|
||||||
|
hostname = 'host{}'.format(i)
|
||||||
|
# Simulate Nova's separate requests for each version of metadata API
|
||||||
|
vendordata = None
|
||||||
|
token = None
|
||||||
|
for j in range(3):
|
||||||
|
response = requests.post(
|
||||||
|
server + '/novavendordata',
|
||||||
|
data=json.dumps(vendordata_request(instance_id, project_id, hostname))
|
||||||
|
)
|
||||||
|
assert response.status_code == 201
|
||||||
|
assert 'location' in response.headers
|
||||||
|
location_path = response.headers['location'].split('/')
|
||||||
|
assert location_path[1] == 'hosttokens'
|
||||||
|
vendordata = json.loads(response.content)
|
||||||
|
assert 'token' in vendordata
|
||||||
|
tok = vendordata['token']
|
||||||
|
if token is None:
|
||||||
|
token = tok
|
||||||
|
else:
|
||||||
|
assert token == tok
|
||||||
|
assert token == location_path[-1]
|
||||||
|
assert 'auth_pub_key_user' in vendordata
|
||||||
|
assert vendordata['auth_pub_key_user'] == ca_user
|
||||||
|
assert 'principals' in vendordata
|
||||||
|
assert vendordata['principals'] == 'admin'
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
server + '/noauth/hostcerts',
|
||||||
|
data=json.dumps(host_request(token, instance_id, pub_key))
|
||||||
|
)
|
||||||
|
assert response.status_code == 201
|
||||||
|
assert 'location' in response.headers
|
||||||
|
location = response.headers['location']
|
||||||
|
location_path = location.split('/')
|
||||||
|
assert location_path[1] == 'hostcerts'
|
||||||
|
assert location_path[2] == instance_id
|
||||||
|
assert location_path[3] == fingerprint
|
||||||
|
|
||||||
|
response = requests.get(server + location)
|
||||||
|
assert response.status_code == 200
|
||||||
|
hostcert = json.loads(response.content)
|
||||||
|
assert 'host_id' in hostcert
|
||||||
|
assert hostcert['host_id'] == instance_id
|
||||||
|
assert 'fingerprint' in hostcert
|
||||||
|
assert hostcert['fingerprint']
|
||||||
|
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
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
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
|
import oslo_messaging
|
||||||
@ -14,15 +26,15 @@ LOG = logging.getLogger(__name__)
|
|||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
DOMAIN = 'tatu'
|
DOMAIN = 'tatu'
|
||||||
|
|
||||||
class NotificationEndpoint(object):
|
|
||||||
|
|
||||||
|
class NotificationEndpoint(object):
|
||||||
filter_rule = oslo_messaging.NotificationFilter(
|
filter_rule = oslo_messaging.NotificationFilter(
|
||||||
publisher_id='^identity.*',
|
publisher_id='^identity.*',
|
||||||
event_type='^identity.project.created')
|
event_type='^identity.project.created')
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.engine = create_engine(get_url())
|
self.engine = create_engine(get_url())
|
||||||
#Base.metadata.create_all(self.engine)
|
# Base.metadata.create_all(self.engine)
|
||||||
self.Session = scoped_session(sessionmaker(self.engine))
|
self.Session = scoped_session(sessionmaker(self.engine))
|
||||||
|
|
||||||
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
def info(self, ctxt, publisher_id, event_type, payload, metadata):
|
||||||
@ -46,12 +58,13 @@ class NotificationEndpoint(object):
|
|||||||
else:
|
else:
|
||||||
LOG.error("Status update or unknown")
|
LOG.error("Status update or unknown")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
logging.register_options(CONF)
|
logging.register_options(CONF)
|
||||||
extra_log_level_defaults = ['tatu=DEBUG', '__main__=DEBUG']
|
extra_log_level_defaults = ['tatu=DEBUG', '__main__=DEBUG']
|
||||||
logging.set_defaults(
|
logging.set_defaults(
|
||||||
default_log_levels=logging.get_default_log_levels() +
|
default_log_levels=logging.get_default_log_levels() +
|
||||||
extra_log_level_defaults)
|
extra_log_level_defaults)
|
||||||
logging.setup(CONF, DOMAIN)
|
logging.setup(CONF, DOMAIN)
|
||||||
|
|
||||||
transport = oslo_messaging.get_notification_transport(CONF)
|
transport = oslo_messaging.get_notification_transport(CONF)
|
||||||
@ -74,5 +87,6 @@ def main():
|
|||||||
server.stop()
|
server.stop()
|
||||||
server.wait()
|
server.wait()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
@ -1,4 +1,14 @@
|
|||||||
# coding=utf-8
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# 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 json
|
||||||
import falcon
|
import falcon
|
||||||
from falcon import testing
|
from falcon import testing
|
||||||
@ -12,14 +22,17 @@ from Crypto.PublicKey import RSA
|
|||||||
import sshpubkeys
|
import sshpubkeys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def db():
|
def db():
|
||||||
return SQLAlchemySessionManager()
|
return SQLAlchemySessionManager()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def client(db):
|
def client(db):
|
||||||
api = create_app(db)
|
api = create_app(db)
|
||||||
return testing.TestClient(api)
|
return testing.TestClient(api)
|
||||||
|
|
||||||
|
|
||||||
token_id = ''
|
token_id = ''
|
||||||
|
|
||||||
@ -36,404 +49,438 @@ user_fingerprint = sshpubkeys.SSHKey(user_pub_key).hash_md5()
|
|||||||
auth_id = random_uuid()
|
auth_id = random_uuid()
|
||||||
auth_user_pub_key = None
|
auth_user_pub_key = None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency()
|
@pytest.mark.dependency()
|
||||||
def test_post_authority(client, auth_id=auth_id):
|
def test_post_authority(client, auth_id=auth_id):
|
||||||
body = {
|
body = {
|
||||||
'auth_id': auth_id,
|
'auth_id': auth_id,
|
||||||
}
|
}
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/authorities',
|
'/authorities',
|
||||||
body=json.dumps(body)
|
body=json.dumps(body)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_CREATED
|
assert response.status == falcon.HTTP_CREATED
|
||||||
assert response.headers['location'] == '/authorities/' + auth_id
|
assert response.headers['location'] == '/authorities/' + auth_id
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=['test_post_authority'])
|
@pytest.mark.dependency(depends=['test_post_authority'])
|
||||||
def test_post_authority_duplicate(client):
|
def test_post_authority_duplicate(client):
|
||||||
body = {
|
body = {
|
||||||
'auth_id': auth_id,
|
'auth_id': auth_id,
|
||||||
}
|
}
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/authorities',
|
'/authorities',
|
||||||
body=json.dumps(body)
|
body=json.dumps(body)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_CONFLICT
|
assert response.status == falcon.HTTP_CONFLICT
|
||||||
|
|
||||||
|
|
||||||
def test_post_no_body(client):
|
def test_post_no_body(client):
|
||||||
for path in ['/authorities', '/usercerts', '/hosttokens',
|
for path in ['/authorities', '/usercerts', '/hosttokens',
|
||||||
'/hostcerts', '/novavendordata']:
|
'/hostcerts', '/novavendordata']:
|
||||||
response = client.simulate_post(path)
|
response = client.simulate_post(path)
|
||||||
assert response.status == falcon.HTTP_BAD_REQUEST
|
assert response.status == falcon.HTTP_BAD_REQUEST
|
||||||
|
|
||||||
|
|
||||||
def test_post_empty_body(client):
|
def test_post_empty_body(client):
|
||||||
bodystr = json.dumps({})
|
bodystr = json.dumps({})
|
||||||
for path in ['/authorities', '/usercerts', '/hosttokens',
|
for path in ['/authorities', '/usercerts', '/hosttokens',
|
||||||
'/hostcerts', '/novavendordata']:
|
'/hostcerts', '/novavendordata']:
|
||||||
response = client.simulate_post(path, body=bodystr)
|
response = client.simulate_post(path, body=bodystr)
|
||||||
assert response.status == falcon.HTTP_BAD_REQUEST
|
assert response.status == falcon.HTTP_BAD_REQUEST
|
||||||
|
|
||||||
|
|
||||||
def test_post_authority_bad_uuid(client):
|
def test_post_authority_bad_uuid(client):
|
||||||
body = {
|
body = {
|
||||||
'auth_id': 'foobar',
|
'auth_id': 'foobar',
|
||||||
}
|
}
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/authorities',
|
'/authorities',
|
||||||
body=json.dumps(body)
|
body=json.dumps(body)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_BAD_REQUEST
|
assert response.status == falcon.HTTP_BAD_REQUEST
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=['test_post_authority'])
|
@pytest.mark.dependency(depends=['test_post_authority'])
|
||||||
def test_get_authority(client):
|
def test_get_authority(client):
|
||||||
response = client.simulate_get('/authorities/' + auth_id)
|
response = client.simulate_get('/authorities/' + auth_id)
|
||||||
assert response.status == falcon.HTTP_OK
|
assert response.status == falcon.HTTP_OK
|
||||||
body = json.loads(response.content)
|
body = json.loads(response.content)
|
||||||
assert 'auth_id' in body
|
assert 'auth_id' in body
|
||||||
assert 'user_key.pub' in body
|
assert 'user_key.pub' in body
|
||||||
global auth_user_pub_key
|
global auth_user_pub_key
|
||||||
auth_user_pub_key = body['user_key.pub']
|
auth_user_pub_key = body['user_key.pub']
|
||||||
assert 'host_key.pub' in body
|
assert 'host_key.pub' in body
|
||||||
assert 'user_key' not in body
|
assert 'user_key' not in body
|
||||||
assert 'host_key' not in body
|
assert 'host_key' not in body
|
||||||
|
|
||||||
|
|
||||||
def test_get_authority_doesnt_exist(client):
|
def test_get_authority_doesnt_exist(client):
|
||||||
response = client.simulate_get('/authorities/' + random_uuid())
|
response = client.simulate_get('/authorities/' + random_uuid())
|
||||||
assert response.status == falcon.HTTP_NOT_FOUND
|
assert response.status == falcon.HTTP_NOT_FOUND
|
||||||
|
|
||||||
|
|
||||||
def test_get_authority_with_bad_uuid(client):
|
def test_get_authority_with_bad_uuid(client):
|
||||||
response = client.simulate_get('/authorities/foobar')
|
response = client.simulate_get('/authorities/foobar')
|
||||||
assert response.status == falcon.HTTP_BAD_REQUEST
|
assert response.status == falcon.HTTP_BAD_REQUEST
|
||||||
|
|
||||||
|
|
||||||
def user_request(auth=auth_id, user_id=user_id, pub_key=user_pub_key):
|
def user_request(auth=auth_id, user_id=user_id, pub_key=user_pub_key):
|
||||||
return {
|
return {
|
||||||
'user_id': user_id,
|
'user_id': user_id,
|
||||||
'auth_id': auth,
|
'auth_id': auth,
|
||||||
'key.pub': pub_key
|
'key.pub': pub_key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_post_user_bad_uuid(client):
|
def test_post_user_bad_uuid(client):
|
||||||
for key in ['user_id', 'auth_id']:
|
for key in ['user_id', 'auth_id']:
|
||||||
body = user_request()
|
body = user_request()
|
||||||
body[key] = 'foobar'
|
body[key] = 'foobar'
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/usercerts',
|
'/usercerts',
|
||||||
body=json.dumps(body)
|
body=json.dumps(body)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_BAD_REQUEST
|
assert response.status == falcon.HTTP_BAD_REQUEST
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=['test_post_authority'])
|
@pytest.mark.dependency(depends=['test_post_authority'])
|
||||||
def test_post_user(client):
|
def test_post_user(client):
|
||||||
body = user_request()
|
body = user_request()
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/usercerts',
|
'/usercerts',
|
||||||
body=json.dumps(body)
|
body=json.dumps(body)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_CREATED
|
assert response.status == falcon.HTTP_CREATED
|
||||||
assert 'location' in response.headers
|
assert 'location' in response.headers
|
||||||
location = response.headers['location'].split('/')
|
location = response.headers['location'].split('/')
|
||||||
assert location[1] == 'usercerts'
|
assert location[1] == 'usercerts'
|
||||||
assert location[2] == body['user_id']
|
assert location[2] == body['user_id']
|
||||||
assert location[3] == sshpubkeys.SSHKey(body['key.pub']).hash_md5()
|
assert location[3] == sshpubkeys.SSHKey(body['key.pub']).hash_md5()
|
||||||
|
|
||||||
|
|
||||||
@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
|
||||||
assert 'fingerprint' in body
|
assert 'fingerprint' in body
|
||||||
assert 'auth_id' in body
|
assert 'auth_id' in body
|
||||||
assert 'key-cert.pub' in body
|
assert 'key-cert.pub' in body
|
||||||
assert body['auth_id'] == auth_id
|
assert body['auth_id'] == auth_id
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
def test_get_user_with_bad_uuid(client):
|
def test_get_user_with_bad_uuid(client):
|
||||||
response = client.simulate_get('/usercerts/foobar/' + user_fingerprint)
|
response = client.simulate_get('/usercerts/foobar/' + user_fingerprint)
|
||||||
assert response.status == falcon.HTTP_BAD_REQUEST
|
assert response.status == falcon.HTTP_BAD_REQUEST
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=['test_post_user'])
|
@pytest.mark.dependency(depends=['test_post_user'])
|
||||||
def test_post_second_cert_same_user(client):
|
def test_post_second_cert_same_user(client):
|
||||||
key = RSA.generate(2048)
|
key = RSA.generate(2048)
|
||||||
pub_key = key.publickey().exportKey('OpenSSH')
|
pub_key = key.publickey().exportKey('OpenSSH')
|
||||||
body = user_request(pub_key=pub_key)
|
body = user_request(pub_key=pub_key)
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/usercerts',
|
'/usercerts',
|
||||||
body=json.dumps(body)
|
body=json.dumps(body)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_CREATED
|
assert response.status == falcon.HTTP_CREATED
|
||||||
assert 'location' in response.headers
|
assert 'location' in response.headers
|
||||||
location = response.headers['location'].split('/')
|
location = response.headers['location'].split('/')
|
||||||
assert location[1] == 'usercerts'
|
assert location[1] == 'usercerts'
|
||||||
assert location[2] == user_id
|
assert location[2] == user_id
|
||||||
assert location[3] == sshpubkeys.SSHKey(pub_key).hash_md5()
|
assert location[3] == sshpubkeys.SSHKey(pub_key).hash_md5()
|
||||||
|
|
||||||
|
|
||||||
def test_post_user_unknown_auth(client):
|
def test_post_user_unknown_auth(client):
|
||||||
body = user_request(auth=random_uuid())
|
body = user_request(auth=random_uuid())
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/usercerts',
|
'/usercerts',
|
||||||
body=json.dumps(body)
|
body=json.dumps(body)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_NOT_FOUND
|
assert response.status == falcon.HTTP_NOT_FOUND
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=['test_post_user'])
|
@pytest.mark.dependency(depends=['test_post_user'])
|
||||||
def test_post_same_user_and_key_returns_same_result(client):
|
def test_post_same_user_and_key_returns_same_result(client):
|
||||||
test_post_user(client)
|
test_post_user(client)
|
||||||
|
|
||||||
|
|
||||||
def token_request(auth=auth_id, host=host_id):
|
def token_request(auth=auth_id, host=host_id):
|
||||||
return {
|
return {
|
||||||
'host_id': host,
|
'host_id': host,
|
||||||
'auth_id': auth,
|
'auth_id': auth,
|
||||||
'hostname': 'testname.local'
|
'hostname': 'testname.local'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def host_request(token, host=host_id, pub_key=host_pub_key):
|
def host_request(token, host=host_id, pub_key=host_pub_key):
|
||||||
return {
|
return {
|
||||||
'token_id': token,
|
'token_id': token,
|
||||||
'host_id': host,
|
'host_id': host,
|
||||||
'key.pub': pub_key
|
'key.pub': pub_key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def vendordata_request(auth, host):
|
def vendordata_request(auth, host):
|
||||||
return {
|
return {
|
||||||
'instance-id': host,
|
'instance-id': host,
|
||||||
'project-id': auth,
|
'project-id': auth,
|
||||||
'hostname': 'mytest.testing'
|
'hostname': 'mytest.testing'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_post_vendordata_bad_uuid(client):
|
def test_post_vendordata_bad_uuid(client):
|
||||||
for key in ['instance-id', 'project-id']:
|
for key in ['instance-id', 'project-id']:
|
||||||
body = vendordata_request(auth_id, host_id)
|
body = vendordata_request(auth_id, host_id)
|
||||||
body[key] = 'foobar'
|
body[key] = 'foobar'
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/novavendordata',
|
'/novavendordata',
|
||||||
body=json.dumps(body)
|
body=json.dumps(body)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_BAD_REQUEST
|
assert response.status == falcon.HTTP_BAD_REQUEST
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=['test_post_authority'])
|
@pytest.mark.dependency(depends=['test_post_authority'])
|
||||||
def test_post_novavendordata(client):
|
def test_post_novavendordata(client):
|
||||||
req = vendordata_request(auth_id, random_uuid())
|
req = vendordata_request(auth_id, random_uuid())
|
||||||
response = client.simulate_post(
|
|
||||||
'/novavendordata',
|
|
||||||
body=json.dumps(req)
|
|
||||||
)
|
|
||||||
assert response.status == falcon.HTTP_CREATED
|
|
||||||
assert 'location' in response.headers
|
|
||||||
location_path = response.headers['location'].split('/')
|
|
||||||
assert location_path[1] == 'hosttokens'
|
|
||||||
vendordata = json.loads(response.content)
|
|
||||||
assert 'token' in vendordata
|
|
||||||
assert vendordata['token'] == location_path[-1]
|
|
||||||
assert 'auth_pub_key_user' in vendordata
|
|
||||||
assert vendordata['auth_pub_key_user'] == auth_user_pub_key
|
|
||||||
assert 'principals' in vendordata
|
|
||||||
assert vendordata['principals'] == 'admin'
|
|
||||||
|
|
||||||
def test_post_token_bad_uuid(client):
|
|
||||||
for key in ['auth_id', 'host_id']:
|
|
||||||
body = token_request()
|
|
||||||
body[key] = 'foobar'
|
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/hosttokens',
|
'/novavendordata',
|
||||||
body=json.dumps(body)
|
body=json.dumps(req)
|
||||||
)
|
|
||||||
assert response.status == falcon.HTTP_BAD_REQUEST
|
|
||||||
|
|
||||||
def test_post_host_bad_uuid(client):
|
|
||||||
for key in ['token_id', 'host_id']:
|
|
||||||
body = host_request(random_uuid())
|
|
||||||
body[key] = 'foobar'
|
|
||||||
response = client.simulate_post(
|
|
||||||
'/hosttokens',
|
|
||||||
body=json.dumps(body)
|
|
||||||
)
|
|
||||||
assert response.status == falcon.HTTP_BAD_REQUEST
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=['test_post_authority'])
|
|
||||||
def test_post_token_and_host(client):
|
|
||||||
token = token_request()
|
|
||||||
response = client.simulate_post(
|
|
||||||
'/hosttokens',
|
|
||||||
body=json.dumps(token)
|
|
||||||
)
|
|
||||||
assert response.status == falcon.HTTP_CREATED
|
|
||||||
assert 'location' in response.headers
|
|
||||||
location_path = response.headers['location'].split('/')
|
|
||||||
assert location_path[1] == 'hosttokens'
|
|
||||||
# Store the token ID for other tests
|
|
||||||
global token_id
|
|
||||||
token_id = location_path[-1]
|
|
||||||
# Verify that it's a valid UUID
|
|
||||||
uuid.UUID(token_id, version=4)
|
|
||||||
host = host_request(token_id)
|
|
||||||
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
|
|
||||||
# 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()
|
|
||||||
test_post_authority(client, my_auth_id)
|
|
||||||
# Generate a single RSA key pair and reuse it - it takes a few seconds.
|
|
||||||
key = RSA.generate(2048)
|
|
||||||
pub_key = key.publickey().exportKey('OpenSSH')
|
|
||||||
fingerprint = sshpubkeys.SSHKey(pub_key).hash_md5()
|
|
||||||
# Should do about 15 iterations/second, so only do 4 seconds worth.
|
|
||||||
start = time.time()
|
|
||||||
for i in range(60):
|
|
||||||
hid = random_uuid()
|
|
||||||
token = token_request(auth=my_auth_id, host=hid)
|
|
||||||
response = client.simulate_post(
|
|
||||||
'/hosttokens',
|
|
||||||
body=json.dumps(token)
|
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_CREATED
|
assert response.status == falcon.HTTP_CREATED
|
||||||
assert 'location' in response.headers
|
assert 'location' in response.headers
|
||||||
location_path = response.headers['location'].split('/')
|
location_path = response.headers['location'].split('/')
|
||||||
assert location_path[1] == 'hosttokens'
|
assert location_path[1] == 'hosttokens'
|
||||||
|
vendordata = json.loads(response.content)
|
||||||
|
assert 'token' in vendordata
|
||||||
|
assert vendordata['token'] == location_path[-1]
|
||||||
|
assert 'auth_pub_key_user' in vendordata
|
||||||
|
assert vendordata['auth_pub_key_user'] == auth_user_pub_key
|
||||||
|
assert 'principals' in vendordata
|
||||||
|
assert vendordata['principals'] == 'admin'
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_token_bad_uuid(client):
|
||||||
|
for key in ['auth_id', 'host_id']:
|
||||||
|
body = token_request()
|
||||||
|
body[key] = 'foobar'
|
||||||
|
response = client.simulate_post(
|
||||||
|
'/hosttokens',
|
||||||
|
body=json.dumps(body)
|
||||||
|
)
|
||||||
|
assert response.status == falcon.HTTP_BAD_REQUEST
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_host_bad_uuid(client):
|
||||||
|
for key in ['token_id', 'host_id']:
|
||||||
|
body = host_request(random_uuid())
|
||||||
|
body[key] = 'foobar'
|
||||||
|
response = client.simulate_post(
|
||||||
|
'/hosttokens',
|
||||||
|
body=json.dumps(body)
|
||||||
|
)
|
||||||
|
assert response.status == falcon.HTTP_BAD_REQUEST
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.dependency(depends=['test_post_authority'])
|
||||||
|
def test_post_token_and_host(client):
|
||||||
|
token = token_request()
|
||||||
|
response = client.simulate_post(
|
||||||
|
'/hosttokens',
|
||||||
|
body=json.dumps(token)
|
||||||
|
)
|
||||||
|
assert response.status == falcon.HTTP_CREATED
|
||||||
|
assert 'location' in response.headers
|
||||||
|
location_path = response.headers['location'].split('/')
|
||||||
|
assert location_path[1] == 'hosttokens'
|
||||||
|
# Store the token ID for other tests
|
||||||
|
global token_id
|
||||||
token_id = location_path[-1]
|
token_id = location_path[-1]
|
||||||
# Verify that it's a valid UUID
|
# Verify that it's a valid UUID
|
||||||
uuid.UUID(token_id, version=4)
|
uuid.UUID(token_id, version=4)
|
||||||
host = host_request(token_id, host=hid, pub_key=pub_key)
|
host = host_request(token_id)
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/hostcerts',
|
'/hostcerts',
|
||||||
body=json.dumps(host)
|
body=json.dumps(host)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_CREATED
|
assert response.status == falcon.HTTP_CREATED
|
||||||
assert 'location' in response.headers
|
assert 'location' in response.headers
|
||||||
location = response.headers['location'].split('/')
|
location = response.headers['location'].split('/')
|
||||||
assert location[1] == 'hostcerts'
|
assert location[1] == 'hostcerts'
|
||||||
assert location[2] == hid
|
assert location[2] == host_id
|
||||||
assert location[3] == fingerprint
|
assert location[3] == host_fingerprint
|
||||||
assert time.time() - start < 5
|
# 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()
|
||||||
|
test_post_authority(client, my_auth_id)
|
||||||
|
# Generate a single RSA key pair and reuse it - it takes a few seconds.
|
||||||
|
key = RSA.generate(2048)
|
||||||
|
pub_key = key.publickey().exportKey('OpenSSH')
|
||||||
|
fingerprint = sshpubkeys.SSHKey(pub_key).hash_md5()
|
||||||
|
# Should do about 15 iterations/second, so only do 4 seconds worth.
|
||||||
|
start = time.time()
|
||||||
|
for i in range(60):
|
||||||
|
hid = random_uuid()
|
||||||
|
token = token_request(auth=my_auth_id, host=hid)
|
||||||
|
response = client.simulate_post(
|
||||||
|
'/hosttokens',
|
||||||
|
body=json.dumps(token)
|
||||||
|
)
|
||||||
|
assert response.status == falcon.HTTP_CREATED
|
||||||
|
assert 'location' in response.headers
|
||||||
|
location_path = response.headers['location'].split('/')
|
||||||
|
assert location_path[1] == 'hosttokens'
|
||||||
|
token_id = location_path[-1]
|
||||||
|
# Verify that it's a valid UUID
|
||||||
|
uuid.UUID(token_id, version=4)
|
||||||
|
host = host_request(token_id, host=hid, pub_key=pub_key)
|
||||||
|
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] == hid
|
||||||
|
assert location[3] == fingerprint
|
||||||
|
assert time.time() - start < 5
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=['test_post_token_and_host'])
|
@pytest.mark.dependency(depends=['test_post_token_and_host'])
|
||||||
def test_post_token_same_host_id(client):
|
def test_post_token_same_host_id(client):
|
||||||
# Posting with the same host ID should return the same token
|
# Posting with the same host ID should return the same token
|
||||||
token = token_request()
|
token = token_request()
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/hosttokens',
|
'/hosttokens',
|
||||||
body=json.dumps(token)
|
body=json.dumps(token)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_CREATED
|
assert response.status == falcon.HTTP_CREATED
|
||||||
assert 'location' in response.headers
|
assert 'location' in response.headers
|
||||||
location_path = response.headers['location'].split('/')
|
location_path = response.headers['location'].split('/')
|
||||||
assert location_path[1] == 'hosttokens'
|
assert location_path[1] == 'hosttokens'
|
||||||
# The token id should be the same as that from the previous test.
|
# The token id should be the same as that from the previous test.
|
||||||
assert token_id == location_path[-1]
|
assert token_id == location_path[-1]
|
||||||
|
|
||||||
|
|
||||||
@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
|
||||||
assert 'fingerprint' in body
|
assert 'fingerprint' in body
|
||||||
assert 'auth_id' in body
|
assert 'auth_id' in body
|
||||||
assert 'key-cert.pub' in body
|
assert 'key-cert.pub' in body
|
||||||
assert body['host_id'] == host_id
|
assert body['host_id'] == host_id
|
||||||
assert body['fingerprint'] == host_fingerprint
|
assert body['fingerprint'] == host_fingerprint
|
||||||
assert body['auth_id'] == auth_id
|
assert body['auth_id'] == auth_id
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
def test_get_host_with_bad_uuid(client):
|
def test_get_host_with_bad_uuid(client):
|
||||||
response = client.simulate_get('/hostcerts/foobar/' + host_fingerprint)
|
response = client.simulate_get('/hostcerts/foobar/' + host_fingerprint)
|
||||||
assert response.status == falcon.HTTP_BAD_REQUEST
|
assert response.status == falcon.HTTP_BAD_REQUEST
|
||||||
|
|
||||||
|
|
||||||
def test_post_token_unknown_auth(client):
|
def test_post_token_unknown_auth(client):
|
||||||
token = token_request(auth=random_uuid())
|
token = token_request(auth=random_uuid())
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/hosttokens',
|
'/hosttokens',
|
||||||
body=json.dumps(token)
|
body=json.dumps(token)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_NOT_FOUND
|
assert response.status == falcon.HTTP_NOT_FOUND
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=['test_post_authority'])
|
@pytest.mark.dependency(depends=['test_post_authority'])
|
||||||
def test_post_host_with_bogus_token(client):
|
def test_post_host_with_bogus_token(client):
|
||||||
host = host_request(random_uuid(), random_uuid())
|
host = host_request(random_uuid(), random_uuid())
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/hostcerts',
|
'/hostcerts',
|
||||||
body=json.dumps(host)
|
body=json.dumps(host)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_NOT_FOUND
|
assert response.status == falcon.HTTP_NOT_FOUND
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=['test_post_token_and_host'])
|
@pytest.mark.dependency(depends=['test_post_token_and_host'])
|
||||||
def test_post_host_with_wrong_host_id(client):
|
def test_post_host_with_wrong_host_id(client):
|
||||||
# Get a new token for the same host_id as the base test.
|
# Get a new token for the same host_id as the base test.
|
||||||
token = token_request(host=random_uuid())
|
token = token_request(host=random_uuid())
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/hosttokens',
|
'/hosttokens',
|
||||||
body=json.dumps(token)
|
body=json.dumps(token)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_CREATED
|
assert response.status == falcon.HTTP_CREATED
|
||||||
assert 'location' in response.headers
|
assert 'location' in response.headers
|
||||||
location_path = response.headers['location'].split('/')
|
location_path = response.headers['location'].split('/')
|
||||||
assert location_path[1] == 'hosttokens'
|
assert location_path[1] == 'hosttokens'
|
||||||
# Use the token with a different host_id than it was created for.
|
# Use the token with a different host_id than it was created for.
|
||||||
# Use a different public key to avoid other error conditions.
|
# Use a different public key to avoid other error conditions.
|
||||||
key = RSA.generate(2048)
|
key = RSA.generate(2048)
|
||||||
pub_key = key.publickey().exportKey('OpenSSH')
|
pub_key = key.publickey().exportKey('OpenSSH')
|
||||||
host = host_request(location_path[-1], random_uuid(), pub_key)
|
host = host_request(location_path[-1], random_uuid(), pub_key)
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/hostcerts',
|
'/hostcerts',
|
||||||
body=json.dumps(host)
|
body=json.dumps(host)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_CONFLICT
|
assert response.status == falcon.HTTP_CONFLICT
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=['test_post_token_and_host'])
|
@pytest.mark.dependency(depends=['test_post_token_and_host'])
|
||||||
def test_post_host_different_public_key_fails(client):
|
def test_post_host_different_public_key_fails(client):
|
||||||
# Use the same token compared to the test this depends on.
|
# Use the same token compared to the test this depends on.
|
||||||
# Show that using the same host ID and different public key fails.
|
# Show that using the same host ID and different public key fails.
|
||||||
token = token_request()
|
token = token_request()
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/hosttokens',
|
'/hosttokens',
|
||||||
body=json.dumps(token)
|
body=json.dumps(token)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_CREATED
|
assert response.status == falcon.HTTP_CREATED
|
||||||
assert 'location' in response.headers
|
assert 'location' in response.headers
|
||||||
location_path = response.headers['location'].split('/')
|
location_path = response.headers['location'].split('/')
|
||||||
assert location_path[1] == 'hosttokens'
|
assert location_path[1] == 'hosttokens'
|
||||||
key = RSA.generate(2048)
|
key = RSA.generate(2048)
|
||||||
pub_key = key.publickey().exportKey('OpenSSH')
|
pub_key = key.publickey().exportKey('OpenSSH')
|
||||||
host = host_request(location_path[-1], pub_key=pub_key)
|
host = host_request(location_path[-1], pub_key=pub_key)
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/hostcerts',
|
'/hostcerts',
|
||||||
body=json.dumps(host)
|
body=json.dumps(host)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_CONFLICT
|
assert response.status == falcon.HTTP_CONFLICT
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.dependency(depends=['test_post_token_and_host'])
|
@pytest.mark.dependency(depends=['test_post_token_and_host'])
|
||||||
def test_post_host_with_used_token(client):
|
def test_post_host_with_used_token(client):
|
||||||
# Re-use the token from the test this depends on.
|
# Re-use the token from the test this depends on.
|
||||||
# Use the same host_id and different public key to avoid other errors.
|
# Use the same host_id and different public key to avoid other errors.
|
||||||
key = RSA.generate(2048)
|
key = RSA.generate(2048)
|
||||||
pub_key = key.publickey().exportKey('OpenSSH')
|
pub_key = key.publickey().exportKey('OpenSSH')
|
||||||
host = host_request(token_id, host_id, pub_key)
|
host = host_request(token_id, host_id, pub_key)
|
||||||
response = client.simulate_post(
|
response = client.simulate_post(
|
||||||
'/hostcerts',
|
'/hostcerts',
|
||||||
body=json.dumps(host)
|
body=json.dumps(host)
|
||||||
)
|
)
|
||||||
assert response.status == falcon.HTTP_CONFLICT
|
assert response.status == falcon.HTTP_CONFLICT
|
||||||
|
@ -1,45 +1,59 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# 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 os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
def random_uuid():
|
def random_uuid():
|
||||||
return str(uuid.uuid4())
|
return str(uuid.uuid4())
|
||||||
|
|
||||||
|
|
||||||
def generateCert(auth_key, entity_key, hostname=None, principals='root'):
|
def generateCert(auth_key, entity_key, hostname=None, principals='root'):
|
||||||
# Temporarily write the authority private key and entity public key to files
|
# Temporarily write the authority private key and entity public key to files
|
||||||
prefix = uuid.uuid4().hex
|
prefix = uuid.uuid4().hex
|
||||||
# Todo: make the temporary directory configurable or secure it.
|
# Todo: make the temporary directory configurable or secure it.
|
||||||
dir = '/tmp/sshaas'
|
dir = '/tmp/sshaas'
|
||||||
ca_file = ''.join([dir, prefix])
|
ca_file = ''.join([dir, prefix])
|
||||||
pub_file = ''.join([dir, prefix, '.pub'])
|
pub_file = ''.join([dir, prefix, '.pub'])
|
||||||
cert_file = ''.join([dir, prefix, '-cert.pub'])
|
cert_file = ''.join([dir, prefix, '-cert.pub'])
|
||||||
cert = ''
|
|
||||||
try:
|
|
||||||
fd = os.open(ca_file, os.O_WRONLY | os.O_CREAT, 0o600)
|
|
||||||
os.close(fd)
|
|
||||||
with open(ca_file, "w") as text_file:
|
|
||||||
text_file.write(auth_key)
|
|
||||||
with open(pub_file, "w", 0o644) as text_file:
|
|
||||||
text_file.write(entity_key)
|
|
||||||
args = ['ssh-keygen', '-s', ca_file, '-I', 'testID', '-V',
|
|
||||||
'-1d:+365d']
|
|
||||||
if hostname is None:
|
|
||||||
args.extend(['-n', principals, pub_file])
|
|
||||||
else:
|
|
||||||
args.extend(['-h', pub_file])
|
|
||||||
subprocess.check_output(args, stderr=subprocess.STDOUT)
|
|
||||||
# Read the contents of the certificate file
|
|
||||||
cert = ''
|
cert = ''
|
||||||
with open(cert_file, 'r') as text_file:
|
try:
|
||||||
cert = text_file.read()
|
fd = os.open(ca_file, os.O_WRONLY | os.O_CREAT, 0o600)
|
||||||
except Exception as e:
|
os.close(fd)
|
||||||
print e
|
with open(ca_file, "w") as text_file:
|
||||||
finally:
|
text_file.write(auth_key)
|
||||||
# Delete temporary files
|
with open(pub_file, "w", 0o644) as text_file:
|
||||||
for file in [ca_file, pub_file, cert_file]:
|
text_file.write(entity_key)
|
||||||
try:
|
args = ['ssh-keygen', '-s', ca_file, '-I', 'testID', '-V',
|
||||||
os.remove(file)
|
'-1d:+365d']
|
||||||
pass
|
if hostname is None:
|
||||||
except:
|
args.extend(['-n', principals, pub_file])
|
||||||
pass
|
else:
|
||||||
return cert
|
args.extend(['-h', pub_file])
|
||||||
|
subprocess.check_output(args, stderr=subprocess.STDOUT)
|
||||||
|
# Read the contents of the certificate file
|
||||||
|
cert = ''
|
||||||
|
with open(cert_file, 'r') as text_file:
|
||||||
|
cert = text_file.read()
|
||||||
|
except Exception as e:
|
||||||
|
print e
|
||||||
|
finally:
|
||||||
|
# Delete temporary files
|
||||||
|
for file in [ca_file, pub_file, cert_file]:
|
||||||
|
try:
|
||||||
|
os.remove(file)
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return cert
|
||||||
|
Loading…
x
Reference in New Issue
Block a user