410 lines
16 KiB
Python
410 lines
16 KiB
Python
# 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 types so that we can reference ListType in sphinx param declarations.
|
|
# We can't just use list, because sphinx gets confused by
|
|
# openstack.resource.Resource.list and openstack.resource2.Resource.list
|
|
# import jsonpatch
|
|
import types # noqa
|
|
|
|
from openstack.cloud import exc
|
|
from openstack.cloud import _normalize
|
|
from openstack.cloud import _utils
|
|
from openstack import exceptions
|
|
from openstack import proxy
|
|
|
|
|
|
class SecurityGroupCloudMixin(_normalize.Normalizer):
|
|
|
|
def __init__(self):
|
|
self.secgroup_source = self.config.config['secgroup_source']
|
|
|
|
def search_security_groups(self, name_or_id=None, filters=None):
|
|
# `filters` could be a dict or a jmespath (str)
|
|
groups = self.list_security_groups(
|
|
filters=filters if isinstance(filters, dict) else None
|
|
)
|
|
return _utils._filter_list(groups, name_or_id, filters)
|
|
|
|
def list_security_groups(self, filters=None):
|
|
"""List all available security groups.
|
|
|
|
:param filters: (optional) dict of filter conditions to push down
|
|
:returns: A list of security group ``munch.Munch``.
|
|
|
|
"""
|
|
# Security groups not supported
|
|
if not self._has_secgroups():
|
|
raise exc.OpenStackCloudUnavailableFeature(
|
|
"Unavailable feature: security groups"
|
|
)
|
|
|
|
if not filters:
|
|
filters = {}
|
|
|
|
data = []
|
|
# Handle neutron security groups
|
|
if self._use_neutron_secgroups():
|
|
# pass filters dict to the list to filter as much as possible on
|
|
# the server side
|
|
return list(
|
|
self.network.security_groups(allow_unknown_params=True,
|
|
**filters))
|
|
|
|
# Handle nova security groups
|
|
else:
|
|
data = proxy._json_response(self.compute.get(
|
|
'/os-security-groups', params=filters))
|
|
return self._normalize_secgroups(
|
|
self._get_and_munchify('security_groups', data))
|
|
|
|
def get_security_group(self, name_or_id, filters=None):
|
|
"""Get a security group by name or ID.
|
|
|
|
:param name_or_id: Name or ID of the security group.
|
|
:param filters:
|
|
A dictionary of meta data to use for further filtering. Elements
|
|
of this dictionary may, themselves, be dictionaries. Example::
|
|
|
|
{
|
|
'last_name': 'Smith',
|
|
'other': {
|
|
'gender': 'Female'
|
|
}
|
|
}
|
|
|
|
OR
|
|
A string containing a jmespath expression for further filtering.
|
|
Example:: "[?last_name==`Smith`] | [?other.gender]==`Female`]"
|
|
|
|
:returns: A security group ``munch.Munch`` or None if no matching
|
|
security group is found.
|
|
|
|
"""
|
|
return _utils._get_entity(
|
|
self, 'security_group', name_or_id, filters)
|
|
|
|
def get_security_group_by_id(self, id):
|
|
""" Get a security group by ID
|
|
|
|
:param id: ID of the security group.
|
|
:returns: A security group ``munch.Munch``.
|
|
"""
|
|
if not self._has_secgroups():
|
|
raise exc.OpenStackCloudUnavailableFeature(
|
|
"Unavailable feature: security groups"
|
|
)
|
|
error_message = ("Error getting security group with"
|
|
" ID {id}".format(id=id))
|
|
if self._use_neutron_secgroups():
|
|
return self.network.get_security_group(id)
|
|
else:
|
|
data = proxy._json_response(
|
|
self.compute.get(
|
|
'/os-security-groups/{id}'.format(id=id)),
|
|
error_message=error_message)
|
|
return self._normalize_secgroup(
|
|
self._get_and_munchify('security_group', data))
|
|
|
|
def create_security_group(self, name, description, project_id=None):
|
|
"""Create a new security group
|
|
|
|
:param string name: A name for the security group.
|
|
:param string description: Describes the security group.
|
|
:param string project_id:
|
|
Specify the project ID this security group will be created
|
|
on (admin-only).
|
|
|
|
:returns: A ``munch.Munch`` representing the new security group.
|
|
|
|
:raises: OpenStackCloudException on operation error.
|
|
:raises: OpenStackCloudUnavailableFeature if security groups are
|
|
not supported on this cloud.
|
|
"""
|
|
|
|
# Security groups not supported
|
|
if not self._has_secgroups():
|
|
raise exc.OpenStackCloudUnavailableFeature(
|
|
"Unavailable feature: security groups"
|
|
)
|
|
|
|
data = []
|
|
security_group_json = {
|
|
'name': name, 'description': description
|
|
}
|
|
if project_id is not None:
|
|
security_group_json['tenant_id'] = project_id
|
|
if self._use_neutron_secgroups():
|
|
return self.network.create_security_group(
|
|
**security_group_json)
|
|
else:
|
|
data = proxy._json_response(self.compute.post(
|
|
'/os-security-groups',
|
|
json={'security_group': security_group_json}))
|
|
return self._normalize_secgroup(
|
|
self._get_and_munchify('security_group', data))
|
|
|
|
def delete_security_group(self, name_or_id):
|
|
"""Delete a security group
|
|
|
|
:param string name_or_id: The name or unique ID of the security group.
|
|
|
|
:returns: True if delete succeeded, False otherwise.
|
|
|
|
:raises: OpenStackCloudException on operation error.
|
|
:raises: OpenStackCloudUnavailableFeature if security groups are
|
|
not supported on this cloud.
|
|
"""
|
|
# Security groups not supported
|
|
if not self._has_secgroups():
|
|
raise exc.OpenStackCloudUnavailableFeature(
|
|
"Unavailable feature: security groups"
|
|
)
|
|
|
|
# TODO(mordred): Let's come back and stop doing a GET before we do
|
|
# the delete.
|
|
secgroup = self.get_security_group(name_or_id)
|
|
if secgroup is None:
|
|
self.log.debug('Security group %s not found for deleting',
|
|
name_or_id)
|
|
return False
|
|
|
|
if self._use_neutron_secgroups():
|
|
self.network.delete_security_group(
|
|
secgroup['id'], ignore_missing=False)
|
|
return True
|
|
|
|
else:
|
|
proxy._json_response(self.compute.delete(
|
|
'/os-security-groups/{id}'.format(id=secgroup['id'])))
|
|
return True
|
|
|
|
@_utils.valid_kwargs('name', 'description')
|
|
def update_security_group(self, name_or_id, **kwargs):
|
|
"""Update a security group
|
|
|
|
:param string name_or_id: Name or ID of the security group to update.
|
|
:param string name: New name for the security group.
|
|
:param string description: New description for the security group.
|
|
|
|
:returns: A ``munch.Munch`` describing the updated security group.
|
|
|
|
:raises: OpenStackCloudException on operation error.
|
|
"""
|
|
# Security groups not supported
|
|
if not self._has_secgroups():
|
|
raise exc.OpenStackCloudUnavailableFeature(
|
|
"Unavailable feature: security groups"
|
|
)
|
|
|
|
group = self.get_security_group(name_or_id)
|
|
|
|
if group is None:
|
|
raise exc.OpenStackCloudException(
|
|
"Security group %s not found." % name_or_id)
|
|
|
|
if self._use_neutron_secgroups():
|
|
return self.network.update_security_group(
|
|
group['id'],
|
|
**kwargs
|
|
)
|
|
else:
|
|
for key in ('name', 'description'):
|
|
kwargs.setdefault(key, group[key])
|
|
data = proxy._json_response(
|
|
self.compute.put(
|
|
'/os-security-groups/{id}'.format(id=group['id']),
|
|
json={'security_group': kwargs}))
|
|
return self._normalize_secgroup(
|
|
self._get_and_munchify('security_group', data))
|
|
|
|
def create_security_group_rule(self,
|
|
secgroup_name_or_id,
|
|
port_range_min=None,
|
|
port_range_max=None,
|
|
protocol=None,
|
|
remote_ip_prefix=None,
|
|
remote_group_id=None,
|
|
direction='ingress',
|
|
ethertype='IPv4',
|
|
project_id=None):
|
|
"""Create a new security group rule
|
|
|
|
:param string secgroup_name_or_id:
|
|
The security group name or ID to associate with this security
|
|
group rule. If a non-unique group name is given, an exception
|
|
is raised.
|
|
:param int port_range_min:
|
|
The minimum port number in the range that is matched by the
|
|
security group rule. If the protocol is TCP or UDP, this value
|
|
must be less than or equal to the port_range_max attribute value.
|
|
If nova is used by the cloud provider for security groups, then
|
|
a value of None will be transformed to -1.
|
|
:param int port_range_max:
|
|
The maximum port number in the range that is matched by the
|
|
security group rule. The port_range_min attribute constrains the
|
|
port_range_max attribute. If nova is used by the cloud provider
|
|
for security groups, then a value of None will be transformed
|
|
to -1.
|
|
:param string protocol:
|
|
The protocol that is matched by the security group rule. Valid
|
|
values are None, tcp, udp, and icmp.
|
|
:param string remote_ip_prefix:
|
|
The remote IP prefix to be associated with this security group
|
|
rule. This attribute matches the specified IP prefix as the
|
|
source IP address of the IP packet.
|
|
:param string remote_group_id:
|
|
The remote group ID to be associated with this security group
|
|
rule.
|
|
:param string direction:
|
|
Ingress or egress: The direction in which the security group
|
|
rule is applied. For a compute instance, an ingress security
|
|
group rule is applied to incoming (ingress) traffic for that
|
|
instance. An egress rule is applied to traffic leaving the
|
|
instance.
|
|
:param string ethertype:
|
|
Must be IPv4 or IPv6, and addresses represented in CIDR must
|
|
match the ingress or egress rules.
|
|
:param string project_id:
|
|
Specify the project ID this security group will be created
|
|
on (admin-only).
|
|
|
|
:returns: A ``munch.Munch`` representing the new security group rule.
|
|
|
|
:raises: OpenStackCloudException on operation error.
|
|
"""
|
|
# Security groups not supported
|
|
if not self._has_secgroups():
|
|
raise exc.OpenStackCloudUnavailableFeature(
|
|
"Unavailable feature: security groups"
|
|
)
|
|
|
|
secgroup = self.get_security_group(secgroup_name_or_id)
|
|
if not secgroup:
|
|
raise exc.OpenStackCloudException(
|
|
"Security group %s not found." % secgroup_name_or_id)
|
|
|
|
if self._use_neutron_secgroups():
|
|
# NOTE: Nova accepts -1 port numbers, but Neutron accepts None
|
|
# as the equivalent value.
|
|
rule_def = {
|
|
'security_group_id': secgroup['id'],
|
|
'port_range_min':
|
|
None if port_range_min == -1 else port_range_min,
|
|
'port_range_max':
|
|
None if port_range_max == -1 else port_range_max,
|
|
'protocol': protocol,
|
|
'remote_ip_prefix': remote_ip_prefix,
|
|
'remote_group_id': remote_group_id,
|
|
'direction': direction,
|
|
'ethertype': ethertype
|
|
}
|
|
if project_id is not None:
|
|
rule_def['project_id'] = project_id
|
|
rule_def['tenant_id'] = project_id
|
|
|
|
return self.network.create_security_group_rule(
|
|
**rule_def
|
|
)
|
|
else:
|
|
# NOTE: Neutron accepts None for protocol. Nova does not.
|
|
if protocol is None:
|
|
raise exc.OpenStackCloudException('Protocol must be specified')
|
|
|
|
if direction == 'egress':
|
|
self.log.debug(
|
|
'Rule creation failed: Nova does not support egress rules'
|
|
)
|
|
raise exc.OpenStackCloudException(
|
|
'No support for egress rules')
|
|
|
|
# NOTE: Neutron accepts None for ports, but Nova requires -1
|
|
# as the equivalent value for ICMP.
|
|
#
|
|
# For TCP/UDP, if both are None, Neutron allows this and Nova
|
|
# represents this as all ports (1-65535). Nova does not accept
|
|
# None values, so to hide this difference, we will automatically
|
|
# convert to the full port range. If only a single port value is
|
|
# specified, it will error as normal.
|
|
if protocol == 'icmp':
|
|
if port_range_min is None:
|
|
port_range_min = -1
|
|
if port_range_max is None:
|
|
port_range_max = -1
|
|
elif protocol in ['tcp', 'udp']:
|
|
if port_range_min is None and port_range_max is None:
|
|
port_range_min = 1
|
|
port_range_max = 65535
|
|
|
|
security_group_rule_dict = dict(security_group_rule=dict(
|
|
parent_group_id=secgroup['id'],
|
|
ip_protocol=protocol,
|
|
from_port=port_range_min,
|
|
to_port=port_range_max,
|
|
cidr=remote_ip_prefix,
|
|
group_id=remote_group_id
|
|
))
|
|
if project_id is not None:
|
|
security_group_rule_dict[
|
|
'security_group_rule']['tenant_id'] = project_id
|
|
data = proxy._json_response(
|
|
self.compute.post(
|
|
'/os-security-group-rules',
|
|
json=security_group_rule_dict
|
|
))
|
|
return self._normalize_secgroup_rule(
|
|
self._get_and_munchify('security_group_rule', data))
|
|
|
|
def delete_security_group_rule(self, rule_id):
|
|
"""Delete a security group rule
|
|
|
|
:param string rule_id: The unique ID of the security group rule.
|
|
|
|
:returns: True if delete succeeded, False otherwise.
|
|
|
|
:raises: OpenStackCloudException on operation error.
|
|
:raises: OpenStackCloudUnavailableFeature if security groups are
|
|
not supported on this cloud.
|
|
"""
|
|
# Security groups not supported
|
|
if not self._has_secgroups():
|
|
raise exc.OpenStackCloudUnavailableFeature(
|
|
"Unavailable feature: security groups"
|
|
)
|
|
|
|
if self._use_neutron_secgroups():
|
|
self.network.delete_security_group_rule(
|
|
rule_id,
|
|
ignore_missing=False
|
|
)
|
|
return True
|
|
|
|
else:
|
|
try:
|
|
exceptions.raise_from_response(
|
|
self.compute.delete(
|
|
'/os-security-group-rules/{id}'.format(id=rule_id)))
|
|
except exc.OpenStackCloudResourceNotFound:
|
|
return False
|
|
|
|
return True
|
|
|
|
def _has_secgroups(self):
|
|
if not self.secgroup_source:
|
|
return False
|
|
else:
|
|
return self.secgroup_source.lower() in ('nova', 'neutron')
|
|
|
|
def _use_neutron_secgroups(self):
|
|
return (self.has_service('network')
|
|
and self.secgroup_source == 'neutron')
|