From d4e7eb818c9f4ec51fd3a88a0e92d557867511d4 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Mon, 17 Jan 2011 23:18:46 -0500 Subject: Add rules to database, cast refresh message and trickle down to firewall driver. --- nova/api/ec2/admin.py | 43 +++++++++++++++++++++++++++++++++++++++---- nova/compute/api.py | 9 +++++++++ nova/compute/manager.py | 5 +++++ nova/db/api.py | 8 ++++++++ nova/db/sqlalchemy/api.py | 12 ++++++++++++ nova/db/sqlalchemy/models.py | 11 +++++++++++ nova/virt/connection.py | 1 + nova/virt/libvirt_conn.py | 10 ++++++++++ 8 files changed, 95 insertions(+), 4 deletions(-) diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index b784c5a00..1ae5f7094 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -21,7 +21,10 @@ Admin API controller, exposed through http via the api worker. """ import base64 +import IPy +import urllib +from nova import compute from nova import db from nova import exception from nova import log as logging @@ -70,6 +73,9 @@ class AdminController(object): def __str__(self): return 'AdminController' + def __init__(self): + self.compute_api = compute.API() + def describe_user(self, _context, name, **_kwargs): """Returns user data, including access and secret keys.""" return user_dict(manager.AuthManager().get_user(name)) @@ -210,10 +216,39 @@ class AdminController(object): """Returns status info for single node.""" return host_dict(db.host_get(name)) + def _provider_fw_rule_exists(context, rule): + for old_rule in db.provider_fw_rule_get_all(context): + for key in ('cidr', 'from_port', 'to_port', 'protocol'): + dupe = True + if rule[key] != old_rule[key]: + dupe = False + if dupe: + return dupe + return False + + def block_external_addresses(self, context, cidr): """Add provider-level firewall rules to block incoming traffic.""" - LOG.audit(_("Blocking access to all projects incoming from %s"), + LOG.audit(_("Blocking traffic to all projects incoming from %s"), cidr, context=context) - raise NotImplementedError(_("Awaiting implementation.")) - # TODO(todd): implement - # return {'status': 'OK', 'message': 'Disabled (number) IPs'} + rule = {'cidr': IPy.IP(urllib.unquote(cidr).decode())} + tcp_rule = rule.copy() + tcp_rule.update({"protocol": "TCP", "from_port": 1, "to_port": 65535}) + udp_rule = rule.copy() + udp_rule.update({"protocol": "UDP", "from_port": 1, "to_port": 65535}) + icmp_rule = rule.copy() + icmp_rule.update({"protocol": "ICMP", "from_port": -1, "to_port": -1}) + rules_added = 0 + if not self._provider_fw_rule_exists(context, tcp_rule): + db.provider_fw_rule_create(context, tcp_rule) + rules_added += 1 + if not self._provider_fw_rule_exists(context, udp_rule): + db.provider_fw_rule_create(context, udp_rule) + rules_added += 1 + if not self._provider_fw_rule_exists(context, icmp_rule): + db.provider_fw_rule_create(context, icmp_rule) + rules_added += 1 + if rules_added == 0: + raise exception.ApiError(_('Duplicate rule')) + self.compute_api.trigger_provider_fw_rules_refresh(context) + return {'status': 'OK', 'message': 'Disabled (number) IPs'} diff --git a/nova/compute/api.py b/nova/compute/api.py index a6b99c1cb..607a9ef25 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -268,6 +268,15 @@ class API(base.Base): {"method": "refresh_security_group_members", "args": {"security_group_id": group_id}}) + def trigger_provider_fw_rules_refresh(self, context): + """Called when a rule is added to or removed from a security_group""" + + hosts = [x['host'] for x in db.service_get_all_compute_sorted(context)] + for host in hosts: + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "refresh_provider_fw_rules", "args": {}}) + def update(self, context, instance_id, **kwargs): """Updates the instance in the datastore. diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 6f09ce674..9c4a23d08 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -169,6 +169,11 @@ class ComputeManager(manager.Manager): """This call passes straight through to the virtualization driver.""" return self.driver.refresh_security_group_members(security_group_id) + @exception.wrap_exception + def refresh_provider_fw_rules(self, context, **_kwargs): + """This call passes straight through to the virtualization driver.""" + return self.driver.refresh_security_group_rules() + @exception.wrap_exception def run_instance(self, context, instance_id, **_kwargs): """Launch a new instance with specified options.""" diff --git a/nova/db/api.py b/nova/db/api.py index f9d561587..a05c8159e 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -813,6 +813,14 @@ def security_group_rule_destroy(context, security_group_rule_id): ################### +def provider_fw_rule_create(context, rule): + """Add a firewall rule at the provider level (all hosts & instances).""" + return IMPL.provider_fw_rule_create(context, rule) + + +################### + + def user_get(context, id): """Get user by id.""" return IMPL.user_get(context, id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index b63b84bed..4223aa0f7 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1687,6 +1687,18 @@ def security_group_rule_destroy(context, security_group_rule_id): ################### + +@require_admin_context +def provider_fw_rule_create(context, rule): + fw_rule_ref = models.ProviderFirewallRule() + fw_rule_ref.update(rule) + fw_rule_ref.save() + return fw_rule_ref + + +################### + + @require_admin_context def user_get(context, id, session=None): if not session: diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index bf5e48b04..1d82cff18 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -394,6 +394,17 @@ class SecurityGroupIngressRule(BASE, NovaBase): group_id = Column(Integer, ForeignKey('security_groups.id')) +class ProviderFirewallRule(BASE, NovaBase): + """Represents a rule in a security group.""" + __tablename__ = 'provider_fw_rules' + id = Column(Integer, primary_key=True) + + protocol = Column(String(5)) # "tcp", "udp", or "icmp" + from_port = Column(Integer) + to_port = Column(Integer) + cidr = Column(String(255)) + + class KeyPair(BASE, NovaBase): """Represents a public key pair for ssh.""" __tablename__ = 'key_pairs' diff --git a/nova/virt/connection.py b/nova/virt/connection.py index 13181b730..f5a978997 100644 --- a/nova/virt/connection.py +++ b/nova/virt/connection.py @@ -54,6 +54,7 @@ def get_connection(read_only=False): * fake * libvirt * xenapi + * hyperv """ # TODO(termie): maybe lazy load after initial check for permissions # TODO(termie): check whether we can be disconnected diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f38af5ed8..fa5dc502e 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -848,6 +848,9 @@ class LibvirtConnection(object): def refresh_security_group_members(self, security_group_id): self.firewall_driver.refresh_security_group_members(security_group_id) + def refresh_provier_fw_rules(self): + self.firewall_driver.refresh_provider_fw_rules() + class FirewallDriver(object): def prepare_instance_filter(self, instance): @@ -884,6 +887,13 @@ class FirewallDriver(object): the security group.""" raise NotImplementedError() + def refresh_provider_fw_rules(self): + """Refresh common rules for all hosts/instances from data store. + + Gets called when a rule has been added to or removed from + the list of rules (via admin api).""" + raise NotImplementedError() + class NWFilterFirewall(FirewallDriver): """ -- cgit