163489b5e9
Based on Diana's work. Complete for Admin Panels: https://review.openstack.org/#/c/290204/ Horizon used a standard select input. Unfortunately this type of input is only customizable to a small extend. Note: not all ChoiceFields were converted because they have unexpected impact. Change-Id: Id9405c6f0127d6e86db09dfaf772f1275a7d7547 Partially-implements: blueprint horizon-theme-css-reorg
399 lines
16 KiB
Python
399 lines
16 KiB
Python
# Copyright 2013, Big Switch Networks, Inc.
|
|
#
|
|
# 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 django.utils.translation import ugettext_lazy as _
|
|
import netaddr
|
|
|
|
from horizon import exceptions
|
|
from horizon import forms
|
|
from horizon.utils import validators
|
|
from horizon import workflows
|
|
|
|
from openstack_dashboard import api
|
|
|
|
port_validator = validators.validate_port_or_colon_separated_port_range
|
|
|
|
|
|
class AddRuleAction(workflows.Action):
|
|
name = forms.CharField(
|
|
max_length=80,
|
|
label=_("Name"),
|
|
required=False)
|
|
description = forms.CharField(
|
|
max_length=80,
|
|
label=_("Description"),
|
|
required=False)
|
|
protocol = forms.ThemableChoiceField(
|
|
label=_("Protocol"),
|
|
choices=[('tcp', _('TCP')),
|
|
('udp', _('UDP')),
|
|
('icmp', _('ICMP')),
|
|
('any', _('ANY'))],)
|
|
action = forms.ThemableChoiceField(
|
|
label=_("Action"),
|
|
choices=[('allow', _('ALLOW')),
|
|
('deny', _('DENY')),
|
|
('reject', _('REJECT'))],)
|
|
source_ip_address = forms.IPField(
|
|
label=_("Source IP Address/Subnet"),
|
|
version=forms.IPv4 | forms.IPv6,
|
|
required=False, mask=True)
|
|
destination_ip_address = forms.IPField(
|
|
label=_("Destination IP Address/Subnet"),
|
|
version=forms.IPv4 | forms.IPv6,
|
|
required=False, mask=True)
|
|
source_port = forms.CharField(
|
|
max_length=80,
|
|
label=_("Source Port/Port Range"),
|
|
required=False,
|
|
validators=[port_validator])
|
|
destination_port = forms.CharField(
|
|
max_length=80,
|
|
label=_("Destination Port/Port Range"),
|
|
required=False,
|
|
validators=[port_validator])
|
|
ip_version = forms.ThemableChoiceField(
|
|
label=_("IP Version"), required=False,
|
|
choices=[('4', '4'), ('6', '6')])
|
|
shared = forms.BooleanField(
|
|
label=_("Shared"), initial=False, required=False)
|
|
enabled = forms.BooleanField(
|
|
label=_("Enabled"), initial=True, required=False)
|
|
|
|
def __init__(self, request, *args, **kwargs):
|
|
super(AddRuleAction, self).__init__(request, *args, **kwargs)
|
|
|
|
def _check_ip_addr_and_ip_version(self, cleaned_data):
|
|
ip_version = int(str(cleaned_data.get('ip_version')))
|
|
src_ip = cleaned_data.get('source_ip_address')
|
|
dst_ip = cleaned_data.get('destination_ip_address')
|
|
msg = _('Source/Destination Network Address and IP version '
|
|
'are inconsistent. Please make them consistent.')
|
|
if (src_ip and
|
|
netaddr.IPNetwork(src_ip).version != ip_version):
|
|
self._errors['ip_version'] = self.error_class([msg])
|
|
|
|
elif (dst_ip and
|
|
netaddr.IPNetwork(dst_ip).version != ip_version):
|
|
self._errors['ip_version'] = self.error_class([msg])
|
|
|
|
def clean(self):
|
|
cleaned_data = super(AddRuleAction, self).clean()
|
|
self._check_ip_addr_and_ip_version(cleaned_data)
|
|
|
|
class Meta(object):
|
|
name = _("Rule")
|
|
permissions = ('openstack.services.network',)
|
|
help_text = _("Create a firewall rule.\n\n"
|
|
"A Firewall rule is an association of the following "
|
|
"attributes:\n\n"
|
|
"<li>IP Addresses: The addresses from/to which the "
|
|
"traffic filtration needs to be applied.</li>"
|
|
"<li>IP Version: The type of IP packets (IP V4/V6) "
|
|
"that needs to be filtered.</li>"
|
|
"<li>Protocol: Type of packets (UDP, ICMP, TCP, Any) "
|
|
"that needs to be checked.</li>"
|
|
"<li>Action: Action is the type of filtration "
|
|
"required, it can be Reject/Deny/Allow data "
|
|
"packets.</li>\n"
|
|
"The protocol and action fields are required, all "
|
|
"others are optional.")
|
|
|
|
|
|
class AddRuleStep(workflows.Step):
|
|
action_class = AddRuleAction
|
|
contributes = ("name", "description", "protocol", "action",
|
|
"source_ip_address", "source_port",
|
|
"destination_ip_address", "destination_port",
|
|
"enabled", "shared", "ip_version")
|
|
|
|
def contribute(self, data, context):
|
|
context = super(AddRuleStep, self).contribute(data, context)
|
|
if data:
|
|
if context['protocol'] == 'any':
|
|
del context['protocol']
|
|
for field in ['source_port',
|
|
'destination_port',
|
|
'source_ip_address',
|
|
'destination_ip_address']:
|
|
if not context[field]:
|
|
del context[field]
|
|
return context
|
|
|
|
|
|
class AddRule(workflows.Workflow):
|
|
slug = "addrule"
|
|
name = _("Add Rule")
|
|
finalize_button_name = _("Add")
|
|
success_message = _('Added Rule "%s".')
|
|
failure_message = _('Unable to add Rule "%s".')
|
|
success_url = "horizon:project:firewalls:index"
|
|
# fwaas is designed to support a wide range of vendor
|
|
# firewalls. Considering the multitude of vendor firewall
|
|
# features in place today, firewall_rule definition can
|
|
# involve more complex configuration over time. Hence,
|
|
# a workflow instead of a single form is used for
|
|
# firewall_rule add to be ready for future extension.
|
|
default_steps = (AddRuleStep,)
|
|
|
|
def format_status_message(self, message):
|
|
return message % self.context.get('name')
|
|
|
|
def handle(self, request, context):
|
|
try:
|
|
api.fwaas.rule_create(request, **context)
|
|
return True
|
|
except Exception as e:
|
|
msg = self.format_status_message(self.failure_message) + str(e)
|
|
exceptions.handle(request, msg)
|
|
return False
|
|
|
|
|
|
class SelectRulesAction(workflows.Action):
|
|
rule = forms.MultipleChoiceField(
|
|
label=_("Rules"),
|
|
required=False,
|
|
widget=forms.ThemableCheckboxSelectMultiple(),
|
|
help_text=_("Create a policy with selected rules."))
|
|
|
|
class Meta(object):
|
|
name = _("Rules")
|
|
permissions = ('openstack.services.network',)
|
|
help_text = _("Select rules for your policy.")
|
|
|
|
def populate_rule_choices(self, request, context):
|
|
try:
|
|
tenant_id = self.request.user.tenant_id
|
|
rules = api.fwaas.rule_list_for_tenant(request, tenant_id)
|
|
rules = sorted(rules,
|
|
key=lambda rule: rule.name_or_id)
|
|
rule_list = [(rule.id, rule.name_or_id) for rule in rules
|
|
if not rule.firewall_policy_id]
|
|
except Exception as e:
|
|
rule_list = []
|
|
exceptions.handle(request,
|
|
_('Unable to retrieve rules (%(error)s).') % {
|
|
'error': str(e)})
|
|
return rule_list
|
|
|
|
|
|
class SelectRulesStep(workflows.Step):
|
|
action_class = SelectRulesAction
|
|
template_name = "project/firewalls/_update_rules.html"
|
|
contributes = ("firewall_rules",)
|
|
|
|
def contribute(self, data, context):
|
|
if data:
|
|
rules = self.workflow.request.POST.getlist("rule")
|
|
if rules:
|
|
rules = [r for r in rules if r != '']
|
|
context['firewall_rules'] = rules
|
|
return context
|
|
|
|
|
|
class SelectRoutersAction(workflows.Action):
|
|
router = forms.MultipleChoiceField(
|
|
label=_("Routers"),
|
|
required=False,
|
|
widget=forms.ThemableCheckboxSelectMultiple(),
|
|
help_text=_("Create a firewall with selected routers."))
|
|
|
|
class Meta(object):
|
|
name = _("Routers")
|
|
permissions = ('openstack.services.network',)
|
|
help_text = _("Select routers for your firewall.")
|
|
|
|
def populate_router_choices(self, request, context):
|
|
try:
|
|
tenant_id = self.request.user.tenant_id
|
|
routers_list = api.fwaas.firewall_unassociated_routers_list(
|
|
request, tenant_id)
|
|
|
|
except Exception as e:
|
|
routers_list = []
|
|
exceptions.handle(request,
|
|
_('Unable to retrieve routers (%(error)s).') % {
|
|
'error': str(e)})
|
|
routers_list = [(router.id, router.name_or_id)
|
|
for router in routers_list]
|
|
return routers_list
|
|
|
|
|
|
class SelectRoutersStep(workflows.Step):
|
|
action_class = SelectRoutersAction
|
|
template_name = "project/firewalls/_update_routers.html"
|
|
contributes = ("router_ids", "all_routers_selected",
|
|
"Select No Routers")
|
|
|
|
def contribute(self, data, context):
|
|
if data:
|
|
routers = self.workflow.request.POST.getlist("router")
|
|
if routers:
|
|
routers = [r for r in routers if r != '']
|
|
context['router_ids'] = routers
|
|
else:
|
|
context['router_ids'] = []
|
|
return context
|
|
|
|
|
|
class AddPolicyAction(workflows.Action):
|
|
name = forms.CharField(max_length=80,
|
|
label=_("Name"))
|
|
description = forms.CharField(max_length=80,
|
|
label=_("Description"),
|
|
required=False)
|
|
shared = forms.BooleanField(label=_("Shared"),
|
|
initial=False,
|
|
required=False)
|
|
audited = forms.BooleanField(label=_("Audited"),
|
|
initial=False,
|
|
required=False)
|
|
|
|
def __init__(self, request, *args, **kwargs):
|
|
super(AddPolicyAction, self).__init__(request, *args, **kwargs)
|
|
|
|
class Meta(object):
|
|
name = _("Policy")
|
|
permissions = ('openstack.services.network',)
|
|
help_text = _("Create a firewall policy with an ordered list "
|
|
"of firewall rules.\n\n"
|
|
"A firewall policy is an ordered collection of firewall "
|
|
"rules. So if the traffic matches the first rule, the "
|
|
"other rules are not executed. If the traffic does not "
|
|
"match the current rule, then the next rule is "
|
|
"executed. A firewall policy has the following "
|
|
"attributes:\n\n"
|
|
"<li>Shared: A firewall policy can be shared across "
|
|
"tenants. Thus it can also be made part of an audit "
|
|
"workflow wherein the firewall policy can be audited "
|
|
"by the relevant entity that is authorized.</li>"
|
|
"<li>Audited: When audited is set to True, it indicates "
|
|
"that the firewall policy has been audited. "
|
|
"Each time the firewall policy or the associated "
|
|
"firewall rules are changed, this attribute will be "
|
|
"set to False and will have to be explicitly set to "
|
|
"True through an update operation.</li>\n"
|
|
"The name field is required, all others are optional.")
|
|
|
|
|
|
class AddPolicyStep(workflows.Step):
|
|
action_class = AddPolicyAction
|
|
contributes = ("name", "description", "shared", "audited")
|
|
|
|
def contribute(self, data, context):
|
|
context = super(AddPolicyStep, self).contribute(data, context)
|
|
if data:
|
|
return context
|
|
|
|
|
|
class AddPolicy(workflows.Workflow):
|
|
slug = "addpolicy"
|
|
name = _("Add Policy")
|
|
finalize_button_name = _("Add")
|
|
success_message = _('Added Policy "%s".')
|
|
failure_message = _('Unable to add Policy "%s".')
|
|
success_url = "horizon:project:firewalls:index"
|
|
default_steps = (AddPolicyStep, SelectRulesStep)
|
|
|
|
def format_status_message(self, message):
|
|
return message % self.context.get('name')
|
|
|
|
def handle(self, request, context):
|
|
try:
|
|
api.fwaas.policy_create(request, **context)
|
|
return True
|
|
except Exception as e:
|
|
msg = self.format_status_message(self.failure_message) + str(e)
|
|
exceptions.handle(request, msg)
|
|
return False
|
|
|
|
|
|
class AddFirewallAction(workflows.Action):
|
|
name = forms.CharField(max_length=80,
|
|
label=_("Name"),
|
|
required=False)
|
|
description = forms.CharField(max_length=80,
|
|
label=_("Description"),
|
|
required=False)
|
|
firewall_policy_id = forms.ThemableChoiceField(label=_("Policy"))
|
|
admin_state_up = forms.ThemableChoiceField(choices=[(True, _('UP')),
|
|
(False, _('DOWN'))],
|
|
label=_("Admin State"))
|
|
|
|
def __init__(self, request, *args, **kwargs):
|
|
super(AddFirewallAction, self).__init__(request, *args, **kwargs)
|
|
|
|
firewall_policy_id_choices = [('', _("Select a Policy"))]
|
|
try:
|
|
tenant_id = self.request.user.tenant_id
|
|
policies = api.fwaas.policy_list_for_tenant(request, tenant_id)
|
|
policies = sorted(policies, key=lambda policy: policy.name)
|
|
except Exception as e:
|
|
exceptions.handle(
|
|
request,
|
|
_('Unable to retrieve policy list (%(error)s).') % {
|
|
'error': str(e)})
|
|
policies = []
|
|
for p in policies:
|
|
firewall_policy_id_choices.append((p.id, p.name_or_id))
|
|
self.fields['firewall_policy_id'].choices = firewall_policy_id_choices
|
|
|
|
class Meta(object):
|
|
name = _("Firewall")
|
|
permissions = ('openstack.services.network',)
|
|
help_text = _("Create a firewall based on a policy.\n\n"
|
|
"A firewall represents a logical firewall resource that "
|
|
"a tenant can instantiate and manage. A firewall must "
|
|
"be associated with one policy, all other fields are "
|
|
"optional.")
|
|
|
|
|
|
class AddFirewallStep(workflows.Step):
|
|
action_class = AddFirewallAction
|
|
contributes = ("name", "firewall_policy_id", "description",
|
|
"admin_state_up")
|
|
|
|
def contribute(self, data, context):
|
|
context = super(AddFirewallStep, self).contribute(data, context)
|
|
context['admin_state_up'] = (context['admin_state_up'] == 'True')
|
|
return context
|
|
|
|
|
|
class AddFirewall(workflows.Workflow):
|
|
slug = "addfirewall"
|
|
name = _("Add Firewall")
|
|
finalize_button_name = _("Add")
|
|
success_message = _('Added Firewall "%s".')
|
|
failure_message = _('Unable to add Firewall "%s".')
|
|
success_url = "horizon:project:firewalls:index"
|
|
# fwaas is designed to support a wide range of vendor
|
|
# firewalls. Considering the multitude of vendor firewall
|
|
# features in place today, firewall definition can
|
|
# involve more complex configuration over time. Hence,
|
|
# a workflow instead of a single form is used for
|
|
# firewall_rule add to be ready for future extension.
|
|
default_steps = (AddFirewallStep, )
|
|
|
|
def format_status_message(self, message):
|
|
return message % self.context.get('name')
|
|
|
|
def handle(self, request, context):
|
|
try:
|
|
api.fwaas.firewall_create(request, **context)
|
|
return True
|
|
except Exception as e:
|
|
msg = self.format_status_message(self.failure_message) + str(e)
|
|
exceptions.handle(request, msg)
|
|
return False
|