Add support for adding external members to ipagroup.

This patch add support for adding external members to ipagroup which
have the `external` attribute set. It adds another attribute to the
module, `external_members`, which is a list of users or groups from
an external trust, to be added to the group.

This patch requires server-trust-ad to be tested, as such, the tests
have been guarded by a test block, for when such tests are available
in ansible-freeipa CI.

Fixes issue #418
This commit is contained in:
Rafael Guterres Jeffman
2020-10-13 15:23:17 -03:00
parent 698bd81475
commit c7db187801
4 changed files with 260 additions and 67 deletions

View File

@@ -109,6 +109,24 @@ Example playbook to add group members to a group:
- appops - appops
``` ```
Example playbook to add members from a trusted realm to an external group:
```yaml
--
- name: Playbook to handle groups.
hosts: ipaserver
became: true
- name: Create an external group and add members from a trust to it.
ipagroup:
ipaadmin_password: SomeADMINpassword
name: extgroup
external: yes
externalmember:
- WINIPA\\Web Users
- WINIPA\\Developers
```
Example playbook to remove groups: Example playbook to remove groups:
```yaml ```yaml
@@ -148,6 +166,7 @@ Variable | Description | Required
`service` | List of service name strings assigned to this group. Only usable with IPA versions 4.7 and up. | no `service` | List of service name strings assigned to this group. Only usable with IPA versions 4.7 and up. | no
`membermanager_user` | List of member manager users assigned to this group. Only usable with IPA versions 4.8.4 and up. | no `membermanager_user` | List of member manager users assigned to this group. Only usable with IPA versions 4.8.4 and up. | no
`membermanager_group` | List of member manager groups assigned to this group. Only usable with IPA versions 4.8.4 and up. | no `membermanager_group` | List of member manager groups assigned to this group. Only usable with IPA versions 4.8.4 and up. | no
`externalmember` \| `ipaexternalmember` \| `external_member`| List of members of a trusted domain in DOM\\name or name@domain form. | no
`action` | Work on group or member level. It can be on of `member` or `group` and defaults to `group`. | no `action` | Work on group or member level. It can be on of `member` or `group` and defaults to `group`. | no
`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | yes `state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | yes

View File

@@ -92,6 +92,12 @@ options:
- Only usable with IPA versions 4.8.4 and up. - Only usable with IPA versions 4.8.4 and up.
required: false required: false
type: list type: list
externalmember:
description:
- List of members of a trusted domain in DOM\\name or name@domain form.
required: false
type: list
ailases: ["ipaexternalmember", "external_member"]
action: action:
description: Work on group or member level description: Work on group or member level
default: group default: group
@@ -145,7 +151,6 @@ EXAMPLES = """
- sysops - sysops
- appops - appops
# Create a non-POSIX group # Create a non-POSIX group
- ipagroup: - ipagroup:
ipaadmin_password: SomeADMINpassword ipaadmin_password: SomeADMINpassword
@@ -158,6 +163,15 @@ EXAMPLES = """
name: nonposix name: nonposix
posix: yes posix: yes
# Create an external group and add members from a trust to it.
- ipagroup:
ipaadmin_password: SomeADMINpassword
name: extgroup
external: yes
externalmember:
- WINIPA\\Web Users
- WINIPA\\Developers
# Remove goups sysops, appops, ops and nongroup # Remove goups sysops, appops, ops and nongroup
- ipagroup: - ipagroup:
ipaadmin_password: SomeADMINpassword ipaadmin_password: SomeADMINpassword
@@ -203,7 +217,7 @@ def gen_args(description, gid, nomembers):
return _args return _args
def gen_member_args(user, group, service): def gen_member_args(user, group, service, externalmember):
_args = {} _args = {}
if user is not None: if user is not None:
_args["member_user"] = user _args["member_user"] = user
@@ -211,12 +225,24 @@ def gen_member_args(user, group, service):
_args["member_group"] = group _args["member_group"] = group
if service is not None: if service is not None:
_args["member_service"] = service _args["member_service"] = service
if externalmember is not None:
_args["member_external"] = externalmember
return _args return _args
def is_external_group(res_find):
"""Verify if the result group is an external group."""
return res_find and 'ipaexternalgroup' in res_find['objectclass']
def is_posix_group(res_find):
"""Verify if the result group is an external group."""
return res_find and 'posixgroup' in res_find['objectclass']
def check_objectclass_args(module, res_find, nonposix, posix, external): def check_objectclass_args(module, res_find, nonposix, posix, external):
if res_find and 'posixgroup' in res_find['objectclass']: if is_posix_group(res_find):
if ( if (
(posix is not None and posix is False) (posix is not None and posix is False)
or nonposix or nonposix
@@ -226,7 +252,7 @@ def check_objectclass_args(module, res_find, nonposix, posix, external):
msg="Cannot change `POSIX` status of a group " msg="Cannot change `POSIX` status of a group "
"to `non-POSIX` or `external`.") "to `non-POSIX` or `external`.")
# Can't change an existing external group # Can't change an existing external group
if res_find and 'ipaexternalgroup' in res_find['objectclass']: if is_external_group(res_find):
if ( if (
posix posix
or (nonposix is not None and nonposix is False) or (nonposix is not None and nonposix is False)
@@ -242,10 +268,10 @@ def should_modify_group(module, res_find, args, nonposix, posix, external):
return True return True
if any([posix, nonposix]): if any([posix, nonposix]):
set_posix = posix or (nonposix is not None and not nonposix) set_posix = posix or (nonposix is not None and not nonposix)
if set_posix and 'posixgroup' not in res_find['objectclass']: if set_posix and not is_posix_group(res_find):
return True return True
if 'ipaexternalgroup' not in res_find['objectclass'] and external: if not is_external_group(res_find) and external:
if 'posixgroup' not in res_find['objectclass']: if not is_posix_group(res_find):
return True return True
return False return False
@@ -272,6 +298,11 @@ def main():
membermanager_user=dict(required=False, type='list', default=None), membermanager_user=dict(required=False, type='list', default=None),
membermanager_group=dict(required=False, type='list', membermanager_group=dict(required=False, type='list',
default=None), default=None),
externalmember=dict(required=False, type='list', default=None,
aliases=[
"ipaexternalmember",
"external_member"
]),
action=dict(type="str", default="group", action=dict(type="str", default="group",
choices=["member", "group"]), choices=["member", "group"]),
# state # state
@@ -308,6 +339,7 @@ def main():
"membermanager_user") "membermanager_user")
membermanager_group = module_params_get(ansible_module, membermanager_group = module_params_get(ansible_module,
"membermanager_group") "membermanager_group")
externalmember = module_params_get(ansible_module, "externalmember")
action = module_params_get(ansible_module, "action") action = module_params_get(ansible_module, "action")
# state # state
state = module_params_get(ansible_module, "state") state = module_params_get(ansible_module, "state")
@@ -334,7 +366,7 @@ def main():
invalid = ["description", "gid", "posix", "nonposix", "external", invalid = ["description", "gid", "posix", "nonposix", "external",
"nomembers"] "nomembers"]
if action == "group": if action == "group":
invalid.extend(["user", "group", "service"]) invalid.extend(["user", "group", "service", "externalmember"])
for x in invalid: for x in invalid:
if vars()[x] is not None: if vars()[x] is not None:
ansible_module.fail_json( ansible_module.fail_json(
@@ -404,10 +436,19 @@ def main():
if external: if external:
args['external'] = True args['external'] = True
commands.append([name, "group_add", args]) commands.append([name, "group_add", args])
# Set res_find to empty dict for next step # Set res_find dict for next step
res_find = {} res_find = {}
member_args = gen_member_args(user, group, service) # if we just created/modified the group, update res_find
res_find.setdefault("objectclass", [])
if external and not is_external_group(res_find):
res_find["objectclass"].append("ipaexternalgroup")
if posix and not is_posix_group(res_find):
res_find["objectclass"].append("posixgroup")
member_args = gen_member_args(
user, group, service, externalmember
)
if not compare_args_ipa(ansible_module, member_args, if not compare_args_ipa(ansible_module, member_args,
res_find): res_find):
# Generate addition and removal lists # Generate addition and removal lists
@@ -420,40 +461,48 @@ def main():
service_add, service_del = gen_add_del_lists( service_add, service_del = gen_add_del_lists(
service, res_find.get("member_service")) service, res_find.get("member_service"))
(externalmember_add,
externalmember_del) = gen_add_del_lists(
externalmember, res_find.get("member_external"))
# setup member args for add/remove members.
add_member_args = {
"user": user_add,
"group": group_add,
}
del_member_args = {
"user": user_del,
"group": group_del,
}
if has_add_member_service: if has_add_member_service:
# Add members add_member_args["service"] = service_add
if len(user_add) > 0 or len(group_add) > 0 or \ del_member_args["service"] = service_del
len(service_add) > 0:
commands.append([name, "group_add_member", if is_external_group(res_find):
{ add_member_args["ipaexternalmember"] = \
"user": user_add, externalmember_add
"group": group_add, del_member_args["ipaexternalmember"] = \
"service": service_add, externalmember_del
}]) elif externalmember or external:
# Remove members ansible_module.fail_json(
if len(user_del) > 0 or len(group_del) > 0 or \ msg="Cannot add external members to a "
len(service_del) > 0: "non-external group."
commands.append([name, "group_remove_member", )
{
"user": user_del, # Add members
"group": group_del, add_members = any([user_add, group_add,
"service": service_del, service_add, externalmember_add])
}]) if add_members:
else: commands.append(
# Add members [name, "group_add_member", add_member_args]
if len(user_add) > 0 or len(group_add) > 0: )
commands.append([name, "group_add_member", # Remove members
{ remove_members = any([user_del, group_del,
"user": user_add, service_del, externalmember_del])
"group": group_add, if remove_members:
}]) commands.append(
# Remove members [name, "group_remove_member", del_member_args]
if len(user_del) > 0 or len(group_del) > 0: )
commands.append([name, "group_remove_member",
{
"user": user_del,
"group": group_del,
}])
membermanager_user_add, membermanager_user_del = \ membermanager_user_add, membermanager_user_del = \
gen_add_del_lists( gen_add_del_lists(
@@ -492,19 +541,25 @@ def main():
elif action == "member": elif action == "member":
if res_find is None: if res_find is None:
ansible_module.fail_json(msg="No group '%s'" % name) ansible_module.fail_json(msg="No group '%s'" % name)
add_member_args = {
"user": user,
"group": group,
}
if has_add_member_service: if has_add_member_service:
commands.append([name, "group_add_member", add_member_args["service"] = service
{ if is_external_group(res_find):
"user": user, add_member_args["ipaexternalmember"] = externalmember
"group": group, elif externalmember:
"service": service, ansible_module.fail_json(
}]) msg="Cannot add external members to a "
else: "non-external group."
commands.append([name, "group_add_member", )
{
"user": user, if any([user, group, service, externalmember]):
"group": group, commands.append(
}]) [name, "group_add_member", add_member_args]
)
if has_add_membermanager: if has_add_membermanager:
# Add membermanager users and groups # Add membermanager users and groups
@@ -527,19 +582,24 @@ def main():
if res_find is None: if res_find is None:
ansible_module.fail_json(msg="No group '%s'" % name) ansible_module.fail_json(msg="No group '%s'" % name)
del_member_args = {
"user": user,
"group": group,
}
if has_add_member_service: if has_add_member_service:
commands.append([name, "group_remove_member", del_member_args["service"] = service
{ if is_external_group(res_find):
"user": user, del_member_args["ipaexternalmember"] = externalmember
"group": group, elif externalmember:
"service": service, ansible_module.fail_json(
}]) msg="Cannot add external members to a "
else: "non-external group."
commands.append([name, "group_remove_member", )
{
"user": user, if any([user, group, service, externalmember]):
"group": group, commands.append(
}]) [name, "group_remove_member", del_member_args]
)
if has_add_membermanager: if has_add_membermanager:
# Remove membermanager users and groups # Remove membermanager users and groups

View File

@@ -16,3 +16,4 @@
set_fact: set_fact:
ipa_version: "{{ ipa_cmd_version.stdout_lines[0] }}" ipa_version: "{{ ipa_cmd_version.stdout_lines[0] }}"
ipa_api_version: "{{ ipa_cmd_version.stdout_lines[1] }}" ipa_api_version: "{{ ipa_cmd_version.stdout_lines[1] }}"
trust_test_is_supported: no

View File

@@ -0,0 +1,113 @@
---
- name: find trust
hosts: ipaserver
become: true
gather_facts: false
tasks:
- include_tasks: ../env_freeipa_facts.yml
- block:
- name: Add nonposix group.
ipagroup:
ipaadmin_password: SomeADMINpassword
name: extgroup
nonposix: yes
register: result
failed_when: result.failed or not result.changed
- name: Set group to be external
ipagroup:
ipaadmin_password: SomeADMINpassword
name: extgroup
external: yes
register: result
failed_when: result.failed or not result.changed
- name: Add AD users to group
ipagroup:
ipaadmin_password: SomeADMINpassword
name: extgroup
external_member: "AD\\Domain Users"
register: result
failed_when: result.failed or not result.changed
- name: Add AD users to group, again
ipagroup:
ipaadmin_password: SomeADMINpassword
name: extgroup
external_member: "AD\\Domain Users"
register: result
failed_when: result.failed or result.changed
- name: Remove external group
ipagroup:
ipaadmin_password: SomeADMINpassword
name: extgroup
state: absent
register: result
failed_when: result.failed or not result.changed
- name: Add nonposix, external group, with AD users.
ipagroup:
ipaadmin_password: SomeADMINpassword
name: extgroup
nonposix: yes
external: yes
external_member: "AD\\Domain Users"
register: result
failed_when: result.failed or not result.changed
- name: Add nonposix, external group, with AD users, again.
ipagroup:
ipaadmin_password: SomeADMINpassword
name: extgroup
nonposix: yes
external: yes
external_member: "AD\\Domain Users"
register: result
failed_when: result.failed or result.changed
- name: Remove group
ipagroup:
ipaadmin_password: SomeADMINpassword
name: extgroup
state: absent
register: result
failed_when: result.failed or not result.changed
- name: Add nonposix group.
ipagroup:
ipaadmin_password: SomeADMINpassword
name: extgroup
nonposix: yes
register: result
failed_when: result.failed or not result.changed
- name: Set group to be external, and add users.
ipagroup:
ipaadmin_password: SomeADMINpassword
name: extgroup
external: yes
external_member: "AD\\Domain Users"
register: result
failed_when: result.failed or not result.changed
- name: Set group to be external, and add users, again.
ipagroup:
ipaadmin_password: SomeADMINpassword
name: extgroup
external: yes
external_member: "AD\\Domain Users"
register: result
failed_when: result.failed or result.changed
- name: Cleanup environment.
ipagroup:
ipaadmin_password: SomeADMINpassword
name: extgroup
state: absent
when: trust_test_is_supported | default(false)