diff --git a/files/paste.ini b/files/paste.ini index 1a1b646..403662f 100644 --- a/files/paste.ini +++ b/files/paste.ini @@ -5,7 +5,7 @@ port = 18322 [composite:main] use = egg:Paste#urlmap -/ = auth +/ = myapp /noauth = myapp [pipeline:auth] @@ -16,7 +16,7 @@ paste.filter_factory = keystonemiddleware.auth_token:filter_factory www_authenticate_uri = http://localhost/identity identity_uri = http://localhost/identity #auth_version = v2 -admin_token = gAAAAABaO-LnZQ03QZArlHYnXJL9Lg6valCBRUQ0n4eu4JOhIR3lHnxxoNuK1Zod41V_xDbkEqk75BO5rdvjuwDqDNOptje6E-XsE4dCu1WFJAhVyzLDd9DLctlNoeY8fnia-L8VacaNWQQ3EGX3uay434ZOErqKJ6Joxal11cG6u7VmYtu10xQ +admin_token = gAAAAABaPEXR3jF2TqsraXh7qkpKOiPcVbnmHdMEsrSYRKUnfQvpCAR9Sq02vDZNcQLoodVLdxu449zc0AwGXeleRMuf7pEPBgjHPfR6ykkYV6_WXNuMt_Cmqvo_fnRdvPh8bJzjsWEqvEM9s_aCM-le8DkaatoacDrUDA3odBAP1AVdJ0PdJdM #admin_user = nova #admin_password = pinot #admin_tenant_name = service diff --git a/files/tatu.conf b/files/tatu.conf index 9f3dc29..9c378cb 100644 --- a/files/tatu.conf +++ b/files/tatu.conf @@ -2,11 +2,13 @@ [tatu] use_barbican_key_manager = True - -[key_manager] -auth_url = http://147.75.72.229/identity -auth_type = keystone_password -username = admin +#use_pat_bastions = True +#num_total_pats = 3 +#num_pat_bastions_per_server = 2 +#pat_dns_zone_name = tatuPAT.com. +#pat_dns_zone_email = tatu@nono.nono +#sqlalchemy_engine = mysql+pymysql://root:pinot@127.0.0.1/neutron?charset=utf8 +auth_url = http://localhost/identity/v3 +user_id = fab01a1f2a7749b78a53dffe441a1879 password = pinot project_id = 2e6c998ad16f4045821304470a57d160 -user_domain_name = default diff --git a/scripts/add-project-ca b/scripts/add-project-ca index 0c34e77..c6af0aa 100755 --- a/scripts/add-project-ca +++ b/scripts/add-project-ca @@ -11,13 +11,25 @@ # License for the specific language governing permissions and limitations # under the License. +import argparse import json import requests import sys import uuid -server = 'http://172.24.4.1:18322' -auth_id = str(uuid.UUID(sys.argv[1], version=4)) +parser = argparse.ArgumentParser(description="Get the CA's public keys from Tatu API.") +parser.add_argument('--projid', '-P', required=True) +parser.add_argument('--tatu-url', default= 'http://127.0.0.1:18322', + help='URL of the Tatu API') +args = parser.parse_args() + +try: + auth_id = str(uuid.UUID(args.projid, version=4)) +except: + print '--projid should be the UUID of a Tatu CA (usually a cloud tenant/project).' + exit() + +server = args.tatu_url response = requests.post( server + '/authorities', data=json.dumps({'auth_id': auth_id}) diff --git a/tatu/api/models.py b/tatu/api/models.py index 23eb57e..bc724e8 100644 --- a/tatu/api/models.py +++ b/tatu/api/models.py @@ -86,6 +86,24 @@ class Authorities(object): resp.status = falcon.HTTP_201 resp.location = '/authorities/' + req.body['auth_id'] + @falcon.before(validate) + def on_get(self, req, resp): + auths = db.getAuthorities(self.session) + items = [] + for auth in auths: + 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') + items.append({ + 'auth_id': auth.auth_id, + 'user_key.pub': user_pub_key, + 'host_key.pub': host_pub_key + }) + body = {'items': items} + resp.body = json.dumps(body) + resp.status = falcon.HTTP_OK + class Authority(object): @falcon.before(validate) diff --git a/tatu/castellano.py b/tatu/castellano.py index 21ad494..ac1365e 100644 --- a/tatu/castellano.py +++ b/tatu/castellano.py @@ -17,48 +17,35 @@ from castellan.key_manager.key_manager import KeyManager from oslo_config import cfg from oslo_log import log as logging +from tatu.config import CONTEXT + LOG = logging.getLogger(__name__) -_context = None -_api = None +_api = API() -def context(): - global _context - if _context is None and cfg.CONF.tatu.use_barbican_key_manager: - _context = credential_factory(conf=cfg.CONF) - return _context - - -def api(): - global _api - if _api is None: - _api = API() - return _api - - -def delete_secret(id, ctx=None): +def delete_secret(id): """delete a secret from the external key manager :param id: The identifier of the secret to delete :param ctx: The context, and associated authentication, to use with this operation (defaults to the current context) """ - api().delete(ctx or context(), id) + _api.delete(CONTEXT, id) -def get_secret(id, ctx=None): +def get_secret(id): """get a secret associated with an id :param id: The identifier of the secret to retrieve :param ctx: The context, and associated authentication, to use with this operation (defaults to the current context) """ - key = api().get(ctx or context(), id) + key = _api.get(CONTEXT, id) return key.get_encoded() -def store_secret(secret, ctx=None): +def store_secret(secret): """store a secret and return its identifier :param secret: The secret to store, this should be a string @@ -66,7 +53,7 @@ def store_secret(secret, ctx=None): this operation (defaults to the current context) """ key = Passphrase(secret) - return api().store(ctx or context(), key) + return _api.store(CONTEXT, key) """ diff --git a/tatu/config.py b/tatu/config.py index f644144..1fbc304 100644 --- a/tatu/config.py +++ b/tatu/config.py @@ -18,6 +18,7 @@ from keystoneauth1.identity import v3 from novaclient import client as nova_client from neutronclient.v2_0 import client as neutron_client from oslo_config import cfg +from oslo_context import context from oslo_log import log as logging from castellan.options import set_defaults as set_castellan_defaults @@ -63,11 +64,9 @@ logging.register_options(CONF) log_levels = logging.get_default_log_levels() + \ ['tatu=DEBUG', '__main__=DEBUG'] logging.set_defaults(default_log_levels=log_levels) - try: CONF(args=[], default_config_files=['/etc/tatu/tatu.conf', - 'tatu.conf', 'files/tatu.conf' ] ) @@ -92,6 +91,10 @@ NOVA = nova_client.Client('2', session=session) NEUTRON = neutron_client.Client(session=session) DESIGNATE = designate_client.Client(session=session) -dragonflow_cfg.CONF(default_config_files=['/etc/neutron/dragonflow.ini']) +dragonflow_cfg.CONF(args=[], default_config_files=['/etc/neutron/dragonflow.ini']) dragonflow_cfg.CONF.set_override('enable_df_pub_sub', False, group='df') DRAGONFLOW = api_nb.NbApi.get_instance(False) + +# Create a context for use by Castellan +CONTEXT = context.RequestContext(auth_token=auth.get_token(session), + tenant=auth.get_project_id(session)) diff --git a/tatu/db/models.py b/tatu/db/models.py index 7a08fb3..1e637ae 100644 --- a/tatu/db/models.py +++ b/tatu/db/models.py @@ -36,6 +36,10 @@ def getAuthority(session, auth_id): return session.query(Authority).get(auth_id) +def getAuthorities(session): + return session.query(Authority) + + def getAuthUserKey(auth): return get_secret(auth.user_key) diff --git a/tatu/dns.py b/tatu/dns.py index d09f95a..be94fd4 100644 --- a/tatu/dns.py +++ b/tatu/dns.py @@ -26,7 +26,7 @@ def _setup_zone(): ZONE = DESIGNATE.zones.create(CONF.tatu.pat_dns_zone_name, email=CONF.tatu.pat_dns_zone_email) except Conflict: - pass + ZONE = DESIGNATE.zones.get(CONF.tatu.pat_dns_zone_name) def bastion_name_from_ip(ip_address): diff --git a/tatu/pat.py b/tatu/pat.py index 2655a4e..a90d425 100644 --- a/tatu/pat.py +++ b/tatu/pat.py @@ -12,7 +12,7 @@ from dragonflow.db.models.core import Chassis from dragonflow.db.models.l2 import LogicalPort -from dragonflow.db.models.l2 import LogicalRouter +from dragonflow.db.models.l3 import LogicalRouter from dragonflow.db.models.l3 import PAT from dragonflow.db.models.l3 import PATEntry from oslo_log import log as logging @@ -28,15 +28,15 @@ def _sync_pats(): # TODO(pino): re-bind PATs when hypervisors fail (here and on notification) all_chassis = DRAGONFLOW.get_all(Chassis) # Filter the chassis that already have PATS assigned + # TODO: This doesn't work now because the p.chassis is a ChassisProxy free_chassis = set(all_chassis).difference(p.chassis for p in PATS) # Don't make more PATs than there are free chassis num_to_make = min(CONF.tatu.num_total_pats - len(PATS), len(free_chassis)) - if num_to_make <= 0: - return - assigned_chassis = random.sample(free_chassis, num_to_make) - for c in assigned_chassis: - _add_pat(c) + if num_to_make > 0: + assigned_chassis = random.sample(free_chassis, num_to_make) + for c in assigned_chassis: + _add_pat(c) dns.sync_bastions(str(p.ip_address) for p in PATS) @@ -49,16 +49,19 @@ def _add_pat(chassis): "admin_state_up": True, "name": 'TatuPAT', # TODO(pino): set device owner to Tatu? "network_id": network_id, + "port_security_enabled": False, + "security_groups": [], } } neutron_port = NEUTRON.create_port(body) lport = DRAGONFLOW.get(LogicalPort(id=neutron_port['port']['id'])) - ip = get_ip4_from_lport(lport) + ip = _get_ip4_from_lport(lport) pat = PAT( id = str(ip), topic = 'tatu', # TODO(pino): What topic? Admin project_id? ip_address = ip, - lport = lport + lport = lport, + chassis = chassis, ) # We only need to store the PAT in dragonflow's DB, not API/MySQL DRAGONFLOW.create(pat) @@ -72,7 +75,7 @@ def _get_ip4_from_lport(lport): return None -def df_find_lrouter_by_lport(lport): +def _df_find_lrouter_by_lport(lport): lrouters = DRAGONFLOW.get_all(LogicalRouter) for lr in lrouters: for lp in lr.ports: @@ -88,7 +91,7 @@ def create_pat_entries(sql_session, instance_id, fixed_l4_port, for iface in ifaces: lport = DRAGONFLOW.get(LogicalPort(id=iface['port_id'])) # TODO(pino): no router? consider SNAT of source IP to 169.254.169.254 - lrouter = df_find_lrouter_by_lport(lport) + lrouter = _df_find_lrouter_by_lport(lport) if lrouter is None: continue # Reserve N l4 ports on distinct IPs. pats = PATS @@ -97,6 +100,8 @@ def create_pat_entries(sql_session, instance_id, fixed_l4_port, for pat in pats: pat_l4_port = tatu_db.reserve_l4_port(sql_session, str(pat.ip)) pat_entry = PATEntry( + id = '{}:{}'.format(pat.id, pat_l4_port), + topic = 'tatu', pat = pat, pat_l4_port = pat_l4_port, fixed_ip_address = _get_ip4_from_lport(lport), @@ -111,4 +116,4 @@ def create_pat_entries(sql_session, instance_id, fixed_l4_port, return port_ip_tuples -_sync_pats() \ No newline at end of file +_sync_pats()