Permalink
Browse files

0.1.0 [Fri Feb 17 10:21:04 EET 2012]

* [AUTH] Added authorization in REST API
* [simple] added resolve project_id=>project_name if nova configure with
  keystone auth
  • Loading branch information...
Nikita Savin
Nikita Savin committed Feb 17, 2012
1 parent 42a756f commit 0dd20beb9eb3c9eddeb9b461b3b231881acb58d4
Showing with 140 additions and 98 deletions.
  1. +4 −0 Changelog
  2. +4 −15 README
  3. +2 −2 TODO
  4. +13 −3 doc/source/config.rst
  5. +1 −1 nova-dns.spec
  6. +4 −1 nova_dns/__init__.py
  7. +90 −0 nova_dns/auth.py
  8. +14 −5 nova_dns/dns.py
  9. +0 −64 nova_dns/keystone_utils.py
  10. +8 −7 nova_dns/listener/simple/__init__.py
View
@@ -1,3 +1,7 @@
+0.1.0 [Fri Feb 17 10:21:04 EET 2012]
+* [AUTH] Added authorization in REST API
+* [simple] added resolve project_id=>project_name if nova configure with
+ keystone auth
0.0.6 [Wed Feb 15 18:48:16 EET 2012]
* [Doc] added project documentation
* [REST] updated 'GET /' endpoint
View
19 README
@@ -1,17 +1,14 @@
Overview
===========================
-The dns service listen for attach/detach floating ip to an instance and
-update dns server accordingly.
+The dns service listen for start/terminate instances and update dns
+server accordingly.
Configuration
===========================
-TODO
-
-The server saves logs to ``/var/log/nova/nova-dns.log``.
-
+Check config.html for details
RPM Building
===========================
@@ -26,13 +23,5 @@ directory and run::
Quickstart
===========================
-Install the package::
-
- # yum install nova-dns
-
-Start the server::
-
- # /etc/init.d/nova-dns start
-
-Now instance state changes will be stored to a database.
+Check quickstart.html for details
View
4 TODO
@@ -1,12 +1,12 @@
+* check rpm deps - nova, keystone, ...
* DNS API
-** rest docs/tests
+** rest tests
** DNS api docs
** powerdns tests
** keystone auth in REST
** add support for 'robot' records - refuse for robot to update record if
it somebody added/changed it
* AMQP
-** add support for terminate
** on run instance create zone(s) hostname.projectname.fixedzone and add
'A' record
** work with PTR records - probably support both plain reverse zone
View
@@ -42,6 +42,19 @@ Core options
* ``dns_soa_expire``
Indicates when the zone data is no longer authoritative
(integer, *604800* by default)
+* ``dns_zone``
+ Nova DNS base zone
+ (string, *localzone* by default)
+* ``dns_auth``
+ "Auth mode in REST API"
+ (enum ("none", "keystone"), *keystone* by default)
+* ``dns_nova_auth``
+ "Auth mode in Nova"
+ (enum ("none", "keystone"), *keystone* by defautl)
+* ``dns_auth_role``
+ "Role name in REST API"
+ (string, *DNS_Admin* by default)
+
nova_dns.dnsmanager.powerdns
++++++++++++++++++++++++++++
@@ -51,9 +64,6 @@ nova_dns.dnsmanager.powerdns
nova_dns.listener.simple
++++++++++++++++++++++++
-* ``dns_zone``
- Nova DNS base zone
- (string, *localzone* by default)
* ``dns_ns``
Name servers, in format ns1:ip1, ns2:ip2
(list, *ns1:127.0.0.1* by default)
View
@@ -5,7 +5,7 @@
%define mod_name nova_dns
Name: nova-dns
-Version: 0.0.5
+Version: 0.1.0
Release: 1
Summary: REST API for DNS configuration and service to add records for fixed ips
License: GNU LGPL v2.1
View
@@ -1,5 +1,5 @@
-__version__ = "0.0.6"
+__version__ = "0.1.0"
try:
from nova import flags
@@ -9,6 +9,9 @@
"DNS manager class")
flags.DEFINE_string("dns_listener", "nova_dns.listener.simple.Listener",
"Class to process AMQP messages")
+ flags.DEFINE_string("dns_api_paste_config", "/etc/nova-dns/dns-api-paste.ini",
+ "File name for the paste.deploy config for nova-dns api")
+
except:
#make setup.py happy
pass
View
@@ -0,0 +1,90 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Nova DNS
+# Copyright (C) GridDynamics Openstack Core Team, GridDynamics
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Authorization
+"""
+
+
+import ConfigParser
+
+from nova import flags
+from keystoneclient.v2_0 import client as keystone_client
+from dnsmanager import DNSRecord
+
+FLAGS = flags.FLAGS
+
+flags.DEFINE_enum("dns_auth", "keystone", ["none", "keystone"],
+ "Auth mode in REST API")
+flags.DEFINE_enum("dns_nova_auth", "keystone", ["none", "keystone"],
+ "Auth mode in Nova")
+flags.DEFINE_string("dns_auth_role", "DNS_Admin", "Role name in REST API")
+flags.DEFINE_string("dns_zone", "localzone", "Nova DNS base zone")
+
+
+
+class NoAuth(object):
+ def tenant2zonename(self, project_id):
+ return "%s.%s" % (DNSRecord.normname(project_id), FLAGS.dns_zone)
+ def can(self, req, zone_name):
+ return {"read":True, "write":True}
+
+class KeystoneAuth(NoAuth):
+ def __init__(self):
+ config = ConfigParser.RawConfigParser()
+ config.read(FLAGS.dns_api_paste_config)
+ self.token = config.get("filter:authtoken", "admin_token")
+ self.url = "%s://%s:%s/v2.0" % (
+ config.get("filter:authtoken", "auth_protocol"),
+ config.get("filter:authtoken", "auth_host"),
+ config.get("filter:authtoken", "auth_port"))
+ self.client = keystone_client.Client(
+ endpoint=self.url, token=self.token)
+ self.tenants = {}
+
+ def tenant2zonename(self, project_id):
+ #project_id is a really project_id :)
+ return super(KeystoneAuth, self).tenant2zonename(self._get_tenant(project_id))
+
+ def can(self, req, zone_name):
+ roles = [r.strip()
+ for r in req.headers.get('X_ROLE', '').split(',')]
+ if "Admin" in roles:
+ return {"read":True, "write":True}
+ if FLAGS.dns_auth_role not in roles:
+ return {"read":True, "write":False}
+ # will raise if no X_TENANT_ID header
+ name = self.tenant2zonename(req.headers['X_TENANT_ID'])
+ can_write = DNSRecord.normname(zone_name) == DNSRecord.normname(name)
+ return {"read":True, "write":can_write}
+
+
+ def _get_tenant(self, id):
+ if not self.tenants.has_key(id):
+ #probably new tenant, let's re-read cache
+ self.tenants={}
+ for t in self.client.tenants.list():
+ self.tenants[t.id] = t.name
+ name = self.tenants.get(id, None)
+ if not name:
+ #nope, not new
+ raise ValueError('Unknown tenant_id: %s' % (str(id)))
+ return name
+
+AUTH = NoAuth() if FLAGS.dns_auth == 'none' else KeystoneAuth()
+
View
@@ -33,16 +33,14 @@
from nova import wsgi
from nova import service
-#from nova_dns import keystone_utils
from nova_dns import __version__
from nova_dns.dnsmanager import DNSRecord, DNSSOARecord
+from nova_dns.auth import AUTH
LOG = logging.getLogger("nova_dns.dns")
FLAGS = flags.FLAGS
-flags.DEFINE_string("dns_api_paste_config", "/etc/nova-dns/dns-api-paste.ini",
- "File name for the paste.deploy config for nova-dns api")
flags.DEFINE_string("dns_listen", "0.0.0.0",
"IP address for DNS API to listen")
flags.DEFINE_integer("dns_listen_port", 15353,
@@ -102,9 +100,20 @@ def __call__(self, req):
"""
"""
try:
- args=req.environ["wsgiorg.routing_args"][1]
- action=args["action"]
+ args = req.environ["wsgiorg.routing_args"][1]
+ action = args["action"]
+ if action in ('index', 'zone_get', 'list'):
+ action_type = "read"
+ else:
+ action_type = "write"
+ #TODO remove keystone middleware and directly authenticate
+ #with keystoneclient.tokens.authneticate - right now this is
+ #buggy - if token incorect, keystonectlient return amazing
+ #error 'maximum recursion depth exceeded in cmp'
+ if not AUTH.can(req, args.get('zonename', None))[action_type]:
+ raise Exception('unauthorized')
result={}
+
if action=="index":
result=self.manager.list()
elif action=="zone_get":
View
@@ -1,64 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright (C) GridDynamics Openstack Core Team, GridDynamics
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""
-Module for communication with Keystone.
-"""
-
-import ConfigParser
-
-from keystoneclient.v2_0 import client as keystone_client
-
-from nova import flags
-from nova import service
-
-# derived from nova_billing
-
-FLAGS = flags.FLAGS
-
-_keystone_admin_token = None
-_keystone_admin_url = None
-
-
-def _get_keystone_stuff():
- config = ConfigParser.RawConfigParser()
- config.read(FLAGS.dns_api_paste_config)
- global _keystone_admin_token, _keystone_admin_url
- _keystone_admin_token = config.get("filter:authtoken", "admin_token")
- _keystone_admin_url = "%s://%s:%s/v2.0" % (
- config.get("filter:authtoken", "auth_protocol"),
- config.get("filter:authtoken", "auth_host"),
- config.get("filter:authtoken", "auth_port"))
-
-
-def get_keystone_admin_token():
- if not _keystone_admin_token:
- _get_keystone_stuff()
- return _keystone_admin_token
-
-
-def get_keystone_admin_url():
- if not _keystone_admin_url:
- _get_keystone_stuff()
- return _keystone_admin_url
-
-
-class KeystoneTenants(object):
- def get_tenants(self, token):
- return keystone_client.Client(
- endpoint=get_keystone_admin_url(),
- token=token).tenants.list()
@@ -14,19 +14,19 @@
from nova_dns.dnsmanager import DNSRecord
from nova_dns.listener import AMQPListener
-
+from nova_dns import auth
LOG = logging.getLogger("nova_dns.listener.simple")
FLAGS = flags.FLAGS
-SLEEP = 60
+SLEEP = 60
-#TODO make own zone for every instance
-flags.DEFINE_string("dns_zone", "localzone", "Nova DNS base zone")
-flags.DEFINE_list("dns_ns", "ns1:127.0.0.1", "Name servers, in format ns1:ip1, ns2:ip2")
+AUTH = auth.AUTH
+#TODO make own zone for every instance
#TODO add flag "dns_create_ptr", and create PTR records
+flags.DEFINE_list("dns_ns", "ns1:127.0.0.1", "Name servers, in format ns1:ip1, ns2:ip2")
class Listener(AMQPListener):
def __init__(self):
@@ -55,7 +55,8 @@ def event(self, e):
LOG.info("Instance %s hostname '%s' was terminated" %
(id, rec.hostname))
#TODO check if record was added/changed by admin
- zone=self.dnsmanager.get(rec.project_id+'.'+FLAGS.dns_zone)
+ zonename = AUTH.tenant2zonename(rec.project_id)
+ zone=self.dnsmanager.get(zonename)
zone.delete(rec.hostname, 'A')
except:
pass
@@ -89,7 +90,7 @@ def _pollip(self):
LOG.warn(str(e))
except:
pass
- zonename=r.project_id+'.'+FLAGS.dns_zone
+ zonename = AUTH.tenant2zonename(r.project_id)
if zonename not in zones_list:
#TODO add exception ZoneExists and pass only it
try:

0 comments on commit 0dd20be

Please sign in to comment.