mirror of
https://github.com/freeipa/ansible-freeipa.git
synced 2026-07-31 11:54:47 +00:00
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:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
113
tests/group/test_group_external_members.yml
Normal file
113
tests/group/test_group_external_members.yml
Normal 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)
|
||||||
Reference in New Issue
Block a user