4 changed files with 634 additions and 0 deletions
@ -0,0 +1,85 @@
|
||||
--- |
||||
# General run of tests |
||||
# - Prepare projects/network objects |
||||
# - Create rbac object |
||||
# - Get rbac object info |
||||
# - Verify RBAC object match |
||||
# - Delete rbac object |
||||
# - Get rbac object info |
||||
# - Verify RBAC object deleted |
||||
|
||||
- name: Create source project |
||||
openstack.cloud.project: |
||||
cloud: "{{ cloud }}" |
||||
state: present |
||||
name: source_project |
||||
description: Source project for network RBAC test |
||||
domain_id: default |
||||
enabled: True |
||||
register: source_project |
||||
|
||||
- name: Create network - generic |
||||
openstack.cloud.network: |
||||
cloud: "{{ cloud }}" |
||||
name: "{{ network_name }}" |
||||
state: present |
||||
project: "{{ source_project.project.id }}" |
||||
shared: false |
||||
external: "{{ network_external }}" |
||||
register: network |
||||
|
||||
- name: Create target project |
||||
openstack.cloud.project: |
||||
cloud: "{{ cloud }}" |
||||
state: present |
||||
name: ansible_project |
||||
description: Target project for network RBAC test |
||||
domain_id: default |
||||
enabled: True |
||||
register: target_project |
||||
|
||||
- name: Create a new network RBAC policy |
||||
openstack.cloud.neutron_rbac_policy: |
||||
cloud: "{{ cloud }}" |
||||
object_id: "{{ network.network.id }}" |
||||
object_type: 'network' |
||||
action: 'access_as_shared' |
||||
target_project_id: "{{ target_project.project.id }}" |
||||
project_id: "{{ source_project.project.id }}" |
||||
register: rbac_policy |
||||
|
||||
- name: Get all rbac policies for {{ source_project.project.name }} - after creation |
||||
openstack.cloud.neutron_rbac_policies_info: |
||||
cloud: "{{ cloud }}" |
||||
project_id: "{{ source_project.project.id }}" |
||||
register: rbac_policies |
||||
|
||||
- name: Capture all existing policy IDs |
||||
set_fact: |
||||
rbac_policy_ids: "{{ rbac_policies.policies | map(attribute='id') | list }}" |
||||
|
||||
- name: Verify policy exists - after creation |
||||
assert: |
||||
that: |
||||
- rbac_policy.policy.id in rbac_policy_ids |
||||
|
||||
- name: Delete RBAC policy |
||||
openstack.cloud.neutron_rbac_policy: |
||||
cloud: "{{ cloud }}" |
||||
policy_id: "{{ rbac_policy.policy.id }}" |
||||
state: absent |
||||
|
||||
- name: Get all rbac policies for {{ source_project.project.name }} - after deletion |
||||
openstack.cloud.neutron_rbac_policies_info: |
||||
cloud: "{{ cloud }}" |
||||
project_id: "{{ source_project.project.id }}" |
||||
register: rbac_policies_remaining |
||||
|
||||
- name: Capture all remaining policy IDs |
||||
set_fact: |
||||
remaining_rbac_policy_ids: "{{ rbac_policies_remaining.policies | map(attribute='id') | list }}" |
||||
|
||||
- name: Verify policy does not exist - after deletion |
||||
assert: |
||||
that: |
||||
- not rbac_policy.policy.id in remaining_rbac_policy_ids |
@ -0,0 +1,237 @@
|
||||
#!/usr/bin/python |
||||
# coding: utf-8 -*- |
||||
|
||||
# Copyright: Ansible Project |
||||
# (c) 2021, Ashraf Hasson <ahasson@redhat.com> |
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) |
||||
|
||||
from __future__ import (absolute_import, division, print_function) |
||||
__metaclass__ = type |
||||
|
||||
DOCUMENTATION = r''' |
||||
--- |
||||
module: neutron_rbac_policies_info |
||||
short_description: Fetch Neutron policies. |
||||
author: OpenStack Ansible SIG |
||||
description: |
||||
- Get RBAC policies against a network, security group or a QoS Policy for one or more projects. |
||||
- If a C(policy_id) was not provided, this module will attempt to fetch all available policies. |
||||
- Accepts same arguments as OpenStackSDK network proxy C(find_rbac_policy) and C(rbac_policies) functions which are ultimately passed over to C(RBACPolicy) |
||||
- All parameters passed in to this module act as a filter for when no C(policy_id) was provided, otherwise they're ignored. |
||||
- Returns None if no matching policy was found as opposed to failing. |
||||
|
||||
options: |
||||
policy_id: |
||||
description: |
||||
- The RBAC policy ID |
||||
- If provided, all other filters are ignored |
||||
type: str |
||||
object_id: |
||||
description: |
||||
- The object ID (the subject of the policy) to which the RBAC rules applies |
||||
- This would be the ID of a network, security group or a qos policy |
||||
- Mutually exclusive with the C(object_type) |
||||
type: str |
||||
object_type: |
||||
description: |
||||
- Can be one of the following object types C(network), C(security_group) or C(qos_policy) |
||||
- Mutually exclusive with the C(object_id) |
||||
choices: ['network', 'security_group', 'qos_policy'] |
||||
type: str |
||||
target_project_id: |
||||
description: |
||||
- Filters the RBAC rules based on the target project id |
||||
- Logically AND'ed with other filters |
||||
- Mutually exclusive with C(project_id) |
||||
type: str |
||||
project_id: |
||||
description: |
||||
- Filters the RBAC rules based on the project id to which the object belongs to |
||||
- Logically AND'ed with other filters |
||||
- Mutually exclusive with C(target_project_id) |
||||
type: str |
||||
project: |
||||
description: |
||||
- Filters the RBAC rules based on the project name |
||||
- Logically AND'ed with other filters |
||||
type: str |
||||
action: |
||||
description: |
||||
- Can be either of the following options C(access_as_shared) | C(access_as_external) |
||||
- Logically AND'ed with other filters |
||||
choices: ['access_as_shared', 'access_as_external'] |
||||
type: str |
||||
|
||||
extends_documentation_fragment: |
||||
- openstack.cloud.openstack |
||||
''' |
||||
|
||||
EXAMPLES = r''' |
||||
# Gather all rbac policies for a project |
||||
- name: Get all rbac policies for {{ project }} |
||||
openstack.cloud.neutron_rbac_policies_info: |
||||
project_id: "{{ project.id }}" |
||||
''' |
||||
|
||||
RETURN = r''' |
||||
# return value can either be plural or signular depending on what was passed in as parameters |
||||
policies: |
||||
description: |
||||
- List of rbac policies, this could also be returned as a singular element, i.e., 'policy' |
||||
type: complex |
||||
returned: always |
||||
contains: |
||||
object_id: |
||||
description: |
||||
- The UUID of the object to which the RBAC rules apply |
||||
type: str |
||||
sample: "7422172b-2961-475c-ac68-bd0f2a9960ad" |
||||
target_project_id: |
||||
description: |
||||
- The UUID of the target project |
||||
type: str |
||||
sample: "c201a689c016435c8037977166f77368" |
||||
project_id: |
||||
description: |
||||
- The UUID of the project to which access is granted |
||||
type: str |
||||
sample: "84b8774d595b41e89f3dfaa1fd76932c" |
||||
object_type: |
||||
description: |
||||
- The object type to which the RBACs apply |
||||
type: str |
||||
sample: "network" |
||||
action: |
||||
description: |
||||
- The access model specified by the RBAC rules |
||||
type: str |
||||
sample: "access_as_shared" |
||||
id: |
||||
description: |
||||
- The ID of the RBAC rule/policy |
||||
type: str |
||||
sample: "4154ce0c-71a7-4d87-a905-09762098ddb9" |
||||
name: |
||||
description: |
||||
- The name of the RBAC rule; usually null |
||||
type: str |
||||
sample: null |
||||
location: |
||||
description: |
||||
- A dictionary of the project details to which access is granted |
||||
type: dict |
||||
sample: >- |
||||
{ |
||||
"cloud": "devstack", |
||||
"region_name": "", |
||||
"zone": null, |
||||
"project": { |
||||
"id": "84b8774d595b41e89f3dfaa1fd76932c", |
||||
"name": null, |
||||
"domain_id": null, |
||||
"domain_name": null |
||||
} |
||||
} |
||||
''' |
||||
|
||||
import re |
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule |
||||
|
||||
|
||||
class NeutronRbacPoliciesInfo(OpenStackModule): |
||||
argument_spec = dict( |
||||
policy_id=dict(), |
||||
object_id=dict(), # ID of the object that this RBAC policy affects. |
||||
object_type=dict(choices=['security_group', 'qos_policy', 'network']), # Type of the object that this RBAC policy affects. |
||||
target_project_id=dict(), # The ID of the project this RBAC will be enforced. |
||||
project_id=dict(), # The owner project ID. |
||||
project=dict(), |
||||
action=dict(choices=['access_as_external', 'access_as_shared']), # Action for the RBAC policy. |
||||
) |
||||
|
||||
module_kwargs = dict( |
||||
supports_check_mode=True, |
||||
) |
||||
|
||||
def _filter_policies_by(self, policies, key, value): |
||||
filtered = [] |
||||
regexp = re.compile(r"location\.project\.([A-Za-z]+)") |
||||
if regexp.match(key): |
||||
attribute = key.split('.')[-1] |
||||
for p in policies: |
||||
if p['location']['project'][attribute] == value: |
||||
filtered.append(p) |
||||
else: |
||||
for p in policies: |
||||
if getattr(p, key) == value: |
||||
filtered.append(p) |
||||
|
||||
return filtered |
||||
|
||||
def _get_rbac_policies(self): |
||||
object_type = self.params.get('object_type') |
||||
project_id = self.params.get('project_id') |
||||
action = self.params.get('action') |
||||
|
||||
search_attributes = {} |
||||
if object_type is not None: |
||||
search_attributes['object_type'] = object_type |
||||
if project_id is not None: |
||||
search_attributes['project_id'] = project_id |
||||
if action is not None: |
||||
search_attributes['action'] = action |
||||
|
||||
try: |
||||
policies = [] |
||||
generator = self.conn.network.rbac_policies(**search_attributes) |
||||
for p in generator: |
||||
policies.append(p) |
||||
except self.sdk.exceptions.OpenStackCloudException as ex: |
||||
self.fail_json(msg='Failed to get RBAC policies: {0}'.format(str(ex))) |
||||
|
||||
return policies |
||||
|
||||
def run(self): |
||||
policy_id = self.params.get('policy_id') |
||||
object_id = self.params.get('object_id') |
||||
object_type = self.params.get('object_type') |
||||
project_id = self.params.get('project_id') |
||||
project = self.params.get('project') |
||||
target_project_id = self.params.get('target_project_id') |
||||
|
||||
if self.ansible.check_mode: |
||||
self.exit_json(changed=False) |
||||
|
||||
if policy_id is not None: |
||||
try: |
||||
policy = self.conn.network.get_rbac_policy(policy_id) |
||||
self.exit_json(changed=False, policy=policy) |
||||
except self.sdk.exceptions.ResourceNotFound: |
||||
self.exit_json(changed=False, policy=None) |
||||
except self.sdk.exceptions.OpenStackCloudException as ex: |
||||
self.fail_json(msg='Failed to get RBAC policy: {0}'.format(str(ex))) |
||||
else: |
||||
if object_id is not None and object_type is not None: |
||||
self.fail_json(msg='object_id and object_type are mutually exclusive, please specify one of the two.') |
||||
if project_id is not None and target_project_id is not None: |
||||
self.fail_json(msg='project_id and target_project_id are mutually exclusive, please specify one of the two.') |
||||
|
||||
filtered_policies = self._get_rbac_policies() |
||||
|
||||
if project is not None: |
||||
filtered_policies = self._filter_policies_by(filtered_policies, 'location.project.name', project) |
||||
if object_id is not None: |
||||
filtered_policies = self._filter_policies_by(filtered_policies, 'object_id', object_id) |
||||
if target_project_id is not None: |
||||
filtered_policies = self._filter_policies_by(filtered_policies, 'target_project_id', target_project_id) |
||||
|
||||
self.exit_json(policies=filtered_policies, changed=False) |
||||
|
||||
|
||||
def main(): |
||||
module = NeutronRbacPoliciesInfo() |
||||
module() |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
main() |
@ -0,0 +1,308 @@
|
||||
#!/usr/bin/python |
||||
|
||||
# Copyright: Ansible Project |
||||
# (c) 2021, Ashraf Hasson <ahasson@redhat.com> |
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) |
||||
|
||||
DOCUMENTATION = r''' |
||||
--- |
||||
module: neutron_rbac_policy |
||||
short_description: Create or delete a Neutron policy to apply a RBAC rule against an object. |
||||
author: OpenStack Ansible SIG |
||||
description: |
||||
- Create a policy to apply a RBAC rule against a network, security group or a QoS Policy or update/delete an existing policy. |
||||
- If a C(policy_id) was provided but not found, this module will attempt to create a new policy rather than error out when updating an existing rule. |
||||
- Accepts same arguments as OpenStackSDK network proxy C(find_rbac_policy) and C(rbac_policies) functions which are ultimately passed over to C(RBACPolicy) |
||||
|
||||
options: |
||||
policy_id: |
||||
description: |
||||
- The RBAC policy ID |
||||
- Required when deleting or updating an existing RBAC policy rule, ignored otherwise |
||||
type: str |
||||
object_id: |
||||
description: |
||||
- The object ID (the subject of the policy) to which the RBAC rule applies |
||||
- Cannot be changed when updating an existing policy |
||||
- Required when creating a RBAC policy rule, ignored when deleting a policy |
||||
type: str |
||||
object_type: |
||||
description: |
||||
- Can be one of the following object types C(network), C(security_group) or C(qos_policy) |
||||
- Cannot be changed when updating an existing policy |
||||
- Required when creating a RBAC policy rule, ignored when deleting a policy |
||||
choices: ['network', 'security_group', 'qos_policy'] |
||||
type: str |
||||
target_project_id: |
||||
description: |
||||
- The project to which access to be allowed or revoked/disallowed |
||||
- Can be specified/changed when updating an existing policy |
||||
- Required when creating or updating a RBAC policy rule, ignored when deleting a policy |
||||
type: str |
||||
project_id: |
||||
description: |
||||
- The project to which the object_id belongs |
||||
- Cannot be changed when updating an existing policy |
||||
- Required when creating a RBAC policy rule, ignored when deleting a policy |
||||
type: str |
||||
action: |
||||
description: |
||||
- Can be either of the following options C(access_as_shared) | C(access_as_external) |
||||
- Cannot be changed when updating an existing policy |
||||
- Required when creating a RBAC policy rule, ignored when deleting a policy |
||||
choices: ['access_as_shared', 'access_as_external'] |
||||
type: str |
||||
state: |
||||
description: |
||||
- Whether the RBAC rule should be C(present) or C(absent). |
||||
choices: ['present', 'absent'] |
||||
default: present |
||||
type: str |
||||
|
||||
extends_documentation_fragment: |
||||
- openstack.cloud.openstack |
||||
''' |
||||
|
||||
EXAMPLES = r''' |
||||
# Ensure network RBAC policy exists |
||||
- name: Create a new network RBAC policy |
||||
neutron_rbac_policy: |
||||
object_id: '7422172b-2961-475c-ac68-bd0f2a9960ad' |
||||
object_type: 'network' |
||||
target_project_id: 'a12f9ce1de0645e0a0b01c2e679f69ec' |
||||
project_id: '84b8774d595b41e89f3dfaa1fd76932d' |
||||
|
||||
# Update network RBAC policy |
||||
- name: Update an existing network RBAC policy |
||||
neutron_rbac_policy: |
||||
policy_id: 'f625242a-6a73-47ac-8d1f-91440b2c617f' |
||||
target_project_id: '163c89e065a94e069064e551e15daf0e' |
||||
|
||||
# Delete an existing RBAC policy |
||||
- name: Delete RBAC policy |
||||
openstack.cloud.openstack.neutron_rbac_policy: |
||||
policy_id: 'f625242a-6a73-47ac-8d1f-91440b2c617f' |
||||
state: absent |
||||
''' |
||||
|
||||
RETURN = r''' |
||||
policy: |
||||
description: |
||||
- A hash representing the policy |
||||
type: complex |
||||
returned: always |
||||
contains: |
||||
object_id: |
||||
description: |
||||
- The UUID of the object to which the RBAC rules apply |
||||
type: str |
||||
sample: "7422172b-2961-475c-ac68-bd0f2a9960ad" |
||||
target_project_id: |
||||
description: |
||||
- The UUID of the target project |
||||
type: str |
||||
sample: "c201a689c016435c8037977166f77368" |
||||
project_id: |
||||
description: |
||||
- The UUID of the project to which access is granted |
||||
type: str |
||||
sample: "84b8774d595b41e89f3dfaa1fd76932c" |
||||
object_type: |
||||
description: |
||||
- The object type to which the RBACs apply |
||||
type: str |
||||
sample: "network" |
||||
action: |
||||
description: |
||||
- The access model specified by the RBAC rules |
||||
type: str |
||||
sample: "access_as_shared" |
||||
id: |
||||
description: |
||||
- The ID of the RBAC rule/policy |
||||
type: str |
||||
sample: "4154ce0c-71a7-4d87-a905-09762098ddb9" |
||||
name: |
||||
description: |
||||
- The name of the RBAC rule; usually null |
||||
type: str |
||||
sample: null |
||||
location: |
||||
description: |
||||
- A dictionary of the project details to which access is granted |
||||
type: dict |
||||
sample: >- |
||||
{ |
||||
"cloud": "devstack", |
||||
"region_name": "", |
||||
"zone": null, |
||||
"project": { |
||||
"id": "84b8774d595b41e89f3dfaa1fd76932c", |
||||
"name": null, |
||||
"domain_id": null, |
||||
"domain_name": null |
||||
} |
||||
} |
||||
''' |
||||
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule |
||||
|
||||
|
||||
class NeutronRbacPolicy(OpenStackModule): |
||||
argument_spec = dict( |
||||
policy_id=dict(), |
||||
object_id=dict(), # ID of the object that this RBAC policy affects. |
||||
object_type=dict(choices=['security_group', 'qos_policy', 'network']), # Type of the object that this RBAC policy affects. |
||||
target_project_id=dict(), # The ID of the project this RBAC will be enforced. |
||||
project_id=dict(), # The owner project ID. |
||||
action=dict(choices=['access_as_external', 'access_as_shared']), # Action for the RBAC policy. |
||||
state=dict(default='present', choices=['absent', 'present']) |
||||
) |
||||
|
||||
module_kwargs = dict( |
||||
supports_check_mode=True, |
||||
) |
||||
|
||||
def _delete_rbac_policy(self, policy): |
||||
""" |
||||
Delete an existing RBAC policy |
||||
returns: the "Changed" state |
||||
""" |
||||
|
||||
if policy is None: |
||||
self.fail_json(msg='Must specify policy_id for delete') |
||||
|
||||
try: |
||||
self.conn.network.delete_rbac_policy(policy.id) |
||||
except self.sdk.exceptions.OpenStackCloudException as ex: |
||||
self.fail_json(msg='Failed to delete RBAC policy: {0}'.format(str(ex))) |
||||
|
||||
return True |
||||
|
||||
def _create_rbac_policy(self): |
||||
""" |
||||
Creates a new RBAC policy |
||||
returns: the "Changed" state of the RBAC policy |
||||
""" |
||||
|
||||
object_id = self.params.get('object_id') |
||||
object_type = self.params.get('object_type') |
||||
target_project_id = self.params.get('target_project_id') |
||||
project_id = self.params.get('project_id') |
||||
action = self.params.get('action') |
||||
|
||||
attributes = { |
||||
'object_id': object_id, |
||||
'object_type': object_type, |
||||
'target_project_id': target_project_id, |
||||
'project_id': project_id, |
||||
'action': action |
||||
} |
||||
|
||||
if not all(attributes.values()): |
||||
self.fail_json(msg='Missing one or more required parameter for creating a RBAC policy') |
||||
|
||||
try: |
||||
search_attributes = dict(attributes) |
||||
del search_attributes['object_id'] |
||||
del search_attributes['target_project_id'] |
||||
policies = self.conn.network.rbac_policies(**search_attributes) |
||||
for p in policies: |
||||
if p.object_id == object_id and p.target_project_id == target_project_id: |
||||
return (False, p) |
||||
|
||||
# if no matching policy exists, attempt to create one |
||||
policy = self.conn.network.create_rbac_policy(**attributes) |
||||
except self.sdk.exceptions.OpenStackCloudException as ex: |
||||
self.fail_json(msg='Failed to create RBAC policy: {0}'.format(str(ex))) |
||||
|
||||
return (True, policy) |
||||
|
||||
def _update_rbac_policy(self, policy): |
||||
""" |
||||
Updates an existing RBAC policy |
||||
returns: the "Changed" state of the RBAC policy |
||||
""" |
||||
|
||||
object_id = self.params.get('object_id') |
||||
object_type = self.params.get('object_type') |
||||
target_project_id = self.params.get('target_project_id') |
||||
project_id = self.params.get('project_id') |
||||
action = self.params.get('action') |
||||
|
||||
allowed_attributes = { |
||||
'rbac_policy': policy.id, |
||||
'target_project_id': target_project_id |
||||
} |
||||
|
||||
disallowed_attributes = { |
||||
'object_id': object_id, |
||||
'object_type': object_type, |
||||
'project_id': project_id, |
||||
'action': action |
||||
} |
||||
|
||||
if not all(allowed_attributes.values()): |
||||
self.fail_json(msg='Missing one or more required parameter for updating a RBAC policy') |
||||
|
||||
if any(disallowed_attributes.values()): |
||||
self.fail_json(msg='Cannot change disallowed parameters while updating a RBAC policy: ["object_id", "object_type", "project_id", "action"]') |
||||
|
||||
try: |
||||
policy = self.conn.network.update_rbac_policy(**allowed_attributes) |
||||
except self.sdk.exceptions.OpenStackCloudException as ex: |
||||
self.fail_json(msg='Failed to update the RBAC policy: {0}'.format(str(ex))) |
||||
|
||||
return (True, policy) |
||||
|
||||
def _policy_state_change(self, policy): |
||||
state = self.params['state'] |
||||
if state == 'present': |
||||
if not policy: |
||||
return True |
||||
if state == 'absent' and policy: |
||||
return True |
||||
return False |
||||
|
||||
def run(self): |
||||
policy_id = self.params.get('policy_id') |
||||
state = self.params.get('state') |
||||
|
||||
if policy_id is not None: |
||||
try: |
||||
policy = self.conn.network.get_rbac_policy(policy_id) |
||||
except self.sdk.exceptions.ResourceNotFound: |
||||
policy = None |
||||
except self.sdk.exceptions.OpenStackCloudException as ex: |
||||
self.fail_json(msg='Failed to get RBAC policy: {0}'.format(str(ex))) |
||||
else: |
||||
policy = None |
||||
|
||||
if self.ansible.check_mode: |
||||
self.exit_json(changed=self._policy_state_change(policy), policy=policy) |
||||
|
||||
if state == 'absent': |
||||
if policy is None and policy_id: |
||||
self.exit_json(changed=False) |
||||
if policy_id is None: |
||||
self.fail_json(msg='Must specify policy_id when state is absent') |
||||
if policy is not None: |
||||
changed = self._delete_rbac_policy(policy) |
||||
self.exit_json(changed=changed) |
||||
# state == 'present' |
||||
else: |
||||
if policy is None: |
||||
(changed, new_policy) = self._create_rbac_policy() |
||||
else: |
||||
(changed, new_policy) = self._update_rbac_policy(policy) |
||||
|
||||
self.exit_json(changed=changed, policy=new_policy) |
||||
|
||||
|
||||
def main(): |
||||
module = NeutronRbacPolicy() |
||||
module() |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
main() |
Loading…
Reference in new issue