initial playbooks

This commit is contained in:
Louis Abel 2021-02-01 01:08:26 -07:00
parent 04c3e14b0a
commit 686680ffd3
Signed by: label
GPG Key ID: B37E62D143879B36
14 changed files with 629 additions and 95 deletions

View File

@ -1,10 +1,10 @@
# Ansible AWX Template: Template # Ansible AWX Template: IPA Management
Ansible AWX is the method used for the Rocky Linux infrastructure, as a replacement for using the CLI. This template should be copied, as to manage playbooks and tasks into reproducible, repeatable, and organized manner. Ansible AWX is the method used for the Rocky Linux infrastructure, as a replacement for using the CLI.
## Provides / Information ## Provides / Information
This repository is for AWX templates. This repository is for IPA Management.
``` ```
. .
@ -27,95 +27,3 @@ This repository is for AWX templates.
└── main.yml └── main.yml
``` ```
## Guidelines
These are the general guidelines for creating and maintaining these repositories. Please read carefully to ensure that you are meeting the criteria.
1. Copy this template into a new repository with the format `ansible-type-usage`. For example, if this is for ipa management, you could use a name like `ansible-ipa-management`.
2. Change the top of the `README.md` from "Template" to an appropriate name for your repo.
3. Modify the `README.md` file at the Provides/Information section of what these tasks do. Please be descriptive and list all of the playbooks and accompanying tasks (see the example). Hint: Use the `tree` command.
4. List any requirements to run the playbooks, such as vars, mandatory or optional in playbooks. Optionally, you may list them in the `README.md` here.
5. Run `pre-commit install` - There is already a provided `.pre-commit-config.yaml` with some default settings.
6. (Optional) Remove everything starting at "Guidelines" in this README to reduce clutter.
## GitLab Steps
1. Create a new project
2. Click "import project"
3. Click "Repo by URL"
4. Put in the URL: https://git.rockylinux.org/infrastructure/public/ansible/ansible-awx-template.git
5. Type in the project name as outlined in `Guidelines` above
6. Ensure your project URL and slug are appropriate
## Designing Playbooks
Generally, your playbooks should be doing the following:
1. Checking if ansible can be ran on a specific host
2. Asserting if variables are filled and are correctly formed
3. Importing tasks from the `./tasks` directory
4. Importing roles, if necessary
5. Post tasks, if necessary
**Note**: At no point should you be using `./tasks/main.yml`
### Pre-flight and Post-flight tasks
```
pre_tasks:
- name: Check if ansible cannot be run here
stat:
path: /etc/no-ansible
register: no_ansible
- name: Verify if we can run ansible
assert:
that:
- "not no_ansible.stat.exists"
success_msg: "We are able to run on this node"
fail_msg: "/etc/no-ansible exists - skipping run on this node"
# Assertions and other checks here
# Import roles/tasks here
post_tasks:
- name: Touching run file that ansible has ran here
file:
path: /var/log/ansible.run
state: touch
mode: '0644'
owner: root
group: root
```
### Comments
Each playbook should have comments or a name descriptor that explains what the playbook does or how it is used. If not available, README-... files can be used in place, especially in the case of adhoc playbooks that take input. Documentation for each playbook/role does not have to be on this wiki. Comments or README's should be sufficient.
### Tags
Ensure that you use relevant tags where necessary for your tasks.
### Playbook naming
```
init-* -> Starting infrastructure playbooks that run solo or import other
playbooks that start with import-
adhoc -> These playbooks are one-off playbooks that can be used on the CLI or
in AWX. These are typically for basic tasks.
import -> Playbooks that should be imported from the top level playbooks
role-* -> These playbooks call roles specifically for infrastructure tasks.
Playbooks that do not call a role should be named init or adhoc based
on their usage.
```
### Pre-commits / linting
When pushing to your own forked version of this repository, pre-commit must run to verify your changes. They must be passing to be pushed up. This is an absolute requirement, even for roles.
When the linter passes, the push will complete and you will be able to open a PR.
## How are these repositories used?
These repositories are generally cloned/pulled into AWX for the latest version, so they can be called within AWX either by hand or at a scheduled time.

41
adhoc-ipabinder.yml Normal file
View File

@ -0,0 +1,41 @@
---
# This playbook is meant to be used with callable variables, like adhoc or AWX.
# What: Creates a binder account for read-only binds
# What is expected:
# -> ipa_binder_name: Bind account name, in the form of name_binder
# -> ipa_binder_password: Bind account password
- name: Create binder account
hosts: ipaserver
become: true
tasks:
- name: "Check for user variables"
assert:
that:
- ipa_binder_name | mandatory
- ipa_binder_password | mandatory
success_msg: "Required variables provided"
fail_msg: "We are missing user information"
- name: "Creating bind account template - binder"
template:
src: "tmp/binder_template.update"
dest: "/tmp/binder.update"
owner: root
group: root
mode: '0600'
tags:
- users
- name: "Adding in the bind account"
command: "/usr/sbin/ipa-ldap-updater /tmp/binder.update"
register: bind_account
changed_when: "bind_account.rc == 0"
tags:
- users
- name: "Remove template"
file:
path: "/tmp/binder.update"
state: absent

56
adhoc-ipadnsrecord.yml Normal file
View File

@ -0,0 +1,56 @@
---
# This playbook is meant to be used with callable variables, like adhoc or AWX.
# What: Creates dns records in the idm infrastructure based on the variables
# provided.
# What is expected:
# -> ipaadmin_password: This should be the password of the admin user
# -> ipa_admin: The admin user that has kerberos management capabilities (default is admin)
# -> ipa_zone: The zone name (eg, rockylinux.org)
# -> ipa_name: The shortname (eg, buildbox instead of buildbox.rockylinux.org)
# -> ipa_name_type: Type of record (eg, CNAME, A, AAAA, PTR)
# -> ipa_name_value: Record value (depends on type of record)
# -> ipa_presence: present or absent
# NOTE: For usage in AWX, select an IPA server
- name: Create a DNS Record
hosts: all
become: false
gather_facts: false
tasks:
- name: "Checking for user variables"
assert:
that:
- ipaadmin_password | mandatory
- ipa_zone | mandatory
- ipa_name | mandatory
- ipa_name_type | mandatory
- ipa_name_value | mandatory
- ipa_presence | mandatory
success_msg: "Required variables provided"
fail_msg: "We are missing zone information or ipa admin password"
- name: "Creating DNS Record"
freeipa.ansible_freeipa.ipadnsrecord:
ipaadmin_principal: "{{ ipa_admin|default('admin') }}"
ipaadmin_password: "{{ ipaadmin_password }}"
zone_name: "{{ ipa_zone }}"
name: "{{ ipa_name }}"
record_type: "{{ ipa_name_type }}"
record_value: "{{ ipa_name_value }}"
state: "{{ ipa_presence }}"
tags:
- dns
# We try to do this just in case because if a certificate is being issued
# that wants a CNAME, the host has to "manage" said host. However, if the
# host doesn't exist, we'll ignore it.
- name: "Creating host object for CNAME"
freeipa.ansible_freeipa.ipahost:
ipaadmin_principal: "{{ ipa_admin|default('admin') }}"
ipaadmin_password: "{{ ipaadmin_password }}"
name: "{{ ipa_name }}.{{ ipa_zone }}"
force: true
managedby:
- "{{ ipa_name_value[:-1] }}"
ignore_errors: true

28
adhoc-ipadnszone.yml Normal file
View File

@ -0,0 +1,28 @@
---
# This playbook is meant to be used with callable variables, like adhoc or AWX.
# What: Creates dns zones in the idm infrastructure based on the variables
# provided.
# NOTE: For usage in AWX, select an IPA server
- name: Create a DNS Zone
hosts: all
become: false
gather_facts: false
tasks:
- name: "Checking for user variables"
assert:
that:
- ipa_admin | mandatory
- ipaadmin_password | mandatory
- ipa_zone | mandatory
success_msg: "Required variables provided"
fail_msg: "We are missing zone information or ipa admin password"
- name: "Creating DNS Zone"
freeipa.ansible_freeipa.ipadnszone:
ipaadmin_principal: "{{ ipa_admin }}"
ipaadmin_password: "{{ ipaadmin_password }}"
name: "{{ ipa_zone }}"
tags:
- dns

33
adhoc-ipagetcert.yml Normal file
View File

@ -0,0 +1,33 @@
---
# This playbook is meant to be used with callable variables, like adhoc or AWX.
# What: Requests and signs a certificate from an IPA CA
# What is expected:
# -> getcert_name: Name of the certificate (FQDN)
# -> getcert_owner: what account owns the certificate files
# -> getcert_key: path to the certificate key
# -> getcert_cert: path to the certificate
# -> getcert_postcmd: command to run after a certificate renews
# -> getcert_chain: true/false, should cert and key be chained together
# -> getcert_chain_location: location for the chain
# TODO: Add CNAME/SAN support
# NOTE: For usage in AWX, select an appropriate server
- name: Request and sign an IPA Certificate
hosts: all
become: true
vars:
ipa_getcert_requested_hostnames:
- name: "{{ getcert_name|default(ansible_fqdn) }}"
owner: "{{ getcert_owner|default('root') }}"
key_location: "{{ getcert_key|default('/etc/pki/tls/private/newcert.key') }}"
cert_location: "{{ getcert_cert|default('/etc/pki/tls/certs/newcert.crt') }}"
nss_db_dir: "{{ getcert_nss_db_dir|default('/etc/pki/tls/db') }}"
nss_nickname: "{{ getcert_nss_nickname|default(ansible_fqdn) }}"
postcmd: "{{ getcert_postcmd|default(false) }}"
ipa_getcert_chain: "{{ getcert_chain|default(false) }}"
ipa_getcert_chain_location: "{{ getcert_chain_location|default('/etc/pki/tls/chain') }}"
ipa_getcert_nss: "{{ getcert_nss|default(false) }}"
roles:
- role: rockylinux.ipagetcert
state: present

136
adhoc-ipagetkeytab.yml Normal file
View File

@ -0,0 +1,136 @@
---
# This playbook is meant to be used with callable variables, like adhoc or AWX.
# Special thanks to @remyabel for assisting in improving this playbook with
# extended security posture
# What: Pulls keytabs for a kerberos service
# What is expected:
# -> host: The host in the inventory, this MUST be FQDN.
# -> ipa_service: using this format: SVC/hostname.rockylinux.org@ROCKYLINUX.ORG
# Note: This service MUST exist
# -> ipa_keytab_fullpath: The full path to the keytab. Example: /etc/gitlab/gitlab.keytab
# -> ipa_server: This needs to be one of the IPA servers
# -> ipa_owner: If applicable, the local account that can read this keytab (eg apache)
# -> ipa_admin: The admin user that has kerberos management capabilities (default is admin)
# -> ipaadmin_password: This should be the password of the admin user
# NOTE: For usage in AWX, select an appropriate host
- name: Pull keytab from IPA
hosts: all
become: true
gather_facts: false
tasks:
- name: "Checking for user variables"
assert:
that:
- ipa_admin | mandatory
- ipaadmin_password | mandatory
- ipa_service | mandatory
- ipa_keytab_fullpath | mandatory
- ipa_server | mandatory
success_msg: "Required variables provided"
fail_msg: "We are missing required information"
- name: "Check that a keytab doesn't already exist"
stat:
path: "{{ ipa_keytab_fullpath }}"
register: keytab_status
check_mode: false
changed_when: "1 != 1"
- name: "Verify keytab existence"
assert:
that:
- "not keytab_status.stat.exists"
success_msg: "Keytab doesn't exist, moving on..."
fail_msg: "Keytab with that name already exists, skipping."
- name: "Grant {{ host }} and {{ ipa_admin }} access to the service keytab"
delegate_to: "{{ ipa_server }}"
freeipa.ansible_freeipa.ipaservice:
ipaadmin_principal: "{{ ipa_admin }}"
ipaadmin_password: "{{ ipaadmin_password }}"
name: "{{ ipa_service }}"
allow_retrieve_keytab_user:
- "{{ ipa_admin }}"
allow_retrieve_keytab_host:
- "{{ host }}"
action: member
- name: "Grant {{ host }} and {{ ipa_admin }} access to the host keytab"
delegate_to: "{{ ipa_server }}"
freeipa.ansible_freeipa.ipahost:
ipaadmin_principal: "{{ ipa_admin }}"
ipaadmin_password: "{{ ipaadmin_password }}"
name: "{{ host }}"
state: present
allow_retrieve_keytab_user:
- "{{ ipa_admin }}"
managedby_host: "{{ host }}"
action: member
- name: "Get kerberos ticket"
delegate_to: "{{ ipa_server }}"
shell: "set -o pipefail && echo \"{{ ipaadmin_password }}\" | kinit {{ ipa_admin }}"
check_mode: false
changed_when: "1 != 1"
when: not keytab_status.stat.exists
- name: "Attempt to retrieve keytab"
delegate_to: "{{ ipa_server }}"
command: "ipa-getkeytab -r -s {{ ipa_server }} -p {{ ipa_service }} -k /tmp/{{ host }}.kt"
register: ret_result
check_mode: false
changed_when: "1 != 1"
failed_when: "not ('Keytab successfully retrieved' in ret_result.stderr or 'krbPrincipalKey not found' in ret_result.stderr)"
- name: "Create keytab if it didn't exist, based on the last task"
delegate_to: "{{ ipa_server }}"
command: "ipa-getkeytab -s {{ ipa_server }} -p {{ ipa_service }} -k /tmp/{{ host }}.kt"
when: "'krbPrincipalKey not found' in ret_result.stderr"
- name: "Destroy admin ticket"
delegate_to: "{{ ipa_server }}"
command: "kdestroy -A"
register: kdestroy_result
changed_when: "kdestroy_result.rc == 0"
- name: "Put the keytab into a register"
delegate_to: "{{ ipa_server }}"
command: "base64 /tmp/{{ host }}.kt"
register: keytab
check_mode: false
changed_when: "keytab.rc == 0"
- name: "Destroy local keytab"
delegate_to: "{{ ipa_server }}"
file:
path: "/tmp/{{ host }}.kt"
state: absent
- name: "Deploy keytab to {{ host }} from register"
copy:
dest: "{{ ipa_keytab_fullpath }}.b64"
content: "{{ keytab.stdout }}"
owner: "{{ ipa_owner|default('root') }}"
group: "{{ ipa_owner|default('root') }}"
mode: '0600'
- name: "Decode keytab"
shell: "umask 077 && base64 -d {{ ipa_keytab_fullpath }}.b64 > {{ ipa_keytab_fullpath }}"
changed_when: "1 != 1"
- name: "Destroy encoded keytab"
file:
path: "{{ ipa_keytab_fullpath }}.b64"
state: absent
- name: "Set ownership if applicable, otherwise it's root owned"
file:
path: "{{ ipa_keytab_fullpath }}"
owner: "{{ ipa_owner|default('root') }}"
group: "{{ ipa_owner|default('root') }}"
mode: '0600'
state: file
tags:
- keytab

47
adhoc-ipagroup.yml Normal file
View File

@ -0,0 +1,47 @@
---
# This playbook is meant to be used with callable variables, like adhoc or AWX.
# What: Creates groups in the idm infrastructure based on the variables provided
# You MUST provide an ipa_admin user to run this.
# If group is going to be a fas group (exposed in noggin), ensure ipa_fas is
# set to true.
- name: Create our initial users
hosts: ipaserver
become: false
gather_facts: false
tasks:
- name: "Checking for user variables"
assert:
that:
- ipa_admin | mandatory
- ipaadmin_password | mandatory
- ipa_group | mandatory
- ipa_description | mandatory
- ipa_nonposix | mandatory
success_msg: "Required variables provided"
fail_msg: "We are missing group information or ipa admin password"
- name: "Creating New Group"
freeipa.ansible_freeipa.ipagroup:
ipaadmin_principal: "{{ ipa_admin }}"
ipaadmin_password: "{{ ipaadmin_password }}"
name: "{{ ipa_group }}"
description: "{{ ipa_description }}"
nonposix: "{{ ipa_nonposix }}"
membermanager_user: "{{ ipa_group_manager_user | default(omit) }}"
membermanager_group: "{{ ipa_group_manager_group | default(omit) }}"
tags:
- groups
- name: "Prepare FAS if required"
shell: "set -o pipefail && echo \"{{ ipaadmin_password }}\" | kinit {{ ipa_admin }}"
check_mode: false
changed_when: "1 != 1"
when: ipa_fas
- name: "Apply FAS"
command: "ipa group-mod --fasgroup {{ ipa_group }}"
check_mode: false
changed_when: "1 != 1"
when: ipa_fas

28
adhoc-ipaservice.yml Normal file
View File

@ -0,0 +1,28 @@
---
# This playbook is meant to be used with callable variables, like adhoc or AWX.
# What: Creates kerberos services in the idm infrastructure based on the variables provided
- name: Create Services
hosts: ipaserver
become: false
gather_facts: false
tasks:
- name: "Checking for user variables"
assert:
that:
- ipa_admin | mandatory
- ipaadmin_password | mandatory
- ipa_service | mandatory
success_msg: "Required variables provided"
fail_msg: "We are missing required information"
- name: "Creating Kerberos Service"
freeipa.ansible_freeipa.ipaservice:
ipaadmin_principal: "{{ ipa_admin }}"
ipaadmin_password: "{{ ipaadmin_password }}"
name: "{{ ipa_service }}"
skip_host_check: "{{ ipa_skip_host_check | default(false) }}"
force: "{{ ipa_force | default(false) }}"
tags:
- services

28
adhoc-ipauser-disable.yml Normal file
View File

@ -0,0 +1,28 @@
---
# This playbook is meant to be used with callable variables, like adhoc or AWX.
# What: Creates users in the idm infrastructure based on the variables provided.
# NOTE: For usage in AWX, select an appropriate host
- name: Create a User
hosts: all
become: false
gather_facts: false
tasks:
- name: "Checking for user variables"
assert:
that:
- ipa_admin | mandatory
- ipaadmin_password | mandatory
- ipa_name | mandatory
success_msg: "Required variables provided"
fail_msg: "We are missing user information or ipa admin password"
- name: "Disabling User Account"
freeipa.ansible_freeipa.ipauser:
ipaadmin_principal: "{{ ipa_admin }}"
ipaadmin_password: "{{ ipaadmin_password }}"
name: "{{ ipa_name }}"
state: disabled
tags:
- users

28
adhoc-ipauser-enable.yml Normal file
View File

@ -0,0 +1,28 @@
---
# This playbook is meant to be used with callable variables, like adhoc or AWX.
# What: Creates users in the idm infrastructure based on the variables provided.
# NOTE: For usage in AWX, select an appropriate host
- name: Create a User
hosts: all
become: false
gather_facts: false
tasks:
- name: "Checking for user variables"
assert:
that:
- ipa_admin | mandatory
- ipaadmin_password | mandatory
- ipa_name | mandatory
success_msg: "Required variables provided"
fail_msg: "We are missing user information or ipa admin password"
- name: "Enabling User Account"
freeipa.ansible_freeipa.ipauser:
ipaadmin_principal: "{{ ipa_admin }}"
ipaadmin_password: "{{ ipaadmin_password }}"
name: "{{ ipa_name }}"
state: enabled
tags:
- users

39
adhoc-ipauser.yml Normal file
View File

@ -0,0 +1,39 @@
---
# This playbook is meant to be used with callable variables, like adhoc or AWX.
# What: Creates users in the idm infrastructure based on the variables provided.
# NOTE: For usage in AWX, select an appropriate host
- name: Create a User
hosts: all
become: false
gather_facts: false
tasks:
- name: "Checking for user variables"
assert:
that:
- ipa_admin | mandatory
- ipaadmin_password | mandatory
- ipa_name | mandatory
- ipa_first | mandatory
- ipa_last | mandatory
- ipa_email | mandatory
- ipa_password | mandatory
- ipa_title | mandatory
success_msg: "Required variables provided"
fail_msg: "We are missing user information or ipa admin password"
- name: "Creating User Account"
freeipa.ansible_freeipa.ipauser:
ipaadmin_principal: "{{ ipa_admin }}"
ipaadmin_password: "{{ ipaadmin_password }}"
name: "{{ ipa_name }}"
first: "{{ ipa_first }}"
last: "{{ ipa_last }}"
email: "{{ ipa_email }}"
password: "{{ ipa_password }}"
title: "{{ ipa_title }}"
loginshell: "{{ ipa_loginshell|default('/sbin/nologin', True) }}"
update_password: on_create
tags:
- users

38
role-rocky-ipa-client.yml Normal file
View File

@ -0,0 +1,38 @@
---
# Configures an IPA client for the Rocky infrastructure
# NOTE: For usage in AWX, select an appropriate host
- name: Configure IPA client
hosts: all
become: true
pre_tasks:
- name: Check if ansible cannot be run here
stat:
path: /etc/no-ansible
register: no_ansible
- name: Verify if we can run ansible
assert:
that:
- "not no_ansible.stat.exists"
success_msg: "We are able to run on this node"
fail_msg: "/etc/no-ansible exists - skipping run on this node"
- name: Apply hostname based on inventory name
hostname:
name: "{{ inventory_hostname }}"
use: systemd
when: ansible_fqdn != inventory_hostname
roles:
- role: freeipa.ansible_freeipa.ipaclient
state: present
post_tasks:
- name: Touching run file that ansible has ran here
file:
path: /var/log/ansible.run
state: touch
mode: '0644'
owner: root
group: root

View File

@ -0,0 +1,62 @@
---
# Creates an IPA replica
# NOTE: Select the appropriate host or hostgroup
- name: Configure IPA server
hosts: all
become: true
# This is to try to avoid the handler issue in pre/post tasks
handlers:
- import_tasks: handlers/main.yml
pre_tasks:
- name: Check if ansible cannot be run here
stat:
path: /etc/no-ansible
register: no_ansible
- name: Verify if we can run ansible
assert:
that:
- "not no_ansible.stat.exists"
success_msg: "We are able to run on this node"
fail_msg: "/etc/no-ansible exists - skipping run on this node"
- name: Ensure 'dns=none' is set for Network Manager
ini_file:
path: /etc/NetworkManager/NetworkManager.conf
state: present
no_extra_spaces: true
section: main
option: dns
value: none
owner: root
group: root
mode: '0644'
backup: true
notify:
- reload_networkmanager
- name: Ensure resolv.conf is pointing to main master
template:
src: etc/resolv.conf.j2
dest: /etc/resolv.conf
owner: root
group: root
mode: '0644'
backup: true
notify:
- reload_networkmanager
roles:
- role: freeipa.ansible_freeipa.ipareplica
state: present
post_tasks:
- name: Touching run file that ansible has ran here
file:
path: /var/log/ansible.run
state: touch
mode: '0644'
owner: root
group: root

62
role-rocky-ipa.yml Normal file
View File

@ -0,0 +1,62 @@
---
# Creates the first server for an IPA infrastructure
# Recommended specs for the IPA systems, that scale based on number of objects:
# CPU: 2 cores
# Memory: 4GB
# Storage: 10G /var/lib/dirsrv
# System fully up to date
# Define "host" as a hostgroup name or a single host
# NOTE: For AWX, choose the appropriate host or host group
- name: Configure IPA server
hosts: all
become: true
# This is to try to avoid the handler issue in pre/post tasks
handlers:
- import_tasks: handlers/main.yml
pre_tasks:
- name: Check if ansible cannot be run here
stat:
path: /etc/no-ansible
register: no_ansible
- name: Verify if we can run ansible
assert:
that:
- "not no_ansible.stat.exists"
success_msg: "We are able to run on this node"
fail_msg: "/etc/no-ansible exists - skipping run on this node"
- name: Ensure 'dns=none' is set for Network Manager to avoid change
ini_file:
path: /etc/NetworkManager/NetworkManager.conf
state: present
no_extra_spaces: true
section: main
option: dns
value: none
owner: root
group: root
mode: '0644'
backup: true
notify:
- reload_networkmanager
roles:
- role: freeipa.ansible_freeipa.ipaserver
state: present
post_tasks:
- name: Touching run file that ansible has ran here
file:
path: /var/log/ansible.run
state: touch
mode: '0644'
owner: root
group: root
- name: Turn on reverse zone syncing
freeipa.ansible_freeipa.ipadnsconfig:
ipaadmin_password: '{{ ipaadmin_password }}'
allow_sync_ptr: true