Permalink
Browse files

FEATURE: Refactor the firewall comands to use a new scoping scheme.

BREAKING CHANGE:
You can no longer pass `network=all` or `output-network=all` to
the `add firewall` commands. That is the default, so if you want
the firewall rule to apply to all networks, just don't specify
the `network` or `output-network` parameters. This is how it really
worked in the previous code, specifying `all` was just a nop.

BREAKING CHANGE:
There is new a database schema for the firewall rules. This SQL
will update an existing DB, but you will lose your existing
firewall rules in the process:

```
DROP TABLE IF EXISTS global_firewall;
DROP TABLE IF EXISTS os_firewall;
DROP TABLE IF EXISTS appliance_firewall;
DROP TABLE IF EXISTS node_firewall;
DROP TABLE IF EXISTS environment_firewall;

CREATE TABLE scope_map (
	id		INT AUTO_INCREMENT PRIMARY KEY,
	scope		ENUM('global','appliance','os','environment', 'host') NOT NULL,
	appliance_id	INT DEFAULT NULL,
	os_id		INT DEFAULT NULL,
	environment_id	INT DEFAULT NULL,
	node_id		INT DEFAULT NULL,
	INDEX (scope),
	FOREIGN KEY (appliance_id) REFERENCES appliances(id) ON DELETE CASCADE,
	FOREIGN KEY (os_id) REFERENCES oses(id) ON DELETE CASCADE,
	FOREIGN KEY (environment_id) REFERENCES environments(id) ON DELETE CASCADE,
	FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE
);

CREATE TABLE firewall_rules (
	id		INT AUTO_INCREMENT PRIMARY KEY,
	scope_map_id	INT NOT NULL,
	name		VARCHAR(256) NOT NULL,
	table_type	ENUM('nat','filter','mangle','raw') NOT NULL,
	chain		VARCHAR(256) NOT NULL,
	action		VARCHAR(256) NOT NULL,
	service		VARCHAR(256) NOT NULL,
	protocol 	VARCHAR(256) NOT NULL,
	in_subnet_id	INT DEFAULT NULL,
	out_subnet_id	INT DEFAULT NULL,
	flags		VARCHAR(256) DEFAULT NULL,
	comment		VARCHAR(256) DEFAULT NULL,
	INDEX (name),
	INDEX (table_type),
	FOREIGN KEY (scope_map_id) REFERENCES scope_map(id) ON DELETE CASCADE,
	FOREIGN KEY (in_subnet_id) REFERENCES subnets(id) ON DELETE CASCADE,
	FOREIGN KEY (out_subnet_id) REFERENCES subnets(id) ON DELETE CASCADE
);
```

BUGFIX: Make sure commands using `Command::command` get the correct usage message on exception
  • Loading branch information...
caladd committed Nov 5, 2018
1 parent 58f62bc commit 5a2ab3a28969ca14742cfc1005d1038581463a27
Showing with 1,777 additions and 1,442 deletions.
  1. +36 −88 common/nodes/database-schema.xml
  2. +83 −2 common/src/stack/command/stack/commands/__init__.py
  3. +25 −49 common/src/stack/command/stack/commands/add/appliance/firewall/__init__.py
  4. +33 −48 common/src/stack/command/stack/commands/add/environment/firewall/__init__.py
  5. +177 −150 common/src/stack/command/stack/commands/add/firewall/__init__.py
  6. +27 −48 common/src/stack/command/stack/commands/add/host/firewall/__init__.py
  7. +21 −45 common/src/stack/command/stack/commands/add/os/firewall/__init__.py
  8. +6 −17 common/src/stack/command/stack/commands/list/appliance/firewall/__init__.py
  9. +1 −1 common/src/stack/command/stack/commands/list/environment/firewall/__init__.py
  10. +112 −40 common/src/stack/command/stack/commands/list/firewall/__init__.py
  11. +0 −301 common/src/stack/command/stack/commands/list/firewall/plugin_default.py
  12. +87 −0 common/src/stack/command/stack/commands/list/firewall/plugin_intrinsic.py
  13. +2 −3 common/src/stack/command/stack/commands/list/host/firewall/__init__.py
  14. +6 −17 common/src/stack/command/stack/commands/list/os/firewall/__init__.py
  15. +3 −24 common/src/stack/command/stack/commands/remove/appliance/firewall/__init__.py
  16. +0 −33 common/src/stack/command/stack/commands/remove/appliance/plugin_firewall.py
  17. +3 −24 common/src/stack/command/stack/commands/remove/environment/firewall/__init__.py
  18. +0 −18 common/src/stack/command/stack/commands/remove/environment/plugin_firewall.py
  19. +31 −15 common/src/stack/command/stack/commands/remove/firewall/__init__.py
  20. +3 −25 common/src/stack/command/stack/commands/remove/host/firewall/__init__.py
  21. +0 −26 common/src/stack/command/stack/commands/remove/host/plugin_firewall.py
  22. +3 −19 common/src/stack/command/stack/commands/remove/os/firewall/__init__.py
  23. +0 −30 common/src/stack/command/stack/commands/remove/os/plugin_firewall.py
  24. +10 −28 common/src/stack/command/stack/commands/report/host/firewall/__init__.py
  25. +4 −4 redhat/nodes/firewall-server.xml
  26. +4 −4 sles/nodes/firewall-server.xml
  27. +92 −0 test-framework/test-suites/integration/test-files/list/host_firewall_backend_intrinsic.json
  28. +122 −0 test-framework/test-suites/integration/test-files/list/host_firewall_frontend_intrinsic_no_dns.json
  29. +137 −0 ...-framework/test-suites/integration/test-files/list/host_firewall_frontend_intrinsic_with_dns.json
  30. +182 −0 test-framework/test-suites/integration/test-files/list/host_firewall_scope_overriding.json
  31. +4 −4 test-framework/test-suites/integration/test-files/report/host_firewall_nat_rules.txt
  32. +28 −72 test-framework/test-suites/integration/tests/add/test_add_appliance_firewall.py
  33. +259 −7 test-framework/test-suites/integration/tests/add/test_add_environment_firewall.py
  34. +94 −115 test-framework/test-suites/integration/tests/add/test_add_firewall.py
  35. +28 −77 test-framework/test-suites/integration/tests/add/test_add_host_firewall.py
  36. +29 −87 test-framework/test-suites/integration/tests/add/test_add_os_firewall.py
  37. +104 −0 test-framework/test-suites/integration/tests/list/test_list_host_firewall.py
  38. +6 −6 test-framework/test-suites/integration/tests/remove/test_remove_appliance_firewall.py
  39. +4 −4 test-framework/test-suites/integration/tests/remove/test_remove_environment_firewall.py
  40. +2 −2 test-framework/test-suites/integration/tests/remove/test_remove_firewall.py
  41. +4 −4 test-framework/test-suites/integration/tests/remove/test_remove_host_firewall.py
  42. +4 −4 test-framework/test-suites/integration/tests/remove/test_remove_os_firewall.py
  43. +1 −1 test-framework/test-suites/integration/tests/report/test_report_host_firewall.py
@@ -347,94 +347,42 @@ CREATE TABLE partitions (

<!-- Firewall Tables -->

DROP TABLE IF EXISTS global_firewall;
CREATE TABLE global_firewall (
Tabletype enum('nat','filter','mangle','raw') NOT NULL
default 'filter',
Name varchar(256) default NULL,
InSubnet int(11) default NULL references subnets,
OutSubnet int(11) default NULL references subnets,
Service varchar(256) default NULL,
Protocol varchar(256) default NULL,
Action varchar(256) default NULL,
Chain varchar(256) default NULL,
Flags varchar(256) default NULL,
Comment varchar(256) default NULL,
INDEX (Name),
INDEX (Service)
);

DROP TABLE IF EXISTS os_firewall;
CREATE TABLE os_firewall (
OS varchar(32) NOT NULL default '&os;',
Tabletype enum('nat','filter','mangle','raw') NOT NULL
default 'filter',
Name varchar(256) default NULL,
InSubnet int(11) default NULL references subnets,
OutSubnet int(11) default NULL references subnets,
Service varchar(256) default NULL,
Protocol varchar(256) default NULL,
Action varchar(256) default NULL,
Chain varchar(256) default NULL,
Flags varchar(256) default NULL,
Comment varchar(256) default NULL,
INDEX (OS),
INDEX (Name),
INDEX (Service)
);

DROP TABLE IF EXISTS appliance_firewall;
CREATE TABLE appliance_firewall (
Appliance int(11) NOT NULL default '0' references appliances,
Tabletype enum('nat','filter','mangle','raw') NOT NULL
default 'filter',
Name varchar(256) default NULL,
InSubnet int(11) default NULL references subnets,
OutSubnet int(11) default NULL references subnets,
Service varchar(256) default NULL,
Protocol varchar(256) default NULL,
Action varchar(256) default NULL,
Chain varchar(256) default NULL,
Flags varchar(256) default NULL,
Comment varchar(256) default NULL,
INDEX (Name),
INDEX (Service)
);

DROP TABLE IF EXISTS node_firewall;
CREATE TABLE node_firewall (
Node int(11) NOT NULL default '0' references nodes,
Tabletype enum('nat','filter','mangle','raw') NOT NULL
default 'filter',
Name varchar(256) default NULL,
InSubnet int(11) default NULL references subnets,
OutSubnet int(11) default NULL references subnets,
Service varchar(256) default NULL,
Protocol varchar(256) default NULL,
Action varchar(256) default NULL,
Chain varchar(256) default NULL,
Flags varchar(256) default NULL,
Comment varchar(256) default NULL,
INDEX (Name),
INDEX (Service)
);

DROP TABLE IF EXISTS environment_firewall;
CREATE TABLE environment_firewall (
Environment int(11) NOT NULL default '0',
Tabletype enum('nat','filter','mangle','raw') NOT NULL
default 'filter',
Name varchar(256) default NULL,
InSubnet int(11) default NULL references subnets,
OutSubnet int(11) default NULL references subnets,
Service varchar(256) default NULL,
Protocol varchar(256) default NULL,
Action varchar(256) default NULL,
Chain varchar(256) default NULL,
Flags varchar(256) default NULL,
Comment varchar(256) default NULL,
INDEX (Name),
INDEX (Service)
<!-- Gotta drop the referenced tables last -->
DROP TABLE IF EXISTS firewall_rules;
DROP TABLE IF EXISTS scope_map;

CREATE TABLE scope_map (
id INT AUTO_INCREMENT PRIMARY KEY,
scope ENUM('global','appliance','os','environment', 'host') NOT NULL,
appliance_id INT DEFAULT NULL,
os_id INT DEFAULT NULL,
environment_id INT DEFAULT NULL,
node_id INT DEFAULT NULL,
INDEX (scope),
FOREIGN KEY (appliance_id) REFERENCES appliances(id) ON DELETE CASCADE,
FOREIGN KEY (os_id) REFERENCES oses(id) ON DELETE CASCADE,
FOREIGN KEY (environment_id) REFERENCES environments(id) ON DELETE CASCADE,
FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE
);

CREATE TABLE firewall_rules (
id INT AUTO_INCREMENT PRIMARY KEY,
scope_map_id INT NOT NULL,
name VARCHAR(256) NOT NULL,
table_type ENUM('nat','filter','mangle','raw') NOT NULL,
chain VARCHAR(256) NOT NULL,
action VARCHAR(256) NOT NULL,
service VARCHAR(256) NOT NULL,
protocol VARCHAR(256) NOT NULL,
in_subnet_id INT DEFAULT NULL,
out_subnet_id INT DEFAULT NULL,
flags VARCHAR(256) DEFAULT NULL,
comment VARCHAR(256) DEFAULT NULL,
INDEX (name),
INDEX (table_type),
FOREIGN KEY (scope_map_id) REFERENCES scope_map(id) ON DELETE CASCADE,
FOREIGN KEY (in_subnet_id) REFERENCES subnets(id) ON DELETE CASCADE,
FOREIGN KEY (out_subnet_id) REFERENCES subnets(id) ON DELETE CASCADE
);

<!-- Miscellaneous -->
@@ -36,7 +36,7 @@
import stack
from stack.cond import EvalCondExpr
from stack.exception import (
CommandError, ParamRequired, ArgNotFound, ArgRequired, ArgUnique
CommandError, ParamRequired, ArgNotFound, ArgRequired, ArgUnique, ParamError
)
from stack.bool import str2bool, bool2str
from stack.util import flatten
@@ -743,6 +743,81 @@ def getSingleHost(self, args):
return hosts[0]


class ScopeArgumentProcessor(
ApplianceArgumentProcessor,
OSArgumentProcessor,
EnvironmentArgumentProcessor,
HostArgumentProcessor
):
def getScopeMappings(self, args=None, scope=None):
# We will return a list of these
ScopeMapping = namedtuple(
'ScopeMapping',
['scope', 'appliance_id', 'os_id', 'environment_id', 'node_id']
)
scope_mappings = []

# Validate the different scopes and get the keys to the targets
if scope == 'global':
# Global scope has no friends
scope_mappings.append(
ScopeMapping(scope, None, None, None, None)
)

elif scope == 'appliance':
# Piggy-back to resolve the appliance names
names = self.getApplianceNames(args)

# Now we have to convert the names to the primary keys
for appliance_id in flatten(self.db.select(
'id from appliances where name in %s', (names,)
)):
scope_mappings.append(
ScopeMapping(scope, appliance_id, None, None, None)
)

elif scope == 'os':
# Piggy-back to resolve the os names
names = self.getOSNames(args)

# Now we have to convert the names to the primary keys
for os_id in flatten(self.db.select(
'id from oses where name in %s', (names,)
)):
scope_mappings.append(
ScopeMapping(scope, None, os_id, None, None)
)

elif scope == 'environment':
# Piggy-back to resolve the environment names
names = self.getEnvironmentNames(args)

# Now we have to convert the names to the primary keys
for environment_id in flatten(self.db.select(
'id from environments where name in %s', (names,)
)):
scope_mappings.append(
ScopeMapping(scope, None, None, environment_id, None)
)

elif scope == 'host':
# Piggy-back to resolve the host names
names = self.getHostnames(args)

# Now we have to convert the names to the primary keys
for node_id in flatten(self.db.select(
'id from nodes where name in %s', (names,)
)):
scope_mappings.append(
ScopeMapping(scope, None, None, None, node_id)
)

else:
raise ParamError(self, 'scope', 'is not valid')

return scope_mappings


class DocStringHandler(handler.ContentHandler,
handler.DTDHandler,
handler.EntityResolver,
@@ -1626,7 +1701,13 @@ def command(self, command, args=[]):
# class member self.rc so the caller can check
# the return code. The actual text is what we return.

self.rc = o.runWrapper(name, args, self.level + 1)
try:
self.rc = o.runWrapper(name, args, self.level + 1)
except CommandError as e:
# We need to catch any CommandError, point it to the calling cmd,
# and then re-raise it so it will have the correct usage message
e.cmd = self
raise e

return o.getText()

@@ -11,53 +11,56 @@
# @rocks@

import stack.commands
import stack.commands.add
import stack.commands.add.firewall
from stack.exception import ArgRequired, CommandError
from stack.exception import ArgRequired

class Command(stack.commands.add.firewall.command,
stack.commands.add.appliance.command):

class Command(stack.commands.add.appliance.command):
"""
Add a firewall rule for an appliance type.
<arg type='string' name='appliance' repeat='1'>
<arg type='string' name='appliance' repeat='1' optional='0'>
Appliance type (e.g., "backend").
</arg>
<param type='string' name='service' optional='0'>
The service identifier, port number or port range. For example
"www", 8080 or 0:1024.
To have this firewall rule apply to all services, specify the
keyword 'all'.
A comma seperated list of service identifier, port number or port range.
For example "www", 8080, 0:1024, or "1:1024,8080".
To have this firewall rule apply to all services, specify the keyword 'all'.
</param>
<param type='string' name='protocol' optional='0'>
The protocol associated with the service. For example, "tcp" or "udp".
The protocol associated with the rule. For example, "tcp" or "udp".
To have this firewall rule apply to all protocols, specify the
keyword 'all'.
</param>
<param type='string' name='network'>
The network for this rule. This is a named network
The network this rule should be applied to. This is a named network
(e.g., 'private') and must be one listed by the command
'stack list network'.
To have this firewall rule apply to all networks, specify the
keyword 'all'.
By default, the rule will apply to all networks.
</param>
<param type='string' name='output-network'>
The output network for this rule. This is a named
<param type='string' name='output-network' optional='1'>
The output network this rule should be applied to. This is a named
network (e.g., 'private') and must be one listed by the command
'stack list network'.
By default, the rule will apply to all networks.
</param>
<param type='string' name='chain' optional='0'>
The iptables 'chain' for this this rule (e.g., INPUT, OUTPUT, FORWARD).
The iptables 'chain' this rule should be applied to (e.g.,
INPUT, OUTPUT, FORWARD).
</param>
<param type='string' name='action' optional='0'>
The iptables 'action' this rule (e.g., ACCEPT, REJECT, DROP).
The iptables 'action' this rule should be applied to (e.g.,
ACCEPT, REJECT, DROP).
</param>
<param type='string' name='flags'>
@@ -89,7 +92,7 @@ class Command(stack.commands.add.firewall.command,
"-A FORWARD -i eth0 -j ACCEPT"
</example>
<example cmd='add appliance firewall login network=all service="8649" protocol="udp" action="REJECT" chain="INPUT"'>
<example cmd='add appliance firewall login service="8649" protocol="udp" action="REJECT" chain="INPUT"'>
Reject UDP packets with a destination port of 8649 on all networks for
the INPUT chain.
On login appliances, this will be translated into the following
@@ -102,32 +105,5 @@ def run(self, params, args):
if len(args) == 0:
raise ArgRequired(self, 'appliance')

apps = self.getApplianceNames(args)

(service, network, outnetwork, chain, action, protocol, flags,
comment, table, rulename) = self.doParams()

# Make sure we have a new rule
for app in apps:
if self.db.count("""(*) from appliance_firewall where
appliance = (select id from appliances where name = %s) and
service = %s and action = %s and chain = %s and
if (%s is NULL, insubnet is NULL, insubnet = %s) and
if (%s is NULL, outsubnet is NULL, outsubnet = %s) and
if (%s is NULL, protocol is NULL, protocol = %s) and
if (%s is NULL, flags is NULL, flags = %s)""",
(app, service, action, chain, network, network, outnetwork,
outnetwork, protocol, protocol, flags, flags)
) > 0:
raise CommandError(self, 'firewall rule already exists')

# Now let's add them
for app in apps:
self.db.execute("""insert into appliance_firewall
(appliance, insubnet, outsubnet, service, protocol,
action, chain, flags, comment, tabletype, name)
values ((select id from appliances where name=%s),
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""",
(app, network, outnetwork, service, protocol, action,
chain, flags, comment, table, rulename)
)
self.command('add.firewall', self._argv + ['scope=appliance'])
return self.rc
Oops, something went wrong.

0 comments on commit 5a2ab3a

Please sign in to comment.