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
|
Paste # MIT
|
||||||
dogpile.cache
|
dogpile.cache
|
||||||
python-memcached
|
python-memcached
|
||||||
|
python-designateclient
|
||||||
|
python-neutronclient
|
||||||
|
python-novaclient
|
||||||
|
dragonflow
|
||||||
oslo_concurrency
|
oslo_concurrency
|
||||||
eventlet
|
eventlet
|
||||||
vine
|
vine
|
||||||
|
@ -10,11 +10,19 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from Crypto.PublicKey import RSA
|
|
||||||
import falcon
|
import falcon
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
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
|
from tatu.db import models as db
|
||||||
|
|
||||||
@ -225,3 +233,11 @@ class NovaVendorData(object):
|
|||||||
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
|
||||||
|
|
||||||
|
# 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")
|
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():
|
def main():
|
||||||
transport = oslo_messaging.get_notification_transport(cfg.CONF)
|
transport = oslo_messaging.get_notification_transport(cfg.CONF)
|
||||||
targets = [oslo_messaging.Target(topic='notifications')]
|
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…
x
Reference in New Issue
Block a user