#!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2013, John Dewey # # This module 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 software 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 software. If not, see . import locale import os import six try: from novaclient.openstack.common import uuidutils from novaclient.openstack.common import strutils from novaclient.v1_1 import client from novaclient.v1_1 import security_groups from novaclient.v1_1 import security_group_rules from novaclient import exceptions except ImportError: print("failed=True msg='novaclient is required for this module to work'") DOCUMENTATION = ''' --- module: security_group version_added: "1.5" short_description: Maintain nova security groups. description: - Manage nova security groups using the python-novaclient library. options: login_username: description: - Login username to authenticate to keystone. If not set then the value of the OS_USERNAME environment variable is used. required: false default: None login_password: description: - Password of login user. If not set then the value of the OS_PASSWORD environment variable is used. required: false default: None login_tenant_name: description: - The tenant name of the login user. If not set then the value of the OS_TENANT_NAME environment variable is used. required: false default: None auth_url: description: - The keystone url for authentication. If not set then the value of the OS_AUTH_URL environment variable is used. required: false default: None region_name: description: - Name of the region. required: false default: None name: description: - Name of the security group. required: true description: description: - Description of the security group. required: true rules: description: - List of firewall rules to enforce in this group (see example). Must specify either an IPv4 'cidr' address or 'group' UUID. required: true state: description: - Indicate desired state of the resource. choices: ['present', 'absent'] required: false default: 'present' requirements: ["novaclient"] ''' EXAMPLES = ''' - name: create example group and rules local_action: module: security_group name: example description: an example nova group rules: - ip_protocol: tcp from_port: 80 to_port: 80 cidr: 0.0.0.0/0 - ip_protocol: tcp from_port: 3306 to_port: 3306 group: "{{ group_uuid }}" - ip_protocol: icmp from_port: -1 to_port: -1 cidr: 0.0.0.0/0 - name: delete rule from example group local_action: module: security_group name: example description: an example nova group rules: - ip_protocol: tcp from_port: 80 to_port: 80 cidr: 0.0.0.0/0 - ip_protocol: icmp from_port: -1 to_port: -1 cidr: 0.0.0.0/0 state: absent ''' class NovaGroup(object): def __init__(self, client): self._sg = security_groups.SecurityGroupManager(client) # Taken from novaclient/v1_1/shell.py. def _get_secgroup(self, secgroup): # Check secgroup is an UUID if uuidutils.is_uuid_like(strutils.safe_encode(secgroup)): try: sg = self._sg.get(secgroup) return sg except exceptions.NotFound: return False # Check secgroup as a name for s in self._sg.list(): encoding = (locale.getpreferredencoding() or sys.stdin.encoding or 'UTF-8') if not six.PY3: s.name = s.name.encode(encoding) if secgroup == s.name: return s return False class SecurityGroup(NovaGroup): def __init__(self, client, module): super(SecurityGroup, self).__init__(client) self._module = module self._name = module.params.get('name') self._description = module.params.get('description') def get(self): return self._get_secgroup(self._name) def create(self): return self._sg.create(self._name, self._description) def delete(self): return self._sg.delete(self._name) class SecurityGroupRule(NovaGroup): def __init__(self, client, module): super(SecurityGroupRule, self).__init__(client) self._module = module self._name = module.params.get('name') self._rules = module.params.get('rules') self._validate_rules() self._sgr = security_group_rules.SecurityGroupRuleManager(client) self._secgroup = self._get_secgroup(self._name) self._current_rules = self._lookup_dict(self._secgroup.rules) def _concat_security_group_rule(self, rule): """ Normalize the given rule into a string in the format of: protocol-from_port-to_port-group The `group` needs a bit of massaging. 1. If an empty dict -- return None. 2. If a dict -- lookup group UUID (novaclient only returns the name). 3. Return `group` from rules dict. :param rule: A novaclient SecurityGroupRule object. """ group = rule.get('group') # Oddly novaclient occasionaly returns None as {}. if group is not None and not any(group): group = None elif type(group) == dict: g = group.get('name') group = self._get_secgroup(g) r = "%s-%s-%s-%s" % (rule.get('ip_protocol'), rule.get('from_port'), rule.get('to_port'), group) return r def _lookup_dict(self, rules): """ Populate a dict with current rules. :param rule: A novaclient SecurityGroupRule object. """ return {self._concat_security_group_rule(rule): rule for rule in rules} def _get_rule(self, rule): """ Return rule when found and False when not. :param rule: A novaclient SecurityGroupRule object. """ r = self._concat_security_group_rule(rule) if r in self._current_rules: return self._current_rules[r] def _validate_rules(self): for rule in self._rules: if 'group' in rule and 'cidr' in rule: self._module.fail_json(msg="Specify group OR cidr") def create(self): changed = False filtered = [rule for rule in self._rules if rule.get('state') != 'absent'] for rule in filtered: if not self._get_rule(rule): if 'cidr' in rule: self._sgr.create(self._secgroup.id, rule.get('ip_protocol'), rule.get('from_port'), rule.get('to_port'), cidr=rule.get('cidr')) changed = True if 'group' in rule: self._sgr.create(self._secgroup.id, rule.get('ip_protocol'), rule.get('from_port'), rule.get('to_port'), group_id=rule.get('group')) changed = True return changed def delete(self): changed = False filtered = [rule for rule in self._rules if rule.get('state') == 'absent'] for rule in filtered: r = self._get_rule(rule) if r: self._sgr.delete(r.get('id')) changed = True return changed def update(self): changed = False if self.create(): changed = True if self.delete(): changed = True return changed def main(): module = AnsibleModule( argument_spec=dict( name=dict(required=True), description=dict(required=True), rules=dict(), login_username=dict(), login_password=dict(no_log=True), login_tenant_name=dict(), auth_url= dict(), region_name=dict(default=None), state = dict(default='present', choices=['present', 'absent']), ), supports_check_mode=False, ) login_username = module.params.get('login_username') login_password = module.params.get('login_password') login_tenant_name = module.params.get('login_tenant_name') auth_url = module.params.get('auth_url') # allow stackrc environment variables to be used if ansible vars aren't set if not login_username and 'OS_USERNAME' in os.environ: login_username = os.environ['OS_USERNAME'] if not login_password and 'OS_PASSWORD' in os.environ: login_password = os.environ['OS_PASSWORD'] if not login_tenant_name and 'OS_TENANT_NAME' in os.environ: login_tenant_name = os.environ['OS_TENANT_NAME'] if not auth_url and 'OS_AUTH_URL' in os.environ: auth_url = os.environ['OS_AUTH_URL'] nova = client.Client(login_username, login_password, login_tenant_name, auth_url, service_type='compute') try: nova.authenticate() except exceptions.Unauthorized as e: module.fail_json(msg="Invalid OpenStack Nova credentials.: %s" % e.message) except exceptions.AuthorizationFailure as e: module.fail_json(msg="Unable to authorize user: %s" % e.message) rules = module.params.get('rules') state = module.params.get('state') security_group = SecurityGroup(nova, module) changed = False group_id = None group = security_group.get() if group: group_id = group.id if state == 'absent': security_group.delete() changed = True elif state == 'present': group = security_group.create() changed = True group_id = group.id if rules is not None: security_group_rules = SecurityGroupRule(nova, module) if security_group_rules.update(): changed = True module.exit_json(changed=changed, group_id=group_id) # this is magic, see lib/ansible/module_common.py #<> main()