ipa/client: Combine operations on the IPA server

The reason for this is to avoid having to do the same or similar things
over and over again for each host in the play, especially since these
operations are delegated to the IPA server, i.e. had to run sequentially
host after host in order to avoid race conditions.

To achieve this, the IPA client related group variables are prepared in
suitable structures in `prepare-ipa-info.yml` and consumed by
`common.yml`, `hbac.yml` and `sudo.yml`, which do most operations in one
go per e.g. host group on the affected IPA server(s).

Additionally:
- Remove compat for legacy `fas_client_groups`, only check for its
  presence and warn.
- Remove the prepared but masked out task to manage password-less sudo
  access.
- Make yamllint a little happier on the changed files.

Signed-off-by: Nils Philippsen <nils@redhat.com>
This commit is contained in:
Nils Philippsen 2021-02-09 17:54:24 +01:00 committed by nphilipp
parent fdcd55c176
commit 3d1c5218f7
6 changed files with 248 additions and 78 deletions

View file

@ -1,3 +1,4 @@
---
# REMOVEME: As soon as all (affected) hosts have been migrated over from fas_client, 2fa_client to
# ipa/client, this can go away.
#

View file

@ -1,22 +1,38 @@
---
## This will only run once per play (as per `main.yml`), so needs to consider all affected hosts.
## Cluster-wide rules
- name: Check that sysadmin-main group exists
command: "getent group sysadmin-main"
changed_when: False
# This is supposed to fail if a configured group doesn't exist. In this case, either add the group
# manually or remove the reference from configuration.
- name: Check that sysadmin-main and referenced groups exist on IPA server(s)
delegate_to: "{{ item[0] }}"
command: "getent group {{ item[1] }}"
changed_when: false
loop: >-
{{
ipa_server_user_groups
+ (ipa_server_all_groups_hosts_dict | list | product(['sysadmin-main']) | list)
}}
## Rules specific to hosts, host groups
- name: Ensure IPA host group exists
- name: Ensure IPA host groups exist on IPA server(s)
delegate_to: "{{ item[0] }}"
ipahostgroup:
name: "{{ ipa_host_group }}"
description: "{{ ipa_host_group_desc | default(omit) }}"
ipaadmin_password: "{{ ipa_admin_password }}"
name: "{{ item[1] }}"
description: "{{ ipa_server_host_groups_dict[item[1]].desc | default(omit) }}"
ipaadmin_password: "{{ ipa_server_admin_passwords[item[0]] }}"
state: present
loop: "{{ ipa_server_host_groups }}"
- name: Ensure host is in IPA host group
- name: Ensure hosts are in IPA host groups
delegate_to: "{{ item[0] }}"
ipahostgroup:
name: "{{ ipa_host_group }}"
ipaadmin_password: "{{ ipa_admin_password }}"
name: "{{ item[1] }}"
ipaadmin_password: "{{ ipa_server_admin_passwords[item[0]] }}"
action: member
state: present
host: "{{ inventory_hostname }}"
host: "{{ item[2] | list }}"
loop: "{{ ipa_server_host_groups_hosts }}"

View file

@ -1,80 +1,101 @@
---
## This will only run once per play (as per `main.yml`), so needs to consider all affected hosts.
## Cluster-wide rules
- name: "Give members of group sysadmin-main access to anything, anywhere"
delegate_to: "{{ item.key }}"
ipahbacrule:
name: "usergroup/sysadmin-main"
description: "Give members of group sysadmin-main access to anything, anywhere"
hostcategory: "all"
servicecategory: "all"
ipaadmin_password: "{{ ipa_admin_password }}"
ipaadmin_password: "{{ item.value }}"
state: present
group:
- sysadmin-main
loop: "{{ ipa_server_admin_passwords | dict2items }}"
- name: "Enable usergroup/sysadmin-main HBAC rule"
delegate_to: "{{ item.key }}"
ipahbacrule:
name: "usergroup/sysadmin-main"
ipaadmin_password: "{{ ipa_admin_password }}"
ipaadmin_password: "{{ item.value }}"
state: enabled
loop: "{{ ipa_server_admin_passwords | dict2items }}"
- name: "Disable allow_all HBAC rule"
delegate_to: "{{ item.key }}"
ipahbacrule:
name: allow_all
ipaadmin_password: "{{ ipa_admin_password }}"
ipaadmin_password: "{{ item.value }}"
state: disabled
loop: "{{ ipa_server_admin_passwords | dict2items }}"
- name: "Let everybody run sudo"
delegate_to: "{{ item.key }}"
ipahbacrule:
name: "all-users/sudo"
description: "Allow all users to execute the sudo command"
state: present
ipaadmin_password: "{{ ipa_admin_password }}"
ipaadmin_password: "{{ item.value }}"
hostcategory: "all"
usercategory: "all"
hbacsvcgroup:
- Sudo
loop: "{{ ipa_server_admin_passwords | dict2items }}"
## Host-specific rules
- name: Add the sshd HBAC service in IPA
delegate_to: "{{ item.key }}"
ipahbacsvc:
name: sshd
description: SSH daemon
ipaadmin_password: "{{ item.value }}"
loop: "{{ ipa_server_admin_passwords | dict2items }}"
- name: Add the shell-access service group in IPA
delegate_to: "{{ item.key }}"
ipahbacsvcgroup:
name: shell-access
description: Group of shell access services
ipaadmin_password: "{{ item.value }}"
hbacsvc:
- sshd
loop: "{{ ipa_server_admin_passwords | dict2items }}"
## Host group- & host-specific rules
# shell access
- name: "Warn if `fas_client_groups` is set"
fail:
msg: "`fas_client_groups` is defined, should be converted to a list and named `ipa_client_shell_groups`"
msg: >-
`fas_client_groups` is defined, please convert to a (group var) list named
`ipa_client_shell_groups`
ignore_errors: true
when: fas_client_groups is defined
- name: "Convert `fas_client_groups` string to `ipa_client_shell_groups` list if missing"
set_fact:
ipa_client_shell_groups: "{{ fas_client_groups.split(',') | list }}"
when: fas_client_groups is defined and ipa_client_shell_groups is not defined
- name: "Warn if IPA client variables are unset"
fail:
msg: "`{{ item }}` is not defined"
ignore_errors: true
when: lookup('vars', item, default="<undefined-sentinel>") == "<undefined-sentinel>"
loop:
- ipa_host_group
- ipa_host_group_desc
- ipa_client_shell_groups
- ipa_client_sudo_groups
- name: Add the sshd HBAC service in IPA
ipahbacsvc:
name: sshd
description: SSH daemon
ipaadmin_password: "{{ ipa_admin_password }}"
- name: Add the shell-access service group in IPA
ipahbacsvcgroup:
name: shell-access
description: Group of shell access services
ipaadmin_password: "{{ ipa_admin_password }}"
hbacsvc:
- sshd
- name: Check that shell access user groups exist
command: "getent group {{ item }}"
changed_when: False
loop: "{{ (ipa_client_shell_groups | default([])) | list }}"
- name: "Give certain groups shell access on host group {{ ipa_host_group }}"
- name: Give certain groups shell access per host group
delegate_to: "{{ item[0] }}"
ipahbacrule:
name: "hostgroup/{{ ipa_host_group }}/shell-access"
description: "Give certain groups shell access on host group {{ ipa_host_group }}"
ipaadmin_password: "{{ ipa_admin_password }}"
name: "hostgroup/{{ item[1] }}/shell-access"
description: "Grant shell access on host group {{ item[1] }}"
ipaadmin_password: "{{ ipa_server_admin_passwords[item[0]] }}"
action: member
hbacsvcgroup:
- shell-access
state: present
group: "{{ ipa_client_shell_groups | default([]) | list }}"
hostgroup: "{{ ipa_host_group }}"
group: "{{ ipa_server_host_groups_dict[item[0]][item[1]]['shell_groups'] }}"
hostgroup: "{{ item[1] }}"
loop: "{{ ipa_server_host_groups }}"

View file

@ -1,3 +1,4 @@
---
- name: Install IPA client packages
package:
name:
@ -29,15 +30,23 @@
- ipa/client
- fas-client-cleanup
- name: Basic configuration for client on IPA cluster
delegate_to: "{{ ipa_server }}"
- name: Prepare IPA-related information to make the following more efficient
delegate_to: localhost
import_tasks: prepare-ipa-info.yml
tags:
- ipa/client
- config
run_once: yes
- name: Basic configuration for clients on IPA cluster
delegate_to: localhost
import_tasks: common.yml
# don't muck with prod for now
when: env == 'staging'
tags:
- ipa/client
- config
throttle: 1
run_once: yes
- name: Configure HBAC on IPA cluster
delegate_to: "{{ ipa_server }}"
@ -47,7 +56,7 @@
tags:
- ipa/client
- config
throttle: 1
run_once: yes
- name: Configure sudo on IPA cluster
delegate_to: "{{ ipa_server }}"
@ -57,4 +66,4 @@
tags:
- ipa/client
- config
throttle: 1
run_once: yes

View file

@ -0,0 +1,137 @@
---
# NOTE: configuration is based on host groups, i.e. set the ipa_* vars only in group_vars
# Thanks to having two environments, staging and prod, this has to deal with the "responsible" IPA
# server for individual hosts.
# ipa_server_host_groups_dict ->
# {
# "ipa_server_1": {
# "host_group_1": {
# "shell_groups": [...],
# "sudo_groups": [...],
# "hosts": { # <-- This could be a list with Ansible >= 2.10
# "host_1": true,
# ...,
# }
# }, ...
# }, ...
# }
#
# ipa_server_all_groups_hosts_dict ->
# {
# "ipa_server_1": {
# groups: [...],
# hosts: {
# "host_1": true,
# ...,
# }
# }, ...
# }
#
# ipa_server_passwords: ->
# {
# "ipa_server_1": "ipa_password_1",
# "ipa_server_2": "ipa_password_2",
# ...
# }
- name: Create dictionary about IPA servers, host groups, their IPA vars and contained hosts
set_fact:
ipa_server_host_groups_dict: >-
{{
ipa_server_host_groups_dict | default({}) | combine(
{
hostvars[item]['ipa_server']: {
hostvars[item]['ipa_host_group']: {
'desc': hostvars[item]['ipa_host_group_desc'] | default(omit),
'shell_groups': hostvars[item]['ipa_client_shell_groups'] | default(omit),
'sudo_groups': hostvars[item]['ipa_client_sudo_groups'] | default(omit),
'hosts': {item: true},
}
}
},
recursive=True
)
}}
ipa_server_all_groups_hosts_dict: >-
{{
(ipa_server_all_groups_hosts_dict | default({})) | combine(
{
hostvars[item]['ipa_server']: {
'groups': hostvars[item]['ipa_client_shell_groups'] | default([]) | union(
hostvars[item]['ipa_client_sudo_groups'] | default([])
),
'hosts': {item: True},
}
},
recursive=True
)
}}
ipa_server_admin_passwords: >-
{{
(ipa_server_admin_passwords | default({})) | combine(
{hostvars[item]['ipa_server']: hostvars[item]['ipa_admin_password']}
)
}}
loop: "{{ ansible_play_hosts }}"
when: hostvars[item]['ipa_server'] is defined and hostvars[item]['ipa_host_group'] is defined
# ipa_server_host_groups ->
# [
# ["ipa_server_1", "host_group_1"],
# ["ipa_server_1", "host_group_2"],
# ...
# ["ipa_server_2", "host_group_1"],
# ...
# ]
- name: Transform ipa_server_host_groups_dict into an iterable list
set_fact:
ipa_server_host_groups: >-
{{
(ipa_server_host_groups | default([]))
+ ([item.key] | product(item.value | list) | list)
}}
loop: "{{ ipa_server_host_groups_dict | dict2items }}"
# ipa_server_host_user_groups_hosts ->
# [
# [
# "ipa_server_1",
# "host_group_1",
# ["user_group_1", ...], # <-- shell access user groups
# ["user_group_2", ...], # <-- sudo access user groups
# ["host_1", ...],
# ],
# [
# "ipa_server_1",
# "host_group_2",
# ["user_group_3", ...],
# ["user_group_4", ...],
# ["host_2", ...],
# ],
# ...
# [
# "ipa_server_2",
# "host_group_1",
# ["user_group_5", ...],
# ["user_group_6", ...],
# ["host_3", ...],
# ],
# ...
# ]
- name: Make semi-flat list of IPA servers, host groups and the user groups and hosts contained
set_fact:
ipa_server_host_user_groups_hosts: >-
{{
ipa_server_host_user_groups_hosts | default([])
+ [
[
item[0],
item[1],
ipa_server_host_groups_dict[item[0]][item[1]]['shell_groups'],
ipa_server_host_groups_dict[item[0]][item[1]]['sudo_groups'],
ipa_server_host_groups_dict[item[0]][item[1]]['hosts'] | list,
]
]
}}
loop: "{{ ipa_server_host_groups }}"

View file

@ -1,13 +1,12 @@
- name: Check that configured sudo groups exist
command: "getent group {{ item }}"
changed_when: False
loop: "{{ (ipa_client_sudo_groups | default([])) + (ipa_client_sudo_nopasswd_groups | default([])) | list }}"
---
## This will only run once per play (as per `main.yml`), so needs to consider all affected hosts.
- name: "Give members of `sysadmin-main` sudo access to anything, anywhere"
delegate_to: "{{ item.key }}"
ipasudorule:
name: "usergroup/sysadmin-main"
description: "Allow members of `sysadmin-main` to use sudo to do anything, anywhere"
ipaadmin_password: "{{ ipa_admin_password }}"
ipaadmin_password: "{{ item.value }}"
state: present
cmdcategory: "all"
hostcategory: "all"
@ -15,31 +14,18 @@
runasgroupcategory: "all"
group:
- sysadmin-main
loop: "{{ ipa_server_admin_passwords | dict2items }}"
- name: "Give certain groups sudo access to anything on host group {{ ipa_host_group }}"
- name: Give certain groups sudo access to anything per host group
delegate_to: "{{ item[0] }}"
ipasudorule:
name: "hostgroup/{{ ipa_host_group }}"
description: "Give members of groups sudo access to anything on host group {{ ipa_host_group }}"
ipaadmin_password: "{{ ipa_admin_password }}"
name: "hostgroup/{{ item[1] }}"
description: "Grant sudo access to anything on host group {{ item[1] }}"
ipaadmin_password: "{{ ipa_server_admin_passwords[item[0]] }}"
state: present
group: "{{ ipa_client_sudo_groups | list }}"
hostgroup: "{{ ipa_host_group }}"
group: "{{ ipa_server_host_groups_dict[item[0]][item[1]]['sudo_groups'] }}"
hostgroup: "{{ item[1] }}"
cmdcategory: "all"
runasusercategory: "all"
runasgroupcategory: "all"
when: ipa_client_sudo_groups is defined and ipa_client_sudo_groups | length > 0
## Disabled: Remove "False and" from when: to re-enable
- name: "Give certain groups password-less sudo access to anything on host group {{ ipa_host_group }}"
ipasudorule:
name: "hostgroup/{{ ipa_host_group }}/nopasswd"
description: "Give members of groups password-less sudo access to anything on host group {{ ipa_host_group }}"
ipaadmin_password: "{{ ipa_admin_password }}"
state: present
group: "{{ ipa_client_sudo_groups_nopasswd | list }}"
hostgroup: "{{ ipa_host_group }}"
cmdcategory: "all"
runasusercategory: "all"
runasgroupcategory: "all"
options: "!authenticate"
when: False and ipa_client_sudo_groups_nopasswd is defined and ipa_client_sudo_groups_nopasswd | length > 0
loop: "{{ ipa_server_host_groups }}"