diff --git a/roles/ipa/client/tasks/cleanup.yml b/roles/ipa/client/tasks/cleanup.yml index 984f05a65b..8a4a70a5bd 100644 --- a/roles/ipa/client/tasks/cleanup.yml +++ b/roles/ipa/client/tasks/cleanup.yml @@ -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. # diff --git a/roles/ipa/client/tasks/common.yml b/roles/ipa/client/tasks/common.yml index 1cb244a1b3..f5fd4d10a6 100644 --- a/roles/ipa/client/tasks/common.yml +++ b/roles/ipa/client/tasks/common.yml @@ -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 }}" diff --git a/roles/ipa/client/tasks/hbac.yml b/roles/ipa/client/tasks/hbac.yml index 72ed8b48f8..ad3aebef27 100644 --- a/roles/ipa/client/tasks/hbac.yml +++ b/roles/ipa/client/tasks/hbac.yml @@ -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="") == "" + 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 }}" diff --git a/roles/ipa/client/tasks/main.yml b/roles/ipa/client/tasks/main.yml index e0d591b4d1..7a530bd784 100644 --- a/roles/ipa/client/tasks/main.yml +++ b/roles/ipa/client/tasks/main.yml @@ -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 diff --git a/roles/ipa/client/tasks/prepare-ipa-info.yml b/roles/ipa/client/tasks/prepare-ipa-info.yml new file mode 100644 index 0000000000..cfb8835c52 --- /dev/null +++ b/roles/ipa/client/tasks/prepare-ipa-info.yml @@ -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 }}" diff --git a/roles/ipa/client/tasks/sudo.yml b/roles/ipa/client/tasks/sudo.yml index 350594eac4..c2ae4f6f17 100644 --- a/roles/ipa/client/tasks/sudo.yml +++ b/roles/ipa/client/tasks/sudo.yml @@ -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 }}"