Draft bastion support and Designate integration.
This commit is contained in:
parent
91c0b33338
commit
27b180f864
@ -18,6 +18,10 @@ pyramid>=1.9.1 # BSD-derived (http://www.repoze.org/LICENSE.txt)
|
||||
Paste # MIT
|
||||
dogpile.cache
|
||||
python-memcached
|
||||
python-designateclient
|
||||
python-neutronclient
|
||||
python-novaclient
|
||||
dragonflow
|
||||
oslo_concurrency
|
||||
eventlet
|
||||
vine
|
||||
|
@ -10,11 +10,19 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from Crypto.PublicKey import RSA
|
||||
import falcon
|
||||
import json
|
||||
import logging
|
||||
import uuid
|
||||
from Crypto.PublicKey import RSA
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from tatu.dns import add_srv_records
|
||||
from tatu.pat import create_pat_entries
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
from tatu.db import models as db
|
||||
|
||||
@ -225,3 +233,11 @@ class NovaVendorData(object):
|
||||
resp.body = json.dumps(vendordata)
|
||||
resp.location = '/hosttokens/' + token.token_id
|
||||
resp.status = falcon.HTTP_201
|
||||
|
||||
# TODO(pino): make the whole workflow fault-tolerant
|
||||
# TODO(pino): make this configurable per project or subnet
|
||||
if CONF.tatu.use_pat_bastion:
|
||||
pat_entries = create_pat_entries(req.body['instance-id'], 22,
|
||||
num=CONF.tatu.bastion_redundancy)
|
||||
add_srv_records(req.body['project-id'], req.body['hostname'],
|
||||
pat_entries)
|
||||
|
67
tatu/dns.py
Normal file
67
tatu/dns.py
Normal file
@ -0,0 +1,67 @@
|
||||
# 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
|
||||
from designateclient.exceptions import Conflict
|
||||
from designateclient.v2 import client
|
||||
from keystoneclient import session
|
||||
from keystoneclient.auth.identity.generic.password import Password
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
auth = Password(auth_url=os.getenv('OS_AUTH_URL'),
|
||||
username=os.getenv('OS_USERNAME'),
|
||||
password=os.getenv('OS_PASSWORD'),
|
||||
project_name=os.getenv('OS_PROJECT_NAME'),
|
||||
project_domain_id='default',
|
||||
user_domain_id='default')
|
||||
|
||||
s = session.Session(auth=auth)
|
||||
|
||||
client = client.Client(session=s)
|
||||
zone = None
|
||||
bastions = {}
|
||||
|
||||
|
||||
def setup(bastions=[]):
|
||||
# TODO: retrieve the zone name and email from configuration
|
||||
try:
|
||||
global zone
|
||||
zone = client.zones.create('julia.com.', email='pino@yahoo.com')
|
||||
except Conflict:
|
||||
pass
|
||||
|
||||
# TODO: fetch all existing bastions
|
||||
|
||||
|
||||
def add_bastion(ip_address, project_id, project_name, num):
|
||||
bastion_name = "{}-{}-{}.{}".format(str(project_id)[:8], project_name, num,
|
||||
zone['name'])
|
||||
client.recordsets.create(zone['id'], bastion_name, 'A', [ip_address])
|
||||
bastions.add(ip_address, bastion_name)
|
||||
return bastion_name
|
||||
|
||||
|
||||
def add_srv_records(project_id, hostname, pat_entries):
|
||||
records = []
|
||||
for pat_entry in pat_entries:
|
||||
b = bastions[pat_entries.pat.ip_address]
|
||||
# SRV record format is: priority weight port A-name
|
||||
records.add(
|
||||
'10 50 {} {}'.format(pat_entry.pat_l4_port, b))
|
||||
|
||||
client.recordsets.create(zone['id'],
|
||||
'ssh.{}.{}'.format(hostname, project_id[:8]),
|
||||
'SRV', records)
|
@ -62,6 +62,9 @@ class NotificationEndpoint(object):
|
||||
LOG.error("Status update or unknown")
|
||||
|
||||
|
||||
# TODO(pino): listen to host delete notifications, clean up PATs and DNS
|
||||
# TODO(pino): Listen to user deletions and revoke their certs
|
||||
|
||||
def main():
|
||||
transport = oslo_messaging.get_notification_transport(cfg.CONF)
|
||||
targets = [oslo_messaging.Target(topic='notifications')]
|
||||
|
90
tatu/pat.py
Normal file
90
tatu/pat.py
Normal file
@ -0,0 +1,90 @@
|
||||
# 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 dragonflow.db import api_nb
|
||||
from dragonflow.db.models import l3
|
||||
from oslo_log import log
|
||||
from neutronclient.v2_0 import client
|
||||
from novaclient import client
|
||||
import random
|
||||
from tatu.db import models as db
|
||||
|
||||
# Need to load /etc/neutron/dragonflow.ini
|
||||
# config.init(sys.argv[1:])
|
||||
dragonflow = api_nb.NbApi.get_instance(False)
|
||||
|
||||
def add_pat():
|
||||
# First choose a host where the PAT will be bound.
|
||||
nova = client.Client(VERSION, USERNAME, PASSWORD, PROJECT_ID, AUTH_URL)
|
||||
hosts = nova.servers.list()
|
||||
host_id = random.sample(hosts, 1)[0].id
|
||||
|
||||
# Now create the new port on the public network.
|
||||
neutron = client.Client(username=USER,
|
||||
password=PASS,
|
||||
project_name=PROJECT_NAME,
|
||||
auth_url=KEYSTONE_URL)
|
||||
|
||||
# Find the public network and allocate 2 ports.
|
||||
networks = neutron.list_networks(name='public')
|
||||
network_id = networks['networks'][0]['id']
|
||||
|
||||
body_value = {
|
||||
"port": {
|
||||
"admin_state_up": True,
|
||||
"name": TatuPAT,
|
||||
"network_id": network_id,
|
||||
"binding: host_id": host_id
|
||||
}
|
||||
}
|
||||
pat_lport = neutron.create_port()
|
||||
# TODO: Bind the port to a specific host
|
||||
|
||||
pat = l3.PAT(
|
||||
topic = 'foo',
|
||||
ip_address = pat_lport.ip,
|
||||
lport = pat_lport
|
||||
)
|
||||
dragonflow.create(pat)
|
||||
db.add_pat(pat.lport)
|
||||
return pat_lport.ip
|
||||
|
||||
|
||||
# At startup, we create 1 PAT if none exists
|
||||
if not db.get_pats():
|
||||
add_pat()
|
||||
|
||||
# TODO(pino): need to re-bind PATs when hosts fail.
|
||||
|
||||
|
||||
def create_pat_entries(instance_id, fixed_l4_port, num=2):
|
||||
# TODO(pino): Use Neutron client to find a suitable lport on the instance
|
||||
lport = None
|
||||
lrouter = None
|
||||
# Reserve N assignments (i.e. IP:port pairs) on distinct IPs.
|
||||
pats = db.get_pats()
|
||||
pat_entries = set()
|
||||
if (num < len(pats)):
|
||||
pats = random.sample(pats, num_assignments)
|
||||
for pat in pats:
|
||||
pat_l4_port = db.reserve_l4_port(pat.ip, lport.id, lport.ip, fixed_l4_port)
|
||||
pat_entry = l3.PATEntry(
|
||||
pat = pat,
|
||||
pat_l4_port = pat_l4_port,
|
||||
fixed_ip_address = lport.ip,
|
||||
fixed_l4_port = fixed_l4_port,
|
||||
lport = lport,
|
||||
lrouter = df_fields.ReferenceField(LogicalRouter),
|
||||
)
|
||||
dragonflow.create(pat_entry)
|
||||
pat_entries.add(pat_entry)
|
||||
return pat_entries
|
Loading…
Reference in New Issue
Block a user