Skip to content

Commit 6f31af4

Browse files
authored
Merge pull request #241 from stackhpc/feat/freeipa-nocontainer
Add freeipa (not containerised)
2 parents ae1c4d9 + 966d350 commit 6f31af4

File tree

31 files changed

+530
-34
lines changed

31 files changed

+530
-34
lines changed

ansible/adhoc/backup-keytabs.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Use ONE of the following tags on this playbook:
2+
# - retrieve: copies keytabs out of the state volume to the environment
3+
# - deploy: copies keytabs from the environment to the state volume
4+
5+
- hosts: freeipa_client
6+
become: yes
7+
gather_facts: no
8+
tasks:
9+
- import_role:
10+
name: freeipa
11+
tasks_from: backup-keytabs.yml

ansible/bootstrap.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@
8282
policy: "{{ selinux_policy }}"
8383
register: sestatus
8484

85+
- hosts: freeipa_server
86+
# Done here as it might be providing DNS
87+
tags:
88+
- freeipa
89+
- freeipa_server
90+
gather_facts: yes
91+
become: yes
92+
tasks:
93+
- name: Install FreeIPA server
94+
import_role:
95+
name: freeipa
96+
tasks_from: server.yml
97+
98+
# --- tasks after here require access to package repos ---
99+
85100
- hosts: firewalld
86101
gather_facts: false
87102
become: yes

ansible/extras.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
- hosts: basic_users
2+
become: yes
3+
tags:
4+
- basic_users
5+
- users
6+
gather_facts: yes
7+
tasks:
8+
- import_role:
9+
name: basic_users
10+
111
- name: Setup EESSI
212
hosts: eessi
313
tags: eessi

ansible/fatimage.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@
3434
state: stopped
3535
enabled: false
3636

37+
# - import_playbook: iam.yml
38+
- name: Install FreeIPA client
39+
import_role:
40+
name: freeipa
41+
tasks_from: client-install.yml
42+
when: "'freeipa_client' in group_names"
43+
3744
# - import_playbook: filesystems.yml
3845
- name: nfs
3946
dnf:
@@ -149,8 +156,6 @@
149156
name: cloudalchemy.grafana
150157
tasks_from: install.yml
151158

152-
# - import_playbook: iam.yml - nothing to do
153-
154159
- name: Run post.yml hook
155160
vars:
156161
appliances_environment_root: "{{ lookup('env', 'APPLIANCES_ENVIRONMENT_ROOT') }}"

ansible/iam.yml

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,42 @@
1-
- hosts: basic_users
1+
- hosts: freeipa_client
2+
tags:
3+
- freeipa
4+
- freeipa_server # as this is only relevant if using freeipa_server
5+
- freeipa_host
6+
gather_facts: no
27
become: yes
8+
tasks:
9+
- name: Ensure FreeIPA client hosts are added to the FreeIPA server
10+
import_role:
11+
name: freeipa
12+
tasks_from: addhost.yml
13+
when: groups['freeipa_server'] | length > 0
14+
15+
- hosts: freeipa_client
316
tags:
4-
- basic_users
17+
- freeipa
18+
- freeipa_client
519
gather_facts: yes
20+
become: yes
21+
tasks:
22+
- name: Install FreeIPA client
23+
import_role:
24+
name: freeipa
25+
tasks_from: client-install.yml
26+
- name: Enrol FreeIPA client
27+
import_role:
28+
name: freeipa
29+
tasks_from: enrol.yml
30+
31+
- hosts: freeipa_server
32+
tags:
33+
- freeipa
34+
- freeipa_server
35+
- users
36+
gather_facts: yes
37+
become: yes
638
tasks:
7-
- import_role:
8-
name: basic_users
39+
- name: Add FreeIPA users
40+
import_role:
41+
name: freeipa
42+
tasks_from: users.yml

ansible/roles/etc_hosts/README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
Hosts in the `etc_hosts` groups have `/etc/hosts` created with entries of the format `IP_address canonical_hostname [alias]`.
44

5-
By default, an entry is created for each host in this group, using `ansible_host` as the IP_address and `inventory_hostname` as the canonical hostname. This may need overriding for multi-homed hosts or hosts with multiple aliases.
5+
By default, an entry is created for each host in this group as follows:
6+
- The value of `ansible_host` is used as the IP_address.
7+
- If `node_fqdn` is defined then that is used as the canonical hostname and `inventory_hostname` as an alias. Otherwise `inventory_hostname` is used as the canonical hostname.
8+
This may need overriding for multi-homed hosts or hosts with multiple aliases.
69

710
# Variables
811

912
- `etc_hosts_template`: Template file to use. Default is the in-role template.
10-
- `etc_hosts_hostvars`: A list of variable names, used (in the order supplied) to create the entry for each host. Default is `['ansible_host', 'inventory_hostname']`
13+
- `etc_hosts_hostvars`: A list of variable names, used (in the order supplied) to create the entry for each host. Default is described above.
1114
- `etc_hosts_extra_hosts`: String (possibly multi-line) defining additional hosts to add to `/etc/hosts`. Default is empty string.
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
11
etc_hosts_template: hosts.j2
2-
etc_hosts_hostvars:
3-
- ansible_host
4-
- inventory_hostname
2+
etc_hosts_hostvars: "{{ ['ansible_host'] + (['node_fqdn'] if node_fqdn is defined else []) + ['inventory_hostname'] }}"
53
etc_hosts_extra_hosts: ''

ansible/roles/freeipa/README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
2+
# freeipa
3+
4+
Support FreeIPA in the appliance. In production use it is expected the FreeIPA server(s) will be external to the cluster, implying that hosts and users are managed outside the appliance. However for testing and development the role can also deploy an "in-appliance" FreeIPA server, add hosts to it and manage users in FreeIPA.
5+
6+
# FreeIPA Client
7+
8+
## Usage
9+
- Add hosts to the `freeipa_client` group and run (at a minimum) the `ansible/iam.yml` playbook.
10+
- Host names must match the domain name. By default (using the skeleton Terraform) hostnames are of the form `nodename.cluster_name.cluster_domain_suffix` where `cluster_name` and `cluster_domain_suffix` are Terraform variables.
11+
- Hosts discover the FreeIPA server FQDN (and their own domain) from DNS records. If DNS servers are not set this is not set from DHCP, then use the `resolv_conf` role to configure this. For example when using the in-appliance FreeIPA development server:
12+
13+
```ini
14+
# environments/<env>/groups
15+
...
16+
[resolv_conf:children]
17+
freeipa_client
18+
...
19+
```
20+
21+
```yaml
22+
# environments/<env>/inventory/group_vars/all/resolv_conf.yml
23+
resolv_conf_nameservers:
24+
- "{{ hostvars[groups['freeipa_server'] | first].ansible_host }}"
25+
```
26+
27+
28+
- For production use with an external FreeIPA server, a random one-time password (OTP) must be generated when adding hosts to FreeIPA (e.g. using `ipa host-add --random ...`). This password should be set as a hostvar `freeipa_host_password`. Initial host enrolment will use this OTP to enrol the host. After this it becomes irrelevant so it does not need to be committed to git. This approach means the appliance does not require the FreeIPA administrator password.
29+
- For development use with the in-appliance FreeIPA server, `freeipa_host_password` will be automatically generated in memory.
30+
- The `control` host must define `appliances_state_dir` (on persistent storage). This is used to back-up keytabs to allow FreeIPA clients to automatically re-enrol after e.g. reimaging. Note that:
31+
- This is implemented when using the skeleton Terraform; on the control node `appliances_state_dir` defaults to `/var/lib/state` which is mounted from a volume.
32+
- Nodes are not re-enroled by a [Slurm-driven reimage](../../collections/ansible_collections/stackhpc/slurm_openstack_tools/roles/rebuild/README.md) (as that does not run this role).
33+
- If both a backed-up keytab and `freeipa_host_password` exist, the former is used.
34+
35+
36+
## Role Variables for Clients
37+
38+
- `freeipa_host_password`. Required for initial enrolment only, FreeIPA host password as described above.
39+
- `freeipa_setup_dns`: Optional, whether to use the FreeIPA server as the client's nameserver. Defaults to `true` when `freeipa_server` contains a host, otherwise `false`.
40+
41+
See also use of `appliances_state_dir` on the control node as described above.
42+
43+
# FreeIPA Server
44+
As noted above this is only intended for development and testing. Note it cannot be run on the `openondemand` node as no other virtual servers must be defined in the Apache configuration.
45+
46+
## Usage
47+
- Add a single host to the `freeipa_server` group and run (at a minimum) the `ansible/bootstrap.yml` and `ansible/iam.yml` playbooks.
48+
- As well as configuring the FreeIPA server, the role will also:
49+
- Add ansible hosts in the group `freeipa_client` as FreeIPA hosts.
50+
- Optionally control users in FreeIPA - see `freeipa_users` below.
51+
52+
The FreeIPA GUI will be available on `https://<freeipa_server_ip>/ipa/ui`.
53+
54+
## Role Variables for Server
55+
56+
These role variables are only required when using `freeipa_server`:
57+
58+
- `freeipa_realm`: Optional, name of realm. Default is `{{ openhpc_cluster_name | upper }}.INVALID`
59+
- `freeipa_domain`: Optional, name of domain. Default is lowercased `freeipa_realm`.
60+
- `freeipa_ds_password`: Optional, password to be used by the Directory Server for the Directory Manager user (`ipa-server-install --ds-password`). Default is generated in `environments/<environment>/inventory/group_vars/all/secrets.yml`
61+
- `freeipa_admin_password`: Optional, password for the IPA `admin` user. Default is generated as for `freeipa_ds_password`.
62+
- `freeipa_server_ip`: Optional, IP address of freeipa_server host. Default is `ansible_host` of the `freeipa_server` host. Default `false`.
63+
- `freeipa_setup_dns`: Optional bool, whether to configure the FreeIPA server as an integrated DNS server and define a zone and records. NB: This also controls whether `freeipa_client` hosts use the `freeipa_server` host for name resolution. Default `true` when `freeipa_server` contains a host.
64+
- `freeipa_client_ip`: Optional, IP address of FreeIPA client. Default is `ansible_host`.
65+
- `freeipa_users`: A list of dicts defining users to add, with keys/values as for [community.general.ipa_user](https://docs.ansible.com/ansible/latest/collections/community/general/ipa_user_module.html): Note that:
66+
- `name`, `givenname` (firstname) and `sn` (surname) are required.
67+
- `ipa_host`, `ipa_port`, `ipa_prot`, `ipa_user`, `validate_certs` are automatically provided and cannot be overridden.
68+
- If `password` is set, the value should *not* be a hash (unlike `ansible.builtin.user` as used by the `basic_users` role), and it must be changed on first login. `krbpasswordexpiration` does not appear to be able to override this.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#freeipa_realm:
2+
freeipa_domain: "{{ freeipa_realm | lower }}"
3+
#freeipa_ds_password:
4+
#freeipa_admin_password:
5+
#freeipa_server_ip:
6+
freeipa_setup_dns: "{{ groups['freeipa_server'] | length > 0 }}"
7+
freeipa_client_ip: "{{ ansible_host }}" # when run on freeipa_client group!
8+
# freeipa_host_password:
9+
freeipa_user_defaults:
10+
ipa_pass: "{{ freeipa_admin_password | quote }}"
11+
ipa_user: admin
12+
freeipa_users: [] # see community.general.ipa_user
13+
14+
_freeipa_keytab_backup_path: "{{ hostvars[groups['control'].0].appliances_state_dir }}/freeipa/{{ inventory_hostname }}/krb5.keytab"
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
- name: Get ipa host information
2+
# This uses DNS to find the ipa server, which works as this is running on the enrolled ipa server
3+
# It doesn't fail even if the host doesn't exist
4+
community.general.ipa_host:
5+
name: "{{ node_fqdn }}"
6+
ip_address: "{{ freeipa_client_ip }}"
7+
ipa_host: "{{ groups['freeipa_server'].0 }}"
8+
ipa_pass: "{{ vault_freeipa_admin_password }}"
9+
ipa_user: admin
10+
state: present
11+
validate_certs: false
12+
delegate_to: "{{ groups['freeipa_server'].0 }}"
13+
register: _ipa_host_check
14+
check_mode: yes
15+
changed_when: false
16+
17+
- name: Add host to IPA
18+
# Using random_password=true this unenroles an enroled host, hence the check above
19+
community.general.ipa_host:
20+
name: "{{ node_fqdn }}"
21+
ip_address: "{{ freeipa_client_ip }}"
22+
ipa_host: "{{ groups['freeipa_server'].0 }}"
23+
ipa_pass: "{{ vault_freeipa_admin_password }}"
24+
ipa_user: admin
25+
random_password: true
26+
state: present
27+
validate_certs: false
28+
delegate_to: "{{ groups['freeipa_server'].0 }}"
29+
when: "'sshpubkeyfp' not in _ipa_host_check.host"
30+
register: _ipa_host_add
31+
32+
- name: Set fact for ipa host password
33+
set_fact:
34+
freeipa_host_password: "{{ _ipa_host_add.host.randompassword }}"
35+
when: _ipa_host_add.changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
- name: Retrieve keytabs to localhost
2+
fetch:
3+
src: "{{ _freeipa_keytab_backup_path }}"
4+
dest: "{{ appliances_environment_root }}/keytabs/{{ inventory_hostname }}/"
5+
flat: true
6+
delegate_to: "{{ groups['control'].0 }}"
7+
tags: retrieve
8+
9+
- name: Copy keytabs back to control node
10+
copy:
11+
src: "{{ appliances_environment_root }}/keytabs/{{ inventory_hostname }}/"
12+
dest: "{{ _freeipa_keytab_backup_path | dirname }}"
13+
delegate_to: "{{ groups['control'].0 }}"
14+
tags: deploy
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
- name: Install FreeIPA client package
3+
dnf:
4+
name: ipa-client

ansible/roles/freeipa/tasks/enrol.yml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# based on https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/installing_identity_management/assembly_installing-an-idm-client_installing-identity-management
2+
3+
- name: Retrieve persisted keytab from previous enrolement
4+
slurp:
5+
src: "{{ _freeipa_keytab_backup_path }}"
6+
delegate_to: "{{ groups['control'] | first }}"
7+
register: _slurp_persisted_keytab
8+
failed_when: false
9+
10+
- name: Write persisted keytab from previous enrolment
11+
copy:
12+
content: "{{ _slurp_persisted_keytab.content | b64decode }}"
13+
dest: /tmp/krb5.keytab
14+
owner: root
15+
group: root
16+
mode: ug=rw,o=
17+
when: '"content" in _slurp_persisted_keytab'
18+
19+
- name: Re-enrol with FreeIPA using backed-up keytab
20+
# Re-enrolment requires --force-join and --password, or --keytab
21+
# Re-rolement means:
22+
# 1. A new host certificate is issued
23+
# 2. The old host certificate is revoked
24+
# 3. New SSH keys are generated
25+
# 4. ipaUniqueID is preserved
26+
# and ALSO that the keytab is changed!
27+
command:
28+
cmd: >
29+
ipa-client-install
30+
--unattended
31+
--mkhomedir
32+
--enable-dns-updates
33+
--keytab /tmp/krb5.keytab
34+
when: '"content" in _slurp_persisted_keytab'
35+
register: ipa_client_install_keytab
36+
changed_when: ipa_client_install_keytab.rc == 0
37+
failed_when: >
38+
ipa_client_install_keytab.rc !=0 and
39+
'IPA client is already configured' not in ipa_client_install_keytab.stderr
40+
41+
- name: Enrol with FreeIPA using random password
42+
# Note --password is overloaded - it's bulkpassword unless --principal or --force-join is used in which case it's admin password
43+
command:
44+
cmd: >
45+
ipa-client-install
46+
--unattended
47+
--mkhomedir
48+
--enable-dns-updates
49+
--password '{{ freeipa_host_password }}'
50+
when:
51+
- '"content" not in _slurp_persisted_keytab'
52+
- freeipa_host_password is defined
53+
register: ipa_client_install_password
54+
changed_when: ipa_client_install_password.rc == 0
55+
failed_when: >
56+
ipa_client_install_password.rc != 0 and
57+
'IPA client is already configured' not in ipa_client_install_password.stderr
58+
59+
- name: Ensure NFS RPC security service is running
60+
# This service is installed by nfs-utils, which attempts to start it.
61+
# It has ConditionPathExists=/etc/krb5.keytab which fails if host is not enroled.
62+
# This task avoids a reboot.
63+
systemd:
64+
name: rpc-gssd.service
65+
state: started
66+
enabled: true
67+
68+
- name: Retrieve current keytab
69+
slurp:
70+
src: /etc/krb5.keytab
71+
register: _slurp_current_keytab
72+
failed_when: false
73+
74+
- name: Ensure keytab backup directory exists
75+
file:
76+
path: "{{ _freeipa_keytab_backup_path | dirname }}"
77+
state: directory
78+
owner: root
79+
group: root
80+
mode: ug=wrX,o=
81+
delegate_to: "{{ groups['control'] | first }}"
82+
83+
- name: Persist keytab
84+
copy:
85+
content: "{{ _slurp_current_keytab.content | b64decode }}"
86+
dest: "{{ _freeipa_keytab_backup_path }}"
87+
delegate_to: "{{ groups['control'] | first }}"

0 commit comments

Comments
 (0)