1114 lines
26 KiB
Markdown
1114 lines
26 KiB
Markdown
# RHCE Practice Lab
|
||
|
||
This repo contains all the files needed to deploy an RHCE practice lab. The target infrastructure is Openshift Virtualuzation, and network services (eg; DNS) are handled by OPNsense. Once deployed, the lab consists of 7 VMs:
|
||
|
||
- controller
|
||
- utility
|
||
- node1
|
||
- node2
|
||
- node3
|
||
- node4
|
||
- node5
|
||
|
||
The lab uses the domain name `lab.example.com`.
|
||
|
||
All of the files needed to complete the tasks on the exam are hosted on the utility server, eg; [http://utility.lab.example.com](http://utility.lab.example.com/files)
|
||
|
||
You will perform all tasks as the `ansible` user on the `controller` node from the directory `/home/ansible/ansible`.
|
||
|
||
The `ansible` user's password is `ansible` (really original, I know).
|
||
|
||
Unless otherwise specified, the password for any vaulted files is `redhat`.
|
||
|
||
The lab is easily deployed with the following command:
|
||
|
||
`ansible-playbook create-lab.yml -e @vault.yml --vault-password-file vault-password`
|
||
|
||
The lab can be torn down by running the command:
|
||
|
||
`ansible-playbook destroy-lab.yml`
|
||
|
||
**Helpful hints:**
|
||
|
||
`ansible localhost -m setup` to print system facts. You may want to pipe that out to a text file to avoid having to run the command repeatedly and save yourself some time.
|
||
|
||
`ansible-config init --disabled > ansible.cfg` to generate a config file with all options commented.
|
||
|
||
You can use `ansible.builtin.debug` to print out things like facts to make sure your syntax is correct, eg;
|
||
|
||
```
|
||
# printfacts.yml
|
||
- name: Print facts
|
||
hosts: jump01.lab.cudanet.org
|
||
gather_facts: true
|
||
remote_user: root
|
||
|
||
tasks:
|
||
- name: print facts
|
||
ansible.builtin.debug:
|
||
msg: "The default IPv4 address for {{ inventory_hostname }} is {{ ansible_default_ipv4.address }}"
|
||
```
|
||
|
||
## Task 1.
|
||
|
||
**install and configure ansible:**
|
||
|
||
i) Install podman, ansible-core and ansible-navigator. /etc/yum.redos.d/rhce.repo should already be configured to pull packages from utility.lab.example.com.
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
`dnf -y install podman ansible-core ansible-navigator`
|
||
|
||
</details>
|
||
|
||
ii) configure ansible.cfg to install collections by default to `~/ansible/mycollections` and roles to `~/ansible/roles`
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# ansible.cfg
|
||
[defaults]
|
||
inventory = /home/ansible/ansible/inventory
|
||
remote_user = ansible
|
||
roles_path = /home/ansible/ansible/roles
|
||
collections_path = /home/ansible/ansible/mycollections
|
||
```
|
||
|
||
</details>
|
||
|
||
|
||
iii) configure `inventory` as follows:
|
||
|
||
node1 is in the dev group.
|
||
node2 is in the test group.
|
||
nodes 3 and 4 are in the prod group.
|
||
node5 is in the balancers group.
|
||
the prod group is in the webservers group.
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# inventory
|
||
[dev]
|
||
node1
|
||
|
||
[test]
|
||
node2
|
||
|
||
[prod]
|
||
node3
|
||
node4
|
||
|
||
[balancers]
|
||
node5
|
||
|
||
[webservers:children]
|
||
prod
|
||
```
|
||
|
||
</details>
|
||
|
||
iv) ansible-navigator.yml is configured to pull the EE image from the utility server if missing. The registry is located at utility.lab.example.com:5000
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# ansible-navigator.yml
|
||
---
|
||
ansible-navigator:
|
||
execution-environment:
|
||
image: utility.lab.example.com:5000/ee-supported-rhel9:latest
|
||
pull:
|
||
policy: missing
|
||
playbook-artifact:
|
||
enable: false
|
||
```
|
||
|
||
</details>
|
||
|
||
*NOTE: You're basically going to have to memorize the contents of this file, because unlike `ansible.cfg` there is no way to generate an ansible-navigator.yml file with dummy values.*
|
||
|
||
## Task 2.
|
||
|
||
**manage repositories:**
|
||
|
||
Write a playbook called `repos.yml` to add the BaseOS and AppStream repos to all managed hosts with GPG check enabled. Mirror is located at http://utility.lab.example.com/rhel9/
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
---
|
||
# repos.yml
|
||
- name: Add BaseOS and AppStream repos to all hosts
|
||
hosts: all
|
||
become: true
|
||
|
||
vars:
|
||
repos:
|
||
- BaseOS
|
||
- AppStream
|
||
|
||
baseurl: http://utility.lab.example.com/rhel9
|
||
gpgkey_url: http://utility.lab.example.com/rhel9/RPM-GPG-KEY-redhat-release
|
||
repo_file: /etc/yum.repos.d/rhce
|
||
|
||
tasks:
|
||
- name: Add {{ item }} repository
|
||
ansible.builtin.yum_repository:
|
||
name: "EX294_{{ item }}"
|
||
description: "EX294 {{ item }} Repository"
|
||
baseurl: "{{ baseurl }}/{{ item }}"
|
||
enabled: true
|
||
gpgcheck: true
|
||
gpgkey: "{{ gpgkey_url }}"
|
||
file: "{{ repo_file }}"
|
||
loop: "{{ repos }}"
|
||
|
||
```
|
||
|
||
</details>
|
||
|
||
## Task 3.
|
||
**install roles and collections:**
|
||
|
||
i) Install collections for `ansible.posix`, `community.general` and `redhat.rhel_system_roles` to '~/ansible/mycollections/'. Collections are hosted at [http://utility.lab.example.com/files/](http://utility.lab.example.com/files/)
|
||
|
||
ii) install the `balancer` and `phpinfo` roles from [http://utility.lab.example.com/files](http://utility.lab.example.com/files) using a `requirements.yml` file.
|
||
|
||
*NOTE: although, not a requirement, you can specify both roles and collections in your requirements file*
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# requirements.yml
|
||
---
|
||
roles:
|
||
- name: phpinfo
|
||
src: http://utility.lab.example.com/files/phpinfo.tar.gz
|
||
path: /home/ansible/ansible/roles
|
||
|
||
- name: balancer
|
||
src: http://utility.lab.example.com/files/haproxy.tar.gz
|
||
path: /home/ansible/ansible/roles
|
||
|
||
collections:
|
||
- name: ansible.posix
|
||
source: http://utility.lab.example.com/files/ansible-posix-2.1.0.tar.gz
|
||
type: url
|
||
|
||
- name: redhat.rhel_system_roles
|
||
source: http://utility.lab.example.com/files/redhat-rhel_system_roles-1.108.6.tar.gz
|
||
type: url
|
||
|
||
- name: community.general
|
||
source: http://utility.lab.example.com/files/community-general-12.1.0.tar.gz
|
||
type: url
|
||
```
|
||
```
|
||
# bash
|
||
mkdir -p /home/ansible/ansible/{roles,mycollections}
|
||
ansible-galaxy role install -r requirements.yml
|
||
ansible-galaxy collection install -r requirements.yml -p /home/ansible/ansible/mycollections
|
||
```
|
||
|
||
</details>
|
||
|
||
## Task 4:
|
||
|
||
**install packages and groups:**
|
||
|
||
Write a playbook called `install.yml` to install `php` and `httpd` on the `test` group, and `RPM Development Tools` group in `dev` group only
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# install.yml
|
||
---
|
||
- name: Install Packages and Groups
|
||
hosts: all
|
||
|
||
become: true
|
||
|
||
tasks:
|
||
- name: Install packages on test group
|
||
ansible.builtin.dnf:
|
||
name:
|
||
- httpd
|
||
- php
|
||
state: latest
|
||
when: inventory_hostname in groups['test']
|
||
|
||
- name: Install RPM Development Tools group on test group
|
||
ansible.builtin.dnf:
|
||
name: "@RPM Development Tools"
|
||
state: latest
|
||
when: inventory_hostname in groups['dev']
|
||
```
|
||
|
||
</details>
|
||
|
||
## Task 5.
|
||
|
||
**create a role:**
|
||
|
||
i) Create a role called `apache` to install, start and persistently enable `httpd` and `firewalld`.
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# defaults/main.yml
|
||
---
|
||
apache_packages:
|
||
- httpd
|
||
- firewalld
|
||
```
|
||
```
|
||
# handlers/main.yml
|
||
---
|
||
- name: restart httpd
|
||
ansible.builtin.service:
|
||
name: httpd
|
||
state: restarted
|
||
```
|
||
|
||
</details>
|
||
|
||
*NOTE: You can create the basic filestructure of the role with `ansible-galaxy role init apache`*
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
apache/
|
||
├── defaults/
|
||
│ └── main.yml
|
||
├── handlers/
|
||
│ └── main.yml
|
||
├── tasks/
|
||
│ └── main.yml
|
||
├── templates/
|
||
│ └── index.html.j2
|
||
└── meta/
|
||
└── main.yml
|
||
```
|
||
|
||
</details>
|
||
|
||
ii) Allow the HTTP traffic through the firewall.
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# tasks/main.yml
|
||
---
|
||
- name: Install httpd and firewalld
|
||
ansible.builtin.package:
|
||
name: "{{ apache_packages }}"
|
||
state: present
|
||
|
||
- name: Enable and start firewalld
|
||
ansible.builtin.service:
|
||
name: firewalld
|
||
state: started
|
||
enabled: true
|
||
|
||
- name: Enable and start httpd
|
||
ansible.builtin.service:
|
||
name: httpd
|
||
state: started
|
||
enabled: true
|
||
|
||
- name: Allow HTTP service through firewalld
|
||
ansible.posix.firewalld:
|
||
service: http
|
||
permanent: true
|
||
state: enabled
|
||
immediate: true
|
||
|
||
- name: Deploy index.html with FQDN and IPv4
|
||
ansible.builtin.template:
|
||
src: index.html.j2
|
||
dest: /var/www/html/index.html
|
||
owner: root
|
||
group: root
|
||
mode: '0644'
|
||
notify: restart httpd
|
||
```
|
||
```
|
||
# handlers/main.yml
|
||
---
|
||
- name: restart httpd
|
||
ansible.builtin.service:
|
||
name: httpd
|
||
state: restarted
|
||
```
|
||
|
||
</details>
|
||
|
||
iii) Populate out `index.html` with FQDN and IPv4 address using a jinja2 template, pulling those variables from ansible facts.
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# templates/index.html.j2
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>Apache Test Page</title>
|
||
</head>
|
||
<body>
|
||
<h1>Apache is working</h1>
|
||
<p><strong>FQDN:</strong> {{ ansible_facts.fqdn }}</p>
|
||
<p><strong>IPv4 Address:</strong> {{ ansible_facts.default_ipv4.address }}</p>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
</details>
|
||
|
||
iv) Finally, run the role against the `dev` group
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# apache.yml
|
||
---
|
||
- name: Configure Apache web servers
|
||
hosts: dev
|
||
become: true
|
||
roles:
|
||
- apache
|
||
```
|
||
|
||
</details>
|
||
|
||
## Task 6.
|
||
|
||
**use a role:**
|
||
|
||
i) Use roles to apply the `balancer` role to the `balancers` group and `phpinfo` role to `webservers` group. Servers with the `phpinfo` role applied should report the FQDN and IP address of the web server, and refreshing the web browser should round robin between nodes 3 and 4. You should have already installed these roles in task 3.
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# roles.yml
|
||
---
|
||
- name: Configure load balancer
|
||
hosts: balancers
|
||
become: yes
|
||
roles:
|
||
- balancer
|
||
|
||
- name: Configure web servers
|
||
hosts: webservers
|
||
become: yes
|
||
roles:
|
||
- phpinfo
|
||
```
|
||
|
||
</details>
|
||
|
||
## Task 7.
|
||
|
||
**manage SELinux:**
|
||
|
||
i) Use the `ansible.posix.selinux` role to configure SELinux to be enabled and enforcing on all managed hosts. Don't forget - changes to SELinux require a reboot to take effect.
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
---
|
||
- name: Ensure SELinux is enabled and enforcing
|
||
hosts: all
|
||
become: true
|
||
|
||
tasks:
|
||
- name: Set SELinux to enforcing
|
||
ansible.posix.selinux:
|
||
policy: targeted
|
||
state: enforcing
|
||
notify: Reboot if SELinux state changed
|
||
|
||
handlers:
|
||
- name: Reboot if SELinux state changed
|
||
ansible.builtin.reboot:
|
||
msg: "Rebooting to apply SELinux changes"
|
||
reboot_timeout: 600
|
||
```
|
||
|
||
</details>
|
||
|
||
## Task 8.
|
||
|
||
**manage file content:**
|
||
|
||
i) Populate `/etc/issue` with the name of the lifecycle environment, eg; "Development" for `dev`, "Testing" for `test` and "Production" for `prod`.
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# issue.yml
|
||
---
|
||
- name: Automatically populate /etc/issue with environment name
|
||
hosts:
|
||
- dev
|
||
- test
|
||
- prod
|
||
become: true
|
||
|
||
tasks:
|
||
- name: Determine environment name from inventory groups
|
||
ansible.builtin.set_fact:
|
||
env_name: >-
|
||
{% if 'prod' in group_names %}
|
||
Production
|
||
{% elif 'test' in group_names %}
|
||
Testing
|
||
{% elif 'dev' in group_names %}
|
||
Development
|
||
{% endif %}
|
||
|
||
- name: Populate /etc/issue
|
||
ansible.builtin.copy:
|
||
dest: /etc/issue
|
||
content: |
|
||
{{ env_name }}
|
||
owner: root
|
||
group: root
|
||
mode: '0644'
|
||
```
|
||
|
||
</details>
|
||
|
||
## Task 9.
|
||
|
||
**manage storage:**
|
||
|
||
i) Write a playbook called `partition.yml`. It should create a 1500MiB partition on vdb as ext4 mounted at /devmount, a 1500MiB partition on vdc as ext4 mounted at /devmount1, unless there isn't enough space on vdc, in which case make it 800MiB and print a message stating such. Check for vde. If there is no vde present, print message stating there's no such drive.
|
||
|
||
*NOTE: My exam said to create partitions, but all examples I've seen point to logical volumes. Maybe practice both?*
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# partition.yml
|
||
---
|
||
- name: Configure disk partitions and mounts
|
||
hosts: all
|
||
become: true
|
||
gather_facts: true
|
||
|
||
tasks:
|
||
####################################################################
|
||
# /dev/vdb — always create 1500MB partition mounted at /devmount
|
||
####################################################################
|
||
- name: Create 1500MB partition on /dev/vdb
|
||
community.general.parted:
|
||
device: /dev/vdb
|
||
number: 1
|
||
state: present
|
||
part_end: 1500MiB
|
||
|
||
- name: Create XFS filesystem on /dev/vdb1
|
||
ansible.builtin.filesystem:
|
||
fstype: xfs
|
||
dev: /dev/vdb1
|
||
|
||
- name: Mount /dev/vdb1 at /devmount
|
||
ansible.builtin.mount:
|
||
path: /devmount
|
||
src: /dev/vdb1
|
||
fstype: xfs
|
||
state: mounted
|
||
|
||
####################################################################
|
||
# /dev/vdc — size-based logic (1500MB or 800MB)
|
||
####################################################################
|
||
- name: Determine size of /dev/vdc partition
|
||
ansible.builtin.set_fact:
|
||
vdc_part_size: >-
|
||
{{ '1500MiB'
|
||
if (ansible_facts.devices.vdc.sectors | int *
|
||
ansible_facts.devices.vdc.sectorsize | int) >= (1500 * 1024 * 1024)
|
||
else '800MiB' }}
|
||
when: "'vdc' in ansible_facts.devices"
|
||
|
||
- name: Create partition on /dev/vdc
|
||
community.general.parted:
|
||
device: /dev/vdc
|
||
number: 1
|
||
state: present
|
||
part_end: "{{ vdc_part_size }}"
|
||
when: "'vdc' in ansible_facts.devices"
|
||
|
||
- name: Create XFS filesystem on /dev/vdc1
|
||
ansible.builtin.filesystem:
|
||
fstype: xfs
|
||
dev: /dev/vdc1
|
||
when: "'vdc' in ansible_facts.devices"
|
||
|
||
- name: Mount /dev/vdc1
|
||
ansible.builtin.mount:
|
||
path: >-
|
||
{{ '/devmount1'
|
||
if vdc_part_size == '1500MiB'
|
||
else '/dev/mount' }}
|
||
src: /dev/vdc1
|
||
fstype: xfs
|
||
state: mounted
|
||
when: "'vdc' in ansible_facts.devices"
|
||
|
||
####################################################################
|
||
# /dev/vde presence check
|
||
####################################################################
|
||
- name: Warn if /dev/vde is not present
|
||
ansible.builtin.debug:
|
||
msg: "Disk /dev/vde is not present"
|
||
when: "'vde' not in ansible_facts.devices"
|
||
```
|
||
|
||
</details>
|
||
|
||
## Task 10.
|
||
|
||
**manage directories and symlinks:**
|
||
|
||
i) create the directory `/webdev` with `U=RWX,G=RWX,O=RX` permissions. It should be owned by `webdev` group. It should have special permissions `set group id` (I think that means 2775 in octal). Symlink from `/webdev > /var/www/html/webdev`
create `/webdev/index.html` to report hostname and ip address.
|
||
|
||
Allow traffic through the firewall for http.
|
||
|
||
It should be browseable by the dev group.
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# webcontent.yml
|
||
---
|
||
- name: Configure restricted web content for dev hosts
|
||
hosts: dev
|
||
become: true
|
||
gather_facts: true
|
||
|
||
tasks:
|
||
# ---------------- SELinux ----------------
|
||
- name: Ensure SELinux is enforcing
|
||
ansible.posix.selinux:
|
||
policy: targeted
|
||
state: enforcing
|
||
|
||
- name: Install SELinux utilities
|
||
ansible.builtin.package:
|
||
name: policycoreutils-python-utils
|
||
state: present
|
||
|
||
# ---------------- Groups & Users ----------------
|
||
- name: Ensure webdev group exists
|
||
ansible.builtin.group:
|
||
name: webdev
|
||
state: present
|
||
|
||
- name: Add ansible user to webdev group
|
||
ansible.builtin.user:
|
||
name: ansible
|
||
groups: webdev
|
||
append: true
|
||
|
||
# ---------------- Web Content ----------------
|
||
- name: Create /webdev directory with setgid permissions
|
||
ansible.builtin.file:
|
||
path: /webdev
|
||
state: directory
|
||
owner: root
|
||
group: webdev
|
||
mode: "2775"
|
||
|
||
- name: Create index.html using Ansible facts
|
||
ansible.builtin.copy:
|
||
dest: /webdev/index.html
|
||
owner: root
|
||
group: webdev
|
||
mode: "0644"
|
||
content: |
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head><title>WebDev Host Info</title></head>
|
||
<body>
|
||
<h1>WebDev Page</h1>
|
||
<p><strong>Hostname:</strong> {{ ansible_facts['hostname'] }}</p>
|
||
<p><strong>IP Address:</strong> {{ ansible_facts['default_ipv4']['address'] }}</p>
|
||
</body>
|
||
</html>
|
||
|
||
# ---------------- Apache + Symlink ----------------
|
||
- name: Create symlink from /webdev to /var/www/html/webdev
|
||
ansible.builtin.file:
|
||
src: /webdev
|
||
dest: /var/www/html/webdev
|
||
state: link
|
||
force: true
|
||
|
||
# ---------------- SELinux Context ----------------
|
||
- name: Allow Apache to read /webdev via SELinux
|
||
ansible.builtin.command:
|
||
cmd: semanage fcontext -a -t httpd_sys_content_t "/webdev(/.*)?"
|
||
register: semanage_result
|
||
failed_when: semanage_result.rc not in [0,1]
|
||
|
||
- name: Apply SELinux context
|
||
ansible.builtin.command: restorecon -Rv /webdev
|
||
changed_when: false
|
||
|
||
# ---------------- Firewall ----------------
|
||
- name: Ensure firewalld is started and enabled
|
||
ansible.builtin.service:
|
||
name: firewalld
|
||
state: started
|
||
enabled: true
|
||
|
||
- name: Allow HTTP through firewall
|
||
ansible.posix.firewalld:
|
||
service: http
|
||
permanent: true
|
||
immediate: true
|
||
state: enabled
|
||
|
||
# ---------------- Apache Access Control ----------------
|
||
- name: Restrict access to webdev content to node1 only
|
||
ansible.builtin.copy:
|
||
dest: /etc/httpd/conf.d/webdev.conf
|
||
owner: root
|
||
group: root
|
||
mode: "0644"
|
||
content: |
|
||
<Directory "/var/www/html/webdev">
|
||
Options FollowSymLinks
|
||
Require all granted
|
||
Require ip 127.0.0.1
|
||
Require ip {{ ansible_facts['default_ipv4']['address'] }}
|
||
</Directory>
|
||
|
||
# ---------------- Services ----------------
|
||
- name: Ensure httpd is started and enabled
|
||
ansible.builtin.service:
|
||
name: httpd
|
||
state: started
|
||
enabled: true
|
||
|
||
- name: Restart httpd to apply configuration
|
||
ansible.builtin.service:
|
||
name: httpd
|
||
state: restarted
|
||
```
|
||
|
||
</details>
|
||
|
||
## Task 11.
|
||
|
||
**manage file content with templates:**
|
||
|
||
populate /etc/myhosts using hosts.j2 template and hosts.yml. Do not modify hosts.yml at all, it should handle all of the looping through the hosts in the template file
|
||
use a for loop on the j2 template to loop through each host
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# hosts.j2
|
||
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
|
||
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
|
||
{% for node in groups['all'] %}
|
||
{{ hostvars[node]['ansible_facts']['default_ipv4']['address'] }} {{ hostvars[node]['ansible_facts']['fqdn'] }} {{ hostvars[node]['ansible_facts']['hostname'] }}
|
||
{% endfor%}
|
||
```
|
||
|
||
```
|
||
# hosts.yml
|
||
- name: Hosts config deploy
|
||
hosts: all
|
||
become: True
|
||
tasks:
|
||
|
||
- name: Template a file to /etc/myhosts
|
||
when: inventory_hostname in groups['dev']
|
||
ansible.builtin.template:
|
||
src: ./hosts.j2
|
||
dest: /etc/myhosts
|
||
```
|
||
|
||
</details>
|
||
|
||
## Task 12.
|
||
|
||
**modify file contents:**
|
||
|
||
Download `hwreport.empty` from `utility.lab.example.com` to `/root/hwreport.txt` on all hosts.
|
||
|
||
Replace key value pairs for hostname, bios version, memoryMiB, size of vda, vdb and vdc. If device does not exist, put NONE.
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# hwreport.yml
|
||
---
|
||
- name: Generate hardware report
|
||
hosts: all
|
||
become: yes
|
||
|
||
tasks:
|
||
- name: Download empty hwreport file
|
||
get_url:
|
||
url: http://utility.lab.example.com/files/hwreport.empty
|
||
dest: /root/hwreport.txt
|
||
mode: '0644'
|
||
|
||
- name: Set hostname
|
||
lineinfile:
|
||
path: /root/hwreport.txt
|
||
regexp: '^HOST='
|
||
line: "HOST={{ ansible_hostname }}"
|
||
|
||
- name: Set BIOS version
|
||
lineinfile:
|
||
path: /root/hwreport.txt
|
||
regexp: '^BIOS='
|
||
line: "BIOS={{ ansible_bios_version | default('NONE') }}"
|
||
|
||
- name: Set memory size
|
||
lineinfile:
|
||
path: /root/hwreport.txt
|
||
regexp: '^MEMORY='
|
||
line: "MEMORY={{ ansible_memtotal_mb }} MB"
|
||
|
||
- name: Set vdb disk size
|
||
lineinfile:
|
||
path: /root/hwreport.txt
|
||
regexp: '^VDB='
|
||
line: "VDB={{ ansible_devices.vdb.size | default('NONE') }}"
|
||
|
||
- name: Set vdc disk size
|
||
lineinfile:
|
||
path: /root/hwreport.txt
|
||
regexp: '^VDC='
|
||
line: "VDC={{ ansible_devices.vdc.size | default('NONE') }}"
|
||
|
||
- name: Set vdd disk size (NONE if missing)
|
||
lineinfile:
|
||
path: /root/hwreport.txt
|
||
regexp: '^VDD='
|
||
line: >-
|
||
VDD={{ ansible_devices.vdd.size if 'vdd' in ansible_devices else 'NONE' }}
|
||
```
|
||
|
||
</details>
|
||
|
||
## Task 13.
|
||
|
||
**use ansible vault to encrypt a file:**
|
||
|
||
Create an encrypted variable file called `locker.yml` which should contain two variables and their values.
|
||
|
||
*pw_developer is value imadev*
|
||
*pw_manager is value imamgr*
|
||
|
||
`locker.yml` file should be encrypted using the password `whenyouwishuponastar`
|
||
|
||
store the password in a file named `secret.txt`, which is used to encrypt the variable file.
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# secret.txt
|
||
echo "whenyouwishuponastar" > secret.txt
|
||
chmod 600 secret.txt
|
||
```
|
||
```
|
||
# locker.yml
|
||
pw_developer: imadev
|
||
pw_manager: imamgr
|
||
```
|
||
`ansible-vault encrypt locker.yml --vault-password-file secret.txt`
|
||
|
||
</details>
|
||
|
||
## Task 14.
|
||
|
||
**manage users:**
|
||
|
||
Download the variable file "http://utility.lab.example.com/files/user_list.yml" and write a playbook named "users.yml" and then run the playbook on all the nodes using two variable files user_list.yml and locker.yml.
|
||
|
||
i)
|
||
|
||
* Create a group opsdev
|
||
|
||
* Create user from users variable who job is equal to developer and need to be in opsdev group
|
||
|
||
* Assign a password using SHA512 format and run playbook on dev and test group.
|
||
|
||
* User password is {{ pw_developer }}
|
||
|
||
ii)
|
||
|
||
* Create a group opsmgr
|
||
|
||
* Create user from users varaible who job is equal to manager and need to be in opsmgr group
|
||
|
||
* Assign a password using SHA512 format and run playbook on prod group.
|
||
|
||
* User password is {{ pw_manager }}
|
||
|
||
iii) Use when condition for each play
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# user_list.yml
|
||
users:
|
||
- name: Fred
|
||
role: manager
|
||
|
||
- name: Wilma
|
||
role: manager
|
||
|
||
- name: Barney
|
||
role: developer
|
||
|
||
- name: Betty
|
||
role: developer
|
||
```
|
||
```
|
||
# users.yml
|
||
---
|
||
- name: Download user_list.yml variable file
|
||
hosts: all
|
||
gather_facts: false
|
||
tasks:
|
||
- name: Download user_list.yml
|
||
ansible.builtin.get_url:
|
||
url: http://utility.lab.example.com/files/user_list.yml
|
||
dest: ./user_list.yml
|
||
run_once: true
|
||
delegate_to: localhost
|
||
|
||
- name: Create developer users on dev and test
|
||
hosts: dev:test
|
||
become: true
|
||
vars_files:
|
||
- user_list.yml
|
||
- locker.yml
|
||
tasks:
|
||
- name: Ensure opsdev group exists
|
||
ansible.builtin.group:
|
||
name: opsdev
|
||
state: present
|
||
|
||
- name: Create developer users
|
||
ansible.builtin.user:
|
||
name: "{{ item.name }}"
|
||
groups: opsdev
|
||
append: yes
|
||
password: "{{ pw_developer | password_hash('sha512') }}"
|
||
state: present
|
||
loop: "{{ users }}"
|
||
when: item.role == "developer"
|
||
|
||
|
||
- name: Create manager users on prod
|
||
hosts: prod
|
||
become: true
|
||
vars_files:
|
||
- user_list.yml
|
||
- locker.yml
|
||
tasks:
|
||
- name: Ensure opsmgr group exists
|
||
ansible.builtin.group:
|
||
name: opsmgr
|
||
state: present
|
||
|
||
- name: Create manager users
|
||
ansible.builtin.user:
|
||
name: "{{ item.name }}"
|
||
groups: opsmgr
|
||
append: yes
|
||
password: "{{ pw_manager | password_hash('sha512') }}"
|
||
state: present
|
||
loop: "{{ users }}"
|
||
when: item.role == "manager"
|
||
```
|
||
|
||
`ansible-navigator run -m stdout users.yml --vault-password-file secret.txt`
|
||
|
||
</details>
|
||
|
||
## Task 15.
|
||
|
||
**re-encrypt a vaulted file:**
|
||
|
||
Rekey variable file from [http://utility.lab.example.com/files/salaries.yml](http://utility.lab.example.com/files/salaries.yml)
|
||
|
||
i) Old password: changeme
|
||
|
||
ii) New password: redhat
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# salaries.yml
|
||
fred: $100000
|
||
wilma:$100000
|
||
barney: $100000
|
||
betty: $100000
|
||
```
|
||
|
||
`wget http://utility.lab.example.com/files/salaries.yml`
|
||
|
||
`ansible-vault rekey salaries.yml`
|
||
|
||
```
|
||
Vault password: changeme
|
||
New Vault password: redhat
|
||
Confirm New Vault password:redhat
|
||
```
|
||
|
||
</details>
|
||
|
||
## Task 16.
|
||
|
||
**manage cron:**
|
||
|
||
Create a cronjob for the user ansible on all nodes, playbook name is crontab.yml and the job details are below:
|
||
|
||
i) Every 2 minutes the job will execute logger "EX294 in progress".
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# cron.yml
|
||
---
|
||
- name: Create cron job for user ansible
|
||
hosts: all
|
||
become: true
|
||
tasks:
|
||
- name: Ensure cron job runs every 2 minutes
|
||
ansible.builtin.cron:
|
||
name: "EX294 progress log"
|
||
user: ansible
|
||
minute: "*/2"
|
||
job: 'logger "EX294 in progress"'
|
||
state: present
|
||
```
|
||
|
||
</details>
|
||
|
||
## Task 17.
|
||
|
||
**Use the RHEL timesync system role:**
|
||
|
||
i) Create a playbook called "timesync.yml" that:
|
||
- Runs on all managed nodes
|
||
- Uses the timesync role
|
||
- Configures the role to use the currently active NTP provider
|
||
- Configure the role to use the time server utility.lab.example.com
|
||
- Configure the role to enable the iburst parameter
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# timesync.yml
|
||
- name: Configure time synchronization using RHEL timesync role
|
||
hosts: all
|
||
become: true
|
||
roles:
|
||
- role: redhat.rhel_system_roles.timesync
|
||
vars:
|
||
timesync_ntp_provider: auto
|
||
timesync_ntp_servers:
|
||
- hostname: utility.lab.example.com
|
||
iburst: true
|
||
```
|
||
|
||
</details>
|
||
|
||
## Task 18.
|
||
|
||
**configure MOTD:**
|
||
|
||
Create a playbook called motd.yml.
|
||
|
||
i) Run the playbook.
|
||
ii) Whenever you ssh into any node (node1 here), the message will be as follows:
|
||
Welcome to node1
|
||
OS: RedHat 9.4
|
||
Architecture: x86_64
|
||
|
||
<details>
|
||
|
||
<summary>solution</summary>
|
||
|
||
```
|
||
# motd.yml
|
||
---
|
||
- name: Configure MOTD for all nodes
|
||
hosts: all
|
||
become: true
|
||
gather_facts: true
|
||
tasks:
|
||
- name: Set MOTD file
|
||
ansible.builtin.copy:
|
||
dest: /etc/motd
|
||
content: |
|
||
Welcome to {{ inventory_hostname }}
|
||
OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
|
||
Architecture: {{ ansible_architecture }}
|
||
owner: root
|
||
group: root
|
||
mode: '0644'
|
||
```
|
||
|
||
</details>
|